+ * Removed dependency on PL/pgSQL procedural language.
+- Menu system:
+ * Added support for external URLs.
+- Queue module:
+ * Removed from core.
+- HTTP handling:
+ * Added support for a tolerant Base URL.
+ * Output URIs relative to the root, without a base tag.
+
+Drupal 4.6.11, 2007-01-05
+-------------------------
+- Fixed security issue (XSS), see SA-2007-001
+- Fixed security issue (DoS), see SA-2007-002
+
+Drupal 4.6.10, 2006-10-18
+------------------------
+- Fixed security issue (XSS), see SA-2006-024
+- Fixed security issue (CSRF), see SA-2006-025
+- Fixed security issue (Form action attribute injection), see SA-2006-026
+
+Drupal 4.6.9, 2006-08-02
+------------------------
+- Fixed security issue (XSS), see SA-2006-011
+
+Drupal 4.6.8, 2006-06-01
+------------------------
+- Fixed critical upload issue, see SA-2006-007
+- Fixed taxonomy XSS issue, see SA-2006-008
+
+Drupal 4.6.7, 2006-05-24
+------------------------
+- Fixed critical SQL issue, see SA-2006-005
+
+Drupal 4.6.6, 2006-03-13
+------------------------
+- Fixed bugs, including 4 security vulnerabilities.
+
+Drupal 4.6.5, 2005-12-12
+------------------------
+- Fixed bugs: no critical bugs were identified.
+
+Drupal 4.6.4, 2005-11-30
+------------------------
+- Fixed bugs, including 3 security vulnerabilities.
+
+Drupal 4.6.3, 2005-08-15
+------------------------
+- Fixed bugs, including a critical "arbitrary PHP code execution" bug.
+
+Drupal 4.6.2, 2005-06-29
+------------------------
+- Fixed bugs, including two critical "arbitrary PHP code execution" bugs.
+
+Drupal 4.6.1, 2005-06-01
+------------------------
+- Fixed bugs, including a critical input validation bug.
+
+Drupal 4.6.0, 2005-04-15
+------------------------
+- PHP5 compliance
+- Search:
+ * Added UTF-8 support to make it work with all languages.
+ * Improved search indexing algorithm.
+ * Improved search output.
+ * Impose a throttle on indexing of large sites.
+ * Added search block.
+- Syndication:
+ * Made the ping module ping pingomatic.com which, in turn, will ping all the major ping services.
+ * Made Drupal generate RSS 2.0 feeds.
+ * Made RSS feeds extensible.
+ * Added categories to RSS feeds.
+ * Added enclosures to RSS feeds.
+- Flood control mechanism:
+ * Added a mechanism to throttle certain operations.
+- Usability:
+ * Refactored the block configuration pages.
+ * Refactored the statistics pages.
+ * Refactored the watchdog pages.
+ * Refactored the throttle module configuration.
+ * Refactored the access rules page.
+ * Refactored the content administration page.
+ * Introduced forum configuration pages.
+ * Added a 'add child page' link to book pages.
+- Contact module:
+ * Added a simple contact module that allows users to contact each other using e-mail.
+- Multi-site configuration:
+ * Made it possible to run multiple sites from a single code base.
+- Added an image API: enables better image handling.
+- Block system:
+ * Extended the block visibility settings.
+- Theme system:
+ * Added new theme functions.
+- Database backend:
+ * The PEAR database backend is no longer supported.
+- Performance:
+ * Improved performance of the forum topics block.
+ * Improved performance of the tracker module.
+ * Improved performance of the node pages.
+- Documentation:
+ * Improved and extended PHPDoc/Doxygen comments.
+
+Drupal 4.5.8, 2006-03-13
+------------------------
+- Fixed bugs, including 3 security vulnerabilities.
+
+Drupal 4.5.7, 2005-12-12
+------------------------
+- Fixed bugs: no critical bugs were identified.
+
+Drupal 4.5.6, 2005-11-30
+------------------------
+- Fixed bugs, including 3 security vulnerabilities.
+
+Drupal 4.5.5, 2005-08-15
+------------------------
+- Fixed bugs, including a critical "arbitrary PHP code execution" bug.
+
+Drupal 4.5.4, 2005-06-29
+------------------------
+- Fixed bugs, including two critical "arbitrary PHP code execution" bugs.
+
+Drupal 4.5.3, 2005-06-01
+------------------------
+- Fixed bugs, including a critical input validation bug.
+
+Drupal 4.5.2, 2005-01-15
+------------------------
+- Fixed bugs: a cross-site scripting (XSS) vulnerability has been fixed.
+
+Drupal 4.5.1, 2004-12-01
+------------------------
+- Fixed bugs: no critical bugs were identified.
+
+Drupal 4.5.0, 2004-10-18
+------------------------
+- Navigation:
+ * Made it possible to add, delete, rename and move menu items.
+ * Introduced tabs and subtabs for local tasks.
+ * Reorganized the navigation menus.
+- User management:
+ * Added support for multiple roles per user.
+ * Made it possible to add custom profile fields.
+ * Made it possible to browse user profiles by field.
+- Node system:
+ * Added support for node-level permissions.
+- Comment module:
+ * Made it possible to leave contact information without having to register.
+- Upload module:
+ * Added support for uploading documents (includes images).
+- Forum module:
+ * Added support for sticky forum topics.
+ * Made it possible to track forum topics.
+- Syndication:
+ * Added support for RSS ping-notifications of http://technorati.com/.
+ * Refactored the categorization of syndicated news items.
+ * Added an URL alias for 'rss.xml'.
+ * Improved date parsing.
+- Database backend:
+ * Added support for multiple database connections.
+ * The PostgreSQL backend does no longer require PEAR.
+- Theme system:
+ * Changed all GIFs to PNGs.
+ * Reorganised the handling of themes, template engines, templates and styles.
+ * Unified and extended the available theme settings.
+ * Added theme screenshots.
+- Blocks:
+ * Added 'recent comments' block.
+ * Added 'categories' block.
+- Blogger API:
+ * Added support for auto-discovery of blogger API via RSD.
+- Performance:
+ * Added support for sending gzip compressed pages.
+ * Improved performance of the forum module.
+- Accessibility:
+ * Improved the accessibility of the archive module's calendar.
+ * Improved form handling and error reporting.
+ * Added HTTP redirects to prevent submitting twice when refreshing right after a form submission.
+- Refactored 403 (forbidden) handling and added support for custom 403 pages.
+- Documentation:
+ * Added PHPDoc/Doxygen comments.
+- Filter system:
+ * Added support for using multiple input formats on the site
+ * Expanded the embedded PHP-code feature so it can be used everywhere
+ * Added support for role-dependent filtering, through input formats
+- UI translation:
+ * Managing translations is now completely done through the administration interface
+ * Added support for importing/exporting gettext .po files
+
+Drupal 4.4.3, 2005-06-01
+------------------------
+- Fixed bugs, including a critical input validation bug.
+
+Drupal 4.4.2, 2004-07-04
+------------------------
+- Fixed bugs: no critical bugs were identified.
+
+Drupal 4.4.1, 2004-05-01
+------------------------
+- Fixed bugs: no critical bugs were identified.
+
+Drupal 4.4.0, 2004-04-01
+------------------------
+- Added support for the MetaWeblog API and MovableType extensions.
+- Added a file API: enables better document management.
+- Improved the watchdog and search module to log search keys.
+- News aggregator:
+ * Added support for conditional GET.
+ * Added OPML feed subscription list.
+ * Added support for <image>, <pubDate>, <dc:date>, <dcterms:created>, <dcterms:issued> and <dcterms:modified>.
+- Comment module:
+ * Made it possible to disable the "comment viewing controls".
+- Performance:
+ * Improved module loading when serving cached pages.
+ * Made it possible to automatically disable modules when under heavy load.
+ * Made it possible to automatically disable blocks when under heavy load.
+ * Improved performance and memory footprint of the locale module.
+- Theme system:
+ * Made all theme functions start with 'theme_'.
+ * Made all theme functions return their output.
+ * Migrated away from using the BaseTheme class.
+ * Added many new theme functions and refactored existing theme functions.
+ * Added avatar support to 'Xtemplate'.
+ * Replaced theme 'UnConeD' by 'Chameleon'.
+ * Replaced theme 'Marvin' by 'Pushbutton'.
+- Usability:
+ * Added breadcrumb navigation to all pages.
+ * Made it possible to add context-sensitive help to all pages.
+ * Replaced drop-down menus by radio buttons where appropriate.
+ * Removed the 'magic_quotes_gpc = 0' requirement.
+ * Added a 'book navigation' block.
+- Accessibility:
+ * Made themes degrade gracefully in absence of CSS.
+ * Grouped form elements using '<fieldset>' and '<legend>' tags.
+ * Added '<label>' tags to form elements.
+- Refactored 404 (file not found) handling and added support for custom 404 pages.
+- Improved the filter system to prevent conflicts between filters:
+ * Made it possible to change the order in which filters are applied.
+- Documentation:
+ * Added PHPDoc/Doxygen comments.
+
+Drupal 4.3.2, 2004-01-01
+------------------------
+- Fixed bugs: no critical bugs were identified.
+
+Drupal 4.3.1, 2003-12-01
+------------------------
+- Fixed bugs: no critical bugs were identified.
+
+Drupal 4.3.0, 2003-11-01
+------------------------
+- Added support for configurable URLs.
+- Added support for sortable table columns.
+- Database backend:
+ * Added support for selective database table prefixing.
+- Performance:
+ * Optimized many SQL queries for speed by converting left joins to inner joins.
+- Comment module:
+ * Rewrote the comment housekeeping code to be much more efficient and scalable.
+ * Changed the comment module to use the standard pager.
+- User module:
+ * Added support for multiple sessions per user.
+ * Added support for anonymous user sessions.
+- Forum module:
+ * Improved the forum views and the themability thereof.
+- Book module:
+ * Improved integration of non-book nodes in the book outline.
+- Usability:
+ * Added support for "mass node operations" to ease repetitive tasks.
+ * Added support for breadcrumb navigation to several modules' user pages.
+ * Integrated the administration pages with the normal user pages.
+
+Drupal 4.2.0, 2003-08-01
+------------------------
+- Added support for clean URLs.
+- Added textarea hook and support for onload attributes: enables integration of WYSIWYG editors.
+- Rewrote the RSS/RDF parser:
+ * It will now use PHP's built-in XML parser to parse news feeds.
+- Rewrote the administration pages:
+ * Improved the navigational elements and added breadcrumb navigation.
+ * Improved the look and feel.
+ * Added context-sensitive help.
+- Database backend:
+ * Fixed numerous SQL queries to make Drupal ANSI compliant.
+ * Added MSSQL database scheme.
+- Search module:
+ * Changed the search module to use implicit AND'ing instead of implicit OR'ing.
+- Node system:
+ * Replaced the "post content" permission by more fine-grained permissions.
+ * Improved content submission:
+ + Improved teasers: teasers are now optional, teaser length can be configured, teaser and body are edited in a single textarea, users will no longer be bothered with teasers when the post is too short for one.
+ + Added the ability to preview both the short and the full version of your posts.
+ * Extended the node API which allows for better integration.
+ * Added default node settings to control the behavior for promotion, moderation and other options.
+- Themes:
+ * Replaced theme "Goofy" by "Xtemplate", a template driven theme.
+- Removed the 'register_globals = on' requirement.
+- Added better installation instructions.
+
+Drupal 4.1.0, 2003-02-01
+------------------------
+- Collaboratively revised and expanded the Drupal documentation.
+ * Performance improvements: blocks are no longer rendered when not displayed.
+- Rewrote forum.module:
+ * Added a lot of features one can find in stand-alone forum software including but not limited to support for topic paging, added support for icons, rewrote the statistics module, etc.
+- Rewrote statistics.module:
+ * Collects access counts for each node, referrer logs, number of users/guests.
+ * Export blocks displaying top viewed nodes over last 24 hour period, top viewed nodes over all time, last nodes viewed, how many users/guest online.
+- Added throttle.module:
+ * Auto-throttle congestion control mechanism: Drupal can adapt itself based on the server load.
+- Added profile.module:
+ * Enables to extend the user and registration page.
+- Added pager support to the main page.
+- Replaced weblogs.module by ping.module:
+ * Added support for normal and RSS notifications of http://blo.gs/.
+ * Added support for RSS ping-notifications of http://weblogs.com/.
+- Removed the rating module
+- Themes:
+ * Removed a significant portion of hard-coded mark-up.
+
+Drupal 4.0.0, 2002-06-15
+------------------------
+- Added tracker.module:
+ * Replaces the previous "your [site]" links (recent comments and nodes).
+- Added weblogs.module:
+ * This will ping weblogs.com when new content is promoted.
+- Added taxonomy module which replaces the meta module.
+ * Supports relations, hierarchies and synonyms.
+- Added a caching system:
+ * Speeds up pages for anonymous users and reduces system load.
+- Added support for external SMTP libraries.
+- Added an archive extension to the calendar.
+- Added support for the Blogger API.
+- Themes:
+ * Cleaned up the theme system.
+ * Moved themes that are not maintained to contributions CVS repository.
+- Database backend:
+ * Changed to PEAR database abstraction layer.
+ * Using ANSI SQL queries to be more portable.
+- Rewrote the user system:
+ * Added support for Drupal authentication through XML-RPC and through a Jabber server.
+ * Added support for modules to add more user data.
+ * Users may delete their own account.
+ * Added who's new and who's online blocks.
+- Changed block system:
+ * Various hard coded blocks are now dynamic.
+ * Blocks can now be enabled and/or be set by the user.
+ * Blocks can be set to only show up on some pages.
+ * Merged box module with block module.
+- Node system:
+ * Blogs can be updated.
+ * Teasers (abstracts) on all node types.
+ * Improved error checking.
+ * Content versioning support.
+ * Usability improvements.
+- Improved book module to support text, HTML and PHP pages.
+- Improved comment module to mark new comments.
+- Added a general outliner which will let any node type be linked to a book.
+- Added an update script that lets you upgrade from previous releases or on a day to day basis when using the development tree.
+- Search module:
+ * Improved the search system by making it context sensitive.
+ * Added indexing.
+- Various updates:
+ * Changed output to valid XHTML.
+ * Improved multiple sites using the same Drupal database support.
+ * Added support for session IDs in URLs instead of cookies.
+ * Made the type of content on the front page configurable.
+ * Made each cloud site have its own settings.
+ * Modules and themes can now be enabled/disabled using the administration pages.
+- Collaboratively revised and expanded the Drupal documentation.
+
+Drupal 3.0.1, 2001-10-15
+------------------------
+- Various updates:
+ * Added missing translations
+ * Updated the themes: tidied up some HTML code and added new Drupal logos.
+
+Drupal 3.0.0, 2001-09-15
+------------------------
+- Major overhaul of the entire underlying design:
+ * Everything is based on nodes: nodes are a conceptual "black box" to couple and manage different types of content and that promotes reusing existing code, thus reducing the complexity and size of Drupal as well as improving long-term stability.
+- Rewrote submission/moderation queue and renamed it to queue.module.
+- Removed FAQ and documentation module and merged them into a "book module".
+- Removed ban module and integrated it into account.module as "access control":
+ * Access control is based on much more powerful regular expressions (regex) now rather than on MySQL pattern matching.
+- Rewrote watchdog and submission throttle:
+ * Improved watchdog messages and added watchdog filter.
+- Rewrote headline code and renamed it to import.module and export.module:
+ * Added various improvements, including a better parser, bundles and better control over individual feeds.
+- Rewrote section code and renamed it to meta.module:
+ * Supports unlimited amount of nested topics. Topics can be nested to create a multi-level hierarchy.
+- Rewrote configuration file resolving:
+ * Drupal tries to locate a configuration file that matches your domain name or uses conf.php if the former failed. Note also that the configuration files got renamed from .conf to .php for security's sake on mal-configured Drupal sites.
+- Added caching support which makes Drupal extremely scalable.
+- Added access.module:
+ * Allows you to set up 'roles' (groups) and to bind a set of permissions to each group.
+- Added blog.module.
+- Added poll.module.
+- Added system.module:
+ * Moved most of the configuration options from hostname.conf to the new administration section.
+ * Added support for custom "filters".
+- Added statistics.module
+- Added moderate.module:
+ * Allows to assign users editorial/moderator rights to certain nodes or topics.
+- Added page.module:
+ * Allows creation of static (and dynamic) pages through the administration interface.
+- Added help.module:
+ * Groups all available module documentation on a single page.
+- Added forum.module:
+ * Added an integrated forum.
+- Added cvs.module and cvs-to-sql.pl:
+ * Allows to display and mail CVS log messages as daily digests.
+- Added book.module:
+ * Allows collaborative handbook writing: primary used for Drupal documentation.
+- Removed cron.module and integrated it into conf.module.
+- Removed module.module as it was no longer needed.
+- Various updates:
+ * Added "auto-post new submissions" feature versus "moderate new submissions".
+ * Introduced links/Drupal tags: [[link]]
+ * Added preview functionality when submitting new content (such as a story) from the administration pages.
+ * Made the administration section only show those links a user has access to.
+ * Made all modules use specific form_* functions to guarantee a rock-solid forms and more consistent layout.
+ * Improved scheduler:
+ + Content can be scheduled to be 'posted', 'queued' and 'hidden'.
+ * Improved account module:
+ + Added "access control" to allow/deny certain usernames/e-mail addresses/hostnames.
+ * Improved locale module:
+ + Added new overview to easy the translation process.
+ * Improved comment module:
+ + Made it possible to permanently delete comments.
+ * Improved rating module
+ * Improved story module:
+ + Added preview functionality for administrators.
+ + Made it possible to permanently delete stories.
+ * Improved themes:
+ + W3C validation on a best effort basis.
+ + Removed $theme->control() from themes.
+ + Added theme "goofy".
+- Collaboratively revised and expanded the Drupal documentation.
+
+Drupal 2.0.0, 2001-03-15
+------------------------
+- Rewrote the comment/discussion code:
+ * Comment navigation should be less confusing now.
+ * Additional/alternative display and order methods have been added.
+ * Modules can be extended with a "comment system": modules can embed the existing comment system without having to write their own, duplicate comment system.
+- Added sections and section manager:
+ * Story sections can be maintained from the administration pages.
+ * Story sections make the open submission more adaptive in that you can set individual post, dump and expiration thresholds for each section according to the story type and urgency level: stories in certain sections do not "expire" and might stay interesting and active as time passes by, whereas news-related stories are only considered "hot" over a short period of time.
+- Multiple vhosts + multiple directories:
+ * You can set up multiple Drupal sites on top of the same physical source tree either by using vhosts or sub-directories.
+- Added "user ratings" similar to SlashCode's Karma or Scoop's Mojo:
+ * All rating logic is packed into a module to ease experimenting with different rating heuristics/algorithms.
+- Added "search infrastructure":
+ * Improved search page and integrated search functionality in the administration pages.
+ * Because many people would love to see their website showing a lot less of English, and far more of their own language, Drupal provides a framework to set up a multi-lingual website or to overwrite the default English text in English.
+- Added fine-grained user permission (or group) system:
+ * Users can be granted access to specific administration sections. Example: a FAQ maintainer can be given access to maintain the FAQ and translators can be given access to the translation pages.
+- Added FAQ module
+- Changed the "open submission queue" into a (optional) module.
+- Various updates:
+ * Improved account module:
+ + User accounts can be deleted.
+ + Added fine-grained permission support.
+ * Improved block module
+ * Improved diary module:
+ + Diary entries can be deleted
+ * Improved headline module:
+ + Improved parser to support more "generic" RDF/RSS/XML backend.
+ t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))));
+ '#markup' => t('WARNING: You are not using an encrypted connection, so your password will be sent in plain text. <a href="@https-link">Learn more</a>.', array('@https-link' => 'http://drupal.org/https-information')),
+ '#suffix' => '</div>',
+ );
+ }
+
+ // Decide on a default backend.
+ if (isset($form_state['values']['connection_settings']['authorize_filetransfer_default'])) {
+ throw new Exception(t('Error, this type of connection protocol (%backend) does not exist.', array('%backend' => $backend)));
+ }
+ $filetransfer->connect();
+ }
+ catch (Exception $e) {
+ // The format of this error message is similar to that used on the
+ // database connection form in the installer.
+ form_set_error('connection_settings', t('Failed to connect to the server. The server reports the following message: !message For more help installing or updating code on your server, see the <a href="@handbook_url">handbook</a>.', array(
+ * - Allow an item to be repeatedly claimed until it is actually deleted (no
+ * notion of lease time or 'expire' date), to allow multipass operations.
+ */
+
+/**
+ * Defines a batch queue.
+ *
+ * Stale items from failed batches are cleaned from the {queue} table on cron
+ * using the 'created' date.
+ */
+class BatchQueue extends SystemQueue {
+
+ /**
+ * Overrides SystemQueue::claimItem().
+ *
+ * Unlike SystemQueue::claimItem(), this method provides a default lease
+ * time of 0 (no expiration) instead of 30. This allows the item to be
+ * claimed repeatedly until it is deleted.
+ */
+ public function claimItem($lease_time = 0) {
+ $item = db_query_range('SELECT data, item_id FROM {queue} q WHERE name = :name ORDER BY item_id ASC', 0, 1, array(':name' => $this->name))->fetchObject();
+ if ($item) {
+ $item->data = unserialize($item->data);
+ return $item;
+ }
+ return FALSE;
+ }
+
+ /**
+ * Retrieves all remaining items in the queue.
+ *
+ * This is specific to Batch API and is not part of the DrupalQueueInterface.
+ */
+ public function getAllItems() {
+ $result = array();
+ $items = db_query('SELECT data FROM {queue} q WHERE name = :name ORDER BY item_id ASC', array(':name' => $this->name))->fetchAll();
+ foreach ($items as $item) {
+ $result[] = unserialize($item->data);
+ }
+ return $result;
+ }
+}
+
+/**
+ * Defines a batch queue for non-progressive batches.
+ */
+class BatchMemoryQueue extends MemoryQueue {
+
+ /**
+ * Overrides MemoryQueue::claimItem().
+ *
+ * Unlike MemoryQueue::claimItem(), this method provides a default lease
+ * time of 0 (no expiration) instead of 30. This allows the item to be
+ * claimed repeatedly until it is deleted.
+ */
+ public function claimItem($lease_time = 0) {
+ if (!empty($this->queue)) {
+ reset($this->queue);
+ return current($this->queue);
+ }
+ return FALSE;
+ }
+
+ /**
+ * Retrieves all remaining items in the queue.
+ *
+ * This is specific to Batch API and is not part of the DrupalQueueInterface.
+ if ($fast_paths && preg_match($fast_paths, $_GET['q'])) {
+ drupal_add_http_header('Status', '404 Not Found');
+ $fast_404_html = variable_get('404_fast_html', '<!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"><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>The requested URL "@path" was not found on this server.</p></body></html>');
+ // Replace @path in the variable with the page path.
+ public function fieldSetDefault($table, $field, $default) {
+ if (!$this->fieldExists($table, $field)) {
+ throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
+ $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` SET DEFAULT ' . $default);
+ }
+
+ public function fieldSetNoDefault($table, $field) {
+ if (!$this->fieldExists($table, $field)) {
+ throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
+ }
+
+ $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` DROP DEFAULT');
+ }
+
+ public function indexExists($table, $name) {
+ // Returns one row for each column in the index. Result is string or FALSE.
+ // Details at http://dev.mysql.com/doc/refman/5.0/en/show-index.html
+ $row = $this->connection->query('SHOW INDEX FROM {' . $table . "} WHERE key_name = '$name'")->fetchAssoc();
+ return isset($row['Key_name']);
+ }
+
+ public function addPrimaryKey($table, $fields) {
+ if (!$this->tableExists($table)) {
+ throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add primary key to table @table: table doesn't exist.", array('@table' => $table)));
+ }
+ if ($this->indexExists($table, 'PRIMARY')) {
+ throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table @table: primary key already exists.", array('@table' => $table)));
+ public function addIndex($table, $name, $fields) {
+ if (!$this->tableExists($table)) {
+ throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
+ }
+ if ($this->indexExists($table, $name)) {
+ throw new DatabaseSchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", array('@table' => $table, '@name' => $name)));
+ $this->connection->query('ALTER TABLE {' . $table . '} DROP INDEX `' . $name . '`');
+ return TRUE;
+ }
+
+ public function changeField($table, $field, $field_new, $spec, $keys_new = array()) {
+ if (!$this->fieldExists($table, $field)) {
+ throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", array('@table' => $table, '@name' => $field)));
+ }
+ if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
+ throw new DatabaseSchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", array('@table' => $table, '@name' => $field, '@name_new' => $field_new)));
+ // Ignore possible errors when the user doesn't have the necessary
+ // privileges to ALTER the database.
+ }
+
+ // Close the database connection so that the configuration parameter
+ // is applied to the current connection.
+ db_close();
+
+ // Recheck, if it fails, finally just rely on the end user to do the
+ // right thing.
+ if (!$this->checkBinaryOutputSuccess()) {
+ $replacements = array(
+ '%setting' => 'bytea_output',
+ '%current_value' => 'hex',
+ '%needed_value' => 'escape',
+ '!query' => "<code>" . $query . "</code>",
+ );
+ $this->fail(st("The %setting setting is currently set to '%current_value', but needs to be '%needed_value'. Change this by running the following query: !query", $replacements));
+ }
+ }
+ }
+ }
+
+ /**
+ * Verify that a binary data roundtrip returns the original string.
+ */
+ protected function checkBinaryOutputSuccess() {
+ $bytea_output = db_query("SELECT 'encoding'::bytea AS output")->fetchField();
+ return ($bytea_output == 'encoding');
+ }
+
+ /**
+ * Make PostgreSQL Drupal friendly.
+ */
+ function initializeDatabase() {
+ // We create some functions using global names instead of prefixing them
+ // like we do with table names. This is so that we don't double up if more
+ // than one instance of Drupal is running on a single database. We therefore
+ // avoid trying to create them again in that case.
+
+ try {
+ // Create functions.
+ db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric) RETURNS numeric AS
+ \'SELECT CASE WHEN (($1 > $2) OR ($2 IS NULL)) THEN $1 ELSE $2 END;\'
+ LANGUAGE \'sql\''
+ );
+ db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric, numeric) RETURNS numeric AS
+ \'SELECT greatest($1, greatest($2, $3));\'
+ LANGUAGE \'sql\''
+ );
+ // Don't use {} around pg_proc table.
+ if (!db_query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'rand'")->fetchField()) {
+ db_query('CREATE OR REPLACE FUNCTION "rand"() RETURNS float AS
+ \'SELECT random();\'
+ LANGUAGE \'sql\''
+ );
+ }
+
+ db_query('CREATE OR REPLACE FUNCTION "substring_index"(text, text, integer) RETURNS text AS
+ // Don't use {} around information_schema.columns table.
+ $result = $this->connection->query("SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = :schema AND table_name = :table AND (data_type = 'bytea' OR (numeric_precision IS NOT NULL AND column_default LIKE :default))", array(
+ // Split the key into schema and table for querying.
+ $schema = $prefixInfo['schema'];
+ $table_name = $prefixInfo['table'];
+
+ $field_information = (object) array(
+ 'checks' => array(),
+ );
+ $checks = $this->connection->query("SELECT conname FROM pg_class cl INNER JOIN pg_constraint co ON co.conrelid = cl.oid INNER JOIN pg_attribute attr ON attr.attrelid = cl.oid AND attr.attnum = ANY (co.conkey) INNER JOIN pg_namespace ns ON cl.relnamespace = ns.oid WHERE co.contype = 'c' AND ns.nspname = :schema AND cl.relname = :table AND attr.attname = :column", array(
+ ':schema' => $schema,
+ ':table' => $table_name,
+ ':column' => $field,
+ ));
+ $field_information = $checks->fetchCol();
+
+ return $field_information;
+ }
+
+ /**
+ * Generate SQL to create a new table from a Drupal schema definition.
+ *
+ * @param $name
+ * The name of the table to create.
+ * @param $table
+ * A Schema API table definition array.
+ * @return
+ * An array of SQL statements to create the table.
+ */
+ protected function createTableSql($name, $table) {
+ $sql_fields = array();
+ foreach ($table['fields'] as $field_name => $field) {
+ public function fieldSetDefault($table, $field, $default) {
+ if (!$this->fieldExists($table, $field)) {
+ throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
+ $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" SET DEFAULT ' . $default);
+ }
+
+ public function fieldSetNoDefault($table, $field) {
+ if (!$this->fieldExists($table, $field)) {
+ throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
+ }
+
+ $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" DROP DEFAULT');
+ public function addIndex($table, $name, $fields) {
+ if (!$this->tableExists($table)) {
+ throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
+ }
+ if ($this->indexExists($table, $name)) {
+ throw new DatabaseSchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", array('@table' => $table, '@name' => $name)));
+ $this->connection->query('DROP INDEX ' . $this->prefixNonTable($table, $name, 'idx'));
+ return TRUE;
+ }
+
+ public function changeField($table, $field, $field_new, $spec, $new_keys = array()) {
+ if (!$this->fieldExists($table, $field)) {
+ throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", array('@table' => $table, '@name' => $field)));
+ }
+ if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
+ throw new DatabaseSchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", array('@table' => $table, '@name' => $field, '@name_new' => $field_new)));
+ }
+
+ $spec = $this->processField($spec);
+
+ // We need to typecast the new column to best be able to transfer the data
+ // Schema_pgsql::getFieldTypeMap() will return possibilities that are not
+ // 'cast-able' such as 'serial' - so they need to be casted int instead.
+ if (in_array($spec['pgsql_type'], array('serial', 'bigserial', 'numeric'))) {
+ $typecast = 'int';
+ }
+ else {
+ $typecast = $spec['pgsql_type'];
+ }
+
+ if (in_array($spec['pgsql_type'], array('varchar', 'character', 'text')) && isset($spec['length'])) {
+ foreach ($new_keys['unique keys'] as $name => $fields) {
+ $this->addUniqueKey($table, $name, $fields);
+ }
+ }
+ if (isset($new_keys['indexes'])) {
+ foreach ($new_keys['indexes'] as $name => $fields) {
+ $this->addIndex($table, $name, $fields);
+ }
+ }
+ }
+
+ /**
+ * Retrieve a table or column comment.
+ */
+ public function getComment($table, $column = NULL) {
+ $info = $this->getPrefixInfo($table);
+ // Don't use {} around pg_class, pg_attribute tables.
+ if (isset($column)) {
+ return $this->connection->query('SELECT col_description(oid, attnum) FROM pg_class, pg_attribute WHERE attrelid = oid AND relname = ? AND attname = ?', array($info['table'], $column))->fetchField();
+ }
+ else {
+ return $this->connection->query('SELECT obj_description(oid, ?) FROM pg_class WHERE relname = ?', array('pg_class', $info['table']))->fetchField();
+ * We prune empty databases on destruct, but only if tables have been
+ * dropped. This is especially needed when running the test suite, which
+ * creates and destroy databases several times in a row.
+ */
+ public function __destruct() {
+ if ($this->tableDropped && !empty($this->attachedDatabases)) {
+ foreach ($this->attachedDatabases as $prefix) {
+ // Check if the database is now empty, ignore the internal SQLite tables.
+ try {
+ $count = $this->query('SELECT COUNT(*) FROM ' . $prefix . '.sqlite_master WHERE type = :type AND name NOT LIKE :pattern', array(':type' => 'table', ':pattern' => 'sqlite_%'))->fetchField();
+
+ // We can prune the database file if it doesn't have any tables.
+ $form['database']['#description'] = st('The absolute path to the file where @drupal data will be stored. This must be writable by the web server and should exist outside of the web root.', array('@drupal' => drupal_install_profile_distribution_name()));
+ return (bool) $this->connection->query('SELECT 1 FROM ' . $info['schema'] . '.sqlite_master WHERE type = :type AND name = :name', array(':type' => 'table', ':name' => $info['table']))->fetchField();
+ }
+
+ public function fieldExists($table, $column) {
+ $schema = $this->introspectSchema($table);
+ return !empty($schema['fields'][$column]);
+ }
+
+ /**
+ * Generate SQL to create a new table from a Drupal schema definition.
+ *
+ * @param $name
+ * The name of the table to create.
+ * @param $table
+ * A Schema API table definition array.
+ * @return
+ * An array of SQL statements to create the table.
+ public function changeField($table, $field, $field_new, $spec, $keys_new = array()) {
+ if (!$this->fieldExists($table, $field)) {
+ throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field @table.@name: field doesn't exist.", array('@table' => $table, '@name' => $field)));
+ }
+ if (($field != $field_new) && $this->fieldExists($table, $field_new)) {
+ throw new DatabaseSchemaObjectExistsException(t("Cannot rename field @table.@name to @name_new: target field already exists.", array('@table' => $table, '@name' => $field, '@name_new' => $field_new)));
+ }
+
+ $old_schema = $this->introspectSchema($table);
+ $new_schema = $old_schema;
+
+ // Map the old field to the new field.
+ if ($field != $field_new) {
+ $mapping[$field_new] = $field;
+ }
+ else {
+ $mapping = array();
+ }
+
+ // Remove the previous definition and swap in the new one.
+ unset($new_schema['fields'][$field]);
+ $new_schema['fields'][$field_new] = $spec;
+
+ // Map the former indexes to the new column name.
+ * Utility method: rename columns in an index definition according to a new mapping.
+ *
+ * @param $key_definition
+ * The key definition.
+ * @param $mapping
+ * The new mapping.
+ */
+ protected function mapKeyDefinition(array $key_definition, array $mapping) {
+ foreach ($key_definition as &$field) {
+ // The key definition can be an array($field, $length).
+ if (is_array($field)) {
+ $field = &$field[0];
+ }
+ if (isset($mapping[$field])) {
+ $field = $mapping[$field];
+ }
+ }
+ return $key_definition;
+ }
+
+ public function addIndex($table, $name, $fields) {
+ if (!$this->tableExists($table)) {
+ throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
+ }
+ if ($this->indexExists($table, $name)) {
+ throw new DatabaseSchemaObjectExistsException(t("Cannot add index @name to table @table: index already exists.", array('@table' => $table, '@name' => $name)));
+ public function fieldSetDefault($table, $field, $default) {
+ if (!$this->fieldExists($table, $field)) {
+ throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
+ public function fieldSetNoDefault($table, $field) {
+ if (!$this->fieldExists($table, $field)) {
+ throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field @table.@field: field doesn't exist.", array('@table' => $table, '@field' => $field)));
+ watchdog('security', "Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: <code>!htaccess</code>", $variables, WATCHDOG_ERROR);
+ }
+}
+
+/**
+ * Returns the standard .htaccess lines that Drupal writes to file directories.
+ *
+ * @param $private
+ * (Optional) Set to FALSE to return the .htaccess lines for an open and
+ * public directory. The default is TRUE, which returns the .htaccess lines
+ * for a private and protected directory.
+ *
+ * @return
+ * A string representing the desired contents of the .htaccess file.
+ *
+ * @see file_create_htaccess()
+ */
+function file_htaccess_lines($private = TRUE) {
+ $lines = <<<EOF
+# Turn off all options we don't need.
+Options None
+Options +FollowSymLinks
+
+# Set the catch-all handler to prevent scripts from being executed.
+ if (($realpath = drupal_realpath($source->uri)) !== FALSE) {
+ watchdog('file', 'File %file (%realpath) could not be copied, because the destination %destination is invalid. This is often caused by improper use of file_copy() or a missing stream wrapper.', array('%file' => $source->uri, '%realpath' => $realpath, '%destination' => $destination));
+ }
+ else {
+ watchdog('file', 'File %file could not be copied, because the destination %destination is invalid. This is often caused by improper use of file_copy() or a missing stream wrapper.', array('%file' => $source->uri, '%destination' => $destination));
+ }
+ drupal_set_message(t('The specified file %file could not be copied, because the destination is invalid. More information is available in the system log.', array('%file' => $source->uri)), 'error');
+ return FALSE;
+ }
+
+ if ($uri = file_unmanaged_copy($source->uri, $destination, $replace)) {
+ $file = clone $source;
+ $file->fid = NULL;
+ $file->uri = $uri;
+ $file->filename = drupal_basename($uri);
+ // If we are replacing an existing file re-use its database record.
+ // @todo Replace drupal_set_message() calls with exceptions instead.
+ drupal_set_message(t('The specified file %file could not be copied, because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $original_source)), 'error');
+ if (($realpath = drupal_realpath($original_source)) !== FALSE) {
+ watchdog('file', 'File %file (%realpath) could not be copied because it does not exist.', array('%file' => $original_source, '%realpath' => $realpath));
+ }
+ else {
+ watchdog('file', 'File %file could not be copied because it does not exist.', array('%file' => $original_source));
+ watchdog('file', 'File %file could not be copied, because the destination directory %destination is not configured correctly.', array('%file' => $original_source, '%destination' => $dirname));
+ drupal_set_message(t('The specified file %file could not be copied, because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $original_source)), 'error');
+ return FALSE;
+ }
+ }
+
+ // Determine whether we can perform this operation based on overwrite rules.
+ drupal_set_message(t('The file %file could not be copied because a file by that name already exists in the destination directory.', array('%file' => $original_source)), 'error');
+ watchdog('file', 'File %file could not be copied because a file by that name already exists in the destination directory (%directory)', array('%file' => $original_source, '%directory' => $destination));
+ return FALSE;
+ }
+
+ // Assert that the source and destination filenames are not the same.
+ watchdog('file', 'The specified file %file could not be copied to %destination.', array('%file' => $source, '%destination' => $destination), WATCHDOG_ERROR);
+ return FALSE;
+ }
+ }
+
+ // Set the permissions on the new file.
+ drupal_chmod($destination);
+
+ return $destination;
+}
+
+/**
+ * Constructs a URI to Drupal's default files location given a relative path.
+ */
+function file_build_uri($path) {
+ $uri = file_default_scheme() . '://' . $path;
+ return file_stream_wrapper_uri_normalize($uri);
+}
+
+/**
+ * Determines the destination path for a file.
+ *
+ * @param $destination
+ * A string specifying the desired final URI or filepath.
+ * @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 destination filepath, or FALSE if the file already exists
+ if (($realpath = drupal_realpath($source->uri)) !== FALSE) {
+ watchdog('file', 'File %file (%realpath) could not be moved, because the destination %destination is invalid. This may be caused by improper use of file_move() or a missing stream wrapper.', array('%file' => $source->uri, '%realpath' => $realpath, '%destination' => $destination));
+ }
+ else {
+ watchdog('file', 'File %file could not be moved, because the destination %destination is invalid. This may be caused by improper use of file_move() or a missing stream wrapper.', array('%file' => $source->uri, '%destination' => $destination));
+ }
+ drupal_set_message(t('The specified file %file could not be moved, because the destination is invalid. More information is available in the system log.', array('%file' => $source->uri)), 'error');
+ return FALSE;
+ }
+
+ if ($uri = file_unmanaged_move($source->uri, $destination, $replace)) {
+ $delete_source = FALSE;
+
+ $file = clone $source;
+ $file->uri = $uri;
+ // If we are replacing an existing file re-use its database record.
+ if (($realpath = drupal_realpath($file->uri)) !== FALSE) {
+ watchdog('file', 'File %file (%realpath) could not be deleted because it is not a valid URI. This may be caused by improper use of file_delete() or a missing stream wrapper.', array('%file' => $file->uri, '%realpath' => $realpath));
+ }
+ else {
+ watchdog('file', 'File %file could not be deleted because it is not a valid URI. This may be caused by improper use of file_delete() or a missing stream wrapper.', array('%file' => $file->uri));
+ }
+ drupal_set_message(t('The specified file %file could not be deleted, because it is not a valid URI. More information is available in the system log.', array('%file' => $file->uri)), 'error');
+ return FALSE;
+ }
+
+ // If any module still has a usage entry in the file_usage table, the file
+ // will not be deleted, but file_delete() will return a populated array
+ // that tests as TRUE.
+ if (!$force && ($references = file_usage_list($file))) {
+ return $references;
+ }
+
+ // Let other modules clean up any references to the deleted file.
+ drupal_set_message(t('The file %file could not be saved, because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $_FILES['files']['name'][$form_field_name], '%maxsize' => format_size(file_upload_max_size()))), 'error');
+ return FALSE;
+
+ case UPLOAD_ERR_PARTIAL:
+ case UPLOAD_ERR_NO_FILE:
+ drupal_set_message(t('The file %file could not be saved, because the upload did not complete.', array('%file' => $_FILES['files']['name'][$form_field_name])), 'error');
+ return FALSE;
+
+ case UPLOAD_ERR_OK:
+ // Final check that this is a valid upload, if it isn't, use the
+ // default error handler.
+ if (is_uploaded_file($_FILES['files']['tmp_name'][$form_field_name])) {
+ break;
+ }
+
+ // Unknown error
+ default:
+ drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $_FILES['files']['name'][$form_field_name])), 'error');
+ if (!$destination_scheme || !file_stream_wrapper_valid_scheme($destination_scheme)) {
+ drupal_set_message(t('The file could not be uploaded, because the destination %destination is invalid.', array('%destination' => $destination)), 'error');
+ return FALSE;
+ }
+
+ $file->source = $form_field_name;
+ // A URI may already have a trailing slash or look like "public://".
+ // If file_destination() returns FALSE then $replace == FILE_EXISTS_ERROR and
+ // there's an existing file so we need to bail.
+ if ($file->destination === FALSE) {
+ drupal_set_message(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', array('%source' => $form_field_name, '%directory' => $destination)), 'error');
+ return FALSE;
+ }
+
+ // Add in our check of the the file name length.
+ if ($file_limit && $file->filesize > $file_limit) {
+ $errors[] = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($file->filesize), '%maxsize' => format_size($file_limit)));
+ }
+
+ // Save a query by only calling file_space_used() when a limit is provided.
+ if ($user_limit && (file_space_used($user->uid) + $file->filesize) > $user_limit) {
+ $errors[] = t('The file is %filesize which would exceed your disk quota of %quota.', array('%filesize' => format_size($file->filesize), '%quota' => format_size($user_limit)));
+ }
+
+ return $errors;
+}
+
+/**
+ * Checks that the file is recognized by image_get_info() as an image.
+ *
+ * @param $file
+ * A Drupal file object.
+ *
+ * @return
+ * An array. If the file is not an image, it will contain an error message.
+ if ($info['width'] > $width || $info['height'] > $height) {
+ // Try to resize the image to fit the dimensions.
+ if ($image = image_load($file->uri)) {
+ image_scale($image, $width, $height);
+ image_save($image);
+ $file->filesize = $image->info['file_size'];
+ drupal_set_message(t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', array('%dimensions' => $maximum_dimensions)));
+ }
+ else {
+ $errors[] = t('The image is too large; the maximum dimensions are %dimensions pixels.', array('%dimensions' => $maximum_dimensions));
+ }
+ }
+ }
+
+ if ($minimum_dimensions) {
+ // Check that it is larger than the given dimensions.
+ watchdog('file', 'The data could not be saved because the destination %destination is invalid. This may be caused by improper use of file_save_data() or a missing stream wrapper.', array('%destination' => $destination));
+ drupal_set_message(t('The data could not be saved, because the destination is invalid. More information is available in the system log.'), 'error');
+ return FALSE;
+ }
+
+ if ($uri = file_unmanaged_save_data($data, $destination, $replace)) {
+ * Returns a form to collect connection settings credentials.
+ *
+ * Implementing classes can either extend this form with fields collecting the
+ * specific information they need, or override it entirely.
+ */
+ public function getSettingsForm() {
+ $form['username'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Username'),
+ );
+ $form['password'] = array(
+ '#type' => 'password',
+ '#title' => t('Password'),
+ '#description' => t('Your password is not saved in the database and is only used to establish a connection.'),
+ );
+ $form['advanced'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Advanced settings'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+ $form['advanced']['hostname'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Host'),
+ '#default_value' => 'localhost',
+ '#description' => t('The connection will be created between your web server and the machine hosting the web server files. In the vast majority of cases, this will be the same machine, and "localhost" is correct.'),
+ );
+ $form['advanced']['port'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Port'),
+ '#default_value' => NULL,
+ );
+ return $form;
+ }
+}
+
+/**
+ * FileTransferException class.
+ */
+class FileTransferException extends Exception {
+ public $arguments;
+
+ function __construct($message, $code = 0, $arguments = array()) {
+ parent::__construct($message, $code);
+ $this->arguments = $arguments;
+ }
+}
+
+
+/**
+ * A FileTransfer Class implementing this interface can be used to chmod files.
+ */
+interface FileTransferChmodInterface {
+
+ /**
+ * Changes the permissions of the file / directory specified in $path
+ *
+ * @param string $path
+ * Path to change permissions of.
+ * @param long $mode
+ * The new file permission mode to be passed to chmod().
+ * @param boolean $recursive
+ * Pass TRUE to recursively chmod the entire directory specified in $path.
+ */
+ function chmodJailed($path, $mode, $recursive);
+}
+
+/**
+ * Provides an interface for iterating recursively over filesystem directories.
+ *
+ * Manually skips '.' and '..' directories, since no existing method is
+ * available in PHP 5.2.
+ *
+ * @todo Depreciate in favor of RecursiveDirectoryIterator::SKIP_DOTS once PHP
+ protected function removeDirectoryJailed($directory) {
+ if (!is_dir($directory)) {
+ // Programmer error assertion, not something we expect users to see.
+ throw new FileTransferException('removeDirectoryJailed() called with a path (%directory) that is not a directory.', NULL, array('%directory' => $directory));
+ }
+ foreach (new RecursiveIteratorIterator(new SkipDotsRecursiveDirectoryIterator($directory), RecursiveIteratorIterator::CHILD_FIRST) as $filename => $file) {
+ if (isset($validated_forms[$form_id]) && empty($form_state['must_validate'])) {
+ return;
+ }
+
+ // If the session token was set by drupal_prepare_form(), ensure that it
+ // matches the current user's session.
+ if (isset($form['#token'])) {
+ if (!drupal_valid_token($form_state['values']['form_token'], $form['#token'])) {
+ $path = current_path();
+ $query = drupal_get_query_parameters();
+ $url = url($path, array('query' => $query));
+
+ // Setting this error will cause the form to fail validation.
+ form_set_error('form_token', t('The form has become outdated. Copy any unsaved work in the form below and then <a href="@link">reload this page</a>.', array('@link' => $url)));
+
+ // Stop here and don't run any further validation handlers, because they
+ // could invoke non-safe operations which opens the door for CSRF
+ // vulnerabilities.
+ $validated_forms[$form_id] = TRUE;
+ return;
+ }
+ }
+
+ _form_validate($form, $form_state, $form_id);
+ $validated_forms[$form_id] = TRUE;
+
+ // If validation errors are limited then remove any non validated form values,
+ // so that only values that passed validation are left for submit callbacks.
+ if (isset($form_state['triggering_element']['#limit_validation_errors']) && $form_state['triggering_element']['#limit_validation_errors'] !== FALSE) {
+ $values = array();
+ foreach ($form_state['triggering_element']['#limit_validation_errors'] as $section) {
+ // If the section exists within $form_state['values'], even if the value
+ watchdog('image', 'The selected image handling toolkit %toolkit can not correctly process %function.', array('%toolkit' => $image->toolkit, '%function' => $function), WATCHDOG_ERROR);
+ return FALSE;
+}
+
+/**
+ * Gets details about an image.
+ *
+ * Drupal supports GIF, JPG and PNG file formats when used with the GD
+ * toolkit, and may support others, depending on which toolkits are
+ * installed.
+ *
+ * @param $filepath
+ * String specifying the path of the image file.
+ * @param $toolkit
+ * An optional image toolkit name to override the default.
+ *
+ * @return
+ * FALSE, if the file could not be found or is not an image. Otherwise, a
+ * keyed array containing information about the image:
+ * - "width": Width, in pixels.
+ * - "height": Height, in pixels.
+ * - "extension": Commonly used file extension for the image.
+ * - "mime_type": MIME type ('image/jpeg', 'image/gif', 'image/png').
+ * Defines basic Drupal requirements for databases.
+ */
+abstract class DatabaseTasks {
+
+ /**
+ * Structure that describes each task to run.
+ *
+ * @var array
+ *
+ * Each value of the tasks array is an associative array defining the function
+ * to call (optional) and any arguments to be passed to the function.
+ */
+ protected $tasks = array(
+ array(
+ 'function' => 'checkEngineVersion',
+ 'arguments' => array(),
+ ),
+ array(
+ 'arguments' => array(
+ 'CREATE TABLE {drupal_install_test} (id int NULL)',
+ 'Drupal can use CREATE TABLE database commands.',
+ 'Failed to <strong>CREATE</strong> a test table on your database server with the command %query. The server reports the following message: %error.<p>Are you sure the configured username has the necessary permissions to create tables in the database?</p>',
+ TRUE,
+ ),
+ ),
+ array(
+ 'arguments' => array(
+ 'INSERT INTO {drupal_install_test} (id) VALUES (1)',
+ 'Drupal can use INSERT database commands.',
+ 'Failed to <strong>INSERT</strong> a value into a test table on your database server. We tried inserting a value with the command %query and the server reported the following error: %error.',
+ ),
+ ),
+ array(
+ 'arguments' => array(
+ 'UPDATE {drupal_install_test} SET id = 2',
+ 'Drupal can use UPDATE database commands.',
+ 'Failed to <strong>UPDATE</strong> a value in a test table on your database server. We tried updating a value with the command %query and the server reported the following error: %error.',
+ ),
+ ),
+ array(
+ 'arguments' => array(
+ 'DELETE FROM {drupal_install_test}',
+ 'Drupal can use DELETE database commands.',
+ 'Failed to <strong>DELETE</strong> a value from a test table on your database server. We tried deleting a value with the command %query and the server reported the following error: %error.',
+ ),
+ ),
+ array(
+ 'arguments' => array(
+ 'DROP TABLE {drupal_install_test}',
+ 'Drupal can use DROP TABLE database commands.',
+ 'Failed to <strong>DROP</strong> a test table from your database server. We tried dropping a table with the command %query and the server reported the following error %error.',
+ ),
+ ),
+ );
+
+ /**
+ * Results from tasks.
+ *
+ * @var array
+ */
+ protected $results = array();
+
+ /**
+ * Ensure the PDO driver is supported by the version of PHP in use.
+ * Return the minimum required version of the engine.
+ *
+ * @return
+ * A version string. If not NULL, it will be checked against the version
+ * reported by the Database engine using version_compare().
+ */
+ public function minimumVersion() {
+ return NULL;
+ }
+
+ /**
+ * Run database tasks and tests to see if Drupal can run on the database.
+ */
+ public function runTasks() {
+ // We need to establish a connection before we can run tests.
+ if ($this->connect()) {
+ foreach ($this->tasks as $task) {
+ if (!isset($task['function'])) {
+ $task['function'] = 'runTestQuery';
+ }
+ if (method_exists($this, $task['function'])) {
+ // Returning false is fatal. No other tasks can run.
+ if (FALSE === call_user_func_array(array($this, $task['function']), $task['arguments'])) {
+ break;
+ }
+ }
+ else {
+ throw new DatabaseTaskException(st("Failed to run all tasks against the database server. The task %task wasn't found.", array('%task' => $task['function'])));
+ }
+ }
+ }
+ // Check for failed results and compile message
+ $message = '';
+ foreach ($this->results as $result => $success) {
+ $message = 'Resolve all issues below to continue the installation. For help configuring your database server, see the <a href="http://drupal.org/getting-started/install">installation handbook</a>, or contact your hosting provider.' . $message;
+ throw new DatabaseTaskException($message);
+ }
+ }
+
+ /**
+ * Check if we can connect to the database.
+ */
+ protected function connect() {
+ try {
+ // This doesn't actually test the connection.
+ db_set_active();
+ // Now actually do a check.
+ Database::getConnection();
+ $this->pass('Drupal can CONNECT to the database ok.');
+ }
+ catch (Exception $e) {
+ $this->fail(st('Failed to connect to your database server. The server reports the following message: %error.<ul><li>Is the database server running?</li><li>Does the database exist, and have you entered the correct database name?</li><li>Have you entered the correct username and password?</li><li>Have you entered the correct database hostname?</li></ul>', array('%error' => $e->getMessage())));
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /**
+ * Run SQL tests to ensure the database can execute commands with the current user.
+ */
+ protected function runTestQuery($query, $pass, $fail, $fatal = FALSE) {
+ if ($this->minimumVersion() && version_compare(Database::getConnection()->version(), $this->minimumVersion(), '<')) {
+ $this->fail(st("The database version %version is less than the minimum required version %minimum_version.", array('%version' => Database::getConnection()->version(), '%minimum_version' => $this->minimumVersion())));
+ }
+ }
+
+ /**
+ * Return driver specific configuration options.
+ *
+ * @param $database
+ * An array of driver specific configuration options.
+ '#description' => st('The name of the database your @drupal data will be stored in. It must exist on your server before @drupal can be installed.', array('@drupal' => drupal_install_profile_distribution_name())),
+ '#description' => st("These options are only necessary for some sites. If you're not sure what you should enter here, leave the default settings or check with your hosting provider."),
+ '#description' => st('If more than one application will be sharing this database, enter a table prefix such as %prefix for your @drupal site here.', array('@drupal' => drupal_install_profile_distribution_name(), '%prefix' => $db_prefix)),
+ '#description' => st('If your database server is listening to a non-standard port, enter its number.'),
+ );
+
+ return $form;
+ }
+
+ /**
+ * Validates driver specific configuration settings.
+ *
+ * Checks to ensure correct basic database settings and that a proper
+ * connection to the database can be established.
+ *
+ * @param $database
+ * An array of driver specific configuration options.
+ *
+ * @return
+ * An array of driver configuration errors, keyed by form element name.
+ */
+ public function validateDatabaseSettings($database) {
+ $errors = array();
+
+ // Verify the table prefix.
+ if (!empty($database['prefix']) && is_string($database['prefix']) && !preg_match('/^[A-Za-z0-9_.]+$/', $database['prefix'])) {
+ $errors[$database['driver'] . '][advanced_options][db_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']));
+ }
+
+ // Verify the database port.
+ if (!empty($database['port']) && !is_numeric($database['port'])) {
+ $errors[$database['driver'] . '][advanced_options][port'] = st('Database port must be a number.');
+ }
+
+ return $errors;
+ }
+
+}
+
+/**
+ * Exception thrown if the database installer fails.
+ */
+class DatabaseTaskException extends Exception {
+}
+
+/**
+ * Replaces values in settings.php with values in the submitted array.
+ 'description' => st('The following modules are required but were not found. Move them into the appropriate modules subdirectory, such as <em>sites/all/modules</em>. Missing modules: !modules', array('!modules' => implode(', ', $modules))),
+ );
+ }
+ return $requirements;
+}
+
+/**
+ * Installs the system module.
+ *
+ * Separated from the installation of other modules so core system
+ * functions can be made available while other modules are installed.
+ drupal_set_message(t('The translation file %filename appears to have a missing or malformed header.', array('%filename' => $file->filename)), 'error');
+ }
+
+ // Clear cache and force refresh of JavaScript translations.
+ _locale_invalidate_js($langcode);
+ cache_clear_all('locale:', 'cache', TRUE);
+
+ // Rebuild the menu, strings may have changed.
+ menu_rebuild();
+
+ drupal_set_message(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes)));
+ watchdog('locale', 'Imported %file into %locale: %number new strings added, %update updated and %delete removed.', array('%file' => $file->filename, '%locale' => $langcode, '%number' => $additions, '%update' => $updates, '%delete' => $deletes));
+ if ($skips) {
+ $skip_message = format_plural($skips, 'One translation string was skipped because it contains disallowed HTML.', '@count translation strings were skipped because they contain disallowed HTML.');
+ drupal_set_message($skip_message);
+ watchdog('locale', '@count disallowed HTML string(s) in %file', array('@count' => $skips, '%file' => $file->uri), WATCHDOG_WARNING);
+ }
+ return TRUE;
+}
+
+/**
+ * Parses Gettext Portable Object file into an array
+ *
+ * @param $op
+ * Storage operation type: db-store or mem-store.
+ * @param $file
+ * Drupal file object corresponding to the PO file to import.
+ * @param $mode
+ * Should existing translations be replaced LOCALE_IMPORT_KEEP or
+ * LOCALE_IMPORT_OVERWRITE.
+ * @param $lang
+ * Language code.
+ * @param $group
+ * Text group to import PO file into (eg. 'default' for interface
+ // A comment following any other token is a syntax error.
+ _locale_import_message('The translation file %filename contains an error: "msgstr" was expected but not found on line %line.', $file, $lineno);
+ return FALSE;
+ }
+ }
+ elseif (!strncmp('msgid_plural', $line, 12)) {
+ // A plural form for the current message.
+
+ if ($context != 'MSGID') {
+ // A plural form cannot be added to anything else but the id directly.
+ _locale_import_message('The translation file %filename contains an error: "msgid_plural" was expected but not found on line %line.', $file, $lineno);
+ return FALSE;
+ }
+
+ // Remove 'msgid_plural' and trim away whitespace.
+ $line = trim(substr($line, 12));
+ // At this point, $line should now contain only the plural form.
+
+ $quoted = _locale_import_parse_quoted($line);
+ if ($quoted === FALSE) {
+ // The plural form must be wrapped in quotes.
+ _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno);
+ return FALSE;
+ }
+
+ // Append the plural form to the current entry.
+ $current['msgid'] .= "\0" . $quoted;
+
+ $context = 'MSGID_PLURAL';
+ }
+ elseif (!strncmp('msgid', $line, 5)) {
+ // Starting a new message.
+
+ if (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) {
+ // We are currently in a message string, close it out.
+ $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = :context AND textgroup = :textgroup", array(':source' => $source, ':context' => $context, ':textgroup' => $textgroup))->fetchField();
+
+ if (!empty($translation)) {
+ // Skip this string unless it passes a check for dangerous code.
+ // Text groups other than default still can contain HTML tags
+ // (i.e. translatable blocks).
+ if ($textgroup == "default" && !locale_string_is_safe($translation)) {
+ $report['skips']++;
+ $lid = 0;
+ }
+ elseif ($lid) {
+ // We have this source string saved already.
+ db_update('locales_source')
+ ->fields(array(
+ 'location' => $location,
+ ))
+ ->condition('lid', $lid)
+ ->execute();
+
+ $exists = db_query("SELECT COUNT(lid) FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $langcode))->fetchField();
+
+ if (!$exists) {
+ // No translation in this language.
+ db_insert('locales_target')
+ ->fields(array(
+ 'lid' => $lid,
+ 'language' => $langcode,
+ 'translation' => $translation,
+ 'plid' => $plid,
+ 'plural' => $plural,
+ ))
+ ->execute();
+
+ $report['additions']++;
+ }
+ elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
+ // Translation exists, only overwrite if instructed.
+ db_update('locales_target')
+ ->fields(array(
+ 'translation' => $translation,
+ 'plid' => $plid,
+ 'plural' => $plural,
+ ))
+ ->condition('language', $langcode)
+ ->condition('lid', $lid)
+ ->execute();
+
+ $report['updates']++;
+ }
+ }
+ else {
+ // No such source string in the database yet.
+ $lid = db_insert('locales_source')
+ ->fields(array(
+ 'location' => $location,
+ 'source' => $source,
+ 'context' => (string) $context,
+ 'textgroup' => $textgroup,
+ ))
+ ->execute();
+
+ db_insert('locales_target')
+ ->fields(array(
+ 'lid' => $lid,
+ 'language' => $langcode,
+ 'translation' => $translation,
+ 'plid' => $plid,
+ 'plural' => $plural
+ ))
+ ->execute();
+
+ $report['additions']++;
+ }
+ }
+ elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
+ // Empty translation, remove existing if instructed.
+ drupal_set_message(t('The translation file %filepath contains an error: the plural formula could not be parsed.', array('%filepath' => $filepath)), 'error');
+ return FALSE;
+ }
+}
+
+/**
+ * Parses and sanitizes an arithmetic formula into a PHP expression
+ *
+ * While parsing, we ensure, that the operators have the right
+ $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.translation, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.textgroup = :textgroup ORDER BY t.plid, t.plural", array(':language' => $language->language, ':textgroup' => $group));
+ }
+ else {
+ $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid WHERE s.textgroup = :textgroup ORDER BY t.plid, t.plural", array(':textgroup' => $group));
+ * (Re-)Creates the JavaScript translation file for a language.
+ *
+ * @param $language
+ * The language, the translation file should be (re)created for.
+ */
+function _locale_rebuild_js($langcode = NULL) {
+ if (!isset($langcode)) {
+ global $language;
+ }
+ else {
+ // Get information about the locale.
+ $languages = language_list();
+ $language = $languages[$langcode];
+ }
+
+ // Construct the array for JavaScript translations.
+ // Only add strings with a translation to the translations array.
+ $result = db_query("SELECT s.lid, s.source, s.context, 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", array(':language' => $language->language, ':textgroup' => 'default'));
+ // Proceed to the 'created' case as the JavaScript translation file has
+ // been created again.
+ case 'created':
+ watchdog('locale', 'Created JavaScript translation file for the language %language.', array('%language' => t($language->name)));
+ return TRUE;
+ case 'deleted':
+ watchdog('locale', 'Removed JavaScript translation file for the language %language, because no translations currently exist for that language.', array('%language' => t($language->name)));
+ return TRUE;
+ case 'error':
+ watchdog('locale', 'An error occurred during creation of the JavaScript translation file for the language %language.', array('%language' => t($language->name)), WATCHDOG_ERROR);
+ drupal_set_message(format_plural(count($results), 'One translation file imported for the newly installed modules.', '@count translation files imported for the newly installed modules.'));
+ }
+}
+
+/**
+ * Finished callback of language addition locale import batch.
+ * Inform the user of translation files imported.
+ drupal_set_message(format_plural(count($results), 'One translation file imported for the enabled modules.', '@count translation files imported for the enabled modules.'));
+ }
+}
+
+/**
+ * @} End of "locale-autoimport"
+ */
+
+/**
+ * Get list of all predefined and custom countries.
+ *
+ * @return
+ * An array of all country code => country name pairs.
+ */
+function country_get_list() {
+ include_once DRUPAL_ROOT . '/includes/iso.inc';
+ $countries = _country_get_predefined_list();
+ // Allow other modules to modify the country list.
+ drupal_alter('countries', $countries);
+ return $countries;
+}
+
+/**
+ * Save locale specific date formats to the database.
+ *
+ * @param $langcode
+ * Language code, can be 2 characters, e.g. 'en' or 5 characters, e.g.
+ $is_existing = (bool) db_query_range('SELECT 1 FROM {date_format_locale} WHERE language = :langcode AND type = :type', 0, 1, array(':langcode' => $langcode, ':type' => $type))->fetchField();
+ $router_item = db_query_range('SELECT * FROM {menu_router} WHERE path IN (:ancestors) ORDER BY fit DESC', 0, 1, array(':ancestors' => $ancestors))->fetchAssoc();
+
+ if ($router_item) {
+ // Allow modules to alter the router item before it is translated and
+ * Builds a list of bootstrap modules and enabled modules and themes.
+ *
+ * @param $type
+ * The type of list to return:
+ * - module_enabled: All enabled modules.
+ * - bootstrap: All enabled modules required for bootstrap.
+ * - theme: All themes.
+ *
+ * @return
+ * An associative array of modules or themes, keyed by name. For $type
+ * 'bootstrap', the array values equal the keys. For $type 'module_enabled'
+ * or 'theme', the array values are objects representing the respective
+ * database row, with the 'info' property already unserialized.
+ *
+ * @see module_list()
+ * @see list_themes()
+ */
+function system_list($type) {
+ $lists = &drupal_static(__FUNCTION__);
+
+ // For bootstrap modules, attempt to fetch the list from cache if possible.
+ // if not fetch only the required information to fire bootstrap hooks
+ // in case we are going to serve the page from cache.
+ if ($type == 'bootstrap') {
+ if (isset($lists['bootstrap'])) {
+ return $lists['bootstrap'];
+ }
+ if ($cached = cache_get('bootstrap_modules', 'cache_bootstrap')) {
+ $bootstrap_list = $cached->data;
+ }
+ else {
+ $bootstrap_list = db_query("SELECT name, filename FROM {system} WHERE status = 1 AND bootstrap = 1 AND type = 'module' ORDER BY weight ASC, name ASC")->fetchAllAssoc('name');
+ // During the first call to drupal_lookup_path() per language, load the
+ // expected system paths for the page from cache.
+ if (!empty($cache['first_call'])) {
+ $cache['first_call'] = FALSE;
+
+ $cache['map'][$path_language] = array();
+ // Load system paths from cache.
+ $cid = current_path();
+ if ($cached = cache_get($cid, 'cache_path')) {
+ $cache['system_paths'] = $cached->data;
+ // Now fetch the aliases corresponding to these system paths.
+ $args = array(
+ ':system' => $cache['system_paths'],
+ ':language' => $path_language,
+ ':language_none' => LANGUAGE_NONE,
+ );
+ // Always get the language-specific alias before the language-neutral
+ // one. For example 'de' is less than 'und' so the order needs to be
+ // ASC, while 'xx-lolspeak' is more than 'und' so the order needs to
+ // be DESC. We also order by pid ASC so that fetchAllKeyed() returns
+ // the most recently created alias for each source. Subsequent queries
+ // using fetchField() must use pid DESC to have the same effect.
+ // For performance reasons, the query builder is not used here.
+ if ($path_language == LANGUAGE_NONE) {
+ // Prevent PDO from complaining about a token the query doesn't use.
+ unset($args[':language']);
+ $result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language = :language_none ORDER BY pid ASC', $args);
+ }
+ elseif ($path_language < LANGUAGE_NONE) {
+ $result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language IN (:language, :language_none) ORDER BY language ASC, pid ASC', $args);
+ }
+ else {
+ $result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language IN (:language, :language_none) ORDER BY language DESC, pid ASC', $args);
+ $alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND language = :language_none ORDER BY pid DESC", $args)->fetchField();
+ }
+ elseif ($path_language > LANGUAGE_NONE) {
+ $alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", $args)->fetchField();
+ }
+ else {
+ $alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND language IN (:language, :language_none) ORDER BY language ASC, pid DESC", $args)->fetchField();
+ }
+ $cache['map'][$path_language][$path] = $alias;
+ return $alias;
+ }
+ }
+ // Check $no_source for this $path in case we've already determined that there
+ // Look for the value $path within the cached $map
+ $source = FALSE;
+ if (!isset($cache['map'][$path_language]) || !($source = array_search($path, $cache['map'][$path_language]))) {
+ $args = array(
+ ':alias' => $path,
+ ':language' => $path_language,
+ ':language_none' => LANGUAGE_NONE,
+ );
+ // See the queries above.
+ if ($path_language == LANGUAGE_NONE) {
+ unset($args[':language']);
+ $result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language = :language_none ORDER BY pid DESC", $args);
+ }
+ elseif ($path_language > LANGUAGE_NONE) {
+ $result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", $args);
+ }
+ else {
+ $result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language IN (:language, :language_none) ORDER BY language ASC, pid DESC", $args);
+ }
+ if ($source = $result->fetchField()) {
+ $cache['map'][$path_language][$source] = $path;
+ }
+ else {
+ // We can't record anything into $map because we do not have a valid
+ // index and there is no need because we have not learned anything
+ // about any Drupal path. Thus cache to $no_source.
+ if (empty($sid) || (!isset($_COOKIE[session_name()]) && !isset($_COOKIE[$insecure_session_name]))) {
+ $user = drupal_anonymous_user();
+ return '';
+ }
+
+ // Otherwise, if the session is still active, we have a record of the
+ // client's session in the database. If it's HTTPS then we are either have
+ // a HTTPS session or we are about to log in so we check the sessions table
+ // for an anonymous session with the non-HTTPS-only cookie.
+ if ($is_https) {
+ $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.ssid = :ssid", array(':ssid' => $sid))->fetchObject();
+ if (!$user) {
+ if (isset($_COOKIE[$insecure_session_name])) {
+ $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid AND s.uid = 0", array(
+ ':sid' => $_COOKIE[$insecure_session_name]))
+ ->fetchObject();
+ }
+ }
+ }
+ else {
+ $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => $sid))->fetchObject();
+ }
+
+ // We found the client's session record and they are an authenticated,
+ $user->roles += db_query("SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = :uid", array(':uid' => $user->uid))->fetchAllKeyed(0, 1);
+ }
+ elseif ($user) {
+ // The user is anonymous or blocked. Only preserve two fields from the
+ // {sessions} table.
+ $account = drupal_anonymous_user();
+ $account->session = $user->session;
+ $account->timestamp = $user->timestamp;
+ $user = $account;
+ }
+ else {
+ // The session has expired.
+ $user = drupal_anonymous_user();
+ $user->session = '';
+ }
+
+ // Store the session that was read for comparison in _drupal_session_write().
+ * Perform checks about Unicode support in PHP, and set the right settings if
+ * needed.
+ *
+ * Because Drupal needs to be able to handle text in various encodings, we do
+ * not support mbstring function overloading. HTTP input/output conversion must
+ * be disabled for similar reasons.
+ *
+ * @param $errors
+ * Whether to report any fatal errors with form_set_error().
+ */
+function _unicode_check() {
+ // Ensure translations don't break during installation.
+ $t = get_t();
+
+ // Check for mbstring extension
+ if (!function_exists('mb_strlen')) {
+ return array(UNICODE_SINGLEBYTE, $t('Operations on Unicode strings are emulated on a best-effort basis. Install the <a href="@url">PHP mbstring extension</a> for improved Unicode support.', array('@url' => 'http://www.php.net/mbstring')));
+ }
+
+ // Check mbstring configuration
+ if (ini_get('mbstring.func_overload') != 0) {
+ return array(UNICODE_ERROR, $t('Multibyte string function overloading in PHP is active and must be disabled. Check the php.ini <em>mbstring.func_overload</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring')));
+ }
+ if (ini_get('mbstring.encoding_translation') != 0) {
+ return array(UNICODE_ERROR, $t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.encoding_translation</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring')));
+ }
+ // mbstring.http_input and mbstring.http_output are deprecated and empty by
+ // default in PHP 5.6.
+ if (version_compare(PHP_VERSION, '5.6.0') == -1) {
+ if (ini_get('mbstring.http_input') != 'pass') {
+ return array(UNICODE_ERROR, $t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.http_input</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring')));
+ }
+ if (ini_get('mbstring.http_output') != 'pass') {
+ return array(UNICODE_ERROR, $t('Multibyte string output conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.http_output</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring')));
+ }
+ }
+
+ // Set appropriate configuration
+ mb_internal_encoding('utf-8');
+ mb_language('uni');
+ return array(UNICODE_MULTIBYTE, '');
+}
+
+/**
+ * Returns Unicode library status and errors.
+ */
+function unicode_requirements() {
+ // Ensure translations don't break during installation.
+ 'description' => $writable ? '' : 'Drupal requires write permissions to <em>' . $settings_file . '</em> during the update process. If you are unsure how to grant file permissions, consult the <a href="http://drupal.org/server-permissions">online handbook</a>.',
+ ),
+ );
+ update_extra_requirements($requirements);
+ }
+
+ // The new {blocked_ips} table is used in Drupal 7 to store a list of
+ // banned IP addresses. If this table doesn't exist then we are still
+ // running on a Drupal 6 database, so we suppress the unavoidable errors
+ // that occur by creating a static list.
+ $GLOBALS['conf']['blocked_ips'] = array();
+
+ // Check that PDO is available and that the correct PDO database driver is
+ // loaded. Bootstrapping to DRUPAL_BOOTSTRAP_DATABASE will result in a fatal
+ $message = '<h2>PDO is required!</h2><p>Drupal 7 requires PHP ' . DRUPAL_MINIMUM_PHP . ' or higher with the PHP Data Objects (PDO) extension enabled.</p>';
+ }
+ // The PDO::ATTR_DEFAULT_FETCH_MODE constant is not available in the PECL
+ $message = '<h2>The wrong version of PDO is installed!</h2><p>Drupal 7 requires the PHP Data Objects (PDO) extension from PHP core to be enabled. This system has the older PECL version installed.';
+ $message = '<h2>A PDO database driver is required!</h2><p>You need to enable the PDO_' . strtoupper($databases['default']['default']['driver']) . ' database driver for PHP ' . DRUPAL_MINIMUM_PHP . ' or higher so that Drupal 7 can access the database.</p>';
+ }
+ if ($message) {
+ print $message . '<p>See the <a href="' . $pdo_link . '">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);
+
+ // If the site has not updated to Drupal 7 yet, check to make sure that it is
+ // running an up-to-date version of Drupal 6 before proceeding. Note this has
+ // to happen AFTER the database bootstraps because of
+ 'description' => $has_required_schema ? '' : 'Please update your Drupal 6 installation to the most recent version before attempting to upgrade to Drupal 7',
+ ),
+ );
+
+ // Make sure that the database environment is properly set up.
+ try {
+ db_run_tasks(db_driver());
+ }
+ catch (DatabaseTaskException $e) {
+ $requirements['database tasks'] = array(
+ 'title' => 'Database environment',
+ 'value' => 'There is a problem with your database environment',
+ 'severity' => REQUIREMENT_ERROR,
+ 'description' => $e->getMessage(),
+ );
+ }
+
+ update_extra_requirements($requirements);
+
+ // Allow a D6 session to work, since the upgrade has not been performed yet.
+ 'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.',
+ $ret[$module]['warning'] = '<em>' . $module . '</em> module can not be updated. Its schema version is ' . $schema_version . '. Updates up to and including ' . $last_removed . ' have been removed in this release. In order to update <em>' . $module . '</em> module, you will first <a href="http://drupal.org/upgrade">need to upgrade</a> to the last version in which these updates were available.';
+ continue;
+ }
+
+ $updates = drupal_map_assoc($updates);
+ foreach (array_keys($updates) as $update) {
+ if ($update > $schema_version) {
+ // The description for an update comes from its Doxygen.
+ $func = new ReflectionFunction($module . '_update_' . $update);
+ * Invokes hook_update_dependencies() in all installed modules.
+ *
+ * This function is similar to module_invoke_all(), with the main difference
+ * that it does not require that a module be enabled to invoke its hook, only
+ * that it be installed. This allows the update system to properly perform
+ * updates even on modules that are currently disabled.
+ *
+ * @return
+ * An array of return values obtained by merging the results of the
+ * hook_update_dependencies() implementations in all installed modules.
+ *
+ * @see module_invoke_all()
+ * @see hook_update_dependencies()
+ */
+function update_retrieve_dependencies() {
+ $return = array();
+ // Get a list of installed modules, arranged so that we invoke their hooks in
+ // the same order that module_invoke_all() does.
+ $modules = db_query("SELECT name FROM {system} WHERE type = 'module' AND schema_version <> :schema ORDER BY weight ASC, name ASC", array(':schema' => SCHEMA_UNINSTALLED))->fetchCol();
+ foreach ($modules as $module) {
+ $function = $module . '_update_dependencies';
+ if (function_exists($function)) {
+ $result = $function();
+ // Each implementation of hook_update_dependencies() returns a
+ // multidimensional, associative array containing some keys that
+ // represent module names (which are strings) and other keys that
+ // represent update function numbers (which are integers). We cannot use
+ // array_merge_recursive() to properly merge these results, since it
+ // treats strings and integers differently. Therefore, we have to
+ // explicitly loop through the expected array structure here and perform
+ // the merge manually.
+ if (isset($result) && is_array($result)) {
+ foreach ($result as $module => $module_data) {
+ foreach ($module_data as $update => $update_data) {
+ foreach ($update_data as $module_dependency => $update_dependency) {
+ // If there are redundant dependencies declared for the same
+ // update function (so that it is declared to depend on more than
+ // one update from a particular module), record the dependency on
+ // the highest numbered update here, since that automatically
+ // implies the previous ones. For example, if one module's
+ // implementation of hook_update_dependencies() required this
+ // ordering:
+ //
+ // system_update_7001 ---> user_update_7000
+ //
+ // but another module's implementation of the hook required this
+ // one:
+ //
+ // system_update_7002 ---> user_update_7000
+ //
+ // we record the second one, since system_update_7001() is always
+ // guaranteed to run before system_update_7002() anyway (within
+ // an individual module, updates are always run in numerical
+ // order).
+ if (!isset($return[$module][$update][$module_dependency]) || $update_dependency > $return[$module][$update][$module_dependency]) {
+ * Initiates a browser-based installation of Drupal.
+ */
+
+/**
+ * Defines the root directory of the Drupal installation.
+ */
+define('DRUPAL_ROOT', getcwd());
+
+/**
+ * Global flag to indicate the site is in installation mode.
+ */
+define('MAINTENANCE_MODE', 'install');
+
+// Exit early if running an incompatible PHP version to avoid fatal errors.
+if (version_compare(PHP_VERSION, '5.2.4') < 0) {
+ print 'Your PHP installation is too old. Drupal requires at least PHP 5.2.4. See the <a href="http://drupal.org/requirements">system requirements</a> page for more information.';