From cc5d1fe7604417f22d561e8dc74afb21179942f8 Mon Sep 17 00:00:00 2001 From: Tim Steiner <tsteiner2@unl.edu> Date: Tue, 13 Jul 2010 16:05:47 +0000 Subject: [PATCH] Updating to drupal7.0-alpha6 git-svn-id: file:///tmp/wdn_thm_drupal/branches/drupal-7.x@134 20a16fea-79d4-4915-8869-1ea9d5ebf173 --- CHANGELOG.txt | 12 +- MAINTAINERS.txt | 3 +- UPGRADE.txt | 8 +- includes/bootstrap.inc | 227 +- includes/common.inc | 364 +- includes/database/database.inc | 326 +- includes/database/mysql/database.inc | 38 +- includes/database/mysql/query.inc | 122 +- includes/database/mysql/schema.inc | 12 +- includes/database/pgsql/database.inc | 11 +- includes/database/pgsql/install.inc | 65 +- includes/database/query.inc | 93 +- includes/database/schema.inc | 6 +- includes/database/select.inc | 4 +- includes/database/sqlite/database.inc | 41 +- includes/database/sqlite/query.inc | 66 +- includes/entity.inc | 680 +- includes/errors.inc | 34 +- includes/file.inc | 232 +- includes/form.inc | 428 +- includes/install.core.inc | 70 +- includes/install.inc | 16 +- includes/iso.inc | 4 +- includes/locale.inc | 42 +- includes/mail.inc | 4 +- includes/menu.inc | 355 +- includes/module.inc | 32 +- includes/path.inc | 5 +- includes/registry.inc | 123 +- includes/session.inc | 141 +- includes/stream_wrappers.inc | 123 +- includes/theme.inc | 21 +- includes/theme.maintenance.inc | 55 +- includes/unicode.inc | 156 +- includes/update.inc | 47 +- includes/utility.inc | 59 + misc/ajax.js | 28 +- misc/autocomplete.js | 4 +- misc/displace.js | 115 - misc/drupal.js | 22 +- misc/jquery.ba-bbq.js | 6 +- misc/tabledrag.js | 116 +- misc/tableheader.js | 4 +- modules/aggregator/aggregator.admin.inc | 5 +- modules/aggregator/aggregator.info | 6 +- modules/aggregator/tests/aggregator_test.info | 6 +- modules/block/block.admin.inc | 26 +- modules/block/block.info | 6 +- modules/block/block.install | 56 +- modules/block/block.module | 79 +- modules/block/block.test | 67 +- modules/block/tests/block_test.info | 6 +- modules/block/tests/block_test.module | 3 +- modules/blog/blog.info | 6 +- modules/blog/blog.module | 28 +- modules/blog/blog.pages.inc | 17 +- modules/blog/blog.test | 14 +- modules/book/book.admin.inc | 8 +- modules/book/book.info | 6 +- modules/book/book.install | 6 +- modules/book/book.module | 77 +- modules/book/book.pages.inc | 38 +- modules/color/color.info | 6 +- modules/color/color.module | 3 +- modules/color/color.test | 8 +- modules/comment/comment.admin.inc | 14 +- modules/comment/comment.info | 6 +- modules/comment/comment.install | 8 +- modules/comment/comment.module | 212 +- modules/comment/comment.pages.inc | 4 +- modules/comment/comment.test | 177 +- modules/comment/comment.tokens.inc | 28 +- modules/contact/contact.info | 6 +- modules/contact/contact.install | 25 +- modules/contextual/contextual-rtl.css | 17 + modules/contextual/contextual.css | 47 +- modules/contextual/contextual.info | 6 +- modules/contextual/contextual.module | 6 +- modules/dashboard/dashboard.info | 9 +- modules/dashboard/dashboard.js | 10 +- modules/dashboard/dashboard.module | 30 +- modules/dashboard/dashboard.test | 60 + modules/dblog/dblog.info | 6 +- modules/dblog/dblog.install | 17 +- modules/dblog/dblog.module | 14 +- modules/field/field.api.php | 253 +- modules/field/field.attach.inc | 218 +- modules/field/field.crud.inc | 132 +- modules/field/field.default.inc | 70 +- modules/field/field.form.inc | 17 +- modules/field/field.info | 10 +- modules/field/field.info.inc | 89 +- modules/field/field.install | 10 +- modules/field/field.module | 354 +- modules/field/field.multilingual.inc | 8 +- .../field_sql_storage/field_sql_storage.info | 10 +- .../field_sql_storage.module | 230 +- modules/field/modules/list/list.info | 10 +- modules/field/modules/list/list.module | 4 +- .../field/modules/list/tests/list_test.info | 6 +- modules/field/modules/number/number.info | 10 +- modules/field/modules/number/number.module | 10 +- modules/field/modules/options/options.info | 10 +- modules/field/modules/text/text.info | 10 +- modules/field/modules/text/text.js | 54 +- modules/field/modules/text/text.module | 53 +- modules/field/modules/text/text.test | 99 +- modules/field/tests/field.test | 321 +- modules/field/tests/field_test.entity.inc | 58 +- modules/field/tests/field_test.field.inc | 50 +- modules/field/tests/field_test.info | 6 +- modules/field/tests/field_test.install | 33 +- modules/field/tests/field_test.module | 23 +- .../field_ui-display-overview-form.tpl.php | 55 - .../field_ui-display-overview-table.tpl.php | 60 + .../field_ui-field-overview-form.tpl.php | 94 - modules/field_ui/field_ui-rtl.css | 5 +- modules/field_ui/field_ui.admin.inc | 654 +- modules/field_ui/field_ui.css | 14 +- modules/field_ui/field_ui.info | 10 +- modules/field_ui/field_ui.js | 141 +- modules/field_ui/field_ui.module | 195 +- modules/field_ui/field_ui.test | 13 +- modules/file/file.field.inc | 44 +- modules/file/file.info | 6 +- modules/file/file.module | 109 +- modules/file/tests/file.test | 133 +- modules/file/tests/file_module_test.info | 6 +- modules/filter/filter.admin.inc | 25 +- modules/filter/filter.api.php | 12 +- modules/filter/filter.info | 6 +- modules/filter/filter.install | 47 +- modules/filter/filter.module | 28 +- modules/filter/filter.test | 33 +- modules/forum/forum.admin.inc | 10 +- modules/forum/forum.info | 6 +- modules/forum/forum.install | 25 +- modules/forum/forum.module | 10 +- modules/forum/forum.test | 29 +- modules/help/help.info | 6 +- modules/image/image.info | 6 +- modules/image/image.install | 9 +- modules/image/image.module | 6 +- modules/image/image.test | 56 +- modules/locale/locale.admin.inc | 17 +- modules/locale/locale.info | 6 +- modules/locale/locale.install | 47 +- modules/locale/locale.test | 17 +- modules/locale/tests/locale_test.info | 6 +- modules/menu/menu.admin.inc | 20 +- modules/menu/menu.info | 6 +- modules/menu/menu.module | 11 +- modules/node/content_types.inc | 4 +- modules/node/node.admin.inc | 8 +- modules/node/node.api.php | 50 +- modules/node/node.info | 6 +- modules/node/node.module | 56 +- modules/node/node.pages.inc | 79 +- modules/node/node.test | 39 +- modules/node/node.tokens.inc | 10 +- 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-rtl.css | 19 + modules/openid/openid.inc | 37 +- modules/openid/openid.info | 9 +- modules/openid/openid.module | 19 +- modules/openid/openid.test | 67 +- modules/openid/tests/openid_test.info | 6 +- modules/openid/tests/openid_test.module | 40 +- modules/overlay/overlay-child.css | 144 + modules/overlay/overlay-child.js | 134 +- modules/overlay/overlay-parent.css | 177 +- modules/overlay/overlay-parent.js | 1113 +-- modules/overlay/overlay.info | 6 +- modules/overlay/overlay.module | 135 +- modules/overlay/overlay.tpl.php | 37 + modules/path/path.info | 6 +- modules/php/php.info | 6 +- modules/poll/poll.info | 6 +- modules/poll/poll.install | 21 +- modules/poll/poll.module | 64 +- modules/poll/poll.test | 67 +- modules/profile/profile.info | 6 +- modules/profile/profile.module | 6 +- modules/profile/profile.test | 83 +- modules/rdf/rdf.info | 6 +- modules/rdf/rdf.install | 4 +- modules/rdf/rdf.module | 4 +- modules/rdf/rdf.test | 184 +- modules/rdf/tests/rdf_test.info | 6 +- modules/search/search-block-form.tpl.php | 5 +- modules/search/search-result.tpl.php | 26 +- modules/search/search-results.tpl.php | 6 +- modules/search/search-rtl.css | 5 +- modules/search/search.admin.inc | 12 +- modules/search/search.api.php | 8 +- modules/search/search.css | 14 +- modules/search/search.info | 6 +- modules/search/search.module | 80 +- modules/search/search.pages.inc | 22 +- modules/search/search.test | 103 +- modules/shortcut/shortcut.admin.inc | 43 +- modules/shortcut/shortcut.css | 5 +- modules/shortcut/shortcut.info | 6 +- modules/shortcut/shortcut.install | 4 +- modules/shortcut/shortcut.module | 15 +- modules/shortcut/shortcut.test | 41 +- modules/simpletest/drupal_web_test_case.php | 309 +- .../files/css_test_files/comment_hacks.css | 80 + .../comment_hacks.css.optimized.css | 3 + .../comment_hacks.css.unoptimized.css | 80 + .../css_input_with_import.css.optimized.css | 6 +- ...css_input_without_import.css.optimized.css | 6 +- modules/simpletest/simpletest.info | 10 +- modules/simpletest/simpletest.test | 12 +- modules/simpletest/tests/actions.test | 7 +- .../simpletest/tests/actions_loop_test.info | 6 +- modules/simpletest/tests/ajax_forms_test.info | 6 +- modules/simpletest/tests/ajax_test.info | 6 +- modules/simpletest/tests/batch_test.info | 6 +- modules/simpletest/tests/bootstrap.test | 30 +- modules/simpletest/tests/common.test | 38 +- modules/simpletest/tests/common_test.info | 6 +- modules/simpletest/tests/database_test.info | 6 +- .../simpletest/tests/database_test.install | 4 +- modules/simpletest/tests/database_test.module | 6 +- modules/simpletest/tests/database_test.test | 20 +- .../simpletest/tests/entity_cache_test.info | 6 +- .../tests/entity_cache_test_dependency.info | 6 +- modules/simpletest/tests/entity_query.test | 930 ++ modules/simpletest/tests/error_test.info | 6 +- modules/simpletest/tests/file.test | 180 +- modules/simpletest/tests/file_test.info | 6 +- modules/simpletest/tests/file_test.module | 45 +- modules/simpletest/tests/filter_test.info | 6 +- modules/simpletest/tests/form.test | 108 +- modules/simpletest/tests/form_test.info | 6 +- modules/simpletest/tests/form_test.module | 46 +- modules/simpletest/tests/image_test.info | 6 +- modules/simpletest/tests/menu.test | 37 +- modules/simpletest/tests/menu_test.info | 6 +- modules/simpletest/tests/menu_test.module | 25 +- modules/simpletest/tests/module_test.file.inc | 4 +- modules/simpletest/tests/module_test.info | 6 +- .../simpletest/tests/requirements1_test.info | 15 + .../tests/requirements1_test.install | 22 + .../tests/requirements1_test.module | 8 + .../simpletest/tests/requirements2_test.info | 16 + .../tests/requirements2_test.module | 8 + modules/simpletest/tests/session.test | 47 +- 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 | 11 +- modules/simpletest/tests/taxonomy_test.info | 6 +- modules/simpletest/tests/theme_test.info | 6 +- modules/simpletest/tests/unicode.test | 122 +- modules/simpletest/tests/update_test_1.info | 6 +- modules/simpletest/tests/update_test_2.info | 6 +- modules/simpletest/tests/update_test_3.info | 6 +- .../tests/upgrade/drupal-6.bare.database.php | 8132 +++++++++++++++++ modules/simpletest/tests/upgrade/upgrade.test | 351 + modules/simpletest/tests/url_alter_test.info | 6 +- modules/simpletest/tests/xmlrpc_test.info | 6 +- modules/statistics/statistics.admin.inc | 8 +- modules/statistics/statistics.info | 6 +- modules/statistics/statistics.module | 13 +- modules/statistics/statistics.test | 6 +- modules/syslog/syslog.info | 6 +- modules/system/html.tpl.php | 5 +- modules/system/system-behavior-rtl.css | 5 +- modules/system/system-behavior.css | 17 +- modules/system/system-messages.css | 2 +- modules/system/system.admin.inc | 238 +- modules/system/system.api.php | 252 +- modules/system/system.css | 22 +- modules/system/system.info | 6 +- modules/system/system.install | 539 +- modules/system/system.module | 61 +- modules/system/system.queue.inc | 70 +- modules/system/system.test | 209 +- modules/system/system.tokens.inc | 10 +- modules/taxonomy/taxonomy.admin.inc | 154 +- modules/taxonomy/taxonomy.api.php | 16 +- modules/taxonomy/taxonomy.info | 6 +- modules/taxonomy/taxonomy.install | 12 +- modules/taxonomy/taxonomy.module | 55 +- modules/taxonomy/taxonomy.test | 50 +- modules/taxonomy/taxonomy.tokens.inc | 10 +- modules/toolbar/toolbar.css | 37 +- modules/toolbar/toolbar.info | 6 +- modules/toolbar/toolbar.js | 14 +- modules/toolbar/toolbar.module | 34 +- modules/toolbar/toolbar.tpl.php | 4 +- modules/tracker/tracker.info | 6 +- modules/translation/translation.info | 6 +- modules/translation/translation.module | 12 +- modules/translation/translation.test | 15 +- modules/trigger/tests/trigger_test.info | 6 +- modules/trigger/tests/trigger_test.module | 3 +- modules/trigger/trigger.info | 6 +- modules/trigger/trigger.module | 19 +- modules/trigger/trigger.test | 327 +- 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.info | 6 +- modules/update/update.install | 22 +- modules/update/update.manager.inc | 4 +- modules/update/update.settings.inc | 4 +- modules/user/user.admin.inc | 20 +- modules/user/user.info | 6 +- modules/user/user.install | 141 +- modules/user/user.module | 256 +- modules/user/user.pages.inc | 27 +- modules/user/user.test | 191 +- modules/user/user.tokens.inc | 14 +- profiles/minimal/minimal.info | 6 +- profiles/minimal/minimal.install | 16 +- profiles/standard/standard.info | 6 +- profiles/standard/standard.install | 65 +- scripts/dump-database-d6.sh | 86 + sites/default/default.settings.php | 55 +- themes/bartik/bartik.info | 46 + themes/bartik/color/color.inc | 134 + themes/bartik/color/preview.css | 62 + themes/bartik/color/preview.html | 105 + themes/bartik/color/preview.js | 37 + themes/bartik/css/colors.css | 46 + themes/bartik/css/ie-rtl.css | 11 + themes/bartik/css/ie.css | 24 + themes/bartik/css/ie6.css | 11 + themes/bartik/css/layout-rtl.css | 15 + themes/bartik/css/layout.css | 73 + themes/bartik/css/maintenance-page.css | 62 + themes/bartik/css/print.css | 47 + themes/bartik/css/style-rtl.css | 254 + themes/bartik/css/style.css | 1326 +++ themes/bartik/template.php | 146 + .../bartik/templates/comment-wrapper.tpl.php | 54 + themes/bartik/templates/comment.tpl.php | 102 + .../bartik/templates/maintenance-page.tpl.php | 93 + themes/bartik/templates/node.tpl.php | 126 + themes/bartik/templates/page.tpl.php | 280 + themes/garland/garland.info | 6 +- themes/garland/style.css | 30 +- themes/seven/seven.info | 6 +- themes/stark/stark.info | 6 +- themes/tests/test_theme/test_theme.info | 6 +- .../update_test_basetheme.info | 6 +- .../update_test_subtheme.info | 6 +- 354 files changed, 23830 insertions(+), 6109 deletions(-) create mode 100644 includes/utility.inc delete mode 100644 misc/displace.js create mode 100644 modules/contextual/contextual-rtl.css create mode 100644 modules/dashboard/dashboard.test delete mode 100644 modules/field_ui/field_ui-display-overview-form.tpl.php create mode 100644 modules/field_ui/field_ui-display-overview-table.tpl.php delete mode 100644 modules/field_ui/field_ui-field-overview-form.tpl.php create mode 100644 modules/openid/openid-rtl.css create mode 100644 modules/overlay/overlay-child.css create mode 100644 modules/overlay/overlay.tpl.php create mode 100644 modules/simpletest/files/css_test_files/comment_hacks.css create mode 100644 modules/simpletest/files/css_test_files/comment_hacks.css.optimized.css create mode 100644 modules/simpletest/files/css_test_files/comment_hacks.css.unoptimized.css create mode 100644 modules/simpletest/tests/entity_query.test create mode 100644 modules/simpletest/tests/requirements1_test.info create mode 100644 modules/simpletest/tests/requirements1_test.install create mode 100644 modules/simpletest/tests/requirements1_test.module create mode 100644 modules/simpletest/tests/requirements2_test.info create mode 100644 modules/simpletest/tests/requirements2_test.module create mode 100644 modules/simpletest/tests/upgrade/drupal-6.bare.database.php create mode 100644 modules/simpletest/tests/upgrade/upgrade.test create mode 100644 scripts/dump-database-d6.sh create mode 100644 themes/bartik/bartik.info create mode 100644 themes/bartik/color/color.inc create mode 100644 themes/bartik/color/preview.css create mode 100644 themes/bartik/color/preview.html create mode 100644 themes/bartik/color/preview.js create mode 100644 themes/bartik/css/colors.css create mode 100644 themes/bartik/css/ie-rtl.css create mode 100644 themes/bartik/css/ie.css create mode 100644 themes/bartik/css/ie6.css create mode 100644 themes/bartik/css/layout-rtl.css create mode 100644 themes/bartik/css/layout.css create mode 100644 themes/bartik/css/maintenance-page.css create mode 100644 themes/bartik/css/print.css create mode 100644 themes/bartik/css/style-rtl.css create mode 100644 themes/bartik/css/style.css create mode 100644 themes/bartik/template.php create mode 100644 themes/bartik/templates/comment-wrapper.tpl.php create mode 100644 themes/bartik/templates/comment.tpl.php create mode 100644 themes/bartik/templates/maintenance-page.tpl.php create mode 100644 themes/bartik/templates/node.tpl.php create mode 100644 themes/bartik/templates/page.tpl.php diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 0e59762d..cc4eb659 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,13 +1,13 @@ -// $Id: CHANGELOG.txt,v 1.365 2010/05/23 15:26:38 webchick Exp $ +// $Id: CHANGELOG.txt,v 1.369 2010/07/09 00:14:02 webchick Exp $ -Drupal 7.0, alpha 5, 2010-05-23 +Drupal 7.0 alpha 6, 2010-07-08 ---------------------- - Database: * Fully rewritten database layer utilizing PHP 5's PDO abstraction layer. * Drupal now requires MySQL >= 5.0.15 or PostgreSQL >= 8.3. * Added query builders for INSERT, UPDATE, DELETE, MERGE, and SELECT queries. * Support for master/slave replication, transactions, multi-insert queries, - delayed inserts, and other features. + and other features. * Added support for the SQLite database engine. * Default to InnoDB engine, rather than MyISAM, on MySQL when available. This offers increased scalability and data integrity. @@ -32,14 +32,14 @@ Drupal 7.0, alpha 5, 2010-05-23 * Provided descriptions and human-readable names for user permissions. * Removed comment controls for users. * Removed display order settings for comment module. Comment display - order can now be customised using the Views module. + order can now be customized using the Views module. * Removed the 'related terms' feature from taxonomy module since this can now be achieved with Field API. * Added additional features to the default install profile, and implemented a "slimmed down" install profile designed for developers. * Added a built-in, automated cron run feature, which is triggered by site visitors. - * Added an administrator role which is assigned all permisions for + * Added an administrator role which is assigned all permissions for installed modules automatically. * Image toolkits are now provided by modules (rather than requiring a manual file copy to the includes directory). @@ -59,7 +59,7 @@ Drupal 7.0, alpha 5, 2010-05-23 * Improved performance for logged-in users by reducing queries for path alias lookups. * Improved support for HTTP proxies (including reverse proxies), allowing - anonymous pageviews to be served entirely from the proxy. + anonymous page views to be served entirely from the proxy. - Documentation: * Hook API documentation now included in Drupal core. - News aggregator: diff --git a/MAINTAINERS.txt b/MAINTAINERS.txt index 841153f8..d1585112 100644 --- a/MAINTAINERS.txt +++ b/MAINTAINERS.txt @@ -1,4 +1,4 @@ -// $Id: MAINTAINERS.txt,v 1.41 2010/04/20 07:13:34 webchick Exp $ +// $Id: MAINTAINERS.txt,v 1.42 2010/06/03 13:50:54 dries Exp $ Drupal core is maintained by the community. To participate, go to @@ -228,7 +228,6 @@ RDF module - Stéphane Corlosquet 'scor' <http://drupal.org/user/52142> Search module -- Jennifer Hodgdon 'jhodgdon' <http://drupal.org/user/155601> - Doug Green 'douggreen' <http://drupal.org/user/29191> Shortcut module diff --git a/UPGRADE.txt b/UPGRADE.txt index 7521fb32..f9da7d53 100644 --- a/UPGRADE.txt +++ b/UPGRADE.txt @@ -1,4 +1,4 @@ -// $Id: UPGRADE.txt,v 1.21 2010/04/29 04:49:02 webchick Exp $ +// $Id: UPGRADE.txt,v 1.22 2010/05/26 19:51:01 dries Exp $ UPGRADING --------- @@ -14,9 +14,9 @@ Prior to upgrading, you should ensure that: Let's begin! -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 +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" diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index 3a76f2d4..61e61f39 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -1,5 +1,5 @@ <?php -// $Id: bootstrap.inc,v 1.391 2010/05/23 15:26:38 webchick Exp $ +// $Id: bootstrap.inc,v 1.407 2010/07/09 00:14:02 webchick Exp $ /** * @file @@ -9,7 +9,7 @@ /** * The current system version. */ -define('VERSION', '7.0-alpha5'); +define('VERSION', '7.0-alpha6'); /** * Core API compatibility. @@ -19,7 +19,7 @@ define('DRUPAL_CORE_COMPATIBILITY', '7.x'); /** * Minimum supported version of PHP. */ -define('DRUPAL_MINIMUM_PHP', '5.2.1'); +define('DRUPAL_MINIMUM_PHP', '5.2.0'); /** * Minimum recommended value of PHP memory_limit. @@ -560,12 +560,13 @@ function drupal_settings_initialize() { global $base_url, $base_path, $base_root; // Export the following settings.php variables to the global namespace - global $databases, $db_prefix, $cookie_domain, $conf, $installed_profile, $update_free_access, $db_url, $drupal_hash_salt, $is_https, $base_secure_url, $base_insecure_url; + global $databases, $cookie_domain, $conf, $installed_profile, $update_free_access, $db_url, $drupal_hash_salt, $is_https, $base_secure_url, $base_insecure_url; $conf = array(); if (file_exists(DRUPAL_ROOT . '/' . conf_path() . '/settings.php')) { include_once DRUPAL_ROOT . '/' . conf_path() . '/settings.php'; } + $is_https = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on'; if (isset($base_url)) { // Parse fixed base URL from settings.php. @@ -580,7 +581,7 @@ function drupal_settings_initialize() { } else { // Create base URL - $http_protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') ? 'https' : 'http'; + $http_protocol = $is_https ? 'https' : 'http'; $base_root = $http_protocol . '://' . $_SERVER['HTTP_HOST']; $base_url = $base_root; @@ -596,7 +597,6 @@ function drupal_settings_initialize() { $base_path = '/'; } } - $is_https = $http_protocol == 'https'; $base_secure_url = str_replace('http://', 'https://', $base_url); $base_insecure_url = str_replace('https://', 'http://', $base_url); @@ -743,13 +743,26 @@ function drupal_get_filename($type, $name, $filename = NULL) { * file. */ function variable_initialize($conf = array()) { - // NOTE: caching the variables improves performance by 20% when serving cached pages. + // NOTE: caching the variables improves performance by 20% when serving + // cached pages. if ($cached = cache_get('variables', 'cache_bootstrap')) { $variables = $cached->data; } else { - $variables = array_map('unserialize', db_query('SELECT name, value FROM {variable}')->fetchAllKeyed()); - cache_set('variables', $variables, 'cache_bootstrap'); + // Cache miss. Avoid a stampede. + $name = 'variable_init'; + if (!lock_acquire($name, 1)) { + // Another request is building the variable cache. + // Wait, then re-run this function. + lock_wait($name); + return variable_initialize($conf); + } + else { + // Proceed with variable rebuild. + $variables = array_map('unserialize', db_query('SELECT name, value FROM {variable}')->fetchAllKeyed()); + cache_set('variables', $variables, 'cache_bootstrap'); + lock_release($name); + } } foreach ($conf as $name => $value) { @@ -760,7 +773,11 @@ function variable_initialize($conf = array()) { } /** - * Return a persistent variable. + * Returns a persistent variable. + * + * Case-sensitivity of the variable_* functions depends on the database + * collation used. To avoid problems, always use lower case for persistent + * variable names. * * @param $name * The name of the variable to return. @@ -780,7 +797,11 @@ function variable_get($name, $default = NULL) { } /** - * Set a persistent variable. + * Sets a persistent variable. + * + * Case-sensitivity of the variable_* functions depends on the database + * collation used. To avoid problems, always use lower case for persistent + * variable names. * * @param $name * The name of the variable to set. @@ -802,7 +823,11 @@ function variable_set($name, $value) { } /** - * Unset a persistent variable. + * Unsets a persistent variable. + * + * Case-sensitivity of the variable_* functions depends on the database + * collation used. To avoid problems, always use lower case for persistent + * variable names. * * @param $name * The name of the variable to undefine. @@ -877,10 +902,12 @@ function drupal_page_is_cacheable($allow_caching = NULL) { * * @param $hook * The name of the bootstrap hook to invoke. + * + * @see bootstrap_hooks() */ function bootstrap_invoke_all($hook) { - // _drupal_bootstrap_page_cache() already loaded the bootstrap modules, so we - // don't need to tell module_list() to reset its bootstrap list. + // Bootstrap modules should have been loaded when this function is called, so + // we don't need to tell module_list() to reset its bootstrap list. foreach (module_list(FALSE, TRUE) as $module) { drupal_load('module', $module); module_invoke($module, $hook); @@ -1535,10 +1562,51 @@ function request_uri() { } /** - * Log a system message. + * Log an exception. + * + * This is a wrapper function for watchdog() which automatically decodes an + * exception. * * @param $type * The category to which this message belongs. + * @param $exception + * The exception that is going to be logged. + * @param $message + * The message to store in the log. If empty, a text that contains all useful + * information about the passed in exception is used. + * @param $variables + * Array of variables to replace in the message on display. Defaults to the + * return value of drupal_decode_exception(). + * @param $severity + * The severity of the message, as per RFC 3164. + * @param $link + * A link to associate with the message. + * + * @see watchdog() + * @see drupal_decode_exception() + */ +function watchdog_exception($type, Exception $exception, $message = NULL, $variables = array(), $severity = WATCHDOG_ERROR, $link = NULL) { + + // Use a default value if $message is not set. + if (empty($message)) { + $message = '%type: %message in %function (line %line of %file).'; + } + // $variables must be an array so that we can add the exception information. + if (!is_array($variables)) { + $variables = array(); + } + + require_once DRUPAL_ROOT . '/includes/errors.inc'; + $variables += _drupal_decode_exception($exception); + watchdog($type, $message, $variables, $severity, $link); +} + +/** + * Log a system message. + * + * @param $type + * The category to which this message belongs. Can be any string, but the + * general practice is to use the name of the module calling watchdog(). * @param $message * The message to store in the log. Keep $message translatable * by not concatenating dynamic values into it! Variables in the @@ -1550,14 +1618,13 @@ function request_uri() { * NULL if message is already translated or not possible to * translate. * @param $severity - * The severity of the message, as per RFC 3164. + * The severity of the message, as per RFC 3164. Possible values are + * WATCHDOG_ERROR, WATCHDOG_WARNING, etc. * @param $link * A link to associate with the message. * * @see watchdog_severity_levels() * @see hook_watchdog() - * @see DatabaseConnection::rollback() - * @see DatabaseTransaction::rollback() */ function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) { global $user, $base_root; @@ -1584,7 +1651,7 @@ function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NO ); // Call the logging hooks to log/process the message - foreach (module_implements('watchdog', TRUE) as $module) { + foreach (module_implements('watchdog') as $module) { module_invoke($module, 'watchdog', $log_entry); } @@ -2001,11 +2068,10 @@ function _drupal_exception_handler($exception) { catch (Exception $exception2) { // Another uncaught exception was thrown while handling the first one. // If we are displaying errors, then do so with no possibility of a further uncaught exception being thrown. - $error_level = variable_get('error_level', ERROR_REPORTING_DISPLAY_ALL); - if ($error_level == ERROR_REPORTING_DISPLAY_ALL || (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update')) { - print 'Additional uncaught exception thrown while handling exception.<br /><br />'; - print '<b>Original</b><br />' . _drupal_render_exception_safe($exception) . '<br /><br />'; - print '<b>Additional</b><br />' . _drupal_render_exception_safe($exception2) . '<br /><br />'; + if (error_displayable()) { + print '<h1>Additional uncaught exception thrown while handling exception.</h1>'; + print '<h2>Original</h2><p>' . _drupal_render_exception_safe($exception) . '</p>'; + print '<h2>Additional</h2><p>' . _drupal_render_exception_safe($exception2) . '</p><hr />'; } } } @@ -2085,14 +2151,6 @@ function _drupal_bootstrap_page_cache() { * Bootstrap database: Initialize database system and register autoload functions. */ function _drupal_bootstrap_database() { - // The user agent header is used to pass a database prefix in the request when - // running tests. However, for security reasons, it is imperative that we - // validate we ourselves made the request. - if (isset($_SERVER['HTTP_USER_AGENT']) && (strpos($_SERVER['HTTP_USER_AGENT'], "simpletest") !== FALSE) && !drupal_valid_test_ua($_SERVER['HTTP_USER_AGENT'])) { - 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. @@ -2101,13 +2159,46 @@ function _drupal_bootstrap_database() { install_goto('install.php'); } + // The user agent header is used to pass a database prefix in the request when + // running tests. However, for security reasons, it is imperative that we + // validate we ourselves made the request. + if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^(simpletest\d+);/", $_SERVER['HTTP_USER_AGENT'], $matches)) { + if (!drupal_valid_test_ua($_SERVER['HTTP_USER_AGENT'])) { + header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden'); + exit; + } + + // The first part of the user agent is the prefix itself. + $test_prefix = $matches[1]; + + // Set the test run id for use in other parts of Drupal. + $test_info = &$GLOBALS['drupal_test_info']; + $test_info['test_run_id'] = $test_prefix; + $test_info['in_child_site'] = TRUE; + + foreach ($GLOBALS['databases']['default'] as &$value) { + // Extract the current default database prefix. + if (!isset($value['prefix'])) { + $current_prefix = ''; + } + else if (is_array($value['prefix'])) { + $current_prefix = $value['prefix']['default']; + } + else { + $current_prefix = $value['prefix']; + } + + // Remove the current database prefix and replace it by our own. + $value['prefix'] = array( + 'default' => $current_prefix . $test_prefix, + ); + } + } + // 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'; - // Set Drupal's watchdog as the logging callback. - Database::setLoggingCallback('watchdog', WATCHDOG_NOTICE, WATCHDOG_ERROR); - // Register autoload functions so that we can access classes and interfaces. // The database autoload routine comes first so that we can load the database // system without hitting the database. That is especially important during @@ -2123,6 +2214,10 @@ function _drupal_bootstrap_database() { function _drupal_bootstrap_variables() { global $conf; + // Initialize the lock system. + require_once DRUPAL_ROOT . '/' . variable_get('lock_inc', 'includes/lock.inc'); + lock_initialize(); + // Load variables from the database, but do not overwrite variables set in settings.php. $conf = variable_initialize(isset($conf) ? $conf : array()); // Load bootstrap modules. @@ -2136,10 +2231,6 @@ function _drupal_bootstrap_variables() { function _drupal_bootstrap_page_header() { bootstrap_invoke_all('boot'); - // Prepare for non-cached page workflow. - require_once DRUPAL_ROOT . '/' . variable_get('lock_inc', 'includes/lock.inc'); - lock_initialize(); - if (!drupal_is_cli()) { ob_start(); drupal_page_header(); @@ -2161,32 +2252,34 @@ function drupal_get_bootstrap_phase() { * Validate the HMAC and timestamp of a user agent header from simpletest. */ function drupal_valid_test_ua($user_agent) { - global $databases; + global $drupal_hash_salt; list($prefix, $time, $salt, $hmac) = explode(';', $user_agent); $check_string = $prefix . ';' . $time . ';' . $salt; - // We use the database credentials from settings.php to make the HMAC key, since + // We use the salt from settings.php to make the HMAC key, since // 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 = serialize($databases) . filectime($filepath) . fileinode($filepath); - // The HMAC must match. - return $hmac == drupal_hmac_base64($check_string, $key); + $key = $drupal_hash_salt . filectime($filepath) . fileinode($filepath); + $time_diff = REQUEST_TIME - $time; + // Since we are making a local request a 5 second time window is allowed, + // and the HMAC must match. + return ($time_diff >= 0) && ($time_diff <= 5) && ($hmac == drupal_hmac_base64($check_string, $key)); } /** * Generate a user agent string with a HMAC and timestamp for simpletest. */ function drupal_generate_test_ua($prefix) { - global $databases; + global $drupal_hash_salt; static $key; if (!isset($key)) { - // We use the database credentials to make the HMAC key, since we - // check the HMAC before the database is initialized. filectime() - // and fileinode() are not easily determined from remote. + // We use the salt from settings.php to make the HMAC key, since + // 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 = serialize($databases) . filectime($filepath) . fileinode($filepath); + $key = $drupal_hash_salt . filectime($filepath) . fileinode($filepath); } // Generate a moderately secure HMAC based on the database credentials. $salt = uniqid('', TRUE); @@ -2447,13 +2540,21 @@ function ip_address() { // If an array of known reverse proxy IPs is provided, then trust // the XFF header if request really comes from one of them. $reverse_proxy_addresses = variable_get('reverse_proxy_addresses', array()); - if (!empty($reverse_proxy_addresses) && in_array($ip_address, $reverse_proxy_addresses, TRUE)) { - // The "X-Forwarded-For" header is a comma+space separated list of IP addresses, - // the left-most being the farthest downstream client. If there is more than - // one proxy, we are interested in the most recent one (i.e. last one in the list). - $ip_address_parts = explode(',', $_SERVER[$reverse_proxy_header]); - $ip_address = trim(array_pop($ip_address_parts)); - } + + // Turn XFF header into an array. + $forwarded = explode(',', $_SERVER[$reverse_proxy_header]); + + // Trim the forwarded IPs; they may have been delimited by commas and spaces. + $forwarded = array_map('trim', $forwarded); + + // Tack direct client IP onto end of forwarded array. + $forwarded[] = $ip_address; + + // Eliminate all trusted IPs. + $untrusted = array_diff($forwarded, $reverse_proxy_addresses); + + // The right-most IP is the most specific we can trust. + $ip_address = array_pop($untrusted); } } } @@ -2626,7 +2727,7 @@ function _registry_check_code($type, $name = NULL) { $cache_key = $type[0] . $name; if (isset($lookup_cache[$cache_key])) { if ($lookup_cache[$cache_key]) { - require DRUPAL_ROOT . '/' . $lookup_cache[$cache_key]; + require_once DRUPAL_ROOT . '/' . $lookup_cache[$cache_key]; } return (bool) $lookup_cache[$cache_key]; } @@ -2648,7 +2749,7 @@ function _registry_check_code($type, $name = NULL) { $lookup_cache[$cache_key] = $file; if ($file) { - require DRUPAL_ROOT . '/' . $file; + require_once DRUPAL_ROOT . '/' . $file; return TRUE; } else { @@ -2883,6 +2984,7 @@ function drupal_placeholder($variables) { * Array of shutdown functions to be executed. * * @see register_shutdown_function() + * @ingroup php_wrappers */ function &drupal_register_shutdown_function($callback = NULL, $parameters = NULL) { // We cannot use drupal_static() here because the static cache is reset @@ -2916,11 +3018,10 @@ function _drupal_shutdown_function() { } catch (Exception $exception) { // If we are displaying errors, then do so with no possibility of a further uncaught exception being thrown. - $error_level = variable_get('error_level', ERROR_REPORTING_DISPLAY_ALL); - if ($error_level == ERROR_REPORTING_DISPLAY_ALL || (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update')) { - require_once DRUPAL_ROOT . '/includes/errors.inc'; - print 'Uncaught exception thrown in shutdown function.<br /><br />'; - print _drupal_render_exception_safe($exception) . '<br /><br />'; + require_once DRUPAL_ROOT . '/includes/errors.inc'; + if (error_displayable()) { + print '<h1>Uncaught exception thrown in shutdown function.</h1>'; + print '<p>' . _drupal_render_exception_safe($exception) . '</p><hr />'; } } } diff --git a/includes/common.inc b/includes/common.inc index b976c90a..0bae0379 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -1,5 +1,5 @@ <?php -// $Id: common.inc,v 1.1170 2010/05/21 15:52:19 dries Exp $ +// $Id: common.inc,v 1.1191 2010/07/07 17:00:42 webchick Exp $ /** * @file @@ -262,7 +262,7 @@ function drupal_get_rdf_namespaces() { $xml_rdf_namespaces[] = 'xmlns:' . $prefix . '="' . $uri . '"'; } } - return implode("\n ", $xml_rdf_namespaces); + return count($xml_rdf_namespaces) ? "\n " . implode("\n ", $xml_rdf_namespaces) : ''; } /** @@ -659,7 +659,8 @@ 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'])) { + // We do not allow absolute URLs to be passed via $_GET, as this can be an attack vector. + if (isset($_GET['destination']) && !url_is_external($_GET['destination'])) { $destination = drupal_parse_url($_GET['destination']); $path = $destination['path']; $options['query'] = $destination['query']; @@ -765,8 +766,6 @@ function drupal_access_denied() { * A string containing the response body that was received. */ function drupal_http_request($url, array $options = array()) { - global $db_prefix; - $result = new stdClass(); // Parse the URL and make sure we can handle the schema. @@ -866,8 +865,9 @@ function drupal_http_request($url, array $options = array()) { // user-agent is used to ensure that multiple testing sessions running at the // same time won't interfere with each other as they would if the database // prefix were stored statically in a file or database variable. - if (is_string($db_prefix) && preg_match("/simpletest\d+/", $db_prefix, $matches)) { - $options['headers']['User-Agent'] = drupal_generate_test_ua($matches[0]); + $test_info = &$GLOBALS['drupal_test_info']; + if (!empty($test_info['test_run_id'])) { + $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']); } $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n"; @@ -1202,10 +1202,75 @@ function flood_is_allowed($name, $threshold, $window = 3600, $identifier = NULL) */ /** - * Prepare a URL for use in an HTML attribute. Strips harmful protocols. + * Strips dangerous protocols (e.g. 'javascript:') from a URI. + * + * This function must be called for all URIs within user-entered input prior + * to being output to an HTML attribute value. It is often called as part of + * check_url() or filter_xss(), but those functions return an HTML-encoded + * string, so this function can be called independently when the output needs to + * be a plain-text string for passing to t(), l(), drupal_attributes(), or + * another function that will call check_plain() separately. + * + * @param $uri + * A plain-text URI that might contain dangerous protocols. + * + * @return + * A plain-text URI stripped of dangerous protocols. As with all plain-text + * strings, this return value must not be output to an HTML page without + * check_plain() being called on it. However, it can be passed to functions + * expecting plain-text strings. + * + * @see check_url() + */ +function drupal_strip_dangerous_protocols($uri) { + static $allowed_protocols; + + if (!isset($allowed_protocols)) { + $allowed_protocols = array_flip(variable_get('filter_allowed_protocols', array('ftp', 'http', 'https', 'irc', 'mailto', 'news', 'nntp', 'rtsp', 'sftp', 'ssh', 'telnet', 'webcal'))); + } + + // Iteratively remove any invalid protocol found. + do { + $before = $uri; + $colonpos = strpos($uri, ':'); + if ($colonpos > 0) { + // We found a colon, possibly a protocol. Verify. + $protocol = substr($uri, 0, $colonpos); + // If a colon is preceded by a slash, question mark or hash, it cannot + // possibly be part of the URL scheme. This must be a relative URL, which + // inherits the (safe) protocol of the base document. + if (preg_match('![/?#]!', $protocol)) { + break; + } + // Check if this is a disallowed protocol. Per RFC2616, section 3.2.3 + // (URI Comparison) scheme comparison must be case-insensitive. + if (!isset($allowed_protocols[strtolower($protocol)])) { + $uri = substr($uri, $colonpos + 1); + } + } + } while ($before != $uri); + + return $uri; +} + +/** + * Strips dangerous protocols (e.g. 'javascript:') from a URI and encodes it for output to an HTML attribute value. + * + * @param $uri + * A plain-text URI that might contain dangerous protocols. + * + * @return + * A URI stripped of dangerous protocols and encoded for output to an HTML + * attribute value. Because it is already encoded, it should not be set as a + * value within a $attributes array passed to drupal_attributes(), because + * drupal_attributes() expects those values to be plain-text strings. To pass + * a filtered URI to drupal_attributes(), call + * drupal_strip_dangerous_protocols() instead. + * + * @see drupal_strip_dangerous_protocols() */ function check_url($uri) { - return filter_xss_bad_protocol($uri, FALSE); + return check_plain(drupal_strip_dangerous_protocols($uri)); } /** @@ -1223,25 +1288,30 @@ function filter_xss_admin($string) { } /** - * Filter XSS. - * - * Based on kses by Ulf Harnhammar, see - * http://sourceforge.net/projects/kses + * Filters an HTML string to prevent cross-site-scripting (XSS) vulnerabilities. * - * For examples of various XSS attacks, see: - * http://ha.ckers.org/xss.html + * Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses. + * For examples of various XSS attacks, see: http://ha.ckers.org/xss.html. * * This code does four things: - * - Removes characters and constructs that can trick browsers - * - Makes sure all HTML entities are well-formed - * - Makes sure all HTML tags and attributes are well-formed - * - Makes sure no HTML tags contain URLs with a disallowed protocol (e.g. javascript:) + * - Removes characters and constructs that can trick browsers. + * - Makes sure all HTML entities are well-formed. + * - Makes sure all HTML tags and attributes are well-formed. + * - Makes sure no HTML tags contain URLs with a disallowed protocol (e.g. + * javascript:). * * @param $string - * The string with raw HTML in it. It will be stripped of everything that can cause - * an XSS attack. + * The string with raw HTML in it. It will be stripped of everything that can + * cause an XSS attack. * @param $allowed_tags * An array of allowed tags. + * + * @return + * An XSS safe version of $string, or an empty string if $string is not + * valid UTF-8. + * + * @see drupal_validate_utf8() + * @ingroup sanitization */ function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) { // Only operate on valid UTF-8 strings. This is necessary to prevent cross @@ -1436,7 +1506,7 @@ function _filter_xss_attributes($attr) { } // The attribute list ends with a valueless attribute like "selected". - if ($mode == 1) { + if ($mode == 1 && !$skip) { $attrarr[] = $attrname; } return $attrarr; @@ -1448,45 +1518,21 @@ function _filter_xss_attributes($attr) { * @param $string * The string with the attribute value. * @param $decode - * Whether to decode entities in the $string. Set to FALSE if the $string - * is in plain text, TRUE otherwise. Defaults to TRUE. + * (Deprecated) Whether to decode entities in the $string. Set to FALSE if the + * $string is in plain text, TRUE otherwise. Defaults to TRUE. This parameter + * is deprecated and will be removed in Drupal 8. To process a plain-text URI, + * call drupal_strip_dangerous_protocols() or check_url() instead. * @return * Cleaned up and HTML-escaped version of $string. */ function filter_xss_bad_protocol($string, $decode = TRUE) { - static $allowed_protocols; - - if (!isset($allowed_protocols)) { - $allowed_protocols = array_flip(variable_get('filter_allowed_protocols', array('ftp', 'http', 'https', 'irc', 'mailto', 'news', 'nntp', 'rtsp', 'sftp', 'ssh', 'telnet', 'webcal'))); - } - // Get the plain text representation of the attribute value (i.e. its meaning). + // @todo Remove the $decode parameter in Drupal 8, and always assume an HTML + // string that needs decoding. if ($decode) { $string = decode_entities($string); } - - // Iteratively remove any invalid protocol found. - do { - $before = $string; - $colonpos = strpos($string, ':'); - if ($colonpos > 0) { - // We found a colon, possibly a protocol. Verify. - $protocol = substr($string, 0, $colonpos); - // If a colon is preceded by a slash, question mark or hash, it cannot - // possibly be part of the URL scheme. This must be a relative URL, - // which inherits the (safe) protocol of the base document. - if (preg_match('![/?#]!', $protocol)) { - break; - } - // Per RFC2616, section 3.2.3 (URI Comparison) scheme comparison must be case-insensitive - // Check if this is a disallowed protocol. - if (!isset($allowed_protocols[strtolower($protocol)])) { - $string = substr($string, $colonpos + 1); - } - } - } while ($before != $string); - - return check_plain($string); + return check_plain(drupal_strip_dangerous_protocols($string)); } /** @@ -1961,6 +2007,18 @@ function format_username($account) { * dependent URL requires so. * - 'prefix': Only used internally, to modify the path when a language * dependent URL requires so. + * - 'script': The script filename in Drupal's root directory to use when + * clean URLs are disabled, such as 'index.php'. Defaults to an empty + * string, as most modern web servers automatically find 'index.php'. If + * clean URLs are disabled, the value of $path is appended as query + * parameter 'q' to $options['script'] in the returned URL. When deploying + * Drupal on a web server that cannot be configured to automatically find + * index.php, then hook_url_outbound_alter() can be implemented to force + * this value to 'index.php'. + * - 'entity_type': The entity type of the object that called url(). Only set if + * url() is invoked by entity_uri(). + * - 'entity': The entity object (such as a node) for which the URL is being + * generated. Only set if url() is invoked by entity_uri(). * * @return * A string containing a URL to the given path. @@ -1976,13 +2034,13 @@ function url($path = NULL, array $options = array()) { ); if (!isset($options['external'])) { - // Return an external link if $path contains an allowed absolute URL. - // Only call the slow filter_xss_bad_protocol if $path contains a ':' - // before any / ? or #. - // Note: we could use url_is_external($path) here, but that would - // require another function call, and performance inside url() is critical. + // Return an external link if $path contains an allowed absolute URL. Only + // call the slow drupal_strip_dangerous_protocols() if $path contains a ':' + // before any / ? or #. Note: we could use url_is_external($path) here, but + // that would require another function call, and performance inside url() is + // critical. $colonpos = strpos($path, ':'); - $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path)); + $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path); } // Preserve the original path before altering or aliasing. @@ -2076,33 +2134,29 @@ function url($path = NULL, array $options = array()) { // parameters. $query += $options['query']; } - if ($query) { - // On some web servers, such as IIS, we can't omit "index.php". So, we - // generate "index.php?q=foo" instead of "?q=foo" on anything that is not - // Apache. strpos() is fast, so there is no performance benefit to - // statically caching its result. - // @todo This needs to be re-evaluated with modern web servers. Since we - // do not add $script when there aren't query parameters, we're already - // assuming that index.php is setup as a default document on the web - // server. If that's the case, it should be possible to omit "index.php" - // even when there are query parameters: http://drupal.org/node/437228. - $script = (strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') === FALSE) ? 'index.php' : ''; - return $base . $script . '?' . drupal_http_build_query($query) . $options['fragment']; - } - else { - return $base . $options['fragment']; - } + $query = $query ? ('?' . drupal_http_build_query($query)) : ''; + $script = isset($options['script']) ? $options['script'] : ''; + return $base . $script . $query . $options['fragment']; } } /** - * Return TRUE if a path is external (e.g. http://example.com). + * Return TRUE if a path is external to Drupal (e.g. http://example.com). + * + * If a path cannot be assessed by Drupal's menu handler, then we must + * treat it as potentially insecure. + * + * @param $path + * The internal path or external URL being linked to, such as "node/34" or + * "http://example.com/foo". + * @return + * Boolean TRUE or FALSE, where TRUE indicates an external path. */ function url_is_external($path) { $colonpos = strpos($path, ':'); - // Only call the slow filter_xss_bad_protocol if $path contains a ':' - // before any / ? or #. - return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path); + // Only call the slow drupal_strip_dangerous_protocols() if $path contains a + // ':' before any / ? or #. + return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path; } /** @@ -2262,10 +2316,9 @@ function l($text, $path, array $options = array()) { * sends the content to the browser in the needed format. The default delivery * callback is drupal_deliver_html_page() which delivers the content as an HTML * page, complete with blocks in addition to the content. This default can be - * overridden on a per menu item basis by setting 'delivery callback' in - * hook_menu(), hook_menu_alter(), or hook_menu_active_handler_alter(). - * Additionally, modules may use hook_page_delivery_callback_alter() to specify - * a different delivery callback to use for the page request. + * overridden on a per menu router item basis by setting 'delivery callback' in + * hook_menu() or hook_menu_alter(), and can also be overridden on a per request + * basis in hook_page_delivery_callback_alter(). * * For example, the same page callback function can be used for an HTML * version of the page and an AJAX version of the page. The page callback @@ -2287,25 +2340,20 @@ function l($text, $path, array $options = array()) { * (Optional) If given, it is the name of a delivery function most likely * to be appropriate for the page request as determined by the calling * function (e.g., menu_execute_active_handler()). If not given, it is - * determined from the menu router information of the current page. In either - * case, modules have a final chance to alter which function is called. + * determined from the menu router information of the current page. * * @see menu_execute_active_handler() * @see hook_menu() * @see hook_menu_alter() - * @see hook_menu_active_handler_alter() * @see hook_page_delivery_callback_alter() */ function drupal_deliver_page($page_callback_result, $default_delivery_callback = NULL) { if (!isset($default_delivery_callback) && ($router_item = menu_get_item())) { - drupal_alter('menu_active_handler', $router_item); $default_delivery_callback = $router_item['delivery_callback']; } $delivery_callback = !empty($default_delivery_callback) ? $default_delivery_callback : 'drupal_deliver_html_page'; - // Give modules a final chance to alter the delivery callback used. This is - // for modules that need to decide which delivery callback to use based on - // information made available during page callback execution and for pages - // without router items. + // Give modules a chance to alter the delivery callback used, based on + // request-time context (e.g., HTTP request headers). drupal_alter('page_delivery_callback', $delivery_callback); if (function_exists($delivery_callback)) { $delivery_callback($page_callback_result); @@ -2486,7 +2534,9 @@ function drupal_exit($destination = NULL) { * An associative array. */ function drupal_map_assoc($array, $function = NULL) { - $array = array_combine($array, $array); + // array_combine() fails with empty arrays: + // http://bugs.php.net/bug.php?id=34857. + $array = !empty($array) ? array_combine($array, $array) : array(); if (is_callable($function)) { $array = array_map($function, $array); } @@ -2746,7 +2796,7 @@ function drupal_get_css($css = NULL) { $css = drupal_add_css(); } - // Allow modules to alter the CSS items. + // Allow modules and themes to alter the CSS items. drupal_alter('css', $css); // Sort CSS items according to their weights. @@ -3260,14 +3310,21 @@ function drupal_load_stylesheet_content($contents, $optimize = FALSE) { if ($optimize) { // Perform some safe CSS optimizations. - $contents = preg_replace('{ - (?<=\\\\\*/)([^/\*]+/\*)([^\*/]+\*/) # Add a backslash also at the end ie-mac hack comment, so the next pass will not touch it. - # The added backslash does not affect the effectiveness of the hack. - }x', '\1\\\\\2', $contents); - $contents = preg_replace('< - \s*([@{}:;,]|\)\s|\s\()\s* | # Remove whitespace around separators, but keep space around parentheses. - /\*[^*\\\\]*\*+([^/*][^*]*\*+)*/ | # Remove comments that are not CSS hacks. - >x', '\1', $contents); + // Regexp to match comment blocks. + $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'; + // Regexp to match double quoted strings. + $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; + // Regexp to match single quoted strings. + $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'"; + $contents = preg_replace( + "<($double_quot|$single_quot)|$comment>Sus", // Strip all comment blocks + "$1", // but keep double/single + $contents); // quoted strings. + $contents = preg_replace( + '<\s*([@{}:;,]|\)\s|\s\()\s*>S', // Remove whitespace around separators, + '\1', $contents); // but keep space around parentheses. + // End the file with a new line. + $contents .= "\n"; } // Replaces @import commands with the actual stylesheet content. @@ -4271,6 +4328,7 @@ function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgro // Add the table drag JavaScript to the page before the module JavaScript // to ensure that table drag behaviors are registered before any module // uses it. + drupal_add_js('misc/jquery.cookie.js', array('weight' => JS_DEFAULT - 2)); drupal_add_js('misc/tabledrag.js', array('weight' => JS_DEFAULT - 1)); $js_added = TRUE; } @@ -4482,13 +4540,15 @@ function _drupal_bootstrap_full() { module_load_all(); // Make sure all stream wrappers are registered. file_get_stream_wrappers(); - if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'simpletest') !== FALSE) { - // Valid SimpleTest user-agent, log fatal errors to test specific file - // directory. The user-agent is validated in DRUPAL_BOOTSTRAP_DATABASE - // phase so as long as it is a SimpleTest user-agent it is valid. + + $test_info = &$GLOBALS['drupal_test_info']; + if (!empty($test_info['in_child_site'])) { + // Running inside the simpletest child site, log fatal errors to test + // specific file directory. ini_set('log_errors', 1); ini_set('error_log', file_directory_path() . '/error.log'); } + // Initialize $_GET['q'] prior to invoking hook_init(). drupal_path_initialize(); // Set a custom theme for the current page, if there is one. We need to run @@ -4575,27 +4635,15 @@ function drupal_cron_run() { // Try to allocate enough time to run all the hook_cron implementations. drupal_set_time_limit(240); - // Fetch the cron semaphore - $semaphore = variable_get('cron_semaphore', FALSE); - $return = FALSE; // Grab the defined cron queues. $queues = module_invoke_all('cron_queue_info'); drupal_alter('cron_queue_info', $queues); - if ($semaphore) { - if (REQUEST_TIME - $semaphore > 3600) { - // Either cron has been running for more than an hour or the semaphore - // was not reset due to a database error. - watchdog('cron', 'Cron has been running for more than an hour and is most likely stuck.', array(), WATCHDOG_ERROR); - - // Release cron semaphore - variable_del('cron_semaphore'); - } - else { - // Cron is still running normally. - watchdog('cron', 'Attempting to re-run cron while it is already running.', array(), WATCHDOG_WARNING); - } + // Try to acquire cron lock. + if (!lock_acquire('cron', 240.0)) { + // Cron is still running normally. + watchdog('cron', 'Attempting to re-run cron while it is already running.', array(), WATCHDOG_WARNING); } else { // Make sure every queue exists. There is no harm in trying to recreate an @@ -4606,9 +4654,6 @@ function drupal_cron_run() { // Register shutdown callback drupal_register_shutdown_function('drupal_cron_cleanup'); - // Lock cron semaphore - variable_set('cron_semaphore', REQUEST_TIME); - // Iterate through the modules calling their cron handlers (if any): module_invoke_all('cron'); @@ -4616,8 +4661,8 @@ function drupal_cron_run() { variable_set('cron_last', REQUEST_TIME); watchdog('cron', 'Cron run completed.', array(), WATCHDOG_NOTICE); - // Release cron semaphore - variable_del('cron_semaphore'); + // Release cron lock. + lock_release('cron'); // Return TRUE so other functions can check if it did run successfully $return = TRUE; @@ -4654,7 +4699,7 @@ function drupal_cron_cleanup() { /** * 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 @@ -6158,6 +6203,8 @@ function watchdog_severity_levels() { /** * Explode a string of given tags into an array. + * + * @see drupal_implode_tags() */ function drupal_explode_tags($tags) { // This regexp allows the following types of user input: @@ -6182,6 +6229,8 @@ function drupal_explode_tags($tags) { /** * Implode an array of tags into a string. + * + * @see drupal_explode_tags() */ function drupal_implode_tags($tags) { $encoded_tags = array(); @@ -6386,6 +6435,11 @@ function entity_get_info($entity_type = NULL) { 'revision' => '', 'bundle' => '', ); + foreach ($entity_info[$name]['view modes'] as $view_mode => $view_mode_info) { + $entity_info[$name]['view modes'][$view_mode] += array( + 'custom settings' => FALSE, + ); + } // If no bundle key is provided, assume a single bundle, named after // the entity type. if (empty($entity_info[$name]['entity keys']['bundle']) && empty($entity_info[$name]['bundles'])) { @@ -6601,6 +6655,10 @@ function entity_uri($entity_type, $entity) { if (!isset($entity->uri['options'])) { $entity->uri['options'] = array(); } + // Pass the entity data to url() so that alter functions do not need to + // lookup this entity again. + $entity->uri['options']['entity_type'] = $entity_type; + $entity->uri['options']['entity'] = $entity; } else { $entity->uri = FALSE; @@ -6610,17 +6668,51 @@ function entity_uri($entity_type, $entity) { } /** - * Invokes entity insert/update hooks. + * Helper function for attaching field API validation to entity forms. + */ +function entity_form_field_validate($entity_type, $form, &$form_state) { + // All field attach API functions act on an entity object, but during form + // validation, we don't have one. $form_state contains the entity as it was + // prior to processing the current form submission, and we must not update it + // until we have fully validated the submitted input. Therefore, for + // validation, act on a pseudo entity created out of the form values. + $pseudo_entity = (object) $form_state['values']; + field_attach_form_validate($entity_type, $pseudo_entity, $form, $form_state); +} + +/** + * Helper function for copying submitted values to entity properties for simple entity forms. * - * @param $op - * One of 'insert' or 'update'. - * @param $entity_type - * The entity type; e.g. 'node' or 'user'. - * @param $entity - * The entity object being operated on. + * During the submission handling of an entity form's "Save", "Preview", and + * possibly other buttons, the form state's entity needs to be updated with the + * submitted form values. Each entity form implements its own + * $form['#builder_function'] for doing this, appropriate for the particular + * entity and form. Many of these entity builder functions can call this helper + * function to re-use its logic of copying $form_state['values'][PROPERTY] + * values to $entity->PROPERTY for all entries in $form_state['values'] that are + * not field data, and calling field_attach_submit() to copy field data. + * + * For some entity forms (e.g., forms with complex non-field data and forms that + * simultaneously edit multiple entities), this behavior may be inappropriate, + * so the #builder_function for such forms needs to implement the required + * functionality instead of calling this function. */ -function entity_invoke($op, $entity_type, $entity) { - module_invoke_all('entity_' . $op, $entity, $entity_type); +function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_state) { + $info = entity_get_info($entity_type); + list(, , $bundle) = entity_extract_ids($entity_type, $entity); + + // Copy top-level form values that are not for fields to entity properties, + // without changing existing entity properties that are not being edited by + // this form. Copying field values must be done using field_attach_submit(). + $values_excluding_fields = $info['fieldable'] ? array_diff_key($form_state['values'], field_info_instances($entity_type, $bundle)) : $form_state['values']; + foreach ($values_excluding_fields as $key => $value) { + $entity->$key = $value; + } + + // Copy field values to the entity. + if ($info['fieldable']) { + field_attach_submit($entity_type, $entity, $form, $form_state); + } } /** diff --git a/includes/database/database.inc b/includes/database/database.inc index 8c2891f6..5dc7a936 100644 --- a/includes/database/database.inc +++ b/includes/database/database.inc @@ -1,9 +1,13 @@ <?php -// $Id: database.inc,v 1.122 2010/05/05 16:51:30 dries Exp $ +// $Id: database.inc,v 1.126 2010/06/28 19:57:34 dries Exp $ /** * @file - * Base classes for the database layer. + * Core systems for the database layer. + * + * Classes required for basic functioning of the database system should be + * placed in this file. All utility functions should also be placed in this + * file only, as they cannot auto-load the way classes can. */ /** @@ -36,7 +40,7 @@ * one would instead call the Drupal functions: * @code * $result = db_query_range('SELECT n.nid, n.title, n.created - * FROM {node} n WHERE n.uid = :uid', array(':uid' => $uid), 0, 10); + * FROM {node} n WHERE n.uid = :uid', 0, 10, array(':uid' => $uid)); * foreach($result as $record) { * // Perform operations on $node->title, etc. here. * } @@ -141,14 +145,14 @@ * return $id; * } * catch (Exception $e) { - * // Something went wrong somewhere, so flag the entire transaction to - * // roll back instead of getting committed. It doesn't actually roll back - * // yet, just gets flagged to do so. + * // Something went wrong somewhere, so roll back now. * $txn->rollback(); + * // Log the exception to watchdog. + * watchdog_exception('type', $e); * } * - * // $txn goes out of scope here. If there was a problem, it rolls back - * // automatically. If not, it commits automatically. + * // $txn goes out of scope here. Unless the transaction was rolled back, it + * // gets automatically commited here. * } * * function my_other_function($id) { @@ -204,13 +208,6 @@ abstract class DatabaseConnection extends PDO { */ protected $transactionLayers = array(); - /** - * Array of argument arrays for logging post-rollback. - * - * @var array - */ - protected $rollbackLogs = array(); - /** * Index of what driver-specific class to use for various operations. * @@ -262,7 +259,26 @@ abstract class DatabaseConnection extends PDO { */ protected $schema = NULL; + /** + * The default prefix used by this database connection. + * + * Separated from the other prefixes for performance reasons. + * + * @var string + */ + protected $defaultPrefix = ''; + + /** + * The non-default prefixes used by this database connection. + * + * @var array + */ + protected $prefixes = array(); + function __construct($dsn, $username, $password, $driver_options = array()) { + // Initialize and prepare the connection prefix. + $this->setPrefix(isset($this->connectionOptions['prefix']) ? $this->connectionOptions['prefix'] : ''); + // Because the other methods don't seem to work right. $driver_options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION; @@ -345,6 +361,25 @@ abstract class DatabaseConnection extends PDO { return $this->connectionOptions; } + /** + * Preprocess the prefixes used by this database connection. + * + * @param $prefix + * The prefixes, in any of the multiple forms documented in + * default.settings.php. + */ + protected function setPrefix($prefix) { + if (is_array($prefix)) { + $this->defaultPrefix = isset($prefix['default']) ? $prefix['default'] : ''; + unset($prefix['default']); + $this->prefixes = $prefix; + } + else { + $this->defaultPrefix = $prefix; + $this->prefixes = array(); + } + } + /** * Appends a database prefix to all tables in a query. * @@ -360,27 +395,12 @@ abstract class DatabaseConnection extends PDO { * The properly-prefixed string. */ public function prefixTables($sql) { - global $db_prefix; - - if (is_array($db_prefix)) { - if (array_key_exists('default', $db_prefix)) { - $tmp = $db_prefix; - unset($tmp['default']); - foreach ($tmp as $key => $val) { - $sql = strtr($sql, array('{' . $key . '}' => $val . $key)); - } - return strtr($sql, array('{' => $db_prefix['default'] , '}' => '')); - } - else { - foreach ($db_prefix as $key => $val) { - $sql = strtr($sql, array('{' . $key . '}' => $val . $key)); - } - return strtr($sql, array('{' => '' , '}' => '')); - } - } - else { - return strtr($sql, array('{' => $db_prefix , '}' => '')); + // Replace specific table prefixes first. + foreach ($this->prefixes as $key => $val) { + $sql = strtr($sql, array('{' . $key . '}' => $val . $key)); } + // Then replace remaining tables with the default prefix. + return strtr($sql, array('{' => $this->defaultPrefix , '}' => '')); } /** @@ -390,17 +410,12 @@ abstract class DatabaseConnection extends PDO { * is not used in prefixTables due to performance reasons. */ public function tablePrefix($table = 'default') { - global $db_prefix; - if (is_array($db_prefix)) { - if (isset($db_prefix[$table])) { - return $db_prefix[$table]; - } - elseif (isset($db_prefix['default'])) { - return $db_prefix['default']; - } - return ''; + if (isset($this->prefixes[$table])) { + return $this->prefixes[$table]; + } + else { + return $this->defaultPrefix; } - return $db_prefix; } /** @@ -859,26 +874,10 @@ abstract class DatabaseConnection extends PDO { * @param $savepoint_name * The name of the savepoint. The default, 'drupal_transaction', will roll * the entire transaction back. - * @param $type - * The category to which the rollback message belongs. - * @param $message - * The message to store in the log. Keep $message translatable - * by not concatenating dynamic values into it! Variables in the - * message should be added by using placeholder strings alongside - * the variables argument to declare the value of the placeholders. - * @param $variables - * Array of variables to replace in the message on display or - * NULL if message is already translated or not possible to - * translate. - * @param $severity - * The severity of the message, as per RFC 3164. - * @param $link - * A link to associate with the message. * * @see DatabaseTransaction::rollback() - * @see watchdog() */ - public function rollback($savepoint_name = 'drupal_transaction', $type = NULL, $message = NULL, $variables = array(), $severity = NULL, $link = NULL) { + public function rollback($savepoint_name = 'drupal_transaction') { if (!$this->inTransaction()) { throw new DatabaseTransactionNoActiveException(); } @@ -888,29 +887,6 @@ abstract class DatabaseConnection extends PDO { return; } - // Set the severity to the configured default if not specified. - if (!isset($severity)) { - $logging = Database::getLoggingCallback(); - if (is_array($logging)) { - $severity = $logging['default_severity']; - } - } - - // Record in an array to send to the log after transaction rollback. - // Messages written directly to a log (with a database back-end) will roll - // back during the following transaction rollback. This is an array because - // rollback could be requested multiple times during a transaction, and all - // such errors ought to be logged. - if (isset($message)) { - $this->rollbackLogs[] = array( - 'type' => $type, - 'message' => $message, - 'variables' => $variables, - 'severity' => $severity, - 'link' => $link, - ); - } - // We need to find the point we're rolling back to, all other savepoints // before are no longer needed. while ($savepoint = array_pop($this->transactionLayers)) { @@ -928,37 +904,6 @@ abstract class DatabaseConnection extends PDO { if ($this->supportsTransactions()) { parent::rollBack(); } - else { - // Log unsupported rollback. - $this->rollbackLogs[] = array( - 'type' => 'database', - 'message' => t('Explicit rollback failed: not supported on active connection.'), - 'variables' => array(), - ); - } - $this->logRollback(); - } - - /** - * Logs messages from rollback(). - */ - protected function logRollback() { - $logging = Database::getLoggingCallback(); - // If there is no callback defined. We can't do anything. - if (!is_array($logging)) { - return; - } - - $logging_callback = $logging['callback']; - - // Play back the logged errors to the specified logging callback post- - // rollback. - foreach ($this->rollbackLogs as $log_item) { - call_user_func($logging_callback, $log_item['type'], $log_item['message'], $log_item['variables'], $log_item['severity'], $log_item['link']); - } - - // Reset the error logs. - $this->rollbackLogs = array(); } /** @@ -1249,17 +1194,6 @@ abstract class Database { */ static protected $logs = array(); - /** - * A logging function callback array. - * - * The function must accept the same function signature as Drupal's - * watchdog(). The array contains key/value pairs for callback (string), - * default_severity (int), and error_severity (int). - * - * @var string - */ - static protected $logging_callback = NULL; - /** * Starts logging a given logging key on the specified connection. * @@ -1292,38 +1226,6 @@ abstract class Database { return self::$logs[$key]; } - /** - * Sets a logging callback for notices and errors. - * - * @param $logging_callback - * The function to use as the logging callback. - * @param $logging_default_severity - * The default severity level to use for logged messages. - * @param $logging_error_severity - * The severity level to use for logging error messages. - * - * @see watchdog() - */ - final public static function setLoggingCallback($callback, $default_severity, $error_severity) { - self::$logging_callback = array( - 'callback' => $callback, - 'default_severity' => $default_severity, - 'error_severity' => $error_severity, - ); - } - - /** - * Gets the logging callback for notices and errors. - * - * @return - * An array with the logging callback and severity levels. - * - * @see watchdog() - */ - final public static function getLoggingCallback() { - return self::$logging_callback; - } - /** * Retrieves the queries logged on for given logging key. * @@ -1430,6 +1332,20 @@ abstract class Database { if (empty($value['driver'])) { $database_info[$index][$target] = $database_info[$index][$target][mt_rand(0, count($database_info[$index][$target]) - 1)]; } + + // Parse the prefix information. + if (!isset($database_info[$index][$target]['prefix'])) { + // Default to an empty prefix. + $database_info[$index][$target]['prefix'] = array( + 'default' => '', + ); + } + else if (!is_array($database_info[$index][$target]['prefix'])) { + // Transform the flat form into an array form. + $database_info[$index][$target]['prefix'] = array( + 'default' => $database_info[$index][$target]['prefix'], + ); + } } } @@ -1489,7 +1405,58 @@ abstract class Database { if (!empty(self::$databaseInfo[$key])) { return self::$databaseInfo[$key]; } + } + /** + * Rename a connection and its corresponding connection information. + * + * @param $old_key + * The old connection key. + * @param $new_key + * The new connection key. + * @return + * TRUE in case of success, FALSE otherwise. + */ + final public static function renameConnection($old_key, $new_key) { + if (empty(self::$databaseInfo)) { + self::parseConnectionInfo(); + } + + if (!empty(self::$databaseInfo[$old_key]) && empty(self::$databaseInfo[$new_key])) { + // Migrate the database connection information. + self::$databaseInfo[$new_key] = self::$databaseInfo[$old_key]; + unset(self::$databaseInfo[$old_key]); + + // Migrate over the DatabaseConnection object if it exists. + if (isset(self::$connections[$old_key])) { + self::$connections[$new_key] = self::$connections[$old_key]; + unset(self::$connections[$old_key]); + } + + return TRUE; + } + else { + return FALSE; + } + } + + /** + * Remove a connection and its corresponding connection information. + * + * @param $key + * The connection key. + * @return + * TRUE in case of success, FALSE otherwise. + */ + final public static function removeConnection($key) { + if (isset(self::$databaseInfo[$key])) { + unset(self::$databaseInfo[$key]); + unset(self::$connections[$key]); + return TRUE; + } + else { + return FALSE; + } } /** @@ -1502,8 +1469,6 @@ abstract class Database { * The database target to open. */ final protected static function openConnection($key, $target) { - global $db_prefix; - if (empty(self::$databaseInfo)) { self::parseConnectionInfo(); } @@ -1531,13 +1496,6 @@ abstract class Database { $new_connection->setLogger(self::$logs[$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; } @@ -1716,35 +1674,17 @@ class DatabaseTransaction { * Rolls back the current transaction. * * This is just a wrapper method to rollback whatever transaction stack we are - * currently in, which is managed by the connection object itself. - * - * @param $type - * The category to which the rollback message belongs. - * @param $message - * The message to store in the log. Keep $message translatable by not - * concatenating dynamic values into it! Variables in the message should be - * added by using placeholder strings alongside the variable's argument to - * declare the value of the placeholders. - * @param $variables - * Array of variables to replace in the message on display or NULL if the - * message is already translated or not possible to translate. - * @param $severity - * The severity of the message, as per RFC 3164. - * @param $link - * A link to associate with the message. + * currently in, which is managed by the connection object itself. Note that + * logging (preferable with watchdog_exception()) needs to happen after a + * transaction has been rolled back or the log messages will be rolled back + * too. * * @see DatabaseConnection::rollback() - * @see watchdog() + * @see watchdog_exception() */ - public function rollback($type = NULL, $message = NULL, $variables = array(), $severity = NULL, $link = NULL) { + public function rollback() { $this->rolledBack = TRUE; - if (!isset($severity)) { - $logging = Database::getLoggingCallback(); - if (is_array($logging)) { - $severity = $logging['default_severity']; - } - } - $this->connection->rollback($this->name, $type, $message, $variables, $severity, $link); + $this->connection->rollback($this->name); } } diff --git a/includes/database/mysql/database.inc b/includes/database/mysql/database.inc index 856e3120..b74035ab 100644 --- a/includes/database/mysql/database.inc +++ b/includes/database/mysql/database.inc @@ -1,5 +1,5 @@ <?php -// $Id: database.inc,v 1.27 2010/05/05 06:28:39 webchick Exp $ +// $Id: database.inc,v 1.31 2010/06/30 16:55:49 webchick Exp $ /** * @file @@ -13,6 +13,13 @@ class DatabaseConnection_mysql extends DatabaseConnection { + /** + * Flag to indicate if we have registered the nextID cleanup function. + * + * @var boolean + */ + protected $shutdownRegistered = FALSE; + public function __construct(array $connection_options = array()) { // This driver defaults to transaction support, except if explicitly passed FALSE. $this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE); @@ -52,7 +59,7 @@ class DatabaseConnection_mysql extends DatabaseConnection { } public function queryRange($query, $from, $count, array $args = array(), array $options = array()) { - return $this->query($query . ' LIMIT ' . $from . ', ' . $count, $args, $options); + return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options); } public function queryTemporary($query, array $args = array(), array $options = array()) { @@ -75,7 +82,6 @@ class DatabaseConnection_mysql extends DatabaseConnection { } public function nextId($existing_id = 0) { - static $shutdown_registered = FALSE; $new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID)); // This should only happen after an import or similar event. if ($existing_id >= $new_id) { @@ -89,14 +95,16 @@ class DatabaseConnection_mysql extends DatabaseConnection { $this->query('INSERT INTO {sequences} (value) VALUES (:value) ON DUPLICATE KEY UPDATE value = value', array(':value' => $existing_id)); $new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID)); } - if (!$shutdown_registered) { - drupal_register_shutdown_function(array(get_class($this), 'nextIdDelete')); - $shutdown_registered = TRUE; + if (!$this->shutdownRegistered) { + // Use register_shutdown_function() here to keep the database system + // independent of Drupal. + register_shutdown_function(array($this, 'nextIdDelete')); + $shutdownRegistered = TRUE; } return $new_id; } - public static function nextIdDelete() { + public function nextIdDelete() { // While we want to clean up the table to keep it up from occupying too // much storage and memory, we must keep the highest value in the table // because InnoDB uses an in-memory auto-increment counter as long as the @@ -105,9 +113,19 @@ class DatabaseConnection_mysql extends DatabaseConnection { // table based solely on values from the table so deleting all values would // be a problem in this case. Also, TRUNCATE resets the auto increment // counter. - $max_id = db_query('SELECT MAX(value) FROM {sequences}')->fetchField(); - // We know we are using MySQL here, so need for the slower db_delete(). - db_query('DELETE FROM {sequences} WHERE value < :value', array(':value' => $max_id)); + try { + $max_id = $this->query('SELECT MAX(value) FROM {sequences}')->fetchField(); + // We know we are using MySQL here, no need for the slower db_delete(). + $this->query('DELETE FROM {sequences} WHERE value < :value', array(':value' => $max_id)); + } + // During testing, this function is called from shutdown with the + // simpletest prefix stored in $this->connection, and those tables are gone + // by the time shutdown is called so we need to ignore the database + // errors. There is no problem with completely ignoring errors here: if + // these queries fail, the sequence will work just fine, just use a bit + // more database storage and memory. + catch (PDOException $e) { + } } } diff --git a/includes/database/mysql/query.inc b/includes/database/mysql/query.inc index 70770f4a..b1f248a5 100644 --- a/includes/database/mysql/query.inc +++ b/includes/database/mysql/query.inc @@ -1,5 +1,5 @@ <?php -// $Id: query.inc,v 1.17 2010/05/15 07:04:21 dries Exp $ +// $Id: query.inc,v 1.18 2010/06/26 01:40:05 dries Exp $ /** * @ingroup database @@ -87,121 +87,19 @@ class InsertQuery_mysql extends InsertQuery { } } -class MergeQuery_mysql extends MergeQuery { - - public function execute() { - - // A merge query without any key field is invalid. - if (count($this->keyFields) == 0) { - throw new InvalidMergeQueryException("You need to specify key fields before executing a merge query"); - } - - // Set defaults. - if ($this->updateFields) { - $update_fields = $this->updateFields; - } - else { - // When update fields are derived from insert fields, we don't need - // placeholders since we can tell MySQL to reuse insert supplied - // values using the VALUES(col_name) function. - $update_fields = array(); - } - - $insert_fields = $this->insertFields + $this->keyFields; - - $max_placeholder = 0; - $values = array(); - // We assume that the order here is the same as in __toString(). If that's - // not the case, then we have serious problems. - foreach ($insert_fields as $value) { - $values[':db_insert_placeholder_' . $max_placeholder++] = $value; - } - - // Expressions take priority over literal fields, so we process those first - // and remove any literal fields that conflict. - foreach ($this->expressionFields as $field => $data) { - if (!empty($data['arguments'])) { - $values += $data['arguments']; - } - unset($update_fields[$field]); - } - - // Because we filter $fields the same way here and in __toString(), the - // placeholders will all match up properly. - $max_placeholder = 0; - foreach ($update_fields as $field => $value) { - $values[':db_update_placeholder_' . ($max_placeholder++)] = $value; - } - - - // MySQL's INSERT ... ON DUPLICATE KEY UPDATE queries return 1 - // (MergeQuery::STATUS_INSERT) for an INSERT operation or 2 - // (MergeQuery::STATUS_UPDATE) for an UPDATE operation. - // - // @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); - } - - +class TruncateQuery_mysql extends TruncateQuery { public function __toString() { - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; - - // Set defaults. - if ($this->updateFields) { - $update_fields = $this->updateFields; + // TRUNCATE is actually a DDL statement on MySQL, and DDL statements are + // not transactional, and result in an implicit COMMIT. When we are in a + // transaction, fallback to the slower, but transactional, DELETE. + if ($this->connection->inTransaction()) { + // Create a comments string to prepend to the query. + $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '}'; } else { - $update_fields = $this->insertFields; - // If there are no exclude fields, this is a no-op. - foreach ($this->excludeFields as $exclude_field) { - unset($update_fields[$exclude_field]); - } - } - - // If the merge query has no fields to update, add the first key as an - // update field so the query will not fail if a duplicate key is found. - if (!$update_fields && !$this->expressionFields) { - $update_fields = array_slice($this->keyFields, 0, 1, TRUE); - } - - $insert_fields = $this->insertFields + $this->keyFields; - - $query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', array_keys($insert_fields)) . ') VALUES '; - - $max_placeholder = 0; - $values = array(); - // We don't need the $field, but this is a convenient way to count. - foreach ($insert_fields as $field) { - $values[] = ':db_insert_placeholder_' . $max_placeholder++; + return parent::__toString(); } - - $query .= '(' . implode(', ', $values) . ') ON DUPLICATE KEY UPDATE '; - - // Expressions take priority over literal fields, so we process those first - // and remove any literal fields that conflict. - $max_placeholder = 0; - $update = array(); - foreach ($this->expressionFields as $field => $data) { - $update[] = $field . '=' . $data['expression']; - unset($update_fields[$field]); - } - - // Build update fields clauses based on caller supplied list, or derived - // from insert supplied values using the VALUES(col_name) function. - foreach ($update_fields as $field => $value) { - if ($this->updateFields) { - $update[] = ($field . '=:db_update_placeholder_' . $max_placeholder++); - } - else { - $update[] = ($field . '=VALUES(' . $field . ')'); - } - } - - $query .= implode(', ', $update); - - return $query; } } diff --git a/includes/database/mysql/schema.inc b/includes/database/mysql/schema.inc index 56fca57d..6c5595ac 100644 --- a/includes/database/mysql/schema.inc +++ b/includes/database/mysql/schema.inc @@ -1,5 +1,5 @@ <?php -// $Id: schema.inc,v 1.36 2010/04/07 15:07:59 dries Exp $ +// $Id: schema.inc,v 1.38 2010/06/28 19:57:34 dries Exp $ /** * @file @@ -25,7 +25,7 @@ class DatabaseSchema_mysql extends DatabaseSchema { const COMMENT_MAX_COLUMN = 255; /** - * Get information about the table and database name from the db_prefix. + * Get information about the table and database name from the prefix. * * @return * A keyed array with information about the database, table name and prefix. @@ -316,8 +316,8 @@ class DatabaseSchema_mysql extends DatabaseSchema { } $query = 'ALTER TABLE {' . $table . '} ADD '; $query .= $this->createFieldSql($field, $this->processField($spec)); - if (count($keys_new)) { - $query .= ', ADD ' . implode(', ADD ', $this->createKeysSql($keys_new)); + if ($keys_sql = $this->createKeysSql($keys_new)) { + $query .= ', ADD ' . implode(', ADD ', $keys_sql); } $this->connection->query($query); if (isset($spec['initial'])) { @@ -439,8 +439,8 @@ class DatabaseSchema_mysql extends DatabaseSchema { } $sql = 'ALTER TABLE {' . $table . '} CHANGE `' . $field . '` ' . $this->createFieldSql($field_new, $this->processField($spec)); - if (count($keys_new)) { - $sql .= ', ADD ' . implode(', ADD ', $this->createKeysSql($keys_new)); + if ($keys_sql = $this->createKeysSql($keys_new)) { + $sql .= ', ADD ' . implode(', ADD ', $keys_sql); } $this->connection->query($sql); } diff --git a/includes/database/pgsql/database.inc b/includes/database/pgsql/database.inc index a4a2cc7b..eab334e3 100644 --- a/includes/database/pgsql/database.inc +++ b/includes/database/pgsql/database.inc @@ -1,5 +1,5 @@ <?php -// $Id: database.inc,v 1.37 2010/04/29 03:48:16 webchick Exp $ +// $Id: database.inc,v 1.39 2010/06/16 04:56:07 dries Exp $ /** * @file @@ -40,6 +40,13 @@ class DatabaseConnection_pgsql extends DatabaseConnection { $dsn = 'pgsql:host=' . $connection_options['host'] . ' dbname=' . $connection_options['database'] . ' port=' . $connection_options['port']; parent::__construct($dsn, $connection_options['username'], $connection_options['password'], array( + // Prepared statements are most effective for performance when queries + // are recycled (used several times). However, if they are not re-used, + // prepared statements become ineffecient. Since most of Drupal's + // prepared queries are not re-used, it should be faster to emulate + // the preparation than to actually ready statements for re-use. If in + // doubt, reset to FALSE and measure performance. + PDO::ATTR_EMULATE_PREPARES => TRUE, // Convert numeric values to strings when fetching. PDO::ATTR_STRINGIFY_FETCHES => TRUE, // Force column names to lower case. @@ -106,7 +113,7 @@ class DatabaseConnection_pgsql extends DatabaseConnection { } public function queryRange($query, $from, $count, array $args = array(), array $options = array()) { - return $this->query($query . ' LIMIT ' . $count . ' OFFSET ' . $from, $args, $options); + return $this->query($query . ' LIMIT ' . (int) $count . ' OFFSET ' . (int) $from, $args, $options); } public function queryTemporary($query, array $args = array(), array $options = array()) { diff --git a/includes/database/pgsql/install.inc b/includes/database/pgsql/install.inc index f3b47b7e..5d07d9da 100644 --- a/includes/database/pgsql/install.inc +++ b/includes/database/pgsql/install.inc @@ -1,5 +1,5 @@ <?php -// $Id: install.inc,v 1.7 2010/05/13 08:23:56 dries Exp $ +// $Id: install.inc,v 1.9 2010/06/21 01:32:21 webchick Exp $ /** * @file @@ -17,6 +17,10 @@ class DatabaseTasks_pgsql extends DatabaseTasks { 'function' => 'checkEncoding', 'arguments' => array(), ); + $this->tasks[] = array( + 'function' => 'checkPHPVersion', + 'arguments' => array(), + ); $this->tasks[] = array( 'function' => 'initializeDatabase', 'arguments' => array(), @@ -50,6 +54,34 @@ class DatabaseTasks_pgsql extends DatabaseTasks { } } + /** + * Check PHP version. + * + * There is a bug in PHP versions < 5.2.11 and < 5.3.1 that prevents + * PostgreSQL from inserting integer values into numeric columns that exceed + * the PHP_INT_MAX value (value varies dependant on 32 or 64 bit CPU). + */ + function checkPHPVersion() { + try { + $txn = db_transaction(); + db_query("CREATE TABLE test_php_version ( test_int INT NOT NULL )"); + // See http://bugs.php.net/bug.php?id=48924 as to why this query may + // fail. The error will throw an Exception so there is no need to test to + // see if the row inserted or not. + db_query("INSERT INTO test_php_version ( test_int ) VALUES (:big_int)", array(':big_int' => PHP_INT_MAX + 1)); + db_query("DROP TABLE test_php_version"); + $this->pass(st('PHP is at the correct version to run on PostgreSQL.')); + } + catch (Exception $e) { + // Failing is not fatal but the user should still be warned of the + // limitations of running PostgreSQL on the current version of PHP. + $text = 'The version of PHP you are using has known issues with PostgreSQL. You can see more at '; + $text .= l('http://drupal.org/node/515310', 'http://drupal.org/node/515310') . '. '; + $text .= 'We suggest you upgrade PHP to 5.2.11, 5.3.1 or greater. Failing to do so may result in serious data corruption later.'; + drupal_set_message(st($text), 'warning'); + } + } + /** * Make PostgreSQL Drupal friendly. */ @@ -77,18 +109,33 @@ class DatabaseTasks_pgsql extends DatabaseTasks { ); } - // Don't use {} around pg_proc table. - if (!db_query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'concat'")->fetchField()) { - db_query('CREATE OR REPLACE FUNCTION "concat"(text, text) RETURNS text AS - \'SELECT $1 || $2;\' - 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);\' LANGUAGE \'sql\'' ); + + // Using || to concatenate in Drupal is not recommeneded because there are + // database drivers for Drupal that do not support the syntax, however + // they do support CONCAT(item1, item2) which we can replicate in + // PostgreSQL. PostgreSQL requires the function to be defined for each + // different argument variation the function can handle. + db_query('CREATE OR REPLACE FUNCTION "concat"(anynonarray, anynonarray) RETURNS text AS + \'SELECT CAST($1 AS text) || CAST($2 AS text);\' + LANGUAGE \'sql\' + '); + db_query('CREATE OR REPLACE FUNCTION "concat"(text, anynonarray) RETURNS text AS + \'SELECT $1 || CAST($2 AS text);\' + LANGUAGE \'sql\' + '); + db_query('CREATE OR REPLACE FUNCTION "concat"(anynonarray, text) RETURNS text AS + \'SELECT CAST($1 AS text) || $2;\' + LANGUAGE \'sql\' + '); + db_query('CREATE OR REPLACE FUNCTION "concat"(text, text) RETURNS text AS + \'SELECT $1 || $2;\' + LANGUAGE \'sql\' + '); + $this->pass(st('PostgreSQL has initialized itself.')); } catch (Exception $e) { diff --git a/includes/database/query.inc b/includes/database/query.inc index f8e5fdd0..87a836e2 100644 --- a/includes/database/query.inc +++ b/includes/database/query.inc @@ -1,5 +1,5 @@ <?php -// $Id: query.inc,v 1.49 2010/05/15 07:04:21 dries Exp $ +// $Id: query.inc,v 1.52 2010/06/26 01:40:05 dries Exp $ /** * @ingroup database @@ -322,14 +322,6 @@ class InsertQuery extends Query { */ protected $table; - /** - * Whether or not this query is "delay-safe". Different database drivers - * may or may not implement this feature in their own ways. - * - * @var boolean - */ - protected $delay; - /** * An array of fields on which to insert. * @@ -368,7 +360,6 @@ class InsertQuery extends Query { if (!isset($options['return'])) { $options['return'] = Database::RETURN_INSERT_ID; } - $options += array('delay' => FALSE); parent::__construct($connection, $options); $this->table = $table; } @@ -471,11 +462,8 @@ class InsertQuery extends Query { * @return * The last insert ID of the query, if one exists. If the query * was given multiple sets of values to insert, the return value is - * undefined. If the query is flagged "delayed", then the insert ID - * won't be created until later when the query actually runs so the - * return value is also undefined. If no fields are specified, this - * method will do nothing and return NULL. That makes it safe to use - * in multi-insert loops. + * undefined. If no fields are specified, this method will do nothing and + * return NULL. That makes it safe to use in multi-insert loops. */ public function execute() { // If validation fails, simply return NULL. @@ -813,29 +801,45 @@ class MergeQuery extends Query { // In the degenerate case of this query type, we have to run multiple // queries as there is no universal single-query mechanism that will work. - // Our degenerate case is not designed for performance efficiency but - // for comprehensibility. Any practical database driver will override - // this method with database-specific logic, so this function serves only - // as a fallback to aid developers of new drivers. // Wrap multiple queries in a transaction, if the database supports it. $transaction = $this->connection->startTransaction(); - // Manually check if the record already exists. - $select = $this->connection->select($this->table); - foreach ($this->keyFields as $field => $value) { - $select->condition($field, $value); - } - - $select = $select->countQuery(); - $sql = (string) $select; - $arguments = $select->getArguments(); - $num_existing = $this->connection->query($sql, $arguments)->fetchField(); - + try { + // Manually check if the record already exists. + // We build a 'SELECT 1 FROM table WHERE conditions FOR UPDATE' query, + // that will lock the rows that matches our set of conditions as well as + // return the information that there are such rows. + $select = $this->connection->select($this->table); + $select->addExpression('1'); + foreach ($this->keyFields as $field => $value) { + $select->condition($field, $value); + } - if ($num_existing) { - // If there is already an existing record, run an update query. + // Using SELECT FOR UPDATE syntax will lock the rows we want to attempt to update. + $sql = ((string) $select) . ' FOR UPDATE'; + $arguments = $select->getArguments(); + + // If there are no existing records, run an insert query. + if (!$this->connection->query($sql, $arguments)->fetchField()) { + // If there is no existing record, run an insert query. + $insert_fields = $this->insertFields + $this->keyFields; + try { + $this->connection->insert($this->table, $this->queryOptions)->fields($insert_fields)->execute(); + return MergeQuery::STATUS_INSERT; + } + catch (Exception $e) { + // The insert query failed, maybe it's because a racing insert query + // beat us in inserting the same row. Retry the select query, if it + // returns a row, ignore the error and continue with the update + // query below. + if (!$this->connection->query($sql, $arguments)->fetchField()) { + throw $e; + } + } + } + // Proceed with an update query if a row was found. if ($this->updateFields) { $update_fields = $this->updateFields; } @@ -847,7 +851,7 @@ class MergeQuery extends Query { } } if ($update_fields || $this->expressionFields) { - // Only run the update if there are no fields or expressions to update. + // Only run the update if there are fields or expressions to update. $update = $this->connection->update($this->table, $this->queryOptions)->fields($update_fields); foreach ($this->keyFields as $field => $value) { $update->condition($field, $value); @@ -859,13 +863,12 @@ class MergeQuery extends Query { return MergeQuery::STATUS_UPDATE; } } - else { - // If there is no existing record, run an insert query. - $insert_fields = $this->insertFields + $this->keyFields; - $this->connection->insert($this->table, $this->queryOptions)->fields($insert_fields)->execute(); - return MergeQuery::STATUS_INSERT; + catch (Exception $e) { + // Something really wrong happened here, bubble up the exception to the + // caller. + $transaction->rollback(); + throw $e; } - // Transaction commits here where $transaction looses scope. } @@ -1212,7 +1215,15 @@ class DatabaseCondition implements QueryConditionInterface, Countable { public function condition($field, $value = NULL, $operator = NULL) { if (!isset($operator)) { - $operator = is_array($value) ? 'IN' : '='; + if (is_array($value)) { + $operator = 'IN'; + } + elseif (!isset($value)) { + $operator = 'IS NULL'; + } + else { + $operator = '='; + } } $this->conditions[] = array( 'field' => $field, @@ -1237,7 +1248,7 @@ class DatabaseCondition implements QueryConditionInterface, Countable { } public function isNull($field) { - return $this->condition($field, NULL, 'IS NULL'); + return $this->condition($field); } public function isNotNull($field) { diff --git a/includes/database/schema.inc b/includes/database/schema.inc index 8f3b8067..7b8086d0 100644 --- a/includes/database/schema.inc +++ b/includes/database/schema.inc @@ -1,5 +1,5 @@ <?php -// $Id: schema.inc,v 1.35 2010/04/28 20:25:21 dries Exp $ +// $Id: schema.inc,v 1.36 2010/06/28 19:57:34 dries Exp $ /** * @file @@ -170,11 +170,11 @@ abstract class DatabaseSchema implements QueryPlaceholderInterface { } /** - * Get information about the table name and schema from the db_prefix. + * Get information about the table name and schema from the prefix. * * @param * Name of table to look prefix up for. Defaults to 'default' because thats - * default key for db_prefix. + * default key for prefix. * @return * A keyed array with information about the schema, table name and prefix. */ diff --git a/includes/database/select.inc b/includes/database/select.inc index 6e295417..950c2f13 100644 --- a/includes/database/select.inc +++ b/includes/database/select.inc @@ -1,5 +1,5 @@ <?php -// $Id: select.inc,v 1.41 2010/05/15 07:04:21 dries Exp $ +// $Id: select.inc,v 1.42 2010/06/01 09:24:09 dries Exp $ /** * @ingroup database @@ -1407,7 +1407,7 @@ class SelectQuery extends Query implements SelectQueryInterface { // Databases that need a different syntax can override this method and // do whatever alternate logic they need to. if (!empty($this->range)) { - $query .= "\nLIMIT " . $this->range['length'] . " OFFSET " . $this->range['start']; + $query .= "\nLIMIT " . (int) $this->range['length'] . " OFFSET " . (int) $this->range['start']; } // UNION is a little odd, as the select queries to combine are passed into diff --git a/includes/database/sqlite/database.inc b/includes/database/sqlite/database.inc index 60625909..107d0506 100644 --- a/includes/database/sqlite/database.inc +++ b/includes/database/sqlite/database.inc @@ -1,5 +1,5 @@ <?php -// $Id: database.inc,v 1.30 2010/04/06 16:54:15 dries Exp $ +// $Id: database.inc,v 1.33 2010/07/04 02:26:10 webchick Exp $ /** * @file @@ -159,7 +159,7 @@ class DatabaseConnection_sqlite extends DatabaseConnection { } public function queryRange($query, $from, $count, array $args = array(), array $options = array()) { - return $this->query($query . ' LIMIT ' . $from . ', ' . $count, $args, $options); + return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options); } public function queryTemporary($query, array $args = array(), array $options = array()) { @@ -210,9 +210,9 @@ class DatabaseConnection_sqlite extends DatabaseConnection { return (int) $this->query('SELECT value FROM {sequences}')->fetchField(); } - public function rollback($savepoint_name = 'drupal_transaction', $type = NULL, $message = NULL, $variables = array(), $severity = NULL, $link = NULL) { + public function rollback($savepoint_name = 'drupal_transaction') { if ($this->savepointSupport) { - return parent::rollBack($savepoint_name, $type, $message, $variables, $severity, $link); + return parent::rollBack($savepoint_name); } if (!$this->inTransaction()) { @@ -224,29 +224,6 @@ class DatabaseConnection_sqlite extends DatabaseConnection { return; } - // Set the severity to the configured default if not specified. - if (!isset($severity)) { - $logging = Database::getLoggingCallback(); - if (is_array($logging)) { - $severity = $logging['default_severity']; - } - } - - // Record in an array to send to the log after transaction rollback. - // Messages written directly to a log (with a database back-end) will roll - // back during the following transaction rollback. This is an array because - // rollback could be requested multiple times during a transaction, and all - // such errors ought to be logged. - if (isset($message)) { - $this->rollbackLogs[] = array( - 'type' => $type, - 'message' => $message, - 'variables' => $variables, - 'severity' => $severity, - 'link' => $link, - ); - } - // We need to find the point we're rolling back to, all other savepoints // before are no longer needed. while ($savepoint = array_pop($this->transactionLayers)) { @@ -265,15 +242,6 @@ class DatabaseConnection_sqlite extends DatabaseConnection { if ($this->supportsTransactions()) { PDO::rollBack(); } - else { - // Log unsupported rollback. - $this->rollbackLogs[] = array( - 'type' => 'database', - 'message' => t('Explicit rollback failed: not supported on active connection.'), - 'variables' => array(), - ); - } - $this->logRollback(); } public function pushTransaction($name) { @@ -313,7 +281,6 @@ class DatabaseConnection_sqlite extends DatabaseConnection { if ($this->willRollback) { $this->willRollback = FALSE; PDO::rollBack(); - $this->logRollback(); } elseif (!PDO::commit()) { throw new DatabaseTransactionCommitFailedException(); diff --git a/includes/database/sqlite/query.inc b/includes/database/sqlite/query.inc index 8be26c14..2de07e0f 100644 --- a/includes/database/sqlite/query.inc +++ b/includes/database/sqlite/query.inc @@ -1,5 +1,5 @@ <?php -// $Id: query.inc,v 1.10 2010/05/15 07:04:21 dries Exp $ +// $Id: query.inc,v 1.11 2010/07/03 20:45:45 webchick Exp $ /** * @file @@ -63,7 +63,6 @@ class InsertQuery_sqlite extends InsertQuery { * UPDATE test SET name = 'newname' WHERE tid = 1 AND name <> 'newname' */ class UpdateQuery_sqlite extends UpdateQuery { - /** * Helper function that removes the fields that are already in a condition. * @@ -84,6 +83,10 @@ class UpdateQuery_sqlite extends UpdateQuery { } public function execute() { + if (!empty($this->queryOptions['sqlite_return_matched_rows'])) { + return parent::execute(); + } + // Get the fields used in the update query, and remove those that are already // in the condition. $fields = $this->expressionFields + $this->fields; @@ -115,6 +118,65 @@ class UpdateQuery_sqlite extends UpdateQuery { } +/** + * SQLite specific implementation of MergeQuery. + * + * SQLite doesn't support row-level locking, but acquire locks on the whole + * database file. We implement MergeQuery using a different strategy: + * - UPDATE xxx WHERE <key condition> + * - if the previous query hasn't matched, INSERT + * + * The first UPDATE query will acquire a RESERVED lock on the database. + */ +class MergeQuery_sqlite extends MergeQuery { + public function execute() { + // If validation fails, simply return NULL. + // Note that validation routines in preExecute() may throw exceptions instead. + if (!$this->preExecute()) { + return NULL; + } + + // Wrap multiple queries in a transaction. + $transaction = $this->connection->startTransaction(); + + if ($this->updateFields) { + $update_fields = $this->updateFields; + } + else { + $update_fields = $this->insertFields; + // If there are no exclude fields, this is a no-op. + foreach ($this->excludeFields as $exclude_field) { + unset($update_fields[$exclude_field]); + } + } + + // The update fields are empty, fill them with dummy data. + if (!$update_fields && !$this->expressionFields) { + $update_fields = array_slice($this->keyFields, 0, 1); + } + + // Start with an update query, this acquires a RESERVED lock on the database. + // Use the SQLite-specific 'sqlite_return_matched_rows' query option to + // return the number of rows matched by that query, not modified by it. + $update = $this->connection->update($this->table, array('sqlite_return_matched_rows' => TRUE) + $this->queryOptions)->fields($update_fields); + + foreach ($this->keyFields as $field => $value) { + $update->condition($field, $value); + } + foreach ($this->expressionFields as $field => $expression) { + $update->expression($field, $expression['expression'], $expression['arguments']); + } + if ($update->execute()) { + return MergeQuery::STATUS_UPDATE; + } + + // The UPDATE query failed to match rows, proceed with an INSERT. + $insert_fields = $this->insertFields + $this->keyFields; + $this->connection->insert($this->table, $this->queryOptions)->fields($insert_fields)->execute(); + return MergeQuery::STATUS_INSERT; + } +} + /** * SQLite specific implementation of DeleteQuery. * diff --git a/includes/entity.inc b/includes/entity.inc index 159c04e0..8dd9c69b 100644 --- a/includes/entity.inc +++ b/includes/entity.inc @@ -1,5 +1,5 @@ <?php -// $Id: entity.inc,v 1.8 2010/05/09 13:27:31 dries Exp $ +// $Id: entity.inc,v 1.11 2010/07/07 13:13:44 dries Exp $ /** * Interface for entity controller classes. @@ -290,3 +290,681 @@ class DrupalDefaultEntityController implements DrupalEntityControllerInterface { $this->entityCache += $entities; } } + +/** + * Exception thrown by EntityFieldQuery() on unsupported query syntax. + * + * Some storage modules might not support the full range of the syntax for + * conditions, and will raise an EntityFieldQueryException when an unsupported + * condition was specified. + */ +class EntityFieldQueryException extends Exception {} + +/** + * Retrieves entities matching a given set of conditions. + * + * This class allows finding entities based on entity properties (for example, + * node->changed), field values, and generic entity meta data (bundle, + * entity type, entity id, and revision ID). It is not possible to query across + * multiple entity types. For example, there is no facility to find published + * nodes written by users created in the last hour, as this would require + * querying both node->status and user->created. + * + * Normally we would not want to have public properties on the object, as that + * allows the object's state to become inconsistent too easily. However, this + * class's standard use case involves primarily code that does need to have + * direct access to the collected properties in order to handle alternate + * execution routines. We therefore use public properties for simplicity. Note + * that code that is simply creating and running a field query should still use + * the appropriate methods add conditions on the query. + * + * Storage engines are not required to support every type of query. By default, + * an EntityFieldQueryException will be raised if an unsupported condition is + * specified or if the query has field conditions or sorts that are stored in + * different field storage engines. However, this logic can be overridden in + * hook_entity_query(). + */ +class EntityFieldQuery { + /** + * Indicates that both deleted and non-deleted fields should be returned. + * + * @see EntityFieldQuery::deleted() + */ + const RETURN_ALL = NULL; + + /** + * Associative array of entity-generic metadata conditions. + * + * @var array + * + * @see EntityFieldQuery::entityCondition() + */ + public $entityConditions = array(); + + /** + * List of field conditions. + * + * @var array + * + * @see EntityFieldQuery::fieldCondition() + */ + public $fieldConditions = array(); + + /** + * List of property conditions. + * + * @var array + * + * @see EntityFieldQuery::propertyCondition() + */ + public $propertyConditions = array(); + + /** + * List of order clauses for entity-generic metadata. + * + * @var array + * + * @see EntityFieldQuery::entityOrderBy() + */ + public $entityOrder = array(); + + /** + * List of order clauses for fields. + * + * @var array + * + * @see EntityFieldQuery::fieldOrderBy() + */ + public $fieldOrder = array(); + + /** + * List of order clauses for entities. + * + * @var array + * + * @see EntityFieldQuery::entityOrderBy() + */ + public $propertyOrder = array(); + + /** + * The query range. + * + * @var array + * + * @see EntityFieldQuery::range() + */ + public $range = array(); + + /** + * Query behavior for deleted data. + * + * TRUE to return only deleted data, FALSE to return only non-deleted data, + * EntityFieldQuery::RETURN_ALL to return everything. + * + * @see EntityFieldQuery::deleted() + */ + public $deleted = FALSE; + + /** + * A list of field arrays used. + * + * Field names passed to EntityFieldQuery::fieldCondition() and + * EntityFieldQuery::fieldOrderBy() are run through field_info_field() before + * stored in this array. This way, the elements of this array are field + * arrays. + * + * @var array + */ + public $fields = array(); + + /** + * TRUE if this is a count query, FALSE if it isn't. + * + * @var boolean + */ + public $count = FALSE; + + /** + * Flag indicating whether this is querying current or all revisions. + * + * @var int + * + * @see EntityFieldQuery::age() + */ + public $age = FIELD_LOAD_CURRENT; + + /** + * The ordered results. + * + * @var array + * + * @see EntityFieldQuery::execute(). + */ + public $orderedResults = array(); + + /** + * The method executing the query, if it is overriding the default. + * + * @var string + * + * @see EntityFieldQuery::execute(). + */ + public $executeCallback = ''; + + /** + * Adds a condition on entity-generic metadata. + * + * If the overall query contains only entity conditions or ordering, or if + * there are property conditions, then specifying the entity type is + * mandatory. If there are field conditions or ordering but no property + * conditions or ordering, then specifying an entity type is optional. While + * the field storage engine might support field conditions on more than one + * entity type, there is no way to query across multiple entity base tables by + * default. To specify the entity type, pass in 'entity_type' for $name, + * the type as a string for $value, and no $operator (it's disregarded). + * + * 'bundle', 'revision_id' and 'entity_id' have no such restrictions. + * + * @param $name + * 'entity_type', 'bundle', 'revision_id' or 'entity_id'. + * @param $value + * The value for $name. 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 $operator. + * @param $operator + * Possible values: + * - '=', '!=', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These + * operators expect $value to be a literal of the same type as the + * column. + * - 'IN', 'NOT IN': These operators expect $value to be an array of + * literals of the same type as the column. + * - 'BETWEEN': This operator expects $value to be an array of two literals + * of the same type as the column. + * + * @return EntityFieldQuery + * The called object. + */ + public function entityCondition($name, $value, $operator = NULL) { + $this->entityConditions[$name] = array( + 'value' => $value, + 'operator' => $operator, + ); + return $this; + } + + /** + * Adds a condition on field values. + * + * @param $field + * Either a field name or a field array. + * @param $column + * A column defined in the hook_field_schema() of this field. If this is + * omitted then the query will find only entities that have data in this + * field, using the entity and property conditions if there are any. + * @param $value + * The value to test the column value 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 $operator. + * @param $operator + * Possible values: + * - '=', '!=', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These + * operators expect $value to be a literal of the same type as the + * column. + * - 'IN', 'NOT IN': These operators expect $value to be an array of + * literals of the same type as the column. + * - 'BETWEEN': This operator expects $value to be an array of two literals + * of the same type as the column. + * @param $delta_group + * An arbitrary identifier: conditions in the same group must have the same + * $delta_group. For example, let's presume a multivalue field which has + * two columns, 'color' and 'shape', and for entity id 1, there are two + * values: red/square and blue/circle. Entity ID 1 does not have values + * corresponding to 'red circle', however if you pass 'red' and 'circle' as + * conditions, it will appear in the results - by default queries will run + * against any combination of deltas. By passing the conditions with the + * same $delta_group it will ensure that only values attached to the same + * delta are matched, and entity 1 would then be excluded from the results. + * @param $language_group + * An arbitrary identifier: conditions in the same group must have the same + * $language_group. + * + * @return EntityFieldQuery + * The called object. + */ + public function fieldCondition($field, $column = NULL, $value = NULL, $operator = NULL, $delta_group = NULL, $language_group = NULL) { + if (is_scalar($field)) { + $field = field_info_field($field); + } + // Ensure the same index is used for fieldConditions as for fields. + $index = count($this->fields); + $this->fields[$index] = $field; + if (isset($column)) { + $this->fieldConditions[$index] = array( + 'field' => $field, + 'column' => $column, + 'value' => $value, + 'operator' => $operator, + 'delta_group' => $delta_group, + 'language_group' => $language_group, + ); + } + return $this; + } + + /** + * Adds a condition on an entity-specific property. + * + * An $entity_type must be specified by calling + * EntityFieldCondition::entityCondition('entity_type', $entity_type) before + * executing the query. Also, by default only entities stored in SQL are + * supported; however, EntityFieldQuery::executeCallback can be set to handle + * different entity storage. + * + * @param $column + * A column defined in the hook_schema() of the base table of the entity. + * @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 $operator. + * @param $operator + * Possible values: + * - '=', '!=', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS': These + * operators expect $value to be a literal of the same type as the + * column. + * - 'IN', 'NOT IN': These operators expect $value to be an array of + * literals of the same type as the column. + * - 'BETWEEN': This operator expects $value to be an array of two literals + * of the same type as the column. + * The operator can be omitted, and will default to 'IN' if the value is an + * array, or to '=' otherwise. + * + * @return EntityFieldQuery + * The called object. + */ + public function propertyCondition($column, $value, $operator = NULL) { + $this->propertyConditions[] = array( + 'column' => $column, + 'value' => $value, + 'operator' => $operator, + ); + return $this; + } + + /** + * Orders the result set by entity-generic metadata. + * + * If called multiple times, the query will order by each specified column in + * the order this method is called. + * + * @param $name + * 'entity_type', 'bundle', 'revision_id' or 'entity_id'. + * @param $direction + * The direction to sort. Legal values are "ASC" and "DESC". + * + * @return EntityFieldQuery + * The called object. + */ + public function entityOrderBy($name, $direction) { + $this->entityOrder[$name] = $direction; + return $this; + } + + /** + * Orders the result set by a given field column. + * + * If called multiple times, the query will order by each specified column in + * the order this method is called. + * + * @param $field + * Either a field name or a field array. + * @param $column + * A column defined in the hook_field_schema() of this field. entity_id and + * bundle can also be used. + * @param $direction + * The direction to sort. Legal values are "ASC" and "DESC". + * + * @return EntityFieldQuery + * The called object. + */ + public function fieldOrderBy($field, $column, $direction) { + if (is_scalar($field)) { + $field = field_info_field($field); + } + // Ensure the same index is used for fieldOrder as for fields. + $index = count($this->fields); + $this->fields[$index] = $field; + $this->fieldOrder[$index] = array( + 'field' => $field, + 'column' => $column, + 'direction' => $direction, + ); + return $this; + } + + /** + * Orders the result set by an entity-specific property. + * + * An $entity_type must be specified by calling + * EntityFieldCondition::entityCondition('entity_type', $entity_type) before + * executing the query. + * + * If called multiple times, the query will order by each specified column in + * the order this method is called. + * + * @param $column + * The column on which to order. + * @param $direction + * The direction to sort. Legal values are "ASC" and "DESC". + * + * @return EntityFieldQuery + * The called object. + */ + public function propertyOrderBy($column, $direction) { + $this->propertyOrder[] = array( + 'column' => $column, + 'direction' => $direction, + ); + return $this; + } + + /** + * Sets the query to be a count query only. + * + * @return EntityFieldQuery + * The called object. + */ + public function count() { + $this->count = TRUE; + return $this; + } + + /** + * Restricts a query to a given range in the result set. + * + * @param $start + * The first entity from the result set to return. If NULL, removes any + * range directives that are set. + * @param $length + * The number of entities to return from the result set. + * + * @return EntityFieldQuery + * The called object. + */ + public function range($start = NULL, $length = NULL) { + $this->range = array( + 'start' => $start, + 'length' => $length, + ); + return $this; + } + + /** + * Filters on the data being deleted. + * + * @param $deleted + * TRUE to only return deleted data, FALSE to return non-deleted data, + * EntityFieldQuery::RETURN_ALL to return everything. Defaults to FALSE. + * + * @return EntityFieldQuery + * The called object. + */ + public function deleted($deleted = TRUE) { + $this->deleted = $deleted; + return $this; + } + + /** + * Queries the current or every revision. + * + * Note that this only affects field conditions. Property conditions always + * apply to the current revision. + * @TODO: Once revision tables have been cleaned up, revisit this. + * + * @param $age + * - FIELD_LOAD_CURRENT (default): Query the most recent revisions for all + * entities. The results will be keyed by entity type and entity ID. + * - FIELD_LOAD_REVISION: Query all revisions. The results will be keyed by + * entity type and entity revision ID. + * + * @return EntityFieldQuery + * The called object. + */ + public function age($age) { + $this->age = $age; + return $this; + } + + /** + * Executes the query. + * + * After executing the query, $this->orderedResults will contain a list of + * the same stub entities in the order returned by the query. This is only + * relevant if there are multiple entity types in the returned value and + * a field ordering was requested. In every other case, the returned value + * contains everything necessary for processing. + * + * @return + * Either a number if count() was called or an array of associative + * arrays of stub entities. The outer array keys are entity types, and the + * inner array keys are the relevant ID. (In most this cases this will be + * the entity ID. The only exception is when age=FIELD_LOAD_REVISION is used + * and field conditions or sorts are present -- in this case, the key will + * be the revision ID.) The inner array values are always stub entities, as + * returned by entity_create_stub_entity(). To traverse the returned array: + * @code + * foreach ($query->execute() as $entity_type => $entities) { + * foreach ($entities as $entity_id => $entity) { + * @endcode + * Note if the entity type is known, then the following snippet will load + * the entities found: + * @code + * $result = $query->execute(); + * $entities = entity_load($my_type, array_keys($result[$my_type])); + * @endcode + */ + public function execute() { + // Give a chance to other modules to alter the query. + drupal_alter('entity_query', $this); + + // Execute the query using the correct callback. + $result = call_user_func($this->queryCallback(), $this); + + // Sanity checks. + if (!empty($this->propertyConditions)) { + throw new EntityFieldQueryException(t('Property query conditions were not handled in !function.', array('!function' => $function))); + } + if (!empty($this->propertyOrderBy)) { + throw new EntityFieldQueryException(t('Property query order by was not handled in !function.', array('!function' => $function))); + } + return $result; + } + + /** + * Determines the query callback to use for this entity query. + * + * @return + * A callback that can be used with call_user_func(). + */ + public function queryCallback() { + // Use the override from $this->executeCallback. It can be set either + // while building the query, or using hook_entity_query_alter(). + if (function_exists($this->executeCallback)) { + return $this->executeCallback; + } + // If there are no field conditions and sorts, and no execute callback + // then we default to querying entity tables in SQL. + if (empty($this->fields)) { + return array($this, 'propertyQuery'); + } + // If no override, find the storage engine to be used. + foreach ($this->fields as $field) { + if (!isset($storage)) { + $storage = $field['storage']['module']; + } + elseif ($storage != $field['storage']['module']) { + throw new EntityFieldQueryException(t("Can't handle more than one field storage engine")); + } + } + if ($storage) { + // Use hook_field_storage_query() from the field storage. + return $storage . '_field_storage_query'; + } + else { + throw new EntityFieldQueryException(t("Field storage engine not found.")); + } + } + + /** + * Queries entity tables in SQL for property conditions and sorts. + * + * This method is only used if there are no field conditions and sorts. + * + * @return + * See EntityFieldQuery::execute(). + */ + protected function propertyQuery() { + if (empty($this->entityConditions['entity_type'])) { + throw new EntityFieldQueryException(t('For this query an entity type must be specified.')); + } + $entity_type = $this->entityConditions['entity_type']['value']; + unset($this->entityConditions['entity_type']); + $entity_info = entity_get_info($entity_type); + if (empty($entity_info['base table'])) { + throw new EntityFieldQueryException(t('Entity %entity has no base table.', array('%entity' => $entity_type))); + } + $base_table = $entity_info['base table']; + $select_query = db_select($base_table); + $select_query->addExpression(':entity_type', 'entity_type', array(':entity_type' => $entity_type)); + // Process the four possible entity condition. + // The id field is always present in entity keys. + $sql_field = $entity_info['entity keys']['id']; + $id_map['entity_id'] = $sql_field; + $select_query->addField($base_table, $sql_field, 'entity_id'); + if (isset($this->entityConditions['entity_id'])) { + $this->addCondition($select_query, $sql_field, $this->entityConditions['entity_id']); + } + + // If there is a revision key defined, use it. + if (!empty($entity_info['entity keys']['revision'])) { + $sql_field = $entity_info['entity keys']['revision']; + $select_query->addField($base_table, $sql_field, 'revision_id'); + if (isset($this->entityConditions['revision_id'])) { + $this->addCondition($select_query, $sql_field, $this->entityConditions['revision_id']); + } + } + else { + $sql_field = 'revision_id'; + $select_query->addExpression('NULL', 'revision_id'); + } + $id_map['revision_id'] = $sql_field; + + // Handle bundles. + if (!empty($entity_info['entity keys']['bundle'])) { + $sql_field = $entity_info['entity keys']['bundle']; + $select_query->addField($base_table, $sql_field, 'bundle'); + $having = FALSE; + } + else { + $sql_field = 'bundle'; + $select_query->addExpression(':bundle', 'bundle', array(':bundle' => $entity_type)); + $having = TRUE; + } + $id_map['bundle'] = $sql_field; + if (isset($this->entityConditions['bundle'])) { + $this->addCondition($select_query, $sql_field, $this->entityConditions['bundle'], $having); + } + + foreach ($this->entityOrder as $key => $direction) { + if (isset($id_map[$key])) { + $select_query->orderBy($id_map[$key], $direction); + } + else { + throw new EntityFieldQueryException(t('Do not know how to order on @key for @entity_type', array('@key' => $key, '@entity_type' => $entity_type))); + } + } + $this->processProperty($select_query, $base_table); + return $this->finishQuery($select_query); + } + + /** + * Finishes the query. + * + * Adds the range and returns the requested list. + * + * @param SelectQuery $select_query + * A SelectQuery which has entity_type, entity_id, revision_id and bundle + * fields added. + * @param $id_key + * Which field's values to use as the returned array keys. + * + * @return + * See EntityFieldQuery::execute(). + */ + function finishQuery($select_query, $id_key = 'entity_id') { + if ($this->range) { + $select_query->range($this->range['start'], $this->range['length']); + } + if ($this->count) { + return $select_query->countQuery()->execute()->fetchField(); + } + $return = array(); + foreach ($select_query->execute() as $partial_entity) { + $entity = entity_create_stub_entity($partial_entity->entity_type, array($partial_entity->entity_id, $partial_entity->revision_id, $partial_entity->bundle)); + $return[$partial_entity->entity_type][$partial_entity->$id_key] = $entity; + $this->ordered_results[] = $partial_entity; + } + return $return; + } + + /** + * Processes the property condition and orders. + * + * This is a helper for hook_entity_query() and hook_field_storage_query(). + * + * @param SelectQuery $select_query + * A SelectQuery object. + * @param $entity_base_table + * The name of the entity base table. This already should be in + * $select_query. + */ + public function processProperty(SelectQuery $select_query, $entity_base_table) { + foreach ($this->propertyConditions as $entity_condition) { + $this->addCondition($select_query, "$entity_base_table." . $entity_condition['column'], $entity_condition); + } + foreach ($this->propertyOrder as $order) { + $select_query->orderBy("$entity_base_table." . $order['column'], $order['direction']); + } + unset($this->propertyConditions, $this->propertyOrder); + } + + /** + * Adds a condition to an already built SelectQuery (internal function). + * + * This is a helper for hook_entity_query() and hook_field_storage_query(). + * + * @param SelectQuery $select_query + * A SelectQuery object. + * @param $sql_field + * The name of the field. + * @param $condition + * A condition as described in EntityFieldQuery::fieldCondition() and + * EntityFieldQuery::entityCondition(). + * @param $having + * HAVING or WHERE. This is necessary because SQL can't handle WHERE + * conditions on aliased columns. + */ + public function addCondition(SelectQuery $select_query, $sql_field, $condition, $having = FALSE) { + $method = $having ? 'havingCondition' : 'condition'; + $like_prefix = ''; + switch ($condition['operator']) { + case 'CONTAINS': + $like_prefix = '%'; + case 'STARTS_WITH': + $select_query->$method($sql_field, $like_prefix . db_like($condition['value']) . '%', 'LIKE'); + break; + default: + $select_query->$method($sql_field, $condition['value'], $condition['operator']); + } + } + +} diff --git a/includes/errors.inc b/includes/errors.inc index 1c0e71cc..f26319d3 100644 --- a/includes/errors.inc +++ b/includes/errors.inc @@ -1,5 +1,5 @@ <?php -// $Id: errors.inc,v 1.5 2010/04/11 18:33:43 dries Exp $ +// $Id: errors.inc,v 1.8 2010/06/28 20:27:34 dries Exp $ /** * @file @@ -135,7 +135,30 @@ function _drupal_decode_exception($exception) { * An error message. */ function _drupal_render_exception_safe($exception) { - return strtr('%type: %message in %function (line %line of %file).', _drupal_decode_exception($exception)); + return check_plain(strtr('%type: %message in %function (line %line of %file).', _drupal_decode_exception($exception))); +} + +/** + * Determines whether an error should be displayed. + * + * When in maintenance mode or when error_level is ERROR_REPORTING_DISPLAY_ALL, + * all errors should be displayed. For ERROR_REPORTING_DISPLAY_SOME, $error + * will be examined to determine if it should be displayed. + * + * @param $error + * Optional error to examine for ERROR_REPORTING_DISPLAY_SOME. + * + * @return + * TRUE if an error should be displayed. + */ +function error_displayable($error = NULL) { + $error_level = variable_get('error_level', ERROR_REPORTING_DISPLAY_ALL); + $updating = (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update'); + $all_errors_displayed = ($error_level == ERROR_REPORTING_DISPLAY_ALL); + $error_needs_display = ($error_level == ERROR_REPORTING_DISPLAY_SOME && + isset($error) && $error['%type'] != 'Notice' && $error['%type'] != 'Strict warning'); + + return ($updating || $all_errors_displayed || $error_needs_display); } /** @@ -159,7 +182,8 @@ function _drupal_log_error($error, $fatal = FALSE) { // When running inside the testing framework, we relay the errors // to the tested site by the way of HTTP headers. - if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^simpletest\d+;/", $_SERVER['HTTP_USER_AGENT']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) { + $test_info = &$GLOBALS['drupal_test_info']; + if (!empty($test_info['in_child_site']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) { // $number does not use drupal_static as it should not be reset // as it uniquely identifies each PHP error. static $number = 0; @@ -200,9 +224,7 @@ function _drupal_log_error($error, $fatal = FALSE) { else { // Display the message if the current error reporting level allows this type // of message to be displayed, and unconditionnaly in update.php. - $error_level = variable_get('error_level', ERROR_REPORTING_DISPLAY_ALL); - $display_error = $error_level == ERROR_REPORTING_DISPLAY_ALL || ($error_level == ERROR_REPORTING_DISPLAY_SOME && $error['%type'] != 'Notice' && $error['%type'] != 'Strict warning'); - if ($display_error || (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update')) { + if (error_displayable($error)) { $class = 'error'; // If error type is 'User notice' then treat it as debug information diff --git a/includes/file.inc b/includes/file.inc index cef2b150..bb1f825c 100644 --- a/includes/file.inc +++ b/includes/file.inc @@ -1,5 +1,5 @@ <?php -// $Id: file.inc,v 1.209 2010/05/11 10:56:04 dries Exp $ +// $Id: file.inc,v 1.218 2010/07/07 01:10:35 dries Exp $ /** * @file @@ -96,6 +96,7 @@ define('FILE_STATUS_PERMANENT', 1); * * @return * Returns the entire Drupal stream wrapper registry. + * * @see hook_stream_wrappers() * @see hook_stream_wrappers_alter() */ @@ -149,6 +150,7 @@ function file_get_stream_wrappers($filter = STREAM_WRAPPERS_ALL) { * * @param $scheme * Stream scheme. + * * @return * Return string if a scheme has a registered handler, or FALSE. */ @@ -162,9 +164,12 @@ function file_stream_wrapper_get_class($scheme) { * * @param $uri * A stream, referenced as "scheme://target". + * * @return * A string containing the name of the scheme, or FALSE if none. For example, * the URI "public://example.txt" would return "public". + * + * @see file_uri_target() */ function file_uri_scheme($uri) { $data = explode('://', $uri, 2); @@ -181,6 +186,7 @@ function file_uri_scheme($uri) { * * @param $scheme * A URI scheme, a stream is referenced as "scheme://target". + * * @return * Returns TRUE if the string is the name of a validated stream, * or FALSE if the scheme does not have a registered handler. @@ -201,22 +207,19 @@ function file_stream_wrapper_valid_scheme($scheme) { * * @param $uri * A stream, referenced as "scheme://target". + * * @return * A string containing the target (path), or FALSE if none. * For example, the URI "public://sample/test.txt" would return * "sample/test.txt". + * + * @see file_uri_scheme() */ function file_uri_target($uri) { - $data = explode('://', $uri, 2); - - if (count($data) != 2) { - return FALSE; + if ($scheme = file_uri_scheme($uri)) { + return file_stream_wrapper_get_instance_by_scheme($scheme)->getTarget($uri); } - - // Remove erroneous beginning forward slash. - $data[1] = ltrim($data[1], '\/'); - - return $data[1]; + return FALSE; } /** @@ -225,12 +228,12 @@ function file_uri_target($uri) { * A stream is referenced as "scheme://target". * * The following actions are taken: - * - Remove all occurrences of the wrapper's directory path * - Remove trailing slashes from target * - Trim erroneous leading slashes from target. e.g. ":///" becomes "://". * * @param $uri * String reference containing the URI to normalize. + * * @return * The normalized URI. */ @@ -240,15 +243,9 @@ function file_stream_wrapper_uri_normalize($uri) { if ($scheme && file_stream_wrapper_valid_scheme($scheme)) { $target = file_uri_target($uri); - // Remove all occurrences of the wrapper's directory path. - $directory_path = file_stream_wrapper_get_instance_by_scheme($scheme)->getDirectoryPath(); - $target = str_replace($directory_path, '', $target); - - // Trim trailing slashes from target. - $target = rtrim($target, '/'); - - // Trim erroneous leading slashes from target. - $uri = $scheme . '://' . ltrim($target, '/'); + if ($target !== FALSE) { + $uri = $scheme . '://' . $target; + } } return $uri; } @@ -261,6 +258,7 @@ function file_stream_wrapper_uri_normalize($uri) { * * @param $uri * A stream, referenced as "scheme://target". + * * @return * Returns a new stream wrapper object appropriate for the given URI or FALSE * if no registered handler could be found. For example, a URI of @@ -271,7 +269,7 @@ function file_stream_wrapper_get_instance_by_uri($uri) { $scheme = file_uri_scheme($uri); $class = file_stream_wrapper_get_class($scheme); if (class_exists($class)) { - $instance = new $class; + $instance = new $class(); $instance->setUri($uri); return $instance; } @@ -293,6 +291,7 @@ function file_stream_wrapper_get_instance_by_uri($uri) { * * @param $scheme * If the stream was "public://target", "public" would be the scheme. + * * @return * Returns a new stream wrapper object appropriate for the given $scheme. * For example, for the public scheme a stream wrapper object @@ -302,7 +301,7 @@ function file_stream_wrapper_get_instance_by_uri($uri) { function file_stream_wrapper_get_instance_by_scheme($scheme) { $class = file_stream_wrapper_get_class($scheme); if (class_exists($class)) { - $instance = new $class; + $instance = new $class(); $instance->setUri($scheme . '://'); return $instance; } @@ -329,6 +328,7 @@ function file_stream_wrapper_get_instance_by_scheme($scheme) { * @param $uri * The URI to a file for which we need an external URL, or the path to a * shipped file. + * * @return * A string containing a URL that may be used to access the file. * If the provided string already contains a preceding 'http', nothing is done @@ -388,6 +388,7 @@ function file_create_url($uri) { * A bitmask to indicate if the directory should be created if it does * not exist (FILE_CREATE_DIRECTORY) or made writable if it is read-only * (FILE_MODIFY_PERMISSIONS). + * * @return * TRUE if the directory exists (or was created) and is writable. FALSE * otherwise. @@ -470,13 +471,14 @@ function file_create_htaccess($directory, $private = TRUE) { } /** - * Load file objects from the database. + * Loads file objects from the database. * * @param $fids * An array of file IDs. * @param $conditions - * An array of conditions to match against the {files} table. These - * should be supplied in the form array('field_name' => 'field_value'). + * An array of conditions to match against the {file_managed} table. + * These should be supplied in the form array('field_name' => + * 'field_value'). * * @return * An array of file objects, indexed by fid. @@ -512,6 +514,7 @@ function file_load($fid) { * * @param $file * A file object returned by file_load(). + * * @return * The updated file object. * @@ -526,13 +529,13 @@ function file_save(stdClass $file) { drupal_write_record('file_managed', $file); // Inform modules about the newly added file. module_invoke_all('file_insert', $file); - entity_invoke('insert', 'file', $file); + module_invoke_all('entity_insert', $file, 'file'); } else { drupal_write_record('file_managed', $file, 'fid'); // Inform modules that the file has been updated. module_invoke_all('file_update', $file); - entity_invoke('update', 'file', $file); + module_invoke_all('entity_update', $file, 'file'); } return $file; @@ -557,8 +560,8 @@ function file_save(stdClass $file) { * A file object. * @param $destination * A string containing the destination that $source should be copied to. - * This should be a stream wrapper URI. If this value is omitted, Drupal's - * public files scheme will be used, "public://". + * This must be a stream wrapper URI. If this value is omitted, Drupal's + * default files scheme will be used, usually "public://". * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with @@ -619,14 +622,16 @@ function file_copy(stdClass $source, $destination = NULL, $replace = FILE_EXISTS * @param $source * A string specifying the filepath or URI of the original file. * @param $destination - * A URI containing the destination that $source should be copied to. If - * NULL the default scheme will be used as the destination. + * A URI containing the destination that $source should be copied to. + * This must be a stream wrapper URI. If this value is omitted, Drupal's + * default files scheme will be used, usually "public://". * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE - Replace the existing file. * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is * unique. * - FILE_EXISTS_ERROR - Do nothing and return FALSE. + * * @return * The path to the new file, or FALSE in the event of an error. * @@ -716,6 +721,7 @@ function file_build_uri($path) { * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is * unique. * - FILE_EXISTS_ERROR - Do nothing and return FALSE. + * * @return * The destination filepath, or FALSE if the file already exists * and FILE_EXISTS_ERROR is specified. @@ -755,9 +761,9 @@ function file_destination($destination, $replace) { * @param $source * A file object. * @param $destination - * A string containing the destination that $source should be moved to. This - * must be a URI matching a Drupal stream wrapper. If this value is omitted, - * Drupal's 'files' directory will be used. + * A string containing the destination that $source should be moved to. + * This must be a stream wrapper URI. If this value is omitted, Drupal's + * default files scheme will be used, usually "public://". * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with @@ -768,6 +774,7 @@ function file_destination($destination, $replace) { * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is * unique. * - FILE_EXISTS_ERROR - Do nothing and return FALSE. + * * @return * Resulting file object for success, or FALSE in the event of an error. * @@ -817,15 +824,16 @@ function file_move(stdClass $source, $destination = NULL, $replace = FILE_EXISTS * @param $source * A string specifying the filepath or URI of the original file. * @param $destination - * A string containing the destination that $source should be moved to. This - * must be a URI matching a Drupal stream wrapper. If this value is omitted, - * Drupal's 'files' directory will be used. + * A string containing the destination that $source should be moved to. + * This must be a stream wrapper URI. If this value is omitted, Drupal's + * default files scheme will be used, usually "public://". * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE - Replace the existing file. * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is * unique. * - FILE_EXISTS_ERROR - Do nothing and return FALSE. + * * @return * The URI of the moved file, or FALSE in the event of an error. * @@ -896,6 +904,7 @@ function file_munge_filename($filename, $extensions, $alerts = TRUE) { * * @param $filename * String with the filename to be unmunged. + * * @return * An unmunged filename string. */ @@ -913,6 +922,7 @@ function file_unmunge_filename($filename) { * String filename * @param $directory * String containing the directory or parent URI. + * * @return * File path consisting of $directory and a unique filename based off * of $basename. @@ -965,6 +975,7 @@ function file_create_filename($basename, $directory) { * @param $force * Boolean indicating that the file should be deleted even if * hook_file_references() reports that the file is in use. + * * @return mixed * TRUE for success, FALSE in the event of an error, or an array if the file * is being used by another module. The array keys are the module's name and @@ -1003,6 +1014,7 @@ function file_delete(stdClass $file, $force = FALSE) { * * @param $path * A string containing a file path or (streamwrapper) URI. + * * @return * TRUE for success or path does not exist, or FALSE in the event of an * error. @@ -1046,6 +1058,7 @@ function file_unmanaged_delete($path) { * * @param $path * A string containing either an URI or a file or directory path. + * * @return * TRUE for success or if path does not exist, FALSE in the event of an * error. @@ -1079,6 +1092,7 @@ function file_unmanaged_delete_recursive($path) { * @param $status * Optional. File Status to return. Combine with a bitwise OR(|) to return * multiple statuses. The default status is FILE_STATUS_PERMANENT. + * * @return * An integer containing the number of bytes used. */ @@ -1106,15 +1120,23 @@ function file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT) { * @param $validators * An optional, associative array of callback functions used to validate the * file. See file_validate() for a full discussion of the array format. + * If no extension validator is provided it will default to a limited safe + * list of extensions which is as follows: "jpg jpeg gif png txt + * doc xls pdf ppt pps odt ods odp". To allow all extensions you must + * explicitly set the 'file_validate_extensions' validator to an empty array + * (Beware: this is not safe and should only be allowed for trusted users, if + * at all). * @param $destination - * A string containing the URI $source should be copied to. Defaults to - * "temporary://". + * A string containing the URI $source should be copied to. + * This must be a stream wrapper URI. If this value is omitted, Drupal's + * temporary files scheme will be used ("temporary://"). * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE: Replace the existing file. * - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is * unique. * - FILE_EXISTS_ERROR: Do nothing and return FALSE. + * * @return * An object containing the file information if the upload succeeded, FALSE * in the event of an error, or NULL if no file was uploaded. The @@ -1167,28 +1189,56 @@ function file_save_upload($source, $validators = array(), $destination = FALSE, return FALSE; } - // Build the list of non-munged extensions. - // @todo: this should not be here. we need to figure out the right place. - $extensions = ''; - foreach ($user->roles as $rid => $name) { - $extensions .= ' ' . variable_get("upload_extensions_$rid", - variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps odt ods odp')); - } - // Begin building file object. $file = new stdClass(); $file->uid = $user->uid; $file->status = 0; - $file->filename = file_munge_filename(trim(basename($_FILES['files']['name'][$source]), '.'), $extensions); + $file->filename = trim(basename($_FILES['files']['name'][$source]), '.'); $file->uri = $_FILES['files']['tmp_name'][$source]; $file->filemime = file_get_mimetype($file->filename); $file->filesize = $_FILES['files']['size'][$source]; - // Rename potentially executable files, to help prevent exploits. - if (preg_match('/\.(php|pl|py|cgi|asp|js)$/i', $file->filename) && (substr($file->filename, -4) != '.txt')) { + $extensions = ''; + if (isset($validators['file_validate_extensions'])) { + if (isset($validators['file_validate_extensions'][0])) { + // Build the list of non-munged extensions if the caller provided them. + $extensions = $validators['file_validate_extensions'][0]; + } + else { + // If 'file_validate_extensions' is set and the list is empty then the + // caller wants to allow any extension. In this case we have to remove the + // validator or else it will reject all extensions. + unset($validators['file_validate_extensions']); + } + } + else { + // No validator was provided, so add one using the default list. + // Build a default non-munged safe list for file_munge_filename(). + $extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'; + $validators['file_validate_extensions'] = array(); + $validators['file_validate_extensions'][0] = $extensions; + } + + if (!empty($extensions)) { + // Munge the filename to protect against possible malicious extension hiding + // within an unknown file type (ie: filename.html.foo). + $file->filename = file_munge_filename($file->filename, $extensions); + } + + // Rename potentially executable files, to help prevent exploits (i.e. will + // rename filename.php.foo and filename.php to filename.php.foo.txt and + // filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads' + // evaluates to TRUE. + if (!variable_get('allow_insecure_uploads', 0) && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->filename) && (substr($file->filename, -4) != '.txt')) { $file->filemime = 'text/plain'; $file->uri .= '.txt'; $file->filename .= '.txt'; + // The .txt extension may not be in the allowed list of extensions. We have + // to add it here or else the file upload will fail. + if (!empty($extensions)) { + $validators['file_validate_extensions'][0] .= ' txt'; + drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $file->filename))); + } } // If the destination is not provided, use the temporary directory. @@ -1282,6 +1332,7 @@ function file_save_upload($source, $validators = array(), $destination = FALSE, * functions should return an array of error messages; an empty array * indicates that the file passed validation. The functions will be called in * the order specified. + * * @return * An array containing validation error messages. * @@ -1328,6 +1379,7 @@ function file_validate_name_length(stdClass $file) { * A Drupal file object. * @param $extensions * A string with a space separated list of allowed extensions. + * * @return * An array. If the file extension is not allowed, it will contain an error * message. @@ -1357,6 +1409,7 @@ function file_validate_extensions(stdClass $file, $extensions) { * @param $user_limit * An integer specifying the maximum number of bytes the user is allowed. * Zero indicates that no limit should be enforced. + * * @return * An array. If the file size exceeds limits, it will contain an error * message. @@ -1387,6 +1440,7 @@ function file_validate_size(stdClass $file, $file_limit = 0, $user_limit = 0) { * * @param $file * A Drupal file object. + * * @return * An array. If the file is not an image, it will contain an error message. * @@ -1420,6 +1474,7 @@ function file_validate_is_image(stdClass $file) { * @param $minimum_dimensions * An optional string in the form WIDTHxHEIGHT. This will check that the * image meets a minimum size. A value of 0 indicates no restriction. + * * @return * An array. If the file is an image and did not meet the requirements, it * will contain an error message. @@ -1466,9 +1521,9 @@ function file_validate_image_resolution(stdClass $file, $maximum_dimensions = 0, * @param $data * A string containing the contents of the file. * @param $destination - * A string containing the destination URI. If no value is provided then a - * randomly name will be generated and the file saved in Drupal's files - * directory. + * A string containing the destination URI. + * This must be a stream wrapper URI. If this value is omitted, Drupal's + * default files scheme will be used, usually "public://". * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with @@ -1477,6 +1532,7 @@ function file_validate_image_resolution(stdClass $file, $maximum_dimensions = 0, * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is * unique. * - FILE_EXISTS_ERROR - Do nothing and return FALSE. + * * @return * A file object, or FALSE on error. * @@ -1524,15 +1580,17 @@ function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAM * @param $data * A string containing the contents of the file. * @param $destination - * A string containing the destination location. If no value is provided - * then a randomly name will be generated and the file saved in Drupal's - * files directory. + * A string containing the destination location. + * This must be a stream wrapper URI. If no value is provided, a + * randomized name will be generated and the file is saved using Drupal's + * default files scheme, usually "public://". * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE - Replace the existing file. * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is * unique. * - FILE_EXISTS_ERROR - Do nothing and return FALSE. + * * @return * A string with the path of the resulting file, or FALSE on error. * @@ -1671,7 +1729,7 @@ function file_scan_directory($dir, $mask, $options = array(), $depth = 0) { elseif ($depth >= $options['min_depth'] && preg_match($mask, $filename)) { // Always use this match over anything already set in $files with the // same $$options['key']. - $file = new StdClass; + $file = new stdClass(); $file->uri = $uri; $file->filename = $filename; $file->name = pathinfo($filename, PATHINFO_FILENAME); @@ -1700,6 +1758,7 @@ function file_scan_directory($dir, $mask, $options = array(), $depth = 0) { * @param $scheme * A string representing the scheme of a stream. The default wrapper is * is assumed if this is not provided. + * * @return * A string containing the directory path of a stream. FALSE is returned if * the scheme is invalid or a wrapper could not be instantiated. @@ -1783,6 +1842,7 @@ function file_get_mimetype($uri, $mapping = NULL) { * @param $mode * Integer value for the permissions. Consult PHP chmod() documentation for * more information. + * * @return * TRUE for success, FALSE in the event of an error. * @@ -1880,14 +1940,7 @@ function drupal_dirname($uri) { $scheme = file_uri_scheme($uri); if ($scheme && file_stream_wrapper_valid_scheme($scheme)) { - $target = file_uri_target($uri); - $dirname = dirname($target); - - if ($dirname == '.') { - $dirname = ''; - } - - return $scheme . '://' . $dirname; + return file_stream_wrapper_get_instance_by_scheme($scheme)->dirname($uri); } else { return dirname($uri); @@ -1973,6 +2026,55 @@ function drupal_tempnam($directory, $prefix) { } } +/** + * Get the path of system-appropriate temporary directory. + */ +function file_directory_temp() { + $temporary_directory = variable_get('file_directory_temp', NULL); + + if (is_null($temporary_directory)) { + $directories = array(); + + // Has PHP been set with an upload_tmp_dir? + if (ini_get('upload_tmp_dir')) { + $directories[] = ini_get('upload_tmp_dir'); + } + + // Operating system specific dirs. + if (substr(PHP_OS, 0, 3) == 'WIN') { + $directories[] = 'c:\\windows\\temp'; + $directories[] = 'c:\\winnt\\temp'; + $path_delimiter = '\\'; + } + else { + $directories[] = '/tmp'; + $path_delimiter = '/'; + } + // PHP may be able to find an alternative tmp directory. + // This function exists in PHP 5 >= 5.2.1, but Drupal + // requires PHP 5 >= 5.2.0, so we check for it. + if (function_exists('sys_get_temp_dir')) { + $directories[] = sys_get_temp_dir(); + } + + foreach ($directories as $directory) { + if (is_dir($directory) && is_writable($directory)) { + $temporary_directory = $directory; + break; + } + } + + // if a directory has been found, use it, otherwise default to 'files/tmp' or 'files\\tmp'. + if (empty($temporary_directory)) { + $temporary_directory = file_directory_path() . $path_delimiter . 'tmp'; + } + // Save the path of the discovered directory. + variable_set('file_directory_temp', $temporary_directory); + } + + return $temporary_directory; +} + /** * @} End of "defgroup file". */ diff --git a/includes/form.inc b/includes/form.inc index 21008c0c..c0812bb7 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -1,5 +1,5 @@ <?php -// $Id: form.inc,v 1.464 2010/05/19 19:22:24 dries Exp $ +// $Id: form.inc,v 1.476 2010/07/07 17:56:42 webchick Exp $ /** * @defgroup forms Form builder functions @@ -29,21 +29,124 @@ * presentation, while simplifying code and reducing the amount of HTML that * must be explicitly generated by modules. * - * The drupal_get_form() function handles retrieving and processing an HTML - * form for modules automatically. For example: + * The primary function used with forms is drupal_get_form(), which is + * used for forms presented interactively to a user. Forms can also be built and + * submitted programmatically without any user input using the + * drupal_form_submit() function. * + * drupal_get_form() handles retrieving, processing, and displaying a rendered + * HTML form for modules automatically. + * + * Here is an example of how to use drupal_get_form() and a form builder + * function: * @code - * // Display the user registration form. - * $output = drupal_get_form('user_register_form'); + * $form = drupal_get_form('my_module_example_form'); + * ... + * function my_module_example_form($form, &$form_state) { + * $form['submit'] = array( + * '#type' => 'submit', + * '#value' => t('Submit'), + * ); + * return $form; + * } + * function my_module_example_form_validate($form, &$form_state) { + * // Validation logic. + * } + * function my_module_example_form_submit($form, &$form_state) { + * // Submission logic. + * } * @endcode * - * Forms can also be built and submitted programmatically without any user input - * using the drupal_form_submit() function. + * Or with any number of additional arguments: + * @code + * $extra = "extra"; + * $form = drupal_get_form('my_module_example_form', $extra); + * ... + * function my_module_example_form($form, &$form_state, $extra) { + * $form['submit'] = array( + * '#type' => 'submit', + * '#value' => $extra, + * ); + * return $form; + * } + * @endcode * - * For information on the format of the structured arrays used to define forms, - * and more detailed explanations of the Form API workflow, see the - * @link http://api.drupal.org/api/file/developer/topics/forms_api_reference.html reference @endlink - * and the @link http://api.drupal.org/api/file/developer/topics/forms_api.html quickstart guide. @endlink + * The $form argument to form-related functions is a structured array containing + * the elements and properties of the form. For information on the array + * components and format, and more detailed explanations of the Form API + * workflow, see the + * @link http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html Form API reference @endlink + * and the + * @link http://drupal.org/node/37775 Form API section of the handbook. @endlink + * In addition, there is a set of Form API tutorials in + * @link form_example_tutorial.inc the Form Example Tutorial @endlink which + * provide basics all the way up through multistep forms. + * + * In the form builder, validation, submission, and other form functions, + * $form_state is the primary influence on the processing of the form and is + * passed by reference to most functions, so they use it to communicate with + * the form system and each other. + * + * The $form_state keys are: + * - 'values': An associative array of values submitted to the form. The + * validation functions and submit functions use this array for nearly all + * their decision making. (Note that + * @link http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html/7#tree #tree @endlink + * determines whether the values are a flat array or an array whose structure + * parallels the $form array.) + * - 'rebuild': If the submit function sets $form_state['rebuild'] to TRUE, + * submission is not completed and instead the form is rebuilt using any + * information that the submit function has made available to the form builder + * function via $form_state. This is commonly used for wizard-style + * multi-step forms, add-more buttons, and the like. For further information + * see drupal_build_form(). + * - 'redirect': a URL that will be used to redirect the form on submission. + * See drupal_redirect_form() for complete information. + * - 'storage': $form_state['storage'] is not a special key, and no specific + * support is provided for it in the Form API, but by tradition it was + * the location where application-specific data was stored for communication + * between the submit, validation, and form builder functions, especially + * in a multi-step-style form. Form implementations may use any key(s) within + * $form_state (other than the keys listed here and other reserved ones used + * by Form API internals) for this kind of storage. The recommended way to + * ensure that the chosen key doesn't conflict with ones used by the Form API + * or other modules is to use the module name as the key name or a prefix for + * the key name. For example, the Node module uses $form_state['node'] in node + * editing forms to store information about the node being edited, and this + * information stays available across successive clicks of the "Preview" + * button as well as when the "Save" button is finally clicked. + * - 'temporary': Since values for all non-reserved keys in $form_state persist + * throughout a multistep form sequence, the Form API provides the 'temporary' + * key for modules to use for communicating information across form-related + * functions during a single page request only. There is no use-case for this + * functionality in core. + * - 'triggering_element': (read-only) The form element that triggered + * submission. This is the same as the deprecated + * $form_state['clicked_button']. It is the element that caused submission, + * which may or may not be a button (in the case of AJAX forms.) This is + * often used to distinguish between various buttons in a submit handler, + * and is also used in AJAX handlers. + * - 'cache': The typical form workflow involves two page requests. During the + * first page request, a form is built and returned for the user to fill in. + * Then the user fills the form in and submits it, triggering a second page + * request in which the form must be built and processed. By default, $form + * and $form_state are built from scratch during each of these page requests. + * In some special use-cases, it is necessary or desired to persist the $form + * and $form_state variables from the initial page request to the one that + * processes the submission. A form builder function can set 'cache' to TRUE + * to do this. One example where this is needed is to handle AJAX submissions, + * so ajax_process_form() sets this for all forms that include an element with + * a #ajax property. (In AJAX, the handler has no way to build the form + * itself, so must rely on the cached version created on each page load, so + * it's a classic example of this use case.) Note that the persistence of + * $form and $form_state across successive submissions of a multi-step form + * happens automatically regardless of the value for 'cache'. + * - 'input': The array of values as they were submitted by the user. These are + * raw and unvalidated, so should not be used without a thorough understanding + * of security implications. In almost all cases, code should use the data in + * the 'values' array exclusively. The most common use of this key is for + * multi-step forms that need to clear some of the user input when setting + * 'rebuild'. */ /** @@ -483,15 +586,6 @@ function form_state_keys_no_cache() { * $form_state['values']['pass']['pass2'] = 'password'; * $form_state['values']['op'] = t('Create new account'); * drupal_form_submit('user_register_form', $form_state); - * // Create a new node - * $form_state = array(); - * module_load_include('inc', 'node', 'node.pages'); - * $node = array('type' => 'story'); - * $form_state['values']['title'] = 'My node'; - * $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); * @endcode */ function drupal_form_submit($form_id, &$form_state) { @@ -653,7 +747,7 @@ function drupal_process_form($form_id, &$form, &$form_state) { // here, though. 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'); + cache_clear_all('form_state_' . $form_state['values']['form_build_id'], 'cache_form'); } // If batches were set in the submit handlers, we process them now, @@ -941,7 +1035,7 @@ function _form_validate(&$elements, &$form_state, $form_id = NULL) { $options = $elements['#options']; } if (is_array($elements['#value'])) { - $value = $elements['#type'] == 'checkboxes' ? array_keys(array_filter($elements['#value'])) : $elements['#value']; + $value = $elements['#type'] == 'checkboxes' ? array_keys($elements['#value']) : $elements['#value']; foreach ($value as $v) { if (!isset($options[$v])) { form_error($elements, $t('An illegal choice has been detected. Please contact the site administrator.')); @@ -1070,6 +1164,83 @@ function form_execute_handlers($type, &$form, &$form_state) { /** * Files an error against a form element. * + * When a validation error is detected, the validator calls form_set_error() to + * indicate which element needs to be changed and provide an error message. This + * causes the Form API to not execute the form submit handlers, and instead to + * re-display the form to the user with the corresponding elements rendered with + * an 'error' CSS class (shown as red by default). + * + * The standard form_set_error() behavior can be changed if a button provides + * the #limit_validation_errors property. Multistep forms not wanting to + * validate the whole form can set #limit_validation_errors on buttons to + * limit validation errors to only certain elements. For example, pressing the + * "Previous" button in a multistep form should not fire validation errors just + * because the current step has invalid values. If #limit_validation_errors is + * set on a clicked button, the button must also define a #submit property + * (may be set to an empty array). Any #submit handlers will be executed even if + * there is invalid input, so extreme care should be taken with respect to any + * actions taken by them. This is typically not a problem with buttons like + * "Previous" or "Add more" that do not invoke persistent storage of the + * submitted form values. Do not use the #limit_validation_errors property on + * buttons that trigger saving of form values to the database. + * + * The #limit_validation_errors property is a list of "sections" within + * $form_state['values'] that must contain valid values. Each "section" is an + * array with the ordered set of keys needed to reach that part of + * $form_state['values'] (i.e., the #parents property of the element). + * + * Example 1: Allow the "Previous" button to function, regardless of whether any + * user input is valid. + * + * @code + * $form['actions']['previous'] = array( + * '#type' => 'submit', + * '#value' => t('Previous'), + * '#limit_validation_errors' => array(), // No validation. + * '#submit' => array('some_submit_function'), // #submit required. + * ); + * @endcode + * + * Example 2: Require some, but not all, user input to be valid to process the + * submission of a "Previous" button. + * + * @code + * $form['actions']['previous'] = array( + * '#type' => 'submit', + * '#value' => t('Previous'), + * '#limit_validation_errors' => array( + * array('step1'), // Validate $form_state['values']['step1']. + * array('foo', 'bar'), // Validate $form_state['values']['foo']['bar']. + * ), + * '#submit' => array('some_submit_function'), // #submit required. + * ); + * @endcode + * + * This will require $form_state['values']['step1'] and everything within it + * (for example, $form_state['values']['step1']['choice']) to be valid, so + * calls to form_set_error('step1', $message) or + * form_set_error('step1][choice', $message) will prevent the submit handlers + * from running, and result in the error message being displayed to the user. + * However, calls to form_set_error('step2', $message) and + * form_set_error('step2][groupX][choiceY', $message) will be suppressed, + * resulting in the message not being displayed to the user, and the submit + * handlers will run despite $form_state['values']['step2'] and + * $form_state['values']['step2']['groupX']['choiceY'] containing invalid + * values. Errors for an invalid $form_state['values']['foo'] will be + * suppressed, but errors flagging invalid values for + * $form_state['values']['foo']['bar'] and everything within it will be + * flagged and submission prevented. + * + * Partial form validation is implemented by suppressing errors rather than by + * skipping the input processing and validation steps entirely, because some + * forms have button-level submit handlers that call Drupal API functions that + * assume that certain data exists within $form_state['values'], and while not + * doing anything with that data that requires it to be valid, PHP errors + * would be triggered if the input processing and validation steps were fully + * skipped. + * @see http://drupal.org/node/370537 + * @see http://drupal.org/node/763376 + * * @param $name * The name of the form element. If the #parents property of your form * element is array('foo', 'bar', 'baz') then you may set an error on 'foo' @@ -1079,54 +1250,7 @@ function form_execute_handlers($type, &$form, &$form_state) { * The error message to present to the user. * @param $limit_validation_errors * Internal use only. The #limit_validation_errors property of the clicked - * button if it exists. Multistep forms not wanting to validate the whole form - * can set the #limit_validation_errors property on buttons to avoid - * validation errors of some elements preventing the button's submit handlers - * from running. For example, pressing the "Previous" button should not fire - * validation errors just because the current step has invalid values. AJAX is - * another typical example. - * If this property is set on the clicked button, the button must also define - * its #submit property and those handlers will be executed even if there is - * invalid input, so extreme care should be taken with respect to what is - * performed by them. This is typically not a problem with buttons like - * "Previous" or "Add more" that do not invoke persistent storage of the - * submitted form values. - * Do not use the #limit_validation_errors property on buttons that trigger - * saving of form values to the database. - * The #limit_validation_errors property is a list of "sections" within - * $form_state['values'] that must contain valid values. Each "section" is an - * array with the ordered set of keys needed to reach that part of - * $form_state['values'] (i.e., the #parents property of the element). - * For example: - * @code - * $form['actions']['previous']['#limit_validation_errors'] = array( - * array('step1'), - * array('foo', 'bar'), - * ); - * @endcode - * This will require $form_state['values']['step1'] and everything within it - * (for example, $form_state['values']['step1']['choice']) to be valid, so - * calls to form_set_error('step1', $message) or - * form_set_error('step1][choice', $message) will prevent the submit handlers - * from running, and result in the error message being displayed to the user. - * However, calls to form_set_error('step2', $message) and - * form_set_error('step2][groupX][choiceY', $message) will be suppressed, - * resulting in the message not being displayed to the user, and the submit - * handlers will run despite $form_state['values']['step2'] and - * $form_state['values']['step2']['groupX']['choiceY'] containing invalid - * values. Errors for an invalid $form_state['values']['foo'] will be - * suppressed, but errors for invalid values for - * $form_state['values']['foo']['bar'] and everything within it will be - * recorded. If the button doesn't need any user input to be valid, then the - * #limit_validation_errors can be set to an empty array, in which case, all - * calls to form_set_error() will be suppressed. - * Partial form validation is implemented by suppressing errors rather than by - * skipping the input processing and validation steps entirely, because some - * forms have button-level submit handlers that call Drupal API functions that - * assume that certain data exists within $form_state['values'], and while not - * doing anything with that data that requires it to be valid, PHP errors - * would be triggered if the input processing and validation steps were fully - * skipped. See http://drupal.org/node/370537. + * button, if it exists. * * @return * Return value is for internal use only. To get a list of errors, use @@ -1213,10 +1337,83 @@ function form_error(&$element, $message = '') { } /** - * Walk through the structured form array, adding any required - * properties to each element and mapping the incoming input - * data to the proper elements. Also, execute any #process handlers - * attached to a specific element. + * Walk through the structured form array, adding any required properties to + * each element and mapping the incoming input data to the proper elements. + * Also, execute any #process handlers attached to a specific element. + * + * This is one of the three primary functions that recursively iterates a form + * array. This one does it for completing the form building process. The other + * two are _form_validate() (invoked via drupal_validate_form() and used to + * invoke validation logic for each element) and drupal_render() (for rendering + * each element). Each of these three pipelines provides ample opportunity for + * modules to customize what happens. For example, during this function's life + * cycle, the following functions get called for each element: + * - $element['#value_callback']: A function that implements how user input is + * mapped to an element's #value property. This defaults to a function named + * 'form_type_TYPE_value' where TYPE is $element['#type']. + * - $element['#process']: An array of functions called after user input has + * been mapped to the element's #value property. These functions can be used + * to dynamically add child elements: for example, for the 'date' element + * type, one of the functions in this array is form_process_date(), which adds + * the individual 'year', 'month', 'day', etc. child elements. These functions + * can also be used to set additional properties or implement special logic + * other than adding child elements: for example, for the 'fieldset' element + * type, one of the functions in this array is form_process_fieldset(), which + * adds the attributes and JavaScript needed to make the fieldset collapsible + * if the #collapsible property is set. The #process functions are called in + * preorder traversal, meaning they are called for the parent element first, + * then for the child elements. + * - $element['#after_build']: An array of functions called after form_builder() + * is done with its processing of the element. These are called in postorder + * traversal, meaning they are called for the child elements first, then for + * the parent element. + * There are similar properties containing callback functions invoked by + * _form_validate() and drupal_render(), appropriate for those operations. + * + * Developers are strongly encouraged to integrate the functionality needed by + * their form or module within one of these three pipelines, using the + * appropriate callback property, rather than implementing their own recursive + * traversal of a form array. This facilitates proper integration between + * multiple modules. For example, module developers are familiar with the + * relative order in which hook_form_alter() implementations and #process + * functions run. A custom traversal function that affects the building of a + * form is likely to not integrate with hook_form_alter() and #process in the + * expected way. Also, deep recursion within PHP is both slow and memory + * intensive, so it is best to minimize how often it's done. + * + * As stated above, each element's #process functions are executed after its + * #value has been set. This enables those functions to execute conditional + * logic based on the current value. However, all of form_builder() runs before + * drupal_validate_form() is called, so during #process function execution, the + * element's #value has not yet been validated, so any code that requires + * validated values must reside within a submit handler. + * + * As a security measure, user input is used for an element's #value only if the + * element exists within $form, is not disabled (as per the #disabled property), + * and can be accessed (as per the #access property, except that forms submitted + * using drupal_form_submit() bypass #access restrictions). When user input is + * ignored due to #disabled and #access restrictions, the element's default + * value is used. + * + * Because of the preorder traversal, where #process functions of an element run + * before user input for its child elements is processed, and because of the + * Form API security of user input processing with respect to #access and + * #disabled described above, this generally means that #process functions + * should not use an element's (unvalidated) #value to affect the #disabled or + * #access of child elements. Use-cases where a developer may be tempted to + * implement such conditional logic usually fall into one of two categories: + * - Where user input from the current submission must affect the structure of a + * form, including properties like #access and #disabled that affect how the + * next submission needs to be processed, a multi-step workflow is needed. + * This is most commonly implemented with a submit handler setting persistent + * data within $form_state based on *validated* values in + * $form_state['values'] and setting $form_state['rebuild']. The form building + * functions must then be implmented to use the $form_state data to rebuild + * the form with the structure appropriate for the new state. + * - Where user input must affect the rendering of the form without affecting + * its structure, the necessary conditional rendering logic should reside + * within functions that run during the rendering phase (#pre_render, #theme, + * #theme_wrappers, and #post_render). * * @param $form_id * A unique string identifying the form for validation, submission, @@ -1448,19 +1645,20 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) { } } + // With JavaScript or other easy hacking, input can be submitted even for + // elements with #access=FALSE or #disabled=TRUE. For security, these must + // not be processed. Forms that set #disabled=TRUE on an element do not + // expect input for the element, and even forms submitted with + // drupal_form_submit() must not be able to get around this. Forms that set + // #access=FALSE on an element usually allow access for some users, so forms + // submitted with drupal_form_submit() may bypass access restriction and be + // treated as high-privelege users instead. + $process_input = empty($element['#disabled']) && ($form_state['programmed'] || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access']))); + // Set the element's #value property. if (!isset($element['#value']) && !array_key_exists('#value', $element)) { $value_callback = !empty($element['#value_callback']) ? $element['#value_callback'] : 'form_type_' . $element['#type'] . '_value'; - - // With JavaScript or other easy hacking, input can be submitted even for - // elements with #access=FALSE or #disabled=TRUE. For security, these must - // not be processed. Forms that set #disabled=TRUE on an element do not - // expect input for the element, and even forms submitted with - // drupal_form_submit() must not be able to get around this. Forms that set - // #access=FALSE on an element usually allow access for some users, so forms - // submitted with drupal_form_submit() may bypass access restriction and be - // treated as high-privelege users instead. - if (empty($element['#disabled']) && ($form_state['programmed'] || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access'])))) { + if ($process_input) { // Get the input for the current element. NULL values in the input need to // be explicitly distinguished from missing input. (see below) $input = $form_state['input']; @@ -1523,18 +1721,10 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) { } // Determine which element (if any) triggered the submission of the form and - // keep track of all the buttons in the form for form_state_values_clean(). - // @todo We need to add a #access check here, so that someone can't fake the - // click of a button they shouldn't have access to, but first we need to - // fix file.module's managed_file element pipeline to handle the click of - // the remove button in a submit handler instead of in a #process function. - // During the first run of form_builder() after the form is submitted, - // #process functions need to return the expanded element with child - // elements' #access properties matching what they were when the form was - // displayed to the user, since that is what we are processing input for. - // Changes to the form (like toggling the upload/remove button) need to wait - // until form rebuild: http://drupal.org/node/736298. - if (!empty($form_state['input'])) { + // keep track of all the clickable buttons in the form for + // form_state_values_clean(). Enforce the same input processing restrictions + // as above. + if ($process_input) { // Detect if the element triggered the submission via AJAX. if (_form_element_triggered_scripted_submission($element, $form_state)) { $form_state['triggering_element'] = $element; @@ -1547,11 +1737,7 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) { // All buttons in the form need to be tracked for // form_state_values_clean() and for the form_builder() code that handles // a form submission containing no button information in $_POST. - // @todo When #access is checked in an outer if statement (see above), it - // won't need to be checked here. - if ($form_state['programmed'] || !isset($element['#access']) || $element['#access']) { - $form_state['buttons'][] = $element; - } + $form_state['buttons'][] = $element; if (_form_button_was_clicked($element, $form_state)) { $form_state['triggering_element'] = $element; } @@ -1774,11 +1960,8 @@ function form_type_checkboxes_value($element, $input = FALSE) { } return $value; } - // If the checkboxes are enabled, and NULL input is submitted, it means - // they're intentionally unchecked, so we need to return an empty array to - // prevent the default value from being used. - elseif (!isset($input) && empty($element['#disabled'])) { - return array(); + else { + return is_array($input) ? drupal_map_assoc($input) : array(); } } @@ -1921,12 +2104,32 @@ function _form_set_value(&$form_values, $element, $parents, $value) { } } +/** + * Allows PHP array processing of multiple select options with the same value. + * + * Used for form select elements which need to validate HTML option groups + * and multiple options which may return the same value. Associative PHP arrays + * cannot handle these structures, since they share a common key. + * + * @param $array + * The form options array to process. + * + * @return + * An array with all hierarchical elements flattened to a single array. + */ function form_options_flatten($array) { // Always reset static var when first entering the recursion. drupal_static_reset('_form_options_flatten'); return _form_options_flatten($array); } +/** + * Helper function for form_options_flatten(). + * + * Iterates over arrays which may share common values and produces a flat + * array that has removed duplicate keys. Also handles cases where objects + * are passed as array values. + */ function _form_options_flatten($array) { $return = &drupal_static(__FUNCTION__); @@ -1969,7 +2172,7 @@ function theme_select($variables) { } /** - * Convert a select form element's options array into an HTML. + * Converts a select form element's options array into an HTML. * * @param $element * An associative array containing the properties of the element. @@ -2896,7 +3099,12 @@ function theme_form($variables) { $element = $variables['element']; // Anonymous div to satisfy XHTML compliance. $action = $element['#action'] ? 'action="' . check_url($element['#action']) . '" ' : ''; - return '<form ' . $action . ' accept-charset="UTF-8" method="' . $element['#method'] . '" id="' . $element['#id'] . '"' . drupal_attributes($element['#attributes']) . ">\n<div>" . $element['#children'] . "\n</div></form>\n"; + + if (empty($element['#attributes']['accept-charset'])) { + $element['#attributes']['accept-charset'] = "UTF-8"; + } + + return '<form '. $action .' method="'. $element['#method'] .'" id="'. $element['#id'] .'"'. drupal_attributes($element['#attributes']) .">\n<div>". $element['#children'] ."\n</div></form>\n"; } /** @@ -3193,7 +3401,7 @@ function _form_set_class(&$element, $class = array()) { * on the progress of the ongoing operations. * * The API is primarily designed to integrate nicely with the Form API - * workflow, but can also be used by non-FAPI scripts (like update.php) + * workflow, but can also be used by non-Form API scripts (like update.php) * or even simple page callbacks (which should probably be used sparingly). * * Example: @@ -3505,7 +3713,7 @@ function &batch_get() { * Populates a job queue with the operations of a batch set. * * Depending on whether the batch is progressive or not, the BatchQueue or - * BatchStaticQueue handler classes will be used. + * BatchMemoryQueue handler classes will be used. * * @param $batch * The batch array. diff --git a/includes/install.core.inc b/includes/install.core.inc index 5fb86f66..923e8668 100644 --- a/includes/install.core.inc +++ b/includes/install.core.inc @@ -1,5 +1,5 @@ <?php -// $Id: install.core.inc,v 1.19 2010/05/18 18:11:13 dries Exp $ +// $Id: install.core.inc,v 1.25 2010/07/02 15:32:10 dries Exp $ /** * @file @@ -211,6 +211,17 @@ function install_state_defaults() { * modified with information gleaned from the beginning of the page request. */ function install_begin_request(&$install_state) { + // Add any installation parameters passed in via the URL. + $install_state['parameters'] += $_GET; + + // Validate certain core settings that are used throughout the installation. + if (!empty($install_state['parameters']['profile'])) { + $install_state['parameters']['profile'] = preg_replace('/[^a-zA-Z_0-9]/', '', $install_state['parameters']['profile']); + } + if (!empty($install_state['parameters']['locale'])) { + $install_state['parameters']['locale'] = preg_replace('/[^a-zA-Z_0-9\-]/', '', $install_state['parameters']['locale']); + } + // Allow command line scripts to override server variables used by Drupal. require_once DRUPAL_ROOT . '/includes/bootstrap.inc'; if (!$install_state['interactive']) { @@ -299,17 +310,6 @@ function install_begin_request(&$install_state) { // Modify the installation state as appropriate. $install_state['completed_task'] = $task; $install_state['database_tables_exist'] = !empty($task); - - // Add any installation parameters passed in via the URL. - $install_state['parameters'] += $_GET; - - // Validate certain core settings that are used throughout the installation. - if (!empty($install_state['parameters']['profile'])) { - $install_state['parameters']['profile'] = preg_replace('/[^a-zA-Z_0-9]/', '', $install_state['parameters']['profile']); - } - if (!empty($install_state['parameters']['locale'])) { - $install_state['parameters']['locale'] = preg_replace('/[^a-zA-Z_0-9\-]/', '', $install_state['parameters']['locale']); - } } /** @@ -700,7 +700,7 @@ function install_display_output($output, $install_state) { $active_task = $install_state['installation_finished'] ? NULL : $install_state['active_task']; drupal_add_region_content('sidebar_first', theme('task_list', array('items' => install_tasks_to_display($install_state), 'active' => $active_task))); } - print theme($install_state['database_tables_exist'] ? 'maintenance_page' : 'install_page', array('content' => $output)); + print theme('install_page', array('content' => $output)); exit; } @@ -800,7 +800,7 @@ function install_verify_completed_task() { * Verifies the existing settings in settings.php. */ function install_verify_settings() { - global $db_prefix, $databases; + global $databases; // Verify existing settings (if any). if (!empty($databases) && install_verify_pdo()) { @@ -834,7 +834,7 @@ function install_verify_pdo() { * The form API definition for the database configuration form. */ function install_settings_form($form, &$form_state, &$install_state) { - global $databases, $db_prefix; + global $databases; $profile = $install_state['parameters']['profile']; $install_locale = $install_state['parameters']['locale']; @@ -945,6 +945,10 @@ function install_settings_form($form, &$form_state, &$install_state) { * Form API validate for install_settings form. */ function install_settings_form_validate($form, &$form_state) { + // TODO: remove when PIFR will be updated to use 'db_prefix' instead of + // 'prefix' in the database settings form. + $form_state['values']['prefix'] = $form_state['values']['db_prefix']; + form_set_value($form['_database'], $form_state['values'], $form_state); $errors = install_database_errors($form_state['values'], $form_state['values']['settings_file']); foreach ($errors as $name => $message) { @@ -959,8 +963,8 @@ function install_database_errors($database, $settings_file) { global $databases; $errors = array(); // Verify the table prefix. - if (!empty($database['db_prefix']) && is_string($database['db_prefix']) && !preg_match('/^[A-Za-z0-9_.]+$/', $database['db_prefix'])) { - $errors['db_prefix'] = st('The database table prefix you have entered, %db_prefix, is invalid. The table prefix can only contain alphanumeric characters, periods, or underscores.', array('%db_prefix' => $database['db_prefix'])); + if (!empty($database['prefix']) && is_string($database['prefix']) && !preg_match('/^[A-Za-z0-9_.]+$/', $database['prefix'])) { + $errors['prefix'] = st('The database table prefix you have entered, %prefix, is invalid. The table prefix can only contain alphanumeric characters, periods, or underscores.', array('%prefix' => $database['prefix'])); } if (!empty($database['port']) && !is_numeric($database['port'])) { @@ -1000,16 +1004,12 @@ function install_database_errors($database, $settings_file) { function install_settings_form_submit($form, &$form_state) { global $install_state; - $database = array_intersect_key($form_state['values']['_database'], array_flip(array('driver', 'database', 'username', 'password', 'host', 'port'))); + $database = array_intersect_key($form_state['values']['_database'], array_flip(array('driver', 'database', 'username', 'password', 'host', 'port', 'prefix'))); // Update global settings array and save. $settings['databases'] = array( 'value' => array('default' => array('default' => $database)), 'required' => TRUE, ); - $settings['db_prefix'] = array( - 'value' => $form_state['values']['db_prefix'], - 'required' => TRUE, - ); $settings['drupal_hash_salt'] = array( 'value' => drupal_hash_base64(drupal_random_bytes(55)), 'required' => TRUE, @@ -1348,6 +1348,15 @@ function install_profile_modules(&$install_state) { $modules = variable_get('install_profile_modules', array()); $files = system_rebuild_module_data(); variable_del('install_profile_modules'); + + // Install dependencies first. + $modules = array_flip($modules); + foreach ($modules as $module => $weight) { + $modules[$module] = $files[$module]->sort; + } + arsort($modules); + $modules = array_keys($modules); + $operations = array(); foreach ($modules as $module) { $operations[] = array('_install_module_batch', array($module, $files[$module]->info['name'])); @@ -1531,6 +1540,7 @@ function install_check_requirements($install_state) { $writable = FALSE; $conf_path = './' . conf_path(FALSE, TRUE); $settings_file = $conf_path . '/settings.php'; + $default_settings_file = './sites/default/default.settings.php'; $file = $conf_path; $exists = FALSE; // Verify that the directory exists. @@ -1538,19 +1548,30 @@ function install_check_requirements($install_state) { // Check to make sure a settings.php already exists. $file = $settings_file; if (drupal_verify_install_file($settings_file, FILE_EXIST)) { - $exists = TRUE; // If it does, make sure it is writable. $writable = drupal_verify_install_file($settings_file, FILE_READABLE|FILE_WRITABLE); $exists = TRUE; } } + // If default.settings.php does not exist, or is not readable, throw an + // error. + if (!drupal_verify_install_file($default_settings_file, FILE_EXIST|FILE_READABLE)) { + $requirements['default settings file exists'] = array( + 'title' => st('Default settings file'), + 'value' => st('The default settings file does not exist.'), + 'severity' => REQUIREMENT_ERROR, + 'description' => st('The @drupal installer requires that the %default-file file not be modified in any way from the original download.', array('@drupal' => drupal_install_profile_distribution_name(), '%default-file' => $default_settings_file)), + ); + } + + // If settings.php does not exist, throw an error. if (!$exists) { $requirements['settings file exists'] = array( 'title' => st('Settings file'), 'value' => st('The settings file does not exist.'), 'severity' => REQUIREMENT_ERROR, - 'description' => st('The @drupal installer requires that you create a settings file as part of the installation process. Copy the %default_file file to %file. More details about installing Drupal are available in <a href="@install_txt">INSTALL.txt</a>.', array('@drupal' => drupal_install_profile_distribution_name(), '%file' => $file, '%default_file' => $conf_path . '/default.settings.php', '@install_txt' => base_path() . 'INSTALL.txt')), + 'description' => st('The @drupal installer requires that you create a settings file as part of the installation process. Copy the %default_file file to %file. More details about installing Drupal are available in <a href="@install_txt">INSTALL.txt</a>.', array('@drupal' => drupal_install_profile_distribution_name(), '%file' => $file, '%default_file' => $default_settings_file, '@install_txt' => base_path() . 'INSTALL.txt')), ); } else { @@ -1558,6 +1579,7 @@ function install_check_requirements($install_state) { 'title' => st('Settings file'), 'value' => st('The %file file exists.', array('%file' => $file)), ); + // If settings.php is not writable, throw an error. if (!$writable) { $requirements['settings file writable'] = array( 'title' => st('Settings file'), diff --git a/includes/install.inc b/includes/install.inc index 9095e8e7..4397211a 100644 --- a/includes/install.inc +++ b/includes/install.inc @@ -1,5 +1,5 @@ <?php -// $Id: install.inc,v 1.135 2010/05/18 18:11:13 dries Exp $ +// $Id: install.inc,v 1.136 2010/06/21 02:27:47 webchick Exp $ /** * Indicates that a module has not been installed yet. @@ -187,8 +187,18 @@ function drupal_set_installed_schema_version($module, $version) { * @see install_profile_info() */ function drupal_install_profile_distribution_name() { - global $install_state; - return $install_state['profile_info']['distribution_name']; + // During installation, the profile information is stored in the global + // installation state (it might not be saved anywhere yet). + if (drupal_installation_attempted()) { + global $install_state; + return $install_state['profile_info']['distribution_name']; + } + // At all other times, we load the profile via standard methods. + else { + $profile = drupal_get_profile(); + $info = install_profile_info($profile); + return $info['distribution_name']; + } } /** diff --git a/includes/iso.inc b/includes/iso.inc index 9af86391..7206497d 100644 --- a/includes/iso.inc +++ b/includes/iso.inc @@ -1,5 +1,5 @@ <?php -// $Id: iso.inc,v 1.10 2010/04/30 08:15:56 dries Exp $ +// $Id: iso.inc,v 1.11 2010/06/23 19:06:10 dries Exp $ /** * @file @@ -386,7 +386,7 @@ function _locale_get_predefined_list() { 'lv' => array('Latvian', 'Latviešu'), 'mg' => array('Malagasy'), 'mh' => array('Marshallese'), - 'mi' => array('Maori'), + 'mi' => array('Māori'), 'mk' => array('Macedonian', 'Македонски'), 'ml' => array('Malayalam', 'മലയാളം'), 'mn' => array('Mongolian'), diff --git a/includes/locale.inc b/includes/locale.inc index a9c8281b..08c54928 100644 --- a/includes/locale.inc +++ b/includes/locale.inc @@ -1,5 +1,5 @@ <?php -// $Id: locale.inc,v 1.254 2010/05/01 08:12:22 dries Exp $ +// $Id: locale.inc,v 1.256 2010/06/03 13:57:41 dries Exp $ /** * @file @@ -126,9 +126,14 @@ function locale_language_from_user($languages) { function locale_language_from_session($languages) { $param = variable_get('locale_language_negotiation_session_param', 'language'); - // Request parameter. + // Request parameter: we need to update the session parameter only if we have + // an authenticated user. if (isset($_GET[$param]) && isset($languages[$langcode = $_GET[$param]])) { - return $_SESSION[$param] = $langcode; + global $user; + if ($user->uid) { + $_SESSION[$param] = $langcode; + } + return $langcode; } // Session parameter. @@ -1637,35 +1642,12 @@ function _locale_rebuild_js($langcode = NULL) { } // Construct the array for JavaScript translations. - // We sort on plural so that we have all plural forms before singular forms. - $result = db_query("SELECT s.lid, s.source, t.plid, t.plural, t.translation FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location LIKE '%.js%' AND s.textgroup = :textgroup ORDER BY t.plural DESC", array(':language' => $language->language, ':textgroup' => 'default')); + // Only add strings with a translation to the translations array. + $result = db_query("SELECT s.lid, s.source, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location LIKE '%.js%' AND s.textgroup = :textgroup AND t.translation IS NOT NULL", array(':language' => $language->language, ':textgroup' => 'default')); - $translations = $plurals = array(); + $translations = array(); foreach ($result as $data) { - // Only add this to the translations array when there is actually a translation. - if (!empty($data->translation)) { - if ($data->plural) { - // When the translation is a plural form, first add it to another array and - // wait for the singular (parent) translation. - if (!isset($plurals[$data->plid])) { - $plurals[$data->plid] = array($data->plural => $data->translation); - } - else { - $plurals[$data->plid] += array($data->plural => $data->translation); - } - } - elseif (isset($plurals[$data->lid])) { - // There are plural translations for this translation, so get them from - // the plurals array and add them to the final translations array. - $translations[$data->source] = array($data->plural => $data->translation) + $plurals[$data->lid]; - unset($plurals[$data->lid]); - } - else { - // There are no plural forms for this translation, so just add it to - // the translations array. - $translations[$data->source] = $data->translation; - } - } + $translations[$data->source] = $data->translation; } // Construct the JavaScript file, if there are translations. diff --git a/includes/mail.inc b/includes/mail.inc index e10bee72..145b5269 100644 --- a/includes/mail.inc +++ b/includes/mail.inc @@ -1,5 +1,5 @@ <?php -// $Id: mail.inc,v 1.31 2010/05/04 15:11:21 dries Exp $ +// $Id: mail.inc,v 1.32 2010/06/17 13:16:57 dries Exp $ /** * @file @@ -242,7 +242,7 @@ function drupal_mail_system($module, $key) { if (empty($instances[$class])) { $interfaces = class_implements($class); if (isset($interfaces['MailSystemInterface'])) { - $instances[$class] = new $class; + $instances[$class] = new $class(); } else { throw new Exception(t('Class %class does not implement interface %interface', array('%class' => $class, '%interface' => 'MailSystemInterface'))); diff --git a/includes/menu.inc b/includes/menu.inc index ce945481..71181889 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -1,5 +1,5 @@ <?php -// $Id: menu.inc,v 1.392 2010/05/17 18:47:25 dries Exp $ +// $Id: menu.inc,v 1.401 2010/07/07 08:05:01 webchick Exp $ /** * @file @@ -238,6 +238,11 @@ define('MENU_ACCESS_DENIED', 3); */ define('MENU_SITE_OFFLINE', 4); +/** + * Internal menu status code -- Everything is working fine. + */ +define('MENU_SITE_ONLINE', 5); + /** * @} End of "Menu status codes". */ @@ -447,21 +452,23 @@ function menu_get_item($path = NULL, $router_item = NULL) { * the result to the caller (FALSE). */ function menu_execute_active_handler($path = NULL, $deliver = TRUE) { - if (_menu_site_is_offline()) { - $page_callback_result = MENU_SITE_OFFLINE; - } - else { + // Check if site is offline. + $page_callback_result = _menu_site_is_offline() ? MENU_SITE_OFFLINE : MENU_SITE_ONLINE; + + // Allow other modules to change the site status but not the path because that + // would not change the global variable. hook_url_inbound_alter() can be used + // to change the path. Code later will not use the $read_only_path variable. + $read_only_path = !empty($path) ? $path : $_GET['q']; + drupal_alter('menu_site_status', $page_callback_result, $read_only_path); + + // Only continue if the site status is not set. + if ($page_callback_result == MENU_SITE_ONLINE) { // Rebuild if we know it's needed, or if the menu masks are missing which // occurs rarely, likely due to a race condition of multiple rebuilds. if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) { menu_rebuild(); } if ($router_item = menu_get_item($path)) { - // hook_menu_alter() lets modules control menu router information that - // doesn't depend on the details of a particular page request. - // Here, we want to give modules a chance to use request-time information - // to make alterations just for this request. - drupal_alter('menu_active_handler', $router_item, $path); if ($router_item['access']) { if ($router_item['include_file']) { require_once DRUPAL_ROOT . '/' . $router_item['include_file']; @@ -984,80 +991,37 @@ function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) { // If the static variable doesn't have the data, check {cache_menu}. $cache = cache_get($cid, 'cache_menu'); if ($cache && isset($cache->data)) { - // If the cache entry exists, it will just be the cid for the actual data. - // This avoids duplication of large amounts of data. - $cache = cache_get($cache->data, 'cache_menu'); - if ($cache && isset($cache->data)) { - $data = $cache->data; - } - } - // If the tree data was not in the cache, $data will be NULL. - if (!isset($data)) { - // Build the query using a LEFT JOIN since there is no match in - // {menu_router} for an external link. - $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC)); - $query->addTag('translatable'); - $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path'); - $query->fields('ml'); - $query->fields('m', array( - 'load_functions', - 'to_arg_functions', - 'access_callback', - 'access_arguments', - 'page_callback', - 'page_arguments', - 'delivery_callback', - 'title', - 'title_callback', - 'title_arguments', - 'theme_callback', - 'theme_arguments', - 'type', - 'description', - )); - for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) { - $query->orderBy('p' . $i, 'ASC'); - } - $query->condition('ml.menu_name', $menu_name); - if (isset($max_depth)) { - $query->condition('ml.depth', $max_depth, '<='); - } + // If the cache entry exists, it contains the parameters for + // menu_build_tree(). + $tree_parameters = $cache->data; + } + // If the tree data was not in the cache, build $tree_parameters. + if (!isset($tree_parameters)) { + $tree_parameters = array( + 'min_depth' => 1, + 'max_depth' => $max_depth, + ); if ($mlid) { // The tree is for a single item, so we need to match the values in its // p columns and 0 (the top level) with the plid values of other links. - $args = array(0); + $parents = array(0); for ($i = 1; $i < MENU_MAX_DEPTH; $i++) { - $args[] = $link["p$i"]; + if (!empty($link["p$i"])) { + $parents[] = $link["p$i"]; + } } - $args = array_unique($args); - $query->condition('ml.plid', $args, 'IN'); - $parents = $args; - $parents[] = $link['mlid']; + $tree_parameters['expanded'] = $parents; + $tree_parameters['active_trail'] = $parents; + $tree_parameters['active_trail'][] = $mlid; } - else { - // Get all links in this menu. - $parents = array(); - } - // Select the links from the table, and build an ordered array of links - // using the query result object. - $links = array(); - foreach ($query->execute() as $item) { - $links[] = $item; - } - $data['tree'] = menu_tree_data($links, $parents); - $data['node_links'] = array(); - menu_tree_collect_node_links($data['tree'], $data['node_links']); - // Cache the data, if it is not already in the cache. - $tree_cid = _menu_tree_cid($menu_name, $data); - if (!cache_get($tree_cid, 'cache_menu')) { - cache_set($tree_cid, $data, 'cache_menu'); - } - // Cache the cid of the (shared) data using the menu and item-specific cid. - cache_set($cid, $tree_cid, 'cache_menu'); + + // Cache the tree building parameters using the page-specific cid. + cache_set($cid, $tree_parameters, 'cache_menu'); } - // Check access for the current user to each item in the tree. - menu_tree_check_access($data['tree'], $data['node_links']); - $tree[$cid] = $data['tree']; + + // Build the tree using the parameters; the resulting tree will be cached + // by _menu_build_tree()). + $tree[$cid] = menu_build_tree($menu_name, $tree_parameters); } return $tree[$cid]; @@ -1096,23 +1060,25 @@ function menu_tree_page_data($menu_name, $max_depth = NULL) { // If the static variable doesn't have the data, check {cache_menu}. $cache = cache_get($cid, 'cache_menu'); if ($cache && isset($cache->data)) { - // If the cache entry exists, it will just be the cid for the actual data. - // This avoids duplication of large amounts of data. - $cache = cache_get($cache->data, 'cache_menu'); - if ($cache && isset($cache->data)) { - $data = $cache->data; - } + // If the cache entry exists, it contains the parameters for + // menu_build_tree(). + $tree_parameters = $cache->data; } - // If the tree data was not in the cache, $data will be NULL. - if (!isset($data)) { - // Build and run the query, and build the tree. + // If the tree data was not in the cache, build $tree_parameters. + if (!isset($tree_parameters)) { + $tree_parameters = array( + 'min_depth' => 1, + 'max_depth' => $max_depth, + ); + // If the item for the current page is accessible, build the tree + // parameters accordingly. if ($item['access']) { // Check whether a menu link exists that corresponds to the current path. $args[] = $item['href']; if (drupal_is_front_page()) { $args[] = '<front>'; } - $parents = db_select('menu_links') + $active_link = db_select('menu_links') ->fields('menu_links', array( 'p1', 'p2', @@ -1127,10 +1093,10 @@ function menu_tree_page_data($menu_name, $max_depth = NULL) { ->condition('link_path', $args, 'IN') ->execute()->fetchAssoc(); - if (empty($parents)) { + if (empty($active_link)) { // If no link exists, we may be on a local task that's not in the links. // TODO: Handle the case like a local task on a specific node in the menu. - $parents = db_select('menu_links') + $active_link = db_select('menu_links') ->fields('menu_links', array( 'p1', 'p2', @@ -1145,11 +1111,13 @@ function menu_tree_page_data($menu_name, $max_depth = NULL) { ->condition('link_path', $item['tab_root']) ->execute()->fetchAssoc(); } + // We always want all the top-level links with plid == 0. - $parents[] = '0'; + $active_link[] = '0'; + + // Use array_values() so that the indices are numeric. + $parents = $active_link = array_unique(array_values($active_link)); - // Use array_values() so that the indices are numeric for array_merge(). - $args = $parents = array_unique(array_values($parents)); $expanded = variable_get('menu_expanded', array()); // Check whether the current menu has any links set to be expanded. if (in_array($menu_name, $expanded)) { @@ -1161,72 +1129,31 @@ function menu_tree_page_data($menu_name, $max_depth = NULL) { ->condition('menu_name', $menu_name) ->condition('expanded', 1) ->condition('has_children', 1) - ->condition('plid', $args, 'IN') - ->condition('mlid', $args, 'NOT IN') + ->condition('plid', $parents, 'IN') + ->condition('mlid', $parents, 'NOT IN') ->execute(); $num_rows = FALSE; foreach ($result as $item) { - $args[] = $item['mlid']; + $parents[] = $item['mlid']; $num_rows = TRUE; } } while ($num_rows); } + $tree_parameters['expanded'] = $parents; + $tree_parameters['active_trail'] = $active_link; } + // Otherwise, only show the top-level menu items when access is denied. else { - // Show only the top-level menu items when access is denied. - $args = array(0); - $parents = array(); - } - // Select the links from the table, and recursively build the tree. We - // LEFT JOIN since there is no match in {menu_router} for an external - // link. - $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC)); - $query->addTag('translatable'); - $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path'); - $query->fields('ml'); - $query->fields('m', array( - 'load_functions', - 'to_arg_functions', - 'access_callback', - 'access_arguments', - 'page_callback', - 'page_arguments', - 'delivery_callback', - 'title', - 'title_callback', - 'title_arguments', - 'theme_callback', - 'theme_arguments', - 'type', - 'description', - )); - for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) { - $query->orderBy('p' . $i, 'ASC'); + $tree_parameters['expanded'] = array(0); } - $query->condition('ml.menu_name', $menu_name); - $query->condition('ml.plid', $args, 'IN'); - if (isset($max_depth)) { - $query->condition('ml.depth', $max_depth, '<='); - } - // Build an ordered array of links using the query result object. - $links = array(); - foreach ($query->execute() as $item) { - $links[] = $item; - } - $data['tree'] = menu_tree_data($links, $parents); - $data['node_links'] = array(); - menu_tree_collect_node_links($data['tree'], $data['node_links']); - // Cache the data, if it is not already in the cache. - $tree_cid = _menu_tree_cid($menu_name, $data); - if (!cache_get($tree_cid, 'cache_menu')) { - cache_set($tree_cid, $data, 'cache_menu'); - } - // Cache the cid of the (shared) data using the page-specific cid. - cache_set($cid, $tree_cid, 'cache_menu'); + + // Cache the tree building parameters using the page-specific cid. + cache_set($cid, $tree_parameters, 'cache_menu'); } - // Check access for the current user to each item in the tree. - menu_tree_check_access($data['tree'], $data['node_links']); - $tree[$cid] = $data['tree']; + + // Build the tree using the parameters; the resulting tree will be cached + // by _menu_build_tree(). + $tree[$cid] = menu_build_tree($menu_name, $tree_parameters); } return $tree[$cid]; } @@ -1235,10 +1162,116 @@ function menu_tree_page_data($menu_name, $max_depth = NULL) { } /** - * Helper function - compute the real cache ID for menu tree data. + * Build a menu tree, translate links, and check access. + * + * @param $menu_name + * The name of the menu. + * @param $parameters + * (optional) An associative array of build parameters. Possible keys: + * - expanded: An array of parent link ids to return only menu links that are + * children of one of the plids in this list. If empty, the whole menu tree + * is built. + * - active_trail: An array of mlids, representing the coordinates of the + * currently active menu link. + * - min_depth: The minimum depth of menu links in the resulting tree. + * Defaults to 1, which is the default to build a whole tree for a menu, i.e. + * excluding menu container itself. + * - max_depth: The maximum depth of menu links in the resulting tree. + * + * @return + * A fully built menu tree. */ -function _menu_tree_cid($menu_name, $data) { - return 'links:' . $menu_name . ':tree-data:' . $GLOBALS['language']->language . ':' . hash('sha256', serialize($data)); +function menu_build_tree($menu_name, array $parameters = array()) { + // Build the menu tree. + $data = _menu_build_tree($menu_name, $parameters); + // Check access for the current user to each item in the tree. + menu_tree_check_access($data['tree'], $data['node_links']); + return $data['tree']; +} + +/** + * Build a menu tree. + * + * This function may be used build the data for a menu tree only, for example + * to further massage the data manually before further processing happens. + * menu_tree_check_access() needs to be invoked afterwards. + * + * @see menu_build_tree() + */ +function _menu_build_tree($menu_name, array $parameters = array()) { + // Static cache of already built menu trees. + $trees = &drupal_static(__FUNCTION__, array()); + + // Build the cache id; sort parents to prevent duplicate storage and remove + // default parameter values. + if (isset($parameters['expanded'])) { + sort($parameters['expanded']); + } + $tree_cid = 'links:' . $menu_name . ':tree-data:' . $GLOBALS['language']->language . ':' . hash('sha256', serialize($parameters)); + + // If we do not have this tree in the static cache, check {cache_menu}. + if (!isset($trees[$tree_cid])) { + $cache = cache_get($tree_cid, 'cache_menu'); + if ($cache && isset($cache->data)) { + $trees[$tree_cid] = $cache->data; + } + } + + if (!isset($trees[$tree_cid])) { + // Select the links from the table, and recursively build the tree. We + // LEFT JOIN since there is no match in {menu_router} for an external + // link. + $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC)); + $query->addTag('translatable'); + $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path'); + $query->fields('ml'); + $query->fields('m', array( + 'load_functions', + 'to_arg_functions', + 'access_callback', + 'access_arguments', + 'page_callback', + 'page_arguments', + 'delivery_callback', + 'title', + 'title_callback', + 'title_arguments', + 'theme_callback', + 'theme_arguments', + 'type', + 'description', + )); + for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) { + $query->orderBy('p' . $i, 'ASC'); + } + $query->condition('ml.menu_name', $menu_name); + if (!empty($parameters['expanded'])) { + $query->condition('ml.plid', $parameters['expanded'], 'IN'); + } + $min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1); + if ($min_depth != 1) { + $query->condition('ml.depth', $min_depth, '>='); + } + if (isset($parameters['max_depth'])) { + $query->condition('ml.depth', $parameters['max_depth'], '<='); + } + + // Build an ordered array of links using the query result object. + $links = array(); + foreach ($query->execute() as $item) { + $links[] = $item; + } + $active_link = (isset($parameters['active_trail']) ? $parameters['active_trail'] : array()); + $data['tree'] = menu_tree_data($links, $active_link, $min_depth); + $data['node_links'] = array(); + menu_tree_collect_node_links($data['tree'], $data['node_links']); + + // Cache the data, if it is not already in the cache. + cache_set($tree_cid, $data, 'cache_menu'); + $trees[$tree_cid] = $data; + } + + return $trees[$tree_cid]; } /** @@ -1277,10 +1310,10 @@ function menu_tree_check_access(&$tree, $node_links = array()) { if ($node_links) { $nids = array_keys($node_links); - $select = db_select('node'); - $select->addField('node', 'nid'); - $select->condition('status', 1); - $select->condition('nid', $nids, 'IN'); + $select = db_select('node', 'n'); + $select->addField('n', 'nid'); + $select->condition('n.status', 1); + $select->condition('n.nid', $nids, 'IN'); $select->addTag('node_access'); $nids = $select->execute()->fetchCol(); foreach ($nids as $nid) { @@ -2260,6 +2293,7 @@ function menu_cache_clear_all() { * Resets the menu system static cache. */ function menu_reset_static_cache() { + drupal_static_reset('_menu_build_tree'); drupal_static_reset('menu_tree'); drupal_static_reset('menu_tree_all_data'); drupal_static_reset('menu_tree_page_data'); @@ -2307,7 +2341,8 @@ function menu_rebuild() { } } catch (Exception $e) { - $transaction->rollback('menu', $e->getMessage(), array(), WATCHDOG_ERROR); + $transaction->rollback(); + watchdog_exception('menu', $e); } lock_release('menu_rebuild'); @@ -3364,15 +3399,7 @@ function _menu_site_is_offline($check_only = FALSE) { } } else { - // Anonymous users get a FALSE at the login prompt, TRUE otherwise. - if (user_is_anonymous()) { - return ($_GET['q'] != 'user' && $_GET['q'] != 'user/login'); - } - // Logged in users are unprivileged here, so they are logged out. - if (!$check_only) { - require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'user') . '/user.pages.inc'; - user_logout(); - } + return TRUE; } } return FALSE; diff --git a/includes/module.inc b/includes/module.inc index 380d4c8f..73986218 100644 --- a/includes/module.inc +++ b/includes/module.inc @@ -1,5 +1,5 @@ <?php -// $Id: module.inc,v 1.192 2010/05/09 13:49:33 dries Exp $ +// $Id: module.inc,v 1.195 2010/06/26 12:34:44 dries Exp $ /** * @file @@ -173,6 +173,7 @@ function system_list($type) { */ function system_list_reset() { drupal_static_reset('system_list'); + drupal_static_reset('list_themes'); cache_clear_all('bootstrap_modules', 'cache_bootstrap'); cache_clear_all('system_list', 'cache_bootstrap'); } @@ -285,8 +286,25 @@ function module_load_all_includes($type, $name = NULL) { } /** - * Enable a given list of modules. + * Enables or installs a given list of modules. * + * Definitions: + * - "Enabling" is the process of activating a module for use by Drupal. + * - "Disabling" is the process of deactivating a module. + * - "Installing" is the process of enabling it for the first time or after it + * has been uninstalled. + * - "Uninstalling" is the process of removing all traces of a module. + * + * Order of events: + * - Gather and add module dependencies to $module_list (if applicable). + * - For each module that is being enabled: + * - Install module schema and update system registries and caches. + * - If the module is being enabled for the first time or had been + * uninstalled, invoke hook_install() and add it to the list of installed + * modules. + * - Invoke hook_enable(). + * - Invoke hook_modules_installed(). + * - Invoke hook_modules_enabled(). * @param $module_list * An array of module names. * @param $enable_dependencies @@ -296,6 +314,11 @@ function module_load_all_includes($type, $name = NULL) { * * @return * FALSE if one or more dependencies are missing, TRUE otherwise. + * + * @see hook_install() + * @see hook_enable() + * @see hook_modules_installed() + * @see hook_modules_enabled() */ function module_enable($module_list, $enable_dependencies = TRUE) { if ($enable_dependencies) { @@ -585,6 +608,9 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) { } if (!isset($implementations[$hook])) { + // The hook is not cached, so ensure that whether or not it has + // implementations, that the cache is updated at the end of the request. + $implementations['#write_cache'] = TRUE; $hook_info = module_hook_info(); $implementations[$hook] = array(); $list = module_list(FALSE, FALSE, $sort); @@ -592,8 +618,6 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) { $include_file = isset($hook_info[$hook]['group']) && module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']); if (module_hook($module, $hook)) { $implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE; - // We added something to the cache, so write it when we are done. - $implementations['#write_cache'] = TRUE; } } // Allow modules to change the weight of specific implementations but avoid diff --git a/includes/path.inc b/includes/path.inc index 98a59626..09c80cea 100644 --- a/includes/path.inc +++ b/includes/path.inc @@ -1,5 +1,5 @@ <?php -// $Id: path.inc,v 1.63 2010/05/18 18:26:30 dries Exp $ +// $Id: path.inc,v 1.64 2010/06/17 13:48:53 dries Exp $ /** * @file @@ -127,7 +127,6 @@ function drupal_lookup_path($action, $path = '', $path_language = NULL) { // isn't a path that has this alias elseif ($action == 'source' && !isset($cache['no_source'][$path_language][$path])) { // Look for the value $path within the cached $map - $source = ''; if (!isset($cache['map'][$path_language]) || !($source = array_search($path, $cache['map'][$path_language]))) { // Get the most fitting result falling back with alias without language if ($source = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", array( @@ -136,6 +135,7 @@ function drupal_lookup_path($action, $path = '', $path_language = NULL) { ':language_none' => LANGUAGE_NONE)) ->fetchField()) { $cache['map'][$path_language][$source] = $path; + return $source; } else { // We can't record anything into $map because we do not have a valid @@ -144,7 +144,6 @@ function drupal_lookup_path($action, $path = '', $path_language = NULL) { $cache['no_source'][$path_language][$path] = TRUE; } } - return $source; } } diff --git a/includes/registry.inc b/includes/registry.inc index 2d2ded4f..50d2da36 100644 --- a/includes/registry.inc +++ b/includes/registry.inc @@ -1,5 +1,5 @@ <?php -// $Id: registry.inc,v 1.30 2010/04/04 13:44:49 dries Exp $ +// $Id: registry.inc,v 1.32 2010/06/08 03:16:05 webchick Exp $ /** * @file @@ -56,46 +56,53 @@ function _registry_update() { $files["$filename"] = array('module' => '', 'weight' => 0); } - // Allow modules to manually modify the list of files before the registry - // parses them. The $modules array provides the .info file information, which - // includes the list of files registered to each module. Any files in the - // list can then be added to the list of files that the registry will parse, - // or modify attributes of a file. - drupal_alter('registry_files', $files, $modules); - foreach (registry_get_parsed_files() as $filename => $file) { - // Add the file creation and modification dates to those files we have - // already parsed. - if (isset($files[$filename])) { - $files[$filename]['filectime'] = $file['filectime']; - $files[$filename]['filemtime'] = $file['filemtime']; - } - else { - // Flush the registry of resources in files that are no longer on disc - // or are in files that no installed modules require to be parsed. - db_delete('registry') - ->condition('filename', $filename) - ->execute(); - db_delete('registry_file') - ->condition('filename', $filename) - ->execute(); + $transaction = db_transaction(); + try { + // Allow modules to manually modify the list of files before the registry + // parses them. The $modules array provides the .info file information, which + // includes the list of files registered to each module. Any files in the + // list can then be added to the list of files that the registry will parse, + // or modify attributes of a file. + drupal_alter('registry_files', $files, $modules); + foreach (registry_get_parsed_files() as $filename => $file) { + // Add the file creation and modification dates to those files we have + // already parsed. + if (isset($files[$filename])) { + $files[$filename]['filectime'] = $file['filectime']; + $files[$filename]['filemtime'] = $file['filemtime']; + } + else { + // Flush the registry of resources in files that are no longer on disc + // or are in files that no installed modules require to be parsed. + db_delete('registry') + ->condition('filename', $filename) + ->execute(); + db_delete('registry_file') + ->condition('filename', $filename) + ->execute(); + } } - } - $parsed_files = _registry_parse_files($files); + $parsed_files = _registry_parse_files($files); - $unchanged_resources = array(); - $lookup_cache = array(); - if ($cache = cache_get('lookup_cache', 'cache_bootstrap')) { - $lookup_cache = $cache->data; - } - foreach ($lookup_cache as $key => $file) { - // If the file for this cached resource is carried over unchanged from - // the last registry build, then we can safely re-cache it. - if ($file && in_array($file, array_keys($files)) && !in_array($file, $parsed_files)) { - $unchanged_resources[$key] = $file; + $unchanged_resources = array(); + $lookup_cache = array(); + if ($cache = cache_get('lookup_cache', 'cache_bootstrap')) { + $lookup_cache = $cache->data; + } + foreach ($lookup_cache as $key => $file) { + // If the file for this cached resource is carried over unchanged from + // the last registry build, then we can safely re-cache it. + if ($file && in_array($file, array_keys($files)) && !in_array($file, $parsed_files)) { + $unchanged_resources[$key] = $file; + } } + module_implements('', FALSE, TRUE); + _registry_check_code(REGISTRY_RESET_LOOKUP_CACHE); + } + catch (Exception $e) { + $transaction->rollback('registry', $e->getMessage(), array(), WATCHDOG_ERROR); + throw $e; } - module_implements('', FALSE, TRUE); - _registry_check_code(REGISTRY_RESET_LOOKUP_CACHE); // We have some unchanged resources, warm up the cache - no need to pay // for looking them up again. @@ -122,31 +129,37 @@ function registry_get_parsed_files() { */ function _registry_parse_files($files) { $parsed_files = array(); + $filetimes = array(); foreach ($files as $filename => $file) { if (file_exists($filename)) { - $filectime = filectime($filename); - $filemtime = filemtime($filename); + $filetimes[$filename] = array( + 'filectime' => filectime($filename), + 'filemtime' => filemtime($filename), + ); $modified_file = !isset($file['filectime']) || !isset($file['filemtime']) - || $filectime != $file['filectime'] || $filemtime != $file['filemtime']; + || $filetimes[$filename]['filectime'] != $file['filectime'] + || $filetimes[$filename]['filemtime'] != $file['filemtime']; if ($modified_file) { - $contents = file_get_contents($filename); - $parsed_files[] = $filename; - // We update the filectime/filemtime after we've saved the files resources - // rather than here, so if we don't make it through this rebuild, the next - // run will reparse the file. - _registry_parse_file($filename, $contents, $file['module'], $file['weight']); - db_merge('registry_file') - ->key(array('filename' => $filename)) - ->fields(array( - 'filectime' => $filectime, - 'filemtime' => $filemtime, - )) + // Delete registry entries for this file, so we can insert the new resources. + db_delete('registry') + ->condition('filename', $filename) ->execute(); + $parsed_files[$filename] = $file; } } } - return $parsed_files; + foreach ($parsed_files as $filename => $file) { + _registry_parse_file($filename, file_get_contents($filename), $file['module'], $file['weight']); + db_merge('registry_file') + ->key(array('filename' => $filename)) + ->fields(array( + 'filectime' => $filetimes[$filename]['filectime'], + 'filemtime' => $filetimes[$filename]['filemtime'], + )) + ->execute(); + } + return array_keys($parsed_files); } /** @@ -162,10 +175,6 @@ function _registry_parse_files($files) { * (optional) Weight of the module. */ function _registry_parse_file($filename, $contents, $module = '', $weight = 0) { - // Delete registry entries for this file, so we can insert the new resources. - db_delete('registry') - ->condition('filename', $filename) - ->execute(); if (preg_match_all('/^\s*(?:abstract)?\s*(class|interface)\s+([a-zA-Z0-9_]+)/m', $contents, $matches)) { $query = db_insert('registry')->fields(array('name', 'type', 'filename', 'module', 'weight')); foreach ($matches[2] as $key => $name) { diff --git a/includes/session.inc b/includes/session.inc index 07b208d7..236baf19 100644 --- a/includes/session.inc +++ b/includes/session.inc @@ -1,5 +1,5 @@ <?php -// $Id: session.inc,v 1.83 2010/05/01 08:12:22 dries Exp $ +// $Id: session.inc,v 1.87 2010/07/07 13:52:00 dries Exp $ /** * @file @@ -140,57 +140,75 @@ function _drupal_session_read($sid) { function _drupal_session_write($sid, $value) { global $user, $is_https; - if (!drupal_save_session()) { - // We don't have anything to do if we are not allowed to save the session. - return; - } + // The exception handler is not active at this point, so we need to do it manually. + try { + if (!drupal_save_session()) { + // We don't have anything to do if we are not allowed to save the session. + return; + } - $fields = array( - 'uid' => $user->uid, - 'cache' => isset($user->cache) ? $user->cache : 0, - 'hostname' => ip_address(), - 'session' => $value, - 'timestamp' => REQUEST_TIME, - ); - $key = array('sid' => $sid); - if ($is_https) { - $key['ssid'] = $sid; - $insecure_session_name = substr(session_name(), 1); - // The "secure pages" setting allows a site to simultaneously use both - // secure and insecure session cookies. If enabled, use the insecure session - // identifier as the sid. - if (variable_get('https', FALSE) && isset($_COOKIE[$insecure_session_name])) { - $key['sid'] = $_COOKIE[$insecure_session_name]; + // Either ssid or sid or both will be added from $key below. + $fields = array( + 'uid' => $user->uid, + 'cache' => isset($user->cache) ? $user->cache : 0, + 'hostname' => ip_address(), + 'session' => $value, + 'timestamp' => REQUEST_TIME, + ); + + // The "secure pages" setting allows a site to simultaneously use both secure + // and insecure session cookies. If enabled and both cookies are presented + // then use both keys. If not enabled but on HTTPS then use the PHP session + // id as 'ssid'. If on HTTP then use the PHP session id as 'sid'. + if ($is_https) { + $key['ssid'] = $sid; + $insecure_session_name = substr(session_name(), 1); + if (variable_get('https', FALSE) && isset($_COOKIE[$insecure_session_name])) { + $key['sid'] = $_COOKIE[$insecure_session_name]; + } + } + else { + $key['sid'] = $sid; } - } - db_merge('sessions') - ->key($key) - ->fields($fields) - ->execute(); - // Last access time is updated no more frequently than once every 180 seconds. - // This reduces contention in the users table. - if ($user->uid && REQUEST_TIME - $user->access > variable_get('session_write_interval', 180)) { - db_update('users') - ->fields(array( - 'access' => REQUEST_TIME - )) - ->condition('uid', $user->uid) + db_merge('sessions') + ->key($key) + ->fields($fields) ->execute(); - } - return TRUE; + // Last access time is updated no more frequently than once every 180 seconds. + // This reduces contention in the users table. + if ($user->uid && REQUEST_TIME - $user->access > variable_get('session_write_interval', 180)) { + db_update('users') + ->fields(array( + 'access' => REQUEST_TIME + )) + ->condition('uid', $user->uid) + ->execute(); + } + + return TRUE; + } + catch (Exception $exception) { + require_once DRUPAL_ROOT . '/includes/errors.inc'; + // If we are displaying errors, then do so with no possibility of a further uncaught exception being thrown. + if (error_displayable()) { + print '<h1>Uncaught exception thrown in session handler.</h1>'; + print '<p>' . _drupal_render_exception_safe($exception) . '</p><hr />'; + } + return FALSE; + } } /** * Initialize the session handler, starting a session if needed. */ function drupal_session_initialize() { - global $user; + global $user, $is_https; session_set_save_handler('_drupal_session_open', '_drupal_session_close', '_drupal_session_read', '_drupal_session_write', '_drupal_session_destroy', '_drupal_session_garbage_collection'); - if (isset($_COOKIE[session_name()])) { + if (isset($_COOKIE[session_name()]) || ($is_https && variable_get('https', FALSE) && isset($_COOKIE[substr(session_name(), 1)]))) { // If a session cookie exists, initialize the session. Otherwise the // session is only started on demand in drupal_session_commit(), making // anonymous users not use a session cookie unless something is stored in @@ -286,15 +304,49 @@ function drupal_session_regenerate() { global $user, $is_https; if ($is_https && variable_get('https', FALSE)) { $insecure_session_name = substr(session_name(), 1); + if (isset($_COOKIE[$insecure_session_name])) { + $old_insecure_session_id = $_COOKIE[$insecure_session_name]; + } $params = session_get_cookie_params(); $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']); + // If the session cookie lifetime is set, the session will expire $params['lifetime'] seconds from the current request. + // If it is not set, it will expire when the browser is closed. + $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0; + setcookie($insecure_session_name, $session_id, $expire, $params['path'], $params['domain'], FALSE, $params['httponly']); $_COOKIE[$insecure_session_name] = $session_id; } if (drupal_session_started()) { $old_session_id = session_id(); - session_regenerate_id(); + } + session_id(drupal_hash_base64(uniqid(mt_rand(), TRUE) . drupal_random_bytes(55))); + + if (isset($old_session_id)) { + $params = session_get_cookie_params(); + $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0; + setcookie(session_name(), session_id(), $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']); + $fields = array('sid' => session_id()); + if ($is_https) { + $fields['ssid'] = session_id(); + // If the "secure pages" setting is enabled, use the newly-created + // insecure session identifier as the regenerated sid. + if (variable_get('https', FALSE)) { + $fields['sid'] = $session_id; + } + } + db_update('sessions') + ->fields($fields) + ->condition($is_https ? 'ssid' : 'sid', $old_session_id) + ->execute(); + } + elseif (isset($old_insecure_session_id)) { + // If logging in to the secure site, and there was no active session on the + // secure site but a session was active on the insecure site, update the + // insecure session with the new session identifiers. + db_update('sessions') + ->fields(array('sid' => $session_id, 'ssid' => session_id())) + ->condition('sid', $old_insecure_session_id) + ->execute(); } else { // Start the session when it doesn't exist yet. @@ -304,15 +356,6 @@ function drupal_session_regenerate() { drupal_session_start(); $user = $account; } - - if (isset($old_session_id)) { - db_update('sessions') - ->fields(array( - $is_https ? 'ssid' : 'sid' => session_id() - )) - ->condition('sid', $old_session_id) - ->execute(); - } date_default_timezone_set(drupal_get_user_timezone()); } diff --git a/includes/stream_wrappers.inc b/includes/stream_wrappers.inc index 61784fe8..b060a8ab 100644 --- a/includes/stream_wrappers.inc +++ b/includes/stream_wrappers.inc @@ -1,5 +1,5 @@ <?php -// $Id: stream_wrappers.inc,v 1.15 2010/05/06 05:59:30 webchick Exp $ +// $Id: stream_wrappers.inc,v 1.17 2010/06/12 08:15:15 webchick Exp $ /** * @file @@ -145,6 +145,24 @@ interface DrupalStreamWrapperInterface extends StreamWrapperInterface { */ public function getExternalUrl(); + /** + * Returns the local writable target of the resource within the stream. + * + * This function should be used in place of calls to realpath() or similar + * functions when attempting to determine the location of a file. While + * functions like realpath() may return the location of a read-only file, this + * method may return a URI or path suitable for writing that is completely + * separate from the URI used for reading. + * + * @param $uri + * Optional URI. + * + * @return + * Returns a string representing a location suitable for writing of a file, + * or FALSE if unable to write to the file such as with read-only streams. + */ + public function getTarget($uri = NULL); + /** * Returns the MIME type of the resource. * @@ -155,6 +173,7 @@ interface DrupalStreamWrapperInterface extends StreamWrapperInterface { * - 'mimetypes': a list of mimetypes, keyed by an identifier, * - 'extensions': the mapping itself, an associative array in which * the key is the extension and the value is the mimetype identifier. + * * @return * Returns a string containing the MIME type of the resource. */ @@ -169,6 +188,7 @@ interface DrupalStreamWrapperInterface extends StreamWrapperInterface { * @param $mode * Integer value for the permissions. Consult PHP chmod() documentation * for more information. + * * @return * Returns TRUE on success or FALSE on failure. */ @@ -187,6 +207,23 @@ interface DrupalStreamWrapperInterface extends StreamWrapperInterface { * wrapper does not provide an implementation. */ public function realpath(); + + /** + * Gets the name of the directory from a given path. + * + * This method is usually accessed through drupal_dirname(), which wraps + * around the normal PHP dirname() function, which does not support stream + * wrappers. + * + * @param $uri + * An optional URI. + * + * @return + * A string containing the directory name, or FALSE if not applicable. + * + * @see drupal_dirname() + */ + public function dirname($uri = NULL); } @@ -227,6 +264,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface /** * Gets the path that the wrapper is responsible for. + * @TODO: Review this method name in D8 per http://drupal.org/node/701358 * * @return * String specifying the path. @@ -247,6 +285,20 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface return $this->uri; } + /** + * Base implementation of getTarget(). + */ + function getTarget($uri = NULL) { + if (!isset($uri)) { + $uri = $this->uri; + } + + list($scheme, $target) = explode('://', $uri, 2); + + // Remove erroneous leading or trailing, forward-slashes and backslashes. + return trim($target, '\/'); + } + /** * Base implementation of getMimeType(). */ @@ -303,7 +355,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface if (!isset($uri)) { $uri = $this->uri; } - $path = $this->getDirectoryPath() . '/' . file_uri_target($uri); + $path = $this->getDirectoryPath() . '/' . $this->getTarget($uri); $realpath = realpath($path); if (!$realpath) { // This file does not yet exist. @@ -327,8 +379,10 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS. * @param &$opened_path * A string containing the path actually opened. + * * @return * Returns TRUE if file was opened successfully. + * * @see http://php.net/manual/en/streamwrapper.stream-open.php */ public function stream_open($uri, $mode, $options, &$opened_path) { @@ -353,8 +407,10 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * - LOCK_UN to release a lock (shared or exclusive). * - LOCK_NB if you don't want flock() to block while locking (not * supported on Windows). + * * @return * Always returns TRUE at the present time. + * * @see http://php.net/manual/en/streamwrapper.stream-lock.php */ public function stream_lock($operation) { @@ -370,8 +426,10 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * * @param $count * Maximum number of bytes to be read. + * * @return * The string that was read, or FALSE in case of an error. + * * @see http://php.net/manual/en/streamwrapper.stream-read.php */ public function stream_read($count) { @@ -383,8 +441,10 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * * @param $data * The string to be written. + * * @return * The number of bytes written (integer). + * * @see http://php.net/manual/en/streamwrapper.stream-write.php */ public function stream_write($data) { @@ -396,6 +456,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * * @return * TRUE if end-of-file has been reached. + * * @see http://php.net/manual/en/streamwrapper.stream-eof.php */ public function stream_eof() { @@ -409,8 +470,10 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * The byte offset to got to. * @param $whence * SEEK_SET, SEEK_CUR, or SEEK_END. + * * @return * TRUE on success. + * * @see http://php.net/manual/en/streamwrapper.stream-seek.php */ public function stream_seek($offset, $whence) { @@ -422,6 +485,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * * @return * TRUE if data was successfully stored (or there was no data to store). + * * @see http://php.net/manual/en/streamwrapper.stream-flush.php */ public function stream_flush() { @@ -433,6 +497,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * * @return * The current offset in bytes from the beginning of file. + * * @see http://php.net/manual/en/streamwrapper.stream-tell.php */ public function stream_tell() { @@ -445,6 +510,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * @return * An array with file status, or FALSE in case of an error - see fstat() * for a description of this array. + * * @see http://php.net/manual/en/streamwrapper.stream-stat.php */ public function stream_stat() { @@ -456,6 +522,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * * @return * TRUE if stream was successfully closed. + * * @see http://php.net/manual/en/streamwrapper.stream-close.php */ public function stream_close() { @@ -467,8 +534,10 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * * @param $uri * A string containing the uri to the resource to delete. + * * @return * TRUE if resource was successfully deleted. + * * @see http://php.net/manual/en/streamwrapper.unlink.php */ public function unlink($uri) { @@ -483,14 +552,43 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * The uri to the file to rename. * @param $to_uri * The new uri for file. + * * @return * TRUE if file was successfully renamed. + * * @see http://php.net/manual/en/streamwrapper.rename.php */ public function rename($from_uri, $to_uri) { return rename($this->getLocalPath($from_uri), $this->getLocalPath($to_uri)); } + /** + * Gets the name of the directory from a given path. + * + * This method is usually accessed through drupal_dirname(), which wraps + * around the PHP dirname() function because it does not support stream + * wrappers. + * + * @param $uri + * A URI or path. + * + * @return + * A string containing the directory name. + * + * @see drupal_dirname() + */ + public function dirname($uri = NULL) { + list($scheme, $target) = explode('://', $uri, 2); + $target = $this->getTarget($uri); + $dirname = dirname($target); + + if ($dirname == '.') { + $dirname = ''; + } + + return $scheme . '://' . $dirname; + } + /** * Support for mkdir(). * @@ -500,8 +598,10 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * Permission flags - see mkdir(). * @param $options * A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE. + * * @return * TRUE if directory was successfully created. + * * @see http://php.net/manual/en/streamwrapper.mkdir.php */ public function mkdir($uri, $mode, $options) { @@ -510,7 +610,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface if ($recursive) { // $this->getLocalPath() fails if $uri has multiple levels of directories // that do not yet exist. - $localpath = $this->getDirectoryPath() . '/' . file_uri_target($uri); + $localpath = $this->getDirectoryPath() . '/' . $this->getTarget($uri); } else { $localpath = $this->getLocalPath($uri); @@ -530,8 +630,10 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * A string containing the URI to the directory to delete. * @param $options * A bit mask of STREAM_REPORT_ERRORS. + * * @return * TRUE if directory was successfully removed. + * * @see http://php.net/manual/en/streamwrapper.rmdir.php */ public function rmdir($uri, $options) { @@ -551,9 +653,11 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * A string containing the URI to get information about. * @param $flags * A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET. + * * @return * An array with file status, or FALSE in case of an error - see fstat() * for a description of this array. + * * @see http://php.net/manual/en/streamwrapper.url-stat.php */ public function url_stat($uri, $flags) { @@ -573,8 +677,10 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * A string containing the URI to the directory to open. * @param $options * Unknown (parameter is not documented in PHP Manual). + * * @return * TRUE on success. + * * @see http://php.net/manual/en/streamwrapper.dir-opendir.php */ public function dir_opendir($uri, $options) { @@ -589,6 +695,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * * @return * The next filename, or FALSE if there are no more files in the directory. + * * @see http://php.net/manual/en/streamwrapper.dir-readdir.php */ public function dir_readdir() { @@ -600,6 +707,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * * @return * TRUE on success. + * * @see http://php.net/manual/en/streamwrapper.dir-rewinddir.php */ public function dir_rewinddir() { @@ -611,6 +719,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface * * @return * TRUE on success. + * * @see http://php.net/manual/en/streamwrapper.dir-closedir.php */ public function dir_closedir() { @@ -638,7 +747,7 @@ class DrupalPublicStreamWrapper extends DrupalLocalStreamWrapper { * Return the HTML URI of a public file. */ function getExternalUrl() { - $path = str_replace('\\', '/', file_uri_target($this->uri)); + $path = str_replace('\\', '/', $this->getTarget()); return $GLOBALS['base_url'] . '/' . self::getDirectoryPath() . '/' . drupal_encode_path($path); } } @@ -666,7 +775,7 @@ class DrupalPrivateStreamWrapper extends DrupalLocalStreamWrapper { * Return the HTML URI of a private file. */ function getExternalUrl() { - $path = str_replace('\\', '/', file_uri_target($this->uri)); + $path = str_replace('\\', '/', $this->getTarget()); return url('system/files/' . $path, array('absolute' => TRUE)); } } @@ -684,14 +793,14 @@ class DrupalTemporaryStreamWrapper extends DrupalLocalStreamWrapper { * Implements abstract public function getDirectoryPath() */ public function getDirectoryPath() { - return variable_get('file_temporary_path', sys_get_temp_dir()); + return variable_get('file_temporary_path', file_directory_temp()); } /** * Overrides getExternalUrl(). */ public function getExternalUrl() { - $path = str_replace('\\', '/', file_uri_target($this->uri)); + $path = str_replace('\\', '/', $this->getTarget()); return url('system/temporary/' . $path, array('absolute' => TRUE)); } } diff --git a/includes/theme.inc b/includes/theme.inc index f27b9018..8c3aba4e 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -1,5 +1,5 @@ <?php -// $Id: theme.inc,v 1.598 2010/05/19 19:22:24 dries Exp $ +// $Id: theme.inc,v 1.601 2010/07/08 03:41:26 webchick Exp $ /** * @file @@ -81,7 +81,7 @@ function drupal_theme_initialize() { // Only select the user selected theme if it is available in the // list of themes that can be accessed. - $theme = !empty($user->theme) && drupal_theme_access($user->theme) ? $user->theme : variable_get('theme_default', 'garland'); + $theme = !empty($user->theme) && drupal_theme_access($user->theme) ? $user->theme : variable_get('theme_default', 'bartik'); // Allow modules to override the theme. Validation has already been performed // inside menu_get_custom_theme(), so we do not need to check it again here. @@ -1260,7 +1260,7 @@ function theme_enable($theme_list) { */ function theme_disable($theme_list) { // Don't disable the default theme. - if ($pos = array_search(variable_get('theme_default', 'garland'), $theme_list) !== FALSE) { + if ($pos = array_search(variable_get('theme_default', 'bartik'), $theme_list) !== FALSE) { unset($theme_list[$pos]); if (empty($theme_list)) { return; @@ -1854,7 +1854,7 @@ function theme_item_list($variables) { * - url: The url for the link. */ function theme_more_help_link($variables) { - return '<div class="more-help-link">' . t('<a href="@link">More help</a>', array('@link' => check_url($variables['url']))) . '</div>'; + return '<div class="more-help-link">' . l(t('More help'), $variables['url']) . '</div>'; } /** @@ -1868,7 +1868,7 @@ function theme_more_help_link($variables) { function theme_feed_icon($variables) { $text = t('Subscribe to @feed-title', array('@feed-title' => $variables['title'])); if ($image = theme('image', array('path' => 'misc/feed.png', 'alt' => $text))) { - return '<a href="' . check_url($variables['url']) . '" title="' . $text . '" class="feed-icon">' . $image . '</a>'; + return l($image, $variables['url'], array('html' => TRUE, 'attributes' => array('class' => array('feed-icon'), 'title' => $text))); } } @@ -1918,7 +1918,7 @@ function theme_html_tag($variables) { * - title: A descriptive verb for the link, like 'Read more'. */ function theme_more_link($variables) { - return '<div class="more-link">' . t('<a href="@link" title="@title">More</a>', array('@link' => check_url($variables['url']), '@title' => $variables['title'])) . '</div>'; + return '<div class="more-link">' . l(t('More'), $variables['url'], array('attributes' => array('title' => $variables['title']))) . '</div>'; } /** @@ -2172,7 +2172,7 @@ function template_preprocess_html(&$variables) { if (theme_get_setting('toggle_favicon')) { $favicon = theme_get_setting('favicon'); $type = theme_get_setting('favicon_mimetype'); - drupal_add_html_head_link(array('rel' => 'shortcut icon', 'href' => check_url($favicon), 'type' => $type)); + drupal_add_html_head_link(array('rel' => 'shortcut icon', 'href' => drupal_strip_dangerous_protocols($favicon), 'type' => $type)); } // Construct page title. @@ -2339,8 +2339,7 @@ function theme_get_suggestions($args, $base, $delimiter = '__') { /** * The variables array generated here is a mirror of template_preprocess_page(). * This preprocessor will run its course when theme_maintenance_page() is - * invoked. It is also used in theme_install_page() and theme_update_page() to - * keep all the variables consistent. + * invoked. * * An alternate template file of "maintenance-page--offline.tpl.php" can be * used when the database is offline to hide errors and completely replace the @@ -2356,7 +2355,7 @@ function template_preprocess_maintenance_page(&$variables) { if (theme_get_setting('toggle_favicon')) { $favicon = theme_get_setting('favicon'); $type = theme_get_setting('favicon_mimetype'); - drupal_add_html_head_link(array('rel' => 'shortcut icon', 'href' => check_url($favicon), 'type' => $type)); + drupal_add_html_head_link(array('rel' => 'shortcut icon', 'href' => drupal_strip_dangerous_protocols($favicon), 'type' => $type)); } global $theme; @@ -2437,8 +2436,6 @@ function template_preprocess_maintenance_page(&$variables) { /** * The variables array generated here is a mirror of template_process_html(). * This processor will run its course when theme_maintenance_page() is invoked. - * It is also used in theme_install_page() and theme_update_page() to keep all - * the variables consistent. * * @see maintenance-page.tpl.php */ diff --git a/includes/theme.maintenance.inc b/includes/theme.maintenance.inc index 2544bc4e..7fb2f400 100644 --- a/includes/theme.maintenance.inc +++ b/includes/theme.maintenance.inc @@ -1,5 +1,5 @@ <?php -// $Id: theme.maintenance.inc,v 1.59 2010/05/12 09:22:24 dries Exp $ +// $Id: theme.maintenance.inc,v 1.63 2010/07/08 03:41:26 webchick Exp $ /** * @file @@ -44,16 +44,16 @@ function _drupal_maintenance_theme() { spl_autoload_register('db_autoload'); } - // Ensure that system.module is loaded. - if (!function_exists('_system_rebuild_theme_data')) { - $module_list['system']['filename'] = 'modules/system/system.module'; - module_list(TRUE, FALSE, FALSE, $module_list); - drupal_load('module', 'system'); - } - // We use the default theme as the maintenance theme. If a default theme // isn't specified in the database or in settings.php, we use Garland. - $custom_theme = variable_get('maintenance_theme', variable_get('theme_default', 'garland')); + $custom_theme = variable_get('maintenance_theme', variable_get('theme_default', 'bartik')); + } + + // Ensure that system.module is loaded. + if (!function_exists('_system_rebuild_theme_data')) { + $module_list['system']['filename'] = 'modules/system/system.module'; + module_list(TRUE, FALSE, FALSE, $module_list); + drupal_load('module', 'system'); } $themes = list_themes(); @@ -141,33 +141,6 @@ function theme_task_list($variables) { */ function theme_install_page($variables) { drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); - - // Delay setting the message variable so it can be processed below. - $variables['show_messages'] = FALSE; - - // Special handling of error messages - $messages = drupal_set_message(); - if (isset($messages['error'])) { - $title = count($messages['error']) > 1 ? st('The following errors must be resolved before you can continue the installation process') : st('The following error must be resolved before you can continue the installation process'); - $variables['messages'] .= '<h3>' . $title . ':</h3>'; - $variables['messages'] .= theme('status_messages', array('display' => 'error')); - $variables['content'] .= '<p>' . st('Check the error messages and <a href="!url">try again</a>.', array('!url' => check_url(request_uri()))) . '</p>'; - } - - // Special handling of warning messages - if (isset($messages['warning'])) { - $title = count($messages['warning']) > 1 ? st('The following installation warnings should be carefully reviewed') : st('The following installation warning should be carefully reviewed'); - $variables['messages'] .= '<h4>' . $title . ':</h4>'; - $variables['messages'] .= theme('status_messages', array('display' => 'warning')); - } - - // Special handling of status messages - if (isset($messages['status'])) { - $title = count($messages['status']) > 1 ? st('The following installation warnings should be carefully reviewed, but in most cases may be safely ignored') : st('The following installation warning should be carefully reviewed, but in most cases may be safely ignored'); - $variables['messages'] .= '<h4>' . $title . ':</h4>'; - $variables['messages'] .= theme('status_messages', array('display' => 'status')); - } - return theme('maintenance_page', $variables); } @@ -183,17 +156,7 @@ function theme_install_page($variables) { * FALSE can be useful to postpone the messages to a subsequent page. */ function theme_update_page($variables) { - // Set required headers. drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); - - // Special handling of warning messages. - $messages = drupal_set_message(); - if (isset($messages['warning'])) { - $title = count($messages['warning']) > 1 ? 'The following update warnings should be carefully reviewed before continuing' : 'The following update warning should be carefully reviewed before continuing'; - $variables['messages'] .= '<h4>' . $title . ':</h4>'; - $variables['messages'] .= theme('status_messages', array('display' => 'warning')); - } - return theme('maintenance_page', $variables); } diff --git a/includes/unicode.inc b/includes/unicode.inc index cd98e80e..0476ddef 100644 --- a/includes/unicode.inc +++ b/includes/unicode.inc @@ -1,5 +1,5 @@ <?php -// $Id: unicode.inc,v 1.42 2010/03/07 06:38:55 webchick Exp $ +// $Id: unicode.inc,v 1.44 2010/06/14 12:37:15 dries Exp $ /** * Indicates an error during check for PHP unicode support. @@ -17,6 +17,67 @@ define('UNICODE_SINGLEBYTE', 0); */ define('UNICODE_MULTIBYTE', 1); +/** + * Matches Unicode characters that are word boundaries. + * + * @see http://unicode.org/glossary + * + * Characters with the following General_category (gc) property values are used + * as word boundaries. While this does not fully conform to the Word Boundaries + * algorithm described in http://unicode.org/reports/tr29, as PCRE does not + * contain the Word_Break property table, this simpler algorithm has to do. + * - Cc, Cf, Cn, Co, Cs: Other. + * - Pc, Pd, Pe, Pf, Pi, Po, Ps: Punctuation. + * - Sc, Sk, Sm, So: Symbols. + * - Zl, Zp, Zs: Separators. + * + * Non-boundary characters include the following General_category (gc) property + * values: + * - Ll, Lm, Lo, Lt, Lu: Letters. + * - Mc, Me, Mn: Combining Marks. + * - Nd, Nl, No: Numbers. + * + * Note that the PCRE property matcher is not used because we wanted to be + * compatible with Unicode 5.2.0 regardless of the PCRE version used (and any + * bugs in PCRE property tables). + */ +define('PREG_CLASS_UNICODE_WORD_BOUNDARY', + '\x{0}-\x{2F}\x{3A}-\x{40}\x{5B}-\x{60}\x{7B}-\x{A9}\x{AB}-\x{B1}\x{B4}' . + '\x{B6}-\x{B8}\x{BB}\x{BF}\x{D7}\x{F7}\x{2C2}-\x{2C5}\x{2D2}-\x{2DF}' . + '\x{2E5}-\x{2EB}\x{2ED}\x{2EF}-\x{2FF}\x{375}\x{37E}-\x{385}\x{387}\x{3F6}' . + '\x{482}\x{55A}-\x{55F}\x{589}-\x{58A}\x{5BE}\x{5C0}\x{5C3}\x{5C6}' . + '\x{5F3}-\x{60F}\x{61B}-\x{61F}\x{66A}-\x{66D}\x{6D4}\x{6DD}\x{6E9}' . + '\x{6FD}-\x{6FE}\x{700}-\x{70F}\x{7F6}-\x{7F9}\x{830}-\x{83E}' . + '\x{964}-\x{965}\x{970}\x{9F2}-\x{9F3}\x{9FA}-\x{9FB}\x{AF1}\x{B70}' . + '\x{BF3}-\x{BFA}\x{C7F}\x{CF1}-\x{CF2}\x{D79}\x{DF4}\x{E3F}\x{E4F}' . + '\x{E5A}-\x{E5B}\x{F01}-\x{F17}\x{F1A}-\x{F1F}\x{F34}\x{F36}\x{F38}' . + '\x{F3A}-\x{F3D}\x{F85}\x{FBE}-\x{FC5}\x{FC7}-\x{FD8}\x{104A}-\x{104F}' . + '\x{109E}-\x{109F}\x{10FB}\x{1360}-\x{1368}\x{1390}-\x{1399}\x{1400}' . + '\x{166D}-\x{166E}\x{1680}\x{169B}-\x{169C}\x{16EB}-\x{16ED}' . + '\x{1735}-\x{1736}\x{17B4}-\x{17B5}\x{17D4}-\x{17D6}\x{17D8}-\x{17DB}' . + '\x{1800}-\x{180A}\x{180E}\x{1940}-\x{1945}\x{19DE}-\x{19FF}' . + '\x{1A1E}-\x{1A1F}\x{1AA0}-\x{1AA6}\x{1AA8}-\x{1AAD}\x{1B5A}-\x{1B6A}' . + '\x{1B74}-\x{1B7C}\x{1C3B}-\x{1C3F}\x{1C7E}-\x{1C7F}\x{1CD3}\x{1FBD}' . + '\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}' . + '\x{1FFD}-\x{206F}\x{207A}-\x{207E}\x{208A}-\x{208E}\x{20A0}-\x{20B8}' . + '\x{2100}-\x{2101}\x{2103}-\x{2106}\x{2108}-\x{2109}\x{2114}' . + '\x{2116}-\x{2118}\x{211E}-\x{2123}\x{2125}\x{2127}\x{2129}\x{212E}' . + '\x{213A}-\x{213B}\x{2140}-\x{2144}\x{214A}-\x{214D}\x{214F}' . + '\x{2190}-\x{244A}\x{249C}-\x{24E9}\x{2500}-\x{2775}\x{2794}-\x{2B59}' . + '\x{2CE5}-\x{2CEA}\x{2CF9}-\x{2CFC}\x{2CFE}-\x{2CFF}\x{2E00}-\x{2E2E}' . + '\x{2E30}-\x{3004}\x{3008}-\x{3020}\x{3030}\x{3036}-\x{3037}' . + '\x{303D}-\x{303F}\x{309B}-\x{309C}\x{30A0}\x{30FB}\x{3190}-\x{3191}' . + '\x{3196}-\x{319F}\x{31C0}-\x{31E3}\x{3200}-\x{321E}\x{322A}-\x{3250}' . + '\x{3260}-\x{327F}\x{328A}-\x{32B0}\x{32C0}-\x{33FF}\x{4DC0}-\x{4DFF}' . + '\x{A490}-\x{A4C6}\x{A4FE}-\x{A4FF}\x{A60D}-\x{A60F}\x{A673}\x{A67E}' . + '\x{A6F2}-\x{A716}\x{A720}-\x{A721}\x{A789}-\x{A78A}\x{A828}-\x{A82B}' . + '\x{A836}-\x{A839}\x{A874}-\x{A877}\x{A8CE}-\x{A8CF}\x{A8F8}-\x{A8FA}' . + '\x{A92E}-\x{A92F}\x{A95F}\x{A9C1}-\x{A9CD}\x{A9DE}-\x{A9DF}' . + '\x{AA5C}-\x{AA5F}\x{AA77}-\x{AA79}\x{AADE}-\x{AADF}\x{ABEB}' . + '\x{D800}-\x{F8FF}\x{FB29}\x{FD3E}-\x{FD3F}\x{FDFC}-\x{FDFD}' . + '\x{FE10}-\x{FE19}\x{FE30}-\x{FE6B}\x{FEFF}-\x{FF0F}\x{FF1A}-\x{FF20}' . + '\x{FF3B}-\x{FF40}\x{FF5B}-\x{FF65}\x{FFE0}-\x{FFFD}'); + /** * Wrapper around _unicode_check(). */ @@ -213,44 +274,80 @@ function drupal_truncate_bytes($string, $len) { } /** - * Truncate a UTF-8-encoded string safely to a number of characters. + * Truncates a UTF-8-encoded string safely to a number of characters. * * @param $string * The string to truncate. - * @param $len - * An upper limit on the returned string length. + * @param $max_length + * An upper limit on the returned string length, including trailing ellipsis + * if $add_ellipsis is TRUE. * @param $wordsafe - * Flag to truncate at last space within the upper limit. Defaults to FALSE. - * @param $dots - * Flag to add trailing dots. Defaults to FALSE. + * If TRUE, attempt to truncate on a word boundary. Word boundaries are + * spaces, punctuation, and Unicode characters used as word boundaries in + * non-Latin languages; see PREG_CLASS_UNICODE_WORD_BOUNDARY for more + * information. If a word boundary cannot be found that would make the length + * of the returned string fall within length guidelines (see parameters + * $max_return_length and $min_wordsafe_length), word boundaries are ignored. + * @param $add_ellipsis + * If TRUE, add t('...') to the end of the truncated string (defaults to + * FALSE). The string length will still fall within $max_return_length. + * @param $min_wordsafe_length + * If $wordsafe is TRUE, the minimum acceptable length for truncation (before + * adding an ellipsis, if $add_ellipsis is TRUE). Has no effect if $wordsafe + * is FALSE. This can be used to prevent having a very short resulting string + * that will not be understandable. For instance, if you are truncating the + * string "See myverylongurlexample.com for more information" to a word-safe + * return length of 20, the only available word boundary within 20 characters + * is after the word "See", which wouldn't leave a very informative string. If + * you had set $min_wordsafe_length to 10, though, the function would realise + * that "See" alone is too short, and would then just truncate ignoring word + * boundaries, giving you "See myverylongurl..." (assuming you had set + * $add_ellipses to TRUE). + * * @return * The truncated string. */ -function truncate_utf8($string, $len, $wordsafe = FALSE, $dots = FALSE) { +function truncate_utf8($string, $max_length, $wordsafe = FALSE, $add_ellipsis = FALSE, $min_wordsafe_length = 1) { + $ellipsis = ''; + $max_length = max($max_length, 0); + $min_wordsafe_length = max($min_wordsafe_length, 0); - if (drupal_strlen($string) <= $len) { + if (drupal_strlen($string) <= $max_length) { + // No truncation needed, so don't add ellipsis, just return. return $string; } - if ($dots) { - $len -= 4; + if ($add_ellipsis) { + // Truncate ellipsis in case $max_length is small. + $ellipsis = drupal_substr(t('...'), 0, $max_length); + $max_length -= drupal_strlen($ellipsis); + $max_length = max($max_length, 0); + } + + if ($max_length <= $min_wordsafe_length) { + // Do not attempt word-safe if lengths are bad. + $wordsafe = FALSE; } if ($wordsafe) { - $string = drupal_substr($string, 0, $len + 1); // leave one more character - if ($last_space = strrpos($string, ' ')) { // space exists AND is not on position 0 - $string = substr($string, 0, $last_space); + $matches = array(); + // Find the last word boundary, if there is one within $min_wordsafe_length + // to $max_length characters. preg_match() is always greedy, so it will + // find the longest string possible. + $found = preg_match('/^(.{' . $min_wordsafe_length . ',' . $max_length . '})[' . PREG_CLASS_UNICODE_WORD_BOUNDARY . ']/u', $string, $matches); + if ($found) { + $string = $matches[1]; } else { - $string = drupal_substr($string, 0, $len); + $string = drupal_substr($string, 0, $max_length); } } else { - $string = drupal_substr($string, 0, $len); + $string = drupal_substr($string, 0, $max_length); } - if ($dots) { - $string .= ' ...'; + if ($add_ellipsis) { + $string .= $ellipsis; } return $string; @@ -314,14 +411,20 @@ function _mime_header_decode($matches) { } /** - * Decode all HTML entities (including numerical ones) to regular UTF-8 bytes. - * Double-escaped entities will only be decoded once ("&lt;" becomes "<", not "<"). + * Decodes all HTML entities (including numerical ones) to regular UTF-8 bytes. + * + * Double-escaped entities will only be decoded once ("&lt;" becomes "<", + * not "<"). Be careful when using this function, as decode_entities can revert + * previous sanitization efforts (<script> will become <script>). * * @param $text * The text to decode entities in. * @param $exclude * An array of characters which should not be decoded. For example, * array('<', '&', '"'). This affects both named and numerical entities. + * + * @return + * The input $text, with all HTML entities decoded once. */ function decode_entities($text, $exclude = array()) { static $html_entities; @@ -516,16 +619,21 @@ function drupal_substr($text, $start, $length = NULL) { // Count all the continuation bytes from the starting index until we have // found $length characters or reached the end of the string, then // backtrace one byte. - $iend = $istart - 1; $chars = -1; + $iend = $istart - 1; + $chars = -1; + $last_real = FALSE; while ($iend < $strlen - 1 && $chars < $length) { $iend++; $c = ord($text[$iend]); + $last_real = FALSE; if ($c < 0x80 || $c >= 0xC0) { $chars++; + $last_real = TRUE; } } - // Backtrace one byte if the end of the string was not reached. - if ($iend < $strlen - 1) { + // Backtrace one byte if the last character we found was a real character + // and we don't need it. + if ($last_real && $chars >= $length) { $iend--; } } @@ -548,7 +656,7 @@ function drupal_substr($text, $start, $length = NULL) { } else { // $length == 0, return an empty string. - $iend = $istart - 1; + return ''; } return substr($text, $istart, max(0, $iend - $istart + 1)); diff --git a/includes/update.inc b/includes/update.inc index 0a238bb2..31b66f5b 100644 --- a/includes/update.inc +++ b/includes/update.inc @@ -1,5 +1,5 @@ <?php -// $Id: update.inc,v 1.50 2010/05/21 11:53:26 dries Exp $ +// $Id: update.inc,v 1.60 2010/07/07 05:44:03 webchick Exp $ /** * @file @@ -115,7 +115,7 @@ function update_prepare_d7_bootstrap() { 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); @@ -199,7 +199,11 @@ function update_prepare_d7_bootstrap() { // Set a valid timezone for 6 -> 7 upgrade process. drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES); - if (is_numeric(variable_get('date_default_timezone', 0))) { + $timezone_offset = variable_get('date_default_timezone', 0); + if (is_numeric($timezone_offset)) { + // Save the original offset. + variable_set('date_temporary_timezone', $timezone_offset); + // Set the timezone for this request only. $GLOBALS['conf']['date_default_timezone'] = 'UTC'; } } @@ -345,7 +349,8 @@ function update_fix_d7_requirements() { db_add_index('system', 'system_list', array('weight', 'name')); // Add the cache_path table. - $schema['cache_path'] = drupal_get_schema_unprocessed('system', 'cache'); + require_once('./modules/system/system.install'); + $schema['cache_path'] = system_schema_cache_7054(); $schema['cache_path']['description'] = 'Cache table used for path alias lookups.'; db_create_table('cache_path', $schema['cache_path']); @@ -478,7 +483,15 @@ function update_fix_d7_requirements() { ); db_create_table('date_format_type', $schema['date_format_type']); + // Sites that have the Drupal 6 Date module installed already have the + // following tables. + if (db_table_exists('date_formats')) { + db_rename_table('date_formats', 'd6_date_formats'); + } db_create_table('date_formats', $schema['date_formats']); + if (db_table_exists('date_format_locale')) { + db_rename_table('date_format_locale', 'd6_date_format_locale'); + } db_create_table('date_format_locale', $schema['date_format_locale']); // Add the queue table. @@ -499,7 +512,7 @@ function update_fix_d7_requirements() { 'description' => 'The queue name.', ), 'data' => array( - 'type' => 'text', + 'type' => 'blob', 'not null' => FALSE, 'size' => 'big', 'serialize' => TRUE, @@ -570,6 +583,19 @@ function update_fix_d7_requirements() { variable_set('maintenance_mode_message', $message); } + // Add ssid column and index. + db_add_field('sessions', 'ssid', array('description' => "Secure session ID. The value is generated by PHP's Session API.", 'type' => 'varchar', 'length' => 128, 'not null' => TRUE, 'default' => '')); + db_add_index('sessions', 'ssid', array('ssid')); + // Drop existing primary key. + db_drop_primary_key('sessions'); + // Add new primary key. + db_add_primary_key('sessions', array('sid', 'ssid')); + + // Allow longer javascript file names. + if (db_table_exists('languages')) { + db_change_field('languages', 'javascript', 'javascript', array('type' => 'varchar', 'length' => 64, 'not null' => TRUE, 'default' => '')); + } + variable_set('update_d7_requirements', TRUE); } @@ -733,10 +759,15 @@ function update_do_one($module, $number, $dependency_map, &$context) { $ret['results']['query'] = $function($context['sandbox']); $ret['results']['success'] = TRUE; } - // @TODO We may want to do different error handling for different exception - // types, but for now we'll just print the message. + // @TODO We may want to do different error handling for different + // exception types, but for now we'll just log the exception and + // return the message for printing. catch (Exception $e) { - $ret['#abort'] = array('success' => FALSE, 'query' => $e->getMessage()); + watchdog_exception('update', $e); + + require_once DRUPAL_ROOT . '/includes/errors.inc'; + $variables = _drupal_decode_exception($e); + $ret['#abort'] = array('success' => FALSE, 'query' => t('%type: %message in %function (line %line of %file).', $variables)); } if ($context['log']) { diff --git a/includes/utility.inc b/includes/utility.inc new file mode 100644 index 00000000..f2c43fcb --- /dev/null +++ b/includes/utility.inc @@ -0,0 +1,59 @@ +<?php +// $Id: utility.inc,v 1.1 2010/06/28 02:05:47 webchick Exp $ + +/** + * @file + * Miscellaneous functions. + */ + +/** + * Drupal-friendly var_export(). + * + * @param $var + * The variable to export. + * @param $prefix + * A prefix that will be added at the begining of every lines of the output. + * @return + * The variable exported in a way compatible to Drupal's coding standards. + */ +function drupal_var_export($var, $prefix = '') { + if (is_array($var)) { + if (empty($var)) { + $output = 'array()'; + } + else { + $output = "array(\n"; + // Don't export keys if the array is non associative. + $export_keys = array_values($var) != $var; + foreach ($var as $key => $value) { + $output .= ' ' . ($export_keys ? drupal_var_export($key) . ' => ' : '') . drupal_var_export($value, ' ', FALSE) . ",\n"; + } + $output .= ')'; + } + } + else if (is_bool($var)) { + $output = $var ? 'TRUE' : 'FALSE'; + } + else if (is_string($var)) { + $line_safe_var = str_replace("\n", '\n', $var); + if (strpos($var, "\n") !== FALSE || strpos($var, "'") !== FALSE) { + // If the string contains a line break or a single quote, use the + // double quote export mode. Encode backslash and double quotes and + // transform some common control characters. + $var = str_replace(array('\\', '"', "\n", "\r", "\t"), array('\\\\', '\"', '\n', '\r', '\t'), $var); + $output = '"' . $var . '"'; + } + else { + $output = "'" . $var . "'"; + } + } + else { + $output = var_export($var, TRUE); + } + + if ($prefix) { + $output = str_replace("\n", "\n$prefix", $output); + } + + return $output; +} diff --git a/misc/ajax.js b/misc/ajax.js index 463481bf..553d2ccf 100644 --- a/misc/ajax.js +++ b/misc/ajax.js @@ -1,4 +1,4 @@ -// $Id: ajax.js,v 1.17 2010/05/16 19:08:08 dries Exp $ +// $Id: ajax.js,v 1.18 2010/06/25 20:34:07 dries Exp $ (function ($) { /** @@ -189,8 +189,14 @@ Drupal.ajax = function (base, element, element_settings) { */ Drupal.ajax.prototype.beforeSerialize = function (element, options) { // Allow detaching behaviors to update field values before collecting them. - var settings = this.settings || Drupal.settings; - Drupal.detachBehaviors(this.form, settings, 'serialize'); + // This is only needed when field values are added to the POST data, so only + // when there is a form such that this.form.ajaxSubmit() is used instead of + // $.ajax(). When there is no form and $.ajax() is used, beforeSerialize() + // isn't called, but don't rely on that: explicitly check this.form. + if (this.form) { + var settings = this.settings || Drupal.settings; + Drupal.detachBehaviors(this.form, settings, 'serialize'); + } }; /** @@ -249,12 +255,14 @@ Drupal.ajax.prototype.success = function (response, status) { } } - // Reattach behaviors that were detached in beforeSerialize(). The + // Reattach behaviors, if they were detached in beforeSerialize(). The // attachBehaviors() called on the new content from processing the response // commands is not sufficient, because behaviors from the entire form need // to be reattached. - var settings = this.settings || Drupal.settings; - Drupal.attachBehaviors(this.form, settings); + if (this.form) { + var settings = this.settings || Drupal.settings; + Drupal.attachBehaviors(this.form, settings); + } Drupal.unfreezeHeight(); @@ -306,9 +314,11 @@ Drupal.ajax.prototype.error = function (response, uri) { $(this.wrapper).show(); // Re-enable the element. $(this.element).removeClass('progress-disabled').attr('disabled', false); - // Reattach behaviors that were detached in beforeSerialize(). - var settings = response.settings || this.settings || Drupal.settings; - Drupal.attachBehaviors(this.form, settings); + // Reattach behaviors, if they were detached in beforeSerialize(). + if (this.form) { + var settings = response.settings || this.settings || Drupal.settings; + Drupal.attachBehaviors(this.form, settings); + } }; /** diff --git a/misc/autocomplete.js b/misc/autocomplete.js index bda022e4..b05f64f6 100644 --- a/misc/autocomplete.js +++ b/misc/autocomplete.js @@ -1,4 +1,4 @@ -// $Id: autocomplete.js,v 1.36 2010/04/30 05:23:43 webchick Exp $ +// $Id: autocomplete.js,v 1.37 2010/06/13 05:31:02 dries Exp $ (function ($) { /** @@ -115,7 +115,7 @@ Drupal.jsAC.prototype.selectDown = function () { if (this.selected && this.selected.nextSibling) { this.highlight(this.selected.nextSibling); } - else { + else if (this.popup) { var lis = $('li', this.popup); if (lis.size() > 0) { this.highlight(lis.get(0)); diff --git a/misc/displace.js b/misc/displace.js deleted file mode 100644 index 57431742..00000000 --- a/misc/displace.js +++ /dev/null @@ -1,115 +0,0 @@ -// $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 6980b980..18064d69 100644 --- a/misc/drupal.js +++ b/misc/drupal.js @@ -1,4 +1,4 @@ -// $Id: drupal.js,v 1.66 2010/05/14 16:44:37 dries Exp $ +// $Id: drupal.js,v 1.68 2010/05/24 07:22:12 dries Exp $ var Drupal = Drupal || { 'settings': {}, 'behaviors': {}, 'locale': {} }; @@ -216,7 +216,7 @@ Drupal.formatPlural = function (count, singular, plural, args) { else { args['@count[' + index + ']'] = args['@count']; delete args['@count']; - return Drupal.t(plural.replace('@count', '@count[' + index + ']')); + return Drupal.t(plural.replace('@count', '@count[' + index + ']'), args); } }; @@ -299,24 +299,6 @@ 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/jquery.ba-bbq.js b/misc/jquery.ba-bbq.js index c081a937..10b0b029 100644 --- a/misc/jquery.ba-bbq.js +++ b/misc/jquery.ba-bbq.js @@ -1,9 +1,9 @@ -// $Id: jquery.ba-bbq.js,v 1.2 2010/04/11 01:07:14 webchick Exp $ +// $Id: jquery.ba-bbq.js,v 1.3 2010/05/26 11:55:15 dries Exp $ /* * jQuery BBQ: Back Button & Query Library - v1.2.1 - 2/17/2010 * http://benalman.com/projects/jquery-bbq-plugin/ - * + * * Copyright (c) 2010 "Cowboy" Ben Alman * Dual licensed under the MIT and GPL licenses. * http://benalman.com/about/license/ @@ -12,7 +12,7 @@ /* * jQuery hashchange event - v1.2 - 2/11/2010 * http://benalman.com/projects/jquery-hashchange-plugin/ - * + * * Copyright (c) 2010 "Cowboy" Ben Alman * Dual licensed under the MIT and GPL licenses. * http://benalman.com/about/license/ diff --git a/misc/tabledrag.js b/misc/tabledrag.js index 7220b784..4818662d 100644 --- a/misc/tabledrag.js +++ b/misc/tabledrag.js @@ -1,4 +1,4 @@ -// $Id: tabledrag.js,v 1.38 2010/05/18 06:46:45 dries Exp $ +// $Id: tabledrag.js,v 1.39 2010/06/20 17:34:51 webchick Exp $ (function ($) { /** @@ -81,10 +81,29 @@ Drupal.tableDrag = function (table, tableSettings) { // Make each applicable row draggable. // Match immediate children of the parent element to allow nesting. - $('> tr.draggable, > tbody > tr.draggable', table).each(function() { self.makeDraggable(this); }); + $('> tr.draggable, > tbody > tr.draggable', table).each(function () { self.makeDraggable(this); }); + + // Add a link before the table for users to show or hide weight columns. + $(table).before($('<a href="#" class="tabledrag-toggle-weight"></a>') + .attr('title', Drupal.t('Re-order rows by numerical weight instead of dragging.')) + .click(function () { + if ($.cookie('Drupal.tableDrag.showWeight') == 1) { + self.hideColumns(); + } + else { + self.showColumns(); + } + return false; + }) + .wrap('<div class="tabledrag-toggle-weight-wrapper"></div>') + .parent() + ); - // Hide columns containing affected form elements. - this.hideColumns(); + // Initialize the specified columns (for example, weight or parent columns) + // to show or hide according to user preference. This aids accessibility + // so that, e.g., screen reader users can choose to enter weight values and + // manipulate form elements directly, rather than using drag-and-drop.. + self.initColumns(); // Add mouse bindings to the document. The self variable is passed along // as event handlers do not have direct access to the tableDrag object. @@ -93,10 +112,14 @@ Drupal.tableDrag = function (table, tableSettings) { }; /** - * Hide the columns containing form elements according to the settings for - * this tableDrag instance. + * Initialize columns containing form elements to be hidden by default, + * according to the settings for this tableDrag instance. + * + * Identify and mark each cell with a CSS class so we can easily toggle + * show/hide it. Finally, hide columns if user does not have a + * 'Drupal.tableDrag.showWeight' cookie. */ -Drupal.tableDrag.prototype.hideColumns = function () { +Drupal.tableDrag.prototype.initColumns = function () { for (var group in this.tableSettings) { // Find the first field in this group. for (var d in this.tableSettings[group]) { @@ -108,13 +131,13 @@ Drupal.tableDrag.prototype.hideColumns = function () { } } - // Hide the column containing this field. + // Mark the column containing this field so it can be hidden. if (hidden && cell[0] && cell.css('display') != 'none') { // Add 1 to our indexes. The nth-child selector is 1 based, not 0 based. // Match immediate children of the parent element to allow nesting. var columnIndex = $('> td', cell.parent()).index(cell.get(0)) + 1; var headerIndex = $('> td:not(:hidden)', cell.parent()).index(cell.get(0)) + 1; - $('> thead > tr, > tbody > tr, > tr', this.table).each(function(){ + $('> thead > tr, > tbody > tr, > tr', this.table).each(function (){ var row = $(this); var parentTag = row.parent().get(0).tagName.toLowerCase(); var index = (parentTag == 'thead') ? headerIndex : columnIndex; @@ -128,18 +151,83 @@ Drupal.tableDrag.prototype.hideColumns = function () { if (index > 0) { cell = row.children(':nth-child(' + index + ')'); if (cell[0].colSpan > 1) { - // If this cell has a colspan, simply reduce it. - cell[0].colSpan = cell[0].colSpan - 1; + // If this cell has a colspan, mark it so we can reduce the colspan. + $(cell[0]).addClass('tabledrag-has-colspan'); } else { - // Hide table body cells, but remove table header cells entirely - // (Safari doesn't hide properly). - parentTag == 'thead' ? cell.remove() : cell.css('display', 'none'); + // Mark this cell so we can hide it. + $(cell[0]).addClass('tabledrag-hide'); } } }); } } + + // Now hide cells and reduce colspans unless cookie indicates previous choice. + // Set a cookie if it is not already present. + if ($.cookie('Drupal.tableDrag.showWeight') === null) { + $.cookie('Drupal.tableDrag.showWeight', 0, { + path: Drupal.settings.basePath, + // The cookie expires in one year. + expires: 365 + }); + this.hideColumns(); + } + // Check cookie value and show/hide weight columns accordingly. + else { + if ($.cookie('Drupal.tableDrag.showWeight') == 1) { + this.showColumns(); + } + else { + this.hideColumns(); + } + } +}; + +/** + * Hide the columns containing weight/parent form elements. + * Undo showColumns(). + */ +Drupal.tableDrag.prototype.hideColumns = function () { + // Hide weight/parent cells and headers. + $('.tabledrag-hide', 'table.tabledrag-processed').css('display', 'none'); + // Show TableDrag handles. + $('.tabledrag-handle', 'table.tabledrag-processed').css('display', ''); + // Reduce the colspan of any effected multi-span columns. + $('.tabledrag-has-colspan', 'table.tabledrag-processed').each(function () { + this.colSpan = this.colSpan - 1; + }); + // Change link text. + $('.tabledrag-toggle-weight').text(Drupal.t('Show row weights')); + // Change cookie. + $.cookie('Drupal.tableDrag.showWeight', 0, { + path: Drupal.settings.basePath, + // The cookie expires in one year. + expires: 365 + }); +}; + +/** + * Show the columns containing weight/parent form elements + * Undo hideColumns(). + */ +Drupal.tableDrag.prototype.showColumns = function () { + // Show weight/parent cells and headers. + $('.tabledrag-hide', 'table.tabledrag-processed').css('display', ''); + // Hide TableDrag handles. + $('.tabledrag-handle', 'table.tabledrag-processed').css('display', 'none'); + // Increase the colspan for any columns where it was previously reduced. + $('.tabledrag-has-colspan', 'table.tabledrag-processed').each(function () { + this.colSpan = this.colSpan + 1; + }); + // Change link text. + $('.tabledrag-toggle-weight').text(Drupal.t('Hide row weights')); + // Change cookie. + $.cookie('Drupal.tableDrag.showWeight', 1, { + path: Drupal.settings.basePath, + // The cookie expires in one year. + expires: 365 + }); }; /** diff --git a/misc/tableheader.js b/misc/tableheader.js index 17fed66c..4ae90b03 100644 --- a/misc/tableheader.js +++ b/misc/tableheader.js @@ -1,4 +1,4 @@ -// $Id: tableheader.js,v 1.30 2010/05/14 07:45:53 dries Exp $ +// $Id: tableheader.js,v 1.31 2010/05/23 18:23:32 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.displace ? Drupal.displace.getDisplacement('top') : 0; + var topOffset = Drupal.settings.tableHeaderOffset ? eval(Drupal.settings.tableHeaderOffset + '()') : 0; $('.sticky-header').css('top', topOffset + 'px'); // Save positioning data. var viewHeight = document.documentElement.scrollHeight || document.body.scrollHeight; diff --git a/modules/aggregator/aggregator.admin.inc b/modules/aggregator/aggregator.admin.inc index c6741695..7534a9c2 100644 --- a/modules/aggregator/aggregator.admin.inc +++ b/modules/aggregator/aggregator.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: aggregator.admin.inc,v 1.53 2010/05/18 20:53:29 dries Exp $ +// $Id: aggregator.admin.inc,v 1.54 2010/06/26 19:55:47 dries Exp $ /** * @file @@ -300,7 +300,8 @@ function aggregator_form_opml_validate($form, &$form_state) { */ function aggregator_form_opml_submit($form, &$form_state) { $data = ''; - if ($file = file_save_upload('upload')) { + $validators = array('file_validate_extensions' => array('opml xml')); + if ($file = file_save_upload('upload', $validators)) { $data = file_get_contents($file->uri); } else { diff --git a/modules/aggregator/aggregator.info b/modules/aggregator/aggregator.info index 8d455f78..2e143ea7 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/aggregator/tests/aggregator_test.info b/modules/aggregator/tests/aggregator_test.info index 9b75fc65..68da94c3 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/block/block.admin.inc b/modules/block/block.admin.inc index d07f06e8..b0fc69bd 100644 --- a/modules/block/block.admin.inc +++ b/modules/block/block.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: block.admin.inc,v 1.80 2010/05/13 07:53:02 dries Exp $ +// $Id: block.admin.inc,v 1.83 2010/07/08 03:41:27 webchick Exp $ /** * @file @@ -80,6 +80,8 @@ function block_admin_display_form($form, &$form_state, $blocks, $theme) { '#type' => 'weight', '#default_value' => $block['weight'], '#delta' => $weight_delta, + '#title_display' => 'invisible', + '#title' => t('Weight for @row', array('@row' => $block['info'])), ); $form[$key]['region'] = array( '#type' => 'select', @@ -226,7 +228,7 @@ function block_admin_configure($form, &$form_state, $module, $delta) { '#tree' => TRUE, ); - $theme_default = variable_get('theme_default', 'garland'); + $theme_default = variable_get('theme_default', 'bartik'); foreach (list_themes() as $key => $theme) { // Only display enabled themes if ($theme->status) { @@ -270,10 +272,10 @@ function block_admin_configure($form, &$form_state, $module, $delta) { ); $access = user_access('use PHP for settings'); - if (isset($block->visibility) && $block->visibility == 2 && !$access) { + if (isset($block->visibility) && $block->visibility == BLOCK_VISIBILITY_PHP && !$access) { $form['visibility']['path']['visibility'] = array( '#type' => 'value', - '#value' => 2, + '#value' => BLOCK_VISIBILITY_PHP, ); $form['visibility']['path']['pages'] = array( '#type' => 'value', @@ -282,13 +284,13 @@ function block_admin_configure($form, &$form_state, $module, $delta) { } else { $options = array( - t('All pages except those listed'), - t('Only the listed pages'), + BLOCK_VISIBILITY_NOTLISTED => t('All pages except those listed'), + BLOCK_VISIBILITY_LISTED => t('Only the listed pages'), ); $description = t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page.", array('%blog' => 'blog', '%blog-wildcard' => 'blog/*', '%front' => '<front>')); if (module_exists('php') && $access) { - $options[] = t('Pages on which this PHP code returns <code>TRUE</code> (experts only)'); + $options += array(BLOCK_VISIBILITY_PHP => t('Pages on which this PHP code returns <code>TRUE</code> (experts only)')); $title = t('Pages or PHP code'); $description .= ' ' . t('If the PHP option is chosen, enter PHP code between %php. Note that executing incorrect PHP code can break your Drupal site.', array('%php' => '<?php ?>')); } @@ -299,7 +301,7 @@ function block_admin_configure($form, &$form_state, $module, $delta) { '#type' => 'radios', '#title' => t('Show block on specific pages'), '#options' => $options, - '#default_value' => isset($block->visibility) ? $block->visibility : 0, + '#default_value' => isset($block->visibility) ? $block->visibility : BLOCK_VISIBILITY_NOTLISTED, ); $form['visibility']['path']['pages'] = array( '#type' => 'textarea', @@ -344,12 +346,12 @@ function block_admin_configure($form, &$form_state, $module, $delta) { '#type' => 'radios', '#title' => t('Customizable per user'), '#options' => array( - t('Not customizable'), - t('Customizable, visible by default'), - t('Customizable, hidden by default'), + BLOCK_CUSTOM_FIXED => t('Not customizable'), + BLOCK_CUSTOM_ENABLED => t('Customizable, visible by default'), + BLOCK_CUSTOM_DISABLED => t('Customizable, hidden by default'), ), '#description' => t('Allow individual users to customize the visibility of this block in their account settings.'), - '#default_value' => isset($block->custom) ? $block->custom : 0, + '#default_value' => isset($block->custom) ? $block->custom : BLOCK_CUSTOM_FIXED, ); $form['actions'] = array('#type' => 'actions'); diff --git a/modules/block/block.info b/modules/block/block.info index 037493e6..893e7834 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/block/block.install b/modules/block/block.install index fafc39ea..dfe9fbfa 100644 --- a/modules/block/block.install +++ b/modules/block/block.install @@ -1,5 +1,5 @@ <?php -// $Id: block.install,v 1.41 2010/05/19 19:14:43 dries Exp $ +// $Id: block.install,v 1.44 2010/06/28 02:05:47 webchick Exp $ /** * @file @@ -91,7 +91,7 @@ function block_schema() { 'not null' => TRUE, 'default' => 1, 'size' => 'tiny', - 'description' => 'Binary flag to indicate block cache mode. (-1: Do not cache, 1: Cache per role, 2: Cache per user, 4: Cache per page, 8: Block cache global) See BLOCK_CACHE_* constants in block.module for more detailed information.', + 'description' => 'Binary flag to indicate block cache mode. (-2: Custom cache, -1: Do not cache, 1: Cache per role, 2: Cache per user, 4: Cache per page, 8: Block cache global) See DRUPAL_CACHE_* constants in ../includes/common.inc for more detailed information.', ), ), 'primary key' => array('bid'), @@ -188,6 +188,11 @@ function block_install() { ->execute(); } +/** + * @defgroup updates-6.x-to-7.x Block updates from 6.x to 7.x + * @{ + */ + /** * Set system.weight to a low value for block module. * @@ -249,7 +254,7 @@ function block_update_7004() { 'weight' => 0, 'region' => 'help', 'pages' => '', - 'cache' => 1, + 'cache' => DRUPAL_CACHE_PER_ROLE, )); // Add new system generated main page content block. $insert->values(array( @@ -260,7 +265,7 @@ function block_update_7004() { 'weight' => 0, 'region' => 'content', 'pages' => '', - 'cache' => -1, + 'cache' => DRUPAL_NO_CACHE, )); } $insert->execute(); @@ -296,9 +301,9 @@ function block_update_7004() { 'status' => 1, 'weight' => 5, 'region' => 'help', - 'visibility' => 1, + 'visibility' => BLOCK_VISIBILITY_LISTED, 'pages' => 'contact', - 'cache' => -1, + 'cache' => DRUPAL_NO_CACHE, )); } drupal_set_message('The contact form information setting was migrated to <a href="' . url('admin/structure/block/manage/block/' . $bid . '/configure') . '">a custom block</a> and set up to only show on the site-wide contact page. The block was set to use the default text format, which might differ from the HTML based format used before. Check the block and ensure that the output is right.'); @@ -319,9 +324,9 @@ function block_update_7004() { 'status' => 1, 'weight' => 5, 'region' => 'help', - 'visibility' => 1, + 'visibility' => BLOCK_VISIBILITY_LISTED, 'pages' => 'user/register', - 'cache' => -1, + 'cache' => DRUPAL_NO_CACHE, )); } drupal_set_message('The user registration guidelines were migrated to <a href="' . url('admin/structure/block/manage/block/' . $bid . '/configure') . '">a custom block</a> and set up to only show on the user registration page. The block was set to use the default text format, which might differ from the HTML based format used before. Check the block and ensure that the output is right.'); @@ -342,9 +347,9 @@ function block_update_7004() { 'status' => 1, 'weight' => 0, 'region' => 'highlight', - 'visibility' => 1, + 'visibility' => BLOCK_VISIBILITY_LISTED, 'pages' => '<front>', - 'cache' => -1, + 'cache' => DRUPAL_NO_CACHE, )); } drupal_set_message('The site mission was migrated to <a href="' . url('admin/structure/block/manage/block/' . $bid . '/configure') . '">a custom block</a> and set up to only show on the front page in the highlighted content region. The block was set to use the default text format, which might differ from the HTML based format used before. Check the block and ensure that the output is right. If your theme does not have a highlighted content region, you might need to <a href="' . url('admin/structure/block') . '">relocate the block</a>.'); @@ -370,7 +375,7 @@ function block_update_7004() { 'weight' => -10, 'region' => 'footer', 'pages' => '', - 'cache' => -1, + 'cache' => DRUPAL_NO_CACHE, )); } drupal_set_message('The footer message was migrated to <a href="' . url('admin/structure/block/manage/block/' . $bid . '/configure') . '">a custom block</a> and set up to appear in the footer. The block was set to use the default text format, which might differ from the HTML based format used before. Check the block and ensure that the output is right. If your theme does not have a footer region, you might need to <a href="' . url('admin/structure/block') . '">relocate the block</a>.'); @@ -389,8 +394,33 @@ function block_update_7004() { } /** - * Remove {cache_block}.headers column. + * Update the {block_custom}.format column. */ function block_update_7005() { - db_drop_field('cache_block', 'headers'); + // It was previously possible for a value of "0" to be stored in database + // tables to indicate that a particular piece of text should be filtered + // using the default text format. + $default_format = variable_get('filter_default_format', 1); + db_update('block_custom') + ->fields(array('format' => $default_format)) + ->condition('format', 0) + ->execute(); +} + +/** + * Recreates cache_block table. + * + * Converts fields that hold serialized variables from text to blob. + * Removes 'headers' column. + */ +function block_update_7006() { + $schema = system_schema_cache_7054(); + + db_drop_table('cache_block'); + db_create_table('cache_block', $schema); } + +/** + * @} End of "defgroup updates-6.x-to-7.x" + * The next series of updates should start at 8000. + */ diff --git a/modules/block/block.module b/modules/block/block.module index 03ec5152..be4537a2 100644 --- a/modules/block/block.module +++ b/modules/block/block.module @@ -1,5 +1,5 @@ <?php -// $Id: block.module,v 1.421 2010/05/13 07:53:02 dries Exp $ +// $Id: block.module,v 1.426 2010/07/08 03:41:27 webchick Exp $ /** * @file @@ -11,6 +11,36 @@ */ define('BLOCK_REGION_NONE', -1); +/** + * Users cannot control whether or not they see this block. + */ +define('BLOCK_CUSTOM_FIXED', 0); + +/** + * Show this block by default, but let individual users hide it. + */ +define('BLOCK_CUSTOM_ENABLED', 1); + +/** + * Hide this block by default but let individual users show it. + */ +define('BLOCK_CUSTOM_DISABLED', 2); + +/** + * Show this block on every page except the listed pages. + */ +define('BLOCK_VISIBILITY_NOTLISTED', 0); + +/** + * Show this block on only the listed pages. + */ +define('BLOCK_VISIBILITY_LISTED', 1); + +/** + * Show this block if the associated PHP code returns TRUE. + */ +define('BLOCK_VISIBILITY_PHP', 2); + /** * Implements hook_help(). */ @@ -34,7 +64,7 @@ function block_help($path, $arg) { return '<p>' . t('Use this page to create a new custom block.') . '</p>'; } if ($arg[0] == 'admin' && $arg[1] == 'structure' && $arg['2'] == 'block' && (empty($arg[3]) || $arg[3] == 'list')) { - $demo_theme = !empty($arg[4]) ? $arg[4] : variable_get('theme_default', 'garland'); + $demo_theme = !empty($arg[4]) ? $arg[4] : variable_get('theme_default', 'bartik'); $themes = list_themes(); $output = '<p>' . t('This page provides a drag-and-drop interface for assigning a block to a region, and for controlling the order of blocks within regions. Since not all themes implement the same regions, or display regions in the same way, blocks are positioned on a per-theme basis. Remember that your changes will not be saved until you click the <em>Save blocks</em> button at the bottom of the page. Click the <em>configure</em> link next to each block to configure its specific title and visibility settings.') . '</p>'; $output .= '<p>' . l(t('Demonstrate block regions (@theme)', array('@theme' => $themes[$demo_theme]->info['name'])), 'admin/structure/block/demo/' . $demo_theme) . '</p>'; @@ -74,7 +104,7 @@ function block_permission() { * Implements hook_menu(). */ function block_menu() { - $default_theme = variable_get('theme_default', 'garland'); + $default_theme = variable_get('theme_default', 'bartik'); $items['admin/structure/block'] = array( 'title' => 'Blocks', 'description' => 'Configure what block content appears in your site\'s sidebars and other regions.', @@ -242,6 +272,15 @@ function block_page_build(&$page) { $page[$region] = $blocks; } } + // Once we've finished attaching all blocks to the page, clear the static + // cache to allow modules to alter the block list differently in different + // contexts. For example, any code that triggers hook_page_build() more + // than once in the same page request may need to alter the block list + // differently each time, so that only certain parts of the page are + // actually built. We do not clear the cache any earlier than this, though, + // because it is used each time block_get_blocks_by_region() gets called + // above. + drupal_static_reset('block_list'); } else { // Append region description if we are rendering the regions demo page. @@ -356,6 +395,11 @@ function _block_rehash($theme = NULL) { foreach ($database_blocks as $block) { // Preserve info which is not in the database. $block->info = $current_blocks[$block->module][$block->delta]['info']; + // The cache mode can only by set from hook_block_info(), so that has + // precedence over the database's value. + if (isset($current_blocks[$block->module][$block->delta]['cache'])) { + $block->cache = $current_blocks[$block->module][$block->delta]['cache']; + } // Blocks stored in the database override the blocks defined in code. $current_blocks[$block->module][$block->delta] = get_object_vars($block); // Preserve this block. @@ -386,7 +430,7 @@ function _block_rehash($theme = NULL) { $block['region'] = BLOCK_REGION_NONE; } // There is no point saving disabled blocks. Still, we need to save them - // beecause the 'title' attribute is saved to the {blocks} table. + // because the 'title' attribute is saved to the {blocks} table. if (isset($block['bid'])) { // If the block has a bid property, it comes from the database and // the record needs to be updated, so set the primary key to 'bid' @@ -548,12 +592,12 @@ function block_theme_initialize($theme) { // Initialize theme's blocks if none already registered. $has_blocks = (bool) db_query_range('SELECT 1 FROM {block} WHERE theme = :theme', 0, 1, array(':theme' => $theme))->fetchField(); if (!$has_blocks) { - $default_theme = variable_get('theme_default', 'garland'); + $default_theme = variable_get('theme_default', 'bartik'); $regions = system_region_list($theme); $result = db_query("SELECT * FROM {block} WHERE theme = :theme", array(':theme' => $default_theme), array('fetch' => PDO::FETCH_ASSOC)); foreach ($result as $block) { // If the region isn't supported by the theme, assign the block to the theme's default region. - if (!array_key_exists($block['region'], $regions)) { + if ($block['status'] && !array_key_exists($block['region'], $regions)) { $block['region'] = system_default_region($theme); } $block['theme'] = $theme; @@ -617,7 +661,7 @@ function block_load($module, $delta) { // If the block does not exist in the database yet return a stub block // object. if (empty($block)) { - $block = new stdClass; + $block = new stdClass(); $block->module = $module; $block->delta = $delta; } @@ -686,12 +730,12 @@ function block_block_list_alter(&$blocks) { } // Use the user's block visibility setting, if necessary. - if ($block->custom != 0) { + if ($block->custom != BLOCK_CUSTOM_FIXED) { if ($user->uid && isset($user->block[$block->module][$block->delta])) { $enabled = $user->block[$block->module][$block->delta]; } else { - $enabled = ($block->custom == 1); + $enabled = ($block->custom == BLOCK_CUSTOM_ENABLED); } } else { @@ -707,7 +751,7 @@ function block_block_list_alter(&$blocks) { // Convert path to lowercase. This allows comparison of the same path // with different case. Ex: /Page, /page, /PAGE. $pages = drupal_strtolower($block->pages); - if ($block->visibility < 2) { + if ($block->visibility < BLOCK_VISIBILITY_PHP) { // Convert the Drupal path to lowercase $path = drupal_strtolower(drupal_get_path_alias($_GET['q'])); // Compare the lowercase internal and lowercase path alias (if any). @@ -715,9 +759,10 @@ function block_block_list_alter(&$blocks) { if ($path != $_GET['q']) { $page_match = $page_match || drupal_match_path($_GET['q'], $pages); } - // When $block->visibility has a value of 0, the block is displayed on - // all pages except those listed in $block->pages. When set to 1, it - // is displayed only on those pages listed in $block->pages. + // When $block->visibility has a value of 0 (BLOCK_VISIBILITY_NOTLISTED), + // the block is displayed on all pages except those listed in $block->pages. + // When set to 1 (BLOCK_VISIBILITY_LISTED), it is displayed only on those + // pages listed in $block->pages. $page_match = !($block->visibility xor $page_match); } elseif (module_exists('php')) { @@ -833,6 +878,14 @@ function _block_get_cache_id($block) { * Implements hook_flush_caches(). */ function block_flush_caches() { + // Rehash blocks for active themes. We don't use list_themes() here, + // because if MAINTENANCE_MODE is defined it skips reading the database, + // and we can't tell which themes are active. + $themes = db_query("SELECT name FROM {system} WHERE type = 'theme' AND status = 1"); + foreach ($themes as $theme) { + _block_rehash($theme->name); + } + return array('cache_block'); } diff --git a/modules/block/block.test b/modules/block/block.test index 595e6aa8..ec7e2d0e 100644 --- a/modules/block/block.test +++ b/modules/block/block.test @@ -1,5 +1,5 @@ <?php -// $Id: block.test,v 1.52 2010/04/26 14:10:40 dries Exp $ +// $Id: block.test,v 1.54 2010/07/08 03:41:27 webchick Exp $ /** * @file @@ -33,11 +33,11 @@ class BlockTestCase extends DrupalWebTestCase { // Define the existing regions $this->regions = array(); - $this->regions[] = array('name' => 'header', 'class' => 'region region-header clearfix'); - $this->regions[] = array('name' => 'sidebar_first'); - $this->regions[] = array('name' => 'content'); - $this->regions[] = array('name' => 'sidebar_second'); - $this->regions[] = array('name' => 'footer'); + $this->regions[] = 'header'; + $this->regions[] = 'sidebar_first'; + $this->regions[] = 'content'; + $this->regions[] = 'sidebar_second'; + $this->regions[] = 'footer'; } /** @@ -120,7 +120,7 @@ class BlockTestCase extends DrupalWebTestCase { // Set the created custom block to a specific region. $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); $edit = array(); - $edit['block_' . $bid . '[region]'] = $this->regions[1]['name']; + $edit['block_' . $bid . '[region]'] = $this->regions[1]; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); // Confirm that the custom block is being displayed using configured text format. @@ -228,7 +228,7 @@ class BlockTestCase extends DrupalWebTestCase { // For convenience of developers, put the navigation block back. $edit = array(); - $edit[$block['module'] . '_' . $block['delta'] . '[region]'] = $this->regions[1]['name']; + $edit[$block['module'] . '_' . $block['delta'] . '[region]'] = $this->regions[1]; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); $this->assertText(t('The block settings have been updated.'), t('Block successfully move to first sidebar region.')); @@ -237,18 +237,13 @@ class BlockTestCase extends DrupalWebTestCase { } function moveBlockToRegion($block, $region) { - // If an id for an region hasn't been specified, we assume it's the same as the name. - if (!(isset($region['class']))) { - $region['class'] = 'region region-' . str_replace('_', '-', $region['name']); - } - // Set the created block to a specific region. $edit = array(); - $edit[$block['module'] . '_' . $block['delta'] . '[region]'] = $region['name']; + $edit[$block['module'] . '_' . $block['delta'] . '[region]'] = $region; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); // Confirm that the block was moved to the proper region. - $this->assertText(t('The block settings have been updated.'), t('Block successfully moved to %region_name region.', array( '%region_name' => $region['name']))); + $this->assertText(t('The block settings have been updated.'), t('Block successfully moved to %region_name region.', array( '%region_name' => $region))); // Confirm that the block is being displayed. $this->drupalGet('node'); @@ -256,10 +251,33 @@ class BlockTestCase extends DrupalWebTestCase { // Confirm that the custom block was found at the proper region. $xpath = $this->buildXPathQuery('//div[@class=:region-class]//div[@id=:block-id]/*', array( - ':region-class' => $region['class'], + ':region-class' => 'region region-' . str_replace('_', '-', $region), ':block-id' => 'block-' . $block['module'] . '-' . $block['delta'], )); - $this->assertFieldByXPath($xpath, FALSE, t('Custom block found in %region_name region.', array('%region_name' => $region['name']))); + $this->assertFieldByXPath($xpath, FALSE, t('Custom block found in %region_name region.', array('%region_name' => $region))); + } + + /** + * Test _block_rehash(). + */ + function testBlockRehash() { + module_enable(array('block_test')); + $this->assertTrue(module_exists('block_test'), t('Test block module enabled.')); + + // Our new block should be inserted in the database when we visit the + // block management page. + $this->drupalGet('admin/structure/block'); + // Our test block's caching should default to DRUPAL_CACHE_PER_ROLE. + $current_caching = db_query("SELECT cache FROM {block} WHERE module = 'block_test' AND delta = 'test_cache'")->fetchField(); + $this->assertEqual($current_caching, DRUPAL_CACHE_PER_ROLE, t('Test block cache mode defaults to DRUPAL_CACHE_PER_ROLE.')); + + // Disable caching for this block. + variable_set('block_test_caching', DRUPAL_NO_CACHE); + // Flushing all caches should call _block_rehash(). + drupal_flush_all_caches(); + // Verify that the database is updated with the new caching mode. + $current_caching = db_query("SELECT cache FROM {block} WHERE module = 'block_test' AND delta = 'test_cache'")->fetchField(); + $this->assertEqual($current_caching, DRUPAL_NO_CACHE, t("Test block's database entry updated to DRUPAL_NO_CACHE.")); } } @@ -304,12 +322,17 @@ class NewDefaultThemeBlocks extends DrupalWebTestCase { $this->drupalLogin($admin_user); // Ensure no other theme's blocks are in the block table yet. - $count = db_query_range("SELECT 1 FROM {block} WHERE theme NOT IN ('garland', 'seven')", 0, 1)->fetchField(); - $this->assertFalse($count, t('Only Garland and Seven have blocks.')); + $themes = array(); + $themes['default'] = variable_get('theme_default', 'bartik'); + if ($admin_theme = variable_get('admin_theme')) { + $themes['admin'] = $admin_theme; + } + $count = db_query_range('SELECT 1 FROM {block} WHERE theme NOT IN (:themes)', 0, 1, array(':themes' => $themes))->fetchField(); + $this->assertFalse($count, t('Only the default theme and the admin theme have blocks.')); // Populate list of all blocks for matching against new theme. $blocks = array(); - $result = db_query("SELECT * FROM {block} WHERE theme = 'garland'"); + $result = db_query('SELECT * FROM {block} WHERE theme = :theme', array(':theme' => $themes['default'])); foreach ($result as $block) { // $block->theme and $block->bid will not match, so remove them. unset($block->theme, $block->bid); @@ -317,10 +340,10 @@ class NewDefaultThemeBlocks extends DrupalWebTestCase { } // Turn on the Stark theme and ensure that it contains all of the blocks - // that Garland did. + // the default theme had. theme_enable(array('stark')); variable_set('theme_default', 'stark'); - $result = db_query("SELECT * FROM {block} WHERE theme='stark'"); + $result = db_query('SELECT * FROM {block} WHERE theme = :theme', array(':theme' => 'stark')); foreach ($result as $block) { unset($block->theme, $block->bid); $this->assertEqual($blocks[$block->module][$block->delta], $block, t('Block %name matched', array('%name' => $block->module . '-' . $block->delta))); diff --git a/modules/block/tests/block_test.info b/modules/block/tests/block_test.info index 6d4d48fa..c7234fe5 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/block/tests/block_test.module b/modules/block/tests/block_test.module index 5f2ae785..1957f24a 100644 --- a/modules/block/tests/block_test.module +++ b/modules/block/tests/block_test.module @@ -1,5 +1,5 @@ <?php -// $Id: block_test.module,v 1.2 2010/04/26 14:10:40 dries Exp $ +// $Id: block_test.module,v 1.3 2010/06/15 16:19:28 dries Exp $ /** * @file @@ -12,6 +12,7 @@ function block_test_block_info() { $blocks['test_cache'] = array( 'info' => t('Test block caching'), + 'cache' => variable_get('block_test_caching', DRUPAL_CACHE_PER_ROLE), ); $blocks['test_html_id'] = array( diff --git a/modules/blog/blog.info b/modules/blog/blog.info index bdb83120..40bdc2d4 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/blog/blog.module b/modules/blog/blog.module index 132c554e..07bf67c7 100644 --- a/modules/blog/blog.module +++ b/modules/blog/blog.module @@ -1,5 +1,5 @@ <?php -// $Id: blog.module,v 1.353 2010/04/17 12:46:32 dries Exp $ +// $Id: blog.module,v 1.356 2010/05/29 07:53:44 dries Exp $ /** * @file @@ -77,7 +77,7 @@ function blog_view($node, $view_mode) { /** * Implements hook_node_view(). */ -function blog_node_view($node, $view_mode = 'full') { +function blog_node_view($node, $view_mode) { if ($view_mode != 'rss') { if ($node->type == 'blog' && (arg(0) != 'blog' || arg(1) != $node->uid)) { $links['blog_usernames_blog'] = array( @@ -137,6 +137,8 @@ function blog_menu() { * Implements hook_menu_local_tasks_alter(). */ function blog_menu_local_tasks_alter(&$data, $router_item, $root_path) { + global $user; + // Add action link to 'node/add/blog' on 'blog' page. if ($root_path == 'blog') { $item = menu_get_item('node/add/blog'); @@ -148,6 +150,19 @@ function blog_menu_local_tasks_alter(&$data, $router_item, $root_path) { ); } } + // Provide a helper action link to the author on the 'blog/%' page. + else if ($root_path == 'blog/%' && $router_item['page_arguments'][0]->uid == $user->uid) { + $data['actions']['output']['blog'] = array( + '#theme' => 'menu_local_action', + ); + if (user_access('create blog content')) { + $data['actions']['output']['blog']['#link']['title'] = t('Post new blog entry.'); + $data['actions']['output']['blog']['#link']['href'] = 'node/add/blog'; + } + else { + $data['actions']['output']['blog']['#link']['title'] = t('You are not allowed to post a new blog entry.'); + } + } } /** @@ -226,9 +241,14 @@ function blog_block_view($delta = '') { ->execute(); if ($node_title_list = node_title_list($result)) { - $block['content'] = $node_title_list; - $block['content'] .= theme('more_link', array('url' => url('blog'), 'title' => t('Read the latest blog entries.'))); $block['subject'] = t('Recent blog posts'); + $block['content']['blog_list'] = $node_title_list; + $block['content']['blog_more'] = array( + '#theme' => 'more_link', + '#url' => url('blog'), + '#title' => t('Read the latest blog entries.'), + ); + return $block; } } diff --git a/modules/blog/blog.pages.inc b/modules/blog/blog.pages.inc index 6c30e67f..2ce63956 100644 --- a/modules/blog/blog.pages.inc +++ b/modules/blog/blog.pages.inc @@ -1,5 +1,5 @@ <?php -// $Id: blog.pages.inc,v 1.26 2009/12/21 13:47:31 dries Exp $ +// $Id: blog.pages.inc,v 1.27 2010/05/24 04:51:18 webchick Exp $ /** * @file @@ -14,20 +14,7 @@ function blog_page_user($account) { drupal_set_title($title = t("@name's blog", array('@name' => format_username($account))), PASS_THROUGH); - $items = array(); - - if (($account->uid == $user->uid) && user_access('create blog content')) { - $items[] = l(t('Post new blog entry.'), "node/add/blog"); - } - elseif ($account->uid == $user->uid) { - $items[] = t('You are not allowed to post a new blog entry.'); - } - - $build['blog_actions'] = array( - '#items' => $items, - '#theme' => 'item_list', - '#weight' => -1, - ); + $build = array(); $query = db_select('node', 'n')->extend('PagerDefault'); $nids = $query diff --git a/modules/blog/blog.test b/modules/blog/blog.test index 66452b82..78080536 100644 --- a/modules/blog/blog.test +++ b/modules/blog/blog.test @@ -1,5 +1,5 @@ <?php -// $Id: blog.test,v 1.26 2010/03/18 06:26:36 dries Exp $ +// $Id: blog.test,v 1.27 2010/07/08 03:41:27 webchick Exp $ class BlogTestCase extends DrupalWebTestCase { protected $big_user; @@ -122,9 +122,6 @@ class BlogTestCase extends DrupalWebTestCase { * HTTP response code. */ private function verifyBlogs($node_user, $node, $admin, $response = 200) { - $crumb = '›'; - $quote = '''; - $response2 = ($admin) ? 200 : 403; // View blog help node. @@ -144,7 +141,12 @@ class BlogTestCase extends DrupalWebTestCase { $this->drupalGet('node/' . $node->nid); $this->assertResponse(200); $this->assertTitle($node->title . ' | Drupal', t('Blog node was displayed')); - $this->assertText(t('Home ' . $crumb . ' Blogs ' . $crumb . ' @name' . $quote . 's blog', array('@name' => format_username($node_user))), t('Breadcrumbs were displayed')); + $breadcrumb = array( + l(t('Home'), NULL), + l(t('Blogs'), 'blog'), + l(t("!name's blog", array('!name' => format_username($node_user))), 'blog/' . $node_user->uid), + ); + $this->assertRaw(theme('breadcrumb', array('breadcrumb' => $breadcrumb)), t('Breadcrumbs were displayed')); // View blog edit node. $this->drupalGet('node/' . $node->nid . '/edit'); @@ -176,8 +178,6 @@ class BlogTestCase extends DrupalWebTestCase { * The logged in user. */ private function verifyBlogLinks($user) { - $crumb = '›'; - // Confirm blog entries link exists on the user page. $this->drupalGet('user/' . $user->uid); $this->assertResponse(200); diff --git a/modules/book/book.admin.inc b/modules/book/book.admin.inc index 819765d3..d41da65c 100644 --- a/modules/book/book.admin.inc +++ b/modules/book/book.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: book.admin.inc,v 1.35 2010/05/01 08:12:22 dries Exp $ +// $Id: book.admin.inc,v 1.36 2010/06/26 21:32:20 dries Exp $ /** * @file @@ -34,7 +34,7 @@ function book_admin_settings() { $form['book_allowed_types'] = array( '#type' => 'checkboxes', '#title' => t('Content types allowed in book outlines'), - '#default_value' => array('book'), + '#default_value' => variable_get('book_allowed_types', array('book')), '#options' => $types, '#description' => t('Users with the %outline-perm permission can add all content types.', array('%outline-perm' => t('Administer book outlines'))), '#required' => TRUE, @@ -42,14 +42,14 @@ function book_admin_settings() { $form['book_child_type'] = array( '#type' => 'radios', '#title' => t('Content type for child pages'), - '#default_value' => 'book', + '#default_value' => variable_get('book_child_type', 'book'), '#options' => $types, '#required' => TRUE, ); $form['array_filter'] = array('#type' => 'value', '#value' => TRUE); $form['#validate'][] = 'book_admin_settings_validate'; - return system_settings_form($form, TRUE); + return system_settings_form($form); } /** diff --git a/modules/book/book.info b/modules/book/book.info index e13d43e5..2c1d22b2 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/book/book.install b/modules/book/book.install index 3f0a14eb..f8782cf1 100644 --- a/modules/book/book.install +++ b/modules/book/book.install @@ -1,5 +1,5 @@ <?php -// $Id: book.install,v 1.35 2010/05/05 06:55:25 webchick Exp $ +// $Id: book.install,v 1.36 2010/06/23 19:05:15 dries Exp $ /** * @file @@ -19,7 +19,9 @@ function book_install() { */ function book_uninstall() { // Delete menu links. - db_query("DELETE FROM {menu_links} WHERE module = 'book'"); + db_delete('menu_links') + ->condition('module', 'book') + ->execute(); menu_cache_clear_all(); } diff --git a/modules/book/book.module b/modules/book/book.module index 35529981..04a8f8ca 100644 --- a/modules/book/book.module +++ b/modules/book/book.module @@ -1,5 +1,5 @@ <?php -// $Id: book.module,v 1.542 2010/05/06 05:59:31 webchick Exp $ +// $Id: book.module,v 1.547 2010/07/07 01:07:33 dries Exp $ /** * @file @@ -189,13 +189,6 @@ function book_menu() { 'type' => MENU_CALLBACK, 'file' => 'book.pages.inc', ); - $items['book/js/form'] = array( - 'page callback' => 'book_form_update', - 'delivery callback' => 'ajax_deliver', - 'access arguments' => array('access content'), - 'type' => MENU_CALLBACK, - 'file' => 'book.pages.inc', - ); return $items; } @@ -240,6 +233,7 @@ function book_entity_info_alter(&$info) { $info['node']['view modes'] += array( 'print' => array( 'label' => t('Print'), + 'custom settings' => FALSE, ), ); } @@ -369,12 +363,12 @@ function book_get_books() { if ($nids) { $query = db_select('book', 'b', array('fetch' => PDO::FETCH_ASSOC)); - $node_alias = $query->join('node', 'n', 'b.nid = n.nid'); - $menu_links_alias = $query->join('menu_links', 'ml', 'b.mlid = ml.mlid'); + $query->join('node', 'n', 'b.nid = n.nid'); + $query->join('menu_links', 'ml', 'b.mlid = ml.mlid'); $query->addField('n', 'type', 'type'); $query->addField('n', 'title', 'title'); $query->fields('b'); - $query->fields($menu_links_alias); + $query->fields('ml'); $query->condition('n.nid', $nids, 'IN'); $query->condition('n.status', 1); $query->orderBy('ml.weight'); @@ -396,9 +390,6 @@ function book_get_books() { * Implements hook_form_alter(). * * Adds the book fieldset to the node form. - * - * @see book_pick_book_submit() - * @see book_submit() */ function book_form_alter(&$form, &$form_state, $form_id) { if (!empty($form['#node_edit_form'])) { @@ -415,21 +406,35 @@ function book_form_alter(&$form, &$form_state, $form_id) { if ($access) { _book_add_form_elements($form, $form_state, $node); + // Since the "Book" dropdown can't trigger a form submission when + // JavaScript is disabled, add a submit button to do that. book.css hides + // this button when JavaScript is enabled. $form['book']['pick-book'] = array( '#type' => 'submit', '#value' => t('Change book (update list of parents)'), - // Submit the node form so the parent select options get updated. - // This is typically only used when JS is disabled. Since the parent options - // won't be changed via AJAX, a button is provided in the node form to submit - // the form and generate options in the parent select corresponding to the - // selected book. This is similar to what happens during a node preview. - '#submit' => array('node_form_submit_build_node'), + '#submit' => array('book_pick_book_nojs_submit'), '#weight' => 20, ); } } } +/** + * Submit handler to change a node's book. + * + * This handler is run when JavaScript is disabled. It triggers the form to + * rebuild so that the "Parent item" options are changed to reflect the newly + * selected book. When JavaScript is enabled, the submit button that triggers + * this handler is hidden, and the "Book" dropdown directly triggers the + * book_form_update() AJAX callback instead. + * + * @see book_form_update() + */ +function book_pick_book_nojs_submit($form, &$form_state) { + $form_state['node']->book = $form_state['values']['book']; + $form_state['rebuild'] = TRUE; +} + /** * Build the parent selection form element for the node form or outline tab. * @@ -469,6 +474,8 @@ function _book_parent_select($book_link) { '#description' => t('The parent page in the book. The maximum depth for a book and all child pages is !maxdepth. Some pages in the selected book may not be available as parents if selecting them would exceed this limit.', array('!maxdepth' => MENU_MAX_DEPTH)), '#options' => book_toc($book_link['bid'], $book_link['parent_depth_limit'], array($book_link['mlid'])), '#attributes' => array('class' => array('book-title-select')), + '#prefix' => '<div id="edit-book-plid-wrapper">', + '#suffix' => '</div>', ); } @@ -479,8 +486,11 @@ function _book_parent_select($book_link) { * Build the common elements of the book form for the node and outline forms. */ function _book_add_form_elements(&$form, &$form_state, $node) { - // Need this for AJAX. - $form_state['cache'] = TRUE; + // If the form is being processed during the AJAX callback of our book bid + // dropdown, then $form_state will hold the value that was selected. + if (isset($form_state['values']['book'])) { + $node->book = $form_state['values']['book']; + } $form['book'] = array( '#type' => 'fieldset', @@ -545,7 +555,7 @@ function _book_add_form_elements(&$form, &$form_state, $node) { '#weight' => -5, '#attributes' => array('class' => array('book-title-select')), '#ajax' => array( - 'path' => 'book/js/form', + 'callback' => 'book_form_update', 'wrapper' => 'edit-book-plid-wrapper', 'effect' => 'fade', 'speed' => 'fast', @@ -553,6 +563,19 @@ function _book_add_form_elements(&$form, &$form_state, $node) { ); } +/** + * Renders a new parent page select element when the book selection changes. + * + * This function is called via AJAX when the selected book is changed on a node + * or book outline form. + * + * @return + * The rendered parent page select element. + */ +function book_form_update($form, $form_state) { + return $form['book']['plid']; +} + /** * Common helper function to handles additions and updates to the book outline. * @@ -1253,10 +1276,10 @@ function book_menu_subtree_data($link) { // If the subtree data was not in the cache, $data will be NULL. if (!isset($data)) { $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC)); - $menu_router_alias = $query->join('menu_router', 'm', 'm.path = ml.router_path'); - $book_alias = $query->join('book', 'b', 'ml.mlid = b.mlid'); - $query->fields($book_alias); - $query->fields($menu_router_alias, array('load_functions', 'to_arg_functions', 'access_callback', 'access_arguments', 'page_callback', 'page_arguments', 'delivery_callback', 'title', 'title_callback', 'title_arguments', 'type')); + $query->join('menu_router', 'm', 'm.path = ml.router_path'); + $query->join('book', 'b', 'ml.mlid = b.mlid'); + $query->fields('b'); + $query->fields('m', array('load_functions', 'to_arg_functions', 'access_callback', 'access_arguments', 'page_callback', 'page_arguments', 'delivery_callback', 'title', 'title_callback', 'title_arguments', 'type')); $query->fields('ml'); $query->condition('menu_name', $link['menu_name']); for ($i = 1; $i <= MENU_MAX_DEPTH && $link["p$i"]; ++$i) { diff --git a/modules/book/book.pages.inc b/modules/book/book.pages.inc index a3d202f1..14c8342d 100644 --- a/modules/book/book.pages.inc +++ b/modules/book/book.pages.inc @@ -1,5 +1,5 @@ <?php -// $Id: book.pages.inc,v 1.25 2010/03/13 06:55:50 dries Exp $ +// $Id: book.pages.inc,v 1.27 2010/07/07 01:07:33 dries Exp $ /** * @file @@ -217,39 +217,3 @@ function book_remove_form_submit($form, &$form_state) { } $form_state['redirect'] = 'node/' . $node->nid; } - -/** - * AJAX callback to replace the book parent select options. - * - * This function is called when the selected book is changed. It updates the - * cached form (either the node form or the book outline form) and returns - * rendered output to be used to replace the select containing the possible - * parent pages in the newly selected book. - * - * @param $build_id - * The form's build_id. - * @param $bid - * A bid from from among those in the form's book select. - * @return - * Prints the replacement HTML in JSON format. - */ -function book_form_update() { - // Load the form based upon the $_POST data sent via the ajax call. - list($form, $form_state) = ajax_get_form(); - - $bid = $_POST['book']['bid']; - - // Validate the bid. - if (isset($form['book']['bid']['#options'][$bid])) { - $book_link = $form['#node']->book; - $book_link['bid'] = $bid; - // Get the new options and update the cache. - $form['book']['plid'] = _book_parent_select($book_link); - form_set_cache($form['values']['form_build_id'], $form, $form_state); - - // Build the new select element and return it. - $form_state = array(); - $form = form_builder($form['form_id']['#value'], $form, $form_state); - return $form['book']['plid']; - } -} diff --git a/modules/color/color.info b/modules/color/color.info index 6c4cfa56..a6d121dd 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/color/color.module b/modules/color/color.module index 0b5a8898..5966dcf9 100644 --- a/modules/color/color.module +++ b/modules/color/color.module @@ -1,5 +1,5 @@ <?php -// $Id: color.module,v 1.86 2010/05/01 08:12:23 dries Exp $ +// $Id: color.module,v 1.87 2010/06/30 15:03:06 dries Exp $ /** * Implements hook_help(). @@ -406,7 +406,6 @@ function _color_rewrite_stylesheet($theme, &$info, &$paths, $palette, $style) { $themes = list_themes(); // Prepare color conversion table. $conversion = $palette; - unset($conversion['base']); foreach ($conversion as $k => $v) { $conversion[$k] = drupal_strtolower($v); } diff --git a/modules/color/color.test b/modules/color/color.test index c4344b22..d7405a3b 100644 --- a/modules/color/color.test +++ b/modules/color/color.test @@ -1,5 +1,5 @@ <?php -// $Id: color.test,v 1.4 2010/04/11 18:33:43 dries Exp $ +// $Id: color.test,v 1.5 2010/07/08 03:41:27 webchick Exp $ /** * @file @@ -22,8 +22,13 @@ class ColorTestCase extends DrupalWebTestCase { function setUp() { parent::setUp('color'); + // Create users. $this->big_user = $this->drupalCreateUser(array('administer themes')); + + // This test relies on Garland, the mother of all the colorable themes. + theme_enable(array('garland')); + variable_set('theme_default', 'garland'); } /** @@ -55,7 +60,6 @@ class ColorTestCase extends DrupalWebTestCase { $stylesheet_content = join("\n", file($stylesheets[0])); $matched = preg_match('/(.*color: #0c7a00.*)/i', $stylesheet_content, $matches); $this->assertTrue($matched == 1, 'Make sure the color we changed is in the color stylesheet.'); - } } diff --git a/modules/comment/comment.admin.inc b/modules/comment/comment.admin.inc index 5434cd34..e8ad4d43 100644 --- a/modules/comment/comment.admin.inc +++ b/modules/comment/comment.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: comment.admin.inc,v 1.46 2010/03/31 11:49:50 dries Exp $ +// $Id: comment.admin.inc,v 1.47 2010/06/25 21:24:35 dries Exp $ /** * @file @@ -16,19 +16,17 @@ function comment_admin($type = 'new') { return drupal_get_form('comment_multiple_delete_confirm'); } else { - return drupal_get_form('comment_admin_overview', $type, arg(4)); + return drupal_get_form('comment_admin_overview', $type); } } /** - * Form builder; Builds the comment overview form for the admin. + * Form builder for the comment overview administration form. * - * @param $type - * Not used. * @param $arg - * Current path's fourth component deciding the form type (Published comments/Approval queue). - * @return - * The form structure. + * Current path's fourth component: the type of overview form ('approval' or + * 'new'). + * * @ingroup forms * @see comment_admin_overview_validate() * @see comment_admin_overview_submit() diff --git a/modules/comment/comment.info b/modules/comment/comment.info index d646a442..15590402 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/comment/comment.install b/modules/comment/comment.install index f5939a1a..29449e12 100644 --- a/modules/comment/comment.install +++ b/modules/comment/comment.install @@ -1,5 +1,5 @@ <?php -// $Id: comment.install,v 1.66 2010/04/17 12:54:02 dries Exp $ +// $Id: comment.install,v 1.67 2010/05/23 19:10:22 dries Exp $ /** * @file @@ -279,10 +279,12 @@ function comment_update_7012() { 'label' => 'Comment', 'entity_type' => 'comment', 'settings' => array('text_processing' => 1), - // Hide field label by default. + 'required' => TRUE, 'display' => array( - 'full' => array( + 'default' => array( 'label' => 'hidden', + 'type' => 'text_default', + 'weight' => 0, ), ), ); diff --git a/modules/comment/comment.module b/modules/comment/comment.module index 4ac23e9b..1d818f59 100644 --- a/modules/comment/comment.module +++ b/modules/comment/comment.module @@ -1,5 +1,5 @@ <?php -// $Id: comment.module,v 1.875 2010/05/10 20:12:21 webchick Exp $ +// $Id: comment.module,v 1.883 2010/06/23 02:45:35 dries Exp $ /** * @file @@ -108,6 +108,7 @@ function comment_entity_info() { 'view modes' => array( 'full' => array( 'label' => t('Full comment'), + 'custom settings' => FALSE, ), ), 'static cache' => FALSE, @@ -165,15 +166,17 @@ function comment_field_extra_fields() { foreach (node_type_get_types() as $type) { if (variable_get('comment_subject_field_' . $type->type, 1) == 1) { $return['comment']['comment_node_' . $type->type] = array( - 'author' => array( - 'label' => t('Author'), - 'description' => t('Author textfield'), - 'weight' => -2, - ), - 'title' => array( - 'label' => t('Subject'), - 'description' => t('Subject textfield'), - 'weight' => -1, + 'form' => array( + 'author' => array( + 'label' => t('Author'), + 'description' => t('Author textfield'), + 'weight' => -2, + ), + 'title' => array( + 'label' => t('Subject'), + 'description' => t('Subject textfield'), + 'weight' => -1, + ), ), ); } @@ -362,10 +365,11 @@ function _comment_body_field_instance_create($info) { 'bundle' => 'comment_node_' . $info->type, 'settings' => array('text_processing' => 1), 'required' => TRUE, - // Hides field label by default. 'display' => array( - 'full' => array( + 'default' => array( 'label' => 'hidden', + 'type' => 'text_default', + 'weight' => 0, ), ), ); @@ -1277,14 +1281,38 @@ function comment_node_delete($node) { * Implements hook_node_update_index(). */ function comment_node_update_index($node) { - $mode = variable_get('comment_default_mode_' . $node->type, COMMENT_MODE_THREADED); - $comments_per_page = variable_get('comment_default_per_page_' . $node->type, 50); - if ($node->comment && $cids = comment_get_thread($node, $mode, $comments_per_page)) { - $comments = comment_load_multiple($cids); - comment_prepare_thread($comments); - $build = comment_view_multiple($comments, $node); + $index_comments = &drupal_static(__FUNCTION__); + + if ($index_comments === NULL) { + // Find and save roles that can 'access comments' or 'search content'. + $perms = array('access comments' => array(), 'search content' => array()); + $result = db_query("SELECT rid, permission FROM {role_permission} WHERE permission IN ('access comments', 'search content')"); + foreach ($result as $record) { + $perms[$record->permission][$record->rid] = $record->rid; + } + + // Prevent indexing of comments if there are any roles that can search but + // not view comments. + $index_comments = TRUE; + foreach ($perms['search content'] as $rid) { + if (!isset($perms['access comments'][$rid]) && ($rid <= DRUPAL_AUTHENTICATED_RID || !isset($perms['access comments'][DRUPAL_AUTHENTICATED_RID]))) { + $index_comments = FALSE; + break; + } + } + } + + if ($index_comments) { + $mode = variable_get('comment_default_mode_' . $node->type, COMMENT_MODE_THREADED); + $comments_per_page = variable_get('comment_default_per_page_' . $node->type, 50); + if ($node->comment && $cids = comment_get_thread($node, $mode, $comments_per_page)) { + $comments = comment_load_multiple($cids); + comment_prepare_thread($comments); + $build = comment_view_multiple($comments, $node); + return drupal_render($build); + } } - return drupal_render($build); + return ''; } /** @@ -1302,8 +1330,8 @@ function comment_update_index() { * results. */ function comment_node_search_result($node) { - // Do not make a string if comments are hidden. - if ($node->comment != COMMENT_NODE_HIDDEN) { + // Do not make a string if comments are hidden. + if (user_access('access comments') && $node->comment != COMMENT_NODE_HIDDEN) { $comments = db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = :nid', array('nid' => $node->nid))->fetchField(); // Do not make a string if comments are closed and there are currently // zero comments. @@ -1319,25 +1347,19 @@ function comment_node_search_result($node) { function comment_user_cancel($edit, $account, $method) { switch ($method) { case 'user_cancel_block_unpublish': - db_update('comment') - ->fields(array('status' => 0)) - ->condition('uid', $account->uid) - ->execute(); - db_update('node_comment_statistics') - ->fields(array('last_comment_uid' => 0)) - ->condition('last_comment_uid', $account->uid) - ->execute(); + $comments = comment_load_multiple(array(), array('uid' => $account->uid)); + foreach ($comments as $comment) { + $comment->status = 0; + comment_save($comment); + } break; case 'user_cancel_reassign': - db_update('comment') - ->fields(array('uid' => 0)) - ->condition('uid', $account->uid) - ->execute(); - db_update('node_comment_statistics') - ->fields(array('last_comment_uid' => 0)) - ->condition('last_comment_uid', $account->uid) - ->execute(); + $comments = comment_load_multiple(array(), array('uid' => $account->uid)); + foreach ($comments as $comment) { + $comment->uid = 0; + comment_save($comment); + } break; } } @@ -1429,7 +1451,7 @@ function comment_save($comment) { field_attach_update('comment', $comment); // Allow modules to respond to the updating of a comment. module_invoke_all('comment_update', $comment); - entity_invoke('update', 'comment', $comment); + module_invoke_all('entity_update', $comment, 'comment'); } else { // Add the comment to database. This next section builds the thread field. @@ -1518,14 +1540,15 @@ function comment_save($comment) { // Tell the other modules a new comment has been submitted. module_invoke_all('comment_insert', $comment); - entity_invoke('insert', 'comment', $comment); + module_invoke_all('entity_insert', $comment, 'comment'); } if ($comment->status == COMMENT_PUBLISHED) { module_invoke_all('comment_publish', $comment); } } catch (Exception $e) { - $transaction->rollback('comment', $e->getMessage(), array(), WATCHDOG_ERROR); + $transaction->rollback('comment'); + watchdog_exception('comment', $e); throw $e; } @@ -1610,7 +1633,7 @@ class CommentController extends DrupalDefaultEntityController { $query->addField('n', 'type', 'node_type'); $query->innerJoin('users', 'u', 'base.uid = u.uid'); $query->addField('u', 'name', 'registered_name'); - $query->fields('u', array('uid', 'signature', 'picture')); + $query->fields('u', array('uid', 'signature', 'signature_format', 'picture')); return $query; } @@ -1739,6 +1762,32 @@ function comment_edit_page($comment) { function comment_form($form, &$form_state, $comment) { global $user; + // During initial form build, add the comment entity to the form state for + // use during form building and processing. During a rebuild, use what is in + // the form state. + if (!isset($form_state['comment'])) { + $defaults = array( + 'name' => '', + 'mail' => '', + 'homepage' => '', + 'subject' => '', + 'comment' => '', + 'cid' => NULL, + 'pid' => NULL, + 'language' => LANGUAGE_NONE, + 'uid' => 0, + ); + foreach ($defaults as $key => $value) { + if (!isset($comment->$key)) { + $comment->$key = $value; + } + } + $form_state['comment'] = $comment; + } + else { + $comment = $form_state['comment']; + } + $node = node_load($comment->nid); $form['#node'] = $node; @@ -1750,24 +1799,6 @@ function comment_form($form, &$form_state, $comment) { $form['#attributes']['class'][] = 'user-info-from-cookie'; } - $comment = (array) $comment; - // Take into account multi-step rebuilding. - if (isset($form_state['comment'])) { - $comment = $form_state['comment'] + (array) $comment; - } - $comment += array( - 'name' => '', - 'mail' => '', - 'homepage' => '', - 'subject' => '', - 'comment' => '', - 'cid' => NULL, - 'pid' => NULL, - 'language' => LANGUAGE_NONE, - 'uid' => 0, - ); - $comment = (object) $comment; - // If not replying to a comment, use our dedicated page callback for new // comments on nodes. if (empty($comment->cid) && empty($comment->pid)) { @@ -1943,8 +1974,9 @@ function comment_form($form, &$form_state, $comment) { * Build a preview from submitted form values. */ function comment_form_build_preview($form, &$form_state) { - $comment = comment_form_submit_build_comment($form, $form_state); + $comment = $form['#builder_function']($form, $form_state); $form_state['comment_preview'] = comment_preview($comment); + $form_state['rebuild'] = TRUE; } /** @@ -2010,8 +2042,8 @@ function comment_preview($comment) { */ function comment_form_validate($form, &$form_state) { global $user; - $comment = (object) $form_state['values']; - field_attach_form_validate('comment', $comment, $form, $form_state); + + entity_form_field_validate('comment', $form, $form_state); if (!empty($form_state['values']['cid'])) { if ($form_state['values']['date'] && strtotime($form_state['values']['date']) === FALSE) { @@ -2062,49 +2094,56 @@ function comment_form_validate($form, &$form_state) { /** * Prepare a comment for submission. - * - * @param $comment - * An associative array containing the comment data. */ function comment_submit($comment) { - $comment += array('subject' => ''); - if (empty($comment['date'])) { - $comment['date'] = 'now'; + // @todo Legacy support. Remove in Drupal 8. + if (is_array($comment)) { + $comment += array('subject' => ''); + $comment = (object) $comment; + } + + if (empty($comment->date)) { + $comment->date = 'now'; } - $comment['created'] = strtotime($comment['date']); - $comment['changed'] = REQUEST_TIME; + $comment->created = strtotime($comment->date); + $comment->changed = REQUEST_TIME; - if (!empty($comment['name']) && ($account = user_load_by_name($comment['name']))) { - $comment['uid'] = $account->uid; + if (!empty($comment->name) && ($account = user_load_by_name($comment->name))) { + $comment->uid = $account->uid; } // Validate the comment's subject. If not specified, extract from comment body. - if (trim($comment['subject']) == '') { + if (trim($comment->subject) == '') { // The body may be in any format, so: // 1) Filter it into HTML // 2) Strip out all HTML tags // 3) Convert entities back to plain-text. - $comment['subject'] = truncate_utf8(trim(decode_entities(strip_tags(check_markup($comment['comment_body'][LANGUAGE_NONE][0]['value'], $comment['comment_body'][LANGUAGE_NONE][0]['format'])))), 29, TRUE); + $comment->subject = truncate_utf8(trim(decode_entities(strip_tags(check_markup($comment->comment_body[LANGUAGE_NONE][0]['value'], $comment->comment_body[LANGUAGE_NONE][0]['format'])))), 29, TRUE); // Edge cases where the comment body is populated only by HTML tags will // require a default subject. - if ($comment['subject'] == '') { - $comment['subject'] = t('(No subject)'); + if ($comment->subject == '') { + $comment->subject = t('(No subject)'); } } - return (object) $comment; + return $comment; } /** - * Build a comment by processing form values and prepare for a form rebuild. + * Updates the form state's comment entity by processing this submission's values. + * + * This is the default #builder_function for the comment form. It is called + * during the "Save" and "Preview" submit handlers to retrieve the entity to + * save or preview. This function can also be called by a "Next" button of a + * wizard to update the form state's entity with the current step's values + * before proceeding to the next step. + * + * @see comment_form() */ function comment_form_submit_build_comment($form, &$form_state) { - $comment = comment_submit($form_state['values']); - - field_attach_submit('comment', $comment, $form, $form_state); - - $form_state['comment'] = (array) $comment; - $form_state['rebuild'] = TRUE; + $comment = $form_state['comment']; + entity_form_submit_build_entity('comment', $comment, $form, $form_state); + comment_submit($comment); return $comment; } @@ -2113,7 +2152,7 @@ function comment_form_submit_build_comment($form, &$form_state) { */ function comment_form_submit($form, &$form_state) { $node = node_load($form_state['values']['nid']); - $comment = comment_form_submit_build_comment($form, $form_state); + $comment = $form['#builder_function']($form, $form_state); if (user_access('post comments') && (user_access('administer comments') || $node->comment == COMMENT_NODE_OPEN)) { // Save the anonymous user information to a cookie for reuse. if (!$comment->uid) { @@ -2150,7 +2189,6 @@ function comment_form_submit($form, &$form_state) { // Redirect the user to the node they are commenting on. $redirect = 'node/' . $node->nid; } - unset($form_state['rebuild']); $form_state['redirect'] = $redirect; // Clear the block and page caches so that anonymous users see the comment // they have posted. @@ -2251,7 +2289,7 @@ function theme_comment_post_forbidden($variables) { $destination = array('destination' => "node/$node->nid#comment-form"); } - if (variable_get('user_register', 1)) { + if (variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)) { // Users can register themselves. return t('<a href="@login">Log in</a> or <a href="@register">register</a> to post comments', array('@login' => url('user/login', array('query' => $destination)), '@register' => url('user/register', array('query' => $destination)))); } diff --git a/modules/comment/comment.pages.inc b/modules/comment/comment.pages.inc index 100f9233..cdfe5be4 100644 --- a/modules/comment/comment.pages.inc +++ b/modules/comment/comment.pages.inc @@ -1,5 +1,5 @@ <?php -// $Id: comment.pages.inc,v 1.38 2010/04/13 15:13:41 dries Exp $ +// $Id: comment.pages.inc,v 1.39 2010/06/10 06:57:20 dries Exp $ /** * @file @@ -48,7 +48,7 @@ function comment_reply($node, $pid = NULL) { // $pid indicates that this is a reply to a comment. if ($pid) { // Load the comment whose cid = $pid - $comment = db_query('SELECT c.*, u.uid, u.name AS registered_name, u.signature, u.picture, u.data FROM {comment} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = :cid AND c.status = :status', array( + $comment = db_query('SELECT c.*, u.uid, u.name AS registered_name, u.signature, u.signature_format, u.picture, u.data FROM {comment} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = :cid AND c.status = :status', array( ':cid' => $pid, ':status' => COMMENT_PUBLISHED, ))->fetchObject(); diff --git a/modules/comment/comment.test b/modules/comment/comment.test index 5e036e73..a8bfa672 100644 --- a/modules/comment/comment.test +++ b/modules/comment/comment.test @@ -1,5 +1,5 @@ <?php -// $Id: comment.test,v 1.80 2010/05/06 05:59:31 webchick Exp $ +// $Id: comment.test,v 1.84 2010/07/07 17:00:42 webchick Exp $ class CommentHelperCase extends DrupalWebTestCase { protected $admin_user; @@ -481,6 +481,11 @@ class CommentAnonymous extends CommentHelperCase { ); } + function setUp() { + parent::setUp(); + variable_set('user_register', USER_REGISTER_VISITORS); + } + /** * Test anonymous comment functionality. */ @@ -1097,168 +1102,6 @@ class CommentRSSUnitTest extends CommentHelperCase { } } -/** - * Tests RDFa markup for comments. - */ -class CommentRdfaTestCase extends CommentHelperCase { - public static function getInfo() { - return array( - 'name' => 'RDFa comment markup', - 'description' => 'Test RDFa markup in comments.', - 'group' => 'RDF', - ); - } - - function setUp() { - parent::setUp('comment', 'rdf'); - - $this->admin_user = $this->drupalCreateUser(array('administer content types', 'administer comments', 'administer permissions', 'administer blocks')); - $this->web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'create article content', 'access user profiles')); - - // Enables anonymous user comments. - user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( - 'access comments' => TRUE, - 'post comments' => TRUE, - 'post comments without approval' => TRUE, - )); - // Allows anonymous to leave their contact information. - $this->setCommentAnonymous(COMMENT_ANONYMOUS_MAY_CONTACT); - $this->setCommentPreview(DRUPAL_OPTIONAL); - $this->setCommentForm(TRUE); - $this->setCommentSubject(TRUE); - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Comment paging changed.')); - - // Creates the nodes on which the test comments will be posted. - $this->drupalLogin($this->web_user); - $this->node1 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); - $this->node2 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); - $this->drupalLogout(); - } - - /** - * Tests the presence of the RDFa markup for the number of comments. - */ - function testNumberOfCommentsRdfaMarkup() { - // Posts 2 comments as a registered user. - $this->drupalLogin($this->web_user); - $this->postComment($this->node1, $this->randomName(), $this->randomName()); - $this->postComment($this->node1, $this->randomName(), $this->randomName()); - - // Tests number of comments in teaser view. - $this->drupalGet('node'); - $comment_count_teaser = $this->xpath('//div[contains(@typeof, "sioc:Item")]//li[contains(@class, "comment-comments")]/a[contains(@property, "sioc:num_replies") and contains(@content, "2") and @datatype="xsd:integer"]'); - $this->assertTrue(!empty($comment_count_teaser), t('RDFa markup for the number of comments found on teaser view.')); - - // Tests number of comments in full node view. - $this->drupalGet('node/' . $this->node1->nid); - $node_url = url('node/' . $this->node1->nid); - $comment_count_teaser = $this->xpath('/html/head/meta[@about=:node-url and @property="sioc:num_replies" and @content="2" and @datatype="xsd:integer"]', array(':node-url' => $node_url)); - $this->assertTrue(!empty($comment_count_teaser), t('RDFa markup for the number of comments found on full node view.')); - } - - - /** - * Tests the presence of the RDFa markup for the title, date and author and - * homepage on registered users and anonymous comments. - */ - function testCommentRdfaMarkup() { - - // Posts comment #1 as a registered user. - $this->drupalLogin($this->web_user); - $comment1_subject = $this->randomName(); - $comment1_body = $this->randomName(); - $comment1 = $this->postComment($this->node1, $comment1_body, $comment1_subject); - - // Tests comment #1 with access to the user profile. - $this->drupalGet('node/' . $this->node1->nid); - $this->_testBasicCommentRdfaMarkup($comment1); - - // Tests comment #1 with no access to the user profile (as anonymous user). - $this->drupalLogout(); - $this->drupalGet('node/' . $this->node1->nid); - $this->_testBasicCommentRdfaMarkup($comment1); - - - // Posts comment #2 as anonymous user. - $comment2_subject = $this->randomName(); - $comment2_body = $this->randomName(); - $anonymous_user = array(); - $anonymous_user['name'] = $this->randomName(); - $anonymous_user['mail'] = 'tester@simpletest.org'; - $anonymous_user['homepage'] = 'http://example.org/'; - $comment2 = $this->postComment($this->node2, $comment2_body, $comment2_subject, $anonymous_user); - $this->drupalGet('node/' . $this->node2->nid); - - // Tests comment #2 as anonymous user. - $this->_testBasicCommentRdfaMarkup($comment2, $anonymous_user); - // Tests the RDFa markup for the homepage (specific to anonymous comments). - $comment_homepage = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//span[@rel="sioc:has_creator"]/a[contains(@class, "username") and @typeof="sioc:UserAccount" and @property="foaf:name" and @href="http://example.org/" and contains(@rel, "foaf:page")]'); - $this->assertTrue(!empty($comment_homepage), t('RDFa markup for the homepage of anonymous user found.')); - // There should be no about attribute on anonymous comments. - $comment_homepage = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//span[@rel="sioc:has_creator"]/a[@about]'); - $this->assertTrue(empty($comment_homepage), t('No about attribute is present on anonymous user comment.')); - - // Tests comment #2 as logged in user. - $this->drupalLogin($this->web_user); - $this->drupalGet('node/' . $this->node2->nid); - $this->_testBasicCommentRdfaMarkup($comment2, $anonymous_user); - // Tests the RDFa markup for the homepage (specific to anonymous comments). - $comment_homepage = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//span[@rel="sioc:has_creator"]/a[contains(@class, "username") and @typeof="sioc:UserAccount" and @property="foaf:name" and @href="http://example.org/" and contains(@rel, "foaf:page")]'); - $this->assertTrue(!empty($comment_homepage), t("RDFa markup for the homepage of anonymous user found.")); - // There should be no about attribute on anonymous comments. - $comment_homepage = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//span[@rel="sioc:has_creator"]/a[@about]'); - $this->assertTrue(empty($comment_homepage), t("No about attribute is present on anonymous user comment.")); - } - - /** - * Test RDF comment replies. - */ - function testCommentReplyOfRdfaMarkup() { - // Posts comment #1 as a registered user. - $this->drupalLogin($this->web_user); - $comments[] = $this->postComment($this->node1, $this->randomName(), $this->randomName()); - - // Tests the reply_of relationship of a first level comment. - $result = $this->xpath("id('comments')//div[@class='comment' and position()=0]//span[@rel='sioc:reply_of' and @resource=:node]", array(':node' => url("node/{$this->node1->nid}"))); - $this->assertEqual(1, count($result), t('RDFa markup referring to the node is present.')); - $result = $this->xpath("id('comments')//div[@class='comment' and position()=0]//span[@rel='sioc:reply_of' and @resource=:comment]", array(':comment' => url('comment/1#comment-1'))); - $this->assertFalse($result, t('No RDFa markup referring to the comment itself is present.')); - - // Posts a reply to the first comment. - $this->drupalGet('comment/reply/' . $this->node1->nid . '/' . $comments[0]->id); - $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); - - // Tests the reply_of relationship of a second level comment. - $result = $this->xpath("id('comments')//div[@class='comment' and position()=1]//span[@rel='sioc:reply_of' and @resource=:node]", array(':node' => url("node/{$this->node1->nid}"))); - $this->assertEqual(1, count($result), t('RDFa markup referring to the node is present.')); - $result = $this->xpath("id('comments')//div[@class='comment' and position()=1]//span[@rel='sioc:reply_of' and @resource=:comment]", array(':comment' => url('comment/1#comment-1'))); - $this->assertEqual(1, count($result), t('RDFa markup referring to the parent comment is present.')); - } - /** - * Helper function for testCommentRdfaMarkup(). - * - * Tests the current page for basic comment RDFa markup. - * - * @param $comment - * Comment object. - * @param $account - * An array containing information about an anonymous user. - */ - function _testBasicCommentRdfaMarkup($comment, $account = array()) { - $comment_container = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]'); - $this->assertTrue(!empty($comment_container), t("Comment RDF type for comment found.")); - $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.")); - $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.")); - $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.")); - } -} /** * Test to make sure comment content is rebuilt. @@ -1344,21 +1187,21 @@ class CommentTokenReplaceTestCase extends CommentHelperCase { // Generate and test sanitized tokens. $tests = array(); $tests['[comment:cid]'] = $comment->cid; - $tests['[comment:pid]'] = $comment->pid; - $tests['[comment:nid]'] = $comment->nid; - $tests['[comment:uid]'] = $comment->uid; $tests['[comment:hostname]'] = check_plain($comment->hostname); $tests['[comment:name]'] = filter_xss($comment->name); $tests['[comment:mail]'] = check_plain($this->admin_user->mail); - $tests['[comment:homepage]'] = filter_xss_bad_protocol($comment->homepage); + $tests['[comment:homepage]'] = check_url($comment->homepage); $tests['[comment:title]'] = filter_xss($comment->subject); $tests['[comment:body]'] = _text_sanitize($instance, LANGUAGE_NONE, $comment->comment_body[LANGUAGE_NONE][0], 'value'); $tests['[comment:url]'] = url('comment/' . $comment->cid, $url_options + array('fragment' => 'comment-' . $comment->cid)); $tests['[comment:edit-url]'] = url('comment/' . $comment->cid . '/edit', $url_options); $tests['[comment:created:since]'] = format_interval(REQUEST_TIME - $comment->created, 2, $language->language); $tests['[comment:changed:since]'] = format_interval(REQUEST_TIME - $comment->changed, 2, $language->language); + $tests['[comment:parent:cid]'] = $comment->pid; $tests['[comment:parent:title]'] = check_plain($parent_comment->subject); + $tests['[comment:node:nid]'] = $comment->nid; $tests['[comment:node:title]'] = check_plain($node->title); + $tests['[comment:author:uid]'] = $comment->uid; $tests['[comment:author:name]'] = check_plain($this->admin_user->name); // Test to make sure that we generated something for each token. diff --git a/modules/comment/comment.tokens.inc b/modules/comment/comment.tokens.inc index dfd9cd9f..02e8b963 100644 --- a/modules/comment/comment.tokens.inc +++ b/modules/comment/comment.tokens.inc @@ -1,5 +1,5 @@ <?php -// $Id: comment.tokens.inc,v 1.11 2010/04/20 09:48:06 webchick Exp $ +// $Id: comment.tokens.inc,v 1.13 2010/07/07 17:00:42 webchick Exp $ /** * @file @@ -31,18 +31,6 @@ function comment_token_info() { 'name' => t("Comment ID"), 'description' => t("The unique ID of the comment."), ); - $comment['pid'] = array( - 'name' => t("Parent ID"), - 'description' => t("The unique ID of the comment's parent, if comment threading is active."), - ); - $comment['nid'] = array( - 'name' => t("Node ID"), - 'description' => t("The unique ID of the node the comment was posted to."), - ); - $comment['uid'] = array( - 'name' => t("User ID"), - 'description' => t("The unique ID of the user who posted the comment."), - ); $comment['hostname'] = array( 'name' => t("IP Address"), 'description' => t("The IP address of the computer the comment was posted from."), @@ -138,18 +126,6 @@ function comment_tokens($type, $tokens, array $data = array(), array $options = $replacements[$original] = $comment->cid; break; - case 'pid': - $replacements[$original] = $comment->pid; - break; - - case 'nid': - $replacements[$original] = $comment->nid; - break; - - case 'uid': - $replacements[$original] = $comment->uid; - break; - // Poster identity information for comments case 'hostname': $replacements[$original] = $sanitize ? check_plain($comment->hostname) : $comment->hostname; @@ -172,7 +148,7 @@ function comment_tokens($type, $tokens, array $data = array(), array $options = break; case 'homepage': - $replacements[$original] = $sanitize ? filter_xss_bad_protocol($comment->homepage) : $comment->homepage; + $replacements[$original] = $sanitize ? check_url($comment->homepage) : $comment->homepage; break; case 'title': diff --git a/modules/contact/contact.info b/modules/contact/contact.info index 63e15e8d..3906ba63 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/contact/contact.install b/modules/contact/contact.install index 62fbfb13..3011f8df 100644 --- a/modules/contact/contact.install +++ b/modules/contact/contact.install @@ -1,5 +1,5 @@ <?php -// $Id: contact.install,v 1.24 2010/04/20 07:46:33 webchick Exp $ +// $Id: contact.install,v 1.25 2010/06/08 03:48:14 webchick Exp $ /** * @file @@ -89,18 +89,6 @@ function contact_uninstall() { variable_del('contact_threshold_window'); } -/** - * Implements hook_update_dependencies(). - */ -function contact_update_dependencies() { - // Contact update 7002 calls the new user_role_grant_permissions() and - // therefore needs to run after user_update_7006(); - $dependencies['contact'][7002] = array( - 'user' => 7006, - ); - return $dependencies; -} - /** * @defgroup updates-6.x-to-7.x Contact updates from 6.x to 7.x * @{ @@ -128,7 +116,16 @@ function contact_update_7001() { * Enable the 'access user contact forms' for registered users by default. */ function contact_update_7002() { - user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('access user contact forms')); + // Do not use user_role_grant_permission() since it relies on + // hook_permission(), which will not run for contact module if it is + // disabled. + db_merge('role_permission') + ->key(array( + 'rid' => DRUPAL_AUTHENTICATED_RID, + 'permission' => 'access user contact forms', + 'module' => 'contact', + )) + ->execute(); } /** diff --git a/modules/contextual/contextual-rtl.css b/modules/contextual/contextual-rtl.css new file mode 100644 index 00000000..bf044a34 --- /dev/null +++ b/modules/contextual/contextual-rtl.css @@ -0,0 +1,17 @@ +/* $Id: contextual-rtl.css,v 1.1 2010/06/04 20:38:55 dries Exp $ */ + +div.contextual-links-wrapper { + left: 5px; + right: auto; +} +div.contextual-links-wrapper ul.contextual-links { + -moz-border-radius: 0 4px 4px 4px; + -webkit-border-top-left-radius: 0; + -webkit-border-top-right-radius: 4px; + border-radius: 0 4px 4px 4px; + left: 0; + right: auto; +} +a.contextual-links-trigger { + text-indent: -90px; +} diff --git a/modules/contextual/contextual.css b/modules/contextual/contextual.css index b09bd3f2..abaccbba 100644 --- a/modules/contextual/contextual.css +++ b/modules/contextual/contextual.css @@ -1,4 +1,4 @@ -/* $Id: contextual.css,v 1.5 2010/05/05 06:38:57 webchick Exp $ */ +/* $Id: contextual.css,v 1.6 2010/06/04 20:38:55 dries Exp $ */ /** * Contextual links regions. @@ -18,8 +18,8 @@ div.contextual-links-wrapper { display: none; font-size: 90%; position: absolute; - right: 5px; - top: 0px; + right: 5px; /* LTR */ + top: 2px; z-index: 999; } html.js div.contextual-links-wrapper { @@ -27,51 +27,53 @@ html.js div.contextual-links-wrapper { } a.contextual-links-trigger { background: transparent url(images/gear-select.png) no-repeat 2px 0; + border: 1px solid transparent; display: none; height: 18px; - margin: 2px 1px 0 1px; + margin: 0; padding: 0 2px; outline: none; - text-indent: 34px; + text-indent: 34px; /* LTR */ width: 28px; overflow: hidden; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; } a.contextual-links-trigger:hover, div.contextual-links-active a.contextual-links-trigger { background-position: 2px -18px; } div.contextual-links-active a.contextual-links-trigger { + background-position: 2px -18px; background-color: #fff; - border: #ccc 1px solid; + border-color: #ccc; border-bottom: none; - padding: 0 2px; - margin: 1px 0 0 0; position: relative; z-index: 1; - border-radius-topleft: 4px; - border-radius-topright: 4px; - -moz-border-radius-topleft: 4px; - -moz-border-radius-topright: 4px; - -webkit-border-radius-topleft: 4px; - -webkit-border-radius-topright: 4px; + -moz-border-radius: 4px 4px 0 0; + -webkit-border-bottom-right-radius: 0; + -webkit-border-bottom-left-radius: 0; + border-radius: 4px 4px 0 0; } div.contextual-links-wrapper ul.contextual-links { background-color: #fff; - border: #ccc 1px solid; + border: 1px solid #ccc; display: none; margin: 0; padding: 0.25em 0; position: absolute; right: 0; text-align: left; - top: 19px; + top: 18px; white-space: nowrap; - -moz-border-radius: 4px; - -moz-border-radius-topright: 0; - -webkit-border-radius: 4px; - -webkit-border-radius-topright: 0; - border-radius: 4px; - border-radius-topright: 0; + -moz-border-radius: 4px 0 4px 4px; /* LTR */ + -webkit-border-top-left-radius: 4px; /* LTR */ + -webkit-border-top-right-radius: 0; /* LTR */ + -webkit-border-bottom-right-radius: 4px; + -webkit-border-bottom-left-radius: 4px; + border-radius: 4px 0 4px 4px; /* LTR */ } .contextual-links-region:hover a.contextual-links-trigger, div.contextual-links-active a.contextual-links-trigger, @@ -89,7 +91,6 @@ div.contextual-links-wrapper a { text-decoration: none; } ul.contextual-links li a { - /* Color made important so not easily overriden with '#footer a' type selectors */ color: #333 !important; display: block; margin: 0.25em 0; diff --git a/modules/contextual/contextual.info b/modules/contextual/contextual.info index 4fd93e07..317a966f 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/contextual/contextual.module b/modules/contextual/contextual.module index ef179cc8..9d18d424 100644 --- a/modules/contextual/contextual.module +++ b/modules/contextual/contextual.module @@ -1,5 +1,5 @@ <?php -// $Id: contextual.module,v 1.5 2010/01/08 07:36:53 webchick Exp $ +// $Id: contextual.module,v 1.6 2010/06/13 05:36:58 dries Exp $ /** * @file @@ -78,10 +78,10 @@ function contextual_preprocess(&$variables, $hook) { $keys = array_keys($hooks[$hook]['variables']); $key = $keys[0]; } - else { + else if (!empty($hooks[$hook]['render element'])) { $key = $hooks[$hook]['render element']; } - if (isset($variables[$key])) { + if (!empty($key) && isset($variables[$key])) { $element = $variables[$key]; } diff --git a/modules/dashboard/dashboard.info b/modules/dashboard/dashboard.info index a92e8f27..2da7cb38 100644 --- a/modules/dashboard/dashboard.info +++ b/modules/dashboard/dashboard.info @@ -1,15 +1,16 @@ -; $Id: dashboard.info,v 1.3 2009/11/26 06:59:07 webchick Exp $ +; $Id: dashboard.info,v 1.4 2010/06/04 23:04:32 webchick Exp $ name = Dashboard description = Provides a dashboard page in the administrative interface for organizing administrative tasks and tracking information within your site. core = 7.x package = Core version = VERSION files[] = dashboard.module +files[] = dashboard.test dependencies[] = block configure = admin/dashboard/customize -; Information added by drupal.org packaging script on 2010-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/dashboard/dashboard.js b/modules/dashboard/dashboard.js index f9721873..7a309c2c 100644 --- a/modules/dashboard/dashboard.js +++ b/modules/dashboard/dashboard.js @@ -1,13 +1,15 @@ -// $Id: dashboard.js,v 1.11 2010/05/18 12:07:39 dries Exp $ +// $Id: dashboard.js,v 1.13 2010/06/08 05:16:29 webchick Exp $ (function ($) { /** * Implementation of Drupal.behaviors for dashboard. */ 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); + attach: function (context, settings) { + $('#dashboard', context).once(function () { + $(this).prepend('<div class="customize clearfix"><ul class="action-links"><li><a href="#">' + Drupal.t('Customize dashboard') + '</a></li></ul><div class="canvas"></div></div>'); + $('.customize .action-links a', this).click(Drupal.behaviors.dashboard.enterCustomizeMode); + }); Drupal.behaviors.dashboard.addPlaceholders(); if (Drupal.settings.dashboard.launchCustomize) { Drupal.behaviors.dashboard.enterCustomizeMode(); diff --git a/modules/dashboard/dashboard.module b/modules/dashboard/dashboard.module index ecd9fa5d..d74f8001 100644 --- a/modules/dashboard/dashboard.module +++ b/modules/dashboard/dashboard.module @@ -1,5 +1,5 @@ <?php -// $Id: dashboard.module,v 1.29 2010/05/18 12:07:39 dries Exp $ +// $Id: dashboard.module,v 1.31 2010/06/17 13:16:57 dries Exp $ /** * Implements hook_help(). @@ -235,13 +235,35 @@ function dashboard_admin($launch_customize = FALSE) { } /** - * Returns TRUE if the user is currently viewing the dashboard. + * Determines if the dashboard should be displayed on the current page. + * + * This function checks if the user is currently viewing the dashboard and has + * access to see it. It is used by other functions in the dashboard module to + * decide whether or not the dashboard content should be displayed to the + * current user. + * + * Although the menu system normally handles the above tasks, it only does so + * for the main page content. However, the dashboard is not part of the main + * page content, but rather is displayed in special regions of the page (so it + * can interface with the Block module's method of managing page regions). We + * therefore need to maintain this separate function to check the menu item for + * us. + * + * @return + * TRUE if the dashboard should be visible on the current page, FALSE + * otherwise. + * + * @see dashboard_block_list_alter() + * @see dashboard_page_build() */ function dashboard_is_visible() { static $is_visible; if (!isset($is_visible)) { + // If the current menu item represents the page on which we want to display + // the dashboard, and if the current user has access to see it, return + // TRUE. $menu_item = menu_get_item(); - $is_visible = isset($menu_item['page_callback']) && $menu_item['page_callback'] == 'dashboard_admin'; + $is_visible = isset($menu_item['page_callback']) && $menu_item['page_callback'] == 'dashboard_admin' && !empty($menu_item['access']); } return $is_visible; } @@ -341,7 +363,7 @@ function dashboard_update() { foreach ($blocks as $weight => $block_string) { // Parse the query string to determine the block's module and delta. preg_match('/block-([^-]+)-(.+)/', $block_string, $matches); - $block = new stdClass; + $block = new stdClass(); $block->module = $matches[1]; $block->delta = $matches[2]; diff --git a/modules/dashboard/dashboard.test b/modules/dashboard/dashboard.test new file mode 100644 index 00000000..995b4aa1 --- /dev/null +++ b/modules/dashboard/dashboard.test @@ -0,0 +1,60 @@ +<?php +// $Id: dashboard.test,v 1.1 2010/07/02 15:24:31 webchick Exp $ + +/** + * @file + * Tests for the dashboard module. + */ + +class DashboardAccessTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Dashboard access', + 'description' => 'Test access control for the dashboard.', + 'group' => 'Dashboard', + ); + } + + function setUp() { + parent::setUp(); + + // Create and log in an administrative user having access to the dashboard. + $admin_user = $this->drupalCreateUser(array('access administration pages', 'administer blocks')); + $this->drupalLogin($admin_user); + + // Make sure that the dashboard is using the same theme as the rest of the + // site (and in particular, the same theme used on 403 pages). This forces + // the dashboard blocks to be the same for an administrator as for a + // regular user, and therefore lets us test that the dashboard blocks + // themselves are specifically removed for a user who does not have access + // to the dashboard page. + theme_enable(array('stark')); + variable_set('theme_default', 'stark'); + variable_set('admin_theme', 'stark'); + } + + /** + * Test adding a block to the dashboard and checking access to it. + */ + function testDashboardAccess() { + // Add a new custom block to a dashboard region. + $custom_block = array(); + $custom_block['info'] = $this->randomName(8); + $custom_block['title'] = $this->randomName(8); + $custom_block['body[value]'] = $this->randomName(32); + $custom_block['regions[stark]'] = 'dashboard_main'; + $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); + + // Ensure admin access. + $this->drupalGet('admin'); + $this->assertResponse(200, t('Admin has access to the dashboard.')); + $this->assertRaw($custom_block['title'], t('Admin has access to a dashboard block.')); + + // Ensure non-admin access is denied. + $normal_user = $this->drupalCreateUser(); + $this->drupalLogin($normal_user); + $this->drupalGet('admin'); + $this->assertResponse(403, t('Non-admin has no access to the dashboard.')); + $this->assertNoText($custom_block['title'], t('Non-admin has no access to a dashboard block.')); + } +} diff --git a/modules/dblog/dblog.info b/modules/dblog/dblog.info index 3b61d593..83df7f8a 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/dblog/dblog.install b/modules/dblog/dblog.install index 112d4412..6ff2b32e 100644 --- a/modules/dblog/dblog.install +++ b/modules/dblog/dblog.install @@ -1,5 +1,5 @@ <?php -// $Id: dblog.install,v 1.21 2010/04/28 05:29:55 webchick Exp $ +// $Id: dblog.install,v 1.22 2010/06/25 17:47:22 dries Exp $ /** * @file @@ -38,7 +38,7 @@ function dblog_schema() { 'description' => 'Text of log message to be passed into the t() function.', ), 'variables' => array( - 'type' => 'text', + 'type' => 'blob', 'not null' => TRUE, 'size' => 'big', 'description' => 'Serialized array of variables that match the message string and that is passed into the t() function.', @@ -118,6 +118,19 @@ function dblog_update_7003() { db_change_field('watchdog', 'type', 'type', array('type' => 'varchar', 'length' => 64, 'not null' => TRUE, 'default' => '')); } +/** + * Converts fields that store serialized variables from text to blob. + */ +function dblog_update_7004() { + $spec = array( + 'type' => 'blob', + 'not null' => TRUE, + 'size' => 'big', + 'description' => 'Serialized array of variables that match the message string and that is passed into the t() function.', + ); + db_change_field('watchdog', 'variables', 'variables', $spec); +} + /** * @} End of "defgroup updates-6.x-to-7.x" * The next series of updates should start at 8000. diff --git a/modules/dblog/dblog.module b/modules/dblog/dblog.module index e3a84395..e6b33d3a 100644 --- a/modules/dblog/dblog.module +++ b/modules/dblog/dblog.module @@ -1,5 +1,5 @@ <?php -// $Id: dblog.module,v 1.52 2010/03/27 14:24:14 dries Exp $ +// $Id: dblog.module,v 1.53 2010/06/29 00:39:15 dries Exp $ /** * @file @@ -70,6 +70,18 @@ function dblog_menu() { 'type' => MENU_CALLBACK, 'file' => 'dblog.admin.inc', ); + + if (module_exists('search')) { + $items['admin/reports/search'] = array( + 'title' => 'Top search phrases', + 'description' => 'View most popular search phrases.', + 'page callback' => 'dblog_top', + 'page arguments' => array('search'), + 'access arguments' => array('access site reports'), + 'file' => 'dblog.admin.inc', + ); + } + return $items; } diff --git a/modules/field/field.api.php b/modules/field/field.api.php index 0f4078d9..3136abef 100644 --- a/modules/field/field.api.php +++ b/modules/field/field.api.php @@ -1,5 +1,5 @@ <?php -// $Id: field.api.php,v 1.81 2010/05/18 18:30:49 dries Exp $ +// $Id: field.api.php,v 1.85 2010/06/17 13:16:57 dries Exp $ /** * @ingroup field_fieldable_type @@ -7,55 +7,59 @@ */ /** - * Expose "pseudo-field" components on fieldable entities. + * Exposes "pseudo-field" components on fieldable entities. * - * Field UI's 'Manage fields' page lets users re-order fields, but also - * non-field components. For nodes, these include the title, menu settings, and - * other elements exposed by contributed modules through hook_form() and + * Field UI's "Manage fields" and "Manage display" pages let users re-order + * fields, but also non-field components. For nodes, these include the title, + * poll choices, and other elements exposed by modules through hook_form() or * hook_form_alter(). * - * Fieldable entities or contributed modules that want to have their components - * supported should expose them using this hook, and use - * field_attach_extra_weight() to retrieve the user-defined weight when - * inserting the component. + * Fieldable entities or modules that want to have their components supported + * should expose them using this hook. The user-defined settings (weight, + * visibility) are automatically applied on rendered forms and displayed + * entities in a #pre_render callback added by field_attach_form() and + * field_attach_view(). + * + * @see _field_extra_fields_pre_render() + * @see hook_field_extra_fields_alter() * * @return - * A nested array of 'pseudo-field' components. Each list is nested within the - * field bundle to which those components apply. The keys are the name of the - * element as it appears in the form structure. The values are arrays with the - * following key/value pairs: + * A nested array of 'pseudo-field' components. Each list is nested within + * the following keys: entity type, bundle name, context (either 'form' or + * 'display'). The keys are the name of the elements as appearing in the + * renderable array (either the entity form or the displayed entity). The + * value is an associative array: * - label: The human readable name of the component. * - description: A short description of the component contents. * - weight: The default weight of the element. - * - view: (optional) The name of the element as it appears in the rendered - * structure, if different from the name in the form. - * - * @see hook_field_extra_fields_alter() */ function hook_field_extra_fields() { - $extra = array(); - - foreach (node_type_get_types() as $bundle) { - if ($type->has_title) { - $extra['node'][$bundle]['title'] = array( - 'label' => $type->title_label, - 'description' => t('Node module element.'), - 'weight' => -5, - ); - } - } - if (module_exists('poll')) { - $extra['node']['poll']['choice_wrapper'] = array( - 'label' => t('Poll choices'), - 'description' => t('Poll module choices.'), - 'weight' => -4, - ); - $extra['node']['poll']['settings'] = array( - 'label' => t('Poll settings'), - 'description' => t('Poll module settings.'), - 'weight' => -3, - ); - } + $extra['node']['poll'] = array( + 'form' => array( + 'choice_wrapper' => array( + 'label' => t('Poll choices'), + 'description' => t('Poll choices'), + 'weight' => -4, + ), + 'settings' => array( + 'label' => t('Poll settings'), + 'description' => t('Poll module settings'), + 'weight' => -3, + ), + ), + 'display' => array( + 'poll_view_voting' => array( + 'label' => t('Poll vote'), + 'description' => t('Poll vote'), + 'weight' => 0, + ), + 'poll_view_results' => array( + 'label' => t('Poll results'), + 'description' => t('Poll results'), + 'weight' => 0, + ), + ) + ); return $extra; } @@ -560,8 +564,6 @@ function hook_field_delete_revision($entity_type, $entity, $field, $instance, $l /** * Define custom prepare_translation behavior for this module's field types. * - * TODO: This hook may or may not survive in Field API. - * * @param $entity_type * The type of $entity. * @param $entity @@ -574,8 +576,20 @@ function hook_field_delete_revision($entity_type, $entity, $field, $instance, $l * The language associated to $items. * @param $items * $entity->{$field['field_name']}[$langcode], or an empty array if unset. - */ -function hook_field_prepare_translation($entity_type, $entity, $field, $instance, $langcode, &$items) { + * @param $source_entity + * The source entity from which field values are being copied. + * @param $source_langcode + * The source language from which field values are being copied. + */ +function hook_field_prepare_translation($entity_type, $entity, $field, $instance, $langcode, &$items, $source_entity, $source_langcode) { + // If the translating user is not permitted to use the assigned text format, + // we must not expose the source values. + $field_name = $field['field_name']; + $formats = filter_formats(); + $format_id = $source_entity->{$field_name}[$source_langcode][0]['format']; + if (!filter_access($formats[$format_id])) { + $items = array(); + } } /** @@ -1175,6 +1189,26 @@ function hook_field_attach_view_alter(&$output, $context) { // @todo Needs function body. } +/** + * Perform alterations on field_attach_prepare_translation(). + * + * This hook is invoked after the field module has performed the operation. + * + * @param &$entity + * The entity being prepared for translation. + * @param $context + * An associative array containing: + * - entity_type: The type of $entity; e.g. 'node' or 'user'. + * - langcode: The language the entity has to be translated in. + * - source_entity: The entity holding the field values to be translated. + * - source_langcode: The source language from which translate. + */ +function hook_field_attach_prepare_translation_alter(&$entity, $context) { + if ($context['entity_type'] == 'custom_entity_type') { + $entity->custom_field = $context['source_entity']->custom_field; + } +} + /** * Perform alterations on field_language() values. * @@ -1436,24 +1470,20 @@ function hook_field_storage_delete_revision($entity_type, $entity, $fields) { } /** - * Handle a field query. + * Execute an EntityFieldQuery. * - * This hook is invoked from field_attach_query() to ask the field storage - * module to handle a field query. + * This hook is called to find the entities having certain entity and field + * conditions and sort them in the given field order. If the field storage + * engine also handles property sorts and orders, it should unset those + * properties in the called object to signal that those have been handled. * - * @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. - * @param $options - * See field_attach_query(). All option keys are guaranteed to be specified. + * @param EntityFieldQuery $query + * An EntityFieldQuery. * * @return - * See field_attach_query(). + * See EntityFieldQuery::execute() for the return values. */ -function hook_field_storage_query($field_name, $conditions, $options) { +function hook_field_storage_query($query) { // @todo Needs function body } @@ -1624,33 +1654,90 @@ function hook_field_storage_pre_update($entity_type, $entity, &$skip_fields) { } /** - * Act before the storage backend runs the query. + * Alters the display settings of a field before it gets displayed. * - * This hook should be implemented by modules that use - * hook_field_storage_pre_load(), hook_field_storage_pre_insert() and - * hook_field_storage_pre_update() to bypass the regular storage engine, to - * handle field queries. + * Note that instead of hook_field_display_alter(), which is called for all + * fields on all entity types, hook_field_display_ENTITY_TYPE_alter() may be + * used to alter display settings for fields on a specific entity type only. * - * @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. - * @param $options - * See field_attach_query(). All option keys are guaranteed to be specified. - * @param $skip_field - * Boolean, always coming as FALSE. - * @return - * See field_attach_query(). - * The $skip_field parameter should be set to TRUE if the query has been - * handled. + * This hook is called once per field per displayed entity. If the result of the + * hook involves reading from the database, it is highly recommended to + * statically cache the information. + * + * @param $display + * The display settings that will be used to display the field values, as + * found in the 'display' key of $instance definitions. + * @param $context + * An associative array containing: + * - entity_type: The entity type; e.g. 'node' or 'user'. + * - field: The field being rendered. + * - instance: The instance being rendered. + * - view_mode: The view mode, e.g. 'full', 'teaser'... + * + * @see hook_field_display_ENTITY_TYPE_alter() + */ +function hook_field_display_alter(&$display, $context) { + // Leave field labels out of the search index. + // Note: The check against $context['entity_type'] == 'node' could be avoided + // by using hook_field_display_node_alter() instead of + // hook_field_display_alter(), resulting in less function calls when + // rendering non-node entities. + if ($context['entity_type'] == 'node' && $context['view_mode'] == 'search_index') { + $display['label'] = 'hidden'; + } +} + +/** + * Alters the display settings of a field on a given entity type before it gets displayed. + * + * Modules can implement hook_field_display_ENTITY_TYPE_alter() to alter display + * settings for fields on a specific entity type, rather than implementing + * hook_field_display_alter(). + * + * This hook is called once per field per displayed entity. If the result of the + * hook involves reading from the database, it is highly recommended to + * statically cache the information. + * + * @param $display + * The display settings that will be used to display the field values, as + * found in the 'display' key of $instance definitions. + * @param $context + * An associative array containing: + * - entity_type: The entity type; e.g. 'node' or 'user'. + * - field: The field being rendered. + * - instance: The instance being rendered. + * - view_mode: The view mode, e.g. 'full', 'teaser'... + * + * @see hook_field_display_alter() */ -function hook_field_storage_pre_query($field_name, $conditions, $options, &$skip_field) { - // @todo Needs function body. +function hook_field_display_ENTITY_TYPE_alter(&$display, $context) { + // Leave field labels out of the search index. + if ($context['view_mode'] == 'search_index') { + $display['label'] = 'hidden'; + } } +/** + * Alters the display settings of pseudo-fields before an entity is displayed. + * + * This hook is called once per displayed entity. If the result of the hook + * involves reading from the database, it is highly recommended to statically + * cache the information. + * + * @param $displays + * An array of display settings for the pseudo-fields in the entity, keyed + * by pseudo-field names. + * @param $context + * An associative array containing: + * - entity_type: The entity type; e.g. 'node' or 'user'. + * - bundle: The bundle name. + * - view_mode: The view mode, e.g. 'full', 'teaser'... + */ +function hook_field_extra_fields_display_alter(&$displays, $context) { + if ($context['entity_type'] == 'taxonomy_term' && $context['view_mode'] == 'full') { + $displays['description']['visibility'] = FALSE; + } +} /** * @} End of "ingroup field_storage" */ @@ -1718,8 +1805,12 @@ function hook_field_update_forbid($field, $prior_field, $has_data) { // Identify the keys that will be lost. $lost_keys = array_diff(array_keys($field['settings']['allowed_values']), array_keys($prior_field['settings']['allowed_values'])); // If any data exist for those keys, forbid the update. - $count = field_attach_query($prior_field['id'], array('value', $lost_keys, 'IN'), 1); - if ($count > 0) { + $query = new EntityFieldQuery(); + $found = $query + ->fieldCondition($prior_field['field_name'], 'value', $lost_keys) + ->range(0, 1) + ->execute(); + if ($found) { throw new FieldUpdateForbiddenException("Cannot update a list field not to include keys with existing data"); } } diff --git a/modules/field/field.attach.inc b/modules/field/field.attach.inc index 573c58bb..7434dda3 100644 --- a/modules/field/field.attach.inc +++ b/modules/field/field.attach.inc @@ -1,5 +1,5 @@ <?php -// $Id: field.attach.inc,v 1.87 2010/05/23 07:30:56 dries Exp $ +// $Id: field.attach.inc,v 1.91 2010/06/17 13:44:45 dries Exp $ /** * @file @@ -30,15 +30,6 @@ class FieldValidationException extends FieldException { } } -/** - * Exception thrown by field_attach_query() on unsupported query syntax. - * - * Some storage modules might not support the full range of the syntax for - * conditions, and will raise a FieldQueryException when an usupported - * condition was specified. - */ -class FieldQueryException extends FieldException {} - /** * @defgroup field_storage Field Storage API * @{ @@ -552,11 +543,9 @@ function field_attach_form($entity_type, $entity, &$form, &$form_state, $langcod // Add custom weight handling. list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); $form['#attached']['css'][] = drupal_get_path('module', 'field') . '/theme/field.css'; - $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; + $form['#pre_render'][] = '_field_extra_fields_pre_render'; + $form['#entity_type'] = $entity_type; + $form['#bundle'] = $bundle; // Let other modules make changes to the form. // Avoid module_invoke_all() to let parameters be taken by reference. @@ -1044,132 +1033,6 @@ function field_attach_delete_revision($entity_type, $entity) { module_invoke_all('field_attach_delete_revision', $entity_type, $entity); } -/** - * Retrieve entities matching a given set of conditions. - * - * Note that the query 'conditions' only apply to the stored values. - * In a regular field_attach_load() call, field values additionally go through - * hook_field_load() and hook_field_attach_load() invocations, which can add - * to or affect the raw stored values. The results of field_attach_query() - * might therefore differ from what could be expected by looking at a regular, - * fully loaded entity. - * - * @param $field_id - * The id of the field to query. - * @param $conditions - * An array of query conditions. Each condition is a numerically indexed - * array, in the form: array(column, value, operator). - * Not all storage engines are required to support queries on all column, or - * with all operators below. A FieldQueryException will be raised if an - * unsupported condition is specified. - * Supported columns: - * - any of the columns defined in hook_field_schema() for $field_name's - * field type: condition on field value, - * - 'type': condition on entity type (e.g. 'node', 'user'...), - * - 'bundle': condition on entity bundle (e.g. node type), - * - 'entity_id': condition on entity id (e.g node nid, user uid...), - * - 'deleted': condition on whether the field's data is - * marked deleted for the entity (defaults to FALSE if not specified) - * The field_attach_query_revisions() function additionally supports: - * - 'revision_id': condition on entity revision id (e.g node vid). - * Supported operators: - * - '=', '!=', '>', '>=', '<', '<=', 'STARTS_WITH', 'ENDS_WITH', - * 'CONTAINS': these operators expect the value as a literal of the same - * type as the column, - * - 'IN', 'NOT IN': this operator expects the value as an array of - * literals of the same type as the column. - * - 'BETWEEN': this operator expects the value as an array of two literals - * of the same type as the column. - * The operator can be ommitted, and will default to 'IN' if the value is - * an array, or to '=' otherwise. - * Example values for $conditions: - * @code - * array( - * array('type', 'node'), - * ); - * array( - * array('bundle', array('article', 'page')), - * array('value', 12, '>'), - * ); - * @endcode - * @param $options - * An associative array of additional options: - * - limit: The number of results that is requested. This is only a hint to - * the storage engine(s); callers should be prepared to handle fewer or - * more results. Specify FIELD_QUERY_NO_LIMIT to retrieve all available - * entities. This option has a default value of 0 so callers must make an - * explicit choice to potentially retrieve an enormous result set. - * - cursor: A reference to an opaque cursor that allows a caller to iterate - * through multiple result sets. On the first call, pass 0; the correct - * value to pass on the next call will be written into the value on return. - * When there is no more query data available, the value will be filled in - * with FIELD_QUERY_COMPLETE. If cursor is passed as NULL, the first result - * set is returned and no next cursor is returned. - * - count: If TRUE, return a single count of all matching entities; limit and - * cursor are ignored. - * - age: Internal use only. Use field_attach_query_revisions() instead of - * passing FIELD_LOAD_REVISION. - * - FIELD_LOAD_CURRENT (default): query the most recent revisions for all - * entities. The results will be keyed by entity type and entity id. - * - FIELD_LOAD_REVISION: query all revisions. The results will be keyed by - * entity type and entity revision id. - * @return - * An array keyed by entity type (e.g. 'node', 'user'...), then by entity id - * or revision id (depending of the value of the $age parameter). The values - * are pseudo-entities with the bundle, id, and revision id fields filled in. - * Throws a FieldQueryException if the field's storage doesn't support the - * specified conditions. - */ -function field_attach_query($field_id, $conditions, $options = array()) { - // Merge in default options. - $default_options = array( - 'limit' => 0, - 'cursor' => 0, - 'count' => FALSE, - 'age' => FIELD_LOAD_CURRENT, - ); - $options += $default_options; - - // Give a chance to 3rd party modules that bypass the storage engine to - // handle the query. - $skip_field = FALSE; - foreach (module_implements('field_storage_pre_query') as $module) { - $function = $module . '_field_storage_pre_query'; - $results = $function($field_id, $conditions, $options, $skip_field); - // Stop as soon as a module claims it handled the query. - if ($skip_field) { - break; - } - } - // If the request hasn't been handled, let the storage engine handle it. - if (!$skip_field) { - $field = field_info_field_by_id($field_id); - $function = $field['storage']['module'] . '_field_storage_query'; - $results = $function($field_id, $conditions, $options); - } - - return $results; -} - -/** - * Retrieve entity revisions matching a given set of conditions. - * - * See field_attach_query() for more informations. - * - * @param $field_id - * The id of the field to query. - * @param $conditions - * See field_attach_query(). - * @param $options - * An associative array of additional options. See field_attach_query(). - * @return - * See field_attach_query(). - */ -function field_attach_query_revisions($field_id, $conditions, $options = array()) { - $options['age'] = FIELD_LOAD_REVISION; - return field_attach_query($field_id, $conditions, $options); -} - /** * Prepare field data prior to display. * @@ -1185,7 +1048,7 @@ function field_attach_query_revisions($field_id, $conditions, $options = array() * @param $view_mode * View mode, e.g. 'full', 'teaser'... */ -function field_attach_prepare_view($entity_type, $entities, $view_mode = 'full') { +function field_attach_prepare_view($entity_type, $entities, $view_mode) { // To ensure hooks are only run once per entity, only process items without // the _field_view_prepared flag. // @todo: resolve this more generally for both entity and field level hooks. @@ -1250,7 +1113,7 @@ function field_attach_prepare_view($entity_type, $entities, $view_mode = 'full') * @return * A renderable array for the field values. */ -function field_attach_view($entity_type, $entity, $view_mode = 'full', $langcode = NULL) { +function field_attach_view($entity_type, $entity, $view_mode, $langcode = NULL) { // Determine the actual language to display for each field, given the // languages available in the field data. $display_language = field_language($entity_type, $entity, NULL, $langcode); @@ -1262,8 +1125,9 @@ function field_attach_view($entity_type, $entity, $view_mode = 'full', $langcode // Add custom weight handling. list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); - $output['#pre_render'][] = '_field_extra_weights_pre_render'; - $output['#extra_fields'] = field_extra_fields($entity_type, $bundle); + $output['#pre_render'][] = '_field_extra_fields_pre_render'; + $output['#entity_type'] = $entity_type; + $output['#bundle'] = $bundle; // Include CSS styles. $output['#attached']['css'][] = drupal_get_path('module', 'field') . '/theme/field.css'; @@ -1323,23 +1187,42 @@ function field_attach_preprocess($entity_type, $entity, $element, &$variables) { } /** - * Implements hook_node_prepare_translation(). + * Prepares an entity for translation. + * + * This function is used to fill-in the form default values for Field API fields + * while performing entity translation. By default it copies all the source + * values in the given source language to the new entity and assigns them the + * target language. + * + * This is used as part of the 'per entity' translation pattern, which is + * implemented only for nodes by translation.module. Other entity types may be + * supported through contributed modules. * - * TODO D7: We do not yet know if this really belongs in Field API. + * @param $entity_type + * The type of $entity; e.g. 'node' or 'user'. + * @param $entity + * The entity to be prepared for translation. + * @param $langcode + * The language the entity has to be translated in. + * @param $source_entity + * The source entity holding the field values to be translated. + * @param $source_langcode + * The source language from which translate. */ -function field_attach_prepare_translation($node) { - // Prevent against invalid 'nodes' built by broken 3rd party code. - if (isset($node->type)) { - $type = content_types($node->type); - // Save cycles if the type has no fields. - if (!empty($type['instances'])) { - $default_additions = _field_invoke_default('prepare_translation', $node); - $additions = _field_invoke('prepare_translation', $node); - // Merge module additions after the default ones to enable overriding - // of field values. - $node = (object) array_merge((array) $node, $default_additions, $additions); - } - } +function field_attach_prepare_translation($entity_type, $entity, $langcode, $source_entity, $source_langcode) { + $options = array('language' => $langcode); + // Copy source field values into the entity to be prepared. + _field_invoke_default('prepare_translation', $entity_type, $entity, $source_entity, $source_langcode, $options); + // Let field types handle their own advanced translation pattern if needed. + _field_invoke('prepare_translation', $entity_type, $entity, $source_entity, $source_langcode, $options); + // Let other modules alter the entity translation. + $context = array( + 'entity_type' => $entity_type, + 'langcode' => $langcode, + 'source_entity' => $source_entity, + 'source_langcode' => $source_langcode, + ); + drupal_alter('field_attach_prepare_translation', $entity, $context); } /** @@ -1381,6 +1264,14 @@ function field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) { // Clear the cache. field_cache_clear(); + // Update bundle settings. + $settings = variable_get('field_bundle_settings', array()); + if (isset($settings[$entity_type][$bundle_old])) { + $settings[$entity_type][$bundle_new] = $settings[$entity_type][$bundle_old]; + unset($settings[$entity_type][$bundle_old]); + variable_set('field_bundle_settings', $settings); + } + // Let other modules act on renaming the bundle. module_invoke_all('field_attach_rename_bundle', $entity_type, $bundle_old, $bundle_new); } @@ -1410,6 +1301,13 @@ function field_attach_delete_bundle($entity_type, $bundle) { // Clear the cache. field_cache_clear(); + // Clear bundle display settings. + $settings = variable_get('field_bundle_settings', array()); + if (isset($settings[$entity_type][$bundle])) { + unset($settings[$entity_type][$bundle]); + variable_set('field_bundle_settings', $settings); + } + // Let other modules act on deleting the bundle. module_invoke_all('field_attach_delete_bundle', $entity_type, $bundle, $instances); } diff --git a/modules/field/field.crud.inc b/modules/field/field.crud.inc index 71aa31d1..cb4d5d74 100644 --- a/modules/field/field.crud.inc +++ b/modules/field/field.crud.inc @@ -1,5 +1,5 @@ <?php -// $Id: field.crud.inc,v 1.59 2010/05/09 13:40:48 dries Exp $ +// $Id: field.crud.inc,v 1.65 2010/06/17 13:16:57 dries Exp $ /** * @file @@ -31,8 +31,7 @@ * field_attach_load() then loads the 'subtitle' and 'photo' fields * because they are both attached to the 'node' bundle 'article'. * - * Field definitions are (currently) represented as an array of key/value - * pairs. The array properties are: + * Field definitions are represented as an array of key/value pairs. * * @param array $field: * - id (integer, read-only) @@ -96,8 +95,7 @@ * A sub-array of key/value pairs of settings. Each storage backend * defines and documents its own settings. * - * Field Instance definitions are (currently) represented as an array of - * key/value pairs. The array properties are: + * Field instance definitions are represented as an array of key/value pairs. * * @param array $instance: * - id (integer, read-only) @@ -154,11 +152,20 @@ * - module (string, read-only) * The name of the module that implements the widget type. * - display (array) - * A sub-array of key/value pairs identifying view modes and the way the - * field values should be displayed in each mode. - * - full (array) - * A sub-array of key/value pairs of the display options to be used - * when the field is being displayed in the "full" view mode. + * A sub-array of key/value pairs identifying the way field values should + * be displayed in each of the entity type's view modes, plus the 'default' + * mode. For each view mode, Field UI lets site administrators define + * whether they want to use a dedicated set of display options or the + * 'default' options to reduce the number of displays to maintain as they + * add new fields. For nodes, on a fresh install, only the 'teaser' view + * mode is configured to use custom display options, all other view modes + * defined use the 'default' options by default. When programmatically + * adding field instances on nodes, it is therefore recommended to at least + * specify display options for 'default' and 'teaser'. + * - default (array) + * A sub-array of key/value pairs describing the display options to be + * used when the field is being displayed in view modes that are not + * configured to use dedicated display options. * - label (string) * Position of the label. 'inline', 'above' and 'hidden' are the * values recognized by the default 'field' theme implementation. @@ -172,7 +179,11 @@ * displayed in this view mode. * - module (string, read-only) * The name of the module which implements the display formatter. - * - teaser + * - some_mode + * A sub-array of key/value pairs describing the display options to be + * used when the field is being displayed in the 'some_mode' view mode. + * Those options will only be actually applied at run time if the view + * mode is not configured to use default settings for this bundle. * - ... * - other_mode * - ... @@ -607,12 +618,16 @@ function field_delete_field($field_name) { * - settings: each omitted setting is given the default value specified in * hook_field_widget_info(). * - display: - * Settings for the 'full' view mode will be added, and each view mode - * will be completed with the following default values: + * Settings for the 'default' view mode will be added if not present, and + * each view mode in the definition will be completed with the following + * default values: * - label: 'above' * - type: the default formatter specified in hook_field_info(). * - settings: each omitted setting is given the default value specified in * hook_field_formatter_info(). + * View modes not present in the definition are left empty, and the field + * will not be displayed in this mode. + * * @return * The $instance array with the id property filled in. * @throw @@ -627,14 +642,14 @@ function field_create_instance($instance) { } // Check that the required properties exists. if (empty($instance['entity_type'])) { - throw new FieldException(t('Attempt to create an instance of field @field_name without an object type.', array('@field_name' => $instance['field_name']))); + throw new FieldException(t('Attempt to create an instance of field @field_name without an entity type.', array('@field_name' => $instance['field_name']))); } if (empty($instance['bundle'])) { throw new FieldException(t('Attempt to create an instance of field @field_name without a bundle.', array('@field_name' => $instance['field_name']))); } // Check that the field can be attached to this entity type. if (!empty($field['entity_types']) && !in_array($instance['entity_type'], $field['entity_types'])) { - throw new FieldException(t('Attempt to create an instance of field @field_name on forbidden object type @entity_type.', array('@field_name' => $instance['field_name'], '@entity_type' => $instance['entity_type']))); + throw new FieldException(t('Attempt to create an instance of field @field_name on forbidden entity type @entity_type.', array('@field_name' => $instance['field_name'], '@entity_type' => $instance['entity_type']))); } // Set the field id. @@ -689,14 +704,14 @@ function field_update_instance($instance) { // Check that the specified field exists. $field = field_read_field($instance['field_name']); if (empty($field)) { - throw new FieldException("Attempt to update an instance of a nonexistent field."); + throw new FieldException(t('Attempt to update an instance of a nonexistent field @field.', array('@field' => $instance['field_name']))); } // Check that the field instance exists (even if it is inactive, since we // want to be able to replace inactive widgets with new ones). $prior_instance = field_read_instance($instance['entity_type'], $instance['field_name'], $instance['bundle'], array('include_inactive' => TRUE)); if (empty($prior_instance)) { - throw new FieldException("Attempt to update a field instance that doesn't exist."); + throw new FieldException(t("Attempt to update an instance of field @field on bundle @bundle that doesn't exist.", array('@field' => $instance['field_name'], '@bundle' => $instance['bundle']))); } $instance['id'] = $prior_instance['id']; @@ -730,7 +745,6 @@ function _field_write_instance($instance, $update = FALSE) { 'required' => FALSE, 'label' => $instance['field_name'], 'description' => '', - 'weight' => 0, 'deleted' => 0, ); @@ -742,30 +756,55 @@ function _field_write_instance($instance, $update = FALSE) { // TODO: what if no 'default_widget' specified ? 'type' => $field_type['default_widget'], 'settings' => array(), - 'weight' => 0, ); + // If no weight specified, make sure the field sinks at the bottom. + if (!isset($instance['widget']['weight'])) { + $weights = array(); + foreach (field_info_instances($instance['entity_type'], $instance['bundle']) as $existing_instance) { + if ($instance['field_name'] != $existing_instance['field_name']) { + $weights[] = $existing_instance['widget']['weight']; + } + } + foreach (field_extra_fields($instance['entity_type'], $instance['bundle'], 'form') as $extra) { + $weights[] = $extra['weight']; + } + $instance['widget']['weight'] = $weights ? max($weights) + 1 : 0; + } // Check widget module. $widget_type = field_info_widget_types($instance['widget']['type']); $instance['widget']['module'] = $widget_type['module']; $instance['widget']['settings'] += field_info_widget_settings($instance['widget']['type']); - // Make sure there is at least display info for the 'full' view mode. + // Make sure there are at least display settings for the 'default' view mode, + // and fill in defaults for each view mode specified in the definition. $instance['display'] += array( - 'full' => array(), + 'default' => array(), ); - // Set default display settings for each view mode. foreach ($instance['display'] as $view_mode => $display) { - $instance['display'][$view_mode] += array( + $display += array( 'label' => 'above', - // TODO: what if no 'default_formatter' specified ? - 'type' => $field_type['default_formatter'], + 'type' => isset($field_type['default_formatter']) ? $field_type['default_formatter'] : 'hidden', 'settings' => array(), - 'weight' => 0, ); - $formatter_type = field_info_formatter_types($instance['display'][$view_mode]['type']); - // TODO : 'hidden' will raise PHP warnings. - $instance['display'][$view_mode]['module'] = $formatter_type['module']; - $instance['display'][$view_mode]['settings'] += field_info_formatter_settings($instance['display'][$view_mode]['type']); + if ($display['type'] != 'hidden') { + $formatter_type = field_info_formatter_types($display['type']); + $display['module'] = $formatter_type['module']; + $display['settings'] += field_info_formatter_settings($display['type']); + } + // If no weight specified, make sure the field sinks at the bottom. + if (!isset($display['weight'])) { + $weights = array(); + foreach (field_info_instances($instance['entity_type'], $instance['bundle']) as $existing_instance) { + if ($instance['field_name'] != $existing_instance['field_name']) { + $weights[] = $existing_instance['display'][$view_mode]['weight']; + } + } + foreach (field_extra_fields($instance['entity_type'], $instance['bundle'], 'display') as $extra) { + $weights[] = $extra['display'][$view_mode]['weight']; + } + $display['weight'] = $weights ? max($weights) + 1 : 0; + } + $instance['display'][$view_mode] = $display; } // The serialized 'data' column contains everything from $instance that does @@ -997,25 +1036,23 @@ function field_purge_batch($batch_size) { $instances = field_read_instances(array('deleted' => 1), array('include_deleted' => 1)); foreach ($instances as $instance) { + // field_purge_data() will need the field array. $field = field_info_field_by_id($instance['field_id']); + // Retrieve some entities. + $query = new EntityFieldQuery(); + $results = $query + ->fieldCondition($field) + ->entityCondition('bundle', $instance['bundle']) + ->deleted(TRUE) + ->range(0, $batch_size) + ->execute(); - // Retrieve some pseudo-entities. - $entity_types = field_attach_query($instance['field_id'], array(array('bundle', $instance['bundle']), array('deleted', 1)), array('limit' => $batch_size)); - - if (count($entity_types) > 0) { - // Field data for the instance still exists. - foreach ($entity_types as $entity_type => $entities) { - field_attach_load($entity_type, $entities, FIELD_LOAD_CURRENT, array('field_id' => $field['id'], 'deleted' => 1)); - - foreach ($entities as $id => $entity) { - // field_attach_query() may return more results than we asked for. - // Stop when he have done our batch size. - if ($batch_size-- <= 0) { - return; - } - + if ($results) { + foreach ($results as $entity_type => $stub_entities) { + field_attach_load($entity_type, $stub_entities, FIELD_LOAD_CURRENT, array('field_id' => $field['id'], 'deleted' => 1)); + foreach ($stub_entities as $stub_entity) { // Purge the data for the entity. - field_purge_data($entity_type, $entity, $field, $instance); + field_purge_data($entity_type, $stub_entity, $field, $instance); } } } @@ -1105,7 +1142,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, @field_name that still has instances.", array('@field_name' => $field['field_name'])); + throw new FieldException(t('Attempt to purge a field @field_name that still has instances.', array('@field_name' => $field['field_name']))); } db_delete('field_config') @@ -1125,4 +1162,3 @@ function field_purge_field($field) { /** * @} End of "defgroup field_purge". */ - diff --git a/modules/field/field.default.inc b/modules/field/field.default.inc index b47f866d..549ac20e 100644 --- a/modules/field/field.default.inc +++ b/modules/field/field.default.inc @@ -1,5 +1,5 @@ <?php -// $Id: field.default.inc,v 1.35 2010/05/23 07:30:56 dries Exp $ +// $Id: field.default.inc,v 1.38 2010/06/17 13:44:45 dries Exp $ /** * @file @@ -65,25 +65,12 @@ 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']; - - 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]; + // 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); } /** @@ -132,7 +119,10 @@ function field_default_prepare_view($entity_type, $entities, $field, $instances, // Group entities, instances and items by formatter module. $modules = array(); foreach ($instances as $id => $instance) { - $display = is_string($display) ? $instance['display'][$display] : $display; + if (is_string($display)) { + $view_mode = $display; + $display = field_get_display($instance, $view_mode); + } if ($display['type'] !== 'hidden') { $module = $display['module']; $modules[$module] = $module; @@ -183,18 +173,13 @@ function field_default_view($entity_type, $entity, $field, $instance, $langcode, // Prepare incoming display specifications. if (is_string($display)) { $view_mode = $display; - $display = $instance['display'][$view_mode]; + $display = field_get_display($instance, $view_mode); } else { $view_mode = '_custom_display'; } if ($display['type'] !== 'hidden') { - // We never want to index fields labels. - if ($view_mode == 'search_index') { - $display['label'] = 'hidden'; - } - // Calling the formatter function through module_invoke() can have a // performance impact on pages with many fields and values. $function = $display['module'] . '_field_formatter_view'; @@ -228,10 +213,33 @@ function field_default_view($entity_type, $entity, $field, $instance, $langcode, return $addition; } -function field_default_prepare_translation($entity_type, $entity, $field, $instance, $langcode, &$items) { - $addition = array(); - if (isset($entity->translation_source->$field['field_name'])) { - $addition[$field['field_name']] = $entity->translation_source->$field['field_name']; +/** + * Copies source field values into the entity to be prepared. + * + * @param $entity_type + * The type of $entity; e.g. 'node' or 'user'. + * @param $entity + * The entity to be prepared for translation. + * @param $field + * The field structure for the operation. + * @param $instance + * The instance structure for $field on $entity's bundle. + * @param $langcode + * The language the entity has to be translated in. + * @param $items + * $entity->{$field['field_name']}[$langcode], or an empty array if unset. + * @param $source_entity + * The source entity holding the field values to be translated. + * @param $source_langcode + * The source language from which translate. + */ +function field_default_prepare_translation($entity_type, $entity, $field, $instance, $langcode, &$items, $source_entity, $source_langcode) { + $field_name = $field['field_name']; + // If the field is untranslatable keep using LANGUAGE_NONE. + if ($langcode == LANGUAGE_NONE) { + $source_langcode = LANGUAGE_NONE; + } + if (isset($source_entity->{$field_name}[$source_langcode])) { + $items = $source_entity->{$field_name}[$source_langcode]; } - return $addition; } diff --git a/modules/field/field.form.inc b/modules/field/field.form.inc index 77bd8611..7c73aee7 100644 --- a/modules/field/field.form.inc +++ b/modules/field/field.form.inc @@ -1,5 +1,5 @@ <?php -// $Id: field.form.inc,v 1.49 2010/05/23 07:30:56 dries Exp $ +// $Id: field.form.inc,v 1.50 2010/06/17 13:44:45 dries Exp $ /** * @file @@ -359,17 +359,12 @@ function field_default_form_errors($entity_type, $entity, $field, $instance, $la * to return just the changed part of the form. */ function field_add_more_submit($form, &$form_state) { - // Set the form to rebuild and run submit handlers. - if (isset($form['#builder_function']) && function_exists($form['#builder_function'])) { - $entity = $form['#builder_function']($form, $form_state); - - // Make the changes we want to the form state. - $field_name = $form_state['clicked_button']['#field_name']; - $langcode = $form_state['clicked_button']['#language']; - if ($form_state['values'][$field_name . '_add_more']) { - $form_state['field_item_count'][$field_name] = count($form_state['values'][$field_name][$langcode]); - } + $field_name = $form_state['clicked_button']['#field_name']; + $langcode = $form_state['clicked_button']['#language']; + if ($form_state['values'][$field_name . '_add_more']) { + $form_state['field_item_count'][$field_name] = count($form_state['values'][$field_name][$langcode]); } + $form_state['rebuild'] = TRUE; } /** diff --git a/modules/field/field.info b/modules/field/field.info index c4178aef..d80ac9b9 100644 --- a/modules/field/field.info +++ b/modules/field/field.info @@ -1,7 +1,7 @@ -; $Id: field.info,v 1.7 2010/02/12 05:38:09 webchick Exp $ +; $Id: field.info,v 1.8 2010/06/21 02:27:47 webchick Exp $ name = Field description = Field API to add fields to entities like nodes and users. -package = Core - fields +package = Core version = VERSION core = 7.x files[] = field.module @@ -16,8 +16,8 @@ files[] = tests/field.test dependencies[] = field_sql_storage required = TRUE -; Information added by drupal.org packaging script on 2010-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/field/field.info.inc b/modules/field/field.info.inc index 5e870e3d..29270695 100644 --- a/modules/field/field.info.inc +++ b/modules/field/field.info.inc @@ -1,5 +1,5 @@ <?php -// $Id: field.info.inc,v 1.44 2010/03/27 05:52:49 webchick Exp $ +// $Id: field.info.inc,v 1.47 2010/06/29 18:46:12 dries Exp $ /** * @file @@ -186,47 +186,46 @@ function _field_info_collate_fields($reset = FALSE) { if (!isset($info)) { if ($cached = cache_get('field_info_fields', 'cache_field')) { - $definitions = $cached->data; + $info = $cached->data; } else { $definitions = array( 'field_ids' => field_read_fields(array(), array('include_deleted' => 1)), 'instances' => field_read_instances(), ); - cache_set('field_info_fields', $definitions, 'cache_field'); - } - // Populate 'field_ids' with all fields. - $info['field_ids'] = array(); - foreach ($definitions['field_ids'] as $key => $field) { - $info['field_ids'][$key] = $definitions['field_ids'][$key] = _field_info_prepare_field($field); - } + // Populate 'fields' with all fields, keyed by ID. + $info['fields'] = array(); + foreach ($definitions['field_ids'] as $key => $field) { + $info['fields'][$key] = $definitions['field_ids'][$key] = _field_info_prepare_field($field); + } - // Populate 'fields' only with non-deleted fields. - $info['fields'] = array(); - foreach ($info['field_ids'] as $field) { - if (!$field['deleted']) { - $info['fields'][$field['field_name']] = $field; + // Build an array of field IDs for non-deleted fields, keyed by name. + $info['field_ids'] = array(); + foreach ($info['fields'] as $key => $field) { + if (!$field['deleted']) { + $info['field_ids'][$field['field_name']] = $key; + } } - } - // Populate 'instances'. Only non-deleted instances are considered. - $info['instances'] = array(); - foreach (field_info_bundles() as $entity_type => $bundles) { - foreach ($bundles as $bundle => $bundle_info) { - $info['instances'][$entity_type][$bundle] = array(); + // Populate 'instances'. Only non-deleted instances are considered. + $info['instances'] = array(); + foreach (field_info_bundles() as $entity_type => $bundles) { + foreach ($bundles as $bundle => $bundle_info) { + $info['instances'][$entity_type][$bundle] = array(); + } } - } - foreach ($definitions['instances'] as $instance) { - $field = $info['fields'][$instance['field_name']]; - $instance = _field_info_prepare_instance($instance, $field); - $info['instances'][$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance; - // Enrich field definitions with the list of bundles where they have - // instances. NOTE: Deleted fields in $info['field_ids'] are not - // enriched because all of their instances are deleted, too, and - // are thus not in $definitions['instances']. - $info['fields'][$instance['field_name']]['bundles'][$instance['entity_type']][] = $instance['bundle']; - $info['field_ids'][$instance['field_id']]['bundles'][$instance['entity_type']][] = $instance['bundle']; + foreach ($definitions['instances'] as $instance) { + $field = $info['fields'][$instance['field_id']]; + $instance = _field_info_prepare_instance($instance, $field); + $info['instances'][$instance['entity_type']][$instance['bundle']][$instance['field_name']] = $instance; + // Enrich field definitions with the list of bundles where they have + // instances. NOTE: Deleted fields in $info['field_ids'] are not + // enriched because all of their instances are deleted, too, and + // are thus not in $definitions['instances']. + $info['fields'][$instance['field_id']]['bundles'][$instance['entity_type']][] = $instance['bundle']; + } + cache_set('field_info_fields', $info, 'cache_field'); } } @@ -290,11 +289,16 @@ function _field_info_prepare_instance($instance, $field) { $instance['display'][$view_mode] = _field_info_prepare_instance_display($field, $display); } - // Fallback to 'full' display settings for unspecified view modes. + // Fallback to 'hidden' for unspecified view modes. $entity_info = entity_get_info($instance['entity_type']); foreach ($entity_info['view modes'] as $view_mode => $info) { if (!isset($instance['display'][$view_mode])) { - $instance['display'][$view_mode] = $instance['display']['full']; + $instance['display'][$view_mode] = array( + 'type' => 'hidden', + 'label' => 'above', + 'settings' => array(), + 'weight' => 0, + ); } } @@ -516,11 +520,16 @@ function field_info_bundles($entity_type = NULL) { * @return * An array of field definitions, keyed by field name. Each field has an * additional property, 'bundles', which is an array of all the bundles to - * which this field belongs. + * which this field belongs keyed by entity type. */ function field_info_fields() { $info = _field_info_collate_fields(); - return $info['fields']; + foreach ($info['fields'] as $key => $field) { + if (!$field['deleted']) { + $fields[$field['field_name']] = $field; + } + } + return $fields; } /** @@ -533,14 +542,14 @@ function field_info_fields() { * @return * The field array, as returned by field_read_fields(), with an * additional element 'bundles', whose value is an array of all the bundles - * this field belongs to. + * this field belongs to keyed by entity type. * * @see field_info_field_by_id() */ function field_info_field($field_name) { $info = _field_info_collate_fields(); - if (isset($info['fields'][$field_name])) { - return $info['fields'][$field_name]; + if (isset($info['field_ids'][$field_name])) { + return $info['fields'][$info['field_ids'][$field_name]]; } } @@ -560,8 +569,8 @@ function field_info_field($field_name) { */ function field_info_field_by_id($field_id) { $info = _field_info_collate_fields(); - if (isset($info['field_ids'][$field_id])) { - return $info['field_ids'][$field_id]; + if (isset($info['fields'][$field_id])) { + return $info['fields'][$field_id]; } } diff --git a/modules/field/field.install b/modules/field/field.install index 3ed731ee..23cff052 100644 --- a/modules/field/field.install +++ b/modules/field/field.install @@ -1,5 +1,5 @@ <?php -// $Id: field.install,v 1.18 2010/03/27 05:52:49 webchick Exp $ +// $Id: field.install,v 1.19 2010/06/25 17:47:22 dries Exp $ /** * @file @@ -72,8 +72,8 @@ function field_schema() { 'description' => '@TODO', ), 'data' => array( - 'type' => 'text', - 'size' => 'medium', + 'type' => 'blob', + 'size' => 'big', 'not null' => TRUE, 'serialize' => TRUE, 'description' => 'Serialized data containing the field properties that do not warrant a dedicated column.', @@ -143,8 +143,8 @@ function field_schema() { 'default' => '' ), 'data' => array( - 'type' => 'text', - 'size' => 'medium', + 'type' => 'blob', + 'size' => 'big', 'not null' => TRUE, 'serialize' => TRUE, ), diff --git a/modules/field/field.module b/modules/field/field.module index 6186a626..06be61dd 100644 --- a/modules/field/field.module +++ b/modules/field/field.module @@ -1,5 +1,5 @@ <?php -// $Id: field.module,v 1.72 2010/04/13 15:23:02 dries Exp $ +// $Id: field.module,v 1.76 2010/06/24 21:37:49 dries Exp $ /** * @file * Attach custom data fields to Drupal entities. @@ -101,28 +101,6 @@ define('FIELD_LOAD_CURRENT', 'FIELD_LOAD_CURRENT'); */ define('FIELD_LOAD_REVISION', 'FIELD_LOAD_REVISION'); -/** - * @name Field query flags - * @{ - * Flags for field_attach_query(). - */ - -/** - * Limit argument for field_attach_query() to request all available - * entities instead of a limited number. - */ -define('FIELD_QUERY_NO_LIMIT', 'FIELD_QUERY_NO_LIMIT'); - -/** - * Cursor return value for field_attach_query() to indicate that no - * more data is available. - */ -define('FIELD_QUERY_COMPLETE', 'FIELD_QUERY_COMPLETE'); - -/** - * @} End of "Field query flags". - */ - /** * Exception class thrown by hook_field_update_forbid(). */ @@ -197,13 +175,6 @@ function field_cron() { field_purge_batch($limit); } -/** - * Implements hook_modules_installed(). - */ -function field_modules_installed($modules) { - field_cache_clear(); -} - /** * Implements hook_modules_uninstalled(). */ @@ -365,75 +336,308 @@ function _field_sort_items_value_helper($a, $b) { } /** - * Registry of pseudo-field components in a given bundle. + * Gets or sets administratively defined bundle settings. + * + * For each bundle, settings are provided as a nested array with the following + * structure: + * @code + * array( + * 'view_modes' => array( + * // One sub-array per view mode for the entity type: + * 'full' => array( + * 'custom_display' => Whether the view mode uses custom display + * settings or settings of the 'default' mode, + * ), + * 'teaser' => ... + * ), + * 'extra_fields' => array( + * 'form' => array( + * // One sub-array per pseudo-field in displayed entities: + * 'extra_field_1' => array( + * 'weight' => The weight of the pseudo-field, + * ), + * 'extra_field_2' => ... + * ), + * 'display' => array( + * // One sub-array per pseudo-field in displayed entities: + * 'extra_field_1' => array( + * // One sub-array per view mode for the entity type, including + * // the 'default' mode: + * 'default' => array( + * 'weight' => The weight of the pseudo-field, + * 'visibility' => Whether the pseudo-field is visible or hidden, + * ), + * 'full' => ... + * ), + * 'extra_field_2' => ... + * ), + * ), + * ), + * @encode * * @param $entity_type * The type of $entity; e.g. 'node' or 'user'. * @param $bundle * The bundle name. + * @param $settings + * (optional) The settings to store. + * + * @return + * If no $settings are passed, the current settings are returned. + */ +function field_bundle_settings($entity_type, $bundle, $settings = NULL) { + $stored_settings = variable_get('field_bundle_settings', array()); + + if (isset($settings)) { + $stored_settings[$entity_type][$bundle] = $settings; + variable_set('field_bundle_settings', $stored_settings); + drupal_static_reset('field_view_mode_settings'); + drupal_static_reset('field_extra_fields'); + } + else { + $settings = isset($stored_settings[$entity_type][$bundle]) ? $stored_settings[$entity_type][$bundle] : array(); + $settings += array( + 'view_modes' => array(), + 'extra_fields' => array(), + ); + + return $settings; + } +} + +/** + * Returns view mode settings in a given bundle. + * + * @param $entity_type + * The type of entity; e.g. 'node' or 'user'. + * @param $bundle + * The bundle name to return view mode settings for. + * + * @return + * An array keyed by view mode, with the following key/value pairs: + * - custom_settings: Boolean specifying whether the view mode uses a + * dedicated set of display options (TRUE), or the 'default' options + * (FALSE). Defaults to FALSE. + */ +function field_view_mode_settings($entity_type, $bundle) { + $cache = &drupal_static(__FUNCTION__, array()); + + if (!isset($cache[$entity_type][$bundle])) { + $bundle_settings = field_bundle_settings($entity_type, $bundle); + $settings = $bundle_settings['view_modes']; + // Include view modes for which nothing has been stored yet, but whose + // definition in hook_entity_info() specify they should use custom settings + // by default. + $entity_info = entity_get_info($entity_type); + foreach ($entity_info['view modes'] as $view_mode => $view_mode_info) { + if (!isset($settings[$view_mode]['custom_settings']) && $view_mode_info['custom settings']) { + $settings[$view_mode]['custom_settings'] = TRUE; + } + } + $cache[$entity_type][$bundle] = $settings; + } + + return $cache[$entity_type][$bundle]; +} + +/** + * Returns a list and settings of pseudo-field elements in a given bundle. + * + * If $context is 'form', an array with the following structure: + * @code + * array( + * 'name_of_pseudo_field_component' => array( + * 'label' => The human readable name of the component, + * 'description' => A short description of the component content, + * 'weight' => The weight of the component in edit forms, + * ), + * 'name_of_other_pseudo_field_component' => array( + * // ... + * ), + * ); + * @endcode + * + * If $context is 'display', an array with the following structure: + * @code + * array( + * 'name_of_pseudo_field_component' => array( + * 'label' => The human readable name of the component, + * 'description' => A short description of the component content, + * // One entry per view mode, including the 'default' mode: + * 'display' => array( + * 'default' => array( + * 'weight' => The weight of the component in displayed entities in + * this view mode, + * 'visibility' => Whether the component is visible or hidden in + * displayed entities in this view mode, + * ), + * 'teaser' => array( + * // ... + * ), + * ), + * ), + * 'name_of_other_pseudo_field_component' => array( + * // ... + * ), + * ); + * @endcode + * + * @param $entity_type + * The type of entity; e.g. 'node' or 'user'. + * @param $bundle + * The bundle name. + * @param $context + * The context for which the list of pseudo-fields is requested. Either + * 'form' or 'display'. + * * @return * The array of pseudo-field elements in the bundle. */ -function field_extra_fields($entity_type, $bundle) { - $info = &drupal_static(__FUNCTION__, array()); +function field_extra_fields($entity_type, $bundle, $context) { + $extra = &drupal_static(__FUNCTION__); - if (empty($info)) { + if (!isset($extra)) { $info = (array) module_invoke_all('field_extra_fields'); drupal_alter('field_extra_fields', $info); - // Add saved weights. The array is keyed by entity type, bundle and - // element name. - $extra_weights = variable_get('field_extra_weights', array()); - foreach ($extra_weights as $entity_type_name => $bundles) { - foreach ($bundles as $bundle_name => $weights) { - foreach ($weights as $key => $value) { - if (isset($info[$entity_type_name][$bundle_name][$key])) { - $info[$entity_type_name][$bundle_name][$key]['weight'] = $value; + // Merge in saved settings, and make sure we have settings for all view + // modes. + foreach ($info as $entity_type_name => $bundles) { + $entity_type_info = entity_get_info($entity_type_name); + foreach ($bundles as $bundle_name => $extra_fields) { + $bundle_settings = field_bundle_settings($entity_type_name, $bundle_name); + $extra_fields += array('form' => array(), 'display' => array()); + + // Extra fields in forms. + $data = $extra_fields['form']; + foreach ($data as $name => $field_data) { + $settings = isset($bundle_settings['extra_fields']['form'][$name]) ? $bundle_settings['extra_fields']['form'][$name] : array(); + if (isset($settings['weight'])) { + $data[$name]['weight'] = $settings['weight']; + } + } + $extra[$entity_type_name][$bundle_name]['form'] = $data; + + // Extra fields in displayed entities. + $data = $extra_fields['display']; + foreach ($data as $name => $field_data) { + $settings = isset($bundle_settings['extra_fields']['display'][$name]) ? $bundle_settings['extra_fields']['display'][$name] : array(); + $view_modes = array_merge(array('default'), array_keys($entity_type_info['view modes'])); + foreach ($view_modes as $view_mode) { + if (isset($settings[$view_mode])) { + $data[$name]['display'][$view_mode] = $settings[$view_mode]; + } + else { + $data[$name]['display'][$view_mode] = array( + 'weight' => $field_data['weight'], + 'visible' => TRUE, + ); + } + unset($data[$name]['weight']); } } + $extra[$entity_type_name][$bundle_name]['display'] = $data; } } } - return isset($info[$entity_type][$bundle]) ? $info[$entity_type][$bundle]: array(); + return isset($extra[$entity_type][$bundle][$context]) ? $extra[$entity_type][$bundle][$context] : array(); +} + + +/** + * Returns the display settings to use for an instance in a given view mode. + * + * @param $instance + * The field instance being displayed. + * @param $view_mode + * The view mode. + * + * @return + * The display settings to be used when displaying the field values. + */ +function field_get_display($instance, $view_mode) { + // Check whether the view mode uses custom display settings or the 'default' + // mode. + $view_mode_settings = field_view_mode_settings($instance['entity_type'], $instance['bundle']); + $actual_mode = (!empty($view_mode_settings[$view_mode]['custom_settings']) ? $view_mode : 'default'); + $display = $instance['display'][$actual_mode]; + + // Let modules alter the display settings. + $context = array( + 'entity_type' => $instance['entity_type'], + 'field' => field_info_field($instance['field_name']), + 'instance' => $instance, + 'view_mode' => $view_mode, + ); + drupal_alter(array('field_display', 'field_display_' . $instance['entity_type']), $display, $context); + + return $display; } /** - * Retrieve the user-defined weight for a 'pseudo-field' component. + * Returns the display settings to use for pseudo-fields in a given view mode. * * @param $entity_type * The type of $entity; e.g. 'node' or 'user'. * @param $bundle * The bundle name. - * @param $pseudo_field - * The name of the 'pseudo-field'. + * @param $view_mode + * The view mode. + * * @return - * The weight for the 'pseudo-field', respecting the user settings stored by - * field.module. + * The display settings to be used when viewing the bundle's pseudo-fields. */ -function field_extra_field_weight($entity_type, $bundle, $pseudo_field) { - $extra = field_extra_fields($entity_type, $bundle); - if (isset($extra[$pseudo_field])) { - return $extra[$pseudo_field]['weight']; +function field_extra_fields_get_display($entity_type, $bundle, $view_mode) { + // Check whether the view mode uses custom display settings or the 'default' + // mode. + $view_mode_settings = field_view_mode_settings($entity_type, $bundle); + $actual_mode = (!empty($view_mode_settings[$view_mode]['custom_settings'])) ? $view_mode : 'default'; + $extra_fields = field_extra_fields($entity_type, $bundle, 'display'); + + $displays = array(); + foreach ($extra_fields as $name => $value) { + $displays[$name] = $extra_fields[$name]['display'][$actual_mode]; } + + // Let modules alter the display settings. + $context = array( + 'entity_type' => $entity_type, + 'bundle' => $bundle, + 'view_mode' => $view_mode, + ); + drupal_alter('field_extra_fields_display', $displays, $context); + + return $displays; } /** - * Pre-render callback to adjust weights of non-field elements on entities. + * Pre-render callback to adjust weights and visibility of non-field elements. */ -function _field_extra_weights_pre_render($elements) { - if (isset($elements['#extra_fields'])) { - foreach ($elements['#extra_fields'] as $key => $value) { - // Some core 'fields' use a different key in node forms and in 'view' - // render arrays. Ensure that we are not on a form first. - if (!isset($elements['#build_id']) && isset($value['view']) && isset($elements[$value['view']])) { - $elements[$value['view']]['#weight'] = $value['weight']; +function _field_extra_fields_pre_render($elements) { + $entity_type = $elements['#entity_type']; + $bundle = $elements['#bundle']; + + if (isset($elements['#type']) && $elements['#type'] == 'form') { + $extra_fields = field_extra_fields($entity_type, $bundle, 'form'); + foreach ($extra_fields as $name => $settings) { + if (isset($elements[$name])) { + $elements[$name]['#weight'] = $settings['weight']; } - elseif (isset($elements[$key])) { - $elements[$key]['#weight'] = $value['weight']; + } + } + elseif (isset($elements['#view_mode'])) { + $view_mode = $elements['#view_mode']; + $extra_fields = field_extra_fields_get_display($entity_type, $bundle, $view_mode); + foreach ($extra_fields as $name => $settings) { + if (isset($elements[$name])) { + $elements[$name]['#weight'] = $settings['weight']; + // Visibility: make sure we do not accidentally show a hidden element. + $elements[$name]['#access'] = isset($elements[$name]['#access']) ? ($elements[$name]['#access'] && $settings['visible']) : $settings['visible']; } } } + return $elements; } @@ -543,7 +747,7 @@ function field_view_value($entity_type, $entity, $field_name, $item, $display = * display settings specified for this view mode in the $instance * definition for the field in the entity's bundle. * If no display settings are found for the view mode, the settings for - * the 'full' view mode will be used. + * the 'default' view mode will be used. * - An array of display settings, as found in the 'display' entry of * $instance definitions. The following key/value pairs are allowed: * - label: (string) Position of the label. The default 'field' theme @@ -574,15 +778,6 @@ function field_view_field($entity_type, $entity, $field_name, $display = array() // When using custom display settings, fill in default values. $display = _field_info_prepare_instance_display($field, $display); } - else { - // When using a view mode, make sure we have settings for it, or fall - // back to the 'full' view mode. - list(, , $bundle) = entity_extract_ids($entity_type, $entity); - $instance = field_info_instance($entity_type, $field_name, $bundle); - if (!isset($instance['display'][$display])) { - $display = 'full'; - } - } // Hook invocations are done through the _field_invoke() functions in // 'single field' mode, to reuse the language fallback logic. @@ -653,8 +848,12 @@ function field_get_items($entity_type, $entity, $field_name, $langcode = NULL) { * TRUE if the field has data for any entity; FALSE otherwise. */ function field_has_data($field) { - $results = field_attach_query($field['id'], array(), array('limit' => 1)); - return !empty($results); + $query = new EntityFieldQuery(); + return (bool) $query + ->fieldCondition($field) + ->range(0, 1) + ->count() + ->execute(); } /** @@ -756,6 +955,11 @@ function template_preprocess_field(&$variables, $hook) { 'field-type-' . $variables['field_type_css'], 'field-label-' . $element['#label_display'], ); + // Add a "clearfix" class to the wrapper since we float the label and the + // field items in field.css if the label is inline. + if ($element['#label_display'] == 'inline') { + $variables['classes_array'][] = 'clearfix'; + } // Add specific suggestions that can override the default implementation. $variables['theme_hook_suggestions'] = array( @@ -866,7 +1070,7 @@ function theme_field($variables) { $output .= '</div>'; // Render the top-level DIV. - $output = '<div class="' . $variables['classes'] . ' clearfix"' . $variables['attributes'] . '>' . $output . '</div>'; + $output = '<div class="' . $variables['classes'] . '"' . $variables['attributes'] . '>' . $output . '</div>'; return $output; } diff --git a/modules/field/field.multilingual.inc b/modules/field/field.multilingual.inc index 3d868010..5ca0d900 100644 --- a/modules/field/field.multilingual.inc +++ b/modules/field/field.multilingual.inc @@ -1,5 +1,5 @@ <?php -// $Id: field.multilingual.inc,v 1.10 2010/03/25 11:46:20 dries Exp $ +// $Id: field.multilingual.inc,v 1.11 2010/06/01 18:29:41 dries Exp $ /** * @file @@ -31,7 +31,11 @@ function field_multilingual_settings_changed() { * An array of valid language codes. */ function field_available_languages($entity_type, $field) { - $field_languages = &drupal_static(__FUNCTION__, array()); + static $drupal_static_fast; + if (!isset($drupal_static_fast)) { + $drupal_static_fast['field_languages'] = &drupal_static(__FUNCTION__); + } + $field_languages = &$drupal_static_fast['field_languages']; $field_name = $field['field_name']; if (!isset($field_languages[$entity_type][$field_name])) { 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 34b1358a..4eb08ae1 100644 --- a/modules/field/modules/field_sql_storage/field_sql_storage.info +++ b/modules/field/modules/field_sql_storage/field_sql_storage.info @@ -1,7 +1,7 @@ -; $Id: field_sql_storage.info,v 1.3 2009/06/08 09:23:51 dries Exp $ +; $Id: field_sql_storage.info,v 1.4 2010/06/21 02:27:47 webchick Exp $ name = Field SQL storage description = Stores field data in an SQL database. -package = Core - fields +package = Core version = VERSION core = 7.x files[] = field_sql_storage.module @@ -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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" 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 a3c03d24..edeedbd8 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.46 2010/05/06 15:29:51 dries Exp $ +// $Id: field_sql_storage.module,v 1.48 2010/06/26 02:16:23 dries Exp $ /** * @file @@ -358,6 +358,9 @@ function field_sql_storage_field_storage_load($entity_type, $entities, $age, $fi */ function field_sql_storage_field_storage_write($entity_type, $entity, $op, $fields) { list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); + if (!isset($vid)) { + $vid = $id; + } $etid = _field_sql_storage_etid($entity_type); foreach ($fields as $field_id) { @@ -380,14 +383,12 @@ function field_sql_storage_field_storage_write($entity_type, $entity, $op, $fiel ->condition('entity_id', $id) ->condition('language', $languages, 'IN') ->execute(); - if (isset($vid)) { - db_delete($revision_name) - ->condition('etid', $etid) - ->condition('entity_id', $id) - ->condition('revision_id', $vid) - ->condition('language', $languages, 'IN') - ->execute(); - } + db_delete($revision_name) + ->condition('etid', $etid) + ->condition('entity_id', $id) + ->condition('revision_id', $vid) + ->condition('language', $languages, 'IN') + ->execute(); } } @@ -398,9 +399,7 @@ function field_sql_storage_field_storage_write($entity_type, $entity, $op, $fiel $columns[] = _field_sql_storage_columnname($field_name, $column); } $query = db_insert($table_name)->fields($columns); - if (isset($vid)) { - $revision_query = db_insert($revision_name)->fields($columns); - } + $revision_query = db_insert($revision_name)->fields($columns); foreach ($field_languages as $langcode) { $items = (array) $entity->{$field_name}[$langcode]; @@ -433,9 +432,7 @@ function field_sql_storage_field_storage_write($entity_type, $entity, $op, $fiel // Execute the query if we have values to insert. if ($do_insert) { $query->execute(); - if (isset($vid)) { - $revision_query->execute(); - } + $revision_query->execute(); } } } @@ -482,127 +479,114 @@ function field_sql_storage_field_storage_purge($entity_type, $entity, $field, $i /** * Implements hook_field_storage_query(). */ -function field_sql_storage_field_storage_query($field_id, $conditions, $options) { - $load_current = $options['age'] == FIELD_LOAD_CURRENT; - - $field = field_info_field_by_id($field_id); - $field_name = $field['field_name']; - $table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field); - $field_columns = array_keys($field['columns']); - - // Build the query. - $query = db_select($table, 't'); - $query->join('field_config_entity_type', 'e', 't.etid = e.etid'); - - // Add conditions. - foreach ($conditions as $condition) { - // A condition is either a (column, value, operator) triple, or a - // (column, value) pair with implied operator. - @list($column, $value, $operator) = $condition; - // Translate operator and value if needed. - switch ($operator) { - case 'STARTS_WITH': - $operator = 'LIKE'; - $value = db_like($value) . '%'; - break; - - case 'ENDS_WITH': - $operator = 'LIKE'; - $value = '%' . db_like($value); - break; - - case 'CONTAINS': - $operator = 'LIKE'; - $value = '%' . db_like($value) . '%'; - break; +function field_sql_storage_field_storage_query(EntityFieldQuery $query) { + $groups = array(); + if ($query->age == FIELD_LOAD_CURRENT) { + $tablename_function = '_field_sql_storage_tablename'; + $id_key = 'entity_id'; + } + else { + $tablename_function = '_field_sql_storage_revision_tablename'; + $id_key = 'revision_id'; + } + $table_aliases = array(); + // Add tables for the fields used. + foreach ($query->fields as $key => $field) { + $tablename = $tablename_function($field); + // Every field needs a new table. + $table_alias = $tablename . $key; + $table_aliases[$key] = $table_alias; + if ($key) { + $select_query->join($tablename, $table_alias, "$table_alias.etid = $field_base_table.etid AND $table_alias.$id_key = $field_base_table.$id_key"); } - // Translate field columns into prefixed db columns. - if (in_array($column, $field_columns)) { - $column = _field_sql_storage_columnname($field_name, $column); + else { + $select_query = db_select($tablename, $table_alias); + $select_query->fields($table_alias, array('entity_id', 'revision_id', 'bundle')); + // As only a numeric ID is stored instead of the entity type add the + // field_config_entity_type table to resolve the etid to a more readable + // name. + $select_query->join('field_config_entity_type', 'fcet', "fcet.etid = $table_alias.etid"); + $select_query->addField('fcet', 'type', 'entity_type'); + $field_base_table = $table_alias; } - // Translate entity types into numeric ids. Expressing the condition on the - // local 'etid' column rather than the JOINed 'type' column avoids a - // filesort. - if ($column == 'type') { - $column = 't.etid'; - if (is_array($value)) { - foreach (array_keys($value) as $key) { - $value[$key] = _field_sql_storage_etid($value[$key]); + if ($field['cardinality'] != 1) { + $select_query->distinct(); + } + } + + // Add field conditions. + foreach ($query->fieldConditions as $key => $condition) { + $table_alias = $table_aliases[$key]; + $field = $condition['field']; + // Add the specified condition. + $sql_field = "$table_alias." . _field_sql_storage_columnname($field['field_name'], $condition['column']); + $query->addCondition($select_query, $sql_field, $condition); + // Add delta / language group conditions. + foreach (array('delta', 'language') as $column) { + if (isset($condition[$column . '_group'])) { + $group_name = $condition[$column . '_group']; + if (!isset($groups[$column][$group_name])) { + $groups[$column][$group_name] = $table_alias; + } + else { + $select_query->where("$table_alias.$column = " . $groups[$column][$group_name] . ".$column"); } } - else { - $value = _field_sql_storage_etid($value); - } - } - // Track condition on 'deleted'. - if ($column == 'deleted') { - $condition_deleted = TRUE; } - - $query->condition($column, $value, $operator); } - // Exclude deleted data unless we have a condition on it. - if (!isset($condition_deleted)) { - $query->condition('deleted', 0); + // Add field orders. + foreach ($query->fieldOrder as $key => $order) { + $table_alias = $table_aliases[$key]; + $field = $order['field']; + $sql_field = "$table_alias." . _field_sql_storage_columnname($field['field_name'], $order['column']); + $select_query->orderBy($sql_field, $order['direction']); } - // For a count query, return the count now. - if ($options['count']) { - return $query - ->fields('t', array('etid', 'entity_id', 'revision_id')) - ->distinct() - ->countQuery() - ->execute() - ->fetchField(); + if (isset($query->deleted)) { + $select_query->condition("$field_base_table.deleted", (int) $query->deleted); } - - // For a data query, add fields. - $query - ->fields('t', array('bundle', 'entity_id', 'revision_id')) - ->fields('e', array('type')) - // We need to ensure entities arrive in a consistent order for the - // range() operation to work. - ->orderBy('t.etid') - ->orderBy('t.entity_id'); - - // Initialize results array - $return = array(); - - // Getting $count entities possibly requires reading more than $count rows - // since fields with multiple values span over several rows. We query for - // batches of $count rows until we've either read $count entities or received - // less rows than asked for. - $entity_count = 0; - do { - if ($options['limit'] != FIELD_QUERY_NO_LIMIT) { - $query->range($options['cursor'], $options['limit']); + if ($query->propertyConditions || $query->propertyOrder) { + if (empty($query->entityConditions['entity_type']['value'])) { + throw new EntityFieldQueryException('Property conditions and orders must have an entity type defined.'); } - $results = $query->execute(); - - $row_count = 0; - foreach ($results as $row) { - $row_count++; - $options['cursor']++; - // If querying all revisions and the entity type has revisions, we need - // to key the results by revision_ids. - $entity_type = entity_get_info($row->type); - $id = ($load_current || empty($entity_type['entity keys']['revision'])) ? $row->entity_id : $row->revision_id; - - if (!isset($return[$row->type][$id])) { - $return[$row->type][$id] = entity_create_stub_entity($row->type, array($row->entity_id, $row->revision_id, $row->bundle)); - $entity_count++; - } - } - } while ($options['limit'] != FIELD_QUERY_NO_LIMIT && $row_count == $options['limit'] && $entity_count < $options['limit']); - - // The query is complete when the last batch returns less rows than asked - // for. - if ($row_count < $options['limit']) { - $options['cursor'] = FIELD_QUERY_COMPLETE; + $entity_type = $query->entityConditions['entity_type']['value']; + $entity_base_table = _field_sql_storage_query_join_entity($select_query, $entity_type, $field_base_table); + $query->entityConditions['entity_type']['operator'] = '='; + $query->processProperty($select_query, $entity_base_table); + } + foreach ($query->entityConditions as $key => $condition) { + $sql_field = $key == 'entity_type' ? 'fcet.type' : "$field_base_table.$key"; + $query->addCondition($select_query, $sql_field, $condition); } + foreach ($query->entityOrder as $key => $direction) { + $sql_field = $key == 'entity_type' ? 'fcet.type' : "$field_base_table.$key"; + $query->orderBy($sql_field, $direction); + } + return $query->finishQuery($select_query, $id_key); +} - return $return; +/** + * Adds the base entity table to a field query object. + * + * @param SelectQuery $select_query + * A SelectQuery containing at least one table as specified by + * _field_sql_storage_tablename(). + * @param $entity_type + * The entity type for which the base table should be joined. + * @param $field_base_table + * Name of a table in $select_query. As only INNER JOINs are used, it does + * not matter which. + * + * @return + * The name of the entity base table joined in. + */ +function _field_sql_storage_query_join_entity(SelectQuery $select_query, $entity_type, $field_base_table) { + $entity_info = entity_get_info($entity_type); + $entity_base_table = $entity_info['base table']; + $entity_field = $entity_info['entity keys']['id']; + $select_query->join($entity_base_table, $entity_base_table, "$entity_base_table.$entity_field = $field_base_table.entity_id"); + return $entity_base_table; } /** diff --git a/modules/field/modules/list/list.info b/modules/field/modules/list/list.info index ad0f9e83..75562d66 100644 --- a/modules/field/modules/list/list.info +++ b/modules/field/modules/list/list.info @@ -1,15 +1,15 @@ -; $Id: list.info,v 1.6 2009/12/14 20:18:55 dries Exp $ +; $Id: list.info,v 1.7 2010/06/21 02:27:47 webchick Exp $ name = List description = Defines list field types. Use with Options to create selection lists. -package = Core - fields +package = Core version = VERSION core = 7.x files[]=list.module files[]=tests/list.test required = TRUE -; Information added by drupal.org packaging script on 2010-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/field/modules/list/list.module b/modules/field/modules/list/list.module index d3d7b75b..d7b069e0 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.32 2010/05/12 08:55:47 dries Exp $ +// $Id: list.module,v 1.33 2010/06/14 15:41:02 dries Exp $ /** * @file @@ -99,7 +99,7 @@ function list_field_schema($field) { * * @todo: If $has_data, add a form validate function to verify that the * new allowed values do not exclude any keys for which data already - * exists in the databae (use field_attach_query()) to find out. + * exists in the field storage (use EntityFieldQuery to find out). * Implement the validate function via hook_field_update_forbid() so * list.module does not depend on form submission. */ diff --git a/modules/field/modules/list/tests/list_test.info b/modules/field/modules/list/tests/list_test.info index afd5ddbf..bf038790 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/field/modules/number/number.info b/modules/field/modules/number/number.info index 20cf84eb..92f2e1ec 100644 --- a/modules/field/modules/number/number.info +++ b/modules/field/modules/number/number.info @@ -1,14 +1,14 @@ -; $Id: number.info,v 1.4 2009/06/12 08:39:36 dries Exp $ +; $Id: number.info,v 1.5 2010/06/21 02:27:47 webchick Exp $ name = Number description = Defines numeric field types. -package = Core - fields +package = Core version = VERSION core = 7.x files[]=number.module required = TRUE -; Information added by drupal.org packaging script on 2010-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/field/modules/number/number.module b/modules/field/modules/number/number.module index 8a377e47..b2d4af6a 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.39 2010/05/06 05:59:31 webchick Exp $ +// $Id: number.module,v 1.40 2010/06/23 13:45:40 dries Exp $ /** * @file @@ -173,8 +173,8 @@ function number_field_instance_settings_form($field, $instance) { * Implements hook_field_validate(). * * Possible error codes: - * - 'number_min': The value is smaller than the allowed minimum value. - * - 'number_max': The value is larger than the allowed maximum value. + * - 'number_min': The value is less than the allowed minimum value. + * - 'number_max': The value is greater than the allowed maximum value. */ function number_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) { foreach ($items as $delta => $item) { @@ -182,13 +182,13 @@ function number_field_validate($entity_type, $entity, $field, $instance, $langco if (is_numeric($instance['settings']['min']) && $item['value'] < $instance['settings']['min']) { $errors[$field['field_name']][$langcode][$delta][] = array( 'error' => 'number_min', - 'message' => t('%name: the value may be no smaller than %min.', array('%name' => t($instance['label']), '%min' => $instance['settings']['min'])), + 'message' => t('%name: the value may be no less than %min.', array('%name' => t($instance['label']), '%min' => $instance['settings']['min'])), ); } if (is_numeric($instance['settings']['max']) && $item['value'] > $instance['settings']['max']) { $errors[$field['field_name']][$langcode][$delta][] = array( 'error' => 'number_max', - 'message' => t('%name: the value may be no larger than %max.', array('%name' => t($instance['label']), '%max' => $instance['settings']['max'])), + 'message' => t('%name: the value may be no greater than %max.', array('%name' => t($instance['label']), '%max' => $instance['settings']['max'])), ); } } diff --git a/modules/field/modules/options/options.info b/modules/field/modules/options/options.info index 64c9f888..1a01ef3c 100644 --- a/modules/field/modules/options/options.info +++ b/modules/field/modules/options/options.info @@ -1,15 +1,15 @@ -; $Id: options.info,v 1.4 2009/10/17 01:20:00 dries Exp $ +; $Id: options.info,v 1.5 2010/06/21 02:27:47 webchick Exp $ name = Options description = Defines selection, check box and radio button widgets for text and numeric fields. -package = Core - fields +package = Core version = VERSION core = 7.x files[]=options.module files[]=options.test required = TRUE -; Information added by drupal.org packaging script on 2010-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/field/modules/text/text.info b/modules/field/modules/text/text.info index c242cb79..9e78eda5 100644 --- a/modules/field/modules/text/text.info +++ b/modules/field/modules/text/text.info @@ -1,15 +1,15 @@ -; $Id: text.info,v 1.6 2010/01/30 07:59:25 dries Exp $ +; $Id: text.info,v 1.7 2010/06/21 02:27:47 webchick Exp $ name = Text description = Defines simple text field types. -package = Core - fields +package = Core version = VERSION core = 7.x files[] = text.module files[] = text.test required = TRUE -; Information added by drupal.org packaging script on 2010-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/field/modules/text/text.js b/modules/field/modules/text/text.js index 9272ffff..8388a743 100644 --- a/modules/field/modules/text/text.js +++ b/modules/field/modules/text/text.js @@ -1,4 +1,4 @@ -// $Id: text.js,v 1.3 2010/03/08 03:59:25 webchick Exp $ +// $Id: text.js,v 1.4 2010/06/12 08:18:59 webchick Exp $ (function ($) { @@ -9,30 +9,40 @@ Drupal.behaviors.textSummary = { attach: function (context, settings) { $('.text-summary', context).once('text-summary', function () { var $widget = $(this).closest('div.field-type-text-with-summary'); - var $summary = $widget.find('div.text-summary-wrapper'); - var $summaryLabel = $summary.find('label'); - var $full = $widget.find('.form-item:has(.text-full)'); - var $fullLabel = $full.find('label'); + var $summaries = $widget.find('div.text-summary-wrapper'); - // Setup the edit/hide summary link. - var $link = $('<span class="field-edit-link">(<a class="link-edit-summary" href="#">' + Drupal.t('Hide summary') + '</a>)</span>').toggle( - function () { - $summary.hide(); - $(this).find('a').html(Drupal.t('Edit summary')).end().appendTo($fullLabel); - return false; - }, - function () { - $summary.show(); - $(this).find('a').html(Drupal.t('Hide summary')).end().appendTo($summaryLabel); - return false; + $summaries.once('text-summary-wrapper').each(function(index) { + var $summary = $(this); + var $summaryLabel = $summary.find('label'); + var $full = $widget.find('.text-full').eq(index).closest('.form-item'); + var $fullLabel = $full.find('label'); + + // Create a placeholder label when the field cardinality is + // unlimited or greater than 1. + if ($fullLabel.length == 0) { + $fullLabel = $('<label></label>').prependTo($full); } - ).appendTo($summaryLabel); - // If no summary is set, hide the summary field. - if ($(this).val() == '') { - $link.click(); - } - return; + // Setup the edit/hide summary link. + var $link = $('<span class="field-edit-link">(<a class="link-edit-summary" href="#">' + Drupal.t('Hide summary') + '</a>)</span>').toggle( + function () { + $summary.hide(); + $(this).find('a').html(Drupal.t('Edit summary')).end().appendTo($fullLabel); + return false; + }, + function () { + $summary.show(); + $(this).find('a').html(Drupal.t('Hide summary')).end().appendTo($summaryLabel); + return false; + } + ).appendTo($summaryLabel); + + // If no summary is set, hide the summary field. + if ($(this).val() == '') { + $link.click(); + } + return; + }); }); } }; diff --git a/modules/field/modules/text/text.module b/modules/field/modules/text/text.module index 83bee6df..2a3604c7 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.54 2010/05/06 05:59:31 webchick Exp $ +// $Id: text.module,v 1.59 2010/07/07 13:30:06 dries Exp $ /** * @file @@ -43,7 +43,6 @@ function text_field_info() { 'text_long' => array( 'label' => t('Long text'), 'description' => t('This field stores long text in the database.'), - 'settings' => array('max_length' => ''), 'instance_settings' => array('text_processing' => 0), 'default_widget' => 'text_textarea', 'default_formatter' => 'text_default', @@ -51,7 +50,6 @@ function text_field_info() { 'text_with_summary' => array( 'label' => t('Long text and summary'), 'description' => t('This field stores long text in the database along with optional summary text.'), - 'settings' => array('max_length' => ''), 'instance_settings' => array('text_processing' => 1, 'display_summary' => 0), 'default_widget' => 'text_textarea_with_summary', 'default_formatter' => 'text_summary_or_trimmed', @@ -118,17 +116,21 @@ function text_field_schema($field) { function text_field_settings_form($field, $instance, $has_data) { $settings = $field['settings']; - $form['max_length'] = array( - '#type' => 'textfield', - '#title' => t('Maximum length'), - '#default_value' => $settings['max_length'], - '#required' => TRUE, - '#description' => t('The maximum length of the field in characters.'), - '#element_validate' => array('_element_validate_integer_positive'), - // @todo: If $has_data, add a validate handler that only allows - // max_length to increase. - '#disabled' => $has_data, - ); + $form = array(); + + if ($field['type'] == 'text') { + $form['max_length'] = array( + '#type' => 'textfield', + '#title' => t('Maximum length'), + '#default_value' => $settings['max_length'], + '#required' => TRUE, + '#description' => t('The maximum length of the field in characters.'), + '#element_validate' => array('_element_validate_integer_positive'), + // @todo: If $has_data, add a validate handler that only allows + // max_length to increase. + '#disabled' => $has_data, + ); + } return $form; } @@ -511,17 +513,17 @@ function text_field_widget_settings_form($field, $instance) { /** * Implements hook_field_widget_form(). */ -function text_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $base) { - $element = $base; +function text_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { $summary_widget = array(); $main_widget = array(); switch ($instance['widget']['type']) { case 'text_textfield': - $main_widget = $base + array( + $main_widget = $element + array( '#type' => 'textfield', '#default_value' => isset($items[$delta]['value']) ? $items[$delta]['value'] : NULL, '#size' => $instance['widget']['settings']['size'], + '#maxlength' => $field['settings']['max_length'], '#attributes' => array('class' => array('text-full')), ); break; @@ -545,7 +547,7 @@ function text_field_widget_form(&$form, &$form_state, $field, $instance, $langco // Fall through to the next case. case 'text_textarea': - $main_widget = $base + array( + $main_widget = $element + array( '#type' => 'textarea', '#default_value' => isset($items[$delta]['value']) ? $items[$delta]['value'] : NULL, '#rows' => $instance['widget']['settings']['rows'], @@ -590,3 +592,18 @@ function text_field_widget_error($element, $error, $form, &$form_state) { form_error($error_element, $error['message']); } +/** + * Implements hook_field_prepare_translation(). + */ +function text_field_prepare_translation($entity_type, $entity, $field, $instance, $langcode, &$items, $source_entity, $source_langcode) { + // If the translating user is not permitted to use the assigned text format, + // we must not expose the source values. + $field_name = $field['field_name']; + $formats = filter_formats(); + foreach ($source_entity->{$field_name}[$source_langcode] as $delta => $item) { + $format_id = $item['format']; + if (!filter_access($formats[$format_id])) { + unset($items[$delta]); + } + } +} diff --git a/modules/field/modules/text/text.test b/modules/field/modules/text/text.test index bc87bc4c..f6685533 100644 --- a/modules/field/modules/text/text.test +++ b/modules/field/modules/text/text.test @@ -1,5 +1,5 @@ <?php -// $Id: text.test,v 1.22 2010/04/10 10:01:15 dries Exp $ +// $Id: text.test,v 1.24 2010/06/28 20:58:42 dries Exp $ class TextFieldTestCase extends DrupalWebTestCase { protected $instance; @@ -46,7 +46,7 @@ class TextFieldTestCase extends DrupalWebTestCase { 'type' => 'text_textfield', ), 'display' => array( - 'full' => array( + 'default' => array( 'type' => 'text_default', ), ), @@ -94,7 +94,12 @@ class TextFieldTestCase extends DrupalWebTestCase { ), 'widget' => array( 'type' => $widget_type, - ) + ), + 'display' => array( + 'full' => array( + 'type' => 'text_default', + ), + ), ); field_create_instance($this->instance); $langcode = LANGUAGE_NONE; @@ -116,7 +121,7 @@ class TextFieldTestCase extends DrupalWebTestCase { // Display the entity. $entity = field_test_entity_test_load($id); - $entity->content = field_attach_view($entity_type, $entity); + $entity->content = field_attach_view($entity_type, $entity, 'full'); $this->content = drupal_render($entity->content); $this->assertText($value, 'Filtered tags are not displayed'); } @@ -148,7 +153,12 @@ class TextFieldTestCase extends DrupalWebTestCase { ), 'widget' => array( 'type' => $widget_type, - ) + ), + 'display' => array( + 'full' => array( + 'type' => 'text_default', + ), + ), ); field_create_instance($this->instance); $langcode = LANGUAGE_NONE; @@ -180,7 +190,7 @@ class TextFieldTestCase extends DrupalWebTestCase { // Display the entity. $entity = field_test_entity_test_load($id); - $entity->content = field_attach_view($entity_type, $entity); + $entity->content = field_attach_view($entity_type, $entity, 'full'); $this->content = drupal_render($entity->content); $this->assertNoRaw($value, t('HTML tags are not displayed.')); $this->assertRaw(check_plain($value), t('Escaped HTML is displayed correctly.')); @@ -213,7 +223,7 @@ class TextFieldTestCase extends DrupalWebTestCase { // Display the entity. $entity = field_test_entity_test_load($id); - $entity->content = field_attach_view($entity_type, $entity); + $entity->content = field_attach_view($entity_type, $entity, 'full'); $this->content = drupal_render($entity->content); $this->assertRaw($value, t('Value is displayed unfiltered')); } @@ -363,3 +373,78 @@ class TextSummaryTestCase extends DrupalWebTestCase { $this->assertIdentical($summary, $expected, t('Generated summary "@summary" matches expected "@expected".', array('@summary' => $summary, '@expected' => $expected))); } } + +class TextTranslationTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'Text translation', + 'description' => 'Check if the text field is correctly prepared for translation.', + 'group' => 'Field types', + ); + } + + function setUp() { + parent::setUp('locale', 'translation'); + + $this->format = 3; + $this->admin = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages', 'bypass node access', "use text format $this->format")); + $this->translator = $this->drupalCreateUser(array('create article content', 'edit own article content', 'translate content')); + + // Enable an additional language. + $this->drupalLogin($this->admin); + $edit = array('langcode' => 'fr'); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + + // Set "Article" content type to use multilingual support with translation. + $edit = array('language_content_type' => 2); + $this->drupalPost('admin/structure/types/manage/article', $edit, t('Save content type')); + $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Article')), t('Article content type has been updated.')); + } + + /** + * Check that user that does not have access the field format cannot see the + * source value when creating a translation. + */ + function testMultipleTextField() { + // Make node body multiple. + $edit = array('field[cardinality]' => -1); + $this->drupalPost('admin/structure/types/manage/article/fields/body', $edit, t('Save settings')); + $this->drupalGet('node/add/article'); + $this->assertFieldByXPath("//input[@name='body_add_more']", t('Add another item'), t('Body field cardinality set to multiple.')); + + $body = array( + $this->randomName(), + $this->randomName(), + ); + + // Create an article with the first body input format set to "Full HTML". + $langcode = 'en'; + $edit = array( + "title" => $this->randomName(), + 'language' => $langcode, + ); + $this->drupalPost('node/add/article', $edit, t('Save')); + + // Populate the body field: the first item gets the "Full HTML" input + // format, the second one "Filtered HTML". + $format = $this->format; + foreach ($body as $delta => $value) { + $edit = array( + "body[$langcode][$delta][value]" => $value, + "body[$langcode][$delta][format]" => $format--, + ); + $this->drupalPost('node/1/edit', $edit, t('Save')); + $this->assertText($body[$delta], t('The body field with delta @delta has been saved.', array('@delta' => $delta))); + } + + // Login as translator. + $this->drupalLogout(); + $this->drupalLogin($this->translator); + + // Translate the article in french. + $this->drupalGet('node/1/translate'); + $this->clickLink(t('add translation')); + $this->assertNoText($body[0], t('The body field with delta @delta is hidden.', array('@delta' => 0))); + $this->assertText($body[1], t('The body field with delta @delta is shown.', array('@delta' => 1))); + } +} diff --git a/modules/field/tests/field.test b/modules/field/tests/field.test index dbcdaaab..36d6284f 100644 --- a/modules/field/tests/field.test +++ b/modules/field/tests/field.test @@ -1,5 +1,5 @@ <?php -// $Id: field.test,v 1.30 2010/05/23 07:30:56 dries Exp $ +// $Id: field.test,v 1.33 2010/06/17 13:16:57 dries Exp $ /** * @file @@ -623,215 +623,6 @@ class FieldAttachStorageTestCase extends FieldAttachTestCase { $this->assertFalse(field_read_instance('test_entity', $this->field_name, $this->instance['bundle']), "First field is deleted"); $this->assertFalse(field_read_instance('test_entity', $field_name, $instance['bundle']), "Second field is deleted"); } - - /** - * Test field_attach_query(). - */ - function testFieldAttachQuery() { - $cardinality = $this->field['cardinality']; - $langcode = LANGUAGE_NONE; - - // Create an additional bundle with an instance of the field. - field_test_create_bundle('test_bundle_1', 'Test Bundle 1'); - $this->instance2 = $this->instance; - $this->instance2['bundle'] = 'test_bundle_1'; - field_create_instance($this->instance2); - - // Create instances of both fields on the second entity type. - $instance = $this->instance; - $instance['entity_type'] = 'test_cacheable_entity'; - field_create_instance($instance); - $instance2 = $this->instance2; - $instance2['entity_type'] = 'test_cacheable_entity'; - field_create_instance($instance2); - - // Unconditional count query returns 0. - $count = field_attach_query($this->field_id, array(), array('count' => TRUE)); - $this->assertEqual($count, 0, t('With no entities, count query returns 0.')); - - // Create two test entities, using two different types and bundles. - $entity_types = array(1 => 'test_entity', 2 => 'test_cacheable_entity'); - $entities = array(1 => field_test_create_stub_entity(1, 1, 'test_bundle'), 2 => field_test_create_stub_entity(2, 2, 'test_bundle_1')); - - // Create first test entity with random (distinct) values. - $values = array(); - for ($delta = 0; $delta < $cardinality; $delta++) { - do { - $value = mt_rand(1, 127); - } while (in_array($value, $values)); - $values[$delta] = $value; - $entities[1]->{$this->field_name}[$langcode][$delta] = array('value' => $values[$delta]); - } - field_attach_insert($entity_types[1], $entities[1]); - - // Unconditional count query returns 1. - $count = field_attach_query($this->field_id, array(), array('count' => TRUE)); - $this->assertEqual($count, 1, t('With one entity, count query returns @count.', array('@count' => $count))); - - // Create second test entity, sharing a value with the first one. - $common_value = $values[$cardinality - 1]; - $entities[2]->{$this->field_name} = array($langcode => array(array('value' => $common_value))); - field_attach_insert($entity_types[2], $entities[2]); - - // Query on the entity's values. - for ($delta = 0; $delta < $cardinality; $delta++) { - $conditions = array(array('value', $values[$delta])); - $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT)); - $this->assertTrue(isset($result[$entity_types[1]][1]), t('Query on value %delta returns the entity', array('%delta' => $delta))); - - $count = field_attach_query($this->field_id, $conditions, array('count' => TRUE)); - $this->assertEqual($count, ($values[$delta] == $common_value) ? 2 : 1, t('Count query on value %delta counts %count entities', array('%delta' => $delta, '%count' => $count))); - } - - // Query on a value that is not in the entity. - do { - $different_value = mt_rand(1, 127); - } while (in_array($different_value, $values)); - $conditions = array(array('value', $different_value)); - $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT)); - $this->assertFalse(isset($result[$entity_types[1]][1]), t("Query on a value that is not in the entity doesn't return the entity")); - $count = field_attach_query($this->field_id, $conditions, array('count' => TRUE)); - $this->assertEqual($count, 0, t("Count query on a value that is not in the entity doesn't count the entity")); - - // Query on the value shared by both entities, and discriminate using - // additional conditions. - - $conditions = array(array('value', $common_value)); - $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT)); - $this->assertTrue(isset($result[$entity_types[1]][1]) && isset($result[$entity_types[2]][2]), t('Query on a value common to both entities returns both entities')); - $count = field_attach_query($this->field_id, $conditions, array('count' => TRUE)); - $this->assertEqual($count, 2, t('Count query on a value common to both entities counts both entities')); - - $conditions = array(array('type', $entity_types[1]), array('value', $common_value)); - $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT)); - $this->assertTrue(isset($result[$entity_types[1]][1]) && !isset($result[$entity_types[2]][2]), t("Query on a value common to both entities and a 'type' condition only returns the relevant entity")); - $count = field_attach_query($this->field_id, $conditions, array('count' => TRUE)); - $this->assertEqual($count, 1, t("Count query on a value common to both entities and a 'type' condition only returns the relevant entity")); - - $conditions = array(array('bundle', $entities[1]->fttype), array('value', $common_value)); - $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT)); - $this->assertTrue(isset($result[$entity_types[1]][1]) && !isset($result[$entity_types[2]][2]), t("Query on a value common to both entities and a 'bundle' condition only returns the relevant entity")); - $count = field_attach_query($this->field_id, $conditions, array('count' => TRUE)); - $this->assertEqual($count, 1, t("Count query on a value common to both entities and a 'bundle' condition only counts the relevant entity")); - - $conditions = array(array('entity_id', $entities[1]->ftid), array('value', $common_value)); - $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT)); - $this->assertTrue(isset($result[$entity_types[1]][1]) && !isset($result[$entity_types[2]][2]), t("Query on a value common to both entities and an 'entity_id' condition only returns the relevant entity")); - $count = field_attach_query($this->field_id, $conditions, array('count' => TRUE)); - $this->assertEqual($count, 1, t("Count query on a value common to both entities and an 'entity_id' condition only counts the relevant entity")); - - // Test result format. - $conditions = array(array('value', $values[0])); - $result = field_attach_query($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT)); - $expected = array( - $entity_types[1] => array( - $entities[1]->ftid => field_test_create_stub_entity($entities[1]->ftid, $entities[1]->ftvid), - ) - ); - $this->assertEqual($result, $expected, t('Result format is correct.')); - - // Now test the count/offset paging capability. - - // Create a new bundle with an instance of the field. - field_test_create_bundle('offset_bundle', 'Offset Test Bundle'); - $this->instance2 = $this->instance; - $this->instance2['bundle'] = 'offset_bundle'; - field_create_instance($this->instance2); - - // Create 20 test entities, using the new bundle, but with - // non-sequential ids so we can tell we are getting the right ones - // back. We do not need unique values since field_attach_query() - // won't return them anyway. - $offset_entities = array(); - $offset_id = mt_rand(1, 3); - for ($i = 0; $i < 20; ++$i) { - $offset_id += mt_rand(2, 5); - $offset_entities[$offset_id] = field_test_create_stub_entity($offset_id, $offset_id, 'offset_bundle'); - $offset_entities[$offset_id]->{$this->field_name}[$langcode][0] = array('value' => $offset_id); - field_attach_insert('test_entity', $offset_entities[$offset_id]); - } - - // Query for the offset entities in batches, making sure we get - // back the right ones. - $cursor = 0; - foreach (array(1 => 1, 3 => 3, 5 => 5, 8 => 8, 13 => 3) as $count => $expect) { - $found = field_attach_query($this->field_id, array(array('bundle', 'offset_bundle')), array('limit' => $count, 'cursor' => &$cursor)); - if (isset($found['test_entity'])) { - $this->assertEqual(count($found['test_entity']), $expect, t('Requested @count, expected @expect, got @found, cursor @cursor', array('@count' => $count, '@expect' => $expect, '@found' => count($found['test_entity']), '@cursor' => $cursor))); - foreach ($found['test_entity'] as $id => $entity) { - $this->assert(isset($offset_entities[$id]), "Entity $id found"); - unset($offset_entities[$id]); - } - } - else { - $this->assertEqual(0, $expect, t('Requested @count, expected @expect, got @found, cursor @cursor', array('@count' => $count, '@expect' => $expect, '@found' => 0, '@cursor' => $cursor))); - } - } - $this->assertEqual(count($offset_entities), 0, "All entities found"); - $this->assertEqual($cursor, FIELD_QUERY_COMPLETE, "Cursor is FIELD_QUERY_COMPLETE"); - } - - /** - * Test field_attach_query_revisions(). - */ - function testFieldAttachQueryRevisions() { - $cardinality = $this->field['cardinality']; - - // Create first entity revision with random (distinct) values. - $entity_type = 'test_entity'; - $entities = array(1 => field_test_create_stub_entity(1, 1), 2 => field_test_create_stub_entity(1, 2)); - $langcode = LANGUAGE_NONE; - $values = array(); - for ($delta = 0; $delta < $cardinality; $delta++) { - do { - $value = mt_rand(1, 127); - } while (in_array($value, $values)); - $values[$delta] = $value; - $entities[1]->{$this->field_name}[$langcode][$delta] = array('value' => $values[$delta]); - } - field_attach_insert($entity_type, $entities[1]); - - // Create second entity revision, sharing a value with the first one. - $common_value = $values[$cardinality - 1]; - $entities[2]->{$this->field_name}[$langcode][0] = array('value' => $common_value); - field_attach_update($entity_type, $entities[2]); - - // Query on the entity values. - for ($delta = 0; $delta < $cardinality; $delta++) { - $conditions = array(array('value', $values[$delta])); - $result = field_attach_query_revisions($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT)); - $this->assertTrue(isset($result[$entity_type][1]), t('Query on value %delta returns the entity', array('%delta' => $delta))); - } - - // Query on a value that is not in the entity. - do { - $different_value = mt_rand(1, 127); - } while (in_array($different_value, $values)); - $conditions = array(array('value', $different_value)); - $result = field_attach_query_revisions($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT)); - $this->assertFalse(isset($result[$entity_type][1]), t("Query on a value that is not in the entity doesn't return the entity")); - - // Query on the value shared by both entities, and discriminate using - // additional conditions. - - $conditions = array(array('value', $common_value)); - $result = field_attach_query_revisions($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT)); - $this->assertTrue(isset($result[$entity_type][1]) && isset($result[$entity_type][2]), t('Query on a value common to both entities returns both entities')); - - $conditions = array(array('revision_id', $entities[1]->ftvid), array('value', $common_value)); - $result = field_attach_query_revisions($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT)); - $this->assertTrue(isset($result[$entity_type][1]) && !isset($result[$entity_type][2]), t("Query on a value common to both entities and a 'revision_id' condition only returns the relevant entity")); - - // Test FIELD_QUERY_RETURN_IDS result format. - $conditions = array(array('value', $values[0])); - $result = field_attach_query_revisions($this->field_id, $conditions, array('limit' => FIELD_QUERY_NO_LIMIT)); - $expected = array( - $entity_type => array( - $entities[1]->ftid => field_test_create_stub_entity($entities[1]->ftid, $entities[1]->ftvid), - ) - ); - $this->assertEqual($result, $expected, t('FIELD_QUERY_RETURN_IDS result format returns the expect result')); - } } /** @@ -871,8 +662,8 @@ class FieldAttachOtherTestCase extends FieldAttachTestCase { ), ); field_update_instance($this->instance); - field_attach_prepare_view($entity_type, array($entity->ftid => $entity)); - $entity->content = field_attach_view($entity_type, $entity); + field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); + $entity->content = field_attach_view($entity_type, $entity, 'full'); $output = drupal_render($entity->content); $this->content = $output; $this->assertRaw($this->instance['label'], "Label is displayed."); @@ -885,8 +676,8 @@ class FieldAttachOtherTestCase extends FieldAttachTestCase { $entity = clone($entity_init); $this->instance['display']['full']['label'] = 'hidden'; field_update_instance($this->instance); - field_attach_prepare_view($entity_type, array($entity->ftid => $entity)); - $entity->content = field_attach_view($entity_type, $entity); + field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); + $entity->content = field_attach_view($entity_type, $entity, 'full'); $output = drupal_render($entity->content); $this->content = $output; $this->assertNoRaw($this->instance['label'], "Hidden label: label is not displayed."); @@ -900,8 +691,8 @@ class FieldAttachOtherTestCase extends FieldAttachTestCase { ), ); field_update_instance($this->instance); - field_attach_prepare_view($entity_type, array($entity->ftid => $entity)); - $entity->content = field_attach_view($entity_type, $entity); + field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); + $entity->content = field_attach_view($entity_type, $entity, 'full'); $output = drupal_render($entity->content); $this->content = $output; $this->assertNoRaw($this->instance['label'], "Hidden field: label is not displayed."); @@ -922,8 +713,8 @@ class FieldAttachOtherTestCase extends FieldAttachTestCase { ), ); field_update_instance($this->instance); - field_attach_prepare_view($entity_type, array($entity->ftid => $entity)); - $entity->content = field_attach_view($entity_type, $entity); + field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); + $entity->content = field_attach_view($entity_type, $entity, 'full'); $output = drupal_render($entity->content); $display = $formatter_setting; foreach ($values as $delta => $value) { @@ -945,8 +736,8 @@ class FieldAttachOtherTestCase extends FieldAttachTestCase { ), ); field_update_instance($this->instance); - field_attach_prepare_view($entity_type, array($entity->ftid => $entity)); - $entity->content = field_attach_view($entity_type, $entity); + field_attach_prepare_view($entity_type, array($entity->ftid => $entity), 'full'); + $entity->content = field_attach_view($entity_type, $entity, 'full'); $output = drupal_render($entity->content); $this->content = $output; foreach ($values as $delta => $value) { @@ -1332,8 +1123,8 @@ class FieldInfoTestCase extends FieldTestCase { $data['settings'] = array(); $data['widget']['settings'] = 'unavailable_widget'; $data['widget']['settings'] = array(); - $data['display']['full']['type'] = 'unavailable_formatter'; - $data['display']['full']['settings'] = array(); + $data['display']['default']['type'] = 'unavailable_formatter'; + $data['display']['default']['settings'] = array(); db_update('field_config_instance') ->fields(array('data' => serialize($data))) ->condition('field_name', $instance_definition['field_name']) @@ -1354,13 +1145,11 @@ class FieldInfoTestCase extends FieldTestCase { $widget_type = field_info_widget_types($instance['widget']['type']); $this->assertIdentical($instance['widget']['settings'], $widget_type['settings'] , t('All expected widget settings are present.')); - // Check that the default formatter is used and expected settings are in place. - $entity_info = entity_get_info('test_entity'); - foreach ($entity_info['view modes'] as $view_mode => $info) { - $this->assertIdentical($instance['display'][$view_mode]['type'], $field_type['default_formatter'], t('Unavailable formatter replaced with default formatter in view_mode %view_mode', array('%view_mode' => $view_mode))); - $formatter_type = field_info_formatter_types($instance['display'][$view_mode]['type']); - $this->assertIdentical($instance['display'][$view_mode]['settings'], $formatter_type['settings'] , t('All expected formatter settings are present in view_mode %view_mode', array('%view_mode' => $view_mode))); - } + // Check that display settings are set for the 'default' mode. + $display = $instance['display']['default']; + $this->assertIdentical($display['type'], $field_type['default_formatter'], t("Formatter is set for the 'default' view mode")); + $formatter_type = field_info_formatter_types($display['type']); + $this->assertIdentical($display['settings'], $formatter_type['settings'] , t("Formatter settings are set for the 'default' view mode")); } /** @@ -1792,7 +1581,13 @@ class FieldDisplayAPITestCase extends FieldTestCase { 'bundle' => 'test_bundle', 'label' => $this->label, 'display' => array( - 'full' => array( + 'default' => array( + 'type' => 'field_test_default', + 'settings' => array( + 'test_formatter_setting' => $this->randomName(), + ), + ), + 'teaser' => array( 'type' => 'field_test_default', 'settings' => array( 'test_formatter_setting' => $this->randomName(), @@ -1862,19 +1657,19 @@ class FieldDisplayAPITestCase extends FieldTestCase { // View mode: check that display settings specified in the instance are // used. - $output = field_view_field('test_entity', $this->entity, $this->field_name, 'full'); + $output = field_view_field('test_entity', $this->entity, $this->field_name, 'teaser'); $this->drupalSetContent(drupal_render($output)); - $setting = $this->instance['display']['full']['settings']['test_formatter_setting']; + $setting = $this->instance['display']['teaser']['settings']['test_formatter_setting']; $this->assertText($this->label, t('Label was displayed.')); foreach($this->values as $delta => $value) { $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); } - // Unknown view mode: check that display settings for 'full' view mode + // Unknown view mode: check that display settings for 'default' view mode // are used. $output = field_view_field('test_entity', $this->entity, $this->field_name, 'unknown_view_mode'); $this->drupalSetContent(drupal_render($output)); - $setting = $this->instance['display']['full']['settings']['test_formatter_setting']; + $setting = $this->instance['display']['default']['settings']['test_formatter_setting']; $this->assertText($this->label, t('Label was displayed.')); foreach($this->values as $delta => $value) { $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); @@ -1929,17 +1724,17 @@ class FieldDisplayAPITestCase extends FieldTestCase { // View mode: check that display settings specified in the instance are // used. - $setting = $this->instance['display']['full']['settings']['test_formatter_setting']; + $setting = $this->instance['display']['teaser']['settings']['test_formatter_setting']; foreach ($this->values as $delta => $value) { $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; - $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, 'full'); + $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, 'teaser'); $this->drupalSetContent(drupal_render($output)); $this->assertText($setting . '|' . $value['value'], t('Value @delta was displayed with expected setting.', array('@delta' => $delta))); } - // Unknown view mode: check that display settings for 'full' view mode + // Unknown view mode: check that display settings for 'default' view mode // are used. - $setting = $this->instance['display']['full']['settings']['test_formatter_setting']; + $setting = $this->instance['display']['default']['settings']['test_formatter_setting']; foreach ($this->values as $delta => $value) { $item = $this->entity->{$this->field_name}[LANGUAGE_NONE][$delta]; $output = field_view_value('test_entity', $this->entity, $this->field_name, $item, 'unknown_view_mode'); @@ -2446,13 +2241,13 @@ class FieldInstanceCrudTestCase extends FieldTestCase { $this->assertIdentical($record['data']['label'], $this->instance_definition['field_name'], t('Label defaults to field name.')); $this->assertIdentical($record['data']['description'], '', t('Description defaults to empty string.')); $this->assertIdentical($record['data']['widget']['type'], $field_type['default_widget'], t('Default widget has been written.')); - $this->assertTrue(isset($record['data']['display']['full']), t('Display for "full" view_mode has been written.')); - $this->assertIdentical($record['data']['display']['full']['type'], $field_type['default_formatter'], t('Default formatter for "full" view_mode has been written.')); + $this->assertTrue(isset($record['data']['display']['default']), t('Display for "full" view_mode has been written.')); + $this->assertIdentical($record['data']['display']['default']['type'], $field_type['default_formatter'], t('Default formatter for "full" view_mode has been written.')); // Check that default settings are set. $this->assertIdentical($record['data']['settings'], $field_type['instance_settings'] , t('Default instance settings have been written.')); $this->assertIdentical($record['data']['widget']['settings'], $widget_type['settings'] , t('Default widget settings have been written.')); - $this->assertIdentical($record['data']['display']['full']['settings'], $formatter_type['settings'], t('Default formatter settings for "full" view_mode have been written.')); + $this->assertIdentical($record['data']['display']['default']['settings'], $formatter_type['settings'], t('Default formatter settings for "full" view_mode have been written.')); // Guarantee that the field/bundle combination is unique. try { @@ -2535,8 +2330,8 @@ class FieldInstanceCrudTestCase extends FieldTestCase { $instance['settings']['test_instance_setting'] = $this->randomName(); $instance['widget']['settings']['test_widget_setting'] =$this->randomName(); $instance['widget']['weight']++; - $instance['display']['full']['settings']['test_formatter_setting'] = $this->randomName(); - $instance['display']['full']['weight']++; + $instance['display']['default']['settings']['test_formatter_setting'] = $this->randomName(); + $instance['display']['default']['weight']++; field_update_instance($instance); $instance_new = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); @@ -2545,23 +2340,23 @@ class FieldInstanceCrudTestCase extends FieldTestCase { $this->assertEqual($instance['description'], $instance_new['description'], t('"description" change is saved')); $this->assertEqual($instance['widget']['settings']['test_widget_setting'], $instance_new['widget']['settings']['test_widget_setting'], t('Widget setting change is saved')); $this->assertEqual($instance['widget']['weight'], $instance_new['widget']['weight'], t('Widget weight change is saved')); - $this->assertEqual($instance['display']['full']['settings']['test_formatter_setting'], $instance_new['display']['full']['settings']['test_formatter_setting'], t('Formatter setting change is saved')); - $this->assertEqual($instance['display']['full']['weight'], $instance_new['display']['full']['weight'], t('Widget weight change is saved')); + $this->assertEqual($instance['display']['default']['settings']['test_formatter_setting'], $instance_new['display']['default']['settings']['test_formatter_setting'], t('Formatter setting change is saved')); + $this->assertEqual($instance['display']['default']['weight'], $instance_new['display']['default']['weight'], t('Widget weight change is saved')); // Check that changing widget and formatter types updates the default settings. $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); $instance['widget']['type'] = 'test_field_widget_multiple'; - $instance['display']['full']['type'] = 'field_test_multiple'; + $instance['display']['default']['type'] = 'field_test_multiple'; field_update_instance($instance); $instance_new = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); $this->assertEqual($instance['widget']['type'], $instance_new['widget']['type'] , t('Widget type change is saved.')); $settings = field_info_widget_settings($instance_new['widget']['type']); $this->assertIdentical($settings, array_intersect_key($instance_new['widget']['settings'], $settings) , t('Widget type change updates default settings.')); - $this->assertEqual($instance['display']['full']['type'], $instance_new['display']['full']['type'] , t('Formatter type change is saved.')); - $info = field_info_formatter_types($instance_new['display']['full']['type']); + $this->assertEqual($instance['display']['default']['type'], $instance_new['display']['default']['type'] , t('Formatter type change is saved.')); + $info = field_info_formatter_types($instance_new['display']['default']['type']); $settings = $info['settings']; - $this->assertIdentical($settings, array_intersect_key($instance_new['display']['full']['settings'], $settings) , t('Changing formatter type updates default settings.')); + $this->assertIdentical($settings, array_intersect_key($instance_new['display']['default']['settings'], $settings) , t('Changing formatter type updates default settings.')); // Check that adding a new view mode is saved and gets default settings. $instance = field_read_instance('test_entity', $this->instance_definition['field_name'], $this->instance_definition['bundle']); @@ -3022,7 +2817,7 @@ class FieldBulkDeleteTestCase extends FieldTestCase { * the database and that the appropriate Field API functions can * operate on the deleted data and instance. * - * This tests how field_attach_query() interacts with + * This tests how EntityFieldQuery interacts with * field_delete_instance() and could be moved to FieldCrudTestCase, * but depends on this class's setUp(). */ @@ -3031,7 +2826,11 @@ class FieldBulkDeleteTestCase extends FieldTestCase { $field = reset($this->fields); // There are 10 entities of this bundle. - $found = field_attach_query($field['id'], array(array('bundle', $bundle)), array('limit' => FIELD_QUERY_NO_LIMIT)); + $query = new EntityFieldQuery(); + $found = $query + ->fieldCondition($field) + ->entityCondition('bundle', $bundle) + ->execute(); $this->assertEqual(count($found['test_entity']), 10, 'Correct number of entities found before deleting'); // Delete the instance. @@ -3044,12 +2843,21 @@ class FieldBulkDeleteTestCase extends FieldTestCase { $this->assertEqual($instances[0]['bundle'], $bundle, 'The deleted instance is for the correct bundle'); // There are 0 entities of this bundle with non-deleted data. - $found = field_attach_query($field['id'], array(array('bundle', $bundle)), array('limit' => FIELD_QUERY_NO_LIMIT)); + $query = new EntityFieldQuery(); + $found = $query + ->fieldCondition($field) + ->entityCondition('bundle', $bundle) + ->execute(); $this->assertTrue(!isset($found['test_entity']), 'No entities found after deleting'); // There are 10 entities of this bundle when deleted fields are allowed, and // their values are correct. - $found = field_attach_query($field['id'], array(array('bundle', $bundle), array('deleted', 1)), array('limit' => FIELD_QUERY_NO_LIMIT)); + $query = new EntityFieldQuery(); + $found = $query + ->fieldCondition($field) + ->entityCondition('bundle', $bundle) + ->deleted(TRUE) + ->execute(); field_attach_load($this->entity_type, $found[$this->entity_type], FIELD_LOAD_CURRENT, array('field_id' => $field['id'], 'deleted' => 1)); $this->assertEqual(count($found['test_entity']), 10, 'Correct number of entities found after deleting'); foreach ($found['test_entity'] as $id => $entity) { @@ -3081,7 +2889,12 @@ class FieldBulkDeleteTestCase extends FieldTestCase { field_purge_batch($batch_size); // There are $count deleted entities left. - $found = field_attach_query($field['id'], array(array('bundle', $bundle), array('deleted', 1)), array('limit' => FIELD_QUERY_NO_LIMIT)); + $query = new EntityFieldQuery(); + $found = $query + ->fieldCondition($field) + ->entityCondition('bundle', $bundle) + ->deleted(TRUE) + ->execute(); $this->assertEqual($count ? count($found['test_entity']) : count($found), $count, 'Correct number of entities found after purging 2'); } diff --git a/modules/field/tests/field_test.entity.inc b/modules/field/tests/field_test.entity.inc index 066e7e68..34830f56 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.9 2010/05/23 07:30:56 dries Exp $ +// $Id: field_test.entity.inc,v 1.12 2010/06/17 13:44:45 dries Exp $ /** * @file @@ -14,9 +14,11 @@ function field_test_entity_info() { $test_entity_modes = array( 'full' => array( 'label' => t('Full object'), + 'custom settings' => TRUE, ), 'teaser' => array( 'label' => t('Teaser'), + 'custom settings' => TRUE, ), ); @@ -25,6 +27,8 @@ function field_test_entity_info() { 'name' => t('Test Entity'), 'fieldable' => TRUE, 'field cache' => FALSE, + 'base table' => 'test_entity', + 'revision table' => 'test_entity_revision', 'entity keys' => array( 'id' => 'ftid', 'revision' => 'ftvid', @@ -46,6 +50,29 @@ function field_test_entity_info() { 'bundles' => $bundles, 'view modes' => $test_entity_modes, ), + 'test_entity_bundle_key' => array( + 'name' => t('Test Entity with a bundle key.'), + 'base table' => 'test_entity_bundle_key', + 'fieldable' => TRUE, + 'field cache' => FALSE, + 'entity keys' => array( + 'id' => 'ftid', + 'bundle' => 'fttype', + ), + 'bundles' => array('bundle1' => array('label' => 'Bundle1'), 'bundle2' => array('label' => 'Bundle2')), + 'view modes' => $test_entity_modes, + ), + 'test_entity_bundle' => array( + 'name' => t('Test Entity with a specified bundle.'), + 'base table' => 'test_entity_bundle', + 'fieldable' => TRUE, + 'field cache' => FALSE, + 'entity keys' => array( + 'id' => 'ftid', + ), + 'bundles' => array('test_entity_2' => array('label' => 'Test entity 2')), + 'view modes' => $test_entity_modes, + ), ); } @@ -262,10 +289,15 @@ function field_test_entity_edit($entity) { * Test_entity form. */ function field_test_entity_form($form, &$form_state, $entity, $add = FALSE) { - if (isset($form_state['test_entity'])) { - $entity = $form_state['test_entity'] + (array) $entity; + // During initial form build, add the entity to the form state for use during + // form building and processing. During a rebuild, use what is in the form + // state. + if (!isset($form_state['test_entity'])) { + $form_state['test_entity'] = $entity; + } + else { + $entity = $form_state['test_entity']; } - $entity = (object) $entity; foreach (array('ftid', 'ftvid', 'fttype') as $key) { $form[$key] = array( @@ -300,15 +332,14 @@ function field_test_entity_form($form, &$form_state, $entity, $add = FALSE) { * Validate handler for field_test_entity_form(). */ function field_test_entity_form_validate($form, &$form_state) { - $entity = field_test_create_stub_entity($form_state['values']['ftid'], $form_state['values']['ftvid'], $form_state['values']['fttype']); - field_attach_form_validate('test_entity', $entity, $form, $form_state); + entity_form_field_validate('test_entity', $form, $form_state); } /** * Submit handler for field_test_entity_form(). */ function field_test_entity_form_submit($form, &$form_state) { - $entity = field_test_entity_form_submit_builder($form, $form_state); + $entity = $form['#builder_function']($form, $form_state); $insert = empty($entity->ftid); field_test_entity_save($entity); @@ -316,25 +347,20 @@ function field_test_entity_form_submit($form, &$form_state) { drupal_set_message($message); if ($entity->ftid) { - unset($form_state['rebuild']); $form_state['redirect'] = 'test-entity/' . $entity->ftid . '/edit'; } else { // Error on save. drupal_set_message(t('The entity could not be saved.'), 'error'); + $form_state['rebuild'] = TRUE; } } /** - * Builds a test_entity from submitted form values. + * Updates the form state's entity by processing this submission's values. */ function field_test_entity_form_submit_builder($form, &$form_state) { - $entity = field_test_create_stub_entity($form_state['values']['ftid'], $form_state['values']['ftvid'], $form_state['values']['fttype']); - $entity->revision = !empty($form_state['values']['revision']); - field_attach_submit('test_entity', $entity, $form, $form_state); - - $form_state['test_entity'] = (array) $entity; - $form_state['rebuild'] = TRUE; - + $entity = $form_state['test_entity']; + entity_form_submit_build_entity('test_entity', $entity, $form, $form_state); return $entity; } diff --git a/modules/field/tests/field_test.field.inc b/modules/field/tests/field_test.field.inc index 50d96811..79212e68 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.10 2010/05/18 18:30:49 dries Exp $ +// $Id: field_test.field.inc,v 1.11 2010/06/14 15:41:02 dries Exp $ /** * @file @@ -26,6 +26,14 @@ function field_test_field_info() { 'default_widget' => 'test_field_widget', 'default_formatter' => 'field_test_default', ), + 'shape' => array( + 'label' => t('Shape'), + 'description' => t('Another dummy field type.'), + 'settings' => array(), + 'instance_settings' => array(), + 'default_widget' => 'test_field_widget', + 'default_formatter' => 'field_test_default', + ), 'hidden_test_field' => array( 'no_ui' => TRUE, 'label' => t('Hidden from UI test field'), @@ -42,18 +50,36 @@ function field_test_field_info() { * Implements hook_field_schema(). */ function field_test_field_schema($field) { - return array( - 'columns' => array( - 'value' => array( - 'type' => 'int', - 'size' => 'tiny', - 'not null' => FALSE, + if ($field['type'] == 'test_field') { + return array( + 'columns' => array( + 'value' => array( + 'type' => 'int', + 'size' => 'medium', + 'not null' => FALSE, + ), ), - ), - 'indexes' => array( - 'value' => array('value'), - ), - ); + 'indexes' => array( + 'value' => array('value'), + ), + ); + } + else { + return array( + 'columns' => array( + 'shape' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => FALSE, + ), + 'color' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => FALSE, + ), + ), + ); + } } /** diff --git a/modules/field/tests/field_test.info b/modules/field/tests/field_test.info index 09940f99..4cbc63bd 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/field/tests/field_test.install b/modules/field/tests/field_test.install index f2d4f6ae..d5c8ab88 100644 --- a/modules/field/tests/field_test.install +++ b/modules/field/tests/field_test.install @@ -1,5 +1,5 @@ <?php -// $Id: field_test.install,v 1.2 2009/12/04 16:49:46 dries Exp $ +// $Id: field_test.install,v 1.3 2010/06/14 15:41:02 dries Exp $ /** * @file @@ -50,6 +50,37 @@ function field_test_schema() { ), 'primary key' => array('ftid'), ); + $schema['test_entity_bundle_key'] = array( + 'description' => 'The base table for test entities with a bundle key.', + 'fields' => array( + 'ftid' => array( + 'description' => 'The primary indentifier for a test_entity_bundle_key.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'fttype' => array( + 'description' => 'The type of this test_entity.', + 'type' => 'varchar', + 'length' => 32, + 'not null' => FALSE, + 'default' => '', + ), + ), + ); + $schema['test_entity_bundle'] = array( + 'description' => 'The base table for test entities with a bundle.', + 'fields' => array( + 'ftid' => array( + 'description' => 'The primary indentifier for a test_entity_bundle.', + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + ), + ); $schema['test_entity_revision'] = array( 'description' => 'Stores information about each saved version of a {test_entity}.', 'fields' => array( diff --git a/modules/field/tests/field_test.module b/modules/field/tests/field_test.module index 3900a0b7..d86b7fc5 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.7 2010/05/01 08:12:23 dries Exp $ +// $Id: field_test.module,v 1.8 2010/06/24 17:24:40 webchick Exp $ /** * @file @@ -191,3 +191,24 @@ function field_test_field_delete($entity_type, $entity, $field, $instance, $item $args = func_get_args(); field_test_memorize(__FUNCTION__, $args); } + +/** + * Implements hook_entity_query_alter(). + */ +function field_test_entity_query_alter(&$query) { + if (!empty($query->alterMyExecuteCallbackPlease)) { + $query->executeCallback = 'field_test_dummy_field_storage_query'; + } +} + +/** + * Pseudo-implements hook_field_storage_query(). + */ +function field_test_dummy_field_storage_query(EntityFieldQuery $query) { + // Return dummy values that will be checked by the test. + return array( + 'user' => array( + 1 => entity_create_stub_entity('user', array(1, NULL, NULL)), + ), + ); +} diff --git a/modules/field_ui/field_ui-display-overview-form.tpl.php b/modules/field_ui/field_ui-display-overview-form.tpl.php deleted file mode 100644 index fee78018..00000000 --- a/modules/field_ui/field_ui-display-overview-form.tpl.php +++ /dev/null @@ -1,55 +0,0 @@ -<?php -// $Id: field_ui-display-overview-form.tpl.php,v 1.1 2009/08/19 13:31:13 webchick Exp $ - -/** - * @file - * Default theme implementation to configure field display settings. - * - * Available variables: - * - * - $form: The complete form for the field display settings. - * - $contexts: An associative array of the available contexts for these fields. - * On the node field display settings this defaults to including "teaser" and - * "full" as the available contexts. - * - $rows: The field display settings form broken down into rendered rows for - * printing as a table. - * - $submit: The rendered submit button for this form. - * - * @see field_ui_display_overview_form() - * @see template_preprocess_field_ui_display_overview_form() - */ -?> -<?php if ($rows): ?> - <table id="field-display-overview" class="sticky-enabled"> - <thead> - <tr> - <th> </th> - <?php foreach ($contexts as $key => $value): ?> - <th colspan="2"><?php print $value; ?> - <?php endforeach; ?> - </tr> - <tr> - <th><?php print t('Field'); ?></th> - <?php foreach ($contexts as $key => $value): ?> - <th><?php print t('Label'); ?></th> - <th><?php print t('Format'); ?></th> - <?php endforeach; ?> - </tr> - </thead> - <tbody> - <?php - $count = 0; - foreach ($rows as $row): ?> - <tr class="<?php print $count % 2 == 0 ? 'odd' : 'even'; ?>"> - <td><span class="<?php print $row->label_class; ?>"><?php print $row->human_name; ?></span></td> - <?php foreach ($contexts as $context => $title): ?> - <td><?php print $row->{$context}->label; ?></td> - <td><?php print $row->{$context}->type; ?></td> - <?php endforeach; ?> - </tr> - <?php $count++; - endforeach; ?> - </tbody> - </table> - <?php print $submit; ?> -<?php endif; ?> diff --git a/modules/field_ui/field_ui-display-overview-table.tpl.php b/modules/field_ui/field_ui-display-overview-table.tpl.php new file mode 100644 index 00000000..e0679407 --- /dev/null +++ b/modules/field_ui/field_ui-display-overview-table.tpl.php @@ -0,0 +1,60 @@ +<?php +// $Id: field_ui-display-overview-table.tpl.php,v 1.2 2010/05/26 07:49:52 dries Exp $ + +/** + * @file + * Default theme implementation to configure field display settings. + * + * Available variables: + * - $rows: The field display settings form broken down into rendered rows for + * printing as a table. The array is separated in two entries, 'visible' and + * 'hidden'. + * - $id: The HTML id for the table. + * + * @see field_ui_display_overview_form() + * @see template_preprocess_field_ui_display_overview_table() + */ +?> +<?php if ($rows): ?> + <table id="field-display-overview" class="field-display-overview sticky-enabled"> + <thead> + <tr> + <th><?php print t('Field'); ?></th> + <th><?php print t('Weight'); ?></th> + <th><?php print t('Label'); ?></th> + <th><?php print t('Format'); ?></th> + </tr> + </thead> + <tbody> + <tr class="region-message region-visible-message <?php print empty($rows['visible']) ? 'region-empty' : 'region-populated'; ?>"> + <td colspan="4"><em><?php print t('No field is displayed'); ?></em></td> + </tr> + <?php + $count = 0; + foreach ($rows['visible'] as $row): ?> + <tr class="<?php print $count % 2 == 0 ? 'odd' : 'even'; ?> <?php print $row->class ?>"> + <td><span class="<?php print $row->label_class; ?>"><?php print $row->human_name; ?></span></td> + <td><?php print $row->weight . $row->hidden_name; ?></td> + <td><?php if (isset($row->label)) print $row->label; ?></td> + <td><?php print $row->type; ?></td> + </tr> + <?php $count++; + endforeach; ?> + <tr class="region-title region-title-hidden"> + <td colspan="4"><?php print t('Hidden'); ?></td> + </tr> + <tr class="region-message region-hidden-message <?php print empty($rows['hidden']) ? 'region-empty' : 'region-populated'; ?>"> + <td colspan="4"><em><?php print t('No field is hidden'); ?></em></td> + </tr> + <?php foreach ($rows['hidden'] as $row): ?> + <tr class="<?php print $count % 2 == 0 ? 'odd' : 'even'; ?> <?php print $row->class ?>"> + <td><span class="<?php print $row->label_class; ?>"><?php print $row->human_name; ?></span></td> + <td><?php print $row->weight . $row->hidden_name; ?></td> + <td><?php if (isset($row->label)) print $row->label; ?></td> + <td><?php print $row->type; ?></td> + </tr> + <?php $count++; + endforeach; ?> + </tbody> + </table> +<?php endif; ?> diff --git a/modules/field_ui/field_ui-field-overview-form.tpl.php b/modules/field_ui/field_ui-field-overview-form.tpl.php deleted file mode 100644 index d7107319..00000000 --- a/modules/field_ui/field_ui-field-overview-form.tpl.php +++ /dev/null @@ -1,94 +0,0 @@ -<?php -// $Id: field_ui-field-overview-form.tpl.php,v 1.2 2009/10/24 17:26:16 webchick Exp $ - -/** - * @file - * Default theme implementation to configure field settings. - * - * Available variables: - * - * - $form: The complete overview form for the field settings. - * - $contexts: An associative array of the available contexts for these fields. - * On the node field display settings this defaults to including "teaser" and - * "full" as the available contexts. - * - $rows: The field overview form broken down into rendered rows for printing - * as a table. - * - $submit: The rendered submit button for this form. - * - * @see field_ui_field_overview_form() - * @see template_preprocess_field_ui_field_overview_form() - */ -?> -<table id="field-overview" class="sticky-enabled"> - <thead> - <tr> - <th><?php print t('Label'); ?></th> - <th><?php print t('Weight'); ?></th> - <th><?php print t('Name'); ?></th> - <th><?php print t('Field'); ?></th> - <th><?php print t('Widget'); ?></th> - <th colspan="2"><?php print t('Operations'); ?></th> - </tr> - </thead> - <tbody> - <?php - $count = 0; - foreach ($rows as $row): ?> - <tr class="<?php print $count % 2 == 0 ? 'odd' : 'even'; ?> <?php print $row->class ?>"> - <?php - switch ($row->row_type): - case 'field': ?> - <td> - <span class="<?php print $row->label_class; ?>"><?php print $row->label; ?></span> - </td> - <td><?php print $row->weight . $row->hidden_name; ?></td> - <td><?php print $row->field_name; ?></td> - <td><?php print $row->type; ?></td> - <td><?php print $row->widget_type; ?></td> - <td><?php print $row->edit; ?></td> - <td><?php print $row->delete; ?></td> - <?php break; - - case 'extra': ?> - <td> - <span class="<?php print $row->label_class; ?>"><?php print $row->label; ?></span> - </td> - <td><?php print $row->weight . $row->hidden_name; ?></td> - <td><?php print $row->name; ?></td> - <td colspan="2"><?php print $row->description; ?></td> - <td><?php print $row->edit; ?></td> - <td><?php print $row->delete; ?></td> - <?php break; - - case 'add_new_field': ?> - <td> - <div class="<?php print $row->label_class; ?>"> - <div class="new"><?php print t('Add new field'); ?></div> - <?php print $row->label; ?> - </div> - </td> - <td><div class="new"> </div><?php print $row->weight . $row->hidden_name; ?></td> - <td><div class="new"> </div><?php print $row->field_name; ?></td> - <td><div class="new"> </div><?php print $row->type; ?></td> - <td colspan="3"><div class="new"> </div><?php print $row->widget_type; ?></td> - <?php break; - - case 'add_existing_field': ?> - <td> - <div class="<?php print $row->label_class; ?>"> - <div class="new"><?php print t('Add existing field'); ?></div> - <?php print $row->label; ?> - </div> - </td> - <td><div class="new"> </div><?php print $row->weight . $row->hidden_name; ?></td> - <td colspan="2"><div class="new"> </div><?php print $row->field_name; ?></td> - <td colspan="3"><div class="new"> </div><?php print $row->widget_type; ?></td> - <?php break; - endswitch; ?> - </tr> - <?php $count++; - endforeach; ?> - </tbody> -</table> - -<?php print $submit; ?> diff --git a/modules/field_ui/field_ui-rtl.css b/modules/field_ui/field_ui-rtl.css index f7895647..233390b0 100644 --- a/modules/field_ui/field_ui-rtl.css +++ b/modules/field_ui/field_ui-rtl.css @@ -1,8 +1,7 @@ -/* $Id: field_ui-rtl.css,v 1.1 2009/08/19 13:31:13 webchick Exp $ */ +/* $Id: field_ui-rtl.css,v 1.2 2010/06/26 02:06:53 dries Exp $ */ /* 'Manage fields' overview */ -#field-overview .label-add-new-field, -#field-overview .label-add-existing-field { +#field-overview tr.add-new .label-input { float: right; } diff --git a/modules/field_ui/field_ui.admin.inc b/modules/field_ui/field_ui.admin.inc index 629abcc3..1bc3ba8e 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.50 2010/05/18 18:30:49 dries Exp $ +// $Id: field_ui.admin.inc,v 1.57 2010/06/27 18:05:54 webchick Exp $ /** * @file @@ -31,7 +31,7 @@ function field_ui_fields_list() { $rows[$field_name]['data'][2] = implode(', ', $cell['data'][2]); } if (empty($rows)) { - $output = t('No fields have been defined for any content type yet.'); + $output = t('No fields have been defined yet.'); } else { // Sort rows by field name. @@ -62,6 +62,105 @@ function field_ui_inactive_message($entity_type, $bundle) { } } +/** + * Helper function: determines the rendering order of a tree array. + * + * This is intended as a callback for array_reduce(). + */ +function _field_ui_reduce_order($array, $a) { + $array = is_null($array) ? array() : $array; + if ($a['name']) { + $array[] = $a['name']; + } + if (!empty($a['children'])) { + uasort($a['children'], 'drupal_sort_weight'); + $array = array_merge($array, array_reduce($a['children'], '_field_ui_reduce_order')); + } + return $array; +} + +/** + * Theme preprocess function for theme_field_ui_table(). + * + * @see theme_field_ui_table() + */ +function template_preprocess_field_ui_table(&$variables) { + $elements = &$variables['elements']; + + // Build the tree structure from the weight and parenting data contained in + // the flat form structure, to determine row order and indentation. + $tree = array('' => array('name' => '', 'children' => array())); + $parents = array(); + $list = drupal_map_assoc(element_children($elements)); + // Iterate on rows until we can build a known tree path for all of them. + while ($list) { + foreach ($list as $name) { + $row = &$elements[$name]; + $parent = $row['parent_wrapper']['parent']['#value']; + // Proceed if parent is known. + if (empty($parent) || isset($parents[$parent])) { + // Remove from the next iteration. + $parents[$name] = $parent ? array_merge($parents[$parent], array($parent)) : array(); + unset($list[$name]); + + // Add the element in the tree. + $target = &$tree['']; + foreach ($parents[$name] as $key) { + $target = &$target['children'][$key]; + } + $target['children'][$name] = array('name' => $name, 'weight' => $row['weight']['#value']); + + // Add tabledrag indentation to the first row cell. + if ($depth = count($parents[$name])) { + $cell = current(element_children($row)); + $row[$cell]['#prefix'] = theme('indentation', array('size' => $depth)) . (isset($row[$cell]['#prefix']) ? $row[$cell]['#prefix'] : ''); + } + } + } + } + + // Determine rendering order for the tree. + $variables['row_order'] = array_reduce($tree, '_field_ui_reduce_order'); +} + +/** + * Returns HTML for Field UI overview tables. + * + * @param $variables + * An associative array containing: + * - elements: An associative array containing a Form API structure to be + * rendered as a table. + * + * @ingroup themeable + */ +function theme_field_ui_table($variables) { + $elements = $variables['elements']; + $table = array(); + + foreach (array('header', 'attributes') as $key) { + if (isset($elements["#$key"])) { + $table[$key] = $elements["#$key"]; + } + } + + foreach ($variables['row_order'] as $key) { + $element = $elements[$key]; + $row = array('data' => array()); + $row += $element['#attributes']; + + foreach (element_children($element) as $cell_key) { + $cell = array('data' => drupal_render($element[$cell_key])); + if (isset($element[$cell_key]['#cell_attributes'])) { + $cell += $element[$cell_key]['#cell_attributes']; + } + $row['data'][] = $cell; + } + $table['rows'][] = $row; + } + + return theme('table', $table); +} + /** * Menu callback; listing of fields for a bundle. * @@ -83,29 +182,66 @@ function field_ui_field_overview_form($form, &$form_state, $entity_type, $bundle $field_types = field_info_field_types(); $widget_types = field_info_widget_types(); - $extra = field_extra_fields($entity_type, $bundle); + $extra_fields = field_extra_fields($entity_type, $bundle, 'form'); // Store each default weight so that we can add the 'add new' rows after them. $weights = array(); $form += array( - '#tree' => TRUE, '#entity_type' => $entity_type, '#bundle' => $bundle, '#fields' => array_keys($instances), - '#extra' => array_keys($extra), - '#field_rows' => array(), + '#extra' => array_keys($extra_fields), + ); + + $table = array( + '#type' => 'table', + '#tree' => TRUE, + '#header' => array( + t('Label'), + t('Weight'), + t('Parent'), + t('Name'), + t('Field'), + t('Widget'), + array('data' => t('Operations'), 'colspan' => 2), + ), + '#attributes' => array('id' => 'field-overview'), ); + $parent_options = array('' => t('<none>')); + // Fields. foreach ($instances as $name => $instance) { $field = field_info_field($instance['field_name']); $admin_field_path = $admin_path . '/fields/' . $instance['field_name']; $weight = $instance['widget']['weight']; - $form[$name] = array( + $weights[] = $weight; + $table[$name] = array( + '#parents' => array($name), + '#attributes' => array('class' => array('draggable tabledrag-leaf')), 'label' => array( '#markup' => check_plain($instance['label']), ), + 'weight' => array( + '#type' => 'textfield', + '#default_value' => $weight, + '#size' => 3, + '#attributes' => array('class' => array('field-weight')), + ), + 'parent_wrapper' => array( + 'parent' => array( + '#type' => 'select', + '#options' => $parent_options, + '#attributes' => array('class' => array('field-parent')), + '#parents' => array($name, 'parent'), + ), + 'hidden_name' => array( + '#type' => 'hidden', + '#default_value' => $name, + '#attributes' => array('class' => array('field-name')), + ), + ), 'field_name' => array( '#markup' => $instance['field_name'], ), @@ -133,60 +269,60 @@ function field_ui_field_overview_form($form, &$form_state, $entity_type, $bundle '#href' => $admin_field_path . '/delete', '#options' => array('attributes' => array('title' => t('Delete instance.'))), ), - 'weight' => array( - '#type' => 'textfield', - '#default_value' => $weight, - '#size' => 3, - ), - 'hidden_name' => array( - '#type' => 'hidden', - '#default_value' => $instance['field_name'], - ), - '#row_type' => 'field', ); if (!empty($instance['locked'])) { - $form[$name]['edit'] = array('#value' => t('Locked')); - $form[$name]['delete'] = array(); - $form[$name]['#disabled_row'] = TRUE; + $table[$name]['edit'] = array('#value' => t('Locked')); + $table[$name]['delete'] = array(); + $table[$name]['#attributes']['class'][] = 'menu-disabled'; } - $form['#field_rows'][] = $name; - $weights[] = $weight; } // Non-field elements. - foreach ($extra as $name => $label) { - $weight = $extra[$name]['weight']; - $form[$name] = array( + foreach ($extra_fields as $name => $extra_field) { + $weight = $extra_field['weight']; + $weights[] = $weight; + $table[$name] = array( + '#parents' => array($name), + '#attributes' => array('class' => array('draggable', 'tabledrag-leaf', 'menu-disabled')), 'label' => array( - '#markup' => t($extra[$name]['label']), - ), - 'name' => array( - '#markup' => $name, - ), - 'description' => array( - '#markup' => isset($extra[$name]['description']) ? $extra[$name]['description'] : '', + '#markup' => check_plain($extra_field['label']), ), 'weight' => array( '#type' => 'textfield', '#default_value' => $weight, '#size' => 3, + '#attributes' => array('class' => array('field-weight')), + '#title_display' => 'invisible', + '#title' => t('Weight for @row', array('@row' => $extra_field['label'])), + ), + 'parent_wrapper' => array( + 'parent' => array( + '#type' => 'select', + '#options' => $parent_options, + '#attributes' => array('class' => array('field-parent')), + '#parents' => array($name, 'parent'), + ), + 'hidden_name' => array( + '#type' => 'hidden', + '#default_value' => $name, + '#attributes' => array('class' => array('field-name')), + ), + ), + 'field_name' => array( + '#markup' => $name, + ), + 'type' => array( + '#markup' => isset($extra_field['description']) ? $extra_field['description'] : '', + '#cell_attributes' => array('colspan' => 2), ), 'edit' => array( - '#markup' => isset($extra[$name]['edit']) ? $extra[$name]['edit'] : '', + '#markup' => isset($extra_field['edit']) ? $extra_field['edit'] : '', ), 'delete' => array( - '#markup' => isset($extra[$name]['delete']) ? $extra[$name]['delete'] : '', - ), - 'hidden_name' => array( - '#type' => 'hidden', - '#default_value' => $name, + '#markup' => isset($extra_field['delete']) ? $extra_field['delete'] : '', ), - '#disabled_row' => TRUE, - '#row_type' => 'extra', ); - $form['#field_rows'][] = $name; - $weights[] = $weight; } // Additional row: add new field. @@ -197,11 +333,38 @@ function field_ui_field_overview_form($form, &$form_state, $entity_type, $bundle array_unshift($field_type_options, t('- Select a field type -')); array_unshift($widget_type_options, t('- Select a widget -')); $name = '_add_new_field'; - $form[$name] = array( + $table[$name] = array( + '#parents' => array($name), + '#attributes' => array('class' => array('draggable', 'tabledrag-leaf', 'add-new')), 'label' => array( '#type' => 'textfield', '#size' => 15, '#description' => t('Label'), + '#prefix' => '<div class="label-input"><div class="add-new-placeholder">' . t('Add new field') .'</div>', + '#suffix' => '</div>', + ), + 'weight' => array( + '#type' => 'textfield', + '#default_value' => $weight, + '#size' => 3, + '#title_display' => 'invisible', + '#title' => t('Weight for new field'), + '#attributes' => array('class' => array('field-weight')), + '#prefix' => '<div class="add-new-placeholder"> </div>', + ), + 'parent_wrapper' => array( + 'parent' => array( + '#type' => 'select', + '#options' => $parent_options, + '#attributes' => array('class' => array('field-parent')), + '#prefix' => '<div class="add-new-placeholder"> </div>', + '#parents' => array($name, 'parent'), + ), + 'hidden_name' => array( + '#type' => 'hidden', + '#default_value' => $name, + '#attributes' => array('class' => array('field-name')), + ), ), 'field_name' => array( '#type' => 'textfield', @@ -211,30 +374,24 @@ function field_ui_field_overview_form($form, &$form_state, $entity_type, $bundle '#attributes' => array('dir'=>'ltr'), '#size' => 15, '#description' => t('Field name (a-z, 0-9, _)'), + '#prefix' => '<div class="add-new-placeholder"> </div>', ), 'type' => array( '#type' => 'select', '#options' => $field_type_options, '#description' => t('Type of data to store.'), + '#attributes' => array('class' => array('field-type-select')), + '#prefix' => '<div class="add-new-placeholder"> </div>', ), 'widget_type' => array( '#type' => 'select', '#options' => $widget_type_options, '#description' => t('Form element to edit the data.'), + '#attributes' => array('class' => array('widget-type-select')), + '#cell_attributes' => array('colspan' => 3), + '#prefix' => '<div class="add-new-placeholder"> </div>', ), - 'weight' => array( - '#type' => 'textfield', - '#default_value' => $weight, - '#size' => 3, - ), - 'hidden_name' => array( - '#type' => 'hidden', - '#default_value' => $name, - ), - '#add_new' => TRUE, - '#row_type' => 'add_new_field', ); - $form['#field_rows'][] = $name; } // Additional row: add existing field. @@ -243,106 +400,85 @@ function field_ui_field_overview_form($form, &$form_state, $entity_type, $bundle $weight++; array_unshift($existing_field_options, t('- Select an existing field -')); $name = '_add_existing_field'; - $form[$name] = array( + $table[$name] = array( + '#parents' => array($name), + '#attributes' => array('class' => array('draggable', 'tabledrag-leaf', 'menu-disabled')), 'label' => array( '#type' => 'textfield', '#size' => 15, '#description' => t('Label'), + '#attributes' => array('class' => array('label-textfield')), + '#prefix' => '<div class="label-input"><div class="add-new-placeholder">' . t('Add existing field') .'</div>', + '#suffix' => '</div>', + ), + 'weight' => array( + '#type' => 'textfield', + '#default_value' => $weight, + '#size' => 3, + '#title_display' => 'invisible', + '#title' => t('Weight for added field'), + '#attributes' => array('class' => array('field-weight')), + '#prefix' => '<div class="add-new-placeholder"> </div>', + ), + 'parent_wrapper' => array( + 'parent' => array( + '#type' => 'select', + '#options' => $parent_options, + '#attributes' => array('class' => array('field-parent')), + '#prefix' => '<div class="add-new-placeholder"> </div>', + '#parents' => array($name, 'parent'), + ), + 'hidden_name' => array( + '#type' => 'hidden', + '#default_value' => $name, + '#attributes' => array('class' => array('field-name')), + ), ), 'field_name' => array( '#type' => 'select', '#options' => $existing_field_options, '#description' => t('Field to share'), + '#attributes' => array('class' => array('field-select')), + '#cell_attributes' => array('colspan' => 2), + '#prefix' => '<div class="add-new-placeholder"> </div>', ), 'widget_type' => array( '#type' => 'select', '#options' => $widget_type_options, '#description' => t('Form element to edit the data.'), + '#attributes' => array('class' => array('widget-type-select')), + '#cell_attributes' => array('colspan' => 3), + '#prefix' => '<div class="add-new-placeholder"> </div>', ), - 'weight' => array( - '#type' => 'textfield', - '#default_value' => $weight, - '#size' => 3, - ), - 'hidden_name' => array( - '#type' => 'hidden', - '#default_value' => $name, - ), - '#add_new' => TRUE, - '#row_type' => 'add_existing_field', ); - $form['#field_rows'][] = $name; } + $form['table'] = $table; + $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save')); - return $form; -} - -/** - * Theme preprocess function for field_ui-field-overview-form.tpl.php. - */ -function template_preprocess_field_ui_field_overview_form(&$vars) { - $form = &$vars['form']; - drupal_add_css(drupal_get_path('module', 'field_ui') . '/field_ui.css'); - drupal_add_tabledrag('field-overview', 'order', 'sibling', 'field-weight'); - drupal_add_js(drupal_get_path('module', 'field_ui') . '/field_ui.js'); + $form['#attached']['css'][] = drupal_get_path('module', 'field_ui') . '/field_ui.css'; + $form['#attached']['js'][] = drupal_get_path('module', 'field_ui') . '/field_ui.js'; // Add settings for the update selects behavior. $js_fields = array(); - foreach (field_ui_existing_field_options($form['#entity_type'], $form['#bundle']) as $field_name => $fields) { + foreach ($existing_field_options as $field_name => $fields) { $field = field_info_field($field_name); $instance = field_info_instance($form['#entity_type'], $field_name, $form['#bundle']); $js_fields[$field_name] = array('label' => $instance['label'], 'type' => $field['type'], 'widget' => $instance['widget']['type']); } - drupal_add_js(array('fieldWidgetTypes' => field_ui_widget_type_options(), 'fields' => $js_fields), 'setting'); - $order = _field_ui_overview_order($form, $form['#field_rows']); - $rows = array(); + $form['#attached']['js'][] = array( + 'type' => 'setting', + 'data' => array('fields' => $js_fields, 'fieldWidgetTypes' => field_ui_widget_type_options(), + ), + ); - // Identify the 'new item' keys in the form. - $keys = array_keys($form); - $add_rows = array(); - foreach ($keys as $key) { - if (substr($key, 0, 4) == '_add') { - $add_rows[] = $key; - } - } - while ($order) { - $key = reset($order); - $element = &$form[$key]; - $row = new stdClass(); - - // Add target classes for the tabledrag behavior. - $element['weight']['#attributes']['class'][] = 'field-weight'; - $element['hidden_name']['#attributes']['class'][] = 'field-name'; - // Add target classes for the update selects behavior. - switch ($element['#row_type']) { - case 'add_new_field': - $element['type']['#attributes']['class'][] = 'field-type-select'; - $element['widget_type']['#attributes']['class'][] = 'widget-type-select'; - break; - - case 'add_existing_field': - $element['field_name']['#attributes']['class'][] = 'field-select'; - $element['widget_type']['#attributes']['class'][] = 'widget-type-select'; - $element['label']['#attributes']['class'][] = 'label-textfield'; - break; - } - foreach (element_children($element) as $child) { - $row->{$child} = drupal_render($element[$child]); - } - $row->label_class = 'label-' . strtr($element['#row_type'], '_', '-'); - $row->row_type = $element['#row_type']; - $row->class = 'draggable'; - $row->class .= isset($element['#add_new']) ? ' add-new' : ''; - $row->class .= isset($element['#disabled_row']) ? ' menu-disabled' : ''; + // Add tabledrag behavior. + $form['#attached']['drupal_add_tabledrag'][] = array('field-overview', 'order', 'sibling', 'field-weight'); + $form['#attached']['drupal_add_tabledrag'][] = array('field-overview', 'match', 'parent', 'field-parent', 'field-parent', 'field-name'); - $rows[] = $row; - array_shift($order); - } - $vars['rows'] = $rows; - $vars['submit'] = drupal_render_children($form); + return $form; } /** @@ -379,7 +515,7 @@ function _field_ui_field_overview_form_validate_add_new($form, &$form_state) { // Add the 'field_' prefix. if (substr($field_name, 0, 6) != 'field_') { $field_name = 'field_' . $field_name; - form_set_value($form['_add_new_field']['field_name'], $field_name, $form_state); + form_set_value($form['table']['_add_new_field']['field_name'], $field_name, $form_state); } // Invalid field name. @@ -424,7 +560,7 @@ function _field_ui_field_overview_form_validate_add_new($form, &$form_state) { */ function _field_ui_field_overview_form_validate_add_existing($form, &$form_state) { // The form element might be absent if no existing fields can be added to - // this content type + // this bundle. if (isset($form_state['values']['_add_existing_field'])) { $field = $form_state['values']['_add_existing_field']; @@ -464,8 +600,9 @@ function field_ui_field_overview_form_submit($form, &$form_state) { $bundle = $form['#bundle']; $admin_path = _field_ui_bundle_admin_path($entity_type, $bundle); + $bundle_settings = field_bundle_settings($entity_type, $bundle); + // Update field weights. - $extra = array(); foreach ($form_values as $key => $values) { if (in_array($key, $form['#fields'])) { $instance = field_read_instance($entity_type, $key, $bundle); @@ -476,13 +613,11 @@ function field_ui_field_overview_form_submit($form, &$form_state) { field_update_instance($instance); } elseif (in_array($key, $form['#extra'])) { - $extra[$key] = $values['weight']; + $bundle_settings['extra_fields']['form'][$key]['weight'] = $values['weight']; } } - $extra_weights = variable_get('field_extra_weights', array()); - $extra_weights[$entity_type][$bundle] = $extra; - variable_set('field_extra_weights', $extra_weights); + field_bundle_settings($entity_type, $bundle, $bundle_settings); $destinations = array(); @@ -563,12 +698,9 @@ function field_ui_field_overview_form_submit($form, &$form_state) { } /** - * Menu callback; presents a listing of fields display settings for a bundle. - * - * This form includes form widgets to select which fields appear in teaser and - * full view modes, and how the field labels should be rendered. + * Menu callback; presents field display settings for a given view mode. */ -function field_ui_display_overview_form($form, &$form_state, $entity_type, $bundle, $view_modes_selector = 'basic') { +function field_ui_display_overview_form($form, &$form_state, $entity_type, $bundle, $view_mode) { $bundle = field_extract_bundle($entity_type, $bundle); field_ui_inactive_message($entity_type, $bundle); @@ -577,14 +709,14 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund // Gather type information. $instances = field_info_instances($entity_type, $bundle); $field_types = field_info_field_types(); - $view_modes = field_ui_view_modes_tabs($entity_type, $view_modes_selector); + $extra_fields = field_extra_fields($entity_type, $bundle, 'display'); $form += array( - '#tree' => TRUE, '#entity_type' => $entity_type, '#bundle' => $bundle, + '#view_mode' => $view_mode, '#fields' => array_keys($instances), - '#contexts' => $view_modes_selector, + '#extra' => array_keys($extra_fields), ); if (empty($instances)) { @@ -592,85 +724,165 @@ function field_ui_display_overview_form($form, &$form_state, $entity_type, $bund return $form; } - // Fields. - $label_options = array( + $table = array( + '#theme' => 'field_ui_display_overview_table', + '#field_rows' => array(), + '#tree' => TRUE, + ); + + $field_label_options = array( 'above' => t('Above'), 'inline' => t('Inline'), 'hidden' => t('<Hidden>'), ); + $extra_visibility_options = array( + 'visible' => t('Visible'), + 'hidden' => t('Hidden'), + ); + foreach ($instances as $name => $instance) { - $field = field_info_field($instance['field_name']); - $weight = $instance['widget']['weight']; + $display = $instance['display'][$view_mode]; - $form[$name] = array( - 'human_name' => array('#markup' => check_plain($instance['label'])), - 'weight' => array('#type' => 'value', '#value' => $weight), + $table[$name]['human_name'] = array( + '#markup' => check_plain($instance['label']), ); - $defaults = $instance['display']; + $table[$name]['weight'] = array( + '#type' => 'textfield', + '#default_value' => $display['weight'], + '#size' => 3, + ); + $table[$name]['hidden_name'] = array( + '#type' => 'hidden', + '#default_value' => $name, + ); + $table[$name]['label'] = array( + '#type' => 'select', + '#options' => $field_label_options, + '#default_value' => $display['label'], + ); + $field = field_info_field($instance['field_name']); $formatter_options = field_ui_formatter_options($field['type']); $formatter_options['hidden'] = t('<Hidden>'); - foreach ($view_modes as $view_mode) { - $display = isset($instance['display'][$view_mode]) ? $instance['display'][$view_mode] : $instance['display']['full']; - $form[$name][$view_mode]['label'] = array( - '#type' => 'select', - '#options' => $label_options, - '#default_value' => $display['label'], - ); - $form[$name][$view_mode]['type'] = array( - '#type' => 'select', - '#options' => $formatter_options, - '#default_value' => $display['type'], - ); + $table[$name]['type'] = array( + '#type' => 'select', + '#options' => $formatter_options, + '#default_value' => $display['type'], + ); + $table['#field_rows'][] = $name; + + // Collect default formatters for the JS script. + $field_type_info = field_info_field_types($field['type']); + $default_formatters[$name] = $field_type_info['default_formatter']; + } + + // Non-field elements. + foreach ($extra_fields as $name => $extra_field) { + $display = $extra_field['display'][$view_mode]; + $table[$name]['human_name'] = array( + '#markup' => check_plain($extra_field['label']), + ); + $table[$name]['weight'] = array( + '#type' => 'textfield', + '#default_value' => $display['weight'], + '#size' => 3, + ); + $table[$name]['hidden_name'] = array( + '#type' => 'hidden', + '#default_value' => $name, + ); + $table[$name]['type'] = array( + '#type' => 'select', + '#options' => $extra_visibility_options, + '#default_value' => $display['visible'] ? 'visible' : 'hidden', + ); + $table['#field_rows'][] = $name; + } + $form['settings'] = $table; + + // Custom display settings. + if ($view_mode == 'default') { + $form['modes'] = array( + '#type' => 'fieldset', + '#title' => t('Custom display settings'), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + ); + // Collect options and default values for the 'Custom display settings' + // checkboxes. + $options = array(); + $default = array(); + $entity_info = entity_get_info($entity_type); + $view_modes = $entity_info['view modes']; + $view_mode_settings = field_view_mode_settings($entity_type, $bundle); + foreach ($view_modes as $view_mode_name => $view_mode_info) { + $options[$view_mode_name] = $view_mode_info['label']; + if (!empty($view_mode_settings[$view_mode_name]['custom_settings'])) { + $default[] = $view_mode_name; + } } + $form['modes']['view_modes_custom'] = array( + '#type' => 'checkboxes', + '#title' => t('Use custom display settings for the following view modes'), + '#options' => $options, + '#default_value' => $default, + ); } $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save')); + + $form['#attached']['js'][] = drupal_get_path('module', 'field_ui') . '/field_ui.js'; + $form['#attached']['css'][] = drupal_get_path('module', 'field_ui') . '/field_ui.css'; + drupal_add_js(array('fieldDefaultFormatters' => $default_formatters), 'setting'); + return $form; } /** - * Theme preprocess function for field_ui-display-overview-form.tpl.php. + * Theme preprocess function for field_ui-display-overview-table.tpl.php. */ -function template_preprocess_field_ui_display_overview_form(&$vars) { - $form = &$vars['form']; +function template_preprocess_field_ui_display_overview_table(&$vars) { + $elements = &$vars['elements']; - $contexts_selector = $form['#contexts']; - $view_modes = field_ui_view_modes_tabs($form['#entity_type'], $contexts_selector); - $entity_info = entity_get_info($form['#entity_type']); - $view_modes_info = $entity_info['view modes']; - $vars['contexts'] = array(); - foreach ($view_modes as $view_mode) { - $vars['contexts'][$view_mode] = $view_modes_info[$view_mode]['label']; - } + $rows = array( + 'visible' => array(), + 'hidden' => array(), + ); - $order = _field_ui_overview_order($form, $form['#fields']); - if (empty($order)) { - $vars['rows'] = array(); - $vars['submit'] = ''; - return; - } - $rows = array(); - foreach ($order as $key) { - $element = &$form[$key]; - $row = new stdClass(); - foreach (element_children($element) as $child) { - if (array_key_exists('label', $element[$child])) { - $row->{$child} = new stdClass(); - $row->{$child}->label = drupal_render($element[$child]['label']); - $row->{$child}->type = drupal_render($element[$child]['type']); - } - else { - $row->{$child} = drupal_render($element[$child]); + if (!empty($elements['#field_rows'])) { + drupal_add_tabledrag('field-display-overview', 'order', 'sibling', 'field-weight'); + + $order = _field_ui_overview_order($elements, $elements['#field_rows']); + foreach ($order as $key) { + $element = &$elements[$key]; + $visibility = $element['type']['#value'] == 'hidden' ? 'hidden' : 'visible'; + + // Add target classes for the tabledrag behavior. + $element['weight']['#attributes']['class'][] = 'field-weight'; + $element['hidden_name']['#attributes']['class'][] = 'field-name'; + $element['type']['#attributes']['class'][] = 'field-formatter-type'; + $element['type']['#attributes']['class'][] = "field-display-$visibility"; + $element['type']['#attributes']['class'][] = "field-name-$key"; + + $row = new stdClass(); + foreach (element_children($element) as $child) { + if (array_key_exists('label', $element[$child])) { + $row->{$child} = new stdClass(); + $row->{$child}->label = drupal_render($element[$child]['label']); + $row->{$child}->type = drupal_render($element[$child]['type']); + } + else { + $row->{$child} = drupal_render($element[$child]); + } } + $row->class = 'draggable'; + $row->label_class = 'label-field'; + $rows[$visibility][] = $row; } - $row->label_class = 'label-field'; - $rows[] = $row; } $vars['rows'] = $rows; - $vars['submit'] = drupal_render_children($form); } /** @@ -678,17 +890,46 @@ function template_preprocess_field_ui_display_overview_form(&$vars) { */ function field_ui_display_overview_form_submit($form, &$form_state) { $form_values = $form_state['values']; - foreach ($form_values as $key => $values) { - if (in_array($key, $form['#fields'])) { - $instance = field_info_instance($form['#entity_type'], $key, $form['#bundle']); - foreach ($instance['display'] as $view_mode => $display) { - if (isset($values[$view_mode])) { - $instance['display'][$view_mode] = array_merge($instance['display'][$view_mode], $values[$view_mode]); - } + $entity_type = $form['#entity_type']; + $bundle = $form['#bundle']; + $view_mode = $form['#view_mode']; + + // Save data for 'regular' fields. + foreach ($form['#fields'] as $field_name) { + $instance = field_info_instance($entity_type, $field_name, $bundle); + $instance['display'][$view_mode] = $form_values['settings'][$field_name]; + field_update_instance($instance); + } + + // Get current bundle settings. + $bundle_settings = field_bundle_settings($entity_type, $bundle); + + // Save data for 'extra' fields. + foreach ($form['#extra'] as $name) { + $bundle_settings['extra_fields']['display'][$name][$view_mode] = array( + 'weight' => $form_values['settings'][$name]['weight'], + 'visible' => $form_values['settings'][$name]['type'] == 'visible', + ); + } + + // Save view modes data. + if ($view_mode == 'default') { + $entity_info = entity_get_info($entity_type); + foreach ($form_values['view_modes_custom'] as $view_mode_name => $value) { + // Display a message for each view mode newly configured to use custom + // settings. + if (!empty($value) && empty($bundle_settings['view_modes'][$view_mode_name]['custom_settings'])) { + $view_mode_label = $entity_info['view modes'][$view_mode_name]['label']; + $path = _field_ui_bundle_admin_path($entity_type, $bundle) . "/display/$view_mode_name"; + drupal_set_message(t('The %view_mode mode now uses custom display settings. You might want to <a href="@url">configure them</a>.', array('%view_mode' => $view_mode_label, '@url' => url($path)))); } - field_update_instance($instance); + $bundle_settings['view_modes'][$view_mode_name]['custom_settings'] = !empty($value); } } + + // Save updated bundle settings. + field_bundle_settings($entity_type, $bundle, $bundle_settings); + drupal_set_message(t('Your settings have been saved.')); } @@ -964,7 +1205,7 @@ function field_ui_widget_type_form_submit($form, &$form_state) { } /** - * Menu callback; present a form for removing a field from a content type. + * Menu callback; present a form for removing a field instance from a bundle. */ function field_ui_field_delete_form($form, &$form_state, $instance) { $bundle = $instance['bundle']; @@ -994,7 +1235,9 @@ function field_ui_field_delete_form($form, &$form_state, $instance) { } /** - * Remove a field from a content type. + * Removes a field instance from a bundle. + * + * If the field has no more instances, it will be marked as deleted too. */ function field_ui_field_delete_form_submit($form, &$form_state) { $form_values = $form_state['values']; @@ -1282,12 +1525,12 @@ function field_ui_field_edit_form_submit($form, &$form_state) { * Helper functions to handle multipage redirects. */ function field_ui_get_destinations($destinations) { - $query = array(); $path = array_shift($destinations); + $options = drupal_parse_url($path); if ($destinations) { - $query['destinations'] = $destinations; + $options['query']['destinations'] = $destinations; } - return array($path, array('query' => $query)); + return array($options['path'], $options); } /** @@ -1305,6 +1548,7 @@ function field_ui_next_destination($entity_type, $bundle) { /** * Helper function to order fields when theming overview forms. + * @todo Remove when 'Manage display' screen is done. */ function _field_ui_overview_order(&$form, $field_rows) { // Put weight and parenting values into a $dummy render structure and let diff --git a/modules/field_ui/field_ui.css b/modules/field_ui/field_ui.css index 661c2654..ff44e878 100644 --- a/modules/field_ui/field_ui.css +++ b/modules/field_ui/field_ui.css @@ -1,8 +1,7 @@ -/* $Id: field_ui.css,v 1.1 2009/08/19 13:31:13 webchick Exp $ */ +/* $Id: field_ui.css,v 1.3 2010/06/26 02:06:53 dries Exp $ */ /* 'Manage fields' overview */ -#field-overview .label-add-new-field, -#field-overview .label-add-existing-field { +#field-overview tr.add-new .label-input { float: left; /* LTR */ } #field-overview tr.add-new .tabledrag-changed { @@ -11,8 +10,15 @@ #field-overview tr.add-new .description { margin-bottom: 0; } -#field-overview .new { +#field-overview tr.add-new .add-new-placeholder { font-weight: bold; padding-bottom: .5em; } +/* Manage display */ +.field-display-overview tr.region-title td { + font-weight: bold; +} +.field-display-overview tr.region-populated { + display: none; +} diff --git a/modules/field_ui/field_ui.info b/modules/field_ui/field_ui.info index 989fcc6d..54685b02 100644 --- a/modules/field_ui/field_ui.info +++ b/modules/field_ui/field_ui.info @@ -1,6 +1,6 @@ -; $Id: field_ui.info,v 1.2 2009/11/02 03:41:58 webchick Exp $ +; $Id: field_ui.info,v 1.3 2010/06/25 18:51:05 dries Exp $ name = Field UI -description = User Interface for the Field API. +description = User interface for the Field API. package = Core version = VERSION core = 7.x @@ -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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/field_ui/field_ui.js b/modules/field_ui/field_ui.js index a46f170e..8ab363fa 100644 --- a/modules/field_ui/field_ui.js +++ b/modules/field_ui/field_ui.js @@ -1,4 +1,4 @@ -// $Id: field_ui.js,v 1.2 2010/01/09 23:23:43 webchick Exp $ +// $Id: field_ui.js,v 1.3 2010/05/23 19:10:23 dries Exp $ (function($) { @@ -81,4 +81,143 @@ jQuery.fn.fieldPopulateOptions = function (options, selected) { }); }; +/** + * Moves a field in the display settings table from visible to hidden. + * + * This behavior is dependent on the tableDrag behavior, since it uses the + * objects initialized in that behavior to update the row. + */ +Drupal.behaviors.fieldManageDisplayDrag = { + attach: function (context, settings) { + // tableDrag is required for this behavior. + if (!$('table.field-display-overview', context).length || typeof Drupal.tableDrag == 'undefined') { + return; + } + + var defaultFormatters = Drupal.settings.fieldDefaultFormatters; + var tableDrag = Drupal.tableDrag['field-display-overview']; + + // Add a handler for when a row is swapped, update empty regions. + tableDrag.row.prototype.onSwap = function (swappedRow) { + checkEmptyRegions(this.table, this); + }; + + // Add a handler to update the formatter selector when a row is dropped in + // or out of the 'Hidden' section. + tableDrag.onDrop = function () { + var dragObject = this; + var regionRow = $(dragObject.rowObject.element).prevAll('tr.region-message').get(0); + var visibility = regionRow.className.replace(/([^ ]+[ ]+)*region-([^ ]+)-message([ ]+[^ ]+)*/, '$2'); + + // Update the 'format' selector if the visibility changed. + var $select = $('select.field-formatter-type', dragObject.rowObject.element); + var oldVisibility = $select[0].className.replace(/([^ ]+[ ]+)*field-display-([^ ]+)([ ]+[^ ]+)*/, '$2'); + if (visibility != oldVisibility) { + $select.removeClass('field-display-' + oldVisibility).addClass('field-display-' + visibility); + + // Update the selected formatter if coming from an actual drag. + if (!$select.data('noUpdate')) { + if (visibility == 'visible') { + // Restore the formatter back to the previously selected one if + // available, or to the default formatter. + var value = $select.data('oldFormatter'); + if (typeof value == 'undefined') { + // Extract field name from the name of the select. + var fieldName = $select[0].className.match(/\bfield-name-(\S+)\b/)[1].replace('-', '_'); + // Pseudo-fields do not have an entry in the defaultFormatters + // array, we just return to 'visible' for those. + value = (fieldName in defaultFormatters) ? defaultFormatters[fieldName] : 'visible'; + } + $select.data('oldFormatter', value); + } + else { + var value = 'hidden'; + } + $select.val(value); + } + $select.removeData('noUpdate'); + } + }; + + // Add the behavior to each formatter select list. + $('select.field-formatter-type', context).once('field-formatter-type', function () { + // Initialize 'previously selected formatter' as the incoming value. + if ($(this).val() != 'hidden') { + $(this).data('oldFormatter', $(this).val()); + } + + // Add change listener. + $(this).change(function (event) { + var $select = $(this); + var value = $select.val(); + + // Keep track of the last selected formatter. + if (value != 'hidden') { + $select.data('oldFormatter', value); + } + + var visibility = (value == 'hidden') ? 'hidden' : 'visible'; + var oldVisibility = $select[0].className.replace(/([^ ]+[ ]+)*field-display-([^ ]+)([ ]+[^ ]+)*/, '$2'); + if (visibility != oldVisibility) { + // Prevent the onDrop handler from overriding the selected option. + $select.data('noUpdate', true); + + // Make our new row and select field. + var $row = $(this).parents('tr:first'); + var $table = $(this).parents('table'); + var tableDrag = Drupal.tableDrag[$table.attr('id')]; + tableDrag.rowObject = new tableDrag.row($row); + + // Move the row at the bottom of the new section. + if (visibility == 'hidden') { + $('tr:last', tableDrag.table).after($row); + } + else { + $('tr.region-title-hidden', tableDrag.table).before($row); + } + + // Manually update weights and restripe. + tableDrag.updateFields($row.get(0)); + tableDrag.rowObject.changed = true; + if (tableDrag.oldRowElement) { + $(tableDrag.oldRowElement).removeClass('drag-previous'); + } + tableDrag.oldRowElement = $row.get(0); + tableDrag.restripeTable(); + tableDrag.rowObject.markChanged(); + tableDrag.oldRowElement = $row; + $row.addClass('drag-previous'); + + // Modify empty regions with added or removed fields. + checkEmptyRegions($table, tableDrag.rowObject); + } + + // Remove focus from selectbox. + $select.get(0).blur(); + }); + }); + + var checkEmptyRegions = function ($table, rowObject) { + $('tr.region-message', $table).each(function () { + // If the dragged row is in this region, but above the message row, swap + // it down one space. + if ($(this).prev('tr').get(0) == rowObject.element) { + // Prevent a recursion problem when using the keyboard to move rows up. + if ((rowObject.method != 'keyboard' || rowObject.direction == 'down')) { + rowObject.swap('after', this); + } + } + // This region has become empty. + if ($(this).next('tr').is(':not(.draggable)') || $(this).next('tr').length == 0) { + $(this).removeClass('region-populated').addClass('region-empty'); + } + // This region has become populated. + else if ($(this).is('.region-empty')) { + $(this).removeClass('region-empty').addClass('region-populated'); + } + }); + }; + } +}; + })(jQuery); diff --git a/modules/field_ui/field_ui.module b/modules/field_ui/field_ui.module index c7c28615..c3f3296c 100644 --- a/modules/field_ui/field_ui.module +++ b/modules/field_ui/field_ui.module @@ -1,5 +1,5 @@ <?php -// $Id: field_ui.module,v 1.29 2010/04/26 14:40:47 dries Exp $ +// $Id: field_ui.module,v 1.31 2010/06/26 02:06:53 dries Exp $ /** * @file @@ -66,10 +66,11 @@ function field_ui_menu() { if (defined('MAINTENANCE_MODE')) { return $items; } + // Create tabs for all possible bundles. - foreach (entity_get_info() as $entity_type => $info) { - if ($info['fieldable']) { - foreach ($info['bundles'] as $bundle_name => $bundle_info) { + foreach (entity_get_info() as $entity_type => $entity_info) { + if ($entity_info['fieldable']) { + foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) { if (isset($bundle_info['admin'])) { // Extract path information from the bundle. $path = $bundle_info['admin']['path']; @@ -92,7 +93,12 @@ function field_ui_menu() { // items below. $field_position = count(explode('/', $path)) + 1; + // Extract access information, providing defaults. $access = array_intersect_key($bundle_info['admin'], drupal_map_assoc(array('access callback', 'access arguments'))); + $access += array( + 'access callback' => 'user_access', + 'access arguments' => array('administer site configuration'), + ); $items["$path/fields"] = array( 'title' => 'Manage fields', @@ -145,24 +151,38 @@ function field_ui_menu() { 'file' => 'field_ui.admin.inc', ) + $access; - // 'Manage display' tab and context secondary tabs. + // 'Manage display' tab. $items["$path/display"] = array( 'title' => 'Manage display', 'page callback' => 'drupal_get_form', - 'page arguments' => array('field_ui_display_overview_form', $entity_type, $bundle_arg), + 'page arguments' => array('field_ui_display_overview_form', $entity_type, $bundle_arg, 'default'), 'type' => MENU_LOCAL_TASK, 'weight' => 2, 'file' => 'field_ui.admin.inc', ) + $access; - $tabs = field_ui_view_modes_tabs($entity_type); - foreach ($tabs as $key => $tab) { - $items["$path/display/$key"] = array( - 'title' => $tab['title'], - 'page arguments' => array('field_ui_display_overview_form', $entity_type, $bundle_arg, $key), - 'type' => $key == 'basic' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK, - 'weight' => $key == 'basic' ? 0 : 1, + + // View modes secondary tabs. + // The same base $path for the menu item (with a placeholder) can be + // used for all bundles of a given entity type; but depending on + // administrator settings, each bundle has a different set of view + // modes available for customisation. So we define menu items for all + // view modes, and use an access callback to determine which ones are + // actually visible for a given bundle. + $weight = 0; + $view_modes = array('default' => array('label' => t('Default'))) + $entity_info['view modes']; + foreach ($view_modes as $view_mode => $view_mode_info) { + $items["$path/display/$view_mode"] = array( + 'title' => $view_mode_info['label'], + 'page arguments' => array('field_ui_display_overview_form', $entity_type, $bundle_arg, $view_mode), + // The access callback needs to check both the current 'custom + // display' setting for the view mode, and the overall access + // rules for the bundle admin pages. + 'access callback' => '_field_ui_view_mode_menu_access', + 'access arguments' => array_merge(array($entity_type, $bundle_arg, $view_mode, $access['access callback']), $access['access arguments']), + 'type' => ($view_mode == 'default' ? MENU_DEFAULT_LOCAL_TASK : MENU_LOCAL_TASK), + 'weight' => ($view_mode == 'default' ? -10 : $weight++), 'file' => 'field_ui.admin.inc', - ) + $access; + ); } } } @@ -219,100 +239,62 @@ function field_ui_menu_title($instance) { } /** - * Implements hook_theme(). + * Menu access callback for the 'view mode display settings' pages. */ -function field_ui_theme() { - return array( - 'field_ui_field_overview_form' => array( - 'render element' => 'form', - 'file' => 'field_ui.admin.inc', - 'template' => 'field_ui-field-overview-form', - ), - 'field_ui_display_overview_form' => array( - 'render element' => 'form', - 'file' => 'field_ui.admin.inc', - 'template' => 'field_ui-display-overview-form', - ), - ); -} +function _field_ui_view_mode_menu_access($entity_type, $bundle, $view_mode, $access_callback) { + // First, determine visibility according to the 'use custom display' + // setting for the view mode. + $bundle = field_extract_bundle($entity_type, $bundle); + $view_mode_settings = field_view_mode_settings($entity_type, $bundle); + $visibility = ($view_mode == 'default') || !empty($view_mode_settings[$view_mode]['custom_settings']); -/** - * Returns information about tab groups for view modes on an entity type. - * - * On the 'Manage display' page, an administrator can manage the display of - * fields for each view mode for an entity type. The view modes are organized - * into tabs. This function returns the list of entity types to display on - * each tab, as well as the titles of the tabs. - * - * @param $entity_type - * The type of entity to return tab information for. - * @param $tab_selector - * If not NULL, return only information for this particular tab; if NULL, - * return all tab information. - * - * @return - * Array of information about the tabs to display. The keys are internal-use - * tab names and the values are arrays of tab information, with the following - * elements: - * - 'title': Human-readable title of the tab. - * - 'view modes': Array of view modes for this entity type that should - * be displayed on this tab. - * - * @see hook_field_ui_view_modes_tabs() - * - * @todo Remove this completely and use vertical tabs? - */ -function field_ui_view_modes_tabs($entity_type, $tab_selector = NULL) { - $info = &drupal_static(__FUNCTION__); - - if (!isset($info[$entity_type])) { - $info[$entity_type] = module_invoke_all('field_ui_view_modes_tabs', $entity_type); - // Filter out inactive modes. - $entity_info = entity_get_info($entity_type); - foreach ($info[$entity_type] as $tab => $values) { - $modes = array(); - foreach ($info[$entity_type][$tab]['view modes'] as $mode) { - if (isset($entity_info['view modes'][$mode])) { - $modes[] = $mode; - } - } - if ($modes) { - $info[$entity_type][$tab]['view modes'] = $modes; + // Then, determine access according to the $access parameter. This duplicates + // part of _menu_check_access(). + if ($visibility) { + // Grab the variable 'access arguments' part. + $args = array_slice(func_get_args(), 4); + $callback = empty($access_callback) ? 0 : trim($access_callback); + if (is_numeric($callback)) { + return (bool) $callback; + } + else { + // As call_user_func_array() is quite slow and user_access is a very + // common callback, it is worth making a special case for it. + if ($access_callback == 'user_access') { + return (count($args) == 1) ? user_access($args[0]) : user_access($args[0], $args[1]); } - else { - unset($info[$entity_type][$tab]); + elseif (function_exists($access_callback)) { + return call_user_func_array($access_callback, $args); } } } - if ($tab_selector) { - return isset($info[$entity_type][$tab_selector]) ? $info[$entity_type][$tab_selector]['view modes'] : array(); - } - return $info[$entity_type]; } /** - * Implements hook_field_ui_view_modes_tabs() on behalf of other core modules. + * Implements hook_theme(). */ -function field_ui_field_ui_view_modes_tabs() { - $modes = array( - 'basic' => array( - 'title' => t('Basic'), - 'view modes' => array('teaser', 'full'), - ), - 'rss' => array( - 'title' => t('RSS'), - 'view modes' => array('rss'), +function field_ui_theme() { + return array( + 'field_ui_display_overview_table' => array( + 'render element' => 'elements', + 'file' => 'field_ui.admin.inc', + 'template' => 'field_ui-display-overview-table', ), - 'print' => array( - 'title' => t('Print'), - 'view modes' => array('print'), + 'field_ui_table' => array( + 'render element' => 'elements', ), - 'search' => array( - 'title' => t('Search'), - 'view modes' => array('search_index', 'search_result'), + ); +} + +/** + * Implements hook_element_info(). + */ +function field_ui_element_info() { + return array( + 'table' => array( + '#theme' => 'field_ui_table', ), ); - return $modes; } /** @@ -324,31 +306,6 @@ function field_ui_field_attach_create_bundle($entity_type, $bundle) { variable_set('menu_rebuild_needed', TRUE); } -/** - * Implements hook_field_attach_rename_bundle(). - */ -function field_ui_field_attach_rename_bundle($entity_type, $bundle_old, $bundle_new) { - if ($bundle_old !== $bundle_new) { - $extra_weights = variable_get('field_extra_weights', array()); - if (isset($info[$entity_type][$bundle_old])) { - $extra_weights[$entity_type][$bundle_new] = $extra_weights[$entity_type][$bundle_old]; - unset($extra_weights[$entity_type][$bundle_old]); - variable_set('field_extra_weights', $extra_weights); - } - } -} - -/** - * Implements hook_field_attach_delete_bundle(). - */ -function field_ui_field_attach_delete_bundle($entity_type, $bundle) { - $extra_weights = variable_get('field_extra_weights', array()); - if (isset($extra_weights[$entity_type][$bundle])) { - unset($extra_weights[$entity_type][$bundle]); - variable_set('field_extra_weights', $extra_weights); - } -} - /** * Helper function to create the right administration path for a bundle. */ diff --git a/modules/field_ui/field_ui.test b/modules/field_ui/field_ui.test index 337dba1b..1fb5e48b 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.16 2010/05/18 18:30:49 dries Exp $ +// $Id: field_ui.test,v 1.18 2010/06/27 18:05:54 webchick Exp $ /** * @file @@ -34,7 +34,8 @@ class FieldUITestCase extends DrupalWebTestCase { // Create random field name. $this->field_label = $this->randomName(8); - $this->field_name = 'field_' . strtolower($this->randomName(8)); + $this->field_name_input = strtolower($this->randomName(8)); + $this->field_name = 'field_'. $this->field_name_input; } /** @@ -85,7 +86,7 @@ class FieldUITestCase extends DrupalWebTestCase { // Create a test field. $edit = array( '_add_new_field[label]' => $this->field_label, - '_add_new_field[field_name]' => $this->field_name, + '_add_new_field[field_name]' => $this->field_name_input, ); $this->fieldUIAddNewField('admin/structure/types/manage/' . $this->hyphen_type, $edit); @@ -289,7 +290,7 @@ class FieldUITestCase extends DrupalWebTestCase { // 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.')); + $this->assertFieldByXPath('//table[@id="field-overview"]//td[1]', $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. @@ -338,7 +339,7 @@ class FieldUITestCase extends DrupalWebTestCase { $this->assertRaw(t('Saved %label configuration.', array('%label' => $label)), t('Redirected to "Manage fields" page.')); // Check that the field appears in the overview form. - $this->assertFieldByXPath('//table[@id="field-overview"]//span[@class="label-field"]', $label, t('Field was created and appears in the overview page.')); + $this->assertFieldByXPath('//table[@id="field-overview"]//td[1]', $label, t('Field was created and appears in the overview page.')); } /** @@ -369,7 +370,7 @@ class FieldUITestCase extends DrupalWebTestCase { $this->assertRaw(t('Saved %label configuration.', array('%label' => $label)), t('Redirected to "Manage fields" page.')); // Check that the field appears in the overview form. - $this->assertFieldByXPath('//table[@id="field-overview"]//span[@class="label-field"]', $label, t('Field was created and appears in the overview page.')); + $this->assertFieldByXPath('//table[@id="field-overview"]//td[1]', $label, t('Field was created and appears in the overview page.')); } /** diff --git a/modules/file/file.field.inc b/modules/file/file.field.inc index 1a30a9d7..c55d3697 100644 --- a/modules/file/file.field.inc +++ b/modules/file/file.field.inc @@ -1,5 +1,5 @@ <?php -// $Id: file.field.inc,v 1.26 2010/04/13 15:23:03 dries Exp $ +// $Id: file.field.inc,v 1.28 2010/07/02 12:37:57 dries Exp $ /** * @file @@ -122,9 +122,12 @@ function file_field_instance_settings_form($field, $instance) { '#type' => 'textfield', '#title' => t('Allowed file extensions'), '#default_value' => $extensions, - '#description' => t('Separate extensions with a space or comma and do not include the leading dot. Leaving this blank will allow users to upload a file with any extension.'), + '#description' => t('Separate extensions with a space or comma and do not include the leading dot.'), '#element_validate' => array('_file_generic_settings_extensions'), '#weight' => 1, + // By making this field required, we prevent a potential security issue + // that would allow files of any type to be uploaded. + '#required' => TRUE, ); $form['max_filesize'] = array( @@ -764,32 +767,45 @@ function theme_file_widget_multiple($variables) { continue; } - // Render all the buttons in the field as an "operation". - $operations = ''; + // Delay rendering of the buttons, so that they can be rendered later in the + // "operations" column. + $operations_elements = array(); foreach (element_children($element[$key]) as $sub_key) { if (isset($element[$key][$sub_key]['#type']) && $element[$key][$sub_key]['#type'] == 'submit') { - $operations .= drupal_render($element[$key][$sub_key]); + hide($element[$key][$sub_key]); + $operations_elements[] = &$element[$key][$sub_key]; } } - // Render the "Display" option in its own own column. + // Delay rendering of the "Display" option and the weight selector, so that + // each can be rendered later in its own column. + if ($element['#display_field']) { + hide($element[$key]['display']); + } + hide($element[$key]['_weight']); + + // Render everything else together in a column, without the normal wrappers. + $element[$key]['#theme_wrappers'] = array(); + $information = drupal_render($element[$key]); + + // Render the previously hidden elements, using render() instead of + // drupal_render(), to undo the earlier hide(). + $operations = ''; + foreach ($operations_elements as $operation_element) { + $operations .= render($operation_element); + } $display = ''; if ($element['#display_field']) { unset($element[$key]['display']['#title']); $display = array( - 'data' => drupal_render($element[$key]['display']), + 'data' => render($element[$key]['display']), 'class' => array('checkbox'), ); } - - // Render the weight in its own column. $element[$key]['_weight']['#attributes']['class'] = array($weight_class); - $weight = drupal_render($element[$key]['_weight']); - - // Render everything else together in a column, without the normal wrappers. - $element[$key]['#theme_wrappers'] = array(); - $information = drupal_render($element[$key]); + $weight = render($element[$key]['_weight']); + // Arrange the row with all of the rendered columns. $row = array(); $row[] = $information; if ($element['#display_field']) { diff --git a/modules/file/file.info b/modules/file/file.info index 8e37c4ca..ec7ef4f4 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/file/file.module b/modules/file/file.module index 738aa5d9..4075d652 100644 --- a/modules/file/file.module +++ b/modules/file/file.module @@ -1,5 +1,5 @@ <?php -// $Id: file.module,v 1.28 2010/05/09 19:34:26 dries Exp $ +// $Id: file.module,v 1.31 2010/07/02 12:37:57 dries Exp $ /** * @file @@ -65,6 +65,7 @@ function file_element_info() { '#process' => array('file_managed_file_process'), '#value_callback' => 'file_managed_file_value', '#element_validate' => array('file_managed_file_validate'), + '#pre_render' => array('file_managed_file_pre_render'), '#theme' => 'file_managed_file', '#theme_wrappers' => array('form_element'), '#progress_indicator' => 'throbber', @@ -384,25 +385,6 @@ function file_managed_file_process($element, &$form_state, $form) { '#weight' => -5, ); - // @todo It is not good to call these private functions. This should be - // refactored so that the file deletion happens during a submit handler, - // and form changes affected by that (such as toggling the upload and remove - // buttons) happens during the 2nd run of this function that is triggered by - // a form rebuild: http://drupal.org/node/736298. - if (_form_button_was_clicked($element['remove_button'], $form_state) || _form_element_triggered_scripted_submission($element['remove_button'], $form_state)) { - // If it's a temporary file we can safely remove it immediately, otherwise - // it's up to the implementing module to clean up files that are in use. - if ($element['#file'] && $element['#file']->status == 0) { - file_delete($element['#file']); - } - $element['#file'] = FALSE; - $fid = 0; - } - - // Set access on the buttons. - $element['upload_button']['#access'] = empty($fid); - $element['remove_button']['#access'] = !empty($fid); - $element['fid'] = array( '#type' => 'hidden', '#value' => $fid, @@ -436,7 +418,6 @@ function file_managed_file_process($element, &$form_state, $form) { '#name' => 'files[' . implode('_', $element['#parents']) . ']', '#type' => 'file', '#size' => 22, - '#access' => empty($fid), '#theme_wrappers' => array(), '#weight' => -10, ); @@ -565,13 +546,50 @@ function file_managed_file_validate(&$element, &$form_state) { } /** - * Submit handler for non-JavaScript uploads. + * Submit handler for upload and remove buttons of managed_file elements. */ function file_managed_file_submit($form, &$form_state) { - // Do not redirect and leave the page after uploading a file. This keeps - // all the current form values in place. The file is saved by the - // #value_callback on the form element. - $form_state['redirect'] = FALSE; + // Determine whether it was the upload or the remove button that was clicked, + // and set $element to the managed_file element that contains that button. + $parents = $form_state['triggering_element']['#array_parents']; + $button_key = array_pop($parents); + $element = $form; + foreach ($parents as $parent) { + $element = $element[$parent]; + } + + // No action is needed here for the upload button, because all file uploads on + // the form are processed by file_managed_file_value() regardless of which + // button was clicked. Action is needed here for the remove button, because we + // only remove a file in response to its remove button being clicked. + if ($button_key == 'remove_button') { + // If it's a temporary file we can safely remove it immediately, otherwise + // it's up to the implementing module to clean up files that are in use. + if ($element['#file'] && $element['#file']->status == 0) { + file_delete($element['#file']); + } + // Update both $form_state['values'] and $form_state['input'] to reflect + // that the file has been removed, so that the form is rebuilt correctly. + // $form_state['values'] must be updated in case additional submit handlers + // run, and for form building functions that run during the rebuild, such as + // when the managed_file element is part of a field widget. + // $form_state['input'] must be updated so that file_managed_file_value() + // has correct information during the rebuild. The Form API provides no + // equivalent of form_set_value() for updating $form_state['input'], so + // inline that implementation with the same logic that form_set_value() + // uses. + $values_element = $element['#extended'] ? $element['fid'] : $element; + form_set_value($values_element, NULL, $form_state); + _form_set_value($form_state['input'], $values_element, $values_element['#parents'], NULL); + } + + // Set the form to rebuild so that $form is correctly updated in response to + // processing the file removal. Since this function did not change $form_state + // if the upload button was clicked, a rebuild isn't necessary in that + // situation and setting $form_state['redirect'] to FALSE would suffice. + // However, we choose to always rebuild, to keep the form processing workflow + // consistent between the two buttons. + $form_state['rebuild'] = TRUE; } /** @@ -625,6 +643,38 @@ function theme_file_managed_file($variables) { return $output; } +/** + * #pre_render callback to hide display of the upload or remove controls. + * + * Upload controls are hidden when a file is already uploaded. Remove controls + * are hidden when there is no file attached. Controls are hidden here instead + * of in file_managed_file_process(), because #access for these buttons depends + * on the managed_file element's #value. See the documentation of form_builder() + * for more detailed information about the relationship between #process, + * #value, and #access. + * + * Because #access is set here, it affects display only and does not prevent + * JavaScript or other untrusted code from submitting the form as though access + * were enabled. The form processing functions for these elements should not + * assume that the buttons can't be "clicked" just because they are not + * displayed. + * + * @see file_managed_file_process() + * @see form_builder() + */ +function file_managed_file_pre_render($element) { + // If we already have a file, we don't want to show the upload controls. + if (!empty($element['#value']['fid'])) { + $element['upload']['#access'] = FALSE; + $element['upload_button']['#access'] = FALSE; + } + // If we don't already have a file, there is nothing to remove. + else { + $element['remove_button']['#access'] = FALSE; + } + return $element; +} + /** * Returns HTML for a link to a file. * @@ -969,8 +1019,11 @@ function file_get_file_references($file, $field = NULL, $age = FIELD_LOAD_REVISI foreach ($fields as $field_name => $file_field) { if ((empty($field_type) || $field['type'] == $field_type) && !isset($references[$field_name])) { // Get each time this file is used within a field. - $cursor = 0; - $references[$field_name] = field_attach_query($file_field['id'], array(array('fid', $file->fid)), array('limit' => FIELD_QUERY_NO_LIMIT, 'cursor' => &$cursor, 'age'=> $age)); + $query = new EntityFieldQuery(); + $query + ->fieldCondition($file_field, 'fid', $file->fid) + ->age($age); + $references[$field_name] = $query->execute(); } } diff --git a/modules/file/tests/file.test b/modules/file/tests/file.test index 8e595588..51e3b2df 100644 --- a/modules/file/tests/file.test +++ b/modules/file/tests/file.test @@ -1,5 +1,5 @@ <?php -// $Id: file.test,v 1.14 2010/04/20 09:48:06 webchick Exp $ +// $Id: file.test,v 1.18 2010/07/02 12:37:57 dries Exp $ /** * @file @@ -14,7 +14,7 @@ class FileFieldTestCase extends DrupalWebTestCase { function setUp() { parent::setUp('file'); - $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer content types', 'administer nodes', 'create article content', 'edit any article content', 'delete any article content')); + $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer users', 'administer content types', 'administer nodes', 'bypass node access')); $this->drupalLogin($this->admin_user); } @@ -55,11 +55,31 @@ class FileFieldTestCase extends DrupalWebTestCase { $field['settings'] = array_merge($field['settings'], $field_settings); field_create_field($field); + $this->attachFileField($name, 'node', $type_name, $instance_settings, $widget_settings); + } + + /** + * Attach a file field to an entity. + * + * @param $name + * The name of the new field (all lowercase), exclude the "field_" prefix. + * @param $entity_type + * The entity type this field will be added to. + * @param $bundle + * The bundle this field will be added to. + * @param $field_settings + * A list of field settings that will be added to the defaults. + * @param $instance_settings + * A list of instance settings that will be added to the instance defaults. + * @param $widget_settings + * A list of widget settings that will be added to the widget defaults. + */ + function attachFileField($name, $entity_type, $bundle, $instance_settings = array(), $widget_settings = array()) { $instance = array( - 'field_name' => $field['field_name'], - 'entity_type' => 'node', + 'field_name' => $name, 'label' => $name, - 'bundle' => $type_name, + 'entity_type' => $entity_type, + 'bundle' => $bundle, 'required' => !empty($instance_settings['required']), 'settings' => array(), 'widget' => array( @@ -182,6 +202,84 @@ class FileFieldTestCase extends DrupalWebTestCase { } } + +/** + * Test class to test file field upload and remove buttons, with and without AJAX. + */ +class FileFieldWidgetTestCase extends FileFieldTestCase { + public static function getInfo() { + return array( + 'name' => 'File field widget test', + 'description' => 'Test upload and remove buttons, with and without AJAX.', + 'group' => 'File', + ); + } + + /** + * Tests upload and remove buttons, with and without AJAX. + * + * @todo This function currently only tests the "remove" button of a single- + * valued field. Tests should be added for the "upload" button and for each + * button of a multi-valued field. Tests involving multiple AJAX steps on + * the same page will become easier after http://drupal.org/node/789186 + * lands. Testing the "upload" button in AJAX context requires more + * investigation into how jQuery uploads files, so that drupalPostAJAX() can + * emulate that correctly. + */ + function testWidget() { + // Use 'page' instead of 'article', so that the 'article' image field does + // not conflict with this test. If in the future the 'page' type gets its + // own default file or image field, this test can be made more robust by + // using a custom node type. + $type_name = 'page'; + $field_name = strtolower($this->randomName()); + $this->createFileField($field_name, $type_name); + $field = field_info_field($field_name); + $instance = field_info_instance('node', $field_name, $type_name); + + $test_file = $this->getTestFile('text'); + + foreach (array('nojs', 'js') as $type) { + // Create a new node with the uploaded file and ensure it got uploaded + // successfully. + $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); + $node = node_load($nid, NULL, TRUE); + $node_file = (object) $node->{$field_name}[LANGUAGE_NONE][0]; + $this->assertFileExists($node_file, t('New file saved to disk on node creation.')); + + // Ensure the edit page has a remove button instead of an upload button. + $this->drupalGet("node/$nid/edit"); + $this->assertNoFieldByXPath('//input[@type="submit"]', t('Upload'), t('Node with file does not display the "Upload" button.')); + $this->assertFieldByXpath('//input[@type="submit"]', t('Remove'), t('Node with file displays the "Remove" button.')); + + // "Click" the remove button (emulating either a nojs or js submission). + switch ($type) { + case 'nojs': + $this->drupalPost(NULL, array(), t('Remove')); + break; + case 'js': + // @todo This can be simplified after http://drupal.org/node/789186 + // lands. + preg_match('/jQuery\.extend\(Drupal\.settings, (.*?)\);/', $this->content, $matches); + $settings = drupal_json_decode($matches[1]); + $button = $this->xpath('//input[@type="submit" and @value="' . t('Remove') . '"]'); + $button_id = (string) $button[0]['id']; + $this->drupalPostAJAX(NULL, array(), array((string) $button[0]['name'] => (string) $button[0]['value']), $settings['ajax'][$button_id]['url'], array(), array(), NULL, $settings['ajax'][$button_id]); + break; + } + + // Ensure the page now has an upload button instead of a remove button. + $this->assertNoFieldByXPath('//input[@type="submit"]', t('Remove'), t('After clicking the "Remove" button, it is no longer displayed.')); + $this->assertFieldByXpath('//input[@type="submit"]', t('Upload'), t('After clicking the "Remove" button, the "Upload" button is displayed.')); + + // Save the node and ensure it does not have the file. + $this->drupalPost(NULL, array(), t('Save')); + $node = node_load($nid, NULL, TRUE); + $this->assertTrue(empty($node->{$field_name}[LANGUAGE_NONE][0]['fid']), t('File was successfully removed from the node.')); + } + } +} + /** * Test class to test file handling with node revisions. */ @@ -212,6 +310,9 @@ class FileFieldRevisionTestCase extends FileFieldTestCase { $field = field_info_field($field_name); $instance = field_info_instance('node', $field_name, $type_name); + // Attach the same fields to users. + $this->attachFileField($field_name, 'user', 'user'); + $test_file = $this->getTestFile('text'); // Create a new node with the uploaded file. @@ -265,8 +366,21 @@ class FileFieldRevisionTestCase extends FileFieldTestCase { $this->assertFileEntryExists($node_file_r3, t('Second file entry is still available after deleting second revision, since it is being used by the third revision.')); $this->assertFileIsPermanent($node_file_r3, t('Second file entry is still permanent after deleting second revision, since it is being used by the third revision.')); - // Delete the third revision and check that the file is deleted also. + // Attach the second file to a user. + $user = $this->drupalCreateUser(); + $edit = array(); + $edit[$field_name][LANGUAGE_NONE][0] = (array) $node_file_r3; + user_save($user, $edit); + $this->drupalGet('user/' . $user->uid . '/edit'); + + // Delete the third revision and check that the file is not deleted yet. $this->drupalPost('node/' . $nid . '/revisions/' . $node_vid_r3 . '/delete', array(), t('Delete')); + $this->assertFileExists($node_file_r3, t('Second file is still available after deleting third revision, since it is being used by the user.')); + $this->assertFileEntryExists($node_file_r3, t('Second file entry is still available after deleting third revision, since it is being used by the user.')); + $this->assertFileIsPermanent($node_file_r3, t('Second file entry is still permanent after deleting third revision, since it is being used by the user.')); + + // Delete the user and check that the file is also deleted. + user_delete($user->uid); // TODO: This seems like a bug in File API. Clearing the stat cache should // not be necessary here. The file really is deleted, but stream wrappers // doesn't seem to think so unless we clear the PHP file stat() cache. @@ -455,8 +569,8 @@ class FileFieldValidateTestCase extends FileFieldTestCase { $field = field_info_field($field_name); $instance = field_info_instance('node', $field_name, $type_name); - // Get the test file (a GIF image). $test_file = $this->getTestFile('image'); + list(, $test_file_extension) = explode('.', $test_file->filename); // Disable extension checking. $this->updateFileField($field_name, $type_name, array('file_extensions' => '')); @@ -477,7 +591,7 @@ class FileFieldValidateTestCase extends FileFieldTestCase { $this->assertRaw($error_message, t('Node save failed when file uploaded with the wrong extension.')); // Enable extension checking for text and image files. - $this->updateFileField($field_name, $type_name, array('file_extensions' => 'txt gif')); + $this->updateFileField($field_name, $type_name, array('file_extensions' => "txt $test_file_extension")); // Check that the file can be uploaded with extension checking. $nid = $this->uploadNodeFile($test_file, $field_name, $type_name); @@ -611,7 +725,6 @@ class FileTokenReplaceTestCase extends FileFieldTestCase { // Generate and test sanitized tokens. $tests = array(); $tests['[file:fid]'] = $file->fid; - $tests['[file:uid]'] = $file->uid; $tests['[file:name]'] = check_plain($file->filename); $tests['[file:description]'] = filter_xss($file->description); $tests['[file:path]'] = filter_xss($file->uri); @@ -621,7 +734,7 @@ class FileTokenReplaceTestCase extends FileFieldTestCase { $tests['[file:timestamp]'] = format_date($file->timestamp, 'medium', '', NULL, $language->language); $tests['[file:timestamp:short]'] = format_date($file->timestamp, 'short', '', NULL, $language->language); $tests['[file:owner]'] = $this->admin_user->name; - $tests['[file:owner:uid]'] = $this->admin_user->uid; + $tests['[file:owner:uid]'] = $file->uid; // Test to make sure that we generated something for each token. $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.')); diff --git a/modules/file/tests/file_module_test.info b/modules/file/tests/file_module_test.info index 5a9f9fbd..cf131ee8 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/filter/filter.admin.inc b/modules/filter/filter.admin.inc index 59a1200a..0e5a870e 100644 --- a/modules/filter/filter.admin.inc +++ b/modules/filter/filter.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: filter.admin.inc,v 1.61 2010/04/24 14:49:13 dries Exp $ +// $Id: filter.admin.inc,v 1.63 2010/06/26 01:55:29 dries Exp $ /** * @file @@ -153,7 +153,7 @@ function filter_admin_format_form($form, &$form_state, $format) { foreach ($filter_info as $name => $filter) { // Create an empty filter object for new/unconfigured filters. if (!isset($filters[$name])) { - $filters[$name] = new stdClass; + $filters[$name] = new stdClass(); $filters[$name]->status = 0; $filters[$name]->weight = $filter['weight']; $filters[$name]->settings = array(); @@ -322,12 +322,25 @@ function filter_admin_format_form_submit($form, &$form_state) { function filter_admin_delete($form, &$form_state, $format) { $form['#format'] = $format; + $fallback_options = array(); + foreach (filter_formats() as $id => $fallback_format) { + if ($id != $format->format) { + $fallback_options[$id] = $fallback_format->name; + } + } + $form['fallback'] = array( + '#type' => 'select', + '#title' => t('Replacement text format'), + '#options' => $fallback_options, + '#default_value' => filter_fallback_format(), + '#description' => t('Content assigned to the deleted text format will be reassigned to the chosen one.'), + ); + return confirm_form($form, t('Are you sure you want to delete the text format %format?', array('%format' => $format->name)), 'admin/config/content/formats', - t('If you have any content left in this text format, it will be switched to the %fallback text format. This action cannot be undone.', array('%fallback' => filter_fallback_format_title())), - t('Delete'), - t('Cancel') + NULL, + t('Delete') ); } @@ -336,7 +349,7 @@ function filter_admin_delete($form, &$form_state, $format) { */ function filter_admin_delete_submit($form, &$form_state) { $format = $form['#format']; - filter_format_delete($format); + filter_format_delete($format, $form_state['values']['fallback']); drupal_set_message(t('Deleted text format %format.', array('%format' => $format->name))); $form_state['redirect'] = 'admin/config/content/formats'; diff --git a/modules/filter/filter.api.php b/modules/filter/filter.api.php index a9e95771..95d79ef7 100644 --- a/modules/filter/filter.api.php +++ b/modules/filter/filter.api.php @@ -1,5 +1,5 @@ <?php -// $Id: filter.api.php,v 1.19 2010/03/26 17:14:45 dries Exp $ +// $Id: filter.api.php,v 1.20 2010/06/26 01:55:29 dries Exp $ /** * @file @@ -235,15 +235,15 @@ function hook_filter_format_update($format) { /** * Perform actions when a text format has been deleted. * - * It is recommended for modules to implement this hook, when they store - * references to text formats to replace existing references to the deleted - * text format with the fallback format. + * All modules storing references to text formats have to implement this hook. + * + * When a text format is deleted, all content that previously had that format + * assigned needs to be switched to the passed fallback format. * * @param $format * The format object of the format being deleted. * @param $fallback - * The format object of the site's fallback format, which is always available - * to all users. + * The format object of the format to use as replacement. * * @see hook_filter_format_insert() * @see hook_filter_format_update() diff --git a/modules/filter/filter.info b/modules/filter/filter.info index 960b0d53..d2b3c460 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/filter/filter.install b/modules/filter/filter.install index 03e370d4..ad4e65b9 100644 --- a/modules/filter/filter.install +++ b/modules/filter/filter.install @@ -1,5 +1,5 @@ <?php -// $Id: filter.install,v 1.37 2010/05/01 08:12:23 dries Exp $ +// $Id: filter.install,v 1.42 2010/07/01 15:14:27 webchick Exp $ /** * @file @@ -46,7 +46,7 @@ function filter_schema() { 'description' => 'Filter enabled status. (1 = enabled, 0 = disabled)', ), 'settings' => array( - 'type' => 'text', + 'type' => 'blob', 'not null' => FALSE, 'size' => 'big', 'serialize' => TRUE, @@ -143,15 +143,14 @@ function filter_install() { * Implements hook_update_dependencies(). */ function filter_update_dependencies() { - // Filter update 7005 migrates block data and therefore needs to run after - // the {block_custom} table is properly set up. + // Filter update 7007 migrates permissions and therefore needs to run after + // the {role} table is properly set up. $dependencies['filter'][7005] = array( - 'taxonomy' => 7002, + 'user' => 7007, ); return $dependencies; } - /** * @defgroup updates-6.x-to-7.x Filter updates from 6.x to 7.x * @{ @@ -249,6 +248,7 @@ function filter_update_7003() { $query->fields('d6_upgrade_filter', array('format', 'weight')); $query->condition('module', $module); $query->condition('delta', $old_delta); + $query->distinct(); $result = $query->execute(); foreach ($result as $record) { db_insert('filter') @@ -320,7 +320,6 @@ function filter_update_7004() { * in cases that used to rely on a single site-wide default. */ function filter_update_7005() { - // Move role data from the filter system to the user permission system. $all_roles = array_keys(user_roles()); $default_format = variable_get('filter_default_format', 1); @@ -379,22 +378,6 @@ function filter_update_7005() { ->condition('format', $default_format) ->execute(); - // It was previously possible for a value of "0" to be stored in database - // tables to indicate that a particular piece of text should be filtered - // using the default text format. Therefore, we have to convert all such - // instances (in Drupal core) to explicitly use the appropriate format. - // Note that the update of the node body field is handled separately, in - // node_update_7006(), as is the update of the comment body field, in - // comment_update_7013(). - foreach (array('block_custom') as $table) { - if (db_table_exists($table)) { - db_update($table) - ->fields(array('format' => $default_format)) - ->condition('format', 0) - ->execute(); - } - } - // We do not delete the 'filter_default_format' variable, since other modules // may need it in their update functions. // @todo This variable can be deleted in Drupal 8. @@ -421,6 +404,24 @@ function filter_update_7008() { } } +/** + * Converts fields that store serialized variables from text to blob. + */ +function filter_update_7009() { + $spec = array( + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big', + 'serialize' => TRUE, + 'description' => 'A serialized array of name value pairs that store the filter settings for the specific format.', + ); + db_change_field('filter', 'settings', 'settings', $spec); + + $schema = system_schema_cache_7054(); + db_drop_table('cache_filter'); + db_create_table('cache_filter', $schema); +} + /** * @} End of "defgroup updates-6.x-to-7.x" * The next series of updates should start at 8000. diff --git a/modules/filter/filter.module b/modules/filter/filter.module index 16e13fa7..92613c7e 100644 --- a/modules/filter/filter.module +++ b/modules/filter/filter.module @@ -1,5 +1,5 @@ <?php -// $Id: filter.module,v 1.331 2010/05/13 07:53:02 dries Exp $ +// $Id: filter.module,v 1.335 2010/06/29 00:48:14 dries Exp $ /** * @file @@ -260,8 +260,12 @@ function filter_format_save(&$format) { * * @param $format * The text format object to be deleted. + * @param $fallback_id + * (optional) The ID of the text format to use to reassign content that is + * currently using $format. If omitted, the currently stored + * filter_fallback_format() is used. */ -function filter_format_delete($format) { +function filter_format_delete($format, $fallback_id = NULL) { db_delete('filter_format') ->condition('format', $format->format) ->execute(); @@ -270,7 +274,10 @@ function filter_format_delete($format) { ->execute(); // Allow modules to react on text format deletion. - $fallback = filter_format_load(filter_fallback_format()); + if (empty($fallback_id)) { + $fallback_id = filter_fallback_format(); + } + $fallback = filter_format_load($fallback_id); module_invoke_all('filter_format_delete', $format, $fallback); filter_formats_reset(); @@ -488,11 +495,6 @@ function filter_default_format($account = NULL) { * format is initialized to output plain text. Installation profiles and site * administrators have the freedom to configure it further. * - * When a text format is deleted, all content that previously had that format - * assigned should be switched to the fallback format. To facilitate this, - * Drupal passes in the fallback format object as one of the parameters of - * hook_filter_format_delete(). - * * Note that the fallback format is completely distinct from the default * format, which differs per user and is simply the first format which that * user has access to. The default and fallback formats are only guaranteed to @@ -636,6 +638,10 @@ function filter_list_format($format_id) { $filter->title = $filter_info[$name]['title']; // Unpack stored filter settings. $filter->settings = (isset($filter->settings) ? unserialize($filter->settings) : array()); + // Merge in default settings. + if (isset($filter_info[$name]['default settings'])) { + $filter->settings += $filter_info[$name]['default settings']; + } $format_filters[$name] = $filter; } @@ -1190,7 +1196,7 @@ function _filter_html_tips($filter, $format, $long = FALSE) { $output .= '<p>' . t('This site allows HTML content. While learning all of HTML may feel intimidating, learning how to use a very small number of the most basic HTML "tags" is very easy. This table provides examples for each tag that is enabled on this site.') . '</p>'; $output .= '<p>' . t('For more information see W3C\'s <a href="@html-specifications">HTML Specifications</a> or use your favorite search engine to find other sites that explain HTML.', array('@html-specifications' => 'http://www.w3.org/TR/html/')) . '</p>'; $tips = array( - 'a' => array(t('Anchors are used to make links to other pages.'), '<a href="' . $base_url . '">' . variable_get('site_name', 'Drupal') . '</a>'), + 'a' => array(t('Anchors are used to make links to other pages.'), '<a href="' . $base_url . '">' . check_plain(variable_get('site_name', 'Drupal')) . '</a>'), 'br' => array(t('By default line break tags are automatically added, so use this tag to add additional ones. Use of this tag is different because it is not used with an open/close pair like all the others. Use the extra " /" inside the tag to maintain XHTML 1.0 compatibility'), t('Text with <br />line break')), 'p' => array(t('By default paragraph tags are automatically added, so use this tag to add additional ones.'), '<p>' . t('Paragraph one.') . '</p> <p>' . t('Paragraph two.') . '</p>'), 'strong' => array(t('Strong'), '<strong>' . t('Strong') . '</strong>'), @@ -1314,7 +1320,7 @@ function _filter_url_parse_full_links($match) { $match[2] = decode_entities($match[2]); $caption = check_plain(_filter_url_trim($match[2])); $match[2] = check_url($match[2]); - return $match[1] . '<a href="' . $match[2] . '" title="' . $match[2] . '">' . $caption . '</a>' . $match[5]; + return $match[1] . '<a href="' . $match[2] . '">' . $caption . '</a>' . $match[5]; } /** @@ -1324,7 +1330,7 @@ function _filter_url_parse_partial_links($match) { $match[2] = decode_entities($match[2]); $caption = check_plain(_filter_url_trim($match[2])); $match[2] = check_plain($match[2]); - return $match[1] . '<a href="http://' . $match[2] . '" title="' . $match[2] . '">' . $caption . '</a>' . $match[3]; + return $match[1] . '<a href="http://' . $match[2] . '">' . $caption . '</a>' . $match[3]; } /** diff --git a/modules/filter/filter.test b/modules/filter/filter.test index 5c265850..a6a1555c 100644 --- a/modules/filter/filter.test +++ b/modules/filter/filter.test @@ -1,5 +1,5 @@ <?php -// $Id: filter.test,v 1.66 2010/04/11 18:33:44 dries Exp $ +// $Id: filter.test,v 1.69 2010/07/01 19:41:18 dries Exp $ /** * Tests for text format and filter CRUD operations. @@ -22,14 +22,14 @@ class FilterCRUDTestCase extends DrupalWebTestCase { */ function testTextFormatCRUD() { // Add a text format with minimum data only. - $format = new stdClass; + $format = new stdClass(); $format->name = 'Empty format'; filter_format_save($format); $this->verifyTextFormat($format); $this->verifyFilters($format); // Add another text format specifying all possible properties. - $format = new stdClass; + $format = new stdClass(); $format->name = 'Custom format'; $format->filters = array( 'filter_url' => array( @@ -877,7 +877,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase { */ function testFilter() { // Setup dummy filter object. - $filter = new stdClass; + $filter = new stdClass(); $filter->settings = array( 'allowed_html' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>', 'filter_html_help' => 1, @@ -912,6 +912,9 @@ class FilterUnitTestCase extends DrupalUnitTestCase { $f = _filter_html('<p onerror="alert(0);" />', $filter); $this->assertNoNormalized($f, 'onerror', t('HTML filter should remove on* attributes on default.')); + + $f = _filter_html('<code onerror> </code>', $filter); + $this->assertNoNormalized($f, 'onerror', t('HTML filter should remove empty on* attributes on default.')); } /** @@ -919,7 +922,7 @@ class FilterUnitTestCase extends DrupalUnitTestCase { */ function testNoFollowFilter() { // Setup dummy filter object. - $filter = new stdClass; + $filter = new stdClass(); $filter->settings = array( 'allowed_html' => '<a>', 'filter_html_help' => 1, @@ -982,30 +985,30 @@ class FilterUnitTestCase extends DrupalUnitTestCase { */ function testUrlFilter() { // Setup dummy filter object. - $filter = new stdClass; + $filter = new stdClass(); $filter->settings = array( 'filter_url_length' => 496, ); // Converting URLs. $f = _filter_url('http://www.example.com/', $filter); - $this->assertEqual($f, '<a href="http://www.example.com/" title="http://www.example.com/">http://www.example.com/</a>', t('Converting URLs.')); + $this->assertEqual($f, '<a href="http://www.example.com/">http://www.example.com/</a>', t('Converting URLs.')); $f = _filter_url('http://www.example.com/?a=1&b=2', $filter); - $this->assertEqual($f, '<a href="http://www.example.com/?a=1&b=2" title="http://www.example.com/?a=1&b=2">http://www.example.com/?a=1&b=2</a>', t('Converting URLs -- ampersands.')); + $this->assertEqual($f, '<a href="http://www.example.com/?a=1&b=2">http://www.example.com/?a=1&b=2</a>', t('Converting URLs -- ampersands.')); $f = _filter_url('ftp://user:pass@ftp.example.com/dir1/dir2', $filter); - $this->assertEqual($f, '<a href="ftp://user:pass@ftp.example.com/dir1/dir2" title="ftp://user:pass@ftp.example.com/dir1/dir2">ftp://user:pass@ftp.example.com/dir1/dir2</a>', t('Converting URLs -- FTP scheme.')); + $this->assertEqual($f, '<a href="ftp://user:pass@ftp.example.com/dir1/dir2">ftp://user:pass@ftp.example.com/dir1/dir2</a>', t('Converting URLs -- FTP scheme.')); // Converting domain names. $f = _filter_url('www.example.com', $filter); - $this->assertEqual($f, '<a href="http://www.example.com" title="www.example.com">www.example.com</a>', t('Converting domain names.')); + $this->assertEqual($f, '<a href="http://www.example.com">www.example.com</a>', t('Converting domain names.')); $f = _filter_url('<li>www.example.com</li>', $filter); - $this->assertEqual($f, '<li><a href="http://www.example.com" title="www.example.com">www.example.com</a></li>', t('Converting domain names -- domain in a list.')); + $this->assertEqual($f, '<li><a href="http://www.example.com">www.example.com</a></li>', t('Converting domain names -- domain in a list.')); $f = _filter_url('(www.example.com/dir?a=1&b=2#a)', $filter); - $this->assertEqual($f, '(<a href="http://www.example.com/dir?a=1&b=2#a" title="www.example.com/dir?a=1&b=2#a">www.example.com/dir?a=1&b=2#a</a>)', t('Converting domain names -- domain in parentheses.')); + $this->assertEqual($f, '(<a href="http://www.example.com/dir?a=1&b=2#a">www.example.com/dir?a=1&b=2#a</a>)', t('Converting domain names -- domain in parentheses.')); // Converting e-mail addresses. $f = _filter_url('johndoe@example.com', $filter); @@ -1049,13 +1052,13 @@ class FilterUnitTestCase extends DrupalUnitTestCase { // of a sentence, so remove the dot from the link. // @todo It can also be used at the end of a filename or a query string. $f = _filter_url('www.example.com.', $filter); - $this->assertEqual($f, '<a href="http://www.example.com" title="www.example.com">www.example.com</a>.', t('Converting URLs -- do not recognize a dot at the end of a domain name (FQDNs).')); + $this->assertEqual($f, '<a href="http://www.example.com">www.example.com</a>.', t('Converting URLs -- do not recognize a dot at the end of a domain name (FQDNs).')); $f = _filter_url('http://www.example.com.', $filter); - $this->assertEqual($f, '<a href="http://www.example.com" title="http://www.example.com">http://www.example.com</a>.', t('Converting URLs -- do not recognize a dot at the end of an URL (FQDNs).')); + $this->assertEqual($f, '<a href="http://www.example.com">http://www.example.com</a>.', t('Converting URLs -- do not recognize a dot at the end of an URL (FQDNs).')); $f = _filter_url('www.example.com/index.php?a=.', $filter); - $this->assertEqual($f, '<a href="http://www.example.com/index.php?a=" title="www.example.com/index.php?a=">www.example.com/index.php?a=</a>.', t('Converting URLs -- do forget about a dot at the end of a query string.')); + $this->assertEqual($f, '<a href="http://www.example.com/index.php?a=">www.example.com/index.php?a=</a>.', t('Converting URLs -- do forget about a dot at the end of a query string.')); } /** diff --git a/modules/forum/forum.admin.inc b/modules/forum/forum.admin.inc index 8c1c262d..cefe4063 100644 --- a/modules/forum/forum.admin.inc +++ b/modules/forum/forum.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: forum.admin.inc,v 1.33 2010/04/24 14:49:13 dries Exp $ +// $Id: forum.admin.inc,v 1.34 2010/06/26 21:32:20 dries Exp $ /** * @file @@ -210,25 +210,25 @@ function forum_admin_settings($form) { $number = drupal_map_assoc(array(5, 10, 15, 20, 25, 30, 35, 40, 50, 60, 80, 100, 150, 200, 250, 300, 350, 400, 500)); $form['forum_hot_topic'] = array('#type' => 'select', '#title' => t('Hot topic threshold'), - '#default_value' => 15, + '#default_value' => variable_get('forum_hot_topic', 15), '#options' => $number, '#description' => t('The number of replies a topic must have to be considered "hot".'), ); $number = drupal_map_assoc(array(10, 25, 50, 75, 100)); $form['forum_per_page'] = array('#type' => 'select', '#title' => t('Topics per page'), - '#default_value' => 25, + '#default_value' => variable_get('forum_per_page', 25), '#options' => $number, '#description' => t('Default number of forum topics displayed per page.'), ); $forder = array(1 => t('Date - newest first'), 2 => t('Date - oldest first'), 3 => t('Posts - most active first'), 4 => t('Posts - least active first')); $form['forum_order'] = array('#type' => 'radios', '#title' => t('Default order'), - '#default_value' => '1', + '#default_value' => variable_get('forum_order', 1), '#options' => $forder, '#description' => t('Default display order for topics.'), ); - return system_settings_form($form, TRUE); + return system_settings_form($form); } /** diff --git a/modules/forum/forum.info b/modules/forum/forum.info index 177f519e..c8baba56 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/forum/forum.install b/modules/forum/forum.install index 2931135e..22b8d464 100644 --- a/modules/forum/forum.install +++ b/modules/forum/forum.install @@ -1,5 +1,5 @@ <?php -// $Id: forum.install,v 1.46 2010/05/05 06:55:25 webchick Exp $ +// $Id: forum.install,v 1.48 2010/06/24 12:59:22 webchick Exp $ /** * @file @@ -65,6 +65,16 @@ function forum_enable() { 'widget' => array( 'type' => 'options_select', ), + 'display' => array( + 'default' => array( + 'type' => 'taxonomy_term_reference_link', + 'weight' => 10, + ), + 'teaser' => array( + 'type' => 'taxonomy_term_reference_link', + 'weight' => 10, + ), + ), ); field_create_instance($instance); @@ -281,5 +291,16 @@ function forum_update_7001() { ); db_create_table('forum_index', $forum_index); - db_query('INSERT INTO {forum_index} (SELECT n.nid, n.title, f.tid, n.sticky, n.created, ncs.last_comment_timestamp, ncs.comment_count FROM {node} n INNER JOIN {forum} f on n.vid = f.vid INNER JOIN {node_comment_statistics} ncs ON n.nid = ncs.nid)'); + $select = db_select('node', 'n'); + $forum_alias = $select->join('forum', 'f', 'n.vid = f.vid'); + $ncs_alias = $select->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid'); + $select + ->fields('n', array('nid', 'title', 'sticky', 'created')) + ->fields($forum_alias, array('tid')) + ->fields($ncs_alias, array('last_comment_timestamp', 'comment_count')); + + db_insert('forum_index') + ->fields(array('nid', 'title', 'sticky', 'created', 'tid', 'last_comment_timestamp', 'comment_count')) + ->from($select) + ->execute(); } diff --git a/modules/forum/forum.module b/modules/forum/forum.module index d2c72b79..eaae2744 100644 --- a/modules/forum/forum.module +++ b/modules/forum/forum.module @@ -1,5 +1,5 @@ <?php -// $Id: forum.module,v 1.565 2010/04/28 05:54:55 webchick Exp $ +// $Id: forum.module,v 1.567 2010/06/20 23:55:08 webchick Exp $ /** * @file @@ -699,8 +699,8 @@ function forum_block_view($delta = '') { function forum_block_view_pre_render($elements) { $result = $elements['#query']->execute(); if ($node_title_list = node_title_list($result)) { - $elements['forum_list'] = array('#markup' => $node_title_list); - $elements['forum_more'] = array('#markup' => theme('more_link', array('url' => url('forum'), 'title' => t('Read the latest forum topics.')))); + $elements['forum_list'] = $node_title_list; + $elements['forum_more'] = array('#theme' => 'more_link', '#url' => url('forum'), '#title' => t('Read the latest forum topics.')); } return $elements; } @@ -816,7 +816,7 @@ function forum_forum_load($tid = NULL) { $query->join('forum', 'f', 'n.vid = f.vid AND f.tid = :tid', array(':tid' => $forum->tid)); $query->join('node_comment_statistics', 'ncs', 'n.nid = ncs.nid'); $query->join('users', 'u2', 'ncs.last_comment_uid = u2.uid'); - $query->addExpression('IF (ncs.last_comment_uid != 0, u2.name, ncs.last_comment_name)', 'last_comment_name'); + $query->addExpression('CASE ncs.last_comment_uid WHEN 0 THEN ncs.last_comment_name ELSE u2.name END', 'last_comment_name'); $topic = $query ->fields('ncs', array('last_comment_timestamp', 'last_comment_uid')) @@ -902,7 +902,7 @@ function forum_get_topics($tid, $sortby, $forum_per_page) { $nids[] = $record->nid; } if ($nids) { - $result = db_query("SELECT n.title, n.nid, n.type, n.sticky, n.created, n.uid, n.comment AS comment_mode, ncs.*, f.tid AS forum_tid, u.name, IF (ncs.last_comment_uid != 0, u2.name, ncs.last_comment_name) AS last_comment_name FROM {node} n INNER JOIN {node_comment_statistics} ncs ON n.nid = ncs.nid INNER JOIN {forum} f ON n.vid = f.vid INNER JOIN {users} u ON n.uid = u.uid INNER JOIN {users} u2 ON ncs.last_comment_uid = u2.uid WHERE n.nid IN (:nids)", array(':nids' => $nids)); + $result = db_query("SELECT n.title, n.nid, n.type, n.sticky, n.created, n.uid, n.comment AS comment_mode, ncs.*, f.tid AS forum_tid, u.name, CASE ncs.last_comment_uid WHEN 0 THEN ncs.last_comment_name ELSE u2.name END AS last_comment_name FROM {node} n INNER JOIN {node_comment_statistics} ncs ON n.nid = ncs.nid INNER JOIN {forum} f ON n.vid = f.vid INNER JOIN {users} u ON n.uid = u.uid INNER JOIN {users} u2 ON ncs.last_comment_uid = u2.uid WHERE n.nid IN (:nids)", array(':nids' => $nids)); } else { $result = array(); diff --git a/modules/forum/forum.test b/modules/forum/forum.test index 7860937e..45e49481 100644 --- a/modules/forum/forum.test +++ b/modules/forum/forum.test @@ -1,5 +1,5 @@ <?php -// $Id: forum.test,v 1.56 2010/04/28 05:54:55 webchick Exp $ +// $Id: forum.test,v 1.57 2010/07/08 03:41:27 webchick Exp $ class ForumTestCase extends DrupalWebTestCase { protected $admin_user; @@ -353,9 +353,6 @@ class ForumTestCase extends DrupalWebTestCase { * The exptected HTTP response code. */ private function verifyForums($node_user, $node, $admin, $response = 200) { - $crumb = '›'; - $quote = '''; - $response2 = ($admin) ? 200 : 403; // View forum help node. @@ -382,7 +379,13 @@ class ForumTestCase extends DrupalWebTestCase { $this->drupalGet('node/' . $node->nid); $this->assertResponse(200); $this->assertTitle($node->title . ' | Drupal', t('Forum node was displayed')); - $this->assertText(t('Home ' . $crumb . ' Forums ' . $crumb . ' @container ' . $crumb . ' @forum', array('@container' => $this->container['name'], '@forum' => $this->forum['name'])), t('Breadcrumbs were displayed')); + $breadcrumb = array( + l(t('Home'), NULL), + l(t('Forums'), 'forum'), + l($this->container['name'], 'forum/' . $this->container['tid']), + l($this->forum['name'], 'forum/' . $this->forum['tid']), + ); + $this->assertRaw(theme('breadcrumb', array('breadcrumb' => $breadcrumb)), t('Breadcrumbs were displayed')); // View forum edit node. $this->drupalGet('node/' . $node->nid . '/edit'); @@ -424,18 +427,20 @@ class ForumTestCase extends DrupalWebTestCase { * A row from taxonomy_term_data table in array. */ private function verifyForumView($forum, $parent = NULL) { - $crumb = '›'; - // View forum page. $this->drupalGet('forum/' . $forum['tid']); $this->assertResponse(200); $this->assertTitle($forum['name'] . ' | Drupal', t('Forum name was displayed')); + + $breadcrumb = array( + l(t('Home'), NULL), + l(t('Forums'), 'forum'), + ); if (isset($parent)) { - $this->assertText(t('Home ' . $crumb . ' Forums ' . $crumb . ' @name', array('@name' => $parent['name'])), t('Breadcrumbs were displayed')); - } - else { - $this->assertText(t('Home ' . $crumb . ' Forums'), t('Breadcrumbs were displayed')); + $breadcrumb[] = l($parent['name'], 'forum/' . $parent['tid']); } + + $this->assertRaw(theme('breadcrumb', array('breadcrumb' => $breadcrumb)), t('Breadcrumbs were displayed')); } /** @@ -462,8 +467,6 @@ class ForumTestCase extends DrupalWebTestCase { * An array of forum node IDs. */ private function viewForumTopics($nids) { - $crumb = '›'; - for ($i = 0; $i < 2; $i++) { foreach ($nids as $nid) { $this->drupalGet('node/' . $nid); diff --git a/modules/help/help.info b/modules/help/help.info index ce75a2da..e8075b14 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/image/image.info b/modules/image/image.info index be5083cd..6c4bc710 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/image/image.install b/modules/image/image.install index 9bd4b8b9..fae977a6 100644 --- a/modules/image/image.install +++ b/modules/image/image.install @@ -1,5 +1,5 @@ <?php -// $Id: image.install,v 1.8 2010/04/09 12:07:12 dries Exp $ +// $Id: image.install,v 1.10 2010/06/25 17:47:22 dries Exp $ /** * @file @@ -86,7 +86,7 @@ function image_schema() { ), 'data' => array( 'description' => 'The configuration data for the effect.', - 'type' => 'text', + 'type' => 'blob', 'not null' => TRUE, 'size' => 'big', 'serialize' => TRUE, @@ -112,7 +112,7 @@ function image_update_7000() { if (!db_table_exists('image_styles')) { $schema = array(); - $schema['cache_image'] = drupal_get_schema_unprocessed('system', 'cache'); + $schema['cache_image'] = system_schema_cache_7054(); $schema['cache_image']['description'] = 'Cache table used to store information about image manipulations that are in-progress.'; $schema['image_styles'] = array( @@ -168,7 +168,7 @@ function image_update_7000() { ), 'data' => array( 'description' => 'The configuration data for the effect.', - 'type' => 'text', + 'type' => 'blob', 'not null' => TRUE, 'size' => 'big', 'serialize' => TRUE, @@ -183,6 +183,7 @@ function image_update_7000() { 'isid' => array('image_styles' => 'isid'), ), ); + db_create_table('cache_image', $schema['cache_image']); db_create_table('image_styles', $schema['image_styles']); db_create_table('image_effect', $schema['image_effects']); diff --git a/modules/image/image.module b/modules/image/image.module index e5bc1c62..50be71c9 100644 --- a/modules/image/image.module +++ b/modules/image/image.module @@ -1,5 +1,5 @@ <?php -// $Id: image.module,v 1.42 2010/05/01 08:12:23 dries Exp $ +// $Id: image.module,v 1.43 2010/05/31 11:42:11 dries Exp $ /** * @file @@ -135,7 +135,7 @@ function image_menu() { ); $items['admin/config/media/image-styles/edit/%image_style/effects/%image_effect'] = array( 'title' => 'Edit image effect', - 'description' => 'Edit an exiting effect within a style.', + 'description' => 'Edit an existing effect within a style.', 'load arguments' => array(5, (string) IMAGE_STORAGE_EDITABLE), 'page callback' => 'drupal_get_form', 'page arguments' => array('image_effect_form', 5, 7), @@ -145,7 +145,7 @@ function image_menu() { ); $items['admin/config/media/image-styles/edit/%image_style/effects/%image_effect/delete'] = array( 'title' => 'Delete image effect', - 'description' => 'Delete an exiting effect from a style.', + 'description' => 'Delete an existing effect from a style.', 'load arguments' => array(5, (string) IMAGE_STORAGE_EDITABLE), 'page callback' => 'drupal_get_form', 'page arguments' => array('image_effect_delete_form', 5, 7), diff --git a/modules/image/image.test b/modules/image/image.test index 625ba2b6..473d1c52 100644 --- a/modules/image/image.test +++ b/modules/image/image.test @@ -1,5 +1,5 @@ <?php -// $Id: image.test,v 1.21 2010/05/01 08:12:23 dries Exp $ +// $Id: image.test,v 1.23 2010/06/30 22:37:49 webchick Exp $ /** * @file @@ -167,7 +167,7 @@ class ImageStylesPathAndUrlUnitTest extends DrupalWebTestCase { // Create the directories for the styles. $d = $scheme . '://styles/' . $this->style_name; $status = file_prepare_directory($d, FILE_CREATE_DIRECTORY); - $this->assertNotIdentical(FALSE, $status, t('Created the directory for the generated images for the test style.' )); + $this->assertNotIdentical(FALSE, $status, t('Created the directory for the generated images for the test style.')); // Create a working copy of the file. $files = $this->drupalGetTestFiles('image'); @@ -212,8 +212,8 @@ class ImageStylesPathAndUrlUnitTest extends DrupalWebTestCase { $this->drupalGet($expected_generated_url); $this->assertEqual($actual_generated_url, $expected_generated_url, t('Got the download URL for an existing file.')); $this->assertRaw(file_get_contents($generated_uri), t('URL returns expected file.')); - $this->assertEqual($this->drupalGetHeader('Content-Type'), $image_info['mime_type'], t('Expected Content-Type was reported.')); - $this->assertEqual($this->drupalGetHeader('Content-Length'), $image_info['file_size'], t('Expected Content-Length was reported.')); + $this->assertEqual($this->drupalGetHeader('Content-Type'), $generated_image_info['mime_type'], t('Expected Content-Type was reported.')); + $this->assertEqual($this->drupalGetHeader('Content-Length'), $generated_image_info['file_size'], t('Expected Content-Length was reported.')); } } @@ -592,7 +592,7 @@ class ImageAdminStylesUnitTest extends ImageFieldTestCase { // Create an image field that uses the new style. $field_name = strtolower($this->randomName(10)); $instance = $this->createImageField($field_name, 'article'); - $instance['display']['full']['type'] = 'image__' . $style_name; + $instance['display']['default']['type'] = 'image__' . $style_name; field_update_instance($instance); // Create a new node with an image attached. @@ -662,21 +662,21 @@ class ImageFieldDisplayTestCase extends ImageFieldTestCase { // Test the image linked to file formatter. $instance = field_info_instance('node', $field_name, 'article'); - $instance['display']['full']['type'] = 'image_link_file'; + $instance['display']['default']['type'] = 'image_link_file'; field_update_instance($instance); $default_output = l(theme('image', $image_info), file_create_url($image_uri), array('html' => TRUE)); $this->drupalGet('node/' . $nid); $this->assertRaw($default_output, t('Image linked to file formatter displaying correctly on full node view.')); // Test the image linked to content formatter. - $instance['display']['full']['type'] = 'image_link_content'; + $instance['display']['default']['type'] = 'image_link_content'; field_update_instance($instance); $default_output = l(theme('image', $image_info), 'node/' . $nid, array('html' => TRUE, 'attributes' => array('class' => 'active'))); $this->drupalGet('node/' . $nid); $this->assertRaw($default_output, t('Image linked to content formatter displaying correctly on full node view.')); // Test the image style 'thumbnail' formatter. - $instance['display']['full']['type'] = 'image__thumbnail'; + $instance['display']['default']['type'] = 'image__thumbnail'; field_update_instance($instance); // Ensure the derrivative image is generated so we do not have to deal with // image style callback paths. @@ -692,10 +692,12 @@ class ImageFieldDisplayTestCase extends ImageFieldTestCase { * Tests for image field settings. */ function testImageFieldSettings() { + $test_image = current($this->drupalGetTestFiles('image')); + list(, $test_image_extension) = explode('.', $test_image->filename); $field_name = strtolower($this->randomName()); $instance_settings = array( 'alt_field' => 1, - 'file_extensions' => 'gif jpg jpeg', + 'file_extensions' => $test_image_extension, 'max_filesize' => '50 KB', 'max_resolution' => '100x100', 'min_resolution' => '10x10', @@ -709,12 +711,11 @@ class ImageFieldDisplayTestCase extends ImageFieldTestCase { $this->drupalGet('node/add/article'); $this->assertText(t('Files must be less than 50 KB.'), t('Image widget max file size is displayed on article form.')); - $this->assertText(t('Allowed file types: gif jpg jpeg.'), t('Image widget allowed file types displayed on article form.')); + $this->assertText(t('Allowed file types: ' . $test_image_extension . '.'), t('Image widget allowed file types displayed on article form.')); $this->assertText(t('Images must be between 10x10 and 100x100 pixels.'), t('Image widget allowed resolution displayed on article form.')); // We have to create the article first and then edit it because the alt // and title fields do not display until the image has been attached. - $test_image = current($this->drupalGetTestFiles('image')); $nid = $this->uploadNodeImage($test_image, $field_name, 'article'); $this->drupalGet('node/' . $nid . '/edit'); $this->assertFieldByName($field_name . '[' . LANGUAGE_NONE . '][0][alt]', '', t('Alt field displayed on article form.')); @@ -817,20 +818,33 @@ class ImageFieldValidateTestCase extends ImageFieldTestCase { */ function testResolution() { $field_name = strtolower($this->randomName()); + $min_resolution = 50; + $max_resolution = 100; $instance_settings = array( - 'max_resolution' => '100x100', - 'min_resolution' => '50x50', + 'max_resolution' => $max_resolution . 'x' . $max_resolution, + 'min_resolution' => $min_resolution . 'x' . $min_resolution, ); $this->createImageField($field_name, 'article', array(), $instance_settings); - // Use image-test.png which has a resolution of 40x20. - $test_image = current($this->drupalGetTestFiles('image', 48006)); - $nid = $this->uploadNodeImage($test_image, $field_name, 'article'); - $this->assertText(t('The specified file image-test.png could not be uploaded. The image is too small; the minimum dimensions are 50x50 pixels.'), t('Node save failed when minimum image resolution was not met.')); - - // Use image-1.png which has a resolution of 360x240. - $test_image = current($this->drupalGetTestFiles('image', 64027)); - $nid = $this->uploadNodeImage($test_image, $field_name, 'article'); + // We want a test image that is too small, and a test image that is too + // big, so cycle through test image files until we have what we need. + $image_that_is_too_big = FALSE; + $image_that_is_too_small = FALSE; + foreach ($this->drupalGetTestFiles('image') as $image) { + $info = image_get_info($image->uri); + if ($info['width'] > $max_resolution) { + $image_that_is_too_big = $image; + } + if ($info['width'] < $min_resolution) { + $image_that_is_too_small = $image; + } + if ($image_that_is_too_small && $image_that_is_too_big) { + break; + } + } + $nid = $this->uploadNodeImage($image_that_is_too_small, $field_name, 'article'); + $this->assertText(t('The specified file ' . $image_that_is_too_small->filename . ' could not be uploaded. The image is too small; the minimum dimensions are 50x50 pixels.'), t('Node save failed when minimum image resolution was not met.')); + $nid = $this->uploadNodeImage($image_that_is_too_big, $field_name, 'article'); $this->assertText(t('The image was resized to fit within the maximum allowed dimensions of 100x100 pixels.'), t('Image exceeding max resolution was properly resized.')); } } diff --git a/modules/locale/locale.admin.inc b/modules/locale/locale.admin.inc index bd7b1b95..3b79afe2 100644 --- a/modules/locale/locale.admin.inc +++ b/modules/locale/locale.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: locale.admin.inc,v 1.13 2010/05/11 10:49:37 dries Exp $ +// $Id: locale.admin.inc,v 1.16 2010/06/26 21:32:20 dries Exp $ /** * @file @@ -519,7 +519,7 @@ function _locale_languages_configure_form_language_table(&$form, $type) { ); $language_providers = $form['#language_providers']; - $enabled_providers = variable_get("locale_language_providers_enabled_$type", array()); + $enabled_providers = variable_get("language_negotiation_$type", array()); $providers_weight = variable_get("locale_language_providers_weight_$type", array()); // Add missing data to the providers lists. @@ -527,16 +527,13 @@ function _locale_languages_configure_form_language_table(&$form, $type) { if (!isset($providers_weight[$id])) { $providers_weight[$id] = language_provider_weight($provider); } - if (!isset($enabled_providers[$id])) { - $enabled_providers[$id] = FALSE; - } } // Order providers list by weight. asort($providers_weight); foreach ($providers_weight as $id => $weight) { - $enabled = $enabled_providers[$id]; + $enabled = isset($enabled_providers[$id]); $provider = $language_providers[$id]; // List the provider only if the current type is defined in its 'types' key. @@ -665,7 +662,6 @@ function locale_languages_configure_form_submit($form, &$form_state) { } language_negotiation_set($type, $negotiation); - variable_set("locale_language_providers_enabled_$type", $enabled_providers); variable_set("locale_language_providers_weight_$type", $providers_weight); } @@ -697,8 +693,6 @@ function locale_languages_configure_form_submit($form, &$form_state) { * The URL language provider configuration form. */ function locale_language_providers_url_form($form, &$form_state) { - $form = array(); - $form['locale_language_negotiation_url_part'] = array( '#title' => t('Part of the URL that determines language'), '#type' => 'radios', @@ -719,8 +713,6 @@ function locale_language_providers_url_form($form, &$form_state) { * The URL language provider configuration form. */ function locale_language_providers_session_form($form, &$form_state) { - $form = array(); - $form['locale_language_negotiation_session_param'] = array( '#title' => t('Request/session parameter'), '#type' => 'textfield', @@ -988,8 +980,9 @@ function locale_translate_import_form($form) { * Process the locale import form submission. */ function locale_translate_import_form_submit($form, &$form_state) { + $validators = array('file_validate_extensions' => array('po')); // Ensure we have the file uploaded - if ($file = file_save_upload('file')) { + if ($file = file_save_upload('file', $validators)) { // Add language, if not yet supported drupal_static_reset('language_list'); diff --git a/modules/locale/locale.info b/modules/locale/locale.info index 6558742d..8e0957ba 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/locale/locale.install b/modules/locale/locale.install index 73d35370..a398df0a 100644 --- a/modules/locale/locale.install +++ b/modules/locale/locale.install @@ -1,5 +1,5 @@ <?php -// $Id: locale.install,v 1.58 2010/05/01 08:12:23 dries Exp $ +// $Id: locale.install,v 1.61 2010/07/07 05:44:03 webchick Exp $ /** * @file @@ -56,6 +56,15 @@ function locale_update_7001() { // LANGUAGE_NEGOTIATION_PATH_DEFAULT. case 1: $negotiation = array(LOCALE_LANGUAGE_NEGOTIATION_URL); + // In Drupal 6 path prefixes are shown for the default language only when + // language negotiation is set to LANGUAGE_NEGOTIATION_PATH, while in + // Drupal 7 path prefixes are always shown if not empty. Hence we need to + // ensure that the default language has an empty prefix to avoid breaking + // the site URLs with a prefix that previously was missing. + db_update('languages') + ->fields(array('prefix' => '')) + ->condition('language', language_default()->language) + ->execute(); break; // LANGUAGE_NEGOTIATION_PATH. @@ -70,11 +79,23 @@ function locale_update_7001() { break; } - // Save new language negotiation options. + // Save the new language negotiation options. language_negotiation_set(LANGUAGE_TYPE_INTERFACE, array_flip($negotiation)); language_negotiation_set(LANGUAGE_TYPE_CONTENT, array(LOCALE_LANGUAGE_NEGOTIATION_INTERFACE => 0)); language_negotiation_set(LANGUAGE_TYPE_URL, array(LOCALE_LANGUAGE_NEGOTIATION_URL => 0)); + // Save admininstration UI settings. + $type = LANGUAGE_TYPE_INTERFACE; + $provider_weights = array_flip(array_keys(locale_language_negotiation_info())); + variable_set("locale_language_providers_weight_$type", $provider_weights); + + // Update language switcher block delta. + db_update('block') + ->fields(array('delta' => $type)) + ->condition('module', 'locale') + ->condition('delta', 0) + ->execute(); + // Unset the old language negotiation system variable. variable_del('language_negotiation'); @@ -85,7 +106,7 @@ function locale_update_7001() { * Allow longer javascript file names. */ function locale_update_7002() { - db_change_field('languages', 'javascript', 'javascript', array('type' => 'varchar', 'length' => 64, 'not null' => TRUE, 'default' => '')); + // Update moved to update_fix_d7_requirements(). } /** @@ -98,15 +119,18 @@ function locale_update_7002() { function locale_uninstall() { // Delete all JavaScript translation files. $locale_js_directory = 'public://' . variable_get('locale_js_directory', 'languages'); - $files = db_query('SELECT language, javascript FROM {languages}'); - foreach ($files as $file) { - if (!empty($file->javascript)) { - file_unmanaged_delete($locale_js_directory . '/' . $file->language . '_' . $file->javascript . '.js'); + + if (is_dir($locale_js_directory)) { + $files = db_query('SELECT language, javascript FROM {languages}'); + foreach ($files as $file) { + if (!empty($file->javascript)) { + file_unmanaged_delete($locale_js_directory . '/' . $file->language . '_' . $file->javascript . '.js'); + } + } + // Delete the JavaScript translations directory if empty. + if (!file_scan_directory($locale_js_directory, '/.*/')) { + rmdir($locale_js_directory); } - } - // Delete the JavaScript translations directory if empty. - if (!file_scan_directory($locale_js_directory, '/.*/')) { - rmdir($locale_js_directory); } // Clear variables. @@ -124,7 +148,6 @@ function locale_uninstall() { foreach (language_types() as $type) { variable_del("language_negotiation_$type"); - variable_del("locale_language_providers_enabled_$type"); variable_del("locale_language_providers_weight_$type"); } diff --git a/modules/locale/locale.test b/modules/locale/locale.test index 5ef82f81..270eecc5 100644 --- a/modules/locale/locale.test +++ b/modules/locale/locale.test @@ -1,5 +1,5 @@ <?php -// $Id: locale.test,v 1.70 2010/04/28 05:12:43 webchick Exp $ +// $Id: locale.test,v 1.73 2010/06/26 19:55:47 dries Exp $ /** * @file @@ -758,7 +758,7 @@ class LocaleImportFunctionalTest extends DrupalWebTestCase { * Additional options to pass to the translation import form. */ function importPoFile($contents, array $options = array()) { - $name = tempnam(file_directory_path('temporary'), "po_"); + $name = tempnam(file_directory_path('temporary'), "po_") . '.po'; file_put_contents($name, $contents); $options['files[file]'] = $name; $this->drupalPost('admin/config/regional/translate/import', $options, t('Import')); @@ -905,7 +905,7 @@ class LocaleExportFunctionalTest extends DrupalWebTestCase { function testExportTranslation() { // First import some known translations. // This will also automatically enable the 'fr' language. - $name = tempnam(file_directory_path('temporary'), "po_"); + $name = tempnam(file_directory_path('temporary'), "po_") . '.po'; file_put_contents($name, $this->getPoFile()); $this->drupalPost('admin/config/regional/translate/import', array( 'langcode' => 'fr', @@ -1321,6 +1321,7 @@ class LocaleUserCreationTest extends DrupalWebTestCase { function setUp() { parent::setUp('locale'); + variable_set('user_register', USER_REGISTER_VISITORS); } /** @@ -1917,16 +1918,6 @@ class LocaleMultilingualFieldsFunctionalTest extends DrupalWebTestCase { $this->drupalGet("node/$node->nid"); $body = $this->xpath('//div[@id=:id]//div[@property="content:encoded"]/p', array(':id' => 'node-' . $node->nid)); $this->assertEqual(current($body), $node->body['en'][0]['value'], 'Node body is correctly showed.'); - - $settings['body[full][type]'] = 'hidden'; - $this->drupalPost('admin/structure/types/manage/page/display', $settings, t('Save')); - $select = $this->xpath('//select[@id="edit-body-full-type"]/option[@selected="selected"]'); - // Check if body display is actually "hidden" for the "full" view mode. - $this->assertEqual(current($select), '<Hidden>', 'Body display is actually "hidden" for the "full" view mode'); - $this->drupalGet("node/$node->nid"); - // Check if node body is not showed. - $body = $this->xpath('//div[@id=:id]//div[@property="content:encoded"]/p', array(':id' => 'node-' . $node->nid)); - $this->assertFalse(is_array($body), 'Body correctly not showed.'); } } diff --git a/modules/locale/tests/locale_test.info b/modules/locale/tests/locale_test.info index 4cbb417e..b13725ec 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/menu/menu.admin.inc b/modules/menu/menu.admin.inc index 5e391ae6..a526586b 100644 --- a/modules/menu/menu.admin.inc +++ b/modules/menu/menu.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: menu.admin.inc,v 1.79 2010/05/04 20:31:53 dries Exp $ +// $Id: menu.admin.inc,v 1.82 2010/06/26 21:32:20 dries Exp $ /** * @file @@ -86,6 +86,9 @@ function menu_overview_form($form, &$form_state, $menu) { /** * Recursive helper function for menu_overview_form(). + * + * @param $tree + * The menu_tree retrieved by menu_tree_data. */ function _menu_overview_tree_form($tree) { $form = &drupal_static(__FUNCTION__, array('#tree' => TRUE)); @@ -105,16 +108,17 @@ function _menu_overview_tree_form($tree) { $form[$mlid]['weight'] = array( '#type' => 'weight', '#delta' => 50, - '#default_value' => isset($form_state[$mlid]['weight']) ? $form_state[$mlid]['weight'] : $item['weight'], + '#default_value' => $item['weight'], + '#title_display' => 'invisible', + '#title' => t('Weight for @row', array('@row' => $item['title'])), ); $form[$mlid]['mlid'] = array( '#type' => 'hidden', '#value' => $item['mlid'], ); $form[$mlid]['plid'] = array( - '#type' => 'textfield', - '#default_value' => isset($form_state[$mlid]['plid']) ? $form_state[$mlid]['plid'] : $item['plid'], - '#size' => 6, + '#type' => 'hidden', + '#default_value' => $item['plid'], ); // Build a list of operations. $operations = array(); @@ -680,7 +684,7 @@ function menu_configure() { $form['menu_main_links_source'] = array( '#type' => 'select', '#title' => t('Source for the Main links'), - '#default_value' => 'main-menu', + '#default_value' => variable_get('menu_main_links_source', 'main-menu'), '#options' => $main_options, '#tree' => FALSE, '#description' => t('Select what should be displayed as the Main links (typically at the top of the page).'), @@ -690,11 +694,11 @@ function menu_configure() { $form['menu_secondary_links_source'] = array( '#type' => 'select', '#title' => t('Source for the Secondary links'), - '#default_value' => 'user-menu', + '#default_value' => variable_get('menu_secondary_links_source', 'user-menu'), '#options' => $secondary_options, '#tree' => FALSE, '#description' => t("Select the source for the Secondary links. An advanced option allows you to use the same source for both Main links (currently %main) and Secondary links: if your source menu has two levels of hierarchy, the top level menu links will appear in the Main links, and the children of the active link will appear in the Secondary links." , array('%main' => $main_options[$main])), ); - return system_settings_form($form, TRUE); + return system_settings_form($form); } diff --git a/modules/menu/menu.info b/modules/menu/menu.info index 46db58d3..7af94015 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/menu/menu.module b/modules/menu/menu.module index d72fbdd2..86f7543f 100644 --- a/modules/menu/menu.module +++ b/modules/menu/menu.module @@ -1,5 +1,5 @@ <?php -// $Id: menu.module,v 1.229 2010/03/07 07:55:14 webchick Exp $ +// $Id: menu.module,v 1.230 2010/06/17 13:44:45 dries Exp $ /** * @file @@ -596,7 +596,6 @@ function menu_form_alter(&$form, $form_state, $form_id) { return; } $link = $form['#node']->menu; - $form['#submit'][] = 'menu_node_form_submit'; $form['menu'] = array( '#type' => 'fieldset', @@ -661,15 +660,13 @@ function menu_form_alter(&$form, $form_state, $form_id) { } /** - * Submit handler for node form. - * - * @see menu_form_alter() + * Implements hook_node_submit(). */ -function menu_node_form_submit($form, &$form_state) { +function menu_node_submit($node, $form, $form_state) { // Decompose the selected menu parent option into 'menu_name' and 'plid', if // the form used the default parent selection widget. if (!empty($form_state['values']['menu']['parent'])) { - list($form_state['values']['menu']['menu_name'], $form_state['values']['menu']['plid']) = explode(':', $form_state['values']['menu']['parent']); + list($node->menu['menu_name'], $node->menu['plid']) = explode(':', $form_state['values']['menu']['parent']); } } diff --git a/modules/node/content_types.inc b/modules/node/content_types.inc index 72e058eb..9d036585 100644 --- a/modules/node/content_types.inc +++ b/modules/node/content_types.inc @@ -1,5 +1,5 @@ <?php -// $Id: content_types.inc,v 1.114 2010/05/05 06:55:25 webchick Exp $ +// $Id: content_types.inc,v 1.115 2010/06/24 22:23:06 webchick Exp $ /** * @file @@ -363,13 +363,13 @@ 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) { drupal_set_message(t('The content type %name has been updated.', $t_args)); } elseif ($status == SAVED_NEW) { + node_add_body_field($type); drupal_set_message(t('The content type %name has been added.', $t_args)); watchdog('node', 'Added content type %name.', $t_args, WATCHDOG_NOTICE, l(t('view'), 'admin/structure/types')); } diff --git a/modules/node/node.admin.inc b/modules/node/node.admin.inc index ab4556d0..6cb7874c 100644 --- a/modules/node/node.admin.inc +++ b/modules/node/node.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: node.admin.inc,v 1.94 2010/04/24 14:49:14 dries Exp $ +// $Id: node.admin.inc,v 1.97 2010/07/07 17:56:42 webchick Exp $ /** * @file @@ -119,14 +119,12 @@ function node_filters() { function node_build_filter_query(SelectQueryInterface $query) { // Build query $filter_data = isset($_SESSION['node_overview_filter']) ? $_SESSION['node_overview_filter'] : array(); - $counter = 0; foreach ($filter_data as $index => $filter) { list($key, $value) = $filter; switch ($key) { case 'term': - $index = 'ti' . $counter++; - $query->join('taxonomy_index', $index, "n.nid = $index.nid"); - $query->condition($index . '.tid', $value); + $alias = $query->join('taxonomy_index', 'ti', "n.nid = %alias.nid"); + $query->condition($alias . '.tid', $value); break; case 'status': // Note: no exploitable hole as $key/$value have already been checked when submitted diff --git a/modules/node/node.api.php b/modules/node/node.api.php index 81daab58..490205e7 100644 --- a/modules/node/node.api.php +++ b/modules/node/node.api.php @@ -1,5 +1,5 @@ <?php -// $Id: node.api.php,v 1.67 2010/05/05 06:55:25 webchick Exp $ +// $Id: node.api.php,v 1.70 2010/06/17 13:44:45 dries Exp $ /** * @file @@ -96,8 +96,7 @@ * an existing node, it will already be loaded; see the Loading section * above): * - hook_prepare() (node-type-specific) - * - hook_node_prepare() (all); if translation.module is enabled, this will - * also invoke hook_node_prepare_translation() on all modules. + * - hook_node_prepare() (all) * - hook_form() (node-type-specific) * - field_attach_form() * - Validating a node during editing form submit (calling @@ -554,21 +553,6 @@ function hook_node_prepare($node) { } } -/** - * Act on a node object being cloned for translation. - * - * This hook is invoked from translation_node_prepare() after the node is - * loaded. $node->language is set to the language being requested, and - * $node->translation_source is set to the node object being cloned. - * - * @param $node - * The node object being prepared for translation. - * - * @ingroup node_api_hooks - */ -function hook_node_prepare_translation($node) { -} - /** * Act on a node being displayed as a search result. * @@ -680,6 +664,34 @@ function hook_node_validate($node, $form) { } } +/** + * Act on a node after validated form values have been copied to it. + * + * This hook is invoked when a node form is submitted with either the "Save" or + * "Preview" button, after form values have been copied to the form state's node + * object, but before the node is saved or previewed. It is a chance for modules + * to adjust the node's properties from what they are simply after a copy from + * $form_state['values']. This hook is intended for adjusting non-field-related + * properties. See hook_field_attach_submit() for customizing field-related + * properties. + * + * @param $node + * The node being updated in response to a form submission. + * @param $form + * The form being used to edit the node. + * @param $form_state + * The form state array. + * + * @ingroup node_api_hooks + */ +function hook_node_submit($node, $form, &$form_state) { + // Decompose the selected menu parent option into 'menu_name' and 'plid', if + // the form used the default parent selection widget. + if (!empty($form_state['values']['menu']['parent'])) { + list($node->menu['menu_name'], $node->menu['plid']) = explode(':', $form_state['values']['menu']['parent']); + } +} + /** * Act on a node that is being assembled before rendering. * @@ -1142,7 +1154,7 @@ function hook_validate($node, &$form) { * * @ingroup node_api_hooks */ -function hook_view($node, $view_mode = 'full') { +function hook_view($node, $view_mode) { if (node_is_page($node)) { $breadcrumb = array(); $breadcrumb[] = l(t('Home'), NULL); diff --git a/modules/node/node.info b/modules/node/node.info index 928da46b..8165e06e 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/node/node.module b/modules/node/node.module index 04a43811..f9009618 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -1,5 +1,5 @@ <?php -// $Id: node.module,v 1.1273 2010/05/17 07:43:36 dries Exp $ +// $Id: node.module,v 1.1279 2010/06/25 19:33:47 dries Exp $ /** * @file @@ -189,12 +189,15 @@ function node_entity_info() { 'view modes' => array( 'full' => array( 'label' => t('Full content'), + 'custom settings' => FALSE, ), 'teaser' => array( 'label' => t('Teaser'), + 'custom settings' => TRUE, ), 'rss' => array( 'label' => t('RSS'), + 'custom settings' => FALSE, ), ), ), @@ -206,9 +209,11 @@ function node_entity_info() { $return['node']['view modes'] += array( 'search_index' => array( 'label' => t('Search index'), + 'custom settings' => FALSE, ), 'search_result' => array( 'label' => t('Search result'), + 'custom settings' => FALSE, ), ); } @@ -230,6 +235,16 @@ function node_entity_info() { return $return; } +/** + * Implements hook_field_display_ENTITY_TYPE_alter(). + */ +function node_field_display_node_alter(&$display, $context) { + // Hide field labels in search index. + if ($context['view_mode'] == 'search_index') { + $display['label'] = 'hidden'; + } +} + /** * Entity uri callback. */ @@ -256,7 +271,7 @@ function node_admin_paths() { } /** - * Gather a listing of links to nodes. + * Gathers a listing of links to nodes. * * @param $result * A DB result object from a query to fetch node entities. If your query @@ -267,8 +282,8 @@ function node_admin_paths() { * A heading for the resulting list. * * @return - * An HTML list suitable as content for a block, or FALSE if no result can - * fetch from DB result object. + * A renderable array containing a list of linked node titles fetched from + * $result, or FALSE if there are no rows in $result. */ function node_title_list($result, $title = NULL) { $items = array(); @@ -278,7 +293,7 @@ function node_title_list($result, $title = NULL) { $num_rows = TRUE; } - return $num_rows ? theme('item_list__node', array('items' => $items, 'title' => $title)) : FALSE; + return $num_rows ? array('#theme' => 'item_list__node', '#items' => $items, '#title' => $title) : FALSE; } /** @@ -562,10 +577,8 @@ function node_add_body_field($type, $label = 'Body') { '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( + 'default' => array( 'label' => 'hidden', 'type' => 'text_default', ), @@ -588,10 +601,12 @@ function node_field_extra_fields() { foreach (node_type_get_types() as $type) { if ($type->has_title) { $extra['node'][$type->type] = array( - 'title' => array( - 'label' => $type->title_label, - 'description' => t('Node module element.'), - 'weight' => -5, + 'form' => array( + 'title' => array( + 'label' => $type->title_label, + 'description' => t('Node module element'), + 'weight' => -5, + ), ), ); } @@ -1060,7 +1075,7 @@ function node_save($node) { $function('node', $node); module_invoke_all('node_' . $op, $node); - entity_invoke($op, 'node', $node); + module_invoke_all('entity_' . $op, $node, 'node'); // Update the node access table for this node. There's no need to delete // existing records if the node is new. @@ -1075,7 +1090,8 @@ function node_save($node) { db_ignore_slave(); } catch (Exception $e) { - $transaction->rollback('node', $e->getMessage(), array(), WATCHDOG_ERROR); + $transaction->rollback('node'); + watchdog_exception('node', $e); throw $e; } } @@ -3015,15 +3031,15 @@ function node_query_node_access_alter(QueryAlterableInterface $query) { if (!($table instanceof SelectQueryInterface) && $table == 'node') { // The node_access table has the access grants for any given node. - $access_alias = $query->join('node_access', 'na', "na.nid = {$nalias}.nid"); + $access_alias = $query->join('node_access', 'na', '%alias.nid = ' . $nalias . '.nid'); $or = db_or(); // If any grant exists for the specified user, then user has access // to the node for the specified operation. foreach ($grants as $realm => $gids) { foreach ($gids as $gid) { $or->condition(db_and() - ->condition("{$access_alias}.gid", $gid) - ->condition("{$access_alias}.realm", $realm) + ->condition($access_alias . '.gid', $gid) + ->condition($access_alias . '.realm', $realm) ); } } @@ -3032,7 +3048,7 @@ function node_query_node_access_alter(QueryAlterableInterface $query) { $query->condition($or); } - $query->condition("{$access_alias}.grant_$op", 1, '>='); + $query->condition($access_alias . '.grant_' . $op, 1, '>='); } } } @@ -3244,14 +3260,14 @@ function _node_access_rebuild_batch_operation(&$context) { $limit = 20; $nids = db_query_range("SELECT nid FROM {node} WHERE nid > :nid ORDER BY nid ASC", 0, $limit, array(':nid' => $context['sandbox']['current_node']))->fetchCol(); $nodes = node_load_multiple($nids, array(), TRUE); - foreach ($nodes as $node) { + foreach ($nodes as $nid => $node) { // To preserve database integrity, only acquire grants if the node // loads successfully. if (!empty($node)) { node_access_acquire_grants($node); } $context['sandbox']['progress']++; - $context['sandbox']['current_node'] = $node->nid; + $context['sandbox']['current_node'] = $nid; } // Multistep processing : report progress. diff --git a/modules/node/node.pages.inc b/modules/node/node.pages.inc index c39278cd..c5a83078 100644 --- a/modules/node/node.pages.inc +++ b/modules/node/node.pages.inc @@ -1,5 +1,5 @@ <?php -// $Id: node.pages.inc,v 1.126 2010/05/10 06:34:39 dries Exp $ +// $Id: node.pages.inc,v 1.127 2010/06/17 13:44:45 dries Exp $ /** * @file @@ -76,12 +76,15 @@ function node_add($type) { } function node_form_validate($form, &$form_state) { + // $form_state['node'] contains the actual entity being edited, but we must + // not update it with form values that have not yet been validated, so we + // create a pseudo-entity to use during validation. $node = (object) $form_state['values']; node_validate($node, $form); // Field validation. Requires access to $form_state, so this cannot be // done in node_validate() as it currently exists. - field_attach_form_validate('node', $node, $form, $form_state); + entity_form_field_validate('node', $form, $form_state); } /** @@ -89,28 +92,29 @@ function node_form_validate($form, &$form_state) { */ function node_form($form, &$form_state, $node) { global $user; - // This form has its own multistep persistence. - if ($form_state['rebuild']) { - $form_state['input'] = array(); - } - if (isset($form_state['node'])) { - $node = (object) ($form_state['node'] + (array) $node); - } - if (isset($form_state['node_preview'])) { - $form['#prefix'] = $form_state['node_preview']; - } - foreach (array('title') as $key) { - if (!isset($node->$key)) { - $node->$key = NULL; + // During initial form build, add the node entity to the form state for use + // during form building and processing. During a rebuild, use what is in the + // form state. + if (!isset($form_state['node'])) { + if (!isset($node->title)) { + $node->title = NULL; } - } - if (!isset($form_state['node_preview'])) { node_object_prepare($node); + $form_state['node'] = $node; } else { + $node = $form_state['node']; + } + + // Some special stuff when previewing a node. + if (isset($form_state['node_preview'])) { + $form['#prefix'] = $form_state['node_preview']; $node->in_preview = TRUE; } + else { + unset($node->in_preview); + } // Identify this as a node edit form. $form['#node_edit_form'] = TRUE; @@ -140,7 +144,9 @@ function node_form($form, &$form_state, $node) { if (!isset($form['title']['#weight'])) { $form['title']['#weight'] = -5; } - + // @todo Legacy support. Modules adding form building and processing functions + // to the node form are encouraged to access the node using + // $form_state['node']. Remove in Drupal 8. $form['#node'] = $node; $form['additional_settings'] = array( @@ -299,8 +305,9 @@ function node_form_delete_submit($form, &$form_state) { function node_form_build_preview($form, &$form_state) { - $node = node_form_submit_build_node($form, $form_state); + $node = $form['#builder_function']($form, $form_state); $form_state['node_preview'] = node_preview($node); + $form_state['rebuild'] = TRUE; } /** @@ -382,7 +389,7 @@ function theme_node_preview($variables) { } function node_form_submit($form, &$form_state) { - $node = node_form_submit_build_node($form, $form_state); + $node = $form['#builder_function']($form, $form_state); $insert = empty($node->nid); node_save($node); $node_link = l(t('view'), 'node/' . $node->nid); @@ -398,7 +405,6 @@ function node_form_submit($form, &$form_state) { drupal_set_message(t('@type %title has been updated.', $t_args)); } if ($node->nid) { - unset($form_state['rebuild']); $form_state['values']['nid'] = $node->nid; $form_state['nid'] = $node->nid; $form_state['redirect'] = 'node/' . $node->nid; @@ -407,25 +413,42 @@ function node_form_submit($form, &$form_state) { // In the unlikely case something went wrong on save, the node will be // rebuilt and node form redisplayed the same way as in preview. drupal_set_message(t('The post could not be saved.'), 'error'); + $form_state['rebuild'] = TRUE; } // Clear the page and block caches. cache_clear_all(); } /** - * Build a node by processing submitted form values and prepare for a form rebuild. + * Updates the form state's node entity by processing this submission's values. + * + * This is the default #builder_function for the node form. It is called + * during the "Save" and "Preview" submit handlers to retrieve the entity to + * save or preview. This function can also be called by a "Next" button of a + * wizard to update the form state's entity with the current step's values + * before proceeding to the next step. + * + * @see node_form() */ function node_form_submit_build_node($form, &$form_state) { - // Unset any button-level handlers, execute all the form-level submit - // functions to process the form values into an updated node. + // @todo Legacy support for modules that extend the node form with form-level + // submit handlers that adjust $form_state['values'] prior to those values + // being used to update the entity. Module authors are encouraged to instead + // adjust the node directly within a hook_node_submit() implementation. For + // Drupal 8, evaluate whether the pattern of triggering form-level submit + // handlers during button-level submit processing is worth supporting + // properly, and if so, add a Form API function for doing so. unset($form_state['submit_handlers']); form_execute_handlers('submit', $form, $form_state); - $node = node_submit((object) $form_state['values']); - field_attach_submit('node', $node, $form, $form_state); + $node = $form_state['node']; + entity_form_submit_build_entity('node', $node, $form, $form_state); - $form_state['node'] = (array) $node; - $form_state['rebuild'] = TRUE; + node_submit($node); + foreach (module_implements('node_submit') as $module) { + $function = $module . '_node_submit'; + $function($node, $form, $form_state); + } return $node; } diff --git a/modules/node/node.test b/modules/node/node.test index ad2768de..1e5d539f 100644 --- a/modules/node/node.test +++ b/modules/node/node.test @@ -1,5 +1,5 @@ <?php -// $Id: node.test,v 1.85 2010/05/07 15:00:56 dries Exp $ +// $Id: node.test,v 1.89 2010/07/08 03:41:27 webchick Exp $ /** * Test the node_load_multiple() function. @@ -490,7 +490,7 @@ class NodeCreationTestCase extends DrupalWebTestCase { } // Check that the rollback error was logged. - $records = db_query("SELECT wid FROM {watchdog} WHERE message LIKE 'Test exception for rollback.'")->fetchAll(); + $records = db_query("SELECT wid FROM {watchdog} WHERE variables LIKE '%Test exception for rollback.%'")->fetchAll(); $this->assertTrue(count($records) > 0, t('Rollback explanatory error logged to watchdog.')); } } @@ -678,7 +678,8 @@ class NodePostSettingsTestCase extends DrupalWebTestCase { // Check that the post information is displayed. $node = $this->drupalGetNodeByTitle($edit["title"]); - $this->assertRaw('<span class="submitted">', t('Post information is displayed.')); + $elements = $this->xpath('//div[contains(@class,:class)]', array(':class' => 'submitted')); + $this->assertEqual(count($elements), 1, t('Post information is displayed.')); } /** @@ -1056,9 +1057,10 @@ class NodeTypeTestCase extends DrupalWebTestCase { } /** - * Test creating a content type. + * Test creating a content type programmatically and via a form. */ function testNodeTypeCreation() { + // Create a content type programmaticaly. $type = $this->drupalCreateContentType(); $type_exists = db_query('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => $type->type))->fetchField(); @@ -1070,6 +1072,18 @@ class NodeTypeTestCase extends DrupalWebTestCase { $this->drupalGet('node/add/' . str_replace('_', '-', $type->name)); $this->assertResponse(200, 'The new content type can be accessed at node/add.'); + + // Create a content type via the user interface. + $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types')); + $this->drupalLogin($web_user); + $edit = array( + 'name' => 'foo', + 'title_label' => 'title for foo', + 'type' => 'foo', + ); + $this->drupalPost('admin/structure/types/add', $edit, t('Save content type')); + $type_exists = db_query('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => 'foo'))->fetchField(); + $this->assertTrue($type_exists, 'The new content type has been created in the database.'); } /** @@ -1106,6 +1120,7 @@ class NodeTypeTestCase extends DrupalWebTestCase { 'description' => 'Lorem ipsum.', ); $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); + field_info_cache_clear(); $this->drupalGet('node/add'); $this->assertRaw('Bar', t('New name was displayed.')); @@ -1114,6 +1129,14 @@ class NodeTypeTestCase extends DrupalWebTestCase { $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('Body', t('Body field was found.')); + + // Remove the body field. + $this->drupalPost('admin/structure/types/manage/bar/fields/body/delete', NULL, t('Delete')); + // Resave the settings for this type. + $this->drupalPost('admin/structure/types/manage/bar', array(), t('Save content type')); + // Check that the body field doesn't exist. + $this->drupalGet('node/add/bar'); + $this->assertNoRaw('Body', t('Body field was not found.')); } } @@ -1452,8 +1475,10 @@ class NodeBlockFunctionalTest extends DrupalWebTestCase { $custom_block['title'] = $this->randomName(); $custom_block['types[article]'] = TRUE; $custom_block['body[value]'] = $this->randomName(32); - $custom_block['regions[garland]'] = 'content'; - $custom_block['regions[seven]'] = 'content'; + $custom_block['regions[' . variable_get('theme_default', 'bartik') . ']'] = 'content'; + if ($admin_theme = variable_get('admin_theme')) { + $custom_block['regions[' . $admin_theme . ']'] = 'content'; + } $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); @@ -1708,7 +1733,6 @@ class NodeTokenReplaceTestCase extends DrupalWebTestCase { $tests['[node:nid]'] = $node->nid; $tests['[node:vid]'] = $node->vid; $tests['[node:tnid]'] = $node->tnid; - $tests['[node:uid]'] = $node->uid; $tests['[node:type]'] = 'article'; $tests['[node:type-name]'] = 'Article'; $tests['[node:title]'] = check_plain($node->title); @@ -1717,6 +1741,7 @@ class NodeTokenReplaceTestCase extends DrupalWebTestCase { $tests['[node:language]'] = check_plain($node->language); $tests['[node:url]'] = url('node/' . $node->nid, $url_options); $tests['[node:edit-url]'] = url('node/' . $node->nid . '/edit', $url_options); + $tests['[node:author:uid]'] = $node->uid; $tests['[node:author:name]'] = check_plain($account->name); $tests['[node:created:since]'] = format_interval(REQUEST_TIME - $node->created, 2, $language->language); $tests['[node:changed:since]'] = format_interval(REQUEST_TIME - $node->changed, 2, $language->language); diff --git a/modules/node/node.tokens.inc b/modules/node/node.tokens.inc index 499b9a40..46d329fc 100644 --- a/modules/node/node.tokens.inc +++ b/modules/node/node.tokens.inc @@ -1,5 +1,5 @@ <?php -// $Id: node.tokens.inc,v 1.14 2010/04/20 09:48:06 webchick Exp $ +// $Id: node.tokens.inc,v 1.15 2010/06/29 00:57:19 dries Exp $ /** * @file @@ -31,10 +31,6 @@ function node_token_info() { 'name' => t("Translation set ID"), 'description' => t("The unique ID of the original-language version of this node, if one exists."), ); - $node['uid'] = array( - 'name' => t("User ID"), - 'description' => t("The unique ID of the user who posted the node."), - ); $node['type'] = array( 'name' => t("Content type"), 'description' => t("The type of the node."), @@ -125,10 +121,6 @@ function node_tokens($type, $tokens, array $data = array(), array $options = arr $replacements[$original] = $node->tnid; break; - case 'uid': - $replacements[$original] = $node->uid; - break; - case 'type': $replacements[$original] = $sanitize ? check_plain($node->type) : $node->type; break; diff --git a/modules/node/tests/node_access_test.info b/modules/node/tests/node_access_test.info index e5777a6c..a7ae296f 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/node/tests/node_presave_test.info b/modules/node/tests/node_presave_test.info index 57fcd8ae..41ac2204 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/node/tests/node_test.info b/modules/node/tests/node_test.info index ec3291f3..5dcc6321 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/node/tests/node_test_exception.info b/modules/node/tests/node_test_exception.info index da643046..70c5455c 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/openid/openid-rtl.css b/modules/openid/openid-rtl.css new file mode 100644 index 00000000..eac22b0e --- /dev/null +++ b/modules/openid/openid-rtl.css @@ -0,0 +1,19 @@ +/* $Id: openid-rtl.css,v 1.1 2010/06/04 20:39:44 dries Exp $ */ + +#edit-openid-identifier { + background-position: right 50%; + padding-left: 0; + padding-right: 20px; +} +#user-login .openid-links { + padding-right: 0; +} +html.js #user-login-form li.openid-link, +html.js #user-login li.openid-link { + margin-right: 0; +} +#user-login-form li.openid-link a, +#user-login li.openid-link a { + background-position: right top; + padding: 0 1.5em 0 0; +} diff --git a/modules/openid/openid.inc b/modules/openid/openid.inc index aba6ed51..a2779027 100644 --- a/modules/openid/openid.inc +++ b/modules/openid/openid.inc @@ -1,5 +1,5 @@ <?php -// $Id: openid.inc,v 1.33 2010/05/13 17:37:24 dries Exp $ +// $Id: openid.inc,v 1.35 2010/07/01 19:06:56 dries Exp $ /** * @file @@ -50,6 +50,11 @@ define('OPENID_NS_1_1', 'http://openid.net/signon/1.1'); */ define('OPENID_NS_1_0', 'http://openid.net/signon/1.0'); +/** + * OpenID namespace used in Yadis documents. + */ +define('OPENID_NS_OPENID', 'http://openid.net/xmlns/1.0'); + /** * OpenID Simple Registration extension. */ @@ -148,12 +153,12 @@ function _openid_xrds_parse($raw_xml) { foreach ($service_element->Type as $type) { $service['types'][] = (string)$type; } - if ($service_element->children(OPENID_NS_XRD)->Delegate) { - $service['identity'] = (string)$service_element->children(OPENID_NS_XRD)->Delegate; - } if ($service_element->children(OPENID_NS_XRD)->LocalID) { $service['identity'] = (string)$service_element->children(OPENID_NS_XRD)->LocalID; } + elseif ($service_element->children(OPENID_NS_OPENID)->Delegate) { + $service['identity'] = (string)$service_element->children(OPENID_NS_OPENID)->Delegate; + } else { $service['identity'] = FALSE; } @@ -653,22 +658,26 @@ function openid_extract_namespace($response, $extension_namespace, $fallback_pre * * @param $values * An array as returned by openid_extract_namespace(..., OPENID_NS_AX). - * @param $aliases - * An array of aliases used in the fetch request. + * @param $uris + * An array of identifier URIs. * @return * An array of values. * @see http://openid.net/specs/openid-attribute-exchange-1_0.html#fetch_response */ -function openid_extract_ax_values($values, $aliases) { +function openid_extract_ax_values($values, $uris) { $output = array(); - foreach ($aliases as $alias) { - if (isset($values['count.' . $alias])) { - for ($i = 1; $i <= $values['count.' . $alias]; $i++) { - $output[] = $values['value.' . $alias . '.' . $i]; + foreach ($values as $key => $value) { + if (in_array($value, $uris) && preg_match('/^type\.([^.]+)$/', $key, $matches)) { + $alias = $matches[1]; + if (isset($values['count.' . $alias])) { + for ($i = 1; $i <= $values['count.' . $alias]; $i++) { + $output[] = $values['value.' . $alias . '.' . $i]; + } } - } - elseif (isset($values['value.' . $alias])) { - $output[] = $values['value.' . $alias]; + elseif (isset($values['value.' . $alias])) { + $output[] = $values['value.' . $alias]; + } + break; } } return $output; diff --git a/modules/openid/openid.info b/modules/openid/openid.info index 46749768..2bfc7f68 100644 --- a/modules/openid/openid.info +++ b/modules/openid/openid.info @@ -1,4 +1,4 @@ -; $Id: openid.info,v 1.7 2009/06/08 09:23:53 dries Exp $ +; $Id: openid.info,v 1.8 2010/05/26 08:39:54 dries Exp $ name = OpenID description = "Allows users to log into your site using OpenID." version = VERSION @@ -7,12 +7,11 @@ core = 7.x files[] = openid.module files[] = openid.inc files[] = openid.pages.inc -files[] = xrds.inc files[] = openid.install files[] = openid.test -; Information added by drupal.org packaging script on 2010-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/openid/openid.module b/modules/openid/openid.module index 3964e59f..8932847b 100644 --- a/modules/openid/openid.module +++ b/modules/openid/openid.module @@ -1,5 +1,5 @@ <?php -// $Id: openid.module,v 1.89 2010/05/20 08:51:24 dries Exp $ +// $Id: openid.module,v 1.93 2010/07/07 08:05:01 webchick Exp $ /** * @file @@ -38,6 +38,16 @@ function openid_menu() { return $items; } +/** + * Implements hook_menu_site_status_alter(). + */ +function openid_menu_site_status_alter(&$menu_site_status, $path) { + // Allow access to openid/authenticate even if site is in offline mode. + if ($menu_site_status == MENU_SITE_OFFLINE && user_is_anonymous() && $path == 'openid/authenticate') { + $menu_site_status = MENU_SITE_ONLINE; + } +} + /** * Implements hook_help(). */ @@ -175,7 +185,7 @@ function openid_form_user_register_form_alter(&$form, &$form_state) { // Use the nickname returned by Simple Registration if available. $form['account']['name']['#default_value'] = $sreg_values['nickname']; } - elseif ($ax_name_values = openid_extract_ax_values($ax_values, array('name_ao', 'name_son'))) { + elseif ($ax_name_values = openid_extract_ax_values($ax_values, array('http://axschema.org/namePerson/friendly', 'http://schema.openid.net/namePerson/friendly'))) { // Else, use the first nickname returned by AX if available. $form['account']['name']['#default_value'] = current($ax_name_values); } @@ -187,7 +197,7 @@ function openid_form_user_register_form_alter(&$form, &$form_state) { // Use the email returned by Simple Registration if available. $form['account']['mail']['#default_value'] = $sreg_values['email']; } - elseif ($ax_mail_values = openid_extract_ax_values($ax_values, array('mail_ao', 'mail_son'))) { + elseif ($ax_mail_values = openid_extract_ax_values($ax_values, array('http://axschema.org/contact/email', 'http://schema.openid.net/contact/email'))) { // Else, use the first nickname returned by AX if available. $form['account']['mail']['#default_value'] = current($ax_mail_values); } @@ -362,7 +372,6 @@ function openid_complete($response = array()) { */ function openid_discovery($claimed_id) { module_load_include('inc', 'openid'); - module_load_include('inc', 'openid', 'xrds'); $methods = module_invoke_all('openid_discovery_method_info'); drupal_alter('openid_discovery_method_info', $methods); @@ -590,7 +599,7 @@ function openid_authentication($response) { drupal_set_message(t('You must validate your email address for this account before logging in via OpenID.')); } } - elseif (variable_get('user_register', 1)) { + elseif (variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)) { // Register new user. // Save response for use in openid_form_user_register_form_alter(). diff --git a/modules/openid/openid.test b/modules/openid/openid.test index b1676009..5adc06e6 100644 --- a/modules/openid/openid.test +++ b/modules/openid/openid.test @@ -1,5 +1,5 @@ <?php -// $Id: openid.test,v 1.23 2010/05/13 17:37:24 dries Exp $ +// $Id: openid.test,v 1.28 2010/07/07 08:05:01 webchick Exp $ /** * Base class for OpenID tests. @@ -67,7 +67,10 @@ class OpenIDFunctionalTestCase extends OpenIDWebTestCase { // The URL scheme is stripped in order to test that the supplied identifier // is normalized in openid_begin(). $identity = url('openid-test/yadis/xrds', array('absolute' => TRUE)); - $this->addIdentity(preg_replace('@^https?://@', '', $identity), 2, $identity); + $this->addIdentity(preg_replace('@^https?://@', '', $identity), 2, 'http://example.com/xrds', $identity); + + $identity = url('openid-test/yadis/xrds/delegate', array('absolute' => TRUE)); + $this->addIdentity(preg_replace('@^https?://@', '', $identity), 2, 'http://example.com/xrds-delegate', $identity); // Identifier is the URL of an XRDS document containing an OP Identifier // Element. The Relying Party sends the special value @@ -78,7 +81,7 @@ class OpenIDFunctionalTestCase extends OpenIDWebTestCase { // is stripped in order to test that the returned identifier is normalized in // openid_complete(). variable_set('openid_test_response', array('openid.claimed_id' => preg_replace('@^https?://@', '', $identity))); - $this->addIdentity(url('openid-test/yadis/xrds/server', array('absolute' => TRUE)), 2, $identity); + $this->addIdentity(url('openid-test/yadis/xrds/server', array('absolute' => TRUE)), 2, 'http://specs.openid.net/auth/2.0/identifier_select', $identity); variable_set('openid_test_response', array()); // Identifier is the URL of an HTML page that is sent with an HTTP header @@ -91,11 +94,11 @@ class OpenIDFunctionalTestCase extends OpenIDWebTestCase { // Identifier is an XRI. Resolve using our own dummy proxy resolver. variable_set('xri_proxy_resolver', url('openid-test/yadis/xrds/xri', array('absolute' => TRUE)) . '/'); - $this->addIdentity('@example*résumé;%25', 2, 'http://example.com/user'); + $this->addIdentity('@example*résumé;%25', 2, 'http://example.com/xrds', 'http://example.com/user'); // Make sure that unverified CanonicalID are not trusted. variable_set('openid_test_canonical_id_status', 'bad value'); - $this->addIdentity('@example*résumé;%25', 2, FALSE); + $this->addIdentity('@example*résumé;%25', 2, FALSE, FALSE); // HTML-based discovery: // If the User-supplied Identifier is a URL of an HTML page, the page may @@ -103,10 +106,10 @@ class OpenIDFunctionalTestCase extends OpenIDWebTestCase { // Provider Endpoint. OpenID 1 and 2 describe slightly different formats. // OpenID Authentication 1.1, section 3.1: - $this->addIdentity(url('openid-test/html/openid1', array('absolute' => TRUE)), 1); + $this->addIdentity(url('openid-test/html/openid1', array('absolute' => TRUE)), 1, 'http://example.com/html-openid1'); // OpenID Authentication 2.0, section 7.3.3: - $this->addIdentity(url('openid-test/html/openid2', array('absolute' => TRUE)), 2); + $this->addIdentity(url('openid-test/html/openid2', array('absolute' => TRUE)), 2, 'http://example.com/html-openid2'); } /** @@ -143,6 +146,38 @@ class OpenIDFunctionalTestCase extends OpenIDWebTestCase { $this->assertResponse(200); } + /** + * Test login using OpenID during maintenance mode. + */ + function testLoginMaintenanceMode() { + $this->web_user = $this->drupalCreateUser(array('access site in maintenance mode')); + $this->drupalLogin($this->web_user); + + // Use a User-supplied Identity that is the URL of an XRDS document. + $identity = url('openid-test/yadis/xrds', array('absolute' => TRUE)); + $this->addIdentity($identity); + $this->drupalLogout(); + + // Enable maintenance mode. + variable_set('maintenance_mode', 1); + + // Test logging in via the user/login page while the site is offline. + $edit = array('openid_identifier' => $identity); + $this->drupalPost('user/login', $edit, t('Log in')); + + // Check we are on the OpenID redirect form. + $this->assertTitle(t('OpenID redirect'), t('OpenID redirect page was displayed.')); + + // Submit form to the OpenID Provider Endpoint. + $this->drupalPost(NULL, array(), t('Send')); + + $this->assertLink($this->web_user->name, 0, t('User was logged in.')); + + // Verify user was redirected away from user/login to an accessible page. + $this->assertText(t('Operating in maintenance mode.')); + $this->assertResponse(200); + } + /** * Test deleting an OpenID identity from a user's profile. */ @@ -197,11 +232,16 @@ class OpenIDFunctionalTestCase extends OpenIDWebTestCase { * The User-supplied Identifier. * @param $version * The protocol version used by the service. + * @param $local_id + * The expected OP-Local Identifier found during discovery. * @param $claimed_id * The expected Claimed Identifier returned by the OpenID Provider, or FALSE * if the discovery is expected to fail. */ - function addIdentity($identity, $version = 2, $claimed_id = NULL) { + function addIdentity($identity, $version = 2, $local_id = 'http://example.com/xrds', $claimed_id = NULL) { + // Tell openid_test.module to only accept this OP-Local Identifier. + variable_set('openid_test_identity', $local_id); + $edit = array('openid_identifier' => $identity); $this->drupalPost('user/' . $this->web_user->uid . '/openid', $edit, t('Add an OpenID')); @@ -240,6 +280,7 @@ class OpenIDRegistrationTestCase extends OpenIDWebTestCase { function setUp() { parent::setUp('openid', 'openid_test'); + variable_set('user_register', USER_REGISTER_VISITORS); } /** @@ -383,9 +424,11 @@ class OpenIDRegistrationTestCase extends OpenIDWebTestCase { // Tell openid_test.module to respond with these AX fields. variable_set('openid_test_response', array( 'openid.ns.ext123' => 'http://openid.net/srv/ax/1.0', - 'openid.ext123.value.mail_ao' => 'john@example.com', - 'openid.ext123.count.name_son' => '1', - 'openid.ext123.value.name_son.1' => 'john', + 'openid.ext123.type.mail456' => 'http://axschema.org/contact/email', + 'openid.ext123.value.mail456' => 'john@example.com', + 'openid.ext123.type.name789' => 'http://schema.openid.net/namePerson/friendly', + 'openid.ext123.count.name789' => '1', + 'openid.ext123.value.name789.1' => 'john', )); // Use a User-supplied Identity that is the URL of an XRDS document. @@ -456,7 +499,7 @@ class OpenIDUnitTest extends DrupalWebTestCase { 'openid.baz' => 'abc3', 'foobar.foo' => 'abc4', ); - $association = new stdClass; + $association = new stdClass(); $association->mac_key = "1234567890abcdefghij\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF7\xF8\xF9"; $this->assertEqual(_openid_signature($association, $response, array('foo', 'bar')), 'QnKZQzSFstT+GNiJDFOptdcZjrc=', t('Expected signature calculated.')); } diff --git a/modules/openid/tests/openid_test.info b/modules/openid/tests/openid_test.info index 724ada54..93737556 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/openid/tests/openid_test.module b/modules/openid/tests/openid_test.module index a11b1d1f..ce633bee 100644 --- a/modules/openid/tests/openid_test.module +++ b/modules/openid/tests/openid_test.module @@ -1,5 +1,5 @@ <?php -// $Id: openid_test.module,v 1.14 2010/04/23 05:48:13 webchick Exp $ +// $Id: openid_test.module,v 1.17 2010/07/07 08:05:01 webchick Exp $ /** * @file @@ -64,6 +64,16 @@ function openid_test_menu() { return $items; } +/** + * Implements hook_menu_site_status_alter(). + */ +function openid_test_menu_site_status_alter(&$menu_site_status, $path) { + // Allow access to openid endpoint and identity even in offline mode. + if ($menu_site_status == MENU_SITE_OFFLINE && user_is_anonymous() && in_array($path, array('openid-test/yadis/xrds', 'openid-test/endpoint'))) { + $menu_site_status = MENU_SITE_ONLINE; + } +} + /** * Menu callback; XRDS document that references the OP Endpoint URL. */ @@ -88,7 +98,7 @@ function openid_test_yadis_xrds() { } drupal_add_http_header('Content-Type', 'application/xrds+xml'); print '<?xml version="1.0" encoding="UTF-8"?> - <xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)"> + <xrds:XRDS xmlns:xrds="xri://$xrds" xmlns="xri://$xrd*($v*2.0)" xmlns:openid="http://openid.net/xmlns/1.0"> <XRD> <Status cid="' . check_plain(variable_get('openid_test_canonical_id_status', 'verified')) . '"/> <ProviderID>xri://@</ProviderID> @@ -100,6 +110,7 @@ function openid_test_yadis_xrds() { <Type>http://specs.openid.net/auth/2.0/signon</Type> <Type>http://openid.net/srv/ax/1.0</Type> <URI>' . url('openid-test/endpoint', array('absolute' => TRUE)) . '</URI> + <LocalID>http://example.com/xrds</LocalID> </Service> <Service priority="15"> <Type>http://specs.openid.net/auth/2.0/signon</Type> @@ -121,6 +132,15 @@ function openid_test_yadis_xrds() { <URI>' . url('openid-test/endpoint', array('absolute' => TRUE)) . '</URI> </Service>'; } + elseif (arg(3) == 'delegate') { + print ' + <Service priority="5"> + <Type>http://specs.openid.net/auth/2.0/signon</Type> + <Type>http://openid.net/srv/ax/1.0</Type> + <URI>' . url('openid-test/endpoint', array('absolute' => TRUE)) . '</URI> + <openid:Delegate>http://example.com/xrds-delegate</openid:Delegate> + </Service>'; + } print ' </XRD> </xrds:XRDS>'; @@ -158,6 +178,7 @@ function openid_test_yadis_http_equiv() { */ function openid_test_html_openid1() { drupal_add_html_head_link(array('rel' => 'openid.server', 'href' => url('openid-test/endpoint', array('absolute' => TRUE)))); + drupal_add_html_head_link(array('rel' => 'openid.delegate', 'href' => 'http://example.com/html-openid1')); return t('This page includes a <link rel=...> element containing the URL of an OpenID Provider Endpoint.'); } @@ -166,6 +187,7 @@ function openid_test_html_openid1() { */ function openid_test_html_openid2() { drupal_add_html_head_link(array('rel' => 'openid2.provider', 'href' => url('openid-test/endpoint', array('absolute' => TRUE)))); + drupal_add_html_head_link(array('rel' => 'openid2.local_id', 'href' => 'http://example.com/html-openid2')); return t('This page includes a <link rel=...> element containing the URL of an OpenID Provider Endpoint.'); } @@ -249,6 +271,18 @@ function _openid_test_endpoint_associate() { function _openid_test_endpoint_authenticate() { module_load_include('inc', 'openid'); + $expected_identity = variable_get('openid_test_identity'); + if ($expected_identity && $_REQUEST['openid_identity'] != $expected_identity) { + $response = variable_get('openid_test_response', array()) + array( + 'openid.ns' => OPENID_NS_2_0, + 'openid.mode' => 'error', + 'openid.error' => 'Unexpted identity', + ); + drupal_add_http_header('Content-Type', 'text/plain'); + header('Location: ' . url($_REQUEST['openid_return_to'], array('query' => $response, 'external' => TRUE))); + return; + } + // Generate unique identifier for this authentication. $nonce = _openid_nonce(); @@ -268,7 +302,7 @@ function _openid_test_endpoint_authenticate() { ); // Sign the message using the MAC key that was exchanged during association. - $association = new stdClass; + $association = new stdClass(); $association->mac_key = variable_get('mac_key'); $keys_to_sign = explode(',', $response['openid.signed']); $response['openid.sig'] = _openid_signature($association, $response, $keys_to_sign); diff --git a/modules/overlay/overlay-child.css b/modules/overlay/overlay-child.css new file mode 100644 index 00000000..ab838411 --- /dev/null +++ b/modules/overlay/overlay-child.css @@ -0,0 +1,144 @@ +/* $Id: overlay-child.css,v 1.3 2010/06/13 06:03:47 dries Exp $ */ + +html.js { + background: transparent !important; + overflow-y: scroll; +} +html.js body { + background: transparent !important; + padding: 20px 0; +} + +#overlay { + display: table; + margin: 0 auto; + min-height: 100px; + min-width: 700px; + position: relative; + padding: .2em; + padding-right: 26px; + width: 88%; +} +#overlay-titlebar { + padding: 0 20px; + position: relative; + white-space: nowrap; + z-index: 100; +} +#overlay-content { + background: #fff; + clear: both; + color: #000; + padding: .5em 1em; + position: relative; +} + +#overlay-title-wrapper { + overflow: hidden; +} +#overlay-title { + color: #fff; + float: left; + font-family: Verdana,sans-serif; + font-size: 20px; + margin: 0; + padding: 0.3em 0; +} +#overlay-title:active, +#overlay-title:focus { + outline: 0; +} + +#overlay-close-wrapper { + position: absolute; + right: 0; +} +#overlay-close, +#overlay-close:hover { + background: transparent url(images/close.png) no-repeat; + -moz-border-radius-topleft: 0; + -webkit-border-top-left-radius: 0; + border-top-left-radius: 0; + display: block; + height: 26px; + margin: 0; + padding: 0; + /* Replace with position:fixed to get a scrolling close button. */ + position: absolute; + width: 26px; +} +#overlay-title:active, +#overlay-close:hover, +#overlay-close:focus { + padding: 0; +} +#overlay-close span { + display: none; +} + +/** + * Tabs on the overlay. + */ +#overlay-tabs { + line-height: 27px; + margin: -27px 0 0 0; + position: absolute; + right: 20px; + text-transform: uppercase; +} +#overlay-tabs li { + display: inline; + list-style: none; + margin: 0 0 0 -3px; + padding: 0; +} +#overlay-tabs li a, +#overlay-tabs li a:active, +#overlay-tabs li a:visited, +#overlay-tabs li a:hover { + background-color: #a6a7a2; + -moz-border-radius: 8px 8px 0 0; + -webkit-border-top-left-radius: 8px; + -webkit-border-top-right-radius: 8px; + border-radius: 8px 8px 0 0; + color: #000; + display: inline-block; + font-size: 11px; + font-weight: bold; + margin: 0 0 2px 0; + outline: 0; + padding: 0 14px; + text-decoration: none; +} +#overlay-tabs li.active a, +#overlay-tabs li.active a.active, +#overlay-tabs li.active a:active, +#overlay-tabs li.active a:visited { + background-color: #fff; + margin: 0; + padding-bottom: 2px; +} +#overlay-tabs li a:focus, +#overlay-tabs li a:hover { + color: #fff; +} +#overlay-tabs li.active a:focus, +#overlay-tabs li.active a:hover { + color: #000; +} + +/** + * Add to shortcuts link + */ +#overlay-titlebar .add-or-remove-shortcuts { + padding-top: 0.9em; +} + +/** + * IE6 shows elements with position:fixed as position:static so replace + * it with position:absolute; + */ +* html #overlay-close, +* html #overlay-close:hover { + position: absolute; +} diff --git a/modules/overlay/overlay-child.js b/modules/overlay/overlay-child.js index 8eb9ee6b..4072a71c 100644 --- a/modules/overlay/overlay-child.js +++ b/modules/overlay/overlay-child.js @@ -1,52 +1,50 @@ -// $Id: overlay-child.js,v 1.8 2010/04/24 07:14:29 dries Exp $ +// $Id: overlay-child.js,v 1.10 2010/07/08 12:20:23 dries Exp $ (function ($) { -/** - * Overlay object for child windows. - */ -Drupal.overlayChild = Drupal.overlayChild || { processed: false, behaviors: {} }; - /** * Attach the child dialog behavior to new content. */ Drupal.behaviors.overlayChild = { attach: function (context, settings) { - var self = Drupal.overlayChild; - var settings = settings.overlayChild || {}; - // Make sure this behavior is not processed more than once. - if (self.processed) { + if (this.processed) { return; } - self.processed = true; + this.processed = true; - // If we cannot reach the parent window, then we have nothing else to do - // here. - if (!$.isPlainObject(parent.Drupal) || !$.isPlainObject(parent.Drupal.overlay)) { - return; + // If we cannot reach the parent window, break out of the overlay. + if (!parent.Drupal || !parent.Drupal.overlay) { + window.location = window.location.href.replace(/([?&]?)render=overlay&?/g, '$1').replace(/\?$/, ''); + } + + var settings = settings.overlayChild || {}; + + // If the entire parent window should be refreshed when the overlay is + // closed, pass that information to the parent window. + if (settings.refreshPage) { + parent.Drupal.overlay.refreshPage = true; } // If a form has been submitted successfully, then the server side script - // may have decided to tell us the parent window to close the popup dialog. + // may have decided to tell the parent window to close the popup dialog. if (settings.closeOverlay) { parent.Drupal.overlay.bindChild(window, true); // Use setTimeout to close the child window from a separate thread, // because the current one is busy processing Drupal behaviors. setTimeout(function () { - // We need to store the parent variable locally because it will - // disappear as soon as we close the iframe. - var p = parent; - p.Drupal.overlay.close(); if (typeof settings.redirect == 'string') { - p.Drupal.overlay.redirect(settings.redirect); + parent.Drupal.overlay.redirect(settings.redirect); + } + else { + parent.Drupal.overlay.close(); } }, 1); return; } // If one of the regions displaying outside the overlay needs to be - // reloaded, let the parent window know. + // reloaded immediately, let the parent window know. if (settings.refreshRegions) { parent.Drupal.overlay.refreshRegions(settings.refreshRegions); } @@ -54,11 +52,23 @@ Drupal.behaviors.overlayChild = { // Ok, now we can tell the parent window we're ready. parent.Drupal.overlay.bindChild(window); + // IE8 crashes on certain pages if this isn't called; reason unknown. + window.scrollTo(window.scrollX, window.scrollY); + // Attach child related behaviors to the iframe document. - self.attachBehaviors(context, settings); + Drupal.overlayChild.attachBehaviors(context, settings); } }; +/** + * Overlay object for child windows. + */ +Drupal.overlayChild = Drupal.overlayChild || { + behaviors: {} +}; + +Drupal.overlayChild.prototype = {}; + /** * Attach child related behaviors to the iframe document. */ @@ -68,16 +78,6 @@ Drupal.overlayChild.attachBehaviors = function (context, settings) { }); }; -/** - * Scroll to the top of the page. - * - * This makes the overlay visible to users even if it is not as tall as the - * previously shown overlay was. - */ -Drupal.overlayChild.behaviors.scrollToTop = function (context, settings) { - window.scrollTo(0, 0); -}; - /** * Capture and handle clicks. * @@ -86,7 +86,7 @@ Drupal.overlayChild.behaviors.scrollToTop = function (context, settings) { * to bind their own handlers to links and also to prevent overlay's handling. */ Drupal.overlayChild.behaviors.addClickHandler = function (context, settings) { - $(document).bind('click.overlay-event', parent.Drupal.overlay.clickHandler); + $(document).bind('click.drupal-overlay mouseup.drupal-overlay', $.proxy(parent.Drupal.overlay, 'eventhandlerOverrideLink')); }; /** @@ -111,4 +111,70 @@ Drupal.overlayChild.behaviors.parseForms = function (context, settings) { }); }; +/** + * Replace the overlay title with a message while loading another page. + */ +Drupal.overlayChild.behaviors.loading = function (context, settings) { + var $title; + var text = Drupal.t('Loading'); + var dots = ''; + + $(document).bind('drupalOverlayBeforeLoad.drupal-overlay.drupal-overlay-child-loading', function () { + $title = $('#overlay-title').text(text); + var id = setInterval(function () { + dots = (dots.length > 10) ? '' : dots + '.'; + $title.text(text + dots); + }, 500); + }); +}; + +/** + * Switch active tab immediately. + */ +Drupal.overlayChild.behaviors.tabs = function (context, settings) { + var $tabsLinks = $('#overlay-tabs > li > a'); + + $('#overlay-tabs > li > a').bind('click.drupal-overlay', function () { + var active_tab = Drupal.t('(active tab)'); + $tabsLinks.parent().siblings().removeClass('active').find('element-invisible:contains(' + active_tab + ')').appendTo(this); + $(this).parent().addClass('active'); + }); +}; + +/** + * If the shortcut add/delete button exists, move it to the overlay titlebar. + */ +Drupal.overlayChild.behaviors.shortcutAddLink = function (context, settings) { + // Remove any existing shortcut button markup from the titlebar. + $('#overlay-titlebar').find('.add-or-remove-shortcuts').remove(); + // If the shortcut add/delete button exists, move it to the titlebar. + var $addToShortcuts = $('.add-or-remove-shortcuts'); + if ($addToShortcuts.length) { + $addToShortcuts.insertAfter('#overlay-title'); + } + + $(document).bind('drupalOverlayBeforeLoad.drupal-overlay.drupal-overlay-child-loading', function () { + $('#overlay-titlebar').find('.add-or-remove-shortcuts').remove(); + }); +}; + +/** + * Use displacement from parent window. + */ +Drupal.overlayChild.behaviors.alterTableHeaderOffset = function (context, settings) { + if (Drupal.settings.tableHeaderOffset) { + Drupal.overlayChild.prevTableHeaderOffset = Drupal.settings.tableHeaderOffset; + } + Drupal.settings.tableHeaderOffset = 'Drupal.overlayChild.tableHeaderOffset'; +}; + +/** + * Callback for Drupal.settings.tableHeaderOffset. + */ +Drupal.overlayChild.tableHeaderOffset = function () { + var topOffset = Drupal.overlayChild.prevTableHeaderOffset ? eval(Drupal.overlayChild.prevTableHeaderOffset + '()') : 0; + + return topOffset + parseInt($(document.body).css('marginTop')); +}; + })(jQuery); diff --git a/modules/overlay/overlay-parent.css b/modules/overlay/overlay-parent.css index a2005da3..c72d26cb 100644 --- a/modules/overlay/overlay-parent.css +++ b/modules/overlay/overlay-parent.css @@ -1,165 +1,48 @@ -/* $Id: overlay-parent.css,v 1.14 2010/04/28 20:08:39 dries Exp $ */ +/* $Id: overlay-parent.css,v 1.15 2010/06/08 05:16:29 webchick Exp $ */ -/** - * ui-dialog overlay. - */ -.ui-widget-overlay { - opacity: 1; - filter: none; - /* Using a transparent png renders faster than using opacity */ - background: transparent url(images/background.png) repeat; -} - -body.overlay-autofit { - overflow-y: scroll; +html.overlay-open, +html.overlay-open body { + height: 100%; + overflow: hidden; } -/** - * Overlay wrapper. - */ -#overlay-wrapper { - position: absolute; - top: 0; +#overlay-container, +.overlay-modal-background, +.overlay-element { + height: 100%; left: 0; + position: fixed; + top: 0; width: 100%; - z-index: 501; - padding: 20px 0 15px 0; -} - -/** - * jQuery UI Dialog classes. - */ -.overlay { - position: static; - padding-right: 26px; - margin: 0 auto; - width: 88%; - min-width: 700px; - min-height: 100px; + z-index: 500; } -.overlay.ui-widget-content, -.overlay .ui-widget-header { - background: none; - border: none; -} - -.overlay .ui-dialog-titlebar { - white-space: nowrap; - padding: 0 20px; +.overlay-modal-background { + /* Using a transparent png renders faster than using opacity */ + background: transparent url(images/background.png) repeat; } -.overlay .ui-dialog-title { - margin: 0; - padding: 0.3em 0; - color: #fff; - font-size: 20px; -} -.overlay .ui-dialog-title:active, -.overlay .ui-dialog-title:focus { - outline: 0; -} -.overlay .ui-dialog-titlebar-close, -.overlay .ui-dialog-titlebar-close:hover { - display: block; - right: -25px; - top: 100%; - margin: 0; - border: none; - padding: 0; - width: 26px; - height: 36px; - background: transparent url(images/close.png) no-repeat; - border-top-left-radius: 0; - -moz-border-radius-topleft: 0; - -webkit-border-top-left-radius: 0; -} -.overlay .ui-dialog-titlebar-close span { - display: none; +.overlay-element { + background: transparent; + left: -200%; + z-index: 501; } -.overlay .ui-dialog-content { - color: #292929; - background-color: #f8f8f8; +.overlay-element.overlay-active { + left: 0; } -/** - * Overlay content. - */ -.overlay #overlay-container { - margin: 0; - padding: 0; - width: 100%; - overflow: visible; - background: #fff; -} -.overlay #overlay-element { - overflow: hidden; - width: 100%; - height: 100%; +html.overlay-open .displace-top, +html.overlay-open .displace-bottom { + z-index: 600; } /** - * Tabs on the overlay. + * IE6 shows elements with position:fixed as position:static so replace + * it with position:absolute; */ -.overlay .ui-dialog-titlebar ul { +* html #overlay-container, +* html .overlay-modal-background, +* html .overlay-element +{ position: absolute; - right: 20px; - bottom: 0; - margin: 0; - line-height: 27px; - text-transform: uppercase; -} -.overlay .ui-dialog-titlebar ul li { - display: inline; - list-style: none; - margin: 0 0 0 -3px; - padding: 0; -} - -.overlay .ui-dialog-titlebar ul li a, -.overlay .ui-dialog-titlebar ul li a:active, -.overlay .ui-dialog-titlebar ul li a:visited, -.overlay .ui-dialog-titlebar ul li a:hover { - display: inline-block; - background-color: #a6a7a2; - border-radius: 8px 8px 0 0; - -moz-border-radius: 8px 8px 0 0; - -webkit-border-top-left-radius: 8px; - -webkit-border-top-right-radius: 8px; - color: #000; - font-weight: bold; - padding: 0 14px; - text-decoration: none; - font-size: 11px; - margin: 0; -} -.overlay .ui-dialog-titlebar ul li.active a, -.overlay .ui-dialog-titlebar ul li.active a.active, -.overlay .ui-dialog-titlebar ul li.active a:active, -.overlay .ui-dialog-titlebar ul li.active a:visited { - background-color: #fff; - margin: 0; -} -.overlay .ui-dialog-titlebar ul li a:hover { - color: #fff; -} -.overlay .ui-dialog-titlebar ul li.active a:hover { - color: #000; -} - -/** - * Add to shortcuts link - */ -.overlay div.add-or-remove-shortcuts { - padding-top: 0.9em; -} - -/** - * IE 6 Fix. - * - * Use filter to support transparency in IE6 for the overlay background. - */ -* html .ui-widget-overlay { - filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='modules/overlay/images/background.png', sizingMethod='scale'); - background: none; } diff --git a/modules/overlay/overlay-parent.js b/modules/overlay/overlay-parent.js index 5d16c407..f0a7444a 100644 --- a/modules/overlay/overlay-parent.js +++ b/modules/overlay/overlay-parent.js @@ -1,31 +1,30 @@ -// $Id: overlay-parent.js,v 1.41 2010/05/14 07:45:54 dries Exp $ +// $Id: overlay-parent.js,v 1.49 2010/07/08 12:20:23 dries Exp $ (function ($) { /** * Open the overlay, or load content into it, when an admin link is clicked. - * - * http://docs.jquery.com/Namespaced_Events */ Drupal.behaviors.overlayParent = { attach: function (context, settings) { - // Make sure the onhashchange handling below is only processed once. if (this.processed) { return; } this.processed = true; - // Bind event handlers to the parent window. $(window) - // When the hash (URL fragment) changes, open the overlay if needed. - .bind('hashchange.overlay-event', Drupal.overlay.hashchangeHandler) - // Trigger the hashchange event once, after the page is loaded, so that - // permalinks open the overlay. - .trigger('hashchange.overlay-event'); - // Instead of binding a click event handler to every link we bind one to the - // document and only handle events that bubble up. This allows other scripts - // to bind their own handlers to links and also to prevent overlay's handling. - $(document).bind('click.overlay-event', Drupal.overlay.clickHandler); + // When the hash (URL fragment) changes, open the overlay if needed. + .bind('hashchange.drupal-overlay', $.proxy(Drupal.overlay, 'eventhandlerOperateByURLFragment')) + // Trigger the hashchange handler once, after the page is loaded, so that + // permalinks open the overlay. + .triggerHandler('hashchange.drupal-overlay'); + + $(document) + // Instead of binding a click event handler to every link we bind one to + // the document and only handle events that bubble up. This allows other + // scripts to bind their own handlers to links and also to prevent + // overlay's handling. + .bind('click.drupal-overlay mouseup.drupal-overlay', $.proxy(Drupal.overlay, 'eventhandlerOverrideLink')); } }; @@ -40,228 +39,94 @@ Drupal.behaviors.overlayParent = { * - 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. + * - drupalOverlayReady: This event is triggered when the DOM of the overlay + * child document is fully loaded. * - drupalOverlayLoad: This event is triggered when the overlay is finished * loading. + * - drupalOverlayResize: This event is triggered when the overlay is being + * resized to match the parent window. */ Drupal.overlay = Drupal.overlay || { - options: {}, isOpen: false, isOpening: false, isClosing: false, - isLoading: false, - - resizeTimeoutID: null, - lastHeight: 0, - - $wrapper: null, - $dialog: null, - $dialogTitlebar: null, - $container: null, - $iframe: null, - - $iframeWindow: null, - $iframeDocument: null, - $iframeBody: null + isLoading: false }; +Drupal.overlay.prototype = {}; + /** - * Open an overlay. - * - * Ensure that only one overlay is opened ever. Use Drupal.overlay.load() if - * the overlay is already open but a new page needs to be opened. + * Open the overlay. * - * @param options - * Properties of the overlay to open: - * - url: the URL of the page to open in the overlay. - * - width: width of the overlay in pixels. - * - height: height of the overlay in pixels. - * - autoFit: boolean indicating whether the overlay should be resized to - * fit the contents of the document loaded. - * - customDialogOptions: an object with custom jQuery UI Dialog options. + * @param url + * The URL of the page to open in the overlay. * * @return - * If the overlay was opened true, otherwise false. + * TRUE if the overlay was opened, FALSE otherwise. */ -Drupal.overlay.open = function (options) { - var self = this; - +Drupal.overlay.open = function (url) { // Just one overlay is allowed. - if (self.isOpen || self.isOpening) { - return false; + if (this.isOpen || this.isOpening) { + return this.load(url); } - self.isOpening = true; - - var defaultOptions = { - url: options.url, - width: options.width, - height: options.height, - autoFit: (options.autoFit == undefined || options.autoFit), - customDialogOptions: options.customDialogOptions || {} - }; - - self.options = $.extend(defaultOptions, options); + this.isOpening = true; // Create the dialog and related DOM elements. - self.create(); + this.create(); - // Open the dialog. - self.$container.dialog('open'); + this.isOpening = false; + this.isOpen = true; + $(document.documentElement).addClass('overlay-open'); // Allow other scripts to respond to this event. $(document).trigger('drupalOverlayOpen'); - return true; + return this.load(url); }; /** * Create the underlying markup and behaviors for the overlay. - * - * Reuses jQuery UI's dialog component to construct the overlay markup and - * behaviors, sanitizing the options previously set in self.options. */ Drupal.overlay.create = function () { - var self = this; - var $window = $(window); - var $body = $('body'); - - var delayedOuterResize = function() { - setTimeout(self.outerResize, 1); - }; - - // Open callback for jQuery UI Dialog. - var dialogOpen = function () { - // Unbind the keypress handler installed by ui.dialog itself. - // IE does not fire keypress events for some non-alphanumeric keys - // such as the tab character. http://www.quirksmode.org/js/keys.html - // Also, this is not necessary here because we need to deal with an - // iframe element that contains a separate window. - // We'll try to provide our own behavior from bindChild() method. - self.$dialog.unbind('keypress.ui-dialog'); - - // Add title to close button features for accessibility. - self.$dialogTitlebar.find('.ui-dialog-titlebar-close').attr('title', Drupal.t('Close')); - - // Replace the title span element with an h1 element for accessibility. - var $dialogTitle = self.$dialogTitlebar.find('.ui-dialog-title'); - $dialogTitle.replaceWith(Drupal.theme('overlayTitleHeader', $dialogTitle.html())); - - // Wrap the dialog into a div so we can center it using CSS. - self.$dialog.wrap(Drupal.theme('overlayWrapper')); - self.$wrapper = self.$dialog.parent(); - - self.$dialog.css({ - // Remove some CSS properties added by ui.dialog itself. - position: '', left: '', top: '', height: '' - }); - - // Add a class to the body to indicate the overlay is open. - $body.addClass('overlay-open'); - - // Adjust overlay size when window is resized. - $window.bind('resize.overlay-event', delayedOuterResize); - - if (self.options.autoFit) { - $body.addClass('overlay-autofit'); - } - else { - // Add scrollbar to the iframe when autoFit is disabled. - self.$iframe.css('overflow', 'auto').attr('scrolling', 'yes'); - } - - // Compute initial dialog size. - self.outerResize(); - - // Load the document on hidden iframe (see bindChild method). - self.load(self.options.url); - - if ($.isFunction(self.options.onOverlayOpen)) { - self.options.onOverlayOpen(self); - } - - self.isOpen = true; - self.isOpening = false; - }; - - // Before close callback for jQuery UI Dialog. - var dialogBeforeClose = function () { - // Prevent double execution when close is requested more than once. - if (!self.isOpen || self.isClosing) { - return 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; - } - - self.isClosing = true; - - // Stop all animations. - $window.unbind('resize.overlay-event', delayedOuterResize); - clearTimeout(self.resizeTimeoutID); - }; - - // Close callback for jQuery UI Dialog. - var dialogClose = function () { - $(document).unbind('keydown.overlay-event'); - - $body.removeClass('overlay-open').removeClass('overlay-autofit'); - - // When the iframe is still loading don't destroy it immediately but after - // the content is loaded (see self.load). - if (!self.isLoading) { - // As some browsers (webkit) fire a load event when the iframe is removed, - // load handlers need to be unbound before removing the iframe. - self.$iframe.unbind('load.overlay-event'); - self.destroy(); - } - - self.isOpen = false; - self.isClosing = false; - - self.lastHeight = 0; - - // Allow other scripts to respond to this event. - $(document).trigger('drupalOverlayClose'); - }; - - // Default jQuery UI Dialog options. - var dialogOptions = { - autoOpen: false, - closeOnEscape: true, - dialogClass: 'overlay', - draggable: false, - modal: true, - resizable: false, - title: Drupal.t('Loading...'), - zIndex: 500, - - // When the width is not set, use an empty string instead, so that CSS will - // be able to handle it. - width: self.options.width || '', - height: self.options.height, - - open: dialogOpen, - beforeclose: dialogBeforeClose, - close: dialogClose - }; - - // Create the overlay container and iframe. - self.$iframe = $(Drupal.theme('overlayElement')); - self.$container = $(Drupal.theme('overlayContainer')).append(self.$iframe); - - // Allow external script to override the default jQuery UI Dialog options. - $.extend(dialogOptions, self.options.customDialogOptions); - - // Create the jQuery UI Dialog. - self.$container.dialog(dialogOptions); - // Cache dialog selector. - self.$dialog = self.$container.parents('.' + dialogOptions.dialogClass); - self.$dialogTitlebar = self.$dialog.find('.ui-dialog-titlebar'); + this.$container = $(Drupal.theme('overlayContainer')) + .appendTo(document.body); + + // Overlay uses transparent iframes that cover the full parent window. + // When the overlay is open the scrollbar of the parent window is hidden. + // Because some browsers show a white iframe background for a short moment + // while loading a page into an iframe, overlay uses two iframes. By loading + // the page in a hidden (inactive) iframe the user doesn't see the white + // background. When the page is loaded the active and inactive iframes + // are switched. + this.activeFrame = this.$iframeA = $(Drupal.theme('overlayElement')) + .appendTo(this.$container); + + this.inactiveFrame = this.$iframeB = $(Drupal.theme('overlayElement')) + .appendTo(this.$container); + + this.$iframeA.bind('load.drupal-overlay', { self: this.$iframeA[0], sibling: this.$iframeB }, $.proxy(this, 'loadChild')); + this.$iframeB.bind('load.drupal-overlay', { self: this.$iframeB[0], sibling: this.$iframeA }, $.proxy(this, 'loadChild')); + + // Add a second class "drupal-overlay-open" to indicate these event handlers + // should only be bound when the overlay is open. + var eventClass = '.drupal-overlay.drupal-overlay-open'; + $(window) + .bind('resize' + eventClass, $.proxy(this, 'eventhandlerOuterResize')); + $(document) + .bind('drupalOverlayLoad' + eventClass, $.proxy(this, 'eventhandlerOuterResize')) + .bind('drupalOverlayReady' + eventClass + + ' drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerSyncURLFragment')) + .bind('drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerRefreshPage')) + .bind('drupalOverlayBeforeClose' + eventClass + + ' drupalOverlayBeforeLoad' + eventClass + + ' drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerDispatchEvent')) + .bind('keydown' + eventClass, $.proxy(this, 'eventhandlerRestrictKeyboardNavigation')); + + if ($('.overlay-displace-top, .overlay-displace-bottom').length) { + $(document) + .bind('drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerAlterDisplacedElements')) + .bind('drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerRestoreDisplacedElements')); + } }; /** @@ -269,318 +134,163 @@ Drupal.overlay.create = function () { * * Use this method to change the URL being loaded in the overlay if it is * already open. + * + * @return + * TRUE if URL is loaded into the overlay, FALSE otherwise. */ Drupal.overlay.load = function (url) { - var self = this; - var iframeElement = self.$iframe.get(0); - - self.isLoading = true; - - // Change the overlay title. - self.$container.dialog('option', 'title', Drupal.t('Loading...')); - // Remove any existing shortcut button markup in the title section. - self.$dialogTitlebar.find('.add-or-remove-shortcuts').remove(); - - // Remove any existing tabs in the title section, but only if requested url - // is not one of those tabs. If the latter, set that tab active. Only check - // for tabs when the overlay is not empty. - if (self.$iframeBody) { - var urlPath = self.getPath(url); - - // Get the primary tabs - var $tabs = self.$dialogTitlebar.find('ul'); - var $tabsLinks = $tabs.find('> li > a'); - - // Check if clicked on a primary tab - var $activeLink = $tabsLinks.filter(function () { return self.getPath(this) == urlPath; }); - - if ($activeLink.length) { - var active_tab = Drupal.t('(active tab)'); - $tabsLinks.parent().removeClass('active').find('element-invisible:contains(' + active_tab + ')').appendTo($activeLink); - $activeLink.parent().addClass('active'); - removeTabs = false; - } - else { - // Get the secondary tabs - var $secondary = self.$iframeBody.find('ul.secondary'); - var $secondaryLinks = $secondary.find('> li > a'); - - // Check if clicked on a secondary tab - var $activeLinkSecondary = $secondaryLinks.filter(function () { return self.getPath(this) == urlPath; }); - - if ($activeLinkSecondary.length) { - var active_tab = Drupal.t('(active tab)'); - $secondaryLinks.parent().removeClass('active').find('element-invisible:contains(' + active_tab + ')').appendTo($activeLinkSecondary); - $activeLinkSecondary.parent().addClass('active'); - removeTabs = false; - } - else { - $tabs.remove(); - } - } + if (!this.isOpen) { + return false; } - self.$iframeWindow = null; - self.$iframeDocument = null; - self.$iframeBody = null; - - // 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.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(); - } - }); + $(document.documentElement).addClass('overlay-loading'); - // Get the document object of the iframe window. - // See http://xkr.us/articles/dom/iframe-document/. - var iframeDocument = (iframeElement.contentWindow || iframeElement.contentDocument); - if (iframeDocument.document) { - iframeDocument = iframeDocument.document; - } + // The contentDocument property is not supported in IE until IE8. + var iframeDocument = this.inactiveFrame[0].contentDocument || this.inactiveFrame[0].contentWindow.document; // location.replace doesn't create a history entry. location.href does. // In this case, we want location.replace, as we're creating the history // entry using URL fragments. iframeDocument.location.replace(url); + + // Immediately move the focus to the iframe. + this.inactiveFrame.focus(); + return true; }; /** * Close the overlay and remove markup related to it from the document. + * + * @return + * TRUE if the overlay was closed, FALSE otherwise. */ Drupal.overlay.close = function () { - return this.$container.dialog('close'); + // Prevent double execution when close is requested more than once. + if (!this.isOpen || this.isClosing) { + return false; + } + + // Allow other scripts to respond to this event. + var event = $.Event('drupalOverlayBeforeClose'); + $(document).trigger(event); + // If a handler returned false, the close will be prevented. + if (event.isDefaultPrevented()) { + return false; + } + + this.isClosing = true; + this.isOpen = false; + $(document.documentElement).removeClass('overlay-open'); + + // Allow other scripts to respond to this event. + $(document).trigger('drupalOverlayClose'); + + // When the iframe is still loading don't destroy it immediately but after + // the content is loaded (see Drupal.overlay.loadChild). + if (!this.isLoading) { + this.destroy(); + this.isClosing = false; + } + return true; }; /** * Destroy the overlay. */ Drupal.overlay.destroy = function () { - var self = this; - - self.$container.dialog('destroy').remove(); - self.$wrapper.remove(); + $([document, window]).unbind('.drupal-overlay-open'); + this.$iframeA.unbind('.drupal-overlay'); + this.$iframeB.unbind('.drupal-overlay'); + this.$container.remove(); - self.$wrapper = null; - self.$dialog = null; - self.$dialogTitlebar = null; - self.$container = null; - self.$iframe = null; + this.$container = null; + this.$iframeA = null; + this.$iframeB = null; - self.$iframeWindow = null; - self.$iframeDocument = null; - self.$iframeBody = null; + this.iframeWindow = null; }; /** * Redirect the overlay parent window to the given URL. * - * @param link + * @param url * Can be an absolute URL or a relative link to the domain root. */ -Drupal.overlay.redirect = function (link) { - if (link.indexOf('http') != 0 && link.indexOf('https') != 0) { - var absolute = location.href.match(/https?:\/\/[^\/]*/)[0]; - link = absolute + link; - } +Drupal.overlay.redirect = function (url) { + // Create a native Link object, so we can use its object methods. + var link = $(url.link(url)).get(0); // If the link is already open, force the hashchange event to simulate reload. - if (location.href == link) { - $(window).trigger('hashchange.overlay-event'); + if (window.location.href == link.href) { + $(window).triggerHandler('hashchange.drupal-overlay'); } - location.href = link; + window.location.href = link.href; return true; }; /** * Bind the child window. * - * Add tabs on the overlay, keyboard actions and display animation. + * Note that this function is fired earlier than Drupal.overlay.loadChild. */ Drupal.overlay.bindChild = function (iframeWindow, isClosing) { - var self = this; - self.$iframeWindow = iframeWindow.jQuery; - self.$iframeDocument = self.$iframeWindow(iframeWindow.document); - self.$iframeBody = self.$iframeWindow('body'); + this.iframeWindow = iframeWindow; // We are done if the child window is closing. - if (isClosing || self.isClosing || !self.isOpen) { + if (isClosing || this.isClosing || !this.isOpen) { return; } - // Make sure the parent window URL matches the child window URL. - self.syncChildLocation(iframeWindow.document.location); - - // Unbind the mousedown handler installed by ui.dialog because the - // handler interferes with use of the scroll bar in Chrome & Safari. - // After unbinding from the document we bind a handler to the dialog overlay - // which returns false to prevent event bubbling. - // See http://dev.jqueryui.com/ticket/4671. - // See https://bugs.webkit.org/show_bug.cgi?id=19033. - // Do the same for the click handler as prevents default handling of clicks in - // displaced regions (e.g. opening a link in a new browser tab when CTRL was - // pressed while clicking). - $(document).unbind('mousedown.dialog-overlay click.dialog-overlay'); - $('.ui-widget-overlay').bind('mousedown.dialog-overlay click.dialog-overlay', function (){return false;}); - - // Unbind the keydown and keypress handlers installed by ui.dialog because - // they interfere with use of browser's keyboard hotkeys like CTRL+w. - // This may cause problems when using modules that implement keydown or - // keypress handlers as they aren't blocked when overlay is open. - $(document).unbind('keydown.dialog-overlay keypress.dialog-overlay'); - - // Reset the scroll to the top of the window so that the overlay is visible again. - window.scrollTo(0, 0); - - var iframeTitle = self.$iframeDocument.attr('title'); - - // Update the dialog title with the child window title. - self.$container.dialog('option', 'title', iframeTitle); - self.$dialogTitlebar.find('.ui-dialog-title').focus(); - // Add a title attribute to the iframe for accessibility. - self.$iframe.attr('title', Drupal.t('@title dialog', { '@title': iframeTitle })); - - // Remove any existing shortcut button markup in the title section. - self.$dialogTitlebar.find('.add-or-remove-shortcuts').remove(); - // If the shortcut add/delete button exists, move it to the dialog title. - var $addToShortcuts = self.$iframeWindow('.add-or-remove-shortcuts'); - if ($addToShortcuts.length) { - // Move the button markup to the title section. We need to copy markup - // instead of moving the DOM element, because Webkit and IE browsers will - // not move DOM elements between two DOM documents. - $addToShortcuts = $(self.$iframeWindow('<div>').append($addToShortcuts).remove().html()); - - self.$dialogTitlebar.find('.ui-dialog-title').after($addToShortcuts); - } - - // Remove any existing tabs in the title section. - self.$dialogTitlebar.find('ul').remove(); - // If there are tabs in the page, move them to the titlebar. - var $tabs = self.$iframeWindow('ul.primary'); - if ($tabs.length) { - // Move the tabs markup to the title section. We need to copy markup - // instead of moving the DOM element, because Webkit and IE browsers will - // not move DOM elements between two DOM documents. - $tabs = $(self.$iframeWindow('<div>').append($tabs).remove().html()); - - self.$dialogTitlebar.append($tabs); - - // Remove any classes from the list element to avoid theme styles - // clashing with our styling. - $tabs.removeAttr('class'); - } - - // Re-attach the behaviors we lost while copying elements from the iframe - // document to the parent document. - Drupal.attachBehaviors(self.$dialogTitlebar); - - // Try to enhance keyboard based navigation of the overlay. - // Logic inspired by the open() method in ui.dialog.js, and - // http://wiki.codetalks.org/wiki/index.php/Docs/Keyboard_navigable_JS_widgets - - // Get a reference to the close button. - var $closeButton = self.$dialogTitlebar.find('.ui-dialog-titlebar-close'); - - // Search tabbable elements on the iframed document to speed up related - // keyboard events. - // @todo: Do we need to provide a method to update these references when - // AJAX requests update the DOM on the child document? - var $iframeTabbables = self.$iframeWindow(':tabbable:not(form)'); - var $firstTabbable = $iframeTabbables.filter(':first'); - var $lastTabbable = $iframeTabbables.filter(':last'); - - // Unbind keyboard event handlers that may have been enabled previously. - $(document).unbind('keydown.overlay-event'); - $closeButton.unbind('keydown.overlay-event'); - - // When the focus leaves the close button, then we want to jump to the - // first/last inner tabbable element of the child window. - $closeButton.bind('keydown.overlay-event', function (event) { - if (event.keyCode && event.keyCode == $.ui.keyCode.TAB) { - var $target = (event.shiftKey ? $lastTabbable : $firstTabbable); - if (!$target.size()) { - $target = self.$iframeDocument; - } - $target.focus(); - return false; - } - }); + // Allow other scripts to respond to this event. + $(document).trigger('drupalOverlayReady'); +}; - // When the focus leaves the child window, then drive the focus to the - // close button of the dialog. - self.$iframeDocument.bind('keydown.overlay-event', function (event) { - if (event.keyCode) { - if (event.keyCode == $.ui.keyCode.TAB) { - if (event.shiftKey && event.target == $firstTabbable.get(0)) { - $closeButton.focus(); - return false; - } - else if (!event.shiftKey && event.target == $lastTabbable.get(0)) { - $closeButton.focus(); - return false; - } - } - else if (event.keyCode == $.ui.keyCode.ESCAPE) { - self.close(); - return false; - } - } - }); +/** + * Event handler: load event handler for the overlay iframe. + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: load + * - event.currentTarget: iframe + */ +Drupal.overlay.loadChild = function (event) { + var iframe = event.data.self; + var iframeDocument = iframe.contentDocument || iframe.contentWindow.document; + var iframeWindow = iframeDocument.defaultView || iframeDocument.parentWindow; + if (iframeWindow.location == 'about:blank') { + return; + } - // When the focus is captured by the parent document, then try - // to drive the focus back to the first tabbable element, or the - // close button of the dialog (default). - $(document).bind('keydown.overlay-event', function (event) { - if (event.keyCode && event.keyCode == $.ui.keyCode.TAB) { - if (!self.$iframeWindow(':tabbable:not(form):first').focus().size()) { - $closeButton.focus(); - } - return false; + this.isLoading = false; + $(document.documentElement).removeClass('overlay-loading'); + event.data.sibling.removeClass('overlay-active'); + + // Only continue when overlay is still open and not closing. + if (this.isOpen && !this.isClosing) { + // And child document is an actual overlayChild. + if (iframeWindow.Drupal && iframeWindow.Drupal.overlayChild) { + this.activeFrame = $(iframe) + .addClass('overlay-active') + // Add a title attribute to the iframe for accessibility. + .attr('title', Drupal.t('@title dialog', { '@title': iframeWindow.jQuery('#overlay-title').text() })); + this.inactiveFrame = event.data.sibling; + + // Load an empty document into the inactive iframe. + (this.inactiveFrame[0].contentDocument || this.inactiveFrame[0].contentWindow.document).location.replace('about:blank'); + + // Allow other scripts to respond to this event. + $(document).trigger('drupalOverlayLoad'); + } + else { + window.location = iframeWindow.location.href.replace(/([?&]?)render=overlay&?/g, '$1').replace(/\?$/, ''); } - }); - - // Adjust overlay to fit the iframe content? - if (self.options.autoFit) { - self.innerResize(); - - var delayedResize = function() { - if (!self.isOpen) { - clearTimeout(self.resizeTimeoutID); - return; - } - - self.innerResize(); - iframeWindow.scrollTo(0, 0); - self.resizeTimeoutID = setTimeout(delayedResize, 150); - }; - - clearTimeout(self.resizeTimeoutID); - self.resizeTimeoutID = setTimeout(delayedResize, 150); } - - // Scroll to anchor in overlay. This needs to be done after delayedResize(). - if (iframeWindow.document.location.hash) { - window.scrollTo(0, self.$iframeWindow(iframeWindow.document.location.hash).position().top); + else { + this.destroy(); } }; @@ -589,126 +299,150 @@ Drupal.overlay.bindChild = function (iframeWindow, isClosing) { * * @param url * The url to be tested. + * * @return boolean * TRUE if the URL represents an administrative link, FALSE otherwise. */ Drupal.overlay.isAdminLink = function (url) { - var self = this; - var path = self.getPath(url); + var path = this.getPath(url); // Turn the list of administrative paths into a regular expression. - if (!self.adminPathRegExp) { + if (!this.adminPathRegExp) { var adminPaths = '^(' + Drupal.settings.overlay.paths.admin.replace(/\s+/g, ')$|^(') + ')$'; var nonAdminPaths = '^(' + Drupal.settings.overlay.paths.non_admin.replace(/\s+/g, ')$|^(') + ')$'; adminPaths = adminPaths.replace(/\*/g, '.*'); nonAdminPaths = nonAdminPaths.replace(/\*/g, '.*'); - self.adminPathRegExp = new RegExp(adminPaths); - self.nonAdminPathRegExp = new RegExp(nonAdminPaths); + this.adminPathRegExp = new RegExp(adminPaths); + this.nonAdminPathRegExp = new RegExp(nonAdminPaths); } - return self.adminPathRegExp.exec(path) && !self.nonAdminPathRegExp.exec(path); + return this.adminPathRegExp.exec(path) && !this.nonAdminPathRegExp.exec(path); }; /** - * Resize overlay according to the size of its content. + * Event handler: resizes overlay according to the size of the parent window. * - * @todo: Watch for experience in the way we compute the size of the iframed - * document. There are many ways to do it, and none of them seem to be perfect. - * Note, though, that the size of the iframe itself may affect the size of the - * child document, especially on fluid layouts. + * @param event + * Event being triggered, with the following restrictions: + * - event.type: any + * - event.currentTarget: any */ -Drupal.overlay.innerResize = function (height) { - var self = Drupal.overlay; - // Proceed only if the dialog still exists. - if (!self.isOpen || self.isClosing) { +Drupal.overlay.eventhandlerOuterResize = function (event) { + // Proceed only if the overlay still exists. + if (!(this.isOpen || this.isOpening) || this.isClosing || !this.iframeWindow) { return; } - // When no height is given try to get height when iframe content is loaded. - if (!height && self.$iframeBody) { - height = self.$iframeBody.outerHeight() + 25; + // IE6 uses position:absolute instead of position:fixed. + if (typeof document.body.style.maxHeight != 'string') { + this.activeFrame.height($(window).height()); } - // Only resize when height actually is changed. - if (height && height != self.lastHeight) { - // Resize the container. - self.$container.height(height); - // Keep the dim background grow or shrink with the dialog. - $.ui.dialog.overlay.resize(); - - self.lastHeight = height; - } + // Allow other scripts to respond to this event. + $(document).trigger('drupalOverlayResize'); }; /** - * Resize overlay according to the size of the parent window. + * Event handler: resizes displaced elements so they won't overlap the scrollbar + * of overlay's iframe. + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: any + * - event.currentTarget: any */ -Drupal.overlay.outerResize = function () { - var self = Drupal.overlay; - // Proceed only if the dialog still exists. - if (!(self.isOpen || self.isOpening) || self.isClosing) { +Drupal.overlay.eventhandlerAlterDisplacedElements = function (event) { + // Proceed only if the overlay still exists. + if (!(this.isOpen || this.isOpening) || this.isClosing || !this.iframeWindow) { return; } - var displaceTop = Drupal.displace ? Drupal.displace.getDisplacement('top') : 0; - - 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. - if (!self.lastHeight) { - var titleBarHeight = self.$dialogTitlebar.outerHeight(true); + $(this.iframeWindow.document.body).css({ + marginTop: Drupal.overlay.getDisplacement('top'), + marginBottom: Drupal.overlay.getDisplacement('bottom') + }) + // IE7 isn't reflowing the document immediately. + // @todo This might be fixed in a cleaner way. + .addClass('overlay-trigger-reflow').removeClass('overlay-trigger-reflow'); + + var documentHeight = this.iframeWindow.document.body.clientHeight; + var documentWidth = this.iframeWindow.document.body.clientWidth; + // IE6 doesn't support maxWidth, use width instead. + var maxWidthName = (typeof document.body.style.maxWidth == 'string') ? 'maxWidth' : 'width'; + + // Consider any element that should be visible above the overlay (such as + // a toolbar). + $('.overlay-displace-top, .overlay-displace-bottom').each(function () { + var data = $(this).data(); + var maxWidth = documentWidth; + // In IE, Shadow filter makes element to overlap the scrollbar with 1px. + if (this.filters && this.filters.length && this.filters.item('DXImageTransform.Microsoft.Shadow')) { + maxWidth -= 1; + } - if (self.options.autoFit || self.options.height == undefined ||!isNan(self.options.height)) { - self.lastHeight = parseInt($(window).height() - displaceTop - titleBarHeight - 45); + // Prevent displaced elements overlapping window's scrollbar. + var currentMaxWidth = parseInt($(this).css(maxWidthName)); + if ((data.drupalOverlay && data.drupalOverlay.maxWidth) || isNaN(currentMaxWidth) || currentMaxWidth > maxWidth || currentMaxWidth <= 0) { + $(this).css(maxWidthName, maxWidth); + (data.drupalOverlay = data.drupalOverlay || {}).maxWidth = true; } - else { - self.lastHeight = self.options.height; + + // Use a more rigorous approach if the displaced element still overlaps + // window's scrollbar; clip the element on the right. + var offset = $(this).offset(); + var offsetRight = offset.left + $(this).outerWidth(); + if ((data.drupalOverlay && data.drupalOverlay.clip) || offsetRight > maxWidth) { + $(this).css('clip', 'rect(auto, ' + (maxWidth - offset.left) + 'px, ' + (documentHeight - offset.top) + 'px, auto)'); + (data.drupalOverlay = data.drupalOverlay || {}).clip = true; } + }); +}; - self.$container.height(self.lastHeight); +/** + * Event handler: restores size of displaced elements as they were before + * overlay was opened. + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: any + * - event.currentTarget: any + */ +Drupal.overlay.eventhandlerRestoreDisplacedElements = function (event) { + var $displacedElements = $('.overlay-displace-top, .overlay-displace-bottom'); + try { + $displacedElements.css({ maxWidth: null, clip: null }); } - - if (self.options.autoFit) { - self.innerResize(); + // IE bug that doesn't allow unsetting style.clip (http://dev.jquery.com/ticket/6512). + catch (err) { + $displacedElements.attr('style', function (index, attr) { + return attr.replace(/clip\s*:\s*rect\([^)]+\);?/i, ''); + }); } - - // Make the dim background grow or shrink with the dialog. - $.ui.dialog.overlay.resize(); }; /** - * Click event handler. + * Event handler: overrides href of administrative links to be opened in + * the overlay. * - * Instead of binding a click event handler to every link we bind one to the - * document and handle events that bubble up. This allows other scripts to bind - * their own handlers to links and also to prevent overlay's handling. - * - * This handler makes links in displaced regions work correctly, even when the - * overlay is open. - * - * This click event handler should be bound any document (for example the + * This click event handler should be bound to any document (for example the * overlay iframe) of which you want links to open in the overlay. * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: click, mouseup + * - event.currentTarget: document + * * @see Drupal.overlayChild.behaviors.addClickHandler */ -Drupal.overlay.clickHandler = function (event) { - var self = Drupal.overlay; - - var $target = $(event.target); - - 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'); - } - - // Only continue by left-click or right-click. - if (!(event.button == 0 || event.button == 2)) { +Drupal.overlay.eventhandlerOverrideLink = function (event) { + // In some browsers the click event isn't fired for right-clicks. Use the + // mouseup event for right-clicks and the click event for everything else. + if ((event.type == 'click' && event.button == 2) || (event.type == 'mouseup' && event.button != 2)) { return; } + var $target = $(event.target); + // Only continue if clicked target (or one of its parents) is a link. if (!$target.is('a')) { $target = $target.closest('a'); @@ -722,30 +456,45 @@ Drupal.overlay.clickHandler = function (event) { return; } - var href = $target.attr('href'); - // Only continue if link has an href attribute and is not just linking to - // an anchor. - if (href != undefined && href != '' && href.charAt(0) != '#') { + // Close the overlay when the link contains the overlay-close class. + if ($target.hasClass('overlay-close')) { + // Clearing the overlay URL fragment will close the overlay. + $.bbq.removeState('overlay'); + return; + } + + var target = $target[0]; + var href = target.href; + // Only handle links that have an href attribute and use the http(s) protocol. + if (href != undefined && href != '' && target.protocol.match(/^https?\:/)) { + var anchor = href.replace(target.ownerDocument.location.href, ''); + // Skip anchor links. + if (anchor.length == 0 || anchor.charAt(0) == '#') { + return; + } // Open admin links in the overlay. - if (self.isAdminLink(href)) { - href = self.fragmentizeLink($target.get(0)); + else if (this.isAdminLink(href)) { + href = this.fragmentizeLink($target.get(0)); // Only override default behavior when left-clicking and user is not // pressing the ALT, CTRL, META (Command key on the Macintosh keyboard) // or SHIFT key. if (event.button == 0 && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey) { // Redirect to a fragmentized href. This will trigger a hashchange event. - self.redirect(href); + this.redirect(href); // Prevent default action and further propagation of the event. return false; } - // Otherwise only alter clicked link's href. This is being picked up by + // Otherwise alter clicked link's href. This is being picked up by // the default action handler. else { - $target.attr('href', href); + $target + // Restore link's href attribute on blur or next click. + .one('blur mousedown', { target: target, href: target.href }, function (event) { $(event.data.target).attr('href', event.data.href); }) + .attr('href', href); } } // Open external links in a new window. - else if ($target.get(0).hostname != window.location.hostname) { + else if (target.hostname != window.location.hostname) { // Add a target attribute to the clicked link. This is being picked up by // the default action handler. if (!$target.attr('target')) { @@ -755,43 +504,31 @@ Drupal.overlay.clickHandler = function (event) { // Non-admin links should close the overlay and open in the main window. // Only handle them if the overlay is open and the clicked link is inside // the overlay iframe, else default action will do fine. - else if (self.isOpen) { - var inFrame = false; - // W3C: http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-UIEvent-view - if (event.view && event.view.frameElement != null) { - inFrame = true; - } - // IE: http://msdn.microsoft.com/en-us/library/ms534331%28VS.85%29.aspx - else if (event.target.ownerDocument.parentWindow && event.target.ownerDocument.parentWindow.frameElement != null) { - inFrame = true; + else if (this.isOpen && target.ownerDocument === this.iframeWindow.document) { + // When the link has a destination query parameter and that destination + // is an admin link we need to fragmentize it. This will make it reopen + // in the overlay. + var params = $.deparam.querystring(href); + if (params.destination && this.isAdminLink(params.destination)) { + var fragmentizedDestination = $.param.fragment(this.getPath(window.location), { overlay: params.destination }); + $target.attr('href', $.param.querystring(href, { destination: fragmentizedDestination })); } - // Add a target attribute to the clicked link. This is being picked up by - // the default action handler. - if (inFrame) { - // When the link has a destination query parameter and that destination - // is an admin link we need to fragmentize it. This will make it reopen - // in the overlay. - var params = $.deparam.querystring(href); - if (params.destination && self.isAdminLink(params.destination)) { - var fragmentizedDestination = $.param.fragment(self.getPath(window.location), { overlay: params.destination }); - href = $.param.querystring(href, { destination: fragmentizedDestination }); - $target.attr('href', href); - } - - // Make the link to be opening in the immediate parent of the frame. - $target.attr('target', '_parent'); - } + // Make the link to be opening in the immediate parent of the frame. + $target.attr('target', '_parent'); } } }; /** - * Open, reload, or close the overlay, based on the current URL fragment. + * Event handler: opens or closes the overlay based on the current URL fragment. + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: hashchange + * - event.currentTarget: document */ -Drupal.overlay.hashchangeHandler = function (event) { - var self = Drupal.overlay; - +Drupal.overlay.eventhandlerOperateByURLFragment = function (event) { // If we changed the hash to reflect an internal redirect in the overlay, // its location has already been changed, so don't do anything. if ($.data(window.location, window.location.href) === 'redirect') { @@ -803,35 +540,115 @@ Drupal.overlay.hashchangeHandler = function (event) { var state = $.bbq.getState('overlay'); if (state) { // Append render variable, so the server side can choose the right - // rendering and add child modal frame code to the page if needed. - var linkURL = Drupal.settings.basePath + state; - linkURL = $.param.querystring(linkURL, {'render': 'overlay'}); + // rendering and add child frame code to the page if needed. + var url = $.param.querystring(Drupal.settings.basePath + state, { render: 'overlay' }); - var path = self.getPath(state); - self.resetActiveClass(path); + this.open(url); + this.resetActiveClass(this.getPath(Drupal.settings.basePath + state)); + } + // If there is no overlay URL in the fragment and the overlay is (still) + // open, close the overlay. + else if (this.isOpen && !this.isClosing) { + this.close(); + this.resetActiveClass(this.getPath(window.location)); + } +}; - // If the modal frame is already open, replace the loaded document with - // this new one. - if (self.isOpen) { - self.load(linkURL); +/** + * Event handler: makes sure the internal overlay URL is reflected in the parent + * URL fragment. + * + * Normally the parent URL fragment determines the overlay location. However, if + * the overlay redirects internally, the parent doesn't get informed, and the + * parent URL fragment will be out of date. This is a sanity check to make + * sure we're in the right place. + * + * The parent URL fragment is also not updated automatically when overlay's + * open, close or load functions are used directly (instead of through + * eventhandlerOperateByURLFragment). + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: drupalOverlayReady, drupalOverlayClose + * - event.currentTarget: document + */ +Drupal.overlay.eventhandlerSyncURLFragment = function (event) { + if (this.isOpen) { + var expected = $.bbq.getState('overlay'); + // This is just a sanity check, so we're comparing paths, not query strings. + if (this.getPath(Drupal.settings.basePath + expected) != this.getPath(this.iframeWindow.document.location)) { + // There may have been a redirect inside the child overlay window that the + // parent wasn't aware of. Update the parent URL fragment appropriately. + var newLocation = Drupal.overlay.fragmentizeLink(this.iframeWindow.document.location); + // Set a 'redirect' flag on the new location so the hashchange event handler + // knows not to change the overlay's content. + $.data(window.location, newLocation, 'redirect'); + // Use location.replace() so we don't create an extra history entry. + window.location.replace(newLocation); } - else { - // There is not an overlay opened yet; we should open a new one. - var overlayOptions = { - url: linkURL - }; - $(document).one('drupalOverlayClose', function () { - // Clear the overlay URL fragment. - $.bbq.pushState(); - self.resetActiveClass(self.getPath(window.location)); - }); - self.open(overlayOptions); + } + else { + $.bbq.removeState('overlay'); + } +}; + +/** + * Event handler: if the child window suggested that the parent refresh on + * close, force a page refresh. + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: drupalOverlayClose + * - event.currentTarget: document + */ +Drupal.overlay.eventhandlerRefreshPage = function (event) { + if (Drupal.overlay.refreshPage) { + window.location.reload(true); + } +}; + +/** + * Event handler: makes sure that when the overlay is open no elements (except + * for elements inside any displaced elements) of the parent document are + * reachable through keyboard (TAB) navigation. + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: keydown + * - event.currentTarget: document + */ +Drupal.overlay.eventhandlerRestrictKeyboardNavigation = function (event) { + if (!this.$tabbables) { + this.$tabbables = $(':tabbable'); + } + + if (event.keyCode && event.keyCode == $.ui.keyCode.TAB) { + // Whenever the focus is not inside the overlay (or a displaced element) + // move the focus along until it is. + var direction = event.shiftKey ? -1 : 1; + var current = this.$tabbables.index(event.target); + var $allowedParent = '#overlay-container, .overlay-displace-top, .overlay-displace-bottom'; + if (current != -1 && this.$tabbables[current + direction] && !this.$tabbables.eq(current + direction).closest($allowedParent).length) { + while (this.$tabbables[current + direction] && !this.$tabbables.eq(current + direction).closest($allowedParent).length) { + current = current + direction; + } + // Move focus. + this.$tabbables.eq(current).focus(); } } - // If there is no overlay URL in the fragment and the overlay is (still) - // open, close the overlay. - else if (self.isOpen && !self.isClosing) { - self.close(); +}; + +/** + * Event handler: dispatches events to the overlay document. + * + * @param event + * Event being triggered, with the following restrictions: + * - event.type: any + * - event.currentTarget: any + */ +Drupal.overlay.eventhandlerDispatchEvent = function (event) { + if (this.iframeWindow && this.iframeWindow.document) { + this.iframeWindow.jQuery(this.iframeWindow.document).trigger(event); } }; @@ -840,12 +657,12 @@ Drupal.overlay.hashchangeHandler = function (event) { * * @param link * A Javascript Link object (i.e. an <a> element). + * * @return * A URL that will trigger the overlay (in the form * /node/1#overlay=admin/config). */ Drupal.overlay.fragmentizeLink = function (link) { - var self = this; // Don't operate on links that are already overlay-ready. var params = $.deparam.fragment(link.href); if (params.overlay) { @@ -855,41 +672,15 @@ Drupal.overlay.fragmentizeLink = function (link) { // Determine the link's original destination. Set ignorePathFromQueryString to // true to prevent transforming this link into a clean URL while clean URLs // may be disabled. - var path = self.getPath(link, true); - // Preserve existing query and fragment parameters in the URL. - var destination = path + link.search + link.hash; + var path = this.getPath(link, true); + // Preserve existing query and fragment parameters in the URL, except for + // "render=overlay" which is re-added in Drupal.overlay.eventhandlerOperateByURLFragment. + var destination = path + link.search.replace(/&?render=overlay/, '').replace(/\?$/, '') + link.hash; // Assemble and return the overlay-ready link. return $.param.fragment(window.location.href, { overlay: destination }); }; -/** - * Make sure the internal overlay URL is reflected in the parent URL fragment. - * - * Normally the parent URL fragment determines the overlay location. However, if - * the overlay redirects internally, the parent doesn't get informed, and the - * parent URL fragment will be out of date. This is a sanity check to make - * sure we're in the right place. - * - * @param childLocation - * The child window's location object. - */ -Drupal.overlay.syncChildLocation = function (childLocation) { - var expected = $.bbq.getState('overlay'); - // This is just a sanity check, so we're comparing paths, not query strings. - expected = Drupal.settings.basePath + expected.replace(/\?.+/, ''); - var actual = childLocation.pathname; - if (expected !== actual) { - // There may have been a redirect inside the child overlay window that the - // parent wasn't aware of. Update the parent URL fragment appropriately. - var newLocation = Drupal.overlay.fragmentizeLink(childLocation); - // Set a 'redirect' flag on the new location so the hashchange event handler - // knows not to change the overlay's content. - $.data(window.location, newLocation, 'redirect'); - window.location.href = newLocation; - } -}; - /** * Refresh any regions of the page that are displayed outside the overlay. * @@ -905,6 +696,8 @@ Drupal.overlay.refreshRegions = function (data) { $.each(region_info, function (regionClass) { var regionName = region_info[regionClass]; var regionSelector = '.' + regionClass; + // Allow special behaviors to detach. + Drupal.detachBehaviors($(regionSelector)); $.get(Drupal.settings.basePath + Drupal.settings.overlay.ajaxCallback + '/' + regionName, function (newElement) { $(regionSelector).replaceWith($(newElement)); Drupal.attachBehaviors($(regionSelector), Drupal.settings); @@ -914,7 +707,8 @@ Drupal.overlay.refreshRegions = function (data) { }; /** - * Reset the active class on links in displaced regions according to given path. + * Reset the active class on links in displaced elements according to + * given path. * * @param activePath * Path to match links against. @@ -923,16 +717,16 @@ Drupal.overlay.resetActiveClass = function(activePath) { var self = this; var windowDomain = window.location.protocol + window.location.hostname; - $('.displace-top, .displace-bottom') + $('.overlay-displace-top, .overlay-displace-bottom') .find('a[href]') - // Remove active class from all links in displaced regions. + // Remove active class from all links in displaced elements. .removeClass('active') // Add active class to links that match activePath. .each(function () { var linkDomain = this.protocol + this.hostname; var linkPath = self.getPath(this); - if (linkDomain == windowDomain && linkPath == activePath) { + if (linkDomain == windowDomain && activePath.indexOf(linkPath) === 0) { $(this).addClass('active'); } }); @@ -945,8 +739,9 @@ Drupal.overlay.resetActiveClass = function(activePath) { * Link object or string to get the Drupal path from. * @param ignorePathFromQueryString * Boolean whether to ignore path from query string if path appears empty. + * * @return - * The drupal path. + * The Drupal path. */ Drupal.overlay.getPath = function (link, ignorePathFromQueryString) { if (typeof link == 'string') { @@ -959,11 +754,11 @@ Drupal.overlay.getPath = function (link, ignorePathFromQueryString) { if (path.charAt(0) != '/') { path = '/' + path; } - path = path.replace(new RegExp(Drupal.settings.basePath + "(?:index.php)?"), ''); + path = path.replace(new RegExp(Drupal.settings.basePath + '(?:index.php)?'), ''); if (path == '' && !ignorePathFromQueryString) { // If the path appears empty, it might mean the path is represented in the // query string (clean URLs are not used). - var match = new RegExp("([?&])q=(.+)([&#]|$)").exec(link.search); + var match = new RegExp('([?&])q=(.+)([&#]|$)').exec(link.search); if (match && match.length == 4) { path = match[2]; } @@ -973,31 +768,41 @@ Drupal.overlay.getPath = function (link, ignorePathFromQueryString) { }; /** - * Theme function to create the overlay iframe element. + * 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.theme.prototype.overlayElement = function () { - return '<iframe id="overlay-element" frameborder="0" name="overlay-element" scrolling="no" allowtransparency="true"/>'; +Drupal.overlay.getDisplacement = function (region) { + var displacement = 0; + var lastDisplaced = $('.overlay-displace-' + region + ':last'); + if (lastDisplaced.length) { + displacement = lastDisplaced.offset().top + lastDisplaced.outerHeight(); + + // Remove height added by IE Shadow filter. + if (lastDisplaced[0].filters && lastDisplaced[0].filters.length && lastDisplaced[0].filters.item('DXImageTransform.Microsoft.Shadow')) { + displacement -= lastDisplaced[0].filters.item('DXImageTransform.Microsoft.Shadow').strength; + displacement = Math.max(0, displacement); + } + } + return displacement; }; /** - * Theme function to create a container for the overlay iframe element. + * Theme function to create the overlay iframe element. */ Drupal.theme.prototype.overlayContainer = function () { - return '<div id="overlay-container"/>'; -}; - -/** - * Theme function for the overlay title markup. - */ -Drupal.theme.prototype.overlayTitleHeader = function (text) { - return '<h1 id="ui-dialog-title-overlay-container" class="ui-dialog-title" tabindex="-1" unselectable="on">' + text + '</h1>'; + return '<div id="overlay-container" role="dialog"><div class="overlay-modal-background"></div></div>'; }; /** - * Theme function to create a wrapper for the jquery UI dialog. + * Theme function to create an overlay iframe element. */ -Drupal.theme.prototype.overlayWrapper = function () { - return '<div id="overlay-wrapper"/>'; +Drupal.theme.prototype.overlayElement = function (url) { + return '<iframe class="overlay-element" frameborder="0" scrolling="auto" allowtransparency="true" role="document"></iframe>'; }; })(jQuery); diff --git a/modules/overlay/overlay.info b/modules/overlay/overlay.info index 47e7ff7e..362404f7 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/overlay/overlay.module b/modules/overlay/overlay.module index 917f5bd0..11f36905 100644 --- a/modules/overlay/overlay.module +++ b/modules/overlay/overlay.module @@ -1,5 +1,5 @@ <?php -// $Id: overlay.module,v 1.18 2010/05/14 07:45:54 dries Exp $ +// $Id: overlay.module,v 1.23 2010/07/08 12:20:23 dries Exp $ /** * @file @@ -45,6 +45,18 @@ function overlay_permission() { ); } +/** + * Implements hook_theme(). + */ +function overlay_theme() { + return array( + 'overlay' => array( + 'render element' => 'page', + 'template' => 'overlay', + ), + ); +} + /** * Implements hook_init(). * @@ -56,9 +68,12 @@ function overlay_permission() { function overlay_init() { // @todo: custom_theme does not exist anymore. global $custom_theme; - // Only act if the user has access to administration pages. Other modules can - // also enable the overlay directly for other uses of the JavaScript. - if (user_access('access overlay')) { + + $mode = overlay_get_mode(); + + // Only act if the user has access to the overlay and a mode was not already + // set. Other modules can also enable the overlay directly for other uses. + if (empty($mode) && user_access('access overlay')) { $current_path = current_path(); // After overlay is enabled on the modules page, redirect to // <front>#overlay=admin/modules to actually enable the overlay. @@ -150,8 +165,7 @@ function overlay_library() { $module_path . '/overlay-parent.css' => array(), ), 'dependencies' => array( - array('system', 'ui.dialog'), - array('system', 'ui.position'), + array('system', 'ui'), array('system', 'jquery-bbq'), ), ); @@ -163,8 +177,8 @@ function overlay_library() { 'js' => array( $module_path . '/overlay-child.js' => array(), ), - 'dependencies' => array( - array('system', 'ui'), + 'css' => array( + $module_path . '/overlay-child.css' => array(), ), ); @@ -221,6 +235,11 @@ function overlay_page_alter(&$page) { $page[$skipped_region]['#access'] = FALSE; } } + + if (overlay_get_mode() == 'child') { + // Add the overlay wrapper before the html wrapper. + array_unshift($page['#theme_wrappers'], 'overlay'); + } } /** @@ -255,7 +274,7 @@ function overlay_system_info_alter(&$info, $file, $type) { } /** - * Preprocess template variables for html.tpl.php. + * Implements hook_preprocess_html(). * * If the current page request is inside the overlay, add appropriate classes * to the <body> element, and simplify the page title. @@ -266,13 +285,11 @@ function overlay_preprocess_html(&$variables) { if (overlay_get_mode() == 'child') { // Add overlay class, so themes can react to being displayed in the overlay. $variables['classes_array'][] = 'overlay'; - // Do not include site name or slogan in the overlay title. - $variables['head_title'] = drupal_get_title(); } } /** - * Preprocess template variables for maintenance-page.tpl.php. + * Implements hook_preprocess_maintenance_page(). * * If the current page request is inside the overlay, add appropriate classes * to the <body> element, and simplify the page title. @@ -284,7 +301,30 @@ function overlay_preprocess_maintenance_page(&$variables) { } /** - * Preprocess template variables for page.tpl.php. + * Preprocesses template variables for overlay.tpl.php + * + * @see overlay.tpl.php + */ +function template_preprocess_overlay(&$variables) { + $variables['tabs'] = menu_primary_local_tasks(); + $variables['title'] = drupal_get_title(); + + $variables['content_attributes_array']['class'][] = 'clearfix'; +} + +/** + * Processes variables for overlay.tpl.php + * + * @see template_preprocess_overlay() + * @see overlay.tpl.php + */ +function template_process_overlay(&$variables) { + // Place the rendered HTML for the page body into a top level variable. + $variables['page'] = $variables['page']['#children']; +} + +/** + * Implements hook_preprocess_page(). * * Display breadcrumbs correctly inside the overlay. * @@ -296,6 +336,9 @@ function overlay_preprocess_page(&$variables) { $overlay_breadcrumb = drupal_get_breadcrumb(); array_shift($overlay_breadcrumb); $variables['breadcrumb'] = theme('breadcrumb', array('breadcrumb' => $overlay_breadcrumb)); + + $variables['tabs'] = ''; + $variables['primary_local_tasks'] = ''; } } @@ -427,16 +470,28 @@ function overlay_get_mode() { } /** - * Set overlay mode and add proper JavaScript and styles to the page. + * Sets the overlay mode and adds proper JavaScript and styles to the page. * - * @param $mode - * To set the mode, pass in either 'parent' or 'child'. 'parent' is used in - * the context of a parent window (a regular browser window), and JavaScript - * is added so that administrative links in the parent window will open in - * an overlay. 'child' is used in the context of the child overlay window (the - * page actually appearing within the overlay iframe) and JavaScript and CSS - * are added so that Drupal behaves nicely from within the overlay. + * Note that since setting the overlay mode triggers a variety of behaviors + * (including hooks being invoked), it can only be done once per page request. + * Therefore, the first call to this function which passes along a value of the + * $mode parameter controls the overlay mode that will be used. * + * @param $mode + * To set the mode, pass in one of the following values: + * - 'parent': This is used in the context of a parent window (a regular + * browser window). If set, JavaScript is added so that administrative + * links in the parent window will open in an overlay. + * - 'child': This is used in the context of the child overlay window (the + * page actually appearing within the overlay iframe). If set, JavaScript + * and CSS are added so that Drupal behaves nicely from within the overlay. + * - 'none': This is used to avoid adding any overlay-related code to the + * page at all. Modules can set this to explicitly prevent the overlay from + * being used. For example, since the overlay module itself sets the mode + * to 'parent' or 'child' in overlay_init() when certain conditions are + * met, other modules which want to override that behavior can do so by + * setting the mode to 'none' earlier in the page request - e.g., in their + * own hook_init() implementations, if they have a lower weight. * This parameter is optional, and if omitted, the current mode will be * returned with no action taken. * @@ -444,6 +499,7 @@ function overlay_get_mode() { * The current mode, if any has been set, or NULL if no mode has been set. * * @ingroup overlay_api + * @see overlay_init() */ function overlay_set_mode($mode = NULL) { global $base_path; @@ -467,9 +523,6 @@ function overlay_set_mode($mode = NULL) { case 'child': drupal_add_library('overlay', 'child'); - // Pass child's document height on to parent document as quickly as - // possible so it can be updated accordingly. - drupal_add_js('if (parent.Drupal && parent.Drupal.overlay) { parent.Drupal.overlay.innerResize(jQuery(document.body).outerHeight()); }', array('type' => 'inline', 'scope' => 'footer')); // Allow modules to act upon overlay events. module_invoke_all('overlay_child_initialize'); @@ -498,18 +551,21 @@ function overlay_overlay_parent_initialize() { function overlay_overlay_child_initialize() { // Check if the parent window needs to refresh any page regions on this page // request. - overlay_trigger_regions_to_refresh(); + overlay_trigger_refresh(); // If this is a POST request, or a GET request with a token parameter, we // have an indication that something in the supplemental regions of the // overlay might change during the current page request. We therefore store // the initial rendered content of those regions here, so that we can compare // it to the same content rendered in overlay_exit(), at the end of the page // request. This allows us to check if anything actually did change, and, if - // so, trigger an AJAX refresh of the parent window. + // so, trigger an immediate AJAX refresh of the parent window. if (!empty($_POST) || isset($_GET['token'])) { foreach (overlay_supplemental_regions() as $region) { overlay_store_rendered_content($region, overlay_render_region($region)); } + // In addition, notify the parent window that when the overlay closes, + // the entire parent window should be refreshed. + overlay_request_page_refresh(); } // Indicate that when the main page rendering occurs later in the page // request, only the regions that appear within the overlay should be @@ -744,7 +800,7 @@ function overlay_store_rendered_content($id = NULL, $content = NULL) { * The name of the page region to refresh. The parent window will trigger a * refresh of this region on the next page load. * - * @see overlay_trigger_regions_to_refresh() + * @see overlay_trigger_refresh() * @see Drupal.overlay.refreshRegions() */ function overlay_request_refresh($region) { @@ -753,16 +809,27 @@ function overlay_request_refresh($region) { } /** - * Check if the parent window needs to refresh any regions on this page load. + * Request that the entire parent window be reloaded when the overlay closes. * - * If the previous page load requested that any page regions be refreshed, pass - * that request via JavaScript to the child window, so it can in turn pass the - * request to the parent window. + * @see overlay_trigger_refresh() + */ +function overlay_request_page_refresh() { + $_SESSION['overlay_refresh_parent'] = TRUE; +} + +/** + * Check if the parent window needs to be refreshed on this page load. + * + * If the previous page load requested that any page regions be refreshed, or + * if it requested that the entire page be refreshed when the overlay closes, + * pass that request via JavaScript to the child window, so it can in turn pass + * the request to the parent window. * * @see overlay_request_refresh() + * @see overlay_request_page_refresh() * @see Drupal.overlay.refreshRegions() */ -function overlay_trigger_regions_to_refresh() { +function overlay_trigger_refresh() { if (!empty($_SESSION['overlay_regions_to_refresh'])) { $settings = array( 'overlayChild' => array( @@ -772,6 +839,10 @@ function overlay_trigger_regions_to_refresh() { drupal_add_js($settings, array('type' => 'setting')); unset($_SESSION['overlay_regions_to_refresh']); } + if (!empty($_SESSION['overlay_refresh_parent'])) { + drupal_add_js(array('overlayChild' => array('refreshPage' => TRUE)), array('type' => 'setting')); + unset($_SESSION['overlay_refresh_parent']); + } } /** diff --git a/modules/overlay/overlay.tpl.php b/modules/overlay/overlay.tpl.php new file mode 100644 index 00000000..b48de312 --- /dev/null +++ b/modules/overlay/overlay.tpl.php @@ -0,0 +1,37 @@ +<?php +// $Id: overlay.tpl.php,v 1.2 2010/06/11 14:07:32 dries Exp $ + +/** + * @file + * Default theme implementation to display a page in the overlay. + * + * Available variables: + * - $title: the (sanitized) title of the page. + * - $page: The rendered page content. + * - $tabs (array): Tabs linking to any sub-pages beneath the current page + * (e.g., the view and edit tabs when displaying a node). + * + * Helper variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * + * @see template_preprocess() + * @see template_preprocess_overlay() + * @see template_process() + */ +?> + +<div id="overlay" <?php print $attributes; ?>> + <div id="overlay-titlebar" class="clearfix"> + <div id="overlay-title-wrapper" class="clearfix"> + <h1 id="overlay-title"<?php print $title_attributes; ?>><?php print $title; ?></h1> + </div> + <div id="overlay-close-wrapper"> + <a id="overlay-close" href="#" class="overlay-close"><span><?php t('Close overlay'); ?></span></a> + </div> + <?php if ($tabs): ?><ul id="overlay-tabs"><?php print render($tabs); ?></ul><?php endif; ?> + </div> + <div id="overlay-content"<?php print $content_attributes; ?>> + <?php print $page; ?> + </div> +</div> diff --git a/modules/path/path.info b/modules/path/path.info index 6aa6b539..0f0c3dfc 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/php/php.info b/modules/php/php.info index 2af11cd9..95ba116e 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/poll/poll.info b/modules/poll/poll.info index c3e12277..2d2057ad 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/poll/poll.install b/modules/poll/poll.install index d76c6522..f9dce989 100644 --- a/modules/poll/poll.install +++ b/modules/poll/poll.install @@ -1,5 +1,5 @@ <?php -// $Id: poll.install,v 1.30 2010/01/30 00:08:34 webchick Exp $ +// $Id: poll.install,v 1.32 2010/07/01 15:09:11 webchick Exp $ /** * @file @@ -138,11 +138,28 @@ function poll_schema() { } /** + * Use the poll_choice primary key to record votes in poll_votes rather than + * the choice order. Rename chorder to weight. + * * Rename {poll_choices} table to {poll_choice} and {poll_votes} to {poll_vote}. */ function poll_update_7001() { - db_rename_table('poll_choices', 'poll_choice'); + // Add chid column and convert existing votes. + db_add_field('poll_votes', 'chid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0)); + db_add_index('poll_votes', 'chid', array('chid')); + db_update('poll_votes') + ->expression('chid', Database::getConnection()->prefixTables('COALESCE((SELECT chid FROM {poll_choices} c WHERE {poll_votes}.chorder = c.chorder AND {poll_votes}.nid = c.nid), 0)')) + ->execute(); + // Delete invalid votes. + db_delete('poll_votes')->condition('chid', 0)->execute(); + // Remove old chorder column. + db_drop_field('poll_votes', 'chorder'); + + // Change the chorder column to weight in poll_choices. + db_change_field('poll_choices', 'chorder', 'weight', array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'tiny')); + db_rename_table('poll_votes', 'poll_vote'); + db_rename_table('poll_choices', 'poll_choice'); } /** diff --git a/modules/poll/poll.module b/modules/poll/poll.module index 340fd7c1..c9bbd2c7 100644 --- a/modules/poll/poll.module +++ b/modules/poll/poll.module @@ -1,5 +1,5 @@ <?php -// $Id: poll.module,v 1.348 2010/05/09 13:37:32 dries Exp $ +// $Id: poll.module,v 1.352 2010/06/24 18:53:31 dries Exp $ /** * @file @@ -208,16 +208,30 @@ function poll_node_info() { */ function poll_field_extra_fields() { $extra['node']['poll'] = array( - 'choice_wrapper' => array( - 'label' => t('Poll choices'), - 'description' => t('Poll module choices.'), - 'weight' => -4, - ), - 'settings' => array( - 'label' => t('Poll settings'), - 'description' => t('Poll module settings.'), - 'weight' => -3, + 'form' => array( + 'choice_wrapper' => array( + 'label' => t('Poll choices'), + 'description' => t('Poll choices'), + 'weight' => -4, + ), + 'settings' => array( + 'label' => t('Poll settings'), + 'description' => t('Poll module settings'), + 'weight' => -3, + ), ), + 'display' => array( + 'poll_view_voting' => array( + 'label' => t('Poll vote'), + 'description' => t('Poll vote'), + 'weight' => 0, + ), + 'poll_view_results' => array( + 'label' => t('Poll results'), + 'description' => t('Poll results'), + 'weight' => 0, + ), + ) ); return $extra; @@ -352,15 +366,18 @@ function poll_form($node, &$form_state) { * return just the changed part of the form. */ function poll_more_choices_submit($form, &$form_state) { - include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'node') . '/node.pages.inc'; - // Set the form to rebuild and run submit handlers. - node_form_submit_build_node($form, $form_state); - // Make the changes we want to the form state. if ($form_state['values']['poll_more']) { $n = $_GET['q'] == 'system/ajax' ? 1 : 5; $form_state['choice_count'] = count($form_state['values']['choice']) + $n; } + // Renumber the choices. This invalidates the corresponding key/value + // associations in $form_state['input'], so clear that out. This requires + // poll_form() to rebuild the choices with the values in + // $form_state['node']->choice, which it does. + $form_state['node']->choice = array_values($form_state['values']['choice']); + unset($form_state['input']['choice']); + $form_state['rebuild'] = TRUE; } function _poll_choice_form($key, $chid = NULL, $value = '', $votes = 0, $weight = 0, $size = 10) { @@ -447,11 +464,11 @@ function poll_validate($node, $form) { } /** - * Implements hook_node_prepare_translation(). + * Implements hook_field_attach_prepare_translation_alter(). */ -function poll_node_prepare_translation($node) { - if ($node->type == 'poll') { - $node->choice = $node->translation_source->choice; +function poll_field_attach_prepare_translation_alter(&$entity, $context) { + if ($context['entity_type'] == 'node' && $entity->type == 'poll') { + $entity->choice = $context['source_entity']->choice; } } @@ -475,6 +492,7 @@ function poll_load($nodes) { $poll->allowvotes = FALSE; if (user_access('vote on polls') && $poll->active) { if ($user->uid) { + // If authenticated, find existing vote based on uid. $poll->vote = db_query('SELECT chid FROM {poll_vote} WHERE nid = :nid AND uid = :uid', array(':nid' => $node->nid, ':uid' => $user->uid))->fetchField(); if (empty($poll->vote)) { $poll->vote = -1; @@ -482,10 +500,14 @@ function poll_load($nodes) { } } elseif (!empty($_SESSION['poll_vote'][$node->nid])) { + // Otherwise the user is anonymous. Look for an existing vote in the + // user's session. $poll->vote = $_SESSION['poll_vote'][$node->nid]; } else { - $poll->allowvotes = !db_query("SELECT 1 FROM {poll_vote} WHERE nid = :nid AND hostname = :hostname", array(':nid' => $node->nid, ':hostname' => ip_address()))->fetchField(); + // Finally, query the database for an existing vote based on anonymous + // user's hostname. + $poll->allowvotes = !db_query("SELECT 1 FROM {poll_vote} WHERE nid = :nid AND hostname = :hostname AND uid = 0", array(':nid' => $node->nid, ':hostname' => ip_address()))->fetchField(); } } foreach ($poll as $key => $value) { @@ -624,7 +646,7 @@ function poll_block_latest_poll_view($node) { /** * Implements hook_view(). */ -function poll_view($node, $view_mode = 'full') { +function poll_view($node, $view_mode) { global $user; $output = ''; @@ -715,7 +737,7 @@ function poll_vote($form, &$form_state) { 'nid' => $node->nid, 'chid' => $choice, 'uid' => $user->uid, - 'hostname' => $user->uid ? '' : ip_address(), + 'hostname' => ip_address(), 'timestamp' => REQUEST_TIME, )) ->execute(); diff --git a/modules/poll/poll.test b/modules/poll/poll.test index 51e3aff1..0ca7aa81 100644 --- a/modules/poll/poll.test +++ b/modules/poll/poll.test @@ -1,5 +1,5 @@ <?php -// $Id: poll.test,v 1.34 2010/05/12 08:26:15 dries Exp $ +// $Id: poll.test,v 1.35 2010/06/23 19:15:07 dries Exp $ /** * @file @@ -19,7 +19,7 @@ class PollTestCase extends DrupalWebTestCase { function pollCreate($title, $choices, $test_preview = TRUE) { $this->assertTrue(TRUE, 'Create a poll'); - $web_user = $this->drupalCreateUser(array('create poll content', 'access content')); + $web_user = $this->drupalCreateUser(array('create poll content', 'access content', 'edit own poll content')); $this->drupalLogin($web_user); // Get the form first to initialize the state of the internal browser @@ -578,3 +578,66 @@ class PollTokenReplaceTestCase extends PollTestCase { } } } + +class PollExpirationTestCase extends PollTestCase { + public static function getInfo() { + return array( + 'name' => 'Poll expiration', + 'description' => 'Test the poll auto-expiration logic.', + 'group' => 'Poll', + ); + } + + function setUp() { + parent::setUp('poll'); + } + + function testAutoExpire() { + // Set up a poll. + $title = $this->randomName(); + $choices = $this->_generateChoices(2); + $poll_nid = $this->pollCreate($title, $choices, FALSE); + $this->assertTrue($poll_nid, t('Poll for auto-expire test created.'), t('Poll')); + + // Visit the poll edit page and verify that by default, expiration + // is set to unlimited. + $this->drupalGet("node/$poll_nid/edit"); + $this->assertField('runtime', t('Poll expiration setting found.'), t('Poll')); + $elements = $this->xpath('//select[@id="edit-runtime"]/option[@selected="selected"]'); + $this->assertTrue(isset($elements[0]['value']) && $elements[0]['value'] == 0, t('Poll expiration set to unlimited.'), t('Poll')); + + // Set the expiration to one week. + $edit = array(); + $poll_expiration = 604800; // One week. + $edit['runtime'] = $poll_expiration; + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertRaw(t('Poll %title has been updated.', array('%title' => $title)), t('Poll expiration settings saved.'), t('Poll')); + + // Make sure that the changed expiration settings is kept. + $this->drupalGet("node/$poll_nid/edit"); + $elements = $this->xpath('//select[@id="edit-runtime"]/option[@selected="selected"]'); + $this->assertTrue(isset($elements[0]['value']) && $elements[0]['value'] == $poll_expiration, t('Poll expiration set to unlimited.'), t('Poll')); + + // Force a cron run. Since the expiration date has not yet been reached, + // the poll should remain active. + drupal_cron_run(); + $this->drupalGet("node/$poll_nid/edit"); + $elements = $this->xpath('//input[@id="edit-active-1"]'); + $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), t('Poll is still active.'), t('Poll')); + + // Test expiration. Since REQUEST_TIME is a constant and we don't + // want to keep SimpleTest waiting until the moment of expiration arrives, + // we forcibly change the expiration date in the database. + $created = db_query('SELECT created FROM {node} WHERE nid = :nid', array(':nid' => $poll_nid))->fetchField(); + db_update('node') + ->fields(array('created' => $created - ($poll_expiration * 1.01))) + ->condition('nid', $poll_nid) + ->execute(); + + // Run cron and verify that the poll is now marked as "closed". + drupal_cron_run(); + $this->drupalGet("node/$poll_nid/edit"); + $elements = $this->xpath('//input[@id="edit-active-0"]'); + $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), t('Poll has expired.'), t('Poll')); + } +} \ No newline at end of file diff --git a/modules/profile/profile.info b/modules/profile/profile.info index 6d7fc502..6bda1049 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/profile/profile.module b/modules/profile/profile.module index c8acf9fa..466f9caf 100644 --- a/modules/profile/profile.module +++ b/modules/profile/profile.module @@ -1,5 +1,5 @@ <?php -// $Id: profile.module,v 1.290 2010/04/23 04:32:16 webchick Exp $ +// $Id: profile.module,v 1.291 2010/05/29 11:37:33 dries Exp $ /** * @file @@ -179,12 +179,12 @@ function profile_block_view($delta = '') { $output = ''; if ((arg(0) == 'node') && is_numeric(arg(1)) && (arg(2) == NULL)) { $node = node_load(arg(1)); - $account = user_load(array('uid' => $node->uid)); + $account = user_load($node->uid); if ($use_fields = variable_get('profile_block_author_fields', array())) { // Compile a list of fields to show. $fields = array(); - $result = db_query('SELECT name, title, weight, visibility FROM {profile_field} WHERE visibility IN (:visibility) ORDER BY weight', array(':visibility' => array(PROFILE_PUBLIC, PROFILE_PUBLIC_LISTINGS))); + $result = db_query('SELECT * FROM {profile_field} WHERE visibility IN (:visibility) ORDER BY weight', array(':visibility' => array(PROFILE_PUBLIC, PROFILE_PUBLIC_LISTINGS))); foreach ($result as $record) { // Ensure that field is displayed only if it is among the defined block fields and, if it is private, the user has appropriate permissions. if (isset($use_fields[$record->name]) && $use_fields[$record->name]) { diff --git a/modules/profile/profile.test b/modules/profile/profile.test index 8fc70327..559371ce 100644 --- a/modules/profile/profile.test +++ b/modules/profile/profile.test @@ -1,5 +1,5 @@ <?php -// $Id: profile.test,v 1.24 2010/03/08 15:46:58 webchick Exp $ +// $Id: profile.test,v 1.26 2010/05/29 11:37:33 dries Exp $ /** * A class for common methods for testing profile fields. @@ -10,9 +10,9 @@ class ProfileTestCase extends DrupalWebTestCase { function setUp() { parent::setUp('profile'); - variable_set('user_register', 1); + variable_set('user_register', USER_REGISTER_VISITORS); - $this->admin_user = $this->drupalCreateUser(array('administer users', 'access user profiles')); + $this->admin_user = $this->drupalCreateUser(array('administer users', 'access user profiles', 'administer blocks')); // This is the user whose profile will be edited. $this->normal_user = $this->drupalCreateUser(); @@ -364,33 +364,88 @@ class ProfileTestAutocomplete extends ProfileTestCase { } } -class ProfileBlockTestCase extends DrupalWebTestCase { +class ProfileBlockTestCase extends ProfileTestCase { public static function getInfo() { return array( 'name' => 'Block availability', - 'description' => 'Check if the author-information block is available.', + 'description' => 'Check if the Author Information block is available.', 'group' => 'Profile', ); } function setUp() { - parent::setUp('profile'); + parent::setUp(); + + // Login the admin user. + $this->drupalLogin($this->admin_user); + + // Create two fields. + $category = $this->randomName(); + $this->field1 = $this->createProfileField('textfield', $category, array('weight' => 0)); + $this->field2 = $this->createProfileField('textfield', $category, array('weight' => 1)); - // Create and login user - $admin_user = $this->drupalCreateUser(array('administer blocks')); - $this->drupalLogin($admin_user); + // Assign values to those fields. + $this->value1 = $this->setProfileField($this->field1); + $this->value2 = $this->setProfileField($this->field2); + + // Create a node authored by the normal user. + $this->node = $this->drupalCreateNode(array( + 'uid' => $this->normal_user->uid, + )); } function testAuthorInformationBlock() { - // Set block title to confirm that the interface is availble. - $this->drupalPost('admin/structure/block/manage/profile/author-information/configure', array('title' => $this->randomName(8)), t('Save block')); - $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.')); - - // Set the block to a region to confirm block is availble. + // Set the block to a region to confirm the block is availble. $edit = array(); $edit['profile_author-information[region]'] = 'footer'; $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); $this->assertText(t('The block settings have been updated.'), t('Block successfully move to footer region.')); + + // Enable field 1. + $this->drupalPost('admin/structure/block/manage/profile/author-information/configure', array( + 'profile_block_author_fields[' . $this->field1['form_name'] . ']' => TRUE, + ), t('Save block')); + $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.')); + + // Visit the node and confirm that the field is displayed. + $this->drupalGet('node/' . $this->node->nid); + $this->assertRaw($this->value1, t('Field 1 is displayed')); + $this->assertNoRaw($this->value2, t('Field 2 is not displayed')); + + // Enable only field 2. + $this->drupalPost('admin/structure/block/manage/profile/author-information/configure', array( + 'profile_block_author_fields[' . $this->field1['form_name'] . ']' => FALSE, + 'profile_block_author_fields[' . $this->field2['form_name'] . ']' => TRUE, + ), t('Save block')); + $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.')); + + // Visit the node and confirm that the field is displayed. + $this->drupalGet('node/' . $this->node->nid); + $this->assertNoRaw($this->value1, t('Field 1 is not displayed')); + $this->assertRaw($this->value2, t('Field 2 is displayed')); + + // Enable both fields. + $this->drupalPost('admin/structure/block/manage/profile/author-information/configure', array( + 'profile_block_author_fields[' . $this->field1['form_name'] . ']' => TRUE, + 'profile_block_author_fields[' . $this->field2['form_name'] . ']' => TRUE, + ), t('Save block')); + $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.')); + + // Visit the node and confirm that the field is displayed. + $this->drupalGet('node/' . $this->node->nid); + $this->assertRaw($this->value1, t('Field 1 is displayed')); + $this->assertRaw($this->value2, t('Field 2 is displayed')); + + // Enable the link to the user profile. + $this->drupalPost('admin/structure/block/manage/profile/author-information/configure', array( + 'profile_block_author_fields[user_profile]' => TRUE, + ), t('Save block')); + $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.')); + + // Visit the node and confirm that the user profile link is displayed. + $this->drupalGet('node/' . $this->node->nid); + $this->clickLink(t('View full user profile')); + $this->assertEqual($this->getUrl(), url('user/' . $this->normal_user->uid, array('absolute' => TRUE))); } } diff --git a/modules/rdf/rdf.info b/modules/rdf/rdf.info index 4c6c1b5b..a856b73d 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/rdf/rdf.install b/modules/rdf/rdf.install index 1e82f1d5..590b2141 100644 --- a/modules/rdf/rdf.install +++ b/modules/rdf/rdf.install @@ -1,5 +1,5 @@ <?php -// $Id: rdf.install,v 1.3 2010/02/26 18:31:29 dries Exp $ +// $Id: rdf.install,v 1.4 2010/06/25 17:47:22 dries Exp $ /** * @file @@ -27,7 +27,7 @@ function rdf_schema() { ), 'mapping' => array( 'description' => 'The serialized mapping of the bundle type and fields to RDF terms.', - 'type' => 'text', + 'type' => 'blob', 'not null' => FALSE, 'size' => 'big', 'serialize' => TRUE, diff --git a/modules/rdf/rdf.module b/modules/rdf/rdf.module index 37d47ffc..9f3945f3 100644 --- a/modules/rdf/rdf.module +++ b/modules/rdf/rdf.module @@ -1,5 +1,5 @@ <?php -// $Id: rdf.module,v 1.40 2010/05/05 15:49:04 dries Exp $ +// $Id: rdf.module,v 1.41 2010/06/23 02:40:56 dries Exp $ /** * @file @@ -548,7 +548,7 @@ function rdf_preprocess_field(&$variables) { // the file. We correct this by adding a resource attribute to the div if // this field has a URI. if (isset($item['uri'])) { - if (isset($element[$delta]['#image_style'])) { + if (!empty($element[$delta]['#image_style'])) { $variables['item_attributes_array'][$delta]['resource'] = image_style_url($element[$delta]['#image_style'], $item['uri']); } else { diff --git a/modules/rdf/rdf.test b/modules/rdf/rdf.test index 1e1401dc..1e152a78 100644 --- a/modules/rdf/rdf.test +++ b/modules/rdf/rdf.test @@ -1,5 +1,5 @@ <?php -// $Id: rdf.test,v 1.22 2010/04/22 21:41:09 webchick Exp $ +// $Id: rdf.test,v 1.24 2010/06/01 18:29:41 dries Exp $ /** * @file @@ -127,18 +127,27 @@ class RdfRdfaMarkupTestCase extends DrupalWebTestCase { $langcode = LANGUAGE_NONE; $bundle_name = "article"; - // Create file field. - $file_field = 'file_test'; - $edit = array( - '_add_new_field[label]' => $file_field, - '_add_new_field[field_name]' => $file_field, - '_add_new_field[type]' => 'file', - '_add_new_field[widget_type]' => 'file_generic', + $field_name = 'file_test'; + $field = array( + 'field_name' => $field_name, + 'type' => 'file', ); - $this->drupalPost('admin/structure/types/manage/' . $bundle_name . '/fields', $edit, t('Save')); + field_create_field($field); + $instance = array( + 'field_name' => $field_name, + 'entity_type' => 'node', + 'bundle' => $bundle_name, + 'display' => array( + 'teaser' => array( + 'type' => 'file_default', + ), + ), + ); + field_create_instance($instance); + // Set the RDF mapping for the new field. $rdf_mapping = rdf_mapping_load('node', $bundle_name); - $rdf_mapping += array('field_' . $file_field => array('predicates' => array('rdfs:seeAlso'), 'type' => 'rel')); + $rdf_mapping += array($field_name => array('predicates' => array('rdfs:seeAlso'), 'type' => 'rel')); $rdf_mapping_save = array('mapping' => $rdf_mapping, 'type' => 'node', 'bundle' => $bundle_name); rdf_mapping_save($rdf_mapping_save); @@ -152,7 +161,7 @@ class RdfRdfaMarkupTestCase extends DrupalWebTestCase { // Create an array for drupalPost with the field names as the keys and // the uris for the test files as the values. - $edit = array("files[field_" . $file_field . "_" . $langcode . "_0]" => drupal_realpath($file->uri), + $edit = array("files[" . $field_name . "_" . $langcode . "_0]" => drupal_realpath($file->uri), "files[" . $image_field . "_" . $langcode . "_0]" => drupal_realpath($image->uri)); // Create node and save, then edit node to upload files. @@ -382,7 +391,7 @@ class RdfMappingDefinitionTestCase extends TaxonomyWebTestCase { } } -class RdfCommentAttributesTestCase extends DrupalWebTestCase { +class RdfCommentAttributesTestCase extends CommentHelperCase { public static function getInfo() { return array( @@ -393,28 +402,155 @@ class RdfCommentAttributesTestCase extends DrupalWebTestCase { } public function setUp() { - parent::setUp('rdf', 'rdf_test', 'comment'); - // Enable anonymous posting of content. + parent::setUp('comment', 'rdf', 'rdf_test'); + + $this->admin_user = $this->drupalCreateUser(array('administer content types', 'administer comments', 'administer permissions', 'administer blocks')); + $this->web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'create article content', 'access user profiles')); + + // Enables anonymous user comments. user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( - 'create article content' => TRUE, 'access comments' => TRUE, 'post comments' => TRUE, 'post comments without approval' => TRUE, )); + // Allows anonymous to leave their contact information. + $this->setCommentAnonymous(COMMENT_ANONYMOUS_MAY_CONTACT); + $this->setCommentPreview(DRUPAL_OPTIONAL); + $this->setCommentForm(TRUE); + $this->setCommentSubject(TRUE); + $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Comment paging changed.')); + + // Creates the nodes on which the test comments will be posted. + $this->drupalLogin($this->web_user); + $this->node1 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); + $this->node2 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); + $this->drupalLogout(); } - public function testAttributesInTeaser() { - $node = $this->drupalCreateNode(array('type' => 'article', 'uid' => 1, 'promote' => 1)); - $comment = array( - 'subject' => $this->randomName(), - 'comment_body[' . LANGUAGE_NONE . '][0][value]' => $this->randomName(), - ); - $this->drupalPost('comment/reply/' . $node->nid, $comment, t('Save')); - $this->drupalGet(''); - $comment_count_link = $this->xpath('//div[@about=:url]//a[contains(@property, "sioc:num_replies") and @rel=""]', array(':url' => url("node/$node->nid"))); + /** + * Tests the presence of the RDFa markup for the number of comments. + */ + public function testNumberOfCommentsRdfaMarkup() { + // Posts 2 comments as a registered user. + $this->drupalLogin($this->web_user); + $this->postComment($this->node1, $this->randomName(), $this->randomName()); + $this->postComment($this->node1, $this->randomName(), $this->randomName()); + + // Tests number of comments in teaser view. + $this->drupalGet('node'); + $comment_count_teaser = $this->xpath('//div[contains(@typeof, "sioc:Item")]//li[contains(@class, "comment-comments")]/a[contains(@property, "sioc:num_replies") and contains(@content, "2") and @datatype="xsd:integer"]'); + $this->assertTrue(!empty($comment_count_teaser), t('RDFa markup for the number of comments found on teaser view.')); + $comment_count_link = $this->xpath('//div[@about=:url]//a[contains(@property, "sioc:num_replies") and @rel=""]', array(':url' => url("node/{$this->node1->nid}"))); $this->assertTrue(!empty($comment_count_link), t('Empty rel attribute found in comment count link.')); + + // Tests number of comments in full node view. + $this->drupalGet('node/' . $this->node1->nid); + $node_url = url('node/' . $this->node1->nid); + $comment_count_teaser = $this->xpath('/html/head/meta[@about=:node-url and @property="sioc:num_replies" and @content="2" and @datatype="xsd:integer"]', array(':node-url' => $node_url)); + $this->assertTrue(!empty($comment_count_teaser), t('RDFa markup for the number of comments found on full node view.')); + } + + /** + * Tests the presence of the RDFa markup for the title, date and author and + * homepage on registered users and anonymous comments. + */ + public function testCommentRdfaMarkup() { + + // Posts comment #1 as a registered user. + $this->drupalLogin($this->web_user); + $comment1_subject = $this->randomName(); + $comment1_body = $this->randomName(); + $comment1 = $this->postComment($this->node1, $comment1_body, $comment1_subject); + + // Tests comment #1 with access to the user profile. + $this->drupalGet('node/' . $this->node1->nid); + $this->_testBasicCommentRdfaMarkup($comment1); + + // Tests comment #1 with no access to the user profile (as anonymous user). + $this->drupalLogout(); + $this->drupalGet('node/' . $this->node1->nid); + $this->_testBasicCommentRdfaMarkup($comment1); + + // Posts comment #2 as anonymous user. + $comment2_subject = $this->randomName(); + $comment2_body = $this->randomName(); + $anonymous_user = array(); + $anonymous_user['name'] = $this->randomName(); + $anonymous_user['mail'] = 'tester@simpletest.org'; + $anonymous_user['homepage'] = 'http://example.org/'; + $comment2 = $this->postComment($this->node2, $comment2_body, $comment2_subject, $anonymous_user); + $this->drupalGet('node/' . $this->node2->nid); + + // Tests comment #2 as anonymous user. + $this->_testBasicCommentRdfaMarkup($comment2, $anonymous_user); + // Tests the RDFa markup for the homepage (specific to anonymous comments). + $comment_homepage = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//span[@rel="sioc:has_creator"]/a[contains(@class, "username") and @typeof="sioc:UserAccount" and @property="foaf:name" and @href="http://example.org/" and contains(@rel, "foaf:page")]'); + $this->assertTrue(!empty($comment_homepage), t('RDFa markup for the homepage of anonymous user found.')); + // There should be no about attribute on anonymous comments. + $comment_homepage = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//span[@rel="sioc:has_creator"]/a[@about]'); + $this->assertTrue(empty($comment_homepage), t('No about attribute is present on anonymous user comment.')); + + // Tests comment #2 as logged in user. + $this->drupalLogin($this->web_user); + $this->drupalGet('node/' . $this->node2->nid); + $this->_testBasicCommentRdfaMarkup($comment2, $anonymous_user); + // Tests the RDFa markup for the homepage (specific to anonymous comments). + $comment_homepage = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//span[@rel="sioc:has_creator"]/a[contains(@class, "username") and @typeof="sioc:UserAccount" and @property="foaf:name" and @href="http://example.org/" and contains(@rel, "foaf:page")]'); + $this->assertTrue(!empty($comment_homepage), t("RDFa markup for the homepage of anonymous user found.")); + // There should be no about attribute on anonymous comments. + $comment_homepage = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]//span[@rel="sioc:has_creator"]/a[@about]'); + $this->assertTrue(empty($comment_homepage), t("No about attribute is present on anonymous user comment.")); } + /** + * Test RDF comment replies. + */ + public function testCommentReplyOfRdfaMarkup() { + // Posts comment #1 as a registered user. + $this->drupalLogin($this->web_user); + $comments[] = $this->postComment($this->node1, $this->randomName(), $this->randomName()); + + // Tests the reply_of relationship of a first level comment. + $result = $this->xpath("id('comments')//div[@class='comment' and position()=0]//span[@rel='sioc:reply_of' and @resource=:node]", array(':node' => url("node/{$this->node1->nid}"))); + $this->assertEqual(1, count($result), t('RDFa markup referring to the node is present.')); + $result = $this->xpath("id('comments')//div[@class='comment' and position()=0]//span[@rel='sioc:reply_of' and @resource=:comment]", array(':comment' => url('comment/1#comment-1'))); + $this->assertFalse($result, t('No RDFa markup referring to the comment itself is present.')); + + // Posts a reply to the first comment. + $this->drupalGet('comment/reply/' . $this->node1->nid . '/' . $comments[0]->id); + $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); + + // Tests the reply_of relationship of a second level comment. + $result = $this->xpath("id('comments')//div[@class='comment' and position()=1]//span[@rel='sioc:reply_of' and @resource=:node]", array(':node' => url("node/{$this->node1->nid}"))); + $this->assertEqual(1, count($result), t('RDFa markup referring to the node is present.')); + $result = $this->xpath("id('comments')//div[@class='comment' and position()=1]//span[@rel='sioc:reply_of' and @resource=:comment]", array(':comment' => url('comment/1#comment-1'))); + $this->assertEqual(1, count($result), t('RDFa markup referring to the parent comment is present.')); + } + + /** + * Helper function for testCommentRdfaMarkup(). + * + * Tests the current page for basic comment RDFa markup. + * + * @param $comment + * Comment object. + * @param $account + * An array containing information about an anonymous user. + */ + function _testBasicCommentRdfaMarkup($comment, $account = array()) { + $comment_container = $this->xpath('//div[contains(@class, "comment") and contains(@typeof, "sioct:Comment")]'); + $this->assertTrue(!empty($comment_container), t("Comment RDF type for comment found.")); + $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.")); + $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.")); + $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.")); + } } class RdfTrackerAttributesTestCase extends DrupalWebTestCase { diff --git a/modules/rdf/tests/rdf_test.info b/modules/rdf/tests/rdf_test.info index 416586d8..c2bcfb60 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/search/search-block-form.tpl.php b/modules/search/search-block-form.tpl.php index fe8a6f33..3338fb11 100644 --- a/modules/search/search-block-form.tpl.php +++ b/modules/search/search-block-form.tpl.php @@ -1,5 +1,5 @@ <?php -// $Id: search-block-form.tpl.php,v 1.3 2008/12/30 16:43:18 dries Exp $ +// $Id: search-block-form.tpl.php,v 1.4 2010/06/03 13:18:48 dries Exp $ /** * @file @@ -34,5 +34,8 @@ */ ?> <div class="container-inline"> + <?php if (empty($variables['form']['#block']->title)) : ?> + <h2 class="element-invisible"><?php print t('Search form'); ?></h2> + <?php endif; ?> <?php print $search_form; ?> </div> diff --git a/modules/search/search-result.tpl.php b/modules/search/search-result.tpl.php index 021437bc..e804545b 100644 --- a/modules/search/search-result.tpl.php +++ b/modules/search/search-result.tpl.php @@ -1,5 +1,5 @@ <?php -// $Id: search-result.tpl.php,v 1.4 2008/12/30 16:43:18 dries Exp $ +// $Id: search-result.tpl.php,v 1.6 2010/05/29 07:50:33 dries Exp $ /** * @file @@ -46,14 +46,16 @@ * @see template_preprocess_search_result() */ ?> -<dt class="title"> - <a href="<?php print $url; ?>"><?php print $title; ?></a> -</dt> -<dd> - <?php if ($snippet) : ?> - <p class="search-snippet"><?php print $snippet; ?></p> - <?php endif; ?> - <?php if ($info) : ?> - <p class="search-info"><?php print $info; ?></p> - <?php endif; ?> -</dd> +<li> + <h3 class="title"> + <a href="<?php print $url; ?>"><?php print $title; ?></a> + </h3> + <div class="search-snippet-info"> + <?php if ($snippet) : ?> + <p class="search-snippet"><?php print $snippet; ?></p> + <?php endif; ?> + <?php if ($info) : ?> + <p class="search-info"><?php print $info; ?></p> + <?php endif; ?> + </div> +</li> diff --git a/modules/search/search-results.tpl.php b/modules/search/search-results.tpl.php index 48e17d7c..db218d74 100644 --- a/modules/search/search-results.tpl.php +++ b/modules/search/search-results.tpl.php @@ -1,5 +1,5 @@ <?php -// $Id: search-results.tpl.php,v 1.5 2010/01/30 07:59:25 dries Exp $ +// $Id: search-results.tpl.php,v 1.6 2010/05/28 11:53:57 dries Exp $ /** * @file @@ -23,9 +23,9 @@ ?> <?php if ($search_results) : ?> <h2><?php print t('Search results');?></h2> - <dl class="search-results <?php print $type; ?>-results"> + <ol class="search-results <?php print $type; ?>-results"> <?php print $search_results; ?> - </dl> + </ol> <?php print $pager; ?> <?php else : ?> <h2><?php print t('Your search yielded no results');?></h2> diff --git a/modules/search/search-rtl.css b/modules/search/search-rtl.css index 8f3c150b..24f7bb0b 100644 --- a/modules/search/search-rtl.css +++ b/modules/search/search-rtl.css @@ -1,4 +1,4 @@ -/* $Id: search-rtl.css,v 1.3 2007/11/27 12:09:26 goba Exp $ */ +/* $Id: search-rtl.css,v 1.5 2010/05/29 07:50:33 dries Exp $ */ .search-advanced .criterion { float: right; @@ -9,3 +9,6 @@ float: right; clear: right; } +.search-results .search-snippet-info { + padding-right: 1em; /* LTR */ +} \ No newline at end of file diff --git a/modules/search/search.admin.inc b/modules/search/search.admin.inc index cadcbb24..71844a12 100644 --- a/modules/search/search.admin.inc +++ b/modules/search/search.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: search.admin.inc,v 1.14 2010/01/30 07:59:25 dries Exp $ +// $Id: search.admin.inc,v 1.15 2010/06/26 21:32:20 dries Exp $ /** * @file @@ -83,7 +83,7 @@ function search_admin_settings($form) { $form['indexing_throttle']['search_cron_limit'] = array( '#type' => 'select', '#title' => t('Number of items to index per cron run'), - '#default_value' => 100, + '#default_value' => variable_get('search_cron_limit', 100), '#options' => $items, '#description' => t('The maximum number of items indexed in each pass of a <a href="@cron">cron maintenance task</a>. If necessary, reduce the number of items to prevent timeouts and memory errors while indexing.', array('@cron' => url('admin/reports/status'))) ); @@ -98,7 +98,7 @@ function search_admin_settings($form) { $form['indexing_settings']['minimum_word_size'] = array( '#type' => 'textfield', '#title' => t('Minimum word length to index'), - '#default_value' => 3, + '#default_value' => variable_get('minimum_word_size', 3), '#size' => 5, '#maxlength' => 3, '#description' => t('The number of characters a word has to be to be indexed. A lower setting means better search result ranking, but also a larger database. Each search query must contain at least one keyword that is this size (or longer).') @@ -106,7 +106,7 @@ function search_admin_settings($form) { $form['indexing_settings']['overlap_cjk'] = array( '#type' => 'checkbox', '#title' => t('Simple CJK handling'), - '#default_value' => TRUE, + '#default_value' => variable_get('overlap_cjk', TRUE), '#description' => t('Whether to apply a simple Chinese/Japanese/Korean tokenizer based on overlapping sequences. Turn this off if you want to use an external preprocessor for this instead. Does not affect other languages.') ); @@ -116,7 +116,7 @@ function search_admin_settings($form) { ); $form['active']['search_active_modules'] = array( '#type' => 'checkboxes', - '#default_value' => array('node', 'user'), + '#default_value' => variable_get('search_active_modules', array('node', 'user')), '#options' => _search_get_module_names(), '#description' => t('Determine which search modules are active from the available modules.') ); @@ -131,7 +131,7 @@ function search_admin_settings($form) { } } - return system_settings_form($form, TRUE); + return system_settings_form($form); } /** diff --git a/modules/search/search.api.php b/modules/search/search.api.php index f366e643..49b39545 100644 --- a/modules/search/search.api.php +++ b/modules/search/search.api.php @@ -1,5 +1,5 @@ <?php -// $Id: search.api.php,v 1.26 2010/05/12 15:53:43 dries Exp $ +// $Id: search.api.php,v 1.27 2010/06/01 17:56:46 dries Exp $ /** * @file @@ -218,7 +218,7 @@ function hook_search_execute($keys = NULL) { * its search results, which is otherwise themed using theme('search_results'). * * Note that by default, theme('search_results') and theme('search_result') - * work together to create a definition list (DL). So your hook_search_page() + * work together to create an ordered list (OL). So your hook_search_page() * implementation should probably do this as well. * * @see search-result.tpl.php, search-results.tpl.php @@ -231,12 +231,12 @@ function hook_search_execute($keys = NULL) { * a pager included. */ function hook_search_page($results) { - $output = '<dl class="search-results">'; + $output = '<ol class="search-results">'; foreach ($results as $entry) { $output .= theme('search_result', $entry, $type); } - $output .= '</dl>'; + $output .= '</ol>'; $output .= theme('pager', NULL); return $output; diff --git a/modules/search/search.css b/modules/search/search.css index ab886369..223c31a8 100644 --- a/modules/search/search.css +++ b/modules/search/search.css @@ -1,4 +1,4 @@ -/* $Id: search.css,v 1.3 2007/10/31 18:06:38 dries Exp $ */ +/* $Id: search.css,v 1.5 2010/05/29 07:50:33 dries Exp $ */ .search-form { margin-bottom: 1em; @@ -7,15 +7,21 @@ margin-top: 0; margin-bottom: 0; } +.search-results { + list-style: none; +} .search-results p { margin-top: 0; } -.search-results dt { - font-size: 1.1em; +.search-results .title { + font-size: 1.2em; } -.search-results dd { +.search-results li { margin-bottom: 1em; } +.search-results .search-snippet-info { + padding-left: 1em; /* LTR */ +} .search-results .search-info { font-size: 0.85em; } diff --git a/modules/search/search.info b/modules/search/search.info index ab1e5798..2e781b9b 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/search/search.module b/modules/search/search.module index 910b2d20..7f1bfa35 100644 --- a/modules/search/search.module +++ b/modules/search/search.module @@ -1,73 +1,11 @@ <?php -// $Id: search.module,v 1.348 2010/05/09 19:46:11 dries Exp $ +// $Id: search.module,v 1.351 2010/06/30 15:47:50 dries Exp $ /** * @file * Enables site-wide keyword searching. */ -/** - * Matches Unicode character classes to exclude from the search index. - * - * @see http://unicode.org/glossary - * - * Characters with the following General_category (gc) property values are - * excluded from the search index. Also, they are used as word boundaries. - * While this does not fully conform to the Word Boundaries algorithm - * described in http://unicode.org/reports/tr29, as PCRE does not contain the - * Word_Break property table, this simpler algorithm has to do. - * - Cc, Cf, Cn, Co, Cs: Other. - * - Pc, Pd, Pe, Pf, Pi, Po, Ps: Punctuation. - * - Sc, Sk, Sm, So: Symbols. - * - Zl, Zp, Zs: Separators. - * - * Consequently, the index only contains characters with the following - * General_category (gc) property values: - * - Ll, Lm, Lo, Lt, Lu: Letters. - * - Mc, Me, Mn: Combining Marks. - * - Nd, Nl, No: Numbers. - * - * Note that the PCRE property matcher is not used because we wanted to be - * compatible with Unicode 5.2.0 regardless of the PCRE version used (and any - * bugs in PCRE property tables). - */ -define('PREG_CLASS_SEARCH_EXCLUDE', - '\x{0}-\x{2F}\x{3A}-\x{40}\x{5B}-\x{60}\x{7B}-\x{A9}\x{AB}-\x{B1}\x{B4}' . - '\x{B6}-\x{B8}\x{BB}\x{BF}\x{D7}\x{F7}\x{2C2}-\x{2C5}\x{2D2}-\x{2DF}' . - '\x{2E5}-\x{2EB}\x{2ED}\x{2EF}-\x{2FF}\x{375}\x{37E}-\x{385}\x{387}\x{3F6}' . - '\x{482}\x{55A}-\x{55F}\x{589}-\x{58A}\x{5BE}\x{5C0}\x{5C3}\x{5C6}' . - '\x{5F3}-\x{60F}\x{61B}-\x{61F}\x{66A}-\x{66D}\x{6D4}\x{6DD}\x{6E9}' . - '\x{6FD}-\x{6FE}\x{700}-\x{70F}\x{7F6}-\x{7F9}\x{830}-\x{83E}' . - '\x{964}-\x{965}\x{970}\x{9F2}-\x{9F3}\x{9FA}-\x{9FB}\x{AF1}\x{B70}' . - '\x{BF3}-\x{BFA}\x{C7F}\x{CF1}-\x{CF2}\x{D79}\x{DF4}\x{E3F}\x{E4F}' . - '\x{E5A}-\x{E5B}\x{F01}-\x{F17}\x{F1A}-\x{F1F}\x{F34}\x{F36}\x{F38}' . - '\x{F3A}-\x{F3D}\x{F85}\x{FBE}-\x{FC5}\x{FC7}-\x{FD8}\x{104A}-\x{104F}' . - '\x{109E}-\x{109F}\x{10FB}\x{1360}-\x{1368}\x{1390}-\x{1399}\x{1400}' . - '\x{166D}-\x{166E}\x{1680}\x{169B}-\x{169C}\x{16EB}-\x{16ED}' . - '\x{1735}-\x{1736}\x{17B4}-\x{17B5}\x{17D4}-\x{17D6}\x{17D8}-\x{17DB}' . - '\x{1800}-\x{180A}\x{180E}\x{1940}-\x{1945}\x{19DE}-\x{19FF}' . - '\x{1A1E}-\x{1A1F}\x{1AA0}-\x{1AA6}\x{1AA8}-\x{1AAD}\x{1B5A}-\x{1B6A}' . - '\x{1B74}-\x{1B7C}\x{1C3B}-\x{1C3F}\x{1C7E}-\x{1C7F}\x{1CD3}\x{1FBD}' . - '\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}' . - '\x{1FFD}-\x{206F}\x{207A}-\x{207E}\x{208A}-\x{208E}\x{20A0}-\x{20B8}' . - '\x{2100}-\x{2101}\x{2103}-\x{2106}\x{2108}-\x{2109}\x{2114}' . - '\x{2116}-\x{2118}\x{211E}-\x{2123}\x{2125}\x{2127}\x{2129}\x{212E}' . - '\x{213A}-\x{213B}\x{2140}-\x{2144}\x{214A}-\x{214D}\x{214F}' . - '\x{2190}-\x{244A}\x{249C}-\x{24E9}\x{2500}-\x{2775}\x{2794}-\x{2B59}' . - '\x{2CE5}-\x{2CEA}\x{2CF9}-\x{2CFC}\x{2CFE}-\x{2CFF}\x{2E00}-\x{2E2E}' . - '\x{2E30}-\x{3004}\x{3008}-\x{3020}\x{3030}\x{3036}-\x{3037}' . - '\x{303D}-\x{303F}\x{309B}-\x{309C}\x{30A0}\x{30FB}\x{3190}-\x{3191}' . - '\x{3196}-\x{319F}\x{31C0}-\x{31E3}\x{3200}-\x{321E}\x{322A}-\x{3250}' . - '\x{3260}-\x{327F}\x{328A}-\x{32B0}\x{32C0}-\x{33FF}\x{4DC0}-\x{4DFF}' . - '\x{A490}-\x{A4C6}\x{A4FE}-\x{A4FF}\x{A60D}-\x{A60F}\x{A673}\x{A67E}' . - '\x{A6F2}-\x{A716}\x{A720}-\x{A721}\x{A789}-\x{A78A}\x{A828}-\x{A82B}' . - '\x{A836}-\x{A839}\x{A874}-\x{A877}\x{A8CE}-\x{A8CF}\x{A8F8}-\x{A8FA}' . - '\x{A92E}-\x{A92F}\x{A95F}\x{A9C1}-\x{A9CD}\x{A9DE}-\x{A9DF}' . - '\x{AA5C}-\x{AA5F}\x{AA77}-\x{AA79}\x{AADE}-\x{AADF}\x{ABEB}' . - '\x{D800}-\x{F8FF}\x{FB29}\x{FD3E}-\x{FD3F}\x{FDFC}-\x{FDFD}' . - '\x{FE10}-\x{FE19}\x{FE30}-\x{FE6B}\x{FEFF}-\x{FF0F}\x{FF1A}-\x{FF20}' . - '\x{FF3B}-\x{FF40}\x{FF5B}-\x{FF65}\x{FFE0}-\x{FFFD}'); - /** * Matches all 'N' Unicode character classes (numbers) */ @@ -178,9 +116,6 @@ function search_theme() { 'file' => 'search.pages.inc', 'template' => 'search-results', ), - 'search_results_listing' => array( - 'variables' => array('title' => NULL, 'content' => NULL), - ), ); } @@ -250,15 +185,6 @@ function search_menu() { 'type' => MENU_CALLBACK, 'file' => 'search.admin.inc', ); - $items['admin/reports/search'] = array( - 'title' => 'Top search phrases', - 'description' => 'View most popular search phrases.', - 'page callback' => 'dblog_top', - 'page arguments' => array('search'), - 'access arguments' => array('access site reports'), - '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 @@ -454,7 +380,7 @@ function search_simplify($text) { // With the exception of the rules above, we consider all punctuation, // marks, spacers, etc, to be a word boundary. - $text = preg_replace('/[' . PREG_CLASS_SEARCH_EXCLUDE . ']+/u', ' ', $text); + $text = preg_replace('/[' . PREG_CLASS_UNICODE_WORD_BOUNDARY . ']+/u', ' ', $text); return $text; } @@ -1075,7 +1001,7 @@ function search_data($keys = NULL, $type = 'node') { */ function search_excerpt($keys, $text) { // We highlight around non-indexable or CJK characters. - $boundary = '(?:(?<=[' . PREG_CLASS_SEARCH_EXCLUDE . PREG_CLASS_CJK . '])|(?=[' . PREG_CLASS_SEARCH_EXCLUDE . PREG_CLASS_CJK . ']))'; + $boundary = '(?:(?<=[' . PREG_CLASS_UNICODE_WORD_BOUNDARY . PREG_CLASS_CJK . '])|(?=[' . PREG_CLASS_UNICODE_WORD_BOUNDARY . PREG_CLASS_CJK . ']))'; // Extract positive keywords and phrases preg_match_all('/ ("([^"]+)"|(?!OR)([^" ]+))/', ' ' . $keys, $matches); diff --git a/modules/search/search.pages.inc b/modules/search/search.pages.inc index f6058b60..ea776854 100644 --- a/modules/search/search.pages.inc +++ b/modules/search/search.pages.inc @@ -1,5 +1,5 @@ <?php -// $Id: search.pages.inc,v 1.19 2010/05/01 01:04:24 webchick Exp $ +// $Id: search.pages.inc,v 1.20 2010/06/30 15:47:50 dries Exp $ /** * @file @@ -34,10 +34,7 @@ function search_view($type = 'node') { // Construct the search form. $build['search_form'] = drupal_get_form('search_form', NULL, $keys, $type); - $build['search_results'] = array( - '#theme' => 'search_results_listing', - '#content' => $results, - ); + $build['search_results'] = array('#markup' => $results); return $build; } @@ -46,21 +43,6 @@ function search_view($type = 'node') { return drupal_get_form('search_form', NULL, empty($keys) ? '' : $keys, $type); } -/** - * Returns HTML for a listing of search results. - * - * @param $variables - * An associative array containing: - * - title: The subject of the listing. - * - content: The content of the listing. - * - * @ingroup themeable - */ -function theme_search_results_listing($variables) { - $output = '<h2 class="title">' . $variables['title'] . '</h2><div>' . $variables['content'] . '</div>'; - return $output; -} - /** * Process variables for search-results.tpl.php. * diff --git a/modules/search/search.test b/modules/search/search.test index f129e03d..120d11fc 100644 --- a/modules/search/search.test +++ b/modules/search/search.test @@ -1,5 +1,5 @@ <?php -// $Id: search.test,v 1.63 2010/05/12 15:53:43 dries Exp $ +// $Id: search.test,v 1.64 2010/06/08 06:34:36 webchick 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. @@ -674,13 +674,15 @@ class SearchCommentTestCase extends DrupalWebTestCase { // Create and log in an administrative user having access to the Full HTML // text format. $full_html_format = db_query_range('SELECT * FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Full HTML'))->fetchObject(); - $this->admin_user = $this->drupalCreateUser(array( + $permissions = array( 'administer filters', filter_permission_name($full_html_format), 'administer permissions', 'create page content', 'post comments without approval', - )); + 'access comments', + ); + $this->admin_user = $this->drupalCreateUser($permissions); $this->drupalLogin($this->admin_user); } @@ -700,12 +702,7 @@ class SearchCommentTestCase extends DrupalWebTestCase { // Allow anonymous users to search content. $edit = array( DRUPAL_ANONYMOUS_RID . '[search content]' => 1, - // @todo Comments are added to search index without checking first whether - // anonymous users are allowed to access comments. DRUPAL_ANONYMOUS_RID . '[access comments]' => 1, - // @todo Without this permission, "Login or register to post comments" is - // added to the search index. Comment.module is not guilty; that text - // seems to be added via node links. DRUPAL_ANONYMOUS_RID . '[post comments]' => 1, ); $this->drupalPost('admin/people/permissions', $edit, t('Save permissions')); @@ -757,6 +754,96 @@ class SearchCommentTestCase extends DrupalWebTestCase { $this->drupalPost('', $edit, t('Search')); $this->assertNoText($comment_body, t('Comment body text not found in search results.')); } + + /** + * Verify access rules for comment indexing with different permissions. + */ + function testSearchResultsCommentAccess() { + $comment_body = 'Test comment body'; + $this->comment_subject = 'Test comment subject'; + $this->admin_role = $this->admin_user->roles; + unset($this->admin_role[DRUPAL_AUTHENTICATED_RID]); + $this->admin_role = key($this->admin_role); + + // Create a node. + variable_set('comment_preview_article', DRUPAL_OPTIONAL); + $this->node = $this->drupalCreateNode(array('type' => 'article')); + + // Post a comment using 'Full HTML' text format. + $edit_comment = array(); + $edit_comment['subject'] = $this->comment_subject; + $edit_comment['comment_body[' . LANGUAGE_NONE . '][0][value]'] = '<h1>' . $comment_body . '</h1>'; + $this->drupalPost('comment/reply/' . $this->node->nid, $edit_comment, t('Save')); + + $this->drupalLogout(); + $this->setRolePermissions(DRUPAL_ANONYMOUS_RID); + $this->checkCommentAccess('Anon user has search permission but no access comments permission, comments should not be indexed'); + + $this->setRolePermissions(DRUPAL_ANONYMOUS_RID, TRUE); + $this->checkCommentAccess('Anon user has search permission and access comments permission, comments should be indexed', TRUE); + + $this->drupalLogin($this->admin_user); + $this->drupalGet('admin/people/permissions'); + + // Disable search access for authenticated user to test admin user. + $this->setRolePermissions(DRUPAL_AUTHENTICATED_RID, FALSE, FALSE); + + $this->setRolePermissions($this->admin_role); + $this->checkCommentAccess('Admin user has search permission but no access comments permission, comments should not be indexed'); + + $this->setRolePermissions($this->admin_role, TRUE); + $this->checkCommentAccess('Admin user has search permission and access comments permission, comments should be indexed', TRUE); + + $this->setRolePermissions(DRUPAL_AUTHENTICATED_RID); + $this->checkCommentAccess('Authenticated user has search permission but no access comments permission, comments should not be indexed'); + + $this->setRolePermissions(DRUPAL_AUTHENTICATED_RID, TRUE); + $this->checkCommentAccess('Authenticated user has search permission and access comments permission, comments should be indexed', TRUE); + + // Verify that access comments permission is inherited from the + // authenticated role. + $this->setRolePermissions(DRUPAL_AUTHENTICATED_RID, TRUE, FALSE); + $this->setRolePermissions($this->admin_role); + $this->checkCommentAccess('Admin user has search permission and no access comments permission, but comments should be indexed because admin user inherits authenticated user\'s permission to access comments', TRUE); + + // Verify that search content permission is inherited from the authenticated + // role. + $this->setRolePermissions(DRUPAL_AUTHENTICATED_RID, TRUE, TRUE); + $this->setRolePermissions($this->admin_role, TRUE, FALSE); + $this->checkCommentAccess('Admin user has access comments permission and no search permission, but comments should be indexed because admin user inherits authenticated user\'s permission to search', TRUE); + + } + + /** + * Set permissions for role. + */ + function setRolePermissions($rid, $access_comments = FALSE, $search_content = TRUE) { + $permissions = array( + 'access comments' => $access_comments, + 'search content' => $search_content, + ); + user_role_change_permissions($rid, $permissions); + } + + /** + * Update search index and search for comment. + */ + function checkCommentAccess($message, $assume_access = FALSE) { + // Invoke search index update. + search_touch_node($this->node->nid); + $this->cronRun(); + + // Search for the comment subject. + $edit = array( + 'search_block_form' => "'" . $this->comment_subject . "'", + ); + $this->drupalPost('', $edit, t('Search')); + $method = $assume_access ? 'assertText' : 'assertNoText'; + $verb = $assume_access ? 'found' : 'not found'; + $this->{$method}($this->node->title, "Node $verb in search results: " . $message); + $this->{$method}($this->comment_subject, "Comment subject $verb in search results: " . $message); + } + } /** diff --git a/modules/shortcut/shortcut.admin.inc b/modules/shortcut/shortcut.admin.inc index 80d741d4..d8eadca6 100644 --- a/modules/shortcut/shortcut.admin.inc +++ b/modules/shortcut/shortcut.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: shortcut.admin.inc,v 1.13 2010/04/24 14:49:14 dries Exp $ +// $Id: shortcut.admin.inc,v 1.14 2010/06/30 15:12:59 dries Exp $ /** * @file @@ -33,6 +33,7 @@ function shortcut_max_slots() { * An array representing the form definition. * * @ingroup forms + * @see shortcut_set_switch_validate() * @see shortcut_set_switch_submit() */ function shortcut_set_switch($form, &$form_state, $account = NULL) { @@ -101,6 +102,22 @@ function shortcut_set_switch($form, &$form_state, $account = NULL) { return $form; } +/** + * Validation handler for shortcut_set_switch(). + */ +function shortcut_set_switch_validate($form, &$form_state) { + if ($form_state['values']['set'] == 'new') { + // Check to prevent creating a shortcut set with an empty title. + if (trim($form_state['values']['new']) == '') { + form_set_error('new', t('The new set name is required.')); + } + // Check to prevent a duplicate title. + if (shortcut_set_title_exists($form_state['values']['new'])) { + form_set_error('new', t('The shortcut set %name already exists. Choose another name.', array('%name' => $form_state['values']['new']))); + } + } +} + /** * Submit handler for shortcut_set_switch(). */ @@ -184,6 +201,7 @@ function shortcut_set_admin() { * An array representing the form definition. * * @ingroup forms + * @see shortcut_set_add_form_validate() * @see shortcut_set_add_form_submit() */ function shortcut_set_add_form($form, &$form_state) { @@ -191,6 +209,7 @@ function shortcut_set_add_form($form, &$form_state) { '#type' => 'textfield', '#title' => t('Set name'), '#description' => t('The new set is created by copying items from your default shortcut set.'), + '#required' => TRUE, ); $form['actions'] = array('#type' => 'actions'); @@ -202,6 +221,16 @@ function shortcut_set_add_form($form, &$form_state) { return $form; } +/** + * Validation handler for shortcut_set_add_form(). + */ +function shortcut_set_add_form_validate($form, &$form_state) { + // Check to prevent a duplicate title. + if (shortcut_set_title_exists($form_state['values']['new'])) { + form_set_error('new', t('The shortcut set %name already exists. Choose another name.', array('%name' => $form_state['values']['new']))); + } +} + /** * Submit handler for shortcut_set_add_form(). */ @@ -547,6 +576,7 @@ function shortcut_admin_add_link($shortcut_link, &$shortcut_set, $limit = NULL) * An array representing the form definition. * * @ingroup forms + * @see shortcut_set_edit_form_validate() * @see shortcut_set_edit_form_submit() */ function shortcut_set_edit_form($form, &$form_state, $shortcut_set) { @@ -572,6 +602,17 @@ function shortcut_set_edit_form($form, &$form_state, $shortcut_set) { return $form; } +/** + * Validation handler for shortcut_set_edit_form(). + */ +function shortcut_set_edit_form_validate($form, &$form_state) { + // Check to prevent a duplicate title, if the title was edited from its + // original value. + if ($form_state['values']['title'] != $form_state['values']['shortcut_set']->title && shortcut_set_title_exists($form_state['values']['title'])) { + form_set_error('title', t('The shortcut set %name already exists. Choose another name.', array('%name' => $form_state['values']['title']))); + } +} + /** * Submit handler for shortcut_set_edit_form(). */ diff --git a/modules/shortcut/shortcut.css b/modules/shortcut/shortcut.css index f8422921..51556ab2 100644 --- a/modules/shortcut/shortcut.css +++ b/modules/shortcut/shortcut.css @@ -1,4 +1,4 @@ -/* $Id: shortcut.css,v 1.7 2010/03/20 14:45:05 dries Exp $ */ +/* $Id: shortcut.css,v 1.8 2010/05/31 08:18:02 dries Exp $ */ div#toolbar a#edit-shortcuts { float: right; padding: 5px 10px 5px 5px; @@ -62,12 +62,14 @@ div.add-or-remove-shortcuts a span.icon { margin-left:8px; } +div.add-shortcut a:focus span.icon, div.add-shortcut a:hover span.icon { background-position: 0 -12px; } div.remove-shortcut a span.icon { background-position: -12px 0; } +div.remove-shortcut a:focus span.icon, div.remove-shortcut a:hover span.icon { background-position: -12px -12px; } @@ -78,6 +80,7 @@ div.add-or-remove-shortcuts a span.text { display: none; } +div.add-or-remove-shortcuts a:focus span.text, div.add-or-remove-shortcuts a:hover span.text { font-size: 10px; line-height: 12px; diff --git a/modules/shortcut/shortcut.info b/modules/shortcut/shortcut.info index d93bd418..6db5b7e5 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/shortcut/shortcut.install b/modules/shortcut/shortcut.install index df9fa785..b2e19d3e 100644 --- a/modules/shortcut/shortcut.install +++ b/modules/shortcut/shortcut.install @@ -1,5 +1,5 @@ <?php -// $Id: shortcut.install,v 1.4 2010/02/26 18:31:29 dries Exp $ +// $Id: shortcut.install,v 1.5 2010/06/17 13:16:57 dries Exp $ /** * @file @@ -12,7 +12,7 @@ function shortcut_install() { $t = get_t(); // Create an initial default shortcut set. - $shortcut_set = new StdClass(); + $shortcut_set = new stdClass(); $shortcut_set->title = $t('Default'); $shortcut_set->links = array( array( diff --git a/modules/shortcut/shortcut.module b/modules/shortcut/shortcut.module index 6eaa6db7..464312df 100644 --- a/modules/shortcut/shortcut.module +++ b/modules/shortcut/shortcut.module @@ -1,5 +1,5 @@ <?php -// $Id: shortcut.module,v 1.24 2010/03/06 06:33:14 dries Exp $ +// $Id: shortcut.module,v 1.25 2010/06/30 15:12:59 dries Exp $ /** * @file @@ -582,6 +582,19 @@ function shortcut_sets() { ->fetchAllAssoc('set_name'); } +/** + * Check to see if a shortcut set with the given title already exists. + * + * @param $title + * Human-readable name of the shortcut set to check. + * + * @return + * TRUE if a shortcut set with that title exists; FALSE otherwise. + */ +function shortcut_set_title_exists($title) { + return (bool) db_query_range('SELECT 1 FROM {shortcut_set} WHERE title = :title', 0, 1, array(':title' => $title))->fetchField(); +} + /** * Determines if a path corresponds to a valid shortcut link. * diff --git a/modules/shortcut/shortcut.test b/modules/shortcut/shortcut.test index 76ff3c97..f9c93ef3 100644 --- a/modules/shortcut/shortcut.test +++ b/modules/shortcut/shortcut.test @@ -1,5 +1,5 @@ <?php -// $Id: shortcut.test,v 1.1 2010/03/06 06:33:14 dries Exp $ +// $Id: shortcut.test,v 1.3 2010/06/30 15:12:59 dries Exp $ /** * @file @@ -48,7 +48,7 @@ class ShortcutTestCase extends DrupalWebTestCase { * Creates a generic shortcut set. */ function generateShortcutSet($title = '', $default_links = TRUE) { - $set = new stdClass; + $set = new stdClass(); $set->title = empty($title) ? $this->randomName(10) : $title; if ($default_links) { $set->links = array(); @@ -238,6 +238,31 @@ class ShortcutSetsTestCase extends ShortcutTestCase { $this->assertTrue($new_set->set_name == $current_set->set_name, "Successfully switched another user's shortcut set."); } + /** + * Tests switching a user's shortcut set and creating one at the same time. + */ + function testShortcutSetSwitchCreate() { + $edit = array( + 'set' => 'new', + 'new' => $this->randomName(10), + ); + $this->drupalPost('user/' . $this->admin_user->uid . '/shortcuts', $edit, t('Change set')); + $current_set = shortcut_current_displayed_set($this->admin_user); + $this->assertNotEqual($current_set->set_name, $this->set->set_name, 'A shortcut set can be switched to at the same time as it is created.'); + $this->assertEqual($current_set->title, $edit['new'], 'The new set is correctly assigned to the user.'); + } + + /** + * Tests switching a user's shortcut set without providing a new set name. + */ + function testShortcutSetSwitchNoSetName() { + $edit = array('set' => 'new'); + $this->drupalPost('user/' . $this->admin_user->uid . '/shortcuts', $edit, t('Change set')); + $this->assertText(t('The new set name is required.')); + $current_set = shortcut_current_displayed_set($this->admin_user); + $this->assertEqual($current_set->set_name, $this->set->set_name, 'Attempting to switch to a new shortcut set without providing a set name does not succeed.'); + } + /** * Tests that shortcut_set_save() correctly updates existing links. */ @@ -265,6 +290,18 @@ class ShortcutSetsTestCase extends ShortcutTestCase { $this->assertTrue($set->title == $new_title, 'Shortcut set has been successfully renamed.'); } + /** + * Tests renaming a shortcut set to the same name as another set. + */ + function testShortcutSetRenameAlreadyExists() { + $set = $this->generateShortcutSet($this->randomName(10)); + $existing_title = $this->set->title; + $this->drupalPost('admin/config/user-interface/shortcut/' . $set->set_name . '/edit', array('title' => $existing_title), t('Save')); + $this->assertRaw(t('The shortcut set %name already exists. Choose another name.', array('%name' => $existing_title))); + $set = shortcut_set_load($set->set_name); + $this->assertNotEqual($set->title, $existing_title, t('The shortcut set %title cannot be renamed to %new-title because a shortcut set with that title already exists.', array('%title' => $set->title, '%new-title' => $existing_title))); + } + /** * Tests deleting a shortcut set. */ diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index a781958e..6e6785d2 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -1,5 +1,17 @@ <?php -// $Id: drupal_web_test_case.php,v 1.217 2010/05/10 06:38:23 dries Exp $ +// $Id: drupal_web_test_case.php,v 1.224 2010/07/08 12:22:59 dries Exp $ + +/** + * Global variable that holds information about the tests being run. + * + * An array, with the following keys: + * - 'test_run_id': the ID of the test being run, in the form 'simpletest_%" + * - 'in_child_site': TRUE if the current request is a cURL request from + * the parent site. + * + * @var array + */ +global $drupal_test_info; /** * Base class for Drupal tests. @@ -15,11 +27,11 @@ abstract class DrupalTestCase { protected $testId; /** - * The original database prefix, before it was changed for testing purposes. + * The database prefix of this test run. * * @var string */ - protected $originalPrefix = NULL; + protected $databasePrefix = NULL; /** * The original file directory, before it was changed for testing purposes. @@ -63,7 +75,7 @@ abstract class DrupalTestCase { protected $skipClasses = array(__CLASS__ => TRUE); /** - * Constructor for DrupalWebTestCase. + * Constructor for DrupalTestCase. * * @param $test_id * Tests with the same id are reported together. @@ -90,8 +102,6 @@ abstract class DrupalTestCase { * is the caller function itself. */ protected function assert($status, $message = '', $group = 'Other', array $caller = NULL) { - global $db_prefix; - // Convert boolean status to string status. if (is_bool($status)) { $status = $status ? 'pass' : 'fail'; @@ -105,10 +115,6 @@ abstract class DrupalTestCase { $caller = $this->getAssertionCall(); } - // Switch to non-testing database to store results in. - $current_db_prefix = $db_prefix; - $db_prefix = $this->originalPrefix; - // Creation assertion array that can be displayed while tests are running. $this->assertions[] = $assertion = array( 'test_id' => $this->testId, @@ -122,12 +128,11 @@ abstract class DrupalTestCase { ); // Store assertion for display after the test has completed. - db_insert('simpletest') + Database::getConnection('default', 'simpletest_original_default') + ->insert('simpletest') ->fields($assertion) ->execute(); - // Return to testing prefix. - $db_prefix = $current_db_prefix; // We do not use a ternary operator here to allow a breakpoint on // test failure. if ($status == 'pass') { @@ -560,10 +565,9 @@ class DrupalUnitTestCase extends DrupalTestCase { } protected function setUp() { - global $db_prefix, $conf; + global $conf; - // Store necessary current values before switching to prefixed database. - $this->originalPrefix = $db_prefix; + // Store necessary current values before switching to the test environment. $this->originalFileDirectory = file_directory_path(); spl_autoload_register('db_autoload'); @@ -572,11 +576,21 @@ class DrupalUnitTestCase extends DrupalTestCase { drupal_static_reset(); // Generate temporary prefixed database to ensure that tests have a clean starting point. - $db_prefix = Database::getConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}'); - $conf['file_public_path'] = $this->originalFileDirectory . '/' . $db_prefix; + $this->databasePrefix = Database::getConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}'); + $conf['file_public_path'] = $this->originalFileDirectory . '/' . $this->databasePrefix; + + // Clone the current connection and replace the current prefix. + $connection_info = Database::getConnectionInfo('default'); + Database::renameConnection('default', 'simpletest_original_default'); + foreach ($connection_info as $target => $value) { + $connection_info[$target]['prefix'] = array( + 'default' => $value['prefix']['default'] . $this->databasePrefix, + ); + } + Database::addConnectionInfo('default', 'default', $connection_info['default']); // Set user agent to be consistent with web test case. - $_SERVER['HTTP_USER_AGENT'] = $db_prefix; + $_SERVER['HTTP_USER_AGENT'] = $this->databasePrefix; // If locale is enabled then t() will try to access the database and // subsequently will fail as the database is not accessible. @@ -589,15 +603,16 @@ class DrupalUnitTestCase extends DrupalTestCase { } protected function tearDown() { - global $db_prefix, $conf; - if (preg_match('/simpletest\d+/', $db_prefix)) { - $conf['file_public_path'] = $this->originalFileDirectory; - // Return the database prefix to the original. - $db_prefix = $this->originalPrefix; - // Restore modules if necessary. - if (isset($this->originalModuleList)) { - module_list(TRUE, FALSE, FALSE, $this->originalModuleList); - } + global $conf; + + // Get back to the original connection. + Database::removeConnection('default'); + Database::renameConnection('simpletest_original_default', 'default'); + + $conf['file_public_path'] = $this->originalFileDirectory; + // Restore modules if necessary. + if (isset($this->originalModuleList)) { + module_list(TRUE, FALSE, FALSE, $this->originalModuleList); } } } @@ -1086,7 +1101,8 @@ class DrupalWebTestCase extends DrupalTestCase { // Make a request to the logout page, and redirect to the user page, the // idea being if you were properly logged out you should be seeing a login // screen. - $this->drupalGet('user/logout', array('query' => array('destination' => 'user'))); + $this->drupalGet('user/logout'); + $this->drupalGet('user'); $pass = $this->assertField('name', t('Username field found.'), t('Logout')); $pass = $pass && $this->assertField('pass', t('Password field found.'), t('Logout')); @@ -1107,12 +1123,28 @@ class DrupalWebTestCase extends DrupalTestCase { * either a single array or a variable number of string arguments. */ protected function setUp() { - global $db_prefix, $user, $language, $conf; + global $user, $language, $conf; + + // Generate a temporary prefixed database to ensure that tests have a clean starting point. + $this->databasePrefix = 'simpletest' . mt_rand(1000, 1000000); + db_update('simpletest_test_id') + ->fields(array('last_prefix' => $this->databasePrefix)) + ->condition('test_id', $this->testId) + ->execute(); + + // Clone the current connection and replace the current prefix. + $connection_info = Database::getConnectionInfo('default'); + Database::renameConnection('default', 'simpletest_original_default'); + foreach ($connection_info as $target => $value) { + $connection_info[$target]['prefix'] = array( + 'default' => $value['prefix']['default'] . $this->databasePrefix, + ); + } + Database::addConnectionInfo('default', 'default', $connection_info['default']); // Store necessary current values before switching to prefixed database. $this->originalLanguage = $language; $this->originalLanguageDefault = variable_get('language_default'); - $this->originalPrefix = $db_prefix; $this->originalFileDirectory = file_directory_path(); $this->originalProfile = drupal_get_profile(); $clean_url_original = variable_get('clean_url', 0); @@ -1125,18 +1157,10 @@ class DrupalWebTestCase extends DrupalTestCase { $this->originalShutdownCallbacks = $callbacks; $callbacks = array(); - // Generate temporary prefixed database to ensure that tests have a clean starting point. - $db_prefix_new = Database::getConnection()->prefixTables('{simpletest' . mt_rand(1000, 1000000) . '}'); - db_update('simpletest_test_id') - ->fields(array('last_prefix' => $db_prefix_new)) - ->condition('test_id', $this->testId) - ->execute(); - $db_prefix = $db_prefix_new; - // Create test directory ahead of installation so fatal errors and debug // information can be logged during installation process. // Use temporary files directory with the same prefix as the database. - $public_files_directory = $this->originalFileDirectory . '/simpletest/' . substr($db_prefix, 10); + $public_files_directory = $this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10); $private_files_directory = $public_files_directory . '/private'; $temp_files_directory = $private_files_directory . '/temp'; @@ -1154,6 +1178,11 @@ class DrupalWebTestCase extends DrupalTestCase { $conf = array(); drupal_static_reset(); + // Set the test information for use in other parts of Drupal. + $test_info = &$GLOBALS['drupal_test_info']; + $test_info['test_run_id'] = $this->databasePrefix; + $test_info['in_child_site'] = FALSE; + include_once DRUPAL_ROOT . '/includes/install.inc'; drupal_install_system(); @@ -1230,8 +1259,9 @@ class DrupalWebTestCase extends DrupalTestCase { * setup a clean environment for the current test run. */ protected function preloadRegistry() { - db_query('INSERT INTO {registry} SELECT * FROM ' . $this->originalPrefix . 'registry'); - db_query('INSERT INTO {registry_file} SELECT * FROM ' . $this->originalPrefix . 'registry_file'); + $original_connection = Database::getConnection('default', 'simpletest_original_default'); + db_query('INSERT INTO {registry} SELECT * FROM ' . $original_connection->prefixTables('{registry}')); + db_query('INSERT INTO {registry_file} SELECT * FROM ' . $original_connection->prefixTables('{registry_file}')); } /** @@ -1257,68 +1287,64 @@ class DrupalWebTestCase extends DrupalTestCase { * and reset the database prefix. */ protected function tearDown() { - global $db_prefix, $user, $language; + global $user, $language; // In case a fatal error occured that was not in the test process read the // log to pick up any fatal errors. - $db_prefix_temp = $db_prefix; - $db_prefix = $this->originalPrefix; - simpletest_log_read($this->testId, $db_prefix, get_class($this), TRUE); - $db_prefix = $db_prefix_temp; + simpletest_log_read($this->testId, $this->databasePrefix, get_class($this), TRUE); $emailCount = count(variable_get('drupal_test_email_collector', array())); if ($emailCount) { - $message = format_plural($emailCount, t('!count e-mail was sent during this test.'), t('!count e-mails were sent during this test.'), array('!count' => $emailCount)); + $message = format_plural($emailCount, '1 e-mail was sent during this test.', '@count e-mails were sent during this test.'); $this->pass($message, t('E-mail')); } - if (preg_match('/simpletest\d+/', $db_prefix)) { - // Delete temporary files directory. - file_unmanaged_delete_recursive($this->originalFileDirectory . '/simpletest/' . substr($db_prefix, 10)); - - // Remove all prefixed tables (all the tables in the schema). - $schema = drupal_get_schema(NULL, TRUE); - $ret = array(); - foreach ($schema as $name => $table) { - db_drop_table($name); - } + // Delete temporary files directory. + file_unmanaged_delete_recursive($this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10)); - // Return the database prefix to the original. - $db_prefix = $this->originalPrefix; + // Remove all prefixed tables (all the tables in the schema). + $schema = drupal_get_schema(NULL, TRUE); + $ret = array(); + foreach ($schema as $name => $table) { + db_drop_table($name); + } - // Restore original shutdown callbacks array to prevent original - // environment of calling handlers from test run. - $callbacks = &drupal_register_shutdown_function(); - $callbacks = $this->originalShutdownCallbacks; + // Get back to the original connection. + Database::removeConnection('default'); + Database::renameConnection('simpletest_original_default', 'default'); - // Return the user to the original one. - $user = $this->originalUser; - drupal_save_session(TRUE); + // Restore original shutdown callbacks array to prevent original + // environment of calling handlers from test run. + $callbacks = &drupal_register_shutdown_function(); + $callbacks = $this->originalShutdownCallbacks; - // Ensure that internal logged in variable and cURL options are reset. - $this->loggedInUser = FALSE; - $this->additionalCurlOptions = array(); + // Return the user to the original one. + $user = $this->originalUser; + drupal_save_session(TRUE); - // Reload module list and implementations to ensure that test module hooks - // aren't called after tests. - module_list(TRUE); - module_implements('', FALSE, TRUE); + // Ensure that internal logged in variable and cURL options are reset. + $this->loggedInUser = FALSE; + $this->additionalCurlOptions = array(); - // Reset the Field API. - field_cache_clear(); + // Reload module list and implementations to ensure that test module hooks + // aren't called after tests. + module_list(TRUE); + module_implements('', FALSE, TRUE); - // Rebuild caches. - $this->refreshVariables(); + // Reset the Field API. + field_cache_clear(); - // Reset language. - $language = $this->originalLanguage; - if ($this->originalLanguageDefault) { - $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault; - } + // Rebuild caches. + $this->refreshVariables(); - // Close the CURL handler. - $this->curlClose(); + // Reset language. + $language = $this->originalLanguage; + if ($this->originalLanguageDefault) { + $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault; } + + // Close the CURL handler. + $this->curlClose(); } /** @@ -1330,7 +1356,7 @@ class DrupalWebTestCase extends DrupalTestCase { * See the description of $curl_options for other options. */ protected function curlInitialize() { - global $base_url, $db_prefix; + global $base_url; if (!isset($this->curlHandle)) { $this->curlHandle = curl_init(); @@ -1342,6 +1368,7 @@ class DrupalWebTestCase extends DrupalTestCase { CURLOPT_SSL_VERIFYPEER => FALSE, // Required to make the tests run on https. CURLOPT_SSL_VERIFYHOST => FALSE, // Required to make the tests run on https. CURLOPT_HEADERFUNCTION => array(&$this, 'curlHeaderCallback'), + CURLOPT_USERAGENT => $this->databasePrefix, ); if (isset($this->httpauth_credentials)) { $curl_options[CURLOPT_HTTPAUTH] = $this->httpauth_method; @@ -1354,7 +1381,7 @@ class DrupalWebTestCase extends DrupalTestCase { } // We set the user agent header on each request so as to use the current // time and a new uniqid. - if (preg_match('/simpletest\d+/', $db_prefix, $matches)) { + if (preg_match('/simpletest\d+/', $this->databasePrefix, $matches)) { curl_setopt($this->curlHandle, CURLOPT_USERAGENT, drupal_generate_test_ua($matches[0])); } } @@ -1430,10 +1457,10 @@ class DrupalWebTestCase extends DrupalTestCase { '!method' => !empty($curl_options[CURLOPT_NOBODY]) ? 'HEAD' : (empty($curl_options[CURLOPT_POSTFIELDS]) ? 'GET' : 'POST'), '@url' => isset($original_url) ? $original_url : $url, '@status' => $status, - '!length' => format_size(strlen($this->content)) + '!length' => format_size(strlen($this->drupalGetContent())) ); $message = t('!method @url returned @status (!length).', $message_vars); - $this->assertTrue($this->content !== FALSE, $message, t('Browser')); + $this->assertTrue($this->drupalGetContent() !== FALSE, $message, t('Browser')); return $this->drupalGetContent(); } @@ -1498,7 +1525,7 @@ class DrupalWebTestCase extends DrupalTestCase { if (!$this->elements) { // DOM can load HTML soup. But, HTML soup can throw warnings, suppress // them. - @$htmlDom = DOMDocument::loadHTML($this->content); + @$htmlDom = DOMDocument::loadHTML($this->drupalGetContent()); if ($htmlDom) { $this->pass(t('Valid HTML found on "@path"', array('@path' => $this->getUrl())), t('Browser')); // It's much easier to work with simplexml than DOM, luckily enough @@ -1731,7 +1758,7 @@ class DrupalWebTestCase extends DrupalTestCase { if (isset($path)) { $this->drupalGet($path, $options); } - $content = $this->content; + $content = $this->drupalGetContent(); $return = drupal_json_decode($this->drupalPost(NULL, $edit, array('path' => $ajax_path, 'triggering_element' => $triggering_element), $options, $headers, $form_html_id)); // We need $ajax_settings['wrapper'] to perform DOM manipulation. @@ -2090,7 +2117,7 @@ class DrupalWebTestCase extends DrupalTestCase { * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertLink($label, $index = 0, $message = '', $group = 'Other') { - $links = $this->xpath('//a[text()=:label]', array(':label' => $label)); + $links = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label)); $message = ($message ? $message : t('Link with label %label found.', array('%label' => $label))); return $this->assert(isset($links[$index]), $message, $group); } @@ -2110,7 +2137,7 @@ class DrupalWebTestCase extends DrupalTestCase { * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertNoLink($label, $message = '', $group = 'Other') { - $links = $this->xpath('//a[text()=:label]', array(':label' => $label)); + $links = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label)); $message = ($message ? $message : t('Link with label %label not found.', array('%label' => $label))); return $this->assert(empty($links), $message, $group); } @@ -2172,7 +2199,7 @@ class DrupalWebTestCase extends DrupalTestCase { */ protected function clickLink($label, $index = 0) { $url_before = $this->getUrl(); - $urls = $this->xpath('//a[text()=:label]', array(':label' => $label)); + $urls = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label)); if (isset($urls[$index])) { $url_target = $this->getAbsoluteUrl($urls[$index]['href']); @@ -2376,7 +2403,7 @@ class DrupalWebTestCase extends DrupalTestCase { if (!$message) { $message = t('Raw "@raw" found', array('@raw' => $raw)); } - return $this->assert(strpos($this->content, $raw) !== FALSE, $message, $group); + return $this->assert(strpos($this->drupalGetContent(), $raw) !== FALSE, $message, $group); } /** @@ -2396,7 +2423,7 @@ class DrupalWebTestCase extends DrupalTestCase { if (!$message) { $message = t('Raw "@raw" not found', array('@raw' => $raw)); } - return $this->assert(strpos($this->content, $raw) === FALSE, $message, $group); + return $this->assert(strpos($this->drupalGetContent(), $raw) === FALSE, $message, $group); } /** @@ -2451,9 +2478,9 @@ class DrupalWebTestCase extends DrupalTestCase { * @return * TRUE on pass, FALSE on fail. */ - protected function assertTextHelper($text, $message, $group, $not_exists) { + protected function assertTextHelper($text, $message = '', $group, $not_exists) { if ($this->plainTextContent === FALSE) { - $this->plainTextContent = filter_xss($this->content, array()); + $this->plainTextContent = filter_xss($this->drupalGetContent(), array()); } if (!$message) { $message = !$not_exists ? t('"@text" found', array('@text' => $text)) : t('"@text" not found', array('@text' => $text)); @@ -2517,9 +2544,9 @@ class DrupalWebTestCase extends DrupalTestCase { * @return * TRUE on pass, FALSE on fail. */ - protected function assertUniqueTextHelper($text, $message, $group, $be_unique) { + protected function assertUniqueTextHelper($text, $message = '', $group, $be_unique) { if ($this->plainTextContent === FALSE) { - $this->plainTextContent = filter_xss($this->content, array()); + $this->plainTextContent = filter_xss($this->drupalGetContent(), array()); } if (!$message) { $message = '"' . $text . '"' . ($be_unique ? ' found only once' : ' found more than once'); @@ -2583,7 +2610,7 @@ class DrupalWebTestCase extends DrupalTestCase { * @return * TRUE on pass, FALSE on fail. */ - protected function assertTitle($title, $message, $group = 'Other') { + protected function assertTitle($title, $message = '', $group = 'Other') { return $this->assertEqual(current($this->xpath('//title')), $title, $message, $group); } @@ -2599,7 +2626,7 @@ class DrupalWebTestCase extends DrupalTestCase { * @return * TRUE on pass, FALSE on fail. */ - protected function assertNoTitle($title, $message, $group = 'Other') { + protected function assertNoTitle($title, $message = '', $group = 'Other') { return $this->assertNotEqual(current($this->xpath('//title')), $title, $message, $group); } @@ -2617,7 +2644,7 @@ class DrupalWebTestCase extends DrupalTestCase { * @return * TRUE on pass, FALSE on fail. */ - protected function assertFieldByXPath($xpath, $value, $message, $group = 'Other') { + protected function assertFieldByXPath($xpath, $value, $message = '', $group = 'Other') { $fields = $this->xpath($xpath); // If value specified then check array for match. @@ -2689,7 +2716,7 @@ class DrupalWebTestCase extends DrupalTestCase { * @return * TRUE on pass, FALSE on fail. */ - protected function assertNoFieldByXPath($xpath, $value, $message, $group = 'Other') { + protected function assertNoFieldByXPath($xpath, $value, $message = '', $group = 'Other') { $fields = $this->xpath($xpath); // If value specified then check array for match. @@ -2938,7 +2965,9 @@ class DrupalWebTestCase extends DrupalTestCase { } /** - * Assert that the most recently sent e-mail message has a field with the given value. + * Asserts that the most recently sent e-mail message has the given value. + * + * The field in $name must have the content described in $value. * * @param $name * Name of field or message property to assert. Examples: subject, body, id, ... @@ -2946,6 +2975,7 @@ class DrupalWebTestCase extends DrupalTestCase { * Value of the field to assert. * @param $message * Message to display. + * * @return * TRUE on pass, FALSE on fail. */ @@ -2956,13 +2986,76 @@ class DrupalWebTestCase extends DrupalTestCase { } /** - * Log verbose message in a text file. + * Asserts that the most recently sent e-mail message has the string in it. + * + * @param $field_name + * Name of field or message property to assert: subject, body, id, ... + * @param $string + * String to search for. + * @param $email_depth + * Number of emails to search for string, starting with most recent. + * + * @return + * TRUE on pass, FALSE on fail. + */ + protected function assertMailString($field_name, $string, $email_depth) { + $mails = $this->drupalGetMails(); + $string_found = FALSE; + for ($i = sizeof($mails) -1; $i >= sizeof($mails) - $email_depth && $i >= 0; $i--) { + $mail = $mails[$i]; + // Normalize whitespace, as we don't know what the mail system might have + // done. Any run of whitespace becomes a single space. + $normalized_mail = preg_replace('/\s+/', ' ', $mail[$field_name]); + $normalized_string = preg_replace('/\s+/', ' ', $string); + $string_found = (FALSE !== strpos($normalized_mail, $normalized_string)); + if ($string_found) { + break; + } + } + return $this->assertTrue($string_found, t('Expected text found in @field of email message: "@expected".', array('@field' => $field_name, '@expected' => $string))); + } + + /** + * Asserts that the most recently sent e-mail message has the pattern in it. + * + * @param $field_name + * Name of field or message property to assert: subject, body, id, ... + * @param $regex + * Pattern to search for. + * + * @return + * TRUE on pass, FALSE on fail. + */ + protected function assertMailPattern($field_name, $regex, $message) { + $mails = $this->drupalGetMails(); + $mail = end($mails); + $regex_found = preg_match("/$regex/", $mail[$field_name]); + return $this->assertTrue($regex_found, t('Expected text found in @field of email message: "@expected".', array('@field' => $field_name, '@expected' => $regex))); + } + + /** + * Outputs to verbose the most recent $count emails sent. + * + * @param $count + * Optional number of emails to output. + */ + protected function verboseEmail($count = 1) { + $mails = $this->drupalGetMails(); + for ($i = sizeof($mails) -1; $i >= sizeof($mails) - $count && $i >= 0; $i--) { + $mail = $mails[$i]; + $this->verbose(t('Email:') . '<pre>' . print_r($mail, TRUE) . '</pre>'); + } + } + + /** + * Logs verbose message in a text file. * * The a link to the vebose message will be placed in the test results via * as a passing assertion with the text '[verbose message]'. * * @param $message * The verbose message to be stored. + * * @see simpletest_verbose() */ protected function verbose($message) { @@ -2975,7 +3068,7 @@ class DrupalWebTestCase extends DrupalTestCase { } /** - * Log verbose message in a text file. + * Logs verbose message in a text file. * * If verbose mode is enabled then page requests will be dumped to a file and * presented on the test result screen. The messages will be placed in a file @@ -2987,8 +3080,10 @@ class DrupalWebTestCase extends DrupalTestCase { * The original file directory, before it was changed for testing purposes. * @param $test_class * The active test case class. + * * @return * The ID of the message to be placed in related assertion messages. + * * @see DrupalTestCase->originalFileDirectory * @see DrupalWebTestCase->verbose() */ diff --git a/modules/simpletest/files/css_test_files/comment_hacks.css b/modules/simpletest/files/css_test_files/comment_hacks.css new file mode 100644 index 00000000..c47e8429 --- /dev/null +++ b/modules/simpletest/files/css_test_files/comment_hacks.css @@ -0,0 +1,80 @@ +/* +* A sample css file, designed to test the effectiveness and stability +* of function <code>drupal_load_stylesheet_content()</code>. +* +*/ +/* +A large comment block to test for segfaults and speed. This is 60K a's. Extreme but useful to demonstrate flaws in comment striping regexp. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/ +.test1 { display:block;} + +/* A multiline IE-mac hack (v.2) taken fron Zen theme*/ +/* Hides from IE-mac \*/ +html .clear-block { + height: 1%; +} +.clear-block { + display: block; + font:italic bold 12px/30px Georgia, serif; +} + +/* End hide from IE-mac */ +.test2 { display:block; } + +/* v1 of the commented backslash hack. This \ character between rules appears to have the effect +that macIE5 ignores the following rule. Odd, but extremely useful. */ +.bkslshv1 { background-color: #C00; } +.test3 { display:block; } + +/**************** A multiline, multistar comment *************** +****************************************************************/ +.test4 { display:block;} + +/**************************************/ +.comment-in-double-quotes:before { + content: "/* "; +} +.this_rule_must_stay { + color: #F00; + background-color: #FFF; +} +.comment-in-double-quotes:after { + content: " */"; +} +/**************************************/ +.comment-in-single-quotes:before { + content: '/*'; +} +.this_rule_must_stay { + color: #F00; + background-color: #FFF; +} +.comment-in-single-quotes:after { + content: '*/'; +} +/**************************************/ +.comment-in-mixed-quotes:before { + content: '"/*"'; +} +.this_rule_must_stay { + color: #F00; + background-color: #FFF; +} +.comment-in-mixed-quotes:after { + content: "'*/'"; +} +/**************************************/ +.comment-in-quotes-with-escaped:before { + content: '/* \" \' */'; +} +.this_rule_must_stay { + color: #F00; + background-color: #FFF; +} +.comment-in-quotes-with-escaped:after { + content: "*/ \" \ '"; +} +/************************************/ +/* +"This has to go" +'This has to go' +*/ diff --git a/modules/simpletest/files/css_test_files/comment_hacks.css.optimized.css b/modules/simpletest/files/css_test_files/comment_hacks.css.optimized.css new file mode 100644 index 00000000..10c70a11 --- /dev/null +++ b/modules/simpletest/files/css_test_files/comment_hacks.css.optimized.css @@ -0,0 +1,3 @@ + + +.test1{display:block;}html .clear-block{height:1%;}.clear-block{display:block;font:italic bold 12px/30px Georgia,serif;}.test2{display:block;}.bkslshv1{background-color:#C00;}.test3{display:block;}.test4{display:block;}.comment-in-double-quotes:before{content:"/* ";}.this_rule_must_stay{color:#F00;background-color:#FFF;}.comment-in-double-quotes:after{content:" */";}.comment-in-single-quotes:before{content:'/*';}.this_rule_must_stay{color:#F00;background-color:#FFF;}.comment-in-single-quotes:after{content:'*/';}.comment-in-mixed-quotes:before{content:'"/*"';}.this_rule_must_stay{color:#F00;background-color:#FFF;}.comment-in-mixed-quotes:after{content:"'*/'";}.comment-in-quotes-with-escaped:before{content:'/* \" \' */';}.this_rule_must_stay{color:#F00;background-color:#FFF;}.comment-in-quotes-with-escaped:after{content:"*/ \" \ '";} diff --git a/modules/simpletest/files/css_test_files/comment_hacks.css.unoptimized.css b/modules/simpletest/files/css_test_files/comment_hacks.css.unoptimized.css new file mode 100644 index 00000000..ff50eaea --- /dev/null +++ b/modules/simpletest/files/css_test_files/comment_hacks.css.unoptimized.css @@ -0,0 +1,80 @@ +/* +* A sample css file, designed to test the effectiveness and stability +* of function <code>drupal_load_stylesheet_content()</code>. +* +*/ +/* +A large comment block to test for segfaults and speed. This is 60K a's. Extreme but usefull to demonstrate flaws in comment striping regexp. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*/ +.test1 { display:block;} + +/* A multiline IE-mac hack (v.2) taken fron Zen theme*/ +/* Hides from IE-mac \*/ +html .clear-block { + height: 1%; +} +.clear-block { + display: block; + font:italic bold 12px/30px Georgia, serif; +} + +/* End hide from IE-mac */ +.test2 { display:block; } + +/* v1 of the commented backslash hack. This \ character between rules appears to have the effect +that macIE5 ignores the following rule. Odd, but extremely useful. */ +.bkslshv1 { background-color: #C00; } +.test3 { display:block; } + +/**************** A multiline, multistar comment *************** +****************************************************************/ +.test4 { display:block;} + +/**************************************/ +.comment-in-double-quotes:before { + content: "/* "; +} +.this_rule_must_stay { + color: #F00; + background-color: #FFF; +} +.comment-in-double-quotes:after { + content: " */"; +} +/**************************************/ +.comment-in-single-quotes:before { + content: '/*'; +} +.this_rule_must_stay { + color: #F00; + background-color: #FFF; +} +.comment-in-single-quotes:after { + content: '*/'; +} +/**************************************/ +.comment-in-mixed-quotes:before { + content: '"/*"'; +} +.this_rule_must_stay { + color: #F00; + background-color: #FFF; +} +.comment-in-mixed-quotes:after { + content: "'*/'"; +} +/**************************************/ +.comment-in-quotes-with-escaped:before { + content: '/* \" \' */'; +} +.this_rule_must_stay { + color: #F00; + background-color: #FFF; +} +.comment-in-quotes-with-escaped:after { + content: "*/ \" \ '"; +} +/************************************/ +/* +"This has to go" +'This has to go' +*/ diff --git a/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css b/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css index 60006c58..96fb9931 100644 --- a/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css +++ b/modules/simpletest/files/css_test_files/css_input_with_import.css.optimized.css @@ -1,6 +1,8 @@ ul,select{font:1em/160% Verdana,sans-serif;color:#494949;}.ui-icon{background-image:url(images/icon.png);} -p,select{font:1em/160% Verdana,sans-serif;color:#494949;}body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this + +p,select{font:1em/160% Verdana,sans-serif;color:#494949;} +body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this .is .a -.test{font:1em/100% Verdana,sans-serif;color:#494949;}textarea,select{font:1em/160% Verdana,sans-serif;color:#494949;} \ No newline at end of file +.test{font:1em/100% Verdana,sans-serif;color:#494949;}textarea,select{font:1em/160% Verdana,sans-serif;color:#494949;} diff --git a/modules/simpletest/files/css_test_files/css_input_without_import.css.optimized.css b/modules/simpletest/files/css_test_files/css_input_without_import.css.optimized.css index e5920f09..6a90a8f7 100644 --- a/modules/simpletest/files/css_test_files/css_input_without_import.css.optimized.css +++ b/modules/simpletest/files/css_test_files/css_input_without_import.css.optimized.css @@ -3,9 +3,7 @@ -body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;} - -.this +body{margin:0;padding:0;background:#edf5fa;font:76%/170% Verdana,sans-serif;color:#494949;}.this .is .a .test{font:1em/100% Verdana,sans-serif;color:#494949;}.this .is .a -.test{font:1em/100% Verdana,sans-serif;color:#494949;}textarea,select{font:1em/160% Verdana,sans-serif;color:#494949;} \ No newline at end of file +.test{font:1em/100% Verdana,sans-serif;color:#494949;}textarea,select{font:1em/160% Verdana,sans-serif;color:#494949;} diff --git a/modules/simpletest/simpletest.info b/modules/simpletest/simpletest.info index c4466927..17a5d261 100644 --- a/modules/simpletest/simpletest.info +++ b/modules/simpletest/simpletest.info @@ -1,4 +1,4 @@ -; $Id: simpletest.info,v 1.16 2010/02/03 18:16:23 webchick Exp $ +; $Id: simpletest.info,v 1.18 2010/06/28 02:05:47 webchick Exp $ name = Testing description = Provides a framework for unit and functional testing. package = Core @@ -19,6 +19,7 @@ files[] = tests/bootstrap.test files[] = tests/cache.test files[] = tests/common.test files[] = tests/database_test.test +files[] = tests/entity_query.test files[] = tests/error.test files[] = tests/file.test files[] = tests/filetransfer.test @@ -37,9 +38,10 @@ files[] = tests/theme.test files[] = tests/unicode.test files[] = tests/update.test files[] = tests/xmlrpc.test +files[] = tests/upgrade/upgrade.test -; Information added by drupal.org packaging script on 2010-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/simpletest.test b/modules/simpletest/simpletest.test index 6525ae39..32c44ebf 100644 --- a/modules/simpletest/simpletest.test +++ b/modules/simpletest/simpletest.test @@ -1,5 +1,5 @@ <?php -// $Id: simpletest.test,v 1.41 2010/05/10 06:38:23 dries Exp $ +// $Id: simpletest.test,v 1.43 2010/06/28 19:57:34 dries Exp $ class SimpleTestFunctionalTest extends DrupalWebTestCase { /** @@ -272,10 +272,11 @@ class SimpleTestFunctionalTest extends DrupalWebTestCase { /** * Check if the test is being run from inside a CURL request. - * - * @return The test is being run from inside a CURL request. */ function inCURL() { + // We cannot rely on drupal_static('drupal_test_info') here, because + // 'in_child_site' would be FALSE for the parent site when we are + // executing the tests. Default to direct detection of the HTTP headers. return isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^simpletest\d+/", $_SERVER['HTTP_USER_AGENT']); } } @@ -292,6 +293,11 @@ class SimpleTestBrowserTestCase extends DrupalWebTestCase { ); } + function setUp() { + parent::setUp(); + variable_set('user_register', USER_REGISTER_VISITORS); + } + /** * Test DrupalWebTestCase::getAbsoluteUrl(). */ diff --git a/modules/simpletest/tests/actions.test b/modules/simpletest/tests/actions.test index 1dee630e..8e8f176e 100644 --- a/modules/simpletest/tests/actions.test +++ b/modules/simpletest/tests/actions.test @@ -1,5 +1,5 @@ <?php -// $Id: actions.test,v 1.15 2010/05/01 08:12:23 dries Exp $ +// $Id: actions.test,v 1.16 2010/05/26 11:26:49 dries Exp $ class ActionsConfigurationTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -81,7 +81,7 @@ class ActionLoopTestCase extends DrupalWebTestCase { } /** - * Set up a loop with 10-50 recursions, and see if it aborts properly. + * Set up a loop with 3 - 12 recursions, and see if it aborts properly. */ function testActionLoop() { $user = $this->drupalCreateUser(array('administer actions')); @@ -116,10 +116,9 @@ class ActionLoopTestCase extends DrupalWebTestCase { } $expected[] = 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.'; - $result = db_query("SELECT * FROM {watchdog} WHERE type = 'actions_loop_test' OR type = 'actions' ORDER BY timestamp"); + $result = db_query("SELECT message FROM {watchdog} WHERE type = 'actions_loop_test' OR type = 'actions' ORDER BY wid"); $loop_started = FALSE; foreach ($result as $row) { - $expected_message = array_shift($expected); $this->assertEqual($row->message, $expected_message, t('Expected message %expected, got %message.', array('%expected' => $expected_message, '%message' => $row->message))); } diff --git a/modules/simpletest/tests/actions_loop_test.info b/modules/simpletest/tests/actions_loop_test.info index dbf7b16a..65b5abb5 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/ajax_forms_test.info b/modules/simpletest/tests/ajax_forms_test.info index 9d20ddc0..ab46b57b 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/ajax_test.info b/modules/simpletest/tests/ajax_test.info index 042a198a..b4d74d16 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/batch_test.info b/modules/simpletest/tests/batch_test.info index aad88e7b..1822040f 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/bootstrap.test b/modules/simpletest/tests/bootstrap.test index 9bbe1378..95d72448 100644 --- a/modules/simpletest/tests/bootstrap.test +++ b/modules/simpletest/tests/bootstrap.test @@ -1,5 +1,5 @@ <?php -// $Id: bootstrap.test,v 1.30 2010/05/12 08:26:15 dries Exp $ +// $Id: bootstrap.test,v 1.31 2010/06/14 13:24:32 dries Exp $ class BootstrapIPAddressTestCase extends DrupalWebTestCase { @@ -16,8 +16,9 @@ class BootstrapIPAddressTestCase extends DrupalWebTestCase { $this->remote_ip = '127.0.0.1'; $this->proxy_ip = '127.0.0.2'; - $this->forwarded_ip = '127.0.0.3'; - $this->cluster_ip = '127.0.0.4'; + $this->proxy2_ip = '127.0.0.3'; + $this->forwarded_ip = '127.0.0.4'; + $this->cluster_ip = '127.0.0.5'; $this->untrusted_ip = '0.0.0.0'; drupal_static_reset('ip_address'); @@ -42,23 +43,23 @@ class BootstrapIPAddressTestCase extends DrupalWebTestCase { // Test the normal IP address. $this->assertTrue( ip_address() == $this->remote_ip, - t('Got remote IP address') + t('Got remote IP address.') ); // Proxy forwarding on but no proxy addresses defined. variable_set('reverse_proxy', 1); $this->assertTrue( ip_address() == $this->remote_ip, - t('Proxy forwarding without trusted proxies got remote IP address') + t('Proxy forwarding without trusted proxies got remote IP address.') ); // Proxy forwarding on and proxy address not trusted. - variable_set('reverse_proxy_addresses', array($this->proxy_ip)); + variable_set('reverse_proxy_addresses', array($this->proxy_ip, $this->proxy2_ip)); drupal_static_reset('ip_address'); $_SERVER['REMOTE_ADDR'] = $this->untrusted_ip; $this->assertTrue( ip_address() == $this->untrusted_ip, - t('Proxy forwarding with untrusted proxy got remote IP address') + t('Proxy forwarding with untrusted proxy got remote IP address.') ); // Proxy forwarding on and proxy address trusted. @@ -67,7 +68,16 @@ class BootstrapIPAddressTestCase extends DrupalWebTestCase { drupal_static_reset('ip_address'); $this->assertTrue( ip_address() == $this->forwarded_ip, - t('Proxy forwarding with trusted proxy got forwarded IP address') + t('Proxy forwarding with trusted proxy got forwarded IP address.') + ); + + // Multi-tier architecture with comma separated values in header. + $_SERVER['REMOTE_ADDR'] = $this->proxy_ip; + $_SERVER['HTTP_X_FORWARDED_FOR'] = implode(', ', array($this->untrusted_ip, $this->forwarded_ip, $this->proxy2_ip)); + drupal_static_reset('ip_address'); + $this->assertTrue( + ip_address() == $this->forwarded_ip, + t('Proxy forwarding with trusted 2-tier proxy got forwarded IP address.') ); // Custom client-IP header. @@ -76,8 +86,10 @@ class BootstrapIPAddressTestCase extends DrupalWebTestCase { drupal_static_reset('ip_address'); $this->assertTrue( ip_address() == $this->cluster_ip, - t('Cluster environment got cluster client IP') + t('Cluster environment got cluster client IP.') ); + + // Verifies that drupal_valid_http_host() prevents invalid characters. $this->assertFalse(drupal_valid_http_host('security/.drupal.org:80'), t('HTTP_HOST with / is invalid')); $this->assertFalse(drupal_valid_http_host('security\\.drupal.org:80'), t('HTTP_HOST with \\ is invalid')); $this->assertFalse(drupal_valid_http_host('security<.drupal.org:80'), t('HTTP_HOST with < is invalid')); diff --git a/modules/simpletest/tests/common.test b/modules/simpletest/tests/common.test index 3e8d9d69..e81a9503 100644 --- a/modules/simpletest/tests/common.test +++ b/modules/simpletest/tests/common.test @@ -1,5 +1,5 @@ <?php -// $Id: common.test,v 1.112 2010/05/18 06:59:46 dries Exp $ +// $Id: common.test,v 1.116 2010/07/07 17:00:43 webchick Exp $ /** * @file @@ -30,7 +30,7 @@ class DrupalAlterTestCase extends DrupalWebTestCase { $base_theme_info = array(); $array = array('foo' => 'bar'); - $entity = new stdClass; + $entity = new stdClass(); $entity->foo = 'bar'; // Verify alteration of a single argument. @@ -101,13 +101,13 @@ class CommonURLUnitTest extends DrupalWebTestCase { $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'))); + $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(). */ @@ -196,8 +196,13 @@ class CommonURLUnitTest extends DrupalWebTestCase { ); $this->assertEqual(drupal_parse_url($url), $result, t('Absolute URL parsed correctly.')); - // External URL. + // External URL testing. $url = 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo'; + + // Test that drupal can recognize an absolute URL. Used to prevent attack vectors. + $this->assertTrue(url_is_external($url), t('Correctly identified an external URL.')); + + // Test the parsing of absolute URLs. $result = array( 'path' => 'http://drupal.org/foo/bar', 'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''), @@ -222,6 +227,10 @@ class CommonURLUnitTest extends DrupalWebTestCase { // Non-clean URLs #3: URL generated by url() on non-Apache webserver. $url = 'index.php?q=foo/bar&bar=baz#foo'; $this->assertEqual(drupal_parse_url($url), $result, t('Relative URL on non-Apache webserver with clean URLs disabled parsed correctly.')); + + // Test that drupal_parse_url() does not allow spoofing a URL to force a malicious redirect. + $parts = drupal_parse_url('forged:http://cwe.mitre.org/data/definitions/601.html'); + $this->assertFalse(valid_url($parts['path'], TRUE), t('drupal_parse_url() correctly parsed a forged URL.')); } /** @@ -336,7 +345,7 @@ class CommonXssUnitTest extends DrupalUnitTestCase { public static function getInfo() { return array( 'name' => 'String filtering tests', - 'description' => 'Confirm that check_plain() and filter_xss() work correctly, including invalid multi-byte sequences.', + 'description' => 'Confirm that check_plain(), filter_xss(), and check_url() work correctly, including invalid multi-byte sequences.', 'group' => 'System', ); } @@ -363,6 +372,20 @@ class CommonXssUnitTest extends DrupalUnitTestCase { $text = check_plain("<script>"); $this->assertEqual($text, '<script>', 'check_plain() escapes <script>'); } + + /** + * Check that harmful protocols are stripped. + */ + function testBadProtocolStripping() { + // Ensure that check_url() strips out harmful protocols, and encodes for + // HTML. Ensure drupal_strip_dangerous_protocols() can be used to return a + // plain-text string stripped of harmful protocols. + $url = 'javascript:http://www.example.com/?x=1&y=2'; + $expected_plain = 'http://www.example.com/?x=1&y=2'; + $expected_html = 'http://www.example.com/?x=1&y=2'; + $this->assertIdentical(check_url($url), $expected_html, t('check_url() filters a URL and encodes it for HTML.')); + $this->assertIdentical(drupal_strip_dangerous_protocols($url), $expected_plain, t('drupal_strip_dangerous_protocols() filters a URL and returns plain text.')); + } } class CommonSizeTestCase extends DrupalUnitTestCase { @@ -811,7 +834,8 @@ class CascadingStylesheetsUnitTest extends DrupalUnitTestCase { // - Optimized expected content: name.css.optimized.css $testfiles = array( 'css_input_without_import.css', - 'css_input_with_import.css' + 'css_input_with_import.css', + 'comment_hacks.css' ); $path = drupal_get_path('module', 'simpletest') . '/files/css_test_files'; foreach ($testfiles as $file) { diff --git a/modules/simpletest/tests/common_test.info b/modules/simpletest/tests/common_test.info index 0249090e..5b51ca75 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/database_test.info b/modules/simpletest/tests/database_test.info index ef98079f..9768bc73 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/database_test.install b/modules/simpletest/tests/database_test.install index dfb1497e..378ecb9b 100644 --- a/modules/simpletest/tests/database_test.install +++ b/modules/simpletest/tests/database_test.install @@ -1,5 +1,5 @@ <?php -// $Id: database_test.install,v 1.11 2010/03/03 08:40:25 dries Exp $ +// $Id: database_test.install,v 1.12 2010/06/25 17:47:22 dries Exp $ /** * @file @@ -220,7 +220,7 @@ function database_test_schema() { ), 'info' => array( 'description' => "The person's data in serialized form.", - 'type' => 'text', + 'type' => 'blob', 'serialize' => TRUE, ), ), diff --git a/modules/simpletest/tests/database_test.module b/modules/simpletest/tests/database_test.module index 1e0b63b7..06748304 100644 --- a/modules/simpletest/tests/database_test.module +++ b/modules/simpletest/tests/database_test.module @@ -1,5 +1,5 @@ <?php -// $Id: database_test.module,v 1.13 2010/01/30 07:59:25 dries Exp $ +// $Id: database_test.module,v 1.14 2010/06/25 19:33:47 dries Exp $ /** * Implements hook_query_alter(). @@ -11,8 +11,8 @@ function database_test_query_alter(QueryAlterableInterface $query) { } if ($query->hasTag('database_test_alter_add_join')) { - $people_alias = $query->join('test', 'people', "test_task.pid=people.id"); - $name_field = $query->addField('people', 'name', 'name'); + $people_alias = $query->join('test', 'people', "test_task.pid = %alias.id"); + $name_field = $query->addField($people_alias, 'name', 'name'); $query->condition($people_alias . '.id', 2); } diff --git a/modules/simpletest/tests/database_test.test b/modules/simpletest/tests/database_test.test index 492f3b72..e97435ba 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.92 2010/05/15 07:04:21 dries Exp $ +// $Id: database_test.test,v 1.93 2010/05/28 10:13:38 dries Exp $ /** * Dummy class for fetching into a class. @@ -2703,11 +2703,11 @@ class DatabaseTemporaryQueryTestCase extends DrupalWebTestCase { * across multiple kinds of database systems, we test that the * database system interprets SQL syntax in an expected fashion. */ -class DatabaseAnsiSyntaxTestCase extends DatabaseTestCase { +class DatabaseBasicSyntaxTestCase extends DatabaseTestCase { public static function getInfo() { return array( - 'name' => 'ANSI SQL syntax tests', - 'description' => 'Test ANSI SQL syntax interpretation.', + 'name' => 'Basic SQL syntax tests', + 'description' => 'Test SQL syntax interpretation.', 'group' => 'Database', ); } @@ -2717,30 +2717,30 @@ class DatabaseAnsiSyntaxTestCase extends DatabaseTestCase { } /** - * Test for ANSI string concatenation. + * Test for string concatenation. */ function testBasicConcat() { - $result = db_query('SELECT :a1 || :a2 || :a3 || :a4 || :a5', array( + $result = db_query('SELECT CONCAT(:a1, CONCAT(:a2, CONCAT(:a3, CONCAT(:a4, :a5))))', array( ':a1' => 'This', ':a2' => ' ', ':a3' => 'is', ':a4' => ' a ', ':a5' => 'test.', )); - $this->assertIdentical($result->fetchField(), 'This is a test.', t('Basic ANSI Concat works.')); + $this->assertIdentical($result->fetchField(), 'This is a test.', t('Basic CONCAT works.')); } /** - * Test for ANSI string concatenation with field values. + * Test for string concatenation with field values. */ function testFieldConcat() { - $result = db_query('SELECT :a1 || name || :a2 || age || :a3 FROM {test} WHERE age = :age', array( + $result = db_query('SELECT CONCAT(:a1, CONCAT(name, CONCAT(:a2, CONCAT(age, :a3)))) FROM {test} WHERE age = :age', array( ':a1' => 'The age of ', ':a2' => ' is ', ':a3' => '.', ':age' => 25, )); - $this->assertIdentical($result->fetchField(), 'The age of John is 25.', t('Field ANSI Concat works.')); + $this->assertIdentical($result->fetchField(), 'The age of John is 25.', t('Field CONCAT works.')); } /** diff --git a/modules/simpletest/tests/entity_cache_test.info b/modules/simpletest/tests/entity_cache_test.info index d0f2be50..ea9c2be7 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/entity_cache_test_dependency.info b/modules/simpletest/tests/entity_cache_test_dependency.info index 3300857f..757efda0 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/entity_query.test b/modules/simpletest/tests/entity_query.test new file mode 100644 index 00000000..39a176f5 --- /dev/null +++ b/modules/simpletest/tests/entity_query.test @@ -0,0 +1,930 @@ +<?php + +// $Id: entity_query.test,v 1.5 2010/06/26 02:16:23 dries Exp $ + +/** + * @file + * Unit test file for the entity API. + */ + +/** + * Tests EntityFieldQuery. + */ +class EntityFieldQueryTestCase extends DrupalWebTestCase { + + public static function getInfo() { + return array( + 'name' => 'Entity query', + 'description' => 'Test the EntityFieldQuery class.', + 'group' => 'Entity API', + ); + } + + function setUp() { + parent::setUp(array('field_test')); + + field_attach_create_bundle('bundle1', 'test_entity_bundle_key'); + field_attach_create_bundle('bundle2', 'test_entity_bundle_key'); + field_attach_create_bundle('test_bundle', 'test_entity'); + field_attach_create_bundle('test_entity_bundle', 'test_entity_bundle'); + + $instances = array(); + $this->fields = array(); + $this->field_names[0] = $field_name = drupal_strtolower($this->randomName() . '_field_name'); + $field = array('field_name' => $field_name, 'type' => 'test_field', 'cardinality' => 4); + $field = field_create_field($field); + $this->fields[0] = $field; + $instance = array( + 'field_name' => $field_name, + 'entity_type' => '', + 'bundle' => '', + 'label' => $this->randomName() . '_label', + 'description' => $this->randomName() . '_description', + 'weight' => mt_rand(0, 127), + 'settings' => array( + 'test_instance_setting' => $this->randomName(), + ), + 'widget' => array( + 'type' => 'test_field_widget', + 'label' => 'Test Field', + 'settings' => array( + 'test_widget_setting' => $this->randomName(), + ) + ) + ); + + $instances[0] = $instance; + + // Add an instance to that bundle. + $instances[0]['bundle'] = 'bundle1'; + $instances[0]['entity_type'] = 'test_entity_bundle_key'; + field_create_instance($instances[0]); + $instances[0]['bundle'] = $instances[0]['entity_type'] = 'test_entity_bundle'; + field_create_instance($instances[0]); + + $this->field_names[1] = $field_name = drupal_strtolower($this->randomName() . '_field_name'); + $field = array('field_name' => $field_name, 'type' => 'shape', 'cardinality' => 4); + $field = field_create_field($field); + $this->fields[1] = $field; + $instance = array( + 'field_name' => $field_name, + 'entity_type' => '', + 'bundle' => '', + 'label' => $this->randomName() . '_label', + 'description' => $this->randomName() . '_description', + 'weight' => mt_rand(0, 127), + 'settings' => array( + 'test_instance_setting' => $this->randomName(), + ), + 'widget' => array( + 'type' => 'test_field_widget', + 'label' => 'Test Field', + 'settings' => array( + 'test_widget_setting' => $this->randomName(), + ) + ) + ); + + $instances[1] = $instance; + + // Add an instance to that bundle. + $instances[1]['bundle'] = 'bundle1'; + $instances[1]['entity_type'] = 'test_entity_bundle_key'; + field_create_instance($instances[1]); + $instances[1]['bundle'] = $instances[1]['entity_type'] = 'test_entity_bundle'; + field_create_instance($instances[1]); + + $this->instances = $instances; + // Write entity base table if there is one. + $entities = array(); + + // Create entities which have a 'bundle key' defined. + for ($i = 1; $i < 7; $i++) { + $entity = new stdClass(); + $entity->ftid = $i; + $entity->fttype = ($i < 5) ? 'bundle1' : 'bundle2'; + + $entity->{$this->field_names[0]}[LANGUAGE_NONE][0]['value'] = $i; + drupal_write_record('test_entity_bundle_key', $entity); + field_attach_insert('test_entity_bundle_key', $entity); + } + + $entity = new stdClass(); + $entity->ftid = 5; + $entity->fttype = 'bundle2'; + $entity->{$this->field_names[1]}[LANGUAGE_NONE][0]['shape'] = 'square'; + $entity->{$this->field_names[1]}[LANGUAGE_NONE][0]['color'] = 'red'; + $entity->{$this->field_names[1]}[LANGUAGE_NONE][1]['shape'] = 'circle'; + $entity->{$this->field_names[1]}[LANGUAGE_NONE][1]['color'] = 'blue'; + drupal_write_record('test_entity_bundle', $entity); + field_attach_insert('test_entity_bundle', $entity); + + $instances[2] = $instance; + $instances[2]['bundle'] = 'test_bundle'; + $instances[2]['field_name'] = $this->field_names[0]; + $instances[2]['entity_type'] = 'test_entity'; + field_create_instance($instances[2]); + + // Create entities with support for revisions. + for ($i = 1; $i < 5; $i++) { + $entity = new stdClass(); + $entity->ftid = $i; + $entity->ftvid = $i; + $entity->fttype = 'test_bundle'; + $entity->{$this->field_names[0]}[LANGUAGE_NONE][0]['value'] = $i; + + drupal_write_record('test_entity', $entity); + field_attach_insert('test_entity', $entity); + drupal_write_record('test_entity_revision', $entity); + } + + // Add two revisions to an entity. + for ($i = 100; $i < 102; $i++) { + $entity = new stdClass(); + $entity->ftid = 4; + $entity->ftvid = $i; + $entity->fttype = 'test_bundle'; + $entity->{$this->field_names[0]}[LANGUAGE_NONE][0]['value'] = $i; + + drupal_write_record('test_entity', $entity, 'ftid'); + drupal_write_record('test_entity_revision', $entity); + + db_update('test_entity') + ->fields(array('ftvid' => $entity->ftvid)) + ->condition('ftid', $entity->ftid) + ->execute(); + + field_attach_update('test_entity', $entity); + } + } + + /** + * Tests EntityFieldQuery. + */ + function testEntityFieldQuery() { + // Test entity_type condition. + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'test_entity_bundle_key'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test entity entity_type condition.')); + + // Test entity_id condition. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->entityCondition('entity_id', '3'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + ), t('Test entity entity_id condition.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', '3'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + ), t('Test entity entity_id condition and entity_id property condition.')); + + // Test bundle condition. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->entityCondition('bundle', 'bundle1'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + ), t('Test entity bundle condition: bundle1.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->entityCondition('bundle', 'bundle2'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test entity bundle condition: bundle2.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('fttype', 'bundle2'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test entity bundle condition and bundle property condition.')); + + // Test revision_id condition. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity') + ->entityCondition('revision_id', '3'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 3), + ), t('Test entity revision_id condition.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity') + ->propertyCondition('ftvid', '3'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 3), + ), t('Test entity revision_id condition and revision_id property condition.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity') + ->fieldCondition($this->fields[0], 'value', 100, '>=') + ->age(FIELD_LOAD_REVISION); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 100), + array('test_entity', 101), + ), t('Test revision age.')); + + // Test that fields attached to the non-revision supporting entity + // 'test_entity_bundle_key' are reachable in FIELD_LOAD_REVISION. + $query = new EntityFieldQuery(); + $query + ->fieldCondition($this->fields[0], 'value', 100, '<') + ->age(FIELD_LOAD_REVISION); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity', 1), + array('test_entity', 2), + array('test_entity', 3), + array('test_entity', 4), + ), t('Test that fields are reachable from FIELD_LOAD_REVISION even for non-revision entities.')); + + // Test entity sort by entity_id. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->entityOrderBy('entity_id', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test sort entity entity_id in ascending order.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->entityOrderBy('entity_id', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 1), + ), t('Test sort entity entity_id in descending order.'), TRUE); + + // Test property sort by entity id. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyOrderBy('ftid', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test sort entity entity_id property in ascending order.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyOrderBy('ftid', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 1), + ), t('Test sort entity entity_id property in descending order.'), TRUE); + + // Test entity sort by bundle. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->entityOrderBy('bundle', 'ASC') + ->propertyOrderBy('ftid', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test sort entity bundle in ascending order.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->entityOrderBy('bundle', 'DESC') + ->propertyOrderBy('ftid', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 6), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 1), + ), t('Test sort entity bundle in descending order.'), TRUE); + + // Test entity sort by revision_id. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity') + ->entityOrderBy('revision_id', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 1), + array('test_entity', 2), + array('test_entity', 3), + array('test_entity', 4), + ), t('Test sort entity revision_id in ascending order.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity') + ->entityOrderBy('revision_id', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 4), + array('test_entity', 3), + array('test_entity', 2), + array('test_entity', 1), + ), t('Test sort entity revision_id in descending order.'), TRUE); + + // Test property sort by revision_id. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity') + ->propertyOrderBy('ftvid', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 1), + array('test_entity', 2), + array('test_entity', 3), + array('test_entity', 4), + ), t('Test sort entity revision_id property in ascending order.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity') + ->propertyOrderBy('ftvid', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity', 4), + array('test_entity', 3), + array('test_entity', 2), + array('test_entity', 1), + ), t('Test sort entity revision_id property in descending order.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldOrderBy($this->fields[0], 'value', 'ASC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + ), t('Test sort field in ascending order without field condition.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldOrderBy($this->fields[0], 'value', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 1), + ), t('Test sort field in descending order without field condition.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->fieldOrderBy($this->fields[0], 'value', 'asc'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + ), t('Test sort field in ascending order.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->fieldOrderBy($this->fields[0], 'value', 'desc'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 1), + ), t('Test sort field in descending order.'), TRUE); + + // Test "in" operation with entity entity_type condition and entity_id + // property condition. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', array(1, 3, 4), 'IN'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + ), t('Test "in" operation with entity entity_type condition and entity_id property condition.')); + + // Test "in" operation with entity entity_type condition and entity_id + // property condition. Sort in descending order by entity_id. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', array(1, 3, 4), 'IN') + ->propertyOrderBy('ftid', 'DESC'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 1), + ), t('Test "in" operation with entity entity_type condition and entity_id property condition. Sort entity_id in descending order.'), TRUE); + + // Test query count + $query = new EntityFieldQuery(); + $query_count = $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->count() + ->execute(); + $this->assertEqual($query_count, 6, t('Test query count on entity condition.')); + + $query = new EntityFieldQuery(); + $query_count = $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', '1') + ->count() + ->execute(); + $this->assertEqual($query_count, 1, t('Test query count on entity and property condition.')); + + $query = new EntityFieldQuery(); + $query_count = $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', '4', '>') + ->count() + ->execute(); + $this->assertEqual($query_count, 2, t('Test query count on entity and property condition with operator.')); + + $query = new EntityFieldQuery(); + $query_count = $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 3, '=') + ->count() + ->execute(); + $this->assertEqual($query_count, 1, t('Test query count on field condition.')); + + // First, test without options. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', 1, 'CONTAINS'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + ), t('Test the "contains" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', 3, 'CONTAINS'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + array('test_entity', 3), + ), t('Test the "contains" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', 1, '='); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + ), t('Test the "equal to" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', 3, '='); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + array('test_entity', 3), + ), t('Test the "equal to" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', 3, '!='); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test the "not equal to" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', 3, '!='); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 4), + array('test_entity', 1), + array('test_entity', 2), + array('test_entity', 4), + ), t('Test the "not equal to" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', 2, '<'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + ), t('Test the "less than" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', 2, '<'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity', 1), + ), t('Test the "less than" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', 2, '<='); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + ), t('Test the "less than or equal to" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', 2, '<='); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity', 1), + array('test_entity', 2), + ), t('Test the "less than or equal to" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', 4, '>'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test the "greater than" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', 2, '>'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity', 3), + array('test_entity', 4), + ), t('Test the "greater than" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', 4, '>='); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test the "greater than or equal to" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', 3, '>='); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity', 3), + array('test_entity', 4), + ), t('Test the "greater than or equal to" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', array(3, 4), 'NOT IN'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test the "not in" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', array(3, 4, 100, 101), 'NOT IN'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity', 1), + array('test_entity', 2), + ), t('Test the "not in" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', array(3, 4), 'IN'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + ), t('Test the "in" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', array(2, 3), 'IN'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity', 2), + array('test_entity', 3), + ), t('Test the "in" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', array(1, 3), 'BETWEEN'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + ), t('Test the "between" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', array(1, 3), 'BETWEEN'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity', 1), + array('test_entity', 2), + array('test_entity', 3), + ), t('Test the "between" operation on a field.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('fttype', 'bun', 'STARTS_WITH'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test the "starts_with" operation on a property.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[1], 'shape', 'squ', 'STARTS_WITH'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle', 5), + ), t('Test the "starts_with" operation on a field.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', 3); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + array('test_entity', 3), + ), t('Test omission of an operator with a single item.')); + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', array(2, 3)); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + array('test_entity', 2), + array('test_entity', 3), + ), t('Test omission of an operator with multiple items.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyCondition('ftid', 1, '>') + ->fieldCondition($this->fields[0], 'value', 4, '<'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 2), + array('test_entity_bundle_key', 3), + ), t('Test entity, property and field conditions.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->entityCondition('bundle', 'bundle', 'STARTS_WITH') + ->propertyCondition('ftid', 4) + ->fieldCondition($this->fields[0], 'value', 4); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 4), + ), t('Test entity condition with "starts_with" operation, and property and field conditions.')); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyOrderBy('ftid', 'asc') + ->range(0, 2); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + ), t('Test limit on a property.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>=') + ->fieldOrderBy($this->fields[0], 'value', 'asc') + ->range(0, 2); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 1), + array('test_entity_bundle_key', 2), + ), t('Test limit on a field.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->propertyOrderBy('ftid', 'asc') + ->range(4, 6); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 5), + array('test_entity_bundle_key', 6), + ), t('Test offset on a property.'), TRUE); + + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', 0, '>') + ->fieldOrderBy($this->fields[0], 'value', 'asc') + ->range(2, 4); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + ), t('Test offset on a field.'), TRUE); + + for ($i = 6; $i < 10; $i++) { + $entity = new stdClass(); + $entity->ftid = $i; + $entity->{$this->field_names[0]}[LANGUAGE_NONE][0]['value'] = $i - 5; + drupal_write_record('test_entity_bundle', $entity); + field_attach_insert('test_entity_bundle', $entity); + } + + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', 2, '>'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + array('test_entity_bundle_key', 4), + array('test_entity', 3), + array('test_entity', 4), + array('test_entity_bundle', 8), + array('test_entity_bundle', 9), + ), t('Select a field across multiple entities.')); + + $query = new EntityFieldQuery(); + $query + ->fieldCondition($this->fields[1], 'shape', 'square') + ->fieldCondition($this->fields[1], 'color', 'blue'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle', 5), + ), t('Test without a delta group.')); + + $query = new EntityFieldQuery(); + $query + ->fieldCondition($this->fields[1], 'shape', 'square', '=', 'group') + ->fieldCondition($this->fields[1], 'color', 'blue', '=', 'group'); + $this->assertEntityFieldQuery($query, array(), t('Test with a delta group.')); + + // Test query on a deleted field. + field_attach_delete_bundle('test_entity_bundle_key', 'bundle1'); + field_attach_delete_bundle('test_entity', 'test_bundle'); + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', '3'); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle', 8), + ), t('Test query on a field after deleting field from some entities.')); + + field_attach_delete_bundle('test_entity_bundle', 'test_entity_bundle'); + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', '3'); + $this->assertEntityFieldQuery($query, array(), t('Test query on a field after deleting field from all entities.')); + + $query = new EntityFieldQuery(); + $query + ->fieldCondition($this->fields[0], 'value', '3') + ->deleted(TRUE); + $this->assertEntityFieldQuery($query, array( + array('test_entity_bundle_key', 3), + array('test_entity_bundle', 8), + array('test_entity', 3), + ), t('Test query on a deleted field with deleted option set to TRUE.')); + + $pass = FALSE; + $query = new EntityFieldQuery(); + try { + $query->execute(); + } + catch (EntityFieldQueryException $exception) { + $pass = ($exception->getMessage() == t('For this query an entity type must be specified.')); + } + $this->assertTrue($pass, t("Can't query the universe.")); + } + + /** + * Tests the routing feature of EntityFieldQuery. + */ + function testEntityFieldQueryRouting() { + // Entity-only query. + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'test_entity_bundle_key'); + $this->assertIdentical($query->queryCallback(), array($query, 'propertyQuery'), t('Entity-only queries are handled by the propertyQuery handler.')); + + // Field-only query. + $query = new EntityFieldQuery(); + $query->fieldCondition($this->fields[0], 'value', '3'); + $this->assertIdentical($query->queryCallback(), 'field_sql_storage_field_storage_query', t('Pure field queries are handled by the Field storage handler.')); + + // Mixed entity and field query. + $query = new EntityFieldQuery(); + $query + ->entityCondition('entity_type', 'test_entity_bundle_key') + ->fieldCondition($this->fields[0], 'value', '3'); + $this->assertIdentical($query->queryCallback(), 'field_sql_storage_field_storage_query', t('Mixed queries are handled by the Field storage handler.')); + + // Overriding with $query->executeCallback. + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'test_entity_bundle_key'); + $query->executeCallback = 'field_test_dummy_field_storage_query'; + $this->assertEntityFieldQuery($query, array( + array('user', 1), + ), t('executeCallback can override the query handler.')); + + // Overriding with $query->executeCallback via hook_entity_query_alter(). + $query = new EntityFieldQuery(); + $query->entityCondition('entity_type', 'test_entity_bundle_key'); + // Add a flag that will be caught by field_test_entity_query_alter(). + $query->alterMyExecuteCallbackPlease = TRUE; + $this->assertEntityFieldQuery($query, array( + array('user', 1), + ), t('executeCallback can override the query handler when set in a hook_entity_query_alter().')); + + // Mixed-storage queries. + $query = new EntityFieldQuery(); + $query + ->fieldCondition($this->fields[0], 'value', '3') + ->fieldCondition($this->fields[1], 'shape', 'squ', 'STARTS_WITH'); + // Alter the storage of the field. + $query->fields[1]['storage']['module'] = 'dummy_storage'; + try { + $query->queryCallback(); + } + catch (EntityFieldQueryException $exception) { + $pass = ($exception->getMessage() == t("Can't handle more than one field storage engine")); + } + $this->assertTrue($pass, t('Cannot query across field storage engines.')); + } + + /** + * Fetches the results of an EntityFieldQuery and compares. + * + * @param $query + * An EntityFieldQuery to run. + * @param $intended_results + * A list of results, every entry is again a list, first being the entity + * type, the second being the entity_id. + * @param $message + * The message to be displayed as the result of this test. + * @param $ordered + * If FALSE then the result of EntityFieldQuery will match + * $intended_results even if the order is not the same. If TRUE then order + * should match too. + */ + function assertEntityFieldQuery($query, $intended_results, $message, $ordered = FALSE) { + $results = array(); + foreach ($query->execute() as $entity_type => $entity_ids) { + foreach ($entity_ids as $entity_id => $stub_entity) { + $results[] = array($entity_type, $entity_id); + } + } + if (!isset($ordered) || !$ordered) { + sort($results); + sort($intended_results); + } + $this->assertEqual($results, $intended_results, $message); + } +} diff --git a/modules/simpletest/tests/error_test.info b/modules/simpletest/tests/error_test.info index 438f3b65..fb225f3a 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/file.test b/modules/simpletest/tests/file.test index dd4270b9..192674f2 100644 --- a/modules/simpletest/tests/file.test +++ b/modules/simpletest/tests/file.test @@ -1,5 +1,5 @@ <?php -// $Id: file.test,v 1.54 2010/05/11 10:56:04 dries Exp $ +// $Id: file.test,v 1.58 2010/06/30 22:37:49 webchick Exp $ /** * @file @@ -537,12 +537,17 @@ class FileSaveUploadTest extends FileHookTestCase { /** * An image file path for uploading. */ - var $image; + protected $image; + + /** + * A PHP file path for upload security testing. + */ + protected $phpfile; /** * The largest file id when the test starts. */ - var $maxFidBefore; + protected $maxFidBefore; public static function getInfo() { return array( @@ -558,14 +563,18 @@ class FileSaveUploadTest extends FileHookTestCase { $this->drupalLogin($account); $this->image = current($this->drupalGetTestFiles('image')); - $this->assertTrue(is_file($this->image->uri), t("The file we're going to upload exists.")); + list(, $this->image_extension) = explode('.', $this->image->filename); + $this->assertTrue(is_file($this->image->uri), t("The image file we're going to upload exists.")); + + $this->phpfile = current($this->drupalGetTestFiles('php')); + $this->assertTrue(is_file($this->phpfile->uri), t("The PHP file we're going to upload exists.")); $this->maxFidBefore = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField(); - // Upload with replace to gurantee there's something there. + // Upload with replace to guarantee there's something there. $edit = array( 'file_test_replace' => FILE_EXISTS_REPLACE, - 'files[file_test_upload]' => drupal_realpath($this->image->uri) + 'files[file_test_upload]' => drupal_realpath($this->image->uri), ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); @@ -630,6 +639,158 @@ class FileSaveUploadTest extends FileHookTestCase { $this->assertFalse(file_load_multiple(), t('No files were loaded.')); } + /** + * Test extension handling. + */ + function testHandleExtension() { + // The file being tested is a .gif which is in the default safe list + // of extensions to allow when the extension validator isn't used. This is + // implicitly tested at the testNormal() test. Here we tell + // file_save_upload() to only allow ".foo". + $extensions = 'foo'; + $edit = array( + 'file_test_replace' => FILE_EXISTS_REPLACE, + 'files[file_test_upload]' => drupal_realpath($this->image->uri), + 'extensions' => $extensions, + ); + + $this->drupalPost('file-test/upload', $edit, t('Submit')); + $this->assertResponse(200, t('Received a 200 response for posted test file.')); + $message = t('Only files with the following extensions are allowed: ') . '<em class="placeholder">' . $extensions . '</em>'; + $this->assertRaw($message, t('Can\'t upload a disallowed extension')); + $this->assertRaw(t('Epic upload FAIL!'), t('Found the failure message.')); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('validate')); + + // Reset the hook counters. + file_test_reset(); + + $extensions = 'foo ' . $this->image_extension; + // Now tell file_save_upload() to allow the extension of our test image. + $edit = array( + 'file_test_replace' => FILE_EXISTS_REPLACE, + 'files[file_test_upload]' => drupal_realpath($this->image->uri), + 'extensions' => $extensions, + ); + + $this->drupalPost('file-test/upload', $edit, t('Submit')); + $this->assertResponse(200, t('Received a 200 response for posted test file.')); + $this->assertNoRaw(t('Only files with the following extensions are allowed:'), t('Can upload an allowed extension.')); + $this->assertRaw(t('You WIN!'), t('Found the success message.')); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('validate', 'load', 'update')); + + // Reset the hook counters. + file_test_reset(); + + // Now tell file_save_upload() to allow any extension. + $edit = array( + 'file_test_replace' => FILE_EXISTS_REPLACE, + 'files[file_test_upload]' => drupal_realpath($this->image->uri), + 'allow_all_extensions' => TRUE, + ); + $this->drupalPost('file-test/upload', $edit, t('Submit')); + $this->assertResponse(200, t('Received a 200 response for posted test file.')); + $this->assertNoRaw(t('Only files with the following extensions are allowed:'), t('Can upload any extension.')); + $this->assertRaw(t('You WIN!'), t('Found the success message.')); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('validate', 'load', 'update')); + } + + /** + * Test dangerous file handling. + */ + function testHandleDangerousFile() { + // Allow the .php extension and make sure it gets renamed to .txt for + // safety. Also check to make sure its MIME type was changed. + $edit = array( + 'file_test_replace' => FILE_EXISTS_REPLACE, + 'files[file_test_upload]' => drupal_realpath($this->phpfile->uri), + 'is_image_file' => FALSE, + 'extensions' => 'php', + ); + + $this->drupalPost('file-test/upload', $edit, t('Submit')); + $this->assertResponse(200, t('Received a 200 response for posted test file.')); + $message = t('For security reasons, your upload has been renamed to ') . '<em class="placeholder">' . $this->phpfile->filename . '.txt' . '</em>'; + $this->assertRaw($message, t('Dangerous file was renamed.')); + $this->assertRaw(t('File MIME type is text/plain.'), t('Dangerous file\'s MIME type was changed.')); + $this->assertRaw(t('You WIN!'), t('Found the success message.')); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('validate', 'insert')); + + // Ensure dangerous files are not renamed when insecure uploads is TRUE. + // Turn on insecure uploads. + variable_set('allow_insecure_uploads', 1); + // Reset the hook counters. + file_test_reset(); + + $this->drupalPost('file-test/upload', $edit, t('Submit')); + $this->assertResponse(200, t('Received a 200 response for posted test file.')); + $this->assertNoRaw(t('For security reasons, your upload has been renamed'), t('Found no security message.')); + $this->assertRaw(t('File name is !filename', array('!filename' => $this->phpfile->filename)), t('Dangerous file was not renamed when insecure uploads is TRUE.')); + $this->assertRaw(t('You WIN!'), t('Found the success message.')); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('validate', 'insert')); + + // Turn off insecure uploads. + variable_set('allow_insecure_uploads', 0); + } + + /** + * Test file munge handling. + */ + function testHandleFileMunge() { + // Ensure insecure uploads are disabled for this test. + variable_set('allow_insecure_uploads', 0); + $this->image = file_move($this->image, $this->image->uri . '.foo.' . $this->image_extension); + + // Reset the hook counters to get rid of the 'move' we just called. + file_test_reset(); + + $extensions = $this->image_extension; + $edit = array( + 'files[file_test_upload]' => drupal_realpath($this->image->uri), + 'extensions' => $extensions, + ); + + $munged_filename = $this->image->filename; + $munged_filename = substr($munged_filename, 0, strrpos($munged_filename, '.')); + $munged_filename .= '_.' . $this->image_extension; + + $this->drupalPost('file-test/upload', $edit, t('Submit')); + $this->assertResponse(200, t('Received a 200 response for posted test file.')); + $this->assertRaw(t('For security reasons, your upload has been renamed'), t('Found security message.')); + $this->assertRaw(t('File name is !filename', array('!filename' => $munged_filename)), t('File was successfully munged.')); + $this->assertRaw(t('You WIN!'), t('Found the success message.')); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('validate', 'insert')); + + // Ensure we don't munge files if we're allowing any extension. + // Reset the hook counters. + file_test_reset(); + + $edit = array( + 'files[file_test_upload]' => drupal_realpath($this->image->uri), + 'allow_all_extensions' => TRUE, + ); + + $this->drupalPost('file-test/upload', $edit, t('Submit')); + $this->assertResponse(200, t('Received a 200 response for posted test file.')); + $this->assertNoRaw(t('For security reasons, your upload has been renamed'), t('Found no security message.')); + $this->assertRaw(t('File name is !filename', array('!filename' => $this->image->filename)), t('File was not munged when allowing any extension.')); + $this->assertRaw(t('You WIN!'), t('Found the success message.')); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('validate', 'insert')); + } + /** * Test renaming when uploading over a file that already exists. */ @@ -752,7 +913,7 @@ class FileDirectoryTest extends FileTestCase { function testFileDirectoryTemp() { // Temporary directory handling. variable_set('file_directory_temp', NULL); - $temp = file_directory_path('temporary'); + $temp = file_directory_temp(); $this->assertTrue(!is_null($temp), t('Properly set and retrieved temp directory %directory.', array('%directory' => $temp)), 'File'); } @@ -2286,11 +2447,6 @@ class StreamWrapperTest extends DrupalWebTestCase { $instance = file_stream_wrapper_get_instance_by_uri('public://foo'); $this->assertEqual('DrupalPublicStreamWrapper', get_class($instance), t('Got correct class type for public URI.')); - // Test file_stream_wrapper_uri_normalize(). - $uri = 'public:///' . file_directory_path() . '/foo/bar/'; - $uri = file_stream_wrapper_uri_normalize($uri); - $this->assertEqual('public://foo/bar', $uri, t('Got a properly normalized URI @uri', array('@uri' => $uri))); - // Test file_uri_target(). $this->assertEqual(file_uri_target('public://foo/bar.txt'), 'foo/bar.txt', t('Got a valid stream target from public://foo/bar.txt.')); $this->assertFalse(file_uri_target('foo/bar.txt'), t('foo/bar.txt is not a valid stream.')); diff --git a/modules/simpletest/tests/file_test.info b/modules/simpletest/tests/file_test.info index 1280ef9e..5b2f8474 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/file_test.module b/modules/simpletest/tests/file_test.module index d7624e55..033c43d2 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.22 2010/05/11 10:56:04 dries Exp $ +// $Id: file_test.module,v 1.23 2010/06/26 19:55:47 dries Exp $ /** * @file @@ -47,7 +47,7 @@ function file_test_stream_wrappers() { function _file_test_form($form, &$form_state) { $form['file_test_upload'] = array( '#type' => 'file', - '#title' => t('Upload an image'), + '#title' => t('Upload a file'), ); $form['file_test_replace'] = array( '#type' => 'select', @@ -61,9 +61,28 @@ function _file_test_form($form, &$form_state) { ); $form['file_subdir'] = array( '#type' => 'textfield', - '#title' => 'Subdirectory for test image', + '#title' => t('Subdirectory for test file'), '#default_value' => '', ); + + $form['extensions'] = array( + '#type' => 'textfield', + '#title' => t('Allowed extensions.'), + '#default_value' => '', + ); + + $form['allow_all_extensions'] = array( + '#type' => 'checkbox', + '#title' => t('Allow all extensions?'), + '#default_value' => FALSE, + ); + + $form['is_image_file'] = array( + '#type' => 'checkbox', + '#title' => t('Is this an image file?'), + '#default_value' => TRUE, + ); + $form['submit'] = array( '#type' => 'submit', '#value' => t('Submit'), @@ -75,7 +94,7 @@ function _file_test_form($form, &$form_state) { * Process the upload. */ function _file_test_form_submit(&$form, &$form_state) { - // Process the upload and validate that it is an image. Note: we're using the + // Process the upload and perform validation. Note: we're using the // form value for the $replace parameter. if (!empty($form_state['values']['file_subdir'])) { $destination = 'temporary://' . $form_state['values']['file_subdir']; @@ -84,10 +103,26 @@ function _file_test_form_submit(&$form, &$form_state) { else { $destination = FALSE; } - $file = file_save_upload('file_test_upload', array('file_validate_is_image' => array()), $destination, $form_state['values']['file_test_replace']); + + // Setup validators. + $validators = array(); + if ($form_state['values']['is_image_file']) { + $validators['file_validate_is_image'] = array(); + } + + if ($form_state['values']['allow_all_extensions']) { + $validators['file_validate_extensions'] = array(); + } + else if (!empty($form_state['values']['extensions'])) { + $validators['file_validate_extensions'] = array($form_state['values']['extensions']); + } + + $file = file_save_upload('file_test_upload', $validators, $destination, $form_state['values']['file_test_replace']); if ($file) { $form_state['values']['file_test_upload'] = $file; drupal_set_message(t('File @filepath was uploaded.', array('@filepath' => $file->uri))); + drupal_set_message(t('File name is @filename.', array('@filename' => $file->filename))); + drupal_set_message(t('File MIME type is @mimetype.', array('@mimetype' => $file->filemime))); drupal_set_message(t('You WIN!')); } elseif ($file === FALSE) { diff --git a/modules/simpletest/tests/filter_test.info b/modules/simpletest/tests/filter_test.info index 921282e8..6c63a666 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/form.test b/modules/simpletest/tests/form.test index b413f19c..efaac4b0 100644 --- a/modules/simpletest/tests/form.test +++ b/modules/simpletest/tests/form.test @@ -1,5 +1,5 @@ <?php -// $Id: form.test,v 1.50 2010/04/28 16:11:22 dries Exp $ +// $Id: form.test,v 1.53 2010/07/02 12:37:57 dries Exp $ /** * @file @@ -207,6 +207,19 @@ class FormsTestCase extends DrupalWebTestCase { } } } + + /** + * Test Form API protections against input forgery. + * + * @see _form_test_input_forgery() + */ + function testInputForgery() { + $this->drupalGet('form-test/input-forgery'); + $checkbox = $this->xpath('//input[@name="checkboxes[two]"]'); + $checkbox[0]['value'] = 'FORGERY'; + $this->drupalPost(NULL, array('checkboxes[one]' => TRUE, 'checkboxes[two]' => TRUE), t('Submit')); + $this->assertText('An illegal choice has been detected.', t('Input forgery was detected.')); + } } /** @@ -983,14 +996,14 @@ class FormsProgrammaticTestCase extends DrupalWebTestCase { } /** - * Test that FAPI correctly determines $form_state['clicked_button']. + * Test that FAPI correctly determines $form_state['triggering_element']. */ -class FormsClickedButtonTestCase extends DrupalWebTestCase { +class FormsTriggeringElementTestCase extends DrupalWebTestCase { public static function getInfo() { return array( - 'name' => 'Form clicked button determination', - 'description' => 'Test the determination of $form_state[\'clicked_button\'].', + 'name' => 'Form triggering element determination', + 'description' => 'Test the determination of $form_state[\'triggering_element\'].', 'group' => 'Form API', ); } @@ -1000,59 +1013,85 @@ class FormsClickedButtonTestCase extends DrupalWebTestCase { } /** - * Test the determination of $form_state['clicked_button'] when no button + * Test the determination of $form_state['triggering_element'] when no button * information is included in the POST data, as is sometimes the case when * the ENTER key is pressed in a textfield in Internet Explorer. */ function testNoButtonInfoInPost() { $path = 'form-test/clicked-button'; $edit = array(); - $form_id = 'form-test-clicked-button'; + $form_html_id = 'form-test-clicked-button'; // Ensure submitting a form with no buttons results in no - // $form_state['clicked_button'] and the form submit handler not running. - drupal_static_reset('drupal_html_id'); - $this->drupalPost($path, $edit, NULL, array(), array(), $form_id); - $this->assertText('There is no clicked button.', t('$form_state[\'clicked_button\'] set to NULL.')); + // $form_state['triggering_element'] and the form submit handler not + // running. + $this->drupalPost($path, $edit, NULL, array(), array(), $form_html_id); + $this->assertText('There is no clicked button.', t('$form_state[\'triggering_element\'] set to NULL.')); $this->assertNoText('Submit handler for form_test_clicked_button executed.', t('Form submit handler did not execute.')); // Ensure submitting a form with one or more submit buttons results in - // $form_state['clicked_button'] being set to the first one the user has + // $form_state['triggering_element'] being set to the first one the user has // access to. An argument with 'r' in it indicates a restricted // (#access=FALSE) button. - drupal_static_reset('drupal_html_id'); - $this->drupalPost($path . '/s', $edit, NULL, array(), array(), $form_id); - $this->assertText('The clicked button is button1.', t('$form_state[\'clicked_button\'] set to only button.')); + $this->drupalPost($path . '/s', $edit, NULL, array(), array(), $form_html_id); + $this->assertText('The clicked button is button1.', t('$form_state[\'triggering_element\'] set to only button.')); $this->assertText('Submit handler for form_test_clicked_button executed.', t('Form submit handler executed.')); - drupal_static_reset('drupal_html_id'); - $this->drupalPost($path . '/s/s', $edit, NULL, array(), array(), $form_id); - $this->assertText('The clicked button is button1.', t('$form_state[\'clicked_button\'] set to first button.')); + + $this->drupalPost($path . '/s/s', $edit, NULL, array(), array(), $form_html_id); + $this->assertText('The clicked button is button1.', t('$form_state[\'triggering_element\'] set to first button.')); $this->assertText('Submit handler for form_test_clicked_button executed.', t('Form submit handler executed.')); - drupal_static_reset('drupal_html_id'); - $this->drupalPost($path . '/rs/s', $edit, NULL, array(), array(), $form_id); - $this->assertText('The clicked button is button2.', t('$form_state[\'clicked_button\'] set to first available button.')); + + $this->drupalPost($path . '/rs/s', $edit, NULL, array(), array(), $form_html_id); + $this->assertText('The clicked button is button2.', t('$form_state[\'triggering_element\'] set to first available button.')); $this->assertText('Submit handler for form_test_clicked_button executed.', t('Form submit handler executed.')); // Ensure submitting a form with buttons of different types results in - // $form_state['clicked_button'] being set to the first button, regardless - // of type. For the FAPI 'button' type, this should result in the submit - // handler not executing. The types are 's'(ubmit), 'b'(utton), and + // $form_state['triggering_element'] being set to the first button, + // regardless of type. For the FAPI 'button' type, this should result in the + // submit handler not executing. The types are 's'(ubmit), 'b'(utton), and // 'i'(mage_button). - drupal_static_reset('drupal_html_id'); - $this->drupalPost($path . '/s/b/i', $edit, NULL, array(), array(), $form_id); - $this->assertText('The clicked button is button1.', t('$form_state[\'clicked_button\'] set to first button.')); + $this->drupalPost($path . '/s/b/i', $edit, NULL, array(), array(), $form_html_id); + $this->assertText('The clicked button is button1.', t('$form_state[\'triggering_element\'] set to first button.')); $this->assertText('Submit handler for form_test_clicked_button executed.', t('Form submit handler executed.')); - drupal_static_reset('drupal_html_id'); - $this->drupalPost($path . '/b/s/i', $edit, NULL, array(), array(), $form_id); - $this->assertText('The clicked button is button1.', t('$form_state[\'clicked_button\'] set to first button.')); + + $this->drupalPost($path . '/b/s/i', $edit, NULL, array(), array(), $form_html_id); + $this->assertText('The clicked button is button1.', t('$form_state[\'triggering_element\'] set to first button.')); $this->assertNoText('Submit handler for form_test_clicked_button executed.', t('Form submit handler did not execute.')); - drupal_static_reset('drupal_html_id'); - $this->drupalPost($path . '/i/s/b', $edit, NULL, array(), array(), $form_id); - $this->assertText('The clicked button is button1.', t('$form_state[\'clicked_button\'] set to first button.')); + + $this->drupalPost($path . '/i/s/b', $edit, NULL, array(), array(), $form_html_id); + $this->assertText('The clicked button is button1.', t('$form_state[\'triggering_element\'] set to first button.')); $this->assertText('Submit handler for form_test_clicked_button executed.', t('Form submit handler executed.')); } -} + /** + * Test that $form_state['triggering_element'] does not get set to a button + * with #access=FALSE. + */ + function testAttemptAccessControlBypass() { + $path = 'form-test/clicked-button'; + $form_html_id = 'form-test-clicked-button'; + + // Retrieve a form where 'button1' has #access=FALSE and 'button2' doesn't. + $this->drupalGet($path . '/rs/s'); + + // Submit the form with 'button1=button1' in the POST data, which someone + // trying to get around security safeguards could easily do. We have to do + // a little trickery here, to work around the safeguards in drupalPost(): by + // renaming the text field that is in the form to 'button1', we can get the + // data we want into $_POST. + $elements = $this->xpath('//form[@id="' . $form_html_id . '"]//input[@name="text"]'); + $elements[0]['name'] = 'button1'; + $this->drupalPost(NULL, array('button1' => 'button1'), NULL, array(), array(), $form_html_id); + + // Ensure that $form_state['triggering_element'] was not set to the + // restricted button. Do this with both a negative and positive assertion, + // because negative assertions alone can be brittle. See + // testNoButtonInfoInPost() for why the triggering element gets set to + // 'button2'. + $this->assertNoText('The clicked button is button1.', t('$form_state[\'triggering_element\'] not set to a restricted button.')); + $this->assertText('The clicked button is button2.', t('$form_state[\'triggering_element\'] not set to a restricted button.')); + } +} /** * Tests rebuilding of arbitrary forms by altering them. @@ -1088,6 +1127,7 @@ class FormsArbitraryRebuildTestCase extends DrupalWebTestCase { ), ); field_create_instance($instance); + variable_set('user_register', USER_REGISTER_VISITORS); } /** diff --git a/modules/simpletest/tests/form_test.info b/modules/simpletest/tests/form_test.info index 4a88f765..1dcf59e2 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/form_test.module b/modules/simpletest/tests/form_test.module index 354d436c..065df578 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.39 2010/04/28 16:11:22 dries Exp $ +// $Id: form_test.module,v 1.42 2010/07/02 12:37:57 dries Exp $ /** * @file @@ -101,6 +101,14 @@ function form_test_menu() { 'type' => MENU_CALLBACK, ); + $items['form-test/input-forgery'] = array( + 'title' => t('Form test'), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('_form_test_input_forgery'), + 'access callback' => TRUE, + 'type' => MENU_CALLBACK, + ); + $items['form-test/form-rebuild-preserve-values'] = array( 'title' => 'Form values preservation during rebuild test', 'page callback' => 'drupal_get_form', @@ -860,6 +868,35 @@ function _form_test_disabled_elements_submit($form, &$form_state) { exit(); } +/** + * Build a form to test input forgery of enabled elements. + */ +function _form_test_input_forgery($form, &$form_state) { + // For testing that a user can't submit a value not matching one of the + // allowed options. + $form['checkboxes'] = array( + '#type' => 'checkboxes', + '#options' => array( + 'one' => 'One', + 'two' => 'Two', + ), + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Submit'), + ); + return $form; +} + +/** + * Return the form values via JSON. + */ +function _form_test_input_forgery_submit($form, &$form_state) { + drupal_json_output($form_state['values']); + exit(); +} + /** * Form builder for testing preservation of values during a rebuild. */ @@ -1077,8 +1114,8 @@ function form_test_clicked_button($form, &$form_state) { * Form validation handler for the form_test_clicked_button() form. */ function form_test_clicked_button_validate($form, &$form_state) { - if (isset($form_state['clicked_button'])) { - drupal_set_message(t('The clicked button is %name.', array('%name' => $form_state['clicked_button']['#name']))); + if (isset($form_state['triggering_element'])) { + drupal_set_message(t('The clicked button is %name.', array('%name' => $form_state['triggering_element']['#name']))); } else { drupal_set_message('There is no clicked button.'); @@ -1092,7 +1129,6 @@ function form_test_clicked_button_submit($form, &$form_state) { drupal_set_message('Submit handler for form_test_clicked_button executed.'); } - /** * Implements hook_form_FORM_ID_alter() for the registration form. */ @@ -1106,8 +1142,6 @@ function form_test_form_user_register_form_alter(&$form, &$form_state) { if (!empty($_REQUEST['field'])) { $node = (object)array('type' => 'page'); field_attach_form('node', $node, $form, $form_state); - // The form API requires the builder function to set rebuilding, so do so. - $form['#builder_function'] = 'form_test_user_register_form_rebuild'; } } diff --git a/modules/simpletest/tests/image_test.info b/modules/simpletest/tests/image_test.info index 4fe62cb5..8dce514b 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/menu.test b/modules/simpletest/tests/menu.test index 0f29b0ff..b108d06b 100644 --- a/modules/simpletest/tests/menu.test +++ b/modules/simpletest/tests/menu.test @@ -1,5 +1,5 @@ <?php -// $Id: menu.test,v 1.29 2010/04/26 14:06:23 dries Exp $ +// $Id: menu.test,v 1.31 2010/07/08 03:41:27 webchick Exp $ /** * @file @@ -20,6 +20,7 @@ class MenuRouterTestCase extends DrupalWebTestCase { parent::setUp('menu_test'); // Make the tests below more robust by explicitly setting the default theme // and administrative theme that they expect. + theme_enable(array('garland')); variable_set('theme_default', 'garland'); variable_set('admin_theme', 'seven'); } @@ -90,6 +91,39 @@ class MenuRouterTestCase extends DrupalWebTestCase { $this->assertRaw('seven/style.css', t("The administrative theme's CSS appears on the page.")); } + /** + * Make sure the maintenance mode can be bypassed using hook_menu_site_status_alter(). + * + * @see hook_menu_site_status_alter(). + */ + function testMaintenanceModeLoginPaths() { + variable_set('maintenance_mode', TRUE); + + $offline_message = t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))); + $this->drupalLogout(); + $this->drupalGet('node'); + $this->assertText($offline_message); + $this->drupalGet('menu_login_callback'); + $this->assertText('This is menu_login_callback().', t('Maintenance mode can be bypassed through hook_login_paths().')); + } + + /** + * Test that an authenticated user hitting 'user/login' gets redirected to + * 'user' and 'user/register' gets redirected to the user edit page. + */ + function testAuthUserUserLogin() { + $loggedInUser = $this->drupalCreateUser(array()); + $this->drupalLogin($loggedInUser); + + $this->DrupalGet('user/login'); + // Check that we got to 'user'. + $this->assertTrue($this->url == url('user', array('absolute' => TRUE)), t("Logged-in user redirected to q=user on accessing q=user/login")); + + // user/register should redirect to user/UID/edit. + $this->DrupalGet('user/register'); + $this->assertTrue($this->url == url('user/' . $this->loggedInUser->uid . '/edit', array('absolute' => TRUE)), t("Logged-in user redirected to q=user/UID/edit on accessing q=user/register")); + } + /** * Test the theme callback when it is set to use an optional theme. */ @@ -491,4 +525,3 @@ class MenuTreeDataTestCase extends DrupalUnitTestCase { return $this->assert($link1['mlid'] == $link2['mlid'], $message ? $message : t('First link is identical to second link')); } } - diff --git a/modules/simpletest/tests/menu_test.info b/modules/simpletest/tests/menu_test.info index 96719398..bcf19e8c 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/menu_test.module b/modules/simpletest/tests/menu_test.module index 14699ae0..4f84b158 100644 --- a/modules/simpletest/tests/menu_test.module +++ b/modules/simpletest/tests/menu_test.module @@ -1,5 +1,5 @@ <?php -// $Id: menu_test.module,v 1.14 2010/04/26 14:06:23 dries Exp $ +// $Id: menu_test.module,v 1.15 2010/07/07 08:05:01 webchick Exp $ /** * @file @@ -189,6 +189,12 @@ function menu_test_menu() { 'type' => MENU_LOCAL_TASK, ); + $items['menu_login_callback'] = array( + 'title' => 'Used as a login path', + 'page callback' => 'menu_login_callback', + 'access callback' => TRUE, + ); + return $items; } @@ -329,3 +335,20 @@ function menu_test_static_variable($value = NULL) { } return $variable; } + +/** + * Implements hook_menu_site_status_alter(). + */ +function menu_test_menu_site_status_alter(&$menu_site_status, $path) { + // Allow access to ?q=menu_login_callback even if in maintenance mode. + if ($menu_site_status == MENU_SITE_OFFLINE && $path == 'menu_login_callback') { + $menu_site_status = MENU_SITE_ONLINE; + } +} + +/** + * Menu callback to be used as a login path. + */ +function menu_login_callback() { + return 'This is menu_login_callback().'; +} diff --git a/modules/simpletest/tests/module_test.file.inc b/modules/simpletest/tests/module_test.file.inc index ebdaf1f3..b1481836 100644 --- a/modules/simpletest/tests/module_test.file.inc +++ b/modules/simpletest/tests/module_test.file.inc @@ -1,5 +1,5 @@ <?php -// $Id: module_test.file.inc,v 1.1 2010/04/22 18:56:53 dries Exp $ +// $Id: module_test.file.inc,v 1.2 2010/05/26 19:51:01 dries Exp $ /** * @file @@ -11,4 +11,4 @@ */ function module_test_test_hook() { -} \ No newline at end of file +} diff --git a/modules/simpletest/tests/module_test.info b/modules/simpletest/tests/module_test.info index 1572971f..d5dd42e4 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/requirements1_test.info b/modules/simpletest/tests/requirements1_test.info new file mode 100644 index 00000000..ca606b5d --- /dev/null +++ b/modules/simpletest/tests/requirements1_test.info @@ -0,0 +1,15 @@ +; $Id: requirements1_test.info,v 1.1 2010/05/26 07:31:46 dries Exp $ +name = Requirements 1 Test +description = "Tests that a module is not installed when it fails hook_requirements('install')." +package = Core +version = VERSION +core = 7.x +files[] = requirements1_test.install +files[] = requirements1_test.module +hidden = TRUE + +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" +project = "drupal" +datestamp = "1278634806" + diff --git a/modules/simpletest/tests/requirements1_test.install b/modules/simpletest/tests/requirements1_test.install new file mode 100644 index 00000000..c3748895 --- /dev/null +++ b/modules/simpletest/tests/requirements1_test.install @@ -0,0 +1,22 @@ +<?php +// $Id: requirements1_test.install,v 1.1 2010/05/26 07:31:46 dries Exp $ + +/** + * Implements hook_requirements(). + */ +function requirements1_test_requirements($phase) { + $requirements = array(); + // Ensure translations don't break at install time. + $t = get_t(); + + // Always fails requirements. + if ('install' == $phase) { + $requirements['requirements1_test'] = array( + 'title' => $t('Requirements 1 Test'), + 'severity' => REQUIREMENT_ERROR, + 'description' => $t('Requirements 1 Test failed requirements.'), + ); + } + + return $requirements; +} diff --git a/modules/simpletest/tests/requirements1_test.module b/modules/simpletest/tests/requirements1_test.module new file mode 100644 index 00000000..862c34b4 --- /dev/null +++ b/modules/simpletest/tests/requirements1_test.module @@ -0,0 +1,8 @@ +<?php +// $Id: requirements1_test.module,v 1.1 2010/05/26 07:31:46 dries Exp $ + +/** + * @file + * Tests that a module is not installed when it fails + * hook_requirements('install'). + */ diff --git a/modules/simpletest/tests/requirements2_test.info b/modules/simpletest/tests/requirements2_test.info new file mode 100644 index 00000000..0eb03a5c --- /dev/null +++ b/modules/simpletest/tests/requirements2_test.info @@ -0,0 +1,16 @@ +; $Id: requirements2_test.info,v 1.1 2010/05/26 07:31:46 dries Exp $ +name = Requirements 2 Test +description = "Tests that a module is not installed when the one it depends on fails hook_requirements('install)." +dependencies[] = requirements1_test +dependencies[] = comment +package = Core +version = VERSION +core = 7.x +files[] = requirements2_test.module +hidden = TRUE + +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" +project = "drupal" +datestamp = "1278634806" + diff --git a/modules/simpletest/tests/requirements2_test.module b/modules/simpletest/tests/requirements2_test.module new file mode 100644 index 00000000..d84c22aa --- /dev/null +++ b/modules/simpletest/tests/requirements2_test.module @@ -0,0 +1,8 @@ +<?php +// $Id: requirements2_test.module,v 1.1 2010/05/26 07:31:47 dries Exp $ + +/** + * @file + * Tests that a module is not installed when the one it depends on fails + * hook_requirements('install'). + */ diff --git a/modules/simpletest/tests/session.test b/modules/simpletest/tests/session.test index b1aa396f..552f8a80 100644 --- a/modules/simpletest/tests/session.test +++ b/modules/simpletest/tests/session.test @@ -1,5 +1,5 @@ <?php -// $Id: session.test,v 1.28 2010/05/12 08:26:15 dries Exp $ +// $Id: session.test,v 1.29 2010/06/14 12:31:46 dries Exp $ /** * @file @@ -274,7 +274,7 @@ class SessionHttpsTestCase extends DrupalWebTestCase { // Check insecure cookie is not set. $this->assertFalse(isset($this->cookies[$insecure_session_name])); $ssid = $this->cookies[$secure_session_name]['value']; - $this->assertSessionIds($ssid, $ssid, 'Session has two secure SIDs'); + $this->assertSessionIds('', $ssid, 'Session has NULL for SID and a correct secure SID.'); $cookie = $secure_session_name . '=' . $ssid; // Verify that user is logged in on secure URL. @@ -303,12 +303,18 @@ class SessionHttpsTestCase extends DrupalWebTestCase { variable_set('https', TRUE); $this->curlClose(); - $this->drupalGet('session-test/set/1'); + // Start an anonymous session on the insecure site. + $session_data = $this->randomName(); + $this->drupalGet('session-test/set/' . $session_data); // Check secure cookie on insecure page. $this->assertFalse(isset($this->cookies[$secure_session_name]), 'The secure cookie is not sent on insecure pages.'); // Check insecure cookie on insecure page. $this->assertFalse($this->cookies[$insecure_session_name]['secure'], 'The insecure cookie does not have the secure attribute'); + // Store the anonymous cookie so we can validate that its session is killed + // after login. + $anonymous_cookie = $insecure_session_name . '=' . $this->cookies[$insecure_session_name]['value']; + // Check that password request form action is not secure. $this->drupalGet('user/password'); $form = $this->xpath('//form[@id="user-pass"]'); @@ -339,6 +345,11 @@ class SessionHttpsTestCase extends DrupalWebTestCase { $secure_session_name . '=' . $ssid, ); + // Test that session data saved before login is still available on the + // authenticated session. + $this->drupalGet('session-test/get'); + $this->assertText($session_data, 'Session correctly returned the stored data set by the anonymous session.'); + foreach ($cookies as $cookie_key => $cookie) { foreach (array('admin/config', $this->httpsUrl('admin/config')) as $url_key => $url) { $this->curlClose(); @@ -354,6 +365,33 @@ class SessionHttpsTestCase extends DrupalWebTestCase { } } } + + // Test that session data saved before login is not available using the + // pre-login anonymous cookie. + $this->cookies = array(); + $this->drupalGet('session-test/get', array('Cookie: ' . $anonymous_cookie)); + $this->assertNoText($session_data, 'Initial anonymous session is inactive after login.'); + + // Clear browser cookie jar. + $this->cookies = array(); + + // Start an anonymous session on the secure site. + $this->drupalGet($this->httpsUrl('session-test/set/1')); + + // Mock a login to the secure site using the secure session cookie. + $this->drupalGet('user'); + $form = $this->xpath('//form[@id="user-login"]'); + $form[0]['action'] = $this->httpsUrl('user'); + $this->drupalPost(NULL, $edit, t('Log in'), array(), array('Cookie: ' . $secure_session_name . '=' . $this->cookies[$secure_session_name]['value'])); + + // Get the insecure session cookie set by the secure login POST request. + $headers = $this->drupalGetHeaders(TRUE); + strtok($headers[0]['set-cookie'], ';='); + $session_id = strtok(';='); + + // Test that the user is also authenticated on the insecure site. + $this->drupalGet("user/{$user->uid}/edit", array(), array('Cookie: ' . $insecure_session_name . '=' . $session_id)); + $this->assertResponse(200); } /** @@ -375,7 +413,7 @@ class SessionHttpsTestCase extends DrupalWebTestCase { ':sid' => $sid, ':ssid' => $ssid, ); - return $this->assertTrue(db_query('SELECT sid FROM {sessions} WHERE sid = :sid AND ssid = :ssid', $args)->fetchField(), $assertion_text); + return $this->assertTrue(db_query('SELECT timestamp FROM {sessions} WHERE sid = :sid AND ssid = :ssid', $args)->fetchField(), $assertion_text); } protected function httpsUrl($url) { @@ -383,3 +421,4 @@ class SessionHttpsTestCase extends DrupalWebTestCase { return $base_url . '/modules/simpletest/tests/https.php?q=' . $url; } } + diff --git a/modules/simpletest/tests/session_test.info b/modules/simpletest/tests/session_test.info index 5ddbc759..962c8b9e 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/system_dependencies_test.info b/modules/simpletest/tests/system_dependencies_test.info index 0fa29043..6adb07d3 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/system_test.info b/modules/simpletest/tests/system_test.info index 32f3e6ae..6a29e371 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/system_test.module b/modules/simpletest/tests/system_test.module index 613b57a5..355655a2 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.28 2010/05/20 08:51:24 dries Exp $ +// $Id: system_test.module,v 1.30 2010/06/28 20:27:34 dries Exp $ /** * Implements hook_menu(). @@ -220,6 +220,9 @@ function system_test_system_info_alter(&$info, $file, $type) { if ($file->name == 'system_dependencies_test') { $info['hidden'] = FALSE; } + if ($file->name == 'requirements1_test' || $file->name == 'requirements2_test') { + $info['hidden'] = FALSE; + } } /** @@ -300,5 +303,11 @@ function _system_test_second_shutdown_function($arg1, $arg2) { // Output something, page has already been printed and the session stored // so we can't use drupal_set_message. print t('Second shutdown function, arg1 : @arg1, arg2: @arg2', array('@arg1' => $arg1, '@arg2' => $arg2)); + + // Throw an exception with an HTML tag. Since this is called in a shutdown + // function, it will not bubble up to the default exception handler but will + // be catched in _drupal_shutdown_function() and be displayed through + // _drupal_render_exception_safe(). + throw new Exception('Drupal is <blink>awesome</blink>.'); } diff --git a/modules/simpletest/tests/taxonomy_test.info b/modules/simpletest/tests/taxonomy_test.info index 2ecdb6b8..10ac2b8b 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/theme_test.info b/modules/simpletest/tests/theme_test.info index da7a96bc..cbd0558f 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/unicode.test b/modules/simpletest/tests/unicode.test index 493f460e..42cb9876 100644 --- a/modules/simpletest/tests/unicode.test +++ b/modules/simpletest/tests/unicode.test @@ -1,5 +1,5 @@ <?php -// $Id: unicode.test,v 1.5 2009/07/13 21:51:41 webchick Exp $ +// $Id: unicode.test,v 1.6 2010/06/10 15:20:48 dries Exp $ /** * @file @@ -48,6 +48,7 @@ class UnicodeUnitTest extends DrupalWebTestCase { $this->helperTestUcFirst(); $this->helperTestStrLen(); $this->helperTestSubStr(); + $this->helperTestTruncate(); } /** @@ -67,6 +68,7 @@ class UnicodeUnitTest extends DrupalWebTestCase { $this->helperTestUcFirst(); $this->helperTestStrLen(); $this->helperTestSubStr(); + $this->helperTestTruncate(); } function helperTestStrToLower() { @@ -127,10 +129,18 @@ class UnicodeUnitTest extends DrupalWebTestCase { function helperTestSubStr() { $testcase = array( // 012345678901234567890123 + array('frànçAIS is über-åwesome', 0, 0, + ''), array('frànçAIS is über-åwesome', 0, 1, 'f'), array('frànçAIS is über-åwesome', 0, 8, 'frànçAIS'), + array('frànçAIS is über-åwesome', 0, 23, + 'frànçAIS is über-åwesom'), + array('frànçAIS is über-åwesome', 0, 24, + 'frànçAIS is über-åwesome'), + array('frànçAIS is über-åwesome', 0, 25, + 'frànçAIS is über-åwesome'), array('frànçAIS is über-åwesome', 0, 100, 'frànçAIS is über-åwesome'), array('frànçAIS is über-åwesome', 4, 4, @@ -141,16 +151,38 @@ class UnicodeUnitTest extends DrupalWebTestCase { ''), array('frànçAIS is über-åwesome', -4, 2, 'so'), + array('frànçAIS is über-åwesome', -4, 3, + 'som'), + array('frànçAIS is über-åwesome', -4, 4, + 'some'), + array('frànçAIS is über-åwesome', -4, 5, + 'some'), array('frànçAIS is über-åwesome', -7, 10, 'åwesome'), array('frànçAIS is über-åwesome', 5, -10, 'AIS is üb'), + array('frànçAIS is über-åwesome', 0, -10, + 'frànçAIS is üb'), + array('frànçAIS is über-åwesome', 0, -1, + 'frànçAIS is über-åwesom'), + array('frànçAIS is über-åwesome', -7, -2, + 'åweso'), + array('frànçAIS is über-åwesome', -7, -6, + 'å'), + array('frànçAIS is über-åwesome', -7, -7, + ''), + array('frànçAIS is über-åwesome', -7, -8, + ''), + array('...', 0, 2, '..'), + array('以呂波耳・ほへとち。リヌルヲ。', 1, 3, + '呂波耳'), ); foreach ($testcase as $test) { list($input, $start, $length, $output) = $test; - $this->assertEqual(drupal_substr($input, $start, $length), $output, t('%input substring-ed at offset %offset for %length characters is %output', array('%input' => $input, '%offset' => $start, '%length' => $length, '%output' => $output))); + $result = drupal_substr($input, $start, $length); + $this->assertEqual($result, $output, t('%input substring at offset %offset for %length characters is %output (got %result)', array('%input' => $input, '%offset' => $start, '%length' => $length, '%output' => $output, '%result' => $result))); } } @@ -215,4 +247,90 @@ class UnicodeUnitTest extends DrupalWebTestCase { $this->assertIdentical(decode_entities($input, $exclude), $output, t('Make sure the decoded entity of %input, excluding %excludes, is %output', array('%input' => $input, '%excludes' => implode(',', $exclude), '%output' => $output))); } } + + /** + * Tests truncate_utf8(). + */ + function helperTestTruncate() { + // Each case is an array with input string, length to truncate to, and + // expected return value. + + // Test non-wordsafe, non-ellipsis cases. + $non_wordsafe_non_ellipsis_cases = array( + array('frànçAIS is über-åwesome', 24, 'frànçAIS is über-åwesome'), + array('frànçAIS is über-åwesome', 23, 'frànçAIS is über-åwesom'), + array('frànçAIS is über-åwesome', 17, 'frànçAIS is über-'), + array('以呂波耳・ほへとち。リヌルヲ。', 6, '以呂波耳・ほ'), + ); + $this->runTruncateTests($non_wordsafe_non_ellipsis_cases, FALSE, FALSE); + + // Test non-wordsafe, ellipsis cases. + $non_wordsafe_ellipsis_cases = array( + array('frànçAIS is über-åwesome', 24, 'frànçAIS is über-åwesome'), + array('frànçAIS is über-åwesome', 23, 'frànçAIS is über-åwe...'), + array('frànçAIS is über-åwesome', 17, 'frànçAIS is üb...'), + ); + $this->runTruncateTests($non_wordsafe_ellipsis_cases, FALSE, TRUE); + + // Test wordsafe, ellipsis cases. + $wordsafe_ellipsis_cases = array( + array('123', 1, '.'), + array('123', 2, '..'), + array('123', 3, '123'), + array('1234', 3, '...'), + array('1234567890', 10, '1234567890'), + array('12345678901', 10, '1234567...'), + array('12345678901', 11, '12345678901'), + array('123456789012', 11, '12345678...'), + array('12345 7890', 10, '12345 7890'), + array('12345 7890', 9, '12345...'), + array('123 567 90', 10, '123 567 90'), + array('123 567 901', 10, '123 567...'), + array('Stop. Hammertime.', 17, 'Stop. Hammertime.'), + array('Stop. Hammertime.', 16, 'Stop....'), + array('frànçAIS is über-åwesome', 24, 'frànçAIS is über-åwesome'), + array('frànçAIS is über-åwesome', 23, 'frànçAIS is über...'), + array('frànçAIS is über-åwesome', 17, 'frànçAIS is...'), + array('¿Dónde está el niño?', 20, '¿Dónde está el niño?'), + array('¿Dónde está el niño?', 19, '¿Dónde está el...'), + array('¿Dónde está el niño?', 15, '¿Dónde está...'), + array('¿Dónde está el niño?', 10, '¿Dónde...'), + array('Help! Help! Help!', 17, 'Help! Help! Help!'), + array('Help! Help! Help!', 16, 'Help! Help!...'), + array('Help! Help! Help!', 15, 'Help! Help!...'), + array('Help! Help! Help!', 14, 'Help! Help!...'), + array('Help! Help! Help!', 13, 'Help! Help...'), + array('Help! Help! Help!', 12, 'Help!...'), + array('Help! Help! Help!', 11, 'Help!...'), + array('Help! Help! Help!', 10, 'Help!...'), + array('Help! Help! Help!', 9, 'Help!...'), + array('Help! Help! Help!', 8, 'Help!...'), + array('Help! Help! Help!', 7, 'Help...'), + array('Help! Help! Help!', 6, 'Hel...'), + array('Help! Help! Help!', 5, 'He...'), + ); + $this->runTruncateTests($wordsafe_ellipsis_cases, TRUE, TRUE); + } + + /** + * Runs test cases for helperTestTruncate(). + * + * Runs each test case through truncate_utf8() and compares the output + * to the expected output. + * + * @param $cases + * Cases array. Each case is an array with the input string, length to + * truncate to, and expected output. + * @param $wordsafe + * TRUE to use word-safe truncation, FALSE to not use word-safe truncation. + * @param $ellipsis + * TRUE to append ... if the input is truncated, FALSE to not append .... + */ + function runTruncateTests($cases, $wordsafe, $ellipsis) { + foreach ($cases as $case) { + list($input, $max_length, $expected) = $case; + $output = truncate_utf8($input, $max_length, $wordsafe, $ellipsis); + $this->assertEqual($output, $expected, t('%input truncate to %length characters with %wordsafe, %ellipsis is %expected (got %output)', array('%input' => $input, '%length' => $max_length, '%output' => $output, '%expected' => $expected, '%wordsafe' => ($wordsafe ? 'word-safe' : 'not word-safe'), '%ellipsis' => ($ellipsis ? 'ellipsis' : 'not ellipsis')))); + } + } } diff --git a/modules/simpletest/tests/update_test_1.info b/modules/simpletest/tests/update_test_1.info index 696344a9..ad5b98b2 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/update_test_2.info b/modules/simpletest/tests/update_test_2.info index 6cd8bc12..fe1f585f 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/update_test_3.info b/modules/simpletest/tests/update_test_3.info index d10794a6..8e0d7cb1 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/upgrade/drupal-6.bare.database.php b/modules/simpletest/tests/upgrade/drupal-6.bare.database.php new file mode 100644 index 00000000..65c9bc57 --- /dev/null +++ b/modules/simpletest/tests/upgrade/drupal-6.bare.database.php @@ -0,0 +1,8132 @@ +<?php +// $Id: drupal-6.bare.database.php,v 1.1 2010/06/28 02:05:47 webchick Exp $ + +/** + * @file + * Bare installation of Drupal 6.17, for test purposes. + * + * This file was generated by the dump-database-d6.sh tool, from a bare + * installation of Drupal 6, with only the following core optional modules + * enabled: + * - dblog + * - update + * + * Dblog fulfills the requirement that a module take care of watchdog calls, + * and update module is enabled at the end of installation. + */ + +db_create_table('access', array( + 'fields' => array( + 'aid' => array( + 'type' => 'serial', + 'not null' => TRUE, + ), + 'mask' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'type' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'status' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + ), + 'primary key' => array( + 'aid', + ), + 'module' => 'user', + 'name' => 'access', +)); + +db_create_table('actions', array( + 'fields' => array( + 'aid' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '0', + ), + 'type' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'callback' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'parameters' => array( + 'type' => 'text', + 'not null' => TRUE, + 'size' => 'big', + ), + 'description' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '0', + ), + ), + 'primary key' => array( + 'aid', + ), + 'module' => 'system', + 'name' => 'actions', +)); +db_insert('actions')->fields(array( + 'aid', + 'type', + 'callback', + 'parameters', + 'description', +)) +->values(array( + 'aid' => 'node_make_sticky_action', + 'type' => 'node', + 'callback' => 'node_make_sticky_action', + 'parameters' => '', + 'description' => 'Make post sticky', +)) +->values(array( + 'aid' => 'node_make_unsticky_action', + 'type' => 'node', + 'callback' => 'node_make_unsticky_action', + 'parameters' => '', + 'description' => 'Make post unsticky', +)) +->values(array( + 'aid' => 'node_promote_action', + 'type' => 'node', + 'callback' => 'node_promote_action', + 'parameters' => '', + 'description' => 'Promote post to front page', +)) +->values(array( + 'aid' => 'node_publish_action', + 'type' => 'node', + 'callback' => 'node_publish_action', + 'parameters' => '', + 'description' => 'Publish post', +)) +->values(array( + 'aid' => 'node_save_action', + 'type' => 'node', + 'callback' => 'node_save_action', + 'parameters' => '', + 'description' => 'Save post', +)) +->values(array( + 'aid' => 'node_unpromote_action', + 'type' => 'node', + 'callback' => 'node_unpromote_action', + 'parameters' => '', + 'description' => 'Remove post from front page', +)) +->values(array( + 'aid' => 'node_unpublish_action', + 'type' => 'node', + 'callback' => 'node_unpublish_action', + 'parameters' => '', + 'description' => 'Unpublish post', +)) +->values(array( + 'aid' => 'user_block_ip_action', + 'type' => 'user', + 'callback' => 'user_block_ip_action', + 'parameters' => '', + 'description' => 'Ban IP address of current user', +)) +->values(array( + 'aid' => 'user_block_user_action', + 'type' => 'user', + 'callback' => 'user_block_user_action', + 'parameters' => '', + 'description' => 'Block current user', +)) +->execute(); + +db_create_table('actions_aid', array( + 'fields' => array( + 'aid' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + ), + 'primary key' => array( + 'aid', + ), + 'module' => 'system', + 'name' => 'actions_aid', +)); + +db_create_table('authmap', array( + 'fields' => array( + 'aid' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'uid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'authname' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'module' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + ), + 'unique keys' => array( + 'authname' => array( + 'authname', + ), + ), + 'primary key' => array( + 'aid', + ), + 'module' => 'user', + 'name' => 'authmap', +)); + +db_create_table('batch', array( + 'fields' => array( + 'bid' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'token' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + ), + 'timestamp' => array( + 'type' => 'int', + 'not null' => TRUE, + ), + 'batch' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + ), + ), + 'primary key' => array( + 'bid', + ), + 'indexes' => array( + 'token' => array( + 'token', + ), + ), + 'module' => 'system', + 'name' => 'batch', +)); + +db_create_table('blocks', array( + 'fields' => array( + 'bid' => array( + 'type' => 'serial', + 'not null' => TRUE, + ), + 'module' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'default' => '', + ), + 'delta' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '0', + ), + 'theme' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'default' => '', + ), + 'status' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + 'weight' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + 'region' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'default' => '', + ), + 'custom' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + 'throttle' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + 'visibility' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + 'pages' => array( + 'type' => 'text', + 'not null' => TRUE, + ), + 'title' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'default' => '', + ), + 'cache' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 1, + 'size' => 'tiny', + ), + ), + 'primary key' => array( + 'bid', + ), + 'unique keys' => array( + 'tmd' => array( + 'theme', + 'module', + 'delta', + ), + ), + 'indexes' => array( + 'list' => array( + 'theme', + 'status', + 'region', + 'weight', + 'module', + ), + ), + 'module' => 'block', + 'name' => 'blocks', +)); +db_insert('blocks')->fields(array( + 'bid', + 'module', + 'delta', + 'theme', + 'status', + 'weight', + 'region', + 'custom', + 'throttle', + 'visibility', + 'pages', + 'title', + 'cache', +)) +->values(array( + 'bid' => '1', + 'module' => 'user', + 'delta' => '0', + 'theme' => 'garland', + 'status' => '1', + 'weight' => '0', + 'region' => 'left', + 'custom' => '0', + 'throttle' => '0', + 'visibility' => '0', + 'pages' => '', + 'title' => '', + 'cache' => '-1', +)) +->values(array( + 'bid' => '2', + 'module' => 'user', + 'delta' => '1', + 'theme' => 'garland', + 'status' => '1', + 'weight' => '0', + 'region' => 'left', + 'custom' => '0', + 'throttle' => '0', + 'visibility' => '0', + 'pages' => '', + 'title' => '', + 'cache' => '-1', +)) +->values(array( + 'bid' => '3', + 'module' => 'system', + 'delta' => '0', + 'theme' => 'garland', + 'status' => '1', + 'weight' => '10', + 'region' => 'footer', + 'custom' => '0', + 'throttle' => '0', + 'visibility' => '0', + 'pages' => '', + 'title' => '', + 'cache' => '-1', +)) +->execute(); + +db_create_table('blocks_roles', array( + 'fields' => array( + 'module' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + ), + 'delta' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ), + 'rid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + ), + 'primary key' => array( + 'module', + 'delta', + 'rid', + ), + 'indexes' => array( + 'rid' => array( + 'rid', + ), + ), + 'module' => 'block', + 'name' => 'blocks_roles', +)); + +db_create_table('boxes', array( + 'fields' => array( + 'bid' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'body' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + ), + 'info' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'format' => array( + 'type' => 'int', + 'size' => 'small', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'unique keys' => array( + 'info' => array( + 'info', + ), + ), + 'primary key' => array( + 'bid', + ), + 'module' => 'block', + 'name' => 'boxes', +)); + +db_create_table('cache', array( + 'fields' => array( + 'cid' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'data' => array( + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big', + ), + 'expire' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'created' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'headers' => array( + 'type' => 'text', + 'not null' => FALSE, + ), + 'serialized' => array( + 'type' => 'int', + 'size' => 'small', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'expire' => array( + 'expire', + ), + ), + 'primary key' => array( + 'cid', + ), + 'module' => 'system', + 'name' => 'cache', +)); + +db_create_table('cache_block', array( + 'fields' => array( + 'cid' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'data' => array( + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big', + ), + 'expire' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'created' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'headers' => array( + 'type' => 'text', + 'not null' => FALSE, + ), + 'serialized' => array( + 'type' => 'int', + 'size' => 'small', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'expire' => array( + 'expire', + ), + ), + 'primary key' => array( + 'cid', + ), + 'module' => 'block', + 'name' => 'cache_block', +)); + +db_create_table('cache_filter', array( + 'fields' => array( + 'cid' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'data' => array( + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big', + ), + 'expire' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'created' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'headers' => array( + 'type' => 'text', + 'not null' => FALSE, + ), + 'serialized' => array( + 'type' => 'int', + 'size' => 'small', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'expire' => array( + 'expire', + ), + ), + 'primary key' => array( + 'cid', + ), + 'module' => 'filter', + 'name' => 'cache_filter', +)); + +db_create_table('cache_form', array( + 'fields' => array( + 'cid' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'data' => array( + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big', + ), + 'expire' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'created' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'headers' => array( + 'type' => 'text', + 'not null' => FALSE, + ), + 'serialized' => array( + 'type' => 'int', + 'size' => 'small', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'expire' => array( + 'expire', + ), + ), + 'primary key' => array( + 'cid', + ), + 'module' => 'system', + 'name' => 'cache_form', +)); + +db_create_table('cache_menu', array( + 'fields' => array( + 'cid' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'data' => array( + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big', + ), + 'expire' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'created' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'headers' => array( + 'type' => 'text', + 'not null' => FALSE, + ), + 'serialized' => array( + 'type' => 'int', + 'size' => 'small', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'expire' => array( + 'expire', + ), + ), + 'primary key' => array( + 'cid', + ), + 'module' => 'system', + 'name' => 'cache_menu', +)); + +db_create_table('cache_page', array( + 'fields' => array( + 'cid' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'data' => array( + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big', + ), + 'expire' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'created' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'headers' => array( + 'type' => 'text', + 'not null' => FALSE, + ), + 'serialized' => array( + 'type' => 'int', + 'size' => 'small', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'expire' => array( + 'expire', + ), + ), + 'primary key' => array( + 'cid', + ), + 'module' => 'system', + 'name' => 'cache_page', +)); + +db_create_table('cache_update', array( + 'fields' => array( + 'cid' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'data' => array( + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big', + ), + 'expire' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'created' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'headers' => array( + 'type' => 'text', + 'not null' => FALSE, + ), + 'serialized' => array( + 'type' => 'int', + 'size' => 'small', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'expire' => array( + 'expire', + ), + ), + 'primary key' => array( + 'cid', + ), + 'module' => 'update', + 'name' => 'cache_update', +)); + +db_create_table('files', array( + 'fields' => array( + 'fid' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'uid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'filename' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'filepath' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'filemime' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'filesize' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'status' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'timestamp' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'uid' => array( + 'uid', + ), + 'status' => array( + 'status', + ), + 'timestamp' => array( + 'timestamp', + ), + ), + 'primary key' => array( + 'fid', + ), + 'module' => 'system', + 'name' => 'files', +)); + +db_create_table('filter_formats', array( + 'fields' => array( + 'format' => array( + 'type' => 'serial', + 'not null' => TRUE, + ), + 'name' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'roles' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'cache' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + ), + 'primary key' => array( + 'format', + ), + 'unique keys' => array( + 'name' => array( + 'name', + ), + ), + 'module' => 'filter', + 'name' => 'filter_formats', +)); +db_insert('filter_formats')->fields(array( + 'format', + 'name', + 'roles', + 'cache', +)) +->values(array( + 'format' => '1', + 'name' => 'Filtered HTML', + 'roles' => ',1,2,', + 'cache' => '1', +)) +->values(array( + 'format' => '2', + 'name' => 'Full HTML', + 'roles' => '', + 'cache' => '1', +)) +->execute(); + +db_create_table('filters', array( + 'fields' => array( + 'fid' => array( + 'type' => 'serial', + 'not null' => TRUE, + ), + 'format' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'module' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'default' => '', + ), + 'delta' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + 'weight' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + ), + 'primary key' => array( + 'fid', + ), + 'unique keys' => array( + 'fmd' => array( + 'format', + 'module', + 'delta', + ), + ), + 'indexes' => array( + 'list' => array( + 'format', + 'weight', + 'module', + 'delta', + ), + ), + 'module' => 'filter', + 'name' => 'filters', +)); +db_insert('filters')->fields(array( + 'fid', + 'format', + 'module', + 'delta', + 'weight', +)) +->values(array( + 'fid' => '1', + 'format' => '1', + 'module' => 'filter', + 'delta' => '2', + 'weight' => '0', +)) +->values(array( + 'fid' => '2', + 'format' => '1', + 'module' => 'filter', + 'delta' => '0', + 'weight' => '1', +)) +->values(array( + 'fid' => '3', + 'format' => '1', + 'module' => 'filter', + 'delta' => '1', + 'weight' => '2', +)) +->values(array( + 'fid' => '4', + 'format' => '1', + 'module' => 'filter', + 'delta' => '3', + 'weight' => '10', +)) +->values(array( + 'fid' => '5', + 'format' => '2', + 'module' => 'filter', + 'delta' => '2', + 'weight' => '0', +)) +->values(array( + 'fid' => '6', + 'format' => '2', + 'module' => 'filter', + 'delta' => '1', + 'weight' => '1', +)) +->values(array( + 'fid' => '7', + 'format' => '2', + 'module' => 'filter', + 'delta' => '3', + 'weight' => '10', +)) +->execute(); + +db_create_table('flood', array( + 'fields' => array( + 'fid' => array( + 'type' => 'serial', + 'not null' => TRUE, + ), + 'event' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'default' => '', + ), + 'hostname' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'timestamp' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array( + 'fid', + ), + 'indexes' => array( + 'allow' => array( + 'event', + 'hostname', + 'timestamp', + ), + ), + 'module' => 'system', + 'name' => 'flood', +)); + +db_create_table('history', array( + 'fields' => array( + 'uid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'nid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'timestamp' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array( + 'uid', + 'nid', + ), + 'indexes' => array( + 'nid' => array( + 'nid', + ), + ), + 'module' => 'system', + 'name' => 'history', +)); + +db_create_table('menu_links', array( + 'fields' => array( + 'menu_name' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'mlid' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'plid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'link_path' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'router_path' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'link_title' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'options' => array( + 'type' => 'text', + 'not null' => FALSE, + ), + 'module' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => 'system', + ), + 'hidden' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'small', + ), + 'external' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'small', + ), + 'has_children' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'small', + ), + 'expanded' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'small', + ), + 'weight' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'depth' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'small', + ), + 'customized' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'small', + ), + 'p1' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'p2' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'p3' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'p4' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'p5' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'p6' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'p7' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'p8' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'p9' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'updated' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'small', + ), + ), + 'indexes' => array( + 'path_menu' => array( + array( + 'link_path', + 128, + ), + 'menu_name', + ), + 'menu_plid_expand_child' => array( + 'menu_name', + 'plid', + 'expanded', + 'has_children', + ), + 'menu_parents' => array( + 'menu_name', + 'p1', + 'p2', + 'p3', + 'p4', + 'p5', + 'p6', + 'p7', + 'p8', + 'p9', + ), + 'router_path' => array( + array( + 'router_path', + 128, + ), + ), + ), + 'primary key' => array( + 'mlid', + ), + 'module' => 'system', + 'name' => 'menu_links', +)); +db_insert('menu_links')->fields(array( + 'menu_name', + 'mlid', + 'plid', + 'link_path', + 'router_path', + 'link_title', + 'options', + 'module', + 'hidden', + 'external', + 'has_children', + 'expanded', + 'weight', + 'depth', + 'customized', + 'p1', + 'p2', + 'p3', + 'p4', + 'p5', + 'p6', + 'p7', + 'p8', + 'p9', + 'updated', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '1', + 'plid' => '0', + 'link_path' => 'batch', + 'router_path' => 'batch', + 'link_title' => '', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '1', + 'customized' => '0', + 'p1' => '1', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '2', + 'plid' => '0', + 'link_path' => 'admin', + 'router_path' => 'admin', + 'link_title' => 'Administer', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '1', + 'expanded' => '0', + 'weight' => '9', + 'depth' => '1', + 'customized' => '0', + 'p1' => '2', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '3', + 'plid' => '0', + 'link_path' => 'node', + 'router_path' => 'node', + 'link_title' => 'Content', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '1', + 'customized' => '0', + 'p1' => '3', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '4', + 'plid' => '0', + 'link_path' => 'logout', + 'router_path' => 'logout', + 'link_title' => 'Log out', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '10', + 'depth' => '1', + 'customized' => '0', + 'p1' => '4', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '5', + 'plid' => '0', + 'link_path' => 'rss.xml', + 'router_path' => 'rss.xml', + 'link_title' => 'RSS feed', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '1', + 'customized' => '0', + 'p1' => '5', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '6', + 'plid' => '0', + 'link_path' => 'user', + 'router_path' => 'user', + 'link_title' => 'User account', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '1', + 'customized' => '0', + 'p1' => '6', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '7', + 'plid' => '0', + 'link_path' => 'node/%', + 'router_path' => 'node/%', + 'link_title' => '', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '1', + 'customized' => '0', + 'p1' => '7', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '8', + 'plid' => '2', + 'link_path' => 'admin/compact', + 'router_path' => 'admin/compact', + 'link_title' => 'Compact mode', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '2', + 'customized' => '0', + 'p1' => '2', + 'p2' => '8', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '9', + 'plid' => '0', + 'link_path' => 'filter/tips', + 'router_path' => 'filter/tips', + 'link_title' => 'Compose tips', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '1', + 'customized' => '0', + 'p1' => '9', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '10', + 'plid' => '2', + 'link_path' => 'admin/content', + 'router_path' => 'admin/content', + 'link_title' => 'Content management', + 'options' => "a:1:{s:10:\"attributes\";a:1:{s:5:\"title\";s:27:\"Manage your site's content.\";}}", + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '1', + 'expanded' => '0', + 'weight' => '-10', + 'depth' => '2', + 'customized' => '0', + 'p1' => '2', + 'p2' => '10', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '11', + 'plid' => '0', + 'link_path' => 'node/add', + 'router_path' => 'node/add', + 'link_title' => 'Create content', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '1', + 'expanded' => '0', + 'weight' => '1', + 'depth' => '1', + 'customized' => '0', + 'p1' => '11', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '12', + 'plid' => '0', + 'link_path' => 'system/files', + 'router_path' => 'system/files', + 'link_title' => 'File download', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '1', + 'customized' => '0', + 'p1' => '12', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '13', + 'plid' => '2', + 'link_path' => 'admin/reports', + 'router_path' => 'admin/reports', + 'link_title' => 'Reports', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:59:"View reports from system logs and other status information.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '1', + 'expanded' => '0', + 'weight' => '5', + 'depth' => '2', + 'customized' => '0', + 'p1' => '2', + 'p2' => '13', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '14', + 'plid' => '2', + 'link_path' => 'admin/build', + 'router_path' => 'admin/build', + 'link_title' => 'Site building', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:38:"Control how your site looks and feels.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '1', + 'expanded' => '0', + 'weight' => '-10', + 'depth' => '2', + 'customized' => '0', + 'p1' => '2', + 'p2' => '14', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '15', + 'plid' => '2', + 'link_path' => 'admin/settings', + 'router_path' => 'admin/settings', + 'link_title' => 'Site configuration', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:40:"Adjust basic site configuration options.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '1', + 'expanded' => '0', + 'weight' => '-5', + 'depth' => '2', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '16', + 'plid' => '0', + 'link_path' => 'user/autocomplete', + 'router_path' => 'user/autocomplete', + 'link_title' => 'User autocomplete', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '1', + 'customized' => '0', + 'p1' => '16', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '17', + 'plid' => '2', + 'link_path' => 'admin/user', + 'router_path' => 'admin/user', + 'link_title' => 'User management', + 'options' => "a:1:{s:10:\"attributes\";a:1:{s:5:\"title\";s:61:\"Manage your site's users, groups and access to site features.\";}}", + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '1', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '2', + 'customized' => '0', + 'p1' => '2', + 'p2' => '17', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '18', + 'plid' => '0', + 'link_path' => 'user/%', + 'router_path' => 'user/%', + 'link_title' => 'My account', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '1', + 'customized' => '0', + 'p1' => '18', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '19', + 'plid' => '17', + 'link_path' => 'admin/user/rules', + 'router_path' => 'admin/user/rules', + 'link_title' => 'Access rules', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:80:"List and create rules to disallow usernames, e-mail addresses, and IP addresses.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '17', + 'p3' => '19', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '20', + 'plid' => '15', + 'link_path' => 'admin/settings/actions', + 'router_path' => 'admin/settings/actions', + 'link_title' => 'Actions', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:41:"Manage the actions defined for your site.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '20', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '21', + 'plid' => '15', + 'link_path' => 'admin/settings/admin', + 'router_path' => 'admin/settings/admin', + 'link_title' => 'Administration theme', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:55:"Settings for how your administrative pages should look.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '21', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '22', + 'plid' => '14', + 'link_path' => 'admin/build/block', + 'router_path' => 'admin/build/block', + 'link_title' => 'Blocks', + 'options' => "a:1:{s:10:\"attributes\";a:1:{s:5:\"title\";s:79:\"Configure what block content appears in your site's sidebars and other regions.\";}}", + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '14', + 'p3' => '22', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '23', + 'plid' => '15', + 'link_path' => 'admin/settings/clean-urls', + 'router_path' => 'admin/settings/clean-urls', + 'link_title' => 'Clean URLs', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:43:"Enable or disable clean URLs for your site.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '23', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '24', + 'plid' => '10', + 'link_path' => 'admin/content/node', + 'router_path' => 'admin/content/node', + 'link_title' => 'Content', + 'options' => "a:1:{s:10:\"attributes\";a:1:{s:5:\"title\";s:43:\"View, edit, and delete your site's content.\";}}", + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '10', + 'p3' => '24', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '25', + 'plid' => '10', + 'link_path' => 'admin/content/types', + 'router_path' => 'admin/content/types', + 'link_title' => 'Content types', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:82:"Manage posts by content type, including default status, front page promotion, etc.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '10', + 'p3' => '25', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '26', + 'plid' => '15', + 'link_path' => 'admin/settings/date-time', + 'router_path' => 'admin/settings/date-time', + 'link_title' => 'Date and time', + 'options' => "a:1:{s:10:\"attributes\";a:1:{s:5:\"title\";s:89:\"Settings for how Drupal displays date and time, as well as the system's default timezone.\";}}", + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '26', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '27', + 'plid' => '0', + 'link_path' => 'node/%/delete', + 'router_path' => 'node/%/delete', + 'link_title' => 'Delete', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '1', + 'depth' => '1', + 'customized' => '0', + 'p1' => '27', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '28', + 'plid' => '18', + 'link_path' => 'user/%/delete', + 'router_path' => 'user/%/delete', + 'link_title' => 'Delete', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '2', + 'customized' => '0', + 'p1' => '18', + 'p2' => '28', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '29', + 'plid' => '15', + 'link_path' => 'admin/settings/error-reporting', + 'router_path' => 'admin/settings/error-reporting', + 'link_title' => 'Error reporting', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:93:"Control how Drupal deals with errors including 403/404 errors as well as PHP error reporting.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '29', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '30', + 'plid' => '15', + 'link_path' => 'admin/settings/file-system', + 'router_path' => 'admin/settings/file-system', + 'link_title' => 'File system', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:68:"Tell Drupal where to store uploaded files and how they are accessed.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '30', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '31', + 'plid' => '15', + 'link_path' => 'admin/settings/image-toolkit', + 'router_path' => 'admin/settings/image-toolkit', + 'link_title' => 'Image toolkit', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:74:"Choose which image toolkit to use if you have installed optional toolkits.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '31', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '32', + 'plid' => '15', + 'link_path' => 'admin/settings/filters', + 'router_path' => 'admin/settings/filters', + 'link_title' => 'Input formats', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:127:"Configure how content input by users is filtered, including allowed HTML tags. Also allows enabling of module-provided filters.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '32', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '33', + 'plid' => '15', + 'link_path' => 'admin/settings/logging', + 'router_path' => 'admin/settings/logging', + 'link_title' => 'Logging and alerts', + 'options' => "a:1:{s:10:\"attributes\";a:1:{s:5:\"title\";s:156:\"Settings for logging and alerts modules. Various modules can route Drupal's system events to different destination, such as syslog, database, email, ...etc.\";}}", + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '1', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '33', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '34', + 'plid' => '14', + 'link_path' => 'admin/build/modules', + 'router_path' => 'admin/build/modules', + 'link_title' => 'Modules', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:47:"Enable or disable add-on modules for your site.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '14', + 'p3' => '34', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '35', + 'plid' => '15', + 'link_path' => 'admin/settings/performance', + 'router_path' => 'admin/settings/performance', + 'link_title' => 'Performance', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:101:"Enable or disable page caching for anonymous users and set CSS and JS bandwidth optimization options.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '35', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '36', + 'plid' => '17', + 'link_path' => 'admin/user/permissions', + 'router_path' => 'admin/user/permissions', + 'link_title' => 'Permissions', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:64:"Determine access to features by selecting permissions for roles.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '17', + 'p3' => '36', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '37', + 'plid' => '10', + 'link_path' => 'admin/content/node-settings', + 'router_path' => 'admin/content/node-settings', + 'link_title' => 'Post settings', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:126:"Control posting behavior, such as teaser length, requiring previews before posting, and the number of posts on the front page.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '10', + 'p3' => '37', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '38', + 'plid' => '10', + 'link_path' => 'admin/content/rss-publishing', + 'router_path' => 'admin/content/rss-publishing', + 'link_title' => 'RSS publishing', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:92:"Configure the number of items per feed and whether feeds should be titles/teasers/full-text.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '10', + 'p3' => '38', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '39', + 'plid' => '13', + 'link_path' => 'admin/reports/dblog', + 'router_path' => 'admin/reports/dblog', + 'link_title' => 'Recent log entries', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:43:"View events that have recently been logged.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '-1', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '13', + 'p3' => '39', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '40', + 'plid' => '17', + 'link_path' => 'admin/user/roles', + 'router_path' => 'admin/user/roles', + 'link_title' => 'Roles', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:30:"List, edit, or add user roles.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '17', + 'p3' => '40', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '41', + 'plid' => '15', + 'link_path' => 'admin/settings/site-information', + 'router_path' => 'admin/settings/site-information', + 'link_title' => 'Site information', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:107:"Change basic site information, such as the site name, slogan, e-mail address, mission, front page and more.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '41', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '42', + 'plid' => '15', + 'link_path' => 'admin/settings/site-maintenance', + 'router_path' => 'admin/settings/site-maintenance', + 'link_title' => 'Site maintenance', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:63:"Take the site off-line for maintenance or bring it back online.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '42', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '43', + 'plid' => '13', + 'link_path' => 'admin/reports/status', + 'router_path' => 'admin/reports/status', + 'link_title' => 'Status report', + 'options' => "a:1:{s:10:\"attributes\";a:1:{s:5:\"title\";s:74:\"Get a status report about your site's operation and any detected problems.\";}}", + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '10', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '13', + 'p3' => '43', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '44', + 'plid' => '14', + 'link_path' => 'admin/build/themes', + 'router_path' => 'admin/build/themes', + 'link_title' => 'Themes', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:57:"Change which theme your site uses or allows users to set.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '14', + 'p3' => '44', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '45', + 'plid' => '13', + 'link_path' => 'admin/reports/access-denied', + 'router_path' => 'admin/reports/access-denied', + 'link_title' => "Top 'access denied' errors", + 'options' => "a:1:{s:10:\"attributes\";a:1:{s:5:\"title\";s:35:\"View 'access denied' errors (403s).\";}}", + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '13', + 'p3' => '45', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '46', + 'plid' => '13', + 'link_path' => 'admin/reports/page-not-found', + 'router_path' => 'admin/reports/page-not-found', + 'link_title' => "Top 'page not found' errors", + 'options' => "a:1:{s:10:\"attributes\";a:1:{s:5:\"title\";s:36:\"View 'page not found' errors (404s).\";}}", + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '13', + 'p3' => '46', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '47', + 'plid' => '17', + 'link_path' => 'admin/user/settings', + 'router_path' => 'admin/user/settings', + 'link_title' => 'User settings', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:101:"Configure default behavior of users, including registration requirements, e-mails, and user pictures.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '17', + 'p3' => '47', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '48', + 'plid' => '17', + 'link_path' => 'admin/user/user', + 'router_path' => 'admin/user/user', + 'link_title' => 'Users', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:26:"List, add, and edit users.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '17', + 'p3' => '48', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '49', + 'plid' => '32', + 'link_path' => 'admin/settings/filters/%', + 'router_path' => 'admin/settings/filters/%', + 'link_title' => '', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '32', + 'p4' => '49', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '50', + 'plid' => '23', + 'link_path' => 'admin/settings/clean-urls/check', + 'router_path' => 'admin/settings/clean-urls/check', + 'link_title' => 'Clean URL check', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '23', + 'p4' => '50', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '51', + 'plid' => '20', + 'link_path' => 'admin/settings/actions/configure', + 'router_path' => 'admin/settings/actions/configure', + 'link_title' => 'Configure an advanced action', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '20', + 'p4' => '51', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '52', + 'plid' => '22', + 'link_path' => 'admin/build/block/configure', + 'router_path' => 'admin/build/block/configure', + 'link_title' => 'Configure block', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '14', + 'p3' => '22', + 'p4' => '52', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '53', + 'plid' => '33', + 'link_path' => 'admin/settings/logging/dblog', + 'router_path' => 'admin/settings/logging/dblog', + 'link_title' => 'Database logging', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:169:"Settings for logging to the Drupal database logs. This is the most common method for small to medium sites on shared hosting. The logs are viewable from the admin pages.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '33', + 'p4' => '53', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '54', + 'plid' => '26', + 'link_path' => 'admin/settings/date-time/lookup', + 'router_path' => 'admin/settings/date-time/lookup', + 'link_title' => 'Date and time lookup', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '26', + 'p4' => '54', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '55', + 'plid' => '22', + 'link_path' => 'admin/build/block/delete', + 'router_path' => 'admin/build/block/delete', + 'link_title' => 'Delete block', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '14', + 'p3' => '22', + 'p4' => '55', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '56', + 'plid' => '32', + 'link_path' => 'admin/settings/filters/delete', + 'router_path' => 'admin/settings/filters/delete', + 'link_title' => 'Delete input format', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '32', + 'p4' => '56', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '57', + 'plid' => '19', + 'link_path' => 'admin/user/rules/delete', + 'router_path' => 'admin/user/rules/delete', + 'link_title' => 'Delete rule', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '17', + 'p3' => '19', + 'p4' => '57', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '58', + 'plid' => '13', + 'link_path' => 'admin/reports/event/%', + 'router_path' => 'admin/reports/event/%', + 'link_title' => 'Details', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '13', + 'p3' => '58', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '59', + 'plid' => '40', + 'link_path' => 'admin/user/roles/edit', + 'router_path' => 'admin/user/roles/edit', + 'link_title' => 'Edit role', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '17', + 'p3' => '40', + 'p4' => '59', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '60', + 'plid' => '19', + 'link_path' => 'admin/user/rules/edit', + 'router_path' => 'admin/user/rules/edit', + 'link_title' => 'Edit rule', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '17', + 'p3' => '19', + 'p4' => '60', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '61', + 'plid' => '43', + 'link_path' => 'admin/reports/status/php', + 'router_path' => 'admin/reports/status/php', + 'link_title' => 'PHP', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '13', + 'p3' => '43', + 'p4' => '61', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '62', + 'plid' => '37', + 'link_path' => 'admin/content/node-settings/rebuild', + 'router_path' => 'admin/content/node-settings/rebuild', + 'link_title' => 'Rebuild permissions', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '10', + 'p3' => '37', + 'p4' => '62', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '63', + 'plid' => '20', + 'link_path' => 'admin/settings/actions/orphan', + 'router_path' => 'admin/settings/actions/orphan', + 'link_title' => 'Remove orphans', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '20', + 'p4' => '63', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '64', + 'plid' => '43', + 'link_path' => 'admin/reports/status/run-cron', + 'router_path' => 'admin/reports/status/run-cron', + 'link_title' => 'Run cron', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '13', + 'p3' => '43', + 'p4' => '64', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '65', + 'plid' => '43', + 'link_path' => 'admin/reports/status/sql', + 'router_path' => 'admin/reports/status/sql', + 'link_title' => 'SQL', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '13', + 'p3' => '43', + 'p4' => '65', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '66', + 'plid' => '20', + 'link_path' => 'admin/settings/actions/delete/%', + 'router_path' => 'admin/settings/actions/delete/%', + 'link_title' => 'Delete action', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:17:"Delete an action.";}}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '15', + 'p3' => '20', + 'p4' => '66', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '67', + 'plid' => '22', + 'link_path' => 'admin/build/block/list/js', + 'router_path' => 'admin/build/block/list/js', + 'link_title' => 'JavaScript List Form', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '14', + 'p3' => '22', + 'p4' => '67', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '68', + 'plid' => '34', + 'link_path' => 'admin/build/modules/list/confirm', + 'router_path' => 'admin/build/modules/list/confirm', + 'link_title' => 'List', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '14', + 'p3' => '34', + 'p4' => '68', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '69', + 'plid' => '0', + 'link_path' => 'user/reset/%/%/%', + 'router_path' => 'user/reset/%/%/%', + 'link_title' => 'Reset password', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '1', + 'customized' => '0', + 'p1' => '69', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '70', + 'plid' => '34', + 'link_path' => 'admin/build/modules/uninstall/confirm', + 'router_path' => 'admin/build/modules/uninstall/confirm', + 'link_title' => 'Uninstall', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '14', + 'p3' => '34', + 'p4' => '70', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '71', + 'plid' => '0', + 'link_path' => 'node/%/revisions/%/delete', + 'router_path' => 'node/%/revisions/%/delete', + 'link_title' => 'Delete earlier revision', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '1', + 'customized' => '0', + 'p1' => '71', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '72', + 'plid' => '0', + 'link_path' => 'node/%/revisions/%/revert', + 'router_path' => 'node/%/revisions/%/revert', + 'link_title' => 'Revert to earlier revision', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '1', + 'customized' => '0', + 'p1' => '72', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '73', + 'plid' => '0', + 'link_path' => 'node/%/revisions/%/view', + 'router_path' => 'node/%/revisions/%/view', + 'link_title' => 'Revisions', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '1', + 'customized' => '0', + 'p1' => '73', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '74', + 'plid' => '13', + 'link_path' => 'admin/reports/updates', + 'router_path' => 'admin/reports/updates', + 'link_title' => 'Available updates', + 'options' => 'a:1:{s:10:"attributes";a:1:{s:5:"title";s:82:"Get a status report about available updates for your installed modules and themes.";}}', + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '10', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '13', + 'p3' => '74', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '75', + 'plid' => '11', + 'link_path' => 'node/add/page', + 'router_path' => 'node/add/page', + 'link_title' => 'Page', + 'options' => "a:1:{s:10:\"attributes\";a:1:{s:5:\"title\";s:296:\"A <em>page</em>, similar in form to a <em>story</em>, is a simple method for creating and displaying information that rarely changes, such as an \"About us\" section of a website. By default, a <em>page</em> entry does not allow visitor comments and is not featured on the site's initial home page.\";}}", + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '2', + 'customized' => '0', + 'p1' => '11', + 'p2' => '75', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '76', + 'plid' => '11', + 'link_path' => 'node/add/story', + 'router_path' => 'node/add/story', + 'link_title' => 'Story', + 'options' => "a:1:{s:10:\"attributes\";a:1:{s:5:\"title\";s:392:\"A <em>story</em>, similar in form to a <em>page</em>, is ideal for creating and displaying content that informs or engages website visitors. Press releases, site announcements, and informal blog-like entries may all be created with a <em>story</em> entry. By default, a <em>story</em> entry is automatically featured on the site's initial home page, and provides the ability to post comments.\";}}", + 'module' => 'system', + 'hidden' => '0', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '2', + 'customized' => '0', + 'p1' => '11', + 'p2' => '76', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '77', + 'plid' => '74', + 'link_path' => 'admin/reports/updates/check', + 'router_path' => 'admin/reports/updates/check', + 'link_title' => 'Manual update check', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '4', + 'customized' => '0', + 'p1' => '2', + 'p2' => '13', + 'p3' => '74', + 'p4' => '77', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '78', + 'plid' => '10', + 'link_path' => 'admin/content/node-type/page', + 'router_path' => 'admin/content/node-type/page', + 'link_title' => 'Page', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '10', + 'p3' => '78', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '79', + 'plid' => '10', + 'link_path' => 'admin/content/node-type/story', + 'router_path' => 'admin/content/node-type/story', + 'link_title' => 'Story', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '3', + 'customized' => '0', + 'p1' => '2', + 'p2' => '10', + 'p3' => '79', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '80', + 'plid' => '0', + 'link_path' => 'admin/content/node-type/page/delete', + 'router_path' => 'admin/content/node-type/page/delete', + 'link_title' => 'Delete', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '1', + 'customized' => '0', + 'p1' => '80', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->values(array( + 'menu_name' => 'navigation', + 'mlid' => '81', + 'plid' => '0', + 'link_path' => 'admin/content/node-type/story/delete', + 'router_path' => 'admin/content/node-type/story/delete', + 'link_title' => 'Delete', + 'options' => 'a:0:{}', + 'module' => 'system', + 'hidden' => '-1', + 'external' => '0', + 'has_children' => '0', + 'expanded' => '0', + 'weight' => '0', + 'depth' => '1', + 'customized' => '0', + 'p1' => '81', + 'p2' => '0', + 'p3' => '0', + 'p4' => '0', + 'p5' => '0', + 'p6' => '0', + 'p7' => '0', + 'p8' => '0', + 'p9' => '0', + 'updated' => '0', +)) +->execute(); + +db_create_table('menu_router', array( + 'fields' => array( + 'path' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'load_functions' => array( + 'type' => 'text', + 'not null' => TRUE, + ), + 'to_arg_functions' => array( + 'type' => 'text', + 'not null' => TRUE, + ), + 'access_callback' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'access_arguments' => array( + 'type' => 'text', + 'not null' => FALSE, + ), + 'page_callback' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'page_arguments' => array( + 'type' => 'text', + 'not null' => FALSE, + ), + 'fit' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'number_parts' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'small', + ), + 'tab_parent' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'tab_root' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'title' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'title_callback' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'title_arguments' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'type' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'block_callback' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'description' => array( + 'type' => 'text', + 'not null' => TRUE, + ), + 'position' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'weight' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'file' => array( + 'type' => 'text', + 'size' => 'medium', + ), + ), + 'indexes' => array( + 'fit' => array( + 'fit', + ), + 'tab_parent' => array( + 'tab_parent', + ), + 'tab_root_weight_title' => array( + array( + 'tab_root', + 64, + ), + 'weight', + 'title', + ), + ), + 'primary key' => array( + 'path', + ), + 'module' => 'system', + 'name' => 'menu_router', +)); +db_insert('menu_router')->fields(array( + 'path', + 'load_functions', + 'to_arg_functions', + 'access_callback', + 'access_arguments', + 'page_callback', + 'page_arguments', + 'fit', + 'number_parts', + 'tab_parent', + 'tab_root', + 'title', + 'title_callback', + 'title_arguments', + 'type', + 'block_callback', + 'description', + 'position', + 'weight', + 'file', +)) +->values(array( + 'path' => 'admin', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:27:"access administration pages";}', + 'page_callback' => 'system_main_admin_page', + 'page_arguments' => 'a:0:{}', + 'fit' => '1', + 'number_parts' => '1', + 'tab_parent' => '', + 'tab_root' => 'admin', + 'title' => 'Administer', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '9', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/build', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:27:"access administration pages";}', + 'page_callback' => 'system_admin_menu_block_page', + 'page_arguments' => 'a:0:{}', + 'fit' => '3', + 'number_parts' => '2', + 'tab_parent' => '', + 'tab_root' => 'admin/build', + 'title' => 'Site building', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Control how your site looks and feels.', + 'position' => 'right', + 'weight' => '-10', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/build/block', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:17:"administer blocks";}', + 'page_callback' => 'block_admin_display', + 'page_arguments' => 'a:0:{}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/build/block', + 'title' => 'Blocks', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => "Configure what block content appears in your site's sidebars and other regions.", + 'position' => '', + 'weight' => '0', + 'file' => 'modules/block/block.admin.inc', +)) +->values(array( + 'path' => 'admin/build/block/add', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:17:"administer blocks";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:20:"block_add_block_form";}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/build/block', + 'tab_root' => 'admin/build/block', + 'title' => 'Add block', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/block/block.admin.inc', +)) +->values(array( + 'path' => 'admin/build/block/configure', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:17:"administer blocks";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:21:"block_admin_configure";}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/build/block/configure', + 'title' => 'Configure block', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/block/block.admin.inc', +)) +->values(array( + 'path' => 'admin/build/block/delete', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:17:"administer blocks";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:16:"block_box_delete";}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/build/block/delete', + 'title' => 'Delete block', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/block/block.admin.inc', +)) +->values(array( + 'path' => 'admin/build/block/list', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:17:"administer blocks";}', + 'page_callback' => 'block_admin_display', + 'page_arguments' => 'a:0:{}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/build/block', + 'tab_root' => 'admin/build/block', + 'title' => 'List', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '-10', + 'file' => 'modules/block/block.admin.inc', +)) +->values(array( + 'path' => 'admin/build/block/list/bluemarine', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => '_block_themes_access', + 'access_arguments' => 'a:1:{i:0;O:8:"stdClass":12:{s:8:"filename";s:33:"themes/bluemarine/bluemarine.info";s:4:"name";s:10:"bluemarine";s:4:"type";s:5:"theme";s:5:"owner";s:45:"themes/engines/phptemplate/phptemplate.engine";s:6:"status";s:1:"0";s:8:"throttle";s:1:"0";s:9:"bootstrap";s:1:"0";s:14:"schema_version";s:2:"-1";s:6:"weight";s:1:"0";s:4:"info";a:13:{s:4:"name";s:10:"Bluemarine";s:11:"description";s:66:"Table-based multi-column theme with a marine and ash color scheme.";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:6:"engine";s:11:"phptemplate";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:7:"regions";a:5:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";s:7:"content";s:7:"Content";s:6:"header";s:6:"Header";s:6:"footer";s:6:"Footer";}s:8:"features";a:10:{i:0;s:20:"comment_user_picture";i:1;s:7:"favicon";i:2;s:7:"mission";i:3;s:4:"logo";i:4;s:4:"name";i:5;s:17:"node_user_picture";i:6;s:6:"search";i:7;s:6:"slogan";i:8;s:13:"primary_links";i:9;s:15:"secondary_links";}s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:9:"style.css";s:27:"themes/bluemarine/style.css";}}s:7:"scripts";a:1:{s:9:"script.js";s:27:"themes/bluemarine/script.js";}s:10:"screenshot";s:32:"themes/bluemarine/screenshot.png";s:3:"php";s:5:"4.3.5";}s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:9:"style.css";s:27:"themes/bluemarine/style.css";}}s:6:"engine";s:11:"phptemplate";}}', + 'page_callback' => 'block_admin_display', + 'page_arguments' => 'a:1:{i:0;s:10:"bluemarine";}', + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => 'admin/build/block/list', + 'tab_root' => 'admin/build/block', + 'title' => 'Bluemarine', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/block/block.admin.inc', +)) +->values(array( + 'path' => 'admin/build/block/list/chameleon', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => '_block_themes_access', + 'access_arguments' => 'a:1:{i:0;O:8:"stdClass":11:{s:8:"filename";s:31:"themes/chameleon/chameleon.info";s:4:"name";s:9:"chameleon";s:4:"type";s:5:"theme";s:5:"owner";s:32:"themes/chameleon/chameleon.theme";s:6:"status";s:1:"0";s:8:"throttle";s:1:"0";s:9:"bootstrap";s:1:"0";s:14:"schema_version";s:2:"-1";s:6:"weight";s:1:"0";s:4:"info";a:12:{s:4:"name";s:9:"Chameleon";s:11:"description";s:42:"Minimalist tabled theme with light colors.";s:7:"regions";a:2:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";}s:8:"features";a:4:{i:0;s:4:"logo";i:1;s:7:"favicon";i:2;s:4:"name";i:3;s:6:"slogan";}s:11:"stylesheets";a:1:{s:3:"all";a:2:{s:9:"style.css";s:26:"themes/chameleon/style.css";s:10:"common.css";s:27:"themes/chameleon/common.css";}}s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:7:"scripts";a:1:{s:9:"script.js";s:26:"themes/chameleon/script.js";}s:10:"screenshot";s:31:"themes/chameleon/screenshot.png";s:3:"php";s:5:"4.3.5";}s:11:"stylesheets";a:1:{s:3:"all";a:2:{s:9:"style.css";s:26:"themes/chameleon/style.css";s:10:"common.css";s:27:"themes/chameleon/common.css";}}}}', + 'page_callback' => 'block_admin_display', + 'page_arguments' => 'a:1:{i:0;s:9:"chameleon";}', + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => 'admin/build/block/list', + 'tab_root' => 'admin/build/block', + 'title' => 'Chameleon', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/block/block.admin.inc', +)) +->values(array( + 'path' => 'admin/build/block/list/garland', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => '_block_themes_access', + 'access_arguments' => 'a:1:{i:0;O:8:"stdClass":12:{s:8:"filename";s:27:"themes/garland/garland.info";s:4:"name";s:7:"garland";s:4:"type";s:5:"theme";s:5:"owner";s:45:"themes/engines/phptemplate/phptemplate.engine";s:6:"status";s:1:"1";s:8:"throttle";s:1:"0";s:9:"bootstrap";s:1:"0";s:14:"schema_version";s:2:"-1";s:6:"weight";s:1:"0";s:4:"info";a:13:{s:4:"name";s:7:"Garland";s:11:"description";s:66:"Tableless, recolorable, multi-column, fluid width theme (default).";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:6:"engine";s:11:"phptemplate";s:11:"stylesheets";a:2:{s:3:"all";a:1:{s:9:"style.css";s:24:"themes/garland/style.css";}s:5:"print";a:1:{s:9:"print.css";s:24:"themes/garland/print.css";}}s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:7:"regions";a:5:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";s:7:"content";s:7:"Content";s:6:"header";s:6:"Header";s:6:"footer";s:6:"Footer";}s:8:"features";a:10:{i:0;s:20:"comment_user_picture";i:1;s:7:"favicon";i:2;s:7:"mission";i:3;s:4:"logo";i:4;s:4:"name";i:5;s:17:"node_user_picture";i:6;s:6:"search";i:7;s:6:"slogan";i:8;s:13:"primary_links";i:9;s:15:"secondary_links";}s:7:"scripts";a:1:{s:9:"script.js";s:24:"themes/garland/script.js";}s:10:"screenshot";s:29:"themes/garland/screenshot.png";s:3:"php";s:5:"4.3.5";}s:11:"stylesheets";a:2:{s:3:"all";a:1:{s:9:"style.css";s:24:"themes/garland/style.css";}s:5:"print";a:1:{s:9:"print.css";s:24:"themes/garland/print.css";}}s:6:"engine";s:11:"phptemplate";}}', + 'page_callback' => 'block_admin_display', + 'page_arguments' => 'a:1:{i:0;s:7:"garland";}', + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => 'admin/build/block/list', + 'tab_root' => 'admin/build/block', + 'title' => 'Garland', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '-10', + 'file' => 'modules/block/block.admin.inc', +)) +->values(array( + 'path' => 'admin/build/block/list/js', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:17:"administer blocks";}', + 'page_callback' => 'block_admin_display_js', + 'page_arguments' => 'a:0:{}', + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => '', + 'tab_root' => 'admin/build/block/list/js', + 'title' => 'JavaScript List Form', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/block/block.admin.inc', +)) +->values(array( + 'path' => 'admin/build/block/list/marvin', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => '_block_themes_access', + 'access_arguments' => 'a:1:{i:0;O:8:"stdClass":12:{s:8:"filename";s:35:"themes/chameleon/marvin/marvin.info";s:4:"name";s:6:"marvin";s:4:"type";s:5:"theme";s:5:"owner";s:0:"";s:6:"status";s:1:"0";s:8:"throttle";s:1:"0";s:9:"bootstrap";s:1:"0";s:14:"schema_version";s:2:"-1";s:6:"weight";s:1:"0";s:4:"info";a:13:{s:4:"name";s:6:"Marvin";s:11:"description";s:31:"Boxy tabled theme in all grays.";s:7:"regions";a:2:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";}s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:10:"base theme";s:9:"chameleon";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:8:"features";a:10:{i:0;s:20:"comment_user_picture";i:1;s:7:"favicon";i:2;s:7:"mission";i:3;s:4:"logo";i:4;s:4:"name";i:5;s:17:"node_user_picture";i:6;s:6:"search";i:7;s:6:"slogan";i:8;s:13:"primary_links";i:9;s:15:"secondary_links";}s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:9:"style.css";s:33:"themes/chameleon/marvin/style.css";}}s:7:"scripts";a:1:{s:9:"script.js";s:33:"themes/chameleon/marvin/script.js";}s:10:"screenshot";s:38:"themes/chameleon/marvin/screenshot.png";s:3:"php";s:5:"4.3.5";}s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:9:"style.css";s:33:"themes/chameleon/marvin/style.css";}}s:10:"base_theme";s:9:"chameleon";}}', + 'page_callback' => 'block_admin_display', + 'page_arguments' => 'a:1:{i:0;s:6:"marvin";}', + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => 'admin/build/block/list', + 'tab_root' => 'admin/build/block', + 'title' => 'Marvin', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/block/block.admin.inc', +)) +->values(array( + 'path' => 'admin/build/block/list/minnelli', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => '_block_themes_access', + 'access_arguments' => 'a:1:{i:0;O:8:"stdClass":13:{s:8:"filename";s:37:"themes/garland/minnelli/minnelli.info";s:4:"name";s:8:"minnelli";s:4:"type";s:5:"theme";s:5:"owner";s:45:"themes/engines/phptemplate/phptemplate.engine";s:6:"status";s:1:"0";s:8:"throttle";s:1:"0";s:9:"bootstrap";s:1:"0";s:14:"schema_version";s:2:"-1";s:6:"weight";s:1:"0";s:4:"info";a:14:{s:4:"name";s:8:"Minnelli";s:11:"description";s:56:"Tableless, recolorable, multi-column, fixed width theme.";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:10:"base theme";s:7:"garland";s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:12:"minnelli.css";s:36:"themes/garland/minnelli/minnelli.css";}}s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:7:"regions";a:5:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";s:7:"content";s:7:"Content";s:6:"header";s:6:"Header";s:6:"footer";s:6:"Footer";}s:8:"features";a:10:{i:0;s:20:"comment_user_picture";i:1;s:7:"favicon";i:2;s:7:"mission";i:3;s:4:"logo";i:4;s:4:"name";i:5;s:17:"node_user_picture";i:6;s:6:"search";i:7;s:6:"slogan";i:8;s:13:"primary_links";i:9;s:15:"secondary_links";}s:7:"scripts";a:1:{s:9:"script.js";s:33:"themes/garland/minnelli/script.js";}s:10:"screenshot";s:38:"themes/garland/minnelli/screenshot.png";s:3:"php";s:5:"4.3.5";s:6:"engine";s:11:"phptemplate";}s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:12:"minnelli.css";s:36:"themes/garland/minnelli/minnelli.css";}}s:6:"engine";s:11:"phptemplate";s:10:"base_theme";s:7:"garland";}}', + 'page_callback' => 'block_admin_display', + 'page_arguments' => 'a:1:{i:0;s:8:"minnelli";}', + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => 'admin/build/block/list', + 'tab_root' => 'admin/build/block', + 'title' => 'Minnelli', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/block/block.admin.inc', +)) +->values(array( + 'path' => 'admin/build/block/list/pushbutton', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => '_block_themes_access', + 'access_arguments' => 'a:1:{i:0;O:8:"stdClass":12:{s:8:"filename";s:33:"themes/pushbutton/pushbutton.info";s:4:"name";s:10:"pushbutton";s:4:"type";s:5:"theme";s:5:"owner";s:45:"themes/engines/phptemplate/phptemplate.engine";s:6:"status";s:1:"0";s:8:"throttle";s:1:"0";s:9:"bootstrap";s:1:"0";s:14:"schema_version";s:2:"-1";s:6:"weight";s:1:"0";s:4:"info";a:13:{s:4:"name";s:10:"Pushbutton";s:11:"description";s:52:"Tabled, multi-column theme in blue and orange tones.";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:6:"engine";s:11:"phptemplate";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:7:"regions";a:5:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";s:7:"content";s:7:"Content";s:6:"header";s:6:"Header";s:6:"footer";s:6:"Footer";}s:8:"features";a:10:{i:0;s:20:"comment_user_picture";i:1;s:7:"favicon";i:2;s:7:"mission";i:3;s:4:"logo";i:4;s:4:"name";i:5;s:17:"node_user_picture";i:6;s:6:"search";i:7;s:6:"slogan";i:8;s:13:"primary_links";i:9;s:15:"secondary_links";}s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:9:"style.css";s:27:"themes/pushbutton/style.css";}}s:7:"scripts";a:1:{s:9:"script.js";s:27:"themes/pushbutton/script.js";}s:10:"screenshot";s:32:"themes/pushbutton/screenshot.png";s:3:"php";s:5:"4.3.5";}s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:9:"style.css";s:27:"themes/pushbutton/style.css";}}s:6:"engine";s:11:"phptemplate";}}', + 'page_callback' => 'block_admin_display', + 'page_arguments' => 'a:1:{i:0;s:10:"pushbutton";}', + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => 'admin/build/block/list', + 'tab_root' => 'admin/build/block', + 'title' => 'Pushbutton', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/block/block.admin.inc', +)) +->values(array( + 'path' => 'admin/build/modules', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:14:"system_modules";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/build/modules', + 'title' => 'Modules', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Enable or disable add-on modules for your site.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/build/modules/list', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:14:"system_modules";}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/build/modules', + 'tab_root' => 'admin/build/modules', + 'title' => 'List', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/build/modules/list/confirm', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:14:"system_modules";}', + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => '', + 'tab_root' => 'admin/build/modules/list/confirm', + 'title' => 'List', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/build/modules/uninstall', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:24:"system_modules_uninstall";}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/build/modules', + 'tab_root' => 'admin/build/modules', + 'title' => 'Uninstall', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/build/modules/uninstall/confirm', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:24:"system_modules_uninstall";}', + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => '', + 'tab_root' => 'admin/build/modules/uninstall/confirm', + 'title' => 'Uninstall', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/build/themes', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:2:{i:0;s:18:"system_themes_form";i:1;N;}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/build/themes', + 'title' => 'Themes', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Change which theme your site uses or allows users to set.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/build/themes/select', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:2:{i:0;s:18:"system_themes_form";i:1;N;}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/build/themes', + 'tab_root' => 'admin/build/themes', + 'title' => 'List', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => 'Select the default theme.', + 'position' => '', + 'weight' => '-1', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/build/themes/settings', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:21:"system_theme_settings";}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/build/themes', + 'tab_root' => 'admin/build/themes', + 'title' => 'Configure', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/build/themes/settings/bluemarine', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => '_system_themes_access', + 'access_arguments' => 'a:1:{i:0;O:8:"stdClass":12:{s:8:"filename";s:33:"themes/bluemarine/bluemarine.info";s:4:"name";s:10:"bluemarine";s:4:"type";s:5:"theme";s:5:"owner";s:45:"themes/engines/phptemplate/phptemplate.engine";s:6:"status";s:1:"0";s:8:"throttle";s:1:"0";s:9:"bootstrap";s:1:"0";s:14:"schema_version";s:2:"-1";s:6:"weight";s:1:"0";s:4:"info";a:13:{s:4:"name";s:10:"Bluemarine";s:11:"description";s:66:"Table-based multi-column theme with a marine and ash color scheme.";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:6:"engine";s:11:"phptemplate";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:7:"regions";a:5:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";s:7:"content";s:7:"Content";s:6:"header";s:6:"Header";s:6:"footer";s:6:"Footer";}s:8:"features";a:10:{i:0;s:20:"comment_user_picture";i:1;s:7:"favicon";i:2;s:7:"mission";i:3;s:4:"logo";i:4;s:4:"name";i:5;s:17:"node_user_picture";i:6;s:6:"search";i:7;s:6:"slogan";i:8;s:13:"primary_links";i:9;s:15:"secondary_links";}s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:9:"style.css";s:27:"themes/bluemarine/style.css";}}s:7:"scripts";a:1:{s:9:"script.js";s:27:"themes/bluemarine/script.js";}s:10:"screenshot";s:32:"themes/bluemarine/screenshot.png";s:3:"php";s:5:"4.3.5";}s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:9:"style.css";s:27:"themes/bluemarine/style.css";}}s:6:"engine";s:11:"phptemplate";}}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:2:{i:0;s:21:"system_theme_settings";i:1;s:10:"bluemarine";}', + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => 'admin/build/themes/settings', + 'tab_root' => 'admin/build/themes', + 'title' => 'Bluemarine', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/build/themes/settings/chameleon', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => '_system_themes_access', + 'access_arguments' => 'a:1:{i:0;O:8:"stdClass":11:{s:8:"filename";s:31:"themes/chameleon/chameleon.info";s:4:"name";s:9:"chameleon";s:4:"type";s:5:"theme";s:5:"owner";s:32:"themes/chameleon/chameleon.theme";s:6:"status";s:1:"0";s:8:"throttle";s:1:"0";s:9:"bootstrap";s:1:"0";s:14:"schema_version";s:2:"-1";s:6:"weight";s:1:"0";s:4:"info";a:12:{s:4:"name";s:9:"Chameleon";s:11:"description";s:42:"Minimalist tabled theme with light colors.";s:7:"regions";a:2:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";}s:8:"features";a:4:{i:0;s:4:"logo";i:1;s:7:"favicon";i:2;s:4:"name";i:3;s:6:"slogan";}s:11:"stylesheets";a:1:{s:3:"all";a:2:{s:9:"style.css";s:26:"themes/chameleon/style.css";s:10:"common.css";s:27:"themes/chameleon/common.css";}}s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:7:"scripts";a:1:{s:9:"script.js";s:26:"themes/chameleon/script.js";}s:10:"screenshot";s:31:"themes/chameleon/screenshot.png";s:3:"php";s:5:"4.3.5";}s:11:"stylesheets";a:1:{s:3:"all";a:2:{s:9:"style.css";s:26:"themes/chameleon/style.css";s:10:"common.css";s:27:"themes/chameleon/common.css";}}}}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:2:{i:0;s:21:"system_theme_settings";i:1;s:9:"chameleon";}', + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => 'admin/build/themes/settings', + 'tab_root' => 'admin/build/themes', + 'title' => 'Chameleon', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/build/themes/settings/garland', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => '_system_themes_access', + 'access_arguments' => 'a:1:{i:0;O:8:"stdClass":12:{s:8:"filename";s:27:"themes/garland/garland.info";s:4:"name";s:7:"garland";s:4:"type";s:5:"theme";s:5:"owner";s:45:"themes/engines/phptemplate/phptemplate.engine";s:6:"status";s:1:"1";s:8:"throttle";s:1:"0";s:9:"bootstrap";s:1:"0";s:14:"schema_version";s:2:"-1";s:6:"weight";s:1:"0";s:4:"info";a:13:{s:4:"name";s:7:"Garland";s:11:"description";s:66:"Tableless, recolorable, multi-column, fluid width theme (default).";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:6:"engine";s:11:"phptemplate";s:11:"stylesheets";a:2:{s:3:"all";a:1:{s:9:"style.css";s:24:"themes/garland/style.css";}s:5:"print";a:1:{s:9:"print.css";s:24:"themes/garland/print.css";}}s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:7:"regions";a:5:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";s:7:"content";s:7:"Content";s:6:"header";s:6:"Header";s:6:"footer";s:6:"Footer";}s:8:"features";a:10:{i:0;s:20:"comment_user_picture";i:1;s:7:"favicon";i:2;s:7:"mission";i:3;s:4:"logo";i:4;s:4:"name";i:5;s:17:"node_user_picture";i:6;s:6:"search";i:7;s:6:"slogan";i:8;s:13:"primary_links";i:9;s:15:"secondary_links";}s:7:"scripts";a:1:{s:9:"script.js";s:24:"themes/garland/script.js";}s:10:"screenshot";s:29:"themes/garland/screenshot.png";s:3:"php";s:5:"4.3.5";}s:11:"stylesheets";a:2:{s:3:"all";a:1:{s:9:"style.css";s:24:"themes/garland/style.css";}s:5:"print";a:1:{s:9:"print.css";s:24:"themes/garland/print.css";}}s:6:"engine";s:11:"phptemplate";}}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:2:{i:0;s:21:"system_theme_settings";i:1;s:7:"garland";}', + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => 'admin/build/themes/settings', + 'tab_root' => 'admin/build/themes', + 'title' => 'Garland', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/build/themes/settings/global', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:21:"system_theme_settings";}', + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => 'admin/build/themes/settings', + 'tab_root' => 'admin/build/themes', + 'title' => 'Global settings', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '-1', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/build/themes/settings/marvin', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => '_system_themes_access', + 'access_arguments' => 'a:1:{i:0;O:8:"stdClass":12:{s:8:"filename";s:35:"themes/chameleon/marvin/marvin.info";s:4:"name";s:6:"marvin";s:4:"type";s:5:"theme";s:5:"owner";s:0:"";s:6:"status";s:1:"0";s:8:"throttle";s:1:"0";s:9:"bootstrap";s:1:"0";s:14:"schema_version";s:2:"-1";s:6:"weight";s:1:"0";s:4:"info";a:13:{s:4:"name";s:6:"Marvin";s:11:"description";s:31:"Boxy tabled theme in all grays.";s:7:"regions";a:2:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";}s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:10:"base theme";s:9:"chameleon";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:8:"features";a:10:{i:0;s:20:"comment_user_picture";i:1;s:7:"favicon";i:2;s:7:"mission";i:3;s:4:"logo";i:4;s:4:"name";i:5;s:17:"node_user_picture";i:6;s:6:"search";i:7;s:6:"slogan";i:8;s:13:"primary_links";i:9;s:15:"secondary_links";}s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:9:"style.css";s:33:"themes/chameleon/marvin/style.css";}}s:7:"scripts";a:1:{s:9:"script.js";s:33:"themes/chameleon/marvin/script.js";}s:10:"screenshot";s:38:"themes/chameleon/marvin/screenshot.png";s:3:"php";s:5:"4.3.5";}s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:9:"style.css";s:33:"themes/chameleon/marvin/style.css";}}s:10:"base_theme";s:9:"chameleon";}}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:2:{i:0;s:21:"system_theme_settings";i:1;s:6:"marvin";}', + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => 'admin/build/themes/settings', + 'tab_root' => 'admin/build/themes', + 'title' => 'Marvin', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/build/themes/settings/minnelli', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => '_system_themes_access', + 'access_arguments' => 'a:1:{i:0;O:8:"stdClass":13:{s:8:"filename";s:37:"themes/garland/minnelli/minnelli.info";s:4:"name";s:8:"minnelli";s:4:"type";s:5:"theme";s:5:"owner";s:45:"themes/engines/phptemplate/phptemplate.engine";s:6:"status";s:1:"0";s:8:"throttle";s:1:"0";s:9:"bootstrap";s:1:"0";s:14:"schema_version";s:2:"-1";s:6:"weight";s:1:"0";s:4:"info";a:14:{s:4:"name";s:8:"Minnelli";s:11:"description";s:56:"Tableless, recolorable, multi-column, fixed width theme.";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:10:"base theme";s:7:"garland";s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:12:"minnelli.css";s:36:"themes/garland/minnelli/minnelli.css";}}s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:7:"regions";a:5:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";s:7:"content";s:7:"Content";s:6:"header";s:6:"Header";s:6:"footer";s:6:"Footer";}s:8:"features";a:10:{i:0;s:20:"comment_user_picture";i:1;s:7:"favicon";i:2;s:7:"mission";i:3;s:4:"logo";i:4;s:4:"name";i:5;s:17:"node_user_picture";i:6;s:6:"search";i:7;s:6:"slogan";i:8;s:13:"primary_links";i:9;s:15:"secondary_links";}s:7:"scripts";a:1:{s:9:"script.js";s:33:"themes/garland/minnelli/script.js";}s:10:"screenshot";s:38:"themes/garland/minnelli/screenshot.png";s:3:"php";s:5:"4.3.5";s:6:"engine";s:11:"phptemplate";}s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:12:"minnelli.css";s:36:"themes/garland/minnelli/minnelli.css";}}s:6:"engine";s:11:"phptemplate";s:10:"base_theme";s:7:"garland";}}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:2:{i:0;s:21:"system_theme_settings";i:1;s:8:"minnelli";}', + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => 'admin/build/themes/settings', + 'tab_root' => 'admin/build/themes', + 'title' => 'Minnelli', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/build/themes/settings/pushbutton', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => '_system_themes_access', + 'access_arguments' => 'a:1:{i:0;O:8:"stdClass":12:{s:8:"filename";s:33:"themes/pushbutton/pushbutton.info";s:4:"name";s:10:"pushbutton";s:4:"type";s:5:"theme";s:5:"owner";s:45:"themes/engines/phptemplate/phptemplate.engine";s:6:"status";s:1:"0";s:8:"throttle";s:1:"0";s:9:"bootstrap";s:1:"0";s:14:"schema_version";s:2:"-1";s:6:"weight";s:1:"0";s:4:"info";a:13:{s:4:"name";s:10:"Pushbutton";s:11:"description";s:52:"Tabled, multi-column theme in blue and orange tones.";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:6:"engine";s:11:"phptemplate";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:7:"regions";a:5:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";s:7:"content";s:7:"Content";s:6:"header";s:6:"Header";s:6:"footer";s:6:"Footer";}s:8:"features";a:10:{i:0;s:20:"comment_user_picture";i:1;s:7:"favicon";i:2;s:7:"mission";i:3;s:4:"logo";i:4;s:4:"name";i:5;s:17:"node_user_picture";i:6;s:6:"search";i:7;s:6:"slogan";i:8;s:13:"primary_links";i:9;s:15:"secondary_links";}s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:9:"style.css";s:27:"themes/pushbutton/style.css";}}s:7:"scripts";a:1:{s:9:"script.js";s:27:"themes/pushbutton/script.js";}s:10:"screenshot";s:32:"themes/pushbutton/screenshot.png";s:3:"php";s:5:"4.3.5";}s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:9:"style.css";s:27:"themes/pushbutton/style.css";}}s:6:"engine";s:11:"phptemplate";}}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:2:{i:0;s:21:"system_theme_settings";i:1;s:10:"pushbutton";}', + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => 'admin/build/themes/settings', + 'tab_root' => 'admin/build/themes', + 'title' => 'Pushbutton', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/by-module', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:27:"access administration pages";}', + 'page_callback' => 'system_admin_by_module', + 'page_arguments' => 'a:0:{}', + 'fit' => '3', + 'number_parts' => '2', + 'tab_parent' => 'admin', + 'tab_root' => 'admin', + 'title' => 'By module', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '2', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/by-task', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:27:"access administration pages";}', + 'page_callback' => 'system_main_admin_page', + 'page_arguments' => 'a:0:{}', + 'fit' => '3', + 'number_parts' => '2', + 'tab_parent' => 'admin', + 'tab_root' => 'admin', + 'title' => 'By task', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/compact', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:27:"access administration pages";}', + 'page_callback' => 'system_admin_compact_page', + 'page_arguments' => 'a:0:{}', + 'fit' => '3', + 'number_parts' => '2', + 'tab_parent' => '', + 'tab_root' => 'admin/compact', + 'title' => 'Compact mode', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/content', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:27:"access administration pages";}', + 'page_callback' => 'system_admin_menu_block_page', + 'page_arguments' => 'a:0:{}', + 'fit' => '3', + 'number_parts' => '2', + 'tab_parent' => '', + 'tab_root' => 'admin/content', + 'title' => 'Content management', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => "Manage your site's content.", + 'position' => 'left', + 'weight' => '-10', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/content/node', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:16:"administer nodes";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:18:"node_admin_content";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/content/node', + 'title' => 'Content', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => "View, edit, and delete your site's content.", + 'position' => '', + 'weight' => '0', + 'file' => 'modules/node/node.admin.inc', +)) +->values(array( + 'path' => 'admin/content/node-settings', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:16:"administer nodes";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:14:"node_configure";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/content/node-settings', + 'title' => 'Post settings', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Control posting behavior, such as teaser length, requiring previews before posting, and the number of posts on the front page.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/node/node.admin.inc', +)) +->values(array( + 'path' => 'admin/content/node-settings/rebuild', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:27:"access administration pages";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:30:"node_configure_rebuild_confirm";}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/content/node-settings/rebuild', + 'title' => 'Rebuild permissions', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/node/node.admin.inc', +)) +->values(array( + 'path' => 'admin/content/node-type/page', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:24:"administer content types";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => "a:2:{i:0;s:14:\"node_type_form\";i:1;O:8:\"stdClass\":14:{s:4:\"type\";s:4:\"page\";s:4:\"name\";s:4:\"Page\";s:6:\"module\";s:4:\"node\";s:11:\"description\";s:296:\"A <em>page</em>, similar in form to a <em>story</em>, is a simple method for creating and displaying information that rarely changes, such as an \"About us\" section of a website. By default, a <em>page</em> entry does not allow visitor comments and is not featured on the site's initial home page.\";s:4:\"help\";s:0:\"\";s:9:\"has_title\";s:1:\"1\";s:11:\"title_label\";s:5:\"Title\";s:8:\"has_body\";s:1:\"1\";s:10:\"body_label\";s:4:\"Body\";s:14:\"min_word_count\";s:1:\"0\";s:6:\"custom\";s:1:\"1\";s:8:\"modified\";s:1:\"1\";s:6:\"locked\";s:1:\"0\";s:9:\"orig_type\";s:4:\"page\";}}", + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/content/node-type/page', + 'title' => 'Page', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/node/content_types.inc', +)) +->values(array( + 'path' => 'admin/content/node-type/page/delete', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:24:"administer content types";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => "a:2:{i:0;s:24:\"node_type_delete_confirm\";i:1;O:8:\"stdClass\":14:{s:4:\"type\";s:4:\"page\";s:4:\"name\";s:4:\"Page\";s:6:\"module\";s:4:\"node\";s:11:\"description\";s:296:\"A <em>page</em>, similar in form to a <em>story</em>, is a simple method for creating and displaying information that rarely changes, such as an \"About us\" section of a website. By default, a <em>page</em> entry does not allow visitor comments and is not featured on the site's initial home page.\";s:4:\"help\";s:0:\"\";s:9:\"has_title\";s:1:\"1\";s:11:\"title_label\";s:5:\"Title\";s:8:\"has_body\";s:1:\"1\";s:10:\"body_label\";s:4:\"Body\";s:14:\"min_word_count\";s:1:\"0\";s:6:\"custom\";s:1:\"1\";s:8:\"modified\";s:1:\"1\";s:6:\"locked\";s:1:\"0\";s:9:\"orig_type\";s:4:\"page\";}}", + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => '', + 'tab_root' => 'admin/content/node-type/page/delete', + 'title' => 'Delete', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/node/content_types.inc', +)) +->values(array( + 'path' => 'admin/content/node-type/page/edit', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:24:"administer content types";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => "a:2:{i:0;s:14:\"node_type_form\";i:1;O:8:\"stdClass\":14:{s:4:\"type\";s:4:\"page\";s:4:\"name\";s:4:\"Page\";s:6:\"module\";s:4:\"node\";s:11:\"description\";s:296:\"A <em>page</em>, similar in form to a <em>story</em>, is a simple method for creating and displaying information that rarely changes, such as an \"About us\" section of a website. By default, a <em>page</em> entry does not allow visitor comments and is not featured on the site's initial home page.\";s:4:\"help\";s:0:\"\";s:9:\"has_title\";s:1:\"1\";s:11:\"title_label\";s:5:\"Title\";s:8:\"has_body\";s:1:\"1\";s:10:\"body_label\";s:4:\"Body\";s:14:\"min_word_count\";s:1:\"0\";s:6:\"custom\";s:1:\"1\";s:8:\"modified\";s:1:\"1\";s:6:\"locked\";s:1:\"0\";s:9:\"orig_type\";s:4:\"page\";}}", + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => 'admin/content/node-type/page', + 'tab_root' => 'admin/content/node-type/page', + 'title' => 'Edit', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/node/content_types.inc', +)) +->values(array( + 'path' => 'admin/content/node-type/story', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:24:"administer content types";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => "a:2:{i:0;s:14:\"node_type_form\";i:1;O:8:\"stdClass\":14:{s:4:\"type\";s:5:\"story\";s:4:\"name\";s:5:\"Story\";s:6:\"module\";s:4:\"node\";s:11:\"description\";s:392:\"A <em>story</em>, similar in form to a <em>page</em>, is ideal for creating and displaying content that informs or engages website visitors. Press releases, site announcements, and informal blog-like entries may all be created with a <em>story</em> entry. By default, a <em>story</em> entry is automatically featured on the site's initial home page, and provides the ability to post comments.\";s:4:\"help\";s:0:\"\";s:9:\"has_title\";s:1:\"1\";s:11:\"title_label\";s:5:\"Title\";s:8:\"has_body\";s:1:\"1\";s:10:\"body_label\";s:4:\"Body\";s:14:\"min_word_count\";s:1:\"0\";s:6:\"custom\";s:1:\"1\";s:8:\"modified\";s:1:\"1\";s:6:\"locked\";s:1:\"0\";s:9:\"orig_type\";s:5:\"story\";}}", + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/content/node-type/story', + 'title' => 'Story', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/node/content_types.inc', +)) +->values(array( + 'path' => 'admin/content/node-type/story/delete', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:24:"administer content types";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => "a:2:{i:0;s:24:\"node_type_delete_confirm\";i:1;O:8:\"stdClass\":14:{s:4:\"type\";s:5:\"story\";s:4:\"name\";s:5:\"Story\";s:6:\"module\";s:4:\"node\";s:11:\"description\";s:392:\"A <em>story</em>, similar in form to a <em>page</em>, is ideal for creating and displaying content that informs or engages website visitors. Press releases, site announcements, and informal blog-like entries may all be created with a <em>story</em> entry. By default, a <em>story</em> entry is automatically featured on the site's initial home page, and provides the ability to post comments.\";s:4:\"help\";s:0:\"\";s:9:\"has_title\";s:1:\"1\";s:11:\"title_label\";s:5:\"Title\";s:8:\"has_body\";s:1:\"1\";s:10:\"body_label\";s:4:\"Body\";s:14:\"min_word_count\";s:1:\"0\";s:6:\"custom\";s:1:\"1\";s:8:\"modified\";s:1:\"1\";s:6:\"locked\";s:1:\"0\";s:9:\"orig_type\";s:5:\"story\";}}", + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => '', + 'tab_root' => 'admin/content/node-type/story/delete', + 'title' => 'Delete', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/node/content_types.inc', +)) +->values(array( + 'path' => 'admin/content/node-type/story/edit', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:24:"administer content types";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => "a:2:{i:0;s:14:\"node_type_form\";i:1;O:8:\"stdClass\":14:{s:4:\"type\";s:5:\"story\";s:4:\"name\";s:5:\"Story\";s:6:\"module\";s:4:\"node\";s:11:\"description\";s:392:\"A <em>story</em>, similar in form to a <em>page</em>, is ideal for creating and displaying content that informs or engages website visitors. Press releases, site announcements, and informal blog-like entries may all be created with a <em>story</em> entry. By default, a <em>story</em> entry is automatically featured on the site's initial home page, and provides the ability to post comments.\";s:4:\"help\";s:0:\"\";s:9:\"has_title\";s:1:\"1\";s:11:\"title_label\";s:5:\"Title\";s:8:\"has_body\";s:1:\"1\";s:10:\"body_label\";s:4:\"Body\";s:14:\"min_word_count\";s:1:\"0\";s:6:\"custom\";s:1:\"1\";s:8:\"modified\";s:1:\"1\";s:6:\"locked\";s:1:\"0\";s:9:\"orig_type\";s:5:\"story\";}}", + 'fit' => '31', + 'number_parts' => '5', + 'tab_parent' => 'admin/content/node-type/story', + 'tab_root' => 'admin/content/node-type/story', + 'title' => 'Edit', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/node/content_types.inc', +)) +->values(array( + 'path' => 'admin/content/node/overview', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:16:"administer nodes";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:18:"node_admin_content";}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/content/node', + 'tab_root' => 'admin/content/node', + 'title' => 'List', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '-10', + 'file' => 'modules/node/node.admin.inc', +)) +->values(array( + 'path' => 'admin/content/rss-publishing', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:25:"system_rss_feeds_settings";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/content/rss-publishing', + 'title' => 'RSS publishing', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Configure the number of items per feed and whether feeds should be titles/teasers/full-text.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/content/types', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:24:"administer content types";}', + 'page_callback' => 'node_overview_types', + 'page_arguments' => 'a:0:{}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/content/types', + 'title' => 'Content types', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Manage posts by content type, including default status, front page promotion, etc.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/node/content_types.inc', +)) +->values(array( + 'path' => 'admin/content/types/add', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:24:"administer content types";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:14:"node_type_form";}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/content/types', + 'tab_root' => 'admin/content/types', + 'title' => 'Add content type', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/node/content_types.inc', +)) +->values(array( + 'path' => 'admin/content/types/list', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:24:"administer content types";}', + 'page_callback' => 'node_overview_types', + 'page_arguments' => 'a:0:{}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/content/types', + 'tab_root' => 'admin/content/types', + 'title' => 'List', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '-10', + 'file' => 'modules/node/content_types.inc', +)) +->values(array( + 'path' => 'admin/reports', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:19:"access site reports";}', + 'page_callback' => 'system_admin_menu_block_page', + 'page_arguments' => 'a:0:{}', + 'fit' => '3', + 'number_parts' => '2', + 'tab_parent' => '', + 'tab_root' => 'admin/reports', + 'title' => 'Reports', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'View reports from system logs and other status information.', + 'position' => 'left', + 'weight' => '5', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/reports/access-denied', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:19:"access site reports";}', + 'page_callback' => 'dblog_top', + 'page_arguments' => 'a:1:{i:0;s:13:"access denied";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/reports/access-denied', + 'title' => "Top 'access denied' errors", + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => "View 'access denied' errors (403s).", + 'position' => '', + 'weight' => '0', + 'file' => 'modules/dblog/dblog.admin.inc', +)) +->values(array( + 'path' => 'admin/reports/dblog', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:19:"access site reports";}', + 'page_callback' => 'dblog_overview', + 'page_arguments' => 'a:0:{}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/reports/dblog', + 'title' => 'Recent log entries', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'View events that have recently been logged.', + 'position' => '', + 'weight' => '-1', + 'file' => 'modules/dblog/dblog.admin.inc', +)) +->values(array( + 'path' => 'admin/reports/event/%', + 'load_functions' => 'a:1:{i:3;N;}', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:19:"access site reports";}', + 'page_callback' => 'dblog_event', + 'page_arguments' => 'a:1:{i:0;i:3;}', + 'fit' => '14', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/reports/event/%', + 'title' => 'Details', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/dblog/dblog.admin.inc', +)) +->values(array( + 'path' => 'admin/reports/page-not-found', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:19:"access site reports";}', + 'page_callback' => 'dblog_top', + 'page_arguments' => 'a:1:{i:0;s:14:"page not found";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/reports/page-not-found', + 'title' => "Top 'page not found' errors", + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => "View 'page not found' errors (404s).", + 'position' => '', + 'weight' => '0', + 'file' => 'modules/dblog/dblog.admin.inc', +)) +->values(array( + 'path' => 'admin/reports/status', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'system_status', + 'page_arguments' => 'a:0:{}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/reports/status', + 'title' => 'Status report', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => "Get a status report about your site's operation and any detected problems.", + 'position' => '', + 'weight' => '10', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/reports/status/php', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'system_php', + 'page_arguments' => 'a:0:{}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/reports/status/php', + 'title' => 'PHP', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/reports/status/run-cron', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'system_run_cron', + 'page_arguments' => 'a:0:{}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/reports/status/run-cron', + 'title' => 'Run cron', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/reports/status/sql', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'system_sql', + 'page_arguments' => 'a:0:{}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/reports/status/sql', + 'title' => 'SQL', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/reports/updates', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'update_status', + 'page_arguments' => 'a:0:{}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/reports/updates', + 'title' => 'Available updates', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Get a status report about available updates for your installed modules and themes.', + 'position' => '', + 'weight' => '10', + 'file' => 'modules/update/update.report.inc', +)) +->values(array( + 'path' => 'admin/reports/updates/check', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'update_manual_status', + 'page_arguments' => 'a:0:{}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/reports/updates/check', + 'title' => 'Manual update check', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/update/update.fetch.inc', +)) +->values(array( + 'path' => 'admin/reports/updates/list', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'update_status', + 'page_arguments' => 'a:0:{}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/reports/updates', + 'tab_root' => 'admin/reports/updates', + 'title' => 'List', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/update/update.report.inc', +)) +->values(array( + 'path' => 'admin/reports/updates/settings', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:15:"update_settings";}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/reports/updates', + 'tab_root' => 'admin/reports/updates', + 'title' => 'Settings', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/update/update.settings.inc', +)) +->values(array( + 'path' => 'admin/settings', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:27:"access administration pages";}', + 'page_callback' => 'system_settings_overview', + 'page_arguments' => 'a:0:{}', + 'fit' => '3', + 'number_parts' => '2', + 'tab_parent' => '', + 'tab_root' => 'admin/settings', + 'title' => 'Site configuration', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Adjust basic site configuration options.', + 'position' => 'right', + 'weight' => '-5', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/actions', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:18:"administer actions";}', + 'page_callback' => 'system_actions_manage', + 'page_arguments' => 'a:0:{}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/actions', + 'title' => 'Actions', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Manage the actions defined for your site.', + 'position' => '', + 'weight' => '0', + 'file' => '', +)) +->values(array( + 'path' => 'admin/settings/actions/configure', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:18:"administer actions";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:24:"system_actions_configure";}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/actions/configure', + 'title' => 'Configure an advanced action', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => '', +)) +->values(array( + 'path' => 'admin/settings/actions/delete/%', + 'load_functions' => 'a:1:{i:4;s:12:"actions_load";}', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:18:"administer actions";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:2:{i:0;s:26:"system_actions_delete_form";i:1;i:4;}', + 'fit' => '30', + 'number_parts' => '5', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/actions/delete/%', + 'title' => 'Delete action', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => 'Delete an action.', + 'position' => '', + 'weight' => '0', + 'file' => '', +)) +->values(array( + 'path' => 'admin/settings/actions/manage', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:18:"administer actions";}', + 'page_callback' => 'system_actions_manage', + 'page_arguments' => 'a:0:{}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/settings/actions', + 'tab_root' => 'admin/settings/actions', + 'title' => 'Manage actions', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => 'Manage the actions defined for your site.', + 'position' => '', + 'weight' => '-2', + 'file' => '', +)) +->values(array( + 'path' => 'admin/settings/actions/orphan', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:18:"administer actions";}', + 'page_callback' => 'system_actions_remove_orphans', + 'page_arguments' => 'a:0:{}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/actions/orphan', + 'title' => 'Remove orphans', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => '', +)) +->values(array( + 'path' => 'admin/settings/admin', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:27:"system_admin_theme_settings";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/admin', + 'title' => 'Administration theme', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => 'system_admin_theme_settings', + 'description' => 'Settings for how your administrative pages should look.', + 'position' => 'left', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/clean-urls', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:25:"system_clean_url_settings";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/clean-urls', + 'title' => 'Clean URLs', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Enable or disable clean URLs for your site.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/clean-urls/check', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => '1', + 'access_arguments' => 'a:0:{}', + 'page_callback' => 'drupal_json', + 'page_arguments' => 'a:1:{i:0;a:1:{s:6:"status";b:1;}}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/clean-urls/check', + 'title' => 'Clean URL check', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => '', +)) +->values(array( + 'path' => 'admin/settings/date-time', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:25:"system_date_time_settings";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/date-time', + 'title' => 'Date and time', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => "Settings for how Drupal displays date and time, as well as the system's default timezone.", + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/date-time/lookup', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'system_date_time_lookup', + 'page_arguments' => 'a:0:{}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/date-time/lookup', + 'title' => 'Date and time lookup', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/error-reporting', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:31:"system_error_reporting_settings";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/error-reporting', + 'title' => 'Error reporting', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Control how Drupal deals with errors including 403/404 errors as well as PHP error reporting.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/file-system', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:27:"system_file_system_settings";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/file-system', + 'title' => 'File system', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Tell Drupal where to store uploaded files and how they are accessed.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/filters', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:18:"administer filters";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:21:"filter_admin_overview";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/filters', + 'title' => 'Input formats', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Configure how content input by users is filtered, including allowed HTML tags. Also allows enabling of module-provided filters.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/filter/filter.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/filters/%', + 'load_functions' => 'a:1:{i:3;s:18:"filter_format_load";}', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:18:"administer filters";}', + 'page_callback' => 'filter_admin_format_page', + 'page_arguments' => 'a:1:{i:0;i:3;}', + 'fit' => '14', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/filters/%', + 'title' => '', + 'title_callback' => 'filter_admin_format_title', + 'title_arguments' => 'a:1:{i:0;i:3;}', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/filter/filter.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/filters/%/configure', + 'load_functions' => 'a:1:{i:3;s:18:"filter_format_load";}', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:18:"administer filters";}', + 'page_callback' => 'filter_admin_configure_page', + 'page_arguments' => 'a:1:{i:0;i:3;}', + 'fit' => '29', + 'number_parts' => '5', + 'tab_parent' => 'admin/settings/filters/%', + 'tab_root' => 'admin/settings/filters/%', + 'title' => 'Configure', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '1', + 'file' => 'modules/filter/filter.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/filters/%/edit', + 'load_functions' => 'a:1:{i:3;s:18:"filter_format_load";}', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:18:"administer filters";}', + 'page_callback' => 'filter_admin_format_page', + 'page_arguments' => 'a:1:{i:0;i:3;}', + 'fit' => '29', + 'number_parts' => '5', + 'tab_parent' => 'admin/settings/filters/%', + 'tab_root' => 'admin/settings/filters/%', + 'title' => 'Edit', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/filter/filter.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/filters/%/order', + 'load_functions' => 'a:1:{i:3;s:18:"filter_format_load";}', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:18:"administer filters";}', + 'page_callback' => 'filter_admin_order_page', + 'page_arguments' => 'a:1:{i:0;i:3;}', + 'fit' => '29', + 'number_parts' => '5', + 'tab_parent' => 'admin/settings/filters/%', + 'tab_root' => 'admin/settings/filters/%', + 'title' => 'Rearrange', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '2', + 'file' => 'modules/filter/filter.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/filters/add', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:18:"administer filters";}', + 'page_callback' => 'filter_admin_format_page', + 'page_arguments' => 'a:0:{}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/settings/filters', + 'tab_root' => 'admin/settings/filters', + 'title' => 'Add input format', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '1', + 'file' => 'modules/filter/filter.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/filters/delete', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:18:"administer filters";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:19:"filter_admin_delete";}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/filters/delete', + 'title' => 'Delete input format', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/filter/filter.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/filters/list', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:18:"administer filters";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:21:"filter_admin_overview";}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/settings/filters', + 'tab_root' => 'admin/settings/filters', + 'title' => 'List', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/filter/filter.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/image-toolkit', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:29:"system_image_toolkit_settings";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/image-toolkit', + 'title' => 'Image toolkit', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Choose which image toolkit to use if you have installed optional toolkits.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/logging', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'system_logging_overview', + 'page_arguments' => 'a:0:{}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/logging', + 'title' => 'Logging and alerts', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => "Settings for logging and alerts modules. Various modules can route Drupal's system events to different destination, such as syslog, database, email, ...etc.", + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/logging/dblog', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:20:"dblog_admin_settings";}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/logging/dblog', + 'title' => 'Database logging', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Settings for logging to the Drupal database logs. This is the most common method for small to medium sites on shared hosting. The logs are viewable from the admin pages.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/dblog/dblog.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/performance', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:27:"system_performance_settings";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/performance', + 'title' => 'Performance', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Enable or disable page caching for anonymous users and set CSS and JS bandwidth optimization options.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/site-information', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:32:"system_site_information_settings";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/site-information', + 'title' => 'Site information', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Change basic site information, such as the site name, slogan, e-mail address, mission, front page and more.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/settings/site-maintenance', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:29:"administer site configuration";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:32:"system_site_maintenance_settings";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/settings/site-maintenance', + 'title' => 'Site maintenance', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Take the site off-line for maintenance or bring it back online.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/user', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:27:"access administration pages";}', + 'page_callback' => 'system_admin_menu_block_page', + 'page_arguments' => 'a:0:{}', + 'fit' => '3', + 'number_parts' => '2', + 'tab_parent' => '', + 'tab_root' => 'admin/user', + 'title' => 'User management', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => "Manage your site's users, groups and access to site features.", + 'position' => 'left', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'admin/user/permissions', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:22:"administer permissions";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:15:"user_admin_perm";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/user/permissions', + 'title' => 'Permissions', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Determine access to features by selecting permissions for roles.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.admin.inc', +)) +->values(array( + 'path' => 'admin/user/roles', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:22:"administer permissions";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:19:"user_admin_new_role";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/user/roles', + 'title' => 'Roles', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'List, edit, or add user roles.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.admin.inc', +)) +->values(array( + 'path' => 'admin/user/roles/edit', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:22:"administer permissions";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:15:"user_admin_role";}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/user/roles/edit', + 'title' => 'Edit role', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.admin.inc', +)) +->values(array( + 'path' => 'admin/user/rules', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:22:"administer permissions";}', + 'page_callback' => 'user_admin_access', + 'page_arguments' => 'a:0:{}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/user/rules', + 'title' => 'Access rules', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'List and create rules to disallow usernames, e-mail addresses, and IP addresses.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.admin.inc', +)) +->values(array( + 'path' => 'admin/user/rules/add', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:22:"administer permissions";}', + 'page_callback' => 'user_admin_access_add', + 'page_arguments' => 'a:0:{}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/user/rules', + 'tab_root' => 'admin/user/rules', + 'title' => 'Add rule', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.admin.inc', +)) +->values(array( + 'path' => 'admin/user/rules/check', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:22:"administer permissions";}', + 'page_callback' => 'user_admin_access_check', + 'page_arguments' => 'a:0:{}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/user/rules', + 'tab_root' => 'admin/user/rules', + 'title' => 'Check rules', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.admin.inc', +)) +->values(array( + 'path' => 'admin/user/rules/delete', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:22:"administer permissions";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:32:"user_admin_access_delete_confirm";}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/user/rules/delete', + 'title' => 'Delete rule', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.admin.inc', +)) +->values(array( + 'path' => 'admin/user/rules/edit', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:22:"administer permissions";}', + 'page_callback' => 'user_admin_access_edit', + 'page_arguments' => 'a:0:{}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => '', + 'tab_root' => 'admin/user/rules/edit', + 'title' => 'Edit rule', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.admin.inc', +)) +->values(array( + 'path' => 'admin/user/rules/list', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:22:"administer permissions";}', + 'page_callback' => 'user_admin_access', + 'page_arguments' => 'a:0:{}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/user/rules', + 'tab_root' => 'admin/user/rules', + 'title' => 'List', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '-10', + 'file' => 'modules/user/user.admin.inc', +)) +->values(array( + 'path' => 'admin/user/settings', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:16:"administer users";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:19:"user_admin_settings";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/user/settings', + 'title' => 'User settings', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'Configure default behavior of users, including registration requirements, e-mails, and user pictures.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.admin.inc', +)) +->values(array( + 'path' => 'admin/user/user', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:16:"administer users";}', + 'page_callback' => 'user_admin', + 'page_arguments' => 'a:1:{i:0;s:4:"list";}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'admin/user/user', + 'title' => 'Users', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => 'List, add, and edit users.', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.admin.inc', +)) +->values(array( + 'path' => 'admin/user/user/create', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:16:"administer users";}', + 'page_callback' => 'user_admin', + 'page_arguments' => 'a:1:{i:0;s:6:"create";}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/user/user', + 'tab_root' => 'admin/user/user', + 'title' => 'Add user', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.admin.inc', +)) +->values(array( + 'path' => 'admin/user/user/list', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:16:"administer users";}', + 'page_callback' => 'user_admin', + 'page_arguments' => 'a:1:{i:0;s:4:"list";}', + 'fit' => '15', + 'number_parts' => '4', + 'tab_parent' => 'admin/user/user', + 'tab_root' => 'admin/user/user', + 'title' => 'List', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '-10', + 'file' => 'modules/user/user.admin.inc', +)) +->values(array( + 'path' => 'batch', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => '1', + 'access_arguments' => 'a:0:{}', + 'page_callback' => 'system_batch_page', + 'page_arguments' => 'a:0:{}', + 'fit' => '1', + 'number_parts' => '1', + 'tab_parent' => '', + 'tab_root' => 'batch', + 'title' => '', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/system/system.admin.inc', +)) +->values(array( + 'path' => 'filter/tips', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => '1', + 'access_arguments' => 'a:0:{}', + 'page_callback' => 'filter_tips_long', + 'page_arguments' => 'a:0:{}', + 'fit' => '3', + 'number_parts' => '2', + 'tab_parent' => '', + 'tab_root' => 'filter/tips', + 'title' => 'Compose tips', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '20', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/filter/filter.pages.inc', +)) +->values(array( + 'path' => 'logout', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_is_logged_in', + 'access_arguments' => 'a:0:{}', + 'page_callback' => 'user_logout', + 'page_arguments' => 'a:0:{}', + 'fit' => '1', + 'number_parts' => '1', + 'tab_parent' => '', + 'tab_root' => 'logout', + 'title' => 'Log out', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '10', + 'file' => 'modules/user/user.pages.inc', +)) +->values(array( + 'path' => 'node', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:14:"access content";}', + 'page_callback' => 'node_page_default', + 'page_arguments' => 'a:0:{}', + 'fit' => '1', + 'number_parts' => '1', + 'tab_parent' => '', + 'tab_root' => 'node', + 'title' => 'Content', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => '', +)) +->values(array( + 'path' => 'node/%', + 'load_functions' => 'a:1:{i:1;s:9:"node_load";}', + 'to_arg_functions' => '', + 'access_callback' => 'node_access', + 'access_arguments' => 'a:2:{i:0;s:4:"view";i:1;i:1;}', + 'page_callback' => 'node_page_view', + 'page_arguments' => 'a:1:{i:0;i:1;}', + 'fit' => '2', + 'number_parts' => '2', + 'tab_parent' => '', + 'tab_root' => 'node/%', + 'title' => '', + 'title_callback' => 'node_page_title', + 'title_arguments' => 'a:1:{i:0;i:1;}', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => '', +)) +->values(array( + 'path' => 'node/%/delete', + 'load_functions' => 'a:1:{i:1;s:9:"node_load";}', + 'to_arg_functions' => '', + 'access_callback' => 'node_access', + 'access_arguments' => 'a:2:{i:0;s:6:"delete";i:1;i:1;}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:2:{i:0;s:19:"node_delete_confirm";i:1;i:1;}', + 'fit' => '5', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'node/%/delete', + 'title' => 'Delete', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '1', + 'file' => 'modules/node/node.pages.inc', +)) +->values(array( + 'path' => 'node/%/edit', + 'load_functions' => 'a:1:{i:1;s:9:"node_load";}', + 'to_arg_functions' => '', + 'access_callback' => 'node_access', + 'access_arguments' => 'a:2:{i:0;s:6:"update";i:1;i:1;}', + 'page_callback' => 'node_page_edit', + 'page_arguments' => 'a:1:{i:0;i:1;}', + 'fit' => '5', + 'number_parts' => '3', + 'tab_parent' => 'node/%', + 'tab_root' => 'node/%', + 'title' => 'Edit', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '1', + 'file' => 'modules/node/node.pages.inc', +)) +->values(array( + 'path' => 'node/%/revisions', + 'load_functions' => 'a:1:{i:1;s:9:"node_load";}', + 'to_arg_functions' => '', + 'access_callback' => '_node_revision_access', + 'access_arguments' => 'a:1:{i:0;i:1;}', + 'page_callback' => 'node_revision_overview', + 'page_arguments' => 'a:1:{i:0;i:1;}', + 'fit' => '5', + 'number_parts' => '3', + 'tab_parent' => 'node/%', + 'tab_root' => 'node/%', + 'title' => 'Revisions', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '2', + 'file' => 'modules/node/node.pages.inc', +)) +->values(array( + 'path' => 'node/%/revisions/%/delete', + 'load_functions' => 'a:2:{i:1;a:1:{s:9:"node_load";a:1:{i:0;i:3;}}i:3;N;}', + 'to_arg_functions' => '', + 'access_callback' => '_node_revision_access', + 'access_arguments' => 'a:2:{i:0;i:1;i:1;s:6:"delete";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:2:{i:0;s:28:"node_revision_delete_confirm";i:1;i:1;}', + 'fit' => '21', + 'number_parts' => '5', + 'tab_parent' => '', + 'tab_root' => 'node/%/revisions/%/delete', + 'title' => 'Delete earlier revision', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/node/node.pages.inc', +)) +->values(array( + 'path' => 'node/%/revisions/%/revert', + 'load_functions' => 'a:2:{i:1;a:1:{s:9:"node_load";a:1:{i:0;i:3;}}i:3;N;}', + 'to_arg_functions' => '', + 'access_callback' => '_node_revision_access', + 'access_arguments' => 'a:2:{i:0;i:1;i:1;s:6:"update";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:2:{i:0;s:28:"node_revision_revert_confirm";i:1;i:1;}', + 'fit' => '21', + 'number_parts' => '5', + 'tab_parent' => '', + 'tab_root' => 'node/%/revisions/%/revert', + 'title' => 'Revert to earlier revision', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/node/node.pages.inc', +)) +->values(array( + 'path' => 'node/%/revisions/%/view', + 'load_functions' => 'a:2:{i:1;a:1:{s:9:"node_load";a:1:{i:0;i:3;}}i:3;N;}', + 'to_arg_functions' => '', + 'access_callback' => '_node_revision_access', + 'access_arguments' => 'a:1:{i:0;i:1;}', + 'page_callback' => 'node_show', + 'page_arguments' => 'a:3:{i:0;i:1;i:1;N;i:2;b:1;}', + 'fit' => '21', + 'number_parts' => '5', + 'tab_parent' => '', + 'tab_root' => 'node/%/revisions/%/view', + 'title' => 'Revisions', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => '', +)) +->values(array( + 'path' => 'node/%/view', + 'load_functions' => 'a:1:{i:1;s:9:"node_load";}', + 'to_arg_functions' => '', + 'access_callback' => 'node_access', + 'access_arguments' => 'a:2:{i:0;s:4:"view";i:1;i:1;}', + 'page_callback' => 'node_page_view', + 'page_arguments' => 'a:1:{i:0;i:1;}', + 'fit' => '5', + 'number_parts' => '3', + 'tab_parent' => 'node/%', + 'tab_root' => 'node/%', + 'title' => 'View', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '-10', + 'file' => '', +)) +->values(array( + 'path' => 'node/add', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => '_node_add_access', + 'access_arguments' => 'a:0:{}', + 'page_callback' => 'node_add_page', + 'page_arguments' => 'a:0:{}', + 'fit' => '3', + 'number_parts' => '2', + 'tab_parent' => '', + 'tab_root' => 'node/add', + 'title' => 'Create content', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '1', + 'file' => 'modules/node/node.pages.inc', +)) +->values(array( + 'path' => 'node/add/page', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'node_access', + 'access_arguments' => 'a:2:{i:0;s:6:"create";i:1;s:4:"page";}', + 'page_callback' => 'node_add', + 'page_arguments' => 'a:1:{i:0;i:2;}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'node/add/page', + 'title' => 'Page', + 'title_callback' => 'check_plain', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => "A <em>page</em>, similar in form to a <em>story</em>, is a simple method for creating and displaying information that rarely changes, such as an \"About us\" section of a website. By default, a <em>page</em> entry does not allow visitor comments and is not featured on the site's initial home page.", + 'position' => '', + 'weight' => '0', + 'file' => 'modules/node/node.pages.inc', +)) +->values(array( + 'path' => 'node/add/story', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'node_access', + 'access_arguments' => 'a:2:{i:0;s:6:"create";i:1;s:5:"story";}', + 'page_callback' => 'node_add', + 'page_arguments' => 'a:1:{i:0;i:2;}', + 'fit' => '7', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'node/add/story', + 'title' => 'Story', + 'title_callback' => 'check_plain', + 'title_arguments' => '', + 'type' => '6', + 'block_callback' => '', + 'description' => "A <em>story</em>, similar in form to a <em>page</em>, is ideal for creating and displaying content that informs or engages website visitors. Press releases, site announcements, and informal blog-like entries may all be created with a <em>story</em> entry. By default, a <em>story</em> entry is automatically featured on the site's initial home page, and provides the ability to post comments.", + 'position' => '', + 'weight' => '0', + 'file' => 'modules/node/node.pages.inc', +)) +->values(array( + 'path' => 'rss.xml', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:14:"access content";}', + 'page_callback' => 'node_feed', + 'page_arguments' => 'a:0:{}', + 'fit' => '1', + 'number_parts' => '1', + 'tab_parent' => '', + 'tab_root' => 'rss.xml', + 'title' => 'RSS feed', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => '', +)) +->values(array( + 'path' => 'system/files', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => '1', + 'access_arguments' => 'a:0:{}', + 'page_callback' => 'file_download', + 'page_arguments' => 'a:0:{}', + 'fit' => '3', + 'number_parts' => '2', + 'tab_parent' => '', + 'tab_root' => 'system/files', + 'title' => 'File download', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => '', +)) +->values(array( + 'path' => 'user', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => '1', + 'access_arguments' => 'a:0:{}', + 'page_callback' => 'user_page', + 'page_arguments' => 'a:0:{}', + 'fit' => '1', + 'number_parts' => '1', + 'tab_parent' => '', + 'tab_root' => 'user', + 'title' => 'User account', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.pages.inc', +)) +->values(array( + 'path' => 'user/%', + 'load_functions' => 'a:1:{i:1;s:22:"user_uid_optional_load";}', + 'to_arg_functions' => 'a:1:{i:1;s:24:"user_uid_optional_to_arg";}', + 'access_callback' => 'user_view_access', + 'access_arguments' => 'a:1:{i:0;i:1;}', + 'page_callback' => 'user_view', + 'page_arguments' => 'a:1:{i:0;i:1;}', + 'fit' => '2', + 'number_parts' => '2', + 'tab_parent' => '', + 'tab_root' => 'user/%', + 'title' => 'My account', + 'title_callback' => 'user_page_title', + 'title_arguments' => 'a:1:{i:0;i:1;}', + 'type' => '6', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.pages.inc', +)) +->values(array( + 'path' => 'user/%/delete', + 'load_functions' => 'a:1:{i:1;s:9:"user_load";}', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:16:"administer users";}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:2:{i:0;s:19:"user_confirm_delete";i:1;i:1;}', + 'fit' => '5', + 'number_parts' => '3', + 'tab_parent' => '', + 'tab_root' => 'user/%/delete', + 'title' => 'Delete', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.pages.inc', +)) +->values(array( + 'path' => 'user/%/edit', + 'load_functions' => 'a:1:{i:1;a:1:{s:18:"user_category_load";a:2:{i:0;s:4:"%map";i:1;s:6:"%index";}}}', + 'to_arg_functions' => '', + 'access_callback' => 'user_edit_access', + 'access_arguments' => 'a:1:{i:0;i:1;}', + 'page_callback' => 'user_edit', + 'page_arguments' => 'a:1:{i:0;i:1;}', + 'fit' => '5', + 'number_parts' => '3', + 'tab_parent' => 'user/%', + 'tab_root' => 'user/%', + 'title' => 'Edit', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.pages.inc', +)) +->values(array( + 'path' => 'user/%/edit/account', + 'load_functions' => 'a:1:{i:1;a:1:{s:18:"user_category_load";a:2:{i:0;s:4:"%map";i:1;s:6:"%index";}}}', + 'to_arg_functions' => '', + 'access_callback' => 'user_edit_access', + 'access_arguments' => 'a:1:{i:0;i:1;}', + 'page_callback' => 'user_edit', + 'page_arguments' => 'a:1:{i:0;i:1;}', + 'fit' => '11', + 'number_parts' => '4', + 'tab_parent' => 'user/%/edit', + 'tab_root' => 'user/%', + 'title' => 'Account', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.pages.inc', +)) +->values(array( + 'path' => 'user/%/view', + 'load_functions' => 'a:1:{i:1;s:9:"user_load";}', + 'to_arg_functions' => '', + 'access_callback' => 'user_view_access', + 'access_arguments' => 'a:1:{i:0;i:1;}', + 'page_callback' => 'user_view', + 'page_arguments' => 'a:1:{i:0;i:1;}', + 'fit' => '5', + 'number_parts' => '3', + 'tab_parent' => 'user/%', + 'tab_root' => 'user/%', + 'title' => 'View', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '-10', + 'file' => 'modules/user/user.pages.inc', +)) +->values(array( + 'path' => 'user/autocomplete', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_access', + 'access_arguments' => 'a:1:{i:0;s:20:"access user profiles";}', + 'page_callback' => 'user_autocomplete', + 'page_arguments' => 'a:0:{}', + 'fit' => '3', + 'number_parts' => '2', + 'tab_parent' => '', + 'tab_root' => 'user/autocomplete', + 'title' => 'User autocomplete', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.pages.inc', +)) +->values(array( + 'path' => 'user/login', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_is_anonymous', + 'access_arguments' => 'a:0:{}', + 'page_callback' => 'user_page', + 'page_arguments' => 'a:0:{}', + 'fit' => '3', + 'number_parts' => '2', + 'tab_parent' => 'user', + 'tab_root' => 'user', + 'title' => 'Log in', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '136', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.pages.inc', +)) +->values(array( + 'path' => 'user/password', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_is_anonymous', + 'access_arguments' => 'a:0:{}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:9:"user_pass";}', + 'fit' => '3', + 'number_parts' => '2', + 'tab_parent' => 'user', + 'tab_root' => 'user', + 'title' => 'Request new password', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.pages.inc', +)) +->values(array( + 'path' => 'user/register', + 'load_functions' => '', + 'to_arg_functions' => '', + 'access_callback' => 'user_register_access', + 'access_arguments' => 'a:0:{}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:1:{i:0;s:13:"user_register";}', + 'fit' => '3', + 'number_parts' => '2', + 'tab_parent' => 'user', + 'tab_root' => 'user', + 'title' => 'Create new account', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '128', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.pages.inc', +)) +->values(array( + 'path' => 'user/reset/%/%/%', + 'load_functions' => 'a:3:{i:2;N;i:3;N;i:4;N;}', + 'to_arg_functions' => '', + 'access_callback' => '1', + 'access_arguments' => 'a:0:{}', + 'page_callback' => 'drupal_get_form', + 'page_arguments' => 'a:4:{i:0;s:15:"user_pass_reset";i:1;i:2;i:2;i:3;i:3;i:4;}', + 'fit' => '24', + 'number_parts' => '5', + 'tab_parent' => '', + 'tab_root' => 'user/reset/%/%/%', + 'title' => 'Reset password', + 'title_callback' => 't', + 'title_arguments' => '', + 'type' => '4', + 'block_callback' => '', + 'description' => '', + 'position' => '', + 'weight' => '0', + 'file' => 'modules/user/user.pages.inc', +)) +->execute(); + +db_create_table('node', array( + 'fields' => array( + 'nid' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'vid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'type' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'language' => array( + 'type' => 'varchar', + 'length' => 12, + 'not null' => TRUE, + 'default' => '', + ), + 'title' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'uid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'status' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 1, + ), + 'created' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'changed' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'comment' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'promote' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'moderate' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'sticky' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'tnid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'translate' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'node_changed' => array( + 'changed', + ), + 'node_created' => array( + 'created', + ), + 'node_moderate' => array( + 'moderate', + ), + 'node_promote_status' => array( + 'promote', + 'status', + ), + 'node_status_type' => array( + 'status', + 'type', + 'nid', + ), + 'node_title_type' => array( + 'title', + array( + 'type', + 4, + ), + ), + 'node_type' => array( + array( + 'type', + 4, + ), + ), + 'uid' => array( + 'uid', + ), + 'tnid' => array( + 'tnid', + ), + 'translate' => array( + 'translate', + ), + ), + 'unique keys' => array( + 'vid' => array( + 'vid', + ), + ), + 'primary key' => array( + 'nid', + ), + 'module' => 'node', + 'name' => 'node', +)); + +db_create_table('node_access', array( + 'fields' => array( + 'nid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'gid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'realm' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'grant_view' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + 'grant_update' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + 'grant_delete' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + ), + 'primary key' => array( + 'nid', + 'gid', + 'realm', + ), + 'module' => 'node', + 'name' => 'node_access', +)); +db_insert('node_access')->fields(array( + 'nid', + 'gid', + 'realm', + 'grant_view', + 'grant_update', + 'grant_delete', +)) +->values(array( + 'nid' => '0', + 'gid' => '0', + 'realm' => 'all', + 'grant_view' => '1', + 'grant_update' => '0', + 'grant_delete' => '0', +)) +->execute(); + +db_create_table('node_counter', array( + 'fields' => array( + 'nid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'totalcount' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'size' => 'big', + ), + 'daycount' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'size' => 'medium', + ), + 'timestamp' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array( + 'nid', + ), + 'module' => 'node', + 'name' => 'node_counter', +)); + +db_create_table('node_revisions', array( + 'fields' => array( + 'nid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'vid' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'uid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'title' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'body' => array( + 'type' => 'text', + 'not null' => TRUE, + 'size' => 'big', + ), + 'teaser' => array( + 'type' => 'text', + 'not null' => TRUE, + 'size' => 'big', + ), + 'log' => array( + 'type' => 'text', + 'not null' => TRUE, + 'size' => 'big', + ), + 'timestamp' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'format' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'nid' => array( + 'nid', + ), + 'uid' => array( + 'uid', + ), + ), + 'primary key' => array( + 'vid', + ), + 'module' => 'node', + 'name' => 'node_revisions', +)); + +db_create_table('node_type', array( + 'fields' => array( + 'type' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + ), + 'name' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'module' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + ), + 'description' => array( + 'type' => 'text', + 'not null' => TRUE, + 'size' => 'medium', + ), + 'help' => array( + 'type' => 'text', + 'not null' => TRUE, + 'size' => 'medium', + ), + 'has_title' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'size' => 'tiny', + ), + 'title_label' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'has_body' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'size' => 'tiny', + ), + 'body_label' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'min_word_count' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'size' => 'small', + ), + 'custom' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + 'modified' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + 'locked' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + 'orig_type' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + ), + 'primary key' => array( + 'type', + ), + 'module' => 'node', + 'name' => 'node_type', +)); +db_insert('node_type')->fields(array( + 'type', + 'name', + 'module', + 'description', + 'help', + 'has_title', + 'title_label', + 'has_body', + 'body_label', + 'min_word_count', + 'custom', + 'modified', + 'locked', + 'orig_type', +)) +->values(array( + 'type' => 'page', + 'name' => 'Page', + 'module' => 'node', + 'description' => "A <em>page</em>, similar in form to a <em>story</em>, is a simple method for creating and displaying information that rarely changes, such as an \"About us\" section of a website. By default, a <em>page</em> entry does not allow visitor comments and is not featured on the site's initial home page.", + 'help' => '', + 'has_title' => '1', + 'title_label' => 'Title', + 'has_body' => '1', + 'body_label' => 'Body', + 'min_word_count' => '0', + 'custom' => '1', + 'modified' => '1', + 'locked' => '0', + 'orig_type' => 'page', +)) +->values(array( + 'type' => 'story', + 'name' => 'Story', + 'module' => 'node', + 'description' => "A <em>story</em>, similar in form to a <em>page</em>, is ideal for creating and displaying content that informs or engages website visitors. Press releases, site announcements, and informal blog-like entries may all be created with a <em>story</em> entry. By default, a <em>story</em> entry is automatically featured on the site's initial home page, and provides the ability to post comments.", + 'help' => '', + 'has_title' => '1', + 'title_label' => 'Title', + 'has_body' => '1', + 'body_label' => 'Body', + 'min_word_count' => '0', + 'custom' => '1', + 'modified' => '1', + 'locked' => '0', + 'orig_type' => 'story', +)) +->execute(); + +db_create_table('permission', array( + 'fields' => array( + 'pid' => array( + 'type' => 'serial', + 'not null' => TRUE, + ), + 'rid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'perm' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + ), + 'tid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array( + 'pid', + ), + 'indexes' => array( + 'rid' => array( + 'rid', + ), + ), + 'module' => 'user', + 'name' => 'permission', +)); +db_insert('permission')->fields(array( + 'pid', + 'rid', + 'perm', + 'tid', +)) +->values(array( + 'pid' => '1', + 'rid' => '1', + 'perm' => 'access content', + 'tid' => '0', +)) +->values(array( + 'pid' => '2', + 'rid' => '2', + 'perm' => 'access comments, access content, post comments, post comments without approval', + 'tid' => '0', +)) +->execute(); + +db_create_table('role', array( + 'fields' => array( + 'rid' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'name' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'default' => '', + ), + ), + 'unique keys' => array( + 'name' => array( + 'name', + ), + ), + 'primary key' => array( + 'rid', + ), + 'module' => 'user', + 'name' => 'role', +)); +db_insert('role')->fields(array( + 'rid', + 'name', +)) +->values(array( + 'rid' => '1', + 'name' => 'anonymous user', +)) +->values(array( + 'rid' => '2', + 'name' => 'authenticated user', +)) +->execute(); + +db_create_table('semaphore', array( + 'fields' => array( + 'name' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'value' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'expire' => array( + 'type' => 'float', + 'size' => 'big', + 'not null' => TRUE, + ), + ), + 'indexes' => array( + 'expire' => array( + 'expire', + ), + ), + 'primary key' => array( + 'name', + ), + 'module' => 'system', + 'name' => 'semaphore', +)); + +db_create_table('sessions', array( + 'fields' => array( + 'uid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'sid' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'default' => '', + ), + 'hostname' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'timestamp' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'cache' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'session' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + ), + ), + 'primary key' => array( + 'sid', + ), + 'indexes' => array( + 'timestamp' => array( + 'timestamp', + ), + 'uid' => array( + 'uid', + ), + ), + 'module' => 'system', + 'name' => 'sessions', +)); + +db_create_table('system', array( + 'fields' => array( + 'filename' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'name' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'type' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'owner' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'status' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'throttle' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + 'bootstrap' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'schema_version' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => -1, + 'size' => 'small', + ), + 'weight' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'info' => array( + 'type' => 'text', + 'not null' => FALSE, + ), + ), + 'primary key' => array( + 'filename', + ), + 'indexes' => array( + 'modules' => array( + array( + 'type', + 12, + ), + 'status', + 'weight', + 'filename', + ), + 'bootstrap' => array( + array( + 'type', + 12, + ), + 'status', + 'bootstrap', + 'weight', + 'filename', + ), + 'type_name' => array( + array( + 'type', + 12, + ), + 'name', + ), + ), + 'module' => 'system', + 'name' => 'system', +)); +db_insert('system')->fields(array( + 'filename', + 'name', + 'type', + 'owner', + 'status', + 'throttle', + 'bootstrap', + 'schema_version', + 'weight', + 'info', +)) +->values(array( + 'filename' => 'modules/aggregator/aggregator.module', + 'name' => 'aggregator', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:10:"Aggregator";s:11:"description";s:57:"Aggregates syndicated content (RSS, RDF, and Atom feeds).";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/block/block.module', + 'name' => 'block', + 'type' => 'module', + 'owner' => '', + 'status' => '1', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '0', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:5:"Block";s:11:"description";s:62:"Controls the boxes that are displayed around the main content.";s:7:"package";s:15:"Core - required";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/blog/blog.module', + 'name' => 'blog', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:4:"Blog";s:11:"description";s:69:"Enables keeping easily and regularly updated user web pages or blogs.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/blogapi/blogapi.module', + 'name' => 'blogapi', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:8:"Blog API";s:11:"description";s:79:"Allows users to post content using applications that support XML-RPC blog APIs.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/book/book.module', + 'name' => 'book', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:4:"Book";s:11:"description";s:63:"Allows users to structure site pages in a hierarchy or outline.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/color/color.module', + 'name' => 'color', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:5:"Color";s:11:"description";s:61:"Allows the user to change the color scheme of certain themes.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/comment/comment.module', + 'name' => 'comment', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:7:"Comment";s:11:"description";s:57:"Allows users to comment on and discuss published content.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/contact/contact.module', + 'name' => 'contact', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:7:"Contact";s:11:"description";s:61:"Enables the use of both personal and site-wide contact forms.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/dblog/dblog.module', + 'name' => 'dblog', + 'type' => 'module', + 'owner' => '', + 'status' => '1', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '6000', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:16:"Database logging";s:11:"description";s:47:"Logs and records system events to the database.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/filter/filter.module', + 'name' => 'filter', + 'type' => 'module', + 'owner' => '', + 'status' => '1', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '0', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:6:"Filter";s:11:"description";s:60:"Handles the filtering of content in preparation for display.";s:7:"package";s:15:"Core - required";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/forum/forum.module', + 'name' => 'forum', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:5:"Forum";s:11:"description";s:50:"Enables threaded discussions about general topics.";s:12:"dependencies";a:2:{i:0;s:8:"taxonomy";i:1;s:7:"comment";}s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/help/help.module', + 'name' => 'help', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:4:"Help";s:11:"description";s:35:"Manages the display of online help.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/locale/locale.module', + 'name' => 'locale', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:6:"Locale";s:11:"description";s:119:"Adds language handling functionality and enables the translation of the user interface to languages other than English.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/menu/menu.module', + 'name' => 'menu', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:4:"Menu";s:11:"description";s:60:"Allows administrators to customize the site navigation menu.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/node/node.module', + 'name' => 'node', + 'type' => 'module', + 'owner' => '', + 'status' => '1', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '0', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:4:"Node";s:11:"description";s:66:"Allows content to be submitted to the site and displayed on pages.";s:7:"package";s:15:"Core - required";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/openid/openid.module', + 'name' => 'openid', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:6:"OpenID";s:11:"description";s:48:"Allows users to log into your site using OpenID.";s:7:"version";s:4:"6.17";s:7:"package";s:15:"Core - optional";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/path/path.module', + 'name' => 'path', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:4:"Path";s:11:"description";s:28:"Allows users to rename URLs.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/php/php.module', + 'name' => 'php', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:10:"PHP filter";s:11:"description";s:50:"Allows embedded PHP code/snippets to be evaluated.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/ping/ping.module', + 'name' => 'ping', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:4:"Ping";s:11:"description";s:51:"Alerts other sites when your site has been updated.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/poll/poll.module', + 'name' => 'poll', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:4:"Poll";s:11:"description";s:95:"Allows your site to capture votes on different topics in the form of multiple choice questions.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/profile/profile.module', + 'name' => 'profile', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:7:"Profile";s:11:"description";s:36:"Supports configurable user profiles.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/search/search.module', + 'name' => 'search', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:6:"Search";s:11:"description";s:36:"Enables site-wide keyword searching.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/statistics/statistics.module', + 'name' => 'statistics', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:10:"Statistics";s:11:"description";s:37:"Logs access statistics for your site.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/syslog/syslog.module', + 'name' => 'syslog', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:6:"Syslog";s:11:"description";s:41:"Logs and records system events to syslog.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/system/system.module', + 'name' => 'system', + 'type' => 'module', + 'owner' => '', + 'status' => '1', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '6055', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:6:"System";s:11:"description";s:54:"Handles general site configuration for administrators.";s:7:"package";s:15:"Core - required";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/taxonomy/taxonomy.module', + 'name' => 'taxonomy', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:8:"Taxonomy";s:11:"description";s:38:"Enables the categorization of content.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/throttle/throttle.module', + 'name' => 'throttle', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:8:"Throttle";s:11:"description";s:66:"Handles the auto-throttling mechanism, to control site congestion.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/tracker/tracker.module', + 'name' => 'tracker', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:7:"Tracker";s:11:"description";s:43:"Enables tracking of recent posts for users.";s:12:"dependencies";a:1:{i:0;s:7:"comment";}s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/translation/translation.module', + 'name' => 'translation', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:19:"Content translation";s:11:"description";s:57:"Allows content to be translated into different languages.";s:12:"dependencies";a:1:{i:0;s:6:"locale";}s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/trigger/trigger.module', + 'name' => 'trigger', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:7:"Trigger";s:11:"description";s:90:"Enables actions to be fired on certain system events, such as when new content is created.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/update/update.module', + 'name' => 'update', + 'type' => 'module', + 'owner' => '', + 'status' => '1', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '6000', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:13:"Update status";s:11:"description";s:88:"Checks the status of available updates for Drupal and your installed modules and themes.";s:7:"version";s:4:"6.17";s:7:"package";s:15:"Core - optional";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/upload/upload.module', + 'name' => 'upload', + 'type' => 'module', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:6:"Upload";s:11:"description";s:51:"Allows users to upload and attach files to content.";s:7:"package";s:15:"Core - optional";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'modules/user/user.module', + 'name' => 'user', + 'type' => 'module', + 'owner' => '', + 'status' => '1', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '0', + 'weight' => '0', + 'info' => 'a:10:{s:4:"name";s:4:"User";s:11:"description";s:47:"Manages the user registration and login system.";s:7:"package";s:15:"Core - required";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:12:"dependencies";a:0:{}s:10:"dependents";a:0:{}s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'themes/bluemarine/bluemarine.info', + 'name' => 'bluemarine', + 'type' => 'theme', + 'owner' => 'themes/engines/phptemplate/phptemplate.engine', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:13:{s:4:"name";s:10:"Bluemarine";s:11:"description";s:66:"Table-based multi-column theme with a marine and ash color scheme.";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:6:"engine";s:11:"phptemplate";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:7:"regions";a:5:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";s:7:"content";s:7:"Content";s:6:"header";s:6:"Header";s:6:"footer";s:6:"Footer";}s:8:"features";a:10:{i:0;s:20:"comment_user_picture";i:1;s:7:"favicon";i:2;s:7:"mission";i:3;s:4:"logo";i:4;s:4:"name";i:5;s:17:"node_user_picture";i:6;s:6:"search";i:7;s:6:"slogan";i:8;s:13:"primary_links";i:9;s:15:"secondary_links";}s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:9:"style.css";s:27:"themes/bluemarine/style.css";}}s:7:"scripts";a:1:{s:9:"script.js";s:27:"themes/bluemarine/script.js";}s:10:"screenshot";s:32:"themes/bluemarine/screenshot.png";s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'themes/chameleon/chameleon.info', + 'name' => 'chameleon', + 'type' => 'theme', + 'owner' => 'themes/chameleon/chameleon.theme', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:12:{s:4:"name";s:9:"Chameleon";s:11:"description";s:42:"Minimalist tabled theme with light colors.";s:7:"regions";a:2:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";}s:8:"features";a:4:{i:0;s:4:"logo";i:1;s:7:"favicon";i:2;s:4:"name";i:3;s:6:"slogan";}s:11:"stylesheets";a:1:{s:3:"all";a:2:{s:9:"style.css";s:26:"themes/chameleon/style.css";s:10:"common.css";s:27:"themes/chameleon/common.css";}}s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:7:"scripts";a:1:{s:9:"script.js";s:26:"themes/chameleon/script.js";}s:10:"screenshot";s:31:"themes/chameleon/screenshot.png";s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'themes/chameleon/marvin/marvin.info', + 'name' => 'marvin', + 'type' => 'theme', + 'owner' => '', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:13:{s:4:"name";s:6:"Marvin";s:11:"description";s:31:"Boxy tabled theme in all grays.";s:7:"regions";a:2:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";}s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:10:"base theme";s:9:"chameleon";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:8:"features";a:10:{i:0;s:20:"comment_user_picture";i:1;s:7:"favicon";i:2;s:7:"mission";i:3;s:4:"logo";i:4;s:4:"name";i:5;s:17:"node_user_picture";i:6;s:6:"search";i:7;s:6:"slogan";i:8;s:13:"primary_links";i:9;s:15:"secondary_links";}s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:9:"style.css";s:33:"themes/chameleon/marvin/style.css";}}s:7:"scripts";a:1:{s:9:"script.js";s:33:"themes/chameleon/marvin/script.js";}s:10:"screenshot";s:38:"themes/chameleon/marvin/screenshot.png";s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'themes/garland/garland.info', + 'name' => 'garland', + 'type' => 'theme', + 'owner' => 'themes/engines/phptemplate/phptemplate.engine', + 'status' => '1', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:13:{s:4:"name";s:7:"Garland";s:11:"description";s:66:"Tableless, recolorable, multi-column, fluid width theme (default).";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:6:"engine";s:11:"phptemplate";s:11:"stylesheets";a:2:{s:3:"all";a:1:{s:9:"style.css";s:24:"themes/garland/style.css";}s:5:"print";a:1:{s:9:"print.css";s:24:"themes/garland/print.css";}}s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:7:"regions";a:5:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";s:7:"content";s:7:"Content";s:6:"header";s:6:"Header";s:6:"footer";s:6:"Footer";}s:8:"features";a:10:{i:0;s:20:"comment_user_picture";i:1;s:7:"favicon";i:2;s:7:"mission";i:3;s:4:"logo";i:4;s:4:"name";i:5;s:17:"node_user_picture";i:6;s:6:"search";i:7;s:6:"slogan";i:8;s:13:"primary_links";i:9;s:15:"secondary_links";}s:7:"scripts";a:1:{s:9:"script.js";s:24:"themes/garland/script.js";}s:10:"screenshot";s:29:"themes/garland/screenshot.png";s:3:"php";s:5:"4.3.5";}', +)) +->values(array( + 'filename' => 'themes/garland/minnelli/minnelli.info', + 'name' => 'minnelli', + 'type' => 'theme', + 'owner' => 'themes/engines/phptemplate/phptemplate.engine', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:14:{s:4:"name";s:8:"Minnelli";s:11:"description";s:56:"Tableless, recolorable, multi-column, fixed width theme.";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:10:"base theme";s:7:"garland";s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:12:"minnelli.css";s:36:"themes/garland/minnelli/minnelli.css";}}s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:7:"regions";a:5:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";s:7:"content";s:7:"Content";s:6:"header";s:6:"Header";s:6:"footer";s:6:"Footer";}s:8:"features";a:10:{i:0;s:20:"comment_user_picture";i:1;s:7:"favicon";i:2;s:7:"mission";i:3;s:4:"logo";i:4;s:4:"name";i:5;s:17:"node_user_picture";i:6;s:6:"search";i:7;s:6:"slogan";i:8;s:13:"primary_links";i:9;s:15:"secondary_links";}s:7:"scripts";a:1:{s:9:"script.js";s:33:"themes/garland/minnelli/script.js";}s:10:"screenshot";s:38:"themes/garland/minnelli/screenshot.png";s:3:"php";s:5:"4.3.5";s:6:"engine";s:11:"phptemplate";}', +)) +->values(array( + 'filename' => 'themes/pushbutton/pushbutton.info', + 'name' => 'pushbutton', + 'type' => 'theme', + 'owner' => 'themes/engines/phptemplate/phptemplate.engine', + 'status' => '0', + 'throttle' => '0', + 'bootstrap' => '0', + 'schema_version' => '-1', + 'weight' => '0', + 'info' => 'a:13:{s:4:"name";s:10:"Pushbutton";s:11:"description";s:52:"Tabled, multi-column theme in blue and orange tones.";s:7:"version";s:4:"6.17";s:4:"core";s:3:"6.x";s:6:"engine";s:11:"phptemplate";s:7:"project";s:6:"drupal";s:9:"datestamp";s:10:"1275505216";s:7:"regions";a:5:{s:4:"left";s:12:"Left sidebar";s:5:"right";s:13:"Right sidebar";s:7:"content";s:7:"Content";s:6:"header";s:6:"Header";s:6:"footer";s:6:"Footer";}s:8:"features";a:10:{i:0;s:20:"comment_user_picture";i:1;s:7:"favicon";i:2;s:7:"mission";i:3;s:4:"logo";i:4;s:4:"name";i:5;s:17:"node_user_picture";i:6;s:6:"search";i:7;s:6:"slogan";i:8;s:13:"primary_links";i:9;s:15:"secondary_links";}s:11:"stylesheets";a:1:{s:3:"all";a:1:{s:9:"style.css";s:27:"themes/pushbutton/style.css";}}s:7:"scripts";a:1:{s:9:"script.js";s:27:"themes/pushbutton/script.js";}s:10:"screenshot";s:32:"themes/pushbutton/screenshot.png";s:3:"php";s:5:"4.3.5";}', +)) +->execute(); + +db_create_table('url_alias', array( + 'fields' => array( + 'pid' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'src' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'dst' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'language' => array( + 'type' => 'varchar', + 'length' => 12, + 'not null' => TRUE, + 'default' => '', + ), + ), + 'unique keys' => array( + 'dst_language_pid' => array( + 'dst', + 'language', + 'pid', + ), + ), + 'primary key' => array( + 'pid', + ), + 'indexes' => array( + 'src_language_pid' => array( + 'src', + 'language', + 'pid', + ), + ), + 'module' => 'system', + 'name' => 'url_alias', +)); + +db_create_table('users', array( + 'fields' => array( + 'uid' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'name' => array( + 'type' => 'varchar', + 'length' => 60, + 'not null' => TRUE, + 'default' => '', + ), + 'pass' => array( + 'type' => 'varchar', + 'length' => 32, + 'not null' => TRUE, + 'default' => '', + ), + 'mail' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => FALSE, + 'default' => '', + ), + 'mode' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + 'sort' => array( + 'type' => 'int', + 'not null' => FALSE, + 'default' => 0, + 'size' => 'tiny', + ), + 'threshold' => array( + 'type' => 'int', + 'not null' => FALSE, + 'default' => 0, + 'size' => 'tiny', + ), + 'theme' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'signature' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'signature_format' => array( + 'type' => 'int', + 'size' => 'small', + 'not null' => TRUE, + 'default' => 0, + ), + 'created' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'access' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'login' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'status' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + 'timezone' => array( + 'type' => 'varchar', + 'length' => 8, + 'not null' => FALSE, + ), + 'language' => array( + 'type' => 'varchar', + 'length' => 12, + 'not null' => TRUE, + 'default' => '', + ), + 'picture' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'init' => array( + 'type' => 'varchar', + 'length' => 64, + 'not null' => FALSE, + 'default' => '', + ), + 'data' => array( + 'type' => 'text', + 'not null' => FALSE, + 'size' => 'big', + ), + ), + 'indexes' => array( + 'access' => array( + 'access', + ), + 'created' => array( + 'created', + ), + 'mail' => array( + 'mail', + ), + ), + 'unique keys' => array( + 'name' => array( + 'name', + ), + ), + 'primary key' => array( + 'uid', + ), + 'module' => 'user', + 'name' => 'users', +)); +db_insert('users')->fields(array( + 'uid', + 'name', + 'pass', + 'mail', + 'mode', + 'sort', + 'threshold', + 'theme', + 'signature', + 'signature_format', + 'created', + 'access', + 'login', + 'status', + 'timezone', + 'language', + 'picture', + 'init', + 'data', +)) +->values(array( + 'uid' => 1, + 'name' => '', + 'pass' => '', + 'mail' => '', + 'mode' => '0', + 'sort' => '0', + 'threshold' => '0', + 'theme' => '', + 'signature' => '', + 'signature_format' => '0', + 'created' => '0', + 'access' => '0', + 'login' => '0', + 'status' => '0', + 'timezone' => NULL, + 'language' => '', + 'picture' => '', + 'init' => '', + 'data' => NULL, +)) +->values(array( + 'uid' => 2, + 'name' => 'admin', + 'pass' => '21232f297a57a5a743894a0e4a801fc3', + 'mail' => 'admin@example.com', + 'mode' => '0', + 'sort' => '0', + 'threshold' => '0', + 'theme' => '', + 'signature' => '', + 'signature_format' => '0', + 'created' => '1277671599', + 'access' => '1277671612', + 'login' => '1277671612', + 'status' => '1', + 'timezone' => NULL, + 'language' => '', + 'picture' => '', + 'init' => 'admin@example.com', + 'data' => 'a:0:{}', +)) +->execute(); +db_query('UPDATE {users} SET uid = uid - 1'); + +db_create_table('users_roles', array( + 'fields' => array( + 'uid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'rid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array( + 'uid', + 'rid', + ), + 'indexes' => array( + 'rid' => array( + 'rid', + ), + ), + 'module' => 'user', + 'name' => 'users_roles', +)); + +db_create_table('variable', array( + 'fields' => array( + 'name' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'value' => array( + 'type' => 'text', + 'not null' => TRUE, + 'size' => 'big', + ), + ), + 'primary key' => array( + 'name', + ), + 'module' => 'system', + 'name' => 'variable', +)); +db_insert('variable')->fields(array( + 'name', + 'value', +)) +->values(array( + 'name' => 'clean_url', + 'value' => 's:1:"1";', +)) +->values(array( + 'name' => 'comment_page', + 'value' => 's:21:"COMMENT_NODE_DISABLED";', +)) +->values(array( + 'name' => 'css_js_query_string', + 'value' => 's:20:"D0000000000000000000";', +)) +->values(array( + 'name' => 'date_default_timezone', + 'value' => 's:1:"0";', +)) +->values(array( + 'name' => 'drupal_private_key', + 'value' => 's:64:"3848c2187413fa0ce132f8e222fdb6893b386ed133e8cf602bd3e40dc9dc12db";', +)) +->values(array( + 'name' => 'filter_html_1', + 'value' => 'i:1;', +)) +->values(array( + 'name' => 'install_profile', + 'value' => 's:7:"default";', +)) +->values(array( + 'name' => 'install_task', + 'value' => 's:4:"done";', +)) +->values(array( + 'name' => 'install_time', + 'value' => 'i:1277671612;', +)) +->values(array( + 'name' => 'menu_expanded', + 'value' => 'a:0:{}', +)) +->values(array( + 'name' => 'menu_masks', + 'value' => 'a:13:{i:0;i:31;i:1;i:30;i:2;i:29;i:3;i:24;i:4;i:21;i:5;i:15;i:6;i:14;i:7;i:11;i:8;i:7;i:9;i:5;i:10;i:3;i:11;i:2;i:12;i:1;}', +)) +->values(array( + 'name' => 'node_options_forum', + 'value' => 'a:1:{i:0;s:6:"status";}', +)) +->values(array( + 'name' => 'node_options_page', + 'value' => 'a:1:{i:0;s:6:"status";}', +)) +->values(array( + 'name' => 'site_mail', + 'value' => 's:17:"admin@example.com";', +)) +->values(array( + 'name' => 'site_name', + 'value' => 's:8:"Drupal 6";', +)) +->values(array( + 'name' => 'theme_default', + 'value' => 's:7:"garland";', +)) +->values(array( + 'name' => 'theme_settings', + 'value' => 'a:1:{s:21:"toggle_node_info_page";b:0;}', +)) +->values(array( + 'name' => 'user_email_verification', + 'value' => 'b:1;', +)) +->execute(); + +db_create_table('watchdog', array( + 'fields' => array( + 'wid' => array( + 'type' => 'serial', + 'not null' => TRUE, + ), + 'uid' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'type' => array( + 'type' => 'varchar', + 'length' => 16, + 'not null' => TRUE, + 'default' => '', + ), + 'message' => array( + 'type' => 'text', + 'not null' => TRUE, + 'size' => 'big', + ), + 'variables' => array( + 'type' => 'text', + 'not null' => TRUE, + 'size' => 'big', + ), + 'severity' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'size' => 'tiny', + ), + 'link' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'location' => array( + 'type' => 'text', + 'not null' => TRUE, + ), + 'referer' => array( + 'type' => 'text', + 'not null' => FALSE, + ), + 'hostname' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ), + 'timestamp' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array( + 'wid', + ), + 'indexes' => array( + 'type' => array( + 'type', + ), + ), + 'module' => 'dblog', + 'name' => 'watchdog', +)); + diff --git a/modules/simpletest/tests/upgrade/upgrade.test b/modules/simpletest/tests/upgrade/upgrade.test new file mode 100644 index 00000000..7f2ddd27 --- /dev/null +++ b/modules/simpletest/tests/upgrade/upgrade.test @@ -0,0 +1,351 @@ +<?php +// $Id: upgrade.test,v 1.2 2010/06/29 00:21:00 webchick Exp $ + +/** + * Perform end-to-end tests of the upgrade path. + */ +abstract class UpgradePathTestCase extends DrupalWebTestCase { + + /** + * The file path to the dumped database to load into the child site. + */ + var $databaseDumpFile = NULL; + + /** + * Flag that indicates whether the child site has been upgraded. + */ + var $upgradedSite = FALSE; + + /** + * Array of errors triggered during the upgrade process. + */ + var $upgradeErrors = array(); + + /** + * Override of DrupalWebTestCase::setUp() specialized for upgrade testing. + */ + protected function setUp() { + global $user, $language, $conf; + + // Reset flags. + $this->upgradedSite = FALSE; + $this->upgradeErrors = array(); + + // Generate a temporary prefixed database to ensure that tests have a clean starting point. + $this->databasePrefix = 'simpletest' . mt_rand(1000, 1000000); + db_update('simpletest_test_id') + ->fields(array('last_prefix' => $this->databasePrefix)) + ->condition('test_id', $this->testId) + ->execute(); + + // Clone the current connection and replace the current prefix. + $connection_info = Database::getConnectionInfo('default'); + Database::renameConnection('default', 'simpletest_original_default'); + foreach ($connection_info as $target => $value) { + $connection_info[$target]['prefix'] = array( + 'default' => $value['prefix']['default'] . $this->databasePrefix, + ); + } + Database::addConnectionInfo('default', 'default', $connection_info['default']); + + // Store necessary current values before switching to prefixed database. + $this->originalLanguage = $language; + $this->originalLanguageDefault = variable_get('language_default'); + $this->originalFileDirectory = file_directory_path(); + $this->originalProfile = drupal_get_profile(); + $clean_url_original = variable_get('clean_url', 0); + + // Unregister the registry. + // This is required to make sure that the database layer works properly. + spl_autoload_unregister('drupal_autoload_class'); + spl_autoload_unregister('drupal_autoload_interface'); + + // Create test directories ahead of installation so fatal errors and debug + // information can be logged during installation process. + // Use mock files directories with the same prefix as the database. + $public_files_directory = $this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10); + $private_files_directory = $public_files_directory . '/private'; + $temp_files_directory = $private_files_directory . '/temp'; + + // Create the directories. + file_prepare_directory($public_files_directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); + file_prepare_directory($private_files_directory, FILE_CREATE_DIRECTORY); + file_prepare_directory($temp_files_directory, FILE_CREATE_DIRECTORY); + $this->generatedTestFiles = FALSE; + + // Log fatal errors. + ini_set('log_errors', 1); + ini_set('error_log', $public_files_directory . '/error.log'); + + // Reset all statics and variables to perform tests in a clean environment. + $conf = array(); + + // Load the database from the portable PHP dump. + require $this->databaseDumpFile; + + // Set path variables. + $this->variable_set('file_public_path', $public_files_directory); + $this->variable_set('file_private_path', $private_files_directory); + $this->variable_set('file_temporary_path', $temp_files_directory); + + $this->pass('Finished loading the dump.'); + + // Load user 1. + $this->originalUser = $user; + drupal_save_session(FALSE); + $user = db_query('SELECT * FROM {users} WHERE uid = :uid', array(':uid' => 1))->fetchObject(); + + // Generate and set a session cookie. + $this->curlInitialize(); + $sid = drupal_hash_base64(uniqid(mt_rand(), TRUE) . drupal_random_bytes(55)); + curl_setopt($this->curlHandle, CURLOPT_COOKIE, rawurlencode($this->session_name) . '=' . rawurlencode($sid)); + + // Force our way into the session of the child site. + drupal_save_session(TRUE); + _drupal_session_write($sid, ''); + drupal_save_session(FALSE); + + // Restore necessary variables. + $this->variable_set('clean_url', $clean_url_original); + $this->variable_set('site_mail', 'simpletest@example.com'); + + drupal_set_time_limit($this->timeLimit); + } + + /** + * Override of DrupalWebTestCase::tearDown() specialized for upgrade testing. + */ + protected function tearDown() { + global $user, $language; + + // In case a fatal error occured that was not in the test process read the + // log to pick up any fatal errors. + simpletest_log_read($this->testId, $this->databasePrefix, get_class($this), TRUE); + + // Delete temporary files directory. + file_unmanaged_delete_recursive($this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10)); + + // Get back to the original connection. + Database::removeConnection('default'); + Database::renameConnection('simpletest_original_default', 'default'); + + // Remove all prefixed tables. + $tables = db_find_tables($this->databasePrefix . '%'); + foreach ($tables as $table) { + db_drop_table($table); + } + + // Return the user to the original one. + $user = $this->originalUser; + drupal_save_session(TRUE); + + // Ensure that internal logged in variable and cURL options are reset. + $this->loggedInUser = FALSE; + $this->additionalCurlOptions = array(); + + // Reload module list and implementations to ensure that test module hooks + // aren't called after tests. + module_list(TRUE); + module_implements('', FALSE, TRUE); + + // Reset the Field API. + field_cache_clear(); + + // Rebuild caches. + parent::refreshVariables(); + + // Reset language. + $language = $this->originalLanguage; + if ($this->originalLanguageDefault) { + $GLOBALS['conf']['language_default'] = $this->originalLanguageDefault; + } + + // Close the CURL handler. + $this->curlClose(); + } + + /** + * Specialized variable_set() that works even if the child site is not upgraded. + * + * @param $name + * The name of the variable to set. + * @param $value + * The value to set. This can be any PHP data type; these functions take care + * of serialization as necessary. + */ + protected function variable_set($name, $value) { + db_delete('variable') + ->condition('name', $name) + ->execute(); + db_insert('variable') + ->fields(array( + 'name' => $name, + 'value' => serialize($value), + )) + ->execute(); + + try { + cache_clear_all('variables', 'cache'); + cache_clear_all('variables', 'cache_bootstrap'); + } + // Since cache_bootstrap won't exist in a Drupal 6 site, ignore the + // exception if the above fails. + catch (Exception $e) {} + } + + /** + * Specialized refreshVariables(). + */ + protected function refreshVariables() { + // No operation if the child has not been upgraded yet. + if (!$this->upgradedSite) { + return parent::refreshVariables(); + } + } + + /** + * Perform the upgrade. + * + * @param $register_errors + * Register the errors during the upgrade process as failures. + * @return + * TRUE if the upgrade succeeded, FALSE otherwise. + */ + protected function performUpgrade($register_errors = TRUE) { + $update_url = $GLOBALS['base_url'] . '/update.php'; + + // Load the first update screen. + $this->drupalGet($update_url, array('external' => TRUE)); + if (!$this->assertResponse(200)) { + return FALSE; + } + + // Continue. + $this->drupalPost(NULL, array(), t('Continue')); + if (!$this->assertResponse(200)) { + return FALSE; + } + + // Go! + $this->drupalPost(NULL, array(), t('Apply pending updates')); + if (!$this->assertResponse(200)) { + return FALSE; + } + + // Check for errors during the update process. + foreach ($this->xpath('//li[@class=:class]', array(':class' => 'failure')) as $element) { + $message = strip_tags($element->asXML()); + $this->upgradeErrors[] = $message; + if ($register_errors) { + $this->fail($message); + } + } + + if (!empty($this->upgradeErrors)) { + // Upgrade failed, the installation might be in an inconsistent state, + // don't process. + return FALSE; + } + + // Check if there still are pending updates. + $this->drupalGet($update_url, array('external' => TRUE)); + $this->drupalPost(NULL, array(), t('Continue')); + if (!$this->assertText(t('No pending updates.'), t('No pending updates at the end of the update process.'))) { + return FALSE; + } + + // Upgrade succeed, rebuild the environment so that we can call the API + // of the child site directly from this request. + $this->upgradedSite = TRUE; + + // Reload module list and implementations. + module_list(TRUE); + module_implements('', FALSE, TRUE); + + // Rebuild caches. + drupal_static_reset(); + drupal_flush_all_caches(); + + // Register actions declared by any modules. + actions_synchronize(); + + // Reload global $conf array and permissions. + $this->refreshVariables(); + $this->checkPermissions(array(), TRUE); + + return TRUE; + } + +} + +/** + * Perform basic upgrade tests. + * + * Load a bare installation of Drupal 6 and run the upgrade process on it. + * + * The install only contains dblog (although it's optional, it's only so that + * another hook_watchdog module can take its place, the site is not functional + * without watchdog) and update. + */ +class BasicUpgradePath extends UpgradePathTestCase { + public static function getInfo() { + return array( + 'name' => 'Basic upgrade path', + 'description' => 'Basic upgrade path tests.', + 'group' => 'Upgrade path', + ); + } + + public function setUp() { + // Path to the database dump. + $this->databaseDumpFile = drupal_get_path('module', 'simpletest') . '/tests/upgrade/drupal-6.bare.database.php'; + parent::setUp(); + } + + /** + * Test a failed upgrade, and verify that the failure is reported. + */ + public function testFailedUpgrade() { + // Destroy a table that the upgrade process needs. + db_drop_table('access'); + // Assert that the upgrade fails. + $this->assertFalse($this->performUpgrade(FALSE), t('A failed upgrade should return messages.')); + } + + /** + * Test a successful upgrade. + */ + public function testBasicUpgrade() { + $this->assertTrue($this->performUpgrade(), t('The upgrade was completed successfully.')); + + // Hit the frontpage. + $this->drupalGet(''); + $this->assertResponse(200); + + // Verify that we are still logged in. + $this->drupalGet('user'); + $this->clickLink(t('Edit')); + $this->assertEqual($this->getUrl(), url('user/1/edit', array('absolute' => TRUE)), t('We are still logged in as admin at the end of the upgrade.')); + + // Logout and verify that we can login back in with our initial password. + $this->drupalLogout(); + $this->drupalLogin((object) array( + 'uid' => 1, + 'name' => 'admin', + 'pass_raw' => 'admin', + )); + + // Test that the site name is correctly displayed. + $this->assertText('Drupal 6', t('The site name is correctly displayed.')); + + // Verify that the main admin sections are available. + $this->drupalGet('admin'); + $this->assertText(t('Content')); + $this->assertText(t('Appearance')); + $this->assertText(t('People')); + $this->assertText(t('Configuration')); + $this->assertText(t('Reports')); + $this->assertText(t('Structure')); + $this->assertText(t('Modules')); + } +} diff --git a/modules/simpletest/tests/url_alter_test.info b/modules/simpletest/tests/url_alter_test.info index a171d908..7b825d60 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/simpletest/tests/xmlrpc_test.info b/modules/simpletest/tests/xmlrpc_test.info index 244b58f6..d923ef3f 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/statistics/statistics.admin.inc b/modules/statistics/statistics.admin.inc index 9de0b3a7..0885cde5 100644 --- a/modules/statistics/statistics.admin.inc +++ b/modules/statistics/statistics.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: statistics.admin.inc,v 1.39 2010/02/28 20:10:34 dries Exp $ +// $Id: statistics.admin.inc,v 1.40 2010/06/26 21:32:20 dries Exp $ /** * @file @@ -248,13 +248,13 @@ function statistics_settings_form() { $form['access']['statistics_enable_access_log'] = array( '#type' => 'checkbox', '#title' => t('Enable access log'), - '#default_value' => 0, + '#default_value' => variable_get('statistics_enable_access_log', 0), '#description' => t('Log each page access. Required for referrer statistics.'), ); $form['access']['statistics_flush_accesslog_timer'] = array( '#type' => 'select', '#title' => t('Discard access logs older than'), - '#default_value' => 259200, + '#default_value' => variable_get('statistics_flush_accesslog_timer', 259200), '#options' => array(0 => t('Never')) + drupal_map_assoc(array(3600, 10800, 21600, 32400, 43200, 86400, 172800, 259200, 604800, 1209600, 2419200, 4838400, 9676800), 'format_interval'), '#description' => t('Older access log entries (including referrer statistics) will be automatically discarded. (Requires a correctly configured <a href="@cron">cron maintenance task</a>.)', array('@cron' => url('admin/reports/status'))), ); @@ -267,7 +267,7 @@ function statistics_settings_form() { $form['content']['statistics_count_content_views'] = array( '#type' => 'checkbox', '#title' => t('Count content views'), - '#default_value' => 0, + '#default_value' => variable_get('statistics_count_content_views', 0), '#description' => t('Increment a counter each time content is viewed.'), ); diff --git a/modules/statistics/statistics.info b/modules/statistics/statistics.info index 5922dd37..2031fa1a 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/statistics/statistics.module b/modules/statistics/statistics.module index 08d8612c..8b059f7e 100644 --- a/modules/statistics/statistics.module +++ b/modules/statistics/statistics.module @@ -1,5 +1,5 @@ <?php -// $Id: statistics.module,v 1.335 2010/05/18 18:26:30 dries Exp $ +// $Id: statistics.module,v 1.336 2010/05/29 07:53:44 dries Exp $ /** * @file @@ -350,21 +350,24 @@ function statistics_block_view($delta = '') { $daytop = variable_get('statistics_block_top_day_num', 0); if ($daytop && ($result = statistics_title_list('daycount', $daytop)) && ($node_title_list = node_title_list($result, t("Today's:")))) { - $content[] = $node_title_list; + $content['top_day'] = $node_title_list; + $content['top_day']['#suffix'] = '<br />'; } $alltimetop = variable_get('statistics_block_top_all_num', 0); if ($alltimetop && ($result = statistics_title_list('totalcount', $alltimetop)) && ($node_title_list = node_title_list($result, t('All time:')))) { - $content[] = $node_title_list; + $content['top_all'] = $node_title_list; + $content['top_all']['#suffix'] = '<br />'; } $lasttop = variable_get('statistics_block_top_last_num', 0); if ($lasttop && ($result = statistics_title_list('timestamp', $lasttop)) && ($node_title_list = node_title_list($result, t('Last viewed:')))) { - $content[] = $node_title_list; + $content['top_last'] = $node_title_list; + $content['top_last']['#suffix'] = '<br />'; } if (count($content)) { - $block['content'] = implode('<br />', $content); + $block['content'] = $content; $block['subject'] = t('Popular content'); return $block; } diff --git a/modules/statistics/statistics.test b/modules/statistics/statistics.test index e35c6ada..2548d86a 100644 --- a/modules/statistics/statistics.test +++ b/modules/statistics/statistics.test @@ -1,5 +1,5 @@ <?php -// $Id: statistics.test,v 1.18 2010/05/18 18:26:30 dries Exp $ +// $Id: statistics.test,v 1.20 2010/07/08 03:41:27 webchick Exp $ /** * Sets up a base class for the Statistics module. @@ -165,7 +165,7 @@ class StatisticsReportsTestCase extends StatisticsTestCase { // Configure and save the block. $block = block_load('statistics', 'popular'); - $block->theme = 'garland'; + $block->theme = variable_get('theme_default', 'bartik'); $block->status = 1; $block->pages = ''; $block->region = 'sidebar_first'; @@ -217,7 +217,7 @@ class StatisticsBlockVisitorsTestCase extends StatisticsTestCase { $this->assertText(t('IP address blocking'), t('IP blocking page displayed.')); $edit = array(); $edit['ip'] = $test_ip_address; - $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Save')); + $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add')); $ip = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $edit['ip']))->fetchField(); $this->assertNotEqual($ip, FALSE, t('IP address found in database')); $this->assertRaw(t('The IP address %ip has been blocked.', array('%ip' => $edit['ip'])), t('IP address was blocked.')); diff --git a/modules/syslog/syslog.info b/modules/syslog/syslog.info index 40efff40..6cff42ea 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/system/html.tpl.php b/modules/system/html.tpl.php index 988b4b6f..7ef8fc20 100644 --- a/modules/system/html.tpl.php +++ b/modules/system/html.tpl.php @@ -1,5 +1,5 @@ <?php -// $Id: html.tpl.php,v 1.3 2009/11/16 05:34:24 webchick Exp $ +// $Id: html.tpl.php,v 1.4 2010/05/26 10:47:20 dries Exp $ /** * @file @@ -35,8 +35,7 @@ */ ?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php print $language->language; ?>" version="XHTML+RDFa 1.0" dir="<?php print $language->dir; ?>" - <?php print $rdf_namespaces; ?>> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php print $language->language; ?>" version="XHTML+RDFa 1.0" dir="<?php print $language->dir; ?>"<?php print $rdf_namespaces; ?>> <head profile="<?php print $grddl_profile; ?>"> <?php print $head; ?> diff --git a/modules/system/system-behavior-rtl.css b/modules/system/system-behavior-rtl.css index 06c03fb1..1158f6e4 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.3 2010/04/28 20:08:39 dries Exp $ */ +/* $Id: system-behavior-rtl.css,v 1.4 2010/06/20 17:34:51 webchick Exp $ */ /** * Autocomplete @@ -75,6 +75,9 @@ div.tree-child, div.tree-child-last { background-position: -65px center; } +.tabledrag-toggle-weight-wrapper { + text-align: left; /* RTL */ +} /** * Multiselect form diff --git a/modules/system/system-behavior.css b/modules/system/system-behavior.css index 14ed433f..19242abe 100644 --- a/modules/system/system-behavior.css +++ b/modules/system/system-behavior.css @@ -1,4 +1,4 @@ -/* $Id: system-behavior.css,v 1.9 2010/04/28 20:08:39 dries Exp $ */ +/* $Id: system-behavior.css,v 1.11 2010/06/26 20:10:24 dries Exp $ */ /** * Autocomplete @@ -130,6 +130,12 @@ div.tree-child-last { div.tree-child-horizontal { background: url(../../misc/tree.png) no-repeat -11px center; } +.tabledrag-toggle-weight-wrapper { + text-align: right; /* LTR */ +} +.tabledrag-toggle-weight { + font-size: 0.9em; +} /** * Progress bar @@ -296,10 +302,11 @@ html.js .js-hide { * the site where visual display is undesirable. Information provided in this * manner should be kept concise, to avoid unnecessary burden on the user. Must * not be used for focusable elements (such as links and form elements) as this - * causes issues for keyboard only or voice recognition users. + * causes issues for keyboard only or voice recognition users. "!important" is + * used to prevent unintentional overrides. */ .element-invisible { - height: 0; - overflow: hidden; - position: absolute; + position: absolute !important; + clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ + clip: rect(1px, 1px, 1px, 1px); } diff --git a/modules/system/system-messages.css b/modules/system/system-messages.css index 90d43d07..e68b0b8a 100644 --- a/modules/system/system-messages.css +++ b/modules/system/system-messages.css @@ -33,4 +33,4 @@ div.ok, tr.ok { background-color: #dfd; color: #020; -} \ No newline at end of file +} diff --git a/modules/system/system.admin.inc b/modules/system/system.admin.inc index 263b0630..e81150b3 100644 --- a/modules/system/system.admin.inc +++ b/modules/system/system.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: system.admin.inc,v 1.284 2010/05/21 11:37:55 dries Exp $ +// $Id: system.admin.inc,v 1.292 2010/07/08 03:41:27 webchick Exp $ /** * @file @@ -147,7 +147,7 @@ function system_admin_menu_block_page() { 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] = new stdClass(); $module_info[$module]->info = $info; } uasort($module_info, 'system_sort_modules_by_info_name'); @@ -200,10 +200,10 @@ function system_settings_overview() { */ function system_themes_page() { // Get current list of themes. - $themes = list_themes(); + $themes = system_rebuild_theme_data(); uasort($themes, 'system_sort_modules_by_info_name'); - $theme_default = variable_get('theme_default', 'garland'); + $theme_default = variable_get('theme_default', 'bartik'); $theme_groups = array(); foreach ($themes as &$theme) { @@ -389,7 +389,7 @@ function system_theme_disable() { // Check if the specified theme is one recognized by the system. if (!empty($themes[$theme])) { - if ($theme == variable_get('theme_default', 'garland')) { + if ($theme == variable_get('theme_default', 'bartik')) { // Don't disable the default theme. drupal_set_message(t('%theme is the default theme and cannot be disabled.', array('%theme' => $themes[$theme]->info['name'])), 'error'); } @@ -643,7 +643,7 @@ function system_theme_settings($form, &$form_state, $key = '') { } } - $form = system_settings_form($form, FALSE); + $form = system_settings_form($form); // We don't want to call system_settings_form_submit(), so change #submit. array_pop($form['#submit']); $form['#submit'][] = 'system_theme_settings_submit'; @@ -828,7 +828,7 @@ function system_modules($form, $form_state = array()) { // Remove hidden modules from display list. $visible_files = $files; foreach ($visible_files as $filename => $file) { - if (!empty($file->info['hidden']) || !empty($file->info['required'])) { + if (!empty($file->info['hidden'])) { unset($visible_files[$filename]); } } @@ -849,10 +849,19 @@ function system_modules($form, $form_state = array()) { // Used when checking if module implements a help page. $help_arg = module_exists('help') ? drupal_help_arg() : FALSE; + // Used when displaying modules that are required by the install profile. + require_once DRUPAL_ROOT . '/includes/install.inc'; + $distribution_name = check_plain(drupal_install_profile_distribution_name()); + // Iterate through each of the modules. foreach ($visible_files as $filename => $module) { $extra = array(); $extra['enabled'] = (bool) $module->status; + if (!empty($module->info['required'] )) { + $extra['disabled'] = TRUE; + $extra['required_by'][] = $distribution_name; + } + // If this module requires other modules, add them to the array. foreach ($module->requires as $requires => $v) { if (!isset($files[$requires])) { @@ -929,6 +938,7 @@ function system_modules($form, $form_state = array()) { } $form['modules'][$module->info['package']][$filename] = _system_modules_build_row($module->info, $extra); } + // Add basic information to the fieldsets. foreach (element_children($form['modules']) as $package) { $form['modules'][$package] += array( @@ -1070,7 +1080,7 @@ function system_modules_confirm_form($modules, $storage) { $form['validation_modules'] = array('#type' => 'value', '#value' => $modules); $form['status']['#tree'] = TRUE; - foreach ($storage['more_modules'] as $info) { + foreach ($storage['more_required'] as $info) { $t_argument = array( '@module' => $info['name'], '@required' => implode(', ', $info['requires']), @@ -1106,9 +1116,12 @@ function system_modules_confirm_form($modules, $storage) { */ function system_modules_submit($form, &$form_state) { include_once DRUPAL_ROOT . '/includes/install.inc'; + + // Builds list of modules. $modules = array(); // If we're not coming from the confirmation form, build the list of modules. if (empty($form_state['storage'])) { + // If we're not coming from the confirmation form, build the module list. foreach ($form_state['values']['modules'] as $group_name => $group) { foreach ($group as $module => $enabled) { $modules[$module] = array('group' => $group_name, 'enabled' => $enabled['enable']); @@ -1121,114 +1134,108 @@ function system_modules_submit($form, &$form_state) { $modules = $form_state['storage']['modules']; } - // Get a list of all modules, it will be used to find which module requires - // which. + // Collect data for all modules to be able to determine dependencies. $files = system_rebuild_module_data(); - // The modules to be enabled. - $modules_to_be_enabled = array(); - // The modules to be disabled. - $disable_modules = array(); - // The modules to be installed. - $new_modules = array(); - // Modules that need to be switched on because other modules require them. - $more_modules = array(); - $missing_modules = array(); + // Sorts modules by weight. + $sort = array(); + foreach (array_keys($modules) as $module) { + $sort[$module] = $files[$module]->sort; + } + array_multisort($sort, $modules); - // Go through each module, finding out if we should enable, install, or - // disable it. Also, we find out if there are modules it requires that are - // not enabled. + // Makes sure all required modules are set to be enabled. + $more_required = array(); + $missing_modules = array(); foreach ($modules as $name => $module) { - // If it's enabled, find out whether to just - // enable it, or install it. if ($module['enabled']) { - if (drupal_get_installed_schema_version($name) == SCHEMA_UNINSTALLED) { - $new_modules[$name] = $name; - } - elseif (!module_exists($name)) { - $modules_to_be_enabled[$name] = $name; - } - - // If we're not coming from a confirmation form, search for modules the - // new ones require and see whether there are any that additionally - // need to be switched on. - if (empty($form_state['storage'])) { - foreach ($form['modules'][$module['group']][$name]['#requires'] as $requires => $v) { - if (!isset($files[$requires])) { - // The required module is missing, mark this module as disabled. - $missing_modules[$requires]['depends'][] = $name; - $modules[$name]['enabled'] = FALSE; + // Checks that all dependencies are set to be enabled. Stores the ones + // that are not in $dependencies variable so that the user can be alerted + // in the confirmation form that more modules need to be enabled. + $dependencies = array(); + foreach (array_keys($files[$name]->requires) as $required) { + if (empty($modules[$required]['enabled'])) { + if (isset($files[$required])) { + $dependencies[] = $files[$required]->info['name']; + $modules[$required]['enabled'] = TRUE; } else { - if (!$modules[$requires]['enabled']) { - if (!isset($more_modules[$name])) { - $more_modules[$name]['name'] = $files[$name]->info['name']; - } - $more_modules[$name]['requires'][$requires] = $files[$requires]->info['name']; - } - $modules[$requires] = array('group' => $files[$requires]->info['package'], 'enabled' => TRUE); + $missing_modules[$required]['depends'][] = $name; + $modules[$name]['enabled'] = FALSE; } } } - } - } - // A second loop is necessary, otherwise the modules set to be enabled in the - // previous loop would not be found. - foreach ($modules as $name => $module) { - if (module_exists($name) && !$module['enabled']) { - $disable_modules[$name] = $name; + + // Stores additional modules that need to be enabled in $more_required. + if (!empty($dependencies)) { + $more_required[$name] = array( + 'name' => $files[$name]->info['name'], + 'requires' => $dependencies, + ); + } } } - if ($more_modules || $missing_modules) { - // If we need to switch on more modules because other modules require - // them and they haven't confirmed, don't process the submission yet. Store - // the form submission data needed later. + // Redirects to confirmation form if more modules need to be enabled. + if ((!empty($more_required) || !empty($missing_modules)) && !isset($form_state['values']['confirm'])) { $form_state['storage'] = array( - 'more_modules' => $more_modules, + 'more_required' => $more_required, + 'modules' => $modules, 'missing_modules' => $missing_modules, - 'modules' => $modules ); $form_state['rebuild'] = TRUE; return; } - $old_module_list = module_list(); - - // Enable the modules needing enabling. - if (!empty($modules_to_be_enabled)) { - $sort = array(); - foreach ($modules_to_be_enabled as $module) { - $sort[$module] = $files[$module]->sort; - } - array_multisort($sort, SORT_DESC, $modules_to_be_enabled); - module_enable($modules_to_be_enabled, FALSE); - } - // Disable the modules that need disabling. - if (!empty($disable_modules)) { - $sort = array(); - foreach ($disable_modules as $module) { - $sort[$module] = $files[$module]->sort; + // Invokes hook_requirements('install'). If failures are detected, makes sure + // the dependent modules aren't installed either. + foreach ($modules as $name => $module) { + // Only invoke hook_requirements() on modules that are going to be installed. + if ($module['enabled'] && drupal_get_installed_schema_version($name) == SCHEMA_UNINSTALLED) { + if (!drupal_check_module($name)) { + $modules[$name]['enabled'] = FALSE; + foreach (array_keys($files[$name]->required_by) as $required_by) { + $modules[$required_by]['enabled'] = FALSE; + } + } } - array_multisort($sort, SORT_ASC, $disable_modules); - module_disable($disable_modules, FALSE); } - // Install new modules. - if (!empty($new_modules)) { - $sort = array(); - foreach ($new_modules as $key => $module) { - if (!drupal_check_module($module)) { - unset($new_modules[$key]); + // Initializes array of actions. + $actions = array( + 'enable' => array(), + 'disable' => array(), + 'install' => array(), + ); + + // Builds arrays of modules that need to be enabled, disabled, and installed. + foreach ($modules as $name => $module) { + if ($module['enabled']) { + if (drupal_get_installed_schema_version($name) == SCHEMA_UNINSTALLED) { + $actions['install'][] = $name; } - $sort[$module] = $files[$module]->sort; + elseif (!module_exists($name)) { + $actions['enable'][] = $name; + } + } + elseif (module_exists($name)) { + $actions['disable'][] = $name; } - array_multisort($sort, SORT_DESC, $new_modules); - module_enable($new_modules, FALSE); } - $current_module_list = module_list(TRUE); - if ($old_module_list != $current_module_list) { + // Gets list of modules prior to install process, unsets $form_state['storage'] + // so we don't get redirected back to the confirmation form. + $pre_install_list = module_list(); + unset($form_state['storage']); + + // Installs, enables, and disables modules. + module_enable($actions['enable']); + module_disable($actions['disable']); + module_enable($actions['install']); + + // Gets module list after install process, displays message if there are changes. + $post_install_list = module_list(TRUE); + if ($pre_install_list != $post_install_list) { drupal_set_message(t('The configuration options have been saved.')); } @@ -1248,7 +1255,7 @@ function system_modules_submit($form, &$form_state) { // Notify locale module about module changes, so translations can be // imported. This might start a batch, and only return to the redirect // path after that. - module_invoke('locale', 'system_update', $new_modules); + module_invoke('locale', 'system_update', $actions['install']); // Synchronize to catch any actions that were added or removed. actions_synchronize(); @@ -1398,7 +1405,7 @@ function system_modules_uninstall_submit($form, &$form_state) { function system_ip_blocking($default_ip = '') { $output = ''; $rows = array(); - $header = array(t('IP address'), t('Operations')); + $header = array(t('Blocked IP addresses'), t('Operations')); $result = db_query('SELECT * FROM {blocked_ips}'); foreach ($result as $ip) { $rows[] = array( @@ -1429,15 +1436,15 @@ function system_ip_blocking_form($form, $form_state, $default_ip) { $form['ip'] = array( '#title' => t('IP address'), '#type' => 'textfield', - '#size' => 64, - '#maxlength' => 32, + '#size' => 48, + '#maxlength' => 40, '#default_value' => $default_ip, '#description' => t('Enter a valid IP address.'), ); $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array( '#type' => 'submit', - '#value' => t('Save'), + '#value' => t('Add'), ); $form['#submit'][] = 'system_ip_blocking_form_submit'; $form['#validate'][] = 'system_ip_blocking_form_validate'; @@ -1560,7 +1567,7 @@ function system_site_information_settings() { $form['#validate'][] = 'system_site_information_settings_validate'; - return system_settings_form($form, FALSE); + return system_settings_form($form); } /** @@ -1589,7 +1596,7 @@ function system_logging_settings() { $form['error_level'] = array( '#type' => 'radios', '#title' => t('Error messages to display'), - '#default_value' => ERROR_REPORTING_DISPLAY_ALL, + '#default_value' => variable_get('error_level', ERROR_REPORTING_DISPLAY_ALL), '#options' => array( ERROR_REPORTING_HIDE => t('None'), ERROR_REPORTING_DISPLAY_SOME => t('Errors and warnings'), @@ -1688,7 +1695,7 @@ function system_performance_settings() { $form['#submit'][] = 'drupal_clear_css_cache'; $form['#submit'][] = 'drupal_clear_js_cache'; - return system_settings_form($form, FALSE); + return system_settings_form($form); } /** @@ -1708,11 +1715,10 @@ function system_clear_cache_submit($form, &$form_state) { * @see system_settings_form() */ function system_file_system_settings() { - $form['file_public_path'] = array( '#type' => 'textfield', '#title' => t('Public file system path'), - '#default_value' => file_directory_path(), + '#default_value' => variable_get('file_public_path', file_directory_path()), '#maxlength' => 255, '#description' => t('A local file system path where public files will be stored. This directory must exist and be writable by Drupal. This directory must be relative to the Drupal installation directory and be accessible over the web.'), '#after_build' => array('system_check_directory'), @@ -1721,7 +1727,7 @@ function system_file_system_settings() { $form['file_private_path'] = array( '#type' => 'textfield', '#title' => t('Private file system path'), - '#default_value' => file_directory_path('private'), + '#default_value' => variable_get('file_private_path', file_directory_path('private')), '#maxlength' => 255, '#description' => t('A local file system path where private files will be stored. This directory must exist and be writable by Drupal. This directory should not be accessible over the web.'), '#after_build' => array('system_check_directory'), @@ -1730,7 +1736,7 @@ function system_file_system_settings() { $form['file_temporary_path'] = array( '#type' => 'textfield', '#title' => t('Temporary directory'), - '#default_value' => file_directory_path('temporary'), + '#default_value' => variable_get('file_temporary_path', file_directory_path('temporary')), '#maxlength' => 255, '#description' => t('A local file system path where temporary files will be stored. This directory should not be accessible over the web.'), '#after_build' => array('system_check_directory'), @@ -1745,13 +1751,13 @@ function system_file_system_settings() { $form['file_default_scheme'] = array( '#type' => 'radios', '#title' => t('Default download method'), - '#default_value' => isset($options['public']) ? 'public' : key($options), + '#default_value' => variable_get('file_default_scheme', 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.'), ); } - return system_settings_form($form, TRUE); + return system_settings_form($form); } /** @@ -1776,7 +1782,7 @@ function system_image_toolkit_settings() { $form['image_toolkit'] = array( '#type' => 'radios', '#title' => t('Select an image processing toolkit'), - '#default_value' => $current_toolkit, + '#default_value' => variable_get('image_toolkit', $current_toolkit), '#options' => $toolkits_available ); } @@ -1790,7 +1796,7 @@ function system_image_toolkit_settings() { $form['image_toolkit_settings'] = $function(); } - return system_settings_form($form, TRUE); + return system_settings_form($form); } /** @@ -1800,29 +1806,28 @@ function system_image_toolkit_settings() { * @see system_settings_form() */ function system_rss_feeds_settings() { - $form['feed_description'] = array( '#type' => 'textarea', '#title' => t('Feed description'), - '#default_value' => '', + '#default_value' => variable_get('feed_description', ''), '#description' => t('Description of your site, included in each feed.') ); $form['feed_default_items'] = array( '#type' => 'select', '#title' => t('Number of items in each feed'), - '#default_value' => 10, + '#default_value' => variable_get('feed_default_items', 10), '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30)), '#description' => t('Default number of items to include in each feed.') ); $form['feed_item_length'] = array( '#type' => 'select', '#title' => t('Feed content'), - '#default_value' => 'fulltext', + '#default_value' => variable_get('feed_item_length', 'fulltext'), '#options' => array('title' => t('Titles only'), 'teaser' => t('Titles plus teaser'), 'fulltext' => t('Full text')), '#description' => t('Global setting for the default display of content items in each feed.') ); - return system_settings_form($form, TRUE); + return system_settings_form($form); } /** @@ -1909,7 +1914,7 @@ function system_regional_settings() { '#description' => t('Only applied if users may set their own time zone.') ); - return system_settings_form($form, FALSE); + return system_settings_form($form); } /** @@ -1984,7 +1989,7 @@ function system_date_time_settings() { // Display a message if no date types configured. $form['#empty_text'] = t('No date types available. <a href="@link">Add date type</a>.', array('@link' => url('admin/config/regional/date-time/types/add'))); - return system_settings_form($form, FALSE); + return system_settings_form($form); } /** @@ -2145,17 +2150,17 @@ function system_site_maintenance_mode() { $form['maintenance_mode'] = array( '#type' => 'checkbox', '#title' => t('Put site into maintenance mode'), - '#default_value' => 0, + '#default_value' => variable_get('maintenance_mode', 0), '#description' => t('When enabled, only users with the "Access site in maintenance mode" <a href="@permissions-url">permission</a> are able to access your site to perform maintenance; all other visitors see the maintenance mode message configured below. Authorized users can log in directly via the <a href="@user-login">user login</a> page.', array('@permissions-url' => url('admin/people/permissions'), '@user-login' => url('user'))), ); $form['maintenance_mode_message'] = array( '#type' => 'textarea', '#title' => t('Maintenance mode message'), - '#default_value' => t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))), + '#default_value' => variable_get('maintenance_mode_message', t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))), '#description' => t('Message to show visitors when the site is in maintenance mode.') ); - return system_settings_form($form, TRUE); + return system_settings_form($form); } /** @@ -2186,7 +2191,7 @@ function system_clean_url_settings($form, &$form_state) { $form['clean_url'] = array( '#type' => 'checkbox', '#title' => t('Enable clean URLs'), - '#default_value' => 0, + '#default_value' => variable_get('clean_url', 0), '#description' => t('Use URLs like <code>example.com/user</code> instead of <code>example.com/?q=user</code>.'), ); $form = system_settings_form($form); @@ -2784,6 +2789,7 @@ function system_configure_date_formats_form($form, &$form_state, $dfid = 0) { '#attached' => array( 'js' => array(drupal_get_path('module', 'system') . '/system.js', $js_settings), ), + '#required' => TRUE, ); $form['actions'] = array('#type' => 'actions'); diff --git a/modules/system/system.api.php b/modules/system/system.api.php index 9ab12228..26be3714 100644 --- a/modules/system/system.api.php +++ b/modules/system/system.api.php @@ -1,5 +1,5 @@ <?php -// $Id: system.api.php,v 1.167 2010/05/07 12:59:07 dries Exp $ +// $Id: system.api.php,v 1.178 2010/07/07 08:05:01 webchick Exp $ /** * @file @@ -67,9 +67,6 @@ function hook_hook_info_alter(&$hooks) { * can be loaded via entity_load() and, optionally, to which fields can be * attached). * - * @see entity_load() - * @see hook_entity_info_alter() - * * @return * An array whose keys are entity type names and whose values identify * properties of those types that the system needs to know about: @@ -140,9 +137,24 @@ function hook_hook_info_alter(&$hooks) { * ('full' mode), on the home page or taxonomy listings ('teaser' mode), or * in an RSS feed ('rss' mode). Modules taking part in the display of the * entity (notably the Field API) can adjust their behavior depending on - * the requested view mode. Keys of the array are view mode names. Each - * view mode is described by an array with the following key/value pairs: + * the requested view mode. An additional 'default' view mode is available + * for all entity types. This view mode is not intended for actual entity + * display, but holds default display settings. For each available view + * mode, administrators can configure whether it should use its own set of + * field display settings, or just replicate the settings of the 'default' + * view mode, thus reducing the amount of display configurations to keep + * track of. Keys of the array are view mode names. Each view mode is + * described by an array with the following key/value pairs: * - label: The human-readable name of the view mode + * - custom settings: A boolean specifying whether the view mode should by + * default use its own custom field display settings. If FALSE, entities + * displayed in this view mode will reuse the 'default' display settings by + * default (e.g. right after the module exposing the view mode is enabled), + * but administrators can later use the Field UI to apply custom display + * settings specific to the view mode. + * + * @see entity_load() + * @see hook_entity_info_alter() */ function hook_entity_info() { $return = array( @@ -151,7 +163,7 @@ function hook_entity_info() { 'controller class' => 'NodeController', 'base table' => 'node', 'revision table' => 'node_revision', - 'path callback' => 'node_path', + 'uri callback' => 'node_uri', 'fieldable' => TRUE, 'entity keys' => array( 'id' => 'nid', @@ -164,13 +176,16 @@ function hook_entity_info() { 'bundles' => array(), 'view modes' => array( 'full' => array( - 'label' => t('Full node'), + 'label' => t('Full content'), + 'custom settings' => FALSE, ), 'teaser' => array( 'label' => t('Teaser'), + 'custom settings' => TRUE, ), 'rss' => array( 'label' => t('RSS'), + 'custom settings' => FALSE, ), ), ), @@ -182,9 +197,11 @@ function hook_entity_info() { $return['node']['view modes'] += array( 'search_index' => array( 'label' => t('Search index'), + 'custom settings' => FALSE, ), 'search_result' => array( 'label' => t('Search result'), + 'custom settings' => FALSE, ), ); } @@ -213,10 +230,10 @@ function hook_entity_info() { * entity. All properties that are available in hook_entity_info() can be * altered here. * - * @see hook_entity_info() - * * @param $entity_info * The entity info array, keyed by entity name. + * + * @see hook_entity_info() */ function hook_entity_info_alter(&$entity_info) { // Set the controller class for nodes to an alternate implementation of the @@ -244,8 +261,6 @@ function hook_entity_load($entities, $type) { /** * Act on entities when inserted. * - * Generic insert hook called for all entity types via entity_invoke(). - * * @param $entity * The entity object. * @param $type @@ -257,8 +272,6 @@ function hook_entity_insert($entity, $type) { /** * Act on entities when updated. * - * Generic update hook called for all entity types via entity_invoke(). - * * @param $entity * The entity object. * @param $type @@ -267,6 +280,25 @@ function hook_entity_insert($entity, $type) { function hook_entity_update($entity, $type) { } +/** + * Alter or execute an EntityFieldQuery. + * + * @param EntityFieldQuery $query + * An EntityFieldQuery. One of the most important properties to be changed is + * EntityFieldQuery::executeCallback. If this is set to an existing function, + * this function will get the query as its single argument and its result + * will be the returned as the result of EntityFieldQuery::execute(). This can + * be used to change the behavior of EntityFieldQuery entirely. For example, + * the default implementation can only deal with one field storage engine, but + * it is possible to write a module that can query across field storage + * engines. Also, the default implementation presumes entities are stored in + * SQL, but the execute callback could instead query any other entity storage, + * local or remote. + */ +function hook_entity_query_alter($query) { + $query->executeCallback = 'my_module_query_callback'; +} + /** * Define administrative paths. * @@ -1733,6 +1765,7 @@ function hook_theme($existing, $type, $theme, $path) { * * @param $theme_registry * The entire cache of theme registry information, post-processing. + * * @see hook_theme() * @see _theme_process_registry() */ @@ -1830,11 +1863,11 @@ function hook_xmlrpc() { * definition, so module must be prepared to handle either format for * each callback being altered. * - * @see hook_xmlrpc() - * * @param $methods * Associative array of method callback definitions returned from * hook_xmlrpc. + * + * @see hook_xmlrpc() */ function hook_xmlrpc_alter(&$methods) { @@ -2006,10 +2039,10 @@ function hook_mail($key, &$message, $params) { * tables that will be cleared by the Clear button on the Performance page or * whenever drupal_flush_all_caches is invoked. * - * @see drupal_flush_all_caches() - * * @return * An array of cache table names. + * + * @see drupal_flush_all_caches() */ function hook_flush_caches() { return array('cache_example'); @@ -2018,15 +2051,18 @@ function hook_flush_caches() { /** * Perform necessary actions after modules are installed. * - * This function differs from hook_install() as it gives all other - * modules a chance to perform actions when a module is installed, - * whereas hook_install() will only be called on the module actually - * being installed. - * - * @see hook_install() + * This function differs from hook_install() in that it gives all other modules + * a chance to perform actions when a module is installed, whereas + * hook_install() is only called on the module actually being installed. See + * module_enable() for a detailed description of the order in which install and + * enable hooks are invoked. * * @param $modules * An array of the installed modules. + * + * @see module_enable() + * @see hook_modules_enabled() + * @see hook_install() */ function hook_modules_installed($modules) { if (in_array('lousy_module', $modules)) { @@ -2037,15 +2073,18 @@ function hook_modules_installed($modules) { /** * Perform necessary actions after modules are enabled. * - * This function differs from hook_enable() as it gives all other - * modules a chance to perform actions when modules are enabled, - * whereas hook_enable() will only be called on the module actually - * being enabled. - * - * @see hook_enable() + * This function differs from hook_enable() in that it gives all other modules a + * chance to perform actions when modules are enabled, whereas hook_enable() is + * only called on the module actually being enabled. See module_enable() for a + * detailed description of the order in which install and enable hooks are + * invoked. * * @param $modules * An array of the enabled modules. + * + * @see hook_enable() + * @see hook_modules_installed() + * @see module_enable() */ function hook_modules_enabled($modules) { if (in_array('lousy_module', $modules)) { @@ -2057,15 +2096,15 @@ function hook_modules_enabled($modules) { /** * Perform necessary actions after modules are disabled. * - * This function differs from hook_disable() as it gives all other - * modules a chance to perform actions when modules are disabled, - * whereas hook_disable() will only be called on the module actually - * being disabled. - * - * @see hook_disable() + * This function differs from hook_disable() in that it gives all other modules + * a chance to perform actions when modules are disabled, whereas hook_disable() + * is only called on the module actually being disabled. * * @param $modules * An array of the disabled modules. + * + * @see hook_disable() + * @see hook_modules_uninstalled() */ function hook_modules_disabled($modules) { if (in_array('lousy_module', $modules)) { @@ -2076,18 +2115,18 @@ function hook_modules_disabled($modules) { /** * Perform necessary actions after modules are uninstalled. * - * This function differs from hook_uninstall() as it gives all other - * modules a chance to perform actions when a module is uninstalled, - * whereas hook_uninstall() will only be called on the module actually - * being uninstalled. + * This function differs from hook_uninstall() in that it gives all other + * modules a chance to perform actions when a module is uninstalled, whereas + * hook_uninstall() is only called on the module actually being uninstalled. * * It is recommended that you implement this module if your module * stores data that may have been set by other modules. * - * @see hook_uninstall() - * * @param $modules * An array of the uninstalled modules. + * + * @see hook_uninstall() + * @see hook_modules_disabled() */ function hook_modules_uninstalled($modules) { foreach ($modules as $module) { @@ -2588,12 +2627,13 @@ function hook_schema_alter(&$schema) { * Structured (aka dynamic) queries that have tags associated may be altered by any module * before the query is executed. * + * @param $query + * A Query object describing the composite parts of a SQL query. + * * @see hook_query_TAG_alter() * @see node_query_node_access_alter() * @see QueryAlterableInterface * @see SelectQueryInterface - * @param $query - * A Query object describing the composite parts of a SQL query. */ function hook_query_alter(QueryAlterableInterface $query) { if ($query->hasTag('micro_limit')) { @@ -2604,13 +2644,13 @@ function hook_query_alter(QueryAlterableInterface $query) { /** * Perform alterations to a structured query for a given tag. * + * @param $query + * An Query object describing the composite parts of a SQL query. + * * @see hook_query_alter() * @see node_query_node_access_alter() * @see QueryAlterableInterface * @see SelectQueryInterface - * - * @param $query - * An Query object describing the composite parts of a SQL query. */ function hook_query_TAG_alter(QueryAlterableInterface $query) { // Skip the extra expensive alterations if site has no node access control modules. @@ -2624,14 +2664,14 @@ function hook_query_TAG_alter(QueryAlterableInterface $query) { // Skip the extra joins and conditions for node admins. if (!user_access('bypass node access')) { // The node_access table has the access grants for any given node. - $access_alias = $query->join('node_access', 'na', 'na.nid = n.nid'); + $access_alias = $query->join('node_access', 'na', '%alias.nid = n.nid'); $or = db_or(); // If any grant exists for the specified user, then user has access to the node for the specified operation. foreach (node_access_grants($op, $query->getMetaData('account')) as $realm => $gids) { foreach ($gids as $gid) { $or->condition(db_and() - ->condition("{$access_alias}.gid", $gid) - ->condition("{$access_alias}.realm", $realm) + ->condition($access_alias . '.gid', $gid) + ->condition($access_alias . '.realm', $realm) ); } } @@ -2640,7 +2680,7 @@ function hook_query_TAG_alter(QueryAlterableInterface $query) { $query->condition($or); } - $query->condition("{$access_alias}.grant_$op", 1, '>='); + $query->condition($access_alias . 'grant_' . $op, 1, '>='); } } } @@ -2651,11 +2691,11 @@ function hook_query_TAG_alter(QueryAlterableInterface $query) { * If the module implements hook_schema(), the database tables will * be created before this hook is fired. * - * The hook will be called the first time a module is installed, and the - * module's schema version will be set to the module's greatest numbered update - * hook. Because of this, anytime a hook_update_N() is added to the module, this - * function needs to be updated to reflect the current version of the database - * schema. + * This hook will only be called the first time a module is enabled or after it + * is re-enabled after being uninstalled. The module's schema version will be + * set to the module's greatest numbered update hook. Because of this, anytime a + * hook_update_N() is added to the module, this function needs to be updated to + * reflect the current version of the database schema. * * See the Schema API documentation at * @link http://drupal.org/node/146843 http://drupal.org/node/146843 @endlink @@ -2669,8 +2709,12 @@ function hook_query_TAG_alter(QueryAlterableInterface $query) { * Please be sure that anything added or modified in this function that can * be removed during uninstall should be removed with hook_uninstall(). * - * @see hook_uninstall() * @see hook_schema() + * @see module_enable() + * @see hook_enable() + * @see hook_disable() + * @see hook_uninstall() + * @see hook_modules_installed() */ function hook_install() { // Populate the default {node_access} record. @@ -2768,12 +2812,14 @@ function hook_update_N(&$sandbox) { // We'll -1 to disregard the uid 0... $sandbox['max'] = db_query('SELECT COUNT(DISTINCT uid) FROM {users}')->fetchField() - 1; } - db_select('users', 'u') + + $users = db_select('users', 'u') ->fields('u', array('uid', 'name')) ->condition('uid', $sandbox['current_uid'], '>') ->range(0, 3) ->orderBy('uid', 'ASC') ->execute(); + foreach ($users as $user) { $user->name .= '!'; db_update('users') @@ -2869,8 +2915,8 @@ function hook_update_last_removed() { * - variables that the module has set using variable_set() or system_settings_form() * - modifications to existing tables * - * The module should not remove its entry from the {system} table. Database tables - * defined by hook_schema() will be removed automatically. + * The module should not remove its entry from the {system} table. Database + * tables defined by hook_schema() will be removed automatically. * * The uninstall hook will fire when the module gets uninstalled but before the * module's database tables are removed, allowing your module to query its own @@ -2878,6 +2924,8 @@ function hook_update_last_removed() { * * @see hook_install() * @see hook_schema() + * @see hook_disable() + * @see hook_modules_uninstalled() */ function hook_uninstall() { variable_del('upload_file_types'); @@ -2886,7 +2934,11 @@ function hook_uninstall() { /** * Perform necessary actions after module is enabled. * - * The hook is called everytime module is enabled. + * The hook is called every time the module is enabled. + * + * @see module_enable() + * @see hook_install() + * @see hook_modules_enabled() */ function hook_enable() { mymodule_cache_rebuild(); @@ -2895,7 +2947,10 @@ function hook_enable() { /** * Perform necessary actions before module is disabled. * - * The hook is called everytime module is disabled. + * The hook is called every time the module is disabled. + * + * @see hook_uninstall() + * @see hook_modules_disabled() */ function hook_disable() { mymodule_cache_rebuild(); @@ -3184,6 +3239,7 @@ function hook_install_tasks_alter(&$tasks, $install_state) { * An array of mimetypes correlated to the extensions that relate to them. * The array has 'mimetypes' and 'extensions' elements, each of which is an * array. + * * @see file_default_mimetype_mapping() */ function hook_file_mimetype_mapping_alter(&$mapping) { @@ -3344,11 +3400,11 @@ function hook_archiver_info_alter(&$info) { * recommended that each date type starts with the module name. A date type * can consist of letters, numbers and underscores. * - * @see hook_date_formats() - * @see format_date() - * * @return * A list of date types in 'key' => 'label' format. + * + * @see hook_date_formats() + * @see format_date() */ function hook_date_format_types() { return array( @@ -3407,8 +3463,6 @@ function hook_date_format_types_alter(&$types) { * that aren't specific to any one locale, for example, "Y m". For these cases * the locales field should be omitted. * - * @see hook_date_format_types() - * * @return * A list of date formats. Each date format is a keyed array * consisting of three elements: @@ -3427,6 +3481,8 @@ function hook_date_format_types_alter(&$types) { * first one will be used unless overridden via * admin/config/regional/date-time/locale. If your date format is not * language specific, leave this field empty. + * + * @see hook_date_format_types() */ function hook_date_formats() { return array( @@ -3460,34 +3516,6 @@ function hook_date_formats_alter(&$formats) { } } -/** - * Alters the router item for the active menu handler. - * - * Called by menu_execute_active_handler() to allow modules to alter the - * information that will be used to handle the page request. Only use this - * hook if an alteration specific to the page request is needed. Otherwise - * use hook_menu_alter(). - * - * @param $router_item - * An array with the following keys: - * - access: Boolean. Whether the user is allowed to see this page. - * - file: A path to a file to include prior to invoking the page callback. - * - page_callback: The function to call to build the page content. - * - page_arguments: Arguments to pass to the page callback. - * - delivery_callback: The function to call to deliver the result of the - * page callback to the browser. - * @param $path - * The drupal path that was used for retrieving the router item. - * - * @see menu_execute_active_handler() - * @see hook_menu() - * @see hook_menu_alter() - */ -function hook_menu_active_handler_alter(&$router_item, $path = NULL) { - // Turn off access for all pages for all users. - $router_item['access'] = FALSE; -} - /** * Alters the delivery callback used to send the result of the page callback to the browser. * @@ -3498,8 +3526,7 @@ function hook_menu_active_handler_alter(&$router_item, $path = NULL) { * information unrelated to the path of the page accessed. For example, * it can be used to set the delivery callback based on a HTTP request * header (as shown in the code sample). To specify a delivery callback - * based on path information, use hook_menu(), hook_menu_alter() or - * hook_menu_active_handler_alter(). + * based on path information, use hook_menu() or hook_menu_alter(). * * This hook can also be used as an API function that can be used to explicitly * set the delivery callback from some other function. For example, for a module @@ -3796,17 +3823,17 @@ function hook_batch_alter(&$batch) { */ function hook_token_info_alter(&$data) { // Modify description of node tokens for our site. - $node['nid'] = array( + $data['tokens']['node']['nid'] = array( 'name' => t("Node ID"), 'description' => t("The unique ID of the article."), ); - $node['title'] = array( + $data['tokens']['node']['title'] = array( 'name' => t("Title"), 'description' => t("The title of the article."), ); // Chained tokens for nodes. - $node['created'] = array( + $data['tokens']['node']['created'] = array( 'name' => t("Date created"), 'description' => t("The date the article was posted."), 'type' => 'date', @@ -3933,6 +3960,31 @@ function hook_filetransfer_backends() { return $backends; } +/** + * Control site status before menu dispatching. + * + * The hook is called after checking whether the site is offline but before + * the current router item is retrieved and executed by + * menu_execute_active_handler(). If the site is in offline mode, + * $menu_site_status is set to MENU_SITE_OFFLINE. + * + * @param $menu_site_status + * Supported values are MENU_SITE_OFFLINE, MENU_ACCESS_DENIED, + * MENU_NOT_FOUND and MENU_SITE_ONLINE. Any other value than + * MENU_SITE_ONLINE will skip the default menu handling system and be passed + * for delivery to drupal_deliver_page() with a NULL + * $default_delivery_callback. + * @param $path + * Contains the system path that is going to be loaded. This is read only, + * use hook_url_inbound_alter() to change the path. + */ +function hook_menu_site_status_alter(&$menu_site_status, $path) { + // Allow access to my_module/authentication even if site is in offline mode. + if ($menu_site_status == MENU_SITE_OFFLINE && user_is_anonymous() && $path == 'my_module/authentication') { + $menu_site_status = MENU_SITE_ONLINE; + } +} + /** * @} End of "addtogroup hooks". */ diff --git a/modules/system/system.css b/modules/system/system.css index bdf9017d..1b2de28d 100644 --- a/modules/system/system.css +++ b/modules/system/system.css @@ -1,4 +1,4 @@ -/* $Id: system.css,v 1.76 2010/05/14 07:45:54 dries Exp $ */ +/* $Id: system.css,v 1.77 2010/05/23 18:23:32 dries Exp $ */ /* ** HTML elements @@ -274,6 +274,26 @@ tr.selected td { position: absolute; } +/* +** 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 b15f30f0..a8a37d24 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/system/system.install b/modules/system/system.install index 923be4e9..89c4edd7 100644 --- a/modules/system/system.install +++ b/modules/system/system.install @@ -1,5 +1,5 @@ <?php -// $Id: system.install,v 1.469 2010/05/19 19:14:43 dries Exp $ +// $Id: system.install,v 1.488 2010/07/08 03:41:27 webchick Exp $ /** * @file @@ -74,6 +74,8 @@ function system_requirements($phase) { if (version_compare($phpversion, DRUPAL_MINIMUM_PHP) < 0) { $requirements['php']['description'] = $t('Your PHP installation is too old. Drupal requires at least PHP %version.', array('%version' => DRUPAL_MINIMUM_PHP)); $requirements['php']['severity'] = REQUIREMENT_ERROR; + // If PHP is old, it's not safe to continue with the requirements check. + return $requirements; } // Test PHP register_globals setting. @@ -267,8 +269,18 @@ function system_requirements($phase) { // 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()), ); + + // Do not check for the temporary files directory at install time + // unless it has been set in settings.php. In this case the user has + // no alternative but to fix the directory if it is not writable. + if ($phase == 'install') { + $directories[] = variable_get('file_temporary_path', FALSE); + } + else { + $directories[] = variable_get('file_temporary_path', file_directory_temp()); + } + $requirements['file system'] = array( 'title' => $t('File system'), ); @@ -360,10 +372,11 @@ function system_requirements($phase) { // Display an error if a newly introduced dependency in a module is not resolved. if ($phase == 'update') { + $profile = drupal_get_profile(); $files = system_rebuild_module_data(); foreach ($files as $module => $file) { - // Ignore disabled modules. - if (!$file->status) { + // Ignore disabled modules and install profiles. + if (!$file->status || $module == $profile) { continue; } // Check the module's PHP version. @@ -467,11 +480,11 @@ function system_install() { system_rebuild_theme_data(); // Enable the default theme. - variable_set('theme_default', 'garland'); + variable_set('theme_default', 'bartik'); db_update('system') ->fields(array('status' => 1)) ->condition('type', 'theme') - ->condition('name', 'garland') + ->condition('name', 'bartik') ->execute(); // Populate the cron key variable. @@ -498,7 +511,7 @@ function system_schema() { ), 'value' => array( 'description' => 'The value of the variable.', - 'type' => 'text', + 'type' => 'blob', 'not null' => TRUE, 'size' => 'big', 'translatable' => TRUE, @@ -533,7 +546,7 @@ function system_schema() { ), 'parameters' => array( 'description' => 'Parameters to be passed to the callback function.', - 'type' => 'text', + 'type' => 'blob', 'not null' => TRUE, 'size' => 'big', ), @@ -572,7 +585,7 @@ function system_schema() { ), 'batch' => array( 'description' => 'A serialized array containing the processing data for the batch.', - 'type' => 'text', + 'type' => 'blob', 'not null' => FALSE, 'size' => 'big', ), @@ -647,7 +660,6 @@ function system_schema() { ), 'primary key' => array('cid'), ); - $schema['cache_bootstrap'] = $schema['cache']; $schema['cache_bootstrap']['description'] = 'Cache table for data required to bootstrap Drupal, may be routed to a shared memory cache.'; $schema['cache_form'] = $schema['cache']; @@ -901,12 +913,12 @@ function system_schema() { ), 'load_functions' => array( 'description' => 'A serialized array of function names (like node_load) to be called to load an object corresponding to a part of the current path.', - 'type' => 'text', + 'type' => 'blob', 'not null' => TRUE, ), 'to_arg_functions' => array( 'description' => 'A serialized array of function names (like user_uid_optional_to_arg) to be called to replace a part of the router path with another string.', - 'type' => 'text', + 'type' => 'blob', 'not null' => TRUE, ), 'access_callback' => array( @@ -918,7 +930,7 @@ function system_schema() { ), 'access_arguments' => array( 'description' => 'A serialized array of arguments for the access callback.', - 'type' => 'text', + 'type' => 'blob', 'not null' => FALSE, ), 'page_callback' => array( @@ -930,7 +942,7 @@ function system_schema() { ), 'page_arguments' => array( 'description' => 'A serialized array of arguments for the page callback.', - 'type' => 'text', + 'type' => 'blob', 'not null' => FALSE, ), 'delivery_callback' => array( @@ -1100,7 +1112,7 @@ function system_schema() { ), 'options' => array( 'description' => 'A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.', - 'type' => 'text', + 'type' => 'blob', 'not null' => FALSE, 'translatable' => TRUE, ), @@ -1256,7 +1268,7 @@ function system_schema() { 'description' => 'The queue name.', ), 'data' => array( - 'type' => 'text', + 'type' => 'blob', 'not null' => FALSE, 'size' => 'big', 'serialize' => TRUE, @@ -1401,18 +1413,18 @@ function system_schema() { 'not null' => TRUE, ), 'sid' => array( - 'description' => "Primary key: A session ID. The value is generated by PHP's Session API.", + 'description' => "A session ID. The value is generated by PHP's Session API.", 'type' => 'varchar', - 'length' => 64, + 'length' => 128, 'not null' => TRUE, 'default' => '', ), 'ssid' => array( - 'description' => "Unique key: Secure session ID. The value is generated by PHP's Session API.", + 'description' => "Secure session ID. The value is generated by PHP's Session API.", 'type' => 'varchar', - 'length' => 64, - 'not null' => FALSE, - 'default' => NULL, + 'length' => 128, + 'not null' => TRUE, + 'default' => '', ), 'hostname' => array( 'description' => 'The IP address that last used this session ID (sid).', @@ -1435,17 +1447,18 @@ function system_schema() { ), 'session' => array( 'description' => 'The serialized contents of $_SESSION, an array of name/value pairs that persists across page requests by this session ID. Drupal loads $_SESSION from here at the start of each request and saves it at the end.', - 'type' => 'text', + 'type' => 'blob', 'not null' => FALSE, 'size' => 'big', ), ), - 'primary key' => array('sid'), + 'primary key' => array( + 'sid', + 'ssid', + ), 'indexes' => array( 'timestamp' => array('timestamp'), 'uid' => array('uid'), - ), - 'unique keys' => array( 'ssid' => array('ssid'), ), 'foreign keys' => array( @@ -1511,7 +1524,7 @@ function system_schema() { ), 'info' => array( 'description' => "A serialized array containing information from the module's .info file; keys can include name, description, package, version, core, dependencies, dependents, and php.", - 'type' => 'text', + 'type' => 'blob', 'not null' => FALSE, ), ), @@ -1555,17 +1568,69 @@ function system_schema() { ), ), 'unique keys' => array( - 'alias_language_pid' => array('alias', 'language', 'pid'), + 'alias_language' => array('alias', 'language'), ), 'primary key' => array('pid'), 'indexes' => array( - 'source_language_pid' => array('source', 'language', 'pid'), + 'source_language' => array('source', 'language'), ), ); return $schema; } +/** + * The cache schema corresponding to system_update_7054. + * + * Drupal 7 adds several new cache tables. Since they all have identical schema + * this helper function allows them to re-use the same definition, without + * relying on system_schema(), which may change with future updates. + */ +function system_schema_cache_7054() { + return array( + 'description' => 'Generic cache table for caching things not separated out into their own tables. Contributed modules may also use this to store cached items.', + 'fields' => array( + 'cid' => array( + 'description' => 'Primary Key: Unique cache ID.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'data' => array( + 'description' => 'A collection of data to cache.', + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big', + ), + 'expire' => array( + 'description' => 'A Unix timestamp indicating when the cache entry should expire, or 0 for never.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'created' => array( + 'description' => 'A Unix timestamp indicating when the cache entry was created.', + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + ), + 'serialized' => array( + 'description' => 'A flag to indicate whether content is serialized (1) or not (0).', + 'type' => 'int', + 'size' => 'small', + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'indexes' => array( + 'expire' => array('expire'), + ), + 'primary key' => array('cid'), + ); +} + + // Updates for core. function system_update_last_removed() { @@ -1781,18 +1846,7 @@ function system_update_7007() { * the choice order. Rename chorder to weight. */ function system_update_7008() { - if (db_table_exists('poll_votes')) { - // Add chid column and convert existing votes. - db_add_field('poll_votes', 'chid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0)); - db_add_index('poll_votes', 'chid', array('chid')); - db_query("UPDATE {poll_votes} SET chid = (SELECT chid FROM {poll_choices} c WHERE {poll_votes}.chorder = c.chorder AND {poll_votes}.nid = c.nid)"); - // Remove old chorder column. - db_drop_field('poll_votes', 'chorder'); - } - if (db_table_exists('poll_choices')) { - // Change the chorder column to weight in poll_choices. - db_change_field('poll_choices', 'chorder', 'weight', array('type' => 'int', 'not null' => TRUE, 'default' => 0, 'size' => 'tiny')); - } + // Moved to poll_update_7001(). } /** @@ -1861,11 +1915,22 @@ function system_update_7013() { // schema found. } } + + // Check to see if timezone was overriden in update_prepare_d7_bootstrap(). + $offset = variable_get('date_temporary_timezone'); + // If not, use the default. + if (!isset($offset)) { + $offset = variable_get('date_default_timezone', 0); + } + // If the previous default time zone was a non-zero offset, guess the site's // intended time zone based on that offset and the server's daylight saving // time status. - if (!$timezone && ($offset = variable_get('date_default_timezone', 0)) && ($timezone_name = timezone_name_from_abbr('', intval($offset), date('I'))) && isset($timezones[$timezone_name])) { - $timezone = $timezone_name; + if (!$timezone && $offset) { + $timezone_name = timezone_name_from_abbr('', intval($offset), date('I')); + if ($timezone_name && isset($timezones[$timezone_name])) { + $timezone = $timezone_name; + } } // Otherwise, the default time zone offset was zero, which is UTC. if (!$timezone) { @@ -1873,6 +1938,8 @@ function system_update_7013() { } variable_set('date_default_timezone', $timezone); drupal_set_message('The default time zone has been set to <em>' . check_plain($timezone) . '</em>. Check the ' . l('date and time configuration page', 'admin/config/regional/settings') . ' to configure it correctly.', 'warning'); + // Remove temporary override. + variable_del('date_temporary_timezone'); } /** @@ -2146,27 +2213,7 @@ function system_update_7034() { * Migrate upload module files to the new {file_managed} table. */ function system_update_7035() { - if (!db_table_exists('upload')) { - return; - } - - // The old {files} tables still exists. We migrate core data from upload - // module, but any contrib module using it will need to do its own update. - $result = db_query('SELECT f.fid, uid, filename, filepath AS uri, filemime, filesize, status, timestamp FROM {files} f INNER JOIN {upload} u ON u.fid = f.fid', array(), array('fetch' => PDO::FETCH_ASSOC)); - - // We will convert filepaths to uri using the default schmeme - // and stripping off the existing file directory path. - $basename = variable_get('file_directory_path', conf_path() . '/files'); - $scheme = variable_get('file_default_scheme', 'public') . '://'; - $fids = array(); - // TODO: does this function need to run in batch mode, or should we use a multi-insert? - foreach ($result as $file) { - $file['uri'] = $scheme . str_replace($basename, '', $file['uri']); - $file['uri'] = file_stream_wrapper_uri_normalize($file['uri']); - db_insert('file_managed')->fields($file)->execute(); - $fids[] = $file['fid']; - } - // TODO: delete the found fids from {files}? + // Update merged into system_update_7059(). } /** @@ -2360,29 +2407,363 @@ function system_update_7052() { */ 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(); + if (db_table_exists('block')) { + 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(); + if (db_table_exists('menu_custom')) { + // 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(); + 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'); + // Update: update_fix_d7_requirements() installs this version for cache_path + // already, so we don't include it in this particular update. It should be + // included in later updates though. + $cache_tables = array( + 'cache' => 'Generic cache table for caching things not separated out into their own tables. Contributed modules may also use this to store cached items.', + 'cache_form' => 'Cache table for the form system to store recently built forms and their storage data, to be used in subsequent page requests.', + 'cache_page' => 'Cache table used to store compressed pages for anonymous users, if page caching is enabled.', + 'cache_menu' => 'Cache table for the menu system to store router information as well as generated link trees for various menu/page/user combinations.', + ); + $schema = system_schema_cache_7054(); + foreach ($cache_tables as $table => $description) { + $schema['description'] = $description; + db_drop_table($table); + db_create_table($table, $schema); + } +} + +/** + * Converts fields that store serialized variables from text to blob. + */ +function system_update_7055() { + $spec = array( + 'description' => 'The value of the variable.', + 'type' => 'blob', + 'not null' => TRUE, + 'size' => 'big', + 'translatable' => TRUE, + ); + db_change_field('variable', 'value', 'value', $spec); + + $spec = array( + 'description' => 'Parameters to be passed to the callback function.', + 'type' => 'blob', + 'not null' => TRUE, + 'size' => 'big', + ); + db_change_field('actions', 'parameters', 'parameters', $spec); + + $spec = array( + 'description' => 'A serialized array containing the processing data for the batch.', + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big', + ); + db_change_field('batch', 'batch', 'batch', $spec); + + $spec = array( + 'description' => 'A serialized array of function names (like node_load) to be called to load an object corresponding to a part of the current path.', + 'type' => 'blob', + 'not null' => TRUE, + ); + db_change_field('menu_router', 'load_functions', 'load_functions', $spec); + + $spec = array( + 'description' => 'A serialized array of function names (like user_uid_optional_to_arg) to be called to replace a part of the router path with another string.', + 'type' => 'blob', + 'not null' => TRUE, + ); + db_change_field('menu_router', 'to_arg_functions', 'to_arg_functions', $spec); + + $spec = array( + 'description' => 'A serialized array of arguments for the access callback.', + 'type' => 'blob', + 'not null' => FALSE, + ); + db_change_field('menu_router', 'access_arguments', 'access_arguments', $spec); + + $spec = array( + 'description' => 'A serialized array of arguments for the page callback.', + 'type' => 'blob', + 'not null' => FALSE, + ); + db_change_field('menu_router', 'page_arguments', 'page_arguments', $spec); + + $spec = array( + 'description' => 'A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.', + 'type' => 'blob', + 'not null' => FALSE, + 'translatable' => TRUE, + ); + db_change_field('menu_links', 'options', 'options', $spec); + + $spec = array( + 'description' => 'The serialized contents of $_SESSION, an array of name/value pairs that persists across page requests by this session ID. Drupal loads $_SESSION from here at the start of each request and saves it at the end.', + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big', + ); + db_change_field('sessions', 'session', 'session', $spec); + + $spec = array( + 'description' => "A serialized array containing information from the module's .info file; keys can include name, description, package, version, core, dependencies, dependents, and php.", + 'type' => 'blob', + 'not null' => FALSE, + ); + db_change_field('system', 'info', 'info', $spec); +} + +/** + * Remove pid from indexes and unique keys of {url_alias}. + */ +function system_update_7056() { + // Drop indexes. + db_drop_index('url_alias', 'source_language_pid'); + db_drop_unique_key('url_alias', 'alias_language_pid'); + // Add indexes back. + db_add_index('url_alias', 'source_language', array('source', 'language')); + db_add_unique_key('url_alias', 'alias_language', array('alias', 'language')); +} + +/** + * Increase the size of session-ids. + */ +function system_update_7057() { + $spec = array( + 'description' => "A session ID. The value is generated by PHP's Session API.", + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + ); + db_change_field('sessions', 'sid', 'sid', $spec); +} + +/** + * Remove cron semaphore variable. + */ +function system_update_7058() { + variable_del('cron_semaphore'); +} + +/** + * Migrate upload.module to file.module. + */ +function system_update_7059(&$sandbox) { + if (!db_table_exists('upload')) { + return; + } + + if (!isset($sandbox['progress'])) { + // Initialize batch update information. + $sandbox['progress'] = 0; + $sandbox['last_vid_processed'] = -1; + $sandbox['max'] = db_query("SELECT COUNT(DISTINCT u.vid) FROM {upload} u")->fetchField(); + + // Check which node types have upload.module attachments enabled. + $context['types'] = array(); + foreach (node_type_get_types() as $node_type => $node_info) { + if (variable_get('upload_' . $node_type, 1)) { + $context['types'][$node_type] = $node_type; + } + variable_del('upload_' . $node_type); + } + + // The {upload} table will be deleted when this update is complete so we + // want to be careful to migrate all the data, even for node types that + // may have had attachments disabled after files were uploaded. Look for + // any other node types referenced by the upload records and add those to + // the list. The admin can always remove the field later. + $results = db_query('SELECT DISTINCT type FROM {node} n INNER JOIN {upload} u ON n.vid = u.vid'); + foreach ($results as $row) { + if (!isset($context['types'][$row->type])) { + drupal_set_message(t('The content type %rowtype had uploads disabled but contained uploaded file data. Uploads have been re-enabled to migrate the existing data. You may delete the "File attachments" field in the %rowtype type if this data is not necessary.', array('%rowtype' => $row->type))); + $context['types'][$row->type] = $row->type; + } + } + + // Create a single "field_upload" field on all the content types that have + // uploads enabled, then add an instance to each enabled type. + if (count($context['types']) > 0) { + module_enable(array('file')); + module_load_include('inc', 'field', 'field.crud'); + + $field = array( + 'field_name' => 'file', + 'type' => 'file', + 'locked' => FALSE, + 'cardinality' => FIELD_CARDINALITY_UNLIMITED, + 'translatable' => FALSE, + 'settings' => array( + 'display_field' => 1, + 'display_default' => variable_get('upload_list_default', 1), + 'uri_scheme' => variable_get('file_default_scheme', 'public'), + 'default_file' => 0, + ), + ); + + $upload_size = variable_get('upload_uploadsize_default', 1); + $instance = array( + 'field_name' => 'file', + 'entity_type' => 'node', + 'bundle' => NULL, + 'label' => 'File attachments', + 'widget_type' => 'file_generic', + 'required' => 0, + 'description' => '', + 'widget' => array( + 'weight' => '1', + 'settings' => array( + 'progress_indicator' => 'throbber', + ), + 'type' => 'file_generic', + ), + 'settings' => array( + 'max_filesize' => $upload_size ? ($upload_size . ' MB') : '', + 'file_extensions' => variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'), + 'file_directory' => '', + 'description_field' => 1, + ), + 'display' => array( + 'full' => array( + 'label' => 'hidden', + 'type' => 'file_table', + 'settings' => array(), + 'weight' => 0, + 'module' => 'file', + ), + 'teaser' => array( + 'label' => 'hidden', + 'type' => 'hidden', + 'settings' => array(), + 'weight' => 0, + 'module' => NULL, + ), + 'rss' => array( + 'label' => 'hidden', + 'type' => 'file_table', + 'settings' => array(), + 'weight' => 0, + 'module' => 'file', + ), + ), + ); + + // Create the field. Save the field id for the data insertion later on. + $field = field_create_field($field); + $sandbox['field_id'] = $field['id']; + + // Create the instances. + foreach ($context['types'] as $bundle) { + $instance['bundle'] = $bundle; + field_create_instance($instance); + } + } + else { + // No uploads or content types with uploads enabled. + db_drop_table('upload'); + // We're done: return without specifying a #progress. + return; + } + } + + // Migrate a batch of files from the upload table to the appropriate field. + $limit = 500; + $result = db_query_range('SELECT DISTINCT u.fid, u.vid, u.list, u.description, n.nid, n.type FROM {upload} u INNER JOIN {node_revision} nr ON u.vid = nr.vid INNER JOIN {node} n ON n.nid = nr.nid WHERE u.vid > :lastvid ORDER BY u.vid, u.weight', 0, $limit, array(':lastvid' => $sandbox['last_vid_processed'])); + foreach ($result as $record) { + // Note that we still reference the old files table here, since upload will + // not know about the new FID in the new file_managed table. + $file = db_select('files', 'f') + ->fields('f', array('fid', 'uid', 'filename', 'filepath', 'filemime', 'filesize', 'status', 'timestamp')) + ->condition('f.fid', $record->fid) + ->execute() + ->fetchAssoc(); + if (!$file) { + continue; + } + + $file['description'] = $record->description; + $file['display'] = $record->list; + + $node_revisions[$record->vid]['nid'] = $record->nid; + $node_revisions[$record->vid]['vid'] = $record->vid; + $node_revisions[$record->vid]['type'] = $record->type; + $node_revisions[$record->vid]['file'][LANGUAGE_NONE][] = $file; + } + + // To make sure we process an entire node all at once, toss the last node + // revision (which might be partial) unless it's the last one. + if ((count($node_revisions) > 1) && ($result->rowCount() == $limit)) { + array_pop($node_revisions); + } + else { + $finished = TRUE; + } + + $basename = variable_get('file_directory_path', conf_path() . '/files'); + $scheme = variable_get('file_default_scheme', 'public') . '://'; + foreach ($node_revisions as $vid => $revision) { + // We will convert filepaths to uri using the default scheme + // and stripping off the existing file directory path. + $fids = array(); + foreach ($revision['file'][LANGUAGE_NONE] as $delta => $file) { + // Insert into the file_managed table. + $file['uri'] = $scheme . str_replace($basename, '', $file['filepath']); + $file['uri'] = file_stream_wrapper_uri_normalize($file['uri']); + unset($file['filepath']); + // Each fid should only be stored once in file_managed. + db_merge('file_managed') + ->key(array( + 'fid' => $file['fid'], + )) + ->fields(array( + 'uid' => $file['uid'], + 'filename' => $file['filename'], + 'uri' => $file['uri'], + 'filemime' => $file['filemime'], + 'filesize' => $file['filesize'], + 'status' => $file['status'], + 'timestamp' => $file['timestamp'], + )) + ->execute(); + + // Update the node field with the file URI. + $revision['file'][LANGUAGE_NONE][$delta] = $file; + } + + // Insert the revision's files into the field_upload table. + $node = (object) $revision; + field_sql_storage_field_storage_write('node', $node, FIELD_STORAGE_INSERT, array($sandbox['field_id'])); + + // Update our progress information for the batch update. + $sandbox['progress']++; + $sandbox['last_vid_processed'] = $vid; + } + + // If there's no max value then there's nothing to update and we're finished. + if (empty($sandbox['max']) || isset($finished)) { + db_drop_table('upload'); + return t('Upload module has been migrated to File module.'); + } + else { + // Indicate our current progress to the batch update system. + $sandbox['#finished'] = $sandbox['progress'] / $sandbox['max']; } } diff --git a/modules/system/system.module b/modules/system/system.module index 03178102..c963cb4f 100644 --- a/modules/system/system.module +++ b/modules/system/system.module @@ -1,5 +1,5 @@ <?php -// $Id: system.module,v 1.933 2010/05/20 08:47:00 dries Exp $ +// $Id: system.module,v 1.946 2010/07/07 17:56:42 webchick Exp $ /** * @file @@ -135,7 +135,7 @@ function system_help($path, $arg) { case 'admin/config/system/actions/configure': return t('An advanced action offers additional configuration options which may be filled out below. Changing the <em>Description</em> field is recommended, in order to better identify the precise action taking place. This description will be displayed in modules such as the Trigger module when assigning actions to system events, so it is best if it is as descriptive as possible (for example, "Send e-mail to Moderation Team" rather than simply "Send e-mail").'); case 'admin/config/people/ip-blocking': - return '<p>' . t('IP addresses listed here are blocked from your site before any modules are loaded. You may add IP addresses to the list, or delete existing entries.') . '</p>'; + return '<p>' . t('IP addresses listed here are blocked from your site. Blocked addresses are completely forbidden from accessing the site and instead see a brief message explaining the situation.') . '</p>'; case 'admin/reports/status': return '<p>' . t("Here you can find a short overview of your site's parameters as well as any problems detected with your installation. It may be useful to copy and paste this information into support requests filed on drupal.org's support forums and project issue queues.") . '</p>'; } @@ -1216,6 +1216,11 @@ function system_library() { ), 'dependencies' => array( array('system', 'ui.widget'), + array('system', 'ui.button'), + array('system', 'ui.draggable'), + array('system', 'ui.mouse'), + array('system', 'ui.position'), + array('system', 'ui.resizable'), ), ); $libraries['ui.draggable'] = array( @@ -2225,7 +2230,7 @@ function _system_rebuild_module_data() { // Include the install profile in modules that are loaded. $profile = drupal_get_profile(); - $modules[$profile] = new stdClass; + $modules[$profile] = new stdClass(); $modules[$profile]->name = $profile; $modules[$profile]->uri = 'profiles/' . $profile . '/' . $profile . '.profile'; $modules[$profile]->filename = $profile . '.profile'; @@ -2263,14 +2268,22 @@ function _system_rebuild_module_data() { // Merge in defaults and save. $modules[$key]->info = $module->info + $defaults; + // Install profiles are hidden by default, unless explicitly specified + // otherwise in the .info file. + if ($key == $profile && !isset($modules[$key]->info['hidden'])) { + $modules[$key]->info['hidden'] = TRUE; + } + // Invoke hook_system_info_alter() to give installed modules a chance to // modify the data in the .info files if necessary. $type = 'module'; drupal_alter('system_info', $modules[$key]->info, $modules[$key], $type); } - // The install profile is required. - $modules[$profile]->info['required'] = TRUE; + // The install profile is required, if it's a valid module. + if (isset($modules[$profile])) { + $modules[$profile]->info['required'] = TRUE; + } return $modules; } @@ -2560,32 +2573,11 @@ function system_default_region($theme) { return isset($regions[0]) ? $regions[0] : ''; } -function _system_settings_form_automatic_defaults($form) { - // Get an array of all non-property keys - $keys = element_children($form); - - foreach ($keys as $key) { - // If the property (key) '#default_value' exists, replace it. - if (array_key_exists('#default_value', $form[$key])) { - $form[$key]['#default_value'] = variable_get($key, $form[$key]['#default_value']); - } - else { - // Recurse through child elements - $form[$key] = _system_settings_form_automatic_defaults($form[$key]); - } - } - - return $form; -} - /** * Add default buttons to a form and set its prefix. * * @param $form * An associative array containing the structure of the form. - * @param $automatic_defaults - * Automatically load the saved values for each field from the system variables - * (defaults to TRUE). * * @return * The form structure. @@ -2593,14 +2585,10 @@ function _system_settings_form_automatic_defaults($form) { * @see system_settings_form_submit() * @ingroup forms */ -function system_settings_form($form, $automatic_defaults = TRUE) { +function system_settings_form($form) { $form['actions']['#type'] = 'actions'; $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save configuration')); - if ($automatic_defaults) { - $form = _system_settings_form_automatic_defaults($form); - } - if (!empty($_POST) && form_get_errors()) { drupal_set_message(t('The settings have not been saved because of the errors.'), 'error'); } @@ -2848,6 +2836,8 @@ function system_cron() { function system_flush_caches() { // Rebuild list of date formats. system_date_formats_rebuild(); + // Reset the menu static caches. + menu_reset_static_cache(); } /** @@ -2871,7 +2861,7 @@ function system_action_info() { 'type' => 'user', 'label' => t('Ban IP address of current user'), 'configurable' => FALSE, - 'triggers' => array(), + 'triggers' => array('any'), ), 'system_goto_action' => array( 'type' => 'system', @@ -3140,7 +3130,7 @@ function system_time_zones($blank = NULL) { */ function system_check_http_request() { // Try to get the content of the front page via drupal_http_request(). - $result = drupal_http_request(url('', array('absolute' => TRUE))); + $result = drupal_http_request(url('', array('absolute' => TRUE)), array('max_redirects' => 0)); // We only care that we get a http response - this means that Drupal // can make a http request. $works = isset($result->code) && ($result->code >= 100) && ($result->code < 600); @@ -3273,7 +3263,10 @@ function system_page_alter(&$page) { * Run the automated cron if enabled. */ function system_run_automated_cron() { - if (($threshold = variable_get('cron_safe_threshold', DRUPAL_CRON_DEFAULT_THRESHOLD)) > 0) { + // If the site is not fully installed, suppress the automated cron run. + // Otherwise it could be triggered prematurely by AJAX requests during + // installation. + if (($threshold = variable_get('cron_safe_threshold', DRUPAL_CRON_DEFAULT_THRESHOLD)) > 0 && variable_get('install_task') == 'done') { $cron_last = variable_get('cron_last', NULL); if (!isset($cron_last) || (REQUEST_TIME - $cron_last > $threshold)) { drupal_cron_run(); diff --git a/modules/system/system.queue.inc b/modules/system/system.queue.inc index 259830d6..28f2dd79 100644 --- a/modules/system/system.queue.inc +++ b/modules/system/system.queue.inc @@ -1,5 +1,5 @@ <?php -// $Id: system.queue.inc,v 1.12 2010/04/26 13:02:40 dries Exp $ +// $Id: system.queue.inc,v 1.13 2010/06/14 13:10:31 dries Exp $ /** * @file @@ -24,7 +24,8 @@ * DrupalQueueInterface::deleteItem(). If the consumer dies, the item will be * made available again by the DrupalQueueInterface implementation once the * lease expires. Another consumer will then be able to receive it when calling - * DrupalQueueInterface::claimItem(). + * DrupalQueueInterface::claimItem(). Due to this, the processing code should + * be aware that an item might be handed over for processing more than once. * * The $item object used by the DrupalQueueInterface can contain arbitrary * metadata depending on the implementation. Systems using the interface should @@ -33,18 +34,23 @@ * DrupalQueueInterface::claimItem() needs to be passed to * DrupalQueueInterface::deleteItem() once processing is completed. * - * While the queue system makes a best effort to preserve order in messages, - * due to the pluggable nature of the queue, there is no guarantee that items - * will be delivered on claim in the order they were sent. For example, some - * implementations like beanstalkd or others with distributed back-ends like + * There are two kinds of queue backends available: reliable, which preserves + * the order of messages and guarantees that every item will be executed at + * least once. The non-reliable kind only does a best effort to preserve order + * in messages and to execute them at least once but there is a small chance + * that some items get lost. For example, some distributed back-ends like * Amazon SQS will be managing jobs for a large set of producers and consumers - * where a strict FIFO ordering will likely not be preserved. - * - * The system also makes no guarantees about a task only being executed once: - * callers that have non-idempotent tasks either need to live with the - * possiblity of the task being invoked multiple times in cases where a claim - * lease expires, or need to implement their own transactions to make their - * tasks idempotent. + * where a strict FIFO ordering will likely not be preserved. Another example + * would be an in-memory queue backend which might lose items if it crashes. + * However, such a backend would be able to deal with significantly more writes + * than a reliable queue and for many tasks this is more important. See + * aggregator_cron() for an example of how can this not be a problem. Another + * example is doing Twitter statistics -- the small possibility of losing a few + * items is insignificant next to power of the queue being able to keep up with + * writes. As described in the processing section, regardless of the queue + * being reliable or not, the processing code should be aware that an item + * might be handed over for processing more than once (because the processing + * code might time out before it finishes). */ /** @@ -52,21 +58,38 @@ */ class DrupalQueue { /** - * Get a queue object for a given name. + * Returns the queue object for a given name. + * + * The following variables can be set by variable_set or $conf overrides: + * - queue_class_$name: the class to be used for the queue $name. + * - queue_default_class: the class to use when queue_class_$name is not + * defined. Defaults to SystemQueue, a reliable backend using SQL. + * - queue_default_reliable_class: the class to use when queue_class_$name is + * not defined and the queue_default_class is not reliable. Defaults to + * SystemQueue. * * @param $name * Arbitrary string. The name of the queue to work with. + * @param $reliable + * TRUE if the ordering of items and guaranteeing every item executes at + * least once is important, FALSE if scalability is the main concern. + * * @return * The queue object for a given name. */ - public static function get($name) { + public static function get($name, $reliable = FALSE) { static $queues; if (!isset($queues[$name])) { $class = variable_get('queue_class_' . $name, NULL); if (!$class) { $class = variable_get('queue_default_class', 'SystemQueue'); } - $queues[$name] = new $class($name); + $object = new $class($name); + if ($reliable && !$object instanceof DrupalReliableQueueInterface) { + $class = variable_get('queue_default_reliable_class', 'SystemQueue'); + $object = new $class($name); + } + $queues[$name] = $object; } return $queues[$name]; } @@ -89,8 +112,8 @@ interface DrupalQueueInterface { * @return * TRUE if the item was successfully created and was (best effort) added * to the queue, otherwise FALSE. We don't guarantee the item was - * committed to disk, that your disk wasn't hit by a meteor, etc, but as - * far as we know, the item is now in the queue. + * committed to disk etc, but as far as we know, the item is now in the + * queue. */ public function createItem($data); @@ -166,10 +189,19 @@ interface DrupalQueueInterface { public function deleteQueue(); } +/** + * Reliable queue interface. + * + * Classes implementing this interface preserve the order of messages and + * guarantee that every item will be executed at least once. + */ +interface DrupalReliableQueueInterface extends DrupalQueueInterface { +} + /** * Default queue implementation. */ -class SystemQueue implements DrupalQueueInterface { +class SystemQueue implements DrupalReliableQueueInterface { /** * The name of the queue this instance is working with. * diff --git a/modules/system/system.test b/modules/system/system.test index 273244be..721edf4c 100644 --- a/modules/system/system.test +++ b/modules/system/system.test @@ -1,5 +1,5 @@ <?php -// $Id: system.test,v 1.124 2010/04/20 09:48:06 webchick Exp $ +// $Id: system.test,v 1.131 2010/07/07 08:05:01 webchick Exp $ /** * Helper class for module test cases. @@ -169,6 +169,35 @@ class EnableDisableTestCase extends ModuleTestCase { } } +/** + * Tests failure of hook_requirements('install'). + */ +class HookRequirementsTestCase extends ModuleTestCase { + public static function getInfo() { + return array( + 'name' => 'Requirements hook failure', + 'description' => "Attempts enabling a module that fails hook_requirements('install').", + 'group' => 'Module', + ); + } + + /** + * Assert that a module cannot be installed if it fails hook_requirements(). + */ + function testHookRequirementsFailure() { + $this->assertModules(array('requirements1_test'), FALSE); + + // Attempt to install the requirements1_test module. + $edit = array(); + $edit['modules[Core][requirements1_test][enable]'] = 'requirements1_test'; + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + + // Makes sure the module was NOT installed. + $this->assertText(t('Requirements 1 Test failed requirements'), t('Modules status has been updated.')); + $this->assertModules(array('requirements1_test'), FALSE); + } +} + /** * Test module dependency functionality. */ @@ -232,6 +261,30 @@ class ModuleDependencyTestCase extends ModuleTestCase { // Verify that the module has been disabled. $this->assertModules(array('system_dependencies_test'), FALSE); } + + /** + * Tests enabling a module that depends on a module which fails hook_requirements(). + */ + function testEnableRequirementsFailureDependency() { + $this->assertModules(array('requirements1_test'), FALSE); + $this->assertModules(array('requirements2_test'), FALSE); + + // Attempt to install both modules at the same time. + $edit = array(); + $edit['modules[Core][requirements1_test][enable]'] = 'requirements1_test'; + $edit['modules[Core][requirements2_test][enable]'] = 'requirements2_test'; + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + + // Makes sure the modules were NOT installed. + $this->assertText(t('Requirements 1 Test failed requirements'), t('Modules status has been updated.')); + $this->assertModules(array('requirements1_test'), FALSE); + $this->assertModules(array('requirements2_test'), FALSE); + + // Makes sure that already enabled modules the failing modules depend on + // were not disabled. + $this->assertModules(array('comment'), TRUE); + + } } /** @@ -311,11 +364,20 @@ class ModuleRequiredTestCase extends ModuleTestCase { * Assert that core required modules cannot be disabled. */ function testDisableRequired() { - $required_modules = drupal_required_modules(); + $module_info = system_get_info('module'); $this->drupalGet('admin/modules'); - foreach ($required_modules as $module) { - // Check to make sure the checkbox for required module is not found. - $this->assertNoFieldByName('modules[Core][' . $module . '][enable]'); + foreach ($module_info as $module => $info) { + // Check to make sure the checkbox for each required module is disabled + // and checked (or absent from the page if the module is also hidden). + if (!empty($info['required'])) { + $field_name = "modules[{$info['package']}][$module][enable]"; + if (empty($info['hidden'])) { + $this->assertFieldByXPath("//input[@name='$field_name' and @disabled='disabled' and @checked='checked']", '', t('Field @name was disabled and checked.', array('@name' => $field_name))); + } + else { + $this->assertNoFieldByName($field_name); + } + } } } } @@ -354,7 +416,7 @@ class IPAddressBlockingTestCase extends DrupalWebTestCase { // Block a valid IP address. $edit = array(); $edit['ip'] = '192.168.1.1'; - $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Save')); + $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add')); $ip = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $edit['ip']))->fetchField(); $this->assertTrue($ip, t('IP address found in database.')); $this->assertRaw(t('The IP address %ip has been blocked.', array('%ip' => $edit['ip'])), t('IP address was blocked.')); @@ -362,30 +424,30 @@ class IPAddressBlockingTestCase extends DrupalWebTestCase { // Try to block an IP address that's already blocked. $edit = array(); $edit['ip'] = '192.168.1.1'; - $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Save')); + $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add')); $this->assertText(t('This IP address is already blocked.')); // Try to block a reserved IP address. $edit = array(); $edit['ip'] = '255.255.255.255'; - $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Save')); + $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add')); $this->assertText(t('Enter a valid IP address.')); // Try to block a reserved IP address. $edit = array(); $edit['ip'] = 'test.example.com'; - $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Save')); + $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add')); $this->assertText(t('Enter a valid IP address.')); // Submit an empty form. $edit = array(); $edit['ip'] = ''; - $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Save')); + $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add')); $this->assertText(t('Enter a valid IP address.')); // Pass an IP address as a URL parameter and submit it. $submit_ip = '1.2.3.4'; - $this->drupalPost('admin/config/people/ip-blocking/' . $submit_ip, NULL, t('Save')); + $this->drupalPost('admin/config/people/ip-blocking/' . $submit_ip, NULL, t('Add')); $ip = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $submit_ip))->fetchField(); $this->assertTrue($ip, t('IP address found in database')); $this->assertRaw(t('The IP address %ip has been blocked.', array('%ip' => $submit_ip)), t('IP address was blocked.')); @@ -708,8 +770,6 @@ class SiteMaintenanceTestCase extends DrupalWebTestCase { $this->assertText($offline_message); $this->drupalGet('user/register'); $this->assertText($offline_message); - $this->drupalGet('user/password'); - $this->assertText($offline_message); // Verify that user is able to log in. $this->drupalGet('user'); @@ -742,6 +802,23 @@ class SiteMaintenanceTestCase extends DrupalWebTestCase { $this->drupalLogout(); $this->drupalGet(''); $this->assertRaw($offline_message, t('Found the site offline message.')); + + // Verify that custom site offline message is not displayed on user/password. + $this->drupalGet('user/password'); + $this->assertText(t('Username or e-mail address'), t('Anonymous users can access user/password')); + + // Submit password reset form. + $edit = array( + 'name' => $this->user->name, + ); + $this->drupalPost('user/password', $edit, t('E-mail new password')); + $mails = $this->drupalGetMails(); + $start = strpos($mails[0]['body'], 'user/reset/'. $this->user->uid); + $path = substr($mails[0]['body'], $start, 66 + strlen($this->user->uid)); + + // Log in with temporary login link. + $this->drupalPost($path, array(), t('Log in')); + $this->assertText($user_message); } } @@ -1164,83 +1241,6 @@ class SystemMainContentFallback extends DrupalWebTestCase { } } -class SystemSettingsForm extends DrupalWebTestCase { - /** - * Implement getInfo(). - */ - public static function getInfo() { - return array( - 'name' => 'System setting forms', - 'description' => 'Tests correctness of system_settings_form() processing.', - 'group' => 'System' - ); - } - - /** - * Implement setUp(). - */ - function setUp() { - parent::setUp(); - - variable_set('system_settings_form_test', TRUE); - variable_set('system_settings_form_test_4', TRUE); - } - - /** - * Reset page title. - */ - function tearDown() { - variable_del('system_settings_form_test'); - variable_del('system_settings_form_test_4'); - - parent::tearDown(); - } - - /** - * Tests the handling of automatic defaults in systems_settings_form(). - */ - function testAutomaticDefaults() { - $form['system_settings_form_test'] = array( - '#type' => 'checkbox', - '#default_value' => FALSE, - ); - - $form['system_settings_form_test_2'] = array( - '#type' => 'checkbox', - '#default_value' => FALSE, - ); - - $form['system_settings_form_test_3'] = array( - '#type' => 'checkbox', - '#default_value' => TRUE, - ); - - $form['has_children']['system_settings_form_test_4'] = array( - '#type' => 'checkbox', - '#default_value' => FALSE, - ); - - $form['has_children']['system_settings_form_test_5'] = array( - '#type' => 'checkbox', - '#default_value' => TRUE, - ); - - $automatic = system_settings_form($form, FALSE); - $this->assertFalse($automatic['system_settings_form_test']['#default_value']); - $this->assertFalse($automatic['system_settings_form_test_2']['#default_value']); - $this->assertTrue($automatic['system_settings_form_test_3']['#default_value']); - $this->assertFalse($automatic['has_children']['system_settings_form_test_4']['#default_value']); - $this->assertTrue($automatic['has_children']['system_settings_form_test_5']['#default_value']); - - $no_automatic = system_settings_form($form); - $this->assertTrue($no_automatic['system_settings_form_test']['#default_value']); - $this->assertFalse($no_automatic['system_settings_form_test_2']['#default_value']); - $this->assertTrue($no_automatic['system_settings_form_test_3']['#default_value']); - $this->assertTrue($no_automatic['has_children']['system_settings_form_test_4']['#default_value']); - $this->assertTrue($no_automatic['has_children']['system_settings_form_test_5']['#default_value']); - } -} - /** * Tests for the theme interface functionality. */ @@ -1686,6 +1686,31 @@ class UpdateScriptFunctionalTest extends DrupalWebTestCase { $this->assertResponse(200); } + /** + * Tests the detection of requirements for the update script to proceed. + */ + function testUpdateRequirements() { + $this->drupalLogin($this->update_user); + $this->drupalGet($this->update_url, array('external' => TRUE)); + $this->assertResponse(200); + // Test if disabling a module that another enabled module depends on will + // prevent the update from proceeding. + module_disable(array('block'), FALSE); + $this->assertFalse(module_exists('block'), t('Block module is disabled.')); + $this->assertTrue(module_exists('dashboard'), t('Dashboard module is enabled.')); + $this->drupalGet($this->update_url, array('external' => TRUE)); + $this->assertText(t('Unresolved dependency'), t('The update process cannot proceed when a module dependency is not enabled.')); + + // Test if modules required by the current install profile are not required + // to be enabled for an update to proceed. + module_enable(array('block')); + $this->assertTrue(module_exists('block'), t('Block module is enabled.')); + module_disable(array('overlay')); + $this->assertFalse(module_exists('overlay'), t('Overlay module is disabled.')); + $this->drupalGet($this->update_url, array('external' => TRUE)); + $this->assertNoText(t('Unresolved dependency'), t('The update process can proceed when modules from the install profile are disabled.')); + } + /** * Tests the effect of using the update script on the theme system. */ @@ -1808,6 +1833,10 @@ class ShutdownFunctionsTest extends DrupalWebTestCase { $this->drupalGet('system-test/shutdown-functions/' . $arg1 . '/' . $arg2); $this->assertText(t('First shutdown function, arg1 : @arg1, arg2: @arg2', array('@arg1' => $arg1, '@arg2' => $arg2))); $this->assertText(t('Second shutdown function, arg1 : @arg1, arg2: @arg2', array('@arg1' => $arg1, '@arg2' => $arg2))); + + // Make sure exceptions displayed through _drupal_render_exception_safe() + // are correctly escaped. + $this->assertText('Drupal is <blink>awesome</blink>.'); } } diff --git a/modules/system/system.tokens.inc b/modules/system/system.tokens.inc index 2c128431..f07adcf8 100644 --- a/modules/system/system.tokens.inc +++ b/modules/system/system.tokens.inc @@ -1,5 +1,5 @@ <?php -// $Id: system.tokens.inc,v 1.9 2010/04/20 09:48:06 webchick Exp $ +// $Id: system.tokens.inc,v 1.10 2010/06/29 00:57:19 dries Exp $ /** * @file @@ -89,10 +89,6 @@ function system_token_info() { 'name' => t("File ID"), 'description' => t("The unique ID of the uploaded file."), ); - $file['uid'] = array( - 'name' => t("User ID"), - 'description' => t("The unique ID of the user who owns the file."), - ); $file['name'] = array( 'name' => t("File name"), 'description' => t("The name of the file on disk."), @@ -237,10 +233,6 @@ function system_tokens($type, $tokens, array $data = array(), array $options = a $replacements[$original] = $file->fid; break; - case 'uid': - $replacements[$original] = $file->uid; - break; - // Essential file data case 'name': $replacements[$original] = $sanitize ? check_plain($file->filename) : $file->filename; diff --git a/modules/taxonomy/taxonomy.admin.inc b/modules/taxonomy/taxonomy.admin.inc index bba7f859..98d23c7e 100644 --- a/modules/taxonomy/taxonomy.admin.inc +++ b/modules/taxonomy/taxonomy.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: taxonomy.admin.inc,v 1.105 2010/05/13 07:53:02 dries Exp $ +// $Id: taxonomy.admin.inc,v 1.106 2010/06/17 13:44:45 dries Exp $ /** * @file @@ -100,17 +100,33 @@ function theme_taxonomy_overview_vocabularies($variables) { * @see taxonomy_form_vocabulary_submit() */ function taxonomy_form_vocabulary($form, &$form_state, $edit = array()) { - if (!is_array($edit)) { - $edit = (array) $edit; - } - $edit += array( - 'name' => '', - 'machine_name' => '', - 'description' => '', - 'hierarchy' => 0, - 'weight' => 0, - ); - $form['#vocabulary'] = (object) $edit; + // During initial form build, add the entity to the form state for use + // during form building and processing. During a rebuild, use what is in the + // form state. + if (!isset($form_state['vocabulary'])) { + $vocabulary = is_object($edit) ? $edit : (object) $edit; + $defaults = array( + 'name' => '', + 'machine_name' => '', + 'description' => '', + 'hierarchy' => 0, + 'weight' => 0, + ); + foreach ($defaults as $key => $value) { + if (!isset($vocabulary->$key)) { + $vocabulary->$key = $value; + } + } + $form_state['vocabulary'] = $vocabulary; + } + else { + $vocabulary = $form_state['vocabulary']; + } + + // @todo Legacy support. Modules are encouraged to access the entity using + // $form_state. Remove in Drupal 8. + $form['#vocabulary'] = $form_state['vocabulary']; + // Check whether we need a deletion confirmation form. if (isset($form_state['confirm_delete']) && isset($form_state['values']['vid'])) { return taxonomy_vocabulary_confirm_delete($form, $form_state, $form_state['values']['vid']); @@ -118,7 +134,7 @@ function taxonomy_form_vocabulary($form, &$form_state, $edit = array()) { $form['name'] = array( '#type' => 'textfield', '#title' => t('Name'), - '#default_value' => $edit['name'], + '#default_value' => $vocabulary->name, '#maxlength' => 255, '#required' => TRUE, '#field_suffix' => ' <small id="edit-name-suffix"> </small>', @@ -139,7 +155,7 @@ function taxonomy_form_vocabulary($form, &$form_state, $edit = array()) { $form['machine_name'] = array( '#type' => 'textfield', '#title' => t('Machine-readable name'), - '#default_value' => $edit['machine_name'], + '#default_value' => $vocabulary->machine_name, '#maxlength' => 255, '#description' => t('The unique machine-readable name for this vocabulary, used for theme templates. Can only contain lowercase letters, numbers, and underscores.'), '#required' => TRUE, @@ -150,7 +166,7 @@ function taxonomy_form_vocabulary($form, &$form_state, $edit = array()) { $form['description'] = array( '#type' => 'textfield', '#title' => t('Description'), - '#default_value' => $edit['description'], + '#default_value' => $vocabulary->description, ); // Set the hierarchy to "multiple parents" by default. This simplifies the // vocabulary form and standardizes the term form. @@ -161,10 +177,10 @@ function taxonomy_form_vocabulary($form, &$form_state, $edit = array()) { $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save')); - if (isset($edit['vid'])) { + if (isset($vocabulary->vid)) { $form['actions']['delete'] = array('#type' => 'submit', '#value' => t('Delete')); - $form['vid'] = array('#type' => 'value', '#value' => $edit['vid']); - $form['module'] = array('#type' => 'value', '#value' => $edit['module']); + $form['vid'] = array('#type' => 'value', '#value' => $vocabulary->vid); + $form['module'] = array('#type' => 'value', '#value' => $vocabulary->module); } return $form; } @@ -201,16 +217,17 @@ function taxonomy_form_vocabulary_validate($form, &$form_state) { * Accept the form submission for a vocabulary and save the results. */ function taxonomy_form_vocabulary_submit($form, &$form_state) { - $old_vocabulary = $form['#vocabulary']; if ($form_state['clicked_button']['#value'] == t('Delete')) { // Rebuild the form to confirm vocabulary deletion. $form_state['rebuild'] = TRUE; $form_state['confirm_delete'] = TRUE; return; } - $vocabulary = (object) $form_state['values']; - if ($vocabulary->machine_name != $old_vocabulary->machine_name) { - field_attach_rename_bundle('taxonomy_term', $old_vocabulary->machine_name, $vocabulary->machine_name); + $old_machine_name = $form_state['vocabulary']->machine_name; + $vocabulary = $form_state['vocabulary']; + entity_form_submit_build_entity('taxonomy_vocabulary', $vocabulary, $form, $form_state); + if ($vocabulary->machine_name != $old_machine_name) { + field_attach_rename_bundle('taxonomy_term', $old_machine_name, $vocabulary->machine_name); } switch (taxonomy_vocabulary_save($vocabulary)) { case SAVED_NEW: @@ -617,33 +634,45 @@ function theme_taxonomy_overview_terms($variables) { * @see taxonomy_form_term_submit() */ 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( - 'name' => '', - 'description' => '', - 'format' => filter_default_format(), - 'vocabulary_machine_name' => $vocabulary->machine_name, - 'tid' => NULL, - 'weight' => 0, - ); - - // Take into account multi-step rebuilding. - if (isset($form_state['term'])) { - $edit = $form_state['term'] + $edit; + // During initial form build, add the term entity to the form state for use + // during form building and processing. During a rebuild, use what is in the + // form state. + if (!isset($form_state['term'])) { + $term = is_object($edit) ? $edit : (object) $edit; + if (!isset($vocabulary) && isset($term->vid)) { + $vocabulary = taxonomy_vocabulary_load($term->vid); + } + $defaults = array( + 'name' => '', + 'description' => '', + 'format' => filter_default_format(), + 'vocabulary_machine_name' => isset($vocabulary) ? $vocabulary->machine_name : NULL, + 'tid' => NULL, + 'weight' => 0, + ); + foreach ($defaults as $key => $value) { + if (!isset($term->$key)) { + $term->$key = $value; + } + } + $form_state['term'] = $term; + } + else { + $term = $form_state['term']; + if (!isset($vocabulary) && isset($term->vid)) { + $vocabulary = taxonomy_vocabulary_load($term->vid); + } } - $parent = array_keys(taxonomy_get_parents($edit['tid'])); - $form['#term'] = $edit; + $parent = array_keys(taxonomy_get_parents($term->tid)); + $form['#term'] = (array) $term; $form['#term']['parent'] = $parent; $form['#vocabulary'] = $vocabulary; $form['#builder_function'] = 'taxonomy_form_term_submit_builder'; // Check for confirmation forms. if (isset($form_state['confirm_delete'])) { - return array_merge($form, taxonomy_term_confirm_delete($form, $form_state, $edit['tid'])); + return array_merge($form, taxonomy_term_confirm_delete($form, $form_state, $term->tid)); } elseif (isset($form_state['confirm_parents'])) { return array_merge($form, taxonomy_term_confirm_parents($form, $form_state, $vocabulary)); @@ -652,7 +681,7 @@ function taxonomy_form_term($form, &$form_state, $edit = array(), $vocabulary = $form['name'] = array( '#type' => 'textfield', '#title' => t('Name'), - '#default_value' => $edit['name'], + '#default_value' => $term->name, '#maxlength' => 255, '#required' => TRUE, '#weight' => -5, @@ -660,18 +689,18 @@ function taxonomy_form_term($form, &$form_state, $edit = array(), $vocabulary = $form['description'] = array( '#type' => 'text_format', '#title' => t('Description'), - '#default_value' => $edit['description'], - '#format' => $edit['format'], + '#default_value' => $term->description, + '#format' => $term->format, '#weight' => 0, ); $form['vocabulary_machine_name'] = array( '#type' => 'textfield', '#access' => FALSE, - '#value' => isset($edit['vocabulary_machine_name']) ? $edit['vocabulary_machine_name'] : $vocabulary->name, + '#value' => isset($term->vocabulary_machine_name) ? $term->vocabulary_machine_name : $vocabulary->name, ); - field_attach_form('taxonomy_term', (object) $edit, $form, $form_state); + field_attach_form('taxonomy_term', $term, $form, $form_state); $form['relations'] = array( '#type' => 'fieldset', @@ -686,23 +715,23 @@ function taxonomy_form_term($form, &$form_state, $edit = array(), $vocabulary = // full vocabulary. Contrib modules can then intercept before // hook_form_alter to provide scalable alternatives. if (!variable_get('taxonomy_override_selector', FALSE)) { - $parent = array_keys(taxonomy_get_parents($edit['tid'])); - $children = taxonomy_get_tree($vocabulary->vid, $edit['tid']); + $parent = array_keys(taxonomy_get_parents($term->tid)); + $children = taxonomy_get_tree($vocabulary->vid, $term->tid); // A term can't be the child of itself, nor of its children. foreach ($children as $child) { $exclude[] = $child->tid; } - $exclude[] = $edit['tid']; + $exclude[] = $term->tid; $tree = taxonomy_get_tree($vocabulary->vid); $options = array('<' . t('root') . '>'); if (empty($parent)) { $parent = array(0); } - foreach ($tree as $term) { - if (!in_array($term->tid, $exclude)) { - $options[$term->tid] = str_repeat('-', $term->depth) . $term->name; + foreach ($tree as $item) { + if (!in_array($item->tid, $exclude)) { + $options[$item->tid] = str_repeat('-', $item->depth) . $item->name; } } $form['relations']['parent'] = array( @@ -718,7 +747,7 @@ function taxonomy_form_term($form, &$form_state, $edit = array(), $vocabulary = '#type' => 'textfield', '#title' => t('Weight'), '#size' => 6, - '#default_value' => $edit['weight'], + '#default_value' => $term->weight, '#description' => t('Terms are displayed in ascending order by weight.'), '#required' => TRUE, ); @@ -728,7 +757,7 @@ function taxonomy_form_term($form, &$form_state, $edit = array(), $vocabulary = ); $form['tid'] = array( '#type' => 'value', - '#value' => $edit['tid'], + '#value' => $term->tid, ); $form['actions'] = array('#type' => 'actions'); @@ -738,7 +767,7 @@ function taxonomy_form_term($form, &$form_state, $edit = array(), $vocabulary = '#weight' => 5, ); - if ($edit['tid']) { + if ($term->tid) { $form['actions']['delete'] = array( '#type' => 'submit', '#value' => t('Delete'), @@ -759,7 +788,7 @@ function taxonomy_form_term($form, &$form_state, $edit = array(), $vocabulary = * @see taxonomy_form_term() */ function taxonomy_form_term_validate($form, &$form_state) { - field_attach_form_validate('taxonomy_term', (object) $form_state['values'], $form, $form_state); + entity_form_field_validate('taxonomy_term', $form, $form_state); // Ensure numeric values. if (isset($form_state['values']['weight']) && !is_numeric($form_state['values']['weight'])) { @@ -790,7 +819,7 @@ function taxonomy_form_term_submit($form, &$form_state) { return; } - $term = taxonomy_form_term_submit_builder($form, $form_state); + $term = $form['#builder_function']($form, $form_state); $status = taxonomy_term_save($term); switch ($status) { @@ -833,21 +862,16 @@ function taxonomy_form_term_submit($form, &$form_state) { } /** - * Build a term by processing form values and prepare for a form rebuild. + * Updates the form state's term entity by processing this submission's values. */ function taxonomy_form_term_submit_builder($form, &$form_state) { - $term = (object) $form_state['values']; + $term = $form_state['term']; + entity_form_submit_build_entity('taxonomy_term', $term, $form, $form_state); // 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['rebuild'] = TRUE; - return $term; } diff --git a/modules/taxonomy/taxonomy.api.php b/modules/taxonomy/taxonomy.api.php index 11f45d59..433a054d 100644 --- a/modules/taxonomy/taxonomy.api.php +++ b/modules/taxonomy/taxonomy.api.php @@ -1,5 +1,5 @@ <?php -// $Id: taxonomy.api.php,v 1.9 2010/05/14 04:41:54 webchick Exp $ +// $Id: taxonomy.api.php,v 1.10 2010/06/24 22:21:51 webchick Exp $ /** * @file @@ -26,6 +26,20 @@ function hook_taxonomy_vocabulary_load($vocabularies) { } } + +/** + * Act on taxonomy vocabularies before they are saved. + * + * Modules implementing this hook can act on the vocabulary object before it is + * inserted or updated. + * + * @param $vocabulary + * A taxonomy vocabulary object. + */ +function hook_taxonomy_vocabulary_presave($vocabulary) { + $vocabulary->foo = 'bar'; +} + /** * Act on taxonomy vocabularies when inserted. * diff --git a/modules/taxonomy/taxonomy.info b/modules/taxonomy/taxonomy.info index 4c445807..2460d84b 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/taxonomy/taxonomy.install b/modules/taxonomy/taxonomy.install index 31a1d551..385f5565 100644 --- a/modules/taxonomy/taxonomy.install +++ b/modules/taxonomy/taxonomy.install @@ -1,5 +1,5 @@ <?php -// $Id: taxonomy.install,v 1.42 2010/05/06 06:08:28 webchick Exp $ +// $Id: taxonomy.install,v 1.43 2010/05/23 19:10:23 dries Exp $ /** * @file @@ -380,6 +380,16 @@ function taxonomy_update_7004() { 'widget' => array( 'type' => $vocabulary->tags ? 'taxonomy_autocomplete' : 'select', ), + 'display' => array( + 'default' => array( + 'type' => 'taxonomy_term_reference_link', + 'weight' => 10, + ), + 'teaser' => array( + 'type' => 'taxonomy_term_reference_link', + 'weight' => 10, + ), + ), ); field_create_instance($instance); } diff --git a/modules/taxonomy/taxonomy.module b/modules/taxonomy/taxonomy.module index 7762e7e9..7a212341 100644 --- a/modules/taxonomy/taxonomy.module +++ b/modules/taxonomy/taxonomy.module @@ -1,5 +1,5 @@ <?php -// $Id: taxonomy.module,v 1.591 2010/05/21 20:27:45 dries Exp $ +// $Id: taxonomy.module,v 1.597 2010/06/27 16:49:58 dries Exp $ /** * @file @@ -101,6 +101,7 @@ function taxonomy_entity_info() { // @todo View mode for display as a field (when attached to nodes etc). 'full' => array( 'label' => t('Taxonomy term page'), + 'custom settings' => FALSE, ), ), ), @@ -146,16 +147,25 @@ function taxonomy_field_extra_fields() { $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'), - 'weight' => -5, + 'form' => array( + 'name' => array( + 'label' => t('Name'), + 'description' => t('Term name textfield'), + 'weight' => -5, + ), + 'description' => array( + 'label' => t('Description'), + 'description' => t('Term description textarea'), + 'weight' => 0, + ), + ), + 'display' => array( + 'description' => array( + 'label' => t('Description'), + 'description' => t('Term description'), + 'weight' => 0, + ), ), - 'description' => array( - 'label' => t('Description'), - 'description' => t('Term description textarea'), - 'weight' => 0, - ) ); } @@ -365,16 +375,18 @@ function taxonomy_vocabulary_save($vocabulary) { $vocabulary->module = 'taxonomy'; } + module_invoke_all('taxonomy_vocabulary_presave', $vocabulary); + if (!empty($vocabulary->vid) && !empty($vocabulary->name)) { $status = drupal_write_record('taxonomy_vocabulary', $vocabulary, 'vid'); module_invoke_all('taxonomy_vocabulary_update', $vocabulary); - entity_invoke('update', 'taxonomy_vocabulary', $vocabulary); + module_invoke_all('entity_update', $vocabulary, 'taxonomy_vocabulary'); } elseif (empty($vocabulary->vid)) { $status = drupal_write_record('taxonomy_vocabulary', $vocabulary); field_attach_create_bundle('taxonomy_term', $vocabulary->machine_name); module_invoke_all('taxonomy_vocabulary_insert', $vocabulary); - entity_invoke('insert', 'taxonomy_vocabulary', $vocabulary); + module_invoke_all('entity_insert', $vocabulary, 'taxonomy_vocabulary'); } cache_clear_all(); @@ -394,13 +406,14 @@ function taxonomy_vocabulary_save($vocabulary) { function taxonomy_vocabulary_delete($vid) { $vocabulary = (array) taxonomy_vocabulary_load($vid); - db_delete('taxonomy_vocabulary') - ->condition('vid', $vid) - ->execute(); - $result = db_query('SELECT tid FROM {taxonomy_term_data} WHERE vid = :vid', array(':vid' => $vid))->fetchCol(); + // Only load terms without a parent, child terms will get deleted too. + $result = db_query('SELECT t.tid FROM {taxonomy_term_data} t INNER JOIN {taxonomy_term_hierarchy} th ON th.tid = t.tid WHERE t.vid = :vid AND th.parent = 0', array(':vid' => $vid))->fetchCol(); foreach ($result as $tid) { taxonomy_term_delete($tid); } + db_delete('taxonomy_vocabulary') + ->condition('vid', $vid) + ->execute(); field_attach_delete_bundle('taxonomy_term', $vocabulary['machine_name']); module_invoke_all('taxonomy', 'delete', 'vocabulary', $vocabulary); @@ -475,7 +488,7 @@ function taxonomy_term_save($term) { $status = drupal_write_record('taxonomy_term_data', $term); field_attach_insert('taxonomy_term', $term); module_invoke_all('taxonomy_term_insert', $term); - entity_invoke('insert', 'taxonomy_term', $term); + module_invoke_all('entity_insert', $term, 'taxonomy_term'); if (!isset($term->parent)) { $term->parent = array(0); } @@ -484,7 +497,7 @@ function taxonomy_term_save($term) { $status = drupal_write_record('taxonomy_term_data', $term, 'tid'); field_attach_update('taxonomy_term', $term); module_invoke_all('taxonomy_term_update', $term); - entity_invoke('update', 'taxonomy_term', $term); + module_invoke_all('entity_update', $term, 'taxonomy_term'); if (isset($term->parent)) { db_delete('taxonomy_term_hierarchy') ->condition('tid', $term->tid) @@ -663,8 +676,8 @@ function taxonomy_terms_static_reset() { /** * Return an array of all vocabulary objects. * - * @param $type - * If set, return only those vocabularies associated with this node type. + * @return + * An array of all vocabulary objects, indexed by vid. */ function taxonomy_get_vocabularies() { return taxonomy_vocabulary_load_multiple(FALSE, array()); @@ -1012,6 +1025,8 @@ function _taxonomy_get_tid_from_term($term) { /** * Implodes a list of tags of a certain vocabulary into a string. + * + * @see drupal_explode_tags() */ function taxonomy_implode_tags($tags, $vid = NULL) { $typed_tags = array(); diff --git a/modules/taxonomy/taxonomy.test b/modules/taxonomy/taxonomy.test index cbd659ee..5778abb8 100644 --- a/modules/taxonomy/taxonomy.test +++ b/modules/taxonomy/taxonomy.test @@ -1,5 +1,5 @@ <?php -// $Id: taxonomy.test,v 1.78 2010/04/30 12:52:10 dries Exp $ +// $Id: taxonomy.test,v 1.81 2010/07/01 00:42:34 dries Exp $ /** * @file @@ -217,6 +217,40 @@ class TaxonomyVocabularyUnitTest extends TaxonomyWebTestCase { $this->assertTrue($vocabulary->vid == $vid, t('Valid vocabulary vid is the same as our previously invalid one.')); } + /** + * Test deleting a taxonomy that contains terms. + */ + function testTaxonomyVocabularyDeleteWithTerms() { + // Delete any existing vocabularies. + foreach (taxonomy_get_vocabularies() as $vocabulary) { + taxonomy_vocabulary_delete($vocabulary->vid); + } + + // Assert that there are no terms left. + $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {taxonomy_term_data}')->fetchField()); + + // Create a new vocabulary and add a few terms to it. + $vocabulary = $this->createVocabulary(); + $terms = array(); + for ($i = 0; $i < 5; $i++) { + $terms[$i] = $this->createTerm($vocabulary); + } + + // Set up hierarchy. term 2 is a child of 1 and 4 a child of 1 and 2. + $terms[2]->parent = array($terms[1]->tid); + taxonomy_term_save($terms[2]); + $terms[4]->parent = array($terms[1]->tid, $terms[2]->tid); + taxonomy_term_save($terms[4]); + + // Assert that there are now 5 terms. + $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {taxonomy_term_data}')->fetchField()); + + taxonomy_vocabulary_delete($vocabulary->vid); + + // Assert that there are no terms left. + $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {taxonomy_term_data}')->fetchField()); + } + /** * Ensure that the vocabulary static reset works correctly. */ @@ -347,7 +381,7 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase { 'type' => 'options_select', ), 'display' => array( - 'full' => array( + 'default' => array( 'type' => 'taxonomy_term_reference_link', ), ), @@ -848,7 +882,12 @@ class TaxonomyTermFieldTestCase extends TaxonomyWebTestCase { 'label' => $this->randomName() . '_label', 'widget' => array( 'type' => 'options_select', - ) + ), + 'display' => array( + 'full' => array( + 'type' => 'taxonomy_term_reference_link', + ), + ), ); field_create_instance($this->instance); @@ -873,7 +912,7 @@ class TaxonomyTermFieldTestCase extends TaxonomyWebTestCase { $entity = field_test_entity_test_load($id); $entities = array($id => $entity); field_attach_prepare_view($entity_type, $entities, 'full'); - $entity->content = field_attach_view($entity_type, $entity); + $entity->content = field_attach_view($entity_type, $entity, 'full'); $this->content = drupal_render($entity->content); $this->assertText($term->name, t('Term name is displayed')); } @@ -921,7 +960,7 @@ class TaxonomyTokenReplaceTestCase extends TaxonomyWebTestCase { 'type' => 'options_select', ), 'display' => array( - 'full' => array( + 'default' => array( 'type' => 'taxonomy_term_reference_link', ), ), @@ -954,7 +993,6 @@ class TaxonomyTokenReplaceTestCase extends TaxonomyWebTestCase { // Generate and test sanitized tokens. $tests = array(); $tests['[term:tid]'] = $term2->tid; - $tests['[term:vid]'] = $term2->vid; $tests['[term:name]'] = check_plain($term2->name); $tests['[term:description]'] = check_markup($term2->description, $term2->format); $tests['[term:url]'] = url('taxonomy/term/' . $term2->tid, array('absolute' => TRUE)); diff --git a/modules/taxonomy/taxonomy.tokens.inc b/modules/taxonomy/taxonomy.tokens.inc index ff2df326..f9e93461 100644 --- a/modules/taxonomy/taxonomy.tokens.inc +++ b/modules/taxonomy/taxonomy.tokens.inc @@ -1,5 +1,5 @@ <?php -// $Id: taxonomy.tokens.inc,v 1.6 2010/01/29 22:56:54 dries Exp $ +// $Id: taxonomy.tokens.inc,v 1.7 2010/07/01 00:42:34 dries Exp $ /** * @file @@ -26,10 +26,6 @@ function taxonomy_token_info() { 'name' => t("Term ID"), 'description' => t("The unique ID of the taxonomy term."), ); - $term['vid'] = array( - 'name' => t("Vocabulary ID"), - 'description' => t("The unique ID of the vocabulary the term belongs to."), - ); $term['name'] = array( 'name' => t("Name"), 'description' => t("The name of the taxonomy term."), @@ -106,10 +102,6 @@ function taxonomy_tokens($type, $tokens, array $data = array(), array $options = $replacements[$original] = $term->tid; break; - case 'vid': - $replacements[$original] = $term->vid; - break; - case 'name': $replacements[$original] = $sanitize ? check_plain($term->name) : $term->name; break; diff --git a/modules/toolbar/toolbar.css b/modules/toolbar/toolbar.css index f276c59c..d5fced76 100644 --- a/modules/toolbar/toolbar.css +++ b/modules/toolbar/toolbar.css @@ -1,6 +1,12 @@ -/* $Id: toolbar.css,v 1.21 2010/05/14 07:45:54 dries Exp $ */ +/* $Id: toolbar.css,v 1.22 2010/05/23 18:23:32 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 @@ -26,8 +32,10 @@ font: normal 0.9em "Lucida Grande", Verdana, sans-serif; background: #666; color: #ccc; -} -.displace-processed #toolbar { + position: fixed; + top: 0; + left: 0; + right: 0; margin: 0 -20px; padding: 0 20px; z-index: 600; @@ -37,14 +45,6 @@ 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,3 +132,18 @@ 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 a14c5153..740cb27b 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/toolbar/toolbar.js b/modules/toolbar/toolbar.js index 5fb9ad6c..fb1788cb 100644 --- a/modules/toolbar/toolbar.js +++ b/modules/toolbar/toolbar.js @@ -1,4 +1,4 @@ -// $Id: toolbar.js,v 1.17 2010/05/14 07:45:54 dries Exp $ +// $Id: toolbar.js,v 1.19 2010/06/08 05:16:29 webchick Exp $ (function ($) { Drupal.toolbar = Drupal.toolbar || {}; @@ -48,6 +48,7 @@ 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, @@ -69,6 +70,7 @@ 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, @@ -92,4 +94,14 @@ 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 3f978a53..9f5cd30e 100644 --- a/modules/toolbar/toolbar.module +++ b/modules/toolbar/toolbar.module @@ -1,5 +1,5 @@ <?php -// $Id: toolbar.module,v 1.39 2010/05/14 16:52:27 dries Exp $ +// $Id: toolbar.module,v 1.43 2010/07/02 02:22:26 dries Exp $ /** * @file @@ -150,6 +150,16 @@ function toolbar_preprocess_html(&$vars) { } } +/** + * Implements hook_preprocess_toolbar(). + * + * Adding the 'overlay-displace-top' class to the toolbar pushes the overlay + * down, so it appears below the toolbar. + */ +function toolbar_preprocess_toolbar(&$variables) { + $variables['classes_array'][] = "overlay-displace-top"; +} + /** * Implements hook_system_info_alter(). * @@ -177,9 +187,12 @@ function toolbar_view() { '#theme' => 'toolbar', '#attached'=> array( 'js' => array( - array('data' => 'misc/displace.js', 'weight' => JS_LIBRARY - 1), - array('data' => 'misc/jquery.cookie.js', 'weight' => JS_LIBRARY + 2), $module_path . '/toolbar.js', + array('data' => 'misc/jquery.cookie.js', 'weight' => JS_LIBRARY + 2), + array( + 'data' => array('tableHeaderOffset' => 'Drupal.toolbar.height'), + 'type' => 'setting' + ), ), 'css' => array( $module_path . '/toolbar.css', @@ -266,14 +279,13 @@ function toolbar_view() { */ function toolbar_get_menu_tree() { $tree = array(); - $admin_link = db_query("SELECT * FROM {menu_links} WHERE menu_name = 'management' AND module = 'system' AND link_path = 'admin'")->fetchAssoc(); + $admin_link = db_query('SELECT * FROM {menu_links} WHERE menu_name = :menu_name AND module = :module AND link_path = :path', array(':menu_name' => 'management', ':module' => 'system', ':path' => 'admin'))->fetchAssoc(); if ($admin_link) { - // @todo Use a function like book_menu_subtree_data(). - $tree = menu_tree_all_data('management', $admin_link, $admin_link['depth'] + 1); - // The tree will be a sub-tree with the admin link as a single root item. - // @todo It is wrong to assume it's the last. - $admin_link = array_pop($tree); - $tree = $admin_link['below'] ? $admin_link['below'] : array(); + $tree = menu_build_tree('management', array( + 'expanded' => array($admin_link['mlid']), + 'min_depth' => $admin_link['depth'] + 1, + 'max_depth' => $admin_link['depth'] + 1, + )); } return $tree; @@ -296,7 +308,7 @@ function toolbar_menu_navigation_links($tree) { $link = $item['link']['localized_options']; $link['href'] = $item['link']['href']; // Add icon placeholder. - $link['title'] = '<span class="icon"></span>' . $item['link']['title']; + $link['title'] = '<span class="icon"></span>' . check_plain($item['link']['title']); // Add admin link ID. $link['attributes'] = array('id' => 'toolbar-link-' . $id); if (!empty($item['link']['description'])) { diff --git a/modules/toolbar/toolbar.tpl.php b/modules/toolbar/toolbar.tpl.php index 8f96bbd6..d2294267 100644 --- a/modules/toolbar/toolbar.tpl.php +++ b/modules/toolbar/toolbar.tpl.php @@ -1,5 +1,5 @@ <?php -// $Id: toolbar.tpl.php,v 1.10 2010/05/14 07:45:54 dries Exp $ +// $Id: toolbar.tpl.php,v 1.11 2010/05/23 18:23:32 dries Exp $ /** * @file @@ -22,7 +22,7 @@ * @see template_preprocess_toolbar() */ ?> -<div id="toolbar" class="<?php print $classes; ?> displace-top clearfix"> +<div id="toolbar" class="<?php print $classes; ?> 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 2e11e659..fd7a8e87 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/translation/translation.info b/modules/translation/translation.info index 706d1e73..ec937f25 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/translation/translation.module b/modules/translation/translation.module index 472999ac..883810a7 100644 --- a/modules/translation/translation.module +++ b/modules/translation/translation.module @@ -1,5 +1,5 @@ <?php -// $Id: translation.module,v 1.79 2010/04/16 13:52:23 dries Exp $ +// $Id: translation.module,v 1.80 2010/06/06 00:24:16 webchick Exp $ /** * @file @@ -240,13 +240,9 @@ function translation_node_prepare($node) { $node->translation_source = $source_node; $node->title = $source_node->title; - // If user has no access to the filter used for the body, Drupal core - // does not let the edit form to appear, so we should avoid exposing - // the source text here too. - $formats = filter_formats(); - $node->body = (filter_access($formats[$source_node->body[$source_node->language][0]['format']])) ? $source_node->body : ''; - // Let every module add custom translated fields. - module_invoke_all('node_prepare_translation', $node); + // Add field translations and let other modules module add custom translated + // fields. + field_attach_prepare_translation('node', $node, $node->language, $source_node, $source_node->language); } } diff --git a/modules/translation/translation.test b/modules/translation/translation.test index c2748968..1a50fe33 100644 --- a/modules/translation/translation.test +++ b/modules/translation/translation.test @@ -1,5 +1,5 @@ <?php -// $Id: translation.test,v 1.25 2010/03/31 20:05:06 dries Exp $ +// $Id: translation.test,v 1.26 2010/06/06 00:24:16 webchick Exp $ class TranslationTestCase extends DrupalWebTestCase { protected $book; @@ -48,7 +48,7 @@ class TranslationTestCase extends DrupalWebTestCase { // Submit translation in Spanish. $node_translation_title = $this->randomName(); $node_translation_body = $this->randomName(); - $node_translation = $this->createTranslation($node->nid, $node_translation_title, $node_translation_body, 'es'); + $node_translation = $this->createTranslation($node, $node_translation_title, $node_translation_body, 'es'); // Attempt to submit a duplicate translation by visiting the node/add page // with identical query string. @@ -153,13 +153,16 @@ class TranslationTestCase extends DrupalWebTestCase { * @param string $body Body of basic page in specified language. * @param string $language Language code. */ - function createTranslation($nid, $title, $body, $language) { - $this->drupalGet('node/add/page', array('query' => array('translation' => $nid, 'language' => $language))); + function createTranslation($node, $title, $body, $language) { + $this->drupalGet('node/add/page', array('query' => array('translation' => $node->nid, 'language' => $language))); + + $body_key = "body[$language][0][value]"; + $this->assertFieldByXPath('//input[@id="edit-title"]', $node->title, "Original title value correctly populated."); + $this->assertFieldByXPath("//textarea[@name='$body_key']", $node->body[$node->language][0]['value'], "Original body value correctly populated."); $edit = array(); - $langcode = LANGUAGE_NONE; $edit["title"] = $title; - $edit["body[$language][0][value]"] = $body; + $edit[$body_key] = $body; $this->drupalPost(NULL, $edit, t('Save')); $this->assertRaw(t('Basic page %title has been created.', array('%title' => $title)), t('Translation created.')); diff --git a/modules/trigger/tests/trigger_test.info b/modules/trigger/tests/trigger_test.info index dde61ad2..965efc6c 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/trigger/tests/trigger_test.module b/modules/trigger/tests/trigger_test.module index afa4c23b..d19f8dc3 100644 --- a/modules/trigger/tests/trigger_test.module +++ b/modules/trigger/tests/trigger_test.module @@ -1,5 +1,5 @@ <?php -// $Id: trigger_test.module,v 1.7 2010/03/26 20:44:10 dries Exp $ +// $Id: trigger_test.module,v 1.8 2010/06/29 18:24:10 dries Exp $ /** * @file @@ -35,7 +35,6 @@ function trigger_test_action_info() { 'comment_insert', 'comment_update', 'comment_delete', - 'user_presave', 'user_insert', 'user_update', 'user_delete', diff --git a/modules/trigger/trigger.info b/modules/trigger/trigger.info index 19bc329a..a122cfbc 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/trigger/trigger.module b/modules/trigger/trigger.module index 72c67e9a..bca7c52a 100644 --- a/modules/trigger/trigger.module +++ b/modules/trigger/trigger.module @@ -1,5 +1,5 @@ <?php -// $Id: trigger.module,v 1.62 2010/04/11 18:33:44 dries Exp $ +// $Id: trigger.module,v 1.63 2010/06/29 18:24:10 dries Exp $ /** * @file @@ -145,9 +145,6 @@ function trigger_trigger_info() { ), ), 'user' => array( - 'user_presave' => array( - 'label' => t('When either creating a new user account or updating an existing'), - ), 'user_insert' => array( 'label' => t('After creating a new user account'), ), @@ -488,17 +485,10 @@ function trigger_user_login(&$edit, $account, $category) { * Implements hook_user_logout(). */ function trigger_user_logout($account) { - $edit = NULL; + $edit = array(); _trigger_user('user_logout', $edit, $account); } -/** - * Implements hook_user_presave(). - */ -function trigger_user_presave(&$edit, $account, $category) { - _trigger_user('user_presave', $edit, $account, $category); -} - /** * Implements hook_user_insert(). */ @@ -528,7 +518,8 @@ function trigger_user_cancel($edit, $account, $method) { * Implements hook_user_delete(). */ function trigger_user_delete($account) { - _trigger_user('user_delete', $edit, $account, $method); + $edit = array(); + _trigger_user('user_delete', $edit, $account, NULL); } /** @@ -557,7 +548,7 @@ function _trigger_user($hook, &$edit, $account, $category = NULL) { if (!isset($objects[$type])) { $objects[$type] = _trigger_normalize_user_context($type, $account); } - $context['account'] = $account; + $context['user'] = $account; actions_do($aid, $objects[$type], $context); } else { diff --git a/modules/trigger/trigger.test b/modules/trigger/trigger.test index d9af9c36..0188e7d7 100644 --- a/modules/trigger/trigger.test +++ b/modules/trigger/trigger.test @@ -1,8 +1,8 @@ <?php -// $Id: trigger.test,v 1.32 2010/05/01 08:12:23 dries Exp $ +// $Id: trigger.test,v 1.33 2010/06/29 18:24:10 dries Exp $ /** - * Class with common helper methods. + * Provides common helper methods. */ class TriggerWebTestCase extends DrupalWebTestCase { @@ -14,6 +14,7 @@ class TriggerWebTestCase extends DrupalWebTestCase { * @param $edit * The $edit array for the form to be used to configure. * Example members would be 'actions_label' (always), 'message', etc. + * * @return * the aid (action id) of the configured action, or FALSE if none. */ @@ -30,7 +31,7 @@ class TriggerWebTestCase extends DrupalWebTestCase { } /** - * Test node triggers. + * Provides tests for node triggers. */ class TriggerContentTestCase extends TriggerWebTestCase { var $_cleanup_roles = array(); @@ -49,7 +50,9 @@ class TriggerContentTestCase extends TriggerWebTestCase { } /** - * Various tests, all in one function to assure they happen in the right order. + * Tests several content-oriented trigger issues. + * + * These are in one function to assure they happen in the right order. */ function testActionsContent() { global $user; @@ -98,8 +101,10 @@ class TriggerContentTestCase extends TriggerWebTestCase { } /** - * Test that node actions are fired for each node individually if acting on - * multiple nodes. + * Tests multiple node actions. + * + * Verifies that node actions are fired for each node individually, if acting + * on multiple nodes. */ function testActionContentMultiple() { // Assign an action to the node save/update trigger. @@ -127,10 +132,13 @@ class TriggerContentTestCase extends TriggerWebTestCase { } /** - * Helper function for testActionsContent(): returns some info about each of the content actions. + * Returns some info about each of the content actions. + * + * This is helper function for testActionsContent(). * * @param $action * The name of the action to return info about. + * * @return * An associative array of info about the action. */ @@ -172,7 +180,7 @@ class TriggerContentTestCase extends TriggerWebTestCase { } /** - * Test cron trigger. + * Tests cron trigger. */ class TriggerCronTestCase extends TriggerWebTestCase { public static function getInfo() { @@ -188,7 +196,7 @@ class TriggerCronTestCase extends TriggerWebTestCase { } /** - * Test assigning multiple actions to the cron trigger. + * Tests assigning multiple actions to the cron trigger. * * This test ensures that both simple and multiple complex actions * succeed properly. This is done in the cron trigger test because @@ -239,7 +247,296 @@ class TriggerCronTestCase extends TriggerWebTestCase { } /** - * Test other triggers. + * Provides a base class with trigger assignments and test comparisons. + */ +class TriggerActionTestCase extends TriggerWebTestCase { + + function setUp() { + parent::setUp('trigger'); + } + + /** + * Creates a message with tokens. + * + * @param $trigger + * + * @return + * A message with embedded tokens. + */ + function generateMessageWithTokens($trigger) { + // Note that subject is limited to 254 characters in action configuration. + $message = t('Action was triggered by trigger @trigger user:name=[user:name] user:uid=[user:uid] user:mail=[user:mail] user:url=[user:url] user:edit-url=[user:edit-url] user:created=[user:created]', + array('@trigger' => $trigger)); + return trim($message); + } + + /** + * Generates a comparison message to match the pre-token-replaced message. + * + * @param $trigger + * Trigger, like 'user_login'. + * @param $account + * Associated user account. + * + * @return + * The token-replaced equivalent message. This does not use token + * functionality. + * + * @see generateMessageWithTokens() + */ + function generateTokenExpandedComparison($trigger, $account) { + // Note that user:last-login was omitted because it changes and can't + // be properly verified. + $message = t('Action was triggered by trigger @trigger user:name=@username user:uid=@uid user:mail=@mail user:url=@user_url user:edit-url=@user_edit_url user:created=@user_created', + array( + '@trigger' => $trigger, + '@username' => $account->name, + '@uid' => !empty($account->uid) ? $account->uid : t('not yet assigned'), + '@mail' => $account->mail, + '@user_url' => !empty($account->uid) ? url("user/$account->uid", array('absolute' => TRUE)) : t('not yet assigned'), + '@user_edit_url' => !empty($account->uid) ? url("user/$account->uid/edit", array('absolute' => TRUE)) : t('not yet assigned'), + '@user_created' => isset($account->created) ? format_date($account->created, 'medium') : t('not yet created'), + ) + ); + return trim($message); + } + + + /** + * Assigns a simple (non-configurable) action to a trigger. + * + * @param $trigger + * The trigger to assign to, like 'user_login'. + * @param $action + * The simple action to be assigned, like 'comment_insert'. + */ + function assignSimpleAction($trigger, $action) { + $form_name = "trigger_{$trigger}_assign_form"; + $form_html_id = strtr($form_name, '_', '-'); + $edit = array('aid' => drupal_hash_base64($action)); + $trigger_type = preg_replace('/_.*/', '', $trigger); + $this->drupalPost("admin/structure/trigger/$trigger_type", $edit, t('Assign'), array(), array(), $form_html_id); + $actions = trigger_get_assigned_actions($trigger); + $this->assertTrue(!empty($actions[$action]), t('Simple action @action assigned to trigger @trigger', array('@action' => $action, '@trigger' => $trigger))); + } + + /** + * Assigns a system message action to the passed-in trigger. + * + * @param $trigger + * For example, 'user_login' + */ + function assignSystemMessageAction($trigger) { + $form_name = "trigger_{$trigger}_assign_form"; + $form_html_id = strtr($form_name, '_', '-'); + // Assign a configurable action 'System message' to the passed trigger. + $action_edit = array( + 'actions_label' => $trigger . "_system_message_action_" . $this->randomName(16), + 'message' => $this->generateMessageWithTokens($trigger), + ); + + // Configure an advanced action that we can assign. + $aid = $this->configureAdvancedAction('system_message_action', $action_edit); + + $edit = array('aid' => drupal_hash_base64($aid)); + $this->drupalPost('admin/structure/trigger/user', $edit, t('Assign'), array(), array(), $form_html_id); + } + + + /** + * Assigns a system_send_email_action to the passed-in trigger. + * + * @param $trigger + * For example, 'user_login' + */ + function assignSystemEmailAction($trigger) { + $form_name = "trigger_{$trigger}_assign_form"; + $form_html_id = strtr($form_name, '_', '-'); + + $message = $this->generateMessageWithTokens($trigger); + // Assign a configurable action 'System message' to the passed trigger. + $action_edit = array( + // 'actions_label' => $trigger . "_system_send_message_action_" . $this->randomName(16), + 'actions_label' => $trigger . "_system_send_email_action", + 'recipient' => '[user:mail]', + 'subject' => $message, + 'message' => $message, + ); + + // Configure an advanced action that we can assign. + $aid = $this->configureAdvancedAction('system_send_email_action', $action_edit); + + $edit = array('aid' => drupal_hash_base64($aid)); + $this->drupalPost('admin/structure/trigger/user', $edit, t('Assign'), array(), array(), $form_html_id); + } + + /** + * Asserts correct token replacement in both system message and email. + * + * @param $trigger + * A trigger like 'user_login'. + * @param $account + * The user account which triggered the action. + * @param $email_depth + * Number of emails to scan, starting with most recent. + */ + function assertSystemMessageAndEmailTokenReplacement($trigger, $account, $email_depth = 1) { + $this->assertSystemMessageTokenReplacement($trigger, $account); + $this->assertSystemEmailTokenReplacement($trigger, $account, $email_depth); + } + + /** + * Asserts correct token replacement for the given trigger and account. + * + * @param $trigger + * A trigger like 'user_login'. + * @param $account + * The user account which triggered the action. + */ + function assertSystemMessageTokenReplacement($trigger, $account) { + $expected = $this->generateTokenExpandedComparison($trigger, $account); + $this->assertText($expected, + t('Expected system message to contain token-replaced text "@expected" found in configured system message action', array('@expected' => $expected )) ); + } + + + /** + * Asserts correct token replacement for the given trigger and account. + * + * @param $trigger + * A trigger like 'user_login'. + * @param $account + * The user account which triggered the action. + * @param $email_depth + * Number of emails to scan, starting with most recent. + */ + function assertSystemEmailTokenReplacement($trigger, $account, $email_depth = 1) { + $this->verboseEmail($email_depth); + $expected = $this->generateTokenExpandedComparison($trigger, $account); + $this->assertMailString('subject', $expected, $email_depth); + $this->assertMailString('body', $expected, $email_depth); + $this->assertMail('to', $account->mail, t('Mail sent to correct destination')); + } +} + +/** + * Tests token substitution in trigger actions. + * + * This tests nearly every permutation of user triggers with system actions + * and checks the token replacement. + */ +class TriggerUserTokenTestCase extends TriggerActionTestCase { + public static function getInfo() { + return array( + 'name' => 'Test user triggers', + 'description' => 'Test user triggers and system actions with token replacement.', + 'group' => 'Trigger', + ); + } + + + /** + * Tests a variety of token replacements in actions. + */ + function testUserTriggerTokenReplacement() { + $test_user = $this->drupalCreateUser(array('administer actions', 'administer users', 'change own username', 'access user profiles')); + $this->drupalLogin($test_user); + + $triggers = array('user_login', 'user_insert', 'user_update', 'user_delete', 'user_logout', 'user_view'); + foreach ($triggers as $trigger) { + $this->assignSystemMessageAction($trigger); + $this->assignSystemEmailAction($trigger); + } + + $this->drupalLogout(); + $this->assertSystemEmailTokenReplacement('user_logout', $test_user); + + $this->drupalLogin($test_user); + $this->assertSystemMessageAndEmailTokenReplacement('user_login', $test_user, 2); + $this->assertSystemMessageAndEmailTokenReplacement('user_view', $test_user, 2); + + $this->drupalPost("user/{$test_user->uid}/edit", array('name' => $test_user->name . '_changed'), t('Save')); + $test_user->name .= '_changed'; // Since we just changed it. + $this->assertSystemMessageAndEmailTokenReplacement('user_update', $test_user, 2); + + $this->drupalGet('user'); + $this->assertSystemMessageAndEmailTokenReplacement('user_view', $test_user); + + $new_user = $this->drupalCreateUser(array('administer actions', 'administer users', 'cancel account', 'access administration pages')); + $this->assertSystemEmailTokenReplacement('user_insert', $new_user); + + $this->drupalLogin($new_user); + $user_to_delete = $this->drupalCreateUser(array('access content')); + variable_set('user_cancel_method', 'user_cancel_delete'); + + $this->drupalPost("user/{$user_to_delete->uid}/cancel", array(), t('Cancel account')); + $this->assertSystemMessageAndEmailTokenReplacement('user_delete', $user_to_delete); + } + + +} + +/** + * Tests token substitution in trigger actions. + * + * This tests nearly every permutation of user triggers with system actions + * and checks the token replacement. + */ +class TriggerUserActionTestCase extends TriggerActionTestCase { + public static function getInfo() { + return array( + 'name' => 'Test user actions', + 'description' => 'Test user actions.', + 'group' => 'Trigger', + ); + } + + /** + * Tests user action assignment and execution. + */ + function testUserActionAssignmentExecution() { + $test_user = $this->drupalCreateUser(array('administer actions', 'create article content', 'access comments', 'administer comments', 'post comments without approval', 'edit own comments')); + $this->drupalLogin($test_user); + + $triggers = array('comment_presave', 'comment_insert', 'comment_update'); + // system_block_ip_action is difficult to test without ruining the test. + $actions = array('user_block_user_action'); + foreach ($triggers as $trigger) { + foreach ($actions as $action) { + $this->assignSimpleAction($trigger, $action); + } + } + + $node = $this->drupalCreateNode(array('type' => 'article')); + $this->drupalPost("node/{$node->nid}", array('comment_body[und][0][value]' => t("my comment"), 'subject' => t("my comment subject")), t('Save')); + // Posting a comment should have blocked this user. + $account = user_load($test_user->uid, TRUE); + $this->assertTrue($account->status == 0, t('Account is blocked')); + $comment_author_uid = $account->uid; + // Now rehabilitate the comment author so it can be be blocked again when + // the comment is updated. + user_save($account, array('status' => TRUE)); + + $test_user = $this->drupalCreateUser(array('administer actions', 'create article content', 'access comments', 'administer comments', 'post comments without approval', 'edit own comments')); + $this->drupalLogin($test_user); + + // Our original comment will have been comment 1. + $this->drupalPost("comment/1/edit", array('comment_body[und][0][value]' => t("my comment, updated"), 'subject' => t("my comment subject")), t('Save')); + $comment_author_account = user_load($comment_author_uid, TRUE); + $this->assertTrue($comment_author_account->status == 0, t('Comment author account (uid=@uid) is blocked after update to comment', array('@uid' => $comment_author_uid))); + + // Verify that the comment was updated. + $test_user = $this->drupalCreateUser(array('administer actions', 'create article content', 'access comments', 'administer comments', 'post comments without approval', 'edit own comments')); + $this->drupalLogin($test_user); + + $this->drupalGet("node/$node->nid"); + $this->assertText(t("my comment, updated")); + $this->verboseEmail(); + } +} + +/** + * Tests other triggers. */ class TriggerOtherTestCase extends TriggerWebTestCase { var $_cleanup_roles = array(); @@ -258,7 +555,7 @@ class TriggerOtherTestCase extends TriggerWebTestCase { } /** - * Test triggering on user create and user login. + * Tests triggering on user create and user login. */ function testActionsUser() { // Assign an action to the create user trigger. @@ -315,7 +612,7 @@ class TriggerOtherTestCase extends TriggerWebTestCase { } /** - * Test triggering on comment save. + * Tests triggering on comment save. */ function testActionsComment() { // Assign an action to the comment save trigger. @@ -344,7 +641,7 @@ class TriggerOtherTestCase extends TriggerWebTestCase { } /** - * Test triggering on taxonomy new term. + * Tests triggering on taxonomy new term. */ function testActionsTaxonomy() { // Assign an action to the taxonomy term save trigger. @@ -382,7 +679,7 @@ class TriggerOtherTestCase extends TriggerWebTestCase { } /** - * Test that orphaned actions are properly handled. + * Tests that orphaned actions are properly handled. */ class TriggerOrphanedActionsTestCase extends DrupalWebTestCase { @@ -399,7 +696,7 @@ class TriggerOrphanedActionsTestCase extends DrupalWebTestCase { } /** - * Test logic around orphaned actions. + * Tests logic around orphaned actions. */ function testActionsOrphaned() { $action = 'trigger_test_generic_any_action'; diff --git a/modules/update/tests/aaa_update_test.info b/modules/update/tests/aaa_update_test.info index 3446b3c8..9900353e 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/update/tests/bbb_update_test.info b/modules/update/tests/bbb_update_test.info index 9506f04d..72ec11c3 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/update/tests/ccc_update_test.info b/modules/update/tests/ccc_update_test.info index 5627ee6b..139d2ad0 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/update/tests/update_test.info b/modules/update/tests/update_test.info index fde94233..5b2e0e14 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/update/update.info b/modules/update/update.info index 171fa18a..ee88b84b 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/update/update.install b/modules/update/update.install index 242b04d3..00d659be 100644 --- a/modules/update/update.install +++ b/modules/update/update.install @@ -1,5 +1,5 @@ <?php -// $Id: update.install,v 1.18 2010/05/19 19:14:43 dries Exp $ +// $Id: update.install,v 1.19 2010/06/25 17:47:22 dries Exp $ /** * @file @@ -159,6 +159,11 @@ function _update_requirement_check($project, $type) { return $requirement; } +/** + * @defgroup updates-6.x-to-7.x Filter updates from 6.x to 7.x + * @{ + */ + /** * Create a queue to store tasks for requests to fetch available update data. */ @@ -169,8 +174,19 @@ function update_update_7000() { } /** - * Remove {cache_update}.headers column. + * Recreates cache_update table. + * + * Converts fields that hold serialized variables from text to blob. + * Removes 'headers' column. */ function update_update_7001() { - db_drop_field('cache_update', 'headers'); + $schema = system_schema_cache_7054(); + + db_drop_table('cache_update'); + db_create_table('cache_update', $schema); } + +/** + * @} End of "defgroup updates-6.x-to-7.x" + * The next series of updates should start at 8000. + */ diff --git a/modules/update/update.manager.inc b/modules/update/update.manager.inc index 8dbef154..7c437b9a 100644 --- a/modules/update/update.manager.inc +++ b/modules/update/update.manager.inc @@ -1,5 +1,5 @@ <?php -// $Id: update.manager.inc,v 1.22 2010/05/14 04:50:18 webchick Exp $ +// $Id: update.manager.inc,v 1.23 2010/05/26 11:50:58 dries Exp $ /** * @file @@ -66,8 +66,6 @@ function update_manager_update_form($form, $form_state = array(), $context) { return $form; } - $form['#attached']['library'][] = array('system', 'ui.dialog'); - $form['#attached']['js'][] = drupal_get_path('module', 'update') . '/update.manager.js'; $form['#attached']['css'][] = drupal_get_path('module', 'update') . '/update.css'; // This will be a nested array. The first key is the kind of project, which diff --git a/modules/update/update.settings.inc b/modules/update/update.settings.inc index d1afb548..aa862a83 100644 --- a/modules/update/update.settings.inc +++ b/modules/update/update.settings.inc @@ -1,5 +1,5 @@ <?php -// $Id: update.settings.inc,v 1.10 2009/12/02 01:05:11 dries Exp $ +// $Id: update.settings.inc,v 1.11 2010/06/26 21:32:20 dries Exp $ /** * @file @@ -47,7 +47,7 @@ function update_settings($form) { '#description' => t('You can choose to send e-mail only if a security update is available, or to be notified about all newer versions. If there are updates available of Drupal core or any of your installed modules and themes, your site will always print a message on the <a href="@status_report">status report</a> page, and will also display an error message on administration pages if there is a security update.', array('@status_report' => url('admin/reports/status'))) ); - $form = system_settings_form($form, FALSE); + $form = system_settings_form($form); // Custom validation callback for the email notification setting. $form['#validate'][] = 'update_settings_validate'; // We need to call our own submit callback first, not the one from diff --git a/modules/user/user.admin.inc b/modules/user/user.admin.inc index dedfd1d5..50e4a5ed 100644 --- a/modules/user/user.admin.inc +++ b/modules/user/user.admin.inc @@ -1,5 +1,5 @@ <?php -// $Id: user.admin.inc,v 1.109 2010/05/06 05:59:31 webchick Exp $ +// $Id: user.admin.inc,v 1.114 2010/07/07 17:56:42 webchick Exp $ /** * @file @@ -297,11 +297,11 @@ function user_admin_settings() { $form['registration_cancellation']['user_register'] = array( '#type' => 'radios', '#title' => t('Who can register accounts?'), - '#default_value' => variable_get('user_register', 1), + '#default_value' => variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL), '#options' => array( - t('Administrators only'), - t('Visitors'), - t('Visitors, but administrator approval is required'), + USER_REGISTER_ADMINISTRATORS_ONLY => t('Administrators only'), + USER_REGISTER_VISITORS => t('Visitors'), + USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL => t('Visitors, but administrator approval is required'), ) ); $form['registration_cancellation']['user_email_verification'] = array( @@ -422,13 +422,13 @@ function user_admin_settings() { ); // These email tokens are shared for all settings, so just define // the list once to help ensure they stay in sync. - $email_token_help = t('You can use the following tokens in your e-mail message text: !site-name-token, !site-url-token, !user-name-token, !user-mail-token, !site-login-url-token, !user-edit-url-token, !user-one-time-login-url-token, !user-cancel-url-token', array('!site-name-token' => '[site:name]', '!site-url-token' => '[site:url]', '!user-name-token' => '[user:name]', '!user-mail-token' => '[user:mail]', '!site-login-url-token' => '[site:login-url]', '!user-edit-url-token' => '[user:edit-url]', '!user-one-time-login-url-token' => '[user:one-time-login-url]', '!user-cancel-url-token' => '[user:cancel-url]')); + $email_token_help = t('Available variables are: [site:name], [site:url], [user:name], [user:mail], [site:login-url], [site:url-brief], [user:edit-url], [user:one-time-login-url], [user:cancel-url].'); $form['email_admin_created'] = array( '#type' => 'fieldset', '#title' => t('Welcome (new user created by administrator)'), '#collapsible' => TRUE, - '#collapsed' => (variable_get('user_register', 1) != 0), + '#collapsed' => (variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) != USER_REGISTER_ADMINISTRATORS_ONLY), '#description' => t('Edit the welcome e-mail messages sent to new member accounts created by an administrator.') . ' ' . $email_token_help, '#group' => 'email', ); @@ -449,7 +449,7 @@ function user_admin_settings() { '#type' => 'fieldset', '#title' => t('Welcome (awaiting approval)'), '#collapsible' => TRUE, - '#collapsed' => (variable_get('user_register', 1) != 2), + '#collapsed' => (variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) != USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL), '#description' => t('Edit the welcome e-mail messages sent to new members upon registering, when administrative approval is required.') . ' ' . $email_token_help, '#group' => 'email', ); @@ -470,7 +470,7 @@ function user_admin_settings() { '#type' => 'fieldset', '#title' => t('Welcome (no approval required)'), '#collapsible' => TRUE, - '#collapsed' => (variable_get('user_register', 1) != 1), + '#collapsed' => (variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) != USER_REGISTER_VISITORS), '#description' => t('Edit the welcome e-mail messages sent to new members upon registering, when no administrator approval is required.') . ' ' . $email_token_help, '#group' => 'email', ); @@ -635,7 +635,7 @@ function user_admin_settings() { '#rows' => 3, ); - return system_settings_form($form, FALSE); + return system_settings_form($form); } /** diff --git a/modules/user/user.info b/modules/user/user.info index 5e3139cc..7fd1d34c 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/modules/user/user.install b/modules/user/user.install index 21f1da02..6ed94a7d 100644 --- a/modules/user/user.install +++ b/modules/user/user.install @@ -1,5 +1,5 @@ <?php -// $Id: user.install,v 1.46 2010/05/05 06:55:25 webchick Exp $ +// $Id: user.install,v 1.54 2010/06/28 14:59:24 webchick Exp $ /** * @file @@ -144,7 +144,7 @@ function user_schema() { 'length' => 254, 'not null' => FALSE, 'default' => '', - 'description' => "User's email address.", + 'description' => "User's e-mail address.", ), 'theme' => array( 'type' => 'varchar', @@ -160,6 +160,13 @@ function user_schema() { 'default' => '', 'description' => "User's signature.", ), + 'signature_format' => array( + 'type' => 'int', + 'size' => 'small', + 'not null' => TRUE, + 'default' => 0, + 'description' => 'The {filter_format}.format of the signature.', + ), 'created' => array( 'type' => 'int', 'not null' => TRUE, @@ -206,13 +213,13 @@ function user_schema() { ), 'init' => array( 'type' => 'varchar', - 'length' => 64, + 'length' => 254, 'not null' => FALSE, 'default' => '', - 'description' => 'Email address used for initial account creation.', + 'description' => 'E-mail address used for initial account creation.', ), 'data' => array( - 'type' => 'text', + 'type' => 'blob', 'not null' => FALSE, 'size' => 'big', 'serialize' => TRUE, @@ -228,6 +235,9 @@ function user_schema() { 'name' => array('name'), ), 'primary key' => array('uid'), + 'foreign keys' => array( + 'signature_format' => array('filter_format' => 'format'), + ), ); $schema['users_roles'] = array( @@ -313,6 +323,23 @@ function user_install() { } } +/** + * Implements hook_update_dependencies(). + */ +function user_update_dependencies() { + // Run all the critical user upgrades before everything. + $dependencies['system'][7000] = array( + 'user' => 7008, + ); + // user_update_7006 relies on filter_update_7002. + // TODO: move user_update_7006 down below in the upgrade process. + $dependencies['user'][7006] = array( + 'filter' => 7002, + ); + + return $dependencies; +} + /** * @defgroup user-updates-6.x-to-7.x User updates from 6.x to 7.x * @{ @@ -548,11 +575,26 @@ function user_update_7004(&$sandbox) { } /** - * Change the users table to allow longer email addresses - 254 characters instead of 64. + * Changes the users table to allow longer e-mail addresses. */ function user_update_7005(&$sandbox) { - $schema = user_schema(); - db_change_field('users', 'mail', 'mail', $schema['users']['fields']['mail']); + $mail_field = array( + 'type' => 'varchar', + 'length' => 254, + 'not null' => FALSE, + 'default' => '', + 'description' => "User's e-mail address.", + ); + $init_field = array( + 'type' => 'varchar', + 'length' => 254, + 'not null' => FALSE, + 'default' => '', + 'description' => 'E-mail address used for initial account creation.', + ); + db_drop_index('users', 'mail'); + db_change_field('users', 'mail', 'mail', $mail_field, array('indexes' => array('mail' => array('mail')))); + db_change_field('users', 'init', 'init', $init_field); } /** @@ -589,6 +631,89 @@ function user_update_7007() { db_add_index('role', 'name_weight', array('name', 'weight')); } +/** + * If 'user_register' variable was unset in Drupal 6, set it to be the same as + * the Drupal 6 default setting. + */ +function user_update_7008() { + if (!isset($GLOBALS['conf']['user_register'])) { + // Set to the Drupal 6 default, "visitors can create accounts". + variable_set('user_register', USER_REGISTER_VISITORS); + } +} + +/** + * Converts fields that store serialized variables from text to blob. + */ +function user_update_7009() { + $spec = array( + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big', + 'serialize' => TRUE, + 'description' => 'A serialized array of name value pairs that are related to the user. Any form values posted during user edit are stored and are loaded into the $user object during user_load(). Use of this field is discouraged and it will likely disappear in a future version of Drupal.', + ); + db_change_field('users', 'data', 'data', $spec); +} + +/** + * Update the {user}.signature_format column. + */ +function user_update_7010() { + // It was previously possible for a value of "0" to be stored in database + // tables to indicate that a particular piece of text should be filtered + // using the default text format. + $default_format = variable_get('filter_default_format', 1); + db_update('users') + ->fields(array('signature_format' => $default_format)) + ->condition('signature_format', 0) + ->execute(); +} + + +/** + * Updates email templates to use new tokens. + * + * This function upgrades customized email templates from the old !token format + * to the new core tokens format. Additionally, in Drupal 7 we no longer e-mail + * plain text passwords to users, and there is no token for a plain text + * password in the new token system. Therefore, it also modifies any saved + * templates using the old '!password' token such that the token is removed, and + * displays a warning to users that they may need to go and modify the wording + * of their templates. + */ +function user_update_7011() { + $message = ''; + + $tokens = array( + '!site-name-token' => '[site:name]', + '!site-url-token' => '[site:url]', + '!user-name-token' => '[user:name]', + '!user-mail-token' => '[user:mail]', + '!site-login-url-token' => '[site:login-url]', + '!site-url-brief-token' => '[site:url-brief]', + '!user-edit-url-token' => '[user:edit-url]', + '!user-one-time-login-url-token' => '[user:one-time-login-url]', + '!user-cancel-url-token' => '[user:cancel-url]', + '!password' => '', + ); + + $result = db_select('variable', 'v') + ->fields('v', array('name', 'value')) + ->condition('value', db_like('user_mail_') . '%', 'LIKE') + ->execute(); + + foreach ($result as $row) { + if (empty($message) && (strpos($row->value, '!password') !== FALSE)) { + $message = t('The ability to send users their passwords in plain text has been removed in Drupal 7. Your existing email templates have been modified to remove it. You should <a href="@template-url">review these templates</a> to make sure they read properly.', array('@template-url' => url('admin/config/people/accounts'))); + } + + variable_set($row->name, str_replace(array_keys($tokens), $tokens, $row->value)); + } + + return $message; +} + /** * @} End of "defgroup user-updates-6.x-to-7.x" * The next series of updates should start at 8000. diff --git a/modules/user/user.module b/modules/user/user.module index 505fe687..fcb915a2 100644 --- a/modules/user/user.module +++ b/modules/user/user.module @@ -1,5 +1,5 @@ <?php -// $Id: user.module,v 1.1170 2010/05/12 08:35:39 dries Exp $ +// $Id: user.module,v 1.1180 2010/07/07 08:05:01 webchick Exp $ /** * @file @@ -16,6 +16,22 @@ define('USERNAME_MAX_LENGTH', 60); */ define('EMAIL_MAX_LENGTH', 254); +/** + * Only administrators can create user accounts. + */ +define('USER_REGISTER_ADMINISTRATORS_ONLY', 0); + +/** + * Visitors can create their own accounts. + */ +define('USER_REGISTER_VISITORS', 1); + +/** + * Visitors can create accounts, but they don't become active without + * administrative approval. + */ +define('USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL', 2); + /** * Implement hook_help(). */ @@ -146,6 +162,7 @@ function user_entity_info() { 'view modes' => array( 'full' => array( 'label' => t('User account'), + 'custom settings' => FALSE, ), ), ), @@ -167,20 +184,24 @@ function user_uri($user) { */ function user_field_extra_fields() { $return['user']['user'] = array( - 'account' => array( - 'label' => 'User name and password', - 'description' => t('User module account form elements'), - 'weight' => -10, - ), - 'timezone' => array( - 'label' => 'Timezone', - 'description' => t('User module timezone form element.'), - 'weight' => 6, + 'form' => array( + 'account' => array( + 'label' => 'User name and password', + 'description' => t('User module account form elements'), + 'weight' => -10, + ), + 'timezone' => array( + 'label' => 'Timezone', + 'description' => t('User module timezone form element.'), + 'weight' => 6, + ), ), - 'summary' => array( - 'label' => 'History', - 'description' => t('User module history view element.'), - 'weight' => 5, + 'display' => array( + 'summary' => array( + 'label' => 'History', + 'description' => t('User module history view element.'), + 'weight' => 5, + ), ), ); @@ -490,7 +511,7 @@ function user_save($account, $edit = array(), $category = 'account') { } user_module_invoke('update', $edit, $user, $category); - entity_invoke('update', 'user', $user); + module_invoke_all('entity_update', $user, 'user'); } else { // Allow 'uid' to be set by the caller. There is no danger of writing an @@ -523,7 +544,7 @@ function user_save($account, $edit = array(), $category = 'account') { field_attach_insert('user', $user); user_module_invoke('insert', $edit, $user, $category); - entity_invoke('insert', 'user', $user); + module_invoke_all('entity_insert', $user, 'user'); // Save user roles. if (isset($edit['roles']) && is_array($edit['roles'])) { @@ -543,7 +564,8 @@ function user_save($account, $edit = array(), $category = 'account') { return $user; } catch (Exception $e) { - $transaction->rollback('user', $e->getMessage(), array(), WATCHDOG_ERROR); + $transaction->rollback(); + watchdog_exception('user', $e); throw $e; } } @@ -920,7 +942,7 @@ 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() @@ -1015,7 +1037,7 @@ function user_account_form(&$form, &$form_state) { $status = isset($account->status) ? $account->status : 1; } else { - $status = $register ? variable_get('user_register', 1) == 1 : $account->status; + $status = $register ? variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) == USER_REGISTER_VISITORS : $account->status; } $form['account']['status'] = array( '#type' => 'radios', @@ -1060,11 +1082,13 @@ function user_account_form(&$form, &$form_state) { '#weight' => 1, '#access' => (!$register && variable_get('user_signatures', 0)), ); + $form['signature_settings']['signature'] = array( - '#type' => 'textarea', + '#type' => 'text_format', '#title' => t('Signature'), '#default_value' => isset($account->signature) ? $account->signature : '', '#description' => t('Your signature will be publicly displayed at the end of your comments.'), + '#format' => isset($account->signature_format) ? $account->signature_format : NULL, ); // Picture/avatar. @@ -1098,7 +1122,7 @@ 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) { @@ -1124,7 +1148,7 @@ 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) { @@ -1157,6 +1181,11 @@ function user_account_form_validate($form, &$form_state) { // Make sure the signature isn't longer than the size of the database field. // Signatures are disabled by default, so make sure it exists first. if (isset($form_state['values']['signature'])) { + // Move text format for user signature into 'signature_format'. + $form_state['values']['signature_format'] = $form_state['values']['signature']['format']; + // Move text value for user signature into 'signature'. + $form_state['values']['signature'] = $form_state['values']['signature']['value']; + $user_schema = drupal_get_schema('users'); if (drupal_strlen($form_state['values']['signature']) > $user_schema['fields']['signature']['length']) { form_set_error('signature', t('The signature is too long: it must be %max characters or less.', array('%max' => $user_schema['fields']['signature']['length']))); @@ -1224,7 +1253,7 @@ function user_login_block($form) { '#value' => t('Log in'), ); $items = array(); - if (variable_get('user_register', 1)) { + if (variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)) { $items[] = l(t('Create new account'), 'user/register', array('attributes' => array('title' => t('Create a new user account.')))); } $items[] = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.')))); @@ -1426,7 +1455,7 @@ function user_is_logged_in() { } function user_register_access() { - return user_is_anonymous() && variable_get('user_register', 1); + return user_is_anonymous() && variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL); } @@ -1711,6 +1740,48 @@ function user_menu() { return $items; } +/** + * Implements hook_menu_site_status_alter(). + */ +function user_menu_site_status_alter(&$menu_site_status, $path) { + if ($menu_site_status == MENU_SITE_OFFLINE) { + // If the site is offline, log out unprivileged users. + if (user_is_logged_in() && !user_access('access site in maintenance mode')) { + module_load_include('pages.inc', 'user', 'user'); + user_logout(); + } + + if (user_is_anonymous()) { + switch ($path) { + case 'user': + // Forward anonymous user to login page. + drupal_goto('user/login'); + case 'user/login': + case 'user/password': + // Disable offline mode. + $menu_site_status = MENU_SITE_ONLINE; + break; + default: + if (strpos($path, 'user/reset/') === 0) { + // Disable offline mode. + $menu_site_status = MENU_SITE_ONLINE; + } + break; + } + } + } + if (user_is_logged_in()) { + if ($path == 'user/login') { + // If user is logged in, redirect to 'user' instead of giving 403. + drupal_goto('user'); + } + if ($path == 'user/register') { + // Authenticated user should be redirected to user edit page. + drupal_goto('user/' . $GLOBALS['user']->uid . '/edit'); + } + } +} + /** * Implements hook_init(). */ @@ -2085,8 +2156,8 @@ function user_login_submit($form, &$form_state) { * populated and login tasks are performed. */ function user_external_login_register($name, $module) { - $account = user_load_by_name($name); - if (!$account->uid) { + $account = user_external_load($name); + if (!$account) { // Register this new user. $userinfo = array( 'name' => $name, @@ -2387,141 +2458,141 @@ function _user_mail_text($key, $language = NULL, $variables = array(), $replace // No override, return default string. switch ($key) { case 'register_no_approval_required_subject': - $text = t('Account details for !user-name-token at !site-name-token', array('!user-name-token' => '[user:name]', '!site-name-token' => '[site:name]'), array('langcode' => $langcode)); + $text = t('Account details for [user:name] at [site:name]', array(), array('langcode' => $langcode)); break; case 'register_no_approval_required_body': - $text = t("!user-name-token, + $text = t("[user:name], -Thank you for registering at !site-name-token. You may now log in by clicking this link or copying and pasting it to your browser: +Thank you for registering at [site:name]. You may now log in by clicking this link or copying and pasting it to your browser: -!one-time-login-token +[user:one-time-login-url] This link can only be used once to log in and will lead you to a page where you can set your password. -After setting your password, you will be able to log in at !site-login-url-token in the future using: +After setting your password, you will be able to log in at [site:login-url] in the future using: -username: !user-name-token +username: [user:name] password: Your password --- !site-name-token team", array('!user-name-token' => '[user:name]', '!site-name-token' => '[site:name]', '!one-time-login-token' => '[user:one-time-login-url]', '!site-login-url-token' => '[site:login-url]'), array('langcode' => $langcode)); +-- [site:name] team", array(), array('langcode' => $langcode)); break; case 'register_admin_created_subject': - $text = t('An administrator created an account for you at !site-name-token', array('!site-name-token' => '[site:name]'), array('langcode' => $langcode)); + $text = t('An administrator created an account for you at [site:name]', array(), array('langcode' => $langcode)); break; case 'register_admin_created_body': - $text = t("!user-name-token, + $text = t("[user:name], -A site administrator at !site-name-token has created an account for you. You may now log in by clicking this link or copying and pasting it to your browser: +A site administrator at [site:name] has created an account for you. You may now log in by clicking this link or copying and pasting it to your browser: -!one-time-login-token +[user:one-time-login-url] This link can only be used once to log in and will lead you to a page where you can set your password. -After setting your password, you will be able to log in at !site-login-url-token in the future using: +After setting your password, you will be able to log in at [site:login-url] in the future using: -username: !user-name-token +username: [user:name] password: Your password --- !site-name-token team", array('!user-name-token' => '[user:name]', '!site-name-token' => '[site:name]', '!one-time-login-token' => '[user:one-time-login-url]', '!site-login-url-token' => '[site:login-url]'), array('langcode' => $langcode)); +-- [site:name] team", array(), array('langcode' => $langcode)); break; case 'register_pending_approval_subject': case 'register_pending_approval_admin_subject': - $text = t('Account details for !user-name-token at !site-name-token (pending admin approval)', array('!user-name-token' => '[user:name]', '!site-name-token' => '[site:name]'), array('langcode' => $langcode)); + $text = t('Account details for [user:name] at [site:name] (pending admin approval)', array(), array('langcode' => $langcode)); break; case 'register_pending_approval_body': - $text = t("!user-name-token, + $text = t("[user:name], -Thank you for registering at !site-name-token. Your application for an account is currently pending approval. Once it has been approved, you will receive another e-mail containing information about how to log in, set your password, and other details. +Thank you for registering at [site:name]. Your application for an account is currently pending approval. Once it has been approved, you will receive another e-mail containing information about how to log in, set your password, and other details. --- !site-name-token team", array('!user-name-token' => '[user:name]', '!site-name-token' => '[site:name]'), array('langcode' => $langcode)); +-- [site:name] team", array(), array('langcode' => $langcode)); break; case 'register_pending_approval_admin_body': - $text = t("!user-name-token has applied for an account. + $text = t("[user:name] has applied for an account. -!user-edit-url-token", array('!user-name-token' => '[user:name]', '!user-edit-url-token' => '[user:edit-url]'), array('langcode' => $langcode)); +[user:edit-url]", array(), array('langcode' => $langcode)); break; case 'password_reset_subject': - $text = t('Replacement login information for !user-name-token at !site-name-token', array('!user-name-token' => '[user:name]', '!site-name-token' => '[site:name]'), array('langcode' => $langcode)); + $text = t('Replacement login information for [user:name] at [site:name]', array(), array('langcode' => $langcode)); break; case 'password_reset_body': - $text = t("!user-name-token, + $text = t("[user:name], -A request to reset the password for your account has been made at !site-name-token. +A request to reset the password for your account has been made at [site:name]. You may now log in by clicking this link or copying and pasting it to your browser: -!one-time-login-token +[user:one-time-login-url] This link can only be used once to log in and will lead you to a page where you can set your password. It expires after one day and nothing will happen if it's not used. --- !site-name-token team", array('!user-name-token' => '[user:name]', '!site-name-token' => '[site:name]', '!one-time-login-token' => '[user:one-time-login-url]'), array('langcode' => $langcode)); +-- [site:name] team", array(), array('langcode' => $langcode)); break; case 'status_activated_subject': - $text = t('Account details for !user-name-token at !site-name-token (approved)', array('!user-name-token' => '[user:name]', '!site-name-token' => '[site:name]'), array('langcode' => $langcode)); + $text = t('Account details for [user:name] at [site:name] (approved)', array(), array('langcode' => $langcode)); break; case 'status_activated_body': - $text = t("!user-name-token, + $text = t("[user:name], -Your account at !site-name-token has been activated. +Your account at [site:name] has been activated. You may now log in by clicking this link or copying and pasting it into your browser: -!one-time-login-token +[site:login-url] This link can only be used once to log in and will lead you to a page where you can set your password. -After setting your password, you will be able to log in at !site-login-url-token in the future using: +After setting your password, you will be able to log in at [site:login-url] in the future using: -username: !user-name-token +username: [user:name] password: Your password --- !site-name-token team", array('!user-name-token' => '[user:name]', '!site-name-token' => '[site:name]', '!site-login-url-token' => '[site:login-url]', '!one-time-login-token' => '[user:one-time-login-url]'), array('langcode' => $langcode)); +-- [site:name] team", array(), array('langcode' => $langcode)); break; case 'status_blocked_subject': - $text = t('Account details for !user-name-token at !site-name-token (blocked)', array('!user-name-token' => '[user:name]', '!site-name-token' => '[site:name]'), array('langcode' => $langcode)); + $text = t('Account details for [user:name] at [site:name] (blocked)', array(), array('langcode' => $langcode)); break; case 'status_blocked_body': - $text = t("!user-name-token, + $text = t("[user:name], -Your account on !site-name-token has been blocked. +Your account on [site:name] has been blocked. --- !site-name-token team", array('!user-name-token' => '[user:name]', '!site-name-token' => '[site:name]'), array('langcode' => $langcode)); +-- [site:name] team", array(), array('langcode' => $langcode)); break; case 'cancel_confirm_subject': - $text = t('Account cancellation request for !user-name-token at !site-name-token', array('!user-name-token' => '[user:name]', '!site-name-token' => '[site:name]'), array('langcode' => $langcode)); + $text = t('Account cancellation request for [user:name] at [site:name]', array(), array('langcode' => $langcode)); break; case 'cancel_confirm_body': - $text = t("!user-name-token, + $text = t("[user:name], -A request to cancel your account has been made at !site-name-token. +A request to cancel your account has been made at [site:name]. -You may now cancel your account on !site-url-brief-token by clicking this link or copying and pasting it into your browser: +You may now cancel your account on [site:url-brief] by clicking this link or copying and pasting it into your browser: -!user-cancel-url-token +[user:cancel-url] NOTE: The cancellation of your account is not reversible. This link expires in one day and nothing will happen if it is not used. --- !site-name-token team", array('!user-name-token' => '[user:name]', '!site-name-token' => '[site:name]', '!site-url-brief-token' => '[site:url-brief]', '!user-cancel-url-token' => '[user:cancel-url]'), array('langcode' => $langcode)); +-- [site:name] team", array(), array('langcode' => $langcode)); break; case 'status_canceled_subject': - $text = t('Account details for !user-name-token at !site-name-token (canceled)', array('!user-name-token' => '[user:name]', '!site-name-token' => '[site:name]'), array('langcode' => $langcode)); + $text = t('Account details for [user:name] at [site:name] (canceled)', array(), array('langcode' => $langcode)); break; case 'status_canceled_body': - $text = t("!user-name-token, + $text = t("[user:name], -Your account on !site-name-token has been canceled. +Your account on [site:name] has been canceled. --- !site-name-token team", array('!user-name-token' => '[user:name]', '!site-name-token' => '[site:name]'), array('langcode' => $langcode)); +-- [site:name] team", array(), array('langcode' => $langcode)); break; } } @@ -3115,8 +3186,6 @@ function user_filters() { */ function user_build_filter_query(SelectQuery $query) { $filters = user_filters(); - $role_count = 0; - $permission_count = 0; // Extend Query with filter conditions. foreach (isset($_SESSION['user_overview_filter']) ? $_SESSION['user_overview_filter'] : array() as $filter) { list($key, $value) = $filter; @@ -3130,19 +3199,13 @@ function user_build_filter_query(SelectQuery $query) { if (user_access($value, $account)) { continue; } - $user_role_alias = 'ur' . $role_count; - $permission_alias = 'p' . $permission_count; - $query->innerJoin('users_roles', $user_role_alias, $user_role_alias . '.uid = u.uid'); - $query->innerJoin('role_permission', $permission_alias , $user_role_alias . '.rid = ' . $permission_alias . '.rid'); + $user_role_alias = $query->join('users_roles', 'ur', '%alias.uid = u.uid'); + $permission_alias = $query->join('role_permission', 'p', $user_role_alias . '.rid = %alias.rid'); $query->condition($permission_alias . '.permission', $value); - $role_count++; - $permission_count++; } else if ($key == 'role') { - $user_role_alias = 'ur' . $role_count; - $query->innerJoin('users_roles', $user_role_alias, $user_role_alias . '.uid = u.uid'); + $user_roles_alias = $query->join('users_roles', 'ur', '%alias.uid = u.uid'); $query->condition($user_role_alias . '.rid' , $value); - $role_count++; } else { $query->condition($filters[$key]['field'], $value); @@ -3164,7 +3227,11 @@ function user_forms() { */ function user_comment_view($comment) { if (variable_get('user_signatures', 0) && !empty($comment->signature)) { - $comment->signature = check_markup($comment->signature, $comment->format, '', TRUE); + // @todo This alters and replaces the original object value, so a + // hypothetical process of loading, viewing, and saving will hijack the + // stored data. Consider renaming to $comment->signature_safe or similar + // here and elsewhere in Drupal 8. + $comment->signature = check_markup($comment->signature, $comment->signature_format, '', TRUE); } else { $comment->signature = ''; @@ -3348,7 +3415,7 @@ function user_action_info() { 'label' => t('Block current user'), 'type' => 'user', 'configurable' => FALSE, - 'triggers' => array(), + 'triggers' => array('any'), ), ); } @@ -3359,22 +3426,25 @@ function user_action_info() { * @ingroup actions */ function user_block_user_action(&$entity, $context = array()) { + // First priority: If there is a $entity->uid, block that user. + // This is most likely a user object or the author if a node or comment. if (isset($entity->uid)) { $uid = $entity->uid; } elseif (isset($context['uid'])) { $uid = $context['uid']; } + // If neither of those are valid, then block the current user. else { - global $user; - $uid = $user->uid; + $uid = $GLOBALS['user']->uid; } db_update('users') ->fields(array('status' => 0)) ->condition('uid', $uid) ->execute(); drupal_session_destroy_uid($uid); - watchdog('action', 'Blocked user %name.', array('%name' => $user->name)); + $account = user_load($uid); + watchdog('action', 'Blocked user %name.', array('%name' => $account->name)); } /** @@ -3539,6 +3609,16 @@ function user_modules_uninstalled($modules) { ->execute(); } +/** + * Implements hook_filter_format_delete(). + */ +function user_filter_format_delete($format, $fallback) { + db_update('users') + ->fields(array('signature_format' => $fallback->format)) + ->condition('signature_format', $format->format) + ->execute(); +} + /** * Helper function to rewrite the destination to avoid redirecting to login page after login. * diff --git a/modules/user/user.pages.inc b/modules/user/user.pages.inc index 9bbf4908..65f96dd4 100644 --- a/modules/user/user.pages.inc +++ b/modules/user/user.pages.inc @@ -1,5 +1,5 @@ <?php -// $Id: user.pages.inc,v 1.72 2010/05/06 05:59:31 webchick Exp $ +// $Id: user.pages.inc,v 1.73 2010/06/17 13:44:45 dries Exp $ /** * @file @@ -243,6 +243,18 @@ function template_preprocess_user_profile_category(&$variables) { function user_profile_form($form, &$form_state, $account, $category = 'account') { global $user; + // During initial form build, add the entity to the form state for use during + // form building and processing. During a rebuild, use what is in the form + // state. + if (!isset($form_state['user'])) { + $form_state['user'] = $account; + } + else { + $account = $form_state['user']; + } + + // @todo Legacy support. Modules are encouraged to access the entity using + // $form_state. Remove in Drupal 8. $form['#user'] = $account; $form['#user_category'] = $category; @@ -278,22 +290,23 @@ 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']; - field_attach_form_validate('user', $edit, $form, $form_state); + entity_form_field_validate('user', $form, $form_state); } /** * Submit function for the user account and profile editing form. */ function user_profile_form_submit($form, &$form_state) { - $account = $form['#user']; + $account = $form_state['user']; $category = $form['#user_category']; // Remove unneeded values. form_state_values_clean($form_state); - $edit = (object) $form_state['values']; - field_attach_submit('user', $edit, $form, $form_state); - $edit = (array) $edit; + entity_form_submit_build_entity('user', $account, $form, $form_state); + + // Populate $edit with the properties of $account, which have been edited on + // this form by taking over all values, which appear in the form values too. + $edit = array_intersect_key((array) $account, $form_state['values']); user_save($account, $edit, $category); $form_state['values']['uid'] = $account->uid; diff --git a/modules/user/user.test b/modules/user/user.test index 560ca7a8..75da3a92 100644 --- a/modules/user/user.test +++ b/modules/user/user.test @@ -1,5 +1,5 @@ <?php -// $Id: user.test,v 1.92 2010/05/07 14:48:10 dries Exp $ +// $Id: user.test,v 1.95 2010/06/10 06:57:20 dries Exp $ class UserRegistrationTestCase extends DrupalWebTestCase { public static function getInfo() { @@ -15,12 +15,12 @@ class UserRegistrationTestCase extends DrupalWebTestCase { variable_set('user_email_verification', TRUE); // Set registration to administrator only. - variable_set('user_register', 0); + variable_set('user_register', USER_REGISTER_ADMINISTRATORS_ONLY); $this->drupalGet('user/register'); $this->assertResponse(403, t('Registration page is inaccessible when only administrators can create accounts.')); // Allow registration by site visitors without administrator approval. - variable_set('user_register', 1); + variable_set('user_register', USER_REGISTER_VISITORS); $edit = array(); $edit['name'] = $name = $this->randomName(); $edit['mail'] = $mail = $edit['name'] . '@example.com'; @@ -31,7 +31,7 @@ class UserRegistrationTestCase extends DrupalWebTestCase { $this->assertTrue($new_user->status, t('New account is active after registration.')); // Allow registration by site visitors, but require administrator approval. - variable_set('user_register', 2); + variable_set('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL); $edit = array(); $edit['name'] = $name = $this->randomName(); $edit['mail'] = $mail = $edit['name'] . '@example.com'; @@ -46,7 +46,7 @@ class UserRegistrationTestCase extends DrupalWebTestCase { variable_set('user_email_verification', FALSE); // Allow registration by site visitors without administrator approval. - variable_set('user_register', 1); + variable_set('user_register', USER_REGISTER_VISITORS); $edit = array(); $edit['name'] = $name = $this->randomName(); $edit['mail'] = $mail = $edit['name'] . '@example.com'; @@ -67,7 +67,7 @@ class UserRegistrationTestCase extends DrupalWebTestCase { $this->drupalLogout(); // Allow registration by site visitors, but require administrator approval. - variable_set('user_register', 2); + variable_set('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL); $edit = array(); $edit['name'] = $name = $this->randomName(); $edit['mail'] = $mail = $edit['name'] . '@example.com'; @@ -102,7 +102,7 @@ class UserRegistrationTestCase extends DrupalWebTestCase { function testRegistrationDefaultValues() { // Allow registration by site visitors without administrator approval. - variable_set('user_register', 1); + variable_set('user_register', USER_REGISTER_VISITORS); // Don't require e-mail verification. variable_set('user_email_verification', FALSE); @@ -131,7 +131,7 @@ class UserRegistrationTestCase extends DrupalWebTestCase { $this->assertEqual($new_user->theme, '', t('Correct theme field.')); $this->assertEqual($new_user->signature, '', t('Correct signature field.')); $this->assertTrue(($new_user->created > REQUEST_TIME - 20 ), t('Correct creation time.')); - $this->assertEqual($new_user->status, variable_get('user_register', 1) == 1 ? 1 : 0, t('Correct status field.')); + $this->assertEqual($new_user->status, variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) == USER_REGISTER_VISITORS ? 1 : 0, t('Correct status field.')); $this->assertEqual($new_user->timezone, variable_get('date_default_timezone'), t('Correct time zone field.')); $this->assertEqual($new_user->language, '', t('Correct language field.')); $this->assertEqual($new_user->picture, '', t('Correct picture field.')); @@ -1435,6 +1435,90 @@ class UserEditTestCase extends DrupalWebTestCase { } /** + * Test case for user signatures. + */ +class UserSignatureTestCase extends DrupalWebTestCase { + public static function getInfo() { + return array( + 'name' => 'User signatures', + 'description' => 'Test user signatures.', + 'group' => 'User', + ); + } + + function setUp() { + parent::setUp('comment'); + + // Enable user signatures. + variable_set('user_signatures', 1); + + // Prefetch text formats. + $this->full_html_format = db_query_range('SELECT * FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Full HTML'))->fetchObject(); + $this->plain_text_format = db_query_range('SELECT * FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Plain text'))->fetchObject(); + + // Create regular and administrative users. + $this->web_user = $this->drupalCreateUser(array()); + $admin_permissions = array('administer comments'); + foreach (filter_formats() as $format) { + if ($permission = filter_permission_name($format)) { + $admin_permissions[] = $permission; + } + } + $this->admin_user = $this->drupalCreateUser($admin_permissions); + } + + /** + * Test that a user can change their signature format and that it is respected + * upon display. + */ + function testUserSignature() { + // Create a new node with comments on. + $node = $this->drupalCreateNode(array('comment' => COMMENT_NODE_OPEN)); + + // Verify that user signature field is not displayed on registration form. + $this->drupalGet('user/register'); + $this->assertNoText(t('Signature')); + + // Log in as a regular user and create a signature. + $this->drupalLogin($this->web_user); + $signature_text = "<h1>" . $this->randomName() . "</h1>"; + $edit = array( + 'signature[value]' => $signature_text, + 'signature[format]' => $this->plain_text_format->format, + ); + $this->drupalPost('user/' . $this->web_user->uid . '/edit', $edit, t('Save')); + + // Verify that values were stored. + $this->assertFieldByName('signature[value]', $edit['signature[value]'], 'Submitted signature text found.'); + $this->assertFieldByName('signature[format]', $edit['signature[format]'], 'Submitted signature format found.'); + + // Create a comment. + $langcode = LANGUAGE_NONE; + $edit = array(); + $edit['subject'] = $this->randomName(8); + $edit['comment_body[' . $langcode . '][0][value]'] = $this->randomName(16); + $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview')); + $this->drupalPost(NULL, array(), t('Save')); + + // Get the comment ID. (This technique is the same one used in the Comment + // module's CommentHelperCase test case.) + preg_match('/#comment-([0-9]+)/', $this->getURL(), $match); + $comment_id = $match[1]; + + // Log in as an administrator and edit the comment to use Full HTML, so + // that the comment text itself is not filtered at all. + $this->drupalLogin($this->admin_user); + $edit['comment_body[' . $langcode . '][0][format]'] = $this->full_html_format->format; + $this->drupalPost('comment/' . $comment_id . '/edit', $edit, t('Save')); + + // Assert that the signature did not make it through unfiltered. + $this->drupalGet('node/' . $node->nid); + $this->assertNoRaw($signature_text, 'Unfiltered signature text not found.'); + $this->assertRaw(check_markup($signature_text, $this->plain_text_format->format), 'Filtered signature text found.'); + } +} + +/* * Test that a user, having editing their own account, can still log in. */ class UserEditedOwnAccountTestCase extends DrupalWebTestCase { @@ -1450,7 +1534,7 @@ class UserEditedOwnAccountTestCase extends DrupalWebTestCase { function testUserEditedOwnAccount() { // Change account setting 'Who can register accounts?' to Administrators // only. - variable_set('user_register', 0); + variable_set('user_register', USER_REGISTER_ADMINISTRATORS_ONLY); // Create a new user account and log in. $account = $this->drupalCreateUser(array('change own username')); @@ -1630,3 +1714,92 @@ class UserUserSearchTestCase extends DrupalWebTestCase { } +/** + * Test role assignment. + */ +class UserRolesAssignmentTestCase extends DrupalWebTestCase { + protected $admin_user; + + public static function getInfo() { + return array( + 'name' => t('Role assignment'), + 'description' => t('Tests that users can be assigned and unassigned roles.'), + 'group' => t('User') + ); + } + + function setUp() { + parent::setUp(); + $this->admin_user = $this->drupalCreateUser(array('administer permissions', 'administer users')); + $this->drupalLogin($this->admin_user); + } + + /** + * Tests that a user can be assigned a role and that the role can be removed + * again. + */ + function testAssignAndRemoveRole() { + $rid = $this->drupalCreateRole(array('administer content types')); + $account = $this->drupalCreateUser(); + + // Assign the role to the user. + $this->drupalPost('user/' . $account->uid . '/edit', array("roles[$rid]" => $rid), t('Save')); + $this->assertText(t('The changes have been saved.')); + $this->assertFieldChecked('edit-roles-' . $rid, t('Role is assigned.')); + $this->userLoadAndCheckRoleAssigned($account, $rid); + + // Remove the role from the user. + $this->drupalPost('user/' . $account->uid . '/edit', array("roles[$rid]" => FALSE), t('Save')); + $this->assertText(t('The changes have been saved.')); + $this->assertNoFieldChecked('edit-roles-' . $rid, t('Role is removed from user.')); + $this->userLoadAndCheckRoleAssigned($account, $rid, FALSE); + } + + /** + * Tests that when creating a user the role can be assigned. And that it can + * be removed again. + */ + function testCreateUserWithRole() { + $rid = $this->drupalCreateRole(array('administer content types')); + // Create a new user and add the role at the same time. + $edit = array( + 'name' => $this->randomName(), + 'mail' => $this->randomName() . '@example.com', + 'pass[pass1]' => $pass = $this->randomString(), + 'pass[pass2]' => $pass, + "roles[$rid]" => $rid, + ); + $this->drupalPost('admin/people/create', $edit, t('Create new account')); + $this->assertText(t('Created a new user account for !name.', array('!name' => $edit['name']))); + // Get the newly added user. + $account = user_load_by_name($edit['name']); + + $this->drupalGet('user/' . $account->uid . '/edit'); + $this->assertFieldChecked('edit-roles-' . $rid, t('Role is assigned.')); + $this->userLoadAndCheckRoleAssigned($account, $rid); + + // Remove the role again. + $this->drupalPost('user/' . $account->uid . '/edit', array("roles[$rid]" => FALSE), t('Save')); + $this->assertText(t('The changes have been saved.')); + $this->assertNoFieldChecked('edit-roles-' . $rid, t('Role is removed from user.')); + $this->userLoadAndCheckRoleAssigned($account, $rid, FALSE); + } + + /** + * Check role on user object. + * + * @param object $account User. + * @param integer $rid Role id. + * @param bool $is_assigned True if the role should present on the account. + */ + private function userLoadAndCheckRoleAssigned($account, $rid, $is_assigned = TRUE) { + $account = user_load($account->uid, TRUE); + if ($is_assigned) { + $this->assertTrue(array_key_exists($rid, $account->roles), t('The role is present in the user object.')); + } + else { + $this->assertFalse(array_key_exists($rid, $account->roles), t('The role is not present in the user object.')); + } + } +} + diff --git a/modules/user/user.tokens.inc b/modules/user/user.tokens.inc index f1db1fed..c0c7b3c6 100644 --- a/modules/user/user.tokens.inc +++ b/modules/user/user.tokens.inc @@ -1,5 +1,5 @@ <?php -// $Id: user.tokens.inc,v 1.6 2010/04/20 09:48:06 webchick Exp $ +// $Id: user.tokens.inc,v 1.7 2010/06/29 18:24:10 dries Exp $ /** * @file @@ -81,7 +81,8 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr switch ($name) { // Basic user account information. case 'uid': - $replacements[$original] = $account->uid; + // In the case of hook user_presave uid is not set yet. + $replacements[$original] = !empty($account->uid) ? $account->uid : t('not yet assigned'); break; case 'name': @@ -94,20 +95,21 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr break; case 'url': - $replacements[$original] = url("user/$account->uid", $url_options); + $replacements[$original] = !empty($account->uid) ? url("user/$account->uid", $url_options) : t('not yet assigned'); break; case 'edit-url': - $replacements[$original] = url("user/$account->uid/edit", $url_options); + $replacements[$original] = !empty($account->uid) ? url("user/$account->uid/edit", $url_options) : t('not yet assigned'); break; // These tokens are default variations on the chained tokens handled below. case 'last-login': - $replacements[$original] = format_date($account->login, 'medium', '', NULL, $language_code); + $replacements[$original] = !empty($account->login) ? format_date($account->login, 'medium', '', NULL, $language_code) : t('never'); break; case 'created': - $replacements[$original] = format_date($account->created, 'medium', '', NULL, $language_code); + // In the case of user_presave the created date may not yet be set. + $replacements[$original] = !empty($account->created) ? format_date($account->created, 'medium', '', NULL, $language_code) : t('not yet created'); break; } } diff --git a/profiles/minimal/minimal.info b/profiles/minimal/minimal.info index 190e4b49..8d0a77ea 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/profiles/minimal/minimal.install b/profiles/minimal/minimal.install index 7d36ab39..ea325998 100644 --- a/profiles/minimal/minimal.install +++ b/profiles/minimal/minimal.install @@ -1,5 +1,5 @@ <?php -// $Id: minimal.install,v 1.4 2010/03/04 21:42:00 dries Exp $ +// $Id: minimal.install,v 1.6 2010/07/08 03:41:27 webchick Exp $ /** * Implements hook_install(). @@ -8,11 +8,12 @@ */ function minimal_install() { // Enable some standard blocks. + $default_theme = variable_get('theme_default', 'bartik'); $values = array( array( 'module' => 'system', 'delta' => 'main', - 'theme' => 'garland', + 'theme' => $default_theme, 'status' => 1, 'weight' => 0, 'region' => 'content', @@ -22,7 +23,7 @@ function minimal_install() { array( 'module' => 'user', 'delta' => 'login', - 'theme' => 'garland', + 'theme' => $default_theme, 'status' => 1, 'weight' => 0, 'region' => 'sidebar_first', @@ -32,7 +33,7 @@ function minimal_install() { array( 'module' => 'system', 'delta' => 'navigation', - 'theme' => 'garland', + 'theme' => $default_theme, 'status' => 1, 'weight' => 0, 'region' => 'sidebar_first', @@ -42,7 +43,7 @@ function minimal_install() { array( 'module' => 'system', 'delta' => 'management', - 'theme' => 'garland', + 'theme' => $default_theme, 'status' => 1, 'weight' => 1, 'region' => 'sidebar_first', @@ -52,7 +53,7 @@ function minimal_install() { array( 'module' => 'system', 'delta' => 'help', - 'theme' => 'garland', + 'theme' => $default_theme, 'status' => 1, 'weight' => 0, 'region' => 'help', @@ -66,6 +67,9 @@ function minimal_install() { } $query->execute(); + // Allow visitor account creation, but with administrative approval. + variable_set('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL); + // Enable default permissions for system roles. user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access content')); user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('access content')); diff --git a/profiles/standard/standard.info b/profiles/standard/standard.info index 96121252..f64d09c8 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/profiles/standard/standard.install b/profiles/standard/standard.install index fe43d5b7..cf459ce3 100644 --- a/profiles/standard/standard.install +++ b/profiles/standard/standard.install @@ -1,5 +1,5 @@ <?php -// $Id: standard.install,v 1.15 2010/05/16 10:41:04 dries Exp $ +// $Id: standard.install,v 1.18 2010/07/08 03:41:27 webchick Exp $ /** * Implements hook_install(). @@ -62,11 +62,13 @@ function standard_install() { filter_format_save($full_html_format); // Enable some standard blocks. + $default_theme = variable_get('theme_default', 'bartik'); + $admin_theme = 'seven'; $values = array( array( 'module' => 'system', 'delta' => 'main', - 'theme' => 'garland', + 'theme' => $default_theme, 'status' => 1, 'weight' => 0, 'region' => 'content', @@ -76,7 +78,7 @@ function standard_install() { array( 'module' => 'search', 'delta' => 'form', - 'theme' => 'garland', + 'theme' => $default_theme, 'status' => 1, 'weight' => -1, 'region' => 'sidebar_first', @@ -86,7 +88,7 @@ function standard_install() { array( 'module' => 'node', 'delta' => 'recent', - 'theme' => 'seven', + 'theme' => $admin_theme, 'status' => 1, 'weight' => 10, 'region' => 'dashboard_main', @@ -96,7 +98,7 @@ function standard_install() { array( 'module' => 'user', 'delta' => 'login', - 'theme' => 'garland', + 'theme' => $default_theme, 'status' => 1, 'weight' => 0, 'region' => 'sidebar_first', @@ -106,7 +108,7 @@ function standard_install() { array( 'module' => 'system', 'delta' => 'navigation', - 'theme' => 'garland', + 'theme' => $default_theme, 'status' => 1, 'weight' => 0, 'region' => 'sidebar_first', @@ -116,7 +118,7 @@ function standard_install() { array( 'module' => 'system', 'delta' => 'management', - 'theme' => 'garland', + 'theme' => $default_theme, 'status' => 1, 'weight' => 1, 'region' => 'sidebar_first', @@ -126,7 +128,7 @@ function standard_install() { array( 'module' => 'system', 'delta' => 'powered-by', - 'theme' => 'garland', + 'theme' => $default_theme, 'status' => 1, 'weight' => 10, 'region' => 'footer', @@ -136,7 +138,7 @@ function standard_install() { array( 'module' => 'system', 'delta' => 'help', - 'theme' => 'garland', + 'theme' => $default_theme, 'status' => 1, 'weight' => 0, 'region' => 'help', @@ -146,7 +148,7 @@ function standard_install() { array( 'module' => 'system', 'delta' => 'main', - 'theme' => 'seven', + 'theme' => $admin_theme, 'status' => 1, 'weight' => 0, 'region' => 'content', @@ -156,7 +158,7 @@ function standard_install() { array( 'module' => 'system', 'delta' => 'help', - 'theme' => 'seven', + 'theme' => $admin_theme, 'status' => 1, 'weight' => 0, 'region' => 'help', @@ -166,7 +168,7 @@ function standard_install() { array( 'module' => 'user', 'delta' => 'login', - 'theme' => 'seven', + 'theme' => $admin_theme, 'status' => 1, 'weight' => 10, 'region' => 'content', @@ -176,7 +178,7 @@ function standard_install() { array( 'module' => 'user', 'delta' => 'new', - 'theme' => 'seven', + 'theme' => $admin_theme, 'status' => 1, 'weight' => 0, 'region' => 'dashboard_sidebar', @@ -186,7 +188,7 @@ function standard_install() { array( 'module' => 'search', 'delta' => 'form', - 'theme' => 'seven', + 'theme' => $admin_theme, 'status' => 1, 'weight' => -10, 'region' => 'dashboard_sidebar', @@ -272,6 +274,9 @@ function standard_install() { variable_set('user_picture_file_size', '800'); variable_set('user_picture_style', 'thumbnail'); + // Allow visitor account creation with administrative approval. + variable_set('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL); + // Create a default vocabulary named "Tags", enabled for the 'article' content type. $description = st('Use tags to group articles on similar topics into categories.'); $help = st('Enter a comma-separated list of words to describe your content.'); @@ -310,6 +315,16 @@ function standard_install() { 'type' => 'taxonomy_autocomplete', 'weight' => 4, ), + 'display' => array( + 'default' => array( + 'type' => 'taxonomy_term_reference_link', + 'weight' => 10, + ), + 'teaser' => array( + 'type' => 'taxonomy_term_reference_link', + 'weight' => 10, + ), + ), ); field_create_instance($instance); @@ -367,34 +382,14 @@ function standard_install() { ), 'display' => array( - 'full' => array( + 'default' => array( 'label' => 'hidden', 'type' => 'image__large', - 'settings' => array(), 'weight' => -1, ), 'teaser' => array( 'label' => 'hidden', 'type' => 'image_link_content__medium', - 'settings' => array(), - 'weight' => -1, - ), - 'rss' => array( - 'label' => 'hidden', - 'type' => 'image__large', - 'settings' => array(), - 'weight' => -1, - ), - 'search_index' => array( - 'label' => 'hidden', - 'type' => 'image__large', - 'settings' => array(), - 'weight' => -1, - ), - 'search_results' => array( - 'label' => 'hidden', - 'type' => 'image__large', - 'settings' => array(), 'weight' => -1, ), ), diff --git a/scripts/dump-database-d6.sh b/scripts/dump-database-d6.sh new file mode 100644 index 00000000..4ea0e360 --- /dev/null +++ b/scripts/dump-database-d6.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env php +<?php +// $Id: dump-database-d6.sh,v 1.2 2010/06/29 00:21:00 webchick Exp $ + +/** + * Dump a Drupal 6 database into a Drupal 7 PHP script to test the upgrade + * process. + * + * Run this script at the root of an existing Drupal 6 installation. + * + * The output of this script is a PHP script that can be ran inside Drupal 7 + * and recreates the Drupal 6 database as dumped. Transient data from cache + * session and watchdog tables are not recorded. + */ + +// Define default settings. +$cmd = 'index.php'; +$_SERVER['HTTP_HOST'] = 'default'; +$_SERVER['PHP_SELF'] = '/index.php'; +$_SERVER['REMOTE_ADDR'] = '127.0.0.1'; +$_SERVER['SERVER_SOFTWARE'] = NULL; +$_SERVER['REQUEST_METHOD'] = 'GET'; +$_SERVER['QUERY_STRING'] = ''; +$_SERVER['PHP_SELF'] = $_SERVER['REQUEST_URI'] = '/'; +$_SERVER['HTTP_USER_AGENT'] = 'console'; + +// Bootstrap Drupal. +include_once './includes/bootstrap.inc'; +drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); + +// Include the utility drupal_var_export() function. +include_once dirname(__FILE__) . '/../includes/utility.inc'; + +// Output the PHP header. +$output = "<?php\n\n"; + +// Get the current schema, order it by table name. +$schema = drupal_get_schema(); +ksort($schema); + +// Export all the tables in the schema. +foreach ($schema as $table => $data) { + // Remove descriptions to save time and code. + unset($data['description']); + foreach ($data['fields'] as &$field) { + unset($field['description']); + } + + // Dump the table structure. + $output .= "db_create_table('" . $table . "', " . drupal_var_export($data) . ");\n"; + + // Don't output values for those tables. + if (substr($table, 0, 5) == 'cache' || $table == 'sessions' || $table == 'watchdog') { + $output .= "\n"; + continue; + } + + // Prepare the export of values. + $result = db_query('SELECT * FROM {'. $table .'}'); + $insert = ''; + while ($record = db_fetch_array($result)) { + // users.uid is a serial and inserting 0 into a serial can break MySQL. + // So record uid + 1 instead of uid for every uid and once all records + // are in place, fix them up. + if ($table == 'users') { + $record['uid']++; + } + $insert .= '->values('. drupal_var_export($record) .")\n"; + } + + // Dump the values if there are some. + if ($insert) { + $output .= "db_insert('". $table . "')->fields(". drupal_var_export(array_keys($data['fields'])) .")\n"; + $output .= $insert; + $output .= "->execute();\n"; + } + + // Add the statement fixing the serial in the user table. + if ($table == 'users') { + $output .= "db_query('UPDATE {users} SET uid = uid - 1');\n"; + } + + $output .= "\n"; +} + +print $output; diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php index 2cee9810..aa39ae8a 100644 --- a/sites/default/default.settings.php +++ b/sites/default/default.settings.php @@ -1,5 +1,5 @@ <?php -// $Id: default.settings.php,v 1.44 2010/04/07 15:07:59 dries Exp $ +// $Id: default.settings.php,v 1.48 2010/06/28 19:57:34 dries Exp $ /** * @file @@ -61,6 +61,7 @@ * 'password' => 'password', * 'host' => 'localhost', * 'port' => 3306, + * 'prefix' => 'myprefix_', * ); * * The "driver" property indicates what Drupal database driver the @@ -106,44 +107,45 @@ * 'username' => 'username', * 'password' => 'password', * 'host' => 'localhost', + * 'prefix' => 'main_', * ); * * You can optionally set prefixes for some or all database table names - * by using the $db_prefix setting. If a prefix is specified, the table + * by using the 'prefix' setting. If a prefix is specified, the table * name will be prepended with its value. Be sure to use valid database * characters only, usually alphanumeric and underscore. If no prefixes * are desired, leave it as an empty string ''. * - * To have all database names prefixed, set $db_prefix as a string: + * To have all database names prefixed, set 'prefix' as a string: * - * $db_prefix = 'main_'; + * 'prefix' => 'main_', * - * To provide prefixes for specific tables, set $db_prefix as an array. + * To provide prefixes for specific tables, set 'prefix' as an array. * The array's keys are the table names and the values are the prefixes. - * The 'default' element holds the prefix for any tables not specified - * elsewhere in the array. Example: + * The 'default' element is mandatory and holds the prefix for any tables + * not specified elsewhere in the array. Example: * - * $db_prefix = array( + * 'prefix' => array( * 'default' => 'main_', - * 'users' => 'shared_', + * 'users' => 'shared_', * 'sessions' => 'shared_', * 'role' => 'shared_', * 'authmap' => 'shared_', - * ); + * ), * - * You can also use db_prefix as a reference to a schema/database. This maybe + * You can also use a reference to a schema/database as a prefix. This maybe * useful if your Drupal installation exists in a schema that is not the default - * or you want to access several databases from the same code base at the same + * or you want to access several databases from the same code base at the same * time. * Example: * - * $db_prefix = array( - * 'default' => 'main.', - * 'users' => 'shared.', + * 'prefix' => array( + * 'default' => 'main.', + * 'users' => 'shared.', * 'sessions' => 'shared.', * 'role' => 'shared.', * 'authmap' => 'shared.', - * ); + * ); * * NOTE: MySQL and SQLite's definition of a schema is a database. * @@ -154,6 +156,7 @@ * 'username' => 'username', * 'password' => 'password', * 'host' => 'localhost', + * 'prefix' => '', * ); * $databases['default']['default'] = array( * 'driver' => 'pgsql', @@ -161,6 +164,7 @@ * 'username' => 'username', * 'password' => 'password', * 'host' => 'localhost', + * 'prefix' => '', * ); * $databases['default']['default'] = array( * 'driver' => 'sqlite', @@ -168,7 +172,6 @@ * ); */ $databases = array(); -$db_prefix = ''; /** * Access control for update.php script. @@ -205,9 +208,10 @@ $drupal_hash_salt = ''; /** * Base URL (optional). * - * If you are experiencing issues with different site domains, - * uncomment the Base URL statement below (remove the leading hash sign) - * and fill in the absolute URL to your Drupal installation. + * If Drupal is generating incorrect URLs on your site, which could + * be in HTML headers (links to CSS and JS files) or visible links on pages + * (such as in menus), uncomment the Base URL statement below (remove the + * leading hash sign) and fill in the absolute URL to your Drupal installation. * * You might also want to force users to use a given domain. * See the .htaccess file for more information. @@ -258,6 +262,17 @@ ini_set('session.gc_maxlifetime', 200000); */ ini_set('session.cookie_lifetime', 2000000); +/** + * If you encounter a situation where users post a large amount of text, and + * the result is stripped out upon viewing but can still be edited, Drupal's + * output filter may not have sufficient memory to process it. If you + * experience this issue, you may wish to uncomment the following two lines + * and increase the limits of these variables. For more information, see + * http://php.net/manual/en/pcre.configuration.php. + */ +# ini_set('pcre.backtrack_limit', 200000); +# ini_set('pcre.recursion_limit', 200000); + /** * Drupal automatically generates a unique session cookie name for each site * based on on its full domain name. If you have multiple domains pointing at diff --git a/themes/bartik/bartik.info b/themes/bartik/bartik.info new file mode 100644 index 00000000..92e2fd04 --- /dev/null +++ b/themes/bartik/bartik.info @@ -0,0 +1,46 @@ +; $Id: bartik.info,v 1.1 2010/07/06 05:25:51 webchick Exp $ + +name = Bartik +description = A flexible, recolorable theme with many regions. +package = Core +version = VERSION +core = 7.x +engine = phptemplate + +stylesheets[all][] = css/layout.css +stylesheets[all][] = css/style.css +stylesheets[all][] = css/colors.css +stylesheets[print][] = css/print.css +stylesheets[all][] = css/maintenance-page.css + +scripts[] = scripts/search.js + +regions[header] = Header +regions[help] = Help +regions[page_top] = Page top +regions[page_bottom] = Page bottom +regions[highlight] = Highlighted + +regions[featured] = Featured +regions[content] = Content +regions[sidebar_first] = Sidebar first +regions[sidebar_second] = Sidebar second + +regions[triptych_first] = Triptych first +regions[triptych_middle] = Triptych middle +regions[triptych_last] = Triptych last + +regions[footer_firstcolumn] = Footer first column +regions[footer_secondcolumn] = Footer second column +regions[footer_thirdcolumn] = Footer third column +regions[footer_fourthcolumn] = Footer fourth column +regions[footer] = Footer + +settings[shortcut_module_link] = 0 + + +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" +project = "drupal" +datestamp = "1278634806" + diff --git a/themes/bartik/color/color.inc b/themes/bartik/color/color.inc new file mode 100644 index 00000000..4b8fa17f --- /dev/null +++ b/themes/bartik/color/color.inc @@ -0,0 +1,134 @@ +<?php +// $Id: color.inc,v 1.1 2010/07/06 05:25:51 webchick Exp $ + +// Put the logo path into JavaScript for the live preview. +drupal_add_js(array('color' => array('logo' => theme_get_setting('logo', 'bartik'))), 'setting'); + +$info = array( + // Available colors and color labels used in theme. + 'fields' => array( + 'bg' => t('Main background'), + 'link' => t('Link color'), + 'top' => t('Header top'), + 'bottom' => t('Header bottom'), + 'text' => t('Text color'), + 'sidebar' => t('Sidebar background'), + 'sidebarborders' => t('Sidebar borders'), + 'footer' => t('Footer background'), + 'titleslogan' => t('Title and slogan'), + ), + // Pre-defined color schemes. + 'schemes' => array( + 'default' => array( + 'title' => t('Blue Lagoon (default)'), + 'colors' => array( + 'bg' => '#ffffff', + 'link' => '#288CC9', + 'top' => '#0779bf', + 'bottom' => '#48a9e4', + 'text' => '#3b3b3b', + 'sidebar' => '#f6f6f2', + 'sidebarborders' => '#f9f9f9', + 'footer' => '#2d1e0f', + 'titleslogan' => '#fffeff', + ), + ), + 'Slate' => array( + 'title' => t('Slate'), + 'colors' => array( + 'bg' => '#ffffff', + 'link' => '#0073b6', + 'top' => '#4a4a4a', + 'bottom' => '#4e4e4e', + 'text' => '#3b3b3b', + 'sidebar' => '#feffff', + 'sidebarborders' => '#d0d0d0', + 'footer' => '#161617', + 'titleslogan' => '#fffeff', + ), + ), + 'Firehouse' => array( + 'title' => t('Firehouse'), + 'colors' => array( + 'bg' => '#ffffff', + 'link' => '#d6121f', + 'top' => '#cd2d2d', + 'bottom' => '#cf3535', + 'text' => '#3b3b3b', + 'sidebar' => '#f1f1f1', + 'sidebarborders' => '#c2c2c2', + 'footer' => '#1f1d1c', + 'titleslogan' => '#fffeff', + ), + ), + 'Plum' => array( + 'title' => t('Plum'), + 'colors' => array( + 'bg' => '#fffdf7', + 'link' => '#9d408d', + 'top' => '#4c1c58', + 'bottom' => '#593662', + 'text' => '#301313', + 'sidebar' => '#f8f3e7', + 'sidebarborders' => '#e4e3d4', + 'footer' => '#2C2C28', + 'titleslogan' => '#fffeff', + ), + ), + 'Ice' => array( + 'title' => t('Ice'), + 'colors' => array( + 'bg' => '#ffffff', + 'link' => '#019DBF', + 'top' => '#d0d0d0', + 'bottom' => '#c2c4c5', + 'text' => '#4A4A4A', + 'sidebar' => '#feffff', + 'sidebarborders' => '#cccccc', + 'footer' => '#24272c', + 'titleslogan' => '#0b0b0b', + ), + ), + ), + + // CSS files (excluding @import) to rewrite with new color scheme. + 'css' => array( + 'css/colors.css', + ), + + // Files to copy. + 'copy' => array( + 'logo.png', + ), + + // Gradient definitions. + 'gradients' => array( + array( + // (x, y, width, height). + 'dimension' => array(0, 0, 0, 0), + // Direction of gradient ('vertical' or 'horizontal'). + 'direction' => 'vertical', + // Keys of colors to use for the gradient. + 'colors' => array('top', 'bottom'), + ), + ), + + // Color areas to fill (x, y, width, height). + 'fill' => array(), + + // Coordinates of all the theme slices (x, y, width, height) + // with their filename as used in the stylesheet. + 'slices' => array(), + + // Reference color used for blending. Matches the base.png's colors. + 'blend_target' => '#ffffff', + + // Preview files. + 'preview_image' => 'color/preview.png', + 'preview_css' => 'color/preview.css', + 'preview_js' => 'color/preview.js', + 'preview_html' => 'color/preview.html', + + // Base file for image generation. + 'base_image' => 'color/base.png', +); diff --git a/themes/bartik/color/preview.css b/themes/bartik/color/preview.css new file mode 100644 index 00000000..05293338 --- /dev/null +++ b/themes/bartik/color/preview.css @@ -0,0 +1,62 @@ +/* $Id: preview.css,v 1.1 2010/07/06 05:25:51 webchick Exp $ */ + +/* Bring in the rest of the theme's CSS styles. */ +@import url("../css/style.css"); + +/* From color.css. */ +#preview, #preview-header { + background-color: #000000; +} +/* Undoing Seven's reset. */ +#preview #preview-header #preview-logo img { + border: 0; +} + +/* ---------- Basic Preview Styles ----------- */ + +#preview { + width: 640px; + overflow: hidden; + font-size: 1em; +} +#preview #preview-header div.section, +#preview #preview-navigation div.section, +#preview #preview-featured div.section, +#preview #preview-main { + width: 960px; + margin-left: auto; + margin-right: auto; + padding: 0 20px; +} +#preview #preview-content, +#preview .sidebar { + display: inline; + float: left; /* LTR */ + position: relative; +} +#preview #preview-content { + width: 720px; +} +#preview .sidebar { + width: 240px; +} +#preview #preview-content .section, +.sidebar .section { + padding: 0 10px; +} +#preview #footer-wrapper { + padding: 35px 20px 30px; +} +#preview #footer-firstcolumn, +#preview #footer-secondcolumn, +#preview #footer-thirdcolumn, +#preview #footer-fourthcolumn { + padding: 0 10px; + width: 220px; + display: inline; + float: left; /* LTR */ + position: relative; +} +#preview #footer { + width: 940px; +} diff --git a/themes/bartik/color/preview.html b/themes/bartik/color/preview.html new file mode 100644 index 00000000..13537eda --- /dev/null +++ b/themes/bartik/color/preview.html @@ -0,0 +1,105 @@ +<div id="preview"> + <div id="preview-header"> + <div class="section clearfix"> + <a href="/" title="Home" rel="home" id="preview-logo" name="logo"><img src="../../../themes/bartik/logo.png" alt="Home" /></a> + <div id="preview-name-and-slogan"> + <div id="preview-site-name"> + <strong><a href="#" title="Home" rel="home"><span>Bartik</span></a></strong> + </div> + </div><!-- /#name-and-slogan --> + </div> + </div><!-- /.section, /#header --> + <div id="preview-main-wrapper"> + <div id="preview-main" class="clearfix"> + <div id="preview-sidebar-first" class="column sidebar"> + <div class="section"> + <div class="region region-sidebar-first"> + <div id="preview-block-user-login" class="block block-user first last even"> + <h2> + Etiam est risus + </h2> + <div class="content"> + <p> + Maecenas id porttitor felis. Pellentesque mollis urna in nibh pharetra semper. Nulla erat odio, imperdiet quis cursus vitae, ultricies + at diam. + </p> + </div> + </div> + </div> + </div> + </div><!-- /.section, /#sidebar-first --> + <div id="preview-content" class="column"> + <div class="section"> + <a id="preview-main-content" name="main-content"></a> + <h1 class="title" id="preview-page-title"> + Lorem ipsum dolor + </h1> + <div class="region region-content"> + <div id="preview-block-system-main" class="block block-system first last even"> + <div class="content"> + <div id="preview-node-1" class="node node-page clearfix" about="/node/1" typeof="foaf:Document"> + <div class="content clearfix"> + <div class="field field-name-body field-type-text-with-summary field-label-hidden clearfix"> + <div class="field-items"> + <div class="field-item even" property="content:encoded"> + <p> + Sit amet, <a href="#">consectetur adipisicing elit</a>, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim + veniam, quis nostrud <a href="#">exercitation ullamco</a> laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor + in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, + sunt in culpa qui officia deserunt mollit anim id est laborum. + </p> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + </div><!-- /.section, /#content --> + </div> + </div><!-- /#main, /#main-wrapper --> + + <div id="footer-wrapper"><div class="section"> + <div id="footer-columns" class="clearfix"> + <div id="footer-firstcolumn" class="region sitemap"><div class="section"> + <ul> + <li><a>Donec placerat</a></li> + <li><a>Nullam nibh dolor</a></li> + <li><a>Blandit sed</a></li> + <li><a>Fermentum id</a></li> + </ul> + </div></div> <!-- /.section, /#footer-firstcolumn --> + <div id="footer-secondcolumn" class="region sitemap"><div class="section"> + <ul> + <li><a>Imperdiet sit amet</a></li> + <li><a>Nam mollis</a></li> + <li><a>Ultrices justo</a></li> + <li><a>Sed tempor</a></li> + </ul> + <ul> + </div></div> <!-- /.section, /#footer-secondcolumn --> + <div id="footer-thirdcolumn" class="region sitemap"><div class="section"> + <ul> + <li><a>Sit amet</a></li> + <li><a>Gravida eget</a></li> + <li><a>Porta at</a></li> + <li><a>Nam non</a></li> + </ul> + </div></div> <!-- /.section, /#footer-thirdcolumn --> + <div id="footer-fourthcolumn" class="region sitemap"><div class="section"> + <ul> + <li><a>Sed vitae</a></li> + <li><a>Tellus</a></li> + <li><a>Etiam sem</a></li> + <li><a>Arcu eleifend</a></li> + </ul> + </div></div> <!-- /.section, /#footer-fourthcolumn --> + </div><!-- /#footer-columns --> + <div id="footer" class="clearfix"> + Aliquam aliquet, est <a>a ullamcorper</a> condimentum. + </div><!-- /#footer --> + </div></div> <!-- /.section, /#footer-wrapper --> + +</div><!-- /#preview --> diff --git a/themes/bartik/color/preview.js b/themes/bartik/color/preview.js new file mode 100644 index 00000000..6335a28d --- /dev/null +++ b/themes/bartik/color/preview.js @@ -0,0 +1,37 @@ +/* $Id: preview.js,v 1.1 2010/07/06 05:25:51 webchick Exp $ */ + +(function ($) { + Drupal.color = { + logoChanged: false, + callback: function(context, settings, form, farb, height, width) { + // Change the logo to be the real one. + if (!this.logoChanged) { + $('#preview #preview-logo img').attr('src', Drupal.settings.color.logo); + this.logoChanged = true; + } + + // Solid background. + $('#preview', form).css('backgroundColor', $('#palette input[name="palette[bg]"]', form).val()); + + // Text preview. + $('#preview #preview-main h2, #preview #preview-main p', form).css('color', $('#palette input[name="palette[text]"]', form).val()); + $('#preview #preview-content a', form).css('color', $('#palette input[name="palette[link]"]', form).val()); + + // Sidebar background. + $('#preview .sidebar .block', form).css('background-color', $('#palette input[name="palette[sidebar]"]', form).val()); + + // Footer background. + $('#preview #footer-wrapper', form).css('background-color', $('#palette input[name="palette[footer]"]', form).val()); + + $('#preview .sidebar .block', form).css('border-color', $('#palette input[name="palette[sidebarborders]"]', form).val()); + + // CSS3 Gradients. + var gradient_start = $('#palette input[name="palette[top]"]', form).val(); + var gradient_end = $('#palette input[name="palette[bottom]"]', form).val(); + + $('#preview #preview-header', form).attr('style', "background-color: " + gradient_start + "; background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(" + gradient_start + "), to(" + gradient_end + ")); background-image: -moz-linear-gradient(-90deg, " + gradient_start + ", " + gradient_end + ");"); + + $('#preview #preview-name-and-slogan a', form).css('color', $('#palette input[name="palette[titleslogan]"]', form).val()); + } + }; +})(jQuery); diff --git a/themes/bartik/css/colors.css b/themes/bartik/css/colors.css new file mode 100644 index 00000000..4bbda70e --- /dev/null +++ b/themes/bartik/css/colors.css @@ -0,0 +1,46 @@ +/* $Id: colors.css,v 1.1 2010/07/06 05:25:51 webchick Exp $ */ + +/* ---------- Color Module Styles ----------- */ + +body { + background-color: #2d1e0f; + color: #ffffff; +} +html, +#page-wrapper, +body.overlay { + background-color: #ffffff; + color: #3b3b3b; +} +#navigation ul.links li.active-trail a { + background: #ffffff; +} +.tabs ul.primary li a.active { + background-color: #ffffff; +} +#header { + background-color: #48a9e4; + background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#0779bf), to(#48a9e4)); + background-image: -moz-linear-gradient(-90deg, #0779bf, #48a9e4); +} +a { + color: #288CC9; +} +a:hover, +a:focus { + color: #018fe2; +} +a:active { + color: #23aeff; +} +.sidebar .block { + background-color: #f6f6f2; + border-color: #f9f9f9; +} +#footer-wrapper { + background: #2d1e0f; +} +#header #name-and-slogan, +#header #name-and-slogan a { + color: #fffeff; +} diff --git a/themes/bartik/css/ie-rtl.css b/themes/bartik/css/ie-rtl.css new file mode 100644 index 00000000..8fefa7d0 --- /dev/null +++ b/themes/bartik/css/ie-rtl.css @@ -0,0 +1,11 @@ +/* $Id: ie-rtl.css,v 1.1 2010/07/06 05:25:51 webchick Exp $ */ + +fieldset legend { + left: 6px; +} +.tabs ul.primary li a.active { + padding: 0 10px 0 7px; +} +ul.action-links li a { + zoom: 1; +} diff --git a/themes/bartik/css/ie.css b/themes/bartik/css/ie.css new file mode 100644 index 00000000..4cbddeee --- /dev/null +++ b/themes/bartik/css/ie.css @@ -0,0 +1,24 @@ +/* $Id: ie.css,v 1.1 2010/07/06 05:25:51 webchick Exp $ */ + +.block { + zoom: 1; +} +input.form-text { + font-family: sans-serif; +} +#password-strength-text { + margin-top: 0; +} +fieldset legend { + left: -8px; + padding: 0; +} +.tabs ul.primary { + height: auto; +} +.tabs ul.primary li a.active { + padding: 0 7px 0 10px; /* LTR */ +} +#footer-wrapper #footer .block { + height: 100%; +} diff --git a/themes/bartik/css/ie6.css b/themes/bartik/css/ie6.css new file mode 100644 index 00000000..c664228f --- /dev/null +++ b/themes/bartik/css/ie6.css @@ -0,0 +1,11 @@ +/* $Id: ie6.css,v 1.1 2010/07/06 05:25:51 webchick Exp $ */ + +#content { + overflow: hidden; +} +.form-item-search-block-form { + width: 50%; +} +.tabs ul.primary { + zoom: 1; +} diff --git a/themes/bartik/css/layout-rtl.css b/themes/bartik/css/layout-rtl.css new file mode 100644 index 00000000..40aae3d0 --- /dev/null +++ b/themes/bartik/css/layout-rtl.css @@ -0,0 +1,15 @@ +/* $Id: layout-rtl.css,v 1.1 2010/07/06 05:25:51 webchick Exp $ */ + +/* ---------- Basic Layout RTL Styles ----------- */ + +#content, +.sidebar, +#triptych-first, +#triptych-middle, +#triptych-last, +#footer-firstcolumn, +#footer-secondcolumn, +#footer-thirdcolumn, +#footer-fourthcolumn { + float: right; +} diff --git a/themes/bartik/css/layout.css b/themes/bartik/css/layout.css new file mode 100644 index 00000000..b0f138a8 --- /dev/null +++ b/themes/bartik/css/layout.css @@ -0,0 +1,73 @@ +/* $Id: layout.css,v 1.1 2010/07/06 05:25:51 webchick Exp $ */ + +/* ---------- Basic Layout Styles ----------- */ + +#header div.section, +#navigation div.section, +#featured div.section, +#messages, +#main, +#triptych, +#footer-columns, +#footer { + width: 960px; + margin-left: auto; + margin-right: auto; +} +#content, +.sidebar, +#triptych-first, +#triptych-middle, +#triptych-last, +#footer-firstcolumn, +#footer-secondcolumn, +#footer-thirdcolumn, +#footer-fourthcolumn { + display: inline; + float: left; /* LTR */ + position: relative; +} +.one-sidebar #content { + width: 720px; +} +.two-sidebars #content { + width: 480px; +} +.no-sidebars #content { + width: 960px; + float: none; +} +.sidebar { + width: 240px; +} +#main-wrapper { + min-height: 500px; +} +#messages div.section, +#content .section, +.sidebar .section { + padding: 0 15px; +} +#breadcrumb { + margin: 0 15px; +} +#triptych-first, +#triptych-middle, +#triptych-last { + margin: 20px 20px 30px; + width: 280px; +} +#footer-wrapper { + padding: 35px 5px 30px; +} +#footer-firstcolumn, +#footer-secondcolumn, +#footer-thirdcolumn, +#footer-fourthcolumn { + padding: 0 10px; + width: 220px; +} +#footer { + width: 940px; + min-width: 920px; +} diff --git a/themes/bartik/css/maintenance-page.css b/themes/bartik/css/maintenance-page.css new file mode 100644 index 00000000..9c7ca38f --- /dev/null +++ b/themes/bartik/css/maintenance-page.css @@ -0,0 +1,62 @@ +/* $Id: maintenance-page.css,v 1.1 2010/07/06 05:25:51 webchick Exp $ */ + +body.maintenance-page { + background-color: #fff; + color: #000; +} +.maintenance-page #page-wrapper { + margin-left: auto; + margin-right: auto; + width: 800px; + border: 1px solid #cbcbcb; + margin-top: 40px; +} +.maintenance-page #page { + margin: 20px 40px 40px; +} +.maintenance-page #main-wrapper { + min-height: inherit; +} +.maintenance-page #header, +.maintenance-page #messages, +.maintenance-page #main { + width: auto; +} +.maintenance-page #header div.section, +.maintenance-page #navigation div.section, +.maintenance-page #messages, +.maintenance-page #main { + width: 700px; +} +.maintenance-page #main { + margin: 0; +} +.maintenance-page #content .section { + padding: 0 0 0 10px; +} +.maintenance-page #header { + background-color: #fff; + background-image: none; +} +.maintenance-page #header #name-and-slogan { + margin-bottom: 50px; + padding-top: 20px; + font-size: 90%; +} +.maintenance-page #header, +.maintenance-page #header a, +.maintenance-page #header a:hover, +.maintenance-page #header a:hover { + color: #777; +} +.maintenance-page h1#page-title { + line-height: 1em; + margin-top: 0; +} +.maintenance-page #messages { + padding: 0; + margin-top: 30px; +} +.maintenance-page #messages div.section { + padding: 0; +} diff --git a/themes/bartik/css/print.css b/themes/bartik/css/print.css new file mode 100644 index 00000000..f6bf5e70 --- /dev/null +++ b/themes/bartik/css/print.css @@ -0,0 +1,47 @@ +/* $Id: print.css,v 1.1 2010/07/06 05:25:51 webchick Exp $ */ + +/* ---------- General Layout ---------- */ + +body, +input, +textarea, +select { + color: #000; + background: none; +} +body.two-sidebars, +body.sidebar-first, +body.sidebar-second, +body { + width: 640px; +} +#sidebar-first, +#sidebar-second, +#navigation, +#toolbar, +#footer-wrapper, +.tabs, +.add-or-remove-shortcuts { + display: none; +} +.one-sidebar #content, +.two-sidebars #content { + width: 100%; +} +#triptych-wrapper { + width: 960px; + margin: 0; + padding: 0; + border: none; +} +#triptych-first, #triptych-middle, #triptych-last { + width: 250px; +} + +/* ---------- Node Pages ---------- */ + +#comments .title, +#comments form, +.comment_forbidden { + display: none; +} diff --git a/themes/bartik/css/style-rtl.css b/themes/bartik/css/style-rtl.css new file mode 100644 index 00000000..ff1b1c77 --- /dev/null +++ b/themes/bartik/css/style-rtl.css @@ -0,0 +1,254 @@ +/* $Id: style-rtl.css,v 1.1 2010/07/06 05:25:51 webchick Exp $ */ + +/* ------------------ Reset Styles ------------------ */ + +caption, +th, +td { + text-align: right; +} +blockquote{ + border-left: none; + border-right: 4px solid #afafaf; +} +blockquote:before { + content: "\201D"; +} +blockquote:after { + content: "\201C"; +} + +/* ------------------ List Styles ------------------ */ + +.region-content ul, +.region-content ol { + padding: 2.5em 0 0.25em 0; +} +.item-list ul li { + padding: 0.2em 0 0 0.5em; +} +ul.tips { + padding: 0 1.25em 0 0; +} + +/* ------------------ Header ------------------ */ + +#header #logo { + float: right; + padding: 15px 0 15px 30px; +} +/* Menus when in the header region. */ +#header div.block-menu, +#header div#block-system-main-menu, +#header div#block-system-secondary-menu { + float: right; +} +#header .block-menu li a, +#header #block-system-main-menu li a, +#header #block-system-secondary-menu li a { + float: right; + border-left: 1px solid #555; + + border-right: none; +} +#header .block-menu li.first a, +#header #block-system-main-menu li.first a, +#header #block-system-secondary-menu li.first a { + padding-right: 0; + padding-left: 12px; +} +#header .block-menu li.last a, +#header #block-system-main-menu li.last a, +#header #block-system-secondary-menu li.last a { + padding-left: 0; + padding-right: 12px; + border-left: none; +} +#header #block-search-form { + float: left; +} +#header #block-user-login { + left: 2%; + right: auto; + float: left; +} +#header #block-user-login .form-text { + margin-left: 5px; +} +#header #block-user-login .item-list ul li { + margin: 0 15px 0 5px; +} +#header #block-user-login .userbox { + margin-right: 10px; +} + +/* --------------- Main Navigation ------------ */ + +#navigation ul.links li.first { + padding-right: 2px; + padding-left: 0; +} + +/* ----------------- Content ------------------ */ + +#block-system-main .submitted .user-picture img { + float: right; + margin-left: 5px; + margin-right: 0; +} +#block-system-main div.field-name-taxonomy-tags div.field-label, +#block-system-main div.field-name-taxonomy-tags div.field-items, +#block-system-main div.field-name-taxonomy-tags div.field-item { + float: right; + padding-left: 10px; + padding-right: 0; +} +#block-system-main .link-wrapper { + margin-right: 236px; + margin-left: 0; +} + +/* ----------------- Comments ----------------- */ + +#comment-wrapper div.user-picture img { + margin-right: 0; +} +#comments .attribution { + float: right; +} +#comments .comment-arrow { + background: url(../images/comment-arrow-rtl.png); + margin-right: -45px; +} +#comments .comment-text { + margin-right: 140px; + margin-left: 0; +} +#comments .indented { + margin-right: 40px; + margin-left: 0; +} + +/* -------------- Password Meter ------------- */ + +#password-strength { + left: auto; + margin-top: 2em; + right: 16em; +} +#password-strength-text { + margin-top: 0; + float: left; +} +.form-item-pass-pass2 label { + clear: right; +} + +/* ------------------ Footer ------------------ */ + +#footer-columns ul { + padding-right: 0; +} +#footer-columns li a { + padding: 0.8em 20px 0.8em 2px; +} +#footer li a { + float: right; + border-left: 1px solid #555; + border-color: rgba(255,255,255,0.15); + border-right: none; +} +#footer li.first a { + padding-right: 0; + padding-left: 12px; +} +#footer li.last a { + padding-left: 0; + padding-right: 12px; + border-left: none; +} + +/* --------------- System Tabs --------------- */ + +.tabs ul.primary li { + margin: 0 0 0 5px; + float: right; + zoom: 1; +} +.tabs ul.secondary li { + float: right; +} +.tabs ul.secondary li:first-child { + padding-right: 0; +} +.tabs ul.secondary li:last-child { + border-left: none; +} +ul.action-links li a { + background-position: right center; + padding-left: 0; + padding-right: 15px; +} + +/* -------------- Form Elements ------------- */ + +.fieldset-legend span.summary { + margin-left: 0; +} +#user-profile-form input#edit-submit { + margin-left: 0; +} +.password-suggestions ul li { + margin-right: 1.2em; + margin-left: 0; +} + +/* Animated throbber */ +html.js input.form-autocomplete { + background-position: 1% 4px; +} +html.js input.throbbing { + background-position: 1% -16px; +} + +/* Comment form */ +#comment-form .form-type-textfield label, +#comment-form .form-type-item label { + float: right; +} +#comment-form .form-type-textfield input, +#comment-form .form-item .username { + float: left; +} +#comment-form .form-item .description { + float: left; +} + +/* -------------- Shortcut Links ------------- */ + +.shortcut-wrapper h1#page-title { + float: right; +} + +/* ---------- Poll ----------- */ + +.poll .vote-form { + text-align: right; +} +.poll .total { + text-align: left; +} + +/* ---------- Color Form ----------- */ + +.color-form #palette { + margin-left: 0; + margin-right: 20px; +} +.color-form .form-item label { + float: right; +} +.color-form #palette .lock { + right: -20px; + left: 0; +} diff --git a/themes/bartik/css/style.css b/themes/bartik/css/style.css new file mode 100644 index 00000000..1ba022d1 --- /dev/null +++ b/themes/bartik/css/style.css @@ -0,0 +1,1326 @@ +/* $Id: style.css,v 1.1 2010/07/06 05:25:51 webchick Exp $ */ + +/* ---------- Overall Specifications ---------- */ + +body, +#preview { + background-color: #fff; + line-height: 1.4em; + font-size: 0.8em; + word-wrap: break-word; + margin: 0; + padding: 0; + border: 0; + outline: 0; +} +a:link, +a:visited { + text-decoration: none; +} +h1, +h2, +h3, +h4, +h5, +h6, +#preview h1, +#preview h2, +#preview h3, +#preview h4, +#preview h5, +#preview h6 { + margin: 1.0em 0 0.5em; + font-weight: inherit; +} +h1, +#preview h1 { + font-size: 140%; + color: #000; +} +h2, +#preview h1 { + font-size: 120%; +} +p, +#preview p { + margin: 0 0 1.2em; +} +del { + text-decoration: line-through; +} +tr.odd { + background-color: #dddddd; +} +img, +#preview img { + outline: 0; +} + +/* ------------------ Fonts ------------------ */ + +body, +#preview, +#header #site-slogan, +.ui-widget { + font-family: Georgia, "Times New Roman", Times, serif; +} +#header, +#footer-wrapper, +#preview #preview-header, +ul.contextual-links, +ul.links, +ul.primary, +div.field-type-taxonomy-term-reference, +div.messages, +div.meta, +p.comment-time, +table, +.breadcrumb { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +} +input, +textarea, +select, +a.button { + font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, sans-serif; +} + +/* ------------------ Reset Styles ------------------ */ + +caption { + text-align: left; /* LTR */ + font-weight: normal; +} +blockquote { + background: #f7f7f7; + border-left: 1px solid #bbb; + font-style: italic; + margin: 1.5em 10px; + padding: .5em 10px; +} +blockquote:before { + color: #bbb; + content: "\201C"; + font-size: 3em; + line-height: .1em; + margin-right: .2em; + vertical-align: -.4em; +} +blockquote:after { + color: #bbb; + content: "\201D"; + font-size: 3em; + line-height: .1em; + vertical-align: -.45em; +} +blockquote :first-child { + display: inline; +} +a.feed-icon { + display: inline-block; + padding: 15px 0 0 0; +} + +/* ------------------ Table Styles ------------------ */ + +table { + border-spacing: 0; + font-size: 0.85em; + width: 100%; + margin: 10px 0; +} +#footer-wrapper table { + font-size: 1em; +} +table tr th { + background-color: #757575; + border-bottom-style: none; +} +table thead tr th, +table thead tr th a, +table thead tr th a:hover { + color: #FFF; + font-weight: bold; +} +tr td, +tr th { + padding: 4px 9px; + border: 1px solid #fff; + text-align: left; /* LTR */ +} +#footer-wrapper tr td, +#footer-wrapper tr th { + border-color: #555; + border-color: rgba(255,255,255,0.18); +} +tr.odd { + background-color: #e4e4e4; +} +tr, +tr.even { + background-color: #efefef; +} +#block-system-main table ul.links { + font-size: 1em; + margin: 0; + padding: 0; +} +#block-system-main table ul.links li { + padding: 0 1em 0 0; +} + +/* ------------------ List Styles ------------------ */ + +.block ul { + margin: 0; +} +ul.contextual-links { + font-size: 90%; +} +ul.menu li { + margin: 0; +} +.region-content ul, +.region-content ol { + margin: 1em 0; + padding: 0 0 0.25em 2.5em; /* LTR */ +} +.item-list ul li { + margin: 0; + padding: 0.2em 0.5em 0 0; /* LTR */ +} +ul.tips { + padding: 0 0 0 1.25em; /* LTR */ +} + +/* ------------------ Header ------------------ */ + +#skip-link { + position: absolute; + top: -9999px; +} +#header, +#preview #preview-header { + color: #fff; +} +#header a, +#preview #preview-header a { + color: #fff; +} +#header a:hover, +#header a:focus { + color: #b5b7b9; +} +#header a:active { + color: #adb0bf; +} +#header #logo, +#preview #preview-header #preview-logo { + float: left; /* LTR */ + padding: 15px 30px 15px 0; /* LTR */ +} +#header #name-and-slogan, +#preview #preview-header #preview-name-and-slogan { + padding-top: 38px; + margin-bottom: 30px; + margin-left: 15px; +} +#header #site-name, +#preview #preview-header #preview-site-name { + font-size: 200%; + color: #686868; +} +#header h1#site-name, +#preview #preview-header h1#preview-site-name { + margin: 0; +} +#header #site-name a, +#preview-header #preview-site-name a { + font-weight: normal; +} +#header #site-slogan { + margin-top: 7px; + font-size: 100%; + word-spacing: 0.1em; + font-style: italic; +} +/* Menus when in the header region. */ +#header .block-menu { + height: 1.1em; + margin: 10px 0; + font-size: 120%; + float: left; /* LTR */ + width: 75%; +} +#header .block ul, /* Any menu block in the header region. */ +#header .block li { + list-style: none; + margin: 0; + padding: 0; +} +#header .block-menu li a { + float: left; /* LTR */ + padding: 0 12px; + display: block; + border-right: 1px solid #555; /* LTR */ +} +#header .block-menu li.first a { + padding-left: 0; /* LTR */ +} +#header .block-menu li.last a { + padding-right: 0; /* LTR */ + border-right: none; /* LTR */ +} +#header #block-search-form { /* Search block in the Header region. */ + float: right; /* LTR */ + width: 24%; + margin: 8px 0; +} +#header #block-user-login { /* Login block in the Header region. */ + display: block; + position: absolute; + top: 0; + right: 2%; /* LTR */ + background: #464748; + color: #fff; + float: right; /* LTR */ + font-size: 13px; + padding: 4px 10px 5px; + text-transform: uppercase; + font-size: 80%; +} +#header #block-user-login a { + color: #fff; + font-size: 90%; +} +#header #block-user-login .form-item label { + display: inline; + font-weight: normal; +} +#header #block-user-login .form-item { + display: inline; + font-weight: normal; +} +#header #block-user-login .form-text { + margin-right: 5px; /* LTR */ +} +#header #block-user-login .form-item label .form-required { + display: none; +} +#header #block-user-login div.item-list, +#header #block-user-login .item-list ul { + display: inline; +} +#header #block-user-login .item-list ul li { + display: inline; + margin: 0 5px 0 15px; /* LTR */ +} +#header #block-user-login div.item-list { + margin-top: 5px; + font-style: italic; + font-size: 130%; + text-transform: none; +} +#header #block-user-login .userbox { + margin-left: 10px; /* LTR */ +} +#header #block-user-login input.form-submit { + color: #fff; + background: #929599; + border: 1px solid #73767c; + padding: 1px; + text-transform: uppercase; + margin-top: 1px; + position: relative; + display: inline; +} +#header #block-user-login #user-login-form div.item-list { /* Hide create new account + email replacement password links. */ + display: none; +} + +/* --------------- Main Navigation ------------ */ + +#navigation { + padding: 0 15px; + clear: both; +} +#navigation a { + color: #d9d9d9; + padding: 0.6em 1em 0.4em; +} +#navigation ul { + padding: 2px 0; +} +#navigation ul.links { + font-size: 105%; + padding: 0.6em 0.6em 4px; +} +#navigation ul.links li a { + color: #333; + background: #ccc; + background: rgba(255, 255, 255, 0.7); + text-shadow: 0 1px #eee; + -khtml-border-radius-topleft: 8px; + -moz-border-radius-topleft: 8px; + -webkit-border-top-left-radius: 8px; + border-top-left-radius: 8px; + -khtml-border-radius-topright: 8px; + -moz-border-radius-topright: 8px; + -webkit-border-top-right-radius: 8px; + border-top-right-radius: 8px; +} +#navigation ul.links li a:hover, +#navigation ul.links li a:focus { + background: #fff; + background: rgba(255, 255, 255, 0.95); +} +#navigation ul.links li a:active { + background: #b3b3b3; + background: rgba(255, 255, 255, 1); +} +#navigation ul.links li.active-trail a { + border-bottom: none; +} +.featured #navigation ul.links li.active-trail a { + background: #f0f0f0; + background: rgba(240, 240, 240, 1.0); +} +#navigation ul.links li { + display: inline; + list-style-type: none; + padding: 0.6em 0 0.4em; +} +#navigation ul.links li.first { + padding-left: 2px; /* LTR */ +} + +/* ------------------- Main ------------------- */ + +#main, +#preview #preview-main { + margin-top: 20px; + margin-bottom: 40px; +} + +/* ----------------- Featured ----------------- */ + +#featured { + text-align: center; + font-size: 180%; + font-weight: normal; + line-height: 1.4em; + padding: 50px 0 45px; + margin: 0; + background: #f0f0f0; + border-bottom: 1px solid #e7e7e7; + text-shadow: 1px 1px #fff; +} +#featured p { + margin: 0; + padding: 0; +} + +/* ----------------- Content ------------------ */ + +.content { + margin-top: 1em; +} +#block-system-main, +#preview #preview-block-system-main { + font-size: 115%; + line-height: 1.5em; +} +body.page-admin-appearance-settings-bartik #block-system-main { + font-size: 100%; +} +h1#page-title, +#preview h1#preview-page-title { + font-size: 220%; + line-height: 1.2em; +} +#block-system-main h2, +#preview #preview-block-system-main h2 { + margin-bottom: 2px; + font-size: 137%; + line-height: 1.4em; +} +#block-system-main .node-teaser h2 a { + color: #181818; +} +#block-system-main .node-teaser { + border-bottom: 1px solid #d3d7d9; + margin-bottom: 30px; + padding-bottom: 15px; +} +#block-system-main .node-teaser .content { + font-size: 92%; + line-height: 1.65em; + color: #3b3b3b; + clear: none; +} +#block-system-main .meta { + font-size: 80%; + color: #68696b; + margin-bottom: -5px; +} +* html #block-system-main .meta { + margin-bottom: 10px; +} +*:first-child+html #block-system-main .meta { + margin-bottom: 10px; +} +#block-system-main .submitted .user-picture img { + float: left; /* LTR */ + height: 20px; + margin: 1px 5px 0 0; /* LTR */ +} +#block-system-main div.field-type-taxonomy-term-reference div.field-label, +#block-system-main div.field-type-taxonomy-term-reference div.field-items, +#block-system-main div.field-type-taxonomy-term-reference div.field-item { + display: inline; + float: left; /* LTR */ + padding-right: 10px; /* LTR */ + font-weight: normal; +} +#block-system-main div.field-name-field-tags div.field-label { + font-size: 86%; + color: #68696b; +} +#block-system-main div.field-name-field-tags div.field-items, +#block-system-main div.field-name-field-tags div.field-item { + font-size: 95%; +} +#block-system-main div.field-name-field-tags a:hover, +#block-system-main div.field-name-field-tags a:focus { + text-decoration: underline; +} +#block-system-main .link-wrapper { + text-align: right; +} +#block-system-main ul.links { + color: #68696b; + font-size: 80%; +} +#block-system-main .field-type-image img, +#block-system-main .user-picture img { + margin: 0 0 1em; +} + +/* ----------------- Comments ----------------- */ + +#comment-wrapper { + border-top: 1px solid #d3d7d9; + padding-top: 15px; +} +#comments h2.title { + margin-bottom: 1em; +} +#comments div.user-picture img { + margin-left: 0; /* LTR */ +} +#comments .comment { + margin-bottom: 20px; +} +#comments .attribution { + float: left; /* LTR */ + width: 110px; +} +#comments .attribution img { + margin: 0; + border: 1px solid #d3d7d9; +} +#comments .submitted p { + margin: 4px 0; + font-size: 110%; + line-height: 1.2em; +} +#comments .submitted .comment-time { + font-size: 70%; + color: #68696b; +} +#comments .content { + font-size: 90%; +} +#comments .comment-arrow { + background: url(../images/comment-arrow.png); /* LTR */ + height: 40px; + width: 20px; + margin-left: -45px; /* LTR */ + margin-top: 10px; + position: absolute; +} +#comments .comment-text { + margin-left: 140px; /* LTR */ + padding: 10px 25px; + border: 1px solid #d3d7d9; +} +#comments .indented { + margin-left: 40px; /* LTR */ +} + +/* ------------------ Sidebar ----------------- */ + +.sidebar .block { + border: 1px solid; + padding: 15px 20px; + margin: 20px 0; +} +.sidebar h2, +#preview .sidebar h2 { + margin: 0 0 0.5em; + border-bottom: 1px solid #d6d6d6; + padding-bottom: 5px; + text-shadow: 0 1px 0 #fff; +} +.sidebar tbody { + border: none; +} +.sidebar tr.even, +.sidebar tr.odd { + background: none; + border-bottom: 1px solid #d6d6d6; +} + +/* ----------------- Triptych ----------------- */ + +#triptych-wrapper { + background-color: #fafafa; + background: rgba(40,40,0,0.08); +} +#triptych h2 { + color: #000; + font-size: 194%; + margin-bottom: .8em; + text-shadow: 0px 1px 0 #fff; + text-align: center; + line-height: 1.2em; +} +#triptych .block { + margin-bottom: 2em; + padding-bottom: 2em; + border-bottom: 1px solid #dfdfdf; +} +#triptych .block.last { + border-bottom: none; +} +#triptych .block ul li, +#triptych .block ol li { + list-style: none; +} +#triptych .block ul, +#triptych .block ol { + padding-left: 0; +} +#triptych #block-user-login .form-text { + width: 185px; +} +#triptych #block-user-online p { + margin-bottom: 0; +} +#triptych #block-node-syndicate h2 { + overflow: hidden; + width: 0; + height: 0; +} +#triptych-last #block-node-syndicate { + text-align: right; +} +#triptych #block-search-form .form-type-textfield input { + width: 185px; +} +#triptych-middle #block-system-powered-by { + text-align: center; +} +#triptych-last #block-system-powered-by { + text-align: right; +} + +/* ------------------ Footer ------------------ */ + +#footer-wrapper { + color: #c0c0c0; + color: rgba(255,255,255,0.65); + font-size: 90%; +} +#footer-wrapper a { + color: #fcfcfc; + color: rgba(255,255,255,0.8); +} +#footer-wrapper a:hover, +#footer-wrapper a:focus { + color: #fefefe; + color: rgba(255,255,255,0.95); + text-decoration: underline; +} +#footer-wrapper .block { + margin: 20px 0; + border: 1px solid #444; + border-color: rgba(255,255,255,0.1); + padding: 10px; +} +#footer-columns .block-menu, +#footer .block { + margin: 0; + padding: 0; + border: none; +} +#footer .block, +#footer .block .content { + overflow: hidden; + margin: .5em 0; +} +#footer .block h2 { + margin: 0; +} +#footer-wrapper ul#secondary-menu { + margin: 1em 0; +} +#footer-columns h2 { + border-bottom: 1px solid #555; + border-color: rgba(255,255,255,0.15); + font-size: 104%; + margin-bottom: 0; + padding-bottom: 3px; + text-transform: uppercase; +} +#footer-columns .content { + margin-top: 0; +} +#footer-columns p { + margin-top: 1em; +} +#footer-columns .content ul { + list-style: none; + padding-left: 0; /* LTR */ + margin-left: 0 +} +#footer-columns .content li { + list-style: none; + margin: 0; + padding: 0; +} +#footer-columns .content li a { + display: block; + border-bottom: 1px solid #555; + border-color: rgba(255,255,255,0.15); + line-height: 1.2em; + padding: 0.8em 2px 0.8em 20px; /* LTR */ + text-indent: -15px; +} +#footer-columns .content li a:hover, +#footer-columns .content li a:focus { + background-color: #1f1f21; + background-color: rgba(255,255,255,.05); + text-decoration: none; +} +#footer { + font-size: 92%; + letter-spacing: 0.2px; + margin-top: 30px; + border-top: 1px solid #555; + border-color: rgba(255,255,255,0.15); +} +#footer .region { + margin-top: 20px; +} +#footer .block { + clear: both; +} +#footer ul, +#footer li { + list-style: none; + margin: 0; + padding: 0; +} +#footer li a { + float: left; /* LTR */ + padding: 0 12px; + display: block; + border-right: 1px solid #555; /* LTR */ + border-color: rgba(255,255,255,0.15); +} +#footer li.first a { + padding-left: 0; /* LTR */ +} +#footer li.last a { + padding-right: 0; /* LTR */ + border-right: none; /* LTR */ +} +#footer-wrapper tr.odd { + background-color: transparent; +} +#footer-wrapper tr.even { + background-color: #2c2c2c; + background-color: rgba(0,0,0,0.15) +} + +/* --------------- System Tabs --------------- */ + +.tabs { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + margin-bottom: 20px; +} +.tabs ul.primary { + padding: 0 3px; + margin: 0; + overflow: hidden; + border: none; + background: transparent url(../images/tabs-border.png) repeat-x left bottom; +} +.tabs ul.primary li { + display: block; + float: left; /* LTR */ + vertical-align: bottom; + margin: 0 5px 0 0; /* LTR */ +} +.tabs ul.primary li a.active { + border-bottom: 1px solid #fff; +} +.tabs ul.primary li a { + color: #000; + background-color: #ededed; + height: 1.8em; + line-height: 1.8em; + display: block; + float: left; /* not LTR */ + padding: 0 10px 3px; + margin: 0; + text-shadow: 0 1px 0 #fff; + -khtml-border-radius-topleft: 6px; + -moz-border-radius-topleft: 6px; + -webkit-border-top-left-radius: 6px; + border-top-left-radius: 6px; + -khtml-border-radius-topright: 6px; + -moz-border-radius-topright: 6px; + -webkit-border-top-right-radius: 6px; + border-top-right-radius: 6px; +} +.tabs ul.secondary { + border-bottom: none; + padding: 0.5em 0; +} +.tabs ul.secondary li { + display: block; + float: left; /* LTR */ +} +.tabs ul.secondary li:last-child { + border-right: none; /* LTR */ +} +.tabs ul.secondary li:first-child { + padding-left: 0; /* LTR */ +} +.tabs ul.secondary li a { + padding: .25em .5em; +} +.tabs ul.secondary li a.active { + background: #f2f2f2; + border-bottom: none; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + -khtml-border-radius: 5px; + border-radius: 5px; +} +ul.action-links { + list-style: none; + margin: 5px; + padding: .5em 1em; +} +ul.action-links li { + display: inline-block; + margin-left: 10px; +} +ul.action-links li a { + padding-left: 15px; + background: url(../images/add.png) no-repeat left center; + margin: 0 10px 0 0; +} + +/* ---------------- Messages ----------------- */ + +#messages { + padding: 20px 0 5px; + margin: 0 auto; +} +div.messages { + padding: 1.2em 2em 1em; + margin: 8px 0; +} +div.status, tr.status { + background-color: #c7ffc0; + border: 1px solid #89d47f; +} +div.warning, tr.warning { + background-color: #fcfca7; + border: 1px solid #e1c46b; +} +div.error, tr.error { + background-color: #ffcccc; + border: 1px solid #fb6b6b; +} + +/* -------------- Breadcrumbs -------------- */ + +.breadcrumb { + font-size:92%; +} + +/* -------------- User Profile -------------- */ + +.profile .user-picture { + float: none; +} + +/* -------------- Password Meter ------------- */ + +.password-parent, +div.form-item div.password-suggestions { + position: relative; + width: auto; +} +#password-strength { + float: none; + left: 16em; + position: absolute; + width: 11.5em; +} +#password-strength-text, +.password-strength-title, +div.password-confirm { + font-size: 0.82em; +} +#password-strength-text { + margin-top: 0.2em; +} + +/* ---------------- Buttons ---------------- */ + +input.form-submit, +a.button { + background: #fff url(../images/buttons.png) 0 0 repeat-x; + border: 1px solid #e4e4e4; + border-bottom: 1px solid #b4b4b4; + border-left-color: #d2d2d2; + border-right-color: #d2d2d2; + color: #3a3a3a; + cursor: pointer; + font-size: 90%; + font-weight: normal; + text-align: center; + margin-bottom: 1em; + padding: 4px 17px; + -khtml-border-radius: 15px; + -moz-border-radius: 20px; + -webkit-border-radius: 15px; + border-radius: 15px; +} +a.button:link, +a.button:visited, +a.button:hover, +a.button:focus, +a.button:active { + text-decoration: none; + color: #5a5a5a; +} + +/* -------------- Form Elements ------------- */ + +fieldset { + background: #ffffff; + border: 1px solid #cccccc; + margin-top: 10px; + margin-bottom: 32px; + padding: 0 0 10px; + position: relative; + top: 12px; /* Offsets the negative margin of legends */ + -khtml-border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; +} +.fieldset-wrapper { + margin-top: 25px; +} +.filter-wrapper { + -khtml-border-radius-topright: 0; + -khtml-border-radius-topleft: 0; + -moz-border-radius-topright: 0; + -moz-border-radius-topleft: 0; + -webkit-border-top-left-radius: 0; + -webkit-border-top-right-radius: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +fieldset.collapsed { + background: transparent; + -khtml-border-radius: 0; + -moz-border-radius: 0; + -webkit-border-radius: 0; + border-radius: 0; +} +fieldset legend { + background: #dbdbdb; + border: 1px solid #ccc; + border-bottom: none; + color: #3b3b3b; + display: block; + height: 2em; + left: -1px; /* LTR */ + font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, sans-serif; + line-height: 2em; + padding: 0; + position: absolute; + text-indent: 10px; + text-shadow: 0 1px 0 #fff; + top: -12px; + width: 100%; + -khtml-border-radius-topleft: 4px; + -moz-border-radius-topleft: 4px; + -webkit-border-top-left-radius: 4px; + border-top-left-radius: 4px; + -khtml-border-radius-topright: 4px; + -moz-border-radius-topright: 4px; + -webkit-border-top-right-radius: 4px; + border-top-right-radius: 4px; +} +fieldset.collapsed legend { + -khtml-border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; +} +fieldset legend a { + color: #3b3b3b; +} +fieldset legend a:hover, +fieldset legend a:focus, +fieldset legend a:active { + color: #000; +} +fieldset .fieldset-wrapper { + padding: 0 10px; +} +input { + margin: 2px 0; + padding: 4px; +} +textarea.form-textarea, +select.form-select { + padding: 4px; +} +input.form-text, +textarea.form-textarea, +select.form-select { + border: 1px solid #ccc; +} +input.form-submit:hover, +input.form-submit:focus { + background: #dedede; +} +.password-suggestions ul li { + margin-left: 1.2em; /* LTR */ +} +.form-item { + margin-bottom: 1em; + margin-top: 2px; +} +.form-item label { + font-size: 90%; +} +.form-radio { + margin-right: 4px; +} +fieldset .description { + margin-top: 5px; + line-height: 1.4em; + color: #3c3c3c; + font-style: italic; +} +.form-actions { + padding-top: 10px; +} + +/* Animated throbber */ +html.js input.form-autocomplete { + background-position: 100% 4px; /* LTR */ +} +html.js input.throbbing { + background-position: 100% -16px; /* LTR */ +} + +/* Comment form */ +#comment-form .form-item { + overflow: hidden; + margin-bottom: .8em; +} +#comment-form .form-type-textfield label, +#comment-form .form-type-item label { + float: left; /* LTR */ +} +#comment-form .form-type-textfield input, +#comment-form .form-item .username { + float: right; /* LTR */ + width: 75%; + -khtml-border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; +} +#comment-form .form-item .description { + font-size: .75em; + line-height: 1em; + float: right; /* LTR */ + width: 76%; +} +#comment-form .form-textarea { + -khtml-border-radius-topleft: 4px; + -khtml-border-radius-topright: 4px; + -webkit-border-top-left-radius: 4px; + -webkit-border-top-right-radius: 4px; + -moz-border-radius-topleft: 4px; + -moz-border-radius-topright: 4px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +#comment-form fieldset.filter-wrapper { + top: 0; + padding-bottom: 10px; +} +#comment-form fieldset.filter-wrapper .fieldset-wrapper, +#comment-form .text-format-wrapper .form-item { + margin-top: 0; + margin-bottom: 0; +} +#comment-form fieldset.filter-wrapper .tips { + font-size: .75em; +} + +/* -------------- Other Overrides ------------- */ + +div.password-suggestions { + border: 0; +} +.ui-widget-overlay { + background: #222222; + opacity: 0.7; +} +div.vertical-tabs .vertical-tabs-panes fieldset.vertical-tabs-pane { + padding: 1em; +} +#forum tr td.forum { + padding-left: 35px; +} + +/* --------------- Search Form ---------------- */ + +#block-search-form .content { + margin-top: 0; +} +#block-search-form .form-item-search-block-form input { + width: 70%; +} +.region-content #block-search-form .form-item-search-block-form input, +.region-footer #block-search-form .form-item-search-block-form input { + width: auto; +} +#block-search-form #edit-actions { + float: right; +} +.region-content #block-search-form #edit-actions, +.region-footer #block-search-form #edit-actions { + float: none; +} +#block-search-form .form-actions { + padding-top: 0; +} +#search-block-form input.form-submit, +#search-form input.form-submit { + height: 24px; + width: 14px; + overflow: hidden; + cursor: pointer; + text-indent: -9999px; + border: none; + background: url(../images/search-button.png) no-repeat left center; +} + +/* -------------- Shortcut Links -------------- */ + +.shortcut-wrapper { + margin: 2.2em 0 1.1em 0; /* Same as usual h1#page-title margin. */ +} +.shortcut-wrapper h1#page-title { + float: left; /* LTR */ + margin: 0; +} +div.add-or-remove-shortcuts { + padding-top: 0.9em; +} +.overlay div.add-or-remove-shortcuts { + padding-top: 0.8em; +} + +/* ---------- Admin-specific Theming ---------- */ + +.page-admin #block-system-main img { + margin-right: 15px; /* LTR */ +} +.page-admin-structure-block-demo .block-region { + color: #000000; +} +.page-admin #admin-dblog img { + margin: 0 5px; +} +/* Fix spacing when Seven is used in the overlay. */ +#system-theme-settings fieldset { + padding: 0; +} +#system-theme-settings fieldset .fieldset-legend { + margin-top: 0; +} +/* Configuration. */ +div.admin .right, +div.admin .left { + width: 49%; + margin: 0; +} +div.admin-panel { + background: #fbfbfb; + border: 1px solid #ccc; + margin: 10px 0; + padding: 0px 5px 5px; +} +div.admin-panel h3 { + margin: 16px 7px; +} +div.admin-panel dt { + border-top: 1px solid #ccc; + padding: 7px 0 0; +} +div.admin-panel dd { + margin: 0 0 10px; +} +div.admin-panel .description { + margin: 0 0 14px 7px; +} + +/* ---------- Overlay layout styles ----------- */ + +.overlay #main, +.overlay #content { + width: auto; + float: none; +} +.overlay #page { + padding: 0 2em; +} +.overlay #skip-link, +.overlay .region-page-top, +.overlay #header, +.overlay #page-title, +.overlay #featured, +.overlay #sidebar-first, +.overlay #triptych-wrapper, +.overlay #footer-wrapper { + display: none; +} +.overlay-processed .field-type-image { + display: block; + float: none; +} +.overlay #messages { + width: auto; +} + +/* ---------- Poll ----------- */ + +.node .poll { + margin: 2em 0; +} +.node .poll #edit-choice { + margin: 0 0 1.5em; +} +.poll .vote-form { + text-align: left; /* LTR */ +} +.poll .vote-form .choices { + margin: 0; +} +.poll .percent { + font-size: 86%; + font-style: italic; + margin-bottom: 3em; + margin-top: -3.2em; + float: right; + text-align: right; +} +.poll .text { + clear: right; +} +.poll .total { + font-size: 95%; + font-style: italic; + text-align: right; /* LTR */ + clear: both; +} +.poll .form-item label { + font-size: 100%; +} +.node .poll { + margin: 1.8em 0 0; +} +.node .poll #edit-choice { + margin: 0 0 1.2em; +} +.poll .bar .foreground { + background-color: #666; +} +#footer-wrapper .poll .bar { + background-color: #666; +} +#footer-wrapper .poll .bar .foreground { + background-color: #ddd; +} + +/* ---------- Color Form ----------- */ + +.color-form #placeholder { + position: static; + width: 195px; +} +.overlay .color-form #placeholder { + position: absolute; +} +.color-form #palette { + margin-left: 20px; /* LTR */ +} +.color-form .form-item { + border: 1px solid #fbfbfb; + margin: 0; + padding: .5em; + position: relative; + max-width: 350px; + -khtml-border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + border-radius: 5px; +} +.color-form .form-item label { + float: left; /* LTR */ + font-size: 1em; + width: 150px; +} +.color-form .form-item.item-selected { + background-color: #eeeeee; + border: 1px solid #cfcfcf; +} +.color-form #palette .lock { + position: absolute; + top: 5px; + left: -20px; /* LTR */ +} +.color-form #preview { + font-size: .85em; +} +.overlay #preview #footer-wrapper { + display: block; +} diff --git a/themes/bartik/template.php b/themes/bartik/template.php new file mode 100644 index 00000000..3ddeedf8 --- /dev/null +++ b/themes/bartik/template.php @@ -0,0 +1,146 @@ +<?php +// $Id: template.php,v 1.1 2010/07/06 05:25:51 webchick Exp $ + +/** + * Add body classes if certain regions have content. + */ +function bartik_preprocess_html(&$variables) { + if (!empty($variables['page']['featured'])) { + $variables['classes_array'][] = 'featured'; + } + + if (!empty($variables['page']['triptych_first']) + || !empty($variables['page']['triptych_middle']) + || !empty($variables['page']['triptych_last'])) { + $variables['classes_array'][] = 'triptych'; + } + + if (!empty($variables['page']['footer_firstcolumn']) + || !empty($variables['page']['footer_secondcolumn']) + || !empty($variables['page']['footer_thirdcolumn']) + || !empty($variables['page']['footer_fourthcolumn'])) { + $variables['classes_array'][] = 'footer-columns'; + } + + // Add conditional stylesheets for IE + drupal_add_css(path_to_theme() . '/css/ie.css', array('weight' => CSS_THEME, 'browsers' => array('IE' => 'lte IE 7', '!IE' => FALSE), 'preprocess' => FALSE)); + drupal_add_css(path_to_theme() . '/css/ie6.css', array('weight' => CSS_THEME, 'browsers' => array('IE' => 'IE 6', '!IE' => FALSE), 'preprocess' => FALSE)); +} + +/** + * Override or insert variables into the page template for HTML output. + */ +function bartik_process_html(&$variables) { + // Hook into color.module. + if (module_exists('color')) { + _color_html_alter($variables); + } +} + +/** + * Override or insert variables into the page template. + */ +function bartik_process_page(&$variables) { + // Hook into color.module. + if (module_exists('color')) { + _color_page_alter($variables); + } + // Always print the site name and slogan, but if they are toggled off, we'll + // just hide them visually. + $variables['hide_site_name'] = theme_get_setting('toggle_name') ? FALSE : TRUE; + $variables['hide_site_slogan'] = theme_get_setting('toggle_slogan') ? FALSE : TRUE; + if ($variables['hide_site_name']) { + // If toggle_name is FALSE, the site_name will be empty, so we rebuild it. + $variables['site_name'] = filter_xss_admin(variable_get('site_name', 'Drupal')); + } + if ($variables['hide_site_slogan']) { + // If toggle_site_slogan is FALSE, the site_slogan will be empty, so we rebuild it. + $variables['site_slogan'] = filter_xss_admin(variable_get('site_slogan', '')); + } + // Since the title and the shortcut link are both block level elements, + // positioning them next to each other is much simpler with a wrapper div. + if (!empty($variables['title_suffix']['add_or_remove_shortcut']) && $variables['title']) { + // Add a wrapper div using the title_prefix and title_suffix render elements. + $variables['title_prefix']['shortcut_wrapper'] = array( + '#markup' => '<div class="shortcut-wrapper clearfix">', + '#weight' => 100, + ); + $variables['title_suffix']['shortcut_wrapper'] = array( + '#markup' => '</div>', + '#weight' => -99, + ); + // Make sure the shortcut link is the first item in title_suffix. + $variables['title_suffix']['add_or_remove_shortcut']['#weight'] = -100; + } +} + +/** + * Override or insert variables into the maintenance page template. + */ +function bartik_process_maintenance_page(&$variables) { + // Always print the site name and slogan, but if they are toggled off, we'll + // just hide them visually. + $variables['hide_site_name'] = theme_get_setting('toggle_name') ? FALSE : TRUE; + $variables['hide_site_slogan'] = theme_get_setting('toggle_slogan') ? FALSE : TRUE; + if ($variables['hide_site_name']) { + // If toggle_name is FALSE, the site_name will be empty, so we rebuild it. + $variables['site_name'] = filter_xss_admin(variable_get('site_name', 'Drupal')); + } + if ($variables['hide_site_slogan']) { + // If toggle_site_slogan is FALSE, the site_slogan will be empty, so we rebuild it. + $variables['site_slogan'] = filter_xss_admin(variable_get('site_slogan', '')); + } +} + +/** + * Override or insert variables into the block template. + */ +function bartik_preprocess_block(&$variables) { + // In the header region, visually hide the title of any menu block or of the + // user login block, but leave it accessible. + if ($variables['block']->region == 'header' && ($variables['block']->module == 'menu' || $variables['block']->module == 'user' && $variables['block']->delta == 'login')) { + $variables['title_attributes_array']['class'][] = 'element-invisible'; + } + // System menu blocks should get the same class as menu module blocks. + if (in_array($variables['block']->delta, array_keys(menu_list_system_menus()))) { + $variables['classes_array'][] = 'block-menu'; + // Also, hide the title if its in the header region. + if ($variables['block']->region == 'header') { + $variables['title_attributes_array']['class'][] = 'element-invisible'; + } + } + // Set "first" and "last" classes. + if ($variables['block']->position_first){ + $variables['classes_array'][] = 'first'; + } + if ($variables['block']->position_last){ + $variables['classes_array'][] = 'last'; + } + // Set "odd" & "even" classes. + $variables['classes_array'][] = $variables['block']->position % 2 == 0 ? 'odd' : 'even'; +} + +/** + * Implements hook_page_alter(). + */ +function bartik_page_alter(&$page) { + // Determine the position and count of blocks within regions. + foreach ($page as &$region) { + // Make sure this is a "region" element. + if (is_array($region) && isset($region['#region'])) { + $i = 0; + foreach ($region as &$block) { + // Make sure this is a "block" element. + if (is_array($block) && isset($block['#block'])) { + $block['#block']->position = $i++; + // Set a flag for "first" and "last" blocks. + $block['#block']->position_first = ($block['#block']->position == 0); + $block['#block']->position_last = FALSE; + $last_block =& $block; + } + } + $last_block['#block']->position_last = TRUE; + $region['#block_count'] = $i; + } + } +} diff --git a/themes/bartik/templates/comment-wrapper.tpl.php b/themes/bartik/templates/comment-wrapper.tpl.php new file mode 100644 index 00000000..d4c391f1 --- /dev/null +++ b/themes/bartik/templates/comment-wrapper.tpl.php @@ -0,0 +1,54 @@ +<?php +// $Id: comment-wrapper.tpl.php,v 1.1 2010/07/06 05:25:51 webchick Exp $ + +/** + * @file + * Bartik's theme implementation to provide an HTML container for comments. + * + * Available variables: + * - $content: The array of content-related elements for the node. Use + * render($content) to print them all, or + * print a subset such as render($content['comment_form']). + * - $classes: String of classes that can be used to style contextually through + * 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. + * The constants below the variables show the possible values and should be + * used for comparison. + * - $display_mode + * - COMMENT_MODE_FLAT + * - COMMENT_MODE_THREADED + * + * Other variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * + * @see template_preprocess_comment_wrapper() + * @see theme_comment_wrapper() + */ +?> +<div id="comments-wrapper"> + <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']); ?> + + <?php if ($content['comment_form']): ?> + <h2 class="title comment-form"><?php print t('Add new comment'); ?></h2> + <?php print render($content['comment_form']); ?> + <?php endif; ?> + </div> +</div> diff --git a/themes/bartik/templates/comment.tpl.php b/themes/bartik/templates/comment.tpl.php new file mode 100644 index 00000000..4b36aabc --- /dev/null +++ b/themes/bartik/templates/comment.tpl.php @@ -0,0 +1,102 @@ +<?php +// $Id: comment.tpl.php,v 1.1 2010/07/06 05:25:51 webchick Exp $ + +/** + * @file + * Bartik's theme implementation for comments. + * + * Available variables: + * - $author: Comment author. Can be link or plain text. + * - $content: An array of comment items. Use render($content) to print them all, or + * print a subset such as render($content['field_example']). Use + * hide($content['field_example']) to temporarily suppress the printing of a + * given element. + * - $created: Formatted date and time for when the comment was created. + * Preprocess functions can reformat it by calling format_date() with the + * desired parameters on the $comment->created variable. + * - $changed: Formatted date and time for when the comment was last changed. + * Preprocess functions can reformat it by calling format_date() with the + * desired parameters on the $comment->changed variable. + * - $new: New comment marker. + * - $permalink: Comment permalink. + * - $picture: Authors picture. + * - $signature: Authors signature. + * - $status: Comment status. Possible values are: + * comment-unpublished, comment-published or comment-preview. + * - $title: Linked title. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the following: + * - comment: The current template type, i.e., "theming hook". + * - comment-by-anonymous: Comment by an unregistered user. + * - comment-by-node-author: Comment by the author of the parent node. + * - comment-preview: When previewing a new or edited comment. + * The following applies only to viewers who are registered users: + * - comment-unpublished: An unpublished comment visible only to administrators. + * - comment-by-viewer: Comment by the user currently viewing the page. + * - comment-new: New comment since last the visit. + * - $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. + * + * These two variables are provided for context: + * - $comment: Full comment object. + * - $node: Node object the comments are attached to. + * + * Other variables: + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * + * @see template_preprocess() + * @see template_preprocess_comment() + * @see template_process() + * @see theme_comment() + */ +?> +<div class="<?php print $classes; ?> clearfix"<?php print $attributes; ?>> + + <div class="attribution"> + + <?php print $picture; ?> + + <div class="submitted"> + <?php print $permalink; ?> + <p class="commenter-name"> + <?php print $author; ?> + </p> + <p class="comment-time"> + <?php print $created; ?> + </p> + </div> + </div> + + <div class="comment-text"> + <div class="comment-arrow"></div> + + <?php if ($new): ?> + <span class="new"><?php print $new; ?></span> + <?php endif; ?> + + <?php print render($title_prefix); ?> + <h3<?php print $title_attributes; ?>><?php print $title; ?></h3> + <?php print render($title_suffix); ?> + + <div class="content"<?php print $content_attributes; ?>> + <?php + // We hide the comments and links now so that we can render them later. + hide($content['links']); + print render($content); + ?> + <?php if ($signature): ?> + <div class="user-signature clearfix"> + <?php print $signature; ?> + </div> + <?php endif; ?> + </div> <!-- /.content --> + + <?php print render($content['links']); ?> + </div> <!-- /.comment-text --> +</div> diff --git a/themes/bartik/templates/maintenance-page.tpl.php b/themes/bartik/templates/maintenance-page.tpl.php new file mode 100644 index 00000000..403f7740 --- /dev/null +++ b/themes/bartik/templates/maintenance-page.tpl.php @@ -0,0 +1,93 @@ +<?php +// $Id: maintenance-page.tpl.php,v 1.1 2010/07/06 05:25:51 webchick Exp $ + +/** + * @file + * Bartik's theme implementation to display a single Drupal page while offline. + * + * All the available variables are mirrored in page.tpl.php. Some may be left + * blank but they are provided for consistency. + * + * @see template_preprocess() + * @see template_preprocess_maintenance_page() + * @see bartik_process_maintenance_page() + */ +?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php print $language->language; ?>" lang="<?php print $language->language; ?>" dir="<?php print $language->dir; ?>"> + +<head> + <?php print $head; ?> + <title><?php print $head_title; ?></title> + <?php print $styles; ?> + <?php print $scripts; ?> +</head> +<body class="<?php print $classes; ?>" <?php print $attributes;?>> + <div id="skip-link"> + <a href="#main-content"><?php print t('Skip to main content'); ?></a> + </div> + <?php print $page_top; ?> + +<div id="page-wrapper"><div id="page"> + + <div id="header"><div class="section clearfix"> + + <?php if ($site_name || $site_slogan): ?> + <div id="name-and-slogan"<?php if ($hide_site_name && $hide_site_slogan) { print ' class="element-invisible"'; } ?>> + + <?php if ($site_name): ?> + <?php if ($title): ?> + <div id="site-name"<?php if ($hide_site_name) { print ' class="element-invisible"'; } ?>> + <strong> + <a href="<?php print $front_page; ?>" title="<?php print t('Home'); ?>" rel="home"><span><?php print $site_name; ?></span></a> + </strong> + </div> + <?php else: /* Use h1 when the content title is empty */ ?> + <h1 id="site-name"<?php if ($hide_site_name) { print ' class="element-invisible"'; } ?>> + <a href="<?php print $front_page; ?>" title="<?php print t('Home'); ?>" rel="home"><span><?php print $site_name; ?></span></a> + </h1> + <?php endif; ?> + <?php endif; ?> + + <?php if ($site_slogan): ?> + <div id="site-slogan"<?php if ($hide_site_slogan) { print ' class="element-invisible"'; } ?>> + <?php print $site_slogan; ?> + </div> + <?php endif; ?> + + </div> <!-- /#name-and-slogan --> + <?php endif; ?> + + <?php print $header; ?> + + </div></div> <!-- /.section, /#header --> + + <div id="main-wrapper"><div id="main" class="clearfix"> + + <div id="content" class="column"><div class="section"> + <?php if ($highlight): ?><div id="highlight"><?php print $highlight; ?></div><?php endif; ?> + <a id="main-content"></a> + <?php if ($title): ?> + <h1 class="title" id="page-title"> + <?php print $title; ?> + </h1> + <?php endif; ?> + <?php print $content; ?> + + <?php if ($messages): ?> + <div id="messages"><div class="section clearfix"> + <?php print $messages; ?> + </div></div> <!-- /.section, /#messages --> + <?php endif; ?> + + </div></div> <!-- /.section, /#content --> + + + </div></div> <!-- /#main, /#main-wrapper --> + +</div></div> <!-- /#page, /#page-wrapper --> + + <?php print $page_bottom; ?> +</body> +</html> diff --git a/themes/bartik/templates/node.tpl.php b/themes/bartik/templates/node.tpl.php new file mode 100644 index 00000000..6961698e --- /dev/null +++ b/themes/bartik/templates/node.tpl.php @@ -0,0 +1,126 @@ +<?php +// $Id: node.tpl.php,v 1.1 2010/07/06 05:25:51 webchick Exp $ + +/** + * @file + * Bartik's theme implementation to display a node. + * + * Available variables: + * - $title: the (sanitized) title of the node. + * - $content: An array of node items. Use render($content) to print them all, + * or print a subset such as render($content['field_example']). Use + * hide($content['field_example']) to temporarily suppress the printing of a + * given element. + * - $user_picture: The node author's picture from user-picture.tpl.php. + * - $date: Formatted creation date. Preprocess functions can reformat it by + * calling format_date() with the desired parameters on the $created variable. + * - $name: Themed username of node author output from theme_username(). + * - $node_url: Direct url of the current node. + * - $display_submitted: whether submission information should be displayed. + * - $classes: String of classes that can be used to style contextually through + * CSS. It can be manipulated through the variable $classes_array from + * preprocess functions. The default values can be one or more of the + * following: + * - node: The current template type, i.e., "theming hook". + * - node-[type]: The current node type. For example, if the node is a + * "Blog entry" it would result in "node-blog". Note that the machine + * name will often be in a short form of the human readable label. + * - node-teaser: Nodes in teaser form. + * - node-preview: Nodes in preview mode. + * The following are controlled through the node publishing options. + * - node-promoted: Nodes promoted to the front page. + * - node-sticky: Nodes ordered above other non-sticky nodes in teaser + * listings. + * - node-unpublished: Unpublished nodes visible only to administrators. + * - $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. + * + * Other variables: + * - $node: Full node object. Contains data that may not be safe. + * - $type: Node type, i.e. story, page, blog, etc. + * - $comment_count: Number of comments attached to the node. + * - $uid: User ID of the node author. + * - $created: Time the node was published formatted in Unix timestamp. + * - $classes_array: Array of html class attribute values. It is flattened + * into a string within the variable $classes. + * - $zebra: Outputs either "even" or "odd". Useful for zebra striping in + * teaser listings. + * - $id: Position of the node. Increments each time it's output. + * + * Node status variables: + * - $view_mode: View mode, e.g. 'full', 'teaser'... + * - $teaser: Flag for the teaser state (shortcut for $view_mode == 'teaser'). + * - $page: Flag for the full page state. + * - $promote: Flag for front page promotion state. + * - $sticky: Flags for sticky post setting. + * - $status: Flag for published status. + * - $comment: State of comment settings for the node. + * - $readmore: Flags true if the teaser content of the node cannot hold the + * main body content. + * - $is_front: Flags true when presented in the front page. + * - $logged_in: Flags true when the current user is a logged-in member. + * - $is_admin: Flags true when the current user is an administrator. + * + * Field variables: for each field instance attached to the node a corresponding + * variable is defined, e.g. $node->body becomes $body. When needing to access + * a field's raw values, developers/themers are strongly encouraged to use these + * variables. Otherwise they will have to explicitly specify the desired field + * language, e.g. $node->body['en'], thus overriding any language negotiation + * rule that was previously applied. + * + * @see template_preprocess() + * @see template_preprocess_node() + * @see template_process() + */ +?> +<div id="node-<?php print $node->nid; ?>" class="<?php print $classes; ?> clearfix"<?php print $attributes; ?>> + + <?php print render($title_prefix); ?> + <?php if (!$page): ?> + <h2<?php print $title_attributes; ?>> + <a href="<?php print $node_url; ?>"><?php print $title; ?></a> + </h2> + <?php endif; ?> + <?php print render($title_suffix); ?> + + <?php if ($display_submitted): ?> + <div class="meta submitted"> + <?php print $user_picture; ?> + <?php + print t('published by !username on !datetime', + array('!username' => $name, '!datetime' => $date)); + ?> + </div> + <?php endif; ?> + + <div class="content clearfix"<?php print $content_attributes; ?>> + <?php + // We hide the comments and links now so that we can render them later. + hide($content['comments']); + hide($content['links']); + print render($content); + ?> + </div> + + <?php + // Remove the "Add new comment" link on the teaser page or if the comment + // form is being displayed on the same page. + if ($teaser || !empty($content['comments']['comment_form'])) { + unset($content['links']['comment']['#links']['comment-add']); + } + // Only display the wrapper div if there are links. + $links = render($content['links']); + if ($links): + ?> + <div class="link-wrapper"> + <?php print $links; ?> + </div> + <?php endif; ?> + + <?php print render($content['comments']); ?> + +</div> diff --git a/themes/bartik/templates/page.tpl.php b/themes/bartik/templates/page.tpl.php new file mode 100644 index 00000000..5ac4491c --- /dev/null +++ b/themes/bartik/templates/page.tpl.php @@ -0,0 +1,280 @@ +<?php +// $Id: page.tpl.php,v 1.1 2010/07/06 05:25:51 webchick Exp $ + +/** + * @file + * Bartik's theme implementation to display a single Drupal page. + * + * The doctype, html, head and body tags are not in this template. Instead they + * can be found in the html.tpl.php template normally located in the + * modules/system folder. + * + * Available variables: + * + * General utility variables: + * - $base_path: The base URL path of the Drupal installation. At the very + * least, this will always default to /. + * - $directory: The directory the template is located in, e.g. modules/system + * or themes/garland. + * - $is_front: TRUE if the current page is the front page. + * - $logged_in: TRUE if the user is registered and signed in. + * - $is_admin: TRUE if the user has permission to access administration pages. + * + * Site identity: + * - $front_page: The URL of the front page. Use this instead of $base_path, + * when linking to the front page. This includes the language domain or + * prefix. + * - $logo: The path to the logo image, as defined in theme configuration. + * - $site_name: The name of the site, empty when display has been disabled + * in theme settings. + * - $site_slogan: The slogan of the site, empty when display has been disabled + * in theme settings. + * - $hide_site_name: TRUE if the site name has been toggled off on the theme + * settings page. If hidden, the "element-invisible" class is added to make + * the site name visually hidden, but still accessible. + * - $hide_site_slogan: TRUE if the site slogan has been toggled off on the + * theme settings page. If hidden, the "element-invisible" class is added to + * make the site slogan visually hidden, but still accessible. + * + * Navigation: + * - $main_menu (array): An array containing the Main menu links for the + * site, if they have been configured. + * - $secondary_menu (array): An array containing the Secondary menu links for + * the site, if they have been configured. + * - $breadcrumb: The breadcrumb trail for the current page. + * + * Page content (in order of occurrence in the default page.tpl.php): + * - $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: The page title, for use in the actual HTML content. + * - $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. + * - $messages: HTML for status and error messages. Should be displayed + * prominently. + * - $tabs (array): Tabs linking to any sub-pages beneath the current page + * (e.g., the view and edit tabs when displaying a node). + * - $action_links (array): Actions local to the page, such as 'Add menu' on the + * menu administration interface. + * - $feed_icons: A string of all feed icons for the current page. + * - $node: The node object, if there is an automatically-loaded node + * associated with the page, and the node ID is the second argument + * in the page's path (e.g. node/12345 and node/12345/revisions, but not + * comment/reply/12345). + * + * Regions: + * - $page['header']: Items for the header region. + * - $page['featured']: Items for the featured region. + * - $page['highlight']: Items for the highlighted content region. + * - $page['help']: Dynamic help text, mostly for admin pages. + * - $page['content']: The main content of the current page. + * - $page['sidebar_first']: Items for the first sidebar. + * - $page['triptych_first']: Items for the first triptych. + * - $page['triptych_middle']: Items for the middle triptych. + * - $page['triptych_last']: Items for the last triptych. + * - $page['footer_firstcolumn']: Items for the first footer column. + * - $page['footer_secondcolumn']: Items for the second footer column. + * - $page['footer_thirdcolumn']: Items for the third footer column. + * - $page['footer_fourthcolumn']: Items for the fourth footer column. + * - $page['footer']: Items for the footer region. + * + * @see template_preprocess() + * @see template_preprocess_page() + * @see template_process() + * @see bartik_process_page() + */ +?> +<div id="page-wrapper"><div id="page"> + + <div id="header"><div class="section clearfix"> + + <?php if ($logo): ?> + <a href="<?php print $front_page; ?>" title="<?php print t('Home'); ?>" rel="home" id="logo"> + <img src="<?php print $logo; ?>" alt="<?php print t('Home'); ?>" /> + </a> + <?php endif; ?> + + <?php if ($site_name || $site_slogan): ?> + <div id="name-and-slogan"<?php if ($hide_site_name && $hide_site_slogan) { print ' class="element-invisible"'; } ?>> + + <?php if ($site_name): ?> + <?php if ($title): ?> + <div id="site-name"<?php if ($hide_site_name) { print ' class="element-invisible"'; } ?>> + <strong> + <a href="<?php print $front_page; ?>" title="<?php print t('Home'); ?>" rel="home"><span><?php print $site_name; ?></span></a> + </strong> + </div> + <?php else: /* Use h1 when the content title is empty */ ?> + <h1 id="site-name"<?php if ($hide_site_name) { print ' class="element-invisible"'; } ?>> + <a href="<?php print $front_page; ?>" title="<?php print t('Home'); ?>" rel="home"><span><?php print $site_name; ?></span></a> + </h1> + <?php endif; ?> + <?php endif; ?> + + <?php if ($site_slogan): ?> + <div id="site-slogan"<?php if ($hide_site_slogan) { print ' class="element-invisible"'; } ?>> + <?php print $site_slogan; ?> + </div> + <?php endif; ?> + + </div> <!-- /#name-and-slogan --> + <?php endif; ?> + + <?php print render($page['header']); ?> + + <?php if ($main_menu): ?> + <div id="navigation"><div class="section clearfix"> + <?php print theme('links__system_main_menu', array( + 'links' => $main_menu, + 'attributes' => array( + 'id' => 'main-menu', + 'class' => array('links', 'clearfix'), + ), + 'heading' => array( + 'text' => t('Main menu'), + 'level' => 'h2', + 'class' => array('element-invisible'), + ), + )); ?> + </div></div> <!-- /.section, /#navigation --> + <?php endif; ?> + + </div></div> <!-- /.section, /#header --> + + <?php if ($messages): ?> + <div id="messages"><div class="section clearfix"> + <?php print $messages; ?> + </div></div> <!-- /.section, /#messages --> + <?php endif; ?> + + <?php if ($page['featured']): ?> + <div id="featured" class="region"><div class="section clearfix"> + <?php print render($page['featured']); ?> + </div></div> <!-- /.section, /#featured --> + <?php endif; ?> + + <div id="main-wrapper" class="clearfix"><div id="main" class="clearfix"> + + <?php if ($breadcrumb): ?> + <div id="breadcrumb"><?php print $breadcrumb; ?></div> + <?php endif; ?> + + <?php if ($page['sidebar_first']): ?> + <div id="sidebar-first" class="column sidebar"><div class="section"> + <?php print render($page['sidebar_first']); ?> + </div></div> <!-- /.section, /#sidebar-first --> + <?php endif; ?> + + + <div id="content" class="column"><div class="section"> + <?php if ($page['highlight']): ?><div id="highlight"><?php print render($page['highlight']); ?></div><?php endif; ?> + <a id="main-content"></a> + <?php print render($title_prefix); ?> + <?php if ($title): ?> + <h1 class="title" id="page-title"> + <?php print $title; ?> + </h1> + <?php endif; ?> + <?php print render($title_suffix); ?> + <?php if ($tabs): ?> + <div class="tabs"> + <?php print render($tabs); ?> + </div> + <?php endif; ?> + <?php print render($page['help']); ?> + <?php if ($action_links): ?> + <ul class="action-links"> + <?php print render($action_links); ?> + </ul> + <?php endif; ?> + <?php print render($page['content']); ?> + <?php print $feed_icons; ?> + + </div></div> <!-- /.section, /#content --> + + <?php if ($page['sidebar_second']): ?> + <div id="sidebar-second" class="column sidebar"><div class="section"> + <?php print render($page['sidebar_second']); ?> + </div></div> <!-- /.section, /#sidebar-second --> + <?php endif; ?> + + </div></div> <!-- /#main, /#main-wrapper --> + + <?php if ($page['triptych_first'] || $page['triptych_middle'] || $page['triptych_last']): ?> + <div id="triptych-wrapper"><div id="triptych" class="clearfix"> + + <?php if ($page['triptych_first']): ?> + <div id="triptych-first" class="region triptych"><div class="section"> + <?php print render($page['triptych_first']); ?> + </div></div> <!-- /.section, /#triptych-first --> + <?php endif; ?> + + <?php if ($page['triptych_middle']): ?> + <div id="triptych-middle" class="region triptych"><div class="section"> + <?php print render($page['triptych_middle']); ?> + </div></div> <!-- /.section, /#triptych-middle --> + <?php endif; ?> + + <?php if ($page['triptych_last']): ?> + <div id="triptych-last" class="region triptych"><div class="section"> + <?php print render($page['triptych_last']); ?> + </div></div> <!-- /.section, /#triptych-last --> + <?php endif; ?> + + </div></div> <!-- /#triptych, /#triptych-wrapper --> + <?php endif; ?> + + <div id="footer-wrapper"><div class="section"> + + <?php if ($page['footer_firstcolumn'] || $page['footer_secondcolumn'] || $page['footer_thirdcolumn'] || $page['footer_fourthcolumn']): ?> + <div id="footer-columns" class="clearfix"> + + <?php if ($page['footer_firstcolumn']): ?> + <div id="footer-firstcolumn" class="region sitemap"><div class="section"> + <?php print render($page['footer_firstcolumn']); ?> + </div></div> <!-- /.section, /#footer-firstcolumn --> + <?php endif; ?> + + <?php if ($page['footer_secondcolumn']): ?> + <div id="footer-secondcolumn" class="region sitemap"><div class="section"> + <?php print render($page['footer_secondcolumn']); ?> + </div></div> <!-- /.section, /#footer-secondcolumn --> + <?php endif; ?> + + <?php if ($page['footer_thirdcolumn']): ?> + <div id="footer-thirdcolumn" class="region sitemap"><div class="section"> + <?php print render($page['footer_thirdcolumn']); ?> + </div></div> <!-- /.section, /#footer-thirdcolumn --> + <?php endif; ?> + + <?php if ($page['footer_fourthcolumn']): ?> + <div id="footer-fourthcolumn" class="region sitemap"><div class="section"> + <?php print render($page['footer_fourthcolumn']); ?> + </div></div> <!-- /.section, /#footer-fourthcolumn --> + <?php endif; ?> + + </div><!-- /#footer-columns --> + <?php endif; ?> + + <?php if ($page['footer'] || $secondary_menu): ?> + <div id="footer" class="clearfix"> + <?php print theme('links__system_secondary_menu', array( + 'links' => $secondary_menu, + 'attributes' => array( + 'id' => 'secondary-menu', + 'class' => array('links', 'clearfix'), + ), + 'heading' => array( + 'text' => t('Secondary menu'), + 'level' => 'h2', + 'class' => array('element-invisible'), + ), + )); ?> + <?php print render($page['footer']); ?> + </div><!-- /#footer --> + <?php endif; ?> + + </div></div> <!-- /.section, /#footer-wrapper --> + +</div></div> <!-- /#page, /#page-wrapper --> diff --git a/themes/garland/garland.info b/themes/garland/garland.info index 381dd6f3..f5a5ee6d 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/themes/garland/style.css b/themes/garland/style.css index 49d85613..80f4b4ea 100644 --- a/themes/garland/style.css +++ b/themes/garland/style.css @@ -1,4 +1,4 @@ -/* $Id: style.css,v 1.82 2010/05/18 11:56:59 dries Exp $ */ +/* $Id: style.css,v 1.83 2010/06/08 05:16:29 webchick Exp $ */ /** * Generic elements @@ -629,16 +629,32 @@ div#branding strong { margin-bottom: 2em; } -/* Don't display any header elements when within the overlay, and adjust the - page height accordingly. */ -body.overlay #header * { +/** + * Overlay + */ +#overlay #overlay-tabs li a { + background: #d9eaf5; + color: #000; +} +#overlay #overlay-tabs li a:hover, +#overlay #overlay-tabs li a:focus { + background: #fff; +} +#overlay #overlay-tabs li.active a { + background: url("images/body.png") repeat-x scroll 50% -58px #edf5fa; + color: #fff; +} +#overlay-content { + padding: 1px; +} +#overlay-content #header { display: none; } - -body.overlay { - margin-top: -80px; +#overlay-content #wrapper { + background-position: 50% -80px; } + /** * Primary navigation */ diff --git a/themes/seven/seven.info b/themes/seven/seven.info index b719a435..9c7c9399 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/themes/stark/stark.info b/themes/stark/stark.info index 62661c61..b642df2c 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/themes/tests/test_theme/test_theme.info b/themes/tests/test_theme/test_theme.info index d1d76d73..0214fefd 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/themes/tests/update_test_basetheme/update_test_basetheme.info b/themes/tests/update_test_basetheme/update_test_basetheme.info index 86fcb269..df00aa32 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" diff --git a/themes/tests/update_test_subtheme/update_test_subtheme.info b/themes/tests/update_test_subtheme/update_test_subtheme.info index 00a72491..f4430a57 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-05-23 -version = "7.0-alpha5" +; Information added by drupal.org packaging script on 2010-07-09 +version = "7.0-alpha6" project = "drupal" -datestamp = "1274628610" +datestamp = "1278634806" -- GitLab