Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • develop
  • issue-424
  • issue-525
  • master
  • master-no-logins
  • svn-staging
  • svn-testing
  • svn-trunk
  • topics/add-contribution-info
  • 2010-11-11
  • 2010-12-15
  • 2011-01-11
  • 2011-01-18
  • 2011-01-26
  • 2011-02-10
  • 2011-02-23
  • 2011-03-09
  • 2011-03-15
  • 2011-03-30
  • 2011-04-05
  • 2011-05-03
  • 2011-05-12
  • 2011-06-16
  • 2011-06-21
  • 2011-06-23
  • 2011-06-29
  • 2011-06-30
  • 2011-07-11
  • 2011-07-18
  • 2011-07-20
  • 2011-07-21
  • 2011-07-28
  • 2011-08-03
  • 2011-08-05
  • 2011-08-15
  • 2011-08-17
  • 2011-08-29
  • 2011-08-30
  • 2011-09-19
  • 2011-10-03
  • 2011-10-06
  • 2011-10-27
  • 2011-11-01
  • 2011-11-08
  • 2011-11-08.2
  • 2011-11-14
  • 2011-11-17
  • 2011-12-05
  • 2011-12-16
  • 2012-01-12
  • 2012-01-13
  • 2012-02-07
  • 2012-03-01
  • 2012-04-02
  • 2012-04-03
  • 2012-04-18
  • 20120207
  • 7.1
  • 7.2
  • 7.3
  • 7.3.1
  • 7.4
  • 7.5
  • 7.5.1
  • 7.6
  • 7.6.1
66 results

Target

Select target project
  • rklusman2/UNL-CMS
  • yzha1/UNL-CMS
  • pear/UNL-CMS
  • bbieber2/UNL-CMS
  • tsteiner2/UNL-CMS
  • erasmussen2/UNL-CMS
  • UNL-Information-Services/UNL-CMS
7 results
Select Git revision
  • develop
  • issue-424
  • master
  • master-no-logins
  • svn-staging
  • svn-testing
  • svn-trunk
  • topics/add-contribution-info
  • 2010-11-11
  • 2010-12-15
  • 2011-01-11
  • 2011-01-18
  • 2011-01-26
  • 2011-02-10
  • 2011-02-23
  • 2011-03-09
  • 2011-03-15
  • 2011-03-30
  • 2011-04-05
  • 2011-05-03
  • 2011-05-12
  • 2011-06-16
  • 2011-06-21
  • 2011-06-23
  • 2011-06-29
  • 2011-06-30
  • 2011-07-11
  • 2011-07-18
  • 2011-07-20
  • 2011-07-21
  • 2011-07-28
  • 2011-08-03
  • 2011-08-05
  • 2011-08-15
  • 2011-08-17
  • 2011-08-29
  • 2011-08-30
  • 2011-09-19
  • 2011-10-03
  • 2011-10-06
  • 2011-10-27
  • 2011-11-01
  • 2011-11-08
  • 2011-11-08.2
  • 2011-11-14
  • 2011-11-17
  • 2011-12-05
  • 2011-12-16
  • 2012-01-12
  • 2012-01-13
  • 2012-02-07
  • 2012-03-01
  • 2012-04-02
  • 2012-04-03
  • 2012-04-18
  • 20120207
  • 7.1
  • 7.10
  • 7.11
  • 7.12
  • 7.13
  • 7.14
  • 7.15
  • 7.16
  • 7.16.1
  • 7.17
  • 7.18
  • 7.19
  • 7.2
  • 7.3
  • 7.3.1
  • 7.4
  • 7.5
  • 7.5.1
  • 7.6
  • 7.6.1
  • 7.7
  • 7.7.1
  • 7.7.2
  • 7.7.3
  • 7.8
  • 7.9
82 results
Show changes
Showing
with 3367 additions and 1547 deletions
<?php <?php
// $Id: batch.queue.inc,v 1.1 2010/01/08 06:36:34 webchick Exp $
/** /**
* @file * @file
* Queue handlers used by the Batch API. * Queue handlers used by the Batch API.
* *
* Those implementations: * These implementations:
* - ensure FIFO ordering, * - Ensure FIFO ordering.
* - let an item be repeatedly claimed until it is actually deleted (no notion * - Allow an item to be repeatedly claimed until it is actually deleted (no
* of lease time or 'expire' date), to allow multipass operations. * notion of lease time or 'expire' date), to allow multipass operations.
*/ */
/** /**
* Batch queue implementation. * Defines a batch queue.
* *
* Stale items from failed batches are cleaned from the {queue} table on cron * Stale items from failed batches are cleaned from the {queue} table on cron
* using the 'created' date. * using the 'created' date.
*/ */
class BatchQueue extends SystemQueue { 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) { public function claimItem($lease_time = 0) {
$item = db_query('SELECT data, item_id FROM {queue} q WHERE name = :name ORDER BY item_id ASC', array(':name' => $this->name))->fetchObject(); $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) { if ($item) {
$item->data = unserialize($item->data); $item->data = unserialize($item->data);
return $item; return $item;
...@@ -30,9 +35,9 @@ class BatchQueue extends SystemQueue { ...@@ -30,9 +35,9 @@ class BatchQueue extends SystemQueue {
} }
/** /**
* Retrieve all remaining items in the queue. * Retrieves all remaining items in the queue.
* *
* This is specific to Batch API and is not part of the DrupalQueueInterface, * This is specific to Batch API and is not part of the DrupalQueueInterface.
*/ */
public function getAllItems() { public function getAllItems() {
$result = array(); $result = array();
...@@ -45,10 +50,17 @@ class BatchQueue extends SystemQueue { ...@@ -45,10 +50,17 @@ class BatchQueue extends SystemQueue {
} }
/** /**
* Batch queue implementation used for non-progressive batches. * Defines a batch queue for non-progressive batches.
*/ */
class BatchMemoryQueue extends MemoryQueue { 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) { public function claimItem($lease_time = 0) {
if (!empty($this->queue)) { if (!empty($this->queue)) {
reset($this->queue); reset($this->queue);
...@@ -58,9 +70,9 @@ class BatchMemoryQueue extends MemoryQueue { ...@@ -58,9 +70,9 @@ class BatchMemoryQueue extends MemoryQueue {
} }
/** /**
* Retrieve all remaining items in the queue. * Retrieves all remaining items in the queue.
* *
* This is specific to Batch API and is not part of the DrupalQueueInterface, * This is specific to Batch API and is not part of the DrupalQueueInterface.
*/ */
public function getAllItems() { public function getAllItems() {
$result = array(); $result = array();
......
<?php <?php
// $Id: bootstrap.inc,v 1.430 2010/10/23 05:30:57 webchick Exp $
/** /**
* @file * @file
...@@ -9,7 +8,7 @@ ...@@ -9,7 +8,7 @@
/** /**
* The current system version. * The current system version.
*/ */
define('VERSION', '7.0-beta2'); define('VERSION', '7.12');
/** /**
* Core API compatibility. * Core API compatibility.
...@@ -26,16 +25,6 @@ define('DRUPAL_MINIMUM_PHP', '5.2.4'); ...@@ -26,16 +25,6 @@ define('DRUPAL_MINIMUM_PHP', '5.2.4');
*/ */
define('DRUPAL_MINIMUM_PHP_MEMORY_LIMIT', '32M'); define('DRUPAL_MINIMUM_PHP_MEMORY_LIMIT', '32M');
/**
* Minimum supported version of MySQL, if it is used.
*/
define('DRUPAL_MINIMUM_MYSQL', '5.0.15');
/**
* Minimum supported version of PostgreSQL, if it is used.
*/
define('DRUPAL_MINIMUM_PGSQL', '8.3');
/** /**
* Indicates that the item should never be removed unless explicitly selected. * Indicates that the item should never be removed unless explicitly selected.
* *
...@@ -49,93 +38,69 @@ define('CACHE_PERMANENT', 0); ...@@ -49,93 +38,69 @@ define('CACHE_PERMANENT', 0);
define('CACHE_TEMPORARY', -1); define('CACHE_TEMPORARY', -1);
/** /**
* Log message severity -- Emergency: system is unusable. * @defgroup logging_severity_levels Logging severity levels
* @{
* Logging severity levels as defined in RFC 3164.
* *
* The WATCHDOG_* constant definitions correspond to the logging severity levels * The WATCHDOG_* constant definitions correspond to the logging severity levels
* defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html * defined in RFC 3164, section 4.1.1. PHP supplies predefined LOG_* constants
* * for use in the syslog() function, but their values on Windows builds do not
* correspond to RFC 3164. The associated PHP bug report was closed with the
* comment, "And it's also not a bug, as Windows just have less log levels,"
* and "So the behavior you're seeing is perfectly normal."
*
* @see http://www.faqs.org/rfcs/rfc3164.html
* @see http://bugs.php.net/bug.php?id=18090
* @see http://php.net/manual/function.syslog.php
* @see http://php.net/manual/network.constants.php
* @see watchdog() * @see watchdog()
* @see watchdog_severity_levels() * @see watchdog_severity_levels()
*/ */
/**
* Log message severity -- Emergency: system is unusable.
*/
define('WATCHDOG_EMERGENCY', 0); define('WATCHDOG_EMERGENCY', 0);
/** /**
* Log message severity -- Alert: action must be taken immediately. * Log message severity -- Alert: action must be taken immediately.
*
* The WATCHDOG_* constant definitions correspond to the logging severity levels
* defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
*
* @see watchdog()
* @see watchdog_severity_levels()
*/ */
define('WATCHDOG_ALERT', 1); define('WATCHDOG_ALERT', 1);
/** /**
* Log message severity -- Critical: critical conditions. * Log message severity -- Critical: critical conditions.
*
* The WATCHDOG_* constant definitions correspond to the logging severity levels
* defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
*
* @see watchdog()
* @see watchdog_severity_levels()
*/ */
define('WATCHDOG_CRITICAL', 2); define('WATCHDOG_CRITICAL', 2);
/** /**
* Log message severity -- Error: error conditions. * Log message severity -- Error: error conditions.
*
* The WATCHDOG_* constant definitions correspond to the logging severity levels
* defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
*
* @see watchdog()
* @see watchdog_severity_levels()
*/ */
define('WATCHDOG_ERROR', 3); define('WATCHDOG_ERROR', 3);
/** /**
* Log message severity -- Warning: warning conditions. * Log message severity -- Warning: warning conditions.
*
* The WATCHDOG_* constant definitions correspond to the logging severity levels
* defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
*
* @see watchdog()
* @see watchdog_severity_levels()
*/ */
define('WATCHDOG_WARNING', 4); define('WATCHDOG_WARNING', 4);
/** /**
* Log message severity -- Notice: normal but significant condition. * Log message severity -- Notice: normal but significant condition.
*
* The WATCHDOG_* constant definitions correspond to the logging severity levels
* defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
*
* @see watchdog()
* @see watchdog_severity_levels()
*/ */
define('WATCHDOG_NOTICE', 5); define('WATCHDOG_NOTICE', 5);
/** /**
* Log message severity -- Informational: informational messages. * Log message severity -- Informational: informational messages.
*
* The WATCHDOG_* constant definitions correspond to the logging severity levels
* defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
*
* @see watchdog()
* @see watchdog_severity_levels()
*/ */
define('WATCHDOG_INFO', 6); define('WATCHDOG_INFO', 6);
/** /**
* Log message severity -- Debug: debug-level messages. * Log message severity -- Debug: debug-level messages.
*
* The WATCHDOG_* constant definitions correspond to the logging severity levels
* defined in RFC 3164, section 4.1.1: http://www.faqs.org/rfcs/rfc3164.html
*
* @see watchdog()
* @see watchdog_severity_levels()
*/ */
define('WATCHDOG_DEBUG', 7); define('WATCHDOG_DEBUG', 7);
/**
* @} End of "defgroup logging_severity_levels".
*/
/** /**
* First bootstrap phase: initialize configuration. * First bootstrap phase: initialize configuration.
*/ */
...@@ -172,8 +137,7 @@ define('DRUPAL_BOOTSTRAP_PAGE_HEADER', 5); ...@@ -172,8 +137,7 @@ define('DRUPAL_BOOTSTRAP_PAGE_HEADER', 5);
define('DRUPAL_BOOTSTRAP_LANGUAGE', 6); define('DRUPAL_BOOTSTRAP_LANGUAGE', 6);
/** /**
* Final bootstrap phase: Drupal is fully loaded; validate and fix * Final bootstrap phase: Drupal is fully loaded; validate and fix input data.
* input data.
*/ */
define('DRUPAL_BOOTSTRAP_FULL', 7); define('DRUPAL_BOOTSTRAP_FULL', 7);
...@@ -188,8 +152,9 @@ define('DRUPAL_ANONYMOUS_RID', 1); ...@@ -188,8 +152,9 @@ define('DRUPAL_ANONYMOUS_RID', 1);
define('DRUPAL_AUTHENTICATED_RID', 2); define('DRUPAL_AUTHENTICATED_RID', 2);
/** /**
* The number of bytes in a kilobyte. For more information, visit * The number of bytes in a kilobyte.
* http://en.wikipedia.org/wiki/Kilobyte. *
* For more information, visit http://en.wikipedia.org/wiki/Kilobyte.
*/ */
define('DRUPAL_KILOBYTE', 1024); define('DRUPAL_KILOBYTE', 1024);
...@@ -226,9 +191,16 @@ define('LANGUAGE_LTR', 0); ...@@ -226,9 +191,16 @@ define('LANGUAGE_LTR', 0);
define('LANGUAGE_RTL', 1); define('LANGUAGE_RTL', 1);
/** /**
* For convenience, define a short form of the request time global. * Time of the current request in seconds elapsed since the Unix Epoch.
*
* This differs from $_SERVER['REQUEST_TIME'], which is stored as a float
* since PHP 5.4.0. Float timestamps confuse most PHP functions
* (including date_create()).
*
* @see http://php.net/manual/reserved.variables.server.php
* @see http://php.net/manual/function.time.php
*/ */
define('REQUEST_TIME', $_SERVER['REQUEST_TIME']); define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
/** /**
* Flag for drupal_set_title(); text is not sanitized, so run check_plain(). * Flag for drupal_set_title(); text is not sanitized, so run check_plain().
...@@ -251,10 +223,219 @@ define('REGISTRY_RESET_LOOKUP_CACHE', 1); ...@@ -251,10 +223,219 @@ define('REGISTRY_RESET_LOOKUP_CACHE', 1);
define('REGISTRY_WRITE_LOOKUP_CACHE', 2); define('REGISTRY_WRITE_LOOKUP_CACHE', 2);
/** /**
* Start the timer with the specified name. If you start and stop the same * Regular expression to match PHP function names.
* timer multiple times, the measured intervals will be accumulated. *
* @see http://php.net/manual/en/language.functions.php
*/
define('DRUPAL_PHP_FUNCTION_PATTERN', '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*');
/**
* Provides a caching wrapper to be used in place of large array structures.
*
* This class should be extended by systems that need to cache large amounts
* of data and have it represented as an array to calling functions. These
* arrays can become very large, so ArrayAccess is used to allow different
* strategies to be used for caching internally (lazy loading, building caches
* over time etc.). This can dramatically reduce the amount of data that needs
* to be loaded from cache backends on each request, and memory usage from
* static caches of that same data.
*
* Note that array_* functions do not work with ArrayAccess. Systems using
* DrupalCacheArray should use this only internally. If providing API functions
* that return the full array, this can be cached separately or returned
* directly. However since DrupalCacheArray holds partial content by design, it
* should be a normal PHP array or otherwise contain the full structure.
*
* Note also that due to limitations in PHP prior to 5.3.4, it is impossible to
* write directly to the contents of nested arrays contained in this object.
* Only writes to the top-level array elements are possible. So if you
* previously had set $object['foo'] = array(1, 2, 'bar' => 'baz'), but later
* want to change the value of 'bar' from 'baz' to 'foobar', you cannot do so
* a targeted write like $object['foo']['bar'] = 'foobar'. Instead, you must
* overwrite the entire top-level 'foo' array with the entire set of new
* values: $object['foo'] = array(1, 2, 'bar' => 'foobar'). Due to this same
* limitation, attempts to create references to any contained data, nested or
* otherwise, will fail silently. So $var = &$object['foo'] will not throw an
* error, and $var will be populated with the contents of $object['foo'], but
* that data will be passed by value, not reference. For more information on
* the PHP limitation, see the note in the official PHP documentation at·
* http://php.net/manual/en/arrayaccess.offsetget.php on
* ArrayAccess::offsetGet().
*
* By default, the class accounts for caches where calling functions might
* request keys in the array that won't exist even after a cache rebuild. This
* prevents situations where a cache rebuild would be triggered over and over
* due to a 'missing' item. These cases are stored internally as a value of
* NULL. This means that the offsetGet() and offsetExists() methods
* must be overridden if caching an array where the top level values can
* legitimately be NULL, and where $object->offsetExists() needs to correctly
* return (equivalent to array_key_exists() vs. isset()). This should not
* be necessary in the majority of cases.
*
* Classes extending this class must override at least the
* resolveCacheMiss() method to have a working implementation.
*
* offsetSet() is not overridden by this class by default. In practice this
* means that assigning an offset via arrayAccess will only apply while the
* object is in scope and will not be written back to the persistent cache.
* This follows a similar pattern to static vs. persistent caching in
* procedural code. Extending classes may wish to alter this behaviour, for
* example by overriding offsetSet() and adding an automatic call to persist().
*
* @see SchemaCache
*/
abstract class DrupalCacheArray implements ArrayAccess {
/**
* A cid to pass to cache_set() and cache_get().
*/
protected $cid;
/**
* A bin to pass to cache_set() and cache_get().
*/
protected $bin;
/**
* An array of keys to add to the cache at the end of the request.
*/
protected $keysToPersist = array();
/**
* Storage for the data itself.
*/
protected $storage = array();
/**
* Constructs a DrupalCacheArray object.
*
* @param $cid
* The cid for the array being cached.
* @param $bin
* The bin to cache the array.
*/
public function __construct($cid, $bin) {
$this->cid = $cid;
$this->bin = $bin;
if ($cached = cache_get($this->cid, $this->bin)) {
$this->storage = $cached->data;
}
}
/**
* Implements ArrayAccess::offsetExists().
*/
public function offsetExists($offset) {
return $this->offsetGet($offset) !== NULL;
}
/**
* Implements ArrayAccess::offsetGet().
*/
public function offsetGet($offset) {
if (isset($this->storage[$offset]) || array_key_exists($offset, $this->storage)) {
return $this->storage[$offset];
}
else {
return $this->resolveCacheMiss($offset);
}
}
/**
* Implements ArrayAccess::offsetSet().
*/
public function offsetSet($offset, $value) {
$this->storage[$offset] = $value;
}
/**
* Implements ArrayAccess::offsetUnset().
*/
public function offsetUnset($offset) {
unset($this->storage[$offset]);
}
/**
* Flags an offset value to be written to the persistent cache.
*
* If a value is assigned to a cache object with offsetSet(), by default it
* will not be written to the persistent cache unless it is flagged with this
* method. This allows items to be cached for the duration of a request,
* without necessarily writing back to the persistent cache at the end.
*
* @param $offset
* The array offset that was request.
* @param $persist
* Optional boolean to specify whether the offset should be persisted or
* not, defaults to TRUE. When called with $persist = FALSE the offset will
* be unflagged so that it will not written at the end of the request.
*/
protected function persist($offset, $persist = TRUE) {
$this->keysToPersist[$offset] = $persist;
}
/**
* Resolves a cache miss.
* *
* @param name * When an offset is not found in the object, this is treated as a cache
* miss. This method allows classes implementing the interface to look up
* the actual value and allow it to be cached.
*
* @param $offset
* The offset that was requested.
*
* @return
* The value of the offset, or NULL if no value was found.
*/
abstract protected function resolveCacheMiss($offset);
/**
* Writes a value to the persistent cache immediately.
*
* @param $data
* The data to write to the persistent cache.
* @param $lock
* Whether to acquire a lock before writing to cache.
*/
protected function set($data, $lock = TRUE) {
// Lock cache writes to help avoid stampedes.
// To implement locking for cache misses, override __construct().
$lock_name = $this->cid . ':' . $this->bin;
if (!$lock || lock_acquire($lock_name)) {
if ($cached = cache_get($this->cid, $this->bin)) {
$data = $cached->data + $data;
}
cache_set($this->cid, $data, $this->bin);
if ($lock) {
lock_release($lock_name);
}
}
}
/**
* Destructs the DrupalCacheArray object.
*/
public function __destruct() {
$data = array();
foreach ($this->keysToPersist as $offset => $persist) {
if ($persist) {
$data[$offset] = $this->storage[$offset];
}
}
if (!empty($data)) {
$this->set($data);
}
}
}
/**
* Starts the timer with the specified name.
*
* If you start and stop the same timer multiple times, the measured intervals
* will be accumulated.
*
* @param $name
* The name of the timer. * The name of the timer.
*/ */
function timer_start($name) { function timer_start($name) {
...@@ -265,10 +446,11 @@ function timer_start($name) { ...@@ -265,10 +446,11 @@ function timer_start($name) {
} }
/** /**
* Read the current timer value without stopping the timer. * Reads the current timer value without stopping the timer.
* *
* @param name * @param $name
* The name of the timer. * The name of the timer.
*
* @return * @return
* The current timer value in ms. * The current timer value in ms.
*/ */
...@@ -288,10 +470,11 @@ function timer_read($name) { ...@@ -288,10 +470,11 @@ function timer_read($name) {
} }
/** /**
* Stop the timer with the specified name. * Stops the timer with the specified name.
* *
* @param name * @param $name
* The name of the timer. * The name of the timer.
*
* @return * @return
* A timer array. The array contains the number of times the timer has been * A timer array. The array contains the number of times the timer has been
* started and stopped (count) and the accumulated timer value in ms (time). * started and stopped (count) and the accumulated timer value in ms (time).
...@@ -315,50 +498,49 @@ function timer_stop($name) { ...@@ -315,50 +498,49 @@ function timer_stop($name) {
} }
/** /**
* Find the appropriate configuration directory. * Finds the appropriate configuration directory.
* *
* Try finding a matching configuration directory by stripping the website's * Finds a matching configuration directory by stripping the website's
* hostname from left to right and pathname from right to left. The first * hostname from left to right and pathname from right to left. The first
* configuration file found will be used; the remaining will ignored. If no * configuration file found will be used and the remaining ones will be ignored.
* configuration file is found, return a default value '$confdir/default'. * If no configuration file is found, return a default value '$confdir/default'.
* *
* Example for a fictitious site installed at * With a site located at http://www.example.com:8080/mysite/test/, the file,
* http://www.drupal.org:8080/mysite/test/ the 'settings.php' is searched in * settings.php, is searched for in the following directories:
* the following directories:
* *
* 1. $confdir/8080.www.drupal.org.mysite.test * - $confdir/8080.www.example.com.mysite.test
* 2. $confdir/www.drupal.org.mysite.test * - $confdir/www.example.com.mysite.test
* 3. $confdir/drupal.org.mysite.test * - $confdir/example.com.mysite.test
* 4. $confdir/org.mysite.test * - $confdir/com.mysite.test
* *
* 5. $confdir/8080.www.drupal.org.mysite * - $confdir/8080.www.example.com.mysite
* 6. $confdir/www.drupal.org.mysite * - $confdir/www.example.com.mysite
* 7. $confdir/drupal.org.mysite * - $confdir/example.com.mysite
* 8. $confdir/org.mysite * - $confdir/com.mysite
* *
* 9. $confdir/8080.www.drupal.org * - $confdir/8080.www.example.com
* 10. $confdir/www.drupal.org * - $confdir/www.example.com
* 11. $confdir/drupal.org * - $confdir/example.com
* 12. $confdir/org * - $confdir/com
* *
* 13. $confdir/default * - $confdir/default
* *
* If a file named sites.php is present in the $confdir, it will be loaded * If a file named sites.php is present in the $confdir, it will be loaded
* prior to scanning for directories. It should define an associative array * prior to scanning for directories. It should define an associative array
* named $sites, which maps domains to directories. It should be in the form * named $sites, which maps domains to directories. It should be in the form
* of: * of:
* * @code
* $sites = array( * $sites = array(
* 'The url to alias' => 'A directory within the sites directory' * 'The url to alias' => 'A directory within the sites directory'
* ); * );
* * @endcode
* For example: * For example:
* * @code
* $sites = array( * $sites = array(
* 'devexample.com' => 'example.com', * 'devexample.com' => 'example.com',
* 'localhost.example' => 'example.com', * 'localhost.example' => 'example.com',
* ); * );
* * @endcode
* The above array will cause Drupal to look for a directory named * The above array will cause Drupal to look for a directory named
* "example.com" in the sites directory whenever a request comes from * "example.com" in the sites directory whenever a request comes from
* "example.com", "devexample.com", or "localhost/example". That is useful * "example.com", "devexample.com", or "localhost/example". That is useful
...@@ -367,14 +549,15 @@ function timer_stop($name) { ...@@ -367,14 +549,15 @@ function timer_stop($name) {
* (files, system table, etc.) this will ensure the paths are correct while * (files, system table, etc.) this will ensure the paths are correct while
* accessed on development servers. * accessed on development servers.
* *
* @param $require_settings * @param bool $require_settings
* Only configuration directories with an existing settings.php file * Only configuration directories with an existing settings.php file
* will be recognized. Defaults to TRUE. During initial installation, * will be recognized. Defaults to TRUE. During initial installation,
* this is set to FALSE so that Drupal can detect a matching directory, * this is set to FALSE so that Drupal can detect a matching directory,
* then create a new settings.php file in it. * then create a new settings.php file in it.
* @param reset * @param bool $reset
* Force a full search for matching directories even if one had been * Force a full search for matching directories even if one had been
* found previously. * found previously. Defaults to FALSE.
*
* @return * @return
* The path of the matching directory. * The path of the matching directory.
*/ */
...@@ -388,6 +571,9 @@ function conf_path($require_settings = TRUE, $reset = FALSE) { ...@@ -388,6 +571,9 @@ function conf_path($require_settings = TRUE, $reset = FALSE) {
$confdir = 'sites'; $confdir = 'sites';
$sites = array(); $sites = array();
// UNL Change
$default_domains = array();
// End UNL Change
if (file_exists(DRUPAL_ROOT . '/' . $confdir . '/sites.php')) { if (file_exists(DRUPAL_ROOT . '/' . $confdir . '/sites.php')) {
// This will overwrite $sites with the desired mappings. // This will overwrite $sites with the desired mappings.
include(DRUPAL_ROOT . '/' . $confdir . '/sites.php'); include(DRUPAL_ROOT . '/' . $confdir . '/sites.php');
...@@ -398,6 +584,21 @@ function conf_path($require_settings = TRUE, $reset = FALSE) { ...@@ -398,6 +584,21 @@ function conf_path($require_settings = TRUE, $reset = FALSE) {
for ($i = count($uri) - 1; $i > 0; $i--) { for ($i = count($uri) - 1; $i > 0; $i--) {
for ($j = count($server); $j > 0; $j--) { for ($j = count($server); $j > 0; $j--) {
$dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i)); $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i));
// UNL Change
// Since we're truncating site_dir domains to just unl.edu, we need to skip any site_dir that
// Starts with "unl.edu" unless we're on the default site's domain (ie: unlcms.unl.edu)
if (substr($dir, 0, 7) == 'unl.edu' && count($default_domains) > 0) {
$is_primary_domain = FALSE;
foreach ($default_domains as $default_domain) {
if (substr($_SERVER['HTTP_HOST'], 0, strlen($default_domain)) == $default_domain) {
$is_primary_domain = TRUE;
}
}
if (!$is_primary_domain) {
continue;
}
}
// End UNL Change
if (isset($sites[$dir]) && file_exists(DRUPAL_ROOT . '/' . $confdir . '/' . $sites[$dir])) { if (isset($sites[$dir]) && file_exists(DRUPAL_ROOT . '/' . $confdir . '/' . $sites[$dir])) {
$dir = $sites[$dir]; $dir = $sites[$dir];
} }
...@@ -412,7 +613,7 @@ function conf_path($require_settings = TRUE, $reset = FALSE) { ...@@ -412,7 +613,7 @@ function conf_path($require_settings = TRUE, $reset = FALSE) {
} }
/** /**
* Set appropriate server variables needed for command line scripts to work. * Sets appropriate server variables needed for command line scripts to work.
* *
* This function can be called by command line scripts before bootstrapping * This function can be called by command line scripts before bootstrapping
* Drupal, to ensure that the page loads with the desired server parameters. * Drupal, to ensure that the page loads with the desired server parameters.
...@@ -446,21 +647,23 @@ function conf_path($require_settings = TRUE, $reset = FALSE) { ...@@ -446,21 +647,23 @@ function conf_path($require_settings = TRUE, $reset = FALSE) {
* @see ip_address() * @see ip_address()
*/ */
function drupal_override_server_variables($variables = array()) { function drupal_override_server_variables($variables = array()) {
// Set defaults based on the provided URL. // Allow the provided URL to override any existing values in $_SERVER.
if (isset($variables['url'])) { if (isset($variables['url'])) {
$url = parse_url($variables['url']); $url = parse_url($variables['url']);
unset($variables['url']); if (isset($url['host'])) {
$_SERVER['HTTP_HOST'] = $url['host'];
} }
else { if (isset($url['path'])) {
$url = array(); $_SERVER['SCRIPT_NAME'] = $url['path'];
} }
$url += array( unset($variables['url']);
'path' => '', }
'host' => 'localhost', // Define default values for $_SERVER keys. These will be used if $_SERVER
); // does not already define them and no other values are passed in to this
// function.
$defaults = array( $defaults = array(
'HTTP_HOST' => $url['host'], 'HTTP_HOST' => 'localhost',
'SCRIPT_NAME' => $url['path'], 'SCRIPT_NAME' => NULL,
'REMOTE_ADDR' => '127.0.0.1', 'REMOTE_ADDR' => '127.0.0.1',
'REQUEST_METHOD' => 'GET', 'REQUEST_METHOD' => 'GET',
'SERVER_NAME' => NULL, 'SERVER_NAME' => NULL,
...@@ -472,7 +675,7 @@ function drupal_override_server_variables($variables = array()) { ...@@ -472,7 +675,7 @@ function drupal_override_server_variables($variables = array()) {
} }
/** /**
* Initialize PHP environment. * Initializes the PHP environment.
*/ */
function drupal_environment_initialize() { function drupal_environment_initialize() {
if (!isset($_SERVER['HTTP_REFERER'])) { if (!isset($_SERVER['HTTP_REFERER'])) {
...@@ -513,8 +716,6 @@ function drupal_environment_initialize() { ...@@ -513,8 +716,6 @@ function drupal_environment_initialize() {
// sites/default/default.settings.php contains more runtime settings. // sites/default/default.settings.php contains more runtime settings.
// The .htaccess file contains settings that cannot be changed at runtime. // The .htaccess file contains settings that cannot be changed at runtime.
// Prevent PHP from generating HTML error messages.
ini_set('html_errors', 0);
// Don't escape quotes when reading files from the database, disk, etc. // Don't escape quotes when reading files from the database, disk, etc.
ini_set('magic_quotes_runtime', '0'); ini_set('magic_quotes_runtime', '0');
// Use session cookies, not transparent sessions that puts the session id in // Use session cookies, not transparent sessions that puts the session id in
...@@ -533,7 +734,7 @@ function drupal_environment_initialize() { ...@@ -533,7 +734,7 @@ function drupal_environment_initialize() {
} }
/** /**
* Validate that a hostname (for example $_SERVER['HTTP_HOST']) is safe. * Validates that a hostname (for example $_SERVER['HTTP_HOST']) is safe.
* *
* @return * @return
* TRUE if only containing valid characters, or FALSE otherwise. * TRUE if only containing valid characters, or FALSE otherwise.
...@@ -543,8 +744,7 @@ function drupal_valid_http_host($host) { ...@@ -543,8 +744,7 @@ function drupal_valid_http_host($host) {
} }
/** /**
* Loads the configuration and sets the base URL, cookie domain, and * Sets the base URL, cookie domain, and session name from configuration.
* session name correctly.
*/ */
function drupal_settings_initialize() { function drupal_settings_initialize() {
global $base_url, $base_path, $base_root; global $base_url, $base_path, $base_root;
...@@ -553,6 +753,12 @@ function drupal_settings_initialize() { ...@@ -553,6 +753,12 @@ function drupal_settings_initialize() {
global $databases, $cookie_domain, $conf, $installed_profile, $update_free_access, $db_url, $db_prefix, $drupal_hash_salt, $is_https, $base_secure_url, $base_insecure_url; global $databases, $cookie_domain, $conf, $installed_profile, $update_free_access, $db_url, $db_prefix, $drupal_hash_salt, $is_https, $base_secure_url, $base_insecure_url;
$conf = array(); $conf = array();
// UNL change: include a "global" settings file that applies to all sites.
if (file_exists(DRUPAL_ROOT . '/sites/all/settings.php')) {
include_once DRUPAL_ROOT . '/sites/all/settings.php';
}
// End UNL change.
if (file_exists(DRUPAL_ROOT . '/' . conf_path() . '/settings.php')) { if (file_exists(DRUPAL_ROOT . '/' . conf_path() . '/settings.php')) {
include_once DRUPAL_ROOT . '/' . conf_path() . '/settings.php'; include_once DRUPAL_ROOT . '/' . conf_path() . '/settings.php';
} }
...@@ -630,9 +836,10 @@ function drupal_settings_initialize() { ...@@ -630,9 +836,10 @@ function drupal_settings_initialize() {
} }
/** /**
* Returns and optionally sets the filename for a system item (module, * Returns and optionally sets the filename for a system resource.
* theme, etc.). The filename, whether provided, cached, or retrieved *
* from the database, is only returned if the file exists. * The filename, whether provided, cached, or retrieved from the database, is
* only returned if the file exists.
* *
* This function plays a key role in allowing Drupal's resources (modules * This function plays a key role in allowing Drupal's resources (modules
* and themes) to be located in different places depending on a site's * and themes) to be located in different places depending on a site's
...@@ -660,8 +867,13 @@ function drupal_settings_initialize() { ...@@ -660,8 +867,13 @@ function drupal_settings_initialize() {
function drupal_get_filename($type, $name, $filename = NULL) { function drupal_get_filename($type, $name, $filename = NULL) {
// The location of files will not change during the request, so do not use // The location of files will not change during the request, so do not use
// drupal_static(). // drupal_static().
static $files = array(); static $files = array(), $dirs = array();
// Profiles are a special case: they have a fixed location and naming.
if ($type == 'profile') {
$profile_filename = "profiles/$name/$name.profile";
$files[$type][$name] = file_exists($profile_filename) ? $profile_filename : FALSE;
}
if (!isset($files[$type])) { if (!isset($files[$type])) {
$files[$type] = array(); $files[$type] = array();
} }
...@@ -706,6 +918,8 @@ function drupal_get_filename($type, $name, $filename = NULL) { ...@@ -706,6 +918,8 @@ function drupal_get_filename($type, $name, $filename = NULL) {
$extension = $type; $extension = $type;
} }
if (!isset($dirs[$dir][$extension])) {
$dirs[$dir][$extension] = TRUE;
if (!function_exists('drupal_system_listing')) { if (!function_exists('drupal_system_listing')) {
require_once DRUPAL_ROOT . '/includes/common.inc'; require_once DRUPAL_ROOT . '/includes/common.inc';
} }
...@@ -713,12 +927,13 @@ function drupal_get_filename($type, $name, $filename = NULL) { ...@@ -713,12 +927,13 @@ function drupal_get_filename($type, $name, $filename = NULL) {
// extension, not just the file we are currently looking for. This // extension, not just the file we are currently looking for. This
// prevents unnecessary scans from being repeated when this function is // prevents unnecessary scans from being repeated when this function is
// called more than once in the same page request. // called more than once in the same page request.
$matches = drupal_system_listing("/\.$extension$/", $dir, 'name', 0); $matches = drupal_system_listing("/^" . DRUPAL_PHP_FUNCTION_PATTERN . "\.$extension$/", $dir, 'name', 0);
foreach ($matches as $matched_name => $file) { foreach ($matches as $matched_name => $file) {
$files[$type][$matched_name] = $file->uri; $files[$type][$matched_name] = $file->uri;
} }
} }
} }
}
if (isset($files[$type][$name])) { if (isset($files[$type][$name])) {
return $files[$type][$name]; return $files[$type][$name];
...@@ -726,11 +941,11 @@ function drupal_get_filename($type, $name, $filename = NULL) { ...@@ -726,11 +941,11 @@ function drupal_get_filename($type, $name, $filename = NULL) {
} }
/** /**
* Load the persistent variable table. * Loads the persistent variable table.
* *
* The variable table is composed of values that have been saved in the table * The variable table is composed of values that have been saved in the table
* with variable_set() as well as those explicitly specified in the configuration * with variable_set() as well as those explicitly specified in the
* file. * configuration file.
*/ */
function variable_initialize($conf = array()) { function variable_initialize($conf = array()) {
// NOTE: caching the variables improves performance by 20% when serving // NOTE: caching the variables improves performance by 20% when serving
...@@ -837,7 +1052,7 @@ function variable_del($name) { ...@@ -837,7 +1052,7 @@ function variable_del($name) {
} }
/** /**
* Retrieve the current page from the cache. * Retrieves the current page from the cache.
* *
* Note: we do not serve cached pages to authenticated users, or to anonymous * Note: we do not serve cached pages to authenticated users, or to anonymous
* users when $_SESSION is non-empty. $_SESSION may contain status messages * users when $_SESSION is non-empty. $_SESSION may contain status messages
...@@ -869,7 +1084,7 @@ function drupal_page_get_cache($check_only = FALSE) { ...@@ -869,7 +1084,7 @@ function drupal_page_get_cache($check_only = FALSE) {
} }
/** /**
* Determine the cacheability of the current page. * Determines the cacheability of the current page.
* *
* @param $allow_caching * @param $allow_caching
* Set to FALSE if you want to prevent this page to get cached. * Set to FALSE if you want to prevent this page to get cached.
...@@ -888,7 +1103,7 @@ function drupal_page_is_cacheable($allow_caching = NULL) { ...@@ -888,7 +1103,7 @@ function drupal_page_is_cacheable($allow_caching = NULL) {
} }
/** /**
* Invoke a bootstrap hook in all bootstrap modules that implement it. * Invokes a bootstrap hook in all bootstrap modules that implement it.
* *
* @param $hook * @param $hook
* The name of the bootstrap hook to invoke. * The name of the bootstrap hook to invoke.
...@@ -897,7 +1112,12 @@ function drupal_page_is_cacheable($allow_caching = NULL) { ...@@ -897,7 +1112,12 @@ function drupal_page_is_cacheable($allow_caching = NULL) {
*/ */
function bootstrap_invoke_all($hook) { function bootstrap_invoke_all($hook) {
// Bootstrap modules should have been loaded when this function is called, so // 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. // we don't need to tell module_list() to reset its internal list (and we
// therefore leave the first parameter at its default value of FALSE). We
// still pass in TRUE for the second parameter, though; in case this is the
// first time during the bootstrap that module_list() is called, we want to
// make sure that its internal cache is primed with the bootstrap modules
// only.
foreach (module_list(FALSE, TRUE) as $module) { foreach (module_list(FALSE, TRUE) as $module) {
drupal_load('module', $module); drupal_load('module', $module);
module_invoke($module, $hook); module_invoke($module, $hook);
...@@ -905,8 +1125,9 @@ function bootstrap_invoke_all($hook) { ...@@ -905,8 +1125,9 @@ function bootstrap_invoke_all($hook) {
} }
/** /**
* Includes a file with the provided type and name. This prevents * Includes a file with the provided type and name.
* including a theme, engine, module, etc., more than once. *
* This prevents including a theme, engine, module, etc., more than once.
* *
* @param $type * @param $type
* The type of item to load (i.e. theme, theme_engine, module). * The type of item to load (i.e. theme, theme_engine, module).
...@@ -938,7 +1159,7 @@ function drupal_load($type, $name) { ...@@ -938,7 +1159,7 @@ function drupal_load($type, $name) {
} }
/** /**
* Set an HTTP response header for the current page. * Sets an HTTP response header for the current page.
* *
* Note: When sending a Content-Type header, always include a 'charset' type, * Note: When sending a Content-Type header, always include a 'charset' type,
* too. This is necessary to avoid security bugs (e.g. UTF-7 XSS). * too. This is necessary to avoid security bugs (e.g. UTF-7 XSS).
...@@ -974,11 +1195,12 @@ function drupal_add_http_header($name, $value, $append = FALSE) { ...@@ -974,11 +1195,12 @@ function drupal_add_http_header($name, $value, $append = FALSE) {
} }
/** /**
* Get the HTTP response headers for the current page. * Gets the HTTP response headers for the current page.
* *
* @param $name * @param $name
* An HTTP header name. If omitted, all headers are returned as name/value * An HTTP header name. If omitted, all headers are returned as name/value
* pairs. If an array value is FALSE, the header has been unset. * pairs. If an array value is FALSE, the header has been unset.
*
* @return * @return
* A string containing the header value, or FALSE if the header has been set, * A string containing the header value, or FALSE if the header has been set,
* or NULL if the header has not been set. * or NULL if the header has not been set.
...@@ -995,6 +1217,8 @@ function drupal_get_http_header($name = NULL) { ...@@ -995,6 +1217,8 @@ function drupal_get_http_header($name = NULL) {
} }
/** /**
* Sets the preferred name for the HTTP header.
*
* Header names are case-insensitive, but for maximum compatibility they should * Header names are case-insensitive, but for maximum compatibility they should
* follow "common form" (see RFC 2617, section 4.2). * follow "common form" (see RFC 2617, section 4.2).
*/ */
...@@ -1008,9 +1232,10 @@ function _drupal_set_preferred_header_name($name = NULL) { ...@@ -1008,9 +1232,10 @@ function _drupal_set_preferred_header_name($name = NULL) {
} }
/** /**
* Send the HTTP response headers previously set using drupal_add_http_header(). * Sends the HTTP response headers that were previously set, adding defaults.
* Add default headers, unless they have been replaced or unset using *
* drupal_add_http_header(). * Headers are set in drupal_add_http_header(). Default headers are not set
* if they have been replaced or unset using drupal_add_http_header().
* *
* @param $default_headers * @param $default_headers
* An array of headers as name/value pairs. * An array of headers as name/value pairs.
...@@ -1045,7 +1270,7 @@ function drupal_send_headers($default_headers = array(), $only_default = FALSE) ...@@ -1045,7 +1270,7 @@ function drupal_send_headers($default_headers = array(), $only_default = FALSE)
} }
/** /**
* Set HTTP headers in preparation for a page response. * Sets HTTP headers in preparation for a page response.
* *
* Authenticated users are always given a 'no-cache' header, and will fetch a * Authenticated users are always given a 'no-cache' header, and will fetch a
* fresh page on every request. This prevents authenticated users from seeing * fresh page on every request. This prevents authenticated users from seeing
...@@ -1088,7 +1313,7 @@ function drupal_page_header() { ...@@ -1088,7 +1313,7 @@ function drupal_page_header() {
} }
/** /**
* Set HTTP headers in preparation for a cached page response. * Sets HTTP headers in preparation for a cached page response.
* *
* The headers allow as much as possible in proxies and browsers without any * The headers allow as much as possible in proxies and browsers without any
* particular knowledge about the pages. Modules can override these headers * particular knowledge about the pages. Modules can override these headers
...@@ -1121,13 +1346,12 @@ function drupal_serve_page_from_cache(stdClass $cache) { ...@@ -1121,13 +1346,12 @@ function drupal_serve_page_from_cache(stdClass $cache) {
} }
} }
// If a cache is served from a HTTP proxy without hitting the web server, // If the client sent a session cookie, a cached copy will only be served
// the boot and exit hooks cannot be fired, so only allow caching in // to that one particular client due to Vary: Cookie. Thus, do not set
// proxies if boot hooks are disabled. If the client send a session cookie, // max-age > 0, allowing the page to be cached by external proxies, when a
// do not bother caching the page in a public proxy, because the cached copy // session cookie is present unless the Vary header has been replaced or
// will only be served to that particular user due to Vary: Cookie, unless // unset in hook_boot().
// the Vary header has been replaced or unset in hook_boot() (see below). $max_age = !isset($_COOKIE[session_name()]) || isset($hook_boot_headers['vary']) ? variable_get('page_cache_maximum_age', 0) : 0;
$max_age = !variable_get('page_cache_invoke_hooks', TRUE) && (!isset($_COOKIE[session_name()]) || isset($hook_boot_headers['vary'])) ? variable_get('page_cache_maximum_age', 0) : 0;
$default_headers['Cache-Control'] = 'public, max-age=' . $max_age; $default_headers['Cache-Control'] = 'public, max-age=' . $max_age;
// Entity tag should change if the output changes. // Entity tag should change if the output changes.
...@@ -1168,7 +1392,9 @@ function drupal_serve_page_from_cache(stdClass $cache) { ...@@ -1168,7 +1392,9 @@ function drupal_serve_page_from_cache(stdClass $cache) {
// revalidation. If a Vary header has been set in hook_boot(), it is assumed // revalidation. If a Vary header has been set in hook_boot(), it is assumed
// that the module knows how to cache the page. // that the module knows how to cache the page.
if (!isset($hook_boot_headers['vary']) && !variable_get('omit_vary_cookie')) { if (!isset($hook_boot_headers['vary']) && !variable_get('omit_vary_cookie')) {
header('Vary: Cookie'); // UNL Change!
drupal_add_http_header('Vary', 'Cookie', TRUE);
// End UNL Change!
} }
if ($page_compression) { if ($page_compression) {
...@@ -1192,7 +1418,7 @@ function drupal_serve_page_from_cache(stdClass $cache) { ...@@ -1192,7 +1418,7 @@ function drupal_serve_page_from_cache(stdClass $cache) {
} }
/** /**
* Define the critical hooks that force modules to always be loaded. * Defines the critical hooks that force modules to always be loaded.
*/ */
function bootstrap_hooks() { function bootstrap_hooks() {
return array('boot', 'exit', 'watchdog', 'language_init'); return array('boot', 'exit', 'watchdog', 'language_init');
...@@ -1220,185 +1446,59 @@ function drupal_unpack($obj, $field = 'data') { ...@@ -1220,185 +1446,59 @@ function drupal_unpack($obj, $field = 'data') {
/** /**
* Translates a string to the current language or to a given language. * Translates a string to the current language or to a given language.
* *
* All human-readable text that will be displayed on the site or sent to a user * The t() function serves two purposes. First, at run-time it translates
* should be passed through the t() function. This ensures that sites can be * user-visible text into the appropriate language. Second, various mechanisms
* fully translated into other languages. * that figure out what text needs to be translated work off t() -- the text
* * inside t() calls is added to the database of strings to be translated.
* Here are some examples of translating static text using t(): * These strings are expected to be in English, so the first argument should
* @code * always be in English. To enable a fully-translatable site, it is important
* if (!$info || !$info['extension']) { * that all human-readable text that will be displayed on the site or sent to
* form_set_error('picture_upload', t('The uploaded file was not an image.')); * a user is passed through the t() function, or a related function. See the
* } * @link http://drupal.org/node/322729 Localization API @endlink pages for
* * more information, including recommendations on how to break up or not
* $form['submit'] = array( * break up strings for translation.
* '#type' => 'submit', *
* '#value' => t('Log in'), * You should never use t() to translate variables, such as calling
* ); * @code t($text); @endcode, unless the text that the variable holds has been
* @endcode * passed through t() elsewhere (e.g., $text is one of several translated
* * literal strings in an array). It is especially important never to call
* In addition to translating static text, t() can handle text that should not * @code t($user_text); @endcode, where $user_text is some text that a user
* be translated or that might change from time to time (such as link paths) * entered - doing that can lead to cross-site scripting and other security
* and dynamic text from variables, using special "placeholders". There are * problems. However, you can use variable substitution in your string, to put
* three styles of placeholders: * variable text such as user names or link URLs into translated text. Variable
* - !variable: Indicates that the text should be inserted as-is. This is * substitution looks like this:
* useful for inserting variables into things like e-mail. Example:
* @code
* $message[] = t("If you don't want to receive such e-mails, you can change your settings at !url.", array('!url' => url("user/$account->uid", array('absolute' => TRUE))));
* @endcode
* - @variable: Indicates that the text should be run through check_plain(), to
* escape HTML characters. Use this for any output that is displayed within a
* Drupal page. Example:
* @code
* drupal_set_title($title = t("@name's blog", array('@name' => format_username($account))), PASS_THROUGH);
* @endcode
* - %variable: Indicates that the string should be HTML-escaped and highlighted
* with theme_placeholder(), which shows up by default as <em>emphasized</em>.
* @code
* $message = t('%name-from sent %name-to an e-mail.', array('%name-from' => format_username($user), '%name-to' => format_username($account)));
* @endcode
*
* When using t(), try to put entire paragraphs in one t() call. This makes it
* easier for translators, as it provides context as to what each word refers
* to (and also allows translators to adjust word order, which may not be the
* same in all languages). HTML markup within translation strings is allowed,
* but should be avoided if possible. The exception is embedded links: link
* titles add context for translators and need to be translated, so they should
* be kept in the main string, while link URLs should be generated using
* placeholders.
* - Incorrect HTML in t():
* @code
* $output .= t('<p>Go to the @contact-page.</p>', array('@contact-page' => l(t('contact page'), 'contact')));
* @endcode
* - Correct HTML in t():
* @code
* $output .= '<p>' . t('Go to the <a href="@contact-page">contact page</a>.', array('@contact-page' => url('contact'))) . '</p>';
* @endcode
*
* Another thing that is helpful is to avoid escaping quotation marks wherever
* possible, because it can be confusing to translation teams.
* - Less desirable quotation mark escaping:
* @code
* $output .= t('Don\'t click me.');
* @endcode
* - Better way to use quotation marks:
* @code
* $output .= t("Don't click me.");
* @endcode
*
* It is important that all translation uses the t() mechanism, because in
* addition to actually translating the text at run-time, the t() function is
* also used by text-extraction routines to find text that needs to be
* translated, and build databases of text to be translated for translation
* teams. For that reason, you must put the actual string into the t() function,
* in most cases, and not a variable.
* - Incorrect use of a variable in t():
* @code
* $message = 'An error occurred.';
* drupal_set_message(t($message), 'error');
* $output .= t($message);
* @endcode
* - Correct translation of a variable with t():
* @code * @code
* $message = t('An error occurred.'); * $text = t("@name's blog", array('@name' => format_username($account)));
* drupal_set_message($message, 'error');
* $output .= $message;
* @endcode * @endcode
* Basically, you can put variables like @name into your string, and t() will
* substitute their sanitized values at translation time. (See the
* Localization API pages referenced above and the documentation of
* format_string() for details.) Translators can then rearrange the string as
* necessary for the language (e.g., in Spanish, it might be "blog de @name").
* *
* The only case in which variables can be passed safely through t() is when * During the Drupal installation phase, some resources used by t() wil not be
* code-based versions of the same strings will be passed through t() (or * available to code that needs localization. See st() and get_t() for
* otherwise extracted) elsewhere. * alternatives.
*
* Also, you cannot use t() early in the bootstrap process, prior to the
* DRUPAL_BOOTSTRAP_LANGUAGE phase. The language variables will not be
* initialized yet, so the string will not be translated into the correct
* language. Examples of places where t() cannot be used include:
* - In a PHP define() statement.
* - In a hook_boot() implementation.
*
* In some cases, modules may include strings in code that can't use t()
* calls. For example, a module may use an external PHP application that
* produces strings that are loaded into variables in Drupal for output.
* In these cases, module authors may include a dummy file that passes the
* relevant strings through t(). This approach will allow the strings to be
* extracted.
*
* Sample external (non-Drupal) code:
* @code
* class Time {
* public $yesterday = 'Yesterday';
* public $today = 'Today';
* public $tomorrow = 'Tomorrow';
* }
* @endcode
*
* Sample dummy file:
* @code
* // Dummy function included in example.potx.inc.
* function example_potx() {
* $strings = array(
* t('Yesterday'),
* t('Today'),
* t('Tomorrow'),
* );
* // No return value needed, since this is a dummy function.
* }
* @endcode
*
* Having passed strings through t() in a dummy function, it is then
* possible to pass variables through t():
* @code
* $time = new Time();
* $output .= t($time->today);
* @endcode
*
* However tempting it is, custom data from user input or other non-code
* sources should not be passed through t(). Doing so leads to the following
* problems and errors:
* - The t() system doesn't support updates to existing strings. When user
* data is updated, the next time it's passed through t(), a new record is
* created instead of an update. The database bloats over time and any
* existing translations are orphaned with each update.
* - The t() system assumes any data it receives is in English. User data may
* be in another language, producing translation errors.
* - The "Built-in interface" text group in the locale system is used to
* produce translations for storage in .po files. When non-code strings are
* passed through t(), they are added to this text group, which is rendered
* inaccurate since it is a mix of actual interface strings and various user
* input strings of uncertain origin.
* Instead, translation of these data can be done through the locale system,
* either directly through hook_local() or through helper functions provided by
* contributed modules.
*
* Incorrect:
* @code
* $item = item_load();
* $output .= check_plain(t($item['title']));
* @endcode
*
* During installation, st() is used in place of t(). Code that may be called
* during installation or during normal operation should use the get_t()
* helper function.
* *
* @param $string * @param $string
* A string containing the English string to translate. * A string containing the English string to translate.
* @param $args * @param $args
* An associative array of replacements to make after translation. Incidences * An associative array of replacements to make after translation. Based
* of any key in this array are replaced with the corresponding value. Based * on the first character of the key, the value is escaped and/or themed.
* on the first character of the key, the value is escaped and/or themed: * See format_string() for details.
* - !variable: inserted as is
* - @variable: escape plain text to HTML (using check_plain())
* - %variable: escape text and theme as a placeholder for user-submitted
* content (using check_plain() + theme_placeholder())
* @param $options * @param $options
* An associative array of additional options, with the following keys: * An associative array of additional options, with the following elements:
* - 'langcode' (defaults to the current language) The language code to * - 'langcode' (defaults to the current language): The language code to
* translate to a language other than what is used to display the page. * translate to a language other than what is used to display the page.
* - 'context' (defaults to the empty context) The context the source string * - 'context' (defaults to the empty context): The context the source string
* belongs to. * belongs to.
* *
* @return * @return
* The translated string. * The translated string.
* *
* @see st()
* @see get_t()
* @see format_string()
* @ingroup sanitization * @ingroup sanitization
*/ */
function t($string, array $args = array(), array $options = array()) { function t($string, array $args = array(), array $options = array()) {
...@@ -1425,13 +1525,38 @@ function t($string, array $args = array(), array $options = array()) { ...@@ -1425,13 +1525,38 @@ function t($string, array $args = array(), array $options = array()) {
$string = $custom_strings[$options['langcode']][$options['context']][$string]; $string = $custom_strings[$options['langcode']][$options['context']][$string];
} }
// Translate with locale module if enabled. // Translate with locale module if enabled.
elseif (function_exists('locale') && $options['langcode'] != 'en') { elseif ($options['langcode'] != 'en' && function_exists('locale')) {
$string = locale($string, $options['context'], $options['langcode']); $string = locale($string, $options['context'], $options['langcode']);
} }
if (empty($args)) { if (empty($args)) {
return $string; return $string;
} }
else { else {
return format_string($string, $args);
}
}
/**
* Replaces placeholders with sanitized values in a string.
*
* @param $string
* A string containing placeholders.
* @param $args
* An associative array of replacements to make. Occurrences in $string of
* any key in $args are replaced with the corresponding value, after
* sanitization. The sanitization function depends on the first character of
* the key:
* - !variable: Inserted as is. Use this for text that has already been
* sanitized.
* - @variable: Escaped to HTML using check_plain(). Use this for anything
* displayed on a page on the site.
* - %variable: Escaped as a placeholder for user-submitted content using
* drupal_placeholder(), which shows up as <em>emphasized</em> text.
*
* @see t()
* @ingroup sanitization
*/
function format_string($string, array $args = array()) {
// Transform arguments before inserting them. // Transform arguments before inserting them.
foreach ($args as $key => $value) { foreach ($args as $key => $value) {
switch ($key[0]) { switch ($key[0]) {
...@@ -1452,10 +1577,9 @@ function t($string, array $args = array(), array $options = array()) { ...@@ -1452,10 +1577,9 @@ function t($string, array $args = array(), array $options = array()) {
} }
return strtr($string, $args); return strtr($string, $args);
} }
}
/** /**
* Encode special characters in a plain-text string for display as HTML. * Encodes special characters in a plain-text string for display as HTML.
* *
* Also validates strings as UTF-8 to prevent cross site scripting attacks on * Also validates strings as UTF-8 to prevent cross site scripting attacks on
* Internet Explorer 6. * Internet Explorer 6.
...@@ -1494,6 +1618,7 @@ function check_plain($text) { ...@@ -1494,6 +1618,7 @@ function check_plain($text) {
* *
* @param $text * @param $text
* The text to check. * The text to check.
*
* @return * @return
* TRUE if the text is valid UTF-8, FALSE if not. * TRUE if the text is valid UTF-8, FALSE if not.
*/ */
...@@ -1508,11 +1633,12 @@ function drupal_validate_utf8($text) { ...@@ -1508,11 +1633,12 @@ function drupal_validate_utf8($text) {
} }
/** /**
* Since $_SERVER['REQUEST_URI'] is only available on Apache, we * Returns the equivalent of Apache's $_SERVER['REQUEST_URI'] variable.
* generate an equivalent using other environment variables. *
* Because $_SERVER['REQUEST_URI'] is only available on Apache, we generate an
* equivalent using other environment variables.
*/ */
function request_uri() { function request_uri() {
if (isset($_SERVER['REQUEST_URI'])) { if (isset($_SERVER['REQUEST_URI'])) {
$uri = $_SERVER['REQUEST_URI']; $uri = $_SERVER['REQUEST_URI'];
} }
...@@ -1534,7 +1660,7 @@ function request_uri() { ...@@ -1534,7 +1660,7 @@ function request_uri() {
} }
/** /**
* Log an exception. * Logs an exception.
* *
* This is a wrapper function for watchdog() which automatically decodes an * This is a wrapper function for watchdog() which automatically decodes an
* exception. * exception.
...@@ -1545,7 +1671,7 @@ function request_uri() { ...@@ -1545,7 +1671,7 @@ function request_uri() {
* The exception that is going to be logged. * The exception that is going to be logged.
* @param $message * @param $message
* The message to store in the log. If empty, a text that contains all useful * The message to store in the log. If empty, a text that contains all useful
* information about the passed in exception is used. * information about the passed-in exception is used.
* @param $variables * @param $variables
* Array of variables to replace in the message on display. Defaults to the * Array of variables to replace in the message on display. Defaults to the
* return value of drupal_decode_exception(). * return value of drupal_decode_exception().
...@@ -1561,7 +1687,8 @@ function watchdog_exception($type, Exception $exception, $message = NULL, $varia ...@@ -1561,7 +1687,8 @@ function watchdog_exception($type, Exception $exception, $message = NULL, $varia
// Use a default value if $message is not set. // Use a default value if $message is not set.
if (empty($message)) { if (empty($message)) {
$message = '%type: %message in %function (line %line of %file).'; // The exception message is run through check_plain() by _drupal_decode_exception().
$message = '%type: !message in %function (line %line of %file).';
} }
// $variables must be an array so that we can add the exception information. // $variables must be an array so that we can add the exception information.
if (!is_array($variables)) { if (!is_array($variables)) {
...@@ -1574,7 +1701,7 @@ function watchdog_exception($type, Exception $exception, $message = NULL, $varia ...@@ -1574,7 +1701,7 @@ function watchdog_exception($type, Exception $exception, $message = NULL, $varia
} }
/** /**
* Log a system message. * Logs a system message.
* *
* @param $type * @param $type
* The category to which this message belongs. Can be any string, but the * The category to which this message belongs. Can be any string, but the
...@@ -1634,14 +1761,14 @@ function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NO ...@@ -1634,14 +1761,14 @@ function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NO
} }
/** /**
* Set a message which reflects the status of the performed operation. * Sets a message which reflects the status of the performed operation.
* *
* If the function is called with no arguments, this function returns all set * If the function is called with no arguments, this function returns all set
* messages without clearing them. * messages without clearing them.
* *
* @param $message * @param $message
* The message should begin with a capital letter and always ends with a * The message to be displayed to the user. For consistency with other
* period '.'. * messages, it should begin with a capital letter and end with a period.
* @param $type * @param $type
* The type of the message. One of the following values are possible: * The type of the message. One of the following values are possible:
* - 'status' * - 'status'
...@@ -1670,12 +1797,13 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = TRUE) { ...@@ -1670,12 +1797,13 @@ function drupal_set_message($message = NULL, $type = 'status', $repeat = TRUE) {
} }
/** /**
* Return all messages that have been set. * Returns all messages that have been set.
* *
* @param $type * @param $type
* (optional) Only return messages of this type. * (optional) Only return messages of this type.
* @param $clear_queue * @param $clear_queue
* (optional) Set to FALSE if you do not want to clear the messages queue * (optional) Set to FALSE if you do not want to clear the messages queue
*
* @return * @return
* An associative array, the key is the message type, the value an array * An associative array, the key is the message type, the value an array
* of messages. If the $type parameter is passed, you get only that type, * of messages. If the $type parameter is passed, you get only that type,
...@@ -1703,7 +1831,9 @@ function drupal_get_messages($type = NULL, $clear_queue = TRUE) { ...@@ -1703,7 +1831,9 @@ function drupal_get_messages($type = NULL, $clear_queue = TRUE) {
} }
/** /**
* Get the title of the current page, for display on the page and in the title bar. * Gets the title of the current page.
*
* The title is displayed on the page and in the title bar.
* *
* @return * @return
* The current page's title. * The current page's title.
...@@ -1720,7 +1850,9 @@ function drupal_get_title() { ...@@ -1720,7 +1850,9 @@ function drupal_get_title() {
} }
/** /**
* Set the title of the current page, for display on the page and in the title bar. * Sets the title of the current page.
*
* The title is displayed on the page and in the title bar.
* *
* @param $title * @param $title
* Optional string value to assign to the page title; or if set to NULL * Optional string value to assign to the page title; or if set to NULL
...@@ -1745,7 +1877,7 @@ function drupal_set_title($title = NULL, $output = CHECK_PLAIN) { ...@@ -1745,7 +1877,7 @@ function drupal_set_title($title = NULL, $output = CHECK_PLAIN) {
} }
/** /**
* Check to see if an IP address has been blocked. * Checks to see if an IP address has been blocked.
* *
* Blocked IP addresses are stored in the database by default. However for * Blocked IP addresses are stored in the database by default. However for
* performance reasons we allow an override in settings.php. This allows us * performance reasons we allow an override in settings.php. This allows us
...@@ -1754,6 +1886,7 @@ function drupal_set_title($title = NULL, $output = CHECK_PLAIN) { ...@@ -1754,6 +1886,7 @@ function drupal_set_title($title = NULL, $output = CHECK_PLAIN) {
* *
* @param $ip * @param $ip
* IP address to check. * IP address to check.
*
* @return bool * @return bool
* TRUE if access is denied, FALSE if access is allowed. * TRUE if access is denied, FALSE if access is allowed.
*/ */
...@@ -1779,7 +1912,7 @@ function drupal_is_denied($ip) { ...@@ -1779,7 +1912,7 @@ function drupal_is_denied($ip) {
} }
/** /**
* Handle denied users. * Handles denied users.
* *
* @param $ip * @param $ip
* IP address to check. Prints a message and exits if access is denied. * IP address to check. Prints a message and exits if access is denied.
...@@ -1798,7 +1931,8 @@ function drupal_block_denied($ip) { ...@@ -1798,7 +1931,8 @@ function drupal_block_denied($ip) {
* *
* This function is better than simply calling mt_rand() or any other built-in * This function is better than simply calling mt_rand() or any other built-in
* PHP function because it can return a long string of bytes (compared to < 4 * PHP function because it can return a long string of bytes (compared to < 4
* bytes normally from mt_rand()) and uses the best available pseudo-random source. * bytes normally from mt_rand()) and uses the best available pseudo-random
* source.
* *
* @param $count * @param $count
* The number of characters (bytes) to return in the string. * The number of characters (bytes) to return in the string.
...@@ -1845,7 +1979,7 @@ function drupal_random_bytes($count) { ...@@ -1845,7 +1979,7 @@ function drupal_random_bytes($count) {
} }
/** /**
* Calculate a base-64 encoded, URL-safe sha-256 hmac. * Calculates a base-64 encoded, URL-safe sha-256 hmac.
* *
* @param $data * @param $data
* String to be validated with the hmac. * String to be validated with the hmac.
...@@ -1863,7 +1997,7 @@ function drupal_hmac_base64($data, $key) { ...@@ -1863,7 +1997,7 @@ function drupal_hmac_base64($data, $key) {
} }
/** /**
* Calculate a base-64 encoded, URL-safe sha-256 hash. * Calculates a base-64 encoded, URL-safe sha-256 hash.
* *
* @param $data * @param $data
* String to be hashed. * String to be hashed.
...@@ -1878,6 +2012,80 @@ function drupal_hash_base64($data) { ...@@ -1878,6 +2012,80 @@ function drupal_hash_base64($data) {
return strtr($hash, array('+' => '-', '/' => '_', '=' => '')); return strtr($hash, array('+' => '-', '/' => '_', '=' => ''));
} }
/**
* Merges multiple arrays, recursively, and returns the merged array.
*
* This function is similar to PHP's array_merge_recursive() function, but it
* handles non-array values differently. When merging values that are not both
* arrays, the latter value replaces the former rather than merging with it.
*
* Example:
* @code
* $link_options_1 = array('fragment' => 'x', 'attributes' => array('title' => t('X'), 'class' => array('a', 'b')));
* $link_options_2 = array('fragment' => 'y', 'attributes' => array('title' => t('Y'), 'class' => array('c', 'd')));
*
* // This results in array('fragment' => array('x', 'y'), 'attributes' => array('title' => array(t('X'), t('Y')), 'class' => array('a', 'b', 'c', 'd'))).
* $incorrect = array_merge_recursive($link_options_1, $link_options_2);
*
* // This results in array('fragment' => 'y', 'attributes' => array('title' => t('Y'), 'class' => array('a', 'b', 'c', 'd'))).
* $correct = drupal_array_merge_deep($link_options_1, $link_options_2);
* @endcode
*
* @param ...
* Arrays to merge.
*
* @return
* The merged array.
*
* @see drupal_array_merge_deep_array()
*/
function drupal_array_merge_deep() {
$args = func_get_args();
return drupal_array_merge_deep_array($args);
}
/**
* Merges multiple arrays, recursively, and returns the merged array.
*
* This function is equivalent to drupal_array_merge_deep(), except the
* input arrays are passed as a single array parameter rather than a variable
* parameter list.
*
* The following are equivalent:
* - drupal_array_merge_deep($a, $b);
* - drupal_array_merge_deep_array(array($a, $b));
*
* The following are also equivalent:
* - call_user_func_array('drupal_array_merge_deep', $arrays_to_merge);
* - drupal_array_merge_deep_array($arrays_to_merge);
*
* @see drupal_array_merge_deep()
*/
function drupal_array_merge_deep_array($arrays) {
$result = array();
foreach ($arrays as $array) {
foreach ($array as $key => $value) {
// Renumber integer keys as array_merge_recursive() does. Note that PHP
// automatically converts array keys that are integer strings (e.g., '1')
// to integers.
if (is_integer($key)) {
$result[] = $value;
}
// Recurse when both values are arrays.
elseif (isset($result[$key]) && is_array($result[$key]) && is_array($value)) {
$result[$key] = drupal_array_merge_deep_array(array($result[$key], $value));
}
// Otherwise, use the latter value, overriding any previous value.
else {
$result[$key] = $value;
}
}
}
return $result;
}
/** /**
* Generates a default anonymous $user object. * Generates a default anonymous $user object.
* *
...@@ -1894,20 +2102,22 @@ function drupal_anonymous_user() { ...@@ -1894,20 +2102,22 @@ function drupal_anonymous_user() {
} }
/** /**
* A string describing a phase of Drupal to load. Each phase adds to the * Ensures Drupal is bootstrapped to the specified phase.
* previous one, so invoking a later phase automatically runs the earlier *
* phases too. The most important usage is that if you want to access the * The bootstrap phase is an integer constant identifying a phase of Drupal
* Drupal database from a script without loading anything else, you can * to load. Each phase adds to the previous one, so invoking a later phase
* include bootstrap.inc, and call drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE). * automatically runs the earlier phases as well. To access the Drupal
* database from a script without loading anything else, include bootstrap.inc
* and call drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE).
* *
* @param $phase * @param $phase
* A constant. Allowed values are the DRUPAL_BOOTSTRAP_* constants. * A constant. Allowed values are the DRUPAL_BOOTSTRAP_* constants.
* @param $new_phase * @param $new_phase
* A boolean, set to FALSE if calling drupal_bootstrap from inside a * A boolean, set to FALSE if calling drupal_bootstrap from inside a
* function called from drupal_bootstrap (recursion). * function called from drupal_bootstrap (recursion).
*
* @return * @return
* The most recently completed phase. * The most recently completed phase.
*
*/ */
function drupal_bootstrap($phase = NULL, $new_phase = TRUE) { function drupal_bootstrap($phase = NULL, $new_phase = TRUE) {
// Not drupal_static(), because does not depend on any run-time information. // Not drupal_static(), because does not depend on any run-time information.
...@@ -1986,7 +2196,7 @@ function drupal_bootstrap($phase = NULL, $new_phase = TRUE) { ...@@ -1986,7 +2196,7 @@ function drupal_bootstrap($phase = NULL, $new_phase = TRUE) {
} }
/** /**
* Return the time zone of the current user. * Returns the time zone of the current user.
*/ */
function drupal_get_user_timezone() { function drupal_get_user_timezone() {
global $user; global $user;
...@@ -2001,7 +2211,7 @@ function drupal_get_user_timezone() { ...@@ -2001,7 +2211,7 @@ function drupal_get_user_timezone() {
} }
/** /**
* Custom PHP error handler. * Provides custom PHP error handling.
* *
* @param $error_level * @param $error_level
* The level of the error raised. * The level of the error raised.
...@@ -2012,7 +2222,8 @@ function drupal_get_user_timezone() { ...@@ -2012,7 +2222,8 @@ function drupal_get_user_timezone() {
* @param $line * @param $line
* The line number the error was raised at. * The line number the error was raised at.
* @param $context * @param $context
* An array that points to the active symbol table at the point the error occurred. * An array that points to the active symbol table at the point the error
* occurred.
*/ */
function _drupal_error_handler($error_level, $message, $filename, $line, $context) { function _drupal_error_handler($error_level, $message, $filename, $line, $context) {
require_once DRUPAL_ROOT . '/includes/errors.inc'; require_once DRUPAL_ROOT . '/includes/errors.inc';
...@@ -2020,7 +2231,7 @@ function _drupal_error_handler($error_level, $message, $filename, $line, $contex ...@@ -2020,7 +2231,7 @@ function _drupal_error_handler($error_level, $message, $filename, $line, $contex
} }
/** /**
* Custom PHP exception handler. * Provides custom PHP exception handling.
* *
* Uncaught exceptions are those not enclosed in a try/catch block. They are * Uncaught exceptions are those not enclosed in a try/catch block. They are
* always fatal: the execution of the script will stop as soon as the exception * always fatal: the execution of the script will stop as soon as the exception
...@@ -2048,7 +2259,7 @@ function _drupal_exception_handler($exception) { ...@@ -2048,7 +2259,7 @@ function _drupal_exception_handler($exception) {
} }
/** /**
* Bootstrap configuration: Setup script environment and load settings.php. * Sets up the script environment and loads settings.php.
*/ */
function _drupal_bootstrap_configuration() { function _drupal_bootstrap_configuration() {
// Set the Drupal custom error handler. // Set the Drupal custom error handler.
...@@ -2063,7 +2274,7 @@ function _drupal_bootstrap_configuration() { ...@@ -2063,7 +2274,7 @@ function _drupal_bootstrap_configuration() {
} }
/** /**
* Bootstrap page cache: Try to serve a page from cache. * Attempts to serve a page from the cache.
*/ */
function _drupal_bootstrap_page_cache() { function _drupal_bootstrap_page_cache() {
global $user; global $user;
...@@ -2119,7 +2330,7 @@ function _drupal_bootstrap_page_cache() { ...@@ -2119,7 +2330,7 @@ function _drupal_bootstrap_page_cache() {
} }
/** /**
* Bootstrap database: Initialize database system and register autoload functions. * Initializes the database system and registers autoload functions.
*/ */
function _drupal_bootstrap_database() { function _drupal_bootstrap_database() {
// Redirect the user to the installation script if Drupal has not been // Redirect the user to the installation script if Drupal has not been
...@@ -2133,15 +2344,7 @@ function _drupal_bootstrap_database() { ...@@ -2133,15 +2344,7 @@ function _drupal_bootstrap_database() {
// The user agent header is used to pass a database prefix in the request when // 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 // running tests. However, for security reasons, it is imperative that we
// validate we ourselves made the request. // validate we ourselves made the request.
if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^(simpletest\d+);/", $_SERVER['HTTP_USER_AGENT'], $matches)) { if ($test_prefix = drupal_valid_test_ua()) {
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. // Set the test run id for use in other parts of Drupal.
$test_info = &$GLOBALS['drupal_test_info']; $test_info = &$GLOBALS['drupal_test_info'];
$test_info['test_run_id'] = $test_prefix; $test_info['test_run_id'] = $test_prefix;
...@@ -2174,13 +2377,12 @@ function _drupal_bootstrap_database() { ...@@ -2174,13 +2377,12 @@ function _drupal_bootstrap_database() {
// The database autoload routine comes first so that we can load the database // The database autoload routine comes first so that we can load the database
// system without hitting the database. That is especially important during // system without hitting the database. That is especially important during
// the install or upgrade process. // the install or upgrade process.
spl_autoload_register('db_autoload');
spl_autoload_register('drupal_autoload_class'); spl_autoload_register('drupal_autoload_class');
spl_autoload_register('drupal_autoload_interface'); spl_autoload_register('drupal_autoload_interface');
} }
/** /**
* Bootstrap variables: Load system variables and all enabled bootstrap modules. * Loads system variables and all enabled bootstrap modules.
*/ */
function _drupal_bootstrap_variables() { function _drupal_bootstrap_variables() {
global $conf; global $conf;
...@@ -2197,7 +2399,7 @@ function _drupal_bootstrap_variables() { ...@@ -2197,7 +2399,7 @@ function _drupal_bootstrap_variables() {
} }
/** /**
* Bootstrap page header: Invoke hook_boot(), initialize locking system, and send default HTTP headers. * Invokes hook_boot(), initializes locking system, and sends HTTP headers.
*/ */
function _drupal_bootstrap_page_header() { function _drupal_bootstrap_page_header() {
bootstrap_invoke_all('boot'); bootstrap_invoke_all('boot');
...@@ -2220,26 +2422,43 @@ function drupal_get_bootstrap_phase() { ...@@ -2220,26 +2422,43 @@ function drupal_get_bootstrap_phase() {
} }
/** /**
* Validate the HMAC and timestamp of a user agent header from simpletest. * Returns the test prefix if this is an internal request from SimpleTest.
*
* @return
* Either the simpletest prefix (the string "simpletest" followed by any
* number of digits) or FALSE if the user agent does not contain a valid
* HMAC and timestamp.
*/ */
function drupal_valid_test_ua($user_agent) { function drupal_valid_test_ua() {
global $drupal_hash_salt; global $drupal_hash_salt;
// No reason to reset this.
static $test_prefix;
if (isset($test_prefix)) {
return $test_prefix;
}
list($prefix, $time, $salt, $hmac) = explode(';', $user_agent); if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^(simpletest\d+);(.+);(.+);(.+)$/", $_SERVER['HTTP_USER_AGENT'], $matches)) {
list(, $prefix, $time, $salt, $hmac) = $matches;
$check_string = $prefix . ';' . $time . ';' . $salt; $check_string = $prefix . ';' . $time . ';' . $salt;
// We use the salt 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 database is not yet initialized and we can't access any Drupal variables.
// The file properties add more entropy not easily accessible to others. // The file properties add more entropy not easily accessible to others.
$filepath = DRUPAL_ROOT . '/includes/bootstrap.inc'; $key = $drupal_hash_salt . filectime(__FILE__) . fileinode(__FILE__);
$key = $drupal_hash_salt . filectime($filepath) . fileinode($filepath);
$time_diff = REQUEST_TIME - $time; $time_diff = REQUEST_TIME - $time;
// Since we are making a local request a 5 second time window is allowed, // Since we are making a local request a 5 second time window is allowed,
// and the HMAC must match. // and the HMAC must match.
return ($time_diff >= 0) && ($time_diff <= 5) && ($hmac == drupal_hmac_base64($check_string, $key)); if ($time_diff >= 0 && $time_diff <= 5 && $hmac == drupal_hmac_base64($check_string, $key)) {
$test_prefix = $prefix;
return $test_prefix;
}
}
return FALSE;
} }
/** /**
* Generate a user agent string with a HMAC and timestamp for simpletest. * Generates a user agent string with a HMAC and timestamp for simpletest.
*/ */
function drupal_generate_test_ua($prefix) { function drupal_generate_test_ua($prefix) {
global $drupal_hash_salt; global $drupal_hash_salt;
...@@ -2249,8 +2468,7 @@ function drupal_generate_test_ua($prefix) { ...@@ -2249,8 +2468,7 @@ function drupal_generate_test_ua($prefix) {
// We use the salt 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 database is not yet initialized and we can't access any Drupal variables.
// The file properties add more entropy not easily accessible to others. // The file properties add more entropy not easily accessible to others.
$filepath = DRUPAL_ROOT . '/includes/bootstrap.inc'; $key = $drupal_hash_salt . filectime(__FILE__) . fileinode(__FILE__);
$key = $drupal_hash_salt . filectime($filepath) . fileinode($filepath);
} }
// Generate a moderately secure HMAC based on the database credentials. // Generate a moderately secure HMAC based on the database credentials.
$salt = uniqid('', TRUE); $salt = uniqid('', TRUE);
...@@ -2272,15 +2490,65 @@ function drupal_maintenance_theme() { ...@@ -2272,15 +2490,65 @@ function drupal_maintenance_theme() {
} }
/** /**
* Return TRUE if a Drupal installation is currently being attempted. * Returns a simple 404 Not Found page.
*
* If fast 404 pages are enabled, and this is a matching page then print a
* simple 404 page and exit.
*
* This function is called from drupal_deliver_html_page() at the time when a
* a normal 404 page is generated, but it can also optionally be called directly
* from settings.php to prevent a Drupal bootstrap on these pages. See
* documentation in settings.php for the benefits and drawbacks of using this.
*
* Paths to dynamically-generated content, such as image styles, should also be
* accounted for in this function.
*/
function drupal_fast_404() {
$exclude_paths = variable_get('404_fast_paths_exclude', FALSE);
if ($exclude_paths && !preg_match($exclude_paths, $_GET['q'])) {
$fast_paths = variable_get('404_fast_paths', FALSE);
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', '<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.
print strtr($fast_404_html, array('@path' => check_plain(request_uri())));
exit;
}
}
}
/**
* Returns TRUE if a Drupal installation is currently being attempted.
*/ */
function drupal_installation_attempted() { function drupal_installation_attempted() {
return defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'install'; return defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'install';
} }
/** /**
* Return the name of the localization function. Use in code that needs to * Returns the name of the proper localization function.
* run both during installation and normal operation. *
* get_t() exists to support localization for code that might run during
* the installation phase, when some elements of the system might not have
* loaded.
*
* This would include implementations of hook_install(), which could run
* during the Drupal installation phase, and might also be run during
* non-installation time, such as while installing the module from the the
* module administration page.
*
* Example usage:
* @code
* $t = get_t();
* $translated = $t('translate this');
* @endcode
*
* Use t() if your code will never run during the Drupal installation phase.
* Use st() if your code will only run during installation and never any other
* time. Use get_t() if your code could run in either circumstance.
*
* @see t()
* @see st()
* @ingroup sanitization
*/ */
function get_t() { function get_t() {
static $t; static $t;
...@@ -2293,7 +2561,7 @@ function get_t() { ...@@ -2293,7 +2561,7 @@ function get_t() {
} }
/** /**
* Initialize all the defined language types. * Initializes all the defined language types.
*/ */
function drupal_language_initialize() { function drupal_language_initialize() {
$types = language_types(); $types = language_types();
...@@ -2318,7 +2586,7 @@ function drupal_language_initialize() { ...@@ -2318,7 +2586,7 @@ function drupal_language_initialize() {
} }
/** /**
* The built-in language types. * Returns a list of the built-in language types.
* *
* @return * @return
* An array of key-values pairs where the key is the language type and the * An array of key-values pairs where the key is the language type and the
...@@ -2333,23 +2601,35 @@ function drupal_language_types() { ...@@ -2333,23 +2601,35 @@ function drupal_language_types() {
} }
/** /**
* Return true if there is more than one language enabled. * Returns TRUE if there is more than one language enabled.
*/ */
function drupal_multilingual() { function drupal_multilingual() {
// The "language_count" variable stores the number of enabled languages to
// avoid unnecessarily querying the database when building the list of
// enabled languages on monolingual sites.
return variable_get('language_count', 1) > 1; return variable_get('language_count', 1) > 1;
} }
/** /**
* Return an array of the available language types. * Returns an array of the available language types.
*/ */
function language_types() { function language_types() {
return array_keys(variable_get('language_types', drupal_language_types())); return array_keys(variable_get('language_types', drupal_language_types()));
} }
/** /**
* Get a list of languages set up indexed by the specified key * Returns a list of installed languages, indexed by the specified key.
*
* @param $field
* (optional) The field to index the list with.
* *
* @param $field The field to index the list with. * @return
* An associative array, keyed on the values of $field.
* - If $field is 'weight' or 'enabled', the array is nested, with the outer
* array's values each being associative arrays with language codes as
* keys and language objects as values.
* - For all other values of $field, the array is only one level deep, and
* the array's values are language objects.
*/ */
function language_list($field = 'language') { function language_list($field = 'language') {
$languages = &drupal_static(__FUNCTION__); $languages = &drupal_static(__FUNCTION__);
...@@ -2388,7 +2668,7 @@ function language_list($field = 'language') { ...@@ -2388,7 +2668,7 @@ function language_list($field = 'language') {
} }
/** /**
* Default language used on the site * Returns the default language used on the site
* *
* @param $property * @param $property
* Optional property of the language object to return * Optional property of the language object to return
...@@ -2407,6 +2687,8 @@ function language_default($property = NULL) { ...@@ -2407,6 +2687,8 @@ function language_default($property = NULL) {
* base_path() returns "/drupalfolder/". * base_path() returns "/drupalfolder/".
* - http://example.com/path/alias (which is a path alias for node/306) returns * - http://example.com/path/alias (which is a path alias for node/306) returns
* "path/alias" as opposed to the internal path. * "path/alias" as opposed to the internal path.
* - http://example.com/index.php returns an empty string (meaning: front page).
* - http://example.com/index.php?page=1 returns an empty string.
* *
* @return * @return
* The requested Drupal URL path. * The requested Drupal URL path.
...@@ -2428,11 +2710,19 @@ function request_path() { ...@@ -2428,11 +2710,19 @@ function request_path() {
$path = $_GET['q']; $path = $_GET['q'];
} }
elseif (isset($_SERVER['REQUEST_URI'])) { elseif (isset($_SERVER['REQUEST_URI'])) {
// This is a request using a clean URL. Extract the path from REQUEST_URI. // This request is either a clean URL, or 'index.php', or nonsense.
// Extract the path from REQUEST_URI.
$request_path = strtok($_SERVER['REQUEST_URI'], '?'); $request_path = strtok($_SERVER['REQUEST_URI'], '?');
$base_path_len = strlen(rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/')); $base_path_len = strlen(rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/'));
// Unescape and strip $base_path prefix, leaving q without a leading slash. // Unescape and strip $base_path prefix, leaving q without a leading slash.
$path = substr(urldecode($request_path), $base_path_len + 1); $path = substr(urldecode($request_path), $base_path_len + 1);
// If the path equals the script filename, either because 'index.php' was
// explicitly provided in the URL, or because the server added it to
// $_SERVER['REQUEST_URI'] even when it wasn't provided in the URL (some
// versions of Microsoft IIS do this), the front page should be served.
if ($path == basename($_SERVER['PHP_SELF'])) {
$path = '';
}
} }
else { else {
// This is the front page. // This is the front page.
...@@ -2448,16 +2738,16 @@ function request_path() { ...@@ -2448,16 +2738,16 @@ function request_path() {
} }
/** /**
* Return a component of the current Drupal path. * Returns a component of the current Drupal path.
* *
* When viewing a page at the path "admin/structure/types", for example, arg(0) * When viewing a page at the path "admin/structure/types", for example, arg(0)
* returns "admin", arg(1) returns "structure", and arg(2) returns "types". * returns "admin", arg(1) returns "structure", and arg(2) returns "types".
* *
* Avoid use of this function where possible, as resulting code is hard to read. * Avoid use of this function where possible, as resulting code is hard to
* In menu callback functions, attempt to use named arguments. See the explanation * read. In menu callback functions, attempt to use named arguments. See the
* in menu.inc for how to construct callbacks that take arguments. When attempting * explanation in menu.inc for how to construct callbacks that take arguments.
* to use this function to load an element from the current path, e.g. loading the * When attempting to use this function to load an element from the current
* node on a node page, please use menu_get_object() instead. * path, e.g. loading the node on a node page, use menu_get_object() instead.
* *
* @param $index * @param $index
* The index of the component, where each component is separated by a '/' * The index of the component, where each component is separated by a '/'
...@@ -2467,7 +2757,8 @@ function request_path() { ...@@ -2467,7 +2757,8 @@ function request_path() {
* *
* @return * @return
* The component specified by $index, or NULL if the specified component was * The component specified by $index, or NULL if the specified component was
* not found. * not found. If called without arguments, it returns an array containing all
* the components of the current path.
*/ */
function arg($index = NULL, $path = NULL) { function arg($index = NULL, $path = NULL) {
// Even though $arguments doesn't need to be resettable for any functional // Even though $arguments doesn't need to be resettable for any functional
...@@ -2496,6 +2787,8 @@ function arg($index = NULL, $path = NULL) { ...@@ -2496,6 +2787,8 @@ function arg($index = NULL, $path = NULL) {
} }
/** /**
* Returns the IP address of the client machine.
*
* If Drupal is behind a reverse proxy, we use the X-Forwarded-For header * If Drupal is behind a reverse proxy, we use the X-Forwarded-For header
* instead of $_SERVER['REMOTE_ADDR'], which would be the IP address of * instead of $_SERVER['REMOTE_ADDR'], which would be the IP address of
* the proxy server, and not the client's. The actual header name can be * the proxy server, and not the client's. The actual header name can be
...@@ -2545,7 +2838,7 @@ function ip_address() { ...@@ -2545,7 +2838,7 @@ function ip_address() {
*/ */
/** /**
* Get the schema definition of a table, or the whole database schema. * Gets the schema definition of a table, or the whole database schema.
* *
* The returned schema will include any modifications made by any * The returned schema will include any modifications made by any
* module that implements hook_schema_alter(). * module that implements hook_schema_alter().
...@@ -2556,6 +2849,61 @@ function ip_address() { ...@@ -2556,6 +2849,61 @@ function ip_address() {
* If true, the schema will be rebuilt instead of retrieved from the cache. * If true, the schema will be rebuilt instead of retrieved from the cache.
*/ */
function drupal_get_schema($table = NULL, $rebuild = FALSE) { function drupal_get_schema($table = NULL, $rebuild = FALSE) {
static $schema;
if ($rebuild || !isset($table)) {
$schema = drupal_get_complete_schema($rebuild);
}
elseif (!isset($schema)) {
$schema = new SchemaCache();
}
if (!isset($table)) {
return $schema;
}
if (isset($schema[$table])) {
return $schema[$table];
}
else {
return FALSE;
}
}
/**
* Extends DrupalCacheArray to allow for dynamic building of the schema cache.
*/
class SchemaCache extends DrupalCacheArray {
/**
* Constructs a SchemaCache object.
*/
public function __construct() {
// Cache by request method.
parent::__construct('schema:runtime:' . ($_SERVER['REQUEST_METHOD'] == 'GET'), 'cache');
}
/**
* Overrides DrupalCacheArray::resolveCacheMiss().
*/
protected function resolveCacheMiss($offset) {
$complete_schema = drupal_get_complete_schema();
$value = isset($complete_schema[$offset]) ? $complete_schema[$offset] : NULL;
$this->storage[$offset] = $value;
$this->persist($offset);
return $value;
}
}
/**
* Gets the whole database schema.
*
* The returned schema will include any modifications made by any
* module that implements hook_schema_alter().
*
* @param $rebuild
* If true, the schema will be rebuilt instead of retrieved from the cache.
*/
function drupal_get_complete_schema($rebuild = FALSE) {
static $schema = array(); static $schema = array();
if (empty($schema) || $rebuild) { if (empty($schema) || $rebuild) {
...@@ -2570,15 +2918,11 @@ function drupal_get_schema($table = NULL, $rebuild = FALSE) { ...@@ -2570,15 +2918,11 @@ function drupal_get_schema($table = NULL, $rebuild = FALSE) {
// On some databases this function may be called before bootstrap has // On some databases this function may be called before bootstrap has
// been completed, so we force the functions we need to load just in case. // been completed, so we force the functions we need to load just in case.
if (function_exists('module_load_all_includes')) { if (function_exists('module_load_all_includes')) {
// There is currently a bug in module_list() where it caches what it // This function can be called very early in the bootstrap process, so
// was last called with, which is not always what you want. // we force the module_list() cache to be refreshed to ensure that it
// module_load_all_includes() calls module_list(), but if this function // contains the complete list of modules before we go on to call
// is called very early in the bootstrap process then it will be // module_load_all_includes().
// uninitialized and therefore return no modules. Instead, we have to module_list(TRUE);
// "prime" module_list() here to to values we want, specifically
// "yes rebuild the list and don't limit to bootstrap".
// @todo Remove this call after http://drupal.org/node/222109 is fixed.
module_list(TRUE, FALSE);
module_load_all_includes('install'); module_load_all_includes('install');
} }
...@@ -2601,19 +2945,14 @@ function drupal_get_schema($table = NULL, $rebuild = FALSE) { ...@@ -2601,19 +2945,14 @@ function drupal_get_schema($table = NULL, $rebuild = FALSE) {
if (!empty($schema) && (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL)) { if (!empty($schema) && (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL)) {
cache_set('schema', $schema); cache_set('schema', $schema);
} }
if ($rebuild) {
cache_clear_all('schema:', 'cache', TRUE);
}
} }
} }
if (!isset($table)) {
return $schema; return $schema;
} }
elseif (isset($schema[$table])) {
return $schema[$table];
}
else {
return FALSE;
}
}
/** /**
* @} End of "ingroup schemaapi". * @} End of "ingroup schemaapi".
...@@ -2626,13 +2965,14 @@ function drupal_get_schema($table = NULL, $rebuild = FALSE) { ...@@ -2626,13 +2965,14 @@ function drupal_get_schema($table = NULL, $rebuild = FALSE) {
*/ */
/** /**
* Confirm that an interface is available. * Confirms that an interface is available.
* *
* This function is rarely called directly. Instead, it is registered as an * This function is rarely called directly. Instead, it is registered as an
* spl_autoload() handler, and PHP calls it for us when necessary. * spl_autoload() handler, and PHP calls it for us when necessary.
* *
* @param $interface * @param $interface
* The name of the interface to check or load. * The name of the interface to check or load.
*
* @return * @return
* TRUE if the interface is currently available, FALSE otherwise. * TRUE if the interface is currently available, FALSE otherwise.
*/ */
...@@ -2641,13 +2981,14 @@ function drupal_autoload_interface($interface) { ...@@ -2641,13 +2981,14 @@ function drupal_autoload_interface($interface) {
} }
/** /**
* Confirm that a class is available. * Confirms that a class is available.
* *
* This function is rarely called directly. Instead, it is registered as an * This function is rarely called directly. Instead, it is registered as an
* spl_autoload() handler, and PHP calls it for us when necessary. * spl_autoload() handler, and PHP calls it for us when necessary.
* *
* @param $class * @param $class
* The name of the class to check or load. * The name of the class to check or load.
*
* @return * @return
* TRUE if the class is currently available, FALSE otherwise. * TRUE if the class is currently available, FALSE otherwise.
*/ */
...@@ -2656,7 +2997,7 @@ function drupal_autoload_class($class) { ...@@ -2656,7 +2997,7 @@ function drupal_autoload_class($class) {
} }
/** /**
* Helper to check for a resource in the registry. * Checks for a resource in the registry.
* *
* @param $type * @param $type
* The type of resource we are looking up, or one of the constants * The type of resource we are looking up, or one of the constants
...@@ -2665,6 +3006,7 @@ function drupal_autoload_class($class) { ...@@ -2665,6 +3006,7 @@ function drupal_autoload_class($class) {
* @param $name * @param $name
* The name of the resource, or NULL if either of the REGISTRY_* constants * The name of the resource, or NULL if either of the REGISTRY_* constants
* is passed in. * is passed in.
*
* @return * @return
* TRUE if the resource was found, FALSE if not. * TRUE if the resource was found, FALSE if not.
* NULL if either of the REGISTRY_* constants is passed in as $type. * NULL if either of the REGISTRY_* constants is passed in as $type.
...@@ -2736,7 +3078,7 @@ function _registry_check_code($type, $name = NULL) { ...@@ -2736,7 +3078,7 @@ function _registry_check_code($type, $name = NULL) {
} }
/** /**
* Rescan all enabled modules and rebuild the registry. * Rescans all enabled modules and rebuilds the registry.
* *
* Rescans all code in modules or includes directories, storing the location of * Rescans all code in modules or includes directories, storing the location of
* each interface or class in the database. * each interface or class in the database.
...@@ -2747,7 +3089,7 @@ function registry_rebuild() { ...@@ -2747,7 +3089,7 @@ function registry_rebuild() {
} }
/** /**
* Update the registry based on the latest files listed in the database. * Updates the registry based on the latest files listed in the database.
* *
* This function should be used when system_rebuild_module_data() does not need * This function should be used when system_rebuild_module_data() does not need
* to be called, because it is already known that the list of files in the * to be called, because it is already known that the list of files in the
...@@ -2765,7 +3107,7 @@ function registry_update() { ...@@ -2765,7 +3107,7 @@ function registry_update() {
*/ */
/** /**
* Central static variable storage. * Provides central static variable storage.
* *
* All functions requiring a static variable to persist or cache data within * All functions requiring a static variable to persist or cache data within
* a single page request are encouraged to use this function unless it is * a single page request are encouraged to use this function unless it is
...@@ -2916,7 +3258,7 @@ function &drupal_static($name, $default_value = NULL, $reset = FALSE) { ...@@ -2916,7 +3258,7 @@ function &drupal_static($name, $default_value = NULL, $reset = FALSE) {
} }
/** /**
* Reset one or all centrally stored static variable(s). * Resets one or all centrally stored static variable(s).
* *
* @param $name * @param $name
* Name of the static variable to reset. Omit to reset all variables. * Name of the static variable to reset. Omit to reset all variables.
...@@ -2926,7 +3268,7 @@ function drupal_static_reset($name = NULL) { ...@@ -2926,7 +3268,7 @@ function drupal_static_reset($name = NULL) {
} }
/** /**
* Detect whether the current script is running in a command-line environment. * Detects whether the current script is running in a command-line environment.
*/ */
function drupal_is_cli() { function drupal_is_cli() {
return (!isset($_SERVER['SERVER_SOFTWARE']) && (php_sapi_name() == 'cli' || (is_numeric($_SERVER['argc']) && $_SERVER['argc'] > 0))); return (!isset($_SERVER['SERVER_SOFTWARE']) && (php_sapi_name() == 'cli' || (is_numeric($_SERVER['argc']) && $_SERVER['argc'] > 0)));
...@@ -2934,7 +3276,8 @@ function drupal_is_cli() { ...@@ -2934,7 +3276,8 @@ function drupal_is_cli() {
/** /**
* Formats text for emphasized display in a placeholder inside a sentence. * Formats text for emphasized display in a placeholder inside a sentence.
* Used automatically by t(). *
* Used automatically by format_string().
* *
* @param $text * @param $text
* The text to format (plain-text). * The text to format (plain-text).
...@@ -2947,7 +3290,7 @@ function drupal_placeholder($text) { ...@@ -2947,7 +3290,7 @@ function drupal_placeholder($text) {
} }
/** /**
* Register a function for execution on shutdown. * Registers a function for execution on shutdown.
* *
* Wrapper for register_shutdown_function() that catches thrown exceptions to * Wrapper for register_shutdown_function() that catches thrown exceptions to
* avoid "Exception thrown without a stack frame in Unknown". * avoid "Exception thrown without a stack frame in Unknown".
...@@ -2976,20 +3319,23 @@ function &drupal_register_shutdown_function($callback = NULL) { ...@@ -2976,20 +3319,23 @@ function &drupal_register_shutdown_function($callback = NULL) {
$args = func_get_args(); $args = func_get_args();
array_shift($args); array_shift($args);
// Save callback and arguments // Save callback and arguments
$callbacks[] = array('callback' => $callback, 'arguments' => $args, 'cwd' => getcwd()); $callbacks[] = array('callback' => $callback, 'arguments' => $args);
} }
return $callbacks; return $callbacks;
} }
/** /**
* Internal function used to execute registered shutdown functions. * Executes registered shutdown functions.
*/ */
function _drupal_shutdown_function() { function _drupal_shutdown_function() {
$callbacks = &drupal_register_shutdown_function(); $callbacks = &drupal_register_shutdown_function();
// Set the CWD to DRUPAL_ROOT as it is not guaranteed to be the same as it
// was in the normal context of execution.
chdir(DRUPAL_ROOT);
try { try {
while (list($key, $callback) = each($callbacks)) { while (list($key, $callback) = each($callbacks)) {
chdir($callback['cwd']);
call_user_func_array($callback['callback'], $callback['arguments']); call_user_func_array($callback['callback'], $callback['arguments']);
} }
} }
......
<?php <?php
// $Id: cache-install.inc,v 1.9 2010/05/18 18:26:30 dries Exp $
/** /**
* @file * @file
...@@ -7,7 +6,7 @@ ...@@ -7,7 +6,7 @@
*/ */
/** /**
* A stub cache implementation to be used during the installation process. * Defines a stub cache implementation to be used during installation.
* *
* The stub implementation is needed when database access is not yet available. * The stub implementation is needed when database access is not yet available.
* Because Drupal's caching system never requires that cached data be present, * Because Drupal's caching system never requires that cached data be present,
...@@ -16,22 +15,35 @@ ...@@ -16,22 +15,35 @@
* normal operations would have a negative impact on performance. * normal operations would have a negative impact on performance.
*/ */
class DrupalFakeCache extends DrupalDatabaseCache implements DrupalCacheInterface { class DrupalFakeCache extends DrupalDatabaseCache implements DrupalCacheInterface {
/**
* Overrides DrupalDatabaseCache::get().
*/
function get($cid) { function get($cid) {
return FALSE; return FALSE;
} }
/**
* Overrides DrupalDatabaseCache::getMultiple().
*/
function getMultiple(&$cids) { function getMultiple(&$cids) {
return array(); return array();
} }
/**
* Overrides DrupalDatabaseCache::set().
*/
function set($cid, $data, $expire = CACHE_PERMANENT) { function set($cid, $data, $expire = CACHE_PERMANENT) {
} }
/**
* Overrides DrupalDatabaseCache::clear().
*/
function clear($cid = NULL, $wildcard = FALSE) { function clear($cid = NULL, $wildcard = FALSE) {
// If there is a database cache, attempt to clear it whenever possible. The // If there is a database cache, attempt to clear it whenever possible. The
// reason for doing this is that the database cache can accumulate data // reason for doing this is that the database cache can accumulate data
// during installation due to any full bootstraps that may occur at the // during installation due to any full bootstraps that may occur at the
// same time (for example, AJAX requests triggered by the installer). If we // same time (for example, Ajax requests triggered by the installer). If we
// didn't try to clear it whenever this function is called, the data in the // didn't try to clear it whenever this function is called, the data in the
// cache would become stale; for example, the installer sometimes calls // cache would become stale; for example, the installer sometimes calls
// variable_set(), which updates the {variable} table and then clears the // variable_set(), which updates the {variable} table and then clears the
...@@ -53,6 +65,9 @@ class DrupalFakeCache extends DrupalDatabaseCache implements DrupalCacheInterfac ...@@ -53,6 +65,9 @@ class DrupalFakeCache extends DrupalDatabaseCache implements DrupalCacheInterfac
} }
} }
/**
* Overrides DrupalDatabaseCache::isEmpty().
*/
function isEmpty() { function isEmpty() {
return TRUE; return TRUE;
} }
......
<?php <?php
// $Id: cache.inc,v 1.49 2010/10/07 17:44:53 dries Exp $
/** /**
* Get the cache object for a cache bin. * @file
* Functions and interfaces for cache handling.
*/
/**
* Gets the cache object for a cache bin.
* *
* By default, this returns an instance of the DrupalDatabaseCache class. * By default, this returns an instance of the DrupalDatabaseCache class.
* Classes implementing DrupalCacheInterface can register themselves both as a * Classes implementing DrupalCacheInterface can register themselves both as a
* default implementation and for specific bins. * default implementation and for specific bins.
* *
* @see DrupalCacheInterface
*
* @param $bin * @param $bin
* The cache bin for which the cache object should be returned. * The cache bin for which the cache object should be returned.
* @return DrupalCacheInterface * @return DrupalCacheInterface
* The cache object associated with the specified bin. * The cache object associated with the specified bin.
*
* @see DrupalCacheInterface
*/ */
function _cache_get_object($bin) { function _cache_get_object($bin) {
// We do not use drupal_static() here because we do not want to change the // We do not use drupal_static() here because we do not want to change the
...@@ -30,7 +34,7 @@ function _cache_get_object($bin) { ...@@ -30,7 +34,7 @@ function _cache_get_object($bin) {
} }
/** /**
* Return data from the persistent cache * Returns data from the persistent cache.
* *
* Data may be stored as either plain text or as serialized data. cache_get * Data may be stored as either plain text or as serialized data. cache_get
* will automatically return unserialized objects and arrays. * will automatically return unserialized objects and arrays.
...@@ -45,19 +49,22 @@ function _cache_get_object($bin) { ...@@ -45,19 +49,22 @@ function _cache_get_object($bin) {
* *
* @return * @return
* The cache or FALSE on failure. * The cache or FALSE on failure.
*
* @see cache_set()
*/ */
function cache_get($cid, $bin = 'cache') { function cache_get($cid, $bin = 'cache') {
return _cache_get_object($bin)->get($cid); return _cache_get_object($bin)->get($cid);
} }
/** /**
* Return data from the persistent cache when given an array of cache IDs. * Returns data from the persistent cache when given an array of cache IDs.
* *
* @param $cids * @param $cids
* An array of cache IDs for the data to retrieve. This is passed by * An array of cache IDs for the data to retrieve. This is passed by
* reference, and will have the IDs successfully returned from cache removed. * reference, and will have the IDs successfully returned from cache removed.
* @param $bin * @param $bin
* The cache bin where the data is stored. * The cache bin where the data is stored.
*
* @return * @return
* An array of the items successfully returned from cache indexed by cid. * An array of the items successfully returned from cache indexed by cid.
*/ */
...@@ -66,7 +73,7 @@ function cache_get_multiple(array &$cids, $bin = 'cache') { ...@@ -66,7 +73,7 @@ function cache_get_multiple(array &$cids, $bin = 'cache') {
} }
/** /**
* Store data in the persistent cache. * Stores data in the persistent cache.
* *
* The persistent cache is split up into several cache bins. In the default * The persistent cache is split up into several cache bins. In the default
* cache implementation, each cache bin corresponds to a database table by the * cache implementation, each cache bin corresponds to a database table by the
...@@ -133,13 +140,15 @@ function cache_get_multiple(array &$cids, $bin = 'cache') { ...@@ -133,13 +140,15 @@ function cache_get_multiple(array &$cids, $bin = 'cache') {
* general cache wipe. * general cache wipe.
* - A Unix timestamp: Indicates that the item should be kept at least until * - A Unix timestamp: Indicates that the item should be kept at least until
* the given time, after which it behaves like CACHE_TEMPORARY. * the given time, after which it behaves like CACHE_TEMPORARY.
*
* @see cache_get()
*/ */
function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) { function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) {
return _cache_get_object($bin)->set($cid, $data, $expire); return _cache_get_object($bin)->set($cid, $data, $expire);
} }
/** /**
* Expire data from the cache. * Expires data from the cache.
* *
* If called without arguments, expirable entries will be cleared from the * If called without arguments, expirable entries will be cleared from the
* cache_page and cache_block bins. * cache_page and cache_block bins.
...@@ -147,15 +156,12 @@ function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) { ...@@ -147,15 +156,12 @@ function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) {
* @param $cid * @param $cid
* If set, the cache ID to delete. Otherwise, all cache entries that can * If set, the cache ID to delete. Otherwise, all cache entries that can
* expire are deleted. * expire are deleted.
*
* @param $bin * @param $bin
* If set, the bin $bin to delete from. Mandatory * If set, the cache bin to delete from. Mandatory argument if $cid is set.
* argument if $cid is set.
*
* @param $wildcard * @param $wildcard
* If $wildcard is TRUE, cache IDs starting with $cid are deleted in * If TRUE, cache IDs starting with $cid are deleted in addition to the
* addition to the exact cache ID specified by $cid. If $wildcard is * exact cache ID specified by $cid. If $wildcard is TRUE and $cid is '*',
* TRUE and $cid is '*' then the entire bin $bin is emptied. * the entire cache bin is emptied.
*/ */
function cache_clear_all($cid = NULL, $bin = NULL, $wildcard = FALSE) { function cache_clear_all($cid = NULL, $bin = NULL, $wildcard = FALSE) {
if (!isset($cid) && !isset($bin)) { if (!isset($cid) && !isset($bin)) {
...@@ -171,13 +177,14 @@ function cache_clear_all($cid = NULL, $bin = NULL, $wildcard = FALSE) { ...@@ -171,13 +177,14 @@ function cache_clear_all($cid = NULL, $bin = NULL, $wildcard = FALSE) {
} }
/** /**
* Check if a cache bin is empty. * Checks if a cache bin is empty.
* *
* A cache bin is considered empty if it does not contain any valid data for any * A cache bin is considered empty if it does not contain any valid data for any
* cache ID. * cache ID.
* *
* @param $bin * @param $bin
* The cache bin to check. * The cache bin to check.
*
* @return * @return
* TRUE if the cache bin specified is empty. * TRUE if the cache bin specified is empty.
*/ */
...@@ -186,7 +193,7 @@ function cache_is_empty($bin) { ...@@ -186,7 +193,7 @@ function cache_is_empty($bin) {
} }
/** /**
* Interface for cache implementations. * Defines an interface for cache implementations.
* *
* All cache implementations have to implement this interface. * All cache implementations have to implement this interface.
* DrupalDatabaseCache provides the default implementation, which can be * DrupalDatabaseCache provides the default implementation, which can be
...@@ -198,7 +205,7 @@ function cache_is_empty($bin) { ...@@ -198,7 +205,7 @@ function cache_is_empty($bin) {
* DrupalCacheInterface was called MyCustomCache, the following line would make * DrupalCacheInterface was called MyCustomCache, the following line would make
* Drupal use it for the 'cache_page' bin: * Drupal use it for the 'cache_page' bin:
* @code * @code
* variable_set('cache_page', 'MyCustomCache'); * variable_set('cache_class_cache_page', 'MyCustomCache');
* @endcode * @endcode
* *
* Additionally, you can register your cache implementation to be used by * Additionally, you can register your cache implementation to be used by
...@@ -208,12 +215,23 @@ function cache_is_empty($bin) { ...@@ -208,12 +215,23 @@ function cache_is_empty($bin) {
* variable_set('cache_default_class', 'MyCustomCache'); * variable_set('cache_default_class', 'MyCustomCache');
* @endcode * @endcode
* *
* To implement a completely custom cache bin, use the same variable format:
* @code
* variable_set('cache_class_custom_bin', 'MyCustomCache');
* @endcode
* To access your custom cache bin, specify the name of the bin when storing
* or retrieving cached data:
* @code
* cache_set($cid, $data, 'custom_bin', $expire);
* cache_get($cid, 'custom_bin');
* @endcode
*
* @see _cache_get_object() * @see _cache_get_object()
* @see DrupalDatabaseCache * @see DrupalDatabaseCache
*/ */
interface DrupalCacheInterface { interface DrupalCacheInterface {
/** /**
* Constructor. * Constructs a new cache interface.
* *
* @param $bin * @param $bin
* The cache bin for which the object is created. * The cache bin for which the object is created.
...@@ -221,31 +239,34 @@ interface DrupalCacheInterface { ...@@ -221,31 +239,34 @@ interface DrupalCacheInterface {
function __construct($bin); function __construct($bin);
/** /**
* Return data from the persistent cache. Data may be stored as either plain * Returns data from the persistent cache.
* text or as serialized data. cache_get will automatically return *
* unserialized objects and arrays. * Data may be stored as either plain text or as serialized data. cache_get()
* will automatically return unserialized objects and arrays.
* *
* @param $cid * @param $cid
* The cache ID of the data to retrieve. * The cache ID of the data to retrieve.
*
* @return * @return
* The cache or FALSE on failure. * The cache or FALSE on failure.
*/ */
function get($cid); function get($cid);
/** /**
* Return data from the persistent cache when given an array of cache IDs. * Returns data from the persistent cache when given an array of cache IDs.
* *
* @param $cids * @param $cids
* An array of cache IDs for the data to retrieve. This is passed by * An array of cache IDs for the data to retrieve. This is passed by
* reference, and will have the IDs successfully returned from cache * reference, and will have the IDs successfully returned from cache
* removed. * removed.
*
* @return * @return
* An array of the items successfully returned from cache indexed by cid. * An array of the items successfully returned from cache indexed by cid.
*/ */
function getMultiple(&$cids); function getMultiple(&$cids);
/** /**
* Store data in the persistent cache. * Stores data in the persistent cache.
* *
* @param $cid * @param $cid
* The cache ID of the data to store. * The cache ID of the data to store.
...@@ -266,8 +287,10 @@ interface DrupalCacheInterface { ...@@ -266,8 +287,10 @@ interface DrupalCacheInterface {
/** /**
* Expire data from the cache. If called without arguments, expirable * Expires data from the cache.
* entries will be cleared from the cache_page and cache_block bins. *
* If called without arguments, expirable entries will be cleared from the
* cache_page and cache_block bins.
* *
* @param $cid * @param $cid
* If set, the cache ID to delete. Otherwise, all cache entries that can * If set, the cache ID to delete. Otherwise, all cache entries that can
...@@ -280,7 +303,7 @@ interface DrupalCacheInterface { ...@@ -280,7 +303,7 @@ interface DrupalCacheInterface {
function clear($cid = NULL, $wildcard = FALSE); function clear($cid = NULL, $wildcard = FALSE);
/** /**
* Check if a cache bin is empty. * Checks if a cache bin is empty.
* *
* A cache bin is considered empty if it does not contain any valid data for * A cache bin is considered empty if it does not contain any valid data for
* any cache ID. * any cache ID.
...@@ -292,7 +315,7 @@ interface DrupalCacheInterface { ...@@ -292,7 +315,7 @@ interface DrupalCacheInterface {
} }
/** /**
* Default cache implementation. * Defines a default cache implementation.
* *
* This is Drupal's default cache implementation. It uses the database to store * This is Drupal's default cache implementation. It uses the database to store
* cached data. Each cache bin corresponds to a database table by the same name. * cached data. Each cache bin corresponds to a database table by the same name.
...@@ -300,32 +323,38 @@ interface DrupalCacheInterface { ...@@ -300,32 +323,38 @@ interface DrupalCacheInterface {
class DrupalDatabaseCache implements DrupalCacheInterface { class DrupalDatabaseCache implements DrupalCacheInterface {
protected $bin; protected $bin;
/**
* Constructs a new DrupalDatabaseCache object.
*/
function __construct($bin) { function __construct($bin) {
$this->bin = $bin; $this->bin = $bin;
} }
/**
* Implements DrupalCacheInterface::get().
*/
function get($cid) { function get($cid) {
try { $cids = array($cid);
// Garbage collection necessary when enforcing a minimum cache lifetime. $cache = $this->getMultiple($cids);
$this->garbageCollection($this->bin); return reset($cache);
$cache = db_query("SELECT data, created, expire, serialized FROM {" . $this->bin . "} WHERE cid = :cid", array(':cid' => $cid))->fetchObject();
return $this->prepareItem($cache);
}
catch (Exception $e) {
// If the database is never going to be available, cache requests should
// return FALSE in order to allow exception handling to occur.
return FALSE;
}
} }
/**
* Implements DrupalCacheInterface::getMultiple().
*/
function getMultiple(&$cids) { function getMultiple(&$cids) {
try { try {
// Garbage collection necessary when enforcing a minimum cache lifetime. // Garbage collection necessary when enforcing a minimum cache lifetime.
$this->garbageCollection($this->bin); $this->garbageCollection($this->bin);
$query = db_select($this->bin);
$query->fields($this->bin, array('cid', 'data', 'created', 'expire', 'serialized')); // When serving cached pages, the overhead of using db_select() was found
$query->condition($this->bin . '.cid', $cids, 'IN'); // to add around 30% overhead to the request. Since $this->bin is a
$result = $query->execute(); // variable, this means the call to db_query() here uses a concatenated
// string. This is highly discouraged under any other circumstances, and
// is used here only due to the performance overhead we would incur
// otherwise. When serving an uncached page, the overhead of using
// db_select() is a much smaller proportion of the request.
$result = db_query('SELECT cid, data, created, expire, serialized FROM {' . db_escape_table($this->bin) . '} WHERE cid IN (:cids)', array(':cids' => $cids));
$cache = array(); $cache = array();
foreach ($result as $item) { foreach ($result as $item) {
$item = $this->prepareItem($item); $item = $this->prepareItem($item);
...@@ -366,13 +395,14 @@ class DrupalDatabaseCache implements DrupalCacheInterface { ...@@ -366,13 +395,14 @@ class DrupalDatabaseCache implements DrupalCacheInterface {
} }
/** /**
* Prepare a cached item. * Prepares a cached item.
* *
* Checks that items are either permanent or did not expire, and unserializes * Checks that items are either permanent or did not expire, and unserializes
* data as appropriate. * data as appropriate.
* *
* @param $cache * @param $cache
* An item loaded from cache_get() or cache_get_multiple(). * An item loaded from cache_get() or cache_get_multiple().
*
* @return * @return
* The item with data unserialized as appropriate or FALSE if there is no * The item with data unserialized as appropriate or FALSE if there is no
* valid item to load. * valid item to load.
...@@ -401,6 +431,9 @@ class DrupalDatabaseCache implements DrupalCacheInterface { ...@@ -401,6 +431,9 @@ class DrupalDatabaseCache implements DrupalCacheInterface {
return $cache; return $cache;
} }
/**
* Implements DrupalCacheInterface::set().
*/
function set($cid, $data, $expire = CACHE_PERMANENT) { function set($cid, $data, $expire = CACHE_PERMANENT) {
$fields = array( $fields = array(
'serialized' => 0, 'serialized' => 0,
...@@ -427,6 +460,9 @@ class DrupalDatabaseCache implements DrupalCacheInterface { ...@@ -427,6 +460,9 @@ class DrupalDatabaseCache implements DrupalCacheInterface {
} }
} }
/**
* Implements DrupalCacheInterface::clear().
*/
function clear($cid = NULL, $wildcard = FALSE) { function clear($cid = NULL, $wildcard = FALSE) {
global $user; global $user;
...@@ -489,6 +525,9 @@ class DrupalDatabaseCache implements DrupalCacheInterface { ...@@ -489,6 +525,9 @@ class DrupalDatabaseCache implements DrupalCacheInterface {
} }
} }
/**
* Implements DrupalCacheInterface::isEmpty().
*/
function isEmpty() { function isEmpty() {
$this->garbageCollection(); $this->garbageCollection();
$query = db_select($this->bin); $query = db_select($this->bin);
......
<?php <?php
// $Id: common.inc,v 1.1244 2010/10/21 19:31:39 dries Exp $
/** /**
* @file * @file
...@@ -37,7 +36,7 @@ ...@@ -37,7 +36,7 @@
* $my_substring = drupal_substr($original_string, 0, 5); * $my_substring = drupal_substr($original_string, 0, 5);
* @endcode * @endcode
* *
* @} End of "defgroup php_wrappers". * @}
*/ */
/** /**
...@@ -71,8 +70,7 @@ define('CSS_DEFAULT', 0); ...@@ -71,8 +70,7 @@ define('CSS_DEFAULT', 0);
define('CSS_THEME', 100); define('CSS_THEME', 100);
/** /**
* The default group for JavaScript libraries, settings or jQuery plugins added * The default group for JavaScript and jQuery libraries added to the page.
* to the page.
*/ */
define('JS_LIBRARY', -100); define('JS_LIBRARY', -100);
...@@ -87,10 +85,11 @@ define('JS_DEFAULT', 0); ...@@ -87,10 +85,11 @@ define('JS_DEFAULT', 0);
define('JS_THEME', 100); define('JS_THEME', 100);
/** /**
* Error code indicating that the request made by drupal_http_request() exceeded * Error code indicating that the request exceeded the specified timeout.
* the specified timeout. *
* @see drupal_http_request()
*/ */
define('HTTP_REQUEST_TIMEOUT', 1); define('HTTP_REQUEST_TIMEOUT', -1);
/** /**
* Constants defining cache granularity for blocks and renderable arrays. * Constants defining cache granularity for blocks and renderable arrays.
...@@ -111,31 +110,36 @@ define('HTTP_REQUEST_TIMEOUT', 1); ...@@ -111,31 +110,36 @@ define('HTTP_REQUEST_TIMEOUT', 1);
*/ */
/** /**
* The block should not get cached. This setting should be used: * The block should not get cached.
* - for simple blocks (notably those that do not perform any db query), *
* where querying the db cache would be more expensive than directly generating * This setting should be used:
* the content. * - For simple blocks (notably those that do not perform any db query), where
* - for blocks that change too frequently. * querying the db cache would be more expensive than directly generating the
* content.
* - For blocks that change too frequently.
*/ */
define('DRUPAL_NO_CACHE', -1); define('DRUPAL_NO_CACHE', -1);
/** /**
* The block is handling its own caching in its hook_block_view(). From the * The block is handling its own caching in its hook_block_view().
* perspective of the block cache system, this is equivalent to DRUPAL_NO_CACHE. *
* Useful when time based expiration is needed or a site uses a node access * From the perspective of the block cache system, this is equivalent to
* which invalidates standard block cache. * DRUPAL_NO_CACHE. Useful when time based expiration is needed or a site uses
* a node access which invalidates standard block cache.
*/ */
define('DRUPAL_CACHE_CUSTOM', -2); define('DRUPAL_CACHE_CUSTOM', -2);
/** /**
* The block or element can change depending on the roles the user viewing the * The block or element can change depending on the user's roles.
* page belongs to. This is the default setting for blocks, used when the block *
* does not specify anything. * This is the default setting for blocks, used when the block does not specify
* anything.
*/ */
define('DRUPAL_CACHE_PER_ROLE', 0x0001); define('DRUPAL_CACHE_PER_ROLE', 0x0001);
/** /**
* The block or element can change depending on the user viewing the page. * The block or element can change depending on the user.
*
* This setting can be resource-consuming for sites with large number of users, * This setting can be resource-consuming for sites with large number of users,
* and thus should only be used when DRUPAL_CACHE_PER_ROLE is not sufficient. * and thus should only be used when DRUPAL_CACHE_PER_ROLE is not sufficient.
*/ */
...@@ -147,12 +151,12 @@ define('DRUPAL_CACHE_PER_USER', 0x0002); ...@@ -147,12 +151,12 @@ define('DRUPAL_CACHE_PER_USER', 0x0002);
define('DRUPAL_CACHE_PER_PAGE', 0x0004); define('DRUPAL_CACHE_PER_PAGE', 0x0004);
/** /**
* The block or element is the same for every user on every page where it is visible. * The block or element is the same for every user and page that it is visible.
*/ */
define('DRUPAL_CACHE_GLOBAL', 0x0008); define('DRUPAL_CACHE_GLOBAL', 0x0008);
/** /**
* Add content to a specified region. * Adds content to a specified region.
* *
* @param $region * @param $region
* Page region the content is added to. * Page region the content is added to.
...@@ -169,7 +173,7 @@ function drupal_add_region_content($region = NULL, $data = NULL) { ...@@ -169,7 +173,7 @@ function drupal_add_region_content($region = NULL, $data = NULL) {
} }
/** /**
* Get assigned content for a given region. * Gets assigned content for a given region.
* *
* @param $region * @param $region
* A specified region to fetch content for. If NULL, all regions will be * A specified region to fetch content for. If NULL, all regions will be
...@@ -195,13 +199,13 @@ function drupal_get_region_content($region = NULL, $delimiter = ' ') { ...@@ -195,13 +199,13 @@ function drupal_get_region_content($region = NULL, $delimiter = ' ') {
} }
/** /**
* Get the name of the currently active install profile. * Gets the name of the currently active install profile.
* *
* When this function is called during Drupal's initial installation process, * When this function is called during Drupal's initial installation process,
* the name of the profile that's about to be installed is stored in the global * the name of the profile that's about to be installed is stored in the global
* installation state. At all other times, the standard Drupal systems variable * installation state. At all other times, the standard Drupal systems variable
* table contains the name of the current profile, and we can call variable_get() * table contains the name of the current profile, and we can call
* to determine what one is active. * variable_get() to determine what one is active.
* *
* @return $profile * @return $profile
* The name of the install profile. * The name of the install profile.
...@@ -221,7 +225,7 @@ function drupal_get_profile() { ...@@ -221,7 +225,7 @@ function drupal_get_profile() {
/** /**
* Set the breadcrumb trail for the current page. * Sets the breadcrumb trail for the current page.
* *
* @param $breadcrumb * @param $breadcrumb
* Array of links, starting with "home" and proceeding up to but not including * Array of links, starting with "home" and proceeding up to but not including
...@@ -237,7 +241,7 @@ function drupal_set_breadcrumb($breadcrumb = NULL) { ...@@ -237,7 +241,7 @@ function drupal_set_breadcrumb($breadcrumb = NULL) {
} }
/** /**
* Get the breadcrumb trail for the current page. * Gets the breadcrumb trail for the current page.
*/ */
function drupal_get_breadcrumb() { function drupal_get_breadcrumb() {
$breadcrumb = drupal_set_breadcrumb(); $breadcrumb = drupal_set_breadcrumb();
...@@ -266,7 +270,7 @@ function drupal_get_rdf_namespaces() { ...@@ -266,7 +270,7 @@ function drupal_get_rdf_namespaces() {
} }
/** /**
* Add output to the head tag of the HTML page. * Adds output to the HEAD tag of the HTML page.
* *
* This function can be called as long the headers aren't sent. Pass no * This function can be called as long the headers aren't sent. Pass no
* arguments (or NULL for both) to retrieve the currently stored elements. * arguments (or NULL for both) to retrieve the currently stored elements.
...@@ -334,7 +338,7 @@ function _drupal_default_html_head() { ...@@ -334,7 +338,7 @@ function _drupal_default_html_head() {
} }
/** /**
* Retrieve output to be displayed in the HEAD tag of the HTML page. * Retrieves output to be displayed in the HEAD tag of the HTML page.
*/ */
function drupal_get_html_head() { function drupal_get_html_head() {
$elements = drupal_add_html_head(); $elements = drupal_add_html_head();
...@@ -343,12 +347,12 @@ function drupal_get_html_head() { ...@@ -343,12 +347,12 @@ function drupal_get_html_head() {
} }
/** /**
* Add a feed URL for the current page. * Adds a feed URL for the current page.
* *
* This function can be called as long the HTML header hasn't been sent. * This function can be called as long the HTML header hasn't been sent.
* *
* @param $url * @param $url
* A url for the feed. * An internal system path or a fully qualified external URL of the feed.
* @param $title * @param $title
* The title of the feed. * The title of the feed.
*/ */
...@@ -358,16 +362,20 @@ function drupal_add_feed($url = NULL, $title = '') { ...@@ -358,16 +362,20 @@ function drupal_add_feed($url = NULL, $title = '') {
if (isset($url)) { if (isset($url)) {
$stored_feed_links[$url] = theme('feed_icon', array('url' => $url, 'title' => $title)); $stored_feed_links[$url] = theme('feed_icon', array('url' => $url, 'title' => $title));
drupal_add_html_head_link(array('rel' => 'alternate', drupal_add_html_head_link(array(
'rel' => 'alternate',
'type' => 'application/rss+xml', 'type' => 'application/rss+xml',
'title' => $title, 'title' => $title,
'href' => $url)); // Force the URL to be absolute, for consistency with other <link> tags
// output by Drupal.
'href' => url($url, array('absolute' => TRUE)),
));
} }
return $stored_feed_links; return $stored_feed_links;
} }
/** /**
* Get the feed URLs for the current page. * Gets the feed URLs for the current page.
* *
* @param $delimiter * @param $delimiter
* A delimiter to split feeds by. * A delimiter to split feeds by.
...@@ -384,7 +392,7 @@ function drupal_get_feeds($delimiter = "\n") { ...@@ -384,7 +392,7 @@ function drupal_get_feeds($delimiter = "\n") {
*/ */
/** /**
* Process a URL query parameter array to remove unwanted elements. * Processes a URL query parameter array to remove unwanted elements.
* *
* @param $query * @param $query
* (optional) An array to be processed. Defaults to $_GET. * (optional) An array to be processed. Defaults to $_GET.
...@@ -429,7 +437,7 @@ function drupal_get_query_parameters(array $query = NULL, array $exclude = array ...@@ -429,7 +437,7 @@ function drupal_get_query_parameters(array $query = NULL, array $exclude = array
} }
/** /**
* Split an URL-encoded query string into an array. * Splits a URL-encoded query string into an array.
* *
* @param $query * @param $query
* The query string to split. * The query string to split.
...@@ -449,7 +457,7 @@ function drupal_get_query_array($query) { ...@@ -449,7 +457,7 @@ function drupal_get_query_array($query) {
} }
/** /**
* Parse an array into a valid, rawurlencoded query string. * Parses an array into a valid, rawurlencoded query string.
* *
* This differs from http_build_query() as we need to rawurlencode() (instead of * This differs from http_build_query() as we need to rawurlencode() (instead of
* urlencode()) all query parameters. * urlencode()) all query parameters.
...@@ -490,7 +498,7 @@ function drupal_http_build_query(array $query, $parent = '') { ...@@ -490,7 +498,7 @@ function drupal_http_build_query(array $query, $parent = '') {
} }
/** /**
* Prepare a 'destination' URL query parameter for use in combination with drupal_goto(). * Prepares a 'destination' URL query parameter for use with drupal_goto().
* *
* Used to direct the user back to the referring page after completing a form. * Used to direct the user back to the referring page after completing a form.
* By default the current URL is returned. If a destination exists in the * By default the current URL is returned. If a destination exists in the
...@@ -521,7 +529,7 @@ function drupal_get_destination() { ...@@ -521,7 +529,7 @@ function drupal_get_destination() {
} }
/** /**
* Wrapper around parse_url() to parse a system URL string into an associative array, suitable for url(). * Parses a system URL string into an associative array suitable for url().
* *
* This function should only be used for URLs that have been generated by the * This function should only be used for URLs that have been generated by the
* system, resp. url(). It should not be used for URLs that come from external * system, resp. url(). It should not be used for URLs that come from external
...@@ -618,7 +626,7 @@ function drupal_encode_path($path) { ...@@ -618,7 +626,7 @@ function drupal_encode_path($path) {
} }
/** /**
* Send the user to a different Drupal page. * Sends the user to a different Drupal page.
* *
* This issues an on-site HTTP redirect. The function makes sure the redirected * This issues an on-site HTTP redirect. The function makes sure the redirected
* URL is formatted correctly. * URL is formatted correctly.
...@@ -683,7 +691,7 @@ function drupal_goto($path = '', array $options = array(), $http_response_code = ...@@ -683,7 +691,7 @@ function drupal_goto($path = '', array $options = array(), $http_response_code =
} }
/** /**
* Deliver a "site is under maintenance" message to the browser. * Delivers a "site is under maintenance" message to the browser.
* *
* Page callback functions wanting to report a "site offline" message should * Page callback functions wanting to report a "site offline" message should
* return MENU_SITE_OFFLINE instead of calling drupal_site_offline(). However, * return MENU_SITE_OFFLINE instead of calling drupal_site_offline(). However,
...@@ -695,7 +703,7 @@ function drupal_site_offline() { ...@@ -695,7 +703,7 @@ function drupal_site_offline() {
} }
/** /**
* Deliver a "page not found" error to the browser. * Delivers a "page not found" error to the browser.
* *
* Page callback functions wanting to report a "page not found" message should * Page callback functions wanting to report a "page not found" message should
* return MENU_NOT_FOUND instead of calling drupal_not_found(). However, * return MENU_NOT_FOUND instead of calling drupal_not_found(). However,
...@@ -707,65 +715,56 @@ function drupal_not_found() { ...@@ -707,65 +715,56 @@ function drupal_not_found() {
} }
/** /**
* Deliver a "access denied" error to the browser. * Delivers an "access denied" error to the browser.
* *
* Page callback functions wanting to report an "access denied" message should * Page callback functions wanting to report an "access denied" message should
* return MENU_ACCESS_DENIED instead of calling drupal_access_denied(). However, * return MENU_ACCESS_DENIED instead of calling drupal_access_denied(). However,
* functions that are invoked in contexts where that return value might not * functions that are invoked in contexts where that return value might not
* bubble up to menu_execute_active_handler() should call drupal_access_denied(). * bubble up to menu_execute_active_handler() should call
* drupal_access_denied().
*/ */
function drupal_access_denied() { function drupal_access_denied() {
drupal_deliver_page(MENU_ACCESS_DENIED); drupal_deliver_page(MENU_ACCESS_DENIED);
} }
/** /**
* Perform an HTTP request. * Performs an HTTP request.
* *
* This is a flexible and powerful HTTP client implementation. Correctly * This is a flexible and powerful HTTP client implementation. Correctly
* handles GET, POST, PUT or any other HTTP requests. Handles redirects. * handles GET, POST, PUT or any other HTTP requests. Handles redirects.
* *
* @param $url * @param $url
* A string containing a fully qualified URI. * A string containing a fully qualified URI.
* @param $options * @param array $options
* (optional) An array which can have one or more of following keys: * (optional) An array that can have one or more of the following elements:
* - headers * - headers: An array containing request headers to send as name/value pairs.
* An array containing request headers to send as name/value pairs. * - method: A string containing the request method. Defaults to 'GET'.
* - method * - data: A string containing the request body, formatted as
* A string containing the request method. Defaults to 'GET'. * 'param=value&param=value&...'. Defaults to NULL.
* - data * - max_redirects: An integer representing how many times a redirect
* A string containing the request body. Defaults to NULL. * may be followed. Defaults to 3.
* - max_redirects * - timeout: A float representing the maximum number of seconds the function
* An integer representing how many times a redirect may be followed. * call may take. The default is 30 seconds. If a timeout occurs, the error
* Defaults to 3.
* - timeout
* A float representing the maximum number of seconds the function call
* may take. The default is 30 seconds. If a timeout occurs, the error
* code is set to the HTTP_REQUEST_TIMEOUT constant. * code is set to the HTTP_REQUEST_TIMEOUT constant.
* - context * - context: A context resource created with stream_context_create().
* A context resource created with stream_context_create(). *
* @return * @return object
* An object which can have one or more of the following parameters: * An object that can have one or more of the following components:
* - request * - request: A string containing the request body that was sent.
* A string containing the request body that was sent. * - code: An integer containing the response status code, or the error code
* - code * if an error occurred.
* An integer containing the response status code, or the error code if * - protocol: The response protocol (e.g. HTTP/1.1 or HTTP/1.0).
* an error occurred. * - status_message: The status message from the response, if a response was
* - protocol * received.
* The response protocol (e.g. HTTP/1.1 or HTTP/1.0). * - redirect_code: If redirected, an integer containing the initial response
* - status_message * status code.
* The status message from the response, if a response was received. * - redirect_url: If redirected, a string containing the URL of the redirect
* - redirect_code * target.
* If redirected, an integer containing the initial response status code. * - error: If an error occurred, the error message. Otherwise not set.
* - redirect_url * - headers: An array containing the response headers as name/value pairs.
* If redirected, a string containing the redirection location. * HTTP header names are case-insensitive (RFC 2616, section 4.2), so for
* - error * easy access the array keys are returned in lower case.
* If an error occurred, the error message. Otherwise not set. * - data: A string containing the response body that was received.
* - headers
* An array containing the response headers as name/value pairs. HTTP
* header names are case-insensitive (RFC 2616, section 4.2), so for easy
* access the array keys are returned in lower case.
* - data
* A string containing the response body that was received.
*/ */
function drupal_http_request($url, array $options = array()) { function drupal_http_request($url, array $options = array()) {
$result = new stdClass(); $result = new stdClass();
...@@ -839,7 +838,7 @@ function drupal_http_request($url, array $options = array()) { ...@@ -839,7 +838,7 @@ function drupal_http_request($url, array $options = array()) {
// Mark that this request failed. This will trigger a check of the web // Mark that this request failed. This will trigger a check of the web
// server's ability to make outgoing HTTP requests the next time that // server's ability to make outgoing HTTP requests the next time that
// requirements checking is performed. // requirements checking is performed.
// See system_requirements() // See system_requirements().
variable_set('drupal_http_request_fails', TRUE); variable_set('drupal_http_request_fails', TRUE);
return $result; return $result;
...@@ -867,7 +866,7 @@ function drupal_http_request($url, array $options = array()) { ...@@ -867,7 +866,7 @@ function drupal_http_request($url, array $options = array()) {
// If the server URL has a user then attempt to use basic authentication. // If the server URL has a user then attempt to use basic authentication.
if (isset($uri['user'])) { if (isset($uri['user'])) {
$options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (!empty($uri['pass']) ? ":" . $uri['pass'] : '')); $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (isset($uri['pass']) ? ':' . $uri['pass'] : ''));
} }
// If the database prefix is being used by SimpleTest to run the tests in a copied // If the database prefix is being used by SimpleTest to run the tests in a copied
...@@ -922,7 +921,9 @@ function drupal_http_request($url, array $options = array()) { ...@@ -922,7 +921,9 @@ function drupal_http_request($url, array $options = array()) {
return $result; return $result;
} }
// Parse response headers from the response body. // Parse response headers from the response body.
list($response, $result->data) = explode("\r\n\r\n", $response, 2); // Be tolerant of malformed HTTP responses that separate header and body with
// \n\n or \r\r instead of \r\n\r\n.
list($response, $result->data) = preg_split("/\r\n\r\n|\n\n|\r\r/", $response, 2);
$response = preg_split("/\r\n|\n|\r/", $response); $response = preg_split("/\r\n|\n|\r/", $response);
// Parse the response status line. // Parse the response status line.
...@@ -1014,7 +1015,9 @@ function drupal_http_request($url, array $options = array()) { ...@@ -1014,7 +1015,9 @@ function drupal_http_request($url, array $options = array()) {
$result = drupal_http_request($location, $options); $result = drupal_http_request($location, $options);
$result->redirect_code = $code; $result->redirect_code = $code;
} }
if (!isset($result->redirect_url)) {
$result->redirect_url = $location; $result->redirect_url = $location;
}
break; break;
default: default:
$result->error = $status_message; $result->error = $status_message;
...@@ -1026,6 +1029,14 @@ function drupal_http_request($url, array $options = array()) { ...@@ -1026,6 +1029,14 @@ function drupal_http_request($url, array $options = array()) {
* @} End of "HTTP handling". * @} End of "HTTP handling".
*/ */
/**
* Strips slashes from a string or array of strings.
*
* Callback for array_walk() within fix_gpx_magic().
*
* @param $item
* An individual string or array of strings from superglobals.
*/
function _fix_gpc_magic(&$item) { function _fix_gpc_magic(&$item) {
if (is_array($item)) { if (is_array($item)) {
array_walk($item, '_fix_gpc_magic'); array_walk($item, '_fix_gpc_magic');
...@@ -1036,11 +1047,19 @@ function _fix_gpc_magic(&$item) { ...@@ -1036,11 +1047,19 @@ function _fix_gpc_magic(&$item) {
} }
/** /**
* Helper function to strip slashes from $_FILES skipping over the tmp_name keys * Strips slashes from $_FILES items.
* since PHP generates single backslashes for file paths on Windows systems. *
* Callback for array_walk() within fix_gpc_magic().
*
* The tmp_name key is skipped keys since PHP generates single backslashes for
* file paths on Windows systems.
* *
* tmp_name does not have backslashes added see * @param $item
* http://php.net/manual/en/features.file-upload.php#42280 * An item from $_FILES.
* @param $key
* The key for the item within $_FILES.
*
* @see http://php.net/manual/en/features.file-upload.php#42280
*/ */
function _fix_gpc_magic_files(&$item, $key) { function _fix_gpc_magic_files(&$item, $key) {
if ($key != 'tmp_name') { if ($key != 'tmp_name') {
...@@ -1054,18 +1073,21 @@ function _fix_gpc_magic_files(&$item, $key) { ...@@ -1054,18 +1073,21 @@ function _fix_gpc_magic_files(&$item, $key) {
} }
/** /**
* Fix double-escaping problems caused by "magic quotes" in some PHP installations. * Fixes double-escaping caused by "magic quotes" in some PHP installations.
*
* @see _fix_gpc_magic()
* @see _fix_gpc_magic_files()
*/ */
function fix_gpc_magic() { function fix_gpc_magic() {
$fixed = &drupal_static(__FUNCTION__, FALSE); static $fixed = FALSE;
if (!$fixed && ini_get('magic_quotes_gpc')) { if (!$fixed && ini_get('magic_quotes_gpc')) {
array_walk($_GET, '_fix_gpc_magic'); array_walk($_GET, '_fix_gpc_magic');
array_walk($_POST, '_fix_gpc_magic'); array_walk($_POST, '_fix_gpc_magic');
array_walk($_COOKIE, '_fix_gpc_magic'); array_walk($_COOKIE, '_fix_gpc_magic');
array_walk($_REQUEST, '_fix_gpc_magic'); array_walk($_REQUEST, '_fix_gpc_magic');
array_walk($_FILES, '_fix_gpc_magic_files'); array_walk($_FILES, '_fix_gpc_magic_files');
$fixed = TRUE;
} }
$fixed = TRUE;
} }
/** /**
...@@ -1075,12 +1097,13 @@ function fix_gpc_magic() { ...@@ -1075,12 +1097,13 @@ function fix_gpc_magic() {
*/ */
/** /**
* Verify the syntax of the given e-mail address. * Verifies the syntax of the given e-mail address.
* *
* Empty e-mail addresses are allowed. See RFC 2822 for details. * Empty e-mail addresses are allowed. See RFC 2822 for details.
* *
* @param $mail * @param $mail
* A string containing an e-mail address. * A string containing an e-mail address.
*
* @return * @return
* TRUE if the address is in a valid format. * TRUE if the address is in a valid format.
*/ */
...@@ -1089,7 +1112,7 @@ function valid_email_address($mail) { ...@@ -1089,7 +1112,7 @@ function valid_email_address($mail) {
} }
/** /**
* Verify the syntax of the given URL. * Verifies the syntax of the given URL.
* *
* This function should only be used on actual URLs. It should not be used for * This function should only be used on actual URLs. It should not be used for
* Drupal menu paths, which can contain arbitrary characters. * Drupal menu paths, which can contain arbitrary characters.
...@@ -1098,6 +1121,7 @@ function valid_email_address($mail) { ...@@ -1098,6 +1121,7 @@ function valid_email_address($mail) {
* The URL to verify. * The URL to verify.
* @param $absolute * @param $absolute
* Whether the URL is absolute (beginning with a scheme such as "http:"). * Whether the URL is absolute (beginning with a scheme such as "http:").
*
* @return * @return
* TRUE if the URL is in a valid format. * TRUE if the URL is in a valid format.
*/ */
...@@ -1130,7 +1154,7 @@ function valid_url($url, $absolute = FALSE) { ...@@ -1130,7 +1154,7 @@ function valid_url($url, $absolute = FALSE) {
*/ */
/** /**
* Register an event for the current visitor to the flood control mechanism. * Registers an event for the current visitor to the flood control mechanism.
* *
* @param $name * @param $name
* The name of an event. * The name of an event.
...@@ -1157,7 +1181,7 @@ function flood_register_event($name, $window = 3600, $identifier = NULL) { ...@@ -1157,7 +1181,7 @@ function flood_register_event($name, $window = 3600, $identifier = NULL) {
} }
/** /**
* Make the flood control mechanism forget about an event for the current visitor. * Makes the flood control mechanism forget an event for the current visitor.
* *
* @param $name * @param $name
* The name of an event. * The name of an event.
...@@ -1175,7 +1199,7 @@ function flood_clear_event($name, $identifier = NULL) { ...@@ -1175,7 +1199,7 @@ function flood_clear_event($name, $identifier = NULL) {
} }
/** /**
* Checks whether user is allowed to proceed with the specified event. * Checks whether a user is allowed to proceed with the specified event.
* *
* Events can have thresholds saying that each user can only do that event * Events can have thresholds saying that each user can only do that event
* a certain number of times in a time window. This function verifies that the * a certain number of times in a time window. This function verifies that the
...@@ -1269,7 +1293,7 @@ function drupal_strip_dangerous_protocols($uri) { ...@@ -1269,7 +1293,7 @@ function drupal_strip_dangerous_protocols($uri) {
} }
/** /**
* Strips dangerous protocols (e.g. 'javascript:') from a URI and encodes it for output to an HTML attribute value. * Strips dangerous protocols from a URI and encodes it for output to HTML.
* *
* @param $uri * @param $uri
* A plain-text URI that might contain dangerous protocols. * A plain-text URI that might contain dangerous protocols.
...@@ -1289,7 +1313,7 @@ function check_url($uri) { ...@@ -1289,7 +1313,7 @@ function check_url($uri) {
} }
/** /**
* Very permissive XSS/HTML filter for admin-only use. * Applies a very permissive XSS/HTML filter for admin-only use.
* *
* Use only for fields where it is impractical to use the * Use only for fields where it is impractical to use the
* whole filter system, but where some (mainly inline) mark-up * whole filter system, but where some (mainly inline) mark-up
...@@ -1299,11 +1323,11 @@ function check_url($uri) { ...@@ -1299,11 +1323,11 @@ function check_url($uri) {
* for scripts and styles. * for scripts and styles.
*/ */
function filter_xss_admin($string) { function filter_xss_admin($string) {
return filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'div', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'ol', 'p', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var')); return filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'article', 'aside', 'b', 'bdi', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'command', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em', 'figcaption', 'figure', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'mark', 'menu', 'meter', 'nav', 'ol', 'output', 'p', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'small', 'span', 'strong', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time', 'tr', 'tt', 'u', 'ul', 'var', 'wbr'));
} }
/** /**
* Filters an HTML string to prevent cross-site-scripting (XSS) vulnerabilities. * Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
* *
* Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses. * 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. * For examples of various XSS attacks, see: http://ha.ckers.org/xss.html.
...@@ -1334,21 +1358,21 @@ function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', ...@@ -1334,21 +1358,21 @@ function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite',
if (!drupal_validate_utf8($string)) { if (!drupal_validate_utf8($string)) {
return ''; return '';
} }
// Store the text format // Store the text format.
_filter_xss_split($allowed_tags, TRUE); _filter_xss_split($allowed_tags, TRUE);
// Remove NULL characters (ignored by some browsers) // Remove NULL characters (ignored by some browsers).
$string = str_replace(chr(0), '', $string); $string = str_replace(chr(0), '', $string);
// Remove Netscape 4 JS entities // Remove Netscape 4 JS entities.
$string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string); $string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string);
// Defuse all HTML entities // Defuse all HTML entities.
$string = str_replace('&', '&amp;', $string); $string = str_replace('&', '&amp;', $string);
// Change back only well-formed entities in our whitelist // Change back only well-formed entities in our whitelist:
// Decimal numeric entities // Decimal numeric entities.
$string = preg_replace('/&amp;#([0-9]+;)/', '&#\1', $string); $string = preg_replace('/&amp;#([0-9]+;)/', '&#\1', $string);
// Hexadecimal numeric entities // Hexadecimal numeric entities.
$string = preg_replace('/&amp;#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string); $string = preg_replace('/&amp;#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string);
// Named entities // Named entities.
$string = preg_replace('/&amp;([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string); $string = preg_replace('/&amp;([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string);
return preg_replace_callback('% return preg_replace_callback('%
...@@ -1372,6 +1396,7 @@ function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', ...@@ -1372,6 +1396,7 @@ function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite',
* If $store is FALSE then the array has one element, the HTML tag to process. * If $store is FALSE then the array has one element, the HTML tag to process.
* @param $store * @param $store
* Whether to store $m. * Whether to store $m.
*
* @return * @return
* If the element isn't allowed, an empty string. Otherwise, the cleaned up * If the element isn't allowed, an empty string. Otherwise, the cleaned up
* version of the HTML element. * version of the HTML element.
...@@ -1387,16 +1412,16 @@ function _filter_xss_split($m, $store = FALSE) { ...@@ -1387,16 +1412,16 @@ function _filter_xss_split($m, $store = FALSE) {
$string = $m[1]; $string = $m[1];
if (substr($string, 0, 1) != '<') { if (substr($string, 0, 1) != '<') {
// We matched a lone ">" character // We matched a lone ">" character.
return '&gt;'; return '&gt;';
} }
elseif (strlen($string) == 1) { elseif (strlen($string) == 1) {
// We matched a lone "<" character // We matched a lone "<" character.
return '&lt;'; return '&lt;';
} }
if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?|(<!--.*?-->)$%', $string, $matches)) { if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?|(<!--.*?-->)$%', $string, $matches)) {
// Seriously malformed // Seriously malformed.
return ''; return '';
} }
...@@ -1410,7 +1435,7 @@ function _filter_xss_split($m, $store = FALSE) { ...@@ -1410,7 +1435,7 @@ function _filter_xss_split($m, $store = FALSE) {
} }
if (!isset($allowed_html[strtolower($elem)])) { if (!isset($allowed_html[strtolower($elem)])) {
// Disallowed HTML element // Disallowed HTML element.
return ''; return '';
} }
...@@ -1426,7 +1451,7 @@ function _filter_xss_split($m, $store = FALSE) { ...@@ -1426,7 +1451,7 @@ function _filter_xss_split($m, $store = FALSE) {
$attrlist = preg_replace('%(\s?)/\s*$%', '\1', $attrlist, -1, $count); $attrlist = preg_replace('%(\s?)/\s*$%', '\1', $attrlist, -1, $count);
$xhtml_slash = $count ? ' /' : ''; $xhtml_slash = $count ? ' /' : '';
// Clean up attributes // Clean up attributes.
$attr2 = implode(' ', _filter_xss_attributes($attrlist)); $attr2 = implode(' ', _filter_xss_attributes($attrlist));
$attr2 = preg_replace('/[<>]/', '', $attr2); $attr2 = preg_replace('/[<>]/', '', $attr2);
$attr2 = strlen($attr2) ? ' ' . $attr2 : ''; $attr2 = strlen($attr2) ? ' ' . $attr2 : '';
...@@ -1451,7 +1476,7 @@ function _filter_xss_attributes($attr) { ...@@ -1451,7 +1476,7 @@ function _filter_xss_attributes($attr) {
switch ($mode) { switch ($mode) {
case 0: case 0:
// Attribute name, href for instance // Attribute name, href for instance.
if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) { if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) {
$attrname = strtolower($match[1]); $attrname = strtolower($match[1]);
$skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on'); $skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on');
...@@ -1461,7 +1486,7 @@ function _filter_xss_attributes($attr) { ...@@ -1461,7 +1486,7 @@ function _filter_xss_attributes($attr) {
break; break;
case 1: case 1:
// Equals sign or valueless ("selected") // Equals sign or valueless ("selected").
if (preg_match('/^\s*=\s*/', $attr)) { if (preg_match('/^\s*=\s*/', $attr)) {
$working = 1; $mode = 2; $working = 1; $mode = 2;
$attr = preg_replace('/^\s*=\s*/', '', $attr); $attr = preg_replace('/^\s*=\s*/', '', $attr);
...@@ -1478,7 +1503,7 @@ function _filter_xss_attributes($attr) { ...@@ -1478,7 +1503,7 @@ function _filter_xss_attributes($attr) {
break; break;
case 2: case 2:
// Attribute value, a URL after href= for instance // Attribute value, a URL after href= for instance.
if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) { if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) {
$thisval = filter_xss_bad_protocol($match[1]); $thisval = filter_xss_bad_protocol($match[1]);
...@@ -1515,7 +1540,7 @@ function _filter_xss_attributes($attr) { ...@@ -1515,7 +1540,7 @@ function _filter_xss_attributes($attr) {
} }
if ($working == 0) { if ($working == 0) {
// not well formed, remove and try again // Not well formed; remove and try again.
$attr = preg_replace('/ $attr = preg_replace('/
^ ^
( (
...@@ -1539,15 +1564,16 @@ function _filter_xss_attributes($attr) { ...@@ -1539,15 +1564,16 @@ function _filter_xss_attributes($attr) {
} }
/** /**
* Processes an HTML attribute value and ensures it does not contain an URL with a disallowed protocol (e.g. javascript:). * Processes an HTML attribute value and strips dangerous protocols from URLs.
* *
* @param $string * @param $string
* The string with the attribute value. * The string with the attribute value.
* @param $decode * @param $decode
* (Deprecated) Whether to decode entities in the $string. Set to FALSE if the * (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 * $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, * is deprecated and will be removed in Drupal 8. To process a plain-text URI,
* call drupal_strip_dangerous_protocols() or check_url() instead. * call drupal_strip_dangerous_protocols() or check_url() instead.
*
* @return * @return
* Cleaned up and HTML-escaped version of $string. * Cleaned up and HTML-escaped version of $string.
*/ */
...@@ -1556,6 +1582,10 @@ function filter_xss_bad_protocol($string, $decode = TRUE) { ...@@ -1556,6 +1582,10 @@ function filter_xss_bad_protocol($string, $decode = TRUE) {
// @todo Remove the $decode parameter in Drupal 8, and always assume an HTML // @todo Remove the $decode parameter in Drupal 8, and always assume an HTML
// string that needs decoding. // string that needs decoding.
if ($decode) { if ($decode) {
if (!function_exists('decode_entities')) {
require_once DRUPAL_ROOT . '/includes/unicode.inc';
}
$string = decode_entities($string); $string = decode_entities($string);
} }
return check_plain(drupal_strip_dangerous_protocols($string)); return check_plain(drupal_strip_dangerous_protocols($string));
...@@ -1597,7 +1627,7 @@ function format_rss_channel($title, $link, $description, $items, $langcode = NUL ...@@ -1597,7 +1627,7 @@ function format_rss_channel($title, $link, $description, $items, $langcode = NUL
} }
/** /**
* Format a single RSS item. * Formats a single RSS item.
* *
* Arbitrary elements may be added using the $args associative array. * Arbitrary elements may be added using the $args associative array.
*/ */
...@@ -1613,7 +1643,7 @@ function format_rss_item($title, $link, $description, $args = array()) { ...@@ -1613,7 +1643,7 @@ function format_rss_item($title, $link, $description, $args = array()) {
} }
/** /**
* Format XML elements. * Formats XML elements.
* *
* @param $array * @param $array
* An array where each item represents an element and is either a: * An array where each item represents an element and is either a:
...@@ -1652,7 +1682,7 @@ function format_xml_elements($array) { ...@@ -1652,7 +1682,7 @@ function format_xml_elements($array) {
} }
/** /**
* Format a string containing a count of items. * Formats a string containing a count of items.
* *
* This function ensures that the string is pluralized correctly. Since t() is * This function ensures that the string is pluralized correctly. Since t() is
* called by this function, make sure not to pass already-localized strings to * called by this function, make sure not to pass already-localized strings to
...@@ -1674,31 +1704,27 @@ function format_xml_elements($array) { ...@@ -1674,31 +1704,27 @@ function format_xml_elements($array) {
* @param $count * @param $count
* The item count to display. * The item count to display.
* @param $singular * @param $singular
* The string for the singular case. Please make sure it is clear this is * The string for the singular case. Make sure it is clear this is singular,
* singular, to ease translation (e.g. use "1 new comment" instead of "1 new"). * to ease translation (e.g. use "1 new comment" instead of "1 new"). Do not
* Do not use @count in the singular string. * use @count in the singular string.
* @param $plural * @param $plural
* The string for the plural case. Please make sure it is clear this is plural, * The string for the plural case. Make sure it is clear this is plural, to
* to ease translation. Use @count in place of the item count, as in "@count * ease translation. Use @count in place of the item count, as in
* new comments". * "@count new comments".
* @param $args * @param $args
* An associative array of replacements to make after translation. Incidences * An associative array of replacements to make after translation. Instances
* of any key in this array are replaced with the corresponding value. * of any key in this array are replaced with the corresponding value.
* Based on the first character of the key, the value is escaped and/or themed: * Based on the first character of the key, the value is escaped and/or
* - !variable: inserted as is * themed. See format_string(). Note that you do not need to include @count
* - @variable: escape plain text to HTML (check_plain) * in this array; this replacement is done automatically for the plural case.
* - %variable: escape text and theme as a placeholder for user-submitted
* content (check_plain + theme_placeholder)
* Note that you do not need to include @count in this array.
* This replacement is done automatically for the plural case.
* @param $options * @param $options
* An associative array of additional options, with the following keys: * An associative array of additional options. See t() for allowed keys.
* - 'langcode' (default to the current language) The language code to *
* translate to a language other than what is used to display the page.
* - 'context' (default to the empty context) The context the source string
* belongs to.
* @return * @return
* A translated string. * A translated string.
*
* @see t()
* @see format_string()
*/ */
function format_plural($count, $singular, $plural, array $args = array(), array $options = array()) { function format_plural($count, $singular, $plural, array $args = array(), array $options = array()) {
$args['@count'] = $count; $args['@count'] = $count;
...@@ -1727,11 +1753,12 @@ function format_plural($count, $singular, $plural, array $args = array(), array ...@@ -1727,11 +1753,12 @@ function format_plural($count, $singular, $plural, array $args = array(), array
} }
/** /**
* Parse a given byte count. * Parses a given byte count.
* *
* @param $size * @param $size
* A size expressed as a number of bytes with optional SI or IEC binary unit * A size expressed as a number of bytes with optional SI or IEC binary unit
* prefix (e.g. 2, 3K, 5MB, 10G, 6GiB, 8 bytes, 9mbytes). * prefix (e.g. 2, 3K, 5MB, 10G, 6GiB, 8 bytes, 9mbytes).
*
* @return * @return
* An integer representation of the size in bytes. * An integer representation of the size in bytes.
*/ */
...@@ -1748,13 +1775,14 @@ function parse_size($size) { ...@@ -1748,13 +1775,14 @@ function parse_size($size) {
} }
/** /**
* Generate a string representation for the given byte count. * Generates a string representation for the given byte count.
* *
* @param $size * @param $size
* A size in bytes. * A size in bytes.
* @param $langcode * @param $langcode
* Optional language code to translate to a language other than what is used * Optional language code to translate to a language other than what is used
* to display the page. * to display the page.
*
* @return * @return
* A translated string representation of the size. * A translated string representation of the size.
*/ */
...@@ -1787,19 +1815,20 @@ function format_size($size, $langcode = NULL) { ...@@ -1787,19 +1815,20 @@ function format_size($size, $langcode = NULL) {
} }
/** /**
* Format a time interval with the requested granularity. * Formats a time interval with the requested granularity.
* *
* @param $timestamp * @param $interval
* The length of the interval in seconds. * The length of the interval in seconds.
* @param $granularity * @param $granularity
* How many different units to display in the string. * How many different units to display in the string.
* @param $langcode * @param $langcode
* Optional language code to translate to a language other than * Optional language code to translate to a language other than
* what is used to display the page. * what is used to display the page.
*
* @return * @return
* A translated string representation of the interval. * A translated string representation of the interval.
*/ */
function format_interval($timestamp, $granularity = 2, $langcode = NULL) { function format_interval($interval, $granularity = 2, $langcode = NULL) {
$units = array( $units = array(
'1 year|@count years' => 31536000, '1 year|@count years' => 31536000,
'1 month|@count months' => 2592000, '1 month|@count months' => 2592000,
...@@ -1812,9 +1841,9 @@ function format_interval($timestamp, $granularity = 2, $langcode = NULL) { ...@@ -1812,9 +1841,9 @@ function format_interval($timestamp, $granularity = 2, $langcode = NULL) {
$output = ''; $output = '';
foreach ($units as $key => $value) { foreach ($units as $key => $value) {
$key = explode('|', $key); $key = explode('|', $key);
if ($timestamp >= $value) { if ($interval >= $value) {
$output .= ($output ? ' ' : '') . format_plural(floor($timestamp / $value), $key[0], $key[1], array(), array('langcode' => $langcode)); $output .= ($output ? ' ' : '') . format_plural(floor($interval / $value), $key[0], $key[1], array(), array('langcode' => $langcode));
$timestamp %= $value; $interval %= $value;
$granularity--; $granularity--;
} }
...@@ -1826,26 +1855,30 @@ function format_interval($timestamp, $granularity = 2, $langcode = NULL) { ...@@ -1826,26 +1855,30 @@ function format_interval($timestamp, $granularity = 2, $langcode = NULL) {
} }
/** /**
* Format a date with the given configured format or a custom format string. * Formats a date, using a date type or a custom date format string.
*
* Drupal allows administrators to select formatting strings for 'short',
* 'medium' and 'long' date formats. This function can handle these formats,
* as well as any custom format.
* *
* @param $timestamp * @param $timestamp
* The exact date to format, as a UNIX timestamp. * A UNIX timestamp to format.
* @param $type * @param $type
* The format to use. Can be "short", "medium" or "long" for the preconfigured * (optional) The format to use, one of:
* date formats. If "custom" is specified, then $format is required as well. * - 'short', 'medium', or 'long' (the corresponding built-in date formats).
* - The name of a date type defined by a module in hook_date_format_types(),
* if it's been assigned a format.
* - The machine name of an administrator-defined date format.
* - 'custom', to use $format.
* Defaults to 'medium'.
* @param $format * @param $format
* A PHP date format string as required by date(). A backslash should be used * (optional) If $type is 'custom', a PHP date format string suitable for
* before a character to avoid interpreting the character as part of a date * input to date(). Use a backslash to escape ordinary text, so it does not
* format. * get interpreted as date format characters.
* @param $timezone * @param $timezone
* Time zone identifier; if omitted, the user's time zone is used. * (optional) Time zone identifier, as described at
* http://php.net/manual/en/timezones.php Defaults to the time zone used to
* display the page.
* @param $langcode * @param $langcode
* Optional language code to translate to a language other than what is used * (optional) Language code to translate to. Defaults to the language used to
* to display the page. * display the page.
*
* @return * @return
* A translated date string in the requested format. * A translated date string in the requested format.
*/ */
...@@ -1876,16 +1909,27 @@ function format_date($timestamp, $type = 'medium', $format = '', $timezone = NUL ...@@ -1876,16 +1909,27 @@ function format_date($timestamp, $type = 'medium', $format = '', $timezone = NUL
case 'short': case 'short':
$format = variable_get('date_format_short', 'm/d/Y - H:i'); $format = variable_get('date_format_short', 'm/d/Y - H:i');
break; break;
case 'long': case 'long':
$format = variable_get('date_format_long', 'l, F j, Y - H:i'); $format = variable_get('date_format_long', 'l, F j, Y - H:i');
break; break;
case 'custom': case 'custom':
// No change to format. // No change to format.
break; break;
case 'medium': case 'medium':
default: default:
// Retrieve the format of the custom $type passed.
if ($type != 'medium') {
$format = variable_get('date_format_' . $type, '');
}
// Fall back to 'medium'.
if ($format === '') {
$format = variable_get('date_format_medium', 'D, m/d/Y - H:i'); $format = variable_get('date_format_medium', 'D, m/d/Y - H:i');
} }
break;
}
// Create a DateTime object from the timestamp. // Create a DateTime object from the timestamp.
$date_time = date_create('@' . $timestamp); $date_time = date_create('@' . $timestamp);
...@@ -1912,10 +1956,11 @@ function format_date($timestamp, $type = 'medium', $format = '', $timezone = NUL ...@@ -1912,10 +1956,11 @@ function format_date($timestamp, $type = 'medium', $format = '', $timezone = NUL
/** /**
* Returns an ISO8601 formatted date based on the given date. * Returns an ISO8601 formatted date based on the given date.
* *
* Can be used as a callback for RDF mappings. * Callback for use within hook_rdf_mapping() implementations.
* *
* @param $date * @param $date
* A UNIX timestamp. * A UNIX timestamp.
*
* @return string * @return string
* An ISO8601 formatted date. * An ISO8601 formatted date.
*/ */
...@@ -1926,7 +1971,9 @@ function date_iso8601($date) { ...@@ -1926,7 +1971,9 @@ function date_iso8601($date) {
} }
/** /**
* Callback function for preg_replace_callback(). * Translates a formatted date string.
*
* Callback for preg_replace_callback() within format_date().
*/ */
function _format_date_callback(array $matches = NULL, $new_langcode = NULL) { function _format_date_callback(array $matches = NULL, $new_langcode = NULL) {
// We cache translations to avoid redundant and rather costly calls to t(). // We cache translations to avoid redundant and rather costly calls to t().
...@@ -1962,7 +2009,7 @@ function _format_date_callback(array $matches = NULL, $new_langcode = NULL) { ...@@ -1962,7 +2009,7 @@ function _format_date_callback(array $matches = NULL, $new_langcode = NULL) {
/** /**
* Format a username. * Format a username.
* *
* By default, the passed in object's 'name' property is used if it exists, or * By default, the passed-in object's 'name' property is used if it exists, or
* else, the site-defined value for the 'anonymous' variable. However, a module * else, the site-defined value for the 'anonymous' variable. However, a module
* may override this by implementing hook_username_alter(&$name, $account). * may override this by implementing hook_username_alter(&$name, $account).
* *
...@@ -2041,8 +2088,8 @@ function format_username($account) { ...@@ -2041,8 +2088,8 @@ function format_username($account) {
* Drupal on a web server that cannot be configured to automatically find * Drupal on a web server that cannot be configured to automatically find
* index.php, then hook_url_outbound_alter() can be implemented to force * index.php, then hook_url_outbound_alter() can be implemented to force
* this value to 'index.php'. * this value to 'index.php'.
* - 'entity_type': The entity type of the object that called url(). Only set if * - 'entity_type': The entity type of the object that called url(). Only
* url() is invoked by entity_uri(). * set if url() is invoked by entity_uri().
* - 'entity': The entity object (such as a node) for which the URL is being * - 'entity': The entity object (such as a node) for which the URL is being
* generated. Only set if url() is invoked by entity_uri(). * generated. Only set if url() is invoked by entity_uri().
* *
...@@ -2170,7 +2217,7 @@ function url($path = NULL, array $options = array()) { ...@@ -2170,7 +2217,7 @@ function url($path = NULL, array $options = array()) {
} }
/** /**
* Return TRUE if a path is external to Drupal (e.g. http://example.com). * Returns 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 * If a path cannot be assessed by Drupal's menu handler, then we must
* treat it as potentially insecure. * treat it as potentially insecure.
...@@ -2178,18 +2225,20 @@ function url($path = NULL, array $options = array()) { ...@@ -2178,18 +2225,20 @@ function url($path = NULL, array $options = array()) {
* @param $path * @param $path
* The internal path or external URL being linked to, such as "node/34" or * The internal path or external URL being linked to, such as "node/34" or
* "http://example.com/foo". * "http://example.com/foo".
*
* @return * @return
* Boolean TRUE or FALSE, where TRUE indicates an external path. * Boolean TRUE or FALSE, where TRUE indicates an external path.
*/ */
function url_is_external($path) { function url_is_external($path) {
$colonpos = strpos($path, ':'); $colonpos = strpos($path, ':');
// Only call the slow drupal_strip_dangerous_protocols() if $path contains a // Avoid calling drupal_strip_dangerous_protocols() if there is any
// ':' before any / ? or #. // slash (/), hash (#) or question_mark (?) before the colon (:)
// occurrence - if any - as this would clearly mean it is not a URL.
return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path; return $colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path;
} }
/** /**
* Format an attribute string for a HTTP header. * Formats an attribute string for an HTTP header.
* *
* @param $attributes * @param $attributes
* An associative array of attributes such as 'rel'. * An associative array of attributes such as 'rel'.
...@@ -2211,22 +2260,44 @@ function drupal_http_header_attributes(array $attributes = array()) { ...@@ -2211,22 +2260,44 @@ function drupal_http_header_attributes(array $attributes = array()) {
} }
/** /**
* Format an attribute string to insert in a tag. * Converts an associative array to an XML/HTML tag attribute string.
*
* Each array key and its value will be formatted into an attribute string.
* If a value is itself an array, then its elements are concatenated to a single
* space-delimited string (for example, a class attribute with multiple values).
* *
* Each array key and its value will be formatted into an HTML attribute string. * Attribute values are sanitized by running them through check_plain().
* If a value is itself an array, then each array element is concatenated with a * Attribute names are not automatically sanitized. When using user-supplied
* space between each value (e.g. a multi-value class attribute). * attribute names, it is strongly recommended to allow only white-listed names,
* since certain attributes carry security risks and can be abused.
*
* Examples of security aspects when using drupal_attributes:
* @code
* // By running the value in the following statement through check_plain,
* // the malicious script is neutralized.
* drupal_attributes(array('title' => t('<script>steal_cookie();</script>')));
*
* // The statement below demonstrates dangerous use of drupal_attributes, and
* // will return an onmouseout attribute with JavaScript code that, when used
* // as attribute in a tag, will cause users to be redirected to another site.
* //
* // In this case, the 'onmouseout' attribute should not be whitelisted --
* // you don't want users to have the ability to add this attribute or others
* // that take JavaScript commands.
* drupal_attributes(array('onmouseout' => 'window.location="http://malicious.com/";')));
* @endcode
* *
* @param $attributes * @param $attributes
* An associative array of HTML attributes. * An associative array of key-value pairs to be converted to attributes.
*
* @return * @return
* An HTML string ready for insertion in a tag. * A string ready for insertion in a tag (starts with a space).
*
* @ingroup sanitization
*/ */
function drupal_attributes(array $attributes = array()) { function drupal_attributes(array $attributes = array()) {
foreach ($attributes as $attribute => &$data) { foreach ($attributes as $attribute => &$data) {
if (is_array($data)) { $data = implode(' ', (array) $data);
$data = implode(' ', $data);
}
$data = $attribute . '="' . check_plain($data) . '"'; $data = $attribute . '="' . check_plain($data) . '"';
} }
return $attributes ? ' ' . implode(' ', $attributes) : ''; return $attributes ? ' ' . implode(' ', $attributes) : '';
...@@ -2251,10 +2322,14 @@ function drupal_attributes(array $attributes = array()) { ...@@ -2251,10 +2322,14 @@ function drupal_attributes(array $attributes = array()) {
* @param array $options * @param array $options
* An associative array of additional options, with the following elements: * An associative array of additional options, with the following elements:
* - 'attributes': An associative array of HTML attributes to apply to the * - 'attributes': An associative array of HTML attributes to apply to the
* anchor tag. * anchor tag. If element 'class' is included, it must be an array; 'title'
* must be a string; other elements are more flexible, as they just need
* to work in a call to drupal_attributes($options['attributes']).
* - 'html' (default FALSE): Whether $text is HTML or just plain-text. For * - 'html' (default FALSE): Whether $text is HTML or just plain-text. For
* example, to make an image tag into a link, this must be set to TRUE, or * example, to make an image tag into a link, this must be set to TRUE, or
* you will see the escaped HTML image tag. * you will see the escaped HTML image tag. $text is not sanitized if
* 'html' is TRUE. The calling function must ensure that $text is already
* safe.
* - 'language': An optional language object. If the path being linked to is * - 'language': An optional language object. If the path being linked to is
* internal to the site, $options['language'] is used to determine whether * internal to the site, $options['language'] is used to determine whether
* the link is "active", or pointing to the current page (the language as * the link is "active", or pointing to the current page (the language as
...@@ -2298,7 +2373,7 @@ function l($text, $path, array $options = array()) { ...@@ -2298,7 +2373,7 @@ function l($text, $path, array $options = array()) {
// rendering. // rendering.
if (variable_get('theme_link', TRUE)) { if (variable_get('theme_link', TRUE)) {
drupal_theme_initialize(); drupal_theme_initialize();
$registry = theme_get_registry(); $registry = theme_get_registry(FALSE);
// We don't want to duplicate functionality that's in theme(), so any // We don't want to duplicate functionality that's in theme(), so any
// hint of a module or theme doing anything at all special with the 'link' // hint of a module or theme doing anything at all special with the 'link'
// theme hook should simply result in theme() being called. This includes // theme hook should simply result in theme() being called. This includes
...@@ -2350,9 +2425,9 @@ function l($text, $path, array $options = array()) { ...@@ -2350,9 +2425,9 @@ function l($text, $path, array $options = array()) {
* basis in hook_page_delivery_callback_alter(). * basis in hook_page_delivery_callback_alter().
* *
* For example, the same page callback function can be used for an HTML * 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 * version of the page and an Ajax version of the page. The page callback
* function just needs to decide what content is to be returned and the * function just needs to decide what content is to be returned and the
* delivery callback function will send it as an HTML page or an AJAX * delivery callback function will send it as an HTML page or an Ajax
* response, as appropriate. * response, as appropriate.
* *
* In order for page callbacks to be reusable in different delivery formats, * In order for page callbacks to be reusable in different delivery formats,
...@@ -2406,7 +2481,7 @@ function drupal_deliver_page($page_callback_result, $default_delivery_callback = ...@@ -2406,7 +2481,7 @@ function drupal_deliver_page($page_callback_result, $default_delivery_callback =
} }
/** /**
* Package and send the result of a page callback to the browser as HTML. * Packages and sends the result of a page callback to the browser as HTML.
* *
* @param $page_callback_result * @param $page_callback_result
* The result of a page callback. Can be one of: * The result of a page callback. Can be one of:
...@@ -2426,6 +2501,10 @@ function drupal_deliver_html_page($page_callback_result) { ...@@ -2426,6 +2501,10 @@ function drupal_deliver_html_page($page_callback_result) {
drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
} }
// Send appropriate HTTP-Header for browsers and search engines.
global $language;
drupal_add_http_header('Content-Language', $language->language);
// Menu status constants are integers; page content is a string or array. // Menu status constants are integers; page content is a string or array.
if (is_int($page_callback_result)) { if (is_int($page_callback_result)) {
// @todo: Break these up into separate functions? // @todo: Break these up into separate functions?
...@@ -2436,6 +2515,9 @@ function drupal_deliver_html_page($page_callback_result) { ...@@ -2436,6 +2515,9 @@ function drupal_deliver_html_page($page_callback_result) {
watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING); watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);
// Check for and return a fast 404 page if configured.
drupal_fast_404();
// Keep old path for reference, and to allow forms to redirect to it. // Keep old path for reference, and to allow forms to redirect to it.
if (!isset($_GET['destination'])) { if (!isset($_GET['destination'])) {
$_GET['destination'] = $_GET['q']; $_GET['destination'] = $_GET['q'];
...@@ -2452,7 +2534,7 @@ function drupal_deliver_html_page($page_callback_result) { ...@@ -2452,7 +2534,7 @@ function drupal_deliver_html_page($page_callback_result) {
if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) { if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) {
// Standard 404 handler. // Standard 404 handler.
drupal_set_title(t('Page not found')); drupal_set_title(t('Page not found'));
$return = t('The requested page could not be found.'); $return = t('The requested page "@path" could not be found.', array('@path' => request_uri()));
} }
drupal_set_page_content($return); drupal_set_page_content($return);
...@@ -2508,7 +2590,7 @@ function drupal_deliver_html_page($page_callback_result) { ...@@ -2508,7 +2590,7 @@ function drupal_deliver_html_page($page_callback_result) {
} }
/** /**
* Perform end-of-request tasks. * Performs end-of-request tasks.
* *
* This function sets the page cache if appropriate, and allows modules to * This function sets the page cache if appropriate, and allows modules to
* react to the closing of the page by calling hook_exit(). * react to the closing of the page by calling hook_exit().
...@@ -2535,7 +2617,7 @@ function drupal_page_footer() { ...@@ -2535,7 +2617,7 @@ function drupal_page_footer() {
} }
/** /**
* Perform end-of-request tasks. * Performs end-of-request tasks.
* *
* In some cases page requests need to end without calling drupal_page_footer(). * In some cases page requests need to end without calling drupal_page_footer().
* In these cases, call drupal_exit() instead. There should rarely be a reason * In these cases, call drupal_exit() instead. There should rarely be a reason
...@@ -2557,7 +2639,7 @@ function drupal_exit($destination = NULL) { ...@@ -2557,7 +2639,7 @@ function drupal_exit($destination = NULL) {
} }
/** /**
* Form an associative array from a linear array. * Forms an associative array from a linear array.
* *
* This function walks through the provided array and constructs an associative * This function walks through the provided array and constructs an associative
* array out of it. The keys of the resulting array will be the values of the * array out of it. The keys of the resulting array will be the values of the
...@@ -2569,7 +2651,8 @@ function drupal_exit($destination = NULL) { ...@@ -2569,7 +2651,8 @@ function drupal_exit($destination = NULL) {
* A linear array. * A linear array.
* @param $function * @param $function
* A name of a function to apply to all values before output. * A name of a function to apply to all values before output.
* @result *
* @return
* An associative array. * An associative array.
*/ */
function drupal_map_assoc($array, $function = NULL) { function drupal_map_assoc($array, $function = NULL) {
...@@ -2632,10 +2715,10 @@ function drupal_get_path($type, $name) { ...@@ -2632,10 +2715,10 @@ function drupal_get_path($type, $name) {
} }
/** /**
* Return the base URL path (i.e., directory) of the Drupal installation. * Returns the base URL path (i.e., directory) of the Drupal installation.
* *
* base_path() prefixes and suffixes a "/" onto the returned path if the path is * base_path() adds a "/" to the beginning and end of the returned path if the
* not empty. At the very least, this will return "/". * path is not empty. At the very least, this will return "/".
* *
* Examples: * Examples:
* - http://example.com returns "/" because the path is empty. * - http://example.com returns "/" because the path is empty.
...@@ -2646,12 +2729,12 @@ function base_path() { ...@@ -2646,12 +2729,12 @@ function base_path() {
} }
/** /**
* Add a LINK tag with a distinct 'rel' attribute to the page's HEAD. * Adds a LINK tag with a distinct 'rel' attribute to the page's HEAD.
* *
* This function can be called as long the HTML header hasn't been sent, * This function can be called as long the HTML header hasn't been sent, which
* which on normal pages is up through the preprocess step of theme('html'). * on normal pages is up through the preprocess step of theme('html'). Adding
* Adding a link will overwrite a prior link with the exact same 'rel' and * a link will overwrite a prior link with the exact same 'rel' and 'href'
* 'href' attributes. * attributes.
* *
* @param $attributes * @param $attributes
* Associative array of element attributes including 'href' and 'rel'. * Associative array of element attributes including 'href' and 'rel'.
...@@ -2705,17 +2788,18 @@ function drupal_add_html_head_link($attributes, $header = FALSE) { ...@@ -2705,17 +2788,18 @@ function drupal_add_html_head_link($attributes, $header = FALSE) {
* @param $data * @param $data
* (optional) The stylesheet data to be added, depending on what is passed * (optional) The stylesheet data to be added, depending on what is passed
* through to the $options['type'] parameter: * through to the $options['type'] parameter:
* - 'file': The path to the CSS file relative to the base_path(), e.g., * - 'file': The path to the CSS file relative to the base_path(), or a
* "modules/devel/devel.css". Note that Modules should always prefix the * stream wrapper URI. For example: "modules/devel/devel.css" or
* names of their CSS files with the module name; for example, * "public://generated_css/stylesheet_1.css". Note that Modules should
* system-menus.css rather than simply menus.css. Themes can override * always prefix the names of their CSS files with the module name; for
* module-supplied CSS files based on their filenames, and this prefixing * example, system-menus.css rather than simply menus.css. Themes can
* helps prevent confusing name collisions for theme developers. See * override module-supplied CSS files based on their filenames, and this
* drupal_get_css() where the overrides are performed. Also, if the * prefixing helps prevent confusing name collisions for theme developers.
* See drupal_get_css() where the overrides are performed. Also, if the
* direction of the current language is right-to-left (Hebrew, Arabic, * direction of the current language is right-to-left (Hebrew, Arabic,
* etc.), the function will also look for an RTL CSS file and append it to * etc.), the function will also look for an RTL CSS file and append it to
* the list. The name of this file should have an '-rtl.css' suffix. For * the list. The name of this file should have an '-rtl.css' suffix. For
* example a CSS file called 'mymodule-name.css' will have a * example, a CSS file called 'mymodule-name.css' will have a
* 'mymodule-name-rtl.css' file added to the list, if exists in the same * 'mymodule-name-rtl.css' file added to the list, if exists in the same
* directory. This CSS file should contain overrides for properties which * directory. This CSS file should contain overrides for properties which
* should be reversed or otherwise different in a right-to-left display. * should be reversed or otherwise different in a right-to-left display.
...@@ -2787,6 +2871,8 @@ function drupal_add_html_head_link($attributes, $header = FALSE) { ...@@ -2787,6 +2871,8 @@ function drupal_add_html_head_link($attributes, $header = FALSE) {
* *
* @return * @return
* An array of queued cascading stylesheets. * An array of queued cascading stylesheets.
*
* @see drupal_get_css()
*/ */
function drupal_add_css($data = NULL, $options = NULL) { function drupal_add_css($data = NULL, $options = NULL) {
$css = &drupal_static(__FUNCTION__, array()); $css = &drupal_static(__FUNCTION__, array());
...@@ -2845,7 +2931,7 @@ function drupal_add_css($data = NULL, $options = NULL) { ...@@ -2845,7 +2931,7 @@ function drupal_add_css($data = NULL, $options = NULL) {
} }
/** /**
* Returns a themed representation of all stylesheets that should be attached to the page. * Returns a themed representation of all stylesheets to attach to the page.
* *
* It loads the CSS in order, with 'module' first, then 'theme' afterwards. * It loads the CSS in order, with 'module' first, then 'theme' afterwards.
* This ensures proper cascading of styles so themes can easily override * This ensures proper cascading of styles so themes can easily override
...@@ -2867,8 +2953,11 @@ function drupal_add_css($data = NULL, $options = NULL) { ...@@ -2867,8 +2953,11 @@ function drupal_add_css($data = NULL, $options = NULL) {
* (optional) If set to TRUE, this function skips calling drupal_alter() on * (optional) If set to TRUE, this function skips calling drupal_alter() on
* $css, useful when the calling function passes a $css array that has already * $css, useful when the calling function passes a $css array that has already
* been altered. * been altered.
*
* @return * @return
* A string of XHTML CSS tags. * A string of XHTML CSS tags.
*
* @see drupal_add_css()
*/ */
function drupal_get_css($css = NULL, $skip_alter = FALSE) { function drupal_get_css($css = NULL, $skip_alter = FALSE) {
if (!isset($css)) { if (!isset($css)) {
...@@ -2888,7 +2977,7 @@ function drupal_get_css($css = NULL, $skip_alter = FALSE) { ...@@ -2888,7 +2977,7 @@ function drupal_get_css($css = NULL, $skip_alter = FALSE) {
foreach ($css as $key => $item) { foreach ($css as $key => $item) {
if ($item['type'] == 'file') { if ($item['type'] == 'file') {
// If defined, force a unique basename for this file. // If defined, force a unique basename for this file.
$basename = isset($item['basename']) ? $item['basename'] : basename($item['data']); $basename = isset($item['basename']) ? $item['basename'] : drupal_basename($item['data']);
if (isset($previous_item[$basename])) { if (isset($previous_item[$basename])) {
// Remove the previous item that shared the same base name. // Remove the previous item that shared the same base name.
unset($css[$previous_item[$basename]]); unset($css[$previous_item[$basename]]);
...@@ -2912,15 +3001,28 @@ function drupal_get_css($css = NULL, $skip_alter = FALSE) { ...@@ -2912,15 +3001,28 @@ function drupal_get_css($css = NULL, $skip_alter = FALSE) {
} }
/** /**
* Function used by uasort to sort the array structures returned by drupal_add_css() and drupal_add_js(). * Sorts CSS and JavaScript resources.
*
* Callback for uasort() within:
* - drupal_get_css()
* - drupal_get_js()
* *
* This sort order helps optimize front-end performance while providing modules * This sort order helps optimize front-end performance while providing modules
* and themes with the necessary control for ordering the CSS and JavaScript * and themes with the necessary control for ordering the CSS and JavaScript
* appearing on a page. * appearing on a page.
*
* @param $a
* First item for comparison. The compared items should be associative arrays
* of member items from drupal_add_css() or drupal_add_js().
* @param $b
* Second item for comparison.
*
* @see drupal_add_css()
* @see drupal_add_js()
*/ */
function drupal_sort_css_js($a, $b) { function drupal_sort_css_js($a, $b) {
// First order by group, so that, for example, all items in the CSS_SYSTEM // First order by group, so that, for example, all items in the CSS_SYSTEM
// group appear before items in the CSS_DEFAULT_GROUP, which appear before // group appear before items in the CSS_DEFAULT group, which appear before
// all items in the CSS_THEME group. Modules may create additional groups by // all items in the CSS_THEME group. Modules may create additional groups by
// defining their own constants. // defining their own constants.
if ($a['group'] < $b['group']) { if ($a['group'] < $b['group']) {
...@@ -2969,7 +3071,7 @@ function drupal_sort_css_js($a, $b) { ...@@ -2969,7 +3071,7 @@ function drupal_sort_css_js($a, $b) {
* are always groupable, and items of the 'external' type are never groupable. * are always groupable, and items of the 'external' type are never groupable.
* This function also ensures that the process of grouping items does not change * This function also ensures that the process of grouping items does not change
* their relative order. This requirement may result in multiple groups for the * their relative order. This requirement may result in multiple groups for the
* same type, media, and browsers, if needed to accomodate other items in * same type, media, and browsers, if needed to accommodate other items in
* between. * between.
* *
* @param $css * @param $css
...@@ -2983,6 +3085,7 @@ function drupal_sort_css_js($a, $b) { ...@@ -2983,6 +3085,7 @@ function drupal_sort_css_js($a, $b) {
* 'items' key, which is the subset of items from $css that are in the group. * 'items' key, which is the subset of items from $css that are in the group.
* *
* @see drupal_pre_render_styles() * @see drupal_pre_render_styles()
* @see system_element_info()
*/ */
function drupal_group_css($css) { function drupal_group_css($css) {
$groups = array(); $groups = array();
...@@ -3065,6 +3168,7 @@ function drupal_group_css($css) { ...@@ -3065,6 +3168,7 @@ function drupal_group_css($css) {
* *
* @see drupal_group_css() * @see drupal_group_css()
* @see drupal_pre_render_styles() * @see drupal_pre_render_styles()
* @see system_element_info()
*/ */
function drupal_aggregate_css(&$css_groups) { function drupal_aggregate_css(&$css_groups) {
$preprocess_css = (variable_get('preprocess_css', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')); $preprocess_css = (variable_get('preprocess_css', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update'));
...@@ -3166,6 +3270,12 @@ function drupal_pre_render_styles($elements) { ...@@ -3166,6 +3270,12 @@ function drupal_pre_render_styles($elements) {
// URL changed. // URL changed.
$query_string = variable_get('css_js_query_string', '0'); $query_string = variable_get('css_js_query_string', '0');
// For inline CSS to validate as XHTML, all CSS containing XHTML needs to be
// wrapped in CDATA. To make that backwards compatible with HTML 4, we need to
// comment out the CDATA-tag.
$embed_prefix = "\n<!--/*--><![CDATA[/*><!--*/\n";
$embed_suffix = "\n/*]]>*/-->\n";
// Defaults for LINK and STYLE elements. // Defaults for LINK and STYLE elements.
$link_element_defaults = array( $link_element_defaults = array(
'#type' => 'html_tag', '#type' => 'html_tag',
...@@ -3273,6 +3383,8 @@ function drupal_pre_render_styles($elements) { ...@@ -3273,6 +3383,8 @@ function drupal_pre_render_styles($elements) {
if (isset($group['data'])) { if (isset($group['data'])) {
$element = $style_element_defaults; $element = $style_element_defaults;
$element['#value'] = $group['data']; $element['#value'] = $group['data'];
$element['#value_prefix'] = $embed_prefix;
$element['#value_suffix'] = $embed_suffix;
$element['#attributes']['media'] = $group['media']; $element['#attributes']['media'] = $group['media'];
$element['#browsers'] = $group['browsers']; $element['#browsers'] = $group['browsers'];
$elements[] = $element; $elements[] = $element;
...@@ -3281,6 +3393,8 @@ function drupal_pre_render_styles($elements) { ...@@ -3281,6 +3393,8 @@ function drupal_pre_render_styles($elements) {
foreach ($group['items'] as $item) { foreach ($group['items'] as $item) {
$element = $style_element_defaults; $element = $style_element_defaults;
$element['#value'] = $item['data']; $element['#value'] = $item['data'];
$element['#value_prefix'] = $embed_prefix;
$element['#value_suffix'] = $embed_suffix;
$element['#attributes']['media'] = $item['media']; $element['#attributes']['media'] = $item['media'];
$element['#browsers'] = $group['browsers']; $element['#browsers'] = $group['browsers'];
$elements[] = $element; $elements[] = $element;
...@@ -3316,8 +3430,8 @@ function drupal_pre_render_styles($elements) { ...@@ -3316,8 +3430,8 @@ function drupal_pre_render_styles($elements) {
* in $css while the value is the cache file name. The cache file is generated * in $css while the value is the cache file name. The cache file is generated
* in two cases. First, if there is no file name value for the key, which will * in two cases. First, if there is no file name value for the key, which will
* happen if a new file name has been added to $css or after the lookup * happen if a new file name has been added to $css or after the lookup
* variable is emptied to force a rebuild of the cache. Second, the cache * variable is emptied to force a rebuild of the cache. Second, the cache file
* file is generated if it is missing on disk. Old cache files are not deleted * is generated if it is missing on disk. Old cache files are not deleted
* immediately when the lookup variable is emptied, but are deleted after a set * immediately when the lookup variable is emptied, but are deleted after a set
* period by drupal_delete_file_if_stale(). This ensures that files referenced * period by drupal_delete_file_if_stale(). This ensures that files referenced
* by a cached page will still be available. * by a cached page will still be available.
...@@ -3343,10 +3457,19 @@ function drupal_build_css_cache($css) { ...@@ -3343,10 +3457,19 @@ function drupal_build_css_cache($css) {
// Only 'file' stylesheets can be aggregated. // Only 'file' stylesheets can be aggregated.
if ($stylesheet['type'] == 'file') { if ($stylesheet['type'] == 'file') {
$contents = drupal_load_stylesheet($stylesheet['data'], TRUE); $contents = drupal_load_stylesheet($stylesheet['data'], TRUE);
// Return the path to where this CSS file originated from.
$base = base_path() . dirname($stylesheet['data']) . '/'; // Build the base URL of this CSS file: start with the full URL.
_drupal_build_css_path(NULL, $base); $css_base_url = file_create_url($stylesheet['data']);
// Prefix all paths within this CSS file, ignoring external and absolute paths. // Move to the parent.
$css_base_url = substr($css_base_url, 0, strrpos($css_base_url, '/'));
// Simplify to a relative URL if the stylesheet URL starts with the
// base URL of the website.
if (substr($css_base_url, 0, strlen($GLOBALS['base_root'])) == $GLOBALS['base_root']) {
$css_base_url = substr($css_base_url, strlen($GLOBALS['base_root']));
}
_drupal_build_css_path(NULL, $css_base_url . '/');
// Anchor all paths in the CSS with its base URL, ignoring external and absolute paths.
$data .= preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', '_drupal_build_css_path', $contents); $data .= preg_replace_callback('/url\(\s*[\'"]?(?![a-z]+:|\/+)([^\'")]+)[\'"]?\s*\)/i', '_drupal_build_css_path', $contents);
} }
} }
...@@ -3386,9 +3509,7 @@ function drupal_build_css_cache($css) { ...@@ -3386,9 +3509,7 @@ function drupal_build_css_cache($css) {
} }
/** /**
* Helper function for drupal_build_css_cache(). * Prefixes all paths within a CSS file for drupal_build_css_cache().
*
* This function will prefix all paths within a CSS file.
*/ */
function _drupal_build_css_path($matches, $base = NULL) { function _drupal_build_css_path($matches, $base = NULL) {
$_base = &drupal_static(__FUNCTION__); $_base = &drupal_static(__FUNCTION__);
...@@ -3422,44 +3543,51 @@ function _drupal_build_css_path($matches, $base = NULL) { ...@@ -3422,44 +3543,51 @@ function _drupal_build_css_path($matches, $base = NULL) {
* Name of the stylesheet to be processed. * Name of the stylesheet to be processed.
* @param $optimize * @param $optimize
* Defines if CSS contents should be compressed or not. * Defines if CSS contents should be compressed or not.
* @param $reset_basepath
* Used internally to facilitate recursive resolution of @import commands.
*
* @return * @return
* Contents of the stylesheet, including any resolved @import commands. * Contents of the stylesheet, including any resolved @import commands.
*/ */
function drupal_load_stylesheet($file, $optimize = NULL) { function drupal_load_stylesheet($file, $optimize = NULL, $reset_basepath = TRUE) {
// $_optimize does not use drupal_static as it is set by $optimize. // These statics are not cache variables, so we don't use drupal_static().
static $_optimize; static $_optimize, $basepath;
// Store optimization parameter for preg_replace_callback with nested @import loops. if ($reset_basepath) {
$basepath = '';
}
// Store the value of $optimize for preg_replace_callback with nested
// @import loops.
if (isset($optimize)) { if (isset($optimize)) {
$_optimize = $optimize; $_optimize = $optimize;
} }
$contents = ''; // Stylesheets are relative one to each other. Start by adding a base path
if (file_exists($file)) { // prefix provided by the parent stylesheet (if necessary).
// Load the local CSS stylesheet. if ($basepath && !file_uri_scheme($file)) {
$contents = file_get_contents($file); $file = $basepath . '/' . $file;
}
// Change to the current stylesheet's directory. $basepath = dirname($file);
$cwd = getcwd();
chdir(dirname($file));
// Process the stylesheet.
$contents = drupal_load_stylesheet_content($contents, $_optimize);
// Change back directory. // Load the CSS stylesheet. We suppress errors because themes may specify
chdir($cwd); // stylesheets in their .info file that don't exist in the theme's path,
// but are merely there to disable certain module CSS files.
if ($contents = @file_get_contents($file)) {
// Return the processed stylesheet.
return drupal_load_stylesheet_content($contents, $_optimize);
} }
return $contents; return '';
} }
/** /**
* Process the contents of a stylesheet for aggregation. * Processes the contents of a stylesheet for aggregation.
* *
* @param $contents * @param $contents
* The contents of the stylesheet. * The contents of the stylesheet.
* @param $optimize * @param $optimize
* (optional) Boolean whether CSS contents should be minified. Defaults to * (optional) Boolean whether CSS contents should be minified. Defaults to
* FALSE. * FALSE.
*
* @return * @return
* Contents of the stylesheet including the imported stylesheets. * Contents of the stylesheet including the imported stylesheets.
*/ */
...@@ -3483,12 +3611,9 @@ function drupal_load_stylesheet_content($contents, $optimize = FALSE) { ...@@ -3483,12 +3611,9 @@ function drupal_load_stylesheet_content($contents, $optimize = FALSE) {
); );
// Remove certain whitespace. // Remove certain whitespace.
// There are different conditions for removing leading and trailing // There are different conditions for removing leading and trailing
// whitespace. To be able to use a single backreference in the replacement // whitespace.
// string, the outer pattern uses the ?| modifier, which makes all contained
// subpatterns appear in \1.
// @see http://php.net/manual/en/regexp.reference.subpatterns.php // @see http://php.net/manual/en/regexp.reference.subpatterns.php
$contents = preg_replace('< $contents = preg_replace('<
(?|
# Strip leading and trailing whitespace. # Strip leading and trailing whitespace.
\s*([@{};,])\s* \s*([@{};,])\s*
# Strip only leading whitespace from: # Strip only leading whitespace from:
...@@ -3498,12 +3623,15 @@ function drupal_load_stylesheet_content($contents, $optimize = FALSE) { ...@@ -3498,12 +3623,15 @@ function drupal_load_stylesheet_content($contents, $optimize = FALSE) {
# - Opening parenthesis: Retain "@media (bar) and foo". # - Opening parenthesis: Retain "@media (bar) and foo".
# - Colon: Retain :pseudo-selectors. # - Colon: Retain :pseudo-selectors.
| ([\(:])\s+ | ([\(:])\s+
)
>xS', >xS',
'\1', // Only one of the three capturing groups will match, so its reference
// will contain the wanted value and the references for the
// two non-matching groups will be replaced with empty strings.
'$1$2$3',
$contents $contents
); );
// End the file with a new line. // End the file with a new line.
$contents = trim($contents);
$contents .= "\n"; $contents .= "\n";
} }
...@@ -3522,7 +3650,7 @@ function drupal_load_stylesheet_content($contents, $optimize = FALSE) { ...@@ -3522,7 +3650,7 @@ function drupal_load_stylesheet_content($contents, $optimize = FALSE) {
function _drupal_load_stylesheet($matches) { function _drupal_load_stylesheet($matches) {
$filename = $matches[1]; $filename = $matches[1];
// Load the imported stylesheet and replace @import commands in there as well. // Load the imported stylesheet and replace @import commands in there as well.
$file = drupal_load_stylesheet($filename); $file = drupal_load_stylesheet($filename, NULL, FALSE);
// Determine the file's directory. // Determine the file's directory.
$directory = dirname($filename); $directory = dirname($filename);
...@@ -3555,7 +3683,7 @@ function drupal_delete_file_if_stale($uri) { ...@@ -3555,7 +3683,7 @@ function drupal_delete_file_if_stale($uri) {
} }
/** /**
* Prepare a string for use as a valid CSS identifier (element, class or ID name). * Prepares a string for use as a CSS identifier (element, class, or ID name).
* *
* http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for valid * http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for valid
* CSS identifiers (including element names, classes, and IDs in selectors.) * CSS identifiers (including element names, classes, and IDs in selectors.)
...@@ -3564,6 +3692,7 @@ function drupal_delete_file_if_stale($uri) { ...@@ -3564,6 +3692,7 @@ function drupal_delete_file_if_stale($uri) {
* The identifier to clean. * The identifier to clean.
* @param $filter * @param $filter
* An array of string replacements to use on the identifier. * An array of string replacements to use on the identifier.
*
* @return * @return
* The cleaned identifier. * The cleaned identifier.
*/ */
...@@ -3585,13 +3714,14 @@ function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_ ...@@ -3585,13 +3714,14 @@ function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_
} }
/** /**
* Prepare a string for use as a valid class name. * Prepares a string for use as a valid class name.
* *
* Do not pass one string containing multiple classes as they will be * Do not pass one string containing multiple classes as they will be
* incorrectly concatenated with dashes, i.e. "one two" will become "one-two". * incorrectly concatenated with dashes, i.e. "one two" will become "one-two".
* *
* @param $class * @param $class
* The class name to clean. * The class name to clean.
*
* @return * @return
* The cleaned class name. * The cleaned class name.
*/ */
...@@ -3600,24 +3730,24 @@ function drupal_html_class($class) { ...@@ -3600,24 +3730,24 @@ function drupal_html_class($class) {
} }
/** /**
* Prepare a string for use as a valid HTML ID and guarantee uniqueness. * Prepares a string for use as a valid HTML ID and guarantees uniqueness.
* *
* This function ensures that each passed HTML ID value only exists once on the * This function ensures that each passed HTML ID value only exists once on the
* page. By tracking the already returned ids, this function enables forms, * page. By tracking the already returned ids, this function enables forms,
* blocks, and other content to be output multiple times on the same page, * blocks, and other content to be output multiple times on the same page,
* without breaking (X)HTML validation. * without breaking (X)HTML validation.
* *
* For already existing ids, a counter is appended to the id string. Therefore, * For already existing IDs, a counter is appended to the ID string. Therefore,
* JavaScript and CSS code should not rely on any value that was generated by * JavaScript and CSS code should not rely on any value that was generated by
* this function and instead should rely on manually added CSS classes or * this function and instead should rely on manually added CSS classes or
* similarly reliable constructs. * similarly reliable constructs.
* *
* Two consecutive hyphens separate the counter from the original id. To manage * Two consecutive hyphens separate the counter from the original ID. To manage
* uniqueness across multiple AJAX requests on the same page, AJAX requests * uniqueness across multiple Ajax requests on the same page, Ajax requests
* POST an array of all IDs currently present on the page, which are used to * POST an array of all IDs currently present on the page, which are used to
* prime this function's cache upon first invocation. * prime this function's cache upon first invocation.
* *
* To allow reverse-parsing of ids submitted via AJAX, any multiple consecutive * To allow reverse-parsing of IDs submitted via Ajax, any multiple consecutive
* hyphens in the originally passed $id are replaced with a single hyphen. * hyphens in the originally passed $id are replaced with a single hyphen.
* *
* @param $id * @param $id
...@@ -3627,10 +3757,10 @@ function drupal_html_class($class) { ...@@ -3627,10 +3757,10 @@ function drupal_html_class($class) {
* The cleaned ID. * The cleaned ID.
*/ */
function drupal_html_id($id) { function drupal_html_id($id) {
// If this is an AJAX request, then content returned by this page request will // If this is an Ajax request, then content returned by this page request will
// be merged with content already on the base page. The HTML ids must be // be merged with content already on the base page. The HTML IDs must be
// unique for the fully merged content. Therefore, initialize $seen_ids to // unique for the fully merged content. Therefore, initialize $seen_ids to
// take into account ids that are already in use on the base page. // take into account IDs that are already in use on the base page.
$seen_ids_init = &drupal_static(__FUNCTION__ . ':init'); $seen_ids_init = &drupal_static(__FUNCTION__ . ':init');
if (!isset($seen_ids_init)) { if (!isset($seen_ids_init)) {
// Ideally, Drupal would provide an API to persist state information about // Ideally, Drupal would provide an API to persist state information about
...@@ -3638,7 +3768,7 @@ function drupal_html_id($id) { ...@@ -3638,7 +3768,7 @@ function drupal_html_id($id) {
// function's $seen_ids static variable to that state information in order // function's $seen_ids static variable to that state information in order
// to have it properly initialized for this page request. However, no such // to have it properly initialized for this page request. However, no such
// page state API exists, so instead, ajax.js adds all of the in-use HTML // page state API exists, so instead, ajax.js adds all of the in-use HTML
// ids to the POST data of AJAX submissions. Direct use of $_POST is // IDs to the POST data of Ajax submissions. Direct use of $_POST is
// normally not recommended as it could open up security risks, but because // normally not recommended as it could open up security risks, but because
// the raw POST data is cast to a number before being returned by this // the raw POST data is cast to a number before being returned by this
// function, this usage is safe. // function, this usage is safe.
...@@ -3685,7 +3815,7 @@ function drupal_html_id($id) { ...@@ -3685,7 +3815,7 @@ function drupal_html_id($id) {
// The counter needs to be appended with a delimiter that does not exist in // The counter needs to be appended with a delimiter that does not exist in
// the base ID. Requiring a unique delimiter helps ensure that we really do // the base ID. Requiring a unique delimiter helps ensure that we really do
// return unique IDs and also helps us re-create the $seen_ids array during // return unique IDs and also helps us re-create the $seen_ids array during
// AJAX requests. // Ajax requests.
if (isset($seen_ids[$id])) { if (isset($seen_ids[$id])) {
$id = $id . '--' . ++$seen_ids[$id]; $id = $id . '--' . ++$seen_ids[$id];
} }
...@@ -3703,7 +3833,7 @@ function drupal_html_id($id) { ...@@ -3703,7 +3833,7 @@ function drupal_html_id($id) {
* page region that is output by the theme (Drupal core already handles this in * page region that is output by the theme (Drupal core already handles this in
* the standard template preprocess implementation). Standardizing the class * the standard template preprocess implementation). Standardizing the class
* names in this way allows modules to implement certain features, such as * names in this way allows modules to implement certain features, such as
* drag-and-drop or dynamic AJAX loading, in a theme-independent way. * drag-and-drop or dynamic Ajax loading, in a theme-independent way.
* *
* @param $region * @param $region
* The name of the page region (for example, 'page_top' or 'content'). * The name of the page region (for example, 'page_top' or 'content').
...@@ -3751,6 +3881,7 @@ function drupal_region_class($region) { ...@@ -3751,6 +3881,7 @@ function drupal_region_class($region) {
* array('type' => 'inline', 'scope' => 'footer', 'weight' => 5) * array('type' => 'inline', 'scope' => 'footer', 'weight' => 5)
* ); * );
* drupal_add_js('http://example.com/example.js', 'external'); * drupal_add_js('http://example.com/example.js', 'external');
* drupal_add_js(array('myModule' => array('key' => 'value')), 'setting');
* @endcode * @endcode
* *
* Calling drupal_static_reset('drupal_add_js') will clear all JavaScript added * Calling drupal_static_reset('drupal_add_js') will clear all JavaScript added
...@@ -3770,7 +3901,7 @@ function drupal_region_class($region) { ...@@ -3770,7 +3901,7 @@ function drupal_region_class($region) {
* all typical visitors and most pages of a site. It is critical that all * all typical visitors and most pages of a site. It is critical that all
* preprocessed files are added unconditionally on every page, even if the * preprocessed files are added unconditionally on every page, even if the
* files are not needed on a page. This is normally done by calling * files are not needed on a page. This is normally done by calling
* drupal_add_css() in a hook_init() implementation. * drupal_add_js() in a hook_init() implementation.
* *
* Non-preprocessed files should only be added to the page when they are * Non-preprocessed files should only be added to the page when they are
* actually needed. * actually needed.
...@@ -3783,9 +3914,11 @@ function drupal_region_class($region) { ...@@ -3783,9 +3914,11 @@ function drupal_region_class($region) {
* hosted on the local server. These files will not be aggregated if * hosted on the local server. These files will not be aggregated if
* JavaScript aggregation is enabled. * JavaScript aggregation is enabled.
* - 'setting': An associative array with configuration options. The array is * - 'setting': An associative array with configuration options. The array is
* directly placed in Drupal.settings. All modules should wrap their actual * merged directly into Drupal.settings. All modules should wrap their
* configuration settings in another variable to prevent conflicts in the * actual configuration settings in another variable to prevent conflicts in
* Drupal.settings namespace. * the Drupal.settings namespace. Items added with a string key will replace
* existing settings with that key; items with numeric array keys will be
* added to the existing settings array.
* @param $options * @param $options
* (optional) A string defining the type of JavaScript that is being added in * (optional) A string defining the type of JavaScript that is being added in
* the $data parameter ('file'/'setting'/'inline'/'external'), or an * the $data parameter ('file'/'setting'/'inline'/'external'), or an
...@@ -3885,12 +4018,17 @@ function drupal_add_js($data = NULL, $options = NULL) { ...@@ -3885,12 +4018,17 @@ function drupal_add_js($data = NULL, $options = NULL) {
if (isset($data)) { if (isset($data)) {
// Add jquery.js and drupal.js, as well as the basePath setting, the // Add jquery.js and drupal.js, as well as the basePath setting, the
// first time a Javascript file is added. // first time a JavaScript file is added.
if (empty($javascript)) { if (empty($javascript)) {
// url() generates the prefix using hook_url_outbound_alter(). Instead of
// running the hook_url_outbound_alter() again here, extract the prefix
// from url().
url('', array('prefix' => &$prefix));
$javascript = array( $javascript = array(
'settings' => array( 'settings' => array(
'data' => array( 'data' => array(
array('basePath' => base_path()), array('basePath' => base_path()),
array('pathPrefix' => empty($prefix) ? '' : $prefix),
), ),
'type' => 'setting', 'type' => 'setting',
'scope' => 'header', 'scope' => 'header',
...@@ -3912,7 +4050,7 @@ function drupal_add_js($data = NULL, $options = NULL) { ...@@ -3912,7 +4050,7 @@ function drupal_add_js($data = NULL, $options = NULL) {
); );
// Register all required libraries. // Register all required libraries.
drupal_add_library('system', 'jquery', TRUE); drupal_add_library('system', 'jquery', TRUE);
drupal_add_library('system', 'once', TRUE); drupal_add_library('system', 'jquery.once', TRUE);
} }
switch ($options['type']) { switch ($options['type']) {
...@@ -3928,7 +4066,7 @@ function drupal_add_js($data = NULL, $options = NULL) { ...@@ -3928,7 +4066,7 @@ function drupal_add_js($data = NULL, $options = NULL) {
default: // 'file' and 'external' default: // 'file' and 'external'
// Local and external files must keep their name as the associative key // Local and external files must keep their name as the associative key
// so the same JavaScript file is not be added twice. // so the same JavaScript file is not added twice.
$javascript[$options['data']] = $options; $javascript[$options['data']] = $options;
} }
} }
...@@ -3940,6 +4078,7 @@ function drupal_add_js($data = NULL, $options = NULL) { ...@@ -3940,6 +4078,7 @@ function drupal_add_js($data = NULL, $options = NULL) {
* *
* @param $data * @param $data
* (optional) The default data parameter for the JavaScript item array. * (optional) The default data parameter for the JavaScript item array.
*
* @see drupal_get_js() * @see drupal_get_js()
* @see drupal_add_js() * @see drupal_add_js()
*/ */
...@@ -3983,8 +4122,10 @@ function drupal_js_defaults($data = NULL) { ...@@ -3983,8 +4122,10 @@ function drupal_js_defaults($data = NULL) {
* (optional) If set to TRUE, this function skips calling drupal_alter() on * (optional) If set to TRUE, this function skips calling drupal_alter() on
* $javascript, useful when the calling function passes a $javascript array * $javascript, useful when the calling function passes a $javascript array
* that has already been altered. * that has already been altered.
*
* @return * @return
* All JavaScript code segments and includes for the scope as HTML tags. * All JavaScript code segments and includes for the scope as HTML tags.
*
* @see drupal_add_js() * @see drupal_add_js()
* @see locale_js_alter() * @see locale_js_alter()
* @see drupal_js_defaults() * @see drupal_js_defaults()
...@@ -4026,13 +4167,13 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS ...@@ -4026,13 +4167,13 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
// page request. // page request.
$default_query_string = variable_get('css_js_query_string', '0'); $default_query_string = variable_get('css_js_query_string', '0');
// For inline Javascript to validate as XHTML, all Javascript containing // For inline JavaScript to validate as XHTML, all JavaScript containing
// XHTML needs to be wrapped in CDATA. To make that backwards compatible // XHTML needs to be wrapped in CDATA. To make that backwards compatible
// with HTML 4, we need to comment out the CDATA-tag. // with HTML 4, we need to comment out the CDATA-tag.
$embed_prefix = "\n<!--//--><![CDATA[//><!--\n"; $embed_prefix = "\n<!--//--><![CDATA[//><!--\n";
$embed_suffix = "\n//--><!]]>\n"; $embed_suffix = "\n//--><!]]>\n";
// Since Javascript may look for arguments in the url and act on them, some // Since JavaScript may look for arguments in the URL and act on them, some
// third-party code might require the use of a different query string. // third-party code might require the use of a different query string.
$js_version_string = variable_get('drupal_js_version_query_string', 'v='); $js_version_string = variable_get('drupal_js_version_query_string', 'v=');
...@@ -4070,7 +4211,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS ...@@ -4070,7 +4211,7 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
case 'setting': case 'setting':
$js_element = $element; $js_element = $element;
$js_element['#value_prefix'] = $embed_prefix; $js_element['#value_prefix'] = $embed_prefix;
$js_element['#value'] = 'jQuery.extend(Drupal.settings, ' . drupal_json_encode(call_user_func_array('array_merge_recursive', $item['data'])) . ");"; $js_element['#value'] = 'jQuery.extend(Drupal.settings, ' . drupal_json_encode(drupal_array_merge_deep_array($item['data'])) . ");";
$js_element['#value_suffix'] = $embed_suffix; $js_element['#value_suffix'] = $embed_suffix;
$output .= theme('html_tag', array('element' => $js_element)); $output .= theme('html_tag', array('element' => $js_element));
break; break;
...@@ -4143,12 +4284,12 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS ...@@ -4143,12 +4284,12 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
} }
/** /**
* Add to the page all structures attached to a render() structure. * Adds attachments to a render() structure.
* *
* Libraries, JavaScript, CSS and other types of custom structures are attached * Libraries, JavaScript, CSS and other types of custom structures are attached
* to elements using the #attached property. The #attached property contains an * to elements using the #attached property. The #attached property is an
* associative array, where the keys are the the types of the structure, and * associative array, where the keys are the the attachment types and the values
* the value the attached data. For example: * are the attached data. For example:
* @code * @code
* $build['#attached'] = array( * $build['#attached'] = array(
* 'js' => array(drupal_get_path('module', 'taxonomy') . '/taxonomy.js'), * 'js' => array(drupal_get_path('module', 'taxonomy') . '/taxonomy.js'),
...@@ -4159,13 +4300,21 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS ...@@ -4159,13 +4300,21 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
* 'js', 'css', and 'library' are types that get special handling. For any * 'js', 'css', and 'library' are types that get special handling. For any
* other kind of attached data, the array key must be the full name of the * other kind of attached data, the array key must be the full name of the
* callback function and each value an array of arguments. For example: * callback function and each value an array of arguments. For example:
*
* @code * @code
* $build['#attached']['drupal_add_http_header'] = array( * $build['#attached']['drupal_add_http_header'] = array(
* array('Content-Type', 'application/rss+xml; charset=utf-8'), * array('Content-Type', 'application/rss+xml; charset=utf-8'),
* ); * );
* @endcode * @endcode
* *
* External 'js' and 'css' files can also be loaded. For example:
* @code
* $build['#attached']['js'] = array(
* 'http://code.jquery.com/jquery-1.4.2.min.js' => array(
* 'type' => 'external',
* ),
* );
* @endcode
*
* @param $elements * @param $elements
* The structured array describing the data being rendered. * The structured array describing the data being rendered.
* @param $group * @param $group
...@@ -4174,12 +4323,16 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS ...@@ -4174,12 +4323,16 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS
* assigned to them. * assigned to them.
* @param $dependency_check * @param $dependency_check
* When TRUE, will exit if a given library's dependencies are missing. When * When TRUE, will exit if a given library's dependencies are missing. When
* set to FALSE, will continue to add the libraries, even though one of the * set to FALSE, will continue to add the libraries, even though one or more
* dependencies are missing. Defaults to FALSE. * dependencies are missing. Defaults to FALSE.
* @param $every_page
* Set to TRUE to indicate that the attachments are added to every page on the
* site. Only attachments with the every_page flag set to TRUE can participate
* in JavaScript/CSS aggregation.
* *
* @return * @return
* Will return FALSE if there were any missing library dependencies. TRUE will * FALSE if there were any missing library dependencies; TRUE if all library
* be returned if all library dependencies were met. * dependencies were met.
* *
* @see drupal_add_library() * @see drupal_add_library()
* @see drupal_add_js() * @see drupal_add_js()
...@@ -4238,7 +4391,7 @@ function drupal_process_attached($elements, $group = JS_DEFAULT, $dependency_che ...@@ -4238,7 +4391,7 @@ function drupal_process_attached($elements, $group = JS_DEFAULT, $dependency_che
} }
// Add additional types of attachments specified in the render() structure. // Add additional types of attachments specified in the render() structure.
// Libraries, Javascript and CSS have been added already, as they require // Libraries, JavaScript and CSS have been added already, as they require
// special handling. // special handling.
foreach ($elements['#attached'] as $callback => $options) { foreach ($elements['#attached'] as $callback => $options) {
if (function_exists($callback)) { if (function_exists($callback)) {
...@@ -4329,6 +4482,8 @@ function drupal_process_attached($elements, $group = JS_DEFAULT, $dependency_che ...@@ -4329,6 +4482,8 @@ function drupal_process_attached($elements, $group = JS_DEFAULT, $dependency_che
* The following states may be applied to an element: * The following states may be applied to an element:
* - enabled * - enabled
* - disabled * - disabled
* - required
* - optional
* - visible * - visible
* - invisible * - invisible
* - checked * - checked
...@@ -4337,26 +4492,22 @@ function drupal_process_attached($elements, $group = JS_DEFAULT, $dependency_che ...@@ -4337,26 +4492,22 @@ function drupal_process_attached($elements, $group = JS_DEFAULT, $dependency_che
* - collapsed * - collapsed
* *
* The following states may be used in remote conditions: * The following states may be used in remote conditions:
* - enabled * - empty
* - disabled * - filled
* - visible
* - invisible
* - checked * - checked
* - unchecked * - unchecked
* - expanded
* - collapsed
* - value * - value
* *
* The following states exist for both states and remote conditions, but are not * The following states exist for both elements and remote conditions, but are
* fully implemented and may not change anything on the element: * not fully implemented and may not change anything on the element:
* - required
* - optional
* - relevant * - relevant
* - irrelevant * - irrelevant
* - valid * - valid
* - invalid * - invalid
* - touched * - touched
* - untouched * - untouched
* - filled
* - empty
* - readwrite * - readwrite
* - readonly * - readonly
* *
...@@ -4377,7 +4528,7 @@ function drupal_process_attached($elements, $group = JS_DEFAULT, $dependency_che ...@@ -4377,7 +4528,7 @@ function drupal_process_attached($elements, $group = JS_DEFAULT, $dependency_che
* @see form_example_states_form() * @see form_example_states_form()
*/ */
function drupal_process_states(&$elements) { function drupal_process_states(&$elements) {
$elements['#attached']['js']['misc/states.js'] = array('group' => JS_LIBRARY, 'weight' => 1); $elements['#attached']['library'][] = array('system', 'drupal.states');
$elements['#attached']['js'][] = array( $elements['#attached']['js'][] = array(
'type' => 'setting', 'type' => 'setting',
'data' => array('states' => array('#' . $elements['#id'] => $elements['#states'])), 'data' => array('states' => array('#' . $elements['#id'] => $elements['#states'])),
...@@ -4391,16 +4542,20 @@ function drupal_process_states(&$elements) { ...@@ -4391,16 +4542,20 @@ function drupal_process_states(&$elements) {
* settings, and optionally requiring another library. For example, a library * settings, and optionally requiring another library. For example, a library
* can be a jQuery plugin, a JavaScript framework, or a CSS framework. This * can be a jQuery plugin, a JavaScript framework, or a CSS framework. This
* function allows modules to load a library defined/shipped by itself or a * function allows modules to load a library defined/shipped by itself or a
* depending module; without having to add all files of the library separately. * depending module, without having to add all files of the library separately.
* Each library is only loaded once. * Each library is only loaded once.
* *
* @param $module * @param $module
* The name of the module that registered the library. * The name of the module that registered the library.
* @param $name * @param $name
* The name of the library to add. * The name of the library to add.
* @param $every_page
* Set to TRUE if this library is added to every page on the site. Only items
* with the every_page flag set to TRUE can participate in aggregation.
*
* @return * @return
* TRUE when the library was successfully added or FALSE if the library or one * TRUE if the library was successfully added; FALSE if the library or one of
* of its dependencies could not be added. * its dependencies could not be added.
* *
* @see drupal_get_library() * @see drupal_get_library()
* @see hook_library() * @see hook_library()
...@@ -4444,10 +4599,14 @@ function drupal_add_library($module, $name, $every_page = NULL) { ...@@ -4444,10 +4599,14 @@ function drupal_add_library($module, $name, $every_page = NULL) {
* *
* @param $module * @param $module
* The name of a module that registered a library. * The name of a module that registered a library.
* @param $library * @param $name
* The name of a registered library. * (optional) The name of a registered library to retrieve. By default, all
* libraries registered by $module are returned.
*
* @return * @return
* The definition of the requested library, if existent, or FALSE. * The definition of the requested library, if $name was passed and it exists,
* or FALSE if it does not exist. If no $name was passed, an associative array
* of libraries registered by $module is returned (which may be empty).
* *
* @see drupal_add_library() * @see drupal_add_library()
* @see hook_library() * @see hook_library()
...@@ -4456,7 +4615,7 @@ function drupal_add_library($module, $name, $every_page = NULL) { ...@@ -4456,7 +4615,7 @@ function drupal_add_library($module, $name, $every_page = NULL) {
* @todo The purpose of drupal_get_*() is completely different to other page * @todo The purpose of drupal_get_*() is completely different to other page
* requisite API functions; find and use a different name. * requisite API functions; find and use a different name.
*/ */
function drupal_get_library($module, $name) { function drupal_get_library($module, $name = NULL) {
$libraries = &drupal_static(__FUNCTION__, array()); $libraries = &drupal_static(__FUNCTION__, array());
if (!isset($libraries[$module])) { if (!isset($libraries[$module])) {
...@@ -4479,24 +4638,26 @@ function drupal_get_library($module, $name) { ...@@ -4479,24 +4638,26 @@ function drupal_get_library($module, $name) {
} }
$libraries[$module] = $module_libraries; $libraries[$module] = $module_libraries;
} }
if (empty($libraries[$module][$name])) { if (isset($name)) {
if (!isset($libraries[$module][$name])) {
$libraries[$module][$name] = FALSE; $libraries[$module][$name] = FALSE;
} }
return $libraries[$module][$name]; return $libraries[$module][$name];
} }
return $libraries[$module];
}
/** /**
* Assist in adding the tableDrag JavaScript behavior to a themed table. * Assists in adding the tableDrag JavaScript behavior to a themed table.
* *
* Draggable tables should be used wherever an outline or list of sortable items * Draggable tables should be used wherever an outline or list of sortable items
* needs to be arranged by an end-user. Draggable tables are very flexible and * needs to be arranged by an end-user. Draggable tables are very flexible and
* can manipulate the value of form elements placed within individual columns. * can manipulate the value of form elements placed within individual columns.
* *
* To set up a table to use drag and drop in place of weight select-lists or * To set up a table to use drag and drop in place of weight select-lists or in
* in place of a form that contains parent relationships, the form must be * place of a form that contains parent relationships, the form must be themed
* themed into a table. The table must have an id attribute set. If using * into a table. The table must have an ID attribute set. If using
* theme_table(), the id may be set as such: * theme_table(), the ID may be set as follows:
* @code * @code
* $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'my-module-table'))); * $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'my-module-table')));
* return $output; * return $output;
...@@ -4511,8 +4672,8 @@ function drupal_get_library($module, $name) { ...@@ -4511,8 +4672,8 @@ function drupal_get_library($module, $name) {
* $form['my_elements'][$delta]['weight']['#attributes']['class'] = array('my-elements-weight'); * $form['my_elements'][$delta]['weight']['#attributes']['class'] = array('my-elements-weight');
* @endcode * @endcode
* *
* Each row of the table must also have a class of "draggable" in order to enable the * Each row of the table must also have a class of "draggable" in order to
* drag handles: * enable the drag handles:
* @code * @code
* $row = array(...); * $row = array(...);
* $rows[] = array( * $rows[] = array(
...@@ -4532,8 +4693,8 @@ function drupal_get_library($module, $name) { ...@@ -4532,8 +4693,8 @@ function drupal_get_library($module, $name) {
* @endcode * @endcode
* *
* In a more complex case where there are several groups in one column (such as * In a more complex case where there are several groups in one column (such as
* the block regions on the admin/structure/block page), a separate subgroup class * the block regions on the admin/structure/block page), a separate subgroup
* must also be added to differentiate the groups. * class must also be added to differentiate the groups.
* @code * @code
* $form['my_elements'][$region][$delta]['weight']['#attributes']['class'] = array('my-elements-weight', 'my-elements-weight-' . $region); * $form['my_elements'][$region][$delta]['weight']['#attributes']['class'] = array('my-elements-weight', 'my-elements-weight-' . $region);
* @endcode * @endcode
...@@ -4550,14 +4711,14 @@ function drupal_get_library($module, $name) { ...@@ -4550,14 +4711,14 @@ function drupal_get_library($module, $name) {
* *
* In a situation where tree relationships are present, adding multiple * In a situation where tree relationships are present, adding multiple
* subgroups is not necessary, because the table will contain indentations that * subgroups is not necessary, because the table will contain indentations that
* provide enough information about the sibling and parent relationships. * provide enough information about the sibling and parent relationships. See
* See theme_menu_overview_form() for an example creating a table containing * theme_menu_overview_form() for an example creating a table containing parent
* parent relationships. * relationships.
* *
* Please note that this function should be called from the theme layer, such as * Note that this function should be called from the theme layer, such as in a
* in a .tpl.php file, theme_ function, or in a template_preprocess function, * .tpl.php file, theme_ function, or in a template_preprocess function, not in
* not in a form declaration. Though the same JavaScript could be added to the * a form declaration. Though the same JavaScript could be added to the page
* page using drupal_add_js() directly, this function helps keep template files * using drupal_add_js() directly, this function helps keep template files
* clean and readable. It also prevents tabledrag.js from being added twice * clean and readable. It also prevents tabledrag.js from being added twice
* accidentally. * accidentally.
* *
...@@ -4599,7 +4760,7 @@ function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgro ...@@ -4599,7 +4760,7 @@ function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgro
// Add the table drag JavaScript to the page before the module JavaScript // Add the table drag JavaScript to the page before the module JavaScript
// to ensure that table drag behaviors are registered before any module // to ensure that table drag behaviors are registered before any module
// uses it. // uses it.
drupal_add_js('misc/jquery.cookie.js', array('weight' => -2)); drupal_add_library('system', 'jquery.cookie');
drupal_add_js('misc/tabledrag.js', array('weight' => -1)); drupal_add_js('misc/tabledrag.js', array('weight' => -1));
$js_added = TRUE; $js_added = TRUE;
} }
...@@ -4630,8 +4791,8 @@ function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgro ...@@ -4630,8 +4791,8 @@ function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgro
* $files while the value is the cache file name. The cache file is generated * $files while the value is the cache file name. The cache file is generated
* in two cases. First, if there is no file name value for the key, which will * in two cases. First, if there is no file name value for the key, which will
* happen if a new file name has been added to $files or after the lookup * happen if a new file name has been added to $files or after the lookup
* variable is emptied to force a rebuild of the cache. Second, the cache * variable is emptied to force a rebuild of the cache. Second, the cache file
* file is generated if it is missing on disk. Old cache files are not deleted * is generated if it is missing on disk. Old cache files are not deleted
* immediately when the lookup variable is emptied, but are deleted after a set * immediately when the lookup variable is emptied, but are deleted after a set
* period by drupal_delete_file_if_stale(). This ensures that files referenced * period by drupal_delete_file_if_stale(). This ensures that files referenced
* by a cached page will still be available. * by a cached page will still be available.
...@@ -4695,16 +4856,31 @@ function drupal_clear_js_cache() { ...@@ -4695,16 +4856,31 @@ function drupal_clear_js_cache() {
} }
/** /**
* Converts a PHP variable into its Javascript equivalent. * Converts a PHP variable into its JavaScript equivalent.
* *
* We use HTML-safe strings, i.e. with <, > and & escaped. * We use HTML-safe strings, with several characters escaped.
* *
* @see drupal_json_decode() * @see drupal_json_decode()
* @see drupal_json_encode_helper()
* @ingroup php_wrappers * @ingroup php_wrappers
*/ */
function drupal_json_encode($var) { function drupal_json_encode($var) {
// json_encode() does not escape <, > and &, so we do it with str_replace(). // The PHP version cannot change within a request.
return str_replace(array('<', '>', '&'), array('\u003c', '\u003e', '\u0026'), json_encode($var)); static $php530;
if (!isset($php530)) {
$php530 = version_compare(PHP_VERSION, '5.3.0', '>=');
}
if ($php530) {
// Encode <, >, ', &, and " using the json_encode() options parameter.
return json_encode($var, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT);
}
// json_encode() escapes <, >, ', &, and " using its options parameter, but
// does not support this parameter prior to PHP 5.3.0. Use a helper instead.
include_once DRUPAL_ROOT . '/includes/json-encode.inc';
return drupal_json_encode_helper($var);
} }
/** /**
...@@ -4718,7 +4894,7 @@ function drupal_json_decode($var) { ...@@ -4718,7 +4894,7 @@ function drupal_json_decode($var) {
} }
/** /**
* Return data in JSON format. * Returns data in JSON format.
* *
* This function should be used for JavaScript callback functions returning * This function should be used for JavaScript callback functions returning
* data in JSON format. It sets the header for JavaScript output. * data in JSON format. It sets the header for JavaScript output.
...@@ -4736,7 +4912,7 @@ function drupal_json_output($var = NULL) { ...@@ -4736,7 +4912,7 @@ function drupal_json_output($var = NULL) {
} }
/** /**
* Get a salt useful for hardening against SQL injection. * Gets a salt useful for hardening against SQL injection.
* *
* @return * @return
* A salt based on information in settings.php, not in the database. * A salt based on information in settings.php, not in the database.
...@@ -4749,7 +4925,7 @@ function drupal_get_hash_salt() { ...@@ -4749,7 +4925,7 @@ function drupal_get_hash_salt() {
} }
/** /**
* Ensure the private key variable used to generate tokens is set. * Ensures the private key variable used to generate tokens is set.
* *
* @return * @return
* The private key. * The private key.
...@@ -4763,7 +4939,7 @@ function drupal_get_private_key() { ...@@ -4763,7 +4939,7 @@ function drupal_get_private_key() {
} }
/** /**
* Generate a token based on $value, the current user session and private key. * Generates a token based on $value, the user session, and the private key.
* *
* @param $value * @param $value
* An additional value to base the token on. * An additional value to base the token on.
...@@ -4773,7 +4949,7 @@ function drupal_get_token($value = '') { ...@@ -4773,7 +4949,7 @@ function drupal_get_token($value = '') {
} }
/** /**
* Validate a token based on $value, the current user session and private key. * Validates a token based on $value, the user session, and the private key.
* *
* @param $token * @param $token
* The token to be validated. * The token to be validated.
...@@ -4781,6 +4957,7 @@ function drupal_get_token($value = '') { ...@@ -4781,6 +4957,7 @@ function drupal_get_token($value = '') {
* An additional value to base the token on. * An additional value to base the token on.
* @param $skip_anonymous * @param $skip_anonymous
* Set to true to skip token validation for anonymous users. * Set to true to skip token validation for anonymous users.
*
* @return * @return
* True for a valid token, false for an invalid token. When $skip_anonymous * True for a valid token, false for an invalid token. When $skip_anonymous
* is true, the return value will always be true for anonymous users. * is true, the return value will always be true for anonymous users.
...@@ -4791,12 +4968,12 @@ function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) { ...@@ -4791,12 +4968,12 @@ function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) {
} }
function _drupal_bootstrap_full() { function _drupal_bootstrap_full() {
$called = &drupal_static(__FUNCTION__); static $called = FALSE;
if ($called) { if ($called) {
return; return;
} }
$called = 1; $called = TRUE;
require_once DRUPAL_ROOT . '/' . variable_get('path_inc', 'includes/path.inc'); require_once DRUPAL_ROOT . '/' . variable_get('path_inc', 'includes/path.inc');
require_once DRUPAL_ROOT . '/includes/theme.inc'; require_once DRUPAL_ROOT . '/includes/theme.inc';
require_once DRUPAL_ROOT . '/includes/pager.inc'; require_once DRUPAL_ROOT . '/includes/pager.inc';
...@@ -4849,7 +5026,7 @@ function _drupal_bootstrap_full() { ...@@ -4849,7 +5026,7 @@ function _drupal_bootstrap_full() {
} }
/** /**
* Store the current page in the cache. * Stores the current page in the cache.
* *
* If page_compression is enabled, a gzipped version of the page is stored in * If page_compression is enabled, a gzipped version of the page is stored in
* the cache to avoid compressing the output on each request. The cache entry * the cache to avoid compressing the output on each request. The cache entry
...@@ -4901,10 +5078,10 @@ function drupal_page_set_cache() { ...@@ -4901,10 +5078,10 @@ function drupal_page_set_cache() {
/** /**
* Executes a cron run when called. * Executes a cron run when called.
* *
* Do not call this function from test, use $this->cronRun() instead. * Do not call this function from a test. Use $this->cronRun() instead.
* *
* @return * @return
* Returns TRUE if ran successfully * TRUE if cron ran successfully.
*/ */
function drupal_cron_run() { function drupal_cron_run() {
// Allow execution to continue even if the request gets canceled. // Allow execution to continue even if the request gets canceled.
...@@ -4937,13 +5114,21 @@ function drupal_cron_run() { ...@@ -4937,13 +5114,21 @@ function drupal_cron_run() {
foreach ($queues as $queue_name => $info) { foreach ($queues as $queue_name => $info) {
DrupalQueue::get($queue_name)->createQueue(); DrupalQueue::get($queue_name)->createQueue();
} }
// Register shutdown callback // Register shutdown callback.
drupal_register_shutdown_function('drupal_cron_cleanup'); drupal_register_shutdown_function('drupal_cron_cleanup');
// Iterate through the modules calling their cron handlers (if any): // Iterate through the modules calling their cron handlers (if any):
module_invoke_all('cron'); foreach (module_implements('cron') as $module) {
// Do not let an exception thrown by one module disturb another.
try {
module_invoke($module, 'cron');
}
catch (Exception $e) {
watchdog_exception('cron', $e);
}
}
// Record cron time // Record cron time.
variable_set('cron_last', REQUEST_TIME); variable_set('cron_last', REQUEST_TIME);
watchdog('cron', 'Cron run completed.', array(), WATCHDOG_NOTICE); watchdog('cron', 'Cron run completed.', array(), WATCHDOG_NOTICE);
...@@ -4971,14 +5156,17 @@ function drupal_cron_run() { ...@@ -4971,14 +5156,17 @@ function drupal_cron_run() {
} }
/** /**
* Shutdown function for cron cleanup. * Shutdown function: Performs cron cleanup.
*
* @see drupal_cron_run()
* @see drupal_register_shutdown_function()
*/ */
function drupal_cron_cleanup() { function drupal_cron_cleanup() {
// See if the semaphore is still locked. // See if the semaphore is still locked.
if (variable_get('cron_semaphore', FALSE)) { if (variable_get('cron_semaphore', FALSE)) {
watchdog('cron', 'Cron run exceeded the time limit and was aborted.', array(), WATCHDOG_WARNING); watchdog('cron', 'Cron run exceeded the time limit and was aborted.', array(), WATCHDOG_WARNING);
// Release cron semaphore // Release cron semaphore.
variable_del('cron_semaphore'); variable_del('cron_semaphore');
} }
} }
...@@ -5047,14 +5235,14 @@ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) ...@@ -5047,14 +5235,14 @@ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1)
$searchdir[] = "profiles/$profile/$directory"; $searchdir[] = "profiles/$profile/$directory";
} }
// Always search sites/all/* as well as the global directories // Always search sites/all/* as well as the global directories.
$searchdir[] = 'sites/all/' . $directory; $searchdir[] = 'sites/all/' . $directory;
if (file_exists("$config/$directory")) { if (file_exists("$config/$directory")) {
$searchdir[] = "$config/$directory"; $searchdir[] = "$config/$directory";
} }
// Get current list of items // Get current list of items.
if (!function_exists('file_scan_directory')) { if (!function_exists('file_scan_directory')) {
require_once DRUPAL_ROOT . '/includes/file.inc'; require_once DRUPAL_ROOT . '/includes/file.inc';
} }
...@@ -5068,7 +5256,7 @@ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) ...@@ -5068,7 +5256,7 @@ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1)
// compatible with Drupal core. This may occur during upgrades of Drupal // compatible with Drupal core. This may occur during upgrades of Drupal
// core when new modules exist in core while older contrib modules with the // core when new modules exist in core while older contrib modules with the
// same name exist in a directory such as sites/all/modules/. // same name exist in a directory such as sites/all/modules/.
foreach (array_intersect_key($files_to_add, $files) as $key => $file) { foreach (array_intersect_key($files_to_add, $files) as $file_key => $file) {
// If it has no info file, then we just behave liberally and accept the // If it has no info file, then we just behave liberally and accept the
// new resource on the list for merging. // new resource on the list for merging.
if (file_exists($info_file = dirname($file->uri) . '/' . $file->name . '.info')) { if (file_exists($info_file = dirname($file->uri) . '/' . $file->name . '.info')) {
...@@ -5079,7 +5267,7 @@ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) ...@@ -5079,7 +5267,7 @@ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1)
// from the array for the current search directory, so it is not // from the array for the current search directory, so it is not
// overwritten when merged with the $files array. // overwritten when merged with the $files array.
if (isset($info['core']) && $info['core'] != DRUPAL_CORE_COMPATIBILITY) { if (isset($info['core']) && $info['core'] != DRUPAL_CORE_COMPATIBILITY) {
unset($files_to_add[$key]); unset($files_to_add[$file_key]);
} }
} }
} }
...@@ -5090,7 +5278,7 @@ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) ...@@ -5090,7 +5278,7 @@ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1)
} }
/** /**
* Set the main page content value for later use. * Sets the main page content value for later use.
* *
* Given the nature of the Drupal page handling, this will be called once with * Given the nature of the Drupal page handling, this will be called once with
* a string or array. We store that and return it later as the block is being * a string or array. We store that and return it later as the block is being
...@@ -5098,6 +5286,7 @@ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) ...@@ -5098,6 +5286,7 @@ function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1)
* *
* @param $content * @param $content
* A string or renderable array representing the body of the page. * A string or renderable array representing the body of the page.
*
* @return * @return
* If called without $content, a renderable array representing the body of * If called without $content, a renderable array representing the body of
* the page. * the page.
...@@ -5145,7 +5334,7 @@ function drupal_set_page_content($content = NULL) { ...@@ -5145,7 +5334,7 @@ function drupal_set_page_content($content = NULL) {
* browsers, '#browsers' can be set to array('IE' => 'gte IE 8'). * browsers, '#browsers' can be set to array('IE' => 'gte IE 8').
* *
* @return * @return
* The passed in element with markup for conditional comments potentially * The passed-in element with markup for conditional comments potentially
* added to '#prefix' and '#suffix'. * added to '#prefix' and '#suffix'.
*/ */
function drupal_pre_render_conditional_comments($elements) { function drupal_pre_render_conditional_comments($elements) {
...@@ -5207,7 +5396,7 @@ function drupal_pre_render_conditional_comments($elements) { ...@@ -5207,7 +5396,7 @@ function drupal_pre_render_conditional_comments($elements) {
* - #options: (optional) An array of options to pass to l(). * - #options: (optional) An array of options to pass to l().
* *
* @return * @return
* The passed in elements containing a rendered link in '#markup'. * The passed-in elements containing a rendered link in '#markup'.
*/ */
function drupal_pre_render_link($element) { function drupal_pre_render_link($element) {
// By default, link options to pass to l() are normally set in #options. // By default, link options to pass to l() are normally set in #options.
...@@ -5237,7 +5426,7 @@ function drupal_pre_render_link($element) { ...@@ -5237,7 +5426,7 @@ function drupal_pre_render_link($element) {
if (!isset($element['#id'])) { if (!isset($element['#id'])) {
$element['#id'] = $element['#options']['attributes']['id'] = drupal_html_id('ajax-link'); $element['#id'] = $element['#options']['attributes']['id'] = drupal_html_id('ajax-link');
} }
// If #ajax['path] was not specified, use the href as AJAX request URL. // If #ajax['path] was not specified, use the href as Ajax request URL.
if (!isset($element['#ajax']['path'])) { if (!isset($element['#ajax']['path'])) {
$element['#ajax']['path'] = $element['#href']; $element['#ajax']['path'] = $element['#href'];
$element['#ajax']['options'] = $element['#options']; $element['#ajax']['options'] = $element['#options'];
...@@ -5249,6 +5438,97 @@ function drupal_pre_render_link($element) { ...@@ -5249,6 +5438,97 @@ function drupal_pre_render_link($element) {
return $element; return $element;
} }
/**
* #pre_render callback that collects child links into a single array.
*
* This function can be added as a pre_render callback for a renderable array,
* usually one which will be themed by theme_links(). It iterates through all
* unrendered children of the element, collects any #links properties it finds,
* merges them into the parent element's #links array, and prevents those
* children from being rendered separately.
*
* The purpose of this is to allow links to be logically grouped into related
* categories, so that each child group can be rendered as its own list of
* links if drupal_render() is called on it, but calling drupal_render() on the
* parent element will still produce a single list containing all the remaining
* links, regardless of what group they were in.
*
* A typical example comes from node links, which are stored in a renderable
* array similar to this:
* @code
* $node->content['links'] = array(
* '#theme' => 'links__node',
* '#pre_render' = array('drupal_pre_render_links'),
* 'comment' => array(
* '#theme' => 'links__node__comment',
* '#links' => array(
* // An array of links associated with node comments, suitable for
* // passing in to theme_links().
* ),
* ),
* 'statistics' => array(
* '#theme' => 'links__node__statistics',
* '#links' => array(
* // An array of links associated with node statistics, suitable for
* // passing in to theme_links().
* ),
* ),
* 'translation' => array(
* '#theme' => 'links__node__translation',
* '#links' => array(
* // An array of links associated with node translation, suitable for
* // passing in to theme_links().
* ),
* ),
* );
* @endcode
*
* In this example, the links are grouped by functionality, which can be
* helpful to themers who want to display certain kinds of links independently.
* For example, adding this code to node.tpl.php will result in the comment
* links being rendered as a single list:
* @code
* print render($content['links']['comment']);
* @endcode
*
* (where $node->content has been transformed into $content before handing
* control to the node.tpl.php template).
*
* The pre_render function defined here allows the above flexibility, but also
* allows the following code to be used to render all remaining links into a
* single list, regardless of their group:
* @code
* print render($content['links']);
* @endcode
*
* In the above example, this will result in the statistics and translation
* links being rendered together in a single list (but not the comment links,
* which were rendered previously on their own).
*
* Because of the way this function works, the individual properties of each
* group (for example, a group-specific #theme property such as
* 'links__node__comment' in the example above, or any other property such as
* #attributes or #pre_render that is attached to it) are only used when that
* group is rendered on its own. When the group is rendered together with other
* children, these child-specific properties are ignored, and only the overall
* properties of the parent are used.
*/
function drupal_pre_render_links($element) {
$element += array('#links' => array());
foreach (element_children($element) as $key) {
$child = &$element[$key];
// If the child has links which have not been printed yet and the user has
// access to it, merge its links in to the parent.
if (isset($child['#links']) && empty($child['#printed']) && (!isset($child['#access']) || $child['#access'])) {
$element['#links'] += $child['#links'];
// Mark the child as having been printed already (so that its links
// cannot be mistakenly rendered twice).
$child['#printed'] = TRUE;
}
}
return $element;
}
/** /**
* #pre_render callback to append contents in #markup to #children. * #pre_render callback to append contents in #markup to #children.
* *
...@@ -5257,13 +5537,13 @@ function drupal_pre_render_link($element) { ...@@ -5257,13 +5537,13 @@ function drupal_pre_render_link($element) {
* Note that if also a #theme is defined for the element, then the result of * Note that if also a #theme is defined for the element, then the result of
* the theme callback will override #children. * the theme callback will override #children.
* *
* @see drupal_render()
*
* @param $elements * @param $elements
* A structured array using the #markup key. * A structured array using the #markup key.
* *
* @return * @return
* The passed in elements, but #markup appended to #children. * The passed-in elements, but #markup appended to #children.
*
* @see drupal_render()
*/ */
function drupal_pre_render_markup($elements) { function drupal_pre_render_markup($elements) {
$elements['#children'] = $elements['#markup']; $elements['#children'] = $elements['#markup'];
...@@ -5276,8 +5556,10 @@ function drupal_pre_render_markup($elements) { ...@@ -5276,8 +5556,10 @@ function drupal_pre_render_markup($elements) {
* @param $page * @param $page
* A string or array representing the content of a page. The array consists of * A string or array representing the content of a page. The array consists of
* the following keys: * the following keys:
* - #type: Value is always 'page'. This pushes the theming through page.tpl.php (required). * - #type: Value is always 'page'. This pushes the theming through
* - #show_messages: Suppress drupal_get_message() items. Used by Batch API (optional). * page.tpl.php (required).
* - #show_messages: Suppress drupal_get_message() items. Used by Batch
* API (optional).
* *
* @see hook_page_alter() * @see hook_page_alter()
* @see element_info() * @see element_info()
...@@ -5317,15 +5599,24 @@ function drupal_render_page($page) { ...@@ -5317,15 +5599,24 @@ function drupal_render_page($page) {
* *
* Recursively iterates over each of the array elements, generating HTML code. * Recursively iterates over each of the array elements, generating HTML code.
* *
* HTML generation is controlled by two properties containing theme functions, * Renderable arrays have two kinds of key/value pairs: properties and
* #theme and #theme_wrappers. * children. Properties have keys starting with '#' and their values influence
* how the array will be rendered. Children are all elements whose keys do not
* start with a '#'. Their values should be renderable arrays themselves,
* which will be rendered during the rendering of the parent array. The markup
* provided by the children is typically inserted into the markup generated by
* the parent array.
*
* HTML generation for a renderable array, and the treatment of any children,
* is controlled by two properties containing theme functions, #theme and
* #theme_wrappers.
* *
* #theme is the theme function called first. If it is set and the element has * #theme is the theme function called first. If it is set and the element has
* any children, they have to be rendered there. For elements that are not * any children, it is the responsibility of the theme function to render
* allowed to have any children, e.g. buttons or textfields, it can be used to * these children. For elements that are not allowed to have any children,
* render the element itself. If #theme is not present and the element has * e.g. buttons or textfields, the theme function can be used to render the
* children, they are rendered and concatenated into a string by * element itself. If #theme is not present and the element has children, they
* drupal_render_children(). * are rendered and concatenated into a string by drupal_render_children().
* *
* The #theme_wrappers property contains an array of theme functions which will * The #theme_wrappers property contains an array of theme functions which will
* be called, in order, after #theme has run. These can be used to add further * be called, in order, after #theme has run. These can be used to add further
...@@ -5349,8 +5640,8 @@ function drupal_render_page($page) { ...@@ -5349,8 +5640,8 @@ function drupal_render_page($page) {
* is set, the cache ID is created automatically from these keys. See * is set, the cache ID is created automatically from these keys. See
* drupal_render_cid_create(). * drupal_render_cid_create().
* - 'granularity' (optional): Define the cache granularity using binary * - 'granularity' (optional): Define the cache granularity using binary
* combinations of the cache granularity constants, e.g. DRUPAL_CACHE_PER_USER * combinations of the cache granularity constants, e.g.
* to cache for each user separately or * DRUPAL_CACHE_PER_USER to cache for each user separately or
* DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE to cache separately for each * DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE to cache separately for each
* page and role. If not specified the element is cached globally for each * page and role. If not specified the element is cached globally for each
* theme and language. * theme and language.
...@@ -5375,6 +5666,7 @@ function drupal_render_page($page) { ...@@ -5375,6 +5666,7 @@ function drupal_render_page($page) {
* *
* @param $elements * @param $elements
* The structured array describing the data to be rendered. * The structured array describing the data to be rendered.
*
* @return * @return
* The rendered HTML. * The rendered HTML.
*/ */
...@@ -5390,13 +5682,16 @@ function drupal_render(&$elements) { ...@@ -5390,13 +5682,16 @@ function drupal_render(&$elements) {
} }
// Try to fetch the element's markup from cache and return. // Try to fetch the element's markup from cache and return.
if (isset($elements['#cache']) && $cached_output = drupal_render_cache_get($elements)) { if (isset($elements['#cache'])) {
$cached_output = drupal_render_cache_get($elements);
if ($cached_output !== FALSE) {
return $cached_output; return $cached_output;
} }
}
// If #markup is not empty, set #type. This allows to specify just #markup on // If #markup is set, ensure #type is set. This allows to specify just #markup
// an element without setting #type. // on an element without setting #type.
if (!empty($elements['#markup']) && !isset($elements['#type'])) { if (isset($elements['#markup']) && !isset($elements['#type'])) {
$elements['#type'] = 'markup'; $elements['#type'] = 'markup';
} }
...@@ -5488,7 +5783,7 @@ function drupal_render(&$elements) { ...@@ -5488,7 +5783,7 @@ function drupal_render(&$elements) {
} }
/** /**
* Render children of an element and concatenate them. * Renders children of an element and concatenates them.
* *
* This renders all children of an element using drupal_render() and then * This renders all children of an element using drupal_render() and then
* joins them together into a single string. * joins them together into a single string.
...@@ -5513,13 +5808,17 @@ function drupal_render_children(&$element, $children_keys = NULL) { ...@@ -5513,13 +5808,17 @@ function drupal_render_children(&$element, $children_keys = NULL) {
} }
/** /**
* Render and print an element. * Renders an element.
* *
* This function renders an element using drupal_render(). The top level * This function renders an element using drupal_render(). The top level
* element is always rendered even if hide() had been previously used on it. * element is shown with show() before rendering, so it will always be rendered
* even if hide() had been previously used on it.
* *
* Any nested elements are only rendered if they haven't been rendered before * @param $element
* or if they have been re-enabled with show(). * The element to be rendered.
*
* @return
* The rendered element.
* *
* @see drupal_render() * @see drupal_render()
* @see show() * @see show()
...@@ -5538,7 +5837,22 @@ function render(&$element) { ...@@ -5538,7 +5837,22 @@ function render(&$element) {
} }
/** /**
* Hide an element from later rendering. * Hides an element from later rendering.
*
* The first time render() or drupal_render() is called on an element tree,
* as each element in the tree is rendered, it is marked with a #printed flag
* and the rendered children of the element are cached. Subsequent calls to
* render() or drupal_render() will not traverse the child tree of this element
* again: they will just use the cached children. So if you want to hide an
* element, be sure to call hide() on the element before its parent tree is
* rendered for the first time, as it will have no effect on subsequent
* renderings of the parent tree.
*
* @param $element
* The element to be hidden.
*
* @return
* The element.
* *
* @see render() * @see render()
* @see show() * @see show()
...@@ -5549,10 +5863,25 @@ function hide(&$element) { ...@@ -5549,10 +5863,25 @@ function hide(&$element) {
} }
/** /**
* Show a hidden or already printed element from later rendering. * Shows a hidden element for later rendering.
*
* You can also use render($element), which shows the element while rendering
* it.
* *
* Alternatively, render($element) could be used which automatically shows the * The first time render() or drupal_render() is called on an element tree,
* element while rendering it. * as each element in the tree is rendered, it is marked with a #printed flag
* and the rendered children of the element are cached. Subsequent calls to
* render() or drupal_render() will not traverse the child tree of this element
* again: they will just use the cached children. So if you want to show an
* element, be sure to call show() on the element before its parent tree is
* rendered for the first time, as it will have no effect on subsequent
* renderings of the parent tree.
*
* @param $element
* The element to be shown.
*
* @return
* The element.
* *
* @see render() * @see render()
* @see hide() * @see hide()
...@@ -5563,16 +5892,17 @@ function show(&$element) { ...@@ -5563,16 +5892,17 @@ function show(&$element) {
} }
/** /**
* Get the rendered output of a renderable element from cache. * Gets the rendered output of a renderable element from the cache.
*
* @see drupal_render()
* @see drupal_render_cache_set()
* *
* @param $elements * @param $elements
* A renderable array. * A renderable array.
*
* @return * @return
* A markup string containing the rendered content of the element, or FALSE * A markup string containing the rendered content of the element, or FALSE
* if no cached copy of the element is available. * if no cached copy of the element is available.
*
* @see drupal_render()
* @see drupal_render_cache_set()
*/ */
function drupal_render_cache_get($elements) { function drupal_render_cache_get($elements) {
if (!in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD')) || !$cid = drupal_render_cid_create($elements)) { if (!in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD')) || !$cid = drupal_render_cid_create($elements)) {
...@@ -5593,17 +5923,17 @@ function drupal_render_cache_get($elements) { ...@@ -5593,17 +5923,17 @@ function drupal_render_cache_get($elements) {
} }
/** /**
* Cache the rendered output of a renderable element. * Caches the rendered output of a renderable element.
* *
* This is called by drupal_render() if the #cache property is set on an element. * This is called by drupal_render() if the #cache property is set on an
* * element.
* @see drupal_render()
* @see drupal_render_cache_get()
* *
* @param $markup * @param $markup
* The rendered output string of $elements. * The rendered output string of $elements.
* @param $elements * @param $elements
* A renderable array. * A renderable array.
*
* @see drupal_render_cache_get()
*/ */
function drupal_render_cache_set(&$markup, $elements) { function drupal_render_cache_set(&$markup, $elements) {
// Create the cache ID for the element. // Create the cache ID for the element.
...@@ -5619,8 +5949,9 @@ function drupal_render_cache_set(&$markup, $elements) { ...@@ -5619,8 +5949,9 @@ function drupal_render_cache_set(&$markup, $elements) {
// be retrieved and used. // be retrieved and used.
$data['#markup'] = &$markup; $data['#markup'] = &$markup;
// Persist attached data associated with this element. // Persist attached data associated with this element.
if (isset($elements['#attached'])) { $attached = drupal_render_collect_attached($elements, TRUE);
$data['#attached'] = $elements['#attached']; if ($attached) {
$data['#attached'] = $attached;
} }
$bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache'; $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache';
$expire = isset($elements['#cache']['expire']) ? $elements['#cache']['expire'] : CACHE_PERMANENT; $expire = isset($elements['#cache']['expire']) ? $elements['#cache']['expire'] : CACHE_PERMANENT;
...@@ -5628,9 +5959,53 @@ function drupal_render_cache_set(&$markup, $elements) { ...@@ -5628,9 +5959,53 @@ function drupal_render_cache_set(&$markup, $elements) {
} }
/** /**
* Prepare an element for caching based on a query. This smart caching strategy * Collects #attached for an element and its children into a single array.
* saves Drupal from querying and rendering to HTML when the underlying query is *
* unchanged. * When caching elements, it is necessary to collect all libraries, JavaScript
* and CSS into a single array, from both the element itself and all child
* elements. This allows drupal_render() to add these back to the page when the
* element is returned from cache.
*
* @param $elements
* The element to collect #attached from.
* @param $return
* Whether to return the attached elements and reset the internal static.
*
* @return
* The #attached array for this element and its descendants.
*/
function drupal_render_collect_attached($elements, $return = FALSE) {
$attached = &drupal_static(__FUNCTION__, array());
// Collect all #attached for this element.
if (isset($elements['#attached'])) {
foreach ($elements['#attached'] as $key => $value) {
if (!isset($attached[$key])) {
$attached[$key] = array();
}
$attached[$key] = array_merge($attached[$key], $value);
}
}
if ($children = element_children($elements)) {
foreach ($children as $child) {
drupal_render_collect_attached($elements[$child]);
}
}
// If this was the first call to the function, return all attached elements
// and reset the static cache.
if ($return) {
$return = $attached;
$attached = array();
return $return;
}
}
/**
* Prepares an element for caching based on a query.
*
* This smart caching strategy saves Drupal from querying and rendering to HTML
* when the underlying query is unchanged.
* *
* Expensive queries should use the query builder to create the query and then * Expensive queries should use the query builder to create the query and then
* call this function. Executing the query and formatting results should happen * call this function. Executing the query and formatting results should happen
...@@ -5649,7 +6024,7 @@ function drupal_render_cache_set(&$markup, $elements) { ...@@ -5649,7 +6024,7 @@ function drupal_render_cache_set(&$markup, $elements) {
* *
* @return * @return
* A renderable array with the following keys and values: * A renderable array with the following keys and values:
* - #query: The passed in $query. * - #query: The passed-in $query.
* - #pre_render: $function with a _pre_render suffix. * - #pre_render: $function with a _pre_render suffix.
* - #cache: An associative array prepared for drupal_render_cache_set(). * - #cache: An associative array prepared for drupal_render_cache_set().
*/ */
...@@ -5668,15 +6043,15 @@ function drupal_render_cache_by_query($query, $function, $expire = CACHE_TEMPORA ...@@ -5668,15 +6043,15 @@ function drupal_render_cache_by_query($query, $function, $expire = CACHE_TEMPORA
} }
/** /**
* Returns cache ID parts for building a cache ID.
/**
* Helper function for building cache ids.
* *
* @param $granularity * @param $granularity
* One or more cache granularity constants, e.g. DRUPAL_CACHE_PER_USER to cache * One or more cache granularity constants. For example, to cache separately
* for each user separately or DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE to * for each user, use DRUPAL_CACHE_PER_USER. To cache separately for each
* cache separately for each page and role. * page and role, use the expression:
* @code
* DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE
* @endcode
* *
* @return * @return
* An array of cache ID parts, always containing the active theme. If the * An array of cache ID parts, always containing the active theme. If the
...@@ -5715,7 +6090,7 @@ function drupal_render_cid_parts($granularity = NULL) { ...@@ -5715,7 +6090,7 @@ function drupal_render_cid_parts($granularity = NULL) {
} }
/** /**
* Create the cache ID for a renderable element. * Creates the cache ID for a renderable element.
* *
* This creates the cache ID string, either by returning the #cache['cid'] * This creates the cache ID string, either by returning the #cache['cid']
* property if present or by building the cache ID out of the #cache['keys'] * property if present or by building the cache ID out of the #cache['keys']
...@@ -5762,7 +6137,10 @@ function element_sort_by_title($a, $b) { ...@@ -5762,7 +6137,10 @@ function element_sort_by_title($a, $b) {
} }
/** /**
* Retrieve the default properties for the defined element type. * Retrieves the default properties for the defined element type.
*
* @param $type
* An element type as defined by hook_element_info().
*/ */
function element_info($type) { function element_info($type) {
// Use the advanced drupal_static() pattern, since this is called very often. // Use the advanced drupal_static() pattern, since this is called very often.
...@@ -5784,6 +6162,21 @@ function element_info($type) { ...@@ -5784,6 +6162,21 @@ function element_info($type) {
return isset($cache[$type]) ? $cache[$type] : array(); return isset($cache[$type]) ? $cache[$type] : array();
} }
/**
* Retrieves a single property for the defined element type.
*
* @param $type
* An element type as defined by hook_element_info().
* @param $property_name
* The property within the element type that should be returned.
* @param $default
* (Optional) The value to return if the element type does not specify a
* value for the property. Defaults to NULL.
*/
function element_info_property($type, $property_name, $default = NULL) {
return (($info = element_info($type)) && array_key_exists($property_name, $info)) ? $info[$property_name] : $default;
}
/** /**
* Function used by uasort to sort structured arrays by weight, without the property weight prefix. * Function used by uasort to sort structured arrays by weight, without the property weight prefix.
*/ */
...@@ -5797,33 +6190,50 @@ function drupal_sort_weight($a, $b) { ...@@ -5797,33 +6190,50 @@ function drupal_sort_weight($a, $b) {
} }
/** /**
* Check if the key is a property. * Array sorting callback; sorts elements by 'title' key.
*/
function drupal_sort_title($a, $b) {
if (!isset($b['title'])) {
return -1;
}
if (!isset($a['title'])) {
return 1;
}
return strcasecmp($a['title'], $b['title']);
}
/**
* Checks if the key is a property.
*/ */
function element_property($key) { function element_property($key) {
return $key[0] == '#'; return $key[0] == '#';
} }
/** /**
* Get properties of a structured array element. Properties begin with '#'. * Gets properties of a structured array element (keys beginning with '#').
*/ */
function element_properties($element) { function element_properties($element) {
return array_filter(array_keys((array) $element), 'element_property'); return array_filter(array_keys((array) $element), 'element_property');
} }
/** /**
* Check if the key is a child. * Checks if the key is a child.
*/ */
function element_child($key) { function element_child($key) {
return !isset($key[0]) || $key[0] != '#'; return !isset($key[0]) || $key[0] != '#';
} }
/** /**
* Return the children of an element, optionally sorted by weight. * Identifies the children of an element array, optionally sorted by weight.
*
* The children of a element array are those key/value pairs whose key does
* not start with a '#'. See drupal_render() for details.
* *
* @param $elements * @param $elements
* The element to be sorted. * The element array whose children are to be identified.
* @param $sort * @param $sort
* Boolean to indicate whether the children should be sorted by weight. * Boolean to indicate whether the children should be sorted by weight.
*
* @return * @return
* The array keys of the element's children. * The array keys of the element's children.
*/ */
...@@ -5859,10 +6269,11 @@ function element_children(&$elements, $sort = FALSE) { ...@@ -5859,10 +6269,11 @@ function element_children(&$elements, $sort = FALSE) {
} }
/** /**
* Return the visibile children of an element. * Returns the visible children of an element.
* *
* @param $elements * @param $elements
* The parent element. * The parent element.
*
* @return * @return
* The array keys of the element's visible children. * The array keys of the element's visible children.
*/ */
...@@ -5965,14 +6376,22 @@ function element_set_attributes(array &$element, array $map) { ...@@ -5965,14 +6376,22 @@ function element_set_attributes(array &$element, array $map) {
* An array of parent keys, starting with the outermost key. * An array of parent keys, starting with the outermost key.
* @param $value * @param $value
* The value to set. * The value to set.
* @param $force
* (Optional) If TRUE, the value is forced into the structure even if it
* requires the deletion of an already existing non-array parent value. If
* FALSE, PHP throws an error if trying to add into a value that is not an
* array. Defaults to FALSE.
* *
* @see drupal_array_get_nested_value() * @see drupal_array_get_nested_value()
*/ */
function drupal_array_set_nested_value(array &$array, array $parents, $value) { function drupal_array_set_nested_value(array &$array, array $parents, $value, $force = FALSE) {
$ref = &$array; $ref = &$array;
foreach ($parents as $parent) { foreach ($parents as $parent) {
// Note that PHP is fine with referencing a not existing array key - in this // PHP auto-creates container arrays and NULL entries without error if $ref
// case it just creates an entry with NULL as value. // is NULL, but throws an error if $ref is set, but not an array.
if ($force && isset($ref) && !is_array($ref)) {
$ref = array();
}
$ref = &$ref[$parent]; $ref = &$ref[$parent];
} }
$ref = $value; $ref = $value;
...@@ -6020,7 +6439,7 @@ function drupal_array_set_nested_value(array &$array, array $parents, $value) { ...@@ -6020,7 +6439,7 @@ function drupal_array_set_nested_value(array &$array, array $parents, $value) {
* The array from which to get the value. * The array from which to get the value.
* @param $parents * @param $parents
* An array of parent keys of the value, starting with the outermost key. * An array of parent keys of the value, starting with the outermost key.
* @param &$key_exists * @param $key_exists
* (optional) If given, an already defined variable that is altered by * (optional) If given, an already defined variable that is altered by
* reference. * reference.
* *
...@@ -6036,10 +6455,7 @@ function drupal_array_set_nested_value(array &$array, array $parents, $value) { ...@@ -6036,10 +6455,7 @@ function drupal_array_set_nested_value(array &$array, array $parents, $value) {
function drupal_array_get_nested_value(array &$array, array $parents, &$key_exists = NULL) { function drupal_array_get_nested_value(array &$array, array $parents, &$key_exists = NULL) {
$ref = &$array; $ref = &$array;
foreach ($parents as $parent) { foreach ($parents as $parent) {
// array_key_exists() is slower than isset() and triggers notices if the if (is_array($ref) && array_key_exists($parent, $ref)) {
// second argument is not an array, so only call it when absolutely
// necessary.
if (isset($ref[$parent]) || (is_array($ref) && array_key_exists($parent, $ref))) {
$ref = &$ref[$parent]; $ref = &$ref[$parent];
} }
else { else {
...@@ -6052,7 +6468,7 @@ function drupal_array_get_nested_value(array &$array, array $parents, &$key_exis ...@@ -6052,7 +6468,7 @@ function drupal_array_get_nested_value(array &$array, array $parents, &$key_exis
} }
/** /**
* Determines whether a nested array with variable depth contains all of the requested keys. * Determines whether a nested array contains the requested keys.
* *
* This helper function should be used when the depth of the array element to be * This helper function should be used when the depth of the array element to be
* checked may vary (that is, the number of parent keys is variable). See * checked may vary (that is, the number of parent keys is variable). See
...@@ -6088,14 +6504,11 @@ function drupal_array_nested_key_exists(array $array, array $parents) { ...@@ -6088,14 +6504,11 @@ function drupal_array_nested_key_exists(array $array, array $parents) {
} }
/** /**
* Provide theme registration for themes across .inc files. * Provides theme registration for themes across .inc files.
*/ */
function drupal_common_theme() { function drupal_common_theme() {
return array( return array(
// theme.inc // From theme.inc.
'placeholder' => array(
'variables' => array('text' => NULL)
),
'html' => array( 'html' => array(
'render element' => 'page', 'render element' => 'page',
'template' => 'html', 'template' => 'html',
...@@ -6171,7 +6584,7 @@ function drupal_common_theme() { ...@@ -6171,7 +6584,7 @@ function drupal_common_theme() {
'html_tag' => array( 'html_tag' => array(
'render element' => 'element', 'render element' => 'element',
), ),
// from theme.maintenance.inc // From theme.maintenance.inc.
'maintenance_page' => array( 'maintenance_page' => array(
'variables' => array('content' => NULL, 'show_messages' => TRUE), 'variables' => array('content' => NULL, 'show_messages' => TRUE),
'template' => 'maintenance-page', 'template' => 'maintenance-page',
...@@ -6191,7 +6604,7 @@ function drupal_common_theme() { ...@@ -6191,7 +6604,7 @@ function drupal_common_theme() {
'authorize_report' => array( 'authorize_report' => array(
'variables' => array('messages' => array()), 'variables' => array('messages' => array()),
), ),
// from pager.inc // From pager.inc.
'pager' => array( 'pager' => array(
'variables' => array('tags' => array(), 'element' => 0, 'parameters' => array(), 'quantity' => 9), 'variables' => array('tags' => array(), 'element' => 0, 'parameters' => array(), 'quantity' => 9),
), ),
...@@ -6210,11 +6623,7 @@ function drupal_common_theme() { ...@@ -6210,11 +6623,7 @@ function drupal_common_theme() {
'pager_link' => array( 'pager_link' => array(
'variables' => array('text' => NULL, 'page_new' => NULL, 'element' => NULL, 'parameters' => array(), 'attributes' => array()), 'variables' => array('text' => NULL, 'page_new' => NULL, 'element' => NULL, 'parameters' => array(), 'attributes' => array()),
), ),
// from locale.inc // From menu.inc.
'locale_admin_manage_screen' => array(
'render element' => 'form',
),
// from menu.inc
'menu_link' => array( 'menu_link' => array(
'render element' => 'element', 'render element' => 'element',
), ),
...@@ -6228,9 +6637,9 @@ function drupal_common_theme() { ...@@ -6228,9 +6637,9 @@ function drupal_common_theme() {
'render element' => 'element', 'render element' => 'element',
), ),
'menu_local_tasks' => array( 'menu_local_tasks' => array(
'variables' => array(), 'variables' => array('primary' => array(), 'secondary' => array()),
), ),
// from form.inc // From form.inc.
'select' => array( 'select' => array(
'render element' => 'element', 'render element' => 'element',
), ),
...@@ -6286,7 +6695,7 @@ function drupal_common_theme() { ...@@ -6286,7 +6695,7 @@ function drupal_common_theme() {
'render element' => 'element', 'render element' => 'element',
), ),
'form_required_marker' => array( 'form_required_marker' => array(
'arguments' => array('element' => NULL), 'render element' => 'element',
), ),
'form_element_label' => array( 'form_element_label' => array(
'render element' => 'element', 'render element' => 'element',
...@@ -6306,7 +6715,7 @@ function drupal_common_theme() { ...@@ -6306,7 +6715,7 @@ function drupal_common_theme() {
*/ */
/** /**
* Creates all tables in a module's hook_schema() implementation. * Creates all tables defined in a module's hook_schema().
* *
* Note: This function does not pass the module's schema through * Note: This function does not pass the module's schema through
* hook_schema_alter(). The module's tables will be created exactly as the * hook_schema_alter(). The module's tables will be created exactly as the
...@@ -6325,7 +6734,7 @@ function drupal_install_schema($module) { ...@@ -6325,7 +6734,7 @@ function drupal_install_schema($module) {
} }
/** /**
* Remove all tables that a module defines in its hook_schema(). * Removes all tables defined in a module's hook_schema().
* *
* Note: This function does not pass the module's schema through * Note: This function does not pass the module's schema through
* hook_schema_alter(). The module's tables will be created exactly as the * hook_schema_alter(). The module's tables will be created exactly as the
...@@ -6333,6 +6742,7 @@ function drupal_install_schema($module) { ...@@ -6333,6 +6742,7 @@ function drupal_install_schema($module) {
* *
* @param $module * @param $module
* The module for which the tables will be removed. * The module for which the tables will be removed.
*
* @return * @return
* An array of arrays with the following key/value pairs: * An array of arrays with the following key/value pairs:
* - success: a boolean indicating whether the query succeeded. * - success: a boolean indicating whether the query succeeded.
...@@ -6388,7 +6798,7 @@ function drupal_get_schema_unprocessed($module, $table = NULL) { ...@@ -6388,7 +6798,7 @@ function drupal_get_schema_unprocessed($module, $table = NULL) {
} }
/** /**
* Fill in required default values for table definitions returned by hook_schema(). * Fills in required default values for table definitions from hook_schema().
* *
* @param $schema * @param $schema
* The schema definition array as it was returned by the module's * The schema definition array as it was returned by the module's
...@@ -6419,7 +6829,9 @@ function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRU ...@@ -6419,7 +6829,9 @@ function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRU
} }
/** /**
* Retrieve a list of fields from a table schema. The list is suitable for use in a SQL query. * Retrieves a list of fields from a table schema.
*
* The returned list is suitable for use in an SQL query.
* *
* @param $table * @param $table
* The name of the table from which to retrieve fields. * The name of the table from which to retrieve fields.
...@@ -6427,7 +6839,7 @@ function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRU ...@@ -6427,7 +6839,7 @@ function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRU
* An optional prefix to to all fields. * An optional prefix to to all fields.
* *
* @return An array of fields. * @return An array of fields.
**/ */
function drupal_schema_fields_sql($table, $prefix = NULL) { function drupal_schema_fields_sql($table, $prefix = NULL) {
$schema = drupal_get_schema($table); $schema = drupal_get_schema($table);
$fields = array_keys($schema['fields']); $fields = array_keys($schema['fields']);
...@@ -6444,30 +6856,27 @@ function drupal_schema_fields_sql($table, $prefix = NULL) { ...@@ -6444,30 +6856,27 @@ function drupal_schema_fields_sql($table, $prefix = NULL) {
} }
/** /**
* Saves a record to the database based upon the schema. * Saves (inserts or updates) a record to the database based upon the schema.
*
* Default values are filled in for missing items, and 'serial' (auto increment)
* types are filled in with IDs.
* *
* @param $table * @param $table
* The name of the table; this must be defined by a hook_schema() * The name of the table; this must be defined by a hook_schema()
* implementation. * implementation.
* @param $record * @param $record
* An object or array representing the record to write, passed in by * An object or array representing the record to write, passed in by
* reference. The function will fill in defaults from the schema and add an * reference. If inserting a new record, values not provided in $record will
* ID value to serial fields. * be populated in $record and in the database with the default values from
* the schema, as well as a single serial (auto-increment) field (if present).
* If updating an existing record, only provided values are updated in the
* database, and $record is not modified.
* @param $primary_keys * @param $primary_keys
* If this is an update, specify the primary keys' field names. If this is a * To indicate that this is a new record to be inserted, omit this argument.
* new record, you must not provide this value. If there is only 1 field in * If this is an update, this argument specifies the primary keys' field
* the key, you may pass in a string; if there are multiple fields in the key, * names. If there is only 1 field in the key, you may pass in a string; if
* pass in an array. * there are multiple fields in the key, pass in an array.
* *
* @return * @return
* Failure to write a record will return FALSE. Otherwise SAVED_NEW or * If the record insert or update failed, returns FALSE. If it succeeded,
* SAVED_UPDATED is returned depending on the operation performed. The $object * returns SAVED_NEW or SAVED_UPDATED, depending on the operation performed.
* parameter will contain values for any serial fields defined by the $table.
* For example, $record->nid or $record['nid'] will be populated after
* inserting a new a new node.
*/ */
function drupal_write_record($table, &$record, $primary_keys = array()) { function drupal_write_record($table, &$record, $primary_keys = array()) {
// Standardize $primary_keys to an array. // Standardize $primary_keys to an array.
...@@ -6608,7 +7017,7 @@ function drupal_write_record($table, &$record, $primary_keys = array()) { ...@@ -6608,7 +7017,7 @@ function drupal_write_record($table, &$record, $primary_keys = array()) {
*/ */
/** /**
* Parse Drupal module and theme info file format. * Parses Drupal module and theme .info files.
* *
* Info files are NOT for placing arbitrary theme and module-specific settings. * Info files are NOT for placing arbitrary theme and module-specific settings.
* Use variable_get() and variable_set() for that. * Use variable_get() and variable_set() for that.
...@@ -6619,39 +7028,46 @@ function drupal_write_record($table, &$record, $primary_keys = array()) { ...@@ -6619,39 +7028,46 @@ function drupal_write_record($table, &$record, $primary_keys = array()) {
* - dependencies: An array of shortnames of other modules this module requires. * - dependencies: An array of shortnames of other modules this module requires.
* - package: The name of the package of modules this module belongs to. * - package: The name of the package of modules this module belongs to.
* *
* @see forum.info * See forum.info for an example of a module .info file.
* *
* Information stored in a theme .info file: * Information stored in a theme .info file:
* - name: The real name of the theme for display purposes * - name: The real name of the theme for display purposes.
* - description: Brief description * - description: Brief description.
* - screenshot: Path to screenshot relative to the theme's .info file. * - screenshot: Path to screenshot relative to the theme's .info file.
* - engine: Theme engine, typically: engine = phptemplate * - engine: Theme engine; typically phptemplate.
* - base: Name of a base theme, if applicable, eg: base = zen * - base: Name of a base theme, if applicable; e.g., base = zen.
* - regions: Listed regions eg: region[left] = Left sidebar * - regions: Listed regions; e.g., region[left] = Left sidebar.
* - features: Features available eg: features[] = logo * - features: Features available; e.g., features[] = logo.
* - stylesheets: Theme stylesheets eg: stylesheets[all][] = my-style.css * - stylesheets: Theme stylesheets; e.g., stylesheets[all][] = my-style.css.
* - scripts: Theme scripts eg: scripts[] = my-script.css * - scripts: Theme scripts; e.g., scripts[] = my-script.js.
* *
* @see bartik.info * See bartik.info for an example of a theme .info file.
* *
* @param $filename * @param $filename
* The file we are parsing. Accepts file with relative or absolute path. * The file we are parsing. Accepts file with relative or absolute path.
*
* @return * @return
* The info array. * The info array.
* *
* @see drupal_parse_info_format() * @see drupal_parse_info_format()
*/ */
function drupal_parse_info_file($filename) { function drupal_parse_info_file($filename) {
$info = &drupal_static(__FUNCTION__, array());
if (!isset($info[$filename])) {
if (!file_exists($filename)) { if (!file_exists($filename)) {
return array(); $info[$filename] = array();
} }
else {
$data = file_get_contents($filename); $data = file_get_contents($filename);
return drupal_parse_info_format($data); $info[$filename] = drupal_parse_info_format($data);
}
}
return $info[$filename];
} }
/** /**
* Parse data in Drupal's .info format. * Parses data in Drupal's .info format.
* *
* Data should be in an .ini-like format to specify values. White-space * Data should be in an .ini-like format to specify values. White-space
* generally doesn't matter, except inside values: * generally doesn't matter, except inside values:
...@@ -6681,6 +7097,7 @@ function drupal_parse_info_file($filename) { ...@@ -6681,6 +7097,7 @@ function drupal_parse_info_file($filename) {
* *
* @param $data * @param $data
* A string to parse. * A string to parse.
*
* @return * @return
* The info array. * The info array.
* *
...@@ -6704,19 +7121,19 @@ function drupal_parse_info_format($data) { ...@@ -6704,19 +7121,19 @@ function drupal_parse_info_format($data) {
)\s*$ # Stop at the next end of a line, ignoring trailing whitespace )\s*$ # Stop at the next end of a line, ignoring trailing whitespace
@msx', $data, $matches, PREG_SET_ORDER)) { @msx', $data, $matches, PREG_SET_ORDER)) {
foreach ($matches as $match) { foreach ($matches as $match) {
// Fetch the key and value string // Fetch the key and value string.
$i = 0; $i = 0;
foreach (array('key', 'value1', 'value2', 'value3') as $var) { foreach (array('key', 'value1', 'value2', 'value3') as $var) {
$$var = isset($match[++$i]) ? $match[$i] : ''; $$var = isset($match[++$i]) ? $match[$i] : '';
} }
$value = stripslashes(substr($value1, 1, -1)) . stripslashes(substr($value2, 1, -1)) . $value3; $value = stripslashes(substr($value1, 1, -1)) . stripslashes(substr($value2, 1, -1)) . $value3;
// Parse array syntax // Parse array syntax.
$keys = preg_split('/\]?\[/', rtrim($key, ']')); $keys = preg_split('/\]?\[/', rtrim($key, ']'));
$last = array_pop($keys); $last = array_pop($keys);
$parent = &$info; $parent = &$info;
// Create nested arrays // Create nested arrays.
foreach ($keys as $key) { foreach ($keys as $key) {
if ($key == '') { if ($key == '') {
$key = count($parent); $key = count($parent);
...@@ -6732,7 +7149,7 @@ function drupal_parse_info_format($data) { ...@@ -6732,7 +7149,7 @@ function drupal_parse_info_format($data) {
$value = $constants[$value]; $value = $constants[$value];
} }
// Insert actual value // Insert actual value.
if ($last == '') { if ($last == '') {
$last = count($parent); $last = count($parent);
} }
...@@ -6744,12 +7161,14 @@ function drupal_parse_info_format($data) { ...@@ -6744,12 +7161,14 @@ function drupal_parse_info_format($data) {
} }
/** /**
* Severity levels, as defined in RFC 3164: http://www.ietf.org/rfc/rfc3164.txt. * Returns a list of severity levels, as defined in RFC 3164.
* *
* @return * @return
* Array of the possible severity levels for log messages. * Array of the possible severity levels for log messages.
* *
* @see http://www.ietf.org/rfc/rfc3164.txt
* @see watchdog() * @see watchdog()
* @ingroup logging_severity_levels
*/ */
function watchdog_severity_levels() { function watchdog_severity_levels() {
return array( return array(
...@@ -6766,7 +7185,7 @@ function watchdog_severity_levels() { ...@@ -6766,7 +7185,7 @@ function watchdog_severity_levels() {
/** /**
* Explode a string of given tags into an array. * Explodes a string of tags into an array.
* *
* @see drupal_implode_tags() * @see drupal_implode_tags()
*/ */
...@@ -6792,7 +7211,7 @@ function drupal_explode_tags($tags) { ...@@ -6792,7 +7211,7 @@ function drupal_explode_tags($tags) {
} }
/** /**
* Implode an array of tags into a string. * Implodes an array of tags into a string.
* *
* @see drupal_explode_tags() * @see drupal_explode_tags()
*/ */
...@@ -6810,7 +7229,7 @@ function drupal_implode_tags($tags) { ...@@ -6810,7 +7229,7 @@ function drupal_implode_tags($tags) {
} }
/** /**
* Flush all cached data on the site. * Flushes all cached data on the site.
* *
* Empties cache tables, rebuilds the menu cache and theme registries, and * Empties cache tables, rebuilds the menu cache and theme registries, and
* invokes a hook so that other modules' cache data can be cleared as well. * invokes a hook so that other modules' cache data can be cleared as well.
...@@ -6828,6 +7247,7 @@ function drupal_flush_all_caches() { ...@@ -6828,6 +7247,7 @@ function drupal_flush_all_caches() {
system_rebuild_theme_data(); system_rebuild_theme_data();
drupal_theme_rebuild(); drupal_theme_rebuild();
entity_info_cache_clear();
node_types_rebuild(); node_types_rebuild();
// node_menu() defines menu items based on node types so it needs to come // node_menu() defines menu items based on node types so it needs to come
// after node types are rebuilt. // after node types are rebuilt.
...@@ -6838,7 +7258,7 @@ function drupal_flush_all_caches() { ...@@ -6838,7 +7258,7 @@ function drupal_flush_all_caches() {
// Don't clear cache_form - in-progress form submissions may break. // Don't clear cache_form - in-progress form submissions may break.
// Ordered so clearing the page cache will always be the last action. // Ordered so clearing the page cache will always be the last action.
$core = array('cache', 'cache_filter', 'cache_bootstrap', 'cache_page'); $core = array('cache', 'cache_path', 'cache_filter', 'cache_bootstrap', 'cache_page');
$cache_tables = array_merge(module_invoke_all('flush_caches'), $core); $cache_tables = array_merge(module_invoke_all('flush_caches'), $core);
foreach ($cache_tables as $table) { foreach ($cache_tables as $table) {
cache_clear_all('*', $table, TRUE); cache_clear_all('*', $table, TRUE);
...@@ -6851,10 +7271,10 @@ function drupal_flush_all_caches() { ...@@ -6851,10 +7271,10 @@ function drupal_flush_all_caches() {
} }
/** /**
* Helper function to change query-strings on css/js files. * Changes the dummy query string added to all CSS and JavaScript files.
* *
* Changes the character added to all css/js files as dummy query-string, so * Changing the dummy query string appended to CSS and JavaScript files forces
* that all browsers are forced to reload fresh files. * all browsers to reload fresh files.
*/ */
function _drupal_flush_css_js() { function _drupal_flush_css_js() {
// The timestamp is converted to base 36 in order to make it more compact. // The timestamp is converted to base 36 in order to make it more compact.
...@@ -6862,7 +7282,7 @@ function _drupal_flush_css_js() { ...@@ -6862,7 +7282,7 @@ function _drupal_flush_css_js() {
} }
/** /**
* Debug function used for outputting debug information. * Outputs debug information.
* *
* The debug information is passed on to trigger_error() after being converted * The debug information is passed on to trigger_error() after being converted
* to a string using _drupal_debug_message(). * to a string using _drupal_debug_message().
...@@ -6887,10 +7307,11 @@ function debug($data, $label = NULL, $print_r = FALSE) { ...@@ -6887,10 +7307,11 @@ function debug($data, $label = NULL, $print_r = FALSE) {
} }
/** /**
* Parse a dependency for comparison by drupal_check_incompatibility(). * Parses a dependency for comparison by drupal_check_incompatibility().
* *
* @param $dependency * @param $dependency
* A dependency string, for example 'foo (>=7.x-4.5-beta5, 3.x)'. * A dependency string, for example 'foo (>=7.x-4.5-beta5, 3.x)'.
*
* @return * @return
* An associative array with three keys: * An associative array with three keys:
* - 'name' includes the name of the thing to depend on (e.g. 'foo'). * - 'name' includes the name of the thing to depend on (e.g. 'foo').
...@@ -6944,12 +7365,13 @@ function drupal_parse_dependency($dependency) { ...@@ -6944,12 +7365,13 @@ function drupal_parse_dependency($dependency) {
} }
/** /**
* Check whether a version is compatible with a given dependency. * Checks whether a version is compatible with a given dependency.
* *
* @param $v * @param $v
* The parsed dependency structure from drupal_parse_dependency(). * The parsed dependency structure from drupal_parse_dependency().
* @param $current_version * @param $current_version
* The version to check against (like 4.2). * The version to check against (like 4.2).
*
* @return * @return
* NULL if compatible, otherwise the original dependency version string that * NULL if compatible, otherwise the original dependency version string that
* caused the incompatibility. * caused the incompatibility.
...@@ -7071,12 +7493,24 @@ function entity_info_cache_clear() { ...@@ -7071,12 +7493,24 @@ function entity_info_cache_clear() {
*/ */
function entity_extract_ids($entity_type, $entity) { function entity_extract_ids($entity_type, $entity) {
$info = entity_get_info($entity_type); $info = entity_get_info($entity_type);
// Objects being created might not have id/vid yet. // Objects being created might not have id/vid yet.
$id = isset($entity->{$info['entity keys']['id']}) ? $entity->{$info['entity keys']['id']} : NULL; $id = isset($entity->{$info['entity keys']['id']}) ? $entity->{$info['entity keys']['id']} : NULL;
$vid = ($info['entity keys']['revision'] && isset($entity->{$info['entity keys']['revision']})) ? $entity->{$info['entity keys']['revision']} : NULL; $vid = ($info['entity keys']['revision'] && isset($entity->{$info['entity keys']['revision']})) ? $entity->{$info['entity keys']['revision']} : NULL;
// If no bundle key provided, then we assume a single bundle, named after the
// entity type. if (!empty($info['entity keys']['bundle'])) {
$bundle = $info['entity keys']['bundle'] ? $entity->{$info['entity keys']['bundle']} : $entity_type; // Explicitly fail for malformed entities missing the bundle property.
if (!isset($entity->{$info['entity keys']['bundle']}) || $entity->{$info['entity keys']['bundle']} === '') {
throw new EntityMalformedException(t('Missing bundle property on entity of type @entity_type.', array('@entity_type' => $entity_type)));
}
$bundle = $entity->{$info['entity keys']['bundle']};
}
else {
// The entity type provides no bundle key: assume a single bundle, named
// after the entity type.
$bundle = $entity_type;
}
return array($id, $vid, $bundle); return array($id, $vid, $bundle);
} }
...@@ -7112,8 +7546,7 @@ function entity_create_stub_entity($entity_type, $ids) { ...@@ -7112,8 +7546,7 @@ function entity_create_stub_entity($entity_type, $ids) {
/** /**
* Load entities from the database. * Load entities from the database.
* *
* This function should be used whenever you need to load more than one entity * The entities are stored in a static memory cache, and will not require
* from the database. The entities are loaded into memory and will not require
* database access if loaded again during the same page request. * database access if loaded again during the same page request.
* *
* The actual loading is done through a class that has to implement the * The actual loading is done through a class that has to implement the
...@@ -7128,18 +7561,25 @@ function entity_create_stub_entity($entity_type, $ids) { ...@@ -7128,18 +7561,25 @@ function entity_create_stub_entity($entity_type, $ids) {
* @see hook_entity_info() * @see hook_entity_info()
* @see DrupalEntityControllerInterface * @see DrupalEntityControllerInterface
* @see DrupalDefaultEntityController * @see DrupalDefaultEntityController
* @see EntityFieldQuery
* *
* @param $entity_type * @param $entity_type
* The entity type to load, e.g. node or user. * The entity type to load, e.g. node or user.
* @param $ids * @param $ids
* An array of entity IDs, or FALSE to load all entities. * An array of entity IDs, or FALSE to load all entities.
* @param $conditions * @param $conditions
* An array of conditions in the form 'field' => $value. * (deprecated) An associative array of conditions on the base table, where
* the keys are the database fields and the values are the values those
* fields must have. Instead, it is preferable to use EntityFieldQuery to
* retrieve a list of entity IDs loadable by this function.
* @param $reset * @param $reset
* Whether to reset the internal cache for the requested entity type. * Whether to reset the internal cache for the requested entity type.
* *
* @return * @return
* An array of entity objects indexed by their ids. * An array of entity objects indexed by their ids. When no results are
* found, an empty array is returned.
*
* @todo Remove $conditions in Drupal 8.
*/ */
function entity_load($entity_type, $ids = FALSE, $conditions = array(), $reset = FALSE) { function entity_load($entity_type, $ids = FALSE, $conditions = array(), $reset = FALSE) {
if ($reset) { if ($reset) {
...@@ -7148,6 +7588,28 @@ function entity_load($entity_type, $ids = FALSE, $conditions = array(), $reset = ...@@ -7148,6 +7588,28 @@ function entity_load($entity_type, $ids = FALSE, $conditions = array(), $reset =
return entity_get_controller($entity_type)->load($ids, $conditions); return entity_get_controller($entity_type)->load($ids, $conditions);
} }
/**
* Loads the unchanged, i.e. not modified, entity from the database.
*
* Unlike entity_load() this function ensures the entity is directly loaded from
* the database, thus bypassing any static cache. In particular, this function
* is useful to determine changes by comparing the entity being saved to the
* stored entity.
*
* @param $entity_type
* The entity type to load, e.g. node or user.
* @param $id
* The id of the entity to load.
*
* @return
* The unchanged entity, or FALSE if the entity cannot be loaded.
*/
function entity_load_unchanged($entity_type, $id) {
entity_get_controller($entity_type)->resetCache(array($id));
$result = entity_get_controller($entity_type)->load(array($id));
return reset($result);
}
/** /**
* Get the entity controller class for an entity type. * Get the entity controller class for an entity type.
*/ */
...@@ -7180,8 +7642,15 @@ function entity_get_controller($entity_type) { ...@@ -7180,8 +7642,15 @@ function entity_get_controller($entity_type) {
* The type of entity, i.e. 'node', 'user'. * The type of entity, i.e. 'node', 'user'.
* @param $entities * @param $entities
* The entity objects which are being prepared for view, keyed by object ID. * The entity objects which are being prepared for view, keyed by object ID.
* @param $langcode
* (optional) A language code to be used for rendering. Defaults to the global
* content language of the current request.
*/ */
function entity_prepare_view($entity_type, $entities) { function entity_prepare_view($entity_type, $entities, $langcode = NULL) {
if (!isset($langcode)) {
$langcode = $GLOBALS['language_content']->language;
}
// To ensure hooks are only run once per entity, check for an // To ensure hooks are only run once per entity, check for an
// entity_view_prepared flag and only process items without it. // entity_view_prepared flag and only process items without it.
// @todo: resolve this more generally for both entity and field level hooks. // @todo: resolve this more generally for both entity and field level hooks.
...@@ -7197,7 +7666,7 @@ function entity_prepare_view($entity_type, $entities) { ...@@ -7197,7 +7666,7 @@ function entity_prepare_view($entity_type, $entities) {
} }
if (!empty($prepare)) { if (!empty($prepare)) {
module_invoke_all('entity_prepare_view', $prepare, $entity_type); module_invoke_all('entity_prepare_view', $prepare, $entity_type, $langcode);
} }
} }
...@@ -7214,11 +7683,6 @@ function entity_prepare_view($entity_type, $entities) { ...@@ -7214,11 +7683,6 @@ function entity_prepare_view($entity_type, $entities) {
* uri of its own. * uri of its own.
*/ */
function entity_uri($entity_type, $entity) { function entity_uri($entity_type, $entity) {
// This check enables the URI of an entity to be easily overridden from what
// the callback for the entity type or bundle would return, and it helps
// minimize performance overhead when entity_uri() is called multiple times
// for the same entity.
if (!isset($entity->uri)) {
$info = entity_get_info($entity_type); $info = entity_get_info($entity_type);
list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity); list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
...@@ -7231,45 +7695,39 @@ function entity_uri($entity_type, $entity) { ...@@ -7231,45 +7695,39 @@ function entity_uri($entity_type, $entity) {
$uri_callback = $info['uri callback']; $uri_callback = $info['uri callback'];
} }
else { else {
$uri_callback = NULL; return NULL;
} }
// Invoke the callback to get the URI. If there is no callback, set the // Invoke the callback to get the URI. If there is no callback, return NULL.
// entity's 'uri' property to FALSE to indicate that it is known to not have
// a URI.
if (isset($uri_callback) && function_exists($uri_callback)) { if (isset($uri_callback) && function_exists($uri_callback)) {
$entity->uri = $uri_callback($entity); $uri = $uri_callback($entity);
if (!isset($entity->uri['options'])) {
$entity->uri['options'] = array();
}
// Pass the entity data to url() so that alter functions do not need to // Pass the entity data to url() so that alter functions do not need to
// lookup this entity again. // lookup this entity again.
$entity->uri['options']['entity_type'] = $entity_type; $uri['options']['entity_type'] = $entity_type;
$entity->uri['options']['entity'] = $entity; $uri['options']['entity'] = $entity;
} return $uri;
else {
$entity->uri = FALSE;
}
} }
return $entity->uri ? $entity->uri : NULL;
} }
/** /**
* Returns the label of an entity. * Returns the label of an entity.
* *
* See the 'label callback' component of the hook_entity_info() return value
* for more information.
*
* @param $entity_type * @param $entity_type
* The entity type; e.g. 'node' or 'user'. * The entity type; e.g., 'node' or 'user'.
* @param $entity * @param $entity
* The entity for which to generate a path. * The entity for which to generate the label.
* *
* @return * @return
* A string with the entity label (e.g. node title), or FALSE if not found. * The entity label, or FALSE if not found.
*/ */
function entity_label($entity_type, $entity) { function entity_label($entity_type, $entity) {
$label = FALSE; $label = FALSE;
$info = entity_get_info($entity_type); $info = entity_get_info($entity_type);
if (isset($info['label callback']) && function_exists($info['label callback'])) { if (isset($info['label callback']) && function_exists($info['label callback'])) {
$label = $info['label callback']($entity); $label = $info['label callback']($entity, $entity_type);
} }
elseif (!empty($info['entity keys']['label']) && isset($entity->{$info['entity keys']['label']})) { elseif (!empty($info['entity keys']['label']) && isset($entity->{$info['entity keys']['label']})) {
$label = $entity->{$info['entity keys']['label']}; $label = $entity->{$info['entity keys']['label']};
...@@ -7296,16 +7754,20 @@ function entity_form_field_validate($entity_type, $form, &$form_state) { ...@@ -7296,16 +7754,20 @@ function entity_form_field_validate($entity_type, $form, &$form_state) {
* *
* During the submission handling of an entity form's "Save", "Preview", and * 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 * possibly other buttons, the form state's entity needs to be updated with the
* submitted form values. Each entity form implements its own * submitted form values. Each entity form implements its own builder function
* $form['#builder_function'] for doing this, appropriate for the particular * for doing this, appropriate for the particular entity and form, whereas
* entity and form. Many of these entity builder functions can call this helper * modules may specify additional builder functions in $form['#entity_builders']
* function to re-use its logic of copying $form_state['values'][PROPERTY] * for copying the form values of added form elements to entity properties.
* values to $entity->PROPERTY for all entries in $form_state['values'] that are * Many of the main entity builder functions can call this helper function to
* not field data, and calling field_attach_submit() to copy field data. * 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. Apart from that
* this helper invokes any additional builder functions that have been specified
* in $form['#entity_builders'].
* *
* For some entity forms (e.g., forms with complex non-field data and forms that * For some entity forms (e.g., forms with complex non-field data and forms that
* simultaneously edit multiple entities), this behavior may be inappropriate, * simultaneously edit multiple entities), this behavior may be inappropriate,
* so the #builder_function for such forms needs to implement the required * so the builder function for such forms needs to implement the required
* functionality instead of calling this function. * functionality instead of calling this function.
*/ */
function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_state) { function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_state) {
...@@ -7320,6 +7782,13 @@ function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_st ...@@ -7320,6 +7782,13 @@ function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_st
$entity->$key = $value; $entity->$key = $value;
} }
// Invoke all specified builders for copying form values to entity properties.
if (isset($form['#entity_builders'])) {
foreach ($form['#entity_builders'] as $function) {
$function($entity_type, $entity, $form, $form_state);
}
}
// Copy field values to the entity. // Copy field values to the entity.
if ($info['fieldable']) { if ($info['fieldable']) {
field_attach_submit($entity_type, $entity, $form, $form_state); field_attach_submit($entity_type, $entity, $form, $form_state);
...@@ -7407,11 +7876,12 @@ function archiver_get_extensions() { ...@@ -7407,11 +7876,12 @@ function archiver_get_extensions() {
} }
/** /**
* Create the appropriate archiver for the specified file. * Creates the appropriate archiver for the specified file.
* *
* @param $file * @param $file
* The full path of the archive file. Note that stream wrapper * The full path of the archive file. Note that stream wrapper paths are
* paths are supported, but not remote ones. * supported, but not remote ones.
*
* @return * @return
* A newly created instance of the archiver class appropriate * A newly created instance of the archiver class appropriate
* for the specified file, already bound to that file. * for the specified file, already bound to that file.
...@@ -7440,14 +7910,14 @@ function archiver_get_archiver($file) { ...@@ -7440,14 +7910,14 @@ function archiver_get_archiver($file) {
} }
/** /**
* Drupal Updater registry. * Assembles the Drupal Updater registry.
* *
* An Updater is a class that knows how to update various parts of the Drupal * An Updater is a class that knows how to update various parts of the Drupal
* file system, for example to update modules that have newer releases, or to * file system, for example to update modules that have newer releases, or to
* install a new theme. * install a new theme.
* *
* @return * @return
* Returns the Drupal Updater class registry. * The Drupal Updater class registry.
* *
* @see hook_updater_info() * @see hook_updater_info()
* @see hook_updater_info_alter() * @see hook_updater_info_alter()
...@@ -7461,3 +7931,39 @@ function drupal_get_updaters() { ...@@ -7461,3 +7931,39 @@ function drupal_get_updaters() {
} }
return $updaters; return $updaters;
} }
/**
* Assembles the Drupal FileTransfer registry.
*
* @return
* The Drupal FileTransfer class registry.
*
* @see FileTransfer
* @see hook_filetransfer_info()
* @see hook_filetransfer_info_alter()
*/
function drupal_get_filetransfer_info() {
$info = &drupal_static(__FUNCTION__);
if (!isset($info)) {
// Since we have to manually set the 'file path' default for each
// module separately, we can't use module_invoke_all().
$info = array();
foreach (module_implements('filetransfer_info') as $module) {
$function = $module . '_filetransfer_info';
if (function_exists($function)) {
$result = $function();
if (isset($result) && is_array($result)) {
foreach ($result as &$values) {
if (empty($values['file path'])) {
$values['file path'] = drupal_get_path('module', $module);
}
}
$info = array_merge_recursive($info, $result);
}
}
}
drupal_alter('filetransfer_info', $info);
uasort($info, 'drupal_sort_weight');
}
return $info;
}
<?php <?php
// $Id: database.inc,v 1.141 2010/10/15 18:03:43 webchick Exp $
/** /**
* @file * @file
...@@ -153,7 +152,7 @@ ...@@ -153,7 +152,7 @@
* } * }
* *
* // $txn goes out of scope here. Unless the transaction was rolled back, it * // $txn goes out of scope here. Unless the transaction was rolled back, it
* // gets automatically commited here. * // gets automatically committed here.
* } * }
* *
* function my_other_function($id) { * function my_other_function($id) {
...@@ -193,6 +192,17 @@ abstract class DatabaseConnection extends PDO { ...@@ -193,6 +192,17 @@ abstract class DatabaseConnection extends PDO {
*/ */
protected $target = NULL; protected $target = NULL;
/**
* The key representing this connection.
*
* The key is a unique string which identifies a database connection. A
* connection can be a single server or a cluster of master and slaves (use
* target to pick between master and slave).
*
* @var string
*/
protected $key = NULL;
/** /**
* The current database logging object for this connection. * The current database logging object for this connection.
* *
...@@ -263,20 +273,25 @@ abstract class DatabaseConnection extends PDO { ...@@ -263,20 +273,25 @@ abstract class DatabaseConnection extends PDO {
protected $schema = NULL; protected $schema = NULL;
/** /**
* The default prefix used by this database connection. * The prefixes used by this database connection.
* *
* Separated from the other prefixes for performance reasons. * @var array
*/
protected $prefixes = array();
/**
* List of search values for use in prefixTables().
* *
* @var string * @var array
*/ */
protected $defaultPrefix = ''; protected $prefixSearch = array();
/** /**
* The non-default prefixes used by this database connection. * List of replacement values for use in prefixTables().
* *
* @var array * @var array
*/ */
protected $prefixes = array(); protected $prefixReplace = array();
function __construct($dsn, $username, $password, $driver_options = array()) { function __construct($dsn, $username, $password, $driver_options = array()) {
// Initialize and prepare the connection prefix. // Initialize and prepare the connection prefix.
...@@ -310,7 +325,7 @@ abstract class DatabaseConnection extends PDO { ...@@ -310,7 +325,7 @@ abstract class DatabaseConnection extends PDO {
* PDO::FETCH_OBJ, PDO::FETCH_NUM, or a string representing the name of a * PDO::FETCH_OBJ, PDO::FETCH_NUM, or a string representing the name of a
* class. If a string is specified, each record will be fetched into a new * class. If a string is specified, each record will be fetched into a new
* object of that class. The behavior of all other values is defined by PDO. * object of that class. The behavior of all other values is defined by PDO.
* See http://www.php.net/PDOStatement-fetch * See http://php.net/manual/pdostatement.fetch.php
* - return: Depending on the type of query, different return values may be * - return: Depending on the type of query, different return values may be
* meaningful. This directive instructs the system which type of return * meaningful. This directive instructs the system which type of return
* value is desired. The system will generally set the correct value * value is desired. The system will generally set the correct value
...@@ -365,7 +380,7 @@ abstract class DatabaseConnection extends PDO { ...@@ -365,7 +380,7 @@ abstract class DatabaseConnection extends PDO {
} }
/** /**
* Preprocess the prefixes used by this database connection. * Set the list of prefixes used by this database connection.
* *
* @param $prefix * @param $prefix
* The prefixes, in any of the multiple forms documented in * The prefixes, in any of the multiple forms documented in
...@@ -373,14 +388,27 @@ abstract class DatabaseConnection extends PDO { ...@@ -373,14 +388,27 @@ abstract class DatabaseConnection extends PDO {
*/ */
protected function setPrefix($prefix) { protected function setPrefix($prefix) {
if (is_array($prefix)) { if (is_array($prefix)) {
$this->defaultPrefix = isset($prefix['default']) ? $prefix['default'] : ''; $this->prefixes = $prefix + array('default' => '');
unset($prefix['default']);
$this->prefixes = $prefix;
} }
else { else {
$this->defaultPrefix = $prefix; $this->prefixes = array('default' => $prefix);
$this->prefixes = array(); }
// Set up variables for use in prefixTables(). Replace table-specific
// prefixes first.
$this->prefixSearch = array();
$this->prefixReplace = array();
foreach ($this->prefixes as $key => $val) {
if ($key != 'default') {
$this->prefixSearch[] = '{' . $key . '}';
$this->prefixReplace[] = $val . $key;
}
} }
// Then replace remaining tables with the default prefix.
$this->prefixSearch[] = '{';
$this->prefixReplace[] = $this->prefixes['default'];
$this->prefixSearch[] = '}';
$this->prefixReplace[] = '';
} }
/** /**
...@@ -398,12 +426,7 @@ abstract class DatabaseConnection extends PDO { ...@@ -398,12 +426,7 @@ abstract class DatabaseConnection extends PDO {
* The properly-prefixed string. * The properly-prefixed string.
*/ */
public function prefixTables($sql) { public function prefixTables($sql) {
// Replace specific table prefixes first. return str_replace($this->prefixSearch, $this->prefixReplace, $sql);
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 , '}' => ''));
} }
/** /**
...@@ -417,7 +440,7 @@ abstract class DatabaseConnection extends PDO { ...@@ -417,7 +440,7 @@ abstract class DatabaseConnection extends PDO {
return $this->prefixes[$table]; return $this->prefixes[$table];
} }
else { else {
return $this->defaultPrefix; return $this->prefixes['default'];
} }
} }
...@@ -469,6 +492,28 @@ abstract class DatabaseConnection extends PDO { ...@@ -469,6 +492,28 @@ abstract class DatabaseConnection extends PDO {
return $this->target; return $this->target;
} }
/**
* Tells this connection object what its key is.
*
* @param $target
* The key this connection is for.
*/
public function setKey($key) {
if (!isset($this->key)) {
$this->key = $key;
}
}
/**
* Returns the key this connection is associated with.
*
* @return
* The key of this connection.
*/
public function getKey() {
return $this->key;
}
/** /**
* Associates a logging object with this connection. * Associates a logging object with this connection.
* *
...@@ -508,6 +553,63 @@ abstract class DatabaseConnection extends PDO { ...@@ -508,6 +553,63 @@ abstract class DatabaseConnection extends PDO {
return $this->prefixTables('{' . $table . '}_' . $field . '_seq'); return $this->prefixTables('{' . $table . '}_' . $field . '_seq');
} }
/**
* Flatten an array of query comments into a single comment string.
*
* The comment string will be sanitized to avoid SQL injection attacks.
*
* @param $comments
* An array of query comment strings.
*
* @return
* A sanitized comment string.
*/
public function makeComment($comments) {
if (empty($comments))
return '';
// Flatten the array of comments.
$comment = implode('; ', $comments);
// Sanitize the comment string so as to avoid SQL injection attacks.
return '/* ' . $this->filterComment($comment) . ' */ ';
}
/**
* Sanitize a query comment string.
*
* Ensure a query comment does not include strings such as "* /" that might
* terminate the comment early. This avoids SQL injection attacks via the
* query comment. The comment strings in this example are separated by a
* space to avoid PHP parse errors.
*
* For example, the comment:
* @code
* db_update('example')
* ->condition('id', $id)
* ->fields(array('field2' => 10))
* ->comment('Exploit * / DROP TABLE node; --')
* ->execute()
* @endcode
*
* Would result in the following SQL statement being generated:
* @code
* "/ * Exploit * / DROP TABLE node; -- * / UPDATE example SET field2=..."
* @endcode
*
* Unless the comment is sanitised first, the SQL server would drop the
* node table and ignore the rest of the SQL statement.
*
* @param $comment
* A query comment string.
*
* @return
* A sanitized version of the query comment string.
*/
protected function filterComment($comment = '') {
return preg_replace('/(\/\*\s*)|(\s*\*\/)/', '', $comment);
}
/** /**
* Executes a query string against the database. * Executes a query string against the database.
* *
...@@ -648,13 +750,20 @@ abstract class DatabaseConnection extends PDO { ...@@ -648,13 +750,20 @@ abstract class DatabaseConnection extends PDO {
* *
* @param string $class * @param string $class
* The class for which we want the potentially driver-specific class. * The class for which we want the potentially driver-specific class.
* @param array $files
* The name of the files in which the driver-specific class can be.
* @param $use_autoload
* If TRUE, attempt to load classes using PHP's autoload capability
* as well as the manual approach here.
* @return string * @return string
* The name of the class that should be used for this driver. * The name of the class that should be used for this driver.
*/ */
public function getDriverClass($class) { public function getDriverClass($class, array $files = array(), $use_autoload = FALSE) {
if (empty($this->driverClasses[$class])) { if (empty($this->driverClasses[$class])) {
$this->driverClasses[$class] = $class . '_' . $this->driver(); $driver = $this->driver();
if (!class_exists($this->driverClasses[$class])) { $this->driverClasses[$class] = $class . '_' . $driver;
Database::loadDriverFile($driver, $files);
if (!class_exists($this->driverClasses[$class], $use_autoload)) {
$this->driverClasses[$class] = $class; $this->driverClasses[$class] = $class;
} }
} }
...@@ -662,7 +771,7 @@ abstract class DatabaseConnection extends PDO { ...@@ -662,7 +771,7 @@ abstract class DatabaseConnection extends PDO {
} }
/** /**
* Prepares and returns a SELECT query object with the specified ID. * Prepares and returns a SELECT query object.
* *
* @param $table * @param $table
* The base table for this query, that is, the first table in the FROM * The base table for this query, that is, the first table in the FROM
...@@ -681,12 +790,12 @@ abstract class DatabaseConnection extends PDO { ...@@ -681,12 +790,12 @@ abstract class DatabaseConnection extends PDO {
* @see SelectQuery * @see SelectQuery
*/ */
public function select($table, $alias = NULL, array $options = array()) { public function select($table, $alias = NULL, array $options = array()) {
$class = $this->getDriverClass('SelectQuery'); $class = $this->getDriverClass('SelectQuery', array('query.inc', 'select.inc'));
return new $class($table, $alias, $this, $options); return new $class($table, $alias, $this, $options);
} }
/** /**
* Prepares and returns an INSERT query object with the specified ID. * Prepares and returns an INSERT query object.
* *
* @param $options * @param $options
* An array of options on the query. * An array of options on the query.
...@@ -697,12 +806,12 @@ abstract class DatabaseConnection extends PDO { ...@@ -697,12 +806,12 @@ abstract class DatabaseConnection extends PDO {
* @see InsertQuery * @see InsertQuery
*/ */
public function insert($table, array $options = array()) { public function insert($table, array $options = array()) {
$class = $this->getDriverClass('InsertQuery'); $class = $this->getDriverClass('InsertQuery', array('query.inc'));
return new $class($this, $table, $options); return new $class($this, $table, $options);
} }
/** /**
* Prepares and returns a MERGE query object with the specified ID. * Prepares and returns a MERGE query object.
* *
* @param $options * @param $options
* An array of options on the query. * An array of options on the query.
...@@ -713,13 +822,13 @@ abstract class DatabaseConnection extends PDO { ...@@ -713,13 +822,13 @@ abstract class DatabaseConnection extends PDO {
* @see MergeQuery * @see MergeQuery
*/ */
public function merge($table, array $options = array()) { public function merge($table, array $options = array()) {
$class = $this->getDriverClass('MergeQuery'); $class = $this->getDriverClass('MergeQuery', array('query.inc'));
return new $class($this, $table, $options); return new $class($this, $table, $options);
} }
/** /**
* Prepares and returns an UPDATE query object with the specified ID. * Prepares and returns an UPDATE query object.
* *
* @param $options * @param $options
* An array of options on the query. * An array of options on the query.
...@@ -730,12 +839,12 @@ abstract class DatabaseConnection extends PDO { ...@@ -730,12 +839,12 @@ abstract class DatabaseConnection extends PDO {
* @see UpdateQuery * @see UpdateQuery
*/ */
public function update($table, array $options = array()) { public function update($table, array $options = array()) {
$class = $this->getDriverClass('UpdateQuery'); $class = $this->getDriverClass('UpdateQuery', array('query.inc'));
return new $class($this, $table, $options); return new $class($this, $table, $options);
} }
/** /**
* Prepares and returns a DELETE query object with the specified ID. * Prepares and returns a DELETE query object.
* *
* @param $options * @param $options
* An array of options on the query. * An array of options on the query.
...@@ -746,7 +855,7 @@ abstract class DatabaseConnection extends PDO { ...@@ -746,7 +855,7 @@ abstract class DatabaseConnection extends PDO {
* @see DeleteQuery * @see DeleteQuery
*/ */
public function delete($table, array $options = array()) { public function delete($table, array $options = array()) {
$class = $this->getDriverClass('DeleteQuery'); $class = $this->getDriverClass('DeleteQuery', array('query.inc'));
return new $class($this, $table, $options); return new $class($this, $table, $options);
} }
...@@ -762,7 +871,7 @@ abstract class DatabaseConnection extends PDO { ...@@ -762,7 +871,7 @@ abstract class DatabaseConnection extends PDO {
* @see TruncateQuery * @see TruncateQuery
*/ */
public function truncate($table, array $options = array()) { public function truncate($table, array $options = array()) {
$class = $this->getDriverClass('TruncateQuery'); $class = $this->getDriverClass('TruncateQuery', array('query.inc'));
return new $class($this, $table, $options); return new $class($this, $table, $options);
} }
...@@ -776,7 +885,7 @@ abstract class DatabaseConnection extends PDO { ...@@ -776,7 +885,7 @@ abstract class DatabaseConnection extends PDO {
*/ */
public function schema() { public function schema() {
if (empty($this->schema)) { if (empty($this->schema)) {
$class = $this->getDriverClass('DatabaseSchema'); $class = $this->getDriverClass('DatabaseSchema', array('schema.inc'));
if (class_exists($class)) { if (class_exists($class)) {
$this->schema = new $class($this); $this->schema = new $class($this);
} }
...@@ -907,13 +1016,15 @@ abstract class DatabaseConnection extends PDO { ...@@ -907,13 +1016,15 @@ abstract class DatabaseConnection extends PDO {
throw new DatabaseTransactionNoActiveException(); throw new DatabaseTransactionNoActiveException();
} }
// A previous rollback to an earlier savepoint may mean that the savepoint // A previous rollback to an earlier savepoint may mean that the savepoint
// in question has already been rolled back. // in question has already been accidentally committed.
if (!in_array($savepoint_name, $this->transactionLayers)) { if (!isset($this->transactionLayers[$savepoint_name])) {
return; throw new DatabaseTransactionNoActiveException();
} }
// We need to find the point we're rolling back to, all other savepoints // We need to find the point we're rolling back to, all other savepoints
// before are no longer needed. // before are no longer needed. If we rolled back other active savepoints,
// we need to throw an exception.
$rolled_back_other_active_savepoints = FALSE;
while ($savepoint = array_pop($this->transactionLayers)) { while ($savepoint = array_pop($this->transactionLayers)) {
if ($savepoint == $savepoint_name) { if ($savepoint == $savepoint_name) {
// If it is the last the transaction in the stack, then it is not a // If it is the last the transaction in the stack, then it is not a
...@@ -923,10 +1034,20 @@ abstract class DatabaseConnection extends PDO { ...@@ -923,10 +1034,20 @@ abstract class DatabaseConnection extends PDO {
break; break;
} }
$this->query('ROLLBACK TO SAVEPOINT ' . $savepoint); $this->query('ROLLBACK TO SAVEPOINT ' . $savepoint);
$this->popCommittableTransactions();
if ($rolled_back_other_active_savepoints) {
throw new DatabaseTransactionOutOfOrderException();
}
return; return;
} }
else {
$rolled_back_other_active_savepoints = TRUE;
}
} }
parent::rollBack(); parent::rollBack();
if ($rolled_back_other_active_savepoints) {
throw new DatabaseTransactionOutOfOrderException();
}
} }
/** /**
...@@ -975,15 +1096,32 @@ abstract class DatabaseConnection extends PDO { ...@@ -975,15 +1096,32 @@ abstract class DatabaseConnection extends PDO {
if (!$this->supportsTransactions()) { if (!$this->supportsTransactions()) {
return; return;
} }
if (!$this->inTransaction()) { // The transaction has already been committed earlier. There is nothing we
throw new DatabaseTransactionNoActiveException(); // need to do. If this transaction was part of an earlier out-of-order
// rollback, an exception would already have been thrown by
// Database::rollback().
if (!isset($this->transactionLayers[$name])) {
return;
} }
// Commit everything since SAVEPOINT $name. // Mark this layer as committable.
while($savepoint = array_pop($this->transactionLayers)) { $this->transactionLayers[$name] = FALSE;
if ($savepoint != $name) continue; $this->popCommittableTransactions();
}
/**
* Internal function: commit all the transaction layers that can commit.
*/
protected function popCommittableTransactions() {
// Commit all the committable layers.
foreach (array_reverse($this->transactionLayers) as $name => $active) {
// Stop once we found an active transaction.
if ($active) {
break;
}
// If there are no more layers left then we should commit. // If there are no more layers left then we should commit.
unset($this->transactionLayers[$name]);
if (empty($this->transactionLayers)) { if (empty($this->transactionLayers)) {
if (!parent::commit()) { if (!parent::commit()) {
throw new DatabaseTransactionCommitFailedException(); throw new DatabaseTransactionCommitFailedException();
...@@ -991,7 +1129,6 @@ abstract class DatabaseConnection extends PDO { ...@@ -991,7 +1129,6 @@ abstract class DatabaseConnection extends PDO {
} }
else { else {
$this->query('RELEASE SAVEPOINT ' . $name); $this->query('RELEASE SAVEPOINT ' . $name);
break;
} }
} }
} }
...@@ -1097,7 +1234,7 @@ abstract class DatabaseConnection extends PDO { ...@@ -1097,7 +1234,7 @@ abstract class DatabaseConnection extends PDO {
} }
/** /**
* Returns the type of the database being accessed. * Returns the name of the PDO driver for this connection.
*/ */
abstract public function databaseType(); abstract public function databaseType();
...@@ -1532,6 +1669,7 @@ abstract class Database { ...@@ -1532,6 +1669,7 @@ abstract class Database {
require_once DRUPAL_ROOT . '/includes/database/' . $driver . '/database.inc'; require_once DRUPAL_ROOT . '/includes/database/' . $driver . '/database.inc';
$new_connection = new $driver_class(self::$databaseInfo[$key][$target]); $new_connection = new $driver_class(self::$databaseInfo[$key][$target]);
$new_connection->setTarget($target); $new_connection->setTarget($target);
$new_connection->setKey($key);
// If we have any active logging objects for this connection key, we need // If we have any active logging objects for this connection key, we need
// to associate them with the connection we just opened. // to associate them with the connection we just opened.
...@@ -1581,6 +1719,34 @@ abstract class Database { ...@@ -1581,6 +1719,34 @@ abstract class Database {
self::$ignoreTargets[$key][$target] = TRUE; self::$ignoreTargets[$key][$target] = TRUE;
} }
/**
* Load a file for the database that might hold a class.
*
* @param $driver
* The name of the driver.
* @param array $files
* The name of the files the driver specific class can be.
*/
public static function loadDriverFile($driver, array $files = array()) {
static $base_path;
if (empty($base_path)) {
$base_path = dirname(realpath(__FILE__));
}
$driver_base_path = "$base_path/$driver";
foreach ($files as $file) {
// Load the base file first so that classes extending base classes will
// have the base class loaded.
foreach (array("$base_path/$file", "$driver_base_path/$file") as $filename) {
// The OS caches file_exists() and PHP caches require_once(), so
// we'll let both of those take care of performance here.
if (file_exists($filename)) {
require_once $filename;
}
}
}
}
} }
/** /**
...@@ -1606,6 +1772,11 @@ class DatabaseTransactionCommitFailedException extends Exception { } ...@@ -1606,6 +1772,11 @@ class DatabaseTransactionCommitFailedException extends Exception { }
*/ */
class DatabaseTransactionExplicitCommitNotAllowedException extends Exception { } class DatabaseTransactionExplicitCommitNotAllowedException extends Exception { }
/**
* Exception thrown when a rollback() resulted in other active transactions being rolled-back.
*/
class DatabaseTransactionOutOfOrderException extends Exception { }
/** /**
* Exception thrown for merge queries that do not make semantic sense. * Exception thrown for merge queries that do not make semantic sense.
* *
...@@ -1701,7 +1872,7 @@ class DatabaseTransaction { ...@@ -1701,7 +1872,7 @@ class DatabaseTransaction {
public function __destruct() { public function __destruct() {
// If we rolled back then the transaction would have already been popped. // If we rolled back then the transaction would have already been popped.
if ($this->connection->inTransaction() && !$this->rolledBack) { if (!$this->rolledBack) {
$this->connection->popTransaction($this->name); $this->connection->popTransaction($this->name);
} }
} }
...@@ -1732,21 +1903,19 @@ class DatabaseTransaction { ...@@ -1732,21 +1903,19 @@ class DatabaseTransaction {
} }
/** /**
* A prepared statement. * Represents a prepared statement.
* *
* Some methods in that class are purposely commented out. Due to a change in * Some methods in that class are purposefully commented out. Due to a change in
* how PHP defines PDOStatement, we can't define a signature for those methods * how PHP defines PDOStatement, we can't define a signature for those methods
* that will work the same way between versions older than 5.2.6 and later * that will work the same way between versions older than 5.2.6 and later
* versions. * versions. See http://bugs.php.net/bug.php?id=42452 for more details.
*
* Please refer to http://bugs.php.net/bug.php?id=42452 for more details.
* *
* Child implementations should either extend PDOStatement: * Child implementations should either extend PDOStatement:
* @code * @code
* class DatabaseStatement_oracle extends PDOStatement implements DatabaseStatementInterface {} * class DatabaseStatement_oracle extends PDOStatement implements DatabaseStatementInterface {}
* @endcode * @endcode
* or implement their own class, but in that case they will also have to * or define their own class. If defining their own class, they will also have
* implement the Iterator or IteratorArray interfaces before * to implement either the Iterator or IteratorAggregate interface before
* DatabaseStatementInterface: * DatabaseStatementInterface:
* @code * @code
* class DatabaseStatement_oracle implements Iterator, DatabaseStatementInterface {} * class DatabaseStatement_oracle implements Iterator, DatabaseStatementInterface {}
...@@ -1830,7 +1999,7 @@ interface DatabaseStatementInterface extends Traversable { ...@@ -1830,7 +1999,7 @@ interface DatabaseStatementInterface extends Traversable {
* The numeric index of the field to return. Defaults to the first field. * The numeric index of the field to return. Defaults to the first field.
* *
* @return * @return
* A single field from the next record. * A single field from the next record, or FALSE if there is no next record.
*/ */
public function fetchField($index = 0); public function fetchField($index = 0);
...@@ -1850,7 +2019,7 @@ interface DatabaseStatementInterface extends Traversable { ...@@ -1850,7 +2019,7 @@ interface DatabaseStatementInterface extends Traversable {
* helper method, so one is added. * helper method, so one is added.
* *
* @return * @return
* An associative array. * An associative array, or FALSE if there is no next row.
*/ */
public function fetchAssoc(); public function fetchAssoc();
...@@ -1878,7 +2047,7 @@ interface DatabaseStatementInterface extends Traversable { ...@@ -1878,7 +2047,7 @@ interface DatabaseStatementInterface extends Traversable {
* The index of the column number to fetch. * The index of the column number to fetch.
* *
* @return * @return
* An indexed array. * An indexed array, or an empty array if there is no result set.
*/ */
public function fetchCol($index = 0); public function fetchCol($index = 0);
...@@ -1898,7 +2067,7 @@ interface DatabaseStatementInterface extends Traversable { ...@@ -1898,7 +2067,7 @@ interface DatabaseStatementInterface extends Traversable {
* The numeric index of the field to use as the array value. * The numeric index of the field to use as the array value.
* *
* @return * @return
* An associative array. * An associative array, or an empty array if there is no result set.
*/ */
public function fetchAllKeyed($key_index = 0, $value_index = 1); public function fetchAllKeyed($key_index = 0, $value_index = 1);
...@@ -1917,7 +2086,7 @@ interface DatabaseStatementInterface extends Traversable { ...@@ -1917,7 +2086,7 @@ interface DatabaseStatementInterface extends Traversable {
* set for the query will be used. * set for the query will be used.
* *
* @return * @return
* An associative array. * An associative array, or an empty array if there is no result set.
*/ */
public function fetchAllAssoc($key, $fetch = NULL); public function fetchAllAssoc($key, $fetch = NULL);
} }
...@@ -2108,82 +2277,6 @@ class DatabaseStatementEmpty implements Iterator, DatabaseStatementInterface { ...@@ -2108,82 +2277,6 @@ class DatabaseStatementEmpty implements Iterator, DatabaseStatementInterface {
} }
} }
/**
* Autoload callback for the database system.
*/
function db_autoload($class) {
static $base_path = '';
static $checked = array();
static $files = array(
'query.inc' => array(
'QueryPlaceholderInterface',
'QueryConditionInterface', 'DatabaseCondition',
'Query', 'DeleteQuery', 'InsertQuery', 'UpdateQuery', 'MergeQuery', 'TruncateQuery',
'QueryAlterableInterface',
),
'select.inc' => array('QueryAlterableInterface', 'SelectQueryInterface', 'SelectQuery', 'SelectQueryExtender'),
'database.inc' => array('DatabaseConnection'),
'log.inc' => array('DatabaseLog'),
'prefetch.inc' => array('DatabaseStatementPrefetch'),
'schema.inc' => array('DatabaseSchema'),
);
// If a class doesn't exist, it may get checked a second time
// by class_exists(). If so, just bail out now.
if (isset($checked[$class])) {
return;
}
$checked[$class] = TRUE;
if (empty($base_path)) {
$base_path = dirname(realpath(__FILE__));
}
// If there is an underscore in the class name, we know it's a
// driver-specific file so check for those. If not, it's a generic.
// Note that we use require_once here instead of require because of a
// quirk in class_exists(). By default, class_exists() will try to
// autoload a class if it's not found. However, we cannot tell
// at this point whether or not the class is going to exist, only
// the file that it would be in if it does exist. That means we may
// try to include a file that was already included by another
// autoload call, which would break. Using require_once() neatly
// avoids that issue.
if (strpos($class, '_') !== FALSE) {
list($base, $driver) = explode('_', $class);
// Drivers have an extra file, and may put their SelectQuery implementation
// in the main query file since it's so small.
$driver_files = $files;
$driver_files['query.inc'][] = 'SelectQuery';
$driver_files['install.inc'] = array('DatabaseTasks');
foreach ($driver_files as $file => $classes) {
if (in_array($base, $classes)) {
$filename = "{$base_path}/{$driver}/{$file}";
// We might end up looking in a file that doesn't exist, so check that.
if (file_exists($filename)) {
require_once $filename;
// If the class now exists, we're done. Otherwise keep searching in
// additional files.
if (class_exists($class, FALSE) || interface_exists($class, FALSE)) {
return;
}
}
}
}
}
else {
foreach ($files as $file => $classes) {
if (in_array($class, $classes)) {
require_once $base_path . '/' . $file;
return;
}
}
}
}
/** /**
* The following utility functions are simply convenience wrappers. * The following utility functions are simply convenience wrappers.
* *
...@@ -2915,4 +3008,3 @@ function db_ignore_slave() { ...@@ -2915,4 +3008,3 @@ function db_ignore_slave() {
$_SESSION['ignore_slave_server'] = REQUEST_TIME + $duration; $_SESSION['ignore_slave_server'] = REQUEST_TIME + $duration;
} }
} }
<?php <?php
// $Id: log.inc,v 1.8 2010/03/31 15:09:21 dries Exp $
/** /**
* @file * @file
......
<?php <?php
// $Id: database.inc,v 1.34 2010/10/03 01:29:41 dries Exp $
/** /**
* @file * @file
...@@ -38,14 +37,20 @@ class DatabaseConnection_mysql extends DatabaseConnection { ...@@ -38,14 +37,20 @@ class DatabaseConnection_mysql extends DatabaseConnection {
$dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . (empty($connection_options['port']) ? 3306 : $connection_options['port']); $dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . (empty($connection_options['port']) ? 3306 : $connection_options['port']);
} }
$dsn .= ';dbname=' . $connection_options['database']; $dsn .= ';dbname=' . $connection_options['database'];
parent::__construct($dsn, $connection_options['username'], $connection_options['password'], array( // Allow PDO options to be overridden.
$connection_options += array(
'pdo' => array(),
);
$connection_options['pdo'] += array(
// So we don't have to mess around with cursors and unbuffered queries by default. // So we don't have to mess around with cursors and unbuffered queries by default.
PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE, PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE,
// Because MySQL's prepared statements skip the query cache, because it's dumb. // Because MySQL's prepared statements skip the query cache, because it's dumb.
PDO::ATTR_EMULATE_PREPARES => TRUE, PDO::ATTR_EMULATE_PREPARES => TRUE,
// Force column names to lower case. // Force column names to lower case.
PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_CASE => PDO::CASE_LOWER,
)); );
parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
// Force MySQL to use the UTF-8 character set. Also set the collation, if a // Force MySQL to use the UTF-8 character set. Also set the collation, if a
// certain one has been set; otherwise, MySQL defaults to 'utf8_general_ci' // certain one has been set; otherwise, MySQL defaults to 'utf8_general_ci'
...@@ -57,12 +62,22 @@ class DatabaseConnection_mysql extends DatabaseConnection { ...@@ -57,12 +62,22 @@ class DatabaseConnection_mysql extends DatabaseConnection {
$this->exec('SET NAMES utf8'); $this->exec('SET NAMES utf8');
} }
// Force MySQL's behavior to conform more closely to SQL standards. // Set MySQL init_commands if not already defined. Default Drupal's MySQL
// This allows Drupal to run almost seamlessly on many different // behavior to conform more closely to SQL standards. This allows Drupal
// kinds of database systems. These settings force MySQL to behave // to run almost seamlessly on many different kinds of database systems.
// the same as postgresql, or sqlite in regards to syntax interpretation // These settings force MySQL to behave the same as postgresql, or sqlite
// and invalid data handling. See http://drupal.org/node/344575 for further discussion. // in regards to syntax interpretation and invalid data handling. See
$this->exec("SET sql_mode='ANSI,TRADITIONAL'"); // http://drupal.org/node/344575 for further discussion. Also, as MySQL 5.5
// changed the meaning of TRADITIONAL we need to spell out the modes one by
// one.
$connection_options += array(
'init_commands' => array(),
);
$connection_options['init_commands'] += array(
'sql_mode' => "SET sql_mode = 'ANSI,STRICT_TRANS_TABLES,STRICT_ALL_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER'",
);
// Set connection options.
$this->exec(implode('; ', $connection_options['init_commands']));
} }
public function queryRange($query, $from, $count, array $args = array(), array $options = array()) { public function queryRange($query, $from, $count, array $args = array(), array $options = array()) {
...@@ -134,6 +149,53 @@ class DatabaseConnection_mysql extends DatabaseConnection { ...@@ -134,6 +149,53 @@ class DatabaseConnection_mysql extends DatabaseConnection {
catch (PDOException $e) { catch (PDOException $e) {
} }
} }
/**
* Overridden to work around issues to MySQL not supporting transactional DDL.
*/
protected function popCommittableTransactions() {
// Commit all the committable layers.
foreach (array_reverse($this->transactionLayers) as $name => $active) {
// Stop once we found an active transaction.
if ($active) {
break;
}
// If there are no more layers left then we should commit.
unset($this->transactionLayers[$name]);
if (empty($this->transactionLayers)) {
if (!PDO::commit()) {
throw new DatabaseTransactionCommitFailedException();
}
}
else {
// Attempt to release this savepoint in the standard way.
try {
$this->query('RELEASE SAVEPOINT ' . $name);
}
catch (PDOException $e) {
// However, in MySQL (InnoDB), savepoints are automatically committed
// when tables are altered or created (DDL transactions are not
// supported). This can cause exceptions due to trying to release
// savepoints which no longer exist.
//
// To avoid exceptions when no actual error has occurred, we silently
// succeed for MySQL error code 1305 ("SAVEPOINT does not exist").
if ($e->errorInfo[1] == '1305') {
// If one SAVEPOINT was released automatically, then all were.
// Therefore, clean the transaction stack.
$this->transactionLayers = array();
// We also have to explain to PDO that the transaction stack has
// been cleaned-up.
PDO::commit();
}
else {
throw $e;
}
}
}
}
}
} }
......
<?php <?php
// $Id: install.inc,v 1.4 2010/07/30 01:59:14 dries Exp $
/** /**
* @file * @file
...@@ -10,7 +9,6 @@ ...@@ -10,7 +9,6 @@
* Specifies installation tasks for MySQL and equivalent databases. * Specifies installation tasks for MySQL and equivalent databases.
*/ */
class DatabaseTasks_mysql extends DatabaseTasks { class DatabaseTasks_mysql extends DatabaseTasks {
/** /**
* The PDO driver name for MySQL and equivalent databases. * The PDO driver name for MySQL and equivalent databases.
* *
...@@ -22,7 +20,14 @@ class DatabaseTasks_mysql extends DatabaseTasks { ...@@ -22,7 +20,14 @@ class DatabaseTasks_mysql extends DatabaseTasks {
* Returns a human-readable name string for MySQL and equivalent databases. * Returns a human-readable name string for MySQL and equivalent databases.
*/ */
public function name() { public function name() {
return 'MySQL, MariaDB, or equivalent'; return st('MySQL, MariaDB, or equivalent');
}
/**
* Returns the minimum version for MySQL.
*/
public function minimumVersion() {
return '5.0.15';
} }
} }
<?php <?php
// $Id: query.inc,v 1.18 2010/06/26 01:40:05 dries Exp $
/** /**
* @ingroup database * @ingroup database
...@@ -43,8 +42,8 @@ class InsertQuery_mysql extends InsertQuery { ...@@ -43,8 +42,8 @@ class InsertQuery_mysql extends InsertQuery {
} }
public function __toString() { public function __toString() {
// Create a comments string to prepend to the query. // Create a sanitized comment string to prepend to the query.
$comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; $comments = $this->connection->makeComment($this->comments);
// Default fields are always placed first for consistency. // Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields); $insert_fields = array_merge($this->defaultFields, $this->insertFields);
...@@ -93,8 +92,8 @@ class TruncateQuery_mysql extends TruncateQuery { ...@@ -93,8 +92,8 @@ class TruncateQuery_mysql extends TruncateQuery {
// not transactional, and result in an implicit COMMIT. When we are in a // not transactional, and result in an implicit COMMIT. When we are in a
// transaction, fallback to the slower, but transactional, DELETE. // transaction, fallback to the slower, but transactional, DELETE.
if ($this->connection->inTransaction()) { if ($this->connection->inTransaction()) {
// Create a comments string to prepend to the query. // Create a comment string to prepend to the query.
$comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; $comments = $this->connection->makeComment($this->comments);
return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '}'; return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '}';
} }
else { else {
......
<?php <?php
// $Id: schema.inc,v 1.43 2010/10/22 15:18:56 webchick Exp $
/** /**
* @file * @file
...@@ -337,7 +336,7 @@ class DatabaseSchema_mysql extends DatabaseSchema { ...@@ -337,7 +336,7 @@ class DatabaseSchema_mysql extends DatabaseSchema {
$this->connection->query($query); $this->connection->query($query);
if (isset($spec['initial'])) { if (isset($spec['initial'])) {
$this->connection->update($table) $this->connection->update($table)
->fields(array($field, $spec['initial'])) ->fields(array($field => $spec['initial']))
->execute(); ->execute();
} }
if ($fixnull) { if ($fixnull) {
......
<?php <?php
// $Id: database.inc,v 1.43 2010/10/03 01:29:41 dries Exp $
/** /**
* @file * @file
...@@ -35,11 +34,25 @@ class DatabaseConnection_pgsql extends DatabaseConnection { ...@@ -35,11 +34,25 @@ class DatabaseConnection_pgsql extends DatabaseConnection {
if (empty($connection_options['password'])) { if (empty($connection_options['password'])) {
$connection_options['password'] = NULL; $connection_options['password'] = NULL;
} }
// If the password contains a backslash it is treated as an escape character
// http://bugs.php.net/bug.php?id=53217
// so backslashes in the password need to be doubled up.
// The bug was reported against pdo_pgsql 1.0.2, backslashes in passwords
// will break on this doubling up when the bug is fixed, so check the version
//elseif (phpversion('pdo_pgsql') < 'version_this_was_fixed_in') {
else {
$connection_options['password'] = str_replace('\\', '\\\\', $connection_options['password']);
}
$this->connectionOptions = $connection_options; $this->connectionOptions = $connection_options;
$dsn = 'pgsql:host=' . $connection_options['host'] . ' dbname=' . $connection_options['database'] . ' port=' . $connection_options['port']; $dsn = 'pgsql:host=' . $connection_options['host'] . ' dbname=' . $connection_options['database'] . ' port=' . $connection_options['port'];
parent::__construct($dsn, $connection_options['username'], $connection_options['password'], array(
// Allow PDO options to be overridden.
$connection_options += array(
'pdo' => array(),
);
$connection_options['pdo'] += array(
// Prepared statements are most effective for performance when queries // Prepared statements are most effective for performance when queries
// are recycled (used several times). However, if they are not re-used, // are recycled (used several times). However, if they are not re-used,
// prepared statements become ineffecient. Since most of Drupal's // prepared statements become ineffecient. Since most of Drupal's
...@@ -51,10 +64,16 @@ class DatabaseConnection_pgsql extends DatabaseConnection { ...@@ -51,10 +64,16 @@ class DatabaseConnection_pgsql extends DatabaseConnection {
PDO::ATTR_STRINGIFY_FETCHES => TRUE, PDO::ATTR_STRINGIFY_FETCHES => TRUE,
// Force column names to lower case. // Force column names to lower case.
PDO::ATTR_CASE => PDO::CASE_LOWER, PDO::ATTR_CASE => PDO::CASE_LOWER,
)); );
parent::__construct($dsn, $connection_options['username'], $connection_options['password'], $connection_options['pdo']);
// Force PostgreSQL to use the UTF-8 character set by default. // Force PostgreSQL to use the UTF-8 character set by default.
$this->exec("SET NAMES 'UTF8'"); $this->exec("SET NAMES 'UTF8'");
// Execute PostgreSQL init_commands.
if (isset($connection_options['init_commands'])) {
$this->exec(implode('; ', $connection_options['init_commands']));
}
} }
public function query($query, array $args = array(), $options = array()) { public function query($query, array $args = array(), $options = array()) {
...@@ -137,10 +156,9 @@ class DatabaseConnection_pgsql extends DatabaseConnection { ...@@ -137,10 +156,9 @@ class DatabaseConnection_pgsql extends DatabaseConnection {
if (!isset($specials)) { if (!isset($specials)) {
$specials = array( $specials = array(
// In PostgreSQL, 'LIKE' is case-sensitive. For case-insensitive LIKE // In PostgreSQL, 'LIKE' is case-sensitive. For case-insensitive LIKE
// statements, we need to use ILIKE instead. Use backslash for escaping // statements, we need to use ILIKE instead.
// wildcard characters. 'LIKE' => array('operator' => 'ILIKE'),
'LIKE' => array('operator' => 'ILIKE', 'postfix' => ' ESCAPE ' . $this->quote("\\")), 'NOT LIKE' => array('operator' => 'NOT ILIKE'),
'NOT LIKE' => array('operator' => 'NOT ILIKE', 'postfix' => ' ESCAPE ' . $this->quote("\\")),
); );
} }
......
<?php <?php
// $Id: install.inc,v 1.10 2010/08/16 21:01:23 dries Exp $
/** /**
* @file * @file
...@@ -21,6 +20,10 @@ class DatabaseTasks_pgsql extends DatabaseTasks { ...@@ -21,6 +20,10 @@ class DatabaseTasks_pgsql extends DatabaseTasks {
'function' => 'checkPHPVersion', 'function' => 'checkPHPVersion',
'arguments' => array(), 'arguments' => array(),
); );
$this->tasks[] = array(
'function' => 'checkBinaryOutput',
'arguments' => array(),
);
$this->tasks[] = array( $this->tasks[] = array(
'function' => 'initializeDatabase', 'function' => 'initializeDatabase',
'arguments' => array(), 'arguments' => array(),
...@@ -28,7 +31,11 @@ class DatabaseTasks_pgsql extends DatabaseTasks { ...@@ -28,7 +31,11 @@ class DatabaseTasks_pgsql extends DatabaseTasks {
} }
public function name() { public function name() {
return 'PostgreSQL'; return st('PostgreSQL');
}
public function minimumVersion() {
return '8.3';
} }
/** /**
...@@ -49,7 +56,8 @@ class DatabaseTasks_pgsql extends DatabaseTasks { ...@@ -49,7 +56,8 @@ class DatabaseTasks_pgsql extends DatabaseTasks {
$text .= 'Recreate the database with %encoding encoding. See !link for more details.'; $text .= 'Recreate the database with %encoding encoding. See !link for more details.';
$this->fail(st($text, $replacements)); $this->fail(st($text, $replacements));
} }
} catch (Exception $e) { }
catch (Exception $e) {
$this->fail(st('Drupal could not determine the encoding of the database was set to UTF-8')); $this->fail(st('Drupal could not determine the encoding of the database was set to UTF-8'));
} }
} }
...@@ -71,6 +79,60 @@ class DatabaseTasks_pgsql extends DatabaseTasks { ...@@ -71,6 +79,60 @@ class DatabaseTasks_pgsql extends DatabaseTasks {
}; };
} }
/**
* Check Binary Output.
*
* Unserializing does not work on Postgresql 9 when bytea_output is 'hex'.
*/
function checkBinaryOutput() {
// PostgreSQL < 9 doesn't support bytea_output, so verify we are running
// at least PostgreSQL 9.
$database_connection = Database::getConnection();
if (version_compare($database_connection->version(), '9') >= 0) {
if (!$this->checkBinaryOutputSuccess()) {
// First try to alter the database. If it fails, raise an error telling
// the user to do it themselves.
$connection_options = $database_connection->getConnectionOptions();
// It is safe to include the database name directly here, because this
// code is only called when a connection to the database is already
// established, thus the database name is guaranteed to be a correct
// value.
$query = "ALTER DATABASE \"" . $connection_options['database'] . "\" SET bytea_output = 'escape';";
try {
db_query($query);
}
catch (Exception $e) {
// 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. * Make PostgreSQL Drupal friendly.
*/ */
......
<?php <?php
// $Id: query.inc,v 1.23 2010/09/24 21:24:13 webchick Exp $
/** /**
* @ingroup database * @ingroup database
...@@ -104,8 +103,8 @@ class InsertQuery_pgsql extends InsertQuery { ...@@ -104,8 +103,8 @@ class InsertQuery_pgsql extends InsertQuery {
} }
public function __toString() { public function __toString() {
// Create a comments string to prepend to the query. // Create a sanitized comment string to prepend to the query.
$comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; $comments = $this->connection->makeComment($this->comments);
// Default fields are always placed first for consistency. // Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields); $insert_fields = array_merge($this->defaultFields, $this->insertFields);
...@@ -208,95 +207,3 @@ class UpdateQuery_pgsql extends UpdateQuery { ...@@ -208,95 +207,3 @@ class UpdateQuery_pgsql extends UpdateQuery {
return $stmt->rowCount(); return $stmt->rowCount();
} }
} }
class SelectQuery_pgsql extends SelectQuery {
public function orderRandom() {
$alias = $this->addExpression('RANDOM()', 'random_field');
$this->orderBy($alias);
return $this;
}
/**
* Overrides SelectQuery::orderBy().
*
* PostgreSQL adheres strictly to the SQL-92 standard and requires that when
* using DISTINCT or GROUP BY conditions, fields and expressions that are
* ordered on also need to be selected. This is a best effort implementation
* to handle the cases that can be automated by adding the field if it is not
* yet selected.
*
* @code
* $query = db_select('node', 'n');
* $query->join('node_revision', 'nr', 'n.vid = nr.vid');
* $query
* ->distinct()
* ->fields('n')
* ->orderBy('timestamp');
* @endcode
*
* In this query, it is not possible (without relying on the schema) to know
* whether timestamp belongs to node_revisions and needs to be added or
* belongs to node and is already selected. Queries like this will need to be
* corrected in the original query by adding an explicit call to
* SelectQuery::addField() or SelectQuery::fields().
*
* Since this has a small performance impact, both by the additional
* processing in this function and in the database that needs to return the
* additional fields, this is done as an override instead of implementing it
* directly in SelectQuery::orderBy().
*/
public function orderBy($field, $direction = 'ASC') {
// Call parent function to order on this.
$return = parent::orderBy($field, $direction);
// If there is a table alias specified, split it up.
if (strpos($field, '.') !== FALSE) {
list($table, $table_field) = explode('.', $field);
}
// Figure out if the field has already been added.
foreach ($this->fields as $existing_field) {
if (!empty($table)) {
// If table alias is given, check if field and table exists.
if ($existing_field['table'] == $table && $existing_field['field'] == $table_field) {
return $return;
}
}
else {
// If there is no table, simply check if the field exists as a field or
// an aliased field.
if ($existing_field['alias'] == $field) {
return $return;
}
}
}
// Also check expression aliases.
foreach ($this->expressions as $expression) {
if ($expression['alias'] == $field) {
return $return;
}
}
// If a table loads all fields, it can not be added again. It would
// result in an ambigious alias error because that field would be loaded
// twice: Once through table_alias.* and once directly. If the field
// actually belongs to a different table, it must be added manually.
foreach ($this->tables as $table) {
if (!empty($table['all_fields'])) {
return $return;
}
}
// If $field contains an characters which are not allowed in a field name
// it is considered an expression, these can't be handeld automatically
// either.
if ($this->connection->escapeField($field) != $field) {
return $return;
}
// This is a case that can be handled automatically, add the field.
$this->addField(NULL, $field);
return $return;
}
}
<?php <?php
// $Id: schema.inc,v 1.39 2010/10/22 15:18:56 webchick Exp $
/** /**
* @file * @file
...@@ -74,6 +73,39 @@ class DatabaseSchema_pgsql extends DatabaseSchema { ...@@ -74,6 +73,39 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
return $this->tableInformation[$key]; return $this->tableInformation[$key];
} }
/**
* Fetch the list of CHECK constraints used on a field.
*
* We introspect the database to collect the information required by field
* alteration.
*
* @param $table
* The non-prefixed name of the table.
* @param $field
* The name of the field.
* @return
* An array of all the checks for the field.
*/
public function queryFieldInformation($table, $field) {
$prefixInfo = $this->getPrefixInfo($table, TRUE);
// 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. * Generate SQL to create a new table from a Drupal schema definition.
* *
...@@ -335,7 +367,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema { ...@@ -335,7 +367,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
$this->connection->query($query); $this->connection->query($query);
if (isset($spec['initial'])) { if (isset($spec['initial'])) {
$this->connection->update($table) $this->connection->update($table)
->fields(array($field, $spec['initial'])) ->fields(array($field => $spec['initial']))
->execute(); ->execute();
} }
if ($fixnull) { if ($fixnull) {
...@@ -469,15 +501,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema { ...@@ -469,15 +501,7 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
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))); 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)));
} }
if (!array_key_exists('size', $spec)) { $spec = $this->processField($spec);
$spec['size'] = 'normal';
}
// Map type definition to the PostgreSQL type.
if (!isset($spec['pgsql_type'])) {
$map = $this->getFieldTypeMap();
$spec['pgsql_type'] = $map[$spec['type'] . ':' . $spec['size']];
}
// We need to typecast the new column to best be able to transfer the data // We need to typecast the new column to best be able to transfer the data
// Schema_pgsql::getFieldTypeMap() will return possibilities that are not // Schema_pgsql::getFieldTypeMap() will return possibilities that are not
...@@ -489,8 +513,35 @@ class DatabaseSchema_pgsql extends DatabaseSchema { ...@@ -489,8 +513,35 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
$typecast = $spec['pgsql_type']; $typecast = $spec['pgsql_type'];
} }
if (in_array($spec['pgsql_type'], array('varchar', 'character', 'text')) && isset($spec['length'])) {
$typecast .= '(' . $spec['length'] . ')';
}
elseif (isset($spec['precision']) && isset($spec['scale'])) {
$typecast .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')';
}
// Remove old check constraints.
$field_info = $this->queryFieldInformation($table, $field);
foreach ($field_info as $check) {
$this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $check . '"');
}
// Remove old default.
$this->fieldSetNoDefault($table, $field);
$this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" TYPE ' . $typecast . ' USING "' . $field . '"::' . $typecast); $this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" TYPE ' . $typecast . ' USING "' . $field . '"::' . $typecast);
if (isset($spec['not null'])) {
if ($spec['not null']) {
$nullaction = 'SET NOT NULL';
}
else {
$nullaction = 'DROP NOT NULL';
}
$this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" ' . $nullaction);
}
if (in_array($spec['pgsql_type'], array('serial', 'bigserial'))) { if (in_array($spec['pgsql_type'], array('serial', 'bigserial'))) {
// Type "serial" is known to PostgreSQL, but *only* during table creation, // Type "serial" is known to PostgreSQL, but *only* during table creation,
// not when altering. Because of that, the sequence needs to be created // not when altering. Because of that, the sequence needs to be created
...@@ -508,6 +559,16 @@ class DatabaseSchema_pgsql extends DatabaseSchema { ...@@ -508,6 +559,16 @@ class DatabaseSchema_pgsql extends DatabaseSchema {
$this->connection->query('ALTER TABLE {' . $table . '} RENAME "' . $field . '" TO "' . $field_new . '"'); $this->connection->query('ALTER TABLE {' . $table . '} RENAME "' . $field . '" TO "' . $field_new . '"');
} }
// Add unsigned check if necessary.
if (!empty($spec['unsigned'])) {
$this->connection->query('ALTER TABLE {' . $table . '} ADD CHECK ("' . $field_new . '" >= 0)');
}
// Add default if necessary.
if (isset($spec['default'])) {
$this->fieldSetDefault($table, $field_new, $spec['default']);
}
// Change description if necessary. // Change description if necessary.
if (!empty($spec['description'])) { if (!empty($spec['description'])) {
$this->connection->query('COMMENT ON COLUMN {' . $table . '}."' . $field_new . '" IS ' . $this->prepareComment($spec['description'])); $this->connection->query('COMMENT ON COLUMN {' . $table . '}."' . $field_new . '" IS ' . $this->prepareComment($spec['description']));
......
<?php
/**
* @file
* Select builder for PostgreSQL database engine.
*/
/**
* @ingroup database
* @{
*/
class SelectQuery_pgsql extends SelectQuery {
public function orderRandom() {
$alias = $this->addExpression('RANDOM()', 'random_field');
$this->orderBy($alias);
return $this;
}
/**
* Overrides SelectQuery::orderBy().
*
* PostgreSQL adheres strictly to the SQL-92 standard and requires that when
* using DISTINCT or GROUP BY conditions, fields and expressions that are
* ordered on also need to be selected. This is a best effort implementation
* to handle the cases that can be automated by adding the field if it is not
* yet selected.
*
* @code
* $query = db_select('node', 'n');
* $query->join('node_revision', 'nr', 'n.vid = nr.vid');
* $query
* ->distinct()
* ->fields('n')
* ->orderBy('timestamp');
* @endcode
*
* In this query, it is not possible (without relying on the schema) to know
* whether timestamp belongs to node_revisions and needs to be added or
* belongs to node and is already selected. Queries like this will need to be
* corrected in the original query by adding an explicit call to
* SelectQuery::addField() or SelectQuery::fields().
*
* Since this has a small performance impact, both by the additional
* processing in this function and in the database that needs to return the
* additional fields, this is done as an override instead of implementing it
* directly in SelectQuery::orderBy().
*/
public function orderBy($field, $direction = 'ASC') {
// Call parent function to order on this.
$return = parent::orderBy($field, $direction);
// If there is a table alias specified, split it up.
if (strpos($field, '.') !== FALSE) {
list($table, $table_field) = explode('.', $field);
}
// Figure out if the field has already been added.
foreach ($this->fields as $existing_field) {
if (!empty($table)) {
// If table alias is given, check if field and table exists.
if ($existing_field['table'] == $table && $existing_field['field'] == $table_field) {
return $return;
}
}
else {
// If there is no table, simply check if the field exists as a field or
// an aliased field.
if ($existing_field['alias'] == $field) {
return $return;
}
}
}
// Also check expression aliases.
foreach ($this->expressions as $expression) {
if ($expression['alias'] == $field) {
return $return;
}
}
// If a table loads all fields, it can not be added again. It would
// result in an ambigious alias error because that field would be loaded
// twice: Once through table_alias.* and once directly. If the field
// actually belongs to a different table, it must be added manually.
foreach ($this->tables as $table) {
if (!empty($table['all_fields'])) {
return $return;
}
}
// If $field contains an characters which are not allowed in a field name
// it is considered an expression, these can't be handeld automatically
// either.
if ($this->connection->escapeField($field) != $field) {
return $return;
}
// This is a case that can be handled automatically, add the field.
$this->addField(NULL, $field);
return $return;
}
}
/**
* @} End of "ingroup database".
*/
<?php <?php
// $Id: prefetch.inc,v 1.10 2010/04/30 13:47:46 dries Exp $
/** /**
* @file * @file
...@@ -371,7 +370,7 @@ class DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface ...@@ -371,7 +370,7 @@ class DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface
} }
} }
public function fetchField($index = 0) { public function fetchColumn($index = 0) {
if (isset($this->currentRow) && isset($this->columnNames[$index])) { if (isset($this->currentRow) && isset($this->columnNames[$index])) {
// We grab the value directly from $this->data, and format it. // We grab the value directly from $this->data, and format it.
$return = $this->currentRow[$this->columnNames[$index]]; $return = $this->currentRow[$this->columnNames[$index]];
...@@ -383,6 +382,10 @@ class DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface ...@@ -383,6 +382,10 @@ class DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface
} }
} }
public function fetchField($index = 0) {
return $this->fetchColumn($index);
}
public function fetchObject($class_name = NULL, $constructor_args = array()) { public function fetchObject($class_name = NULL, $constructor_args = array()) {
if (isset($this->currentRow)) { if (isset($this->currentRow)) {
if (!isset($class_name)) { if (!isset($class_name)) {
......
<?php <?php
// $Id: query.inc,v 1.57 2010/10/18 00:50:36 dries Exp $
/** /**
* @ingroup database * @ingroup database
...@@ -17,61 +16,93 @@ ...@@ -17,61 +16,93 @@
interface QueryConditionInterface { interface QueryConditionInterface {
/** /**
* Helper function to build most common conditional clauses. * Helper function: builds the most common conditional clauses.
* *
* This method can take a variable number of parameters. If called with two * This method can take a variable number of parameters. If called with two
* parameters, they are taken as $field and $value with $operator having a value * parameters, they are taken as $field and $value with $operator having a
* of IN if $value is an array and = otherwise. * value of IN if $value is an array and = otherwise.
*
* Do not use this method to test for NULL values. Instead, use
* QueryConditionInterface::isNull() or QueryConditionInterface::isNotNull().
* *
* @param $field * @param $field
* The name of the field to check. If you would like to add a more complex * The name of the field to check. If you would like to add a more complex
* condition involving operators or functions, use where(). * condition involving operators or functions, use where().
* @param $value * @param $value
* The value to test the field against. In most cases, this is a scalar. For more * The value to test the field against. In most cases, this is a scalar.
* complex options, it is an array. The meaning of each element in the array is * For more complex options, it is an array. The meaning of each element in
* dependent on the $operator. * the array is dependent on the $operator.
* @param $operator * @param $operator
* The comparison operator, such as =, <, or >=. It also accepts more complex * The comparison operator, such as =, <, or >=. It also accepts more
* options such as IN, LIKE, or BETWEEN. Defaults to IN if $value is an array * complex options such as IN, LIKE, or BETWEEN. Defaults to IN if $value is
* = otherwise. * an array, and = otherwise.
*
* @return QueryConditionInterface * @return QueryConditionInterface
* The called object. * The called object.
*
* @see QueryConditionInterface::isNull()
* @see QueryConditionInterface::isNotNull()
*/ */
public function condition($field, $value = NULL, $operator = NULL); public function condition($field, $value = NULL, $operator = NULL);
/** /**
* Add an arbitrary WHERE clause to the query. * Adds an arbitrary WHERE clause to the query.
* *
* @param $snippet * @param $snippet
* A portion of a WHERE clause as a prepared statement. It must use named placeholders, * A portion of a WHERE clause as a prepared statement. It must use named
* not ? placeholders. * placeholders, not ? placeholders.
* @param $args * @param $args
* An associative array of arguments. * An associative array of arguments.
*
* @return QueryConditionInterface * @return QueryConditionInterface
* The called object. * The called object.
*/ */
public function where($snippet, $args = array()); public function where($snippet, $args = array());
/** /**
* Set a condition that the specified field be NULL. * Sets a condition that the specified field be NULL.
* *
* @param $field * @param $field
* The name of the field to check. * The name of the field to check.
*
* @return QueryConditionInterface * @return QueryConditionInterface
* The called object. * The called object.
*/ */
public function isNull($field); public function isNull($field);
/** /**
* Set a condition that the specified field be NOT NULL. * Sets a condition that the specified field be NOT NULL.
* *
* @param $field * @param $field
* The name of the field to check. * The name of the field to check.
*
* @return QueryConditionInterface * @return QueryConditionInterface
* The called object. * The called object.
*/ */
public function isNotNull($field); public function isNotNull($field);
/**
* Sets a condition that the specified subquery returns values.
*
* @param SelectQueryInterface $select
* The subquery that must contain results.
*
* @return QueryConditionInterface
* The called object.
*/
public function exists(SelectQueryInterface $select);
/**
* Sets a condition that the specified subquery returns no values.
*
* @param SelectQueryInterface $select
* The subquery that must not contain results.
*
* @return QueryConditionInterface
* The called object.
*/
public function notExists(SelectQueryInterface $select);
/** /**
* Gets a complete list of all conditions in this conditional clause. * Gets a complete list of all conditions in this conditional clause.
* *
...@@ -80,12 +111,13 @@ interface QueryConditionInterface { ...@@ -80,12 +111,13 @@ interface QueryConditionInterface {
* *
* The data structure that is returned is an indexed array of entries, where * The data structure that is returned is an indexed array of entries, where
* each entry looks like the following: * each entry looks like the following:
* * @code
* array( * array(
* 'field' => $field, * 'field' => $field,
* 'value' => $value, * 'value' => $value,
* 'operator' => $operator, * 'operator' => $operator,
* ); * );
* @endcode
* *
* In the special case that $operator is NULL, the $field is taken as a raw * In the special case that $operator is NULL, the $field is taken as a raw
* SQL snippet (possibly containing a function) and $value is an associative * SQL snippet (possibly containing a function) and $value is an associative
...@@ -112,11 +144,19 @@ interface QueryConditionInterface { ...@@ -112,11 +144,19 @@ interface QueryConditionInterface {
* *
* @param $connection * @param $connection
* The database connection for which to compile the conditionals. * The database connection for which to compile the conditionals.
* @param $query * @param $queryPlaceholder
* The query this condition belongs to. If not given, the current query is * The query this condition belongs to. If not given, the current query is
* used. * used.
*/ */
public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder = NULL); public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder);
/**
* Check whether a condition has been previously compiled.
*
* @return
* TRUE if the condition has been previously compiled.
*/
public function compiled();
} }
...@@ -130,12 +170,13 @@ interface QueryAlterableInterface { ...@@ -130,12 +170,13 @@ interface QueryAlterableInterface {
* *
* Tags are strings that identify a query. A query may have any number of * Tags are strings that identify a query. A query may have any number of
* tags. Tags are used to mark a query so that alter hooks may decide if they * tags. Tags are used to mark a query so that alter hooks may decide if they
* wish to take action. Tags should be all lower-case and contain only letters, * wish to take action. Tags should be all lower-case and contain only
* numbers, and underscore, and start with a letter. That is, they should * letters, numbers, and underscore, and start with a letter. That is, they
* follow the same rules as PHP identifiers in general. * should follow the same rules as PHP identifiers in general.
* *
* @param $tag * @param $tag
* The tag to add. * The tag to add.
*
* @return QueryAlterableInterface * @return QueryAlterableInterface
* The called object. * The called object.
*/ */
...@@ -146,6 +187,7 @@ interface QueryAlterableInterface { ...@@ -146,6 +187,7 @@ interface QueryAlterableInterface {
* *
* @param $tag * @param $tag
* The tag to check. * The tag to check.
*
* @return * @return
* TRUE if this query has been marked with this tag, FALSE otherwise. * TRUE if this query has been marked with this tag, FALSE otherwise.
*/ */
...@@ -156,8 +198,10 @@ interface QueryAlterableInterface { ...@@ -156,8 +198,10 @@ interface QueryAlterableInterface {
* *
* @param $tags * @param $tags
* A variable number of arguments, one for each tag to check. * A variable number of arguments, one for each tag to check.
*
* @return * @return
* TRUE if this query has been marked with all specified tags, FALSE otherwise. * TRUE if this query has been marked with all specified tags, FALSE
* otherwise.
*/ */
public function hasAllTags(); public function hasAllTags();
...@@ -166,6 +210,7 @@ interface QueryAlterableInterface { ...@@ -166,6 +210,7 @@ interface QueryAlterableInterface {
* *
* @param $tags * @param $tags
* A variable number of arguments, one for each tag to check. * A variable number of arguments, one for each tag to check.
*
* @return * @return
* TRUE if this query has been marked with at least one of the specified * TRUE if this query has been marked with at least one of the specified
* tags, FALSE otherwise. * tags, FALSE otherwise.
...@@ -184,6 +229,7 @@ interface QueryAlterableInterface { ...@@ -184,6 +229,7 @@ interface QueryAlterableInterface {
* follows the same rules as any other PHP identifier. * follows the same rules as any other PHP identifier.
* @param $object * @param $object
* The additional data to add to the query. May be any valid PHP variable. * The additional data to add to the query. May be any valid PHP variable.
*
* @return QueryAlterableInterface * @return QueryAlterableInterface
* The called object. * The called object.
*/ */
...@@ -194,6 +240,7 @@ interface QueryAlterableInterface { ...@@ -194,6 +240,7 @@ interface QueryAlterableInterface {
* *
* @param $key * @param $key
* The unique identifier for the piece of metadata to retrieve. * The unique identifier for the piece of metadata to retrieve.
*
* @return * @return
* The previously attached metadata object, or NULL if one doesn't exist. * The previously attached metadata object, or NULL if one doesn't exist.
*/ */
...@@ -205,19 +252,25 @@ interface QueryAlterableInterface { ...@@ -205,19 +252,25 @@ interface QueryAlterableInterface {
*/ */
interface QueryPlaceholderInterface { interface QueryPlaceholderInterface {
/**
* Returns a unique identifier for this object.
*/
public function uniqueIdentifier();
/** /**
* Returns the next placeholder ID for the query. * Returns the next placeholder ID for the query.
* *
* @return * @return
* The next available placeholder ID as an integer. * The next available placeholder ID as an integer.
*/ */
function nextPlaceholder(); public function nextPlaceholder();
} }
/** /**
* Base class for the query builders. * Base class for query builders.
* *
* All query builders inherit from a common base class. * Note that query builders use PHP's magic __toString() method to compile the
* query object into a prepared statement.
*/ */
abstract class Query implements QueryPlaceholderInterface { abstract class Query implements QueryPlaceholderInterface {
...@@ -228,6 +281,20 @@ abstract class Query implements QueryPlaceholderInterface { ...@@ -228,6 +281,20 @@ abstract class Query implements QueryPlaceholderInterface {
*/ */
protected $connection; protected $connection;
/**
* The target of the connection object.
*
* @var string
*/
protected $connectionTarget;
/**
* The key of the connection object.
*
* @var string
*/
protected $connectionKey;
/** /**
* The query options to pass on to the connection object. * The query options to pass on to the connection object.
* *
...@@ -235,6 +302,11 @@ abstract class Query implements QueryPlaceholderInterface { ...@@ -235,6 +302,11 @@ abstract class Query implements QueryPlaceholderInterface {
*/ */
protected $queryOptions; protected $queryOptions;
/**
* A unique identifier for this query object.
*/
protected $uniqueIdentifier;
/** /**
* The placeholder counter. * The placeholder counter.
*/ */
...@@ -247,26 +319,76 @@ abstract class Query implements QueryPlaceholderInterface { ...@@ -247,26 +319,76 @@ abstract class Query implements QueryPlaceholderInterface {
*/ */
protected $comments = array(); protected $comments = array();
/**
* Constructs a Query object.
*
* @param DatabaseConnection $connection
* Database connection object.
* @param array $options
* Array of query options.
*/
public function __construct(DatabaseConnection $connection, $options) { public function __construct(DatabaseConnection $connection, $options) {
$this->uniqueIdentifier = uniqid('', TRUE);
$this->connection = $connection; $this->connection = $connection;
$this->connectionKey = $this->connection->getKey();
$this->connectionTarget = $this->connection->getTarget();
$this->queryOptions = $options; $this->queryOptions = $options;
} }
/** /**
* Run the query against the database. * Implements the magic __sleep function to disconnect from the database.
*/
public function __sleep() {
$keys = get_object_vars($this);
unset($keys['connection']);
return array_keys($keys);
}
/**
* Implements the magic __wakeup function to reconnect to the database.
*/
public function __wakeup() {
$this->connection = Database::getConnection($this->connectionTarget, $this->connectionKey);
}
/**
* Implements the magic __clone function.
*/
public function __clone() {
$this->uniqueIdentifier = uniqid('', TRUE);
}
/**
* Runs the query against the database.
*/ */
abstract protected function execute(); abstract protected function execute();
/** /**
* __toString() magic method. * Implements PHP magic __toString method to convert the query to a string.
* *
* The toString operation is how we compile a query object to a prepared statement. * The toString operation is how we compile a query object to a prepared
* statement.
* *
* @return * @return
* A prepared statement query string for this object. * A prepared statement query string for this object.
*/ */
abstract public function __toString(); abstract public function __toString();
/**
* Returns a unique identifier for this object.
*/
public function uniqueIdentifier() {
return $this->uniqueIdentifier;
}
/**
* Gets the next placeholder value for this query object.
*
* @return int
* Next placeholder value.
*/
public function nextPlaceholder() { public function nextPlaceholder() {
return $this->nextPlaceholder++; return $this->nextPlaceholder++;
} }
...@@ -275,12 +397,16 @@ abstract class Query implements QueryPlaceholderInterface { ...@@ -275,12 +397,16 @@ abstract class Query implements QueryPlaceholderInterface {
* Adds a comment to the query. * Adds a comment to the query.
* *
* By adding a comment to a query, you can more easily find it in your * By adding a comment to a query, you can more easily find it in your
* query log or the list of active queries on an sql server. This allows * query log or the list of active queries on an SQL server. This allows
* for easier debugging and allows you to more easily find where a query * for easier debugging and allows you to more easily find where a query
* with a performance problem is being generated. * with a performance problem is being generated.
* *
* The comment string will be sanitized to remove * / and other characters
* that may terminate the string early so as to avoid SQL injection attacks.
*
* @param $comment * @param $comment
* The comment string to be inserted into the query. * The comment string to be inserted into the query.
*
* @return Query * @return Query
* The called object. * The called object.
*/ */
...@@ -297,7 +423,6 @@ abstract class Query implements QueryPlaceholderInterface { ...@@ -297,7 +423,6 @@ abstract class Query implements QueryPlaceholderInterface {
* use of comment() is preferred. * use of comment() is preferred.
* *
* Note that this method must be called by reference as well: * Note that this method must be called by reference as well:
*
* @code * @code
* $comments =& $query->getComments(); * $comments =& $query->getComments();
* @endcode * @endcode
...@@ -311,7 +436,7 @@ abstract class Query implements QueryPlaceholderInterface { ...@@ -311,7 +436,7 @@ abstract class Query implements QueryPlaceholderInterface {
} }
/** /**
* General class for an abstracted INSERT operation. * General class for an abstracted INSERT query.
*/ */
class InsertQuery extends Query { class InsertQuery extends Query {
...@@ -330,7 +455,7 @@ class InsertQuery extends Query { ...@@ -330,7 +455,7 @@ class InsertQuery extends Query {
protected $insertFields = array(); protected $insertFields = array();
/** /**
* An array of fields which should be set to their database-defined defaults. * An array of fields that should be set to their database-defined defaults.
* *
* @var array * @var array
*/ */
...@@ -339,13 +464,17 @@ class InsertQuery extends Query { ...@@ -339,13 +464,17 @@ class InsertQuery extends Query {
/** /**
* A nested array of values to insert. * A nested array of values to insert.
* *
* $insertValues itself is an array of arrays. Each sub-array is an array of * $insertValues is an array of arrays. Each sub-array is either an
* field names to values to insert. Whether multiple insert sets * associative array whose keys are field names and whose values are field
* will be run in a single query or multiple queries is left to individual drivers * values to insert, or a non-associative array of values in the same order
* to implement in whatever manner is most efficient. The order of values in each * as $insertFields.
* sub-array must match the order of fields in $insertFields.
* *
* @var string * Whether multiple insert sets will be run in a single query or multiple
* queries is left to individual drivers to implement in whatever manner is
* most appropriate. The order of values in each sub-array must match the
* order of fields in $insertFields.
*
* @var array
*/ */
protected $insertValues = array(); protected $insertValues = array();
...@@ -356,6 +485,16 @@ class InsertQuery extends Query { ...@@ -356,6 +485,16 @@ class InsertQuery extends Query {
*/ */
protected $fromQuery; protected $fromQuery;
/**
* Constructs an InsertQuery object.
*
* @param DatabaseConnection $connection
* A DatabaseConnection object.
* @param string $table
* Name of the table to associate with this query.
* @param array $options
* Array of database options.
*/
public function __construct($connection, $table, array $options = array()) { public function __construct($connection, $table, array $options = array()) {
if (!isset($options['return'])) { if (!isset($options['return'])) {
$options['return'] = Database::RETURN_INSERT_ID; $options['return'] = Database::RETURN_INSERT_ID;
...@@ -365,7 +504,7 @@ class InsertQuery extends Query { ...@@ -365,7 +504,7 @@ class InsertQuery extends Query {
} }
/** /**
* Add a set of field->value pairs to be inserted. * Adds a set of field->value pairs to be inserted.
* *
* This method may only be called once. Calling it a second time will be * This method may only be called once. Calling it a second time will be
* ignored. To queue up multiple sets of values to be inserted at once, * ignored. To queue up multiple sets of values to be inserted at once,
...@@ -380,6 +519,7 @@ class InsertQuery extends Query { ...@@ -380,6 +519,7 @@ class InsertQuery extends Query {
* @param $values * @param $values
* An array of fields to insert into the database. The values must be * An array of fields to insert into the database. The values must be
* specified in the same order as the $fields array. * specified in the same order as the $fields array.
*
* @return InsertQuery * @return InsertQuery
* The called object. * The called object.
*/ */
...@@ -401,15 +541,16 @@ class InsertQuery extends Query { ...@@ -401,15 +541,16 @@ class InsertQuery extends Query {
} }
/** /**
* Add another set of values to the query to be inserted. * Adds another set of values to the query to be inserted.
* *
* If $values is a numeric array, it will be assumed to be in the same * If $values is a numeric-keyed array, it will be assumed to be in the same
* order as the original fields() call. If it is associative, it may be * order as the original fields() call. If it is associative, it may be
* in any order as long as the keys of the array match the names of the * in any order as long as the keys of the array match the names of the
* fields. * fields.
* *
* @param $values * @param $values
* An array of values to add to the query. * An array of values to add to the query.
*
* @return InsertQuery * @return InsertQuery
* The called object. * The called object.
*/ */
...@@ -429,7 +570,7 @@ class InsertQuery extends Query { ...@@ -429,7 +570,7 @@ class InsertQuery extends Query {
} }
/** /**
* Specify fields for which the database-defaults should be used. * Specifies fields for which the database defaults should be used.
* *
* If you want to force a given field to use the database-defined default, * If you want to force a given field to use the database-defined default,
* not NULL or undefined, use this method to instruct the database to use * not NULL or undefined, use this method to instruct the database to use
...@@ -443,6 +584,7 @@ class InsertQuery extends Query { ...@@ -443,6 +584,7 @@ class InsertQuery extends Query {
* @param $fields * @param $fields
* An array of values for which to use the default values * An array of values for which to use the default values
* specified in the table definition. * specified in the table definition.
*
* @return InsertQuery * @return InsertQuery
* The called object. * The called object.
*/ */
...@@ -451,6 +593,15 @@ class InsertQuery extends Query { ...@@ -451,6 +593,15 @@ class InsertQuery extends Query {
return $this; return $this;
} }
/**
* Sets the fromQuery on this InsertQuery object.
*
* @param SelectQueryInterface $query
* The query to fetch the rows that should be inserted.
*
* @return InsertQuery
* The called object.
*/
public function from(SelectQueryInterface $query) { public function from(SelectQueryInterface $query) {
$this->fromQuery = $query; $this->fromQuery = $query;
return $this; return $this;
...@@ -466,8 +617,8 @@ class InsertQuery extends Query { ...@@ -466,8 +617,8 @@ class InsertQuery extends Query {
* return NULL. That makes it safe to use in multi-insert loops. * return NULL. That makes it safe to use in multi-insert loops.
*/ */
public function execute() { public function execute() {
// If validation fails, simply return NULL. // If validation fails, simply return NULL. Note that validation routines
// Note that validation routines in preExecute() may throw exceptions instead. // in preExecute() may throw exceptions instead.
if (!$this->preExecute()) { if (!$this->preExecute()) {
return NULL; return NULL;
} }
...@@ -508,10 +659,15 @@ class InsertQuery extends Query { ...@@ -508,10 +659,15 @@ class InsertQuery extends Query {
return $last_insert_id; return $last_insert_id;
} }
/**
* Implements PHP magic __toString method to convert the query to a string.
*
* @return string
* The prepared statement.
*/
public function __toString() { public function __toString() {
// Create a sanitized comment string to prepend to the query.
// Create a comments string to prepend to the query. $comments = $this->connection->makeComment($this->comments);
$comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';
// Default fields are always placed first for consistency. // Default fields are always placed first for consistency.
$insert_fields = array_merge($this->defaultFields, $this->insertFields); $insert_fields = array_merge($this->defaultFields, $this->insertFields);
...@@ -531,7 +687,7 @@ class InsertQuery extends Query { ...@@ -531,7 +687,7 @@ class InsertQuery extends Query {
} }
/** /**
* Generic preparation and validation for an INSERT query. * Preprocesses and validates the query.
* *
* @return * @return
* TRUE if the validation was successful, FALSE if not. * TRUE if the validation was successful, FALSE if not.
...@@ -583,13 +739,24 @@ class DeleteQuery extends Query implements QueryConditionInterface { ...@@ -583,13 +739,24 @@ class DeleteQuery extends Query implements QueryConditionInterface {
protected $table; protected $table;
/** /**
* The condition object for this query. Condition handling is handled via * The condition object for this query.
* composition. *
* Condition handling is handled via composition.
* *
* @var DatabaseCondition * @var DatabaseCondition
*/ */
protected $condition; protected $condition;
/**
* Constructs a DeleteQuery object.
*
* @param DatabaseConnection $connection
* A DatabaseConnection object.
* @param string $table
* Name of the table to associate with this query.
* @param array $options
* Array of database options.
*/
public function __construct(DatabaseConnection $connection, $table, array $options = array()) { public function __construct(DatabaseConnection $connection, $table, array $options = array()) {
$options['return'] = Database::RETURN_AFFECTED; $options['return'] = Database::RETURN_AFFECTED;
parent::__construct($connection, $options); parent::__construct($connection, $options);
...@@ -598,38 +765,88 @@ class DeleteQuery extends Query implements QueryConditionInterface { ...@@ -598,38 +765,88 @@ class DeleteQuery extends Query implements QueryConditionInterface {
$this->condition = new DatabaseCondition('AND'); $this->condition = new DatabaseCondition('AND');
} }
/**
* Implements QueryConditionInterface::condition().
*/
public function condition($field, $value = NULL, $operator = NULL) { public function condition($field, $value = NULL, $operator = NULL) {
$this->condition->condition($field, $value, $operator); $this->condition->condition($field, $value, $operator);
return $this; return $this;
} }
/**
* Implements QueryConditionInterface::isNull().
*/
public function isNull($field) { public function isNull($field) {
$this->condition->isNull($field); $this->condition->isNull($field);
return $this; return $this;
} }
/**
* Implements QueryConditionInterface::isNotNull().
*/
public function isNotNull($field) { public function isNotNull($field) {
$this->condition->isNotNull($field); $this->condition->isNotNull($field);
return $this; return $this;
} }
/**
* Implements QueryConditionInterface::exists().
*/
public function exists(SelectQueryInterface $select) {
$this->condition->exists($select);
return $this;
}
/**
* Implements QueryConditionInterface::notExists().
*/
public function notExists(SelectQueryInterface $select) {
$this->condition->notExists($select);
return $this;
}
/**
* Implements QueryConditionInterface::conditions().
*/
public function &conditions() { public function &conditions() {
return $this->condition->conditions(); return $this->condition->conditions();
} }
/**
* Implements QueryConditionInterface::arguments().
*/
public function arguments() { public function arguments() {
return $this->condition->arguments(); return $this->condition->arguments();
} }
/**
* Implements QueryConditionInterface::where().
*/
public function where($snippet, $args = array()) { public function where($snippet, $args = array()) {
$this->condition->where($snippet, $args); $this->condition->where($snippet, $args);
return $this; return $this;
} }
public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder = NULL) { /**
return $this->condition->compile($connection, isset($queryPlaceholder) ? $queryPlaceholder : $this); * Implements QueryConditionInterface::compile().
*/
public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder) {
return $this->condition->compile($connection, $queryPlaceholder);
}
/**
* Implements QueryConditionInterface::compiled().
*/
public function compiled() {
return $this->condition->compiled();
} }
/**
* Executes the DELETE query.
*
* @return
* The return value is dependent on the database connection.
*/
public function execute() { public function execute() {
$values = array(); $values = array();
if (count($this->condition)) { if (count($this->condition)) {
...@@ -640,10 +857,15 @@ class DeleteQuery extends Query implements QueryConditionInterface { ...@@ -640,10 +857,15 @@ class DeleteQuery extends Query implements QueryConditionInterface {
return $this->connection->query((string) $this, $values, $this->queryOptions); return $this->connection->query((string) $this, $values, $this->queryOptions);
} }
/**
* Implements PHP magic __toString method to convert the query to a string.
*
* @return string
* The prepared statement.
*/
public function __toString() { public function __toString() {
// Create a sanitized comment string to prepend to the query.
// Create a comments string to prepend to the query. $comments = $this->connection->makeComment($this->comments);
$comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';
$query = $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} '; $query = $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} ';
...@@ -664,29 +886,61 @@ class DeleteQuery extends Query implements QueryConditionInterface { ...@@ -664,29 +886,61 @@ class DeleteQuery extends Query implements QueryConditionInterface {
class TruncateQuery extends Query { class TruncateQuery extends Query {
/** /**
* The table from which to delete. * The table to truncate.
* *
* @var string * @var string
*/ */
protected $table; protected $table;
/**
* Constructs a TruncateQuery object.
*
* @param DatabaseConnection $connection
* A DatabaseConnection object.
* @param string $table
* Name of the table to associate with this query.
* @param array $options
* Array of database options.
*/
public function __construct(DatabaseConnection $connection, $table, array $options = array()) { public function __construct(DatabaseConnection $connection, $table, array $options = array()) {
$options['return'] = Database::RETURN_AFFECTED; $options['return'] = Database::RETURN_AFFECTED;
parent::__construct($connection, $options); parent::__construct($connection, $options);
$this->table = $table; $this->table = $table;
} }
public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder = NULL) { /**
return $this->condition->compile($connection, isset($queryPlaceholder) ? $queryPlaceholder : $this); * Implements QueryConditionInterface::compile().
*/
public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder) {
return $this->condition->compile($connection, $queryPlaceholder);
} }
/**
* Implements QueryConditionInterface::compiled().
*/
public function compiled() {
return $this->condition->compiled();
}
/**
* Executes the TRUNCATE query.
*
* @return
* Return value is dependent on the database type.
*/
public function execute() { public function execute() {
return $this->connection->query((string) $this, array(), $this->queryOptions); return $this->connection->query((string) $this, array(), $this->queryOptions);
} }
/**
* Implements PHP magic __toString method to convert the query to a string.
*
* @return string
* The prepared statement.
*/
public function __toString() { public function __toString() {
// Create a comments string to prepend to the query. // Create a sanitized comment string to prepend to the query.
$comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; $comments = $this->connection->makeComment($this->comments);
return $comments . 'TRUNCATE {' . $this->connection->escapeTable($this->table) . '} '; return $comments . 'TRUNCATE {' . $this->connection->escapeTable($this->table) . '} ';
} }
...@@ -719,26 +973,39 @@ class UpdateQuery extends Query implements QueryConditionInterface { ...@@ -719,26 +973,39 @@ class UpdateQuery extends Query implements QueryConditionInterface {
protected $arguments = array(); protected $arguments = array();
/** /**
* The condition object for this query. Condition handling is handled via * The condition object for this query.
* composition. *
* Condition handling is handled via composition.
* *
* @var DatabaseCondition * @var DatabaseCondition
*/ */
protected $condition; protected $condition;
/** /**
* An array of fields to update to an expression in case of a duplicate record. * Array of fields to update to an expression in case of a duplicate record.
* *
* This variable is a nested array in the following format: * This variable is a nested array in the following format:
* @code
* <some field> => array( * <some field> => array(
* 'condition' => <condition to execute, as a string> * 'condition' => <condition to execute, as a string>,
* 'arguments' => <array of arguments for condition, or NULL for none> * 'arguments' => <array of arguments for condition, or NULL for none>,
* ); * );
* @endcode
* *
* @var array * @var array
*/ */
protected $expressionFields = array(); protected $expressionFields = array();
/**
* Constructs an UpdateQuery object.
*
* @param DatabaseConnection $connection
* A DatabaseConnection object.
* @param string $table
* Name of the table to associate with this query.
* @param array $options
* Array of database options.
*/
public function __construct(DatabaseConnection $connection, $table, array $options = array()) { public function __construct(DatabaseConnection $connection, $table, array $options = array()) {
$options['return'] = Database::RETURN_AFFECTED; $options['return'] = Database::RETURN_AFFECTED;
parent::__construct($connection, $options); parent::__construct($connection, $options);
...@@ -747,44 +1014,89 @@ class UpdateQuery extends Query implements QueryConditionInterface { ...@@ -747,44 +1014,89 @@ class UpdateQuery extends Query implements QueryConditionInterface {
$this->condition = new DatabaseCondition('AND'); $this->condition = new DatabaseCondition('AND');
} }
/**
* Implements QueryConditionInterface::condition().
*/
public function condition($field, $value = NULL, $operator = NULL) { public function condition($field, $value = NULL, $operator = NULL) {
$this->condition->condition($field, $value, $operator); $this->condition->condition($field, $value, $operator);
return $this; return $this;
} }
/**
* Implements QueryConditionInterface::isNull().
*/
public function isNull($field) { public function isNull($field) {
$this->condition->isNull($field); $this->condition->isNull($field);
return $this; return $this;
} }
/**
* Implements QueryConditionInterface::isNotNull().
*/
public function isNotNull($field) { public function isNotNull($field) {
$this->condition->isNotNull($field); $this->condition->isNotNull($field);
return $this; return $this;
} }
/**
* Implements QueryConditionInterface::exists().
*/
public function exists(SelectQueryInterface $select) {
$this->condition->exists($select);
return $this;
}
/**
* Implements QueryConditionInterface::notExists().
*/
public function notExists(SelectQueryInterface $select) {
$this->condition->notExists($select);
return $this;
}
/**
* Implements QueryConditionInterface::conditions().
*/
public function &conditions() { public function &conditions() {
return $this->condition->conditions(); return $this->condition->conditions();
} }
/**
* Implements QueryConditionInterface::arguments().
*/
public function arguments() { public function arguments() {
return $this->condition->arguments(); return $this->condition->arguments();
} }
/**
* Implements QueryConditionInterface::where().
*/
public function where($snippet, $args = array()) { public function where($snippet, $args = array()) {
$this->condition->where($snippet, $args); $this->condition->where($snippet, $args);
return $this; return $this;
} }
public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder = NULL) { /**
return $this->condition->compile($connection, isset($queryPlaceholder) ? $queryPlaceholder : $this); * Implements QueryConditionInterface::compile().
*/
public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder) {
return $this->condition->compile($connection, $queryPlaceholder);
}
/**
* Implements QueryConditionInterface::compiled().
*/
public function compiled() {
return $this->condition->compiled();
} }
/** /**
* Add a set of field->value pairs to be updated. * Adds a set of field->value pairs to be updated.
* *
* @param $fields * @param $fields
* An associative array of fields to write into the database. The array keys * An associative array of fields to write into the database. The array keys
* are the field names while the values are the values to which to set them. * are the field names and the values are the values to which to set them.
*
* @return UpdateQuery * @return UpdateQuery
* The called object. * The called object.
*/ */
...@@ -794,7 +1106,7 @@ class UpdateQuery extends Query implements QueryConditionInterface { ...@@ -794,7 +1106,7 @@ class UpdateQuery extends Query implements QueryConditionInterface {
} }
/** /**
* Specify fields to be updated as an expression. * Specifies fields to be updated as an expression.
* *
* Expression fields are cases such as counter=counter+1. This method takes * Expression fields are cases such as counter=counter+1. This method takes
* precedence over fields(). * precedence over fields().
...@@ -807,6 +1119,7 @@ class UpdateQuery extends Query implements QueryConditionInterface { ...@@ -807,6 +1119,7 @@ class UpdateQuery extends Query implements QueryConditionInterface {
* @param $arguments * @param $arguments
* If specified, this is an array of key/value pairs for named placeholders * If specified, this is an array of key/value pairs for named placeholders
* corresponding to the expression. * corresponding to the expression.
*
* @return UpdateQuery * @return UpdateQuery
* The called object. * The called object.
*/ */
...@@ -819,6 +1132,12 @@ class UpdateQuery extends Query implements QueryConditionInterface { ...@@ -819,6 +1132,12 @@ class UpdateQuery extends Query implements QueryConditionInterface {
return $this; return $this;
} }
/**
* Executes the UPDATE query.
*
* @return
* The number of rows affected by the update.
*/
public function execute() { public function execute() {
// Expressions take priority over literal fields, so we process those first // Expressions take priority over literal fields, so we process those first
...@@ -847,10 +1166,15 @@ class UpdateQuery extends Query implements QueryConditionInterface { ...@@ -847,10 +1166,15 @@ class UpdateQuery extends Query implements QueryConditionInterface {
return $this->connection->query((string) $this, $update_values, $this->queryOptions); return $this->connection->query((string) $this, $update_values, $this->queryOptions);
} }
/**
* Implements PHP magic __toString method to convert the query to a string.
*
* @return string
* The prepared statement.
*/
public function __toString() { public function __toString() {
// Create a sanitized comment string to prepend to the query.
// Create a comments string to prepend to the query. $comments = $this->connection->makeComment($this->comments);
$comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : '';
// Expressions take priority over literal fields, so we process those first // Expressions take priority over literal fields, so we process those first
// and remove any literal fields that conflict. // and remove any literal fields that conflict.
...@@ -880,7 +1204,7 @@ class UpdateQuery extends Query implements QueryConditionInterface { ...@@ -880,7 +1204,7 @@ class UpdateQuery extends Query implements QueryConditionInterface {
} }
/** /**
* General class for an abstracted MERGE operation. * General class for an abstracted MERGE query operation.
* *
* An ANSI SQL:2003 compatible database would run the following query: * An ANSI SQL:2003 compatible database would run the following query:
* *
...@@ -970,25 +1294,37 @@ class MergeQuery extends Query implements QueryConditionInterface { ...@@ -970,25 +1294,37 @@ class MergeQuery extends Query implements QueryConditionInterface {
protected $updateFields = array(); protected $updateFields = array();
/** /**
* An array of fields to update to an expression in case of a duplicate record. * Array of fields to update to an expression in case of a duplicate record.
* *
* This variable is a nested array in the following format: * This variable is a nested array in the following format:
* @code
* <some field> => array( * <some field> => array(
* 'condition' => <condition to execute, as a string> * 'condition' => <condition to execute, as a string>,
* 'arguments' => <array of arguments for condition, or NULL for none> * 'arguments' => <array of arguments for condition, or NULL for none>,
* ); * );
* @endcode
* *
* @var array * @var array
*/ */
protected $expressionFields = array(); protected $expressionFields = array();
/** /**
* Flag indicated whether an UPDATE is necessary. * Flag indicating whether an UPDATE is necessary.
* *
* @var boolean * @var boolean
*/ */
protected $needsUpdate = FALSE; protected $needsUpdate = FALSE;
/**
* Constructs a MergeQuery object.
*
* @param DatabaseConnection $connection
* A DatabaseConnection object.
* @param string $table
* Name of the table to associate with this query.
* @param array $options
* Array of database options.
*/
public function __construct(DatabaseConnection $connection, $table, array $options = array()) { public function __construct(DatabaseConnection $connection, $table, array $options = array()) {
$options['return'] = Database::RETURN_AFFECTED; $options['return'] = Database::RETURN_AFFECTED;
parent::__construct($connection, $options); parent::__construct($connection, $options);
...@@ -998,7 +1334,7 @@ class MergeQuery extends Query implements QueryConditionInterface { ...@@ -998,7 +1334,7 @@ class MergeQuery extends Query implements QueryConditionInterface {
} }
/** /**
* Set the table or subquery to be used for the condition. * Sets the table or subquery to be used for the condition.
* *
* @param $table * @param $table
* The table name or the subquery to be used. Use a SelectQuery object to * The table name or the subquery to be used. Use a SelectQuery object to
...@@ -1017,7 +1353,7 @@ class MergeQuery extends Query implements QueryConditionInterface { ...@@ -1017,7 +1353,7 @@ class MergeQuery extends Query implements QueryConditionInterface {
* *
* @param $fields * @param $fields
* An associative array of fields to write into the database. The array keys * An associative array of fields to write into the database. The array keys
* are the field names while the values are the values to which to set them. * are the field names and the values are the values to which to set them.
* *
* @return MergeQuery * @return MergeQuery
* The called object. * The called object.
...@@ -1029,7 +1365,7 @@ class MergeQuery extends Query implements QueryConditionInterface { ...@@ -1029,7 +1365,7 @@ class MergeQuery extends Query implements QueryConditionInterface {
} }
/** /**
* Specify fields to be updated as an expression. * Specifies fields to be updated as an expression.
* *
* Expression fields are cases such as counter = counter + 1. This method * Expression fields are cases such as counter = counter + 1. This method
* takes precedence over MergeQuery::updateFields() and it's wrappers, * takes precedence over MergeQuery::updateFields() and it's wrappers,
...@@ -1043,6 +1379,7 @@ class MergeQuery extends Query implements QueryConditionInterface { ...@@ -1043,6 +1379,7 @@ class MergeQuery extends Query implements QueryConditionInterface {
* @param $arguments * @param $arguments
* If specified, this is an array of key/value pairs for named placeholders * If specified, this is an array of key/value pairs for named placeholders
* corresponding to the expression. * corresponding to the expression.
*
* @return MergeQuery * @return MergeQuery
* The called object. * The called object.
*/ */
...@@ -1104,7 +1441,7 @@ class MergeQuery extends Query implements QueryConditionInterface { ...@@ -1104,7 +1441,7 @@ class MergeQuery extends Query implements QueryConditionInterface {
} }
/** /**
* Set common field-value pairs in the INSERT and UPDATE query parts. * Sets common field-value pairs in the INSERT and UPDATE query parts.
* *
* This method should only be called once. It may be called either * This method should only be called once. It may be called either
* with a single associative array or two indexed arrays. If called * with a single associative array or two indexed arrays. If called
...@@ -1114,11 +1451,11 @@ class MergeQuery extends Query implements QueryConditionInterface { ...@@ -1114,11 +1451,11 @@ class MergeQuery extends Query implements QueryConditionInterface {
* and the second array is taken as the corresponding values. * and the second array is taken as the corresponding values.
* *
* @param $fields * @param $fields
* An associative array of fields on which to insert. The keys of the * An array of fields to insert, or an associative array of fields and
* array are taken to be the fields and the values are taken to be * values. The keys of the array are taken to be the fields and the values
* corresponding values to insert. * are taken to be corresponding values to insert.
* @param $values * @param $values
* An array of fields to set into the database. The values must be * An array of values to set into the database. The values must be
* specified in the same order as the $fields array. * specified in the same order as the $fields array.
* *
* @return MergeQuery * @return MergeQuery
...@@ -1137,7 +1474,7 @@ class MergeQuery extends Query implements QueryConditionInterface { ...@@ -1137,7 +1474,7 @@ class MergeQuery extends Query implements QueryConditionInterface {
} }
/** /**
* Set the key field(s) to be used as conditions for this query. * Sets the key field(s) to be used as conditions for this query.
* *
* This method should only be called once. It may be called either * This method should only be called once. It may be called either
* with a single associative array or two indexed arrays. If called * with a single associative array or two indexed arrays. If called
...@@ -1150,10 +1487,11 @@ class MergeQuery extends Query implements QueryConditionInterface { ...@@ -1150,10 +1487,11 @@ class MergeQuery extends Query implements QueryConditionInterface {
* If no other method is called, the UPDATE will become a no-op. * If no other method is called, the UPDATE will become a no-op.
* *
* @param $fields * @param $fields
* An array of fields to set. * An array of fields to set, or an associative array of fields and values.
* @param $values * @param $values
* An array of fields to set into the database. The values must be * An array of values to set into the database. The values must be
* specified in the same order as the $fields array. * specified in the same order as the $fields array.
*
* @return MergeQuery * @return MergeQuery
* The called object. * The called object.
*/ */
...@@ -1168,38 +1506,91 @@ class MergeQuery extends Query implements QueryConditionInterface { ...@@ -1168,38 +1506,91 @@ class MergeQuery extends Query implements QueryConditionInterface {
return $this; return $this;
} }
/**
* Implements QueryConditionInterface::condition().
*/
public function condition($field, $value = NULL, $operator = NULL) { public function condition($field, $value = NULL, $operator = NULL) {
$this->condition->condition($field, $value, $operator); $this->condition->condition($field, $value, $operator);
return $this; return $this;
} }
/**
* Implements QueryConditionInterface::isNull().
*/
public function isNull($field) { public function isNull($field) {
$this->condition->isNull($field); $this->condition->isNull($field);
return $this; return $this;
} }
/**
* Implements QueryConditionInterface::isNotNull().
*/
public function isNotNull($field) { public function isNotNull($field) {
$this->condition->isNotNull($field); $this->condition->isNotNull($field);
return $this; return $this;
} }
/**
* Implements QueryConditionInterface::exists().
*/
public function exists(SelectQueryInterface $select) {
$this->condition->exists($select);
return $this;
}
/**
* Implements QueryConditionInterface::notExists().
*/
public function notExists(SelectQueryInterface $select) {
$this->condition->notExists($select);
return $this;
}
/**
* Implements QueryConditionInterface::conditions().
*/
public function &conditions() { public function &conditions() {
return $this->condition->conditions(); return $this->condition->conditions();
} }
/**
* Implements QueryConditionInterface::arguments().
*/
public function arguments() { public function arguments() {
return $this->condition->arguments(); return $this->condition->arguments();
} }
/**
* Implements QueryConditionInterface::where().
*/
public function where($snippet, $args = array()) { public function where($snippet, $args = array()) {
$this->condition->where($snippet, $args); $this->condition->where($snippet, $args);
return $this; return $this;
} }
public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder = NULL) { /**
return $this->condition->compile($connection, isset($queryPlaceholder) ? $queryPlaceholder : $this); * Implements QueryConditionInterface::compile().
*/
public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder) {
return $this->condition->compile($connection, $queryPlaceholder);
}
/**
* Implements QueryConditionInterface::compiled().
*/
public function compiled() {
return $this->condition->compiled();
} }
/**
* Implements PHP magic __toString method to convert the query to a string.
*
* In the degenerate case, there is no string-able query as this operation
* is potentially two queries.
*
* @return string
* The prepared query statement.
*/
public function __toString() { public function __toString() {
} }
...@@ -1261,25 +1652,59 @@ class MergeQuery extends Query implements QueryConditionInterface { ...@@ -1261,25 +1652,59 @@ class MergeQuery extends Query implements QueryConditionInterface {
*/ */
class DatabaseCondition implements QueryConditionInterface, Countable { class DatabaseCondition implements QueryConditionInterface, Countable {
/**
* Array of conditions.
*
* @var array
*/
protected $conditions = array(); protected $conditions = array();
/**
* Array of arguments.
*
* @var array
*/
protected $arguments = array(); protected $arguments = array();
/**
* Whether the conditions have been changed.
*
* TRUE if the condition has been changed since the last compile.
* FALSE if the condition has been compiled and not changed.
*
* @var bool
*/
protected $changed = TRUE; protected $changed = TRUE;
/**
* The identifier of the query placeholder this condition has been compiled against.
*/
protected $queryPlaceholderIdentifier;
/**
* Constructs a DataBaseCondition object.
*
* @param string $conjunction
* The operator to use to combine conditions: 'AND' or 'OR'.
*/
public function __construct($conjunction) { public function __construct($conjunction) {
$this->conditions['#conjunction'] = $conjunction; $this->conditions['#conjunction'] = $conjunction;
} }
/** /**
* Return the size of this conditional. This is part of the Countable interface. * Implements Countable::count().
* *
* The size of the conditional is the size of its conditional array minus * Returns the size of this conditional. The size of the conditional is the
* one, because one element is the the conjunction. * size of its conditional array minus one, because one element is the the
* conjunction.
*/ */
public function count() { public function count() {
return count($this->conditions) - 1; return count($this->conditions) - 1;
} }
/**
* Implements QueryConditionInterface::condition().
*/
public function condition($field, $value = NULL, $operator = NULL) { public function condition($field, $value = NULL, $operator = NULL) {
if (!isset($operator)) { if (!isset($operator)) {
if (is_array($value)) { if (is_array($value)) {
...@@ -1303,6 +1728,9 @@ class DatabaseCondition implements QueryConditionInterface, Countable { ...@@ -1303,6 +1728,9 @@ class DatabaseCondition implements QueryConditionInterface, Countable {
return $this; return $this;
} }
/**
* Implements QueryConditionInterface::where().
*/
public function where($snippet, $args = array()) { public function where($snippet, $args = array()) {
$this->conditions[] = array( $this->conditions[] = array(
'field' => $snippet, 'field' => $snippet,
...@@ -1314,18 +1742,44 @@ class DatabaseCondition implements QueryConditionInterface, Countable { ...@@ -1314,18 +1742,44 @@ class DatabaseCondition implements QueryConditionInterface, Countable {
return $this; return $this;
} }
/**
* Implements QueryConditionInterface::isNull().
*/
public function isNull($field) { public function isNull($field) {
return $this->condition($field); return $this->condition($field);
} }
/**
* Implements QueryConditionInterface::isNotNull().
*/
public function isNotNull($field) { public function isNotNull($field) {
return $this->condition($field, NULL, 'IS NOT NULL'); return $this->condition($field, NULL, 'IS NOT NULL');
} }
/**
* Implements QueryConditionInterface::exists().
*/
public function exists(SelectQueryInterface $select) {
return $this->condition('', $select, 'EXISTS');
}
/**
* Implements QueryConditionInterface::notExists().
*/
public function notExists(SelectQueryInterface $select) {
return $this->condition('', $select, 'NOT EXISTS');
}
/**
* Implements QueryConditionInterface::conditions().
*/
public function &conditions() { public function &conditions() {
return $this->conditions; return $this->conditions;
} }
/**
* Implements QueryConditionInterface::arguments().
*/
public function arguments() { public function arguments() {
// If the caller forgot to call compile() first, refuse to run. // If the caller forgot to call compile() first, refuse to run.
if ($this->changed) { if ($this->changed) {
...@@ -1334,8 +1788,15 @@ class DatabaseCondition implements QueryConditionInterface, Countable { ...@@ -1334,8 +1788,15 @@ class DatabaseCondition implements QueryConditionInterface, Countable {
return $this->arguments; return $this->arguments;
} }
public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder = NULL) { /**
if ($this->changed) { * Implements QueryConditionInterface::compile().
*/
public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder) {
// Re-compile if this condition changed or if we are compiled against a
// different query placeholder object.
if ($this->changed || isset($this->queryPlaceholderIdentifier) && ($this->queryPlaceholderIdentifier != $queryPlaceholder->uniqueIdentifier())) {
$this->queryPlaceholderIdentifier = $queryPlaceholder->uniqueIdentifier();
$condition_fragments = array(); $condition_fragments = array();
$arguments = array(); $arguments = array();
...@@ -1407,6 +1868,19 @@ class DatabaseCondition implements QueryConditionInterface, Countable { ...@@ -1407,6 +1868,19 @@ class DatabaseCondition implements QueryConditionInterface, Countable {
} }
} }
/**
* Implements QueryConditionInterface::compiled().
*/
public function compiled() {
return !$this->changed;
}
/**
* Implements PHP magic __toString method to convert the conditions to string.
*
* @return string
* A string version of the conditions.
*/
public function __toString() { public function __toString() {
// If the caller forgot to call compile() first, refuse to run. // If the caller forgot to call compile() first, refuse to run.
if ($this->changed) { if ($this->changed) {
...@@ -1415,6 +1889,12 @@ class DatabaseCondition implements QueryConditionInterface, Countable { ...@@ -1415,6 +1889,12 @@ class DatabaseCondition implements QueryConditionInterface, Countable {
return $this->stringVersion; return $this->stringVersion;
} }
/**
* PHP magic __clone() method.
*
* Only copies fields that implement QueryConditionInterface. Also sets
* $this->changed to TRUE.
*/
function __clone() { function __clone() {
$this->changed = TRUE; $this->changed = TRUE;
foreach ($this->conditions as $key => $condition) { foreach ($this->conditions as $key => $condition) {
...@@ -1433,6 +1913,7 @@ class DatabaseCondition implements QueryConditionInterface, Countable { ...@@ -1433,6 +1913,7 @@ class DatabaseCondition implements QueryConditionInterface, Countable {
* *
* @param $operator * @param $operator
* The condition operator, such as "IN", "BETWEEN", etc. Case-sensitive. * The condition operator, such as "IN", "BETWEEN", etc. Case-sensitive.
*
* @return * @return
* The extra handling directives for the specified operator, or NULL. * The extra handling directives for the specified operator, or NULL.
*/ */
...@@ -1442,6 +1923,8 @@ class DatabaseCondition implements QueryConditionInterface, Countable { ...@@ -1442,6 +1923,8 @@ class DatabaseCondition implements QueryConditionInterface, Countable {
'BETWEEN' => array('delimiter' => ' AND '), 'BETWEEN' => array('delimiter' => ' AND '),
'IN' => array('delimiter' => ', ', 'prefix' => ' (', 'postfix' => ')'), 'IN' => array('delimiter' => ', ', 'prefix' => ' (', 'postfix' => ')'),
'NOT IN' => array('delimiter' => ', ', 'prefix' => ' (', 'postfix' => ')'), 'NOT IN' => array('delimiter' => ', ', 'prefix' => ' (', 'postfix' => ')'),
'EXISTS' => array('prefix' => ' (', 'postfix' => ')'),
'NOT EXISTS' => array('prefix' => ' (', 'postfix' => ')'),
'IS NULL' => array('use_value' => FALSE), 'IS NULL' => array('use_value' => FALSE),
'IS NOT NULL' => array('use_value' => FALSE), 'IS NOT NULL' => array('use_value' => FALSE),
// Use backslash for escaping wildcard characters. // Use backslash for escaping wildcard characters.
......
<?php <?php
// $Id: schema.inc,v 1.39 2010/08/22 13:55:53 dries Exp $
/** /**
* @file * @file
* Generic Database schema code. * Generic Database schema code.
*/ */
require_once dirname(__FILE__) . '/query.inc';
/** /**
* @defgroup schemaapi Schema API * @defgroup schemaapi Schema API
* @{ * @{
* API to handle database schemas.
* *
* A Drupal schema definition is an array structure representing one or * A Drupal schema definition is an array structure representing one or
* more tables and their related keys and indexes. A schema is defined by * more tables and their related keys and indexes. A schema is defined by
...@@ -38,10 +40,16 @@ ...@@ -38,10 +40,16 @@
* description might contain "Always holds the largest (most * description might contain "Always holds the largest (most
* recent) {node_revision}.vid value for this nid." * recent) {node_revision}.vid value for this nid."
* - 'type': The generic datatype: 'char', 'varchar', 'text', 'blob', 'int', * - 'type': The generic datatype: 'char', 'varchar', 'text', 'blob', 'int',
* 'float', 'numeric', 'serial', 'date', 'datetime' or 'time'. Most types * 'float', 'numeric', or 'serial'. Most types just map to the according
* types just map to the according database engine specific datatypes. Use * database engine specific datatypes. Use 'serial' for auto incrementing
* 'serial' for auto incrementing fields. This will expand to 'INT * fields. This will expand to 'INT auto_increment' on MySQL.
* auto_increment' on MySQL. * - 'mysql_type', 'pgsql_type', 'sqlite_type', etc.: If you need to
* use a record type not included in the officially supported list
* of types above, you can specify a type for each database
* backend. In this case, you can leave out the type parameter,
* but be advised that your schema will fail to load on backends that
* do not have a type specified. A possible solution can be to
* use the "text" type as a fallback.
* - 'serialize': A boolean indicating whether the field will be stored as * - 'serialize': A boolean indicating whether the field will be stored as
* a serialized string. * a serialized string.
* - 'size': The data size: 'tiny', 'small', 'medium', 'normal', * - 'size': The data size: 'tiny', 'small', 'medium', 'normal',
...@@ -168,10 +176,33 @@ abstract class DatabaseSchema implements QueryPlaceholderInterface { ...@@ -168,10 +176,33 @@ abstract class DatabaseSchema implements QueryPlaceholderInterface {
*/ */
protected $defaultSchema = 'public'; protected $defaultSchema = 'public';
/**
* A unique identifier for this query object.
*/
protected $uniqueIdentifier;
public function __construct($connection) { public function __construct($connection) {
$this->uniqueIdentifier = uniqid('', TRUE);
$this->connection = $connection; $this->connection = $connection;
} }
/**
* Implements the magic __clone function.
*/
public function __clone() {
$this->uniqueIdentifier = uniqid('', TRUE);
}
/**
* Implements QueryPlaceHolderInterface::uniqueIdentifier().
*/
public function uniqueIdentifier() {
return $this->uniqueIdentifier;
}
/**
* Implements QueryPlaceHolderInterface::nextPlaceholder().
*/
public function nextPlaceholder() { public function nextPlaceholder() {
return $this->placeholder++; return $this->placeholder++;
} }
......
<?php <?php
// $Id: select.inc,v 1.52 2010/10/05 01:42:24 dries Exp $
/** /**
* @ingroup database * @ingroup database
* @{ * @{
*/ */
require_once dirname(__FILE__) . '/query.inc';
/** /**
* Interface for extendable query objects. * Interface for extendable query objects.
* *
...@@ -413,7 +414,7 @@ interface SelectQueryInterface extends QueryConditionInterface, QueryAlterableIn ...@@ -413,7 +414,7 @@ interface SelectQueryInterface extends QueryConditionInterface, QueryAlterableIn
* @param $start * @param $start
* The first record from the result set to return. If NULL, removes any * The first record from the result set to return. If NULL, removes any
* range directives that are set. * range directives that are set.
* @param $limit * @param $length
* The number of records to return from the result set. * The number of records to return from the result set.
* @return SelectQueryInterface * @return SelectQueryInterface
* The called object. * The called object.
...@@ -547,18 +548,32 @@ class SelectQueryExtender implements SelectQueryInterface { ...@@ -547,18 +548,32 @@ class SelectQueryExtender implements SelectQueryInterface {
*/ */
protected $connection; protected $connection;
/**
* A unique identifier for this query object.
*/
protected $uniqueIdentifier;
/** /**
* The placeholder counter. * The placeholder counter.
*/ */
protected $placeholder = 0; protected $placeholder = 0;
public function __construct(SelectQueryInterface $query, DatabaseConnection $connection) { public function __construct(SelectQueryInterface $query, DatabaseConnection $connection) {
$this->uniqueIdentifier = uniqid('', TRUE);
$this->query = $query; $this->query = $query;
$this->connection = $connection; $this->connection = $connection;
} }
/* Implementations of QueryPlaceholderInterface. */ /**
* Implements QueryPlaceholderInterface::uniqueIdentifier().
*/
public function uniqueIdentifier() {
return $this->uniqueIdentifier;
}
/**
* Implements QueryPlaceholderInterface::nextPlaceholder().
*/
public function nextPlaceholder() { public function nextPlaceholder() {
return $this->placeholder++; return $this->placeholder++;
} }
...@@ -575,11 +590,13 @@ class SelectQueryExtender implements SelectQueryInterface { ...@@ -575,11 +590,13 @@ class SelectQueryExtender implements SelectQueryInterface {
} }
public function hasAllTags() { public function hasAllTags() {
return call_user_func_array(array($this->query, 'hasAllTags'), func_get_args()); $args = func_get_args();
return call_user_func_array(array($this->query, 'hasAllTags'), $args);
} }
public function hasAnyTag() { public function hasAnyTag() {
return call_user_func_array(array($this->query, 'hasAnyTags'), func_get_args()); $args = func_get_args();
return call_user_func_array(array($this->query, 'hasAnyTags'), $args);
} }
public function addMetaData($key, $object) { public function addMetaData($key, $object) {
...@@ -611,23 +628,27 @@ class SelectQueryExtender implements SelectQueryInterface { ...@@ -611,23 +628,27 @@ class SelectQueryExtender implements SelectQueryInterface {
return $this; return $this;
} }
public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder = NULL) { public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder) {
return $this->condition->compile($connection, isset($queryPlaceholder) ? $queryPlaceholder : $this); return $this->query->compile($connection, $queryPlaceholder);
}
public function compiled() {
return $this->query->compiled();
} }
/* Implementations of QueryConditionInterface for the HAVING clause. */ /* Implementations of QueryConditionInterface for the HAVING clause. */
public function havingCondition($field, $value = NULL, $operator = '=') { public function havingCondition($field, $value = NULL, $operator = '=') {
$this->query->condition($field, $value, $operator, $num_args); $this->query->havingCondition($field, $value, $operator);
return $this; return $this;
} }
public function &havingConditions() { public function &havingConditions() {
return $this->having->conditions(); return $this->query->havingConditions();
} }
public function havingArguments() { public function havingArguments() {
return $this->having->arguments(); return $this->query->havingArguments();
} }
public function having($snippet, $args = array()) { public function having($snippet, $args = array()) {
...@@ -642,7 +663,9 @@ class SelectQueryExtender implements SelectQueryInterface { ...@@ -642,7 +663,9 @@ class SelectQueryExtender implements SelectQueryInterface {
/* Implementations of QueryExtendableInterface. */ /* Implementations of QueryExtendableInterface. */
public function extend($extender_name) { public function extend($extender_name) {
$class = $this->connection->getDriverClass($extender_name); // The extender can be anywhere so this needs to go to the registry, which
// is surely loaded by now.
$class = $this->connection->getDriverClass($extender_name, array(), TRUE);
return new $class($this, $this->connection); return new $class($this, $this->connection);
} }
...@@ -769,31 +792,7 @@ class SelectQueryExtender implements SelectQueryInterface { ...@@ -769,31 +792,7 @@ class SelectQueryExtender implements SelectQueryInterface {
} }
public function countQuery() { public function countQuery() {
// Create our new query object that we will mutate into a count query. return $this->query->countQuery();
$count = clone($this);
// Zero-out existing fields and expressions.
$fields =& $count->getFields();
$fields = array();
$expressions =& $count->getExpressions();
$expressions = array();
// Also remove 'all_fields' statements, which are expanded into tablename.*
// when the query is executed.
$tables = &$count->getTables();
foreach ($tables as $alias => &$table) {
unset($table['all_fields']);
}
// Ordering a count query is a waste of cycles, and breaks on some
// databases anyway.
$orders = &$count->getOrderBy();
$orders = array();
// COUNT() is an expression, so we add that back in.
$count->addExpression('COUNT(*)');
return $count;
} }
function isNull($field) { function isNull($field) {
...@@ -806,11 +805,23 @@ class SelectQueryExtender implements SelectQueryInterface { ...@@ -806,11 +805,23 @@ class SelectQueryExtender implements SelectQueryInterface {
return $this; return $this;
} }
public function exists(SelectQueryInterface $select) {
$this->query->exists($select);
return $this;
}
public function notExists(SelectQueryInterface $select) {
$this->query->notExists($select);
return $this;
}
public function __toString() { public function __toString() {
return (string) $this->query; return (string) $this->query;
} }
public function __clone() { public function __clone() {
$this->uniqueIdentifier = uniqid('', TRUE);
// We need to deep-clone the query we're wrapping, which in turn may // We need to deep-clone the query we're wrapping, which in turn may
// deep-clone other objects. Exciting! // deep-clone other objects. Exciting!
$this->query = clone($this->query); $this->query = clone($this->query);
...@@ -972,11 +983,13 @@ class SelectQuery extends Query implements SelectQueryInterface { ...@@ -972,11 +983,13 @@ class SelectQuery extends Query implements SelectQueryInterface {
} }
public function hasAllTags() { public function hasAllTags() {
return !(boolean)array_diff(func_get_args(), array_keys($this->alterTags)); $args = func_get_args();
return !(boolean)array_diff($args, array_keys($this->alterTags));
} }
public function hasAnyTag() { public function hasAnyTag() {
return (boolean)array_intersect(func_get_args(), array_keys($this->alterTags)); $args = func_get_args();
return (boolean)array_intersect($args, array_keys($this->alterTags));
} }
public function addMetaData($key, $object) { public function addMetaData($key, $object) {
...@@ -1000,7 +1013,35 @@ class SelectQuery extends Query implements SelectQueryInterface { ...@@ -1000,7 +1013,35 @@ class SelectQuery extends Query implements SelectQueryInterface {
} }
public function arguments() { public function arguments() {
return $this->where->arguments(); if (!$this->compiled()) {
return NULL;
}
$args = $this->where->arguments() + $this->having->arguments();
foreach ($this->tables as $table) {
if ($table['arguments']) {
$args += $table['arguments'];
}
// If this table is a subquery, grab its arguments recursively.
if ($table['table'] instanceof SelectQueryInterface) {
$args += $table['table']->arguments();
}
}
foreach ($this->expressions as $expression) {
if ($expression['arguments']) {
$args += $expression['arguments'];
}
}
// If there are any dependent queries to UNION,
// incorporate their arguments recursively.
foreach ($this->union as $union) {
$args += $union['query']->arguments();
}
return $args;
} }
public function where($snippet, $args = array()) { public function where($snippet, $args = array()) {
...@@ -1018,8 +1059,54 @@ class SelectQuery extends Query implements SelectQueryInterface { ...@@ -1018,8 +1059,54 @@ class SelectQuery extends Query implements SelectQueryInterface {
return $this; return $this;
} }
public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder = NULL) { public function exists(SelectQueryInterface $select) {
return $this->where->compile($connection, isset($queryPlaceholder) ? $queryPlaceholder : $this); $this->where->exists($select);
return $this;
}
public function notExists(SelectQueryInterface $select) {
$this->where->notExists($select);
return $this;
}
public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder) {
$this->where->compile($connection, $queryPlaceholder);
$this->having->compile($connection, $queryPlaceholder);
foreach ($this->tables as $table) {
// If this table is a subquery, compile it recursively.
if ($table['table'] instanceof SelectQueryInterface) {
$table['table']->compile($connection, $queryPlaceholder);
}
}
// If there are any dependent queries to UNION, compile it recursively.
foreach ($this->union as $union) {
$union['query']->compile($connection, $queryPlaceholder);
}
}
public function compiled() {
if (!$this->where->compiled() || !$this->having->compiled()) {
return FALSE;
}
foreach ($this->tables as $table) {
// If this table is a subquery, check its status recursively.
if ($table['table'] instanceof SelectQueryInterface) {
if (!$table['table']->compiled()) {
return FALSE;
}
}
}
foreach ($this->union as $union) {
if (!$union['query']->compiled()) {
return FALSE;
}
}
return TRUE;
} }
/* Implementations of QueryConditionInterface for the HAVING clause. */ /* Implementations of QueryConditionInterface for the HAVING clause. */
...@@ -1066,6 +1153,16 @@ class SelectQuery extends Query implements SelectQueryInterface { ...@@ -1066,6 +1153,16 @@ class SelectQuery extends Query implements SelectQueryInterface {
return $this; return $this;
} }
public function havingExists(SelectQueryInterface $select) {
$this->having->exists($select);
return $this;
}
public function havingNotExists(SelectQueryInterface $select) {
$this->having->notExists($select);
return $this;
}
public function forUpdate($set = TRUE) { public function forUpdate($set = TRUE) {
if (isset($set)) { if (isset($set)) {
$this->forUpdate = $set; $this->forUpdate = $set;
...@@ -1103,33 +1200,8 @@ class SelectQuery extends Query implements SelectQueryInterface { ...@@ -1103,33 +1200,8 @@ class SelectQuery extends Query implements SelectQueryInterface {
if (!isset($queryPlaceholder)) { if (!isset($queryPlaceholder)) {
$queryPlaceholder = $this; $queryPlaceholder = $this;
} }
$this->where->compile($this->connection, $queryPlaceholder); $this->compile($this->connection, $queryPlaceholder);
$this->having->compile($this->connection, $queryPlaceholder); return $this->arguments();
$args = $this->where->arguments() + $this->having->arguments();
foreach ($this->tables as $table) {
if ($table['arguments']) {
$args += $table['arguments'];
}
// If this table is a subquery, grab its arguments recursively.
if ($table['table'] instanceof SelectQueryInterface) {
$args += $table['table']->getArguments($queryPlaceholder);
}
}
foreach ($this->expressions as $expression) {
if ($expression['arguments']) {
$args += $expression['arguments'];
}
}
// If there are any dependent queries to UNION,
// incorporate their arguments recursively.
foreach ($this->union as $union) {
$args += $union['query']->getArguments($queryPlaceholder);
}
return $args;
} }
/** /**
...@@ -1350,7 +1422,7 @@ class SelectQuery extends Query implements SelectQueryInterface { ...@@ -1350,7 +1422,7 @@ class SelectQuery extends Query implements SelectQueryInterface {
} }
public function groupBy($field) { public function groupBy($field) {
$this->group[] = $field; $this->group[$field] = $field;
return $this; return $this;
} }
...@@ -1358,22 +1430,25 @@ class SelectQuery extends Query implements SelectQueryInterface { ...@@ -1358,22 +1430,25 @@ class SelectQuery extends Query implements SelectQueryInterface {
// Create our new query object that we will mutate into a count query. // Create our new query object that we will mutate into a count query.
$count = clone($this); $count = clone($this);
$group_by = array_keys($count->getGroupBy()); $group_by = $count->getGroupBy();
$having = $count->havingConditions();
if (!$count->distinct) { if (!$count->distinct && !isset($having[0])) {
// When not executing a distinct query, we can zero-out existing fields // When not executing a distinct query, we can zero-out existing fields
// and expressions that are not used by a GROUP BY. Fields listed in // and expressions that are not used by a GROUP BY or HAVING. Fields
// the GROUP BY clause need to be present in the query. // listed in a GROUP BY or HAVING clause need to be present in the
// query.
$fields =& $count->getFields(); $fields =& $count->getFields();
foreach (array_keys($fields) as $field) { foreach (array_keys($fields) as $field) {
if (!empty($group_by[$field])) { if (empty($group_by[$field])) {
unset($fields[$field]); unset($fields[$field]);
} }
} }
$expressions =& $count->getExpressions(); $expressions =& $count->getExpressions();
foreach (array_keys($expressions) as $field) { foreach (array_keys($expressions) as $field) {
if (!empty($group_by[$field])) { if (empty($group_by[$field])) {
unset($fields[$field]); unset($expressions[$field]);
} }
} }
...@@ -1406,9 +1481,16 @@ class SelectQuery extends Query implements SelectQueryInterface { ...@@ -1406,9 +1481,16 @@ class SelectQuery extends Query implements SelectQueryInterface {
} }
public function __toString() { public function __toString() {
// For convenience, we compile the query ourselves if the caller forgot
// to do it. This allows constructs like "(string) $query" to work. When
// the query will be executed, it will be recompiled using the proper
// placeholder generator anyway.
if (!$this->compiled()) {
$this->compile($this->connection, $this);
}
// Create a comments string to prepend to the query. // Create a sanitized comment string to prepend to the query.
$comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; $comments = $this->connection->makeComment($this->comments);
// SELECT // SELECT
$query = $comments . 'SELECT '; $query = $comments . 'SELECT ';
...@@ -1464,14 +1546,6 @@ class SelectQuery extends Query implements SelectQueryInterface { ...@@ -1464,14 +1546,6 @@ class SelectQuery extends Query implements SelectQueryInterface {
// WHERE // WHERE
if (count($this->where)) { if (count($this->where)) {
// The following line will not generate placeholders correctly if there
// is a subquery. Fortunately, it is also called from getArguments() first
// so it's not a problem in practice... unless you try to call __toString()
// before calling getArguments(). That is a problem that we will have to
// fix in Drupal 8, because it requires more refactoring than we are
// able to do in Drupal 7.
// @todo Move away from __toString() For SelectQuery compilation at least.
$this->where->compile($this->connection, $this);
// There is an implicit string cast on $this->condition. // There is an implicit string cast on $this->condition.
$query .= "\nWHERE " . $this->where; $query .= "\nWHERE " . $this->where;
} }
...@@ -1483,7 +1557,6 @@ class SelectQuery extends Query implements SelectQueryInterface { ...@@ -1483,7 +1557,6 @@ class SelectQuery extends Query implements SelectQueryInterface {
// HAVING // HAVING
if (count($this->having)) { if (count($this->having)) {
$this->having->compile($this->connection, $this);
// There is an implicit string cast on $this->having. // There is an implicit string cast on $this->having.
$query .= "\nHAVING " . $this->having; $query .= "\nHAVING " . $this->having;
} }
......