diff options
Diffstat (limited to 'core/includes')
108 files changed, 0 insertions, 63693 deletions
diff --git a/core/includes/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php b/core/includes/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php deleted file mode 100644 index 278f510e586..00000000000 --- a/core/includes/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php +++ /dev/null @@ -1,96 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ClassLoader; - -/** - * ApcUniversalClassLoader implements a "universal" autoloader cached in APC for PHP 5.3. - * - * It is able to load classes that use either: - * - * * The technical interoperability standards for PHP 5.3 namespaces and - * class names (http://groups.google.com/group/php-standards/web/psr-0-final-proposal); - * - * * The PEAR naming convention for classes (http://pear.php.net/). - * - * Classes from a sub-namespace or a sub-hierarchy of PEAR classes can be - * looked for in a list of locations to ease the vendoring of a sub-set of - * classes for large projects. - * - * Example usage: - * - * require 'vendor/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.php'; - * require 'vendor/symfony/src/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php'; - * - * use Symfony\Component\ClassLoader\ApcUniversalClassLoader; - * - * $loader = new ApcUniversalClassLoader('apc.prefix.'); - * - * // register classes with namespaces - * $loader->registerNamespaces(array( - * 'Symfony\Component' => __DIR__.'/component', - * 'Symfony' => __DIR__.'/framework', - * 'Sensio' => array(__DIR__.'/src', __DIR__.'/vendor'), - * )); - * - * // register a library using the PEAR naming convention - * $loader->registerPrefixes(array( - * 'Swift_' => __DIR__.'/Swift', - * )); - * - * // activate the autoloader - * $loader->register(); - * - * In this example, if you try to use a class in the Symfony\Component - * namespace or one of its children (Symfony\Component\Console for instance), - * the autoloader will first look for the class under the component/ - * directory, and it will then fallback to the framework/ directory if not - * found before giving up. - * - * @author Fabien Potencier <fabien@symfony.com> - * @author Kris Wallsmith <kris@symfony.com> - * - * @api - */ -class ApcUniversalClassLoader extends UniversalClassLoader -{ - private $prefix; - - /** - * Constructor. - * - * @param string $prefix A prefix to create a namespace in APC - * - * @api - */ - public function __construct($prefix) - { - if (!extension_loaded('apc')) { - throw new \RuntimeException('Unable to use ApcUniversalClassLoader as APC is not enabled.'); - } - - $this->prefix = $prefix; - } - - /** - * Finds a file by class name while caching lookups to APC. - * - * @param string $class A class name to resolve to file - */ - public function findFile($class) - { - if (false === $file = apc_fetch($this->prefix.$class)) { - apc_store($this->prefix.$class, $file = parent::findFile($class)); - } - - return $file; - } -} diff --git a/core/includes/Symfony/Component/ClassLoader/ClassCollectionLoader.php b/core/includes/Symfony/Component/ClassLoader/ClassCollectionLoader.php deleted file mode 100644 index da777f29916..00000000000 --- a/core/includes/Symfony/Component/ClassLoader/ClassCollectionLoader.php +++ /dev/null @@ -1,222 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ClassLoader; - -/** - * ClassCollectionLoader. - * - * @author Fabien Potencier <fabien@symfony.com> - */ -class ClassCollectionLoader -{ - static private $loaded; - - /** - * Loads a list of classes and caches them in one big file. - * - * @param array $classes An array of classes to load - * @param string $cacheDir A cache directory - * @param string $name The cache name prefix - * @param Boolean $autoReload Whether to flush the cache when the cache is stale or not - * @param Boolean $adaptive Whether to remove already declared classes or not - * @param string $extension File extension of the resulting file - * - * @throws \InvalidArgumentException When class can't be loaded - */ - static public function load($classes, $cacheDir, $name, $autoReload, $adaptive = false, $extension = '.php') - { - // each $name can only be loaded once per PHP process - if (isset(self::$loaded[$name])) { - return; - } - - self::$loaded[$name] = true; - - if ($adaptive) { - // don't include already declared classes - $classes = array_diff($classes, get_declared_classes(), get_declared_interfaces()); - - // the cache is different depending on which classes are already declared - $name = $name.'-'.substr(md5(implode('|', $classes)), 0, 5); - } - - $cache = $cacheDir.'/'.$name.$extension; - - // auto-reload - $reload = false; - if ($autoReload) { - $metadata = $cacheDir.'/'.$name.$extension.'.meta'; - if (!file_exists($metadata) || !file_exists($cache)) { - $reload = true; - } else { - $time = filemtime($cache); - $meta = unserialize(file_get_contents($metadata)); - - if ($meta[1] != $classes) { - $reload = true; - } else { - foreach ($meta[0] as $resource) { - if (!file_exists($resource) || filemtime($resource) > $time) { - $reload = true; - - break; - } - } - } - } - } - - if (!$reload && file_exists($cache)) { - require_once $cache; - - return; - } - - $files = array(); - $content = ''; - foreach ($classes as $class) { - if (!class_exists($class) && !interface_exists($class) && (!function_exists('trait_exists') || !trait_exists($class))) { - throw new \InvalidArgumentException(sprintf('Unable to load class "%s"', $class)); - } - - $r = new \ReflectionClass($class); - $files[] = $r->getFileName(); - - $c = preg_replace(array('/^\s*<\?php/', '/\?>\s*$/'), '', file_get_contents($r->getFileName())); - - // add namespace declaration for global code - if (!$r->inNamespace()) { - $c = "\nnamespace\n{\n".self::stripComments($c)."\n}\n"; - } else { - $c = self::fixNamespaceDeclarations('<?php '.$c); - $c = preg_replace('/^\s*<\?php/', '', $c); - } - - $content .= $c; - } - - // cache the core classes - if (!is_dir(dirname($cache))) { - mkdir(dirname($cache), 0777, true); - } - self::writeCacheFile($cache, '<?php '.$content); - - if ($autoReload) { - // save the resources - self::writeCacheFile($metadata, serialize(array($files, $classes))); - } - } - - /** - * Adds brackets around each namespace if it's not already the case. - * - * @param string $source Namespace string - * - * @return string Namespaces with brackets - */ - static public function fixNamespaceDeclarations($source) - { - if (!function_exists('token_get_all')) { - return $source; - } - - $output = ''; - $inNamespace = false; - $tokens = token_get_all($source); - - for ($i = 0, $max = count($tokens); $i < $max; $i++) { - $token = $tokens[$i]; - if (is_string($token)) { - $output .= $token; - } elseif (in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) { - // strip comments - continue; - } elseif (T_NAMESPACE === $token[0]) { - if ($inNamespace) { - $output .= "}\n"; - } - $output .= $token[1]; - - // namespace name and whitespaces - while (($t = $tokens[++$i]) && is_array($t) && in_array($t[0], array(T_WHITESPACE, T_NS_SEPARATOR, T_STRING))) { - $output .= $t[1]; - } - if (is_string($t) && '{' === $t) { - $inNamespace = false; - --$i; - } else { - $output .= "\n{"; - $inNamespace = true; - } - } else { - $output .= $token[1]; - } - } - - if ($inNamespace) { - $output .= "}\n"; - } - - return $output; - } - - /** - * Writes a cache file. - * - * @param string $file Filename - * @param string $content Temporary file content - * - * @throws \RuntimeException when a cache file cannot be written - */ - static private function writeCacheFile($file, $content) - { - $tmpFile = tempnam(dirname($file), basename($file)); - if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) { - chmod($file, 0644); - - return; - } - - throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file)); - } - - /** - * Removes comments from a PHP source string. - * - * We don't use the PHP php_strip_whitespace() function - * as we want the content to be readable and well-formatted. - * - * @param string $source A PHP string - * - * @return string The PHP string with the comments removed - */ - static private function stripComments($source) - { - if (!function_exists('token_get_all')) { - return $source; - } - - $output = ''; - foreach (token_get_all($source) as $token) { - if (is_string($token)) { - $output .= $token; - } elseif (!in_array($token[0], array(T_COMMENT, T_DOC_COMMENT))) { - $output .= $token[1]; - } - } - - // replace multiple new lines with a single newline - $output = preg_replace(array('/\s+$/Sm', '/\n+/S'), "\n", $output); - - return $output; - } -} diff --git a/core/includes/Symfony/Component/ClassLoader/DebugUniversalClassLoader.php b/core/includes/Symfony/Component/ClassLoader/DebugUniversalClassLoader.php deleted file mode 100644 index 8a958e01f96..00000000000 --- a/core/includes/Symfony/Component/ClassLoader/DebugUniversalClassLoader.php +++ /dev/null @@ -1,62 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ClassLoader; - -/** - * Checks that the class is actually declared in the included file. - * - * @author Fabien Potencier <fabien@symfony.com> - */ -class DebugUniversalClassLoader extends UniversalClassLoader -{ - /** - * Replaces all regular UniversalClassLoader instances by a DebugUniversalClassLoader ones. - */ - static public function enable() - { - if (!is_array($functions = spl_autoload_functions())) { - return; - } - - foreach ($functions as $function) { - spl_autoload_unregister($function); - } - - foreach ($functions as $function) { - if (is_array($function) && $function[0] instanceof UniversalClassLoader) { - $loader = new static(); - $loader->registerNamespaceFallbacks($function[0]->getNamespaceFallbacks()); - $loader->registerPrefixFallbacks($function[0]->getPrefixFallbacks()); - $loader->registerNamespaces($function[0]->getNamespaces()); - $loader->registerPrefixes($function[0]->getPrefixes()); - - $function[0] = $loader; - } - - spl_autoload_register($function); - } - } - - /** - * {@inheritDoc} - */ - public function loadClass($class) - { - if ($file = $this->findFile($class)) { - require $file; - - if (!class_exists($class, false) && !interface_exists($class, false) && (!function_exists('trait_exists') || !trait_exists($class, false))) { - throw new \RuntimeException(sprintf('The autoloader expected class "%s" to be defined in file "%s". The file was found but the class was not in it, the class name or namespace probably has a typo.', $class, $file)); - } - } - } -} diff --git a/core/includes/Symfony/Component/ClassLoader/LICENSE b/core/includes/Symfony/Component/ClassLoader/LICENSE deleted file mode 100644 index 89df4481b95..00000000000 --- a/core/includes/Symfony/Component/ClassLoader/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2004-2011 Fabien Potencier - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/core/includes/Symfony/Component/ClassLoader/MapClassLoader.php b/core/includes/Symfony/Component/ClassLoader/MapClassLoader.php deleted file mode 100644 index cf17d42642c..00000000000 --- a/core/includes/Symfony/Component/ClassLoader/MapClassLoader.php +++ /dev/null @@ -1,76 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ClassLoader; - -/** - * A class loader that uses a mapping file to look up paths. - * - * @author Fabien Potencier <fabien@symfony.com> - */ -class MapClassLoader -{ - private $map = array(); - - /** - * Constructor. - * - * @param array $map A map where keys are classes and values the absolute file path - */ - public function __construct(array $map) - { - $this->map = $map; - } - - /** - * Registers this instance as an autoloader. - * - * @param Boolean $prepend Whether to prepend the autoloader or not - */ - public function register($prepend = false) - { - spl_autoload_register(array($this, 'loadClass'), true, $prepend); - } - - /** - * Loads the given class or interface. - * - * @param string $class The name of the class - */ - public function loadClass($class) - { - if ('\\' === $class[0]) { - $class = substr($class, 1); - } - - if (isset($this->map[$class])) { - require $this->map[$class]; - } - } - - /** - * Finds the path to the file where the class is defined. - * - * @param string $class The name of the class - * - * @return string|null The path, if found - */ - public function findFile($class) - { - if ('\\' === $class[0]) { - $class = substr($class, 1); - } - - if (isset($this->map[$class])) { - return $this->map[$class]; - } - } -} diff --git a/core/includes/Symfony/Component/ClassLoader/UniversalClassLoader.php b/core/includes/Symfony/Component/ClassLoader/UniversalClassLoader.php deleted file mode 100644 index d296b94d580..00000000000 --- a/core/includes/Symfony/Component/ClassLoader/UniversalClassLoader.php +++ /dev/null @@ -1,265 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\ClassLoader; - -/** - * UniversalClassLoader implements a "universal" autoloader for PHP 5.3. - * - * It is able to load classes that use either: - * - * * The technical interoperability standards for PHP 5.3 namespaces and - * class names (http://groups.google.com/group/php-standards/web/psr-0-final-proposal); - * - * * The PEAR naming convention for classes (http://pear.php.net/). - * - * Classes from a sub-namespace or a sub-hierarchy of PEAR classes can be - * looked for in a list of locations to ease the vendoring of a sub-set of - * classes for large projects. - * - * Example usage: - * - * $loader = new UniversalClassLoader(); - * - * // register classes with namespaces - * $loader->registerNamespaces(array( - * 'Symfony\Component' => __DIR__.'/component', - * 'Symfony' => __DIR__.'/framework', - * 'Sensio' => array(__DIR__.'/src', __DIR__.'/vendor'), - * )); - * - * // register a library using the PEAR naming convention - * $loader->registerPrefixes(array( - * 'Swift_' => __DIR__.'/Swift', - * )); - * - * // activate the autoloader - * $loader->register(); - * - * In this example, if you try to use a class in the Symfony\Component - * namespace or one of its children (Symfony\Component\Console for instance), - * the autoloader will first look for the class under the component/ - * directory, and it will then fallback to the framework/ directory if not - * found before giving up. - * - * @author Fabien Potencier <fabien@symfony.com> - * - * @api - */ -class UniversalClassLoader -{ - private $namespaces = array(); - private $prefixes = array(); - private $namespaceFallbacks = array(); - private $prefixFallbacks = array(); - - /** - * Gets the configured namespaces. - * - * @return array A hash with namespaces as keys and directories as values - */ - public function getNamespaces() - { - return $this->namespaces; - } - - /** - * Gets the configured class prefixes. - * - * @return array A hash with class prefixes as keys and directories as values - */ - public function getPrefixes() - { - return $this->prefixes; - } - - /** - * Gets the directory(ies) to use as a fallback for namespaces. - * - * @return array An array of directories - */ - public function getNamespaceFallbacks() - { - return $this->namespaceFallbacks; - } - - /** - * Gets the directory(ies) to use as a fallback for class prefixes. - * - * @return array An array of directories - */ - public function getPrefixFallbacks() - { - return $this->prefixFallbacks; - } - - /** - * Registers the directory to use as a fallback for namespaces. - * - * @param array $dirs An array of directories - * - * @api - */ - public function registerNamespaceFallbacks(array $dirs) - { - $this->namespaceFallbacks = $dirs; - } - - /** - * Registers the directory to use as a fallback for class prefixes. - * - * @param array $dirs An array of directories - * - * @api - */ - public function registerPrefixFallbacks(array $dirs) - { - $this->prefixFallbacks = $dirs; - } - - /** - * Registers an array of namespaces - * - * @param array $namespaces An array of namespaces (namespaces as keys and locations as values) - * - * @api - */ - public function registerNamespaces(array $namespaces) - { - foreach ($namespaces as $namespace => $locations) { - $this->namespaces[$namespace] = (array) $locations; - } - } - - /** - * Registers a namespace. - * - * @param string $namespace The namespace - * @param array|string $paths The location(s) of the namespace - * - * @api - */ - public function registerNamespace($namespace, $paths) - { - $this->namespaces[$namespace] = (array) $paths; - } - - /** - * Registers an array of classes using the PEAR naming convention. - * - * @param array $classes An array of classes (prefixes as keys and locations as values) - * - * @api - */ - public function registerPrefixes(array $classes) - { - foreach ($classes as $prefix => $locations) { - $this->prefixes[$prefix] = (array) $locations; - } - } - - /** - * Registers a set of classes using the PEAR naming convention. - * - * @param string $prefix The classes prefix - * @param array|string $paths The location(s) of the classes - * - * @api - */ - public function registerPrefix($prefix, $paths) - { - $this->prefixes[$prefix] = (array) $paths; - } - - /** - * Registers this instance as an autoloader. - * - * @param Boolean $prepend Whether to prepend the autoloader or not - * - * @api - */ - public function register($prepend = false) - { - spl_autoload_register(array($this, 'loadClass'), true, $prepend); - } - - /** - * Loads the given class or interface. - * - * @param string $class The name of the class - */ - public function loadClass($class) - { - if ($file = $this->findFile($class)) { - require $file; - } - } - - /** - * Finds the path to the file where the class is defined. - * - * @param string $class The name of the class - * - * @return string|null The path, if found - */ - public function findFile($class) - { - if ('\\' == $class[0]) { - $class = substr($class, 1); - } - - if (false !== $pos = strrpos($class, '\\')) { - // namespaced class name - $namespace = substr($class, 0, $pos); - foreach ($this->namespaces as $ns => $dirs) { - if (0 !== strpos($namespace, $ns)) { - continue; - } - - foreach ($dirs as $dir) { - $className = substr($class, $pos + 1); - $file = $dir.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $namespace).DIRECTORY_SEPARATOR.str_replace('_', DIRECTORY_SEPARATOR, $className).'.php'; - if (file_exists($file)) { - return $file; - } - } - } - - foreach ($this->namespaceFallbacks as $dir) { - $file = $dir.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $class).'.php'; - if (file_exists($file)) { - return $file; - } - } - } else { - // PEAR-like class name - foreach ($this->prefixes as $prefix => $dirs) { - if (0 !== strpos($class, $prefix)) { - continue; - } - - foreach ($dirs as $dir) { - $file = $dir.DIRECTORY_SEPARATOR.str_replace('_', DIRECTORY_SEPARATOR, $class).'.php'; - if (file_exists($file)) { - return $file; - } - } - } - - foreach ($this->prefixFallbacks as $dir) { - $file = $dir.DIRECTORY_SEPARATOR.str_replace('_', DIRECTORY_SEPARATOR, $class).'.php'; - if (file_exists($file)) { - return $file; - } - } - } - } -} diff --git a/core/includes/Symfony/Component/ClassLoader/composer.json b/core/includes/Symfony/Component/ClassLoader/composer.json deleted file mode 100644 index 35b573e5bb8..00000000000 --- a/core/includes/Symfony/Component/ClassLoader/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "symfony/class-loader", - "type": "library", - "description": "Symfony ClassLoader Component", - "keywords": [], - "homepage": "http://symfony.com", - "version": "2.0.4", - "license": "MIT", - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - } - ], - "require": { - "php": ">=5.3.2" - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/ApacheRequest.php b/core/includes/Symfony/Component/HttpFoundation/ApacheRequest.php deleted file mode 100644 index 27215819e49..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/ApacheRequest.php +++ /dev/null @@ -1,51 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation; - -/** - * Request represents an HTTP request from an Apache server. - * - * @author Fabien Potencier <fabien@symfony.com> - */ -class ApacheRequest extends Request -{ - /** - * {@inheritdoc} - */ - protected function prepareRequestUri() - { - return $this->server->get('REQUEST_URI'); - } - - /** - * {@inheritdoc} - */ - protected function prepareBaseUrl() - { - $baseUrl = $this->server->get('SCRIPT_NAME'); - - if (false === strpos($this->server->get('REQUEST_URI'), $baseUrl)) { - // assume mod_rewrite - return rtrim(dirname($baseUrl), '/\\'); - } - - return $baseUrl; - } - - /** - * {@inheritdoc} - */ - protected function preparePathInfo() - { - return $this->server->get('PATH_INFO'); - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/Cookie.php b/core/includes/Symfony/Component/HttpFoundation/Cookie.php deleted file mode 100644 index 8392812ebe5..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/Cookie.php +++ /dev/null @@ -1,207 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation; - -/** - * Represents a cookie - * - * @author Johannes M. Schmitt <schmittjoh@gmail.com> - * - * @api - */ -class Cookie -{ - protected $name; - protected $value; - protected $domain; - protected $expire; - protected $path; - protected $secure; - protected $httpOnly; - - /** - * Constructor. - * - * @param string $name The name of the cookie - * @param string $value The value of the cookie - * @param integer|string|\DateTime $expire The time the cookie expires - * @param string $path The path on the server in which the cookie will be available on - * @param string $domain The domain that the cookie is available to - * @param Boolean $secure Whether the cookie should only be transmitted over a secure HTTPS connection from the client - * @param Boolean $httpOnly Whether the cookie will be made accessible only through the HTTP protocol - * - * @api - */ - public function __construct($name, $value = null, $expire = 0, $path = '/', $domain = null, $secure = false, $httpOnly = true) - { - // from PHP source code - if (preg_match("/[=,; \t\r\n\013\014]/", $name)) { - throw new \InvalidArgumentException(sprintf('The cookie name "%s" contains invalid characters.', $name)); - } - - if (preg_match("/[,; \t\r\n\013\014]/", $value)) { - throw new \InvalidArgumentException(sprintf('The cookie value "%s" contains invalid characters.', $value)); - } - - if (empty($name)) { - throw new \InvalidArgumentException('The cookie name cannot be empty.'); - } - - // convert expiration time to a Unix timestamp - if ($expire instanceof \DateTime) { - $expire = $expire->format('U'); - } elseif (!is_numeric($expire)) { - $expire = strtotime($expire); - - if (false === $expire || -1 === $expire) { - throw new \InvalidArgumentException('The cookie expiration time is not valid.'); - } - } - - $this->name = $name; - $this->value = $value; - $this->domain = $domain; - $this->expire = $expire; - $this->path = empty($path) ? '/' : $path; - $this->secure = (Boolean) $secure; - $this->httpOnly = (Boolean) $httpOnly; - } - - public function __toString() - { - $str = urlencode($this->getName()).'='; - - if ('' === (string) $this->getValue()) { - $str .= 'deleted; expires='.gmdate("D, d-M-Y H:i:s T", time() - 31536001); - } else { - $str .= urlencode($this->getValue()); - - if ($this->getExpiresTime() !== 0) { - $str .= '; expires='.gmdate("D, d-M-Y H:i:s T", $this->getExpiresTime()); - } - } - - if ('/' !== $this->path) { - $str .= '; path='.$this->path; - } - - if (null !== $this->getDomain()) { - $str .= '; domain='.$this->getDomain(); - } - - if (true === $this->isSecure()) { - $str .= '; secure'; - } - - if (true === $this->isHttpOnly()) { - $str .= '; httponly'; - } - - return $str; - } - - /** - * Gets the name of the cookie. - * - * @return string - * - * @api - */ - public function getName() - { - return $this->name; - } - - /** - * Gets the value of the cookie. - * - * @return string - * - * @api - */ - public function getValue() - { - return $this->value; - } - - /** - * Gets the domain that the cookie is available to. - * - * @return string - * - * @api - */ - public function getDomain() - { - return $this->domain; - } - - /** - * Gets the time the cookie expires. - * - * @return integer - * - * @api - */ - public function getExpiresTime() - { - return $this->expire; - } - - /** - * Gets the path on the server in which the cookie will be available on. - * - * @return string - * - * @api - */ - public function getPath() - { - return $this->path; - } - - /** - * Checks whether the cookie should only be transmitted over a secure HTTPS connection from the client. - * - * @return Boolean - * - * @api - */ - public function isSecure() - { - return $this->secure; - } - - /** - * Checks whether the cookie will be made accessible only through the HTTP protocol. - * - * @return Boolean - * - * @api - */ - public function isHttpOnly() - { - return $this->httpOnly; - } - - /** - * Whether this cookie is about to be cleared - * - * @return Boolean - * - * @api - */ - public function isCleared() - { - return $this->expire < time(); - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/File/Exception/AccessDeniedException.php b/core/includes/Symfony/Component/HttpFoundation/File/Exception/AccessDeniedException.php deleted file mode 100644 index 9c7fe6812a3..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/File/Exception/AccessDeniedException.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File\Exception; - -/** - * Thrown when the access on a file was denied. - * - * @author Bernhard Schussek <bernhard.schussek@symfony.com> - */ -class AccessDeniedException extends FileException -{ - /** - * Constructor. - * - * @param string $path The path to the accessed file - */ - public function __construct($path) - { - parent::__construct(sprintf('The file %s could not be accessed', $path)); - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/File/Exception/FileException.php b/core/includes/Symfony/Component/HttpFoundation/File/Exception/FileException.php deleted file mode 100644 index 43c6cc8998c..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/File/Exception/FileException.php +++ /dev/null @@ -1,21 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File\Exception; - -/** - * Thrown when an error occurred in the component File - * - * @author Bernhard Schussek <bernhard.schussek@symfony.com> - */ -class FileException extends \RuntimeException -{ -} diff --git a/core/includes/Symfony/Component/HttpFoundation/File/Exception/FileNotFoundException.php b/core/includes/Symfony/Component/HttpFoundation/File/Exception/FileNotFoundException.php deleted file mode 100644 index 5b1aef8e2b2..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/File/Exception/FileNotFoundException.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File\Exception; - -/** - * Thrown when a file was not found - * - * @author Bernhard Schussek <bernhard.schussek@symfony.com> - */ -class FileNotFoundException extends FileException -{ - /** - * Constructor. - * - * @param string $path The path to the file that was not found - */ - public function __construct($path) - { - parent::__construct(sprintf('The file "%s" does not exist', $path)); - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/File/Exception/UnexpectedTypeException.php b/core/includes/Symfony/Component/HttpFoundation/File/Exception/UnexpectedTypeException.php deleted file mode 100644 index 0444b877821..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/File/Exception/UnexpectedTypeException.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File\Exception; - -class UnexpectedTypeException extends FileException -{ - public function __construct($value, $expectedType) - { - parent::__construct(sprintf('Expected argument of type %s, %s given', $expectedType, is_object($value) ? get_class($value) : gettype($value))); - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/File/Exception/UploadException.php b/core/includes/Symfony/Component/HttpFoundation/File/Exception/UploadException.php deleted file mode 100644 index 694e864d1c5..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/File/Exception/UploadException.php +++ /dev/null @@ -1,21 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File\Exception; - -/** - * Thrown when an error occurred during file upload - * - * @author Bernhard Schussek <bernhard.schussek@symfony.com> - */ -class UploadException extends FileException -{ -} diff --git a/core/includes/Symfony/Component/HttpFoundation/File/File.php b/core/includes/Symfony/Component/HttpFoundation/File/File.php deleted file mode 100644 index 3a900fdcef6..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/File/File.php +++ /dev/null @@ -1,544 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File; - -use Symfony\Component\HttpFoundation\File\Exception\FileException; -use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; -use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser; - -/** - * A file in the file system. - * - * @author Bernhard Schussek <bernhard.schussek@symfony.com> - * - * @api - */ -class File extends \SplFileInfo -{ - /** - * A map of mime types and their default extensions. - * - * @var array - */ - static protected $defaultExtensions = array( - 'application/andrew-inset' => 'ez', - 'application/appledouble' => 'base64', - 'application/applefile' => 'base64', - 'application/commonground' => 'dp', - 'application/cprplayer' => 'pqi', - 'application/dsptype' => 'tsp', - 'application/excel' => 'xls', - 'application/font-tdpfr' => 'pfr', - 'application/futuresplash' => 'spl', - 'application/hstu' => 'stk', - 'application/hyperstudio' => 'stk', - 'application/javascript' => 'js', - 'application/mac-binhex40' => 'hqx', - 'application/mac-compactpro' => 'cpt', - 'application/mbed' => 'mbd', - 'application/mirage' => 'mfp', - 'application/msword' => 'doc', - 'application/ocsp-request' => 'orq', - 'application/ocsp-response' => 'ors', - 'application/octet-stream' => 'bin', - 'application/oda' => 'oda', - 'application/ogg' => 'ogg', - 'application/pdf' => 'pdf', - 'application/x-pdf' => 'pdf', - 'application/pgp-encrypted' => '7bit', - 'application/pgp-keys' => '7bit', - 'application/pgp-signature' => 'sig', - 'application/pkcs10' => 'p10', - 'application/pkcs7-mime' => 'p7m', - 'application/pkcs7-signature' => 'p7s', - 'application/pkix-cert' => 'cer', - 'application/pkix-crl' => 'crl', - 'application/pkix-pkipath' => 'pkipath', - 'application/pkixcmp' => 'pki', - 'application/postscript' => 'ps', - 'application/presentations' => 'shw', - 'application/prs.cww' => 'cw', - 'application/prs.nprend' => 'rnd', - 'application/quest' => 'qrt', - 'application/rtf' => 'rtf', - 'application/sgml-open-catalog' => 'soc', - 'application/sieve' => 'siv', - 'application/smil' => 'smi', - 'application/toolbook' => 'tbk', - 'application/vnd.3gpp.pic-bw-large' => 'plb', - 'application/vnd.3gpp.pic-bw-small' => 'psb', - 'application/vnd.3gpp.pic-bw-var' => 'pvb', - 'application/vnd.3gpp.sms' => 'sms', - 'application/vnd.acucorp' => 'atc', - 'application/vnd.adobe.xfdf' => 'xfdf', - 'application/vnd.amiga.amu' => 'ami', - 'application/vnd.blueice.multipass' => 'mpm', - 'application/vnd.cinderella' => 'cdy', - 'application/vnd.cosmocaller' => 'cmc', - 'application/vnd.criticaltools.wbs+xml' => 'wbs', - 'application/vnd.curl' => 'curl', - 'application/vnd.data-vision.rdz' => 'rdz', - 'application/vnd.dreamfactory' => 'dfac', - 'application/vnd.fsc.weblaunch' => 'fsc', - 'application/vnd.genomatix.tuxedo' => 'txd', - 'application/vnd.hbci' => 'hbci', - 'application/vnd.hhe.lesson-player' => 'les', - 'application/vnd.hp-hpgl' => 'plt', - 'application/vnd.ibm.electronic-media' => 'emm', - 'application/vnd.ibm.rights-management' => 'irm', - 'application/vnd.ibm.secure-container' => 'sc', - 'application/vnd.ipunplugged.rcprofile' => 'rcprofile', - 'application/vnd.irepository.package+xml' => 'irp', - 'application/vnd.jisp' => 'jisp', - 'application/vnd.kde.karbon' => 'karbon', - 'application/vnd.kde.kchart' => 'chrt', - 'application/vnd.kde.kformula' => 'kfo', - 'application/vnd.kde.kivio' => 'flw', - 'application/vnd.kde.kontour' => 'kon', - 'application/vnd.kde.kpresenter' => 'kpr', - 'application/vnd.kde.kspread' => 'ksp', - 'application/vnd.kde.kword' => 'kwd', - 'application/vnd.kenameapp' => 'htke', - 'application/vnd.kidspiration' => 'kia', - 'application/vnd.kinar' => 'kne', - 'application/vnd.llamagraphics.life-balance.desktop' => 'lbd', - 'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe', - 'application/vnd.lotus-1-2-3' => 'wks', - 'application/vnd.mcd' => 'mcd', - 'application/vnd.mfmp' => 'mfm', - 'application/vnd.micrografx.flo' => 'flo', - 'application/vnd.micrografx.igx' => 'igx', - 'application/vnd.mif' => 'mif', - 'application/vnd.mophun.application' => 'mpn', - 'application/vnd.mophun.certificate' => 'mpc', - 'application/vnd.mozilla.xul+xml' => 'xul', - 'application/vnd.ms-artgalry' => 'cil', - 'application/vnd.ms-asf' => 'asf', - 'application/vnd.ms-excel' => 'xls', - 'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm', - 'application/vnd.ms-lrm' => 'lrm', - 'application/vnd.ms-powerpoint' => 'ppt', - 'application/vnd.ms-project' => 'mpp', - 'application/vnd.ms-tnef' => 'base64', - 'application/vnd.ms-works' => 'base64', - 'application/vnd.ms-wpl' => 'wpl', - 'application/vnd.mseq' => 'mseq', - 'application/vnd.nervana' => 'ent', - 'application/vnd.nokia.radio-preset' => 'rpst', - 'application/vnd.nokia.radio-presets' => 'rpss', - 'application/vnd.oasis.opendocument.text' => 'odt', - 'application/vnd.oasis.opendocument.text-template' => 'ott', - 'application/vnd.oasis.opendocument.text-web' => 'oth', - 'application/vnd.oasis.opendocument.text-master' => 'odm', - 'application/vnd.oasis.opendocument.graphics' => 'odg', - 'application/vnd.oasis.opendocument.graphics-template' => 'otg', - 'application/vnd.oasis.opendocument.presentation' => 'odp', - 'application/vnd.oasis.opendocument.presentation-template' => 'otp', - 'application/vnd.oasis.opendocument.spreadsheet' => 'ods', - 'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots', - 'application/vnd.oasis.opendocument.chart' => 'odc', - 'application/vnd.oasis.opendocument.formula' => 'odf', - 'application/vnd.oasis.opendocument.database' => 'odb', - 'application/vnd.oasis.opendocument.image' => 'odi', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', - 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx', - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', - 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', - 'application/vnd.palm' => 'prc', - 'application/vnd.picsel' => 'efif', - 'application/vnd.pvi.ptid1' => 'pti', - 'application/vnd.quark.quarkxpress' => 'qxd', - 'application/vnd.sealed.doc' => 'sdoc', - 'application/vnd.sealed.eml' => 'seml', - 'application/vnd.sealed.mht' => 'smht', - 'application/vnd.sealed.ppt' => 'sppt', - 'application/vnd.sealed.xls' => 'sxls', - 'application/vnd.sealedmedia.softseal.html' => 'stml', - 'application/vnd.sealedmedia.softseal.pdf' => 'spdf', - 'application/vnd.seemail' => 'see', - 'application/vnd.smaf' => 'mmf', - 'application/vnd.sun.xml.calc' => 'sxc', - 'application/vnd.sun.xml.calc.template' => 'stc', - 'application/vnd.sun.xml.draw' => 'sxd', - 'application/vnd.sun.xml.draw.template' => 'std', - 'application/vnd.sun.xml.impress' => 'sxi', - 'application/vnd.sun.xml.impress.template' => 'sti', - 'application/vnd.sun.xml.math' => 'sxm', - 'application/vnd.sun.xml.writer' => 'sxw', - 'application/vnd.sun.xml.writer.global' => 'sxg', - 'application/vnd.sun.xml.writer.template' => 'stw', - 'application/vnd.sus-calendar' => 'sus', - 'application/vnd.vidsoft.vidconference' => 'vsc', - 'application/vnd.visio' => 'vsd', - 'application/vnd.visionary' => 'vis', - 'application/vnd.wap.sic' => 'sic', - 'application/vnd.wap.slc' => 'slc', - 'application/vnd.wap.wbxml' => 'wbxml', - 'application/vnd.wap.wmlc' => 'wmlc', - 'application/vnd.wap.wmlscriptc' => 'wmlsc', - 'application/vnd.webturbo' => 'wtb', - 'application/vnd.wordperfect' => 'wpd', - 'application/vnd.wqd' => 'wqd', - 'application/vnd.wv.csp+wbxml' => 'wv', - 'application/vnd.wv.csp+xml' => '8bit', - 'application/vnd.wv.ssp+xml' => '8bit', - 'application/vnd.yamaha.hv-dic' => 'hvd', - 'application/vnd.yamaha.hv-script' => 'hvs', - 'application/vnd.yamaha.hv-voice' => 'hvp', - 'application/vnd.yamaha.smaf-audio' => 'saf', - 'application/vnd.yamaha.smaf-phrase' => 'spf', - 'application/vocaltec-media-desc' => 'vmd', - 'application/vocaltec-media-file' => 'vmf', - 'application/vocaltec-talker' => 'vtk', - 'application/watcherinfo+xml' => 'wif', - 'application/wordperfect5.1' => 'wp5', - 'application/x-123' => 'wk', - 'application/x-7th_level_event' => '7ls', - 'application/x-authorware-bin' => 'aab', - 'application/x-authorware-map' => 'aam', - 'application/x-authorware-seg' => 'aas', - 'application/x-bcpio' => 'bcpio', - 'application/x-bleeper' => 'bleep', - 'application/x-bzip2' => 'bz2', - 'application/x-cdlink' => 'vcd', - 'application/x-chat' => 'chat', - 'application/x-chess-pgn' => 'pgn', - 'application/x-compress' => 'z', - 'application/x-cpio' => 'cpio', - 'application/x-cprplayer' => 'pqf', - 'application/x-csh' => 'csh', - 'application/x-cu-seeme' => 'csm', - 'application/x-cult3d-object' => 'co', - 'application/x-debian-package' => 'deb', - 'application/x-director' => 'dcr', - 'application/x-dvi' => 'dvi', - 'application/x-envoy' => 'evy', - 'application/x-futuresplash' => 'spl', - 'application/x-gtar' => 'gtar', - 'application/x-gzip' => 'gz', - 'application/x-hdf' => 'hdf', - 'application/x-hep' => 'hep', - 'application/x-html+ruby' => 'rhtml', - 'application/x-httpd-miva' => 'mv', - 'application/x-httpd-php' => 'phtml', - 'application/x-ica' => 'ica', - 'application/x-imagemap' => 'imagemap', - 'application/x-ipix' => 'ipx', - 'application/x-ipscript' => 'ips', - 'application/x-java-archive' => 'jar', - 'application/x-java-jnlp-file' => 'jnlp', - 'application/x-java-serialized-object' => 'ser', - 'application/x-java-vm' => 'class', - 'application/x-javascript' => 'js', - 'application/x-koan' => 'skp', - 'application/x-latex' => 'latex', - 'application/x-mac-compactpro' => 'cpt', - 'application/x-maker' => 'frm', - 'application/x-mathcad' => 'mcd', - 'application/x-midi' => 'mid', - 'application/x-mif' => 'mif', - 'application/x-msaccess' => 'mda', - 'application/x-msdos-program' => 'com', - 'application/x-msdownload' => 'base64', - 'application/x-msexcel' => 'xls', - 'application/x-msword' => 'doc', - 'application/x-netcdf' => 'nc', - 'application/x-ns-proxy-autoconfig' => 'pac', - 'application/x-pagemaker' => 'pm5', - 'application/x-perl' => 'pl', - 'application/x-pn-realmedia' => 'rp', - 'application/x-python' => 'py', - 'application/x-quicktimeplayer' => 'qtl', - 'application/x-rar-compressed' => 'rar', - 'application/x-ruby' => 'rb', - 'application/x-sh' => 'sh', - 'application/x-shar' => 'shar', - 'application/x-shockwave-flash' => 'swf', - 'application/x-sprite' => 'spr', - 'application/x-spss' => 'sav', - 'application/x-spt' => 'spt', - 'application/x-stuffit' => 'sit', - 'application/x-sv4cpio' => 'sv4cpio', - 'application/x-sv4crc' => 'sv4crc', - 'application/x-tar' => 'tar', - 'application/x-tcl' => 'tcl', - 'application/x-tex' => 'tex', - 'application/x-texinfo' => 'texinfo', - 'application/x-troff' => 't', - 'application/x-troff-man' => 'man', - 'application/x-troff-me' => 'me', - 'application/x-troff-ms' => 'ms', - 'application/x-twinvq' => 'vqf', - 'application/x-twinvq-plugin' => 'vqe', - 'application/x-ustar' => 'ustar', - 'application/x-vmsbackup' => 'bck', - 'application/x-wais-source' => 'src', - 'application/x-wingz' => 'wz', - 'application/x-word' => 'base64', - 'application/x-wordperfect6.1' => 'wp6', - 'application/x-x509-ca-cert' => 'crt', - 'application/x-zip-compressed' => 'zip', - 'application/xhtml+xml' => 'xhtml', - 'application/zip' => 'zip', - 'audio/3gpp' => '3gpp', - 'audio/amr' => 'amr', - 'audio/amr-wb' => 'awb', - 'audio/basic' => 'au', - 'audio/evrc' => 'evc', - 'audio/l16' => 'l16', - 'audio/midi' => 'mid', - 'audio/mpeg' => 'mp3', - 'audio/prs.sid' => 'sid', - 'audio/qcelp' => 'qcp', - 'audio/smv' => 'smv', - 'audio/vnd.audiokoz' => 'koz', - 'audio/vnd.digital-winds' => 'eol', - 'audio/vnd.everad.plj' => 'plj', - 'audio/vnd.lucent.voice' => 'lvp', - 'audio/vnd.nokia.mobile-xmf' => 'mxmf', - 'audio/vnd.nortel.vbk' => 'vbk', - 'audio/vnd.nuera.ecelp4800' => 'ecelp4800', - 'audio/vnd.nuera.ecelp7470' => 'ecelp7470', - 'audio/vnd.nuera.ecelp9600' => 'ecelp9600', - 'audio/vnd.sealedmedia.softseal.mpeg' => 'smp3', - 'audio/voxware' => 'vox', - 'audio/x-aiff' => 'aif', - 'audio/x-mid' => 'mid', - 'audio/x-midi' => 'mid', - 'audio/x-mpeg' => 'mp2', - 'audio/x-mpegurl' => 'mpu', - 'audio/x-pn-realaudio' => 'rm', - 'audio/x-pn-realaudio-plugin' => 'rpm', - 'audio/x-realaudio' => 'ra', - 'audio/x-wav' => 'wav', - 'chemical/x-csml' => 'csm', - 'chemical/x-embl-dl-nucleotide' => 'emb', - 'chemical/x-gaussian-cube' => 'cube', - 'chemical/x-gaussian-input' => 'gau', - 'chemical/x-jcamp-dx' => 'jdx', - 'chemical/x-mdl-molfile' => 'mol', - 'chemical/x-mdl-rxnfile' => 'rxn', - 'chemical/x-mdl-tgf' => 'tgf', - 'chemical/x-mopac-input' => 'mop', - 'chemical/x-pdb' => 'pdb', - 'chemical/x-rasmol' => 'scr', - 'chemical/x-xyz' => 'xyz', - 'drawing/dwf' => 'dwf', - 'drawing/x-dwf' => 'dwf', - 'i-world/i-vrml' => 'ivr', - 'image/bmp' => 'bmp', - 'image/cewavelet' => 'wif', - 'image/cis-cod' => 'cod', - 'image/fif' => 'fif', - 'image/gif' => 'gif', - 'image/ief' => 'ief', - 'image/jp2' => 'jp2', - 'image/jpeg' => 'jpg', - 'image/jpm' => 'jpm', - 'image/jpx' => 'jpf', - 'image/pict' => 'pic', - 'image/pjpeg' => 'jpg', - 'image/png' => 'png', - 'image/targa' => 'tga', - 'image/tiff' => 'tif', - 'image/vn-svf' => 'svf', - 'image/vnd.dgn' => 'dgn', - 'image/vnd.djvu' => 'djvu', - 'image/vnd.dwg' => 'dwg', - 'image/vnd.glocalgraphics.pgb' => 'pgb', - 'image/vnd.microsoft.icon' => 'ico', - 'image/vnd.ms-modi' => 'mdi', - 'image/vnd.sealed.png' => 'spng', - 'image/vnd.sealedmedia.softseal.gif' => 'sgif', - 'image/vnd.sealedmedia.softseal.jpg' => 'sjpg', - 'image/vnd.wap.wbmp' => 'wbmp', - 'image/x-bmp' => 'bmp', - 'image/x-cmu-raster' => 'ras', - 'image/x-freehand' => 'fh4', - 'image/x-png' => 'png', - 'image/x-portable-anymap' => 'pnm', - 'image/x-portable-bitmap' => 'pbm', - 'image/x-portable-graymap' => 'pgm', - 'image/x-portable-pixmap' => 'ppm', - 'image/x-rgb' => 'rgb', - 'image/x-xbitmap' => 'xbm', - 'image/x-xpixmap' => 'xpm', - 'image/x-xwindowdump' => 'xwd', - 'message/external-body' => '8bit', - 'message/news' => '8bit', - 'message/partial' => '8bit', - 'message/rfc822' => '8bit', - 'model/iges' => 'igs', - 'model/mesh' => 'msh', - 'model/vnd.parasolid.transmit.binary' => 'x_b', - 'model/vnd.parasolid.transmit.text' => 'x_t', - 'model/vrml' => 'wrl', - 'multipart/alternative' => '8bit', - 'multipart/appledouble' => '8bit', - 'multipart/digest' => '8bit', - 'multipart/mixed' => '8bit', - 'multipart/parallel' => '8bit', - 'text/comma-separated-values' => 'csv', - 'text/css' => 'css', - 'text/html' => 'html', - 'text/plain' => 'txt', - 'text/prs.fallenstein.rst' => 'rst', - 'text/richtext' => 'rtx', - 'text/rtf' => 'rtf', - 'text/sgml' => 'sgml', - 'text/tab-separated-values' => 'tsv', - 'text/vnd.net2phone.commcenter.command' => 'ccc', - 'text/vnd.sun.j2me.app-descriptor' => 'jad', - 'text/vnd.wap.si' => 'si', - 'text/vnd.wap.sl' => 'sl', - 'text/vnd.wap.wml' => 'wml', - 'text/vnd.wap.wmlscript' => 'wmls', - 'text/x-hdml' => 'hdml', - 'text/x-setext' => 'etx', - 'text/x-sgml' => 'sgml', - 'text/x-speech' => 'talk', - 'text/x-vcalendar' => 'vcs', - 'text/x-vcard' => 'vcf', - 'text/xml' => 'xml', - 'ulead/vrml' => 'uvr', - 'video/3gpp' => '3gp', - 'video/dl' => 'dl', - 'video/gl' => 'gl', - 'video/mj2' => 'mj2', - 'video/mpeg' => 'mpeg', - 'video/quicktime' => 'mov', - 'video/vdo' => 'vdo', - 'video/vivo' => 'viv', - 'video/vnd.fvt' => 'fvt', - 'video/vnd.mpegurl' => 'mxu', - 'video/vnd.nokia.interleaved-multimedia' => 'nim', - 'video/vnd.objectvideo' => 'mp4', - 'video/vnd.sealed.mpeg1' => 's11', - 'video/vnd.sealed.mpeg4' => 'smpg', - 'video/vnd.sealed.swf' => 'sswf', - 'video/vnd.sealedmedia.softseal.mov' => 'smov', - 'video/vnd.vivo' => 'vivo', - 'video/x-fli' => 'fli', - 'video/x-ms-asf' => 'asf', - 'video/x-ms-wmv' => 'wmv', - 'video/x-msvideo' => 'avi', - 'video/x-sgi-movie' => 'movie', - 'x-chemical/x-pdb' => 'pdb', - 'x-chemical/x-xyz' => 'xyz', - 'x-conference/x-cooltalk' => 'ice', - 'x-drawing/dwf' => 'dwf', - 'x-world/x-d96' => 'd', - 'x-world/x-svr' => 'svr', - 'x-world/x-vream' => 'vrw', - 'x-world/x-vrml' => 'wrl', - ); - - /** - * Constructs a new file from the given path. - * - * @param string $path The path to the file - * - * @throws FileNotFoundException If the given path is not a file - * - * @api - */ - public function __construct($path) - { - if (!is_file($path)) { - throw new FileNotFoundException($path); - } - - parent::__construct($path); - } - - /** - * Returns the extension based on the mime type. - * - * If the mime type is unknown, returns null. - * - * @return string|null The guessed extension or null if it cannot be guessed - * - * @api - */ - public function guessExtension() - { - $type = $this->getMimeType(); - - return isset(static::$defaultExtensions[$type]) ? static::$defaultExtensions[$type] : null; - } - - /** - * Returns the mime type of the file. - * - * The mime type is guessed using the functions finfo(), mime_content_type() - * and the system binary "file" (in this order), depending on which of those - * is available on the current operating system. - * - * @return string|null The guessed mime type (i.e. "application/pdf") - * - * @api - */ - public function getMimeType() - { - $guesser = MimeTypeGuesser::getInstance(); - - return $guesser->guess($this->getPathname()); - } - - /** - * Returns the extension of the file. - * - * \SplFileInfo::getExtension() is not available before PHP 5.3.6 - * - * @return string The extension - * - * @api - */ - public function getExtension() - { - return pathinfo($this->getBasename(), PATHINFO_EXTENSION); - } - - /** - * Moves the file to a new location. - * - * @param string $directory The destination folder - * @param string $name The new file name - * - * @return File A File object representing the new file - * - * @throws FileException if the target file could not be created - * - * @api - */ - public function move($directory, $name = null) - { - if (!is_dir($directory)) { - if (false === @mkdir($directory, 0777, true)) { - throw new FileException(sprintf('Unable to create the "%s" directory', $directory)); - } - } elseif (!is_writable($directory)) { - throw new FileException(sprintf('Unable to write in the "%s" directory', $directory)); - } - - $target = $directory.DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : basename($name)); - - if (!@rename($this->getPathname(), $target)) { - $error = error_get_last(); - throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error['message']))); - } - - chmod($target, 0666); - - return new File($target); - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/File/MimeType/ContentTypeMimeTypeGuesser.php b/core/includes/Symfony/Component/HttpFoundation/File/MimeType/ContentTypeMimeTypeGuesser.php deleted file mode 100644 index fb900b2bc28..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/File/MimeType/ContentTypeMimeTypeGuesser.php +++ /dev/null @@ -1,62 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File\MimeType; - -use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; -use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; - -/** - * Guesses the mime type using the PHP function mime_content_type(). - * - * @author Bernhard Schussek <bernhard.schussek@symfony.com> - */ -class ContentTypeMimeTypeGuesser implements MimeTypeGuesserInterface -{ - /** - * Returns whether this guesser is supported on the current OS/PHP setup - * - * @return Boolean - */ - static public function isSupported() - { - return function_exists('mime_content_type'); - } - - /** - * Guesses the mime type of the file with the given path - * - * @see MimeTypeGuesserInterface::guess() - */ - public function guess($path) - { - if (!is_file($path)) { - throw new FileNotFoundException($path); - } - - if (!is_readable($path)) { - throw new AccessDeniedException($path); - } - - if (!self::isSupported()) { - return null; - } - - $type = mime_content_type($path); - - // remove charset (added as of PHP 5.3) - if (false !== $pos = strpos($type, ';')) { - $type = substr($type, 0, $pos); - } - - return $type; - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/File/MimeType/FileBinaryMimeTypeGuesser.php b/core/includes/Symfony/Component/HttpFoundation/File/MimeType/FileBinaryMimeTypeGuesser.php deleted file mode 100644 index 1b869f22cb3..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/File/MimeType/FileBinaryMimeTypeGuesser.php +++ /dev/null @@ -1,71 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File\MimeType; - -use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; -use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; - -/** - * Guesses the mime type with the binary "file" (only available on *nix) - * - * @author Bernhard Schussek <bernhard.schussek@symfony.com> - */ -class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface -{ - /** - * Returns whether this guesser is supported on the current OS - * - * @return Boolean - */ - static public function isSupported() - { - return !strstr(PHP_OS, 'WIN'); - } - /** - * Guesses the mime type of the file with the given path - * - * @see MimeTypeGuesserInterface::guess() - */ - public function guess($path) - { - if (!is_file($path)) { - throw new FileNotFoundException($path); - } - - if (!is_readable($path)) { - throw new AccessDeniedException($path); - } - - if (!self::isSupported()) { - return null; - } - - ob_start(); - - // need to use --mime instead of -i. see #6641 - passthru(sprintf('file -b --mime %s 2>/dev/null', escapeshellarg($path)), $return); - if ($return > 0) { - ob_end_clean(); - - return null; - } - - $type = trim(ob_get_clean()); - - if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-]+)#i', $type, $match)) { - // it's not a type, but an error message - return null; - } - - return $match[1]; - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/File/MimeType/FileinfoMimeTypeGuesser.php b/core/includes/Symfony/Component/HttpFoundation/File/MimeType/FileinfoMimeTypeGuesser.php deleted file mode 100644 index 45d5a086eda..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/File/MimeType/FileinfoMimeTypeGuesser.php +++ /dev/null @@ -1,59 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File\MimeType; - -use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; -use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; - -/** - * Guesses the mime type using the PECL extension FileInfo - * - * @author Bernhard Schussek <bernhard.schussek@symfony.com> - */ -class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface -{ - /** - * Returns whether this guesser is supported on the current OS/PHP setup - * - * @return Boolean - */ - static public function isSupported() - { - return function_exists('finfo_open'); - } - - /** - * Guesses the mime type of the file with the given path - * - * @see MimeTypeGuesserInterface::guess() - */ - public function guess($path) - { - if (!is_file($path)) { - throw new FileNotFoundException($path); - } - - if (!is_readable($path)) { - throw new AccessDeniedException($path); - } - - if (!self::isSupported()) { - return null; - } - - if (!$finfo = new \finfo(FILEINFO_MIME_TYPE)) { - return null; - } - - return $finfo->file($path); - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesser.php b/core/includes/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesser.php deleted file mode 100644 index 23dd46324d7..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesser.php +++ /dev/null @@ -1,125 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File\MimeType; - -use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; -use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; - -/** - * A singleton mime type guesser. - * - * By default, all mime type guessers provided by the framework are installed - * (if available on the current OS/PHP setup). You can register custom - * guessers by calling the register() method on the singleton instance. - * - * <code> - * $guesser = MimeTypeGuesser::getInstance(); - * $guesser->register(new MyCustomMimeTypeGuesser()); - * </code> - * - * The last registered guesser is preferred over previously registered ones. - * - * @author Bernhard Schussek <bernhard.schussek@symfony.com> - */ -class MimeTypeGuesser implements MimeTypeGuesserInterface -{ - /** - * The singleton instance - * @var MimeTypeGuesser - */ - static private $instance = null; - - /** - * All registered MimeTypeGuesserInterface instances - * @var array - */ - protected $guessers = array(); - - /** - * Returns the singleton instance - * - * @return MimeTypeGuesser - */ - static public function getInstance() - { - if (null === self::$instance) { - self::$instance = new self(); - } - - return self::$instance; - } - - /** - * Registers all natively provided mime type guessers - */ - private function __construct() - { - if (FileBinaryMimeTypeGuesser::isSupported()) { - $this->register(new FileBinaryMimeTypeGuesser()); - } - - if (ContentTypeMimeTypeGuesser::isSupported()) { - $this->register(new ContentTypeMimeTypeGuesser()); - } - - if (FileinfoMimeTypeGuesser::isSupported()) { - $this->register(new FileinfoMimeTypeGuesser()); - } - } - - /** - * Registers a new mime type guesser - * - * When guessing, this guesser is preferred over previously registered ones. - * - * @param MimeTypeGuesserInterface $guesser - */ - public function register(MimeTypeGuesserInterface $guesser) - { - array_unshift($this->guessers, $guesser); - } - - /** - * Tries to guess the mime type of the given file - * - * The file is passed to each registered mime type guesser in reverse order - * of their registration (last registered is queried first). Once a guesser - * returns a value that is not NULL, this method terminates and returns the - * value. - * - * @param string $path The path to the file - * @return string The mime type or NULL, if none could be guessed - * @throws FileException If the file does not exist - */ - public function guess($path) - { - if (!is_file($path)) { - throw new FileNotFoundException($path); - } - - if (!is_readable($path)) { - throw new AccessDeniedException($path); - } - - $mimeType = null; - - foreach ($this->guessers as $guesser) { - $mimeType = $guesser->guess($path); - - if (null !== $mimeType) { - break; - } - } - - return $mimeType; - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesserInterface.php b/core/includes/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesserInterface.php deleted file mode 100644 index c11158396fe..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/File/MimeType/MimeTypeGuesserInterface.php +++ /dev/null @@ -1,30 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File\MimeType; - -/** - * Guesses the mime type of a file - * - * @author Bernhard Schussek <bernhard.schussek@symfony.com> - */ -interface MimeTypeGuesserInterface -{ - /** - * Guesses the mime type of the file with the given path - * - * @param string $path The path to the file - * @return string The mime type or NULL, if none could be guessed - * @throws FileNotFoundException If the file does not exist - * @throws AccessDeniedException If the file could not be read - */ - function guess($path); -} diff --git a/core/includes/Symfony/Component/HttpFoundation/File/UploadedFile.php b/core/includes/Symfony/Component/HttpFoundation/File/UploadedFile.php deleted file mode 100644 index 936ed705795..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/File/UploadedFile.php +++ /dev/null @@ -1,225 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\File; - -use Symfony\Component\HttpFoundation\File\Exception\FileException; -use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; - -/** - * A file uploaded through a form. - * - * @author Bernhard Schussek <bernhard.schussek@symfony.com> - * @author Florian Eckerstorfer <florian@eckerstorfer.org> - * @author Fabien Potencier <fabien@symfony.com> - * - * @api - */ -class UploadedFile extends File -{ - /** - * Whether the test mode is activated. - * - * Local files are used in test mode hence the code should not enforce HTTP uploads. - * - * @var Boolean - */ - private $test = false; - - /** - * The original name of the uploaded file. - * - * @var string - */ - private $originalName; - - /** - * The mime type provided by the uploader. - * - * @var string - */ - private $mimeType; - - /** - * The file size provided by the uploader. - * - * @var string - */ - private $size; - - /** - * The UPLOAD_ERR_XXX constant provided by the uploader. - * - * @var integer - */ - private $error; - - /** - * Accepts the information of the uploaded file as provided by the PHP global $_FILES. - * - * The file object is only created when the uploaded file is valid (i.e. when the - * isValid() method returns true). Otherwise the only methods that could be called - * on an UploadedFile instance are: - * - * * getClientOriginalName, - * * getClientMimeType, - * * isValid, - * * getError. - * - * Calling any other method on an non-valid instance will cause an unpredictable result. - * - * @param string $path The full temporary path to the file - * @param string $originalName The original file name - * @param string $mimeType The type of the file as provided by PHP - * @param integer $size The file size - * @param integer $error The error constant of the upload (one of PHP's UPLOAD_ERR_XXX constants) - * @param Boolean $test Whether the test mode is active - * - * @throws FileException If file_uploads is disabled - * @throws FileNotFoundException If the file does not exist - * - * @api - */ - public function __construct($path, $originalName, $mimeType = null, $size = null, $error = null, $test = false) - { - if (!ini_get('file_uploads')) { - throw new FileException(sprintf('Unable to create UploadedFile because "file_uploads" is disabled in your php.ini file (%s)', get_cfg_var('cfg_file_path'))); - } - - $this->originalName = basename($originalName); - $this->mimeType = $mimeType ?: 'application/octet-stream'; - $this->size = $size; - $this->error = $error ?: UPLOAD_ERR_OK; - $this->test = (Boolean) $test; - - if (UPLOAD_ERR_OK === $this->error) { - parent::__construct($path); - } - } - - /** - * Returns the original file name. - * - * It is extracted from the request from which the file has been uploaded. - * Then is should not be considered as a safe value. - * - * @return string|null The original name - * - * @api - */ - public function getClientOriginalName() - { - return $this->originalName; - } - - /** - * Returns the file mime type. - * - * It is extracted from the request from which the file has been uploaded. - * Then is should not be considered as a safe value. - * - * @return string|null The mime type - * - * @api - */ - public function getClientMimeType() - { - return $this->mimeType; - } - - /** - * Returns the file size. - * - * It is extracted from the request from which the file has been uploaded. - * Then is should not be considered as a safe value. - * - * @return integer|null The file size - * - * @api - */ - public function getClientSize() - { - return $this->size; - } - - /** - * Returns the upload error. - * - * If the upload was successful, the constant UPLOAD_ERR_OK is returned. - * Otherwise one of the other UPLOAD_ERR_XXX constants is returned. - * - * @return integer The upload error - * - * @api - */ - public function getError() - { - return $this->error; - } - - /** - * Returns whether the file was uploaded successfully. - * - * @return Boolean True if no error occurred during uploading - * - * @api - */ - public function isValid() - { - return $this->error === UPLOAD_ERR_OK; - } - - /** - * Moves the file to a new location. - * - * @param string $directory The destination folder - * @param string $name The new file name - * - * @return File A File object representing the new file - * - * @throws FileException if the file has not been uploaded via Http - * - * @api - */ - public function move($directory, $name = null) - { - if ($this->isValid() && ($this->test || is_uploaded_file($this->getPathname()))) { - return parent::move($directory, $name); - } - - throw new FileException(sprintf('The file "%s" has not been uploaded via Http', $this->getPathname())); - } - - /** - * Returns the maximum size of an uploaded file as configured in php.ini - * - * @return type The maximum size of an uploaded file in bytes - */ - static public function getMaxFilesize() - { - $max = trim(ini_get('upload_max_filesize')); - - if ('' === $max) { - return PHP_INT_MAX; - } - - switch (strtolower(substr($max, -1))) { - case 'g': - $max *= 1024; - case 'm': - $max *= 1024; - case 'k': - $max *= 1024; - } - - return (integer) $max; - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/FileBag.php b/core/includes/Symfony/Component/HttpFoundation/FileBag.php deleted file mode 100644 index 602cff2b3ce..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/FileBag.php +++ /dev/null @@ -1,157 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation; - -use Symfony\Component\HttpFoundation\File\UploadedFile; - -/** - * FileBag is a container for HTTP headers. - * - * @author Fabien Potencier <fabien@symfony.com> - * @author Bulat Shakirzyanov <mallluhuct@gmail.com> - * - * @api - */ -class FileBag extends ParameterBag -{ - static private $fileKeys = array('error', 'name', 'size', 'tmp_name', 'type'); - - /** - * Constructor. - * - * @param array $parameters An array of HTTP files - * - * @api - */ - public function __construct(array $parameters = array()) - { - $this->replace($parameters); - } - - /** - * (non-PHPdoc) - * @see Symfony\Component\HttpFoundation\ParameterBag::replace() - * - * @api - */ - public function replace(array $files = array()) - { - $this->parameters = array(); - $this->add($files); - } - - /** - * (non-PHPdoc) - * @see Symfony\Component\HttpFoundation\ParameterBag::set() - * - * @api - */ - public function set($key, $value) - { - if (is_array($value) || $value instanceof UploadedFile) { - parent::set($key, $this->convertFileInformation($value)); - } else { - throw new \InvalidArgumentException('An uploaded file must be an array or an instance of UploadedFile.'); - } - } - - /** - * (non-PHPdoc) - * @see Symfony\Component\HttpFoundation\ParameterBag::add() - * - * @api - */ - public function add(array $files = array()) - { - foreach ($files as $key => $file) { - $this->set($key, $file); - } - } - - /** - * Converts uploaded files to UploadedFile instances. - * - * @param array|UploadedFile $file A (multi-dimensional) array of uploaded file information - * - * @return array A (multi-dimensional) array of UploadedFile instances - */ - protected function convertFileInformation($file) - { - if ($file instanceof UploadedFile) { - return $file; - } - - $file = $this->fixPhpFilesArray($file); - if (is_array($file)) { - $keys = array_keys($file); - sort($keys); - - if ($keys == self::$fileKeys) { - if (UPLOAD_ERR_NO_FILE == $file['error']) { - $file = null; - } else { - $file = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['size'], $file['error']); - } - } else { - $file = array_map(array($this, 'convertFileInformation'), $file); - } - } - - return $file; - } - - /** - * Fixes a malformed PHP $_FILES array. - * - * PHP has a bug that the format of the $_FILES array differs, depending on - * whether the uploaded file fields had normal field names or array-like - * field names ("normal" vs. "parent[child]"). - * - * This method fixes the array to look like the "normal" $_FILES array. - * - * It's safe to pass an already converted array, in which case this method - * just returns the original array unmodified. - * - * @param array $data - * @return array - */ - protected function fixPhpFilesArray($data) - { - if (!is_array($data)) { - return $data; - } - - $keys = array_keys($data); - sort($keys); - - if (self::$fileKeys != $keys || !isset($data['name']) || !is_array($data['name'])) { - return $data; - } - - $files = $data; - foreach (self::$fileKeys as $k) { - unset($files[$k]); - } - - foreach (array_keys($data['name']) as $key) { - $files[$key] = $this->fixPhpFilesArray(array( - 'error' => $data['error'][$key], - 'name' => $data['name'][$key], - 'type' => $data['type'][$key], - 'tmp_name' => $data['tmp_name'][$key], - 'size' => $data['size'][$key] - )); - } - - return $files; - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/HeaderBag.php b/core/includes/Symfony/Component/HttpFoundation/HeaderBag.php deleted file mode 100644 index f614b094fd2..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/HeaderBag.php +++ /dev/null @@ -1,306 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation; - -/** - * HeaderBag is a container for HTTP headers. - * - * @author Fabien Potencier <fabien@symfony.com> - * - * @api - */ -class HeaderBag -{ - protected $headers; - protected $cacheControl; - - /** - * Constructor. - * - * @param array $headers An array of HTTP headers - * - * @api - */ - public function __construct(array $headers = array()) - { - $this->cacheControl = array(); - $this->headers = array(); - foreach ($headers as $key => $values) { - $this->set($key, $values); - } - } - - /** - * Returns the headers as a string. - * - * @return string The headers - */ - public function __toString() - { - if (!$this->headers) { - return ''; - } - - $beautifier = function ($name) { - return preg_replace_callback('/\-(.)/', function ($match) { return '-'.strtoupper($match[1]); }, ucfirst($name)); - }; - - $max = max(array_map('strlen', array_keys($this->headers))) + 1; - $content = ''; - ksort($this->headers); - foreach ($this->headers as $name => $values) { - foreach ($values as $value) { - $content .= sprintf("%-{$max}s %s\r\n", $beautifier($name).':', $value); - } - } - - return $content; - } - - /** - * Returns the headers. - * - * @return array An array of headers - * - * @api - */ - public function all() - { - return $this->headers; - } - - /** - * Returns the parameter keys. - * - * @return array An array of parameter keys - * - * @api - */ - public function keys() - { - return array_keys($this->headers); - } - - /** - * Replaces the current HTTP headers by a new set. - * - * @param array $headers An array of HTTP headers - * - * @api - */ - public function replace(array $headers = array()) - { - $this->headers = array(); - $this->add($headers); - } - - /** - * Adds new headers the current HTTP headers set. - * - * @param array $headers An array of HTTP headers - * - * @api - */ - public function add(array $headers) - { - foreach ($headers as $key => $values) { - $this->set($key, $values); - } - } - - /** - * Returns a header value by name. - * - * @param string $key The header name - * @param mixed $default The default value - * @param Boolean $first Whether to return the first value or all header values - * - * @return string|array The first header value if $first is true, an array of values otherwise - * - * @api - */ - public function get($key, $default = null, $first = true) - { - $key = strtr(strtolower($key), '_', '-'); - - if (!array_key_exists($key, $this->headers)) { - if (null === $default) { - return $first ? null : array(); - } - - return $first ? $default : array($default); - } - - if ($first) { - return count($this->headers[$key]) ? $this->headers[$key][0] : $default; - } - - return $this->headers[$key]; - } - - /** - * Sets a header by name. - * - * @param string $key The key - * @param string|array $values The value or an array of values - * @param Boolean $replace Whether to replace the actual value of not (true by default) - * - * @api - */ - public function set($key, $values, $replace = true) - { - $key = strtr(strtolower($key), '_', '-'); - - $values = (array) $values; - - if (true === $replace || !isset($this->headers[$key])) { - $this->headers[$key] = $values; - } else { - $this->headers[$key] = array_merge($this->headers[$key], $values); - } - - if ('cache-control' === $key) { - $this->cacheControl = $this->parseCacheControl($values[0]); - } - } - - /** - * Returns true if the HTTP header is defined. - * - * @param string $key The HTTP header - * - * @return Boolean true if the parameter exists, false otherwise - * - * @api - */ - public function has($key) - { - return array_key_exists(strtr(strtolower($key), '_', '-'), $this->headers); - } - - /** - * Returns true if the given HTTP header contains the given value. - * - * @param string $key The HTTP header name - * @param string $value The HTTP value - * - * @return Boolean true if the value is contained in the header, false otherwise - * - * @api - */ - public function contains($key, $value) - { - return in_array($value, $this->get($key, null, false)); - } - - /** - * Removes a header. - * - * @param string $key The HTTP header name - * - * @api - */ - public function remove($key) - { - $key = strtr(strtolower($key), '_', '-'); - - unset($this->headers[$key]); - - if ('cache-control' === $key) { - $this->cacheControl = array(); - } - } - - /** - * Returns the HTTP header value converted to a date. - * - * @param string $key The parameter key - * @param \DateTime $default The default value - * - * @return \DateTime The filtered value - * - * @api - */ - public function getDate($key, \DateTime $default = null) - { - if (null === $value = $this->get($key)) { - return $default; - } - - if (false === $date = \DateTime::createFromFormat(DATE_RFC2822, $value)) { - throw new \RuntimeException(sprintf('The %s HTTP header is not parseable (%s).', $key, $value)); - } - - return $date; - } - - public function addCacheControlDirective($key, $value = true) - { - $this->cacheControl[$key] = $value; - - $this->set('Cache-Control', $this->getCacheControlHeader()); - } - - public function hasCacheControlDirective($key) - { - return array_key_exists($key, $this->cacheControl); - } - - public function getCacheControlDirective($key) - { - return array_key_exists($key, $this->cacheControl) ? $this->cacheControl[$key] : null; - } - - public function removeCacheControlDirective($key) - { - unset($this->cacheControl[$key]); - - $this->set('Cache-Control', $this->getCacheControlHeader()); - } - - protected function getCacheControlHeader() - { - $parts = array(); - ksort($this->cacheControl); - foreach ($this->cacheControl as $key => $value) { - if (true === $value) { - $parts[] = $key; - } else { - if (preg_match('#[^a-zA-Z0-9._-]#', $value)) { - $value = '"'.$value.'"'; - } - - $parts[] = "$key=$value"; - } - } - - return implode(', ', $parts); - } - - /** - * Parses a Cache-Control HTTP header. - * - * @param string $header The value of the Cache-Control HTTP header - * - * @return array An array representing the attribute values - */ - protected function parseCacheControl($header) - { - $cacheControl = array(); - preg_match_all('#([a-zA-Z][a-zA-Z_-]*)\s*(?:=(?:"([^"]*)"|([^ \t",;]*)))?#', $header, $matches, PREG_SET_ORDER); - foreach ($matches as $match) { - $cacheControl[strtolower($match[1])] = isset($match[2]) && $match[2] ? $match[2] : (isset($match[3]) ? $match[3] : true); - } - - return $cacheControl; - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/LICENSE b/core/includes/Symfony/Component/HttpFoundation/LICENSE deleted file mode 100644 index 89df4481b95..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2004-2011 Fabien Potencier - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/core/includes/Symfony/Component/HttpFoundation/ParameterBag.php b/core/includes/Symfony/Component/HttpFoundation/ParameterBag.php deleted file mode 100644 index 3a38b5fab33..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/ParameterBag.php +++ /dev/null @@ -1,245 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation; - -/** - * ParameterBag is a container for key/value pairs. - * - * @author Fabien Potencier <fabien@symfony.com> - * - * @api - */ -class ParameterBag -{ - protected $parameters; - - /** - * Constructor. - * - * @param array $parameters An array of parameters - * - * @api - */ - public function __construct(array $parameters = array()) - { - $this->parameters = $parameters; - } - - /** - * Returns the parameters. - * - * @return array An array of parameters - * - * @api - */ - public function all() - { - return $this->parameters; - } - - /** - * Returns the parameter keys. - * - * @return array An array of parameter keys - * - * @api - */ - public function keys() - { - return array_keys($this->parameters); - } - - /** - * Replaces the current parameters by a new set. - * - * @param array $parameters An array of parameters - * - * @api - */ - public function replace(array $parameters = array()) - { - $this->parameters = $parameters; - } - - /** - * Adds parameters. - * - * @param array $parameters An array of parameters - * - * @api - */ - public function add(array $parameters = array()) - { - $this->parameters = array_replace($this->parameters, $parameters); - } - - /** - * Returns a parameter by name. - * - * @param string $path The key - * @param mixed $default The default value - * @param boolean $deep - * - * @api - */ - public function get($path, $default = null, $deep = false) - { - if (!$deep || false === $pos = strpos($path, '[')) { - return array_key_exists($path, $this->parameters) ? $this->parameters[$path] : $default; - } - - $root = substr($path, 0, $pos); - if (!array_key_exists($root, $this->parameters)) { - return $default; - } - - $value = $this->parameters[$root]; - $currentKey = null; - for ($i=$pos,$c=strlen($path); $i<$c; $i++) { - $char = $path[$i]; - - if ('[' === $char) { - if (null !== $currentKey) { - throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "[" at position %d.', $i)); - } - - $currentKey = ''; - } else if (']' === $char) { - if (null === $currentKey) { - throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "]" at position %d.', $i)); - } - - if (!is_array($value) || !array_key_exists($currentKey, $value)) { - return $default; - } - - $value = $value[$currentKey]; - $currentKey = null; - } else { - if (null === $currentKey) { - throw new \InvalidArgumentException(sprintf('Malformed path. Unexpected "%s" at position %d.', $char, $i)); - } - - $currentKey .= $char; - } - } - - if (null !== $currentKey) { - throw new \InvalidArgumentException(sprintf('Malformed path. Path must end with "]".')); - } - - return $value; - } - - /** - * Sets a parameter by name. - * - * @param string $key The key - * @param mixed $value The value - * - * @api - */ - public function set($key, $value) - { - $this->parameters[$key] = $value; - } - - /** - * Returns true if the parameter is defined. - * - * @param string $key The key - * - * @return Boolean true if the parameter exists, false otherwise - * - * @api - */ - public function has($key) - { - return array_key_exists($key, $this->parameters); - } - - /** - * Removes a parameter. - * - * @param string $key The key - * - * @api - */ - public function remove($key) - { - unset($this->parameters[$key]); - } - - /** - * Returns the alphabetic characters of the parameter value. - * - * @param string $key The parameter key - * @param mixed $default The default value - * @param boolean $deep - * - * @return string The filtered value - * - * @api - */ - public function getAlpha($key, $default = '', $deep = false) - { - return preg_replace('/[^[:alpha:]]/', '', $this->get($key, $default, $deep)); - } - - /** - * Returns the alphabetic characters and digits of the parameter value. - * - * @param string $key The parameter key - * @param mixed $default The default value - * @param boolean $deep - * - * @return string The filtered value - * - * @api - */ - public function getAlnum($key, $default = '', $deep = false) - { - return preg_replace('/[^[:alnum:]]/', '', $this->get($key, $default, $deep)); - } - - /** - * Returns the digits of the parameter value. - * - * @param string $key The parameter key - * @param mixed $default The default value - * @param boolean $deep - * - * @return string The filtered value - * - * @api - */ - public function getDigits($key, $default = '', $deep = false) - { - return preg_replace('/[^[:digit:]]/', '', $this->get($key, $default, $deep)); - } - - /** - * Returns the parameter value converted to integer. - * - * @param string $key The parameter key - * @param mixed $default The default value - * @param boolean $deep - * - * @return string The filtered value - * - * @api - */ - public function getInt($key, $default = 0, $deep = false) - { - return (int) $this->get($key, $default, $deep); - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/RedirectResponse.php b/core/includes/Symfony/Component/HttpFoundation/RedirectResponse.php deleted file mode 100644 index 3318b12bb38..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/RedirectResponse.php +++ /dev/null @@ -1,60 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation; - -/** - * RedirectResponse represents an HTTP response doing a redirect. - * - * @author Fabien Potencier <fabien@symfony.com> - * - * @api - */ -class RedirectResponse extends Response -{ - /** - * Creates a redirect response so that it conforms to the rules defined for a redirect status code. - * - * @param string $url The URL to redirect to - * @param integer $status The status code (302 by default) - * - * @see http://tools.ietf.org/html/rfc2616#section-10.3 - * - * @api - */ - public function __construct($url, $status = 302) - { - if (empty($url)) { - throw new \InvalidArgumentException('Cannot redirect to an empty URL.'); - } - - parent::__construct( - sprintf('<!DOCTYPE html> -<html> - <head> - <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> - <meta http-equiv="refresh" content="1;url=%1$s" /> - - <title>Redirecting to %1$s</title> - </head> - <body> - Redirecting to <a href="%1$s">%1$s</a>. - </body> -</html>', htmlspecialchars($url, ENT_QUOTES, 'UTF-8')), - $status, - array('Location' => $url) - ); - - if (!$this->isRedirect()) { - throw new \InvalidArgumentException(sprintf('The HTTP status code is not a redirect ("%s" given).', $status)); - } - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/Request.php b/core/includes/Symfony/Component/HttpFoundation/Request.php deleted file mode 100644 index e66abb7484e..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/Request.php +++ /dev/null @@ -1,1217 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation; - -/** - * Request represents an HTTP request. - * - * @author Fabien Potencier <fabien@symfony.com> - * - * @api - */ -class Request -{ - static protected $trustProxy = false; - - /** - * @var \Symfony\Component\HttpFoundation\ParameterBag - * - * @api - */ - public $attributes; - - /** - * @var \Symfony\Component\HttpFoundation\ParameterBag - * - * @api - */ - public $request; - - /** - * @var \Symfony\Component\HttpFoundation\ParameterBag - * - * @api - */ - public $query; - - /** - * @var \Symfony\Component\HttpFoundation\ServerBag - * - * @api - */ - public $server; - - /** - * @var \Symfony\Component\HttpFoundation\FileBag - * - * @api - */ - public $files; - - /** - * @var \Symfony\Component\HttpFoundation\ParameterBag - * - * @api - */ - public $cookies; - - /** - * @var \Symfony\Component\HttpFoundation\HeaderBag - * - * @api - */ - public $headers; - - protected $content; - protected $languages; - protected $charsets; - protected $acceptableContentTypes; - protected $pathInfo; - protected $requestUri; - protected $baseUrl; - protected $basePath; - protected $method; - protected $format; - protected $session; - - static protected $formats; - - /** - * Constructor. - * - * @param array $query The GET parameters - * @param array $request The POST parameters - * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) - * @param array $cookies The COOKIE parameters - * @param array $files The FILES parameters - * @param array $server The SERVER parameters - * @param string $content The raw body data - * - * @api - */ - public function __construct(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) - { - $this->initialize($query, $request, $attributes, $cookies, $files, $server, $content); - } - - /** - * Sets the parameters for this request. - * - * This method also re-initializes all properties. - * - * @param array $query The GET parameters - * @param array $request The POST parameters - * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) - * @param array $cookies The COOKIE parameters - * @param array $files The FILES parameters - * @param array $server The SERVER parameters - * @param string $content The raw body data - * - * @api - */ - public function initialize(array $query = array(), array $request = array(), array $attributes = array(), array $cookies = array(), array $files = array(), array $server = array(), $content = null) - { - $this->request = new ParameterBag($request); - $this->query = new ParameterBag($query); - $this->attributes = new ParameterBag($attributes); - $this->cookies = new ParameterBag($cookies); - $this->files = new FileBag($files); - $this->server = new ServerBag($server); - $this->headers = new HeaderBag($this->server->getHeaders()); - - $this->content = $content; - $this->languages = null; - $this->charsets = null; - $this->acceptableContentTypes = null; - $this->pathInfo = null; - $this->requestUri = null; - $this->baseUrl = null; - $this->basePath = null; - $this->method = null; - $this->format = null; - } - - /** - * Creates a new request with values from PHP's super globals. - * - * @return Request A new request - * - * @api - */ - static public function createFromGlobals() - { - $request = new static($_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER); - - if (0 === strpos($request->server->get('CONTENT_TYPE'), 'application/x-www-form-urlencoded') - && in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), array('PUT', 'DELETE')) - ) { - parse_str($request->getContent(), $data); - $request->request = new ParameterBag($data); - } - - return $request; - } - - /** - * Creates a Request based on a given URI and configuration. - * - * @param string $uri The URI - * @param string $method The HTTP method - * @param array $parameters The request (GET) or query (POST) parameters - * @param array $cookies The request cookies ($_COOKIE) - * @param array $files The request files ($_FILES) - * @param array $server The server parameters ($_SERVER) - * @param string $content The raw body data - * - * @return Request A Request instance - * - * @api - */ - static public function create($uri, $method = 'GET', $parameters = array(), $cookies = array(), $files = array(), $server = array(), $content = null) - { - $defaults = array( - 'SERVER_NAME' => 'localhost', - 'SERVER_PORT' => 80, - 'HTTP_HOST' => 'localhost', - 'HTTP_USER_AGENT' => 'Symfony/2.X', - 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', - 'HTTP_ACCEPT_LANGUAGE' => 'en-us,en;q=0.5', - 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', - 'REMOTE_ADDR' => '127.0.0.1', - 'SCRIPT_NAME' => '', - 'SCRIPT_FILENAME' => '', - 'SERVER_PROTOCOL' => 'HTTP/1.1', - 'REQUEST_TIME' => time(), - ); - - $components = parse_url($uri); - if (isset($components['host'])) { - $defaults['SERVER_NAME'] = $components['host']; - $defaults['HTTP_HOST'] = $components['host']; - } - - if (isset($components['scheme'])) { - if ('https' === $components['scheme']) { - $defaults['HTTPS'] = 'on'; - $defaults['SERVER_PORT'] = 443; - } - } - - if (isset($components['port'])) { - $defaults['SERVER_PORT'] = $components['port']; - $defaults['HTTP_HOST'] = $defaults['HTTP_HOST'].':'.$components['port']; - } - - if (!isset($components['path'])) { - $components['path'] = ''; - } - - if (in_array(strtoupper($method), array('POST', 'PUT', 'DELETE'))) { - $request = $parameters; - $query = array(); - $defaults['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'; - } else { - $request = array(); - $query = $parameters; - if (false !== $pos = strpos($uri, '?')) { - $qs = substr($uri, $pos + 1); - parse_str($qs, $params); - - $query = array_merge($params, $query); - } - } - - $queryString = isset($components['query']) ? html_entity_decode($components['query']) : ''; - parse_str($queryString, $qs); - if (is_array($qs)) { - $query = array_replace($qs, $query); - } - - $uri = $components['path'].($queryString ? '?'.$queryString : ''); - - $server = array_replace($defaults, $server, array( - 'REQUEST_METHOD' => strtoupper($method), - 'PATH_INFO' => '', - 'REQUEST_URI' => $uri, - 'QUERY_STRING' => $queryString, - )); - - return new static($query, $request, array(), $cookies, $files, $server, $content); - } - - /** - * Clones a request and overrides some of its parameters. - * - * @param array $query The GET parameters - * @param array $request The POST parameters - * @param array $attributes The request attributes (parameters parsed from the PATH_INFO, ...) - * @param array $cookies The COOKIE parameters - * @param array $files The FILES parameters - * @param array $server The SERVER parameters - * - * @api - */ - public function duplicate(array $query = null, array $request = null, array $attributes = null, array $cookies = null, array $files = null, array $server = null) - { - $dup = clone $this; - if ($query !== null) { - $dup->query = new ParameterBag($query); - } - if ($request !== null) { - $dup->request = new ParameterBag($request); - } - if ($attributes !== null) { - $dup->attributes = new ParameterBag($attributes); - } - if ($cookies !== null) { - $dup->cookies = new ParameterBag($cookies); - } - if ($files !== null) { - $dup->files = new FileBag($files); - } - if ($server !== null) { - $dup->server = new ServerBag($server); - $dup->headers = new HeaderBag($dup->server->getHeaders()); - } - $dup->languages = null; - $dup->charsets = null; - $dup->acceptableContentTypes = null; - $dup->pathInfo = null; - $dup->requestUri = null; - $dup->baseUrl = null; - $dup->basePath = null; - $dup->method = null; - $dup->format = null; - - return $dup; - } - - /** - * Clones the current request. - * - * Note that the session is not cloned as duplicated requests - * are most of the time sub-requests of the main one. - */ - public function __clone() - { - $this->query = clone $this->query; - $this->request = clone $this->request; - $this->attributes = clone $this->attributes; - $this->cookies = clone $this->cookies; - $this->files = clone $this->files; - $this->server = clone $this->server; - $this->headers = clone $this->headers; - } - - /** - * Returns the request as a string. - * - * @return string The request - */ - public function __toString() - { - return - sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n". - $this->headers."\r\n". - $this->getContent(); - } - - /** - * Overrides the PHP global variables according to this request instance. - * - * It overrides $_GET, $_POST, $_REQUEST, $_SERVER, $_COOKIE, and $_FILES. - * - * @api - */ - public function overrideGlobals() - { - $_GET = $this->query->all(); - $_POST = $this->request->all(); - $_SERVER = $this->server->all(); - $_COOKIE = $this->cookies->all(); - // FIXME: populate $_FILES - - foreach ($this->headers->all() as $key => $value) { - $key = strtoupper(str_replace('-', '_', $key)); - if (in_array($key, array('CONTENT_TYPE', 'CONTENT_LENGTH'))) { - $_SERVER[$key] = implode(', ', $value); - } else { - $_SERVER['HTTP_'.$key] = implode(', ', $value); - } - } - - // FIXME: should read variables_order and request_order - // to know which globals to merge and in which order - $_REQUEST = array_merge($_GET, $_POST); - } - - /** - * Trusts $_SERVER entries coming from proxies. - * - * You should only call this method if your application - * is hosted behind a reverse proxy that you manage. - * - * @api - */ - static public function trustProxyData() - { - self::$trustProxy = true; - } - - /** - * Gets a "parameter" value. - * - * This method is mainly useful for libraries that want to provide some flexibility. - * - * Order of precedence: GET, PATH, POST, COOKIE - * Avoid using this method in controllers: - * * slow - * * prefer to get from a "named" source - * - * @param string $key the key - * @param mixed $default the default value - * @param type $deep is parameter deep in multidimensional array - * - * @return mixed - */ - public function get($key, $default = null, $deep = false) - { - return $this->query->get($key, $this->attributes->get($key, $this->request->get($key, $default, $deep), $deep), $deep); - } - - /** - * Gets the Session. - * - * @return Session|null The session - * - * @api - */ - public function getSession() - { - return $this->session; - } - - /** - * Whether the request contains a Session which was started in one of the - * previous requests. - * - * @return boolean - * - * @api - */ - public function hasPreviousSession() - { - // the check for $this->session avoids malicious users trying to fake a session cookie with proper name - return $this->cookies->has(session_name()) && null !== $this->session; - } - - /** - * Whether the request contains a Session object. - * - * @return boolean - * - * @api - */ - public function hasSession() - { - return null !== $this->session; - } - - /** - * Sets the Session. - * - * @param Session $session The Session - * - * @api - */ - public function setSession(Session $session) - { - $this->session = $session; - } - - /** - * Returns the client IP address. - * - * @param Boolean $proxy Whether the current request has been made behind a proxy or not - * - * @return string The client IP address - * - * @api - */ - public function getClientIp($proxy = false) - { - if ($proxy) { - if ($this->server->has('HTTP_CLIENT_IP')) { - return $this->server->get('HTTP_CLIENT_IP'); - } elseif (self::$trustProxy && $this->server->has('HTTP_X_FORWARDED_FOR')) { - return $this->server->get('HTTP_X_FORWARDED_FOR'); - } - } - - return $this->server->get('REMOTE_ADDR'); - } - - /** - * Returns current script name. - * - * @return string - * - * @api - */ - public function getScriptName() - { - return $this->server->get('SCRIPT_NAME', $this->server->get('ORIG_SCRIPT_NAME', '')); - } - - /** - * Returns the path being requested relative to the executed script. - * - * The path info always starts with a /. - * - * Suppose this request is instantiated from /mysite on localhost: - * - * * http://localhost/mysite returns an empty string - * * http://localhost/mysite/about returns '/about' - * * http://localhost/mysite/about?var=1 returns '/about' - * - * @return string - * - * @api - */ - public function getPathInfo() - { - if (null === $this->pathInfo) { - $this->pathInfo = $this->preparePathInfo(); - } - - return $this->pathInfo; - } - - /** - * Returns the root path from which this request is executed. - * - * Suppose that an index.php file instantiates this request object: - * - * * http://localhost/index.php returns an empty string - * * http://localhost/index.php/page returns an empty string - * * http://localhost/web/index.php return '/web' - * - * @return string - * - * @api - */ - public function getBasePath() - { - if (null === $this->basePath) { - $this->basePath = $this->prepareBasePath(); - } - - return $this->basePath; - } - - /** - * Returns the root url from which this request is executed. - * - * The base URL never ends with a /. - * - * This is similar to getBasePath(), except that it also includes the - * script filename (e.g. index.php) if one exists. - * - * @return string - * - * @api - */ - public function getBaseUrl() - { - if (null === $this->baseUrl) { - $this->baseUrl = $this->prepareBaseUrl(); - } - - return $this->baseUrl; - } - - /** - * Gets the request's scheme. - * - * @return string - * - * @api - */ - public function getScheme() - { - return $this->isSecure() ? 'https' : 'http'; - } - - /** - * Returns the port on which the request is made. - * - * @return string - * - * @api - */ - public function getPort() - { - return $this->headers->get('X-Forwarded-Port') ?: $this->server->get('SERVER_PORT'); - } - - /** - * Returns the HTTP host being requested. - * - * The port name will be appended to the host if it's non-standard. - * - * @return string - * - * @api - */ - public function getHttpHost() - { - $scheme = $this->getScheme(); - $port = $this->getPort(); - - if (('http' == $scheme && $port == 80) || ('https' == $scheme && $port == 443)) { - return $this->getHost(); - } - - return $this->getHost().':'.$port; - } - - /** - * Returns the requested URI. - * - * @return string - * - * @api - */ - public function getRequestUri() - { - if (null === $this->requestUri) { - $this->requestUri = $this->prepareRequestUri(); - } - - return $this->requestUri; - } - - /** - * Generates a normalized URI for the Request. - * - * @return string A normalized URI for the Request - * - * @see getQueryString() - * - * @api - */ - public function getUri() - { - $qs = $this->getQueryString(); - if (null !== $qs) { - $qs = '?'.$qs; - } - - return $this->getScheme().'://'.$this->getHttpHost().$this->getBaseUrl().$this->getPathInfo().$qs; - } - - /** - * Generates a normalized URI for the given path. - * - * @param string $path A path to use instead of the current one - * - * @return string The normalized URI for the path - * - * @api - */ - public function getUriForPath($path) - { - return $this->getScheme().'://'.$this->getHttpHost().$this->getBaseUrl().$path; - } - - /** - * Generates the normalized query string for the Request. - * - * It builds a normalized query string, where keys/value pairs are alphabetized - * and have consistent escaping. - * - * @return string A normalized query string for the Request - * - * @api - */ - public function getQueryString() - { - if (!$qs = $this->server->get('QUERY_STRING')) { - return null; - } - - $parts = array(); - $order = array(); - - foreach (explode('&', $qs) as $segment) { - if (false === strpos($segment, '=')) { - $parts[] = $segment; - $order[] = $segment; - } else { - $tmp = explode('=', rawurldecode($segment), 2); - $parts[] = rawurlencode($tmp[0]).'='.rawurlencode($tmp[1]); - $order[] = $tmp[0]; - } - } - array_multisort($order, SORT_ASC, $parts); - - return implode('&', $parts); - } - - /** - * Checks whether the request is secure or not. - * - * @return Boolean - * - * @api - */ - public function isSecure() - { - return ( - (strtolower($this->server->get('HTTPS')) == 'on' || $this->server->get('HTTPS') == 1) - || - (self::$trustProxy && strtolower($this->headers->get('SSL_HTTPS')) == 'on' || $this->headers->get('SSL_HTTPS') == 1) - || - (self::$trustProxy && strtolower($this->headers->get('X_FORWARDED_PROTO')) == 'https') - ); - } - - /** - * Returns the host name. - * - * @return string - * - * @api - */ - public function getHost() - { - if (self::$trustProxy && $host = $this->headers->get('X_FORWARDED_HOST')) { - $elements = explode(',', $host); - - $host = trim($elements[count($elements) - 1]); - } else { - if (!$host = $this->headers->get('HOST')) { - if (!$host = $this->server->get('SERVER_NAME')) { - $host = $this->server->get('SERVER_ADDR', ''); - } - } - } - - // Remove port number from host - $host = preg_replace('/:\d+$/', '', $host); - - return trim($host); - } - - /** - * Sets the request method. - * - * @param string $method - * - * @api - */ - public function setMethod($method) - { - $this->method = null; - $this->server->set('REQUEST_METHOD', $method); - } - - /** - * Gets the request method. - * - * The method is always an uppercased string. - * - * @return string The request method - * - * @api - */ - public function getMethod() - { - if (null === $this->method) { - $this->method = strtoupper($this->server->get('REQUEST_METHOD', 'GET')); - if ('POST' === $this->method) { - $this->method = strtoupper($this->headers->get('X-HTTP-METHOD-OVERRIDE', $this->request->get('_method', 'POST'))); - } - } - - return $this->method; - } - - /** - * Gets the mime type associated with the format. - * - * @param string $format The format - * - * @return string The associated mime type (null if not found) - * - * @api - */ - public function getMimeType($format) - { - if (null === static::$formats) { - static::initializeFormats(); - } - - return isset(static::$formats[$format]) ? static::$formats[$format][0] : null; - } - - /** - * Gets the format associated with the mime type. - * - * @param string $mimeType The associated mime type - * - * @return string The format (null if not found) - * - * @api - */ - public function getFormat($mimeType) - { - if (false !== $pos = strpos($mimeType, ';')) { - $mimeType = substr($mimeType, 0, $pos); - } - - if (null === static::$formats) { - static::initializeFormats(); - } - - foreach (static::$formats as $format => $mimeTypes) { - if (in_array($mimeType, (array) $mimeTypes)) { - return $format; - } - } - - return null; - } - - /** - * Associates a format with mime types. - * - * @param string $format The format - * @param string|array $mimeTypes The associated mime types (the preferred one must be the first as it will be used as the content type) - * - * @api - */ - public function setFormat($format, $mimeTypes) - { - if (null === static::$formats) { - static::initializeFormats(); - } - - static::$formats[$format] = is_array($mimeTypes) ? $mimeTypes : array($mimeTypes); - } - - /** - * Gets the request format. - * - * Here is the process to determine the format: - * - * * format defined by the user (with setRequestFormat()) - * * _format request parameter - * * $default - * - * @param string $default The default format - * - * @return string The request format - * - * @api - */ - public function getRequestFormat($default = 'html') - { - if (null === $this->format) { - $this->format = $this->get('_format', $default); - } - - return $this->format; - } - - /** - * Sets the request format. - * - * @param string $format The request format. - * - * @api - */ - public function setRequestFormat($format) - { - $this->format = $format; - } - - /** - * Checks whether the method is safe or not. - * - * @return Boolean - * - * @api - */ - public function isMethodSafe() - { - return in_array($this->getMethod(), array('GET', 'HEAD')); - } - - /** - * Returns the request body content. - * - * @param Boolean $asResource If true, a resource will be returned - * - * @return string|resource The request body content or a resource to read the body stream. - */ - public function getContent($asResource = false) - { - if (false === $this->content || (true === $asResource && null !== $this->content)) { - throw new \LogicException('getContent() can only be called once when using the resource return type.'); - } - - if (true === $asResource) { - $this->content = false; - - return fopen('php://input', 'rb'); - } - - if (null === $this->content) { - $this->content = file_get_contents('php://input'); - } - - return $this->content; - } - - /** - * Gets the Etags. - * - * @return array The entity tags - */ - public function getETags() - { - return preg_split('/\s*,\s*/', $this->headers->get('if_none_match'), null, PREG_SPLIT_NO_EMPTY); - } - - public function isNoCache() - { - return $this->headers->hasCacheControlDirective('no-cache') || 'no-cache' == $this->headers->get('Pragma'); - } - - /** - * Returns the preferred language. - * - * @param array $locales An array of ordered available locales - * - * @return string The preferred locale - * - * @api - */ - public function getPreferredLanguage(array $locales = null) - { - $preferredLanguages = $this->getLanguages(); - - if (null === $locales) { - return isset($preferredLanguages[0]) ? $preferredLanguages[0] : null; - } - - if (!$preferredLanguages) { - return $locales[0]; - } - - $preferredLanguages = array_values(array_intersect($preferredLanguages, $locales)); - - return isset($preferredLanguages[0]) ? $preferredLanguages[0] : $locales[0]; - } - - /** - * Gets a list of languages acceptable by the client browser. - * - * @return array Languages ordered in the user browser preferences - * - * @api - */ - public function getLanguages() - { - if (null !== $this->languages) { - return $this->languages; - } - - $languages = $this->splitHttpAcceptHeader($this->headers->get('Accept-Language')); - $this->languages = array(); - foreach ($languages as $lang => $q) { - if (strstr($lang, '-')) { - $codes = explode('-', $lang); - if ($codes[0] == 'i') { - // Language not listed in ISO 639 that are not variants - // of any listed language, which can be registered with the - // i-prefix, such as i-cherokee - if (count($codes) > 1) { - $lang = $codes[1]; - } - } else { - for ($i = 0, $max = count($codes); $i < $max; $i++) { - if ($i == 0) { - $lang = strtolower($codes[0]); - } else { - $lang .= '_'.strtoupper($codes[$i]); - } - } - } - } - - $this->languages[] = $lang; - } - - return $this->languages; - } - - /** - * Gets a list of charsets acceptable by the client browser. - * - * @return array List of charsets in preferable order - * - * @api - */ - public function getCharsets() - { - if (null !== $this->charsets) { - return $this->charsets; - } - - return $this->charsets = array_keys($this->splitHttpAcceptHeader($this->headers->get('Accept-Charset'))); - } - - /** - * Gets a list of content types acceptable by the client browser - * - * @return array List of content types in preferable order - * - * @api - */ - public function getAcceptableContentTypes() - { - if (null !== $this->acceptableContentTypes) { - return $this->acceptableContentTypes; - } - - return $this->acceptableContentTypes = array_keys($this->splitHttpAcceptHeader($this->headers->get('Accept'))); - } - - /** - * Returns true if the request is a XMLHttpRequest. - * - * It works if your JavaScript library set an X-Requested-With HTTP header. - * It is known to work with Prototype, Mootools, jQuery. - * - * @return Boolean true if the request is an XMLHttpRequest, false otherwise - * - * @api - */ - public function isXmlHttpRequest() - { - return 'XMLHttpRequest' == $this->headers->get('X-Requested-With'); - } - - /** - * Splits an Accept-* HTTP header. - * - * @param string $header Header to split - */ - public function splitHttpAcceptHeader($header) - { - if (!$header) { - return array(); - } - - $values = array(); - foreach (array_filter(explode(',', $header)) as $value) { - // Cut off any q-value that might come after a semi-colon - if ($pos = strpos($value, ';')) { - $q = (float) trim(substr($value, strpos($value, '=') + 1)); - $value = trim(substr($value, 0, $pos)); - } else { - $q = 1; - } - - if (0 < $q) { - $values[trim($value)] = $q; - } - } - - arsort($values); - reset($values); - - return $values; - } - - /* - * The following methods are derived from code of the Zend Framework (1.10dev - 2010-01-24) - * - * Code subject to the new BSD license (http://framework.zend.com/license/new-bsd). - * - * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com) - */ - - protected function prepareRequestUri() - { - $requestUri = ''; - - if ($this->headers->has('X_REWRITE_URL')) { - // check this first so IIS will catch - $requestUri = $this->headers->get('X_REWRITE_URL'); - } elseif ($this->server->get('IIS_WasUrlRewritten') == '1' && $this->server->get('UNENCODED_URL') != '') { - // IIS7 with URL Rewrite: make sure we get the unencoded url (double slash problem) - $requestUri = $this->server->get('UNENCODED_URL'); - } elseif ($this->server->has('REQUEST_URI')) { - $requestUri = $this->server->get('REQUEST_URI'); - // HTTP proxy reqs setup request uri with scheme and host [and port] + the url path, only use url path - $schemeAndHttpHost = $this->getScheme().'://'.$this->getHttpHost(); - if (strpos($requestUri, $schemeAndHttpHost) === 0) { - $requestUri = substr($requestUri, strlen($schemeAndHttpHost)); - } - } elseif ($this->server->has('ORIG_PATH_INFO')) { - // IIS 5.0, PHP as CGI - $requestUri = $this->server->get('ORIG_PATH_INFO'); - if ($this->server->get('QUERY_STRING')) { - $requestUri .= '?'.$this->server->get('QUERY_STRING'); - } - } - - return $requestUri; - } - - protected function prepareBaseUrl() - { - $filename = basename($this->server->get('SCRIPT_FILENAME')); - - if (basename($this->server->get('SCRIPT_NAME')) === $filename) { - $baseUrl = $this->server->get('SCRIPT_NAME'); - } elseif (basename($this->server->get('PHP_SELF')) === $filename) { - $baseUrl = $this->server->get('PHP_SELF'); - } elseif (basename($this->server->get('ORIG_SCRIPT_NAME')) === $filename) { - $baseUrl = $this->server->get('ORIG_SCRIPT_NAME'); // 1and1 shared hosting compatibility - } else { - // Backtrack up the script_filename to find the portion matching - // php_self - $path = $this->server->get('PHP_SELF', ''); - $file = $this->server->get('SCRIPT_FILENAME', ''); - $segs = explode('/', trim($file, '/')); - $segs = array_reverse($segs); - $index = 0; - $last = count($segs); - $baseUrl = ''; - do { - $seg = $segs[$index]; - $baseUrl = '/'.$seg.$baseUrl; - ++$index; - } while (($last > $index) && (false !== ($pos = strpos($path, $baseUrl))) && (0 != $pos)); - } - - // Does the baseUrl have anything in common with the request_uri? - $requestUri = $this->getRequestUri(); - - if ($baseUrl && 0 === strpos($requestUri, $baseUrl)) { - // full $baseUrl matches - return $baseUrl; - } - - if ($baseUrl && 0 === strpos($requestUri, dirname($baseUrl))) { - // directory portion of $baseUrl matches - return rtrim(dirname($baseUrl), '/'); - } - - $truncatedRequestUri = $requestUri; - if (($pos = strpos($requestUri, '?')) !== false) { - $truncatedRequestUri = substr($requestUri, 0, $pos); - } - - $basename = basename($baseUrl); - if (empty($basename) || !strpos($truncatedRequestUri, $basename)) { - // no match whatsoever; set it blank - return ''; - } - - // If using mod_rewrite or ISAPI_Rewrite strip the script filename - // out of baseUrl. $pos !== 0 makes sure it is not matching a value - // from PATH_INFO or QUERY_STRING - if ((strlen($requestUri) >= strlen($baseUrl)) && ((false !== ($pos = strpos($requestUri, $baseUrl))) && ($pos !== 0))) { - $baseUrl = substr($requestUri, 0, $pos + strlen($baseUrl)); - } - - return rtrim($baseUrl, '/'); - } - - /** - * Prepares base path. - * - * @return string base path - */ - protected function prepareBasePath() - { - $filename = basename($this->server->get('SCRIPT_FILENAME')); - $baseUrl = $this->getBaseUrl(); - if (empty($baseUrl)) { - return ''; - } - - if (basename($baseUrl) === $filename) { - $basePath = dirname($baseUrl); - } else { - $basePath = $baseUrl; - } - - if ('\\' === DIRECTORY_SEPARATOR) { - $basePath = str_replace('\\', '/', $basePath); - } - - return rtrim($basePath, '/'); - } - - /** - * Prepares path info. - * - * @return string path info - */ - protected function preparePathInfo() - { - $baseUrl = $this->getBaseUrl(); - - if (null === ($requestUri = $this->getRequestUri())) { - return '/'; - } - - $pathInfo = '/'; - - // Remove the query string from REQUEST_URI - if ($pos = strpos($requestUri, '?')) { - $requestUri = substr($requestUri, 0, $pos); - } - - if ((null !== $baseUrl) && (false === ($pathInfo = substr(urldecode($requestUri), strlen(urldecode($baseUrl)))))) { - // If substr() returns false then PATH_INFO is set to an empty string - return '/'; - } elseif (null === $baseUrl) { - return $requestUri; - } - - return (string) $pathInfo; - } - - /** - * Initializes HTTP request formats. - */ - static protected function initializeFormats() - { - static::$formats = array( - 'html' => array('text/html', 'application/xhtml+xml'), - 'txt' => array('text/plain'), - 'js' => array('application/javascript', 'application/x-javascript', 'text/javascript'), - 'css' => array('text/css'), - 'json' => array('application/json', 'application/x-json'), - 'xml' => array('text/xml', 'application/xml', 'application/x-xml'), - 'rdf' => array('application/rdf+xml'), - 'atom' => array('application/atom+xml'), - ); - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/RequestMatcher.php b/core/includes/Symfony/Component/HttpFoundation/RequestMatcher.php deleted file mode 100644 index 52ba0784bbc..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/RequestMatcher.php +++ /dev/null @@ -1,177 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation; - -/** - * RequestMatcher compares a pre-defined set of checks against a Request instance. - * - * @author Fabien Potencier <fabien@symfony.com> - * - * @api - */ -class RequestMatcher implements RequestMatcherInterface -{ - private $path; - private $host; - private $methods; - private $ip; - private $attributes; - - public function __construct($path = null, $host = null, $methods = null, $ip = null, array $attributes = array()) - { - $this->path = $path; - $this->host = $host; - $this->methods = $methods; - $this->ip = $ip; - $this->attributes = $attributes; - } - - /** - * Adds a check for the URL host name. - * - * @param string $regexp A Regexp - */ - public function matchHost($regexp) - { - $this->host = $regexp; - } - - /** - * Adds a check for the URL path info. - * - * @param string $regexp A Regexp - */ - public function matchPath($regexp) - { - $this->path = $regexp; - } - - /** - * Adds a check for the client IP. - * - * @param string $ip A specific IP address or a range specified using IP/netmask like 192.168.1.0/24 - */ - public function matchIp($ip) - { - $this->ip = $ip; - } - - /** - * Adds a check for the HTTP method. - * - * @param string|array $method An HTTP method or an array of HTTP methods - */ - public function matchMethod($method) - { - $this->methods = array_map('strtoupper', is_array($method) ? $method : array($method)); - } - - /** - * Adds a check for request attribute. - * - * @param string $key The request attribute name - * @param string $regexp A Regexp - */ - public function matchAttribute($key, $regexp) - { - $this->attributes[$key] = $regexp; - } - - /** - * {@inheritdoc} - * - * @api - */ - public function matches(Request $request) - { - if (null !== $this->methods && !in_array($request->getMethod(), $this->methods)) { - return false; - } - - foreach ($this->attributes as $key => $pattern) { - if (!preg_match('#'.str_replace('#', '\\#', $pattern).'#', $request->attributes->get($key))) { - return false; - } - } - - if (null !== $this->path) { - $path = str_replace('#', '\\#', $this->path); - - if (!preg_match('#'.$path.'#', $request->getPathInfo())) { - return false; - } - } - - if (null !== $this->host && !preg_match('#'.str_replace('#', '\\#', $this->host).'#', $request->getHost())) { - return false; - } - - if (null !== $this->ip && !$this->checkIp($request->getClientIp(), $this->ip)) { - return false; - } - - return true; - } - - protected function checkIp($requestIp, $ip) - { - // IPv6 address - if (false !== strpos($requestIp, ':')) { - return $this->checkIp6($requestIp, $ip); - } else { - return $this->checkIp4($requestIp, $ip); - } - } - - protected function checkIp4($requestIp, $ip) - { - if (false !== strpos($ip, '/')) { - list($address, $netmask) = explode('/', $ip); - - if ($netmask < 1 || $netmask > 32) { - return false; - } - } else { - $address = $ip; - $netmask = 32; - } - - return 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask); - } - - /** - * @author David Soria Parra <dsp at php dot net> - * @see https://github.com/dsp/v6tools - */ - protected function checkIp6($requestIp, $ip) - { - if (!defined('AF_INET6')) { - throw new \RuntimeException('Unable to check Ipv6. Check that PHP was not compiled with option "disable-ipv6".'); - } - - list($address, $netmask) = explode('/', $ip); - - $bytes_addr = unpack("n*", inet_pton($address)); - $bytes_test = unpack("n*", inet_pton($requestIp)); - - for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; $i++) { - $left = $netmask - 16 * ($i-1); - $left = ($left <= 16) ?: 16; - $mask = ~(0xffff >> $left) & 0xffff; - if (($bytes_addr[$i] & $mask) != ($bytes_test[$i] & $mask)) { - return false; - } - } - - return true; - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/RequestMatcherInterface.php b/core/includes/Symfony/Component/HttpFoundation/RequestMatcherInterface.php deleted file mode 100644 index 506ec794015..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/RequestMatcherInterface.php +++ /dev/null @@ -1,33 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation; - -/** - * RequestMatcherInterface is an interface for strategies to match a Request. - * - * @author Fabien Potencier <fabien@symfony.com> - * - * @api - */ -interface RequestMatcherInterface -{ - /** - * Decides whether the rule(s) implemented by the strategy matches the supplied request. - * - * @param Request $request The request to check for a match - * - * @return Boolean true if the request matches, false otherwise - * - * @api - */ - function matches(Request $request); -} diff --git a/core/includes/Symfony/Component/HttpFoundation/Response.php b/core/includes/Symfony/Component/HttpFoundation/Response.php deleted file mode 100644 index e8a7d562449..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/Response.php +++ /dev/null @@ -1,891 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation; - -/** - * Response represents an HTTP response. - * - * @author Fabien Potencier <fabien@symfony.com> - * - * @api - */ -class Response -{ - /** - * @var \Symfony\Component\HttpFoundation\ResponseHeaderBag - */ - public $headers; - - protected $content; - protected $version; - protected $statusCode; - protected $statusText; - protected $charset; - - static public $statusTexts = array( - 100 => 'Continue', - 101 => 'Switching Protocols', - 200 => 'OK', - 201 => 'Created', - 202 => 'Accepted', - 203 => 'Non-Authoritative Information', - 204 => 'No Content', - 205 => 'Reset Content', - 206 => 'Partial Content', - 300 => 'Multiple Choices', - 301 => 'Moved Permanently', - 302 => 'Found', - 303 => 'See Other', - 304 => 'Not Modified', - 305 => 'Use Proxy', - 307 => 'Temporary Redirect', - 400 => 'Bad Request', - 401 => 'Unauthorized', - 402 => 'Payment Required', - 403 => 'Forbidden', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 406 => 'Not Acceptable', - 407 => 'Proxy Authentication Required', - 408 => 'Request Timeout', - 409 => 'Conflict', - 410 => 'Gone', - 411 => 'Length Required', - 412 => 'Precondition Failed', - 413 => 'Request Entity Too Large', - 414 => 'Request-URI Too Long', - 415 => 'Unsupported Media Type', - 416 => 'Requested Range Not Satisfiable', - 417 => 'Expectation Failed', - 418 => 'I\'m a teapot', - 500 => 'Internal Server Error', - 501 => 'Not Implemented', - 502 => 'Bad Gateway', - 503 => 'Service Unavailable', - 504 => 'Gateway Timeout', - 505 => 'HTTP Version Not Supported', - ); - - /** - * Constructor. - * - * @param string $content The response content - * @param integer $status The response status code - * @param array $headers An array of response headers - * - * @api - */ - public function __construct($content = '', $status = 200, $headers = array()) - { - $this->headers = new ResponseHeaderBag($headers); - $this->setContent($content); - $this->setStatusCode($status); - $this->setProtocolVersion('1.0'); - if (!$this->headers->has('Date')) { - $this->setDate(new \DateTime(null, new \DateTimeZone('UTC'))); - } - } - - /** - * Returns the response content as it will be sent (with the headers). - * - * @return string The response content - */ - public function __toString() - { - $this->prepare(); - - return - sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)."\r\n". - $this->headers."\r\n". - $this->getContent(); - } - - /** - * Clones the current Response instance. - */ - public function __clone() - { - $this->headers = clone $this->headers; - } - - /** - * Prepares the Response before it is sent to the client. - * - * This method tweaks the Response to ensure that it is - * compliant with RFC 2616. - */ - public function prepare() - { - if ($this->isInformational() || in_array($this->statusCode, array(204, 304))) { - $this->setContent(''); - } - - // Fix Content-Type - $charset = $this->charset ?: 'UTF-8'; - if (!$this->headers->has('Content-Type')) { - $this->headers->set('Content-Type', 'text/html; charset='.$charset); - } elseif ('text/' === substr($this->headers->get('Content-Type'), 0, 5) && false === strpos($this->headers->get('Content-Type'), 'charset')) { - // add the charset - $this->headers->set('Content-Type', $this->headers->get('Content-Type').'; charset='.$charset); - } - - // Fix Content-Length - if ($this->headers->has('Transfer-Encoding')) { - $this->headers->remove('Content-Length'); - } - } - - /** - * Sends HTTP headers. - */ - public function sendHeaders() - { - // headers have already been sent by the developer - if (headers_sent()) { - return; - } - - $this->prepare(); - - // status - header(sprintf('HTTP/%s %s %s', $this->version, $this->statusCode, $this->statusText)); - - // headers - foreach ($this->headers->all() as $name => $values) { - foreach ($values as $value) { - header($name.': '.$value, false); - } - } - - // cookies - foreach ($this->headers->getCookies() as $cookie) { - setcookie($cookie->getName(), $cookie->getValue(), $cookie->getExpiresTime(), $cookie->getPath(), $cookie->getDomain(), $cookie->isSecure(), $cookie->isHttpOnly()); - } - } - - /** - * Sends content for the current web response. - */ - public function sendContent() - { - echo $this->content; - } - - /** - * Sends HTTP headers and content. - * - * @api - */ - public function send() - { - $this->sendHeaders(); - $this->sendContent(); - - if (function_exists('fastcgi_finish_request')) { - fastcgi_finish_request(); - } - } - - /** - * Sets the response content - * - * Valid types are strings, numbers, and objects that implement a __toString() method. - * - * @param mixed $content - * - * @api - */ - public function setContent($content) - { - if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable(array($content, '__toString'))) { - throw new \UnexpectedValueException('The Response content must be a string or object implementing __toString(), "'.gettype($content).'" given.'); - } - - $this->content = (string) $content; - } - - /** - * Gets the current response content - * - * @return string Content - * - * @api - */ - public function getContent() - { - return $this->content; - } - - /** - * Sets the HTTP protocol version (1.0 or 1.1). - * - * @param string $version The HTTP protocol version - * - * @api - */ - public function setProtocolVersion($version) - { - $this->version = $version; - } - - /** - * Gets the HTTP protocol version. - * - * @return string The HTTP protocol version - * - * @api - */ - public function getProtocolVersion() - { - return $this->version; - } - - /** - * Sets response status code. - * - * @param integer $code HTTP status code - * @param string $text HTTP status text - * - * @throws \InvalidArgumentException When the HTTP status code is not valid - * - * @api - */ - public function setStatusCode($code, $text = null) - { - $this->statusCode = (int) $code; - if ($this->isInvalid()) { - throw new \InvalidArgumentException(sprintf('The HTTP status code "%s" is not valid.', $code)); - } - - $this->statusText = false === $text ? '' : (null === $text ? self::$statusTexts[$this->statusCode] : $text); - } - - /** - * Retrieves status code for the current web response. - * - * @return string Status code - * - * @api - */ - public function getStatusCode() - { - return $this->statusCode; - } - - /** - * Sets response charset. - * - * @param string $charset Character set - * - * @api - */ - public function setCharset($charset) - { - $this->charset = $charset; - } - - /** - * Retrieves the response charset. - * - * @return string Character set - * - * @api - */ - public function getCharset() - { - return $this->charset; - } - - /** - * Returns true if the response is worth caching under any circumstance. - * - * Responses marked "private" with an explicit Cache-Control directive are - * considered uncacheable. - * - * Responses with neither a freshness lifetime (Expires, max-age) nor cache - * validator (Last-Modified, ETag) are considered uncacheable. - * - * @return Boolean true if the response is worth caching, false otherwise - * - * @api - */ - public function isCacheable() - { - if (!in_array($this->statusCode, array(200, 203, 300, 301, 302, 404, 410))) { - return false; - } - - if ($this->headers->hasCacheControlDirective('no-store') || $this->headers->getCacheControlDirective('private')) { - return false; - } - - return $this->isValidateable() || $this->isFresh(); - } - - /** - * Returns true if the response is "fresh". - * - * Fresh responses may be served from cache without any interaction with the - * origin. A response is considered fresh when it includes a Cache-Control/max-age - * indicator or Expiration header and the calculated age is less than the freshness lifetime. - * - * @return Boolean true if the response is fresh, false otherwise - * - * @api - */ - public function isFresh() - { - return $this->getTtl() > 0; - } - - /** - * Returns true if the response includes headers that can be used to validate - * the response with the origin server using a conditional GET request. - * - * @return Boolean true if the response is validateable, false otherwise - * - * @api - */ - public function isValidateable() - { - return $this->headers->has('Last-Modified') || $this->headers->has('ETag'); - } - - /** - * Marks the response as "private". - * - * It makes the response ineligible for serving other clients. - * - * @api - */ - public function setPrivate() - { - $this->headers->removeCacheControlDirective('public'); - $this->headers->addCacheControlDirective('private'); - } - - /** - * Marks the response as "public". - * - * It makes the response eligible for serving other clients. - * - * @api - */ - public function setPublic() - { - $this->headers->addCacheControlDirective('public'); - $this->headers->removeCacheControlDirective('private'); - } - - /** - * Returns true if the response must be revalidated by caches. - * - * This method indicates that the response must not be served stale by a - * cache in any circumstance without first revalidating with the origin. - * When present, the TTL of the response should not be overridden to be - * greater than the value provided by the origin. - * - * @return Boolean true if the response must be revalidated by a cache, false otherwise - * - * @api - */ - public function mustRevalidate() - { - return $this->headers->hasCacheControlDirective('must-revalidate') || $this->headers->has('must-proxy-revalidate'); - } - - /** - * Returns the Date header as a DateTime instance. - * - * @return \DateTime A \DateTime instance - * - * @throws \RuntimeException when the header is not parseable - * - * @api - */ - public function getDate() - { - return $this->headers->getDate('Date'); - } - - /** - * Sets the Date header. - * - * @param \DateTime $date A \DateTime instance - * - * @api - */ - public function setDate(\DateTime $date) - { - $date->setTimezone(new \DateTimeZone('UTC')); - $this->headers->set('Date', $date->format('D, d M Y H:i:s').' GMT'); - } - - /** - * Returns the age of the response. - * - * @return integer The age of the response in seconds - */ - public function getAge() - { - if ($age = $this->headers->get('Age')) { - return $age; - } - - return max(time() - $this->getDate()->format('U'), 0); - } - - /** - * Marks the response stale by setting the Age header to be equal to the maximum age of the response. - * - * @api - */ - public function expire() - { - if ($this->isFresh()) { - $this->headers->set('Age', $this->getMaxAge()); - } - } - - /** - * Returns the value of the Expires header as a DateTime instance. - * - * @return \DateTime A DateTime instance - * - * @api - */ - public function getExpires() - { - return $this->headers->getDate('Expires'); - } - - /** - * Sets the Expires HTTP header with a \DateTime instance. - * - * If passed a null value, it removes the header. - * - * @param \DateTime $date A \DateTime instance - * - * @api - */ - public function setExpires(\DateTime $date = null) - { - if (null === $date) { - $this->headers->remove('Expires'); - } else { - $date = clone $date; - $date->setTimezone(new \DateTimeZone('UTC')); - $this->headers->set('Expires', $date->format('D, d M Y H:i:s').' GMT'); - } - } - - /** - * Sets the number of seconds after the time specified in the response's Date - * header when the the response should no longer be considered fresh. - * - * First, it checks for a s-maxage directive, then a max-age directive, and then it falls - * back on an expires header. It returns null when no maximum age can be established. - * - * @return integer|null Number of seconds - * - * @api - */ - public function getMaxAge() - { - if ($age = $this->headers->getCacheControlDirective('s-maxage')) { - return $age; - } - - if ($age = $this->headers->getCacheControlDirective('max-age')) { - return $age; - } - - if (null !== $this->getExpires()) { - return $this->getExpires()->format('U') - $this->getDate()->format('U'); - } - - return null; - } - - /** - * Sets the number of seconds after which the response should no longer be considered fresh. - * - * This methods sets the Cache-Control max-age directive. - * - * @param integer $value A number of seconds - * - * @api - */ - public function setMaxAge($value) - { - $this->headers->addCacheControlDirective('max-age', $value); - } - - /** - * Sets the number of seconds after which the response should no longer be considered fresh by shared caches. - * - * This methods sets the Cache-Control s-maxage directive. - * - * @param integer $value A number of seconds - * - * @api - */ - public function setSharedMaxAge($value) - { - $this->setPublic(); - $this->headers->addCacheControlDirective('s-maxage', $value); - } - - /** - * Returns the response's time-to-live in seconds. - * - * It returns null when no freshness information is present in the response. - * - * When the responses TTL is <= 0, the response may not be served from cache without first - * revalidating with the origin. - * - * @return integer The TTL in seconds - * - * @api - */ - public function getTtl() - { - if ($maxAge = $this->getMaxAge()) { - return $maxAge - $this->getAge(); - } - - return null; - } - - /** - * Sets the response's time-to-live for shared caches. - * - * This method adjusts the Cache-Control/s-maxage directive. - * - * @param integer $seconds The number of seconds - * - * @api - */ - public function setTtl($seconds) - { - $this->setSharedMaxAge($this->getAge() + $seconds); - } - - /** - * Sets the response's time-to-live for private/client caches. - * - * This method adjusts the Cache-Control/max-age directive. - * - * @param integer $seconds The number of seconds - * - * @api - */ - public function setClientTtl($seconds) - { - $this->setMaxAge($this->getAge() + $seconds); - } - - /** - * Returns the Last-Modified HTTP header as a DateTime instance. - * - * @return \DateTime A DateTime instance - * - * @api - */ - public function getLastModified() - { - return $this->headers->getDate('Last-Modified'); - } - - /** - * Sets the Last-Modified HTTP header with a \DateTime instance. - * - * If passed a null value, it removes the header. - * - * @param \DateTime $date A \DateTime instance - * - * @api - */ - public function setLastModified(\DateTime $date = null) - { - if (null === $date) { - $this->headers->remove('Last-Modified'); - } else { - $date = clone $date; - $date->setTimezone(new \DateTimeZone('UTC')); - $this->headers->set('Last-Modified', $date->format('D, d M Y H:i:s').' GMT'); - } - } - - /** - * Returns the literal value of ETag HTTP header. - * - * @return string The ETag HTTP header - * - * @api - */ - public function getEtag() - { - return $this->headers->get('ETag'); - } - - /** - * Sets the ETag value. - * - * @param string $etag The ETag unique identifier - * @param Boolean $weak Whether you want a weak ETag or not - * - * @api - */ - public function setEtag($etag = null, $weak = false) - { - if (null === $etag) { - $this->headers->remove('Etag'); - } else { - if (0 !== strpos($etag, '"')) { - $etag = '"'.$etag.'"'; - } - - $this->headers->set('ETag', (true === $weak ? 'W/' : '').$etag); - } - } - - /** - * Sets Response cache headers (validation and/or expiration). - * - * Available options are: etag, last_modified, max_age, s_maxage, private, and public. - * - * @param array $options An array of cache options - * - * @api - */ - public function setCache(array $options) - { - if ($diff = array_diff(array_keys($options), array('etag', 'last_modified', 'max_age', 's_maxage', 'private', 'public'))) { - throw new \InvalidArgumentException(sprintf('Response does not support the following options: "%s".', implode('", "', array_keys($diff)))); - } - - if (isset($options['etag'])) { - $this->setEtag($options['etag']); - } - - if (isset($options['last_modified'])) { - $this->setLastModified($options['last_modified']); - } - - if (isset($options['max_age'])) { - $this->setMaxAge($options['max_age']); - } - - if (isset($options['s_maxage'])) { - $this->setSharedMaxAge($options['s_maxage']); - } - - if (isset($options['public'])) { - if ($options['public']) { - $this->setPublic(); - } else { - $this->setPrivate(); - } - } - - if (isset($options['private'])) { - if ($options['private']) { - $this->setPrivate(); - } else { - $this->setPublic(); - } - } - } - - /** - * Modifies the response so that it conforms to the rules defined for a 304 status code. - * - * This sets the status, removes the body, and discards any headers - * that MUST NOT be included in 304 responses. - * - * @see http://tools.ietf.org/html/rfc2616#section-10.3.5 - * - * @api - */ - public function setNotModified() - { - $this->setStatusCode(304); - $this->setContent(null); - - // remove headers that MUST NOT be included with 304 Not Modified responses - foreach (array('Allow', 'Content-Encoding', 'Content-Language', 'Content-Length', 'Content-MD5', 'Content-Type', 'Last-Modified') as $header) { - $this->headers->remove($header); - } - } - - /** - * Returns true if the response includes a Vary header. - * - * @return true if the response includes a Vary header, false otherwise - * - * @api - */ - public function hasVary() - { - return (Boolean) $this->headers->get('Vary'); - } - - /** - * Returns an array of header names given in the Vary header. - * - * @return array An array of Vary names - * - * @api - */ - public function getVary() - { - if (!$vary = $this->headers->get('Vary')) { - return array(); - } - - return is_array($vary) ? $vary : preg_split('/[\s,]+/', $vary); - } - - /** - * Sets the Vary header. - * - * @param string|array $headers - * @param Boolean $replace Whether to replace the actual value of not (true by default) - * - * @api - */ - public function setVary($headers, $replace = true) - { - $this->headers->set('Vary', $headers, $replace); - } - - /** - * Determines if the Response validators (ETag, Last-Modified) matches - * a conditional value specified in the Request. - * - * If the Response is not modified, it sets the status code to 304 and - * remove the actual content by calling the setNotModified() method. - * - * @param Request $request A Request instance - * - * @return Boolean true if the Response validators matches the Request, false otherwise - * - * @api - */ - public function isNotModified(Request $request) - { - $lastModified = $request->headers->get('If-Modified-Since'); - $notModified = false; - if ($etags = $request->getEtags()) { - $notModified = (in_array($this->getEtag(), $etags) || in_array('*', $etags)) && (!$lastModified || $this->headers->get('Last-Modified') == $lastModified); - } elseif ($lastModified) { - $notModified = $lastModified == $this->headers->get('Last-Modified'); - } - - if ($notModified) { - $this->setNotModified(); - } - - return $notModified; - } - - // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html - /** - * @api - */ - public function isInvalid() - { - return $this->statusCode < 100 || $this->statusCode >= 600; - } - - /** - * @api - */ - public function isInformational() - { - return $this->statusCode >= 100 && $this->statusCode < 200; - } - - /** - * @api - */ - public function isSuccessful() - { - return $this->statusCode >= 200 && $this->statusCode < 300; - } - - /** - * @api - */ - public function isRedirection() - { - return $this->statusCode >= 300 && $this->statusCode < 400; - } - - /** - * @api - */ - public function isClientError() - { - return $this->statusCode >= 400 && $this->statusCode < 500; - } - - /** - * @api - */ - public function isServerError() - { - return $this->statusCode >= 500 && $this->statusCode < 600; - } - - /** - * @api - */ - public function isOk() - { - return 200 === $this->statusCode; - } - - /** - * @api - */ - public function isForbidden() - { - return 403 === $this->statusCode; - } - - /** - * @api - */ - public function isNotFound() - { - return 404 === $this->statusCode; - } - - /** - * @api - */ - public function isRedirect($location = null) - { - return in_array($this->statusCode, array(201, 301, 302, 303, 307)) && (null === $location ?: $location == $this->headers->get('Location')); - } - - /** - * @api - */ - public function isEmpty() - { - return in_array($this->statusCode, array(201, 204, 304)); - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/ResponseHeaderBag.php b/core/includes/Symfony/Component/HttpFoundation/ResponseHeaderBag.php deleted file mode 100644 index f243dcc9f6b..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/ResponseHeaderBag.php +++ /dev/null @@ -1,238 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation; - -/** - * ResponseHeaderBag is a container for Response HTTP headers. - * - * @author Fabien Potencier <fabien@symfony.com> - * - * @api - */ -class ResponseHeaderBag extends HeaderBag -{ - const COOKIES_FLAT = 'flat'; - const COOKIES_ARRAY = 'array'; - - protected $computedCacheControl = array(); - protected $cookies = array(); - - /** - * Constructor. - * - * @param array $headers An array of HTTP headers - * - * @api - */ - public function __construct(array $headers = array()) - { - parent::__construct($headers); - - if (!isset($this->headers['cache-control'])) { - $this->set('cache-control', ''); - } - } - - /** - * {@inheritdoc} - */ - public function __toString() - { - $cookies = ''; - foreach ($this->getCookies() as $cookie) { - $cookies .= 'Set-Cookie: '.$cookie."\r\n"; - } - - return parent::__toString().$cookies; - } - - /** - * {@inheritdoc} - * - * @api - */ - public function replace(array $headers = array()) - { - parent::replace($headers); - - if (!isset($this->headers['cache-control'])) { - $this->set('cache-control', ''); - } - } - - /** - * {@inheritdoc} - * - * @api - */ - public function set($key, $values, $replace = true) - { - parent::set($key, $values, $replace); - - // ensure the cache-control header has sensible defaults - if (in_array(strtr(strtolower($key), '_', '-'), array('cache-control', 'etag', 'last-modified', 'expires'))) { - $computed = $this->computeCacheControlValue(); - $this->headers['cache-control'] = array($computed); - $this->computedCacheControl = $this->parseCacheControl($computed); - } - } - - /** - * {@inheritdoc} - * - * @api - */ - public function remove($key) - { - parent::remove($key); - - if ('cache-control' === strtr(strtolower($key), '_', '-')) { - $this->computedCacheControl = array(); - } - } - - /** - * {@inheritdoc} - */ - public function hasCacheControlDirective($key) - { - return array_key_exists($key, $this->computedCacheControl); - } - - /** - * {@inheritdoc} - */ - public function getCacheControlDirective($key) - { - return array_key_exists($key, $this->computedCacheControl) ? $this->computedCacheControl[$key] : null; - } - - /** - * Sets a cookie. - * - * @param Cookie $cookie - * @return void - * - * @api - */ - public function setCookie(Cookie $cookie) - { - $this->cookies[$cookie->getDomain()][$cookie->getPath()][$cookie->getName()] = $cookie; - } - - /** - * Removes a cookie from the array, but does not unset it in the browser - * - * @param string $name - * @param string $path - * @param string $domain - * @return void - * - * @api - */ - public function removeCookie($name, $path = '/', $domain = null) - { - if (null === $path) { - $path = '/'; - } - - unset($this->cookies[$domain][$path][$name]); - - if (empty($this->cookies[$domain][$path])) { - unset($this->cookies[$domain][$path]); - - if (empty($this->cookies[$domain])) { - unset($this->cookies[$domain]); - } - } - } - - /** - * Returns an array with all cookies - * - * @param string $format - * - * @throws \InvalidArgumentException When the $format is invalid - * - * @return array - * - * @api - */ - public function getCookies($format = self::COOKIES_FLAT) - { - if (!in_array($format, array(self::COOKIES_FLAT, self::COOKIES_ARRAY))) { - throw new \InvalidArgumentException(sprintf('Format "%s" invalid (%s).', $format, implode(', ', array(self::COOKIES_FLAT, self::COOKIES_ARRAY)))); - } - - if (self::COOKIES_ARRAY === $format) { - return $this->cookies; - } - - $flattenedCookies = array(); - foreach ($this->cookies as $path) { - foreach ($path as $cookies) { - foreach ($cookies as $cookie) { - $flattenedCookies[] = $cookie; - } - } - } - - return $flattenedCookies; - } - - /** - * Clears a cookie in the browser - * - * @param string $name - * @param string $path - * @param string $domain - * @return void - * - * @api - */ - public function clearCookie($name, $path = '/', $domain = null) - { - $this->setCookie(new Cookie($name, null, 1, $path, $domain)); - } - - /** - * Returns the calculated value of the cache-control header. - * - * This considers several other headers and calculates or modifies the - * cache-control header to a sensible, conservative value. - * - * @return string - */ - protected function computeCacheControlValue() - { - if (!$this->cacheControl && !$this->has('ETag') && !$this->has('Last-Modified') && !$this->has('Expires')) { - return 'no-cache'; - } - - if (!$this->cacheControl) { - // conservative by default - return 'private, must-revalidate'; - } - - $header = $this->getCacheControlHeader(); - if (isset($this->cacheControl['public']) || isset($this->cacheControl['private'])) { - return $header; - } - - // public if s-maxage is defined, private otherwise - if (!isset($this->cacheControl['s-maxage'])) { - return $header.', private'; - } - - return $header; - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/ServerBag.php b/core/includes/Symfony/Component/HttpFoundation/ServerBag.php deleted file mode 100644 index 02db3b18190..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/ServerBag.php +++ /dev/null @@ -1,43 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation; - -/** - * ServerBag is a container for HTTP headers from the $_SERVER variable. - * - * @author Fabien Potencier <fabien@symfony.com> - * @author Bulat Shakirzyanov <mallluhuct@gmail.com> - */ -class ServerBag extends ParameterBag -{ - public function getHeaders() - { - $headers = array(); - foreach ($this->parameters as $key => $value) { - if ('HTTP_' === substr($key, 0, 5)) { - $headers[substr($key, 5)] = $value; - } - // CONTENT_* are not prefixed with HTTP_ - elseif (in_array($key, array('CONTENT_LENGTH', 'CONTENT_MD5', 'CONTENT_TYPE'))) { - $headers[$key] = $this->parameters[$key]; - } - } - - // PHP_AUTH_USER/PHP_AUTH_PW - if (isset($this->parameters['PHP_AUTH_USER'])) { - $pass = isset($this->parameters['PHP_AUTH_PW']) ? $this->parameters['PHP_AUTH_PW'] : ''; - $headers['AUTHORIZATION'] = 'Basic '.base64_encode($this->parameters['PHP_AUTH_USER'].':'.$pass); - } - - return $headers; - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/Session.php b/core/includes/Symfony/Component/HttpFoundation/Session.php deleted file mode 100644 index a835ef7da5e..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/Session.php +++ /dev/null @@ -1,405 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation; - -use Symfony\Component\HttpFoundation\SessionStorage\SessionStorageInterface; - -/** - * Session. - * - * @author Fabien Potencier <fabien@symfony.com> - * - * @api - */ -class Session implements \Serializable -{ - protected $storage; - protected $started; - protected $attributes; - protected $flashes; - protected $oldFlashes; - protected $locale; - protected $defaultLocale; - protected $closed; - - /** - * Constructor. - * - * @param SessionStorageInterface $storage A SessionStorageInterface instance - * @param string $defaultLocale The default locale - */ - public function __construct(SessionStorageInterface $storage, $defaultLocale = 'en') - { - $this->storage = $storage; - $this->defaultLocale = $defaultLocale; - $this->locale = $defaultLocale; - $this->flashes = array(); - $this->oldFlashes = array(); - $this->attributes = array(); - $this->setPhpDefaultLocale($this->defaultLocale); - $this->started = false; - $this->closed = false; - } - - /** - * Starts the session storage. - * - * @api - */ - public function start() - { - if (true === $this->started) { - return; - } - - $this->storage->start(); - - $attributes = $this->storage->read('_symfony2'); - - if (isset($attributes['attributes'])) { - $this->attributes = $attributes['attributes']; - $this->flashes = $attributes['flashes']; - $this->locale = $attributes['locale']; - $this->setPhpDefaultLocale($this->locale); - - // flag current flash messages to be removed at shutdown - $this->oldFlashes = $this->flashes; - } - - $this->started = true; - } - - /** - * Checks if an attribute is defined. - * - * @param string $name The attribute name - * - * @return Boolean true if the attribute is defined, false otherwise - * - * @api - */ - public function has($name) - { - return array_key_exists($name, $this->attributes); - } - - /** - * Returns an attribute. - * - * @param string $name The attribute name - * @param mixed $default The default value - * - * @return mixed - * - * @api - */ - public function get($name, $default = null) - { - return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : $default; - } - - /** - * Sets an attribute. - * - * @param string $name - * @param mixed $value - * - * @api - */ - public function set($name, $value) - { - if (false === $this->started) { - $this->start(); - } - - $this->attributes[$name] = $value; - } - - /** - * Returns attributes. - * - * @return array Attributes - * - * @api - */ - public function all() - { - return $this->attributes; - } - - /** - * Sets attributes. - * - * @param array $attributes Attributes - * - * @api - */ - public function replace(array $attributes) - { - if (false === $this->started) { - $this->start(); - } - - $this->attributes = $attributes; - } - - /** - * Removes an attribute. - * - * @param string $name - * - * @api - */ - public function remove($name) - { - if (false === $this->started) { - $this->start(); - } - - if (array_key_exists($name, $this->attributes)) { - unset($this->attributes[$name]); - } - } - - /** - * Clears all attributes. - * - * @api - */ - public function clear() - { - if (false === $this->started) { - $this->start(); - } - - $this->attributes = array(); - $this->flashes = array(); - $this->setPhpDefaultLocale($this->locale = $this->defaultLocale); - } - - /** - * Invalidates the current session. - * - * @api - */ - public function invalidate() - { - $this->clear(); - $this->storage->regenerate(true); - } - - /** - * Migrates the current session to a new session id while maintaining all - * session attributes. - * - * @api - */ - public function migrate() - { - $this->storage->regenerate(); - } - - /** - * Returns the session ID - * - * @return mixed The session ID - * - * @api - */ - public function getId() - { - if (false === $this->started) { - $this->start(); - } - - return $this->storage->getId(); - } - - /** - * Returns the locale - * - * @return string - */ - public function getLocale() - { - return $this->locale; - } - - /** - * Sets the locale. - * - * @param string $locale - */ - public function setLocale($locale) - { - if (false === $this->started) { - $this->start(); - } - - $this->setPhpDefaultLocale($this->locale = $locale); - } - - /** - * Gets the flash messages. - * - * @return array - */ - public function getFlashes() - { - return $this->flashes; - } - - /** - * Sets the flash messages. - * - * @param array $values - */ - public function setFlashes($values) - { - if (false === $this->started) { - $this->start(); - } - - $this->flashes = $values; - $this->oldFlashes = array(); - } - - /** - * Gets a flash message. - * - * @param string $name - * @param string|null $default - * - * @return string - */ - public function getFlash($name, $default = null) - { - return array_key_exists($name, $this->flashes) ? $this->flashes[$name] : $default; - } - - /** - * Sets a flash message. - * - * @param string $name - * @param string $value - */ - public function setFlash($name, $value) - { - if (false === $this->started) { - $this->start(); - } - - $this->flashes[$name] = $value; - unset($this->oldFlashes[$name]); - } - - /** - * Checks whether a flash message exists. - * - * @param string $name - * - * @return Boolean - */ - public function hasFlash($name) - { - if (false === $this->started) { - $this->start(); - } - - return array_key_exists($name, $this->flashes); - } - - /** - * Removes a flash message. - * - * @param string $name - */ - public function removeFlash($name) - { - if (false === $this->started) { - $this->start(); - } - - unset($this->flashes[$name]); - } - - /** - * Removes the flash messages. - */ - public function clearFlashes() - { - if (false === $this->started) { - $this->start(); - } - - $this->flashes = array(); - $this->oldFlashes = array(); - } - - public function save() - { - if (false === $this->started) { - $this->start(); - } - - $this->flashes = array_diff_key($this->flashes, $this->oldFlashes); - - $this->storage->write('_symfony2', array( - 'attributes' => $this->attributes, - 'flashes' => $this->flashes, - 'locale' => $this->locale, - )); - } - - /** - * This method should be called when you don't want the session to be saved - * when the Session object is garbaged collected (useful for instance when - * you want to simulate the interaction of several users/sessions in a single - * PHP process). - */ - public function close() - { - $this->closed = true; - } - - public function __destruct() - { - if (true === $this->started && !$this->closed) { - $this->save(); - } - } - - public function serialize() - { - return serialize(array($this->storage, $this->defaultLocale)); - } - - public function unserialize($serialized) - { - list($this->storage, $this->defaultLocale) = unserialize($serialized); - $this->attributes = array(); - $this->started = false; - } - - private function setPhpDefaultLocale($locale) - { - // if either the class Locale doesn't exist, or an exception is thrown when - // setting the default locale, the intl module is not installed, and - // the call can be ignored: - try { - if (class_exists('Locale', false)) { - \Locale::setDefault($locale); - } - } catch (\Exception $e) { - } - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/SessionStorage/ArraySessionStorage.php b/core/includes/Symfony/Component/HttpFoundation/SessionStorage/ArraySessionStorage.php deleted file mode 100644 index 62aac40982a..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/SessionStorage/ArraySessionStorage.php +++ /dev/null @@ -1,58 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\SessionStorage; - -/** - * ArraySessionStorage mocks the session for unit tests. - * - * When doing functional testing, you should use FilesystemSessionStorage instead. - * - * @author Fabien Potencier <fabien@symfony.com> - * @author Bulat Shakirzyanov <mallluhuct@gmail.com> - */ - -class ArraySessionStorage implements SessionStorageInterface -{ - private $data = array(); - - public function read($key, $default = null) - { - return array_key_exists($key, $this->data) ? $this->data[$key] : $default; - } - - public function regenerate($destroy = false) - { - if ($destroy) { - $this->data = array(); - } - - return true; - } - - public function remove($key) - { - unset($this->data[$key]); - } - - public function start() - { - } - - public function getId() - { - } - - public function write($key, $data) - { - $this->data[$key] = $data; - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/SessionStorage/FilesystemSessionStorage.php b/core/includes/Symfony/Component/HttpFoundation/SessionStorage/FilesystemSessionStorage.php deleted file mode 100644 index 87abd01bcde..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/SessionStorage/FilesystemSessionStorage.php +++ /dev/null @@ -1,174 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\SessionStorage; - -/** - * FilesystemSessionStorage simulates sessions for functional tests. - * - * This storage does not start the session (session_start()) - * as it is not "available" when running tests on the command line. - * - * @author Fabien Potencier <fabien@symfony.com> - * - * @api - */ -class FilesystemSessionStorage extends NativeSessionStorage -{ - private $path; - private $data; - private $started; - - /** - * Constructor. - */ - public function __construct($path, array $options = array()) - { - $this->path = $path; - $this->started = false; - - parent::__construct($options); - } - - /** - * Starts the session. - * - * @api - */ - public function start() - { - if ($this->started) { - return; - } - - session_set_cookie_params( - $this->options['lifetime'], - $this->options['path'], - $this->options['domain'], - $this->options['secure'], - $this->options['httponly'] - ); - - if (!ini_get('session.use_cookies') && isset($this->options['id']) && $this->options['id'] && $this->options['id'] != session_id()) { - session_id($this->options['id']); - } - - if (!session_id()) { - session_id(hash('md5', uniqid(mt_rand(), true))); - } - - $file = $this->path.'/'.session_id().'.session'; - - $this->data = file_exists($file) ? unserialize(file_get_contents($file)) : array(); - $this->started = true; - } - - /** - * Returns the session ID - * - * @return mixed The session ID - * - * @throws \RuntimeException If the session was not started yet - * - * @api - */ - public function getId() - { - if (!$this->started) { - throw new \RuntimeException('The session must be started before reading its ID'); - } - - return session_id(); - } - - /** - * Reads data from this storage. - * - * The preferred format for a key is directory style so naming conflicts can be avoided. - * - * @param string $key A unique key identifying your data - * - * @return mixed Data associated with the key - * - * @throws \RuntimeException If an error occurs while reading data from this storage - * - * @api - */ - public function read($key, $default = null) - { - return array_key_exists($key, $this->data) ? $this->data[$key] : $default; - } - - /** - * Removes data from this storage. - * - * The preferred format for a key is directory style so naming conflicts can be avoided. - * - * @param string $key A unique key identifying your data - * - * @return mixed Data associated with the key - * - * @throws \RuntimeException If an error occurs while removing data from this storage - * - * @api - */ - public function remove($key) - { - $retval = $this->data[$key]; - - unset($this->data[$key]); - - return $retval; - } - - /** - * Writes data to this storage. - * - * The preferred format for a key is directory style so naming conflicts can be avoided. - * - * @param string $key A unique key identifying your data - * @param mixed $data Data associated with your key - * - * @throws \RuntimeException If an error occurs while writing to this storage - * - * @api - */ - public function write($key, $data) - { - $this->data[$key] = $data; - - if (!is_dir($this->path)) { - mkdir($this->path, 0777, true); - } - - file_put_contents($this->path.'/'.session_id().'.session', serialize($this->data)); - } - - /** - * Regenerates id that represents this storage. - * - * @param Boolean $destroy Destroy session when regenerating? - * - * @return Boolean True if session regenerated, false if error - * - * @throws \RuntimeException If an error occurs while regenerating this storage - * - * @api - */ - public function regenerate($destroy = false) - { - if ($destroy) { - $this->data = array(); - } - - return true; - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/SessionStorage/NativeSessionStorage.php b/core/includes/Symfony/Component/HttpFoundation/SessionStorage/NativeSessionStorage.php deleted file mode 100644 index b759f7411a0..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/SessionStorage/NativeSessionStorage.php +++ /dev/null @@ -1,180 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\SessionStorage; - -/** - * NativeSessionStorage. - * - * @author Fabien Potencier <fabien@symfony.com> - * - * @api - */ -class NativeSessionStorage implements SessionStorageInterface -{ - static protected $sessionIdRegenerated = false; - static protected $sessionStarted = false; - - protected $options; - - /** - * Available options: - * - * * name: The cookie name (null [omitted] by default) - * * id: The session id (null [omitted] by default) - * * lifetime: Cookie lifetime - * * path: Cookie path - * * domain: Cookie domain - * * secure: Cookie secure - * * httponly: Cookie http only - * - * The default values for most options are those returned by the session_get_cookie_params() function - * - * @param array $options An associative array of session options - */ - public function __construct(array $options = array()) - { - $cookieDefaults = session_get_cookie_params(); - - $this->options = array_merge(array( - 'lifetime' => $cookieDefaults['lifetime'], - 'path' => $cookieDefaults['path'], - 'domain' => $cookieDefaults['domain'], - 'secure' => $cookieDefaults['secure'], - 'httponly' => isset($cookieDefaults['httponly']) ? $cookieDefaults['httponly'] : false, - ), $options); - - // Skip setting new session name if user don't want it - if (isset($this->options['name'])) { - session_name($this->options['name']); - } - } - - /** - * Starts the session. - * - * @api - */ - public function start() - { - if (self::$sessionStarted) { - return; - } - - session_set_cookie_params( - $this->options['lifetime'], - $this->options['path'], - $this->options['domain'], - $this->options['secure'], - $this->options['httponly'] - ); - - // disable native cache limiter as this is managed by HeaderBag directly - session_cache_limiter(false); - - if (!ini_get('session.use_cookies') && isset($this->options['id']) && $this->options['id'] && $this->options['id'] != session_id()) { - session_id($this->options['id']); - } - - session_start(); - - self::$sessionStarted = true; - } - - /** - * {@inheritDoc} - * - * @api - */ - public function getId() - { - if (!self::$sessionStarted) { - throw new \RuntimeException('The session must be started before reading its ID'); - } - - return session_id(); - } - - /** - * Reads data from this storage. - * - * The preferred format for a key is directory style so naming conflicts can be avoided. - * - * @param string $key A unique key identifying your data - * @param string $default Default value - * - * @return mixed Data associated with the key - * - * @api - */ - public function read($key, $default = null) - { - return array_key_exists($key, $_SESSION) ? $_SESSION[$key] : $default; - } - - /** - * Removes data from this storage. - * - * The preferred format for a key is directory style so naming conflicts can be avoided. - * - * @param string $key A unique key identifying your data - * - * @return mixed Data associated with the key - * - * @api - */ - public function remove($key) - { - $retval = null; - - if (isset($_SESSION[$key])) { - $retval = $_SESSION[$key]; - unset($_SESSION[$key]); - } - - return $retval; - } - - /** - * Writes data to this storage. - * - * The preferred format for a key is directory style so naming conflicts can be avoided. - * - * @param string $key A unique key identifying your data - * @param mixed $data Data associated with your key - * - * @api - */ - public function write($key, $data) - { - $_SESSION[$key] = $data; - } - - /** - * Regenerates id that represents this storage. - * - * @param Boolean $destroy Destroy session when regenerating? - * - * @return Boolean True if session regenerated, false if error - * - * @api - */ - public function regenerate($destroy = false) - { - if (self::$sessionIdRegenerated) { - return; - } - - session_regenerate_id($destroy); - - self::$sessionIdRegenerated = true; - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/SessionStorage/PdoSessionStorage.php b/core/includes/Symfony/Component/HttpFoundation/SessionStorage/PdoSessionStorage.php deleted file mode 100644 index 78f90b8ec95..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/SessionStorage/PdoSessionStorage.php +++ /dev/null @@ -1,263 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\SessionStorage; - -/** - * PdoSessionStorage. - * - * @author Fabien Potencier <fabien@symfony.com> - * @author Michael Williams <michael.williams@funsational.com> - */ -class PdoSessionStorage extends NativeSessionStorage -{ - private $db; - private $dbOptions; - - /** - * Constructor. - * - * @param \PDO $db A PDO instance - * @param array $options An associative array of session options - * @param array $dbOptions An associative array of DB options - * - * @throws \InvalidArgumentException When "db_table" option is not provided - * - * @see NativeSessionStorage::__construct() - */ - public function __construct(\PDO $db, array $options = array(), array $dbOptions = array()) - { - if (!array_key_exists('db_table', $dbOptions)) { - throw new \InvalidArgumentException('You must provide the "db_table" option for a PdoSessionStorage.'); - } - - $this->db = $db; - $this->dbOptions = array_merge(array( - 'db_id_col' => 'sess_id', - 'db_data_col' => 'sess_data', - 'db_time_col' => 'sess_time', - ), $dbOptions); - - parent::__construct($options); - } - - /** - * Starts the session. - */ - public function start() - { - if (self::$sessionStarted) { - return; - } - - // use this object as the session handler - session_set_save_handler( - array($this, 'sessionOpen'), - array($this, 'sessionClose'), - array($this, 'sessionRead'), - array($this, 'sessionWrite'), - array($this, 'sessionDestroy'), - array($this, 'sessionGC') - ); - - parent::start(); - } - - /** - * Opens a session. - * - * @param string $path (ignored) - * @param string $name (ignored) - * - * @return Boolean true, if the session was opened, otherwise an exception is thrown - */ - public function sessionOpen($path = null, $name = null) - { - return true; - } - - /** - * Closes a session. - * - * @return Boolean true, if the session was closed, otherwise false - */ - public function sessionClose() - { - // do nothing - return true; - } - - /** - * Destroys a session. - * - * @param string $id A session ID - * - * @return Boolean true, if the session was destroyed, otherwise an exception is thrown - * - * @throws \RuntimeException If the session cannot be destroyed - */ - public function sessionDestroy($id) - { - // get table/column - $dbTable = $this->dbOptions['db_table']; - $dbIdCol = $this->dbOptions['db_id_col']; - - // delete the record associated with this id - $sql = "DELETE FROM $dbTable WHERE $dbIdCol = :id"; - - try { - $stmt = $this->db->prepare($sql); - $stmt->bindParam(':id', $id, \PDO::PARAM_STR); - $stmt->execute(); - } catch (\PDOException $e) { - throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e); - } - - return true; - } - - /** - * Cleans up old sessions. - * - * @param int $lifetime The lifetime of a session - * - * @return Boolean true, if old sessions have been cleaned, otherwise an exception is thrown - * - * @throws \RuntimeException If any old sessions cannot be cleaned - */ - public function sessionGC($lifetime) - { - // get table/column - $dbTable = $this->dbOptions['db_table']; - $dbTimeCol = $this->dbOptions['db_time_col']; - - // delete the record associated with this id - $sql = "DELETE FROM $dbTable WHERE $dbTimeCol < (:time - $lifetime)"; - - try { - $this->db->query($sql); - $stmt = $this->db->prepare($sql); - $stmt->bindValue(':time', time(), \PDO::PARAM_INT); - $stmt->execute(); - } catch (\PDOException $e) { - throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e); - } - - return true; - } - - /** - * Reads a session. - * - * @param string $id A session ID - * - * @return string The session data if the session was read or created, otherwise an exception is thrown - * - * @throws \RuntimeException If the session cannot be read - */ - public function sessionRead($id) - { - // get table/columns - $dbTable = $this->dbOptions['db_table']; - $dbDataCol = $this->dbOptions['db_data_col']; - $dbIdCol = $this->dbOptions['db_id_col']; - - try { - $sql = "SELECT $dbDataCol FROM $dbTable WHERE $dbIdCol = :id"; - - $stmt = $this->db->prepare($sql); - $stmt->bindParam(':id', $id, \PDO::PARAM_STR, 255); - - $stmt->execute(); - // it is recommended to use fetchAll so that PDO can close the DB cursor - // we anyway expect either no rows, or one row with one column. fetchColumn, seems to be buggy #4777 - $sessionRows = $stmt->fetchAll(\PDO::FETCH_NUM); - - if (count($sessionRows) == 1) { - return $sessionRows[0][0]; - } - - // session does not exist, create it - $this->createNewSession($id); - - return ''; - } catch (\PDOException $e) { - throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e); - } - } - - /** - * Writes session data. - * - * @param string $id A session ID - * @param string $data A serialized chunk of session data - * - * @return Boolean true, if the session was written, otherwise an exception is thrown - * - * @throws \RuntimeException If the session data cannot be written - */ - public function sessionWrite($id, $data) - { - // get table/column - $dbTable = $this->dbOptions['db_table']; - $dbDataCol = $this->dbOptions['db_data_col']; - $dbIdCol = $this->dbOptions['db_id_col']; - $dbTimeCol = $this->dbOptions['db_time_col']; - - $sql = ('mysql' === $this->db->getAttribute(\PDO::ATTR_DRIVER_NAME)) - ? "INSERT INTO $dbTable ($dbIdCol, $dbDataCol, $dbTimeCol) VALUES (:id, :data, :time) " - ."ON DUPLICATE KEY UPDATE $dbDataCol = VALUES($dbDataCol), $dbTimeCol = CASE WHEN $dbTimeCol = :time THEN (VALUES($dbTimeCol) + 1) ELSE VALUES($dbTimeCol) END" - : "UPDATE $dbTable SET $dbDataCol = :data, $dbTimeCol = :time WHERE $dbIdCol = :id"; - - try { - $stmt = $this->db->prepare($sql); - $stmt->bindParam(':id', $id, \PDO::PARAM_STR); - $stmt->bindParam(':data', $data, \PDO::PARAM_STR); - $stmt->bindValue(':time', time(), \PDO::PARAM_INT); - $stmt->execute(); - - if (!$stmt->rowCount()) { - // No session exists in the database to update. This happens when we have called - // session_regenerate_id() - $this->createNewSession($id, $data); - } - } catch (\PDOException $e) { - throw new \RuntimeException(sprintf('PDOException was thrown when trying to manipulate session data: %s', $e->getMessage()), 0, $e); - } - - return true; - } - - /** - * Creates a new session with the given $id and $data - * - * @param string $id - * @param string $data - */ - private function createNewSession($id, $data = '') - { - // get table/column - $dbTable = $this->dbOptions['db_table']; - $dbDataCol = $this->dbOptions['db_data_col']; - $dbIdCol = $this->dbOptions['db_id_col']; - $dbTimeCol = $this->dbOptions['db_time_col']; - - $sql = "INSERT INTO $dbTable ($dbIdCol, $dbDataCol, $dbTimeCol) VALUES (:id, :data, :time)"; - - $stmt = $this->db->prepare($sql); - $stmt->bindParam(':id', $id, \PDO::PARAM_STR); - $stmt->bindParam(':data', $data, \PDO::PARAM_STR); - $stmt->bindValue(':time', time(), \PDO::PARAM_INT); - $stmt->execute(); - - return true; - } -} diff --git a/core/includes/Symfony/Component/HttpFoundation/SessionStorage/SessionStorageInterface.php b/core/includes/Symfony/Component/HttpFoundation/SessionStorage/SessionStorageInterface.php deleted file mode 100644 index b61a2557b27..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/SessionStorage/SessionStorageInterface.php +++ /dev/null @@ -1,97 +0,0 @@ -<?php - -/* - * This file is part of the Symfony package. - * - * (c) Fabien Potencier <fabien@symfony.com> - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\HttpFoundation\SessionStorage; - -/** - * SessionStorageInterface. - * - * @author Fabien Potencier <fabien@symfony.com> - * - * @api - */ -interface SessionStorageInterface -{ - /** - * Starts the session. - * - * @api - */ - function start(); - - /** - * Returns the session ID - * - * @return mixed The session ID - * - * @throws \RuntimeException If the session was not started yet - * - * @api - */ - function getId(); - - /** - * Reads data from this storage. - * - * The preferred format for a key is directory style so naming conflicts can be avoided. - * - * @param string $key A unique key identifying your data - * - * @return mixed Data associated with the key - * - * @throws \RuntimeException If an error occurs while reading data from this storage - * - * @api - */ - function read($key); - - /** - * Removes data from this storage. - * - * The preferred format for a key is directory style so naming conflicts can be avoided. - * - * @param string $key A unique key identifying your data - * - * @return mixed Data associated with the key - * - * @throws \RuntimeException If an error occurs while removing data from this storage - * - * @api - */ - function remove($key); - - /** - * Writes data to this storage. - * - * The preferred format for a key is directory style so naming conflicts can be avoided. - * - * @param string $key A unique key identifying your data - * @param mixed $data Data associated with your key - * - * @throws \RuntimeException If an error occurs while writing to this storage - * - * @api - */ - function write($key, $data); - - /** - * Regenerates id that represents this storage. - * - * @param Boolean $destroy Destroy session when regenerating? - * - * @return Boolean True if session regenerated, false if error - * - * @throws \RuntimeException If an error occurs while regenerating this storage - * - * @api - */ - function regenerate($destroy = false); -} diff --git a/core/includes/Symfony/Component/HttpFoundation/composer.json b/core/includes/Symfony/Component/HttpFoundation/composer.json deleted file mode 100644 index b3cfbbd1a72..00000000000 --- a/core/includes/Symfony/Component/HttpFoundation/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "symfony/http-foundation", - "type": "library", - "description": "Symfony HttpFoundation Component", - "keywords": [], - "homepage": "http://symfony.com", - "version": "2.0.4", - "license": "MIT", - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "http://symfony.com/contributors" - } - ], - "require": { - "php": ">=5.3.2" - } -} diff --git a/core/includes/actions.inc b/core/includes/actions.inc deleted file mode 100644 index 760de8300b3..00000000000 --- a/core/includes/actions.inc +++ /dev/null @@ -1,383 +0,0 @@ -<?php - -/** - * @file - * This is the actions engine for executing stored actions. - */ - -/** - * @defgroup actions Actions - * @{ - * Functions that perform an action on a certain system object. - * - * Action functions are declared by modules by implementing hook_action_info(). - * Modules can cause action functions to run by calling actions_do(), and - * trigger.module provides a user interface that lets administrators define - * events that cause action functions to run. - * - * Each action function takes two to four arguments: - * - $entity: The object that the action acts on, such as a node, comment, or - * user. - * - $context: Array of additional information about what triggered the action. - * - $a1, $a2: Optional additional information, which can be passed into - * actions_do() and will be passed along to the action function. - * - * @} End of "defgroup actions". - */ - -/** - * Performs a given list of actions by executing their callback functions. - * - * Given the IDs of actions to perform, this function finds out what the - * callback functions for the actions are by querying the database. Then - * it calls each callback using the function call $function($object, $context, - * $a1, $a2), passing the input arguments of this function (see below) to the - * action function. - * - * @param $action_ids - * The IDs of the actions to perform. Can be a single action ID or an array - * of IDs. IDs of configurable actions must be given as numeric action IDs; - * IDs of non-configurable actions may be given as action function names. - * @param $object - * The object that the action will act on: a node, user, or comment object. - * @param $context - * Associative array containing extra information about what triggered - * the action call, with $context['hook'] giving the name of the hook - * that resulted in this call to actions_do(). - * @param $a1 - * Passed along to the callback. - * @param $a2 - * Passed along to the callback. - * @return - * An associative array containing the results of the functions that - * perform the actions, keyed on action ID. - * - * @ingroup actions - */ -function actions_do($action_ids, $object = NULL, $context = NULL, $a1 = NULL, $a2 = NULL) { - // $stack tracks the number of recursive calls. - static $stack; - $stack++; - if ($stack > variable_get('actions_max_stack', 35)) { - watchdog('actions', 'Stack overflow: too many calls to actions_do(). Aborting to prevent infinite recursion.', array(), WATCHDOG_ERROR); - return; - } - $actions = array(); - $available_actions = actions_list(); - $actions_result = array(); - if (is_array($action_ids)) { - $conditions = array(); - foreach ($action_ids as $action_id) { - if (is_numeric($action_id)) { - $conditions[] = $action_id; - } - elseif (isset($available_actions[$action_id])) { - $actions[$action_id] = $available_actions[$action_id]; - } - } - - // When we have action instances we must go to the database to retrieve - // instance data. - if (!empty($conditions)) { - $query = db_select('actions'); - $query->addField('actions', 'aid'); - $query->addField('actions', 'type'); - $query->addField('actions', 'callback'); - $query->addField('actions', 'parameters'); - $query->condition('aid', $conditions, 'IN'); - $result = $query->execute(); - foreach ($result as $action) { - $actions[$action->aid] = $action->parameters ? unserialize($action->parameters) : array(); - $actions[$action->aid]['callback'] = $action->callback; - $actions[$action->aid]['type'] = $action->type; - } - } - - // Fire actions, in no particular order. - foreach ($actions as $action_id => $params) { - // Configurable actions need parameters. - if (is_numeric($action_id)) { - $function = $params['callback']; - if (function_exists($function)) { - $context = array_merge($context, $params); - $actions_result[$action_id] = $function($object, $context, $a1, $a2); - } - else { - $actions_result[$action_id] = FALSE; - } - } - // Singleton action; $action_id is the function name. - else { - $actions_result[$action_id] = $action_id($object, $context, $a1, $a2); - } - } - } - // Optimized execution of a single action. - else { - // If it's a configurable action, retrieve stored parameters. - if (is_numeric($action_ids)) { - $action = db_query("SELECT callback, parameters FROM {actions} WHERE aid = :aid", array(':aid' => $action_ids))->fetchObject(); - $function = $action->callback; - if (function_exists($function)) { - $context = array_merge($context, unserialize($action->parameters)); - $actions_result[$action_ids] = $function($object, $context, $a1, $a2); - } - else { - $actions_result[$action_ids] = FALSE; - } - } - // Singleton action; $action_ids is the function name. - else { - if (function_exists($action_ids)) { - $actions_result[$action_ids] = $action_ids($object, $context, $a1, $a2); - } - else { - // Set to avoid undefined index error messages later. - $actions_result[$action_ids] = FALSE; - } - } - } - $stack--; - return $actions_result; -} - -/** - * Discovers all available actions by invoking hook_action_info(). - * - * This function contrasts with actions_get_all_actions(); see the - * documentation of actions_get_all_actions() for an explanation. - * - * @param $reset - * Reset the action info static cache. - * @return - * An associative array keyed on action function name, with the same format - * as the return value of hook_action_info(), containing all - * modules' hook_action_info() return values as modified by any - * hook_action_info_alter() implementations. - * - * @see hook_action_info() - */ -function actions_list($reset = FALSE) { - $actions = &drupal_static(__FUNCTION__); - if (!isset($actions) || $reset) { - $actions = module_invoke_all('action_info'); - drupal_alter('action_info', $actions); - } - - // See module_implements() for an explanation of this cast. - return (array) $actions; -} - -/** - * Retrieves all action instances from the database. - * - * This function differs from the actions_list() function, which gathers - * actions by invoking hook_action_info(). The actions returned by this - * function and the actions returned by actions_list() are partially - * synchronized. Non-configurable actions from hook_action_info() - * implementations are put into the database when actions_synchronize() is - * called, which happens when admin/config/system/actions is visited. Configurable - * actions are not added to the database until they are configured in the - * user interface, in which case a database row is created for each - * configuration of each action. - * - * @return - * Associative array keyed by numeric action ID. Each value is an associative - * array with keys 'callback', 'label', 'type' and 'configurable'. - */ -function actions_get_all_actions() { - $actions = db_query("SELECT aid, type, callback, parameters, label FROM {actions}")->fetchAllAssoc('aid', PDO::FETCH_ASSOC); - foreach ($actions as &$action) { - $action['configurable'] = (bool) $action['parameters']; - unset($action['parameters']); - unset($action['aid']); - } - return $actions; -} - -/** - * Creates an associative array keyed by hashes of function names or IDs. - * - * Hashes are used to prevent actual function names from going out into HTML - * forms and coming back. - * - * @param $actions - * An associative array with function names or action IDs as keys - * and associative arrays with keys 'label', 'type', etc. as values. - * This is usually the output of actions_list() or actions_get_all_actions(). - * @return - * An associative array whose keys are hashes of the input array keys, and - * whose corresponding values are associative arrays with components - * 'callback', 'label', 'type', and 'configurable' from the input array. - */ -function actions_actions_map($actions) { - $actions_map = array(); - foreach ($actions as $callback => $array) { - $key = drupal_hash_base64($callback); - $actions_map[$key]['callback'] = isset($array['callback']) ? $array['callback'] : $callback; - $actions_map[$key]['label'] = $array['label']; - $actions_map[$key]['type'] = $array['type']; - $actions_map[$key]['configurable'] = $array['configurable']; - } - return $actions_map; -} - -/** - * Given a hash of an action array key, returns the key (function or ID). - * - * Faster than actions_actions_map() when you only need the function name or ID. - * - * @param $hash - * Hash of a function name or action ID array key. The array key - * is a key into the return value of actions_list() (array key is the action - * function name) or actions_get_all_actions() (array key is the action ID). - * @return - * The corresponding array key, or FALSE if no match is found. - */ -function actions_function_lookup($hash) { - // Check for a function name match. - $actions_list = actions_list(); - foreach ($actions_list as $function => $array) { - if (drupal_hash_base64($function) == $hash) { - return $function; - } - } - $aid = FALSE; - // Must be a configurable action; check database. - $result = db_query("SELECT aid FROM {actions} WHERE parameters <> ''")->fetchAll(PDO::FETCH_ASSOC); - foreach ($result as $row) { - if (drupal_hash_base64($row['aid']) == $hash) { - $aid = $row['aid']; - break; - } - } - return $aid; -} - -/** - * Synchronizes actions that are provided by modules in hook_action_info(). - * - * Actions provided by modules in hook_action_info() implementations are - * synchronized with actions that are stored in the actions database table. - * This is necessary so that actions that do not require configuration can - * receive action IDs. - * - * @param $delete_orphans - * If TRUE, any actions that exist in the database but are no longer - * found in the code (for example, because the module that provides them has - * been disabled) will be deleted. - */ -function actions_synchronize($delete_orphans = FALSE) { - $actions_in_code = actions_list(TRUE); - $actions_in_db = db_query("SELECT aid, callback, label FROM {actions} WHERE parameters = ''")->fetchAllAssoc('callback', PDO::FETCH_ASSOC); - - // Go through all the actions provided by modules. - foreach ($actions_in_code as $callback => $array) { - // Ignore configurable actions since their instances get put in when the - // user adds the action. - if (!$array['configurable']) { - // If we already have an action ID for this action, no need to assign aid. - if (isset($actions_in_db[$callback])) { - unset($actions_in_db[$callback]); - } - else { - // This is a new singleton that we don't have an aid for; assign one. - db_insert('actions') - ->fields(array( - 'aid' => $callback, - 'type' => $array['type'], - 'callback' => $callback, - 'parameters' => '', - 'label' => $array['label'], - )) - ->execute(); - watchdog('actions', "Action '%action' added.", array('%action' => $array['label'])); - } - } - } - - // Any actions that we have left in $actions_in_db are orphaned. - if ($actions_in_db) { - $orphaned = array_keys($actions_in_db); - - if ($delete_orphans) { - $actions = db_query('SELECT aid, label FROM {actions} WHERE callback IN (:orphaned)', array(':orphaned' => $orphaned))->fetchAll(); - foreach ($actions as $action) { - actions_delete($action->aid); - watchdog('actions', "Removed orphaned action '%action' from database.", array('%action' => $action->label)); - } - } - else { - $link = l(t('Remove orphaned actions'), 'admin/config/system/actions/orphan'); - $count = count($actions_in_db); - $orphans = implode(', ', $orphaned); - watchdog('actions', '@count orphaned actions (%orphans) exist in the actions table. !link', array('@count' => $count, '%orphans' => $orphans, '!link' => $link), WATCHDOG_INFO); - } - } -} - -/** - * Saves an action and its user-supplied parameter values to the database. - * - * @param $function - * The name of the function to be called when this action is performed. - * @param $type - * The type of action, to describe grouping and/or context, e.g., 'node', - * 'user', 'comment', or 'system'. - * @param $params - * An associative array with parameter names as keys and parameter values as - * values. - * @param $label - * A user-supplied label of this particular action, e.g., 'Send e-mail - * to Jim'. - * @param $aid - * The ID of this action. If omitted, a new action is created. - * @return - * The ID of the action. - */ -function actions_save($function, $type, $params, $label, $aid = NULL) { - // aid is the callback for singleton actions so we need to keep a separate - // table for numeric aids. - if (!$aid) { - $aid = db_next_id(); - } - - db_merge('actions') - ->key(array('aid' => $aid)) - ->fields(array( - 'callback' => $function, - 'type' => $type, - 'parameters' => serialize($params), - 'label' => $label, - )) - ->execute(); - - watchdog('actions', 'Action %action saved.', array('%action' => $label)); - return $aid; -} - -/** - * Retrieves a single action from the database. - * - * @param $aid - * The ID of the action to retrieve. - * @return - * The appropriate action row from the database as an object. - */ -function actions_load($aid) { - return db_query("SELECT aid, type, callback, parameters, label FROM {actions} WHERE aid = :aid", array(':aid' => $aid))->fetchObject(); -} - -/** - * Deletes a single action from the database. - * - * @param $aid - * The ID of the action to delete. - */ -function actions_delete($aid) { - db_delete('actions') - ->condition('aid', $aid) - ->execute(); - module_invoke_all('actions_delete', $aid); -} - diff --git a/core/includes/ajax.inc b/core/includes/ajax.inc deleted file mode 100644 index cda55b42466..00000000000 --- a/core/includes/ajax.inc +++ /dev/null @@ -1,1206 +0,0 @@ -<?php - -/** - * @file - * Functions for use with Drupal's Ajax framework. - */ - -/** - * @defgroup ajax Ajax framework - * @{ - * Functions for Drupal's Ajax framework. - * - * Drupal's Ajax framework is used to dynamically update parts of a page's HTML - * based on data from the server. Upon a specified event, such as a button - * click, a callback function is triggered which performs server-side logic and - * may return updated markup, which is then replaced on-the-fly with no page - * refresh necessary. - * - * This framework creates a PHP macro language that allows the server to - * instruct JavaScript to perform actions on the client browser. When using - * forms, it can be used with the #ajax property. - * The #ajax property can be used to bind events to the Ajax framework. By - * default, #ajax uses 'system/ajax' as its path for submission and thus calls - * ajax_form_callback() and a defined #ajax['callback'] function. - * However, you may optionally specify a different path to request or a - * different callback function to invoke, which can return updated HTML or can - * also return a richer set of @link ajax_commands Ajax framework commands @endlink. - * - * Standard form handling is as follows: - * - A form element has a #ajax property that includes #ajax['callback'] and - * omits #ajax['path']. See below about using #ajax['path'] to implement - * advanced use-cases that require something other than standard form - * handling. - * - On the specified element, Ajax processing is triggered by a change to - * that element. - * - The browser submits an HTTP POST request to the 'system/ajax' Drupal - * path. - * - The menu page callback for 'system/ajax', ajax_form_callback(), calls - * drupal_process_form() to process the form submission and rebuild the - * form if necessary. The form is processed in much the same way as if it - * were submitted without Ajax, with the same #process functions and - * validation and submission handlers called in either case, making it easy - * to create Ajax-enabled forms that degrade gracefully when JavaScript is - * disabled. - * - After form processing is complete, ajax_form_callback() calls the - * function named by #ajax['callback'], which returns the form element that - * has been updated and needs to be returned to the browser, or - * alternatively, an array of custom Ajax commands. - * - The page delivery callback for 'system/ajax', ajax_deliver(), renders the - * element returned by #ajax['callback'], and returns the JSON string - * created by ajax_render() to the browser. - * - The browser unserializes the returned JSON string into an array of - * command objects and executes each command, resulting in the old page - * content within and including the HTML element specified by - * #ajax['wrapper'] being replaced by the new content returned by - * #ajax['callback'], using a JavaScript animation effect specified by - * #ajax['effect']. - * - * A simple example of basic Ajax use from the - * @link http://drupal.org/project/examples Examples module @endlink follows: - * @code - * function main_page() { - * return drupal_get_form('ajax_example_simplest'); - * } - * - * function ajax_example_simplest($form, &$form_state) { - * $form = array(); - * $form['changethis'] = array( - * '#type' => 'select', - * '#options' => array( - * 'one' => 'one', - * 'two' => 'two', - * 'three' => 'three', - * ), - * '#ajax' => array( - * 'callback' => 'ajax_example_simplest_callback', - * 'wrapper' => 'replace_textfield_div', - * ), - * ); - - * // This entire form element will be replaced with an updated value. - * $form['replace_textfield'] = array( - * '#type' => 'textfield', - * '#title' => t("The default value will be changed"), - * '#description' => t("Say something about why you chose") . "'" . - * (!empty($form_state['values']['changethis']) - * ? $form_state['values']['changethis'] : t("Not changed yet")) . "'", - * '#prefix' => '<div id="replace_textfield_div">', - * '#suffix' => '</div>', - * ); - * return $form; - * } - * - * function ajax_example_simplest_callback($form, $form_state) { - * // The form has already been submitted and updated. We can return the replaced - * // item as it is. - * return $form['replace_textfield']; - * } - * @endcode - * - * In the above example, the 'changethis' element is Ajax-enabled. The default - * #ajax['event'] is 'change', so when the 'changethis' element changes, - * an Ajax call is made. The form is submitted and reprocessed, and then the - * callback is called. In this case, the form has been automatically - * built changing $form['replace_textfield']['#description'], so the callback - * just returns that part of the form. - * - * To implement Ajax handling in a form, add '#ajax' to the form - * definition of a field. That field will trigger an Ajax event when it is - * clicked (or changed, depending on the kind of field). #ajax supports - * the following parameters (either 'path' or 'callback' is required at least): - * - #ajax['callback']: The callback to invoke to handle the server side of the - * Ajax event, which will receive a $form and $form_state as arguments, and - * returns a renderable array (most often a form or form fragment), an HTML - * string, or an array of Ajax commands. If returning a renderable array or - * a string, the value will replace the original element named in - * #ajax['wrapper'], and - * theme_status_messages() - * will be prepended to that - * element. (If the status messages are not wanted, return an array - * of Ajax commands instead.) - * #ajax['wrapper']. If an array of Ajax commands is returned, it will be - * executed by the calling code. - * - #ajax['path']: The menu path to use for the request. This is often omitted - * and the default is used. This path should map - * to a menu page callback that returns data using ajax_render(). Defaults to - * 'system/ajax', which invokes ajax_form_callback(), eventually calling - * the function named in #ajax['callback']. If you use a custom - * path, you must set up the menu entry and handle the entire callback in your - * own code. - * - #ajax['wrapper']: The CSS ID of the area to be replaced by the content - * returned by the #ajax['callback'] function. The content returned from - * the callback will replace the entire element named by #ajax['wrapper']. - * The wrapper is usually created using #prefix and #suffix properties in the - * form. Note that this is the wrapper ID, not a CSS selector. So to replace - * the element referred to by the CSS selector #some-selector on the page, - * use #ajax['wrapper'] = 'some-selector', not '#some-selector'. - * - #ajax['effect']: The jQuery effect to use when placing the new HTML. - * Defaults to no effect. Valid options are 'none', 'slide', or 'fade'. - * - #ajax['speed']: The effect speed to use. Defaults to 'slow'. May be - * 'slow', 'fast' or a number in milliseconds which represents the length - * of time the effect should run. - * - #ajax['event']: The JavaScript event to respond to. This is normally - * selected automatically for the type of form widget being used, and - * is only needed if you need to override the default behavior. - * - #ajax['prevent']: A JavaScript event to prevent when 'event' is triggered. - * Defaults to 'click' for #ajax on #type 'submit', 'button', and - * 'image_button'. Multiple events may be specified separated by spaces. - * For example, when binding #ajax behaviors to form buttons, pressing the - * ENTER key within a textfield triggers the 'click' event of the form's first - * submit button. Triggering Ajax in this situation leads to problems, like - * breaking autocomplete textfields. Because of that, Ajax behaviors are bound - * to the 'mousedown' event on form buttons by default. However, binding to - * 'mousedown' rather than 'click' means that it is possible to trigger a - * click by pressing the mouse, holding the mouse button down until the Ajax - * request is complete and the button is re-enabled, and then releasing the - * mouse button. For this case, 'prevent' can be set to 'click', so an - * additional event handler is bound to prevent such a click from triggering a - * non-Ajax form submission. This also prevents a textfield's ENTER press - * triggering a button's non-Ajax form submission behavior. - * - #ajax['method']: The jQuery method to use to place the new HTML. - * Defaults to 'replaceWith'. May be: 'replaceWith', 'append', 'prepend', - * 'before', 'after', or 'html'. See the - * @link http://api.jquery.com/category/manipulation/ jQuery manipulators documentation @endlink - * for more information on these methods. - * - #ajax['progress']: Choose either a throbber or progress bar that is - * displayed while awaiting a response from the callback, and add an optional - * message. Possible keys: 'type', 'message', 'url', 'interval'. - * More information is available in the - * @link http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html/7 Form API Reference @endlink - * - * In addition to using Form API for doing in-form modification, Ajax may be - * enabled by adding classes to buttons and links. By adding the 'use-ajax' - * class to a link, the link will be loaded via an Ajax call. When using this - * method, the href of the link can contain '/nojs/' as part of the path. When - * the Ajax framework makes the request, it will convert this to '/ajax/'. - * The server is then able to easily tell if this request was made through an - * actual Ajax request or in a degraded state, and respond appropriately. - * - * Similarly, submit buttons can be given the class 'use-ajax-submit'. The - * form will then be submitted via Ajax to the path specified in the #action. - * Like the ajax-submit class above, this path will have '/nojs/' replaced with - * '/ajax/' so that the submit handler can tell if the form was submitted - * in a degraded state or not. - * - * When responding to Ajax requests, the server should do what it needs to do - * for that request, then create a commands array. This commands array will - * be converted to a JSON object and returned to the client, which will then - * iterate over the array and process it like a macro language. - * - * Each command item is an associative array which will be converted to a command - * object on the JavaScript side. $command_item['command'] is the type of - * command, e.g. 'alert' or 'replace', and will correspond to a method in the - * Drupal.ajax[command] space. The command array may contain any other data - * that the command needs to process, e.g. 'method', 'selector', 'settings', etc. - * - * Commands are usually created with a couple of helper functions, so they - * look like this: - * @code - * $commands = array(); - * // Replace the content of '#object-1' on the page with 'some html here'. - * $commands[] = ajax_command_replace('#object-1', 'some html here'); - * // Add a visual "changed" marker to the '#object-1' element. - * $commands[] = ajax_command_changed('#object-1'); - * // Menu 'page callback' and #ajax['callback'] functions are supposed to - * // return render arrays. If returning an Ajax commands array, it must be - * // encapsulated in a render array structure. - * return array('#type' => 'ajax', '#commands' => $commands); - * @endcode - * - * When returning an Ajax command array, it is often useful to have - * status messages rendered along with other tasks in the command array. - * In that case the the Ajax commands array may be constructed like this: - * @code - * $commands = array(); - * $commands[] = ajax_command_replace(NULL, $output); - * $commands[] = ajax_command_prepend(NULL, theme('status_messages')); - * return array('#type' => 'ajax', '#commands' => $commands); - * @endcode - * - * See @link ajax_commands Ajax framework commands @endlink - */ - -/** - * Render a commands array into JSON. - * - * @param $commands - * A list of macro commands generated by the use of ajax_command_*() - * functions. - */ -function ajax_render($commands = array()) { - // Ajax responses aren't rendered with html.tpl.php, so we have to call - // drupal_get_css() and drupal_get_js() here, in order to have new files added - // during this request to be loaded by the page. We only want to send back - // files that the page hasn't already loaded, so we implement simple diffing - // logic using array_diff_key(). - foreach (array('css', 'js') as $type) { - // It is highly suspicious if $_POST['ajax_page_state'][$type] is empty, - // since the base page ought to have at least one JS file and one CSS file - // loaded. It probably indicates an error, and rather than making the page - // reload all of the files, instead we return no new files. - if (empty($_POST['ajax_page_state'][$type])) { - $items[$type] = array(); - } - else { - $function = 'drupal_add_' . $type; - $items[$type] = $function(); - drupal_alter($type, $items[$type]); - // @todo Inline CSS and JS items are indexed numerically. These can't be - // reliably diffed with array_diff_key(), since the number can change - // due to factors unrelated to the inline content, so for now, we strip - // the inline items from Ajax responses, and can add support for them - // when drupal_add_css() and drupal_add_js() are changed to using md5() - // or some other hash of the inline content. - foreach ($items[$type] as $key => $item) { - if (is_numeric($key)) { - unset($items[$type][$key]); - } - } - // Ensure that the page doesn't reload what it already has. - $items[$type] = array_diff_key($items[$type], $_POST['ajax_page_state'][$type]); - } - } - - // Render the HTML to load these files, and add AJAX commands to insert this - // HTML in the page. We pass TRUE as the $skip_alter argument to prevent the - // data from being altered again, as we already altered it above. Settings are - // handled separately, afterwards. - if (isset($items['js']['settings'])) { - unset($items['js']['settings']); - } - $styles = drupal_get_css($items['css'], TRUE); - $scripts_footer = drupal_get_js('footer', $items['js'], TRUE); - $scripts_header = drupal_get_js('header', $items['js'], TRUE); - - $extra_commands = array(); - if (!empty($styles)) { - $extra_commands[] = ajax_command_prepend('head', $styles); - } - if (!empty($scripts_header)) { - $extra_commands[] = ajax_command_prepend('head', $scripts_header); - } - if (!empty($scripts_footer)) { - $extra_commands[] = ajax_command_append('body', $scripts_footer); - } - if (!empty($extra_commands)) { - $commands = array_merge($extra_commands, $commands); - } - - // Now add a command to merge changes and additions to Drupal.settings. - $scripts = drupal_add_js(); - if (!empty($scripts['settings'])) { - $settings = $scripts['settings']; - array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $settings['data']), TRUE)); - } - - // Allow modules to alter any Ajax response. - drupal_alter('ajax_render', $commands); - - return drupal_json_encode($commands); -} - -/** - * Get a form submitted via #ajax during an Ajax callback. - * - * This will load a form from the form cache used during Ajax operations. It - * pulls the form info from $_POST. - * - * @return - * An array containing the $form and $form_state. Use the list() function - * to break these apart: - * @code - * list($form, $form_state, $form_id, $form_build_id) = ajax_get_form(); - * @endcode - */ -function ajax_get_form() { - $form_state = form_state_defaults(); - - $form_build_id = $_POST['form_build_id']; - - // Get the form from the cache. - $form = form_get_cache($form_build_id, $form_state); - if (!$form) { - // If $form cannot be loaded from the cache, the form_build_id in $_POST - // must be invalid, which means that someone performed a POST request onto - // system/ajax without actually viewing the concerned form in the browser. - // This is likely a hacking attempt as it never happens under normal - // circumstances, so we just do nothing. - watchdog('ajax', 'Invalid form POST data.', array(), WATCHDOG_WARNING); - drupal_exit(); - } - - // Since some of the submit handlers are run, redirects need to be disabled. - $form_state['no_redirect'] = TRUE; - - // When a form is rebuilt after Ajax processing, its #build_id and #action - // should not change. - // @see drupal_rebuild_form() - $form_state['rebuild_info']['copy']['#build_id'] = TRUE; - $form_state['rebuild_info']['copy']['#action'] = TRUE; - - // The form needs to be processed; prepare for that by setting a few internal - // variables. - $form_state['input'] = $_POST; - $form_id = $form['#form_id']; - - return array($form, $form_state, $form_id, $form_build_id); -} - -/** - * Menu callback; handles Ajax requests for the #ajax Form API property. - * - * This rebuilds the form from cache and invokes the defined #ajax['callback'] - * to return an Ajax command structure for JavaScript. In case no 'callback' has - * been defined, nothing will happen. - * - * The Form API #ajax property can be set both for buttons and other input - * elements. - * - * This function is also the canonical example of how to implement - * #ajax['path']. If processing is required that cannot be accomplished with - * a callback, re-implement this function and set #ajax['path'] to the - * enhanced function. - */ -function ajax_form_callback() { - list($form, $form_state) = ajax_get_form(); - drupal_process_form($form['#form_id'], $form, $form_state); - - // We need to return the part of the form (or some other content) that needs - // to be re-rendered so the browser can update the page with changed content. - // Since this is the generic menu callback used by many Ajax elements, it is - // up to the #ajax['callback'] function of the element (may or may not be a - // button) that triggered the Ajax request to determine what needs to be - // rendered. - if (!empty($form_state['triggering_element'])) { - $callback = $form_state['triggering_element']['#ajax']['callback']; - } - if (!empty($callback) && function_exists($callback)) { - return $callback($form, $form_state); - } -} - -/** - * Theme callback for Ajax requests. - * - * Many different pages can invoke an Ajax request to system/ajax or another - * generic Ajax path. It is almost always desired for an Ajax response to be - * rendered using the same theme as the base page, because most themes are built - * with the assumption that they control the entire page, so if the CSS for two - * themes are both loaded for a given page, they may conflict with each other. - * For example, Bartik is Drupal's default theme, and Seven is Drupal's default - * administration theme. Depending on whether the "Use the administration theme - * when editing or creating content" checkbox is checked, the node edit form may - * be displayed in either theme, but the Ajax response to the Field module's - * "Add another item" button should be rendered using the same theme as the rest - * of the page. Therefore, system_menu() sets the 'theme callback' for - * 'system/ajax' to this function, and it is recommended that modules - * implementing other generic Ajax paths do the same. - */ -function ajax_base_page_theme() { - if (!empty($_POST['ajax_page_state']['theme']) && !empty($_POST['ajax_page_state']['theme_token'])) { - $theme = $_POST['ajax_page_state']['theme']; - $token = $_POST['ajax_page_state']['theme_token']; - - // Prevent a request forgery from giving a person access to a theme they - // shouldn't be otherwise allowed to see. However, since everyone is allowed - // to see the default theme, token validation isn't required for that, and - // bypassing it allows most use-cases to work even when accessed from the - // page cache. - if ($theme === variable_get('theme_default', 'bartik') || drupal_valid_token($token, $theme)) { - return $theme; - } - } -} - -/** - * Package and send the result of a page callback to the browser as an Ajax response. - * - * This function is the equivalent of drupal_deliver_html_page(), but for Ajax - * requests. Like that function, it: - * - Adds needed HTTP headers. - * - Prints rendered output. - * - Performs end-of-request tasks. - * - * @param $page_callback_result - * The result of a page callback. Can be one of: - * - NULL: to indicate no content. - * - An integer menu status constant: to indicate an error condition. - * - A string of HTML content. - * - A renderable array of content. - * - * @see drupal_deliver_html_page() - */ -function ajax_deliver($page_callback_result) { - // Browsers do not allow JavaScript to read the contents of a user's local - // files. To work around that, the jQuery Form plugin submits forms containing - // a file input element to an IFRAME, instead of using XHR. Browsers do not - // normally expect JSON strings as content within an IFRAME, so the response - // must be customized accordingly. - // @see http://malsup.com/jquery/form/#file-upload - // @see Drupal.ajax.prototype.beforeSend() - $iframe_upload = !empty($_POST['ajax_iframe_upload']); - - // Emit a Content-Type HTTP header if none has been added by the page callback - // or by a wrapping delivery callback. - if (is_null(drupal_get_http_header('Content-Type'))) { - if (!$iframe_upload) { - // Standard JSON can be returned to a browser's XHR object, and to - // non-browser user agents. - // @see http://www.ietf.org/rfc/rfc4627.txt?number=4627 - drupal_add_http_header('Content-Type', 'application/json; charset=utf-8'); - } - else { - // Browser IFRAMEs expect HTML. With most other content types, Internet - // Explorer presents the user with a download prompt. - drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); - } - } - - // Print the response. - $commands = ajax_prepare_response($page_callback_result); - $json = ajax_render($commands); - if (!$iframe_upload) { - // Standard JSON can be returned to a browser's XHR object, and to - // non-browser user agents. - print $json; - } - else { - // Browser IFRAMEs expect HTML. Browser extensions, such as Linkification - // and Skype's Browser Highlighter, convert URLs, phone numbers, etc. into - // links. This corrupts the JSON response. Protect the integrity of the - // JSON data by making it the value of a textarea. - // @see http://malsup.com/jquery/form/#file-upload - // @see http://drupal.org/node/1009382 - print '<textarea>' . $json . '</textarea>'; - } - - // Perform end-of-request tasks. - ajax_footer(); -} - -/** - * Converts the return value of a page callback into an Ajax commands array. - * - * @param $page_callback_result - * The result of a page callback. Can be one of: - * - NULL: to indicate no content. - * - An integer menu status constant: to indicate an error condition. - * - A string of HTML content. - * - A renderable array of content. - * - * @return - * An Ajax commands array that can be passed to ajax_render(). - */ -function ajax_prepare_response($page_callback_result) { - $commands = array(); - if (!isset($page_callback_result)) { - // Simply delivering an empty commands array is sufficient. This results - // in the Ajax request being completed, but nothing being done to the page. - } - elseif (is_int($page_callback_result)) { - switch ($page_callback_result) { - case MENU_NOT_FOUND: - $commands[] = ajax_command_alert(t('The requested page could not be found.')); - break; - - case MENU_ACCESS_DENIED: - $commands[] = ajax_command_alert(t('You are not authorized to access this page.')); - break; - - case MENU_SITE_OFFLINE: - $commands[] = ajax_command_alert(filter_xss_admin(variable_get('maintenance_mode_message', - t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal')))))); - break; - } - } - elseif (is_array($page_callback_result) && isset($page_callback_result['#type']) && ($page_callback_result['#type'] == 'ajax')) { - // Complex Ajax callbacks can return a result that contains an error message - // or a specific set of commands to send to the browser. - $page_callback_result += element_info('ajax'); - $error = $page_callback_result['#error']; - if (isset($error) && $error !== FALSE) { - if ((empty($error) || $error === TRUE)) { - $error = t('An error occurred while handling the request: The server received invalid input.'); - } - $commands[] = ajax_command_alert($error); - } - else { - $commands = $page_callback_result['#commands']; - } - } - else { - // Like normal page callbacks, simple Ajax callbacks can return HTML - // content, as a string or render array. This HTML is inserted in some - // relationship to #ajax['wrapper'], as determined by which jQuery DOM - // manipulation method is used. The method used is specified by - // #ajax['method']. The default method is 'replaceWith', which completely - // replaces the old wrapper element and its content with the new HTML. - $html = is_string($page_callback_result) ? $page_callback_result : drupal_render($page_callback_result); - $commands[] = ajax_command_insert(NULL, $html); - // Add the status messages inside the new content's wrapper element, so that - // on subsequent Ajax requests, it is treated as old content. - $commands[] = ajax_command_prepend(NULL, theme('status_messages')); - } - - return $commands; -} - -/** - * Perform end-of-Ajax-request tasks. - * - * This function is the equivalent of drupal_page_footer(), but for Ajax - * requests. - * - * @see drupal_page_footer() - */ -function ajax_footer() { - // Even for Ajax requests, invoke hook_exit() implementations. There may be - // modules that need very fast Ajax responses, and therefore, run Ajax - // requests with an early bootstrap. - if (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')) { - module_invoke_all('exit'); - } - - // Commit the user session. See above comment about the possibility of this - // function running without session.inc loaded. - if (function_exists('drupal_session_commit')) { - drupal_session_commit(); - } -} - -/** - * Form element process callback to handle #ajax. - * - * @param $element - * An associative array containing the properties of the element. - * - * @return - * The processed element. - * - * @see ajax_pre_render_element() - */ -function ajax_process_form($element, &$form_state) { - $element = ajax_pre_render_element($element); - if (!empty($element['#ajax_processed'])) { - $form_state['cache'] = TRUE; - } - return $element; -} - -/** - * Add Ajax information about an element to the page to communicate with JavaScript. - * - * If #ajax['path'] is set on an element, this additional JavaScript is added - * to the page header to attach the Ajax behaviors. See ajax.js for more - * information. - * - * @param $element - * An associative array containing the properties of the element. - * Properties used: - * - #ajax['event'] - * - #ajax['prevent'] - * - #ajax['path'] - * - #ajax['options'] - * - #ajax['wrapper'] - * - #ajax['parameters'] - * - #ajax['effect'] - * - * @return - * The processed element with the necessary JavaScript attached to it. - */ -function ajax_pre_render_element($element) { - // Skip already processed elements. - if (isset($element['#ajax_processed'])) { - return $element; - } - // Initialize #ajax_processed, so we do not process this element again. - $element['#ajax_processed'] = FALSE; - - // Nothing to do if there is neither a callback nor a path. - if (!(isset($element['#ajax']['callback']) || isset($element['#ajax']['path']))) { - return $element; - } - - // Add a reasonable default event handler if none was specified. - if (isset($element['#ajax']) && !isset($element['#ajax']['event'])) { - switch ($element['#type']) { - case 'submit': - case 'button': - case 'image_button': - // Pressing the ENTER key within a textfield triggers the click event of - // the form's first submit button. Triggering Ajax in this situation - // leads to problems, like breaking autocomplete textfields, so we bind - // to mousedown instead of click. - // @see http://drupal.org/node/216059 - $element['#ajax']['event'] = 'mousedown'; - // Retain keyboard accessibility by setting 'keypress'. This causes - // ajax.js to trigger 'event' when SPACE or ENTER are pressed while the - // button has focus. - $element['#ajax']['keypress'] = TRUE; - // Binding to mousedown rather than click means that it is possible to - // trigger a click by pressing the mouse, holding the mouse button down - // until the Ajax request is complete and the button is re-enabled, and - // then releasing the mouse button. Set 'prevent' so that ajax.js binds - // an additional handler to prevent such a click from triggering a - // non-Ajax form submission. This also prevents a textfield's ENTER - // press triggering this button's non-Ajax form submission behavior. - if (!isset($element['#ajax']['prevent'])) { - $element['#ajax']['prevent'] = 'click'; - } - break; - - case 'password': - case 'textfield': - case 'textarea': - $element['#ajax']['event'] = 'blur'; - break; - - case 'radio': - case 'checkbox': - case 'select': - $element['#ajax']['event'] = 'change'; - break; - - case 'link': - $element['#ajax']['event'] = 'click'; - break; - - default: - return $element; - } - } - - // Attach JavaScript settings to the element. - if (isset($element['#ajax']['event'])) { - $element['#attached']['library'][] = array('system', 'jquery.form'); - $element['#attached']['library'][] = array('system', 'drupal.ajax'); - - $settings = $element['#ajax']; - - // Assign default settings. - $settings += array( - 'path' => 'system/ajax', - 'options' => array(), - ); - - // @todo Legacy support. Remove in Drupal 8. - if (isset($settings['method']) && $settings['method'] == 'replace') { - $settings['method'] = 'replaceWith'; - } - - // Change path to URL. - $settings['url'] = url($settings['path'], $settings['options']); - unset($settings['path'], $settings['options']); - - // Add special data to $settings['submit'] so that when this element - // triggers an Ajax submission, Drupal's form processing can determine which - // element triggered it. - // @see _form_element_triggered_scripted_submission() - if (isset($settings['trigger_as'])) { - // An element can add a 'trigger_as' key within #ajax to make the element - // submit as though another one (for example, a non-button can use this - // to submit the form as though a button were clicked). When using this, - // the 'name' key is always required to identify the element to trigger - // as. The 'value' key is optional, and only needed when multiple elements - // share the same name, which is commonly the case for buttons. - $settings['submit']['_triggering_element_name'] = $settings['trigger_as']['name']; - if (isset($settings['trigger_as']['value'])) { - $settings['submit']['_triggering_element_value'] = $settings['trigger_as']['value']; - } - unset($settings['trigger_as']); - } - elseif (isset($element['#name'])) { - // Most of the time, elements can submit as themselves, in which case the - // 'trigger_as' key isn't needed, and the element's name is used. - $settings['submit']['_triggering_element_name'] = $element['#name']; - // If the element is a (non-image) button, its name may not identify it - // uniquely, in which case a match on value is also needed. - // @see _form_button_was_clicked() - if (isset($element['#button_type']) && empty($element['#has_garbage_value'])) { - $settings['submit']['_triggering_element_value'] = $element['#value']; - } - } - - // Convert a simple #ajax['progress'] string into an array. - if (isset($settings['progress']) && is_string($settings['progress'])) { - $settings['progress'] = array('type' => $settings['progress']); - } - // Change progress path to a full URL. - if (isset($settings['progress']['path'])) { - $settings['progress']['url'] = url($settings['progress']['path']); - unset($settings['progress']['path']); - } - - $element['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array('ajax' => array($element['#id'] => $settings)), - ); - - // Indicate that Ajax processing was successful. - $element['#ajax_processed'] = TRUE; - } - return $element; -} - -/** - * @} End of "defgroup ajax". - */ - -/** - * @defgroup ajax_commands Ajax framework commands - * @{ - * Functions to create various Ajax commands. - * - * These functions can be used to create arrays for use with the - * ajax_render() function. - */ - -/** - * Creates a Drupal Ajax 'alert' command. - * - * The 'alert' command instructs the client to display a JavaScript alert - * dialog box. - * - * This command is implemented by Drupal.ajax.prototype.commands.alert() - * defined in misc/ajax.js. - * - * @param $text - * The message string to display to the user. - * - * @return - * An array suitable for use with the ajax_render() function. - */ -function ajax_command_alert($text) { - return array( - 'command' => 'alert', - 'text' => $text, - ); -} - -/** - * Creates a Drupal Ajax 'insert' command using the method in #ajax['method']. - * - * This command instructs the client to insert the given HTML using whichever - * jQuery DOM manipulation method has been specified in the #ajax['method'] - * variable of the element that triggered the request. - * - * This command is implemented by Drupal.ajax.prototype.commands.insert() - * defined in misc/ajax.js. - * - * @param $selector - * A jQuery selector string. If the command is a response to a request from - * an #ajax form element then this value can be NULL. - * @param $html - * The data to use with the jQuery method. - * @param $settings - * An optional array of settings that will be used for this command only. - * - * @return - * An array suitable for use with the ajax_render() function. - */ -function ajax_command_insert($selector, $html, $settings = NULL) { - return array( - 'command' => 'insert', - 'method' => NULL, - 'selector' => $selector, - 'data' => $html, - 'settings' => $settings, - ); -} - -/** - * Creates a Drupal Ajax 'insert/replaceWith' command. - * - * The 'insert/replaceWith' command instructs the client to use jQuery's - * replaceWith() method to replace each element matched matched by the given - * selector with the given HTML. - * - * This command is implemented by Drupal.ajax.prototype.commands.insert() - * defined in misc/ajax.js. - * - * @param $selector - * A jQuery selector string. If the command is a response to a request from - * an #ajax form element then this value can be NULL. - * @param $html - * The data to use with the jQuery replaceWith() method. - * @param $settings - * An optional array of settings that will be used for this command only. - * - * @return - * An array suitable for use with the ajax_render() function. - * - * See @link http://docs.jquery.com/Manipulation/replaceWith#content jQuery replaceWith command @endlink - */ -function ajax_command_replace($selector, $html, $settings = NULL) { - return array( - 'command' => 'insert', - 'method' => 'replaceWith', - 'selector' => $selector, - 'data' => $html, - 'settings' => $settings, - ); -} - -/** - * Creates a Drupal Ajax 'insert/html' command. - * - * The 'insert/html' command instructs the client to use jQuery's html() - * method to set the HTML content of each element matched by the given - * selector while leaving the outer tags intact. - * - * This command is implemented by Drupal.ajax.prototype.commands.insert() - * defined in misc/ajax.js. - * - * @param $selector - * A jQuery selector string. If the command is a response to a request from - * an #ajax form element then this value can be NULL. - * @param $html - * The data to use with the jQuery html() method. - * @param $settings - * An optional array of settings that will be used for this command only. - * - * @return - * An array suitable for use with the ajax_render() function. - * - * @see http://docs.jquery.com/Attributes/html#val - */ -function ajax_command_html($selector, $html, $settings = NULL) { - return array( - 'command' => 'insert', - 'method' => 'html', - 'selector' => $selector, - 'data' => $html, - 'settings' => $settings, - ); -} - -/** - * Creates a Drupal Ajax 'insert/prepend' command. - * - * The 'insert/prepend' command instructs the client to use jQuery's prepend() - * method to prepend the given HTML content to the inside each element matched - * by the given selector. - * - * This command is implemented by Drupal.ajax.prototype.commands.insert() - * defined in misc/ajax.js. - * - * @param $selector - * A jQuery selector string. If the command is a response to a request from - * an #ajax form element then this value can be NULL. - * @param $html - * The data to use with the jQuery prepend() method. - * @param $settings - * An optional array of settings that will be used for this command only. - * - * @return - * An array suitable for use with the ajax_render() function. - * - * @see http://docs.jquery.com/Manipulation/prepend#content - */ -function ajax_command_prepend($selector, $html, $settings = NULL) { - return array( - 'command' => 'insert', - 'method' => 'prepend', - 'selector' => $selector, - 'data' => $html, - 'settings' => $settings, - ); -} - -/** - * Creates a Drupal Ajax 'insert/append' command. - * - * The 'insert/append' command instructs the client to use jQuery's append() - * method to append the given HTML content to the inside of each element matched - * by the given selector. - * - * This command is implemented by Drupal.ajax.prototype.commands.insert() - * defined in misc/ajax.js. - * - * @param $selector - * A jQuery selector string. If the command is a response to a request from - * an #ajax form element then this value can be NULL. - * @param $html - * The data to use with the jQuery append() method. - * @param $settings - * An optional array of settings that will be used for this command only. - * - * @return - * An array suitable for use with the ajax_render() function. - * - * @see http://docs.jquery.com/Manipulation/append#content - */ -function ajax_command_append($selector, $html, $settings = NULL) { - return array( - 'command' => 'insert', - 'method' => 'append', - 'selector' => $selector, - 'data' => $html, - 'settings' => $settings, - ); -} - -/** - * Creates a Drupal Ajax 'insert/after' command. - * - * The 'insert/after' command instructs the client to use jQuery's after() - * method to insert the given HTML content after each element matched by - * the given selector. - * - * This command is implemented by Drupal.ajax.prototype.commands.insert() - * defined in misc/ajax.js. - * - * @param $selector - * A jQuery selector string. If the command is a response to a request from - * an #ajax form element then this value can be NULL. - * @param $html - * The data to use with the jQuery after() method. - * @param $settings - * An optional array of settings that will be used for this command only. - * - * @return - * An array suitable for use with the ajax_render() function. - * - * @see http://docs.jquery.com/Manipulation/after#content - */ -function ajax_command_after($selector, $html, $settings = NULL) { - return array( - 'command' => 'insert', - 'method' => 'after', - 'selector' => $selector, - 'data' => $html, - 'settings' => $settings, - ); -} - -/** - * Creates a Drupal Ajax 'insert/before' command. - * - * The 'insert/before' command instructs the client to use jQuery's before() - * method to insert the given HTML content before each of elements matched by - * the given selector. - * - * This command is implemented by Drupal.ajax.prototype.commands.insert() - * defined in misc/ajax.js. - * - * @param $selector - * A jQuery selector string. If the command is a response to a request from - * an #ajax form element then this value can be NULL. - * @param $html - * The data to use with the jQuery before() method. - * @param $settings - * An optional array of settings that will be used for this command only. - * - * @return - * An array suitable for use with the ajax_render() function. - * - * @see http://docs.jquery.com/Manipulation/before#content - */ -function ajax_command_before($selector, $html, $settings = NULL) { - return array( - 'command' => 'insert', - 'method' => 'before', - 'selector' => $selector, - 'data' => $html, - 'settings' => $settings, - ); -} - -/** - * Creates a Drupal Ajax 'remove' command. - * - * The 'remove' command instructs the client to use jQuery's remove() method - * to remove each of elements matched by the given selector, and everything - * within them. - * - * This command is implemented by Drupal.ajax.prototype.commands.remove() - * defined in misc/ajax.js. - * - * @param $selector - * A jQuery selector string. If the command is a response to a request from - * an #ajax form element then this value can be NULL. - * - * @return - * An array suitable for use with the ajax_render() function. - * - * @see http://docs.jquery.com/Manipulation/remove#expr - */ -function ajax_command_remove($selector) { - return array( - 'command' => 'remove', - 'selector' => $selector, - ); -} - -/** - * Creates a Drupal Ajax 'changed' command. - * - * This command instructs the client to mark each of the elements matched by the - * given selector as 'ajax-changed'. - * - * This command is implemented by Drupal.ajax.prototype.commands.changed() - * defined in misc/ajax.js. - * - * @param $selector - * A jQuery selector string. If the command is a response to a request from - * an #ajax form element then this value can be NULL. - * @param $asterisk - * An optional CSS selector which must be inside $selector. If specified, - * an asterisk will be appended to the HTML inside the $asterisk selector. - * - * @return - * An array suitable for use with the ajax_render() function. - */ -function ajax_command_changed($selector, $asterisk = '') { - return array( - 'command' => 'changed', - 'selector' => $selector, - 'asterisk' => $asterisk, - ); -} - -/** - * Creates a Drupal Ajax 'css' command. - * - * The 'css' command will instruct the client to use the jQuery css() method - * to apply the CSS arguments to elements matched by the given selector. - * - * This command is implemented by Drupal.ajax.prototype.commands.css() - * defined in misc/ajax.js. - * - * @param $selector - * A jQuery selector string. If the command is a response to a request from - * an #ajax form element then this value can be NULL. - * @param $argument - * An array of key/value pairs to set in the CSS for the selector. - * - * @return - * An array suitable for use with the ajax_render() function. - * - * @see http://docs.jquery.com/CSS/css#properties - */ -function ajax_command_css($selector, $argument) { - return array( - 'command' => 'css', - 'selector' => $selector, - 'argument' => $argument, - ); -} - -/** - * Creates a Drupal Ajax 'settings' command. - * - * The 'settings' command instructs the client either to use the given array as - * the settings for ajax-loaded content or to extend Drupal.settings with the - * given array, depending on the value of the $merge parameter. - * - * This command is implemented by Drupal.ajax.prototype.commands.settings() - * defined in misc/ajax.js. - * - * @param $argument - * An array of key/value pairs to add to the settings. This will be utilized - * for all commands after this if they do not include their own settings - * array. - * @param $merge - * Whether or not the passed settings in $argument should be merged into the - * global Drupal.settings on the page. By default (FALSE), the settings that - * are passed to Drupal.attachBehaviors will not include the global - * Drupal.settings. - * - * @return - * An array suitable for use with the ajax_render() function. - */ -function ajax_command_settings($argument, $merge = FALSE) { - return array( - 'command' => 'settings', - 'settings' => $argument, - 'merge' => $merge, - ); -} - -/** - * Creates a Drupal Ajax 'data' command. - * - * The 'data' command instructs the client to attach the name=value pair of - * data to the selector via jQuery's data cache. - * - * This command is implemented by Drupal.ajax.prototype.commands.data() - * defined in misc/ajax.js. - * - * @param $selector - * A jQuery selector string. If the command is a response to a request from - * an #ajax form element then this value can be NULL. - * @param $name - * The name or key (in the key value pair) of the data attached to this - * selector. - * @param $value - * The value of the data. Not just limited to strings can be any format. - * - * @return - * An array suitable for use with the ajax_render() function. - * - * @see http://docs.jquery.com/Core/data#namevalue - */ -function ajax_command_data($selector, $name, $value) { - return array( - 'command' => 'data', - 'selector' => $selector, - 'name' => $name, - 'value' => $value, - ); -} - -/** - * Creates a Drupal Ajax 'invoke' command. - * - * The 'invoke' command will instruct the client to invoke the given jQuery - * method with the supplied arguments on the elements matched by the given - * selector. Intended for simple jQuery commands, such as attr(), addClass(), - * removeClass(), toggleClass(), etc. - * - * This command is implemented by Drupal.ajax.prototype.commands.invoke() - * defined in misc/ajax.js. - * - * @param $selector - * A jQuery selector string. If the command is a response to a request from - * an #ajax form element then this value can be NULL. - * @param $method - * The jQuery method to invoke. - * @param $arguments - * (optional) A list of arguments to the jQuery $method, if any. - * - * @return - * An array suitable for use with the ajax_render() function. - */ -function ajax_command_invoke($selector, $method, array $arguments = array()) { - return array( - 'command' => 'invoke', - 'selector' => $selector, - 'method' => $method, - 'arguments' => $arguments, - ); -} - -/** - * Creates a Drupal Ajax 'restripe' command. - * - * The 'restripe' command instructs the client to restripe a table. This is - * usually used after a table has been modified by a replace or append command. - * - * This command is implemented by Drupal.ajax.prototype.commands.restripe() - * defined in misc/ajax.js. - * - * @param $selector - * A jQuery selector string. - * - * @return - * An array suitable for use with the ajax_render() function. - */ -function ajax_command_restripe($selector) { - return array( - 'command' => 'restripe', - 'selector' => $selector, - ); -} - diff --git a/core/includes/archiver.inc b/core/includes/archiver.inc deleted file mode 100644 index fec053be623..00000000000 --- a/core/includes/archiver.inc +++ /dev/null @@ -1,66 +0,0 @@ -<?php - -/** - * @file - * Shared classes and interfaces for the archiver system. - */ - -/** - * Common interface for all Archiver classes. - */ -interface ArchiverInterface { - - /** - * Constructor for a new archiver instance. - * - * @param $file_path - * The full system path of the archive to manipulate. Only local files - * are supported. If the file does not yet exist, it will be created if - * appropriate. - */ - public function __construct($file_path); - - /** - * Add the specified file or directory to the archive. - * - * @param $file_path - * The full system path of the file or directory to add. Only local files - * and directories are supported. - * @return ArchiverInterface - * The called object. - */ - public function add($file_path); - - /** - * Remove the specified file from the archive. - * - * @param $path - * The file name relative to the root of the archive to remove. - * @return ArchiverInterface - * The called object. - */ - public function remove($path); - - /** - * Extract multiple files in the archive to the specified path. - * - * @param $path - * A full system path of the directory to which to extract files. - * @param $files - * Optionally specify a list of files to be extracted. Files are - * relative to the root of the archive. If not specified, all files - * in the archive will be extracted - * @return ArchiverInterface - * The called object. - */ - public function extract($path, Array $files = array()); - - /** - * List all files in the archive. - * - * @return - * An array of file names relative to the root of the archive. - */ - public function listContents(); -} - diff --git a/core/includes/authorize.inc b/core/includes/authorize.inc deleted file mode 100644 index 862992f6131..00000000000 --- a/core/includes/authorize.inc +++ /dev/null @@ -1,324 +0,0 @@ -<?php - -/** - * @file - * Helper functions and form handlers used for the authorize.php script. - */ - -/** - * Build the form for choosing a FileTransfer type and supplying credentials. - */ -function authorize_filetransfer_form($form, &$form_state) { - global $base_url, $is_https; - $form = array(); - - // If possible, we want to post this form securely via https. - $form['#https'] = TRUE; - - // CSS we depend on lives in modules/system/maintenance.css, which is loaded - // via the default maintenance theme. - $form['#attached']['js'][] = $base_url . '/misc/authorize.js'; - - // Get all the available ways to transfer files. - if (empty($_SESSION['authorize_filetransfer_info'])) { - drupal_set_message(t('Unable to continue, no available methods of file transfer'), 'error'); - return array(); - } - $available_backends = $_SESSION['authorize_filetransfer_info']; - - if (!$is_https) { - $form['information']['https_warning'] = array( - '#prefix' => '<div class="messages error">', - '#markup' => t('WARNING: You are not using an encrypted connection, so your password will be sent in plain text. <a href="@https-link">Learn more</a>.', array('@https-link' => 'http://drupal.org/https-information')), - '#suffix' => '</div>', - ); - } - - // Decide on a default backend. - if (isset($form_state['values']['connection_settings']['authorize_filetransfer_default'])) { - $authorize_filetransfer_default = $form_state['values']['connection_settings']['authorize_filetransfer_default']; - } - elseif ($authorize_filetransfer_default = variable_get('authorize_filetransfer_default', NULL)); - else { - $authorize_filetransfer_default = key($available_backends); - } - - $form['information']['main_header'] = array( - '#prefix' => '<h3>', - '#markup' => t('To continue, provide your server connection details'), - '#suffix' => '</h3>', - ); - - $form['connection_settings']['#tree'] = TRUE; - $form['connection_settings']['authorize_filetransfer_default'] = array( - '#type' => 'select', - '#title' => t('Connection method'), - '#default_value' => $authorize_filetransfer_default, - '#weight' => -10, - ); - - /* - * Here we create two submit buttons. For a JS enabled client, they will - * only ever see submit_process. However, if a client doesn't have JS - * enabled, they will see submit_connection on the first form (when picking - * what filetransfer type to use, and submit_process on the second one (which - * leads to the actual operation). - */ - $form['submit_connection'] = array( - '#prefix' => "<br style='clear:both'/>", - '#name' => 'enter_connection_settings', - '#type' => 'submit', - '#value' => t('Enter connection settings'), - '#weight' => 100, - ); - - $form['submit_process'] = array( - '#name' => 'process_updates', - '#type' => 'submit', - '#value' => t('Continue'), - '#weight' => 100, - '#attributes' => array('style' => 'display:none'), - ); - - // Build a container for each connection type. - foreach ($available_backends as $name => $backend) { - $form['connection_settings']['authorize_filetransfer_default']['#options'][$name] = $backend['title']; - $form['connection_settings'][$name] = array( - '#type' => 'container', - '#attributes' => array('class' => array("filetransfer-$name", 'filetransfer')), - ); - // We can't use #prefix on the container itself since then the header won't - // be hidden and shown when the containers are being manipulated via JS. - $form['connection_settings'][$name]['header'] = array( - '#markup' => '<h4>' . t('@backend connection settings', array('@backend' => $backend['title'])) . '</h4>', - ); - - $form['connection_settings'][$name] += _authorize_filetransfer_connection_settings($name); - - // Start non-JS code. - if (isset($form_state['values']['connection_settings']['authorize_filetransfer_default']) && $form_state['values']['connection_settings']['authorize_filetransfer_default'] == $name) { - - // If the user switches from JS to non-JS, Drupal (and Batch API) will - // barf. This is a known bug: http://drupal.org/node/229825. - setcookie('has_js', '', time() - 3600, '/'); - unset($_COOKIE['has_js']); - - // Change the submit button to the submit_process one. - $form['submit_process']['#attributes'] = array(); - unset($form['submit_connection']); - - // Activate the proper filetransfer settings form. - $form['connection_settings'][$name]['#attributes']['style'] = 'display:block'; - // Disable the select box. - $form['connection_settings']['authorize_filetransfer_default']['#disabled'] = TRUE; - - // Create a button for changing the type of connection. - $form['connection_settings']['change_connection_type'] = array( - '#name' => 'change_connection_type', - '#type' => 'submit', - '#value' => t('Change connection type'), - '#weight' => -5, - '#attributes' => array('class' => array('filetransfer-change-connection-type')), - ); - } - // End non-JS code. - } - return $form; -} - -/** - * Generate the Form API array for the settings for a given connection backend. - * - * @param $backend - * The name of the backend (e.g. 'ftp', 'ssh', etc). - * @return - * Form API array of connection settings for the given backend. - * - * @see hook_filetransfer_backends() - */ -function _authorize_filetransfer_connection_settings($backend) { - $defaults = variable_get('authorize_filetransfer_connection_settings_' . $backend, array()); - $form = array(); - - // Create an instance of the file transfer class to get its settings form. - $filetransfer = authorize_get_filetransfer($backend); - if ($filetransfer) { - $form = $filetransfer->getSettingsForm(); - } - // Fill in the defaults based on the saved settings, if any. - _authorize_filetransfer_connection_settings_set_defaults($form, NULL, $defaults); - return $form; -} - -/** - * Recursively fill in the default settings on a file transfer connection form. - * - * The default settings for the file transfer connection forms are saved in - * the database. The settings are stored as a nested array in the case of a - * settings form that has fieldsets or otherwise uses a nested structure. - * Therefore, to properly add defaults, we need to walk through all the - * children form elements and process those defaults recursively. - * - * @param $element - * Reference to the Form API form element we're operating on. - * @param $key - * The key for our current form element, if any. - * @param array $defaults - * The default settings for the file transfer backend we're operating on. - * @return - * Nothing, this function just sets $element['#default_value'] if needed. - */ -function _authorize_filetransfer_connection_settings_set_defaults(&$element, $key, array $defaults) { - // If we're operating on a form element which isn't a fieldset, and we have - // a default setting saved, stash it in #default_value. - if (!empty($key) && isset($defaults[$key]) && isset($element['#type']) && $element['#type'] != 'fieldset') { - $element['#default_value'] = $defaults[$key]; - } - // Now, we walk through all the child elements, and recursively invoke - // ourself on each one. Since the $defaults settings array can be nested - // (because of #tree, any values inside fieldsets will be nested), if - // there's a subarray of settings for the form key we're currently - // processing, pass in that subarray to the recursive call. Otherwise, just - // pass on the whole $defaults array. - foreach (element_children($element) as $child_key) { - _authorize_filetransfer_connection_settings_set_defaults($element[$child_key], $child_key, ((isset($defaults[$key]) && is_array($defaults[$key])) ? $defaults[$key] : $defaults)); - } -} - -/** - * Validate callback for the filetransfer authorization form. - * - * @see authorize_filetransfer_form() - */ -function authorize_filetransfer_form_validate($form, &$form_state) { - // Only validate the form if we have collected all of the user input and are - // ready to proceed with updating or installing. - if ($form_state['triggering_element']['#name'] != 'process_updates') { - return; - } - - if (isset($form_state['values']['connection_settings'])) { - $backend = $form_state['values']['connection_settings']['authorize_filetransfer_default']; - $filetransfer = authorize_get_filetransfer($backend, $form_state['values']['connection_settings'][$backend]); - try { - if (!$filetransfer) { - throw new Exception(t('Error, this type of connection protocol (%backend) does not exist.', array('%backend' => $backend))); - } - $filetransfer->connect(); - } - catch (Exception $e) { - // The format of this error message is similar to that used on the - // database connection form in the installer. - form_set_error('connection_settings', t('Failed to connect to the server. The server reports the following message: !message For more help installing or updating code on your server, see the <a href="@handbook_url">handbook</a>.', array( - '!message' => '<p class="error">' . $e->getMessage() . '</p>', - '@handbook_url' => 'http://drupal.org/documentation/install/modules-themes', - ))); - } - } -} - -/** - * Submit callback when a file transfer is being authorized. - * - * @see authorize_filetransfer_form() - */ -function authorize_filetransfer_form_submit($form, &$form_state) { - global $base_url; - switch ($form_state['triggering_element']['#name']) { - case 'process_updates': - - // Save the connection settings to the DB. - $filetransfer_backend = $form_state['values']['connection_settings']['authorize_filetransfer_default']; - - // If the database is available then try to save our settings. We have - // to make sure it is available since this code could potentially (will - // likely) be called during the installation process, before the - // database is set up. - try { - $connection_settings = array(); - foreach ($form_state['values']['connection_settings'][$filetransfer_backend] as $key => $value) { - // We do *not* want to store passwords in the database, unless the - // backend explicitly says so via the magic #filetransfer_save form - // property. Otherwise, we store everything that's not explicitly - // marked with #filetransfer_save set to FALSE. - if (!isset($form['connection_settings'][$filetransfer_backend][$key]['#filetransfer_save'])) { - if ($form['connection_settings'][$filetransfer_backend][$key]['#type'] != 'password') { - $connection_settings[$key] = $value; - } - } - // The attribute is defined, so only save if set to TRUE. - elseif ($form['connection_settings'][$filetransfer_backend][$key]['#filetransfer_save']) { - $connection_settings[$key] = $value; - } - } - // Set this one as the default authorize method. - variable_set('authorize_filetransfer_default', $filetransfer_backend); - // Save the connection settings minus the password. - variable_set('authorize_filetransfer_connection_settings_' . $filetransfer_backend, $connection_settings); - - $filetransfer = authorize_get_filetransfer($filetransfer_backend, $form_state['values']['connection_settings'][$filetransfer_backend]); - - // Now run the operation. - authorize_run_operation($filetransfer); - } - catch (Exception $e) { - // If there is no database available, we don't care and just skip - // this part entirely. - } - - break; - - case 'enter_connection_settings': - $form_state['rebuild'] = TRUE; - break; - - case 'change_connection_type': - $form_state['rebuild'] = TRUE; - unset($form_state['values']['connection_settings']['authorize_filetransfer_default']); - break; - } -} - -/** - * Run the operation specified in $_SESSION['authorize_operation'] - * - * @param $filetransfer - * The FileTransfer object to use for running the operation. - */ -function authorize_run_operation($filetransfer) { - $operation = $_SESSION['authorize_operation']; - unset($_SESSION['authorize_operation']); - - if (!empty($operation['page_title'])) { - drupal_set_title($operation['page_title']); - } - - require_once DRUPAL_ROOT . '/' . $operation['file']; - call_user_func_array($operation['callback'], array_merge(array($filetransfer), $operation['arguments'])); -} - -/** - * Get a FileTransfer class for a specific transfer method and settings. - * - * @param $backend - * The FileTransfer backend to get the class for. - * @param $settings - * Array of settings for the FileTransfer. - * @return - * An instantiated FileTransfer object for the requested method and settings, - * or FALSE if there was an error finding or instantiating it. - */ -function authorize_get_filetransfer($backend, $settings = array()) { - $filetransfer = FALSE; - if (!empty($_SESSION['authorize_filetransfer_info'][$backend])) { - $backend_info = $_SESSION['authorize_filetransfer_info'][$backend]; - if (!empty($backend_info['file'])) { - $file = $backend_info['file path'] . '/' . $backend_info['file']; - require_once $file; - } - if (class_exists($backend_info['class'])) { - $filetransfer = $backend_info['class']::factory(DRUPAL_ROOT, $settings); - } - } - return $filetransfer; -} diff --git a/core/includes/batch.inc b/core/includes/batch.inc deleted file mode 100644 index 513a8f9ad0d..00000000000 --- a/core/includes/batch.inc +++ /dev/null @@ -1,534 +0,0 @@ -<?php - - -/** - * @file - * Batch processing API for processes to run in multiple HTTP requests. - * - * Note that batches are usually invoked by form submissions, which is - * why the core interaction functions of the batch processing API live in - * form.inc. - * - * @see form.inc - * @see batch_set() - * @see batch_process() - * @see batch_get() - */ - -/** - * Loads a batch from the database. - * - * @param $id - * The ID of the batch to load. When a progressive batch is being processed, - * the relevant ID is found in $_REQUEST['id']. - * @return - * An array representing the batch, or FALSE if no batch was found. - */ -function batch_load($id) { - $batch = db_query("SELECT batch FROM {batch} WHERE bid = :bid AND token = :token", array( - ':bid' => $id, - ':token' => drupal_get_token($id), - ))->fetchField(); - if ($batch) { - return unserialize($batch); - } - return FALSE; -} - -/** - * State-based dispatcher for the batch processing page. - * - * @see _batch_shutdown() - */ -function _batch_page() { - $batch = &batch_get(); - - if (!isset($_REQUEST['id'])) { - return FALSE; - } - - // Retrieve the current state of the batch. - if (!$batch) { - $batch = batch_load($_REQUEST['id']); - if (!$batch) { - drupal_set_message(t('No active batch.'), 'error'); - drupal_goto(); - } - } - - // Register database update for the end of processing. - drupal_register_shutdown_function('_batch_shutdown'); - - // Add batch-specific CSS. - foreach ($batch['sets'] as $batch_set) { - if (isset($batch_set['css'])) { - foreach ($batch_set['css'] as $css) { - drupal_add_css($css); - } - } - } - - $op = isset($_REQUEST['op']) ? $_REQUEST['op'] : ''; - $output = NULL; - switch ($op) { - case 'start': - $output = _batch_start(); - break; - - case 'do': - // JavaScript-based progress page callback. - _batch_do(); - break; - - case 'do_nojs': - // Non-JavaScript-based progress page. - $output = _batch_progress_page_nojs(); - break; - - case 'finished': - $output = _batch_finished(); - break; - } - - return $output; -} - -/** - * Initialize the batch processing. - * - * JavaScript-enabled clients are identified by the 'has_js' cookie set in - * drupal.js. If no JavaScript-enabled page has been visited during the current - * user's browser session, the non-JavaScript version is returned. - */ -function _batch_start() { - if (isset($_COOKIE['has_js']) && $_COOKIE['has_js']) { - return _batch_progress_page_js(); - } - else { - return _batch_progress_page_nojs(); - } -} - -/** - * Output a batch processing page with JavaScript support. - * - * This initializes the batch and error messages. Note that in JavaScript-based - * processing, the batch processing page is displayed only once and updated via - * AHAH requests, so only the first batch set gets to define the page title. - * Titles specified by subsequent batch sets are not displayed. - * - * @see batch_set() - * @see _batch_do() - */ -function _batch_progress_page_js() { - $batch = batch_get(); - - $current_set = _batch_current_set(); - drupal_set_title($current_set['title'], PASS_THROUGH); - - // Merge required query parameters for batch processing into those provided by - // batch_set() or hook_batch_alter(). - $batch['url_options']['query']['id'] = $batch['id']; - - $js_setting = array( - 'batch' => array( - 'errorMessage' => $current_set['error_message'] . '<br />' . $batch['error_message'], - 'initMessage' => $current_set['init_message'], - 'uri' => url($batch['url'], $batch['url_options']), - ), - ); - drupal_add_js($js_setting, 'setting'); - drupal_add_library('system', 'drupal.batch'); - - return '<div id="progress"></div>'; -} - -/** - * Do one execution pass in JavaScript-mode and return progress to the browser. - * - * @see _batch_progress_page_js() - * @see _batch_process() - */ -function _batch_do() { - // HTTP POST required. - if ($_SERVER['REQUEST_METHOD'] != 'POST') { - drupal_set_message(t('HTTP POST is required.'), 'error'); - drupal_set_title(t('Error')); - return ''; - } - - // Perform actual processing. - list($percentage, $message) = _batch_process(); - - drupal_json_output(array('status' => TRUE, 'percentage' => $percentage, 'message' => $message)); -} - -/** - * Output a batch processing page without JavaScript support. - * - * @see _batch_process() - */ -function _batch_progress_page_nojs() { - $batch = &batch_get(); - - $current_set = _batch_current_set(); - drupal_set_title($current_set['title'], PASS_THROUGH); - - $new_op = 'do_nojs'; - - if (!isset($batch['running'])) { - // This is the first page so we return some output immediately. - $percentage = 0; - $message = $current_set['init_message']; - $batch['running'] = TRUE; - } - else { - // This is one of the later requests; do some processing first. - - // Error handling: if PHP dies due to a fatal error (e.g. a nonexistent - // function), it will output whatever is in the output buffer, followed by - // the error message. - ob_start(); - $fallback = $current_set['error_message'] . '<br />' . $batch['error_message']; - $fallback = theme('maintenance_page', array('content' => $fallback, 'show_messages' => FALSE)); - - // We strip the end of the page using a marker in the template, so any - // additional HTML output by PHP shows up inside the page rather than below - // it. While this causes invalid HTML, the same would be true if we didn't, - // as content is not allowed to appear after </html> anyway. - list($fallback) = explode('<!--partial-->', $fallback); - print $fallback; - - // Perform actual processing. - list($percentage, $message) = _batch_process($batch); - if ($percentage == 100) { - $new_op = 'finished'; - } - - // PHP did not die; remove the fallback output. - ob_end_clean(); - } - - // Merge required query parameters for batch processing into those provided by - // batch_set() or hook_batch_alter(). - $batch['url_options']['query']['id'] = $batch['id']; - $batch['url_options']['query']['op'] = $new_op; - - $url = url($batch['url'], $batch['url_options']); - $element = array( - '#tag' => 'meta', - '#attributes' => array( - 'http-equiv' => 'Refresh', - 'content' => '0; URL=' . $url, - ), - ); - drupal_add_html_head($element, 'batch_progress_meta_refresh'); - - return theme('progress_bar', array('percent' => $percentage, 'message' => $message)); -} - -/** - * Process sets in a batch. - * - * If the batch was marked for progressive execution (default), this executes as - * many operations in batch sets until an execution time of 1 second has been - * exceeded. It will continue with the next operation of the same batch set in - * the next request. - * - * @return - * An array containing a completion value (in percent) and a status message. - */ -function _batch_process() { - $batch = &batch_get(); - $current_set = &_batch_current_set(); - // Indicate that this batch set needs to be initialized. - $set_changed = TRUE; - - // If this batch was marked for progressive execution (e.g. forms submitted by - // drupal_form_submit()), initialize a timer to determine whether we need to - // proceed with the same batch phase when a processing time of 1 second has - // been exceeded. - if ($batch['progressive']) { - timer_start('batch_processing'); - } - - if (empty($current_set['start'])) { - $current_set['start'] = microtime(TRUE); - } - - $queue = _batch_queue($current_set); - - while (!$current_set['success']) { - // If this is the first time we iterate this batch set in the current - // request, we check if it requires an additional file for functions - // definitions. - if ($set_changed && isset($current_set['file']) && is_file($current_set['file'])) { - include_once DRUPAL_ROOT . '/' . $current_set['file']; - } - - $task_message = ''; - // Assume a single pass operation and set the completion level to 1 by - // default. - $finished = 1; - - if ($item = $queue->claimItem()) { - list($function, $args) = $item->data; - - // Build the 'context' array and execute the function call. - $batch_context = array( - 'sandbox' => &$current_set['sandbox'], - 'results' => &$current_set['results'], - 'finished' => &$finished, - 'message' => &$task_message, - ); - call_user_func_array($function, array_merge($args, array(&$batch_context))); - - if ($finished >= 1) { - // Make sure this step is not counted twice when computing $current. - $finished = 0; - // Remove the processed operation and clear the sandbox. - $queue->deleteItem($item); - $current_set['count']--; - $current_set['sandbox'] = array(); - } - } - - // When all operations in the current batch set are completed, browse - // through the remaining sets, marking them 'successfully processed' - // along the way, until we find a set that contains operations. - // _batch_next_set() executes form submit handlers stored in 'control' - // sets (see form_execute_handlers()), which can in turn add new sets to - // the batch. - $set_changed = FALSE; - $old_set = $current_set; - while (empty($current_set['count']) && ($current_set['success'] = TRUE) && _batch_next_set()) { - $current_set = &_batch_current_set(); - $current_set['start'] = microtime(TRUE); - $set_changed = TRUE; - } - - // At this point, either $current_set contains operations that need to be - // processed or all sets have been completed. - $queue = _batch_queue($current_set); - - // If we are in progressive mode, break processing after 1 second. - if ($batch['progressive'] && timer_read('batch_processing') > 1000) { - // Record elapsed wall clock time. - $current_set['elapsed'] = round((microtime(TRUE) - $current_set['start']) * 1000, 2); - break; - } - } - - if ($batch['progressive']) { - // Gather progress information. - - // Reporting 100% progress will cause the whole batch to be considered - // processed. If processing was paused right after moving to a new set, - // we have to use the info from the new (unprocessed) set. - if ($set_changed && isset($current_set['queue'])) { - // Processing will continue with a fresh batch set. - $remaining = $current_set['count']; - $total = $current_set['total']; - $progress_message = $current_set['init_message']; - $task_message = ''; - } - else { - // Processing will continue with the current batch set. - $remaining = $old_set['count']; - $total = $old_set['total']; - $progress_message = $old_set['progress_message']; - } - - // Total progress is the number of operations that have fully run plus the - // completion level of the current operation. - $current = $total - $remaining + $finished; - $percentage = _batch_api_percentage($total, $current); - $elapsed = isset($current_set['elapsed']) ? $current_set['elapsed'] : 0; - $values = array( - '@remaining' => $remaining, - '@total' => $total, - '@current' => floor($current), - '@percentage' => $percentage, - '@elapsed' => format_interval($elapsed / 1000), - // If possible, estimate remaining processing time. - '@estimate' => ($current > 0) ? format_interval(($elapsed * ($total - $current) / $current) / 1000) : '-', - ); - $message = strtr($progress_message, $values); - if (!empty($message)) { - $message .= '<br />'; - } - if (!empty($task_message)) { - $message .= $task_message; - } - - return array($percentage, $message); - } - else { - // If we are not in progressive mode, the entire batch has been processed. - return _batch_finished(); - } -} - -/** - * Helper function for _batch_process(): returns the formatted percentage. - * - * @param $total - * The total number of operations. - * @param $current - * The number of the current operation. This may be a floating point number - * rather than an integer in the case of a multi-step operation that is not - * yet complete; in that case, the fractional part of $current represents the - * fraction of the operation that has been completed. - * @return - * The properly formatted percentage, as a string. We output percentages - * using the correct number of decimal places so that we never print "100%" - * until we are finished, but we also never print more decimal places than - * are meaningful. - */ -function _batch_api_percentage($total, $current) { - if (!$total || $total == $current) { - // If $total doesn't evaluate as true or is equal to the current set, then - // we're finished, and we can return "100". - $percentage = "100"; - } - else { - // We add a new digit at 200, 2000, etc. (since, for example, 199/200 - // would round up to 100% if we didn't). - $decimal_places = max(0, floor(log10($total / 2.0)) - 1); - do { - // Calculate the percentage to the specified number of decimal places. - $percentage = sprintf('%01.' . $decimal_places . 'f', round($current / $total * 100, $decimal_places)); - // When $current is an integer, the above calculation will always be - // correct. However, if $current is a floating point number (in the case - // of a multi-step batch operation that is not yet complete), $percentage - // may be erroneously rounded up to 100%. To prevent that, we add one - // more decimal place and try again. - $decimal_places++; - } while ($percentage == '100'); - } - return $percentage; -} - -/** - * Return the batch set being currently processed. - */ -function &_batch_current_set() { - $batch = &batch_get(); - return $batch['sets'][$batch['current_set']]; -} - -/** - * Retrieve the next set in a batch. - * - * If there is a subsequent set in this batch, assign it as the new set to - * process and execute its form submit handler (if defined), which may add - * further sets to this batch. - * - * @return - * TRUE if a subsequent set was found in the batch. - */ -function _batch_next_set() { - $batch = &batch_get(); - if (isset($batch['sets'][$batch['current_set'] + 1])) { - $batch['current_set']++; - $current_set = &_batch_current_set(); - if (isset($current_set['form_submit']) && ($function = $current_set['form_submit']) && function_exists($function)) { - // We use our stored copies of $form and $form_state to account for - // possible alterations by previous form submit handlers. - $function($batch['form_state']['complete_form'], $batch['form_state']); - } - return TRUE; - } -} - -/** - * End the batch processing. - * - * Call the 'finished' callback of each batch set to allow custom handling of - * the results and resolve page redirection. - */ -function _batch_finished() { - $batch = &batch_get(); - - // Execute the 'finished' callbacks for each batch set, if defined. - foreach ($batch['sets'] as $batch_set) { - if (isset($batch_set['finished'])) { - // Check if the set requires an additional file for function definitions. - if (isset($batch_set['file']) && is_file($batch_set['file'])) { - include_once DRUPAL_ROOT . '/' . $batch_set['file']; - } - if (function_exists($batch_set['finished'])) { - $queue = _batch_queue($batch_set); - $operations = $queue->getAllItems(); - $batch_set['finished']($batch_set['success'], $batch_set['results'], $operations, format_interval($batch_set['elapsed'] / 1000)); - } - } - } - - // Clean up the batch table and unset the static $batch variable. - if ($batch['progressive']) { - db_delete('batch') - ->condition('bid', $batch['id']) - ->execute(); - foreach ($batch['sets'] as $batch_set) { - if ($queue = _batch_queue($batch_set)) { - $queue->deleteQueue(); - } - } - } - $_batch = $batch; - $batch = NULL; - - // Clean-up the session. Not needed for CLI updates. - if (isset($_SESSION)) { - unset($_SESSION['batches'][$batch['id']]); - if (empty($_SESSION['batches'])) { - unset($_SESSION['batches']); - } - } - - // Redirect if needed. - if ($_batch['progressive']) { - // Revert the 'destination' that was saved in batch_process(). - if (isset($_batch['destination'])) { - $_GET['destination'] = $_batch['destination']; - } - - // Determine the target path to redirect to. - if (!isset($_batch['form_state']['redirect'])) { - if (isset($_batch['redirect'])) { - $_batch['form_state']['redirect'] = $_batch['redirect']; - } - else { - $_batch['form_state']['redirect'] = $_batch['source_url']; - } - } - - // Use drupal_redirect_form() to handle the redirection logic. - drupal_redirect_form($_batch['form_state']); - - // If no redirection happened, redirect to the originating page. In case the - // form needs to be rebuilt, save the final $form_state for - // drupal_build_form(). - if (!empty($_batch['form_state']['rebuild'])) { - $_SESSION['batch_form_state'] = $_batch['form_state']; - } - $function = $_batch['redirect_callback']; - if (function_exists($function)) { - $function($_batch['source_url'], array('query' => array('op' => 'finish', 'id' => $_batch['id']))); - } - } -} - -/** - * Shutdown function; store the current batch data for the next request. - */ -function _batch_shutdown() { - if ($batch = batch_get()) { - db_update('batch') - ->fields(array('batch' => serialize($batch))) - ->condition('bid', $batch['id']) - ->execute(); - } -} - diff --git a/core/includes/batch.queue.inc b/core/includes/batch.queue.inc deleted file mode 100644 index 8464836987b..00000000000 --- a/core/includes/batch.queue.inc +++ /dev/null @@ -1,71 +0,0 @@ -<?php - - -/** - * @file - * Queue handlers used by the Batch API. - * - * Those implementations: - * - ensure FIFO ordering, - * - let an item be repeatedly claimed until it is actually deleted (no notion - * of lease time or 'expire' date), to allow multipass operations. - */ - -/** - * Batch queue implementation. - * - * Stale items from failed batches are cleaned from the {queue} table on cron - * using the 'created' date. - */ -class BatchQueue extends SystemQueue { - - public function claimItem($lease_time = 0) { - $item = db_query_range('SELECT data, item_id FROM {queue} q WHERE name = :name ORDER BY item_id ASC', 0, 1, array(':name' => $this->name))->fetchObject(); - if ($item) { - $item->data = unserialize($item->data); - return $item; - } - return FALSE; - } - - /** - * Retrieve all remaining items in the queue. - * - * This is specific to Batch API and is not part of the DrupalQueueInterface, - */ - public function getAllItems() { - $result = array(); - $items = db_query('SELECT data FROM {queue} q WHERE name = :name ORDER BY item_id ASC', array(':name' => $this->name))->fetchAll(); - foreach ($items as $item) { - $result[] = unserialize($item->data); - } - return $result; - } -} - -/** - * Batch queue implementation used for non-progressive batches. - */ -class BatchMemoryQueue extends MemoryQueue { - - public function claimItem($lease_time = 0) { - if (!empty($this->queue)) { - reset($this->queue); - return current($this->queue); - } - return FALSE; - } - - /** - * Retrieve all remaining items in the queue. - * - * This is specific to Batch API and is not part of the DrupalQueueInterface, - */ - public function getAllItems() { - $result = array(); - foreach ($this->queue as $item) { - $result[] = $item->data; - } - return $result; - } -} diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc deleted file mode 100644 index 9780ed242a2..00000000000 --- a/core/includes/bootstrap.inc +++ /dev/null @@ -1,3358 +0,0 @@ -<?php - -/** - * @file - * Functions that need to be loaded on every Drupal request. - */ - -/** - * The current system version. - */ -define('VERSION', '8.0-dev'); - -/** - * Core API compatibility. - */ -define('DRUPAL_CORE_COMPATIBILITY', '8.x'); - -/** - * Minimum supported version of PHP. - */ -define('DRUPAL_MINIMUM_PHP', '5.3.2'); - -/** - * Minimum recommended value of PHP memory_limit. - */ -define('DRUPAL_MINIMUM_PHP_MEMORY_LIMIT', '32M'); - -/** - * Indicates that the item should never be removed unless explicitly selected. - * - * The item may be removed using cache()->delete() with a cache ID. - */ -define('CACHE_PERMANENT', 0); - -/** - * Indicates that the item should be removed at the next general cache wipe. - */ -define('CACHE_TEMPORARY', -1); - -/** - * @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 - * 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_severity_levels() - */ - -/** - * Log message severity -- Emergency: system is unusable. - */ -define('WATCHDOG_EMERGENCY', 0); - -/** - * Log message severity -- Alert: action must be taken immediately. - */ -define('WATCHDOG_ALERT', 1); - -/** - * Log message severity -- Critical: critical conditions. - */ -define('WATCHDOG_CRITICAL', 2); - -/** - * Log message severity -- Error: error conditions. - */ -define('WATCHDOG_ERROR', 3); - -/** - * Log message severity -- Warning: warning conditions. - */ -define('WATCHDOG_WARNING', 4); - -/** - * Log message severity -- Notice: normal but significant condition. - */ -define('WATCHDOG_NOTICE', 5); - -/** - * Log message severity -- Informational: informational messages. - */ -define('WATCHDOG_INFO', 6); - -/** - * Log message severity -- Debug: debug-level messages. - */ -define('WATCHDOG_DEBUG', 7); - -/** - * @} End of "defgroup logging_severity_levels". - */ - -/** - * First bootstrap phase: initialize configuration. - */ -define('DRUPAL_BOOTSTRAP_CONFIGURATION', 0); - -/** - * Second bootstrap phase: try to serve a cached page. - */ -define('DRUPAL_BOOTSTRAP_PAGE_CACHE', 1); - -/** - * Third bootstrap phase: initialize database layer. - */ -define('DRUPAL_BOOTSTRAP_DATABASE', 2); - -/** - * Fourth bootstrap phase: initialize the variable system. - */ -define('DRUPAL_BOOTSTRAP_VARIABLES', 3); - -/** - * Fifth bootstrap phase: initialize session handling. - */ -define('DRUPAL_BOOTSTRAP_SESSION', 4); - -/** - * Sixth bootstrap phase: set up the page header. - */ -define('DRUPAL_BOOTSTRAP_PAGE_HEADER', 5); - -/** - * Seventh bootstrap phase: find out language of the page. - */ -define('DRUPAL_BOOTSTRAP_LANGUAGE', 6); - -/** - * Final bootstrap phase: Drupal is fully loaded; validate and fix - * input data. - */ -define('DRUPAL_BOOTSTRAP_FULL', 7); - -/** - * Role ID for anonymous users; should match what's in the "role" table. - */ -define('DRUPAL_ANONYMOUS_RID', 1); - -/** - * Role ID for authenticated users; should match what's in the "role" table. - */ -define('DRUPAL_AUTHENTICATED_RID', 2); - -/** - * The number of bytes in a kilobyte. For more information, visit - * http://en.wikipedia.org/wiki/Kilobyte. - */ -define('DRUPAL_KILOBYTE', 1024); - -/** - * System language (only applicable to UI). - * - * Refers to the language used in Drupal and module/theme source code. - */ -define('LANGUAGE_SYSTEM', 'system'); - -/** - * The language code used when no language is explicitly assigned. - * - * Defined by ISO639-2 for "Undetermined". - */ -define('LANGUAGE_NONE', 'und'); - -/** - * The type of language used to define the content language. - */ -define('LANGUAGE_TYPE_CONTENT', 'language_content'); - -/** - * The type of language used to select the user interface. - */ -define('LANGUAGE_TYPE_INTERFACE', 'language'); - -/** - * The type of language used for URLs. - */ -define('LANGUAGE_TYPE_URL', 'language_url'); - -/** - * Language written left to right. Possible value of $language->direction. - */ -define('LANGUAGE_LTR', 0); - -/** - * Language written right to left. Possible value of $language->direction. - */ -define('LANGUAGE_RTL', 1); - -/** - * For convenience, define a short form of the request time global. - * - * REQUEST_TIME is a float with microseconds since PHP 5.4.0, but float - * timestamps confuses most of the PHP functions (including date_create()). - */ -define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']); - -/** - * Flag for drupal_set_title(); text is not sanitized, so run check_plain(). - */ -define('CHECK_PLAIN', 0); - -/** - * Flag for drupal_set_title(); text has already been sanitized. - */ -define('PASS_THROUGH', -1); - -/** - * Signals that the registry lookup cache should be reset. - */ -define('REGISTRY_RESET_LOOKUP_CACHE', 1); - -/** - * Signals that the registry lookup cache should be written to storage. - */ -define('REGISTRY_WRITE_LOOKUP_CACHE', 2); - -/** - * Regular expression to match PHP function names. - * - * @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(). - */ - private $cid; - - /** - * A bin to pass to cache()->set() and cache()->get(). - */ - private $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(); - - /** - * Constructor. - * - * @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($bin)->get($this->cid)) { - $this->storage = $cached->data; - } - } - - public function offsetExists($offset) { - return $this->offsetGet($offset) !== NULL; - } - - public function offsetGet($offset) { - if (isset($this->storage[$offset]) || array_key_exists($offset, $this->storage)) { - return $this->storage[$offset]; - } - else { - return $this->resolveCacheMiss($offset); - } - } - - public function offsetSet($offset, $value) { - $this->storage[$offset] = $value; - } - - 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. - * - * 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); - - /** - * Immediately write a value to the persistent cache. - * - * @param $cid - * The cache ID. - * @param $bin - * The cache bin. - * @param $data - * The data to write to the persistent cache. - * @param $lock - * Whether to acquire a lock before writing to cache. - */ - protected function set($cid, $data, $bin, $lock = TRUE) { - // Lock cache writes to help avoid stampedes. - // To implement locking for cache misses, override __construct(). - $lock_name = $cid . ':' . $bin; - if (!$lock || lock_acquire($lock_name)) { - if ($cached = cache($bin)->get($cid)) { - $data = $cached->data + $data; - } - cache($bin)->set($cid, $data); - if ($lock) { - lock_release($lock_name); - } - } - } - - public function __destruct() { - $data = array(); - foreach ($this->keysToPersist as $offset => $persist) { - if ($persist) { - $data[$offset] = $this->storage[$offset]; - } - } - if (!empty($data)) { - $this->set($this->cid, $data, $this->bin); - } - } -} - -/** - * Start 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. - */ -function timer_start($name) { - global $timers; - - $timers[$name]['start'] = microtime(TRUE); - $timers[$name]['count'] = isset($timers[$name]['count']) ? ++$timers[$name]['count'] : 1; -} - -/** - * Read the current timer value without stopping the timer. - * - * @param $name - * The name of the timer. - * - * @return - * The current timer value in ms. - */ -function timer_read($name) { - global $timers; - - if (isset($timers[$name]['start'])) { - $stop = microtime(TRUE); - $diff = round(($stop - $timers[$name]['start']) * 1000, 2); - - if (isset($timers[$name]['time'])) { - $diff += $timers[$name]['time']; - } - return $diff; - } - return $timers[$name]['time']; -} - -/** - * Stop the timer with the specified name. - * - * @param $name - * The name of the timer. - * - * @return - * 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). - */ -function timer_stop($name) { - global $timers; - - if (isset($timers[$name]['start'])) { - $stop = microtime(TRUE); - $diff = round(($stop - $timers[$name]['start']) * 1000, 2); - if (isset($timers[$name]['time'])) { - $timers[$name]['time'] += $diff; - } - else { - $timers[$name]['time'] = $diff; - } - unset($timers[$name]['start']); - } - - return $timers[$name]; -} - -/** - * Finds the appropriate configuration directory. - * - * Finds a matching configuration directory by stripping the website's - * hostname from left to right and pathname from right to left. The first - * configuration file found will be used and the remaining ones will be ignored. - * If no configuration file is found, return a default value '$confdir/default'. - * - * With a site located at http://www.example.com:8080/mysite/test/, the file, - * settings.php, is searched for in the following directories: - * - * - $confdir/8080.www.example.com.mysite.test - * - $confdir/www.example.com.mysite.test - * - $confdir/example.com.mysite.test - * - $confdir/com.mysite.test - * - * - $confdir/8080.www.example.com.mysite - * - $confdir/www.example.com.mysite - * - $confdir/example.com.mysite - * - $confdir/com.mysite - * - * - $confdir/8080.www.example.com - * - $confdir/www.example.com - * - $confdir/example.com - * - $confdir/com - * - * - $confdir/default - * - * 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 - * named $sites, which maps domains to directories. It should be in the form - * of: - * @code - * $sites = array( - * 'The url to alias' => 'A directory within the sites directory' - * ); - * @endcode - * For example: - * @code - * $sites = array( - * 'devexample.com' => 'example.com', - * 'localhost.example' => 'example.com', - * ); - * @endcode - * 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", "devexample.com", or "localhost/example". That is useful - * on development servers, where the domain name may not be the same as the - * domain of the live server. Since Drupal stores file paths into the database - * (files, system table, etc.) this will ensure the paths are correct while - * accessed on development servers. - * - * @param bool $require_settings - * Only configuration directories with an existing settings.php file - * will be recognized. Defaults to TRUE. During initial installation, - * this is set to FALSE so that Drupal can detect a matching directory, - * then create a new settings.php file in it. - * @param bool $reset - * Force a full search for matching directories even if one had been - * found previously. Defaults to FALSE. - * - * @return - * The path of the matching directory. - */ -function conf_path($require_settings = TRUE, $reset = FALSE) { - $conf = &drupal_static(__FUNCTION__, ''); - - if ($conf && !$reset) { - return $conf; - } - - $script_name = $_SERVER['SCRIPT_NAME']; - if (!$script_name) { - $script_name = $_SERVER['SCRIPT_FILENAME']; - } - $http_host = $_SERVER['HTTP_HOST']; - $conf = find_conf_path($http_host, $script_name, $require_settings); - return $conf; -} - -/** - * Finds the appropriate configuration directory for a given host and path. - * - * @param $http_host - * The hostname and optional port number, e.g. "www.example.com" or - * "www.example.com:8080". - * @param $script_name - * The part of the url following the hostname, including the leading slash. - * - * @return - * The path of the matching configuration directory. - * - * @see conf_path() - */ -function find_conf_path($http_host, $script_name, $require_settings = TRUE) { - $confdir = 'sites'; - - $sites = array(); - if (file_exists(DRUPAL_ROOT . '/' . $confdir . '/sites.php')) { - // This will overwrite $sites with the desired mappings. - include(DRUPAL_ROOT . '/' . $confdir . '/sites.php'); - } - - $uri = explode('/', $script_name); - $server = explode('.', implode('.', array_reverse(explode(':', rtrim($http_host, '.'))))); - for ($i = count($uri) - 1; $i > 0; $i--) { - for ($j = count($server); $j > 0; $j--) { - $dir = implode('.', array_slice($server, -$j)) . implode('.', array_slice($uri, 0, $i)); - if (isset($sites[$dir]) && file_exists(DRUPAL_ROOT . '/' . $confdir . '/' . $sites[$dir])) { - $dir = $sites[$dir]; - } - if (file_exists(DRUPAL_ROOT . '/' . $confdir . '/' . $dir . '/settings.php') || (!$require_settings && file_exists(DRUPAL_ROOT . '/' . $confdir . '/' . $dir))) { - $conf = "$confdir/$dir"; - return $conf; - } - } - } - $conf = "$confdir/default"; - return $conf; -} - -/** - * Set appropriate server variables needed for command line scripts to work. - * - * This function can be called by command line scripts before bootstrapping - * Drupal, to ensure that the page loads with the desired server parameters. - * This is because many parts of Drupal assume that they are running in a web - * browser and therefore use information from the global PHP $_SERVER variable - * that does not get set when Drupal is run from the command line. - * - * In many cases, the default way in which this function populates the $_SERVER - * variable is sufficient, and it can therefore be called without passing in - * any input. However, command line scripts running on a multisite installation - * (or on any installation that has settings.php stored somewhere other than - * the sites/default folder) need to pass in the URL of the site to allow - * Drupal to detect the correct location of the settings.php file. Passing in - * the 'url' parameter is also required for functions like request_uri() to - * return the expected values. - * - * Most other parameters do not need to be passed in, but may be necessary in - * some cases; for example, if Drupal's ip_address() function needs to return - * anything but the standard localhost value ('127.0.0.1'), the command line - * script should pass in the desired value via the 'REMOTE_ADDR' key. - * - * @param $variables - * (optional) An associative array of variables within $_SERVER that should - * be replaced. If the special element 'url' is provided in this array, it - * will be used to populate some of the server defaults; it should be set to - * the URL of the current page request, excluding any $_GET request but - * including the script name (e.g., http://www.example.com/mysite/index.php). - * - * @see conf_path() - * @see request_uri() - * @see ip_address() - */ -function drupal_override_server_variables($variables = array()) { - // Allow the provided URL to override any existing values in $_SERVER. - if (isset($variables['url'])) { - $url = parse_url($variables['url']); - if (isset($url['host'])) { - $_SERVER['HTTP_HOST'] = $url['host']; - } - if (isset($url['path'])) { - $_SERVER['SCRIPT_NAME'] = $url['path']; - } - unset($variables['url']); - } - // 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( - 'HTTP_HOST' => 'localhost', - 'SCRIPT_NAME' => NULL, - 'REMOTE_ADDR' => '127.0.0.1', - 'REQUEST_METHOD' => 'GET', - 'SERVER_NAME' => NULL, - 'SERVER_SOFTWARE' => NULL, - 'HTTP_USER_AGENT' => NULL, - ); - // Replace elements of the $_SERVER array, as appropriate. - $_SERVER = $variables + $_SERVER + $defaults; -} - -/** - * Initialize PHP environment. - */ -function drupal_environment_initialize() { - if (!isset($_SERVER['HTTP_REFERER'])) { - $_SERVER['HTTP_REFERER'] = ''; - } - if (!isset($_SERVER['SERVER_PROTOCOL']) || ($_SERVER['SERVER_PROTOCOL'] != 'HTTP/1.0' && $_SERVER['SERVER_PROTOCOL'] != 'HTTP/1.1')) { - $_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.0'; - } - - if (isset($_SERVER['HTTP_HOST'])) { - // As HTTP_HOST is user input, ensure it only contains characters allowed - // in hostnames. See RFC 952 (and RFC 2181). - // $_SERVER['HTTP_HOST'] is lowercased here per specifications. - $_SERVER['HTTP_HOST'] = strtolower($_SERVER['HTTP_HOST']); - if (!drupal_valid_http_host($_SERVER['HTTP_HOST'])) { - // HTTP_HOST is invalid, e.g. if containing slashes it may be an attack. - header($_SERVER['SERVER_PROTOCOL'] . ' 400 Bad Request'); - exit; - } - } - else { - // Some pre-HTTP/1.1 clients will not send a Host header. Ensure the key is - // defined for E_ALL compliance. - $_SERVER['HTTP_HOST'] = ''; - } - - // When clean URLs are enabled, emulate ?q=foo/bar using REQUEST_URI. It is - // not possible to append the query string using mod_rewrite without the B - // flag (this was added in Apache 2.2.8), because mod_rewrite unescapes the - // path before passing it on to PHP. This is a problem when the path contains - // e.g. "&" or "%" that have special meanings in URLs and must be encoded. - $_GET['q'] = request_path(); - - // Enforce E_STRICT, but allow users to set levels not part of E_STRICT. - error_reporting(E_STRICT | E_ALL | error_reporting()); - - // Override PHP settings required for Drupal to work properly. - // sites/default/default.settings.php contains more runtime settings. - // The .htaccess file contains settings that cannot be changed at runtime. - - // Don't escape quotes when reading files from the database, disk, etc. - ini_set('magic_quotes_runtime', '0'); - // Use session cookies, not transparent sessions that puts the session id in - // the query string. - ini_set('session.use_cookies', '1'); - ini_set('session.use_only_cookies', '1'); - ini_set('session.use_trans_sid', '0'); - // Don't send HTTP headers using PHP's session handler. - ini_set('session.cache_limiter', 'none'); - // Use httponly session cookies. - ini_set('session.cookie_httponly', '1'); - - // Set sane locale settings, to ensure consistent string, dates, times and - // numbers handling. - setlocale(LC_ALL, 'C'); -} - -/** - * Validate that a hostname (for example $_SERVER['HTTP_HOST']) is safe. - * - * @return - * TRUE if only containing valid characters, or FALSE otherwise. - */ -function drupal_valid_http_host($host) { - return preg_match('/^\[?(?:[a-zA-Z0-9-:\]_]+\.?)+$/', $host); -} - -/** - * Loads the configuration and sets the base URL, cookie domain, and - * session name correctly. - */ -function drupal_settings_initialize() { - global $base_url, $base_path, $base_root; - - // Export the following settings.php variables to the global namespace - 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(); - - if (file_exists(DRUPAL_ROOT . '/' . conf_path() . '/settings.php')) { - include_once DRUPAL_ROOT . '/' . conf_path() . '/settings.php'; - } - $is_https = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on'; - - if (isset($base_url)) { - // Parse fixed base URL from settings.php. - $parts = parse_url($base_url); - $http_protocol = $parts['scheme']; - if (!isset($parts['path'])) { - $parts['path'] = ''; - } - $base_path = $parts['path'] . '/'; - // Build $base_root (everything until first slash after "scheme://"). - $base_root = substr($base_url, 0, strlen($base_url) - strlen($parts['path'])); - } - else { - // Create base URL - $http_protocol = $is_https ? 'https' : 'http'; - $base_root = $http_protocol . '://' . $_SERVER['HTTP_HOST']; - - $base_url = $base_root; - - // $_SERVER['SCRIPT_NAME'] can, in contrast to $_SERVER['PHP_SELF'], not - // be modified by a visitor. - if ($dir = rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/')) { - // Remove "core" directory if present, allowing install.php, update.php, - // cron.php and others to auto-detect a base path. - $core_position = strrpos($dir, '/core'); - if ($core_position !== FALSE && strlen($dir) - 5 == $core_position) { - $base_path = substr($dir, 0, $core_position); - } - else { - $base_path = $dir; - } - $base_url .= $base_path; - $base_path .= '/'; - } - else { - $base_path = '/'; - } - } - $base_secure_url = str_replace('http://', 'https://', $base_url); - $base_insecure_url = str_replace('https://', 'http://', $base_url); - - if ($cookie_domain) { - // If the user specifies the cookie domain, also use it for session name. - $session_name = $cookie_domain; - } - else { - // Otherwise use $base_url as session name, without the protocol - // to use the same session identifiers across http and https. - list( , $session_name) = explode('://', $base_url, 2); - // HTTP_HOST can be modified by a visitor, but we already sanitized it - // in drupal_settings_initialize(). - if (!empty($_SERVER['HTTP_HOST'])) { - $cookie_domain = $_SERVER['HTTP_HOST']; - // Strip leading periods, www., and port numbers from cookie domain. - $cookie_domain = ltrim($cookie_domain, '.'); - if (strpos($cookie_domain, 'www.') === 0) { - $cookie_domain = substr($cookie_domain, 4); - } - $cookie_domain = explode(':', $cookie_domain); - $cookie_domain = '.' . $cookie_domain[0]; - } - } - // Per RFC 2109, cookie domains must contain at least one dot other than the - // first. For hosts such as 'localhost' or IP Addresses we don't set a cookie domain. - if (count(explode('.', $cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $cookie_domain))) { - ini_set('session.cookie_domain', $cookie_domain); - } - // To prevent session cookies from being hijacked, a user can configure the - // SSL version of their website to only transfer session cookies via SSL by - // using PHP's session.cookie_secure setting. The browser will then use two - // separate session cookies for the HTTPS and HTTP versions of the site. So we - // must use different session identifiers for HTTPS and HTTP to prevent a - // cookie collision. - if ($is_https) { - ini_set('session.cookie_secure', TRUE); - } - $prefix = ini_get('session.cookie_secure') ? 'SSESS' : 'SESS'; - session_name($prefix . substr(hash('sha256', $session_name), 0, 32)); -} - -/** - * Returns and optionally sets the filename for a system item (module, - * theme, etc.). 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 - * and themes) to be located in different places depending on a site's - * configuration. For example, a module 'foo' may legally be be located - * in any of these three places: - * - * modules/foo/foo.module - * sites/all/modules/foo/foo.module - * sites/example.com/modules/foo/foo.module - * - * Calling drupal_get_filename('module', 'foo') will give you one of - * the above, depending on where the module is located. - * - * @param $type - * The type of the item (i.e. theme, theme_engine, module, profile). - * @param $name - * The name of the item for which the filename is requested. - * @param $filename - * The filename of the item if it is to be set explicitly rather - * than by consulting the database. - * - * @return - * The filename of the requested item. - */ -function drupal_get_filename($type, $name, $filename = NULL) { - // The location of files will not change during the request, so do not use - // drupal_static(). - static $files = array(), $dirs = array(); - - if (!isset($files[$type])) { - $files[$type] = array(); - } - - if (!empty($filename) && file_exists($filename)) { - $files[$type][$name] = $filename; - } - elseif (isset($files[$type][$name])) { - // nothing - } - // Verify that we have an active database connection, before querying - // the database. This is required because this function is called both - // before we have a database connection (i.e. during installation) and - // when a database connection fails. - else { - try { - if (function_exists('db_query')) { - $file = db_query("SELECT filename FROM {system} WHERE name = :name AND type = :type", array(':name' => $name, ':type' => $type))->fetchField(); - if (file_exists(DRUPAL_ROOT . '/' . $file)) { - $files[$type][$name] = $file; - } - } - } - catch (Exception $e) { - // The database table may not exist because Drupal is not yet installed, - // or the database might be down. We have a fallback for this case so we - // hide the error completely. - } - // Fallback to searching the filesystem if the database could not find the - // file or the file returned by the database is not found. - if (!isset($files[$type][$name])) { - // We have a consistent directory naming: modules, themes... - $dir = $type . 's'; - if ($type == 'theme_engine') { - $dir = 'themes/engines'; - $extension = 'engine'; - } - elseif ($type == 'theme') { - $extension = 'info'; - } - else { - $extension = $type; - } - - if (!isset($dirs[$dir][$extension])) { - $dirs[$dir][$extension] = TRUE; - if (!function_exists('drupal_system_listing')) { - require_once DRUPAL_ROOT . '/core/includes/common.inc'; - } - // Scan the appropriate directories for all files with the requested - // extension, not just the file we are currently looking for. This - // prevents unnecessary scans from being repeated when this function is - // called more than once in the same page request. - $matches = drupal_system_listing("/^" . DRUPAL_PHP_FUNCTION_PATTERN . "\.$extension$/", $dir, 'name', 0); - foreach ($matches as $matched_name => $file) { - $files[$type][$matched_name] = $file->uri; - } - } - } - } - - if (isset($files[$type][$name])) { - return $files[$type][$name]; - } -} - -/** - * Load the persistent variable 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 - * file. - */ -function variable_initialize($conf = array()) { - // NOTE: caching the variables improves performance by 20% when serving - // cached pages. - if ($cached = cache('bootstrap')->get('variables')) { - $variables = $cached->data; - } - else { - // Cache miss. Avoid a stampede. - $name = 'variable_init'; - if (!lock_acquire($name, 1)) { - // Another request is building the variable cache. - // Wait, then re-run this function. - lock_wait($name); - return variable_initialize($conf); - } - else { - // Proceed with variable rebuild. - $variables = array_map('unserialize', db_query('SELECT name, value FROM {variable}')->fetchAllKeyed()); - cache('bootstrap')->set('variables', $variables); - lock_release($name); - } - } - - foreach ($conf as $name => $value) { - $variables[$name] = $value; - } - - return $variables; -} - -/** - * Returns a persistent variable. - * - * Case-sensitivity of the variable_* functions depends on the database - * collation used. To avoid problems, always use lower case for persistent - * variable names. - * - * @param $name - * The name of the variable to return. - * @param $default - * The default value to use if this variable has never been set. - * - * @return - * The value of the variable. - * - * @see variable_del() - * @see variable_set() - */ -function variable_get($name, $default = NULL) { - global $conf; - - return isset($conf[$name]) ? $conf[$name] : $default; -} - -/** - * Sets a persistent variable. - * - * Case-sensitivity of the variable_* functions depends on the database - * collation used. To avoid problems, always use lower case for persistent - * variable names. - * - * @param $name - * The name of the variable to set. - * @param $value - * The value to set. This can be any PHP data type; these functions take care - * of serialization as necessary. - * - * @see variable_del() - * @see variable_get() - */ -function variable_set($name, $value) { - global $conf; - - db_merge('variable')->key(array('name' => $name))->fields(array('value' => serialize($value)))->execute(); - - cache('bootstrap')->delete('variables'); - - $conf[$name] = $value; -} - -/** - * Unsets a persistent variable. - * - * Case-sensitivity of the variable_* functions depends on the database - * collation used. To avoid problems, always use lower case for persistent - * variable names. - * - * @param $name - * The name of the variable to undefine. - * - * @see variable_get() - * @see variable_set() - */ -function variable_del($name) { - global $conf; - - db_delete('variable') - ->condition('name', $name) - ->execute(); - cache('bootstrap')->delete('variables'); - - unset($conf[$name]); -} - -/** - * Retrieve the current page from the cache. - * - * Note: we do not serve cached pages to authenticated users, or to anonymous - * users when $_SESSION is non-empty. $_SESSION may contain status messages - * from a form submission, the contents of a shopping cart, or other user- - * specific content that should not be cached and displayed to other users. - * - * @param $check_only - * (optional) Set to TRUE to only return whether a previous call found a - * cache entry. - * - * @return - * The cache object, if the page was found in the cache, NULL otherwise. - */ -function drupal_page_get_cache($check_only = FALSE) { - global $base_root; - static $cache_hit = FALSE; - - if ($check_only) { - return $cache_hit; - } - - if (drupal_page_is_cacheable()) { - $cache = cache('page')->get($base_root . request_uri()); - if ($cache !== FALSE) { - $cache_hit = TRUE; - } - return $cache; - } -} - -/** - * Determine the cacheability of the current page. - * - * @param $allow_caching - * Set to FALSE if you want to prevent this page to get cached. - * - * @return - * TRUE if the current page can be cached, FALSE otherwise. - */ -function drupal_page_is_cacheable($allow_caching = NULL) { - $allow_caching_static = &drupal_static(__FUNCTION__, TRUE); - if (isset($allow_caching)) { - $allow_caching_static = $allow_caching; - } - - return $allow_caching_static && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') - && !drupal_is_cli(); -} - -/** - * Invoke a bootstrap hook in all bootstrap modules that implement it. - * - * @param $hook - * The name of the bootstrap hook to invoke. - * - * @see bootstrap_hooks() - */ -function bootstrap_invoke_all($hook) { - // Bootstrap modules should have been loaded when this function is called, so - // 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) { - drupal_load('module', $module); - module_invoke($module, $hook); - } -} - -/** - * Includes a file with the provided type and name. This prevents - * including a theme, engine, module, etc., more than once. - * - * @param $type - * The type of item to load (i.e. theme, theme_engine, module). - * @param $name - * The name of the item to load. - * - * @return - * TRUE if the item is loaded or has already been loaded. - */ -function drupal_load($type, $name) { - // Once a file is included this can't be reversed during a request so do not - // use drupal_static() here. - static $files = array(); - - if (isset($files[$type][$name])) { - return TRUE; - } - - $filename = drupal_get_filename($type, $name); - - if ($filename) { - include_once DRUPAL_ROOT . '/' . $filename; - $files[$type][$name] = TRUE; - - return TRUE; - } - - return FALSE; -} - -/** - * Set an HTTP response header for the current page. - * - * 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). - * - * @param $name - * The HTTP header name, or the special 'Status' header name. - * @param $value - * The HTTP header value; if equal to FALSE, the specified header is unset. - * If $name is 'Status', this is expected to be a status code followed by a - * reason phrase, e.g. "404 Not Found". - * @param $append - * Whether to append the value to an existing header or to replace it. - */ -function drupal_add_http_header($name, $value, $append = FALSE) { - // The headers as name/value pairs. - $headers = &drupal_static('drupal_http_headers', array()); - - $name_lower = strtolower($name); - _drupal_set_preferred_header_name($name); - - if ($value === FALSE) { - $headers[$name_lower] = FALSE; - } - elseif (isset($headers[$name_lower]) && $append) { - // Multiple headers with identical names may be combined using comma (RFC - // 2616, section 4.2). - $headers[$name_lower] .= ',' . $value; - } - else { - $headers[$name_lower] = $value; - } - drupal_send_headers(array($name => $headers[$name_lower]), TRUE); -} - -/** - * Get the HTTP response headers for the current page. - * - * @param $name - * 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. - * @return - * A string containing the header value, or FALSE if the header has been set, - * or NULL if the header has not been set. - */ -function drupal_get_http_header($name = NULL) { - $headers = &drupal_static('drupal_http_headers', array()); - if (isset($name)) { - $name = strtolower($name); - return isset($headers[$name]) ? $headers[$name] : NULL; - } - else { - return $headers; - } -} - -/** - * Header names are case-insensitive, but for maximum compatibility they should - * follow "common form" (see RFC 2617, section 4.2). - */ -function _drupal_set_preferred_header_name($name = NULL) { - static $header_names = array(); - - if (!isset($name)) { - return $header_names; - } - $header_names[strtolower($name)] = $name; -} - -/** - * Send the HTTP response headers previously set using drupal_add_http_header(). - * Add default headers, unless they have been replaced or unset using - * drupal_add_http_header(). - * - * @param $default_headers - * An array of headers as name/value pairs. - * @param $single - * If TRUE and headers have already be sent, send only the specified header. - */ -function drupal_send_headers($default_headers = array(), $only_default = FALSE) { - $headers_sent = &drupal_static(__FUNCTION__, FALSE); - $headers = drupal_get_http_header(); - if ($only_default && $headers_sent) { - $headers = array(); - } - $headers_sent = TRUE; - - $header_names = _drupal_set_preferred_header_name(); - foreach ($default_headers as $name => $value) { - $name_lower = strtolower($name); - if (!isset($headers[$name_lower])) { - $headers[$name_lower] = $value; - $header_names[$name_lower] = $name; - } - } - foreach ($headers as $name_lower => $value) { - if ($name_lower == 'status') { - header($_SERVER['SERVER_PROTOCOL'] . ' ' . $value); - } - // Skip headers that have been unset. - elseif ($value) { - header($header_names[$name_lower] . ': ' . $value); - } - } -} - -/** - * Set HTTP headers in preparation for a page response. - * - * Authenticated users are always given a 'no-cache' header, and will fetch a - * fresh page on every request. This prevents authenticated users from seeing - * locally cached pages. - * - * Also give each page a unique ETag. This will force clients to include both - * an If-Modified-Since header and an If-None-Match header when doing - * conditional requests for the page (required by RFC 2616, section 13.3.4), - * making the validation more robust. This is a workaround for a bug in Mozilla - * Firefox that is triggered when Drupal's caching is enabled and the user - * accesses Drupal via an HTTP proxy (see - * https://bugzilla.mozilla.org/show_bug.cgi?id=269303): When an authenticated - * user requests a page, and then logs out and requests the same page again, - * Firefox may send a conditional request based on the page that was cached - * locally when the user was logged in. If this page did not have an ETag - * header, the request only contains an If-Modified-Since header. The date will - * be recent, because with authenticated users the Last-Modified header always - * refers to the time of the request. If the user accesses Drupal via a proxy - * server, and the proxy already has a cached copy of the anonymous page with an - * older Last-Modified date, the proxy may respond with 304 Not Modified, making - * the client think that the anonymous and authenticated pageviews are - * identical. - * - * @see drupal_page_set_cache() - */ -function drupal_page_header() { - $headers_sent = &drupal_static(__FUNCTION__, FALSE); - if ($headers_sent) { - return TRUE; - } - $headers_sent = TRUE; - - $default_headers = array( - 'Expires' => 'Sun, 19 Nov 1978 05:00:00 GMT', - 'Last-Modified' => gmdate(DATE_RFC1123, REQUEST_TIME), - 'Cache-Control' => 'no-cache, must-revalidate, post-check=0, pre-check=0', - 'ETag' => '"' . REQUEST_TIME . '"', - ); - drupal_send_headers($default_headers); -} - -/** - * Set HTTP headers in preparation for a cached page response. - * - * The headers allow as much as possible in proxies and browsers without any - * particular knowledge about the pages. Modules can override these headers - * using drupal_add_http_header(). - * - * If the request is conditional (using If-Modified-Since and If-None-Match), - * and the conditions match those currently in the cache, a 304 Not Modified - * response is sent. - */ -function drupal_serve_page_from_cache(stdClass $cache) { - // Negotiate whether to use compression. - $page_compression = variable_get('page_compression', TRUE) && extension_loaded('zlib'); - $return_compressed = $page_compression && isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE; - - // Get headers set in hook_boot(). Keys are lower-case. - $hook_boot_headers = drupal_get_http_header(); - - // Headers generated in this function, that may be replaced or unset using - // drupal_add_http_headers(). Keys are mixed-case. - $default_headers = array(); - - foreach ($cache->data['headers'] as $name => $value) { - // In the case of a 304 response, certain headers must be sent, and the - // remaining may not (see RFC 2616, section 10.3.5). Do not override - // headers set in hook_boot(). - $name_lower = strtolower($name); - if (in_array($name_lower, array('content-location', 'expires', 'cache-control', 'vary')) && !isset($hook_boot_headers[$name_lower])) { - drupal_add_http_header($name, $value); - unset($cache->data['headers'][$name]); - } - } - - // If the client sent a session cookie, a cached copy will only be served - // to that one particular client due to Vary: Cookie. Thus, do not set - // max-age > 0, allowing the page to be cached by external proxies, when a - // session cookie is present unless the Vary header has been replaced or - // unset in hook_boot(). - $max_age = !isset($_COOKIE[session_name()]) || isset($hook_boot_headers['vary']) ? variable_get('page_cache_maximum_age', 0) : 0; - $default_headers['Cache-Control'] = 'public, max-age=' . $max_age; - - // Entity tag should change if the output changes. - $etag = '"' . $cache->created . '-' . intval($return_compressed) . '"'; - header('Etag: ' . $etag); - - // See if the client has provided the required HTTP headers. - $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) : FALSE; - $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : FALSE; - - if ($if_modified_since && $if_none_match - && $if_none_match == $etag // etag must match - && $if_modified_since == $cache->created) { // if-modified-since must match - header($_SERVER['SERVER_PROTOCOL'] . ' 304 Not Modified'); - drupal_send_headers($default_headers); - return; - } - - // Send the remaining headers. - foreach ($cache->data['headers'] as $name => $value) { - drupal_add_http_header($name, $value); - } - - $default_headers['Last-Modified'] = gmdate(DATE_RFC1123, $cache->created); - - // HTTP/1.0 proxies does not support the Vary header, so prevent any caching - // by sending an Expires date in the past. HTTP/1.1 clients ignores the - // Expires header if a Cache-Control: max-age= directive is specified (see RFC - // 2616, section 14.9.3). - $default_headers['Expires'] = 'Sun, 19 Nov 1978 05:00:00 GMT'; - - drupal_send_headers($default_headers); - - // Allow HTTP proxies to cache pages for anonymous users without a session - // cookie. The Vary header is used to indicates the set of request-header - // fields that fully determines whether a cache is permitted to use the - // response to reply to a subsequent request for a given URL without - // revalidation. If a Vary header has been set in hook_boot(), it is assumed - // that the module knows how to cache the page. - if (!isset($hook_boot_headers['vary']) && !variable_get('omit_vary_cookie')) { - header('Vary: Cookie'); - } - - if ($page_compression) { - header('Vary: Accept-Encoding', FALSE); - // If page_compression is enabled, the cache contains gzipped data. - if ($return_compressed) { - // $cache->data['body'] is already gzip'ed, so make sure - // zlib.output_compression does not compress it once more. - ini_set('zlib.output_compression', '0'); - header('Content-Encoding: gzip'); - } - else { - // The client does not support compression, so unzip the data in the - // cache. Strip the gzip header and run uncompress. - $cache->data['body'] = gzinflate(substr(substr($cache->data['body'], 10), 0, -8)); - } - } - - // Print the page. - print $cache->data['body']; -} - -/** - * Define the critical hooks that force modules to always be loaded. - */ -function bootstrap_hooks() { - return array('boot', 'exit', 'watchdog', 'language_init'); -} - -/** - * Unserializes and appends elements from a serialized string. - * - * @param $obj - * The object to which the elements are appended. - * @param $field - * The attribute of $obj whose value should be unserialized. - */ -function drupal_unpack($obj, $field = 'data') { - if ($obj->$field && $data = unserialize($obj->$field)) { - foreach ($data as $key => $value) { - if (!empty($key) && !isset($obj->$key)) { - $obj->$key = $value; - } - } - } - return $obj; -} - -/** - * Translates a string to the current language or to a given language. - * - * The t() function serves two purposes. First, at run-time it translates - * user-visible text into the appropriate language. Second, various mechanisms - * 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. - * These strings are expected to be in English, so the first argument should - * always be in English. To enable a fully-translatable site, it is important - * that all human-readable text that will be displayed on the site or sent to - * 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 - * break up strings for translation. - * - * You should never use t() to translate variables, such as calling - * @code t($text); @endcode, unless the text that the variable holds has been - * passed through t() elsewhere (e.g., $text is one of several translated - * literal strings in an array). It is especially important never to call - * @code t($user_text); @endcode, where $user_text is some text that a user - * entered - doing that can lead to cross-site scripting and other security - * problems. However, you can use variable substitution in your string, to put - * variable text such as user names or link URLs into translated text. Variable - * substitution looks like this: - * @code - * $text = t("@name's blog", array('@name' => format_username($account))); - * @endcode - * Basically, you can put variables like @name into your string, and t() will - * substitute their sanitized values at translation time (see $args below or - * the Localization API pages referenced above for details). Translators can - * then rearrange the string as necessary for the language (e.g., in Spanish, - * it might be "blog de @name"). - * - * During the Drupal installation phase, some resources used by t() wil not be - * available to code that needs localization. See st() and get_t() for - * alternatives. - * - * @param $string - * A string containing the English string to translate. - * @param $args - * An associative array of replacements to make after translation. - * See format_string(). - * @param $options - * An associative array of additional options, with the following elements: - * - 'langcode' (defaults to the current language): The language code to - * translate to a language other than what is used to display the page. - * - 'context' (defaults to the empty context): The context the source string - * belongs to. - * - * @return - * The translated string. - * - * @see st() - * @see get_t() - * @ingroup sanitization - */ -function t($string, array $args = array(), array $options = array()) { - global $language; - static $custom_strings; - - // Merge in default. - if (empty($options['langcode'])) { - $options['langcode'] = isset($language->language) ? $language->language : LANGUAGE_SYSTEM; - } - if (empty($options['context'])) { - $options['context'] = ''; - } - - // First, check for an array of customized strings. If present, use the array - // *instead of* database lookups. This is a high performance way to provide a - // handful of string replacements. See settings.php for examples. - // Cache the $custom_strings variable to improve performance. - if (!isset($custom_strings[$options['langcode']])) { - $custom_strings[$options['langcode']] = variable_get('locale_custom_strings_' . $options['langcode'], array()); - } - // Custom strings work for English too, even if locale module is disabled. - if (isset($custom_strings[$options['langcode']][$options['context']][$string])) { - $string = $custom_strings[$options['langcode']][$options['context']][$string]; - } - // Translate with locale module if enabled. - elseif ($options['langcode'] != LANGUAGE_SYSTEM && ($options['langcode'] != 'en' || variable_get('locale_translate_english', FALSE)) && function_exists('locale')) { - $string = locale($string, $options['context'], $options['langcode']); - } - if (empty($args)) { - return $string; - } - else { - return format_string($string, $args); - } -} - -/** - * Replace 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. - foreach ($args as $key => $value) { - switch ($key[0]) { - case '@': - // Escaped only. - $args[$key] = check_plain($value); - break; - - case '%': - default: - // Escaped and placeholder. - $args[$key] = drupal_placeholder($value); - break; - - case '!': - // Pass-through. - } - } - return strtr($string, $args); -} - -/** - * Encode special characters in a plain-text string for display as HTML. - * - * Also validates strings as UTF-8 to prevent cross site scripting attacks on - * Internet Explorer 6. - * - * @param $text - * The text to be checked or processed. - * - * @return - * An HTML safe version of $text, or an empty string if $text is not - * valid UTF-8. - * - * @see drupal_validate_utf8() - * @ingroup sanitization - */ -function check_plain($text) { - return htmlspecialchars($text, ENT_QUOTES, 'UTF-8'); -} - -/** - * Checks whether a string is valid UTF-8. - * - * All functions designed to filter input should use drupal_validate_utf8 - * to ensure they operate on valid UTF-8 strings to prevent bypass of the - * filter. - * - * When text containing an invalid UTF-8 lead byte (0xC0 - 0xFF) is presented - * as UTF-8 to Internet Explorer 6, the program may misinterpret subsequent - * bytes. When these subsequent bytes are HTML control characters such as - * quotes or angle brackets, parts of the text that were deemed safe by filters - * end up in locations that are potentially unsafe; An onerror attribute that - * is outside of a tag, and thus deemed safe by a filter, can be interpreted - * by the browser as if it were inside the tag. - * - * The function does not return FALSE for strings containing character codes - * above U+10FFFF, even though these are prohibited by RFC 3629. - * - * @param $text - * The text to check. - * @return - * TRUE if the text is valid UTF-8, FALSE if not. - */ -function drupal_validate_utf8($text) { - if (strlen($text) == 0) { - return TRUE; - } - // With the PCRE_UTF8 modifier 'u', preg_match() fails silently on strings - // containing invalid UTF-8 byte sequences. It does not reject character - // codes above U+10FFFF (represented by 4 or more octets), though. - return (preg_match('/^./us', $text) == 1); -} - -/** - * Returns the equivalent of Apache's $_SERVER['REQUEST_URI'] variable. - * - * Because $_SERVER['REQUEST_URI'] is only available on Apache, we generate an - * equivalent using other environment variables. - */ -function request_uri() { - if (isset($_SERVER['REQUEST_URI'])) { - $uri = $_SERVER['REQUEST_URI']; - } - else { - if (isset($_SERVER['argv'])) { - $uri = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['argv'][0]; - } - elseif (isset($_SERVER['QUERY_STRING'])) { - $uri = $_SERVER['SCRIPT_NAME'] . '?' . $_SERVER['QUERY_STRING']; - } - else { - $uri = $_SERVER['SCRIPT_NAME']; - } - } - // Prevent multiple slashes to avoid cross site requests via the Form API. - $uri = '/' . ltrim($uri, '/'); - - return $uri; -} - -/** - * Log an exception. - * - * This is a wrapper function for watchdog() which automatically decodes an - * exception. - * - * @param $type - * The category to which this message belongs. - * @param $exception - * The exception that is going to be logged. - * @param $message - * The message to store in the log. If empty, a text that contains all useful - * information about the passed-in exception is used. - * @param $variables - * Array of variables to replace in the message on display. Defaults to the - * return value of drupal_decode_exception(). - * @param $severity - * The severity of the message, as per RFC 3164. - * @param $link - * A link to associate with the message. - * - * @see watchdog() - * @see drupal_decode_exception() - */ -function watchdog_exception($type, Exception $exception, $message = NULL, $variables = array(), $severity = WATCHDOG_ERROR, $link = NULL) { - - // Use a default value if $message is not set. - if (empty($message)) { - // 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. - if (!is_array($variables)) { - $variables = array(); - } - - require_once DRUPAL_ROOT . '/core/includes/errors.inc'; - $variables += _drupal_decode_exception($exception); - watchdog($type, $message, $variables, $severity, $link); -} - -/** - * Log a system message. - * - * @param $type - * The category to which this message belongs. Can be any string, but the - * general practice is to use the name of the module calling watchdog(). - * @param $message - * The message to store in the log. Keep $message translatable - * by not concatenating dynamic values into it! Variables in the - * message should be added by using placeholder strings alongside - * the variables argument to declare the value of the placeholders. - * See t() for documentation on how $message and $variables interact. - * @param $variables - * Array of variables to replace in the message on display or - * NULL if message is already translated or not possible to - * translate. - * @param $severity - * The severity of the message, as per RFC 3164. Possible values are - * WATCHDOG_ERROR, WATCHDOG_WARNING, etc. - * @param $link - * A link to associate with the message. - * - * @see watchdog_severity_levels() - * @see hook_watchdog() - */ -function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) { - global $user, $base_root; - - static $in_error_state = FALSE; - - // It is possible that the error handling will itself trigger an error. In that case, we could - // end up in an infinite loop. To avoid that, we implement a simple static semaphore. - if (!$in_error_state && function_exists('module_implements')) { - $in_error_state = TRUE; - - // Prepare the fields to be logged - $log_entry = array( - 'type' => $type, - 'message' => $message, - 'variables' => $variables, - 'severity' => $severity, - 'link' => $link, - 'user' => $user, - 'request_uri' => $base_root . request_uri(), - 'referer' => isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '', - 'ip' => ip_address(), - 'timestamp' => REQUEST_TIME, - ); - - // Call the logging hooks to log/process the message - foreach (module_implements('watchdog') as $module) { - module_invoke($module, 'watchdog', $log_entry); - } - - // It is critical that the semaphore is only cleared here, in the parent - // watchdog() call (not outside the loop), to prevent recursive execution. - $in_error_state = FALSE; - } -} - -/** - * Set a message which reflects the status of the performed operation. - * - * If the function is called with no arguments, this function returns all set - * messages without clearing them. - * - * @param $message - * The message to be displayed to the user. For consistency with other - * messages, it should begin with a capital letter and end with a period. - * @param $type - * The type of the message. One of the following values are possible: - * - 'status' - * - 'warning' - * - 'error' - * @param $repeat - * If this is FALSE and the message is already set, then the message won't - * be repeated. - */ -function drupal_set_message($message = NULL, $type = 'status', $repeat = TRUE) { - if ($message) { - if (!isset($_SESSION['messages'][$type])) { - $_SESSION['messages'][$type] = array(); - } - - if ($repeat || !in_array($message, $_SESSION['messages'][$type])) { - $_SESSION['messages'][$type][] = $message; - } - - // Mark this page as being uncacheable. - drupal_page_is_cacheable(FALSE); - } - - // Messages not set when DB connection fails. - return isset($_SESSION['messages']) ? $_SESSION['messages'] : NULL; -} - -/** - * Return all messages that have been set. - * - * @param $type - * (optional) Only return messages of this type. - * @param $clear_queue - * (optional) Set to FALSE if you do not want to clear the messages queue - * @return - * 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, - * or an empty array if there are no such messages. If $type is not passed, - * all message types are returned, or an empty array if none exist. - */ -function drupal_get_messages($type = NULL, $clear_queue = TRUE) { - if ($messages = drupal_set_message()) { - if ($type) { - if ($clear_queue) { - unset($_SESSION['messages'][$type]); - } - if (isset($messages[$type])) { - return array($type => $messages[$type]); - } - } - else { - if ($clear_queue) { - unset($_SESSION['messages']); - } - return $messages; - } - } - return array(); -} - -/** - * Get the title of the current page, for display on the page and in the title bar. - * - * @return - * The current page's title. - */ -function drupal_get_title() { - $title = drupal_set_title(); - - // During a bootstrap, menu.inc is not included and thus we cannot provide a title. - if (!isset($title) && function_exists('menu_get_active_title')) { - $title = check_plain(menu_get_active_title()); - } - - return $title; -} - -/** - * Set the title of the current page, for display on the page and in the title bar. - * - * @param $title - * Optional string value to assign to the page title; or if set to NULL - * (default), leaves the current title unchanged. - * @param $output - * Optional flag - normally should be left as CHECK_PLAIN. Only set to - * PASS_THROUGH if you have already removed any possibly dangerous code - * from $title using a function like check_plain() or filter_xss(). With this - * flag the string will be passed through unchanged. - * - * @return - * The updated title of the current page. - */ -function drupal_set_title($title = NULL, $output = CHECK_PLAIN) { - $stored_title = &drupal_static(__FUNCTION__); - - if (isset($title)) { - $stored_title = ($output == PASS_THROUGH) ? $title : check_plain($title); - } - - return $stored_title; -} - -/** - * Check to see if an IP address has been blocked. - * - * Blocked IP addresses are stored in the database by default. However for - * performance reasons we allow an override in settings.php. This allows us - * to avoid querying the database at this critical stage of the bootstrap if - * an administrative interface for IP address blocking is not required. - * - * @param $ip - * IP address to check. - * @return bool - * TRUE if access is denied, FALSE if access is allowed. - */ -function drupal_is_denied($ip) { - // Because this function is called on every page request, we first check - // for an array of IP addresses in settings.php before querying the - // database. - $blocked_ips = variable_get('blocked_ips'); - $denied = FALSE; - if (isset($blocked_ips) && is_array($blocked_ips)) { - $denied = in_array($ip, $blocked_ips); - } - // Only check if database.inc is loaded already. If - // $conf['page_cache_without_database'] = TRUE; is set in settings.php, - // then the database won't be loaded here so the IPs in the database - // won't be denied. However the user asked explicitly not to use the - // database and also in this case it's quite likely that the user relies - // on higher performance solutions like a firewall. - elseif (class_exists('Database', FALSE)) { - $denied = (bool)db_query("SELECT 1 FROM {blocked_ips} WHERE ip = :ip", array(':ip' => $ip))->fetchField(); - } - return $denied; -} - -/** - * Handle denied users. - * - * @param $ip - * IP address to check. Prints a message and exits if access is denied. - */ -function drupal_block_denied($ip) { - // Deny access to blocked IP addresses - t() is not yet available. - if (drupal_is_denied($ip)) { - header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden'); - print 'Sorry, ' . check_plain(ip_address()) . ' has been banned.'; - exit(); - } -} - -/** - * Returns a string of highly randomized bytes (over the full 8-bit range). - * - * This function is better than simply calling mt_rand() or any other built-in - * PHP function because it can return a long string of bytes (compared to < 4 - * bytes normally from mt_rand()) and uses the best available pseudo-random source. - * - * @param $count - * The number of characters (bytes) to return in the string. - */ -function drupal_random_bytes($count) { - // $random_state does not use drupal_static as it stores random bytes. - static $random_state, $bytes; - // Initialize on the first call. The contents of $_SERVER includes a mix of - // user-specific and system information that varies a little with each page. - if (!isset($random_state)) { - $random_state = print_r($_SERVER, TRUE); - if (function_exists('getmypid')) { - // Further initialize with the somewhat random PHP process ID. - $random_state .= getmypid(); - } - $bytes = ''; - } - if (strlen($bytes) < $count) { - // /dev/urandom is available on many *nix systems and is considered the - // best commonly available pseudo-random source. - if ($fh = @fopen('/dev/urandom', 'rb')) { - // PHP only performs buffered reads, so in reality it will always read - // at least 4096 bytes. Thus, it costs nothing extra to read and store - // that much so as to speed any additional invocations. - $bytes .= fread($fh, max(4096, $count)); - fclose($fh); - } - // If /dev/urandom is not available or returns no bytes, this loop will - // generate a good set of pseudo-random bytes on any system. - // Note that it may be important that our $random_state is passed - // through hash() prior to being rolled into $output, that the two hash() - // invocations are different, and that the extra input into the first one - - // the microtime() - is prepended rather than appended. This is to avoid - // directly leaking $random_state via the $output stream, which could - // allow for trivial prediction of further "random" numbers. - while (strlen($bytes) < $count) { - $random_state = hash('sha256', microtime() . mt_rand() . $random_state); - $bytes .= hash('sha256', mt_rand() . $random_state, TRUE); - } - } - $output = substr($bytes, 0, $count); - $bytes = substr($bytes, $count); - return $output; -} - -/** - * Calculate a base-64 encoded, URL-safe sha-256 hmac. - * - * @param $data - * String to be validated with the hmac. - * @param $key - * A secret string key. - * - * @return - * A base-64 encoded sha-256 hmac, with + replaced with -, / with _ and - * any = padding characters removed. - */ -function drupal_hmac_base64($data, $key) { - $hmac = base64_encode(hash_hmac('sha256', $data, $key, TRUE)); - // Modify the hmac so it's safe to use in URLs. - return strtr($hmac, array('+' => '-', '/' => '_', '=' => '')); -} - -/** - * Calculate a base-64 encoded, URL-safe sha-256 hash. - * - * @param $data - * String to be hashed. - * - * @return - * A base-64 encoded sha-256 hash, with + replaced with -, / with _ and - * any = padding characters removed. - */ -function drupal_hash_base64($data) { - $hash = base64_encode(hash('sha256', $data, TRUE)); - // Modify the hash so it's safe to use in URLs. - return strtr($hash, array('+' => '-', '/' => '_', '=' => '')); -} - -/** - * 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() { - return drupal_array_merge_deep_array(func_get_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. - * - * @return Object - the user object. - */ -function drupal_anonymous_user() { - $user = new stdClass(); - $user->uid = 0; - $user->hostname = ip_address(); - $user->roles = array(); - $user->roles[DRUPAL_ANONYMOUS_RID] = 'anonymous user'; - $user->cache = 0; - return $user; -} - -/** - * A string describing a phase of Drupal to load. Each phase adds to the - * 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 - * Drupal database from a script without loading anything else, you can - * include bootstrap.inc, and call drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE). - * - * @param $phase - * A constant. Allowed values are the DRUPAL_BOOTSTRAP_* constants. - * @param $new_phase - * A boolean, set to FALSE if calling drupal_bootstrap from inside a - * function called from drupal_bootstrap (recursion). - * @return - * The most recently completed phase. - * - */ -function drupal_bootstrap($phase = NULL, $new_phase = TRUE) { - // Not drupal_static(), because does not depend on any run-time information. - static $phases = array( - DRUPAL_BOOTSTRAP_CONFIGURATION, - DRUPAL_BOOTSTRAP_PAGE_CACHE, - DRUPAL_BOOTSTRAP_DATABASE, - DRUPAL_BOOTSTRAP_VARIABLES, - DRUPAL_BOOTSTRAP_SESSION, - DRUPAL_BOOTSTRAP_PAGE_HEADER, - DRUPAL_BOOTSTRAP_LANGUAGE, - DRUPAL_BOOTSTRAP_FULL, - ); - // Not drupal_static(), because the only legitimate API to control this is to - // call drupal_bootstrap() with a new phase parameter. - static $final_phase; - // Not drupal_static(), because it's impossible to roll back to an earlier - // bootstrap state. - static $stored_phase = -1; - - // When not recursing, store the phase name so it's not forgotten while - // recursing. - if ($new_phase) { - $final_phase = $phase; - } - if (isset($phase)) { - // Call a phase if it has not been called before and is below the requested - // phase. - while ($phases && $phase > $stored_phase && $final_phase > $stored_phase) { - $current_phase = array_shift($phases); - - // This function is re-entrant. Only update the completed phase when the - // current call actually resulted in a progress in the bootstrap process. - if ($current_phase > $stored_phase) { - $stored_phase = $current_phase; - } - - switch ($current_phase) { - case DRUPAL_BOOTSTRAP_CONFIGURATION: - _drupal_bootstrap_configuration(); - break; - - case DRUPAL_BOOTSTRAP_PAGE_CACHE: - _drupal_bootstrap_page_cache(); - break; - - case DRUPAL_BOOTSTRAP_DATABASE: - _drupal_bootstrap_database(); - break; - - case DRUPAL_BOOTSTRAP_VARIABLES: - _drupal_bootstrap_variables(); - break; - - case DRUPAL_BOOTSTRAP_SESSION: - require_once DRUPAL_ROOT . '/' . variable_get('session_inc', 'core/includes/session.inc'); - drupal_session_initialize(); - break; - - case DRUPAL_BOOTSTRAP_PAGE_HEADER: - _drupal_bootstrap_page_header(); - break; - - case DRUPAL_BOOTSTRAP_LANGUAGE: - drupal_language_initialize(); - break; - - case DRUPAL_BOOTSTRAP_FULL: - require_once DRUPAL_ROOT . '/core/includes/common.inc'; - _drupal_bootstrap_full(); - break; - } - } - } - return $stored_phase; -} - -/** - * Return the time zone of the current user. - */ -function drupal_get_user_timezone() { - global $user; - if (variable_get('configurable_timezones', 1) && $user->uid && $user->timezone) { - return $user->timezone; - } - else { - // Ignore PHP strict notice if time zone has not yet been set in the php.ini - // configuration. - return variable_get('date_default_timezone', @date_default_timezone_get()); - } -} - -/** - * Custom PHP error handler. - * - * @param $error_level - * The level of the error raised. - * @param $message - * The error message. - * @param $filename - * The filename that the error was raised in. - * @param $line - * The line number the error was raised at. - * @param $context - * 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) { - require_once DRUPAL_ROOT . '/core/includes/errors.inc'; - _drupal_error_handler_real($error_level, $message, $filename, $line, $context); -} - -/** - * Custom PHP exception handler. - * - * 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 - * handler exits. - * - * @param $exception - * The exception object that was thrown. - */ -function _drupal_exception_handler($exception) { - require_once DRUPAL_ROOT . '/core/includes/errors.inc'; - - try { - // Log the message to the watchdog and return an error page to the user. - _drupal_log_error(_drupal_decode_exception($exception), TRUE); - } - catch (Exception $exception2) { - // Another uncaught exception was thrown while handling the first one. - // If we are displaying errors, then do so with no possibility of a further uncaught exception being thrown. - if (error_displayable()) { - print '<h1>Additional uncaught exception thrown while handling exception.</h1>'; - print '<h2>Original</h2><p>' . _drupal_render_exception_safe($exception) . '</p>'; - print '<h2>Additional</h2><p>' . _drupal_render_exception_safe($exception2) . '</p><hr />'; - } - } -} - -/** - * Bootstrap configuration: Setup script environment and load settings.php. - */ -function _drupal_bootstrap_configuration() { - // Set the Drupal custom error handler. - set_error_handler('_drupal_error_handler'); - set_exception_handler('_drupal_exception_handler'); - - drupal_environment_initialize(); - // Start a page timer: - timer_start('page'); - // Initialize the configuration, including variables from settings.php. - drupal_settings_initialize(); - - // Hook up the Symfony ClassLoader for loading PSR-0-compatible classes. - require_once(DRUPAL_ROOT . '/core/includes/Symfony/Component/ClassLoader/UniversalClassLoader.php'); - - // By default, use the UniversalClassLoader which is best for development, - // as it does not break when code is moved on the file system. It is slow, - // however, so for production the APC class loader should be used instead. - // @todo Switch to a cleaner way to switch autoloaders than variable_get(). - switch (variable_get('autoloader_mode', 'default')) { - case 'apc': - if (function_exists('apc_store')) { - require_once(DRUPAL_ROOT . '/core/includes/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php'); - $loader = new \Symfony\Component\ClassLoader\ApcUniversalClassLoader('drupal.' . $GLOBALS['drupal_hash_salt']); - break; - } - // If APC was not loaded, fall through to the default loader so that - // the site does not fail completely. - case 'dev': - case 'default': - default: - $loader = new \Symfony\Component\ClassLoader\UniversalClassLoader(); - break; - } - - // Register classes with namespaces. - $loader->registerNamespaces(array( - // All Symfony-borrowed code lives in /core/includes/Symfony. - 'Symfony' => DRUPAL_ROOT . '/core/includes', - // All Drupal-namespaced code in core lives in /core/includes/Drupal. - 'Drupal' => DRUPAL_ROOT . '/core/includes', - )); - - // Activate the autoloader. - $loader->register(); -} - -/** - * Bootstrap page cache: Try to serve a page from cache. - */ -function _drupal_bootstrap_page_cache() { - global $user; - - // Allow specifying special cache handlers in settings.php, like - // using memcached or files for storing cache information. - require_once DRUPAL_ROOT . '/core/includes/cache.inc'; - foreach (variable_get('cache_backends', array()) as $include) { - require_once DRUPAL_ROOT . '/' . $include; - } - // Check for a cache mode force from settings.php. - if (variable_get('page_cache_without_database')) { - $cache_enabled = TRUE; - } - else { - drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES, FALSE); - $cache_enabled = variable_get('cache'); - } - drupal_block_denied(ip_address()); - // If there is no session cookie and cache is enabled (or forced), try - // to serve a cached page. - if (!isset($_COOKIE[session_name()]) && $cache_enabled) { - // Make sure there is a user object because its timestamp will be - // checked, hook_boot might check for anonymous user etc. - $user = drupal_anonymous_user(); - // Get the page from the cache. - $cache = drupal_page_get_cache(); - // If there is a cached page, display it. - if (is_object($cache)) { - header('X-Drupal-Cache: HIT'); - // Restore the metadata cached with the page. - $_GET['q'] = $cache->data['path']; - drupal_set_title($cache->data['title'], PASS_THROUGH); - date_default_timezone_set(drupal_get_user_timezone()); - // If the skipping of the bootstrap hooks is not enforced, call - // hook_boot. - if (variable_get('page_cache_invoke_hooks', TRUE)) { - bootstrap_invoke_all('boot'); - } - drupal_serve_page_from_cache($cache); - // If the skipping of the bootstrap hooks is not enforced, call - // hook_exit. - if (variable_get('page_cache_invoke_hooks', TRUE)) { - bootstrap_invoke_all('exit'); - } - // We are done. - exit; - } - else { - header('X-Drupal-Cache: MISS'); - } - } -} - -/** - * Bootstrap database: Initialize database system and register autoload functions. - */ -function _drupal_bootstrap_database() { - // Redirect the user to the installation script if Drupal has not been - // installed yet (i.e., if no $databases array has been defined in the - // settings.php file) and we are not already installing. - if (empty($GLOBALS['databases']) && !drupal_installation_attempted()) { - include_once DRUPAL_ROOT . '/core/includes/install.inc'; - install_goto('core/install.php'); - } - - // The user agent header is used to pass a database prefix in the request when - // running tests. However, for security reasons, it is imperative that we - // validate we ourselves made the request. - if ($test_prefix = drupal_valid_test_ua()) { - // Set the test run id for use in other parts of Drupal. - $test_info = &$GLOBALS['drupal_test_info']; - $test_info['test_run_id'] = $test_prefix; - $test_info['in_child_site'] = TRUE; - - foreach ($GLOBALS['databases']['default'] as &$value) { - // Extract the current default database prefix. - if (!isset($value['prefix'])) { - $current_prefix = ''; - } - elseif (is_array($value['prefix'])) { - $current_prefix = $value['prefix']['default']; - } - else { - $current_prefix = $value['prefix']; - } - - // Remove the current database prefix and replace it by our own. - $value['prefix'] = array( - 'default' => $current_prefix . $test_prefix, - ); - } - } - - // Initialize the database system. Note that the connection - // won't be initialized until it is actually requested. - require_once DRUPAL_ROOT . '/core/includes/database/database.inc'; - - // Register autoload functions so that we can access classes and interfaces. - // The database autoload routine comes first so that we can load the database - // system without hitting the database. That is especially important during - // the install or upgrade process. - spl_autoload_register('drupal_autoload_class'); - spl_autoload_register('drupal_autoload_interface'); -} - -/** - * Bootstrap variables: Load system variables and all enabled bootstrap modules. - */ -function _drupal_bootstrap_variables() { - global $conf; - - // Initialize the lock system. - require_once DRUPAL_ROOT . '/' . variable_get('lock_inc', 'core/includes/lock.inc'); - lock_initialize(); - - // Load variables from the database, but do not overwrite variables set in settings.php. - $conf = variable_initialize(isset($conf) ? $conf : array()); - // Load bootstrap modules. - require_once DRUPAL_ROOT . '/core/includes/module.inc'; - module_load_all(TRUE); -} - -/** - * Bootstrap page header: Invoke hook_boot(), initialize locking system, and send default HTTP headers. - */ -function _drupal_bootstrap_page_header() { - bootstrap_invoke_all('boot'); - - if (!drupal_is_cli()) { - ob_start(); - drupal_page_header(); - } -} - -/** - * Returns the current bootstrap phase for this Drupal process. - * - * The current phase is the one most recently completed by drupal_bootstrap(). - * - * @see drupal_bootstrap() - */ -function drupal_get_bootstrap_phase() { - return drupal_bootstrap(); -} - -/** - * Checks the current User-Agent string to see if this is an internal request - * from SimpleTest. If so, returns the test prefix for this test. - * - * @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() { - global $drupal_hash_salt; - // No reason to reset this. - static $test_prefix; - - if (isset($test_prefix)) { - return $test_prefix; - } - - 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; - // We use the salt from settings.php to make the HMAC key, since - // the database is not yet initialized and we can't access any Drupal variables. - // The file properties add more entropy not easily accessible to others. - $key = $drupal_hash_salt . filectime(__FILE__) . fileinode(__FILE__); - $time_diff = REQUEST_TIME - $time; - // Since we are making a local request a 5 second time window is allowed, - // and the HMAC must match. - 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. - */ -function drupal_generate_test_ua($prefix) { - global $drupal_hash_salt; - static $key; - - if (!isset($key)) { - // We use the salt from settings.php to make the HMAC key, since - // the database is not yet initialized and we can't access any Drupal variables. - // The file properties add more entropy not easily accessible to others. - $key = $drupal_hash_salt . filectime(__FILE__) . fileinode(__FILE__); - } - // Generate a moderately secure HMAC based on the database credentials. - $salt = uniqid('', TRUE); - $check_string = $prefix . ';' . time() . ';' . $salt; - return $check_string . ';' . drupal_hmac_base64($check_string, $key); -} - -/** - * Enables use of the theme system without requiring database access. - * - * Loads and initializes the theme system for site installs, updates and when - * the site is in maintenance mode. This also applies when the database fails. - * - * @see _drupal_maintenance_theme() - */ -function drupal_maintenance_theme() { - require_once DRUPAL_ROOT . '/core/includes/theme.maintenance.inc'; - _drupal_maintenance_theme(); -} - -/** - * 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; - } - } -} - -/** - * Return TRUE if a Drupal installation is currently being attempted. - */ -function drupal_installation_attempted() { - return defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'install'; -} - -/** - * Returns the name of the proper localization function. - * - * 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() { - static $t; - // This is not converted to drupal_static because there is no point in - // resetting this as it can not change in the course of a request. - if (!isset($t)) { - $t = drupal_installation_attempted() ? 'st' : 't'; - } - return $t; -} - -/** - * Initialize all the defined language types. - */ -function drupal_language_initialize() { - $types = language_types(); - - // Ensure the language is correctly returned, even without multilanguage - // support. Also make sure we have a $language fallback, in case a language - // negotiation callback needs to do a full bootstrap. - // Useful for eg. XML/HTML 'lang' attributes. - $default = language_default(); - foreach ($types as $type) { - $GLOBALS[$type] = $default; - } - if (drupal_multilingual()) { - include_once DRUPAL_ROOT . '/core/includes/language.inc'; - foreach ($types as $type) { - $GLOBALS[$type] = language_initialize($type); - } - // Allow modules to react on language system initialization in multilingual - // environments. - bootstrap_invoke_all('language_init'); - } -} - -/** - * The built-in language types. - * - * @return - * An array of key-values pairs where the key is the language type and the - * value is its configurability. - */ -function drupal_language_types() { - return array( - LANGUAGE_TYPE_INTERFACE => TRUE, - LANGUAGE_TYPE_CONTENT => FALSE, - LANGUAGE_TYPE_URL => FALSE, - ); -} - -/** - * Return true if there is more than one language enabled. - */ -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 an array of the available language types. - */ -function language_types() { - return array_keys(variable_get('language_types', drupal_language_types())); -} - -/** - * Returns a list of installed languages, indexed by the specified key. - * - * @param $field - * (optional) 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') { - $languages = &drupal_static(__FUNCTION__); - // Init language list - if (!isset($languages)) { - $default = language_default(); - if (drupal_multilingual() || module_exists('locale')) { - $languages['language'] = db_query('SELECT * FROM {languages} ORDER BY weight ASC, name ASC')->fetchAllAssoc('language'); - } - else { - // No locale module, so use the default language only. - $languages['language'][$default->language] = $default; - } - - // Initialize default property so callers have an easy reference and - // can save the same object without data loss. - foreach ($languages['language'] as $langcode => $language) { - $languages['language'][$langcode]->default = ($langcode == $default->language); - } - } - - // Return the array indexed by the right field - if (!isset($languages[$field])) { - $languages[$field] = array(); - foreach ($languages['language'] as $lang) { - // Some values should be collected into an array - if (in_array($field, array('enabled', 'weight'))) { - $languages[$field][$lang->$field][$lang->language] = $lang; - } - else { - $languages[$field][$lang->$field] = $lang; - } - } - } - return $languages[$field]; -} - -/** - * Default language used on the site. - * - * @return - * A language object. - */ -function language_default() { - return variable_get( - 'language_default', - (object) array( - 'language' => 'en', - 'name' => 'English', - 'direction' => 0, - 'enabled' => 1, - 'plurals' => 0, - 'formula' => '', - 'domain' => '', - 'prefix' => '', - 'weight' => 0, - 'javascript' => '' - ) - ); -} - -/** - * Returns the requested URL path of the page being viewed. - * - * Examples: - * - http://example.com/node/306 returns "node/306". - * - http://example.com/drupalfolder/node/306 returns "node/306" while - * base_path() returns "/drupalfolder/". - * - http://example.com/path/alias (which is a path alias for node/306) returns - * "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 - * The requested Drupal URL path. - * - * @see current_path() - */ -function request_path() { - static $path; - - if (isset($path)) { - return $path; - } - - if (isset($_GET['q'])) { - // This is a request with a ?q=foo/bar query string. $_GET['q'] is - // overwritten in drupal_path_initialize(), but request_path() is called - // very early in the bootstrap process, so the original value is saved in - // $path and returned in later calls. - $path = $_GET['q']; - } - elseif (isset($_SERVER['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'], '?'); - $base_path_len = strlen(rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/')); - // Unescape and strip $base_path prefix, leaving q without a leading slash. - $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 { - // This is the front page. - $path = ''; - } - - // Under certain conditions Apache's RewriteRule directive prepends the value - // assigned to $_GET['q'] with a slash. Moreover we can always have a trailing - // slash in place, hence we need to normalize $_GET['q']. - $path = trim($path, '/'); - - return $path; -} - -/** - * Return a component of the current Drupal path. - * - * When viewing a page at the path "admin/structure/types", for example, arg(0) - * returns "admin", arg(1) returns "structure", and arg(2) returns "types". - * - * Avoid use of this function where possible, as resulting code is hard to read. - * In menu callback functions, attempt to use named arguments. See the explanation - * in menu.inc for how to construct callbacks that take arguments. When attempting - * to use this function to load an element from the current path, e.g. loading the - * node on a node page, please use menu_get_object() instead. - * - * @param $index - * The index of the component, where each component is separated by a '/' - * (forward-slash), and where the first component has an index of 0 (zero). - * @param $path - * A path to break into components. Defaults to the path of the current page. - * - * @return - * The component specified by $index, or NULL if the specified component was - * not found. If called without arguments, it returns an array containing all - * the components of the current path. - */ -function arg($index = NULL, $path = NULL) { - // Even though $arguments doesn't need to be resettable for any functional - // reasons (the result of explode() does not depend on any run-time - // information), it should be resettable anyway in case a module needs to - // free up the memory used by it. - // Use the advanced drupal_static() pattern, since this is called very often. - static $drupal_static_fast; - if (!isset($drupal_static_fast)) { - $drupal_static_fast['arguments'] = &drupal_static(__FUNCTION__); - } - $arguments = &$drupal_static_fast['arguments']; - - if (!isset($path)) { - $path = $_GET['q']; - } - if (!isset($arguments[$path])) { - $arguments[$path] = explode('/', $path); - } - if (!isset($index)) { - return $arguments[$path]; - } - if (isset($arguments[$path][$index])) { - return $arguments[$path][$index]; - } -} - -/** - * If Drupal is behind a reverse proxy, we use the X-Forwarded-For header - * instead of $_SERVER['REMOTE_ADDR'], which would be the IP address of - * the proxy server, and not the client's. The actual header name can be - * configured by the reverse_proxy_header variable. - * - * @return - * IP address of client machine, adjusted for reverse proxy and/or cluster - * environments. - */ -function ip_address() { - $ip_address = &drupal_static(__FUNCTION__); - - if (!isset($ip_address)) { - $ip_address = $_SERVER['REMOTE_ADDR']; - - if (variable_get('reverse_proxy', 0)) { - $reverse_proxy_header = variable_get('reverse_proxy_header', 'HTTP_X_FORWARDED_FOR'); - if (!empty($_SERVER[$reverse_proxy_header])) { - // If an array of known reverse proxy IPs is provided, then trust - // the XFF header if request really comes from one of them. - $reverse_proxy_addresses = variable_get('reverse_proxy_addresses', array()); - - // Turn XFF header into an array. - $forwarded = explode(',', $_SERVER[$reverse_proxy_header]); - - // Trim the forwarded IPs; they may have been delimited by commas and spaces. - $forwarded = array_map('trim', $forwarded); - - // Tack direct client IP onto end of forwarded array. - $forwarded[] = $ip_address; - - // Eliminate all trusted IPs. - $untrusted = array_diff($forwarded, $reverse_proxy_addresses); - - // The right-most IP is the most specific we can trust. - $ip_address = array_pop($untrusted); - } - } - } - - return $ip_address; -} - -/** - * @ingroup schemaapi - * @{ - */ - -/** - * Get the schema definition of a table, or the whole database schema. - * - * The returned schema will include any modifications made by any - * module that implements hook_schema_alter(). - * - * @param $table - * The name of the table. If not given, the schema of all tables is returned. - * @param $rebuild - * If true, the schema will be rebuilt instead of retrieved from the cache. - */ -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 { - - public function __construct() { - // Cache by request method. - parent::__construct('schema:runtime:' . ($_SERVER['REQUEST_METHOD'] == 'GET'), 'cache'); - } - - 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; - } -} - -/** - * Get 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(); - - if (empty($schema) || $rebuild) { - // Try to load the schema from cache. - if (!$rebuild && $cached = cache()->get('schema')) { - $schema = $cached->data; - } - // Otherwise, rebuild the schema cache. - else { - $schema = array(); - // Load the .install files to get hook_schema. - // 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. - if (function_exists('module_load_all_includes')) { - // This function can be called very early in the bootstrap process, so - // we force the module_list() cache to be refreshed to ensure that it - // contains the complete list of modules before we go on to call - // module_load_all_includes(). - module_list(TRUE); - module_load_all_includes('install'); - } - - require_once DRUPAL_ROOT . '/core/includes/common.inc'; - // Invoke hook_schema for all modules. - foreach (module_implements('schema') as $module) { - // Cast the result of hook_schema() to an array, as a NULL return value - // would cause array_merge() to set the $schema variable to NULL as well. - // That would break modules which use $schema further down the line. - $current = (array) module_invoke($module, 'schema'); - // Set 'module' and 'name' keys for each table, and remove descriptions, - // as they needlessly slow down cache()->get() for every single request. - _drupal_schema_initialize($current, $module); - $schema = array_merge($schema, $current); - } - - drupal_alter('schema', $schema); - // If the schema is empty, avoid saving it: some database engines require - // the schema to perform queries, and this could lead to infinite loops. - if (!empty($schema) && (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL)) { - cache()->set('schema', $schema); - } - if ($rebuild) { - cache()->deletePrefix('schema:'); - } - } - } - - return $schema; -} - -/** - * @} End of "ingroup schemaapi". - */ - - -/** - * @ingroup registry - * @{ - */ - -/** - * Confirm that an interface is available. - * - * This function is rarely called directly. Instead, it is registered as an - * spl_autoload() handler, and PHP calls it for us when necessary. - * - * @param $interface - * The name of the interface to check or load. - * @return - * TRUE if the interface is currently available, FALSE otherwise. - */ -function drupal_autoload_interface($interface) { - return _registry_check_code('interface', $interface); -} - -/** - * Confirm that a class is available. - * - * This function is rarely called directly. Instead, it is registered as an - * spl_autoload() handler, and PHP calls it for us when necessary. - * - * @param $class - * The name of the class to check or load. - * @return - * TRUE if the class is currently available, FALSE otherwise. - */ -function drupal_autoload_class($class) { - return _registry_check_code('class', $class); -} - -/** - * Helper to check for a resource in the registry. - * - * @param $type - * The type of resource we are looking up, or one of the constants - * REGISTRY_RESET_LOOKUP_CACHE or REGISTRY_WRITE_LOOKUP_CACHE, which - * signal that we should reset or write the cache, respectively. - * @param $name - * The name of the resource, or NULL if either of the REGISTRY_* constants - * is passed in. - * @return - * TRUE if the resource was found, FALSE if not. - * NULL if either of the REGISTRY_* constants is passed in as $type. - */ -function _registry_check_code($type, $name = NULL) { - static $lookup_cache, $cache_update_needed; - - if ($type == 'class' && class_exists($name) || $type == 'interface' && interface_exists($name)) { - return TRUE; - } - - if (!isset($lookup_cache)) { - $lookup_cache = array(); - if ($cache = cache('bootstrap')->get('lookup_cache')) { - $lookup_cache = $cache->data; - } - } - - // When we rebuild the registry, we need to reset this cache so - // we don't keep lookups for resources that changed during the rebuild. - if ($type == REGISTRY_RESET_LOOKUP_CACHE) { - $cache_update_needed = TRUE; - $lookup_cache = NULL; - return; - } - - // Called from drupal_page_footer, we write to permanent storage if there - // changes to the lookup cache for this request. - if ($type == REGISTRY_WRITE_LOOKUP_CACHE) { - if ($cache_update_needed) { - cache('bootstrap')->set('lookup_cache', $lookup_cache); - } - return; - } - - // $type is either 'interface' or 'class', so we only need the first letter to - // keep the cache key unique. - $cache_key = $type[0] . $name; - if (isset($lookup_cache[$cache_key])) { - if ($lookup_cache[$cache_key]) { - require_once DRUPAL_ROOT . '/' . $lookup_cache[$cache_key]; - } - return (bool) $lookup_cache[$cache_key]; - } - - // This function may get called when the default database is not active, but - // there is no reason we'd ever want to not use the default database for - // this query. - $file = Database::getConnection('default', 'default')->query("SELECT filename FROM {registry} WHERE name = :name AND type = :type", array( - ':name' => $name, - ':type' => $type, - )) - ->fetchField(); - - // Flag that we've run a lookup query and need to update the cache. - $cache_update_needed = TRUE; - - // Misses are valuable information worth caching, so cache even if - // $file is FALSE. - $lookup_cache[$cache_key] = $file; - - if ($file) { - require_once DRUPAL_ROOT . '/' . $file; - return TRUE; - } - else { - return FALSE; - } -} - -/** - * Rescan all enabled modules and rebuild the registry. - * - * Rescans all code in modules or includes directories, storing the location of - * each interface or class in the database. - */ -function registry_rebuild() { - system_rebuild_module_data(); - registry_update(); -} - -/** - * Update the registry based on the latest files listed in the database. - * - * 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 - * {system} table matches those in the file system. - * - * @see registry_rebuild() - */ -function registry_update() { - require_once DRUPAL_ROOT . '/core/includes/registry.inc'; - _registry_update(); -} - -/** - * @} End of "ingroup registry". - */ - -/** - * Central static variable storage. - * - * 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 - * absolutely certain that the static variable will not need to be reset during - * the page request. By centralizing static variable storage through this - * function, other functions can rely on a consistent API for resetting any - * other function's static variables. - * - * Example: - * @code - * function language_list($field = 'language') { - * $languages = &drupal_static(__FUNCTION__); - * if (!isset($languages)) { - * // If this function is being called for the first time after a reset, - * // query the database and execute any other code needed to retrieve - * // information about the supported languages. - * ... - * } - * if (!isset($languages[$field])) { - * // If this function is being called for the first time for a particular - * // index field, then execute code needed to index the information already - * // available in $languages by the desired field. - * ... - * } - * // Subsequent invocations of this function for a particular index field - * // skip the above two code blocks and quickly return the already indexed - * // information. - * return $languages[$field]; - * } - * function locale_translate_overview_screen() { - * // When building the content for the translations overview page, make - * // sure to get completely fresh information about the supported languages. - * drupal_static_reset('language_list'); - * ... - * } - * @endcode - * - * In a few cases, a function can have certainty that there is no legitimate - * use-case for resetting that function's static variable. This is rare, - * because when writing a function, it's hard to forecast all the situations in - * which it will be used. A guideline is that if a function's static variable - * does not depend on any information outside of the function that might change - * during a single page request, then it's ok to use the "static" keyword - * instead of the drupal_static() function. - * - * Example: - * @code - * function actions_do(...) { - * // $stack tracks the number of recursive calls. - * static $stack; - * $stack++; - * if ($stack > variable_get('actions_max_stack', 35)) { - * ... - * return; - * } - * ... - * $stack--; - * } - * @endcode - * - * In a few cases, a function needs a resettable static variable, but the - * function is called many times (100+) during a single page request, so - * every microsecond of execution time that can be removed from the function - * counts. These functions can use a more cumbersome, but faster variant of - * calling drupal_static(). It works by storing the reference returned by - * drupal_static() in the calling function's own static variable, thereby - * removing the need to call drupal_static() for each iteration of the function. - * Conceptually, it replaces: - * @code - * $foo = &drupal_static(__FUNCTION__); - * @endcode - * with: - * @code - * // Unfortunately, this does not work. - * static $foo = &drupal_static(__FUNCTION__); - * @endcode - * However, the above line of code does not work, because PHP only allows static - * variables to be initializied by literal values, and does not allow static - * variables to be assigned to references. - * - http://php.net/manual/en/language.variables.scope.php#language.variables.scope.static - * - http://php.net/manual/en/language.variables.scope.php#language.variables.scope.references - * The example below shows the syntax needed to work around both limitations. - * For benchmarks and more information, see http://drupal.org/node/619666. - * - * Example: - * @code - * function user_access($string, $account = NULL) { - * // Use the advanced drupal_static() pattern, since this is called very often. - * static $drupal_static_fast; - * if (!isset($drupal_static_fast)) { - * $drupal_static_fast['perm'] = &drupal_static(__FUNCTION__); - * } - * $perm = &$drupal_static_fast['perm']; - * ... - * } - * @endcode - * - * @param $name - * Globally unique name for the variable. For a function with only one static, - * variable, the function name (e.g. via the PHP magic __FUNCTION__ constant) - * is recommended. For a function with multiple static variables add a - * distinguishing suffix to the function name for each one. - * @param $default_value - * Optional default value. - * @param $reset - * TRUE to reset a specific named variable, or all variables if $name is NULL. - * Resetting every variable should only be used, for example, for running - * unit tests with a clean environment. Should be used only though via - * function drupal_static_reset() and the return value should not be used in - * this case. - * - * @return - * Returns a variable by reference. - * - * @see drupal_static_reset() - */ -function &drupal_static($name, $default_value = NULL, $reset = FALSE) { - static $data = array(), $default = array(); - // First check if dealing with a previously defined static variable. - if (isset($data[$name]) || array_key_exists($name, $data)) { - // Non-NULL $name and both $data[$name] and $default[$name] statics exist. - if ($reset) { - // Reset pre-existing static variable to its default value. - $data[$name] = $default[$name]; - } - return $data[$name]; - } - // Neither $data[$name] nor $default[$name] static variables exist. - if (isset($name)) { - if ($reset) { - // Reset was called before a default is set and yet a variable must be - // returned. - return $data; - } - // First call with new non-NULL $name. Initialize a new static variable. - $default[$name] = $data[$name] = $default_value; - return $data[$name]; - } - // Reset all: ($name == NULL). This needs to be done one at a time so that - // references returned by earlier invocations of drupal_static() also get - // reset. - foreach ($default as $name => $value) { - $data[$name] = $value; - } - // As the function returns a reference, the return should always be a - // variable. - return $data; -} - -/** - * Reset one or all centrally stored static variable(s). - * - * @param $name - * Name of the static variable to reset. Omit to reset all variables. - */ -function drupal_static_reset($name = NULL) { - drupal_static($name, NULL, TRUE); -} - -/** - * Detect whether the current script is running in a command-line environment. - */ -function drupal_is_cli() { - return (!isset($_SERVER['SERVER_SOFTWARE']) && (php_sapi_name() == 'cli' || (is_numeric($_SERVER['argc']) && $_SERVER['argc'] > 0))); -} - -/** - * Formats text for emphasized display in a placeholder inside a sentence. - * Used automatically by t(). - * - * @param $text - * The text to format (plain-text). - * - * @return - * The formatted text (html). - */ -function drupal_placeholder($text) { - return '<em class="placeholder">' . check_plain($text) . '</em>'; -} - -/** - * Register a function for execution on shutdown. - * - * Wrapper for register_shutdown_function() that catches thrown exceptions to - * avoid "Exception thrown without a stack frame in Unknown". - * - * @param $callback - * The shutdown function to register. - * @param ... - * Additional arguments to pass to the shutdown function. - * - * @return - * Array of shutdown functions to be executed. - * - * @see register_shutdown_function() - * @ingroup php_wrappers - */ -function &drupal_register_shutdown_function($callback = NULL) { - // We cannot use drupal_static() here because the static cache is reset during - // batch processing, which breaks batch handling. - static $callbacks = array(); - - if (isset($callback)) { - // Only register the internal shutdown function once. - if (empty($callbacks)) { - register_shutdown_function('_drupal_shutdown_function'); - } - $args = func_get_args(); - array_shift($args); - // Save callback and arguments - $callbacks[] = array('callback' => $callback, 'arguments' => $args); - } - return $callbacks; -} - -/** - * Internal function used to execute registered shutdown functions. - */ -function _drupal_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 { - while (list($key, $callback) = each($callbacks)) { - call_user_func_array($callback['callback'], $callback['arguments']); - } - } - catch (Exception $exception) { - // If we are displaying errors, then do so with no possibility of a further uncaught exception being thrown. - require_once DRUPAL_ROOT . '/core/includes/errors.inc'; - if (error_displayable()) { - print '<h1>Uncaught exception thrown in shutdown function.</h1>'; - print '<p>' . _drupal_render_exception_safe($exception) . '</p><hr />'; - } - } -} diff --git a/core/includes/cache-install.inc b/core/includes/cache-install.inc deleted file mode 100644 index 8bcf8b7b1c1..00000000000 --- a/core/includes/cache-install.inc +++ /dev/null @@ -1,68 +0,0 @@ -<?php - -/** - * @file - * Provides a stub cache implementation to be used during installation. - */ - -/** - * A stub cache implementation to be used during the installation process. - * - * The stub implementation is needed when database access is not yet available. - * Because Drupal's caching system never requires that cached data be present, - * these stub functions can short-circuit the process and sidestep the need for - * any persistent storage. Obviously, using this cache implementation during - * normal operations would have a negative impact on performance. - */ -class DrupalFakeCache extends DrupalDatabaseCache implements DrupalCacheInterface { - function get($cid) { - return FALSE; - } - - function getMultiple(&$cids) { - return array(); - } - - function set($cid, $data, $expire = CACHE_PERMANENT) { - } - - function deletePrefix($cid) { - try { - if (class_exists('Database')) { - parent::deletePrefix($cid); - } - } - catch (Exception $e) { - } - } - - function clear($cid = NULL, $wildcard = FALSE) { - // 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 - // during installation due to any full bootstraps that may occur at the - // 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 - // cache would become stale; for example, the installer sometimes calls - // variable_set(), which updates the {variable} table and then clears the - // cache to make sure that the next page request picks up the new value. - // Not actually clearing the cache here therefore leads old variables to be - // loaded on the first page requests after installation, which can cause - // subtle bugs, some of which would not be fixed unless the site - // administrator cleared the cache manually. - try { - if (class_exists('Database')) { - parent::clear($cid, $wildcard); - } - } - // If the attempt at clearing the cache causes an error, that means that - // either the database connection is not set up yet or the relevant cache - // table in the database has not yet been created, so we can safely do - // nothing here. - catch (Exception $e) { - } - } - - function isEmpty() { - return TRUE; - } -} diff --git a/core/includes/cache.inc b/core/includes/cache.inc deleted file mode 100644 index fcf3e5e5187..00000000000 --- a/core/includes/cache.inc +++ /dev/null @@ -1,618 +0,0 @@ -<?php - -/** - * Factory for instantiating and statically caching the correct class for a cache bin. - * - * By default, this returns an instance of the DrupalDatabaseCache class. - * Classes implementing DrupalCacheInterface can register themselves both as a - * default implementation and for specific bins. - * - * @see DrupalCacheInterface - * - * @param $bin - * The cache bin for which the cache object should be returned, defaults to - * 'cache'. - * @return DrupalCacheInterface - * The cache object associated with the specified bin. - */ -function cache($bin = 'cache') { - // Temporary backwards compatibiltiy layer, allow old style prefixed cache - // bin names to be passed as arguments. - $bin = str_replace('cache_', '', $bin); - - // We do not use drupal_static() here because we do not want to change the - // storage of a cache bin mid-request. - static $cache_objects; - if (!isset($cache_objects[$bin])) { - $class = variable_get('cache_class_' . $bin); - if (!isset($class)) { - $class = variable_get('cache_default_class', 'DrupalDatabaseCache'); - } - $cache_objects[$bin] = new $class($bin); - } - return $cache_objects[$bin]; -} - -/** - * Return data from the persistent cache - * - * Data may be stored as either plain text or as serialized data. cache_get - * will automatically return unserialized objects and arrays. - * - * @param $cid - * The cache ID of the data to retrieve. - * @param $bin - * The cache bin to store the data in. Valid core values are 'cache_block', - * 'cache_bootstrap', 'cache_field', 'cache_filter', 'cache_form', - * 'cache_menu', 'cache_page', 'cache_path', 'cache_update' or 'cache' for - * the default cache. - * - * @return - * The cache or FALSE on failure. - */ -function cache_get($cid, $bin = 'cache') { - return cache($bin)->get($cid); -} - -/** - * Return data from the persistent cache when given an array of cache IDs. - * - * @param $cids - * 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. - * @param $bin - * The cache bin where the data is stored. - * @return - * An array of the items successfully returned from cache indexed by cid. - */ -function cache_get_multiple(array &$cids, $bin = 'cache') { - return cache($bin)->getMultiple($cids); -} - -/** - * Store data in the persistent cache. - * - * 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 - * same name. Other implementations might want to store several bins in data - * structures that get flushed together. While it is not a problem for most - * cache bins if the entries in them are flushed before their expire time, some - * might break functionality or are extremely expensive to recalculate. These - * will be marked with a (*). The other bins expired automatically by core. - * Contributed modules can add additional bins and get them expired - * automatically by implementing hook_flush_caches(). - * - * - cache: Generic cache storage bin (used for variables, theme registry, - * locale date, list of simpletest tests etc). - * - * - cache_block: Stores the content of various blocks. - * - * - cache field: Stores the field data belonging to a given object. - * - * - cache_filter: Stores filtered pieces of content. - * - * - cache_form(*): Stores multistep forms. Flushing this bin means that some - * forms displayed to users lose their state and the data already submitted - * to them. - * - * - cache_menu: Stores the structure of visible navigation menus per page. - * - * - cache_page: Stores generated pages for anonymous users. It is flushed - * very often, whenever a page changes, at least for every ode and comment - * submission. This is the only bin affected by the page cache setting on - * the administrator panel. - * - * - cache path: Stores the system paths that have an alias. - * - * - cache update(*): Stores available releases. The update server (for - * example, drupal.org) needs to produce the relevant XML for every project - * installed on the current site. As this is different for (almost) every - * site, it's very expensive to recalculate for the update server. - * - * The reasons for having several bins are as follows: - * - * - smaller bins mean smaller database tables and allow for faster selects and - * inserts - * - we try to put fast changing cache items and rather static ones into - * different bins. The effect is that only the fast changing bins will need a - * lot of writes to disk. The more static bins will also be better cacheable - * with MySQL's query cache. - * - * @param $cid - * The cache ID of the data to store. - * @param $data - * The data to store in the cache. Complex data types will be automatically - * serialized before insertion. - * Strings will be stored as plain text and not serialized. - * @param $bin - * The cache bin to store the data in. Valid core values are 'cache_block', - * 'cache_bootstrap', 'cache_field', 'cache_filter', 'cache_form', - * 'cache_menu', 'cache_page', 'cache_update' or 'cache' for the default - * cache. - * @param $expire - * One of the following values: - * - CACHE_PERMANENT: Indicates that the item should never be removed unless - * explicitly told to using cache_clear_all() with a cache ID. - * - CACHE_TEMPORARY: Indicates that the item should be removed at the next - * general cache wipe. - * - A Unix timestamp: Indicates that the item should be kept at least until - * the given time, after which it behaves like CACHE_TEMPORARY. - */ -function cache_set($cid, $data, $bin = 'cache', $expire = CACHE_PERMANENT) { - return cache($bin)->set($cid, $data, $expire); -} - -/** - * Expire data from the cache. - * - * If called without arguments, expirable entries will be cleared from the - * cache_page and cache_block bins. - * - * @param $cid - * If set, the cache ID to delete. Otherwise, all cache entries that can - * expire are deleted. - * - * @param $bin - * If set, the bin $bin to delete from. Mandatory - * argument if $cid is set. - * - * @param $wildcard - * If $wildcard is TRUE, cache IDs starting with $cid are deleted in - * addition to the exact cache ID specified by $cid. If $wildcard is - * TRUE and $cid is '*' then the entire bin $bin is emptied. - */ -function cache_clear_all($cid = NULL, $bin = NULL, $wildcard = FALSE) { - if (!isset($cid) && !isset($bin)) { - // Clear the block cache first, so stale data will - // not end up in the page cache. - if (module_exists('block')) { - cache('block')->expire(); - } - cache('page')->expire(); - return; - } - return cache($bin)->clear($cid, $wildcard); -} - -/** - * Check if a cache bin is empty. - * - * A cache bin is considered empty if it does not contain any valid data for any - * cache ID. - * - * @param $bin - * The cache bin to check. - * @return - * TRUE if the cache bin specified is empty. - */ -function cache_is_empty($bin) { - return cache($bin)->isEmpty(); -} - -/** - * Interface for cache implementations. - * - * All cache implementations have to implement this interface. - * DrupalDatabaseCache provides the default implementation, which can be - * consulted as an example. - * - * To make Drupal use your implementation for a certain cache bin, you have to - * set a variable with the name of the cache bin as its key and the name of - * your class as its value. For example, if your implementation of - * DrupalCacheInterface was called MyCustomCache, the following line would make - * Drupal use it for the 'cache_page' bin: - * @code - * variable_set('cache_class_cache_page', 'MyCustomCache'); - * @endcode - * - * Additionally, you can register your cache implementation to be used by - * default for all cache bins by setting the variable 'cache_default_class' to - * the name of your implementation of the DrupalCacheInterface, e.g. - * @code - * variable_set('cache_default_class', 'MyCustomCache'); - * @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() - * @see DrupalDatabaseCache - */ -interface DrupalCacheInterface { - /** - * Constructor. - * - * @param $bin - * The cache bin for which the object is created. - */ - function __construct($bin); - - /** - * Return data from the persistent cache. Data may be stored as either plain - * text or as serialized data. cache_get will automatically return - * unserialized objects and arrays. - * - * @param $cid - * The cache ID of the data to retrieve. - * @return - * The cache or FALSE on failure. - */ - function get($cid); - - /** - * Return data from the persistent cache when given an array of cache IDs. - * - * @param $cids - * 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. - * @return - * An array of the items successfully returned from cache indexed by cid. - */ - function getMultiple(&$cids); - - /** - * Store data in the persistent cache. - * - * @param $cid - * The cache ID of the data to store. - * @param $data - * The data to store in the cache. Complex data types will be automatically - * serialized before insertion. - * Strings will be stored as plain text and not serialized. - * @param $expire - * One of the following values: - * - CACHE_PERMANENT: Indicates that the item should never be removed unless - * explicitly told to using cache_clear_all() with a cache ID. - * - CACHE_TEMPORARY: Indicates that the item should be removed at the next - * general cache wipe. - * - A Unix timestamp: Indicates that the item should be kept at least until - * the given time, after which it behaves like CACHE_TEMPORARY. - */ - function set($cid, $data, $expire = CACHE_PERMANENT); - - /** - * Delete an item from the cache. - * - * @param $cid - * The cache ID to delete. - */ - function delete($cid); - - /** - * Delete multiple items from the cache. - * - * @param $cids - * An array of $cids to delete. - */ - function deleteMultiple(Array $cids); - - /** - * Delete items from the cache using a wildcard prefix. - * - * @param $prefix - * A wildcard prefix. - */ - function deletePrefix($prefix); - - /** - * Flush all cache items in a bin. - */ - function flush(); - - /** - * Expire temporary items from cache. - */ - function expire(); - - /** - * Perform garbage collection on a cache bin. - */ - function garbageCollection(); - - /** - * Expire data from the cache. If called without arguments, expirable - * entries will be cleared from the cache_page and cache_block bins. - * - * @param $cid - * If set, the cache ID to delete. Otherwise, all cache entries that can - * expire are deleted. - * @param $wildcard - * If set to TRUE, the $cid is treated as a substring - * to match rather than a complete ID. The match is a right hand - * match. If '*' is given as $cid, the bin $bin will be emptied. - * - * @todo: this method is deprecated, as it's functionality is covered by - * more targeted methods in the interface. - */ - function clear($cid = NULL, $wildcard = FALSE); - - /** - * Check if a cache bin is empty. - * - * A cache bin is considered empty if it does not contain any valid data for - * any cache ID. - * - * @return - * TRUE if the cache bin specified is empty. - */ - function isEmpty(); -} - -/** - * A stub cache implementation. - * - * The stub implementation is needed when database access is not yet available. - * Because Drupal's caching system never requires that cached data be present, - * these stub functions can short-circuit the process and sidestep the need for - * any persistent storage. Using this cache implementation during normal - * operations would have a negative impact on performance. - * - * This also can be used for testing purposes. - */ -class DrupalNullCache implements DrupalCacheInterface { - - function __construct($bin) {} - - function get($cid) { - return FALSE; - } - - function getMultiple(&$cids) { - return array(); - } - - function set($cid, $data, $expire = CACHE_PERMANENT) {} - - function delete($cid) {} - - function deleteMultiple(array $cids) {} - - function deletePrefix($prefix) {} - - function flush() {} - - function expire() {} - - function garbageCollection() {} - - function clear($cid = NULL, $wildcard = FALSE) {} - - function isEmpty() { - return TRUE; - } -} - -/** - * Default cache implementation. - * - * 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. - */ -class DrupalDatabaseCache implements DrupalCacheInterface { - protected $bin; - - function __construct($bin) { - // All cache tables should be prefixed with 'cache_', apart from the - // default 'cache' bin, which would look silly. - if ($bin != 'cache') { - $bin = 'cache_' . $bin; - } - $this->bin = $bin; - } - - function get($cid) { - $cids = array($cid); - $cache = $this->getMultiple($cids); - return reset($cache); - } - - function getMultiple(&$cids) { - try { - // Garbage collection necessary when enforcing a minimum cache lifetime. - $this->garbageCollection($this->bin); - - // When serving cached pages, the overhead of using db_select() was found - // to add around 30% overhead to the request. Since $this->bin is a - // 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(); - foreach ($result as $item) { - $item = $this->prepareItem($item); - if ($item) { - $cache[$item->cid] = $item; - } - } - $cids = array_diff($cids, array_keys($cache)); - return $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 array(); - } - } - - /** - * Prepare a cached item. - * - * Checks that items are either permanent or did not expire, and unserializes - * data as appropriate. - * - * @param $cache - * An item loaded from cache_get() or cache_get_multiple(). - * @return - * The item with data unserialized as appropriate or FALSE if there is no - * valid item to load. - */ - protected function prepareItem($cache) { - global $user; - - if (!isset($cache->data)) { - return FALSE; - } - // If enforcing a minimum cache lifetime, validate that the data is - // currently valid for this user before we return it by making sure the cache - // entry was created before the timestamp in the current session's cache - // timer. The cache variable is loaded into the $user object by _drupal_session_read() - // in session.inc. If the data is permanent or we're not enforcing a minimum - // cache lifetime always return the cached data. - if ($cache->expire != CACHE_PERMANENT && variable_get('cache_lifetime', 0) && $user->cache > $cache->created) { - // This cache data is too old and thus not valid for us, ignore it. - return FALSE; - } - - if ($cache->serialized) { - $cache->data = unserialize($cache->data); - } - - return $cache; - } - - function set($cid, $data, $expire = CACHE_PERMANENT) { - $fields = array( - 'serialized' => 0, - 'created' => REQUEST_TIME, - 'expire' => $expire, - ); - if (!is_string($data)) { - $fields['data'] = serialize($data); - $fields['serialized'] = 1; - } - else { - $fields['data'] = $data; - $fields['serialized'] = 0; - } - - try { - db_merge($this->bin) - ->key(array('cid' => $cid)) - ->fields($fields) - ->execute(); - } - catch (Exception $e) { - // The database may not be available, so we'll ignore cache_set requests. - } - } - - function delete($cid) { - db_delete($this->bin) - ->condition('cid', $cid) - ->execute(); - } - - function deleteMultiple(Array $cids) { - // Delete in chunks when a large array is passed. - do { - db_delete($this->bin) - ->condition('cid', array_splice($cids, 0, 1000), 'IN') - ->execute(); - } - while (count($cids)); - } - - function deletePrefix($prefix) { - db_delete($this->bin) - ->condition('cid', db_like($prefix) . '%', 'LIKE') - ->execute(); - } - - function flush() { - db_truncate($this->bin)->execute(); - } - - function expire() { - if (variable_get('cache_lifetime', 0)) { - // We store the time in the current user's $user->cache variable which - // will be saved into the sessions bin by _drupal_session_write(). We then - // simulate that the cache was flushed for this user by not returning - // cached data that was cached before the timestamp. - $GLOBALS['user']->cache = REQUEST_TIME; - - $cache_flush = variable_get('cache_flush_' . $this->bin, 0); - if ($cache_flush == 0) { - // This is the first request to clear the cache, start a timer. - variable_set('cache_flush_' . $this->bin, REQUEST_TIME); - } - elseif (REQUEST_TIME > ($cache_flush + variable_get('cache_lifetime', 0))) { - // Clear the cache for everyone, cache_lifetime seconds have - // passed since the first request to clear the cache. - db_delete($this->bin) - ->condition('expire', CACHE_PERMANENT, '<>') - ->condition('expire', REQUEST_TIME, '<') - ->execute(); - variable_set('cache_flush_' . $this->bin, 0); - } - } - else { - // No minimum cache lifetime, flush all temporary cache entries now. - db_delete($this->bin) - ->condition('expire', CACHE_PERMANENT, '<>') - ->condition('expire', REQUEST_TIME, '<') - ->execute(); - } - } - - function garbageCollection() { - global $user; - - // When cache lifetime is in force, avoid running garbage collection too - // often since this will remove temporary cache items indiscriminately. - $cache_flush = variable_get('cache_flush_' . $this->bin, 0); - if ($cache_flush && ($cache_flush + variable_get('cache_lifetime', 0) <= REQUEST_TIME)) { - // Reset the variable immediately to prevent a meltdown in heavy load situations. - variable_set('cache_flush_' . $this->bin, 0); - // Time to flush old cache data - db_delete($this->bin) - ->condition('expire', CACHE_PERMANENT, '<>') - ->condition('expire', $cache_flush, '<=') - ->execute(); - } - } - - function clear($cid = NULL, $wildcard = FALSE) { - global $user; - - if (empty($cid)) { - $this->expire(); - } - else { - if ($wildcard) { - if ($cid == '*') { - $this->flush(); - } - else { - $this->deletePrefix($cid); - } - } - elseif (is_array($cid)) { - $this->deleteMultiple($cid); - } - else { - $this->delete($cid); - } - } - } - - function isEmpty() { - $this->garbageCollection(); - $query = db_select($this->bin); - $query->addExpression('1'); - $result = $query->range(0, 1) - ->execute() - ->fetchField(); - return empty($result); - } -} diff --git a/core/includes/common.inc b/core/includes/common.inc deleted file mode 100644 index b2a080f3585..00000000000 --- a/core/includes/common.inc +++ /dev/null @@ -1,7459 +0,0 @@ -<?php - -/** - * @file - * Common functions that many Drupal modules will need to reference. - * - * The functions that are critical and need to be available even when serving - * a cached page are instead located in bootstrap.inc. - */ - -/** - * @defgroup php_wrappers PHP wrapper functions - * @{ - * Functions that are wrappers or custom implementations of PHP functions. - * - * Certain PHP functions should not be used in Drupal. Instead, Drupal's - * replacement functions should be used. - * - * For example, for improved or more secure UTF8-handling, or RFC-compliant - * handling of URLs in Drupal. - * - * For ease of use and memorizing, all these wrapper functions use the same name - * as the original PHP function, but prefixed with "drupal_". Beware, however, - * that not all wrapper functions support the same arguments as the original - * functions. - * - * You should always use these wrapper functions in your code. - * - * Wrong: - * @code - * $my_substring = substr($original_string, 0, 5); - * @endcode - * - * Correct: - * @code - * $my_substring = drupal_substr($original_string, 0, 5); - * @endcode - * - * @} - */ - -/** - * Return status for saving which involved creating a new item. - */ -define('SAVED_NEW', 1); - -/** - * Return status for saving which involved an update to an existing item. - */ -define('SAVED_UPDATED', 2); - -/** - * Return status for saving which deleted an existing item. - */ -define('SAVED_DELETED', 3); - -/** - * The default group for system CSS files added to the page. - */ -define('CSS_SYSTEM', -100); - -/** - * The default group for module CSS files added to the page. - */ -define('CSS_DEFAULT', 0); - -/** - * The default group for theme CSS files added to the page. - */ -define('CSS_THEME', 100); - -/** - * The default group for JavaScript libraries, settings or jQuery plugins added - * to the page. - */ -define('JS_LIBRARY', -100); - -/** - * The default group for module JavaScript code added to the page. - */ -define('JS_DEFAULT', 0); - -/** - * The default group for theme JavaScript code added to the page. - */ -define('JS_THEME', 100); - -/** - * Error code indicating that the request made by drupal_http_request() exceeded - * the specified timeout. - */ -define('HTTP_REQUEST_TIMEOUT', -1); - -/** - * Constants defining cache granularity for blocks and renderable arrays. - * - * Modules specify the caching patterns for their blocks using binary - * combinations of these constants in their hook_block_info(): - * $block[delta]['cache'] = DRUPAL_CACHE_PER_ROLE | DRUPAL_CACHE_PER_PAGE; - * DRUPAL_CACHE_PER_ROLE is used as a default when no caching pattern is - * specified. Use DRUPAL_CACHE_CUSTOM to disable standard block cache and - * implement - * - * The block cache is cleared in cache_clear_all(), and uses the same clearing - * policy than page cache (node, comment, user, taxonomy added or updated...). - * Blocks requiring more fine-grained clearing might consider disabling the - * built-in block cache (DRUPAL_NO_CACHE) and roll their own. - * - * Note that user 1 is excluded from block caching. - */ - -/** - * The block should not get cached. This setting should be used: - * - for simple blocks (notably those that do not perform any db query), - * where querying the db cache would be more expensive than directly generating - * the content. - * - for blocks that change too frequently. - */ -define('DRUPAL_NO_CACHE', -1); - -/** - * The block is handling its own caching in its hook_block_view(). From the - * 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 - * which invalidates standard block cache. - */ -define('DRUPAL_CACHE_CUSTOM', -2); - -/** - * The block or element can change depending on the roles the user viewing the - * page belongs to. This is the default setting for blocks, used when the block - * does not specify anything. - */ -define('DRUPAL_CACHE_PER_ROLE', 0x0001); - -/** - * The block or element can change depending on the user viewing the page. - * 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. - */ -define('DRUPAL_CACHE_PER_USER', 0x0002); - -/** - * The block or element can change depending on the page being viewed. - */ -define('DRUPAL_CACHE_PER_PAGE', 0x0004); - -/** - * The block or element is the same for every user on every page where it is visible. - */ -define('DRUPAL_CACHE_GLOBAL', 0x0008); - -/** - * Add content to a specified region. - * - * @param $region - * Page region the content is added to. - * @param $data - * Content to be added. - */ -function drupal_add_region_content($region = NULL, $data = NULL) { - static $content = array(); - - if (isset($region) && isset($data)) { - $content[$region][] = $data; - } - return $content; -} - -/** - * Get assigned content for a given region. - * - * @param $region - * A specified region to fetch content for. If NULL, all regions will be - * returned. - * @param $delimiter - * Content to be inserted between imploded array elements. - */ -function drupal_get_region_content($region = NULL, $delimiter = ' ') { - $content = drupal_add_region_content(); - if (isset($region)) { - if (isset($content[$region]) && is_array($content[$region])) { - return implode($delimiter, $content[$region]); - } - } - else { - foreach (array_keys($content) as $region) { - if (is_array($content[$region])) { - $content[$region] = implode($delimiter, $content[$region]); - } - } - return $content; - } -} - -/** - * Get the name of the currently active install profile. - * - * 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 - * 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() - * to determine what one is active. - * - * @return $profile - * The name of the install profile. - */ -function drupal_get_profile() { - global $install_state; - - if (isset($install_state['parameters']['profile'])) { - $profile = $install_state['parameters']['profile']; - } - else { - $profile = variable_get('install_profile', 'standard'); - } - - return $profile; -} - - -/** - * Set the breadcrumb trail for the current page. - * - * @param $breadcrumb - * Array of links, starting with "home" and proceeding up to but not including - * the current page. - */ -function drupal_set_breadcrumb($breadcrumb = NULL) { - $stored_breadcrumb = &drupal_static(__FUNCTION__); - - if (isset($breadcrumb)) { - $stored_breadcrumb = $breadcrumb; - } - return $stored_breadcrumb; -} - -/** - * Get the breadcrumb trail for the current page. - */ -function drupal_get_breadcrumb() { - $breadcrumb = drupal_set_breadcrumb(); - - if (!isset($breadcrumb)) { - $breadcrumb = menu_get_active_breadcrumb(); - } - - return $breadcrumb; -} - -/** - * Add output to the head tag of the HTML page. - * - * This function can be called as long as the headers aren't sent. Pass no - * arguments (or NULL for both) to retrieve the currently stored elements. - * - * @param $data - * A renderable array. If the '#type' key is not set then 'html_tag' will be - * added as the default '#type'. - * @param $key - * A unique string key to allow implementations of hook_html_head_alter() to - * identify the element in $data. Required if $data is not NULL. - * - * @return - * An array of all stored HEAD elements. - * - * @see theme_html_tag() - */ -function drupal_add_html_head($data = NULL, $key = NULL) { - $stored_head = &drupal_static(__FUNCTION__); - - if (!isset($stored_head)) { - // Make sure the defaults, including Content-Type, come first. - $stored_head = _drupal_default_html_head(); - } - - if (isset($data) && isset($key)) { - if (!isset($data['#type'])) { - $data['#type'] = 'html_tag'; - } - $stored_head[$key] = $data; - } - return $stored_head; -} - -/** - * Returns elements that are always displayed in the HEAD tag of the HTML page. - */ -function _drupal_default_html_head() { - // Add default elements. Make sure the Content-Type comes first because the - // IE browser may be vulnerable to XSS via encoding attacks from any content - // that comes before this META tag, such as a TITLE tag. - $elements['system_meta_content_type'] = array( - '#type' => 'html_tag', - '#tag' => 'meta', - '#attributes' => array( - 'http-equiv' => 'Content-Type', - 'content' => 'text/html; charset=utf-8', - ), - // Security: This always has to be output first. - '#weight' => -1000, - ); - // Show Drupal and the major version number in the META GENERATOR tag. - // Get the major version. - list($version, ) = explode('.', VERSION); - $elements['system_meta_generator'] = array( - '#type' => 'html_tag', - '#tag' => 'meta', - '#attributes' => array( - 'name' => 'Generator', - 'content' => 'Drupal ' . $version . ' (http://drupal.org)', - ), - ); - // Also send the generator in the HTTP header. - $elements['system_meta_generator']['#attached']['drupal_add_http_header'][] = array('X-Generator', $elements['system_meta_generator']['#attributes']['content']); - return $elements; -} - -/** - * Retrieve output to be displayed in the HEAD tag of the HTML page. - */ -function drupal_get_html_head() { - $elements = drupal_add_html_head(); - drupal_alter('html_head', $elements); - return drupal_render($elements); -} - -/** - * Add a feed URL for the current page. - * - * This function can be called as long the HTML header hasn't been sent. - * - * @param $url - * An internal system path or a fully qualified external URL of the feed. - * @param $title - * The title of the feed. - */ -function drupal_add_feed($url = NULL, $title = '') { - $stored_feed_links = &drupal_static(__FUNCTION__, array()); - - if (isset($url)) { - $stored_feed_links[$url] = theme('feed_icon', array('url' => $url, 'title' => $title)); - - drupal_add_html_head_link(array( - 'rel' => 'alternate', - 'type' => 'application/rss+xml', - 'title' => $title, - // 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; -} - -/** - * Get the feed URLs for the current page. - * - * @param $delimiter - * A delimiter to split feeds by. - */ -function drupal_get_feeds($delimiter = "\n") { - $feeds = drupal_add_feed(); - return implode($feeds, $delimiter); -} - -/** - * @defgroup http_handling HTTP handling - * @{ - * Functions to properly handle HTTP responses. - */ - -/** - * Process a URL query parameter array to remove unwanted elements. - * - * @param $query - * (optional) An array to be processed. Defaults to $_GET. - * @param $exclude - * (optional) A list of $query array keys to remove. Use "parent[child]" to - * exclude nested items. Defaults to array('q'). - * @param $parent - * Internal use only. Used to build the $query array key for nested items. - * - * @return - * An array containing query parameters, which can be used for url(). - */ -function drupal_get_query_parameters(array $query = NULL, array $exclude = array('q'), $parent = '') { - // Set defaults, if none given. - if (!isset($query)) { - $query = $_GET; - } - // If $exclude is empty, there is nothing to filter. - if (empty($exclude)) { - return $query; - } - elseif (!$parent) { - $exclude = array_flip($exclude); - } - - $params = array(); - foreach ($query as $key => $value) { - $string_key = ($parent ? $parent . '[' . $key . ']' : $key); - if (isset($exclude[$string_key])) { - continue; - } - - if (is_array($value)) { - $params[$key] = drupal_get_query_parameters($value, $exclude, $string_key); - } - else { - $params[$key] = $value; - } - } - - return $params; -} - -/** - * Split an URL-encoded query string into an array. - * - * @param $query - * The query string to split. - * - * @return - * An array of url decoded couples $param_name => $value. - */ -function drupal_get_query_array($query) { - $result = array(); - if (!empty($query)) { - foreach (explode('&', $query) as $param) { - $param = explode('=', $param); - $result[$param[0]] = isset($param[1]) ? rawurldecode($param[1]) : ''; - } - } - return $result; -} - -/** - * Parse an array into a valid, rawurlencoded query string. - * - * This differs from http_build_query() as we need to rawurlencode() (instead of - * urlencode()) all query parameters. - * - * @param $query - * The query parameter array to be processed, e.g. $_GET. - * @param $parent - * Internal use only. Used to build the $query array key for nested items. - * - * @return - * A rawurlencoded string which can be used as or appended to the URL query - * string. - * - * @see drupal_get_query_parameters() - * @ingroup php_wrappers - */ -function drupal_http_build_query(array $query, $parent = '') { - $params = array(); - - foreach ($query as $key => $value) { - $key = ($parent ? $parent . '[' . rawurlencode($key) . ']' : rawurlencode($key)); - - // Recurse into children. - if (is_array($value)) { - $params[] = drupal_http_build_query($value, $key); - } - // If a query parameter value is NULL, only append its key. - elseif (!isset($value)) { - $params[] = $key; - } - else { - // For better readability of paths in query strings, we decode slashes. - $params[] = $key . '=' . str_replace('%2F', '/', rawurlencode($value)); - } - } - - return implode('&', $params); -} - -/** - * Prepare a 'destination' URL query parameter for use in combination with drupal_goto(). - * - * 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 - * previous request, that destination is returned. As such, a destination can - * persist across multiple pages. - * - * @see drupal_goto() - */ -function drupal_get_destination() { - $destination = &drupal_static(__FUNCTION__); - - if (isset($destination)) { - return $destination; - } - - if (isset($_GET['destination'])) { - $destination = array('destination' => $_GET['destination']); - } - else { - $path = $_GET['q']; - $query = drupal_http_build_query(drupal_get_query_parameters()); - if ($query != '') { - $path .= '?' . $query; - } - $destination = array('destination' => $path); - } - return $destination; -} - -/** - * Wrapper around parse_url() to parse 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 - * system, resp. url(). It should not be used for URLs that come from external - * sources, or URLs that link to external resources. - * - * The returned array contains a 'path' that may be passed separately to url(). - * For example: - * @code - * $options = drupal_parse_url($_GET['destination']); - * $my_url = url($options['path'], $options); - * $my_link = l('Example link', $options['path'], $options); - * @endcode - * - * This is required, because url() does not support relative URLs containing a - * query string or fragment in its $path argument. Instead, any query string - * needs to be parsed into an associative query parameter array in - * $options['query'] and the fragment into $options['fragment']. - * - * @param $url - * The URL string to parse, f.e. $_GET['destination']. - * - * @return - * An associative array containing the keys: - * - 'path': The path of the URL. If the given $url is external, this includes - * the scheme and host. - * - 'query': An array of query parameters of $url, if existent. - * - 'fragment': The fragment of $url, if existent. - * - * @see url() - * @see drupal_goto() - * @ingroup php_wrappers - */ -function drupal_parse_url($url) { - $options = array( - 'path' => NULL, - 'query' => array(), - 'fragment' => '', - ); - - // External URLs: not using parse_url() here, so we do not have to rebuild - // the scheme, host, and path without having any use for it. - if (strpos($url, '://') !== FALSE) { - // Split off everything before the query string into 'path'. - $parts = explode('?', $url); - $options['path'] = $parts[0]; - // If there is a query string, transform it into keyed query parameters. - if (isset($parts[1])) { - $query_parts = explode('#', $parts[1]); - parse_str($query_parts[0], $options['query']); - // Take over the fragment, if there is any. - if (isset($query_parts[1])) { - $options['fragment'] = $query_parts[1]; - } - } - } - // Internal URLs. - else { - // parse_url() does not support relative URLs, so make it absolute. E.g. the - // relative URL "foo/bar:1" isn't properly parsed. - $parts = parse_url('http://example.com/' . $url); - // Strip the leading slash that was just added. - $options['path'] = substr($parts['path'], 1); - if (isset($parts['query'])) { - parse_str($parts['query'], $options['query']); - } - if (isset($parts['fragment'])) { - $options['fragment'] = $parts['fragment']; - } - } - // The 'q' parameter contains the path of the current page if clean URLs are - // disabled. It overrides the 'path' of the URL when present, even if clean - // URLs are enabled, due to how Apache rewriting rules work. - if (isset($options['query']['q'])) { - $options['path'] = $options['query']['q']; - unset($options['query']['q']); - } - - return $options; -} - -/** - * Encodes a Drupal path for use in a URL. - * - * For aesthetic reasons slashes are not escaped. - * - * Note that url() takes care of calling this function, so a path passed to that - * function should not be encoded in advance. - * - * @param $path - * The Drupal path to encode. - */ -function drupal_encode_path($path) { - return str_replace('%2F', '/', rawurlencode($path)); -} - -/** - * Send the user to a different Drupal page. - * - * This issues an on-site HTTP redirect. The function makes sure the redirected - * URL is formatted correctly. - * - * If a destination was specified in the current request's URI (i.e., - * $_GET['destination']) then it will override the $path and $options values - * passed to this function. This provides the flexibility to build a link to - * user/login and override the default redirection so that the user is - * redirected to a specific path after logging in: - * @code - * $query = array('destination' => "node/$node->nid"); - * $link = l(t('Log in'), 'user/login', array('query' => $query)); - * @endcode - * - * Drupal will ensure that messages set by drupal_set_message() and other - * session data are written to the database before the user is redirected. - * - * This function ends the request; use it instead of a return in your menu - * callback. - * - * @param $path - * A Drupal path or a full URL. - * @param $options - * An associative array of additional URL options to pass to url(). - * @param $http_response_code - * Valid values for an actual "goto" as per RFC 2616 section 10.3 are: - * - 301 Moved Permanently (the recommended value for most redirects) - * - 302 Found (default in Drupal and PHP, sometimes used for spamming search - * engines) - * - 303 See Other - * - 304 Not Modified - * - 305 Use Proxy - * - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance") - * Note: Other values are defined by RFC 2616, but are rarely used and poorly - * supported. - * - * @see drupal_get_destination() - * @see url() - */ -function drupal_goto($path = '', array $options = array(), $http_response_code = 302) { - // A destination in $_GET always overrides the function arguments. - // We do not allow absolute URLs to be passed via $_GET, as this can be an attack vector. - if (isset($_GET['destination']) && !url_is_external($_GET['destination'])) { - $destination = drupal_parse_url($_GET['destination']); - $path = $destination['path']; - $options['query'] = $destination['query']; - $options['fragment'] = $destination['fragment']; - } - - drupal_alter('drupal_goto', $path, $options, $http_response_code); - - // The 'Location' HTTP header must be absolute. - $options['absolute'] = TRUE; - - $url = url($path, $options); - - header('Location: ' . $url, TRUE, $http_response_code); - - // The "Location" header sends a redirect status code to the HTTP daemon. In - // some cases this can be wrong, so we make sure none of the code below the - // drupal_goto() call gets executed upon redirection. - drupal_exit($url); -} - -/** - * Deliver a "site is under maintenance" message to the browser. - * - * Page callback functions wanting to report a "site offline" message should - * return MENU_SITE_OFFLINE instead of calling drupal_site_offline(). However, - * functions that are invoked in contexts where that return value might not - * bubble up to menu_execute_active_handler() should call drupal_site_offline(). - */ -function drupal_site_offline() { - drupal_deliver_page(MENU_SITE_OFFLINE); -} - -/** - * Deliver a "page not found" error to the browser. - * - * Page callback functions wanting to report a "page not found" message should - * return MENU_NOT_FOUND instead of calling drupal_not_found(). However, - * functions that are invoked in contexts where that return value might not - * bubble up to menu_execute_active_handler() should call drupal_not_found(). - */ -function drupal_not_found() { - drupal_deliver_page(MENU_NOT_FOUND); -} - -/** - * Deliver a "access denied" error to the browser. - * - * Page callback functions wanting to report an "access denied" message should - * return MENU_ACCESS_DENIED instead of calling drupal_access_denied(). However, - * functions that are invoked in contexts where that return value might not - * bubble up to menu_execute_active_handler() should call drupal_access_denied(). - */ -function drupal_access_denied() { - drupal_deliver_page(MENU_ACCESS_DENIED); -} - -/** - * Perform an HTTP request. - * - * This is a flexible and powerful HTTP client implementation. Correctly - * handles GET, POST, PUT or any other HTTP requests. Handles redirects. - * - * @param $url - * A string containing a fully qualified URI. - * @param array $options - * (optional) An array that can have one or more of the following elements: - * - headers: An array containing request headers to send as name/value pairs. - * - method: A string containing the request method. Defaults to 'GET'. - * - data: A string containing the request body, formatted as - * 'param=value¶m=value&...'. Defaults to NULL. - * - max_redirects: An integer representing how many times a redirect - * may be followed. 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. - * - context: A context resource created with stream_context_create(). - * - * @return object - * An object that can have one or more of the following components: - * - request: A string containing the request body that was sent. - * - code: An integer containing the response status code, or the error code - * if an error occurred. - * - protocol: The response protocol (e.g. HTTP/1.1 or HTTP/1.0). - * - status_message: The status message from the response, if a response was - * received. - * - redirect_code: If redirected, an integer containing the initial response - * status code. - * - redirect_url: If redirected, a string containing the URL of the redirect - * target. - * - error: If an error occurred, the error message. Otherwise not set. - * - 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()) { - $result = new stdClass(); - - // Parse the URL and make sure we can handle the schema. - $uri = @parse_url($url); - - if ($uri == FALSE) { - $result->error = 'unable to parse URL'; - $result->code = -1001; - return $result; - } - - if (!isset($uri['scheme'])) { - $result->error = 'missing schema'; - $result->code = -1002; - return $result; - } - - timer_start(__FUNCTION__); - - // Merge the default options. - $options += array( - 'headers' => array(), - 'method' => 'GET', - 'data' => NULL, - 'max_redirects' => 3, - 'timeout' => 30.0, - 'context' => NULL, - ); - // stream_socket_client() requires timeout to be a float. - $options['timeout'] = (float) $options['timeout']; - - switch ($uri['scheme']) { - case 'http': - case 'feed': - $port = isset($uri['port']) ? $uri['port'] : 80; - $socket = 'tcp://' . $uri['host'] . ':' . $port; - // RFC 2616: "non-standard ports MUST, default ports MAY be included". - // We don't add the standard port to prevent from breaking rewrite rules - // checking the host that do not take into account the port number. - $options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : ''); - break; - case 'https': - // Note: Only works when PHP is compiled with OpenSSL support. - $port = isset($uri['port']) ? $uri['port'] : 443; - $socket = 'ssl://' . $uri['host'] . ':' . $port; - $options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : ''); - break; - default: - $result->error = 'invalid schema ' . $uri['scheme']; - $result->code = -1003; - return $result; - } - - if (empty($options['context'])) { - $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout']); - } - else { - // Create a stream with context. Allows verification of a SSL certificate. - $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], STREAM_CLIENT_CONNECT, $options['context']); - } - - // Make sure the socket opened properly. - if (!$fp) { - // When a network error occurs, we use a negative number so it does not - // clash with the HTTP status codes. - $result->code = -$errno; - $result->error = trim($errstr) ? trim($errstr) : t('Error opening socket @socket', array('@socket' => $socket)); - - // 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 - // requirements checking is performed. - // See system_requirements() - variable_set('drupal_http_request_fails', TRUE); - - return $result; - } - - // Construct the path to act on. - $path = isset($uri['path']) ? $uri['path'] : '/'; - if (isset($uri['query'])) { - $path .= '?' . $uri['query']; - } - - // Merge the default headers. - $options['headers'] += array( - 'User-Agent' => 'Drupal (+http://drupal.org/)', - ); - - // Only add Content-Length if we actually have any content or if it is a POST - // or PUT request. Some non-standard servers get confused by Content-Length in - // at least HEAD/GET requests, and Squid always requires Content-Length in - // POST/PUT requests. - $content_length = strlen($options['data']); - if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') { - $options['headers']['Content-Length'] = $content_length; - } - - // If the server URL has a user then attempt to use basic authentication. - if (isset($uri['user'])) { - $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 - // database then set the user-agent header to the database prefix so that any - // calls to other Drupal pages will run the SimpleTest prefixed database. The - // user-agent is used to ensure that multiple testing sessions running at the - // same time won't interfere with each other as they would if the database - // prefix were stored statically in a file or database variable. - $test_info = &$GLOBALS['drupal_test_info']; - if (!empty($test_info['test_run_id'])) { - $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']); - } - - $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n"; - foreach ($options['headers'] as $name => $value) { - $request .= $name . ': ' . trim($value) . "\r\n"; - } - $request .= "\r\n" . $options['data']; - $result->request = $request; - // Calculate how much time is left of the original timeout value. - $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000; - if ($timeout > 0) { - stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1))); - fwrite($fp, $request); - } - - // Fetch response. Due to PHP bugs like http://bugs.php.net/bug.php?id=43782 - // and http://bugs.php.net/bug.php?id=46049 we can't rely on feof(), but - // instead must invoke stream_get_meta_data() each iteration. - $info = stream_get_meta_data($fp); - $alive = !$info['eof'] && !$info['timed_out']; - $response = ''; - - while ($alive) { - // Calculate how much time is left of the original timeout value. - $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000; - if ($timeout <= 0) { - $info['timed_out'] = TRUE; - break; - } - stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1))); - $chunk = fread($fp, 1024); - $response .= $chunk; - $info = stream_get_meta_data($fp); - $alive = !$info['eof'] && !$info['timed_out'] && $chunk; - } - fclose($fp); - - if ($info['timed_out']) { - $result->code = HTTP_REQUEST_TIMEOUT; - $result->error = 'request timed out'; - return $result; - } - // Parse response headers from the response body. - // 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); - - // Parse the response status line. - list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3); - $result->protocol = $protocol; - $result->status_message = $status_message; - - $result->headers = array(); - - // Parse the response headers. - while ($line = trim(array_shift($response))) { - list($name, $value) = explode(':', $line, 2); - $name = strtolower($name); - if (isset($result->headers[$name]) && $name == 'set-cookie') { - // RFC 2109: the Set-Cookie response header comprises the token Set- - // Cookie:, followed by a comma-separated list of one or more cookies. - $result->headers[$name] .= ',' . trim($value); - } - else { - $result->headers[$name] = trim($value); - } - } - - $responses = array( - 100 => 'Continue', - 101 => 'Switching Protocols', - 200 => 'OK', - 201 => 'Created', - 202 => 'Accepted', - 203 => 'Non-Authoritative Information', - 204 => 'No Content', - 205 => 'Reset Content', - 206 => 'Partial Content', - 300 => 'Multiple Choices', - 301 => 'Moved Permanently', - 302 => 'Found', - 303 => 'See Other', - 304 => 'Not Modified', - 305 => 'Use Proxy', - 307 => 'Temporary Redirect', - 400 => 'Bad Request', - 401 => 'Unauthorized', - 402 => 'Payment Required', - 403 => 'Forbidden', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 406 => 'Not Acceptable', - 407 => 'Proxy Authentication Required', - 408 => 'Request Time-out', - 409 => 'Conflict', - 410 => 'Gone', - 411 => 'Length Required', - 412 => 'Precondition Failed', - 413 => 'Request Entity Too Large', - 414 => 'Request-URI Too Large', - 415 => 'Unsupported Media Type', - 416 => 'Requested range not satisfiable', - 417 => 'Expectation Failed', - 500 => 'Internal Server Error', - 501 => 'Not Implemented', - 502 => 'Bad Gateway', - 503 => 'Service Unavailable', - 504 => 'Gateway Time-out', - 505 => 'HTTP Version not supported', - ); - // RFC 2616 states that all unknown HTTP codes must be treated the same as the - // base code in their class. - if (!isset($responses[$code])) { - $code = floor($code / 100) * 100; - } - $result->code = $code; - - switch ($code) { - case 200: // OK - case 304: // Not modified - break; - case 301: // Moved permanently - case 302: // Moved temporarily - case 307: // Moved temporarily - $location = $result->headers['location']; - $options['timeout'] -= timer_read(__FUNCTION__) / 1000; - if ($options['timeout'] <= 0) { - $result->code = HTTP_REQUEST_TIMEOUT; - $result->error = 'request timed out'; - } - elseif ($options['max_redirects']) { - // Redirect to the new location. - $options['max_redirects']--; - $result = drupal_http_request($location, $options); - $result->redirect_code = $code; - } - if (!isset($result->redirect_url)) { - $result->redirect_url = $location; - } - break; - default: - $result->error = $status_message; - } - - return $result; -} -/** - * @} End of "HTTP handling". - */ - -function _fix_gpc_magic(&$item) { - if (is_array($item)) { - array_walk($item, '_fix_gpc_magic'); - } - else { - $item = stripslashes($item); - } -} - -/** - * Helper function to strip slashes from $_FILES skipping over the tmp_name keys - * since PHP generates single backslashes for file paths on Windows systems. - * - * tmp_name does not have backslashes added see - * http://php.net/manual/en/features.file-upload.php#42280 - */ -function _fix_gpc_magic_files(&$item, $key) { - if ($key != 'tmp_name') { - if (is_array($item)) { - array_walk($item, '_fix_gpc_magic_files'); - } - else { - $item = stripslashes($item); - } - } -} - -/** - * Fix double-escaping problems caused by "magic quotes" in some PHP installations. - */ -function fix_gpc_magic() { - static $fixed = FALSE; - if (!$fixed && ini_get('magic_quotes_gpc')) { - array_walk($_GET, '_fix_gpc_magic'); - array_walk($_POST, '_fix_gpc_magic'); - array_walk($_COOKIE, '_fix_gpc_magic'); - array_walk($_REQUEST, '_fix_gpc_magic'); - array_walk($_FILES, '_fix_gpc_magic_files'); - } - $fixed = TRUE; -} - -/** - * @defgroup validation Input validation - * @{ - * Functions to validate user input. - */ - -/** - * Verify the syntax of the given e-mail address. - * - * Empty e-mail addresses are allowed. See RFC 2822 for details. - * - * @param $mail - * A string containing an e-mail address. - * @return - * TRUE if the address is in a valid format. - */ -function valid_email_address($mail) { - return (bool)filter_var($mail, FILTER_VALIDATE_EMAIL); -} - -/** - * Verify the syntax of the given URL. - * - * This function should only be used on actual URLs. It should not be used for - * Drupal menu paths, which can contain arbitrary characters. - * Valid values per RFC 3986. - * @param $url - * The URL to verify. - * @param $absolute - * Whether the URL is absolute (beginning with a scheme such as "http:"). - * @return - * TRUE if the URL is in a valid format. - */ -function valid_url($url, $absolute = FALSE) { - if ($absolute) { - return (bool)preg_match(" - /^ # Start at the beginning of the text - (?:ftp|https?|feed):\/\/ # Look for ftp, http, https or feed schemes - (?: # Userinfo (optional) which is typically - (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)* # a username or a username and password - (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@ # combination - )? - (?: - (?:[a-z0-9\-\.]|%[0-9a-f]{2})+ # A domain name or a IPv4 address - |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\]) # or a well formed IPv6 address - ) - (?::[0-9]+)? # Server port number (optional) - (?:[\/|\?] - (?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional) - *)? - $/xi", $url); - } - else { - return (bool)preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url); - } -} - -/** - * @} End of "defgroup validation". - */ - -/** - * Register an event for the current visitor to the flood control mechanism. - * - * @param $name - * The name of an event. - * @param $window - * Optional number of seconds before this event expires. Defaults to 3600 (1 - * hour). Typically uses the same value as the flood_is_allowed() $window - * parameter. Expired events are purged on cron run to prevent the flood table - * from growing indefinitely. - * @param $identifier - * Optional identifier (defaults to the current user's IP address). - */ -function flood_register_event($name, $window = 3600, $identifier = NULL) { - if (!isset($identifier)) { - $identifier = ip_address(); - } - db_insert('flood') - ->fields(array( - 'event' => $name, - 'identifier' => $identifier, - 'timestamp' => REQUEST_TIME, - 'expiration' => REQUEST_TIME + $window, - )) - ->execute(); -} - -/** - * Make the flood control mechanism forget about an event for the current visitor. - * - * @param $name - * The name of an event. - * @param $identifier - * Optional identifier (defaults to the current user's IP address). - */ -function flood_clear_event($name, $identifier = NULL) { - if (!isset($identifier)) { - $identifier = ip_address(); - } - db_delete('flood') - ->condition('event', $name) - ->condition('identifier', $identifier) - ->execute(); -} - -/** - * Checks whether user is allowed to proceed with the specified 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 - * current user has not exceeded this threshold. - * - * @param $name - * The unique name of the event. - * @param $threshold - * The maximum number of times each user can do this event per time window. - * @param $window - * Number of seconds in the time window for this event (default is 3600 - * seconds, or 1 hour). - * @param $identifier - * Unique identifier of the current user. Defaults to their IP address. - * - * @return - * TRUE if the user is allowed to proceed. FALSE if they have exceeded the - * threshold and should not be allowed to proceed. - */ -function flood_is_allowed($name, $threshold, $window = 3600, $identifier = NULL) { - if (!isset($identifier)) { - $identifier = ip_address(); - } - $number = db_query("SELECT COUNT(*) FROM {flood} WHERE event = :event AND identifier = :identifier AND timestamp > :timestamp", array( - ':event' => $name, - ':identifier' => $identifier, - ':timestamp' => REQUEST_TIME - $window)) - ->fetchField(); - return ($number < $threshold); -} - -/** - * @defgroup sanitization Sanitization functions - * @{ - * Functions to sanitize values. - * - * See http://drupal.org/writing-secure-code for information - * on writing secure code. - */ - -/** - * Strips dangerous protocols (e.g. 'javascript:') from a URI. - * - * This function must be called for all URIs within user-entered input prior - * to being output to an HTML attribute value. It is often called as part of - * check_url() or filter_xss(), but those functions return an HTML-encoded - * string, so this function can be called independently when the output needs to - * be a plain-text string for passing to t(), l(), drupal_attributes(), or - * another function that will call check_plain() separately. - * - * @param $uri - * A plain-text URI that might contain dangerous protocols. - * - * @return - * A plain-text URI stripped of dangerous protocols. As with all plain-text - * strings, this return value must not be output to an HTML page without - * check_plain() being called on it. However, it can be passed to functions - * expecting plain-text strings. - * - * @see check_url() - */ -function drupal_strip_dangerous_protocols($uri) { - static $allowed_protocols; - - if (!isset($allowed_protocols)) { - $allowed_protocols = array_flip(variable_get('filter_allowed_protocols', array('ftp', 'http', 'https', 'irc', 'mailto', 'news', 'nntp', 'rtsp', 'sftp', 'ssh', 'tel', 'telnet', 'webcal'))); - } - - // Iteratively remove any invalid protocol found. - do { - $before = $uri; - $colonpos = strpos($uri, ':'); - if ($colonpos > 0) { - // We found a colon, possibly a protocol. Verify. - $protocol = substr($uri, 0, $colonpos); - // If a colon is preceded by a slash, question mark or hash, it cannot - // possibly be part of the URL scheme. This must be a relative URL, which - // inherits the (safe) protocol of the base document. - if (preg_match('![/?#]!', $protocol)) { - break; - } - // Check if this is a disallowed protocol. Per RFC2616, section 3.2.3 - // (URI Comparison) scheme comparison must be case-insensitive. - if (!isset($allowed_protocols[strtolower($protocol)])) { - $uri = substr($uri, $colonpos + 1); - } - } - } while ($before != $uri); - - return $uri; -} - -/** - * Strips dangerous protocols (e.g. 'javascript:') from a URI and encodes it for output to an HTML attribute value. - * - * @param $uri - * A plain-text URI that might contain dangerous protocols. - * - * @return - * A URI stripped of dangerous protocols and encoded for output to an HTML - * attribute value. Because it is already encoded, it should not be set as a - * value within a $attributes array passed to drupal_attributes(), because - * drupal_attributes() expects those values to be plain-text strings. To pass - * a filtered URI to drupal_attributes(), call - * drupal_strip_dangerous_protocols() instead. - * - * @see drupal_strip_dangerous_protocols() - */ -function check_url($uri) { - return check_plain(drupal_strip_dangerous_protocols($uri)); -} - -/** - * Very permissive XSS/HTML filter for admin-only use. - * - * Use only for fields where it is impractical to use the - * whole filter system, but where some (mainly inline) mark-up - * is desired (so check_plain() is not acceptable). - * - * Allows all tags that can be used inside an HTML body, save - * for scripts and styles. - */ -function filter_xss_admin($string) { - 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. - * - * Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses. - * For examples of various XSS attacks, see: http://ha.ckers.org/xss.html. - * - * This code does four things: - * - Removes characters and constructs that can trick browsers. - * - Makes sure all HTML entities are well-formed. - * - Makes sure all HTML tags and attributes are well-formed. - * - Makes sure no HTML tags contain URLs with a disallowed protocol (e.g. - * javascript:). - * - * @param $string - * The string with raw HTML in it. It will be stripped of everything that can - * cause an XSS attack. - * @param $allowed_tags - * An array of allowed tags. - * - * @return - * An XSS safe version of $string, or an empty string if $string is not - * valid UTF-8. - * - * @see drupal_validate_utf8() - * @ingroup sanitization - */ -function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) { - // Only operate on valid UTF-8 strings. This is necessary to prevent cross - // site scripting issues on Internet Explorer 6. - if (!drupal_validate_utf8($string)) { - return ''; - } - // Store the text format - _filter_xss_split($allowed_tags, TRUE); - // Remove NULL characters (ignored by some browsers) - $string = str_replace(chr(0), '', $string); - // Remove Netscape 4 JS entities - $string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string); - - // Defuse all HTML entities - $string = str_replace('&', '&', $string); - // Change back only well-formed entities in our whitelist - // Decimal numeric entities - $string = preg_replace('/&#([0-9]+;)/', '&#\1', $string); - // Hexadecimal numeric entities - $string = preg_replace('/&#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string); - // Named entities - $string = preg_replace('/&([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string); - - return preg_replace_callback('% - ( - <(?=[^a-zA-Z!/]) # a lone < - | # or - <!--.*?--> # a comment - | # or - <[^>]*(>|$) # a string that starts with a <, up until the > or the end of the string - | # or - > # just a > - )%x', '_filter_xss_split', $string); -} - -/** - * Processes an HTML tag. - * - * @param $m - * An array with various meaning depending on the value of $store. - * If $store is TRUE then the array contains the allowed tags. - * If $store is FALSE then the array has one element, the HTML tag to process. - * @param $store - * Whether to store $m. - * @return - * If the element isn't allowed, an empty string. Otherwise, the cleaned up - * version of the HTML element. - */ -function _filter_xss_split($m, $store = FALSE) { - static $allowed_html; - - if ($store) { - $allowed_html = array_flip($m); - return; - } - - $string = $m[1]; - - if (substr($string, 0, 1) != '<') { - // We matched a lone ">" character - return '>'; - } - elseif (strlen($string) == 1) { - // We matched a lone "<" character - return '<'; - } - - if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?|(<!--.*?-->)$%', $string, $matches)) { - // Seriously malformed - return ''; - } - - $slash = trim($matches[1]); - $elem = &$matches[2]; - $attrlist = &$matches[3]; - $comment = &$matches[4]; - - if ($comment) { - $elem = '!--'; - } - - if (!isset($allowed_html[strtolower($elem)])) { - // Disallowed HTML element - return ''; - } - - if ($comment) { - return $comment; - } - - if ($slash != '') { - return "</$elem>"; - } - - // Is there a closing XHTML slash at the end of the attributes? - $attrlist = preg_replace('%(\s?)/\s*$%', '\1', $attrlist, -1, $count); - $xhtml_slash = $count ? ' /' : ''; - - // Clean up attributes - $attr2 = implode(' ', _filter_xss_attributes($attrlist)); - $attr2 = preg_replace('/[<>]/', '', $attr2); - $attr2 = strlen($attr2) ? ' ' . $attr2 : ''; - - return "<$elem$attr2$xhtml_slash>"; -} - -/** - * Processes a string of HTML attributes. - * - * @return - * Cleaned up version of the HTML attributes. - */ -function _filter_xss_attributes($attr) { - $attrarr = array(); - $mode = 0; - $attrname = ''; - - while (strlen($attr) != 0) { - // Was the last operation successful? - $working = 0; - - switch ($mode) { - case 0: - // Attribute name, href for instance - if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) { - $attrname = strtolower($match[1]); - $skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on'); - $working = $mode = 1; - $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr); - } - break; - - case 1: - // Equals sign or valueless ("selected") - if (preg_match('/^\s*=\s*/', $attr)) { - $working = 1; $mode = 2; - $attr = preg_replace('/^\s*=\s*/', '', $attr); - break; - } - - if (preg_match('/^\s+/', $attr)) { - $working = 1; $mode = 0; - if (!$skip) { - $attrarr[] = $attrname; - } - $attr = preg_replace('/^\s+/', '', $attr); - } - break; - - case 2: - // Attribute value, a URL after href= for instance - if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) { - $thisval = filter_xss_bad_protocol($match[1]); - - if (!$skip) { - $attrarr[] = "$attrname=\"$thisval\""; - } - $working = 1; - $mode = 0; - $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr); - break; - } - - if (preg_match("/^'([^']*)'(\s+|$)/", $attr, $match)) { - $thisval = filter_xss_bad_protocol($match[1]); - - if (!$skip) { - $attrarr[] = "$attrname='$thisval'"; - } - $working = 1; $mode = 0; - $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr); - break; - } - - if (preg_match("%^([^\s\"']+)(\s+|$)%", $attr, $match)) { - $thisval = filter_xss_bad_protocol($match[1]); - - if (!$skip) { - $attrarr[] = "$attrname=\"$thisval\""; - } - $working = 1; $mode = 0; - $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr); - } - break; - } - - if ($working == 0) { - // not well formed, remove and try again - $attr = preg_replace('/ - ^ - ( - "[^"]*("|$) # - a string that starts with a double quote, up until the next double quote or the end of the string - | # or - \'[^\']*(\'|$)| # - a string that starts with a quote, up until the next quote or the end of the string - | # or - \S # - a non-whitespace character - )* # any number of the above three - \s* # any number of whitespaces - /x', '', $attr); - $mode = 0; - } - } - - // The attribute list ends with a valueless attribute like "selected". - if ($mode == 1 && !$skip) { - $attrarr[] = $attrname; - } - return $attrarr; -} - -/** - * Processes an HTML attribute value and ensures it does not contain an URL with a disallowed protocol (e.g. javascript:). - * - * @param $string - * The string with the attribute value. - * @param $decode - * (Deprecated) Whether to decode entities in the $string. Set to FALSE if the - * $string is in plain text, TRUE otherwise. Defaults to TRUE. This parameter - * is deprecated and will be removed in Drupal 8. To process a plain-text URI, - * call drupal_strip_dangerous_protocols() or check_url() instead. - * @return - * Cleaned up and HTML-escaped version of $string. - */ -function filter_xss_bad_protocol($string, $decode = TRUE) { - // Get the plain text representation of the attribute value (i.e. its meaning). - // @todo Remove the $decode parameter in Drupal 8, and always assume an HTML - // string that needs decoding. - if ($decode) { - if (!function_exists('decode_entities')) { - require_once DRUPAL_ROOT . '/core/includes/unicode.inc'; - } - - $string = decode_entities($string); - } - return check_plain(drupal_strip_dangerous_protocols($string)); -} - -/** - * @} End of "defgroup sanitization". - */ - -/** - * @defgroup format Formatting - * @{ - * Functions to format numbers, strings, dates, etc. - */ - -/** - * Formats an RSS channel. - * - * Arbitrary elements may be added using the $args associative array. - */ -function format_rss_channel($title, $link, $description, $items, $langcode = NULL, $args = array()) { - global $language_content; - $langcode = $langcode ? $langcode : $language_content->language; - - $output = "<channel>\n"; - $output .= ' <title>' . check_plain($title) . "</title>\n"; - $output .= ' <link>' . check_url($link) . "</link>\n"; - - // The RSS 2.0 "spec" doesn't indicate HTML can be used in the description. - // We strip all HTML tags, but need to prevent double encoding from properly - // escaped source data (such as & becoming &amp;). - $output .= ' <description>' . check_plain(decode_entities(strip_tags($description))) . "</description>\n"; - $output .= ' <language>' . check_plain($langcode) . "</language>\n"; - $output .= format_xml_elements($args); - $output .= $items; - $output .= "</channel>\n"; - - return $output; -} - -/** - * Format a single RSS item. - * - * Arbitrary elements may be added using the $args associative array. - */ -function format_rss_item($title, $link, $description, $args = array()) { - $output = "<item>\n"; - $output .= ' <title>' . check_plain($title) . "</title>\n"; - $output .= ' <link>' . check_url($link) . "</link>\n"; - $output .= ' <description>' . check_plain($description) . "</description>\n"; - $output .= format_xml_elements($args); - $output .= "</item>\n"; - - return $output; -} - -/** - * Format XML elements. - * - * @param $array - * An array where each item represents an element and is either a: - * - (key => value) pair (<key>value</key>) - * - Associative array with fields: - * - 'key': element name - * - 'value': element contents - * - 'attributes': associative array of element attributes - * - * In both cases, 'value' can be a simple string, or it can be another array - * with the same format as $array itself for nesting. - */ -function format_xml_elements($array) { - $output = ''; - foreach ($array as $key => $value) { - if (is_numeric($key)) { - if ($value['key']) { - $output .= ' <' . $value['key']; - if (isset($value['attributes']) && is_array($value['attributes'])) { - $output .= drupal_attributes($value['attributes']); - } - - if (isset($value['value']) && $value['value'] != '') { - $output .= '>' . (is_array($value['value']) ? format_xml_elements($value['value']) : check_plain($value['value'])) . '</' . $value['key'] . ">\n"; - } - else { - $output .= " />\n"; - } - } - } - else { - $output .= ' <' . $key . '>' . (is_array($value) ? format_xml_elements($value) : check_plain($value)) . "</$key>\n"; - } - } - return $output; -} - -/** - * Format a string containing a count of items. - * - * 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 - * it. - * - * For example: - * @code - * $output = format_plural($node->comment_count, '1 comment', '@count comments'); - * @endcode - * - * Example with additional replacements: - * @code - * $output = format_plural($update_count, - * 'Changed the content type of 1 post from %old-type to %new-type.', - * 'Changed the content type of @count posts from %old-type to %new-type.', - * array('%old-type' => $info->old_type, '%new-type' => $info->new_type))); - * @endcode - * - * @param $count - * The item count to display. - * @param $singular - * The string for the singular case. Please make sure it is clear this is - * singular, to ease translation (e.g. use "1 new comment" instead of "1 new"). - * Do not use @count in the singular string. - * @param $plural - * The string for the plural case. Please make sure it is clear this is plural, - * to ease translation. Use @count in place of the item count, as in "@count - * new comments". - * @param $args - * An associative array of replacements to make after translation. Incidences - * of any key in this array are replaced with the corresponding value. - * Based on the first character of the key, the value is escaped and/or themed: - * - !variable: inserted as is - * - @variable: escape plain text to HTML (check_plain) - * - %variable: escape text and theme as a placeholder for user-submitted - * content (check_plain + drupal_placeholder) - * Note that you do not need to include @count in this array. - * This replacement is done automatically for the plural case. - * @param $options - * An associative array of additional options, with the following 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 - * A translated string. - */ -function format_plural($count, $singular, $plural, array $args = array(), array $options = array()) { - $args['@count'] = $count; - if ($count == 1) { - return t($singular, $args, $options); - } - - // Get the plural index through the gettext formula. - $index = (function_exists('locale_get_plural')) ? locale_get_plural($count, isset($options['langcode']) ? $options['langcode'] : NULL) : -1; - // Backwards compatibility. - if ($index < 0) { - return t($plural, $args, $options); - } - else { - switch ($index) { - case "0": - return t($singular, $args, $options); - case "1": - return t($plural, $args, $options); - default: - unset($args['@count']); - $args['@count[' . $index . ']'] = $count; - return t(strtr($plural, array('@count' => '@count[' . $index . ']')), $args, $options); - } - } -} - -/** - * Parse a given byte count. - * - * @param $size - * 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). - * @return - * An integer representation of the size in bytes. - */ -function parse_size($size) { - $unit = preg_replace('/[^bkmgtpezy]/i', '', $size); // Remove the non-unit characters from the size. - $size = preg_replace('/[^0-9\.]/', '', $size); // Remove the non-numeric characters from the size. - if ($unit) { - // Find the position of the unit in the ordered string which is the power of magnitude to multiply a kilobyte by. - return round($size * pow(DRUPAL_KILOBYTE, stripos('bkmgtpezy', $unit[0]))); - } - else { - return round($size); - } -} - -/** - * Generate a string representation for the given byte count. - * - * @param $size - * A size in bytes. - * @param $langcode - * Optional language code to translate to a language other than what is used - * to display the page. - * @return - * A translated string representation of the size. - */ -function format_size($size, $langcode = NULL) { - if ($size < DRUPAL_KILOBYTE) { - return format_plural($size, '1 byte', '@count bytes', array(), array('langcode' => $langcode)); - } - else { - $size = $size / DRUPAL_KILOBYTE; // Convert bytes to kilobytes. - $units = array( - t('@size KB', array(), array('langcode' => $langcode)), - t('@size MB', array(), array('langcode' => $langcode)), - t('@size GB', array(), array('langcode' => $langcode)), - t('@size TB', array(), array('langcode' => $langcode)), - t('@size PB', array(), array('langcode' => $langcode)), - t('@size EB', array(), array('langcode' => $langcode)), - t('@size ZB', array(), array('langcode' => $langcode)), - t('@size YB', array(), array('langcode' => $langcode)), - ); - foreach ($units as $unit) { - if (round($size, 2) >= DRUPAL_KILOBYTE) { - $size = $size / DRUPAL_KILOBYTE; - } - else { - break; - } - } - return str_replace('@size', round($size, 2), $unit); - } -} - -/** - * Format a time interval with the requested granularity. - * - * @param $timestamp - * The length of the interval in seconds. - * @param $granularity - * How many different units to display in the string. - * @param $langcode - * Optional language code to translate to a language other than - * what is used to display the page. - * @return - * A translated string representation of the interval. - */ -function format_interval($timestamp, $granularity = 2, $langcode = NULL) { - $units = array( - '1 year|@count years' => 31536000, - '1 month|@count months' => 2592000, - '1 week|@count weeks' => 604800, - '1 day|@count days' => 86400, - '1 hour|@count hours' => 3600, - '1 min|@count min' => 60, - '1 sec|@count sec' => 1 - ); - $output = ''; - foreach ($units as $key => $value) { - $key = explode('|', $key); - if ($timestamp >= $value) { - $output .= ($output ? ' ' : '') . format_plural(floor($timestamp / $value), $key[0], $key[1], array(), array('langcode' => $langcode)); - $timestamp %= $value; - $granularity--; - } - - if ($granularity == 0) { - break; - } - } - return $output ? $output : t('0 sec', array(), array('langcode' => $langcode)); -} - -/** - * Formats a date, using a date type or a custom date format string. - * - * @param $timestamp - * A UNIX timestamp to format. - * @param $type - * (optional) The format to use, one of: - * - '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 - * (optional) If $type is 'custom', a PHP date format string suitable for - * input to date(). Use a backslash to escape ordinary text, so it does not - * get interpreted as date format characters. - * @param $timezone - * (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 - * (optional) Language code to translate to. Defaults to the language used to - * display the page. - * - * @return - * A translated date string in the requested format. - */ -function format_date($timestamp, $type = 'medium', $format = '', $timezone = NULL, $langcode = NULL) { - // Use the advanced drupal_static() pattern, since this is called very often. - static $drupal_static_fast; - if (!isset($drupal_static_fast)) { - $drupal_static_fast['timezones'] = &drupal_static(__FUNCTION__); - } - $timezones = &$drupal_static_fast['timezones']; - - if (!isset($timezone)) { - $timezone = date_default_timezone_get(); - } - // Store DateTimeZone objects in an array rather than repeatedly - // constructing identical objects over the life of a request. - if (!isset($timezones[$timezone])) { - $timezones[$timezone] = timezone_open($timezone); - } - - // Use the default langcode if none is set. - global $language; - if (empty($langcode)) { - $langcode = isset($language->language) ? $language->language : LANGUAGE_SYSTEM; - } - - switch ($type) { - case 'short': - $format = variable_get('date_format_short', 'm/d/Y - H:i'); - break; - - case 'long': - $format = variable_get('date_format_long', 'l, F j, Y - H:i'); - break; - - case 'custom': - // No change to format. - break; - - case 'medium': - 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'); - } - break; - } - - // Create a DateTime object from the timestamp. - $date_time = date_create('@' . $timestamp); - // Set the time zone for the DateTime object. - date_timezone_set($date_time, $timezones[$timezone]); - - // Encode markers that should be translated. 'A' becomes '\xEF\AA\xFF'. - // xEF and xFF are invalid UTF-8 sequences, and we assume they are not in the - // input string. - // Paired backslashes are isolated to prevent errors in read-ahead evaluation. - // The read-ahead expression ensures that A matches, but not \A. - $format = preg_replace(array('/\\\\\\\\/', '/(?<!\\\\)([AaeDlMTF])/'), array("\xEF\\\\\\\\\xFF", "\xEF\\\\\$1\$1\xFF"), $format); - - // Call date_format(). - $format = date_format($date_time, $format); - - // Pass the langcode to _format_date_callback(). - _format_date_callback(NULL, $langcode); - - // Translate the marked sequences. - return preg_replace_callback('/\xEF([AaeDlMTF]?)(.*?)\xFF/', '_format_date_callback', $format); -} - -/** - * Returns an ISO8601 formatted date based on the given date. - * - * Can be used as a callback for RDF mappings. - * - * @param $date - * A UNIX timestamp. - * @return string - * An ISO8601 formatted date. - */ -function date_iso8601($date) { - // The DATE_ISO8601 constant cannot be used here because it does not match - // date('c') and produces invalid RDF markup. - return date('c', $date); -} - -/** - * Callback function for preg_replace_callback(). - */ -function _format_date_callback(array $matches = NULL, $new_langcode = NULL) { - // We cache translations to avoid redundant and rather costly calls to t(). - static $cache, $langcode; - - if (!isset($matches)) { - $langcode = $new_langcode; - return; - } - - $code = $matches[1]; - $string = $matches[2]; - - if (!isset($cache[$langcode][$code][$string])) { - $options = array( - 'langcode' => $langcode, - ); - - if ($code == 'F') { - $options['context'] = 'Long month name'; - } - - if ($code == '') { - $cache[$langcode][$code][$string] = $string; - } - else { - $cache[$langcode][$code][$string] = t($string, array(), $options); - } - } - return $cache[$langcode][$code][$string]; -} - -/** - * Format a username. - * - * 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 - * may override this by implementing hook_username_alter(&$name, $account). - * - * @see hook_username_alter() - * - * @param $account - * The account object for the user whose name is to be formatted. - * - * @return - * An unsanitized string with the username to display. The code receiving - * this result must ensure that check_plain() is called on it before it is - * printed to the page. - */ -function format_username($account) { - $name = !empty($account->name) ? $account->name : variable_get('anonymous', t('Anonymous')); - drupal_alter('username', $name, $account); - return $name; -} - -/** - * @} End of "defgroup format". - */ - -/** - * Generates an internal or external URL. - * - * When creating links in modules, consider whether l() could be a better - * alternative than url(). - * - * @param $path - * The internal path or external URL being linked to, such as "node/34" or - * "http://example.com/foo". A few notes: - * - If you provide a full URL, it will be considered an external URL. - * - If you provide only the path (e.g. "node/34"), it will be - * considered an internal link. In this case, it should be a system URL, - * and it will be replaced with the alias, if one exists. Additional query - * arguments for internal paths must be supplied in $options['query'], not - * included in $path. - * - If you provide an internal path and $options['alias'] is set to TRUE, the - * path is assumed already to be the correct path alias, and the alias is - * not looked up. - * - The special string '<front>' generates a link to the site's base URL. - * - If your external URL contains a query (e.g. http://example.com/foo?a=b), - * then you can either URL encode the query keys and values yourself and - * include them in $path, or use $options['query'] to let this function - * URL encode them. - * @param $options - * An associative array of additional options, with the following elements: - * - 'query': An array of query key/value-pairs (without any URL-encoding) to - * append to the URL. - * - 'fragment': A fragment identifier (named anchor) to append to the URL. - * Do not include the leading '#' character. - * - 'absolute': Defaults to FALSE. Whether to force the output to be an - * absolute link (beginning with http:). Useful for links that will be - * displayed outside the site, such as in an RSS feed. - * - 'alias': Defaults to FALSE. Whether the given path is a URL alias - * already. - * - 'external': Whether the given path is an external URL. - * - 'language': An optional language object. If the path being linked to is - * internal to the site, $options['language'] is used to look up the alias - * for the URL. If $options['language'] is omitted, the global $language_url - * will be used. - * - 'https': Whether this URL should point to a secure location. If not - * defined, the current scheme is used, so the user stays on http or https - * respectively. TRUE enforces HTTPS and FALSE enforces HTTP, but HTTPS can - * only be enforced when the variable 'https' is set to TRUE. - * - 'base_url': Only used internally, to modify the base URL when a language - * dependent URL requires so. - * - 'prefix': Only used internally, to modify the path when a language - * dependent URL requires so. - * - 'script': The script filename in Drupal's root directory to use when - * clean URLs are disabled, such as 'index.php'. Defaults to an empty - * string, as most modern web servers automatically find 'index.php'. If - * clean URLs are disabled, the value of $path is appended as query - * parameter 'q' to $options['script'] in the returned URL. When deploying - * Drupal on a web server that cannot be configured to automatically find - * index.php, then hook_url_outbound_alter() can be implemented to force - * this value to 'index.php'. - * - 'entity_type': The entity type of the object that called url(). Only set if - * url() is invoked by entity_uri(). - * - 'entity': The entity object (such as a node) for which the URL is being - * generated. Only set if url() is invoked by entity_uri(). - * - * @return - * A string containing a URL to the given path. - */ -function url($path = NULL, array $options = array()) { - // Merge in defaults. - $options += array( - 'fragment' => '', - 'query' => array(), - 'absolute' => FALSE, - 'alias' => FALSE, - 'prefix' => '' - ); - - if (!isset($options['external'])) { - // Return an external link if $path contains an allowed absolute URL. Only - // call the slow drupal_strip_dangerous_protocols() if $path contains a ':' - // before any / ? or #. Note: we could use url_is_external($path) here, but - // that would require another function call, and performance inside url() is - // critical. - $colonpos = strpos($path, ':'); - $options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && drupal_strip_dangerous_protocols($path) == $path); - } - - // Preserve the original path before altering or aliasing. - $original_path = $path; - - // Allow other modules to alter the outbound URL and options. - drupal_alter('url_outbound', $path, $options, $original_path); - - if (isset($options['fragment']) && $options['fragment'] !== '') { - $options['fragment'] = '#' . $options['fragment']; - } - - if ($options['external']) { - // Split off the fragment. - if (strpos($path, '#') !== FALSE) { - list($path, $old_fragment) = explode('#', $path, 2); - // If $options contains no fragment, take it over from the path. - if (isset($old_fragment) && !$options['fragment']) { - $options['fragment'] = '#' . $old_fragment; - } - } - // Append the query. - if ($options['query']) { - $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($options['query']); - } - if (isset($options['https']) && variable_get('https', FALSE)) { - if ($options['https'] === TRUE) { - $path = str_replace('http://', 'https://', $path); - } - elseif ($options['https'] === FALSE) { - $path = str_replace('https://', 'http://', $path); - } - } - // Reassemble. - return $path . $options['fragment']; - } - - global $base_url, $base_secure_url, $base_insecure_url; - - // The base_url might be rewritten from the language rewrite in domain mode. - if (!isset($options['base_url'])) { - if (isset($options['https']) && variable_get('https', FALSE)) { - if ($options['https'] === TRUE) { - $options['base_url'] = $base_secure_url; - $options['absolute'] = TRUE; - } - elseif ($options['https'] === FALSE) { - $options['base_url'] = $base_insecure_url; - $options['absolute'] = TRUE; - } - } - else { - $options['base_url'] = $base_url; - } - } - - // The special path '<front>' links to the default front page. - if ($path == '<front>') { - $path = ''; - } - elseif (!empty($path) && !$options['alias']) { - $language = isset($options['language']) && isset($options['language']->language) ? $options['language']->language : ''; - $alias = drupal_get_path_alias($original_path, $language); - if ($alias != $original_path) { - $path = $alias; - } - } - - $base = $options['absolute'] ? $options['base_url'] . '/' : base_path(); - $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix']; - - // With Clean URLs. - if (!empty($GLOBALS['conf']['clean_url'])) { - $path = drupal_encode_path($prefix . $path); - if ($options['query']) { - return $base . $path . '?' . drupal_http_build_query($options['query']) . $options['fragment']; - } - else { - return $base . $path . $options['fragment']; - } - } - // Without Clean URLs. - else { - $path = $prefix . $path; - $query = array(); - if (!empty($path)) { - $query['q'] = $path; - } - if ($options['query']) { - // We do not use array_merge() here to prevent overriding $path via query - // parameters. - $query += $options['query']; - } - $query = $query ? ('?' . drupal_http_build_query($query)) : ''; - $script = isset($options['script']) ? $options['script'] : ''; - return $base . $script . $query . $options['fragment']; - } -} - -/** - * Return TRUE if a path is external to Drupal (e.g. http://example.com). - * - * If a path cannot be assessed by Drupal's menu handler, then we must - * treat it as potentially insecure. - * - * @param $path - * The internal path or external URL being linked to, such as "node/34" or - * "http://example.com/foo". - * @return - * Boolean TRUE or FALSE, where TRUE indicates an external path. - */ -function url_is_external($path) { - $colonpos = strpos($path, ':'); - // Avoid calling drupal_strip_dangerous_protocols() if there is any - // 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; -} - -/** - * Format an attribute string for a HTTP header. - * - * @param $attributes - * An associative array of attributes such as 'rel'. - * - * @return - * A ; separated string ready for insertion in a HTTP header. No escaping is - * performed for HTML entities, so this string is not safe to be printed. - * - * @see drupal_add_http_header() - */ -function drupal_http_header_attributes(array $attributes = array()) { - foreach ($attributes as $attribute => &$data) { - if (is_array($data)) { - $data = implode(' ', $data); - } - $data = $attribute . '="' . $data . '"'; - } - return $attributes ? ' ' . implode('; ', $attributes) : ''; -} - -/** - * Converts an associative array to an attribute string for use in XML/HTML tags. - * - * 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). - * - * Attribute values are sanitized by running them through check_plain(). - * Attribute names are not automatically sanitized. When using user-supplied - * 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 - * An associative array of key-value pairs to be converted to attributes. - * - * @return - * A string ready for insertion in a tag (starts with a space). - * - * @ingroup sanitization - */ -function drupal_attributes(array $attributes = array()) { - foreach ($attributes as $attribute => &$data) { - $data = implode(' ', (array) $data); - $data = $attribute . '="' . check_plain($data) . '"'; - } - return $attributes ? ' ' . implode(' ', $attributes) : ''; -} - -/** - * Formats an internal or external URL link as an HTML anchor tag. - * - * This function correctly handles aliased paths, and adds an 'active' class - * attribute to links that point to the current page (for theming), so all - * internal links output by modules should be generated by this function if - * possible. - * - * @param $text - * The link text for the anchor tag. - * @param $path - * The internal path or external URL being linked to, such as "node/34" or - * "http://example.com/foo". After the url() function is called to construct - * the URL from $path and $options, the resulting URL is passed through - * check_plain() before it is inserted into the HTML anchor tag, to ensure - * well-formed HTML. See url() for more information and notes. - * @param array $options - * An associative array of additional options, with the following elements: - * - 'attributes': An associative array of HTML attributes to apply to the - * 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 - * example, to make an image tag into a link, this must be set to TRUE, or - * 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 - * internal to the site, $options['language'] is used to determine whether - * the link is "active", or pointing to the current page (the language as - * well as the path must match). This element is also used by url(). - * - Additional $options elements used by the url() function. - * - * @return - * An HTML string containing a link to the given path. - */ -function l($text, $path, array $options = array()) { - global $language_url; - static $use_theme = NULL; - - // Merge in defaults. - $options += array( - 'attributes' => array(), - 'html' => FALSE, - ); - - // Append active class. - if (($path == $_GET['q'] || ($path == '<front>' && drupal_is_front_page())) && - (empty($options['language']) || $options['language']->language == $language_url->language)) { - $options['attributes']['class'][] = 'active'; - } - - // Remove all HTML and PHP tags from a tooltip. For best performance, we act only - // if a quick strpos() pre-check gave a suspicion (because strip_tags() is expensive). - if (isset($options['attributes']['title']) && strpos($options['attributes']['title'], '<') !== FALSE) { - $options['attributes']['title'] = strip_tags($options['attributes']['title']); - } - - // Determine if rendering of the link is to be done with a theme function - // or the inline default. Inline is faster, but if the theme system has been - // loaded and a module or theme implements a preprocess or process function - // or overrides the theme_link() function, then invoke theme(). Preliminary - // benchmarks indicate that invoking theme() can slow down the l() function - // by 20% or more, and that some of the link-heavy Drupal pages spend more - // than 10% of the total page request time in the l() function. - if (!isset($use_theme) && function_exists('theme')) { - // Allow edge cases to prevent theme initialization and force inline link - // rendering. - if (variable_get('theme_link', TRUE)) { - drupal_theme_initialize(); - $registry = theme_get_registry(); - // 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' - // theme hook should simply result in theme() being called. This includes - // the overriding of theme_link() with an alternate function or template, - // the presence of preprocess or process functions, or the presence of - // include files. - $use_theme = !isset($registry['link']['function']) || ($registry['link']['function'] != 'theme_link'); - $use_theme = $use_theme || !empty($registry['link']['preprocess functions']) || !empty($registry['link']['process functions']) || !empty($registry['link']['includes']); - } - else { - $use_theme = FALSE; - } - } - if ($use_theme) { - return theme('link', array('text' => $text, 'path' => $path, 'options' => $options)); - } - // The result of url() is a plain-text URL. Because we are using it here - // in an HTML argument context, we need to encode it properly. - return '<a href="' . check_plain(url($path, $options)) . '"' . drupal_attributes($options['attributes']) . '>' . ($options['html'] ? $text : check_plain($text)) . '</a>'; -} - -/** - * Delivers a page callback result to the browser in the appropriate format. - * - * This function is most commonly called by menu_execute_active_handler(), but - * can also be called by error conditions such as drupal_not_found(), - * drupal_access_denied(), and drupal_site_offline(). - * - * When a user requests a page, index.php calls menu_execute_active_handler(), - * which calls the 'page callback' function registered in hook_menu(). The page - * callback function can return one of: - * - NULL: to indicate no content. - * - An integer menu status constant: to indicate an error condition. - * - A string of HTML content. - * - A renderable array of content. - * Returning a renderable array rather than a string of HTML is preferred, - * because that provides modules with more flexibility in customizing the final - * result. - * - * When the page callback returns its constructed content to - * menu_execute_active_handler(), this function gets called. The purpose of - * this function is to determine the most appropriate 'delivery callback' - * function to route the content to. The delivery callback function then - * sends the content to the browser in the needed format. The default delivery - * callback is drupal_deliver_html_page(), which delivers the content as an HTML - * page, complete with blocks in addition to the content. This default can be - * overridden on a per menu router item basis by setting 'delivery callback' in - * hook_menu() or hook_menu_alter(), and can also be overridden on a per request - * basis in hook_page_delivery_callback_alter(). - * - * For example, the same page callback function can be used for an HTML - * version of the page and an Ajax version of the page. The page callback - * 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 - * response, as appropriate. - * - * In order for page callbacks to be reusable in different delivery formats, - * they should not issue any "print" or "echo" statements, but instead just - * return content. - * - * Also note that this function does not perform access checks. The delivery - * callback function specified in hook_menu(), hook_menu_alter(), or - * hook_page_delivery_callback_alter() will be called even if the router item - * access checks fail. This is intentional (it is needed for JSON and other - * purposes), but it has security implications. Do not call this function - * directly unless you understand the security implications, and be careful in - * writing delivery callbacks, so that they do not violate security. See - * drupal_deliver_html_page() for an example of a delivery callback that - * respects security. - * - * @param $page_callback_result - * The result of a page callback. Can be one of: - * - NULL: to indicate no content. - * - An integer menu status constant: to indicate an error condition. - * - A string of HTML content. - * - A renderable array of content. - * @param $default_delivery_callback - * (Optional) If given, it is the name of a delivery function most likely - * to be appropriate for the page request as determined by the calling - * function (e.g., menu_execute_active_handler()). If not given, it is - * determined from the menu router information of the current page. - * - * @see menu_execute_active_handler() - * @see hook_menu() - * @see hook_menu_alter() - * @see hook_page_delivery_callback_alter() - */ -function drupal_deliver_page($page_callback_result, $default_delivery_callback = NULL) { - if (!isset($default_delivery_callback) && ($router_item = menu_get_item())) { - $default_delivery_callback = $router_item['delivery_callback']; - } - $delivery_callback = !empty($default_delivery_callback) ? $default_delivery_callback : 'drupal_deliver_html_page'; - // Give modules a chance to alter the delivery callback used, based on - // request-time context (e.g., HTTP request headers). - drupal_alter('page_delivery_callback', $delivery_callback); - if (function_exists($delivery_callback)) { - $delivery_callback($page_callback_result); - } - else { - // If a delivery callback is specified, but doesn't exist as a function, - // something is wrong, but don't print anything, since it's not known - // what format the response needs to be in. - watchdog('delivery callback not found', 'callback %callback not found: %q.', array('%callback' => $delivery_callback, '%q' => $_GET['q']), WATCHDOG_ERROR); - } -} - -/** - * Package and send the result of a page callback to the browser as HTML. - * - * @param $page_callback_result - * The result of a page callback. Can be one of: - * - NULL: to indicate no content. - * - An integer menu status constant: to indicate an error condition. - * - A string of HTML content. - * - A renderable array of content. - * - * @see drupal_deliver_page() - */ -function drupal_deliver_html_page($page_callback_result) { - // Emit the correct charset HTTP header, but not if the page callback - // result is NULL, since that likely indicates that it printed something - // in which case, no further headers may be sent, and not if code running - // for this page request has already set the content type header. - if (isset($page_callback_result) && is_null(drupal_get_http_header('Content-Type'))) { - 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. - if (is_int($page_callback_result)) { - // @todo: Break these up into separate functions? - switch ($page_callback_result) { - case MENU_NOT_FOUND: - // Print a 404 page. - drupal_add_http_header('Status', '404 Not Found'); - - 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. - if (!isset($_GET['destination'])) { - $_GET['destination'] = $_GET['q']; - } - - $path = drupal_get_normal_path(variable_get('site_404', '')); - if ($path && $path != $_GET['q']) { - // Custom 404 handler. Set the active item in case there are tabs to - // display, or other dependencies on the path. - menu_set_active_item($path); - $return = menu_execute_active_handler($path, FALSE); - } - - if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) { - // Standard 404 handler. - drupal_set_title(t('Page not found')); - $return = t('The requested page "@path" could not be found.', array('@path' => request_uri())); - } - - drupal_set_page_content($return); - $page = element_info('page'); - print drupal_render_page($page); - break; - - case MENU_ACCESS_DENIED: - // Print a 403 page. - drupal_add_http_header('Status', '403 Forbidden'); - watchdog('access denied', check_plain($_GET['q']), NULL, WATCHDOG_WARNING); - - // Keep old path for reference, and to allow forms to redirect to it. - if (!isset($_GET['destination'])) { - $_GET['destination'] = $_GET['q']; - } - - $path = drupal_get_normal_path(variable_get('site_403', '')); - if ($path && $path != $_GET['q']) { - // Custom 403 handler. Set the active item in case there are tabs to - // display or other dependencies on the path. - menu_set_active_item($path); - $return = menu_execute_active_handler($path, FALSE); - } - - if (empty($return) || $return == MENU_NOT_FOUND || $return == MENU_ACCESS_DENIED) { - // Standard 403 handler. - drupal_set_title(t('Access denied')); - $return = t('You are not authorized to access this page.'); - } - - print drupal_render_page($return); - break; - - case MENU_SITE_OFFLINE: - // Print a 503 page. - drupal_maintenance_theme(); - drupal_add_http_header('Status', '503 Service unavailable'); - drupal_set_title(t('Site under maintenance')); - print theme('maintenance_page', array('content' => filter_xss_admin(variable_get('maintenance_mode_message', - t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))))))); - break; - } - } - elseif (isset($page_callback_result)) { - // Print anything besides a menu constant, assuming it's not NULL or - // undefined. - print drupal_render_page($page_callback_result); - } - - // Perform end-of-request tasks. - drupal_page_footer(); -} - -/** - * Perform end-of-request tasks. - * - * This function sets the page cache if appropriate, and allows modules to - * react to the closing of the page by calling hook_exit(). - */ -function drupal_page_footer() { - global $user; - - module_invoke_all('exit'); - - // Commit the user session, if needed. - drupal_session_commit(); - - if (variable_get('cache', 0) && ($cache = drupal_page_set_cache())) { - drupal_serve_page_from_cache($cache); - } - else { - ob_flush(); - } - - _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE); - drupal_cache_system_paths(); - module_implements_write_cache(); - system_run_automated_cron(); -} - -/** - * Perform end-of-request tasks. - * - * 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 - * to call exit instead of drupal_exit(); - * - * @param $destination - * If this function is called from drupal_goto(), then this argument - * will be a fully-qualified URL that is the destination of the redirect. - * This should be passed along to hook_exit() implementations. - */ -function drupal_exit($destination = NULL) { - if (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL) { - if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') { - module_invoke_all('exit', $destination); - } - drupal_session_commit(); - } - exit; -} - -/** - * Form an associative array from a linear array. - * - * 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 - * input array. The values will be the same as the keys unless a function is - * specified, in which case the output of the function is used for the values - * instead. - * - * @param $array - * A linear array. - * @param $function - * A name of a function to apply to all values before output. - * - * @return - * An associative array. - */ -function drupal_map_assoc($array, $function = NULL) { - // array_combine() fails with empty arrays: - // http://bugs.php.net/bug.php?id=34857. - $array = !empty($array) ? array_combine($array, $array) : array(); - if (is_callable($function)) { - $array = array_map($function, $array); - } - return $array; -} - -/** - * Attempts to set the PHP maximum execution time. - * - * This function is a wrapper around the PHP function set_time_limit(). - * When called, set_time_limit() restarts the timeout counter from zero. - * In other words, if the timeout is the default 30 seconds, and 25 seconds - * into script execution a call such as set_time_limit(20) is made, the - * script will run for a total of 45 seconds before timing out. - * - * It also means that it is possible to decrease the total time limit if - * the sum of the new time limit and the current time spent running the - * script is inferior to the original time limit. It is inherent to the way - * set_time_limit() works, it should rather be called with an appropriate - * value every time you need to allocate a certain amount of time - * to execute a task than only once at the beginning of the script. - * - * Before calling set_time_limit(), we check if this function is available - * because it could be disabled by the server administrator. We also hide all - * the errors that could occur when calling set_time_limit(), because it is - * not possible to reliably ensure that PHP or a security extension will - * not issue a warning/error if they prevent the use of this function. - * - * @param $time_limit - * An integer specifying the new time limit, in seconds. A value of 0 - * indicates unlimited execution time. - * - * @ingroup php_wrappers - */ -function drupal_set_time_limit($time_limit) { - if (function_exists('set_time_limit')) { - @set_time_limit($time_limit); - } -} - -/** - * Returns the path to a system item (module, theme, etc.). - * - * @param $type - * The type of the item (i.e. theme, theme_engine, module, profile). - * @param $name - * The name of the item for which the path is requested. - * - * @return - * The path to the requested item. - */ -function drupal_get_path($type, $name) { - return dirname(drupal_get_filename($type, $name)); -} - -/** - * Return 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 - * not empty. At the very least, this will return "/". - * - * Examples: - * - http://example.com returns "/" because the path is empty. - * - http://example.com/drupal/folder returns "/drupal/folder/". - */ -function base_path() { - return $GLOBALS['base_path']; -} - -/** - * Add 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, - * which on normal pages is up through the preprocess step of theme('html'). - * Adding a link will overwrite a prior link with the exact same 'rel' and - * 'href' attributes. - * - * @param $attributes - * Associative array of element attributes including 'href' and 'rel'. - * @param $header - * Optional flag to determine if a HTTP 'Link:' header should be sent. - */ -function drupal_add_html_head_link($attributes, $header = FALSE) { - $element = array( - '#tag' => 'link', - '#attributes' => $attributes, - ); - $href = $attributes['href']; - - if ($header) { - // Also add a HTTP header "Link:". - $href = '<' . check_plain($attributes['href']) . '>;'; - unset($attributes['href']); - $element['#attached']['drupal_add_http_header'][] = array('Link', $href . drupal_http_header_attributes($attributes), TRUE); - } - - drupal_add_html_head($element, 'drupal_add_html_head_link:' . $attributes['rel'] . ':' . $href); -} - -/** - * Adds a cascading stylesheet to the stylesheet queue. - * - * Calling drupal_static_reset('drupal_add_css') will clear all cascading - * stylesheets added so far. - * - * If CSS aggregation/compression is enabled, all cascading style sheets added - * with $options['preprocess'] set to TRUE will be merged into one aggregate - * file and compressed by removing all extraneous white space. - * Preprocessed inline stylesheets will not be aggregated into this single file; - * instead, they are just compressed upon output on the page. Externally hosted - * stylesheets are never aggregated or compressed. - * - * The reason for aggregating the files is outlined quite thoroughly here: - * http://www.die.net/musings/page_load_time/ "Load fewer external objects. Due - * to request overhead, one bigger file just loads faster than two smaller ones - * half its size." - * - * $options['preprocess'] should be only set to TRUE when a file is required for - * 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 - * files do not happen to be needed on a page. This is normally done by calling - * drupal_add_css() in a hook_init() implementation. - * - * Non-preprocessed files should only be added to the page when they are - * actually needed. - * - * @param $data - * (optional) The stylesheet data to be added, depending on what is passed - * through to the $options['type'] parameter: - * - 'file': The path to the CSS file relative to the base_path(), or a - * stream wrapper URI. For example: "modules/devel/devel.css" or - * "public://generated_css/stylesheet_1.css". Note that Modules should - * always prefix the names of their CSS files with the module name; for - * example, system-menus.css rather than simply menus.css. Themes can - * override module-supplied CSS files based on their filenames, and this - * 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, - * 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 - * 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 - * directory. This CSS file should contain overrides for properties which - * should be reversed or otherwise different in a right-to-left display. - * - 'inline': A string of CSS that should be placed in the given scope. Note - * that it is better practice to use 'file' stylesheets, rather than - * 'inline', as the CSS would then be aggregated and cached. - * - 'external': The absolute path to an external CSS file that is not hosted - * on the local server. These files will not be aggregated if CSS - * aggregation is enabled. - * @param $options - * (optional) A string defining the 'type' of CSS that is being added in the - * $data parameter ('file', 'inline', or 'external'), or an array which can - * have any or all of the following keys: - * - 'type': The type of stylesheet being added. Available options are 'file', - * 'inline' or 'external'. Defaults to 'file'. - * - 'basename': Force a basename for the file being added. Modules are - * expected to use stylesheets with unique filenames, but integration of - * external libraries may make this impossible. The basename of - * 'core/modules/node/node.css' is 'node.css'. If the external library - * "node.js" ships with a 'node.css', then a different, unique basename - * would be 'node.js.css'. - * - 'group': A number identifying the group in which to add the stylesheet. - * Available constants are: - * - CSS_SYSTEM: Any system-layer CSS. - * - CSS_DEFAULT: Any module-layer CSS. - * - CSS_THEME: Any theme-layer CSS. - * The group number serves as a weight: the markup for loading a stylesheet - * within a lower weight group is output to the page before the markup for - * loading a stylesheet within a higher weight group, so CSS within higher - * weight groups take precendence over CSS within lower weight groups. - * - 'every_page': For optimal front-end performance when aggregation is - * enabled, this should be set to TRUE if the stylesheet is present on every - * page of the website for users for whom it is present at all. This - * defaults to FALSE. It is set to TRUE for stylesheets added via module and - * theme .info files. Modules that add stylesheets within hook_init() - * implementations, or from other code that ensures that the stylesheet is - * added to all website pages, should also set this flag to TRUE. All - * stylesheets within the same group that have the 'every_page' flag set to - * TRUE and do not have 'preprocess' set to FALSE are aggregated together - * into a single aggregate file, and that aggregate file can be reused - * across a user's entire site visit, leading to faster navigation between - * pages. However, stylesheets that are only needed on pages less frequently - * visited, can be added by code that only runs for those particular pages, - * and that code should not set the 'every_page' flag. This minimizes the - * size of the aggregate file that the user needs to download when first - * visiting the website. Stylesheets without the 'every_page' flag are - * aggregated into a separate aggregate file. This other aggregate file is - * likely to change from page to page, and each new aggregate file needs to - * be downloaded when first encountered, so it should be kept relatively - * small by ensuring that most commonly needed stylesheets are added to - * every page. - * - 'weight': The weight of the stylesheet specifies the order in which the - * CSS will appear relative to other stylesheets with the same group and - * 'every_page' flag. The exact ordering of stylesheets is as follows: - * - First by group. - * - Then by the 'every_page' flag, with TRUE coming before FALSE. - * - Then by weight. - * - Then by the order in which the CSS was added. For example, all else - * being the same, a stylesheet added by a call to drupal_add_css() that - * happened later in the page request gets added to the page after one for - * which drupal_add_css() happened earlier in the page request. - * - 'media': The media type for the stylesheet, e.g., all, print, screen. - * Defaults to 'all'. - * - 'preprocess': If TRUE and CSS aggregation/compression is enabled, the - * styles will be aggregated and compressed. Defaults to TRUE. - * - 'browsers': An array containing information specifying which browsers - * should load the CSS item. See drupal_pre_render_conditional_comments() - * for details. - * - * @return - * An array of queued cascading stylesheets. - * - * @see drupal_get_css() - */ -function drupal_add_css($data = NULL, $options = NULL) { - $css = &drupal_static(__FUNCTION__, array()); - - // Construct the options, taking the defaults into consideration. - if (isset($options)) { - if (!is_array($options)) { - $options = array('type' => $options); - } - } - else { - $options = array(); - } - - // Create an array of CSS files for each media type first, since each type needs to be served - // to the browser differently. - if (isset($data)) { - $options += array( - 'type' => 'file', - 'group' => CSS_DEFAULT, - 'weight' => 0, - 'every_page' => FALSE, - 'media' => 'all', - 'preprocess' => TRUE, - 'data' => $data, - 'browsers' => array(), - ); - $options['browsers'] += array( - 'IE' => TRUE, - '!IE' => TRUE, - ); - - // Files with a query string cannot be preprocessed. - if ($options['type'] === 'file' && $options['preprocess'] && strpos($options['data'], '?') !== FALSE) { - $options['preprocess'] = FALSE; - } - - // Always add a tiny value to the weight, to conserve the insertion order. - $options['weight'] += count($css) / 1000; - - // Add the data to the CSS array depending on the type. - switch ($options['type']) { - case 'inline': - // For inline stylesheets, we don't want to use the $data as the array - // key as $data could be a very long string of CSS. - $css[] = $options; - break; - default: - // Local and external files must keep their name as the associative key - // so the same CSS file is not be added twice. - $css[$data] = $options; - } - } - - return $css; -} - -/** - * Returns a themed representation of all stylesheets that should be attached to the page. - * - * It loads the CSS in order, with 'module' first, then 'theme' afterwards. - * This ensures proper cascading of styles so themes can easily override - * module styles through CSS selectors. - * - * Themes may replace module-defined CSS files by adding a stylesheet with the - * same filename. For example, themes/bartik/system-menus.css would replace - * modules/system/system-menus.css. This allows themes to override complete - * CSS files, rather than specific selectors, when necessary. - * - * If the original CSS file is being overridden by a theme, the theme is - * responsible for supplying an accompanying RTL CSS file to replace the - * module's. - * - * @param $css - * (optional) An array of CSS files. If no array is provided, the default - * stylesheets array is used instead. - * @param $skip_alter - * (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 - * been altered. - * - * @return - * A string of XHTML CSS tags. - * - * @see drupal_add_css() - */ -function drupal_get_css($css = NULL, $skip_alter = FALSE) { - if (!isset($css)) { - $css = drupal_add_css(); - } - - // Allow modules and themes to alter the CSS items. - if (!$skip_alter) { - drupal_alter('css', $css); - } - - // Sort CSS items, so that they appear in the correct order. - uasort($css, 'drupal_sort_css_js'); - - // Remove the overridden CSS files. Later CSS files override former ones. - $previous_item = array(); - foreach ($css as $key => $item) { - if ($item['type'] == 'file') { - // If defined, force a unique basename for this file. - $basename = isset($item['basename']) ? $item['basename'] : basename($item['data']); - if (isset($previous_item[$basename])) { - // Remove the previous item that shared the same base name. - unset($css[$previous_item[$basename]]); - } - $previous_item[$basename] = $key; - } - } - - // Render the HTML needed to load the CSS. - $styles = array( - '#type' => 'styles', - '#items' => $css, - ); - - // Provide the page with information about the individual CSS files used, - // information not otherwise available when CSS aggregation is enabled. - $setting['ajaxPageState']['css'] = array_fill_keys(array_keys($css), 1); - $styles['#attached']['js'][] = array('type' => 'setting', 'data' => $setting); - - return drupal_render($styles); -} - -/** - * Function used by uasort to sort the array structures returned by drupal_add_css() and drupal_add_js(). - * - * This sort order helps optimize front-end performance while providing modules - * and themes with the necessary control for ordering the CSS and JavaScript - * appearing on a page. - */ -function drupal_sort_css_js($a, $b) { - // 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 - // all items in the CSS_THEME group. Modules may create additional groups by - // defining their own constants. - if ($a['group'] < $b['group']) { - return -1; - } - elseif ($a['group'] > $b['group']) { - return 1; - } - // Within a group, order all infrequently needed, page-specific files after - // common files needed throughout the website. Separating this way allows for - // the aggregate file generated for all of the common files to be reused - // across a site visit without being cut by a page using a less common file. - elseif ($a['every_page'] && !$b['every_page']) { - return -1; - } - elseif (!$a['every_page'] && $b['every_page']) { - return 1; - } - // Finally, order by weight. - elseif ($a['weight'] < $b['weight']) { - return -1; - } - elseif ($a['weight'] > $b['weight']) { - return 1; - } - else { - return 0; - } -} - -/** - * Default callback to group CSS items. - * - * This function arranges the CSS items that are in the #items property of the - * styles element into groups. Arranging the CSS items into groups serves two - * purposes. When aggregation is enabled, files within a group are aggregated - * into a single file, significantly improving page loading performance by - * minimizing network traffic overhead. When aggregation is disabled, grouping - * allows multiple files to be loaded from a single STYLE tag, enabling sites - * with many modules enabled or a complex theme being used to stay within IE's - * 31 CSS inclusion tag limit: http://drupal.org/node/228818. - * - * This function puts multiple items into the same group if they are groupable - * and if they are for the same 'media' and 'browsers'. Items of the 'file' type - * are groupable if their 'preprocess' flag is TRUE, items of the 'inline' type - * 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 - * their relative order. This requirement may result in multiple groups for the - * same type, media, and browsers, if needed to accommodate other items in - * between. - * - * @param $css - * An array of CSS items, as returned by drupal_add_css(), but after - * alteration performed by drupal_get_css(). - * - * @return - * An array of CSS groups. Each group contains the same keys (e.g., 'media', - * 'data', etc.) as a CSS item from the $css parameter, with the value of - * each key applying to the group as a whole. Each group also contains an - * 'items' key, which is the subset of items from $css that are in the group. - * - * @see drupal_pre_render_styles() - */ -function drupal_group_css($css) { - $groups = array(); - // If a group can contain multiple items, we track the information that must - // be the same for each item in the group, so that when we iterate the next - // item, we can determine if it can be put into the current group, or if a - // new group needs to be made for it. - $current_group_keys = NULL; - // When creating a new group, we pre-increment $i, so by initializing it to - // -1, the first group will have index 0. - $i = -1; - foreach ($css as $item) { - // The browsers for which the CSS item needs to be loaded is part of the - // information that determines when a new group is needed, but the order of - // keys in the array doesn't matter, and we don't want a new group if all - // that's different is that order. - ksort($item['browsers']); - - // If the item can be grouped with other items, set $group_keys to an array - // of information that must be the same for all items in its group. If the - // item can't be grouped with other items, set $group_keys to FALSE. We - // put items into a group that can be aggregated together: whether they will - // be aggregated is up to the _drupal_css_aggregate() function or an - // override of that function specified in hook_css_alter(), but regardless - // of the details of that function, a group represents items that can be - // aggregated. Since a group may be rendered with a single HTML tag, all - // items in the group must share the same information that would need to be - // part of that HTML tag. - switch ($item['type']) { - case 'file': - // Group file items if their 'preprocess' flag is TRUE. - // Help ensure maximum reuse of aggregate files by only grouping - // together items that share the same 'group' value and 'every_page' - // flag. See drupal_add_css() for details about that. - $group_keys = $item['preprocess'] ? array($item['type'], $item['group'], $item['every_page'], $item['media'], $item['browsers']) : FALSE; - break; - case 'inline': - // Always group inline items. - $group_keys = array($item['type'], $item['media'], $item['browsers']); - break; - case 'external': - // Do not group external items. - $group_keys = FALSE; - break; - } - - // If the group keys don't match the most recent group we're working with, - // then a new group must be made. - if ($group_keys !== $current_group_keys) { - $i++; - // Initialize the new group with the same properties as the first item - // being placed into it. The item's 'data' and 'weight' properties are - // unique to the item and should not be carried over to the group. - $groups[$i] = $item; - unset($groups[$i]['data'], $groups[$i]['weight']); - $groups[$i]['items'] = array(); - $current_group_keys = $group_keys ? $group_keys : NULL; - } - - // Add the item to the current group. - $groups[$i]['items'][] = $item; - } - return $groups; -} - -/** - * Default callback to aggregate CSS files and inline content. - * - * Having the browser load fewer CSS files results in much faster page loads - * than when it loads many small files. This function aggregates files within - * the same group into a single file unless the site-wide setting to do so is - * disabled (commonly the case during site development). To optimize download, - * it also compresses the aggregate files by removing comments, whitespace, and - * other unnecessary content. Additionally, this functions aggregates inline - * content together, regardless of the site-wide aggregation setting. - * - * @param $css_groups - * An array of CSS groups as returned by drupal_group_css(). This function - * modifies the group's 'data' property for each group that is aggregated. - * - * @see drupal_group_css() - * @see drupal_pre_render_styles() - */ -function drupal_aggregate_css(&$css_groups) { - $preprocess_css = (variable_get('preprocess_css', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')); - - // For each group that needs aggregation, aggregate its items. - foreach ($css_groups as $key => $group) { - switch ($group['type']) { - // If a file group can be aggregated into a single file, do so, and set - // the group's data property to the file path of the aggregate file. - case 'file': - if ($group['preprocess'] && $preprocess_css) { - $css_groups[$key]['data'] = drupal_build_css_cache($group['items']); - } - break; - // Aggregate all inline CSS content into the group's data property. - case 'inline': - $css_groups[$key]['data'] = ''; - foreach ($group['items'] as $item) { - $css_groups[$key]['data'] .= drupal_load_stylesheet_content($item['data'], $item['preprocess']); - } - break; - } - } -} - -/** - * #pre_render callback to add the elements needed for CSS tags to be rendered. - * - * For production websites, LINK tags are preferable to STYLE tags with @import - * statements, because: - * - They are the standard tag intended for linking to a resource. - * - On Firefox 2 and perhaps other browsers, CSS files included with @import - * statements don't get saved when saving the complete web page for offline - * use: http://drupal.org/node/145218. - * - On IE, if only LINK tags and no @import statements are used, all the CSS - * files are downloaded in parallel, resulting in faster page load, but if - * @import statements are used and span across multiple STYLE tags, all the - * ones from one STYLE tag must be downloaded before downloading begins for - * the next STYLE tag. Furthermore, IE7 does not support media declaration on - * the @import statement, so multiple STYLE tags must be used when different - * files are for different media types. Non-IE browsers always download in - * parallel, so this is an IE-specific performance quirk: - * http://www.stevesouders.com/blog/2009/04/09/dont-use-import/. - * - * However, IE has an annoying limit of 31 total CSS inclusion tags - * (http://drupal.org/node/228818) and LINK tags are limited to one file per - * tag, whereas STYLE tags can contain multiple @import statements allowing - * multiple files to be loaded per tag. When CSS aggregation is disabled, a - * Drupal site can easily have more than 31 CSS files that need to be loaded, so - * using LINK tags exclusively would result in a site that would display - * incorrectly in IE. Depending on different needs, different strategies can be - * employed to decide when to use LINK tags and when to use STYLE tags. - * - * The strategy employed by this function is to use LINK tags for all aggregate - * files and for all files that cannot be aggregated (e.g., if 'preprocess' is - * set to FALSE or the type is 'external'), and to use STYLE tags for groups - * of files that could be aggregated together but aren't (e.g., if the site-wide - * aggregation setting is disabled). This results in all LINK tags when - * aggregation is enabled, a guarantee that as many or only slightly more tags - * are used with aggregation disabled than enabled (so that if the limit were to - * be crossed with aggregation enabled, the site developer would also notice the - * problem while aggregation is disabled), and an easy way for a developer to - * view HTML source while aggregation is disabled and know what files will be - * aggregated together when aggregation becomes enabled. - * - * This function evaluates the aggregation enabled/disabled condition on a group - * by group basis by testing whether an aggregate file has been made for the - * group rather than by testing the site-wide aggregation setting. This allows - * this function to work correctly even if modules have implemented custom - * logic for grouping and aggregating files. - * - * @param $element - * A render array containing: - * - '#items': The CSS items as returned by drupal_add_css() and altered by - * drupal_get_css(). - * - '#group_callback': A function to call to group #items to enable the use - * of fewer tags by aggregating files and/or using multiple @import - * statements within a single tag. - * - '#aggregate_callback': A function to call to aggregate the items within - * the groups arranged by the #group_callback function. - * - * @return - * A render array that will render to a string of XHTML CSS tags. - * - * @see drupal_get_css() - */ -function drupal_pre_render_styles($elements) { - // Group and aggregate the items. - if (isset($elements['#group_callback'])) { - $elements['#groups'] = $elements['#group_callback']($elements['#items']); - } - if (isset($elements['#aggregate_callback'])) { - $elements['#aggregate_callback']($elements['#groups']); - } - - // A dummy query-string is added to filenames, to gain control over - // browser-caching. The string changes on every update or full cache - // flush, forcing browsers to load a new copy of the files, as the - // URL changed. - $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. - $link_element_defaults = array( - '#type' => 'html_tag', - '#tag' => 'link', - '#attributes' => array( - 'type' => 'text/css', - 'rel' => 'stylesheet', - ), - ); - $style_element_defaults = array( - '#type' => 'html_tag', - '#tag' => 'style', - '#attributes' => array( - 'type' => 'text/css', - ), - ); - - // Loop through each group. - foreach ($elements['#groups'] as $group) { - switch ($group['type']) { - // For file items, there are three possibilites. - // - The group has been aggregated: in this case, output a LINK tag for - // the aggregate file. - // - The group can be aggregated but has not been (most likely because - // the site administrator disabled the site-wide setting): in this case, - // output as few STYLE tags for the group as possible, using @import - // statement for each file in the group. This enables us to stay within - // IE's limit of 31 total CSS inclusion tags. - // - The group contains items not eligible for aggregation (their - // 'preprocess' flag has been set to FALSE): in this case, output a LINK - // tag for each file. - case 'file': - // The group has been aggregated into a single file: output a LINK tag - // for the aggregate file. - if (isset($group['data'])) { - $element = $link_element_defaults; - $element['#attributes']['href'] = file_create_url($group['data']); - $element['#attributes']['media'] = $group['media']; - $element['#browsers'] = $group['browsers']; - $elements[] = $element; - } - // The group can be aggregated, but hasn't been: combine multiple items - // into as few STYLE tags as possible. - elseif ($group['preprocess']) { - $import = array(); - foreach ($group['items'] as $item) { - // A theme's .info file may have an entry for a file that doesn't - // exist as a way of overriding a module or base theme CSS file from - // being added to the page. Normally, file_exists() calls that need - // to run for every page request should be minimized, but this one - // is okay, because it only runs when CSS aggregation is disabled. - // On a server under heavy enough load that file_exists() calls need - // to be minimized, CSS aggregation should be enabled, in which case - // this code is not run. When aggregation is enabled, - // drupal_load_stylesheet() checks file_exists(), but only when - // building the aggregate file, which is then reused for many page - // requests. - if (file_exists($item['data'])) { - // The dummy query string needs to be added to the URL to control - // browser-caching. IE7 does not support a media type on the - // @import statement, so we instead specify the media for the - // group on the STYLE tag. - $import[] = '@import url("' . check_plain(file_create_url($item['data']) . '?' . $query_string) . '");'; - } - } - // In addition to IE's limit of 31 total CSS inclusion tags, it also - // has a limit of 31 @import statements per STYLE tag. - while (!empty($import)) { - $import_batch = array_slice($import, 0, 31); - $import = array_slice($import, 31); - $element = $style_element_defaults; - $element['#value'] = implode("\n", $import_batch); - $element['#attributes']['media'] = $group['media']; - $element['#browsers'] = $group['browsers']; - $elements[] = $element; - } - } - // The group contains items ineligible for aggregation: output a LINK - // tag for each file. - else { - foreach ($group['items'] as $item) { - $element = $link_element_defaults; - // We do not check file_exists() here, because this code runs for - // files whose 'preprocess' is set to FALSE, and therefore, even - // when aggregation is enabled, and we want to avoid needlessly - // taxing a server that may be under heavy load. The file_exists() - // performed above for files whose 'preprocess' is TRUE is done for - // the benefit of theme .info files, but code that deals with files - // whose 'preprocess' is FALSE is responsible for ensuring the file - // exists. - // The dummy query string needs to be added to the URL to control - // browser-caching. - $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?'; - $element['#attributes']['href'] = file_create_url($item['data']) . $query_string_separator . $query_string; - $element['#attributes']['media'] = $item['media']; - $element['#browsers'] = $group['browsers']; - $elements[] = $element; - } - } - break; - // For inline content, the 'data' property contains the CSS content. If - // the group's 'data' property is set, then output it in a single STYLE - // tag. Otherwise, output a separate STYLE tag for each item. - case 'inline': - if (isset($group['data'])) { - $element = $style_element_defaults; - $element['#value'] = $group['data']; - $element['#value_prefix'] = $embed_prefix; - $element['#value_suffix'] = $embed_suffix; - $element['#attributes']['media'] = $group['media']; - $element['#browsers'] = $group['browsers']; - $elements[] = $element; - } - else { - foreach ($group['items'] as $item) { - $element = $style_element_defaults; - $element['#value'] = $item['data']; - $element['#value_prefix'] = $embed_prefix; - $element['#value_suffix'] = $embed_suffix; - $element['#attributes']['media'] = $item['media']; - $element['#browsers'] = $group['browsers']; - $elements[] = $element; - } - } - break; - // Output a LINK tag for each external item. The item's 'data' property - // contains the full URL. - case 'external': - foreach ($group['items'] as $item) { - $element = $link_element_defaults; - $element['#attributes']['href'] = $item['data']; - $element['#attributes']['media'] = $item['media']; - $element['#browsers'] = $group['browsers']; - $elements[] = $element; - } - break; - } - } - - return $elements; -} - -/** - * Aggregates and optimizes CSS files into a cache file in the files directory. - * - * The file name for the CSS cache file is generated from the hash of the - * aggregated contents of the files in $css. This forces proxies and browsers - * to download new CSS when the CSS changes. - * - * The cache file name is retrieved on a page load via a lookup variable that - * contains an associative array. The array key is the hash of the file names - * in $css while the value is the cache file name. The cache file is generated - * in two cases. First, if there is no file name value for the key, which will - * happen if a new file name has been added to $css or after the lookup - * variable is emptied to force a rebuild of the cache. Second, the cache - * file is generated if it is missing on disk. Old cache files are not deleted - * immediately when the lookup variable is emptied, but are deleted after a set - * period by drupal_delete_file_if_stale(). This ensures that files referenced - * by a cached page will still be available. - * - * @param $css - * An array of CSS files to aggregate and compress into one file. - * - * @return - * The URI of the CSS cache file, or FALSE if the file could not be saved. - */ -function drupal_build_css_cache($css) { - $data = ''; - $uri = ''; - $map = variable_get('drupal_css_cache_files', array()); - $key = hash('sha256', serialize($css)); - if (isset($map[$key])) { - $uri = $map[$key]; - } - - if (empty($uri) || !file_exists($uri)) { - // Build aggregate CSS file. - foreach ($css as $stylesheet) { - // Only 'file' stylesheets can be aggregated. - if ($stylesheet['type'] == 'file') { - $contents = drupal_load_stylesheet($stylesheet['data'], TRUE); - - // Build the base URL of this CSS file: start with the full URL. - $css_base_url = file_create_url($stylesheet['data']); - // 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); - } - } - - // Per the W3C specification at http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, - // @import rules must proceed any other style, so we move those to the top. - $regexp = '/@import[^;]+;/i'; - preg_match_all($regexp, $data, $matches); - $data = preg_replace($regexp, '', $data); - $data = implode('', $matches[0]) . $data; - - // Prefix filename to prevent blocking by firewalls which reject files - // starting with "ad*". - $filename = 'css_' . drupal_hash_base64($data) . '.css'; - // Create the css/ within the files folder. - $csspath = 'public://css'; - $uri = $csspath . '/' . $filename; - // Create the CSS file. - file_prepare_directory($csspath, FILE_CREATE_DIRECTORY); - if (!file_exists($uri) && !file_unmanaged_save_data($data, $uri, FILE_EXISTS_REPLACE)) { - return FALSE; - } - // If CSS gzip compression is enabled, clean URLs are enabled (which means - // that rewrite rules are working) and the zlib extension is available then - // create a gzipped version of this file. This file is served conditionally - // to browsers that accept gzip using .htaccess rules. - if (variable_get('css_gzip_compression', TRUE) && variable_get('clean_url', 0) && extension_loaded('zlib')) { - if (!file_exists($uri . '.gz') && !file_unmanaged_save_data(gzencode($data, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE)) { - return FALSE; - } - } - // Save the updated map. - $map[$key] = $uri; - variable_set('drupal_css_cache_files', $map); - } - return $uri; -} - -/** - * Helper function for drupal_build_css_cache(). - * - * This function will prefix all paths within a CSS file. - */ -function _drupal_build_css_path($matches, $base = NULL) { - $_base = &drupal_static(__FUNCTION__); - // Store base path for preg_replace_callback. - if (isset($base)) { - $_base = $base; - } - - // Prefix with base and remove '../' segments where possible. - $path = $_base . $matches[1]; - $last = ''; - while ($path != $last) { - $last = $path; - $path = preg_replace('`(^|/)(?!\.\./)([^/]+)/\.\./`', '$1', $path); - } - return 'url(' . $path . ')'; -} - -/** - * Loads the stylesheet and resolves all @import commands. - * - * Loads a stylesheet and replaces @import commands with the contents of the - * imported file. Use this instead of file_get_contents when processing - * stylesheets. - * - * The returned contents are compressed removing white space and comments only - * when CSS aggregation is enabled. This optimization will not apply for - * color.module enabled themes with CSS aggregation turned off. - * - * @param $file - * Name of the stylesheet to be processed. - * @param $optimize - * Defines if CSS contents should be compressed or not. - * @param $reset_basepath - * Used internally to facilitate recursive resolution of @import commands. - * - * @return - * Contents of the stylesheet, including any resolved @import commands. - */ -function drupal_load_stylesheet($file, $optimize = NULL, $reset_basepath = TRUE) { - // These statics are not cache variables, so we don't use drupal_static(). - static $_optimize, $basepath; - if ($reset_basepath) { - $basepath = ''; - } - // Store the value of $optimize for preg_replace_callback with nested - // @import loops. - if (isset($optimize)) { - $_optimize = $optimize; - } - - // Stylesheets are relative one to each other. Start by adding a base path - // prefix provided by the parent stylesheet (if necessary). - if ($basepath && !file_uri_scheme($file)) { - $file = $basepath . '/' . $file; - } - $basepath = dirname($file); - - // Load the CSS stylesheet. We suppress errors because themes may specify - // 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 ''; -} - -/** - * Process the contents of a stylesheet for aggregation. - * - * @param $contents - * The contents of the stylesheet. - * @param $optimize - * (optional) Boolean whether CSS contents should be minified. Defaults to - * FALSE. - * @return - * Contents of the stylesheet including the imported stylesheets. - */ -function drupal_load_stylesheet_content($contents, $optimize = FALSE) { - // Remove multiple charset declarations for standards compliance (and fixing Safari problems). - $contents = preg_replace('/^@charset\s+[\'"](\S*)\b[\'"];/i', '', $contents); - - if ($optimize) { - // Perform some safe CSS optimizations. - // Regexp to match comment blocks. - $comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'; - // Regexp to match double quoted strings. - $double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"'; - // Regexp to match single quoted strings. - $single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'"; - // Strip all comment blocks, but keep double/single quoted strings. - $contents = preg_replace( - "<($double_quot|$single_quot)|$comment>Ss", - "$1", - $contents - ); - // Remove certain whitespace. - // There are different conditions for removing leading and trailing - // whitespace. - // @see http://php.net/manual/en/regexp.reference.subpatterns.php - $contents = preg_replace('< - # Strip leading and trailing whitespace. - \s*([@{};,])\s* - # Strip only leading whitespace from: - # - Closing parenthesis: Retain "@media (bar) and foo". - | \s+([\)]) - # Strip only trailing whitespace from: - # - Opening parenthesis: Retain "@media (bar) and foo". - # - Colon: Retain :pseudo-selectors. - | ([\(:])\s+ - >xS', - // 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 - ); - // End the file with a new line. - $contents = trim($contents); - $contents .= "\n"; - } - - // Replaces @import commands with the actual stylesheet content. - // This happens recursively but omits external files. - $contents = preg_replace_callback('/@import\s*(?:url\(\s*)?[\'"]?(?![a-z]+:)([^\'"\()]+)[\'"]?\s*\)?\s*;/', '_drupal_load_stylesheet', $contents); - return $contents; -} - -/** - * Loads stylesheets recursively and returns contents with corrected paths. - * - * This function is used for recursive loading of stylesheets and - * returns the stylesheet content with all url() paths corrected. - */ -function _drupal_load_stylesheet($matches) { - $filename = $matches[1]; - // Load the imported stylesheet and replace @import commands in there as well. - $file = drupal_load_stylesheet($filename, NULL, FALSE); - - // Determine the file's directory. - $directory = dirname($filename); - // If the file is in the current directory, make sure '.' doesn't appear in - // the url() path. - $directory = $directory == '.' ? '' : $directory .'/'; - - // Alter all internal url() paths. Leave external paths alone. We don't need - // to normalize absolute paths here (i.e. remove folder/... segments) because - // that will be done later. - return preg_replace('/url\(\s*([\'"]?)(?![a-z]+:|\/+)/i', 'url(\1'. $directory, $file); -} - -/** - * Deletes old cached CSS files. - */ -function drupal_clear_css_cache() { - variable_del('drupal_css_cache_files'); - file_scan_directory('public://css', '/.*/', array('callback' => 'drupal_delete_file_if_stale')); -} - -/** - * Callback to delete files modified more than a set time ago. - */ -function drupal_delete_file_if_stale($uri) { - // Default stale file threshold is 30 days. - if (REQUEST_TIME - filemtime($uri) > variable_get('drupal_stale_file_threshold', 2592000)) { - file_unmanaged_delete($uri); - } -} - -/** - * Prepare a string for use as a valid CSS identifier (element, class or ID name). - * - * http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for valid - * CSS identifiers (including element names, classes, and IDs in selectors.) - * - * @param $identifier - * The identifier to clean. - * @param $filter - * An array of string replacements to use on the identifier. - * @return - * The cleaned identifier. - */ -function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_' => '-', '/' => '-', '[' => '-', ']' => '')) { - // By default, we filter using Drupal's coding standards. - $identifier = strtr($identifier, $filter); - - // Valid characters in a CSS identifier are: - // - the hyphen (U+002D) - // - a-z (U+0030 - U+0039) - // - A-Z (U+0041 - U+005A) - // - the underscore (U+005F) - // - 0-9 (U+0061 - U+007A) - // - ISO 10646 characters U+00A1 and higher - // We strip out any character not in the above list. - $identifier = preg_replace('/[^\x{002D}\x{0030}-\x{0039}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]/u', '', $identifier); - - return $identifier; -} - -/** - * Prepare a string for use as a valid class name. - * - * Do not pass one string containing multiple classes as they will be - * incorrectly concatenated with dashes, i.e. "one two" will become "one-two". - * - * @param $class - * The class name to clean. - * @return - * The cleaned class name. - */ -function drupal_html_class($class) { - return drupal_clean_css_identifier(drupal_strtolower($class)); -} - -/** - * Prepare a string for use as a valid HTML ID and guarantee uniqueness. - * - * 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, - * blocks, and other content to be output multiple times on the same page, - * without breaking (X)HTML validation. - * - * 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 - * this function and instead should rely on manually added CSS classes or - * similarly reliable constructs. - * - * Two consecutive hyphens separate the counter from the original ID. To manage - * 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 - * prime this function's cache upon first invocation. - * - * To allow reverse-parsing of IDs submitted via Ajax, any multiple consecutive - * hyphens in the originally passed $id are replaced with a single hyphen. - * - * @param $id - * The ID to clean. - * - * @return - * The cleaned ID. - */ -function drupal_html_id($id) { - // 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 - // unique for the fully merged content. Therefore, initialize $seen_ids to - // take into account IDs that are already in use on the base page. - $seen_ids_init = &drupal_static(__FUNCTION__ . ':init'); - if (!isset($seen_ids_init)) { - // Ideally, Drupal would provide an API to persist state information about - // prior page requests in the database, and we'd be able to add this - // function's $seen_ids static variable to that state information in order - // 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 - // 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 - // the raw POST data is cast to a number before being returned by this - // function, this usage is safe. - if (empty($_POST['ajax_html_ids'])) { - $seen_ids_init = array(); - } - else { - // This function ensures uniqueness by appending a counter to the base id - // requested by the calling function after the first occurrence of that - // requested id. $_POST['ajax_html_ids'] contains the ids as they were - // returned by this function, potentially with the appended counter, so - // we parse that to reconstruct the $seen_ids array. - foreach ($_POST['ajax_html_ids'] as $seen_id) { - // We rely on '--' being used solely for separating a base id from the - // counter, which this function ensures when returning an id. - $parts = explode('--', $seen_id, 2); - if (!empty($parts[1]) && is_numeric($parts[1])) { - list($seen_id, $i) = $parts; - } - else { - $i = 1; - } - if (!isset($seen_ids_init[$seen_id]) || ($i > $seen_ids_init[$seen_id])) { - $seen_ids_init[$seen_id] = $i; - } - } - } - } - $seen_ids = &drupal_static(__FUNCTION__, $seen_ids_init); - - $id = strtr(drupal_strtolower($id), array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); - - // As defined in http://www.w3.org/TR/html4/types.html#type-name, HTML IDs can - // only contain letters, digits ([0-9]), hyphens ("-"), underscores ("_"), - // colons (":"), and periods ("."). We strip out any character not in that - // list. Note that the CSS spec doesn't allow colons or periods in identifiers - // (http://www.w3.org/TR/CSS21/syndata.html#characters), so we strip those two - // characters as well. - $id = preg_replace('/[^A-Za-z0-9\-_]/', '', $id); - - // Removing multiple consecutive hyphens. - $id = preg_replace('/\-+/', '-', $id); - // Ensure IDs are unique by appending a counter after the first occurrence. - // 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 - // return unique IDs and also helps us re-create the $seen_ids array during - // Ajax requests. - if (isset($seen_ids[$id])) { - $id = $id . '--' . ++$seen_ids[$id]; - } - else { - $seen_ids[$id] = 1; - } - - return $id; -} - -/** - * Provides a standard HTML class name that identifies a page region. - * - * It is recommended that template preprocess functions apply this class to any - * page region that is output by the theme (Drupal core already handles this in - * the standard template preprocess implementation). Standardizing the class - * names in this way allows modules to implement certain features, such as - * drag-and-drop or dynamic Ajax loading, in a theme-independent way. - * - * @param $region - * The name of the page region (for example, 'page_top' or 'content'). - * - * @return - * An HTML class that identifies the region (for example, 'region-page-top' - * or 'region-content'). - * - * @see template_preprocess_region() - */ -function drupal_region_class($region) { - return drupal_html_class("region-$region"); -} - -/** - * Adds a JavaScript file, setting, or inline code to the page. - * - * The behavior of this function depends on the parameters it is called with. - * Generally, it handles the addition of JavaScript to the page, either as - * reference to an existing file or as inline code. The following actions can be - * performed using this function: - * - Add a file ('file'): Adds a reference to a JavaScript file to the page. - * - Add inline JavaScript code ('inline'): Executes a piece of JavaScript code - * on the current page by placing the code directly in the page (for example, - * to tell the user that a new message arrived, by opening a pop up, alert - * box, etc.). This should only be used for JavaScript that cannot be executed - * from a file. When adding inline code, make sure that you are not relying on - * $() being the jQuery function. Wrap your code in - * @code (function ($) {... })(jQuery); @endcode - * or use jQuery() instead of $(). - * - Add external JavaScript ('external'): Allows the inclusion of external - * JavaScript files that are not hosted on the local server. Note that these - * external JavaScript references do not get aggregated when preprocessing is - * on. - * - Add settings ('setting'): Adds settings to Drupal's global storage of - * JavaScript settings. Per-page settings are required by some modules to - * function properly. All settings will be accessible at Drupal.settings. - * - * Examples: - * @code - * drupal_add_js('core/misc/collapse.js'); - * drupal_add_js('core/misc/collapse.js', 'file'); - * drupal_add_js('jQuery(document).ready(function () { alert("Hello!"); });', 'inline'); - * drupal_add_js('jQuery(document).ready(function () { alert("Hello!"); });', - * array('type' => 'inline', 'scope' => 'footer', 'weight' => 5) - * ); - * drupal_add_js('http://example.com/example.js', 'external'); - * drupal_add_js(array('myModule' => array('key' => 'value')), 'setting'); - * @endcode - * - * Calling drupal_static_reset('drupal_add_js') will clear all JavaScript added - * so far. - * - * If JavaScript aggregation is enabled, all JavaScript files added with - * $options['preprocess'] set to TRUE will be merged into one aggregate file. - * Preprocessed inline JavaScript will not be aggregated into this single file. - * Externally hosted JavaScripts are never aggregated. - * - * The reason for aggregating the files is outlined quite thoroughly here: - * http://www.die.net/musings/page_load_time/ "Load fewer external objects. Due - * to request overhead, one bigger file just loads faster than two smaller ones - * half its size." - * - * $options['preprocess'] should be only set to TRUE when a file is required for - * 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 - * files are not needed on a page. This is normally done by calling - * drupal_add_js() in a hook_init() implementation. - * - * Non-preprocessed files should only be added to the page when they are - * actually needed. - * - * @param $data - * (optional) If given, the value depends on the $options parameter: - * - 'file': Path to the file relative to base_path(). - * - 'inline': The JavaScript code that should be placed in the given scope. - * - 'external': The absolute path to an external JavaScript file that is not - * hosted on the local server. These files will not be aggregated if - * JavaScript aggregation is enabled. - * - 'setting': An associative array with configuration options. The array is - * merged directly into Drupal.settings. All modules should wrap their - * actual configuration settings in another variable to prevent conflicts in - * 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 - * (optional) A string defining the type of JavaScript that is being added in - * the $data parameter ('file'/'setting'/'inline'/'external'), or an - * associative array. JavaScript settings should always pass the string - * 'setting' only. Other types can have the following elements in the array: - * - type: The type of JavaScript that is to be added to the page. Allowed - * values are 'file', 'inline', 'external' or 'setting'. Defaults - * to 'file'. - * - scope: The location in which you want to place the script. Possible - * values are 'header' or 'footer'. If your theme implements different - * regions, you can also use these. Defaults to 'header'. - * - group: A number identifying the group in which to add the JavaScript. - * Available constants are: - * - JS_LIBRARY: Any libraries, settings, or jQuery plugins. - * - JS_DEFAULT: Any module-layer JavaScript. - * - JS_THEME: Any theme-layer JavaScript. - * The group number serves as a weight: JavaScript within a lower weight - * group is presented on the page before JavaScript within a higher weight - * group. - * - every_page: For optimal front-end performance when aggregation is - * enabled, this should be set to TRUE if the JavaScript is present on every - * page of the website for users for whom it is present at all. This - * defaults to FALSE. It is set to TRUE for JavaScript files that are added - * via module and theme .info files. Modules that add JavaScript within - * hook_init() implementations, or from other code that ensures that the - * JavaScript is added to all website pages, should also set this flag to - * TRUE. All JavaScript files within the same group and that have the - * 'every_page' flag set to TRUE and do not have 'preprocess' set to FALSE - * are aggregated together into a single aggregate file, and that aggregate - * file can be reused across a user's entire site visit, leading to faster - * navigation between pages. However, JavaScript that is only needed on - * pages less frequently visited, can be added by code that only runs for - * those particular pages, and that code should not set the 'every_page' - * flag. This minimizes the size of the aggregate file that the user needs - * to download when first visiting the website. JavaScript without the - * 'every_page' flag is aggregated into a separate aggregate file. This - * other aggregate file is likely to change from page to page, and each new - * aggregate file needs to be downloaded when first encountered, so it - * should be kept relatively small by ensuring that most commonly needed - * JavaScript is added to every page. - * - weight: A number defining the order in which the JavaScript is added to - * the page relative to other JavaScript with the same 'scope', 'group', - * and 'every_page' value. In some cases, the order in which the JavaScript - * is presented on the page is very important. jQuery, for example, must be - * added to the page before any jQuery code is run, so jquery.js uses the - * JS_LIBRARY group and a weight of -20, jquery.once.js (a library drupal.js - * depends on) uses the JS_LIBRARY group and a weight of -19, drupal.js uses - * the JS_LIBRARY group and a weight of -1, other libraries use the - * JS_LIBRARY group and a weight of 0 or higher, and all other scripts use - * one of the other group constants. The exact ordering of JavaScript is as - * follows: - * - First by scope, with 'header' first, 'footer' last, and any other - * scopes provided by a custom theme coming in between, as determined by - * the theme. - * - Then by group. - * - Then by the 'every_page' flag, with TRUE coming before FALSE. - * - Then by weight. - * - Then by the order in which the JavaScript was added. For example, all - * else being the same, JavaScript added by a call to drupal_add_js() that - * happened later in the page request gets added to the page after one for - * which drupal_add_js() happened earlier in the page request. - * - defer: If set to TRUE, the defer attribute is set on the <script> - * tag. Defaults to FALSE. - * - cache: If set to FALSE, the JavaScript file is loaded anew on every page - * call; in other words, it is not cached. Used only when 'type' references - * a JavaScript file. Defaults to TRUE. - * - preprocess: If TRUE and JavaScript aggregation is enabled, the script - * file will be aggregated. Defaults to TRUE. - * - * @return - * The current array of JavaScript files, settings, and in-line code, - * including Drupal defaults, anything previously added with calls to - * drupal_add_js(), and this function call's additions. - * - * @see drupal_get_js() - */ -function drupal_add_js($data = NULL, $options = NULL) { - $javascript = &drupal_static(__FUNCTION__, array()); - - // Construct the options, taking the defaults into consideration. - if (isset($options)) { - if (!is_array($options)) { - $options = array('type' => $options); - } - } - else { - $options = array(); - } - $options += drupal_js_defaults($data); - - // Preprocess can only be set if caching is enabled. - $options['preprocess'] = $options['cache'] ? $options['preprocess'] : FALSE; - - // Tweak the weight so that files of the same weight are included in the - // order of the calls to drupal_add_js(). - $options['weight'] += count($javascript) / 1000; - - if (isset($data)) { - // Add jquery.js and drupal.js, as well as the basePath setting, the - // first time a JavaScript file is added. - 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( - 'settings' => array( - 'data' => array( - array('basePath' => base_path()), - array('pathPrefix' => empty($prefix) ? '' : $prefix), - ), - 'type' => 'setting', - 'scope' => 'header', - 'group' => JS_LIBRARY, - 'every_page' => TRUE, - 'weight' => 0, - ), - 'core/misc/drupal.js' => array( - 'data' => 'core/misc/drupal.js', - 'type' => 'file', - 'scope' => 'header', - 'group' => JS_LIBRARY, - 'every_page' => TRUE, - 'weight' => -1, - 'preprocess' => TRUE, - 'cache' => TRUE, - 'defer' => FALSE, - ), - ); - // Register all required libraries. - drupal_add_library('system', 'jquery', TRUE); - drupal_add_library('system', 'jquery.once', TRUE); - } - - switch ($options['type']) { - case 'setting': - // All JavaScript settings are placed in the header of the page with - // the library weight so that inline scripts appear afterwards. - $javascript['settings']['data'][] = $data; - break; - - case 'inline': - $javascript[] = $options; - break; - - default: // 'file' and 'external' - // Local and external files must keep their name as the associative key - // so the same JavaScript file is not added twice. - $javascript[$options['data']] = $options; - } - } - return $javascript; -} - -/** - * Constructs an array of the defaults that are used for JavaScript items. - * - * @param $data - * (optional) The default data parameter for the JavaScript item array. - * @see drupal_get_js() - * @see drupal_add_js() - */ -function drupal_js_defaults($data = NULL) { - return array( - 'type' => 'file', - 'group' => JS_DEFAULT, - 'every_page' => FALSE, - 'weight' => 0, - 'scope' => 'header', - 'cache' => TRUE, - 'defer' => FALSE, - 'preprocess' => TRUE, - 'version' => NULL, - 'data' => $data, - ); -} - -/** - * Returns a themed presentation of all JavaScript code for the current page. - * - * References to JavaScript files are placed in a certain order: first, all - * 'core' files, then all 'module' and finally all 'theme' JavaScript files - * are added to the page. Then, all settings are output, followed by 'inline' - * JavaScript code. If running update.php, all preprocessing is disabled. - * - * Note that hook_js_alter(&$javascript) is called during this function call - * to allow alterations of the JavaScript during its presentation. Calls to - * drupal_add_js() from hook_js_alter() will not be added to the output - * presentation. The correct way to add JavaScript during hook_js_alter() - * is to add another element to the $javascript array, deriving from - * drupal_js_defaults(). See locale_js_alter() for an example of this. - * - * @param $scope - * (optional) The scope for which the JavaScript rules should be returned. - * Defaults to 'header'. - * @param $javascript - * (optional) An array with all JavaScript code. Defaults to the default - * JavaScript array for the given scope. - * @param $skip_alter - * (optional) If set to TRUE, this function skips calling drupal_alter() on - * $javascript, useful when the calling function passes a $javascript array - * that has already been altered. - * @return - * All JavaScript code segments and includes for the scope as HTML tags. - * @see drupal_add_js() - * @see locale_js_alter() - * @see drupal_js_defaults() - */ -function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALSE) { - if (!isset($javascript)) { - $javascript = drupal_add_js(); - } - if (empty($javascript)) { - return ''; - } - - // Allow modules to alter the JavaScript. - if (!$skip_alter) { - drupal_alter('js', $javascript); - } - - // Filter out elements of the given scope. - $items = array(); - foreach ($javascript as $key => $item) { - if ($item['scope'] == $scope) { - $items[$key] = $item; - } - } - - $output = ''; - // The index counter is used to keep aggregated and non-aggregated files in - // order by weight. - $index = 1; - $processed = array(); - $files = array(); - $preprocess_js = (variable_get('preprocess_js', FALSE) && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')); - - // A dummy query-string is added to filenames, to gain control over - // browser-caching. The string changes on every update or full cache - // flush, forcing browsers to load a new copy of the files, as the - // URL changed. Files that should not be cached (see drupal_add_js()) - // get REQUEST_TIME as query-string instead, to enforce reload on every - // page request. - $default_query_string = variable_get('css_js_query_string', '0'); - - // For inline JavaScript to validate as XHTML, all JavaScript 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"; - - // 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. - $js_version_string = variable_get('drupal_js_version_query_string', 'v='); - - // Sort the JavaScript so that it appears in the correct order. - uasort($items, 'drupal_sort_css_js'); - - // Provide the page with information about the individual JavaScript files - // used, information not otherwise available when aggregation is enabled. - $setting['ajaxPageState']['js'] = array_fill_keys(array_keys($items), 1); - unset($setting['ajaxPageState']['js']['settings']); - drupal_add_js($setting, 'setting'); - - // If we're outputting the header scope, then this might be the final time - // that drupal_get_js() is running, so add the setting to this output as well - // as to the drupal_add_js() cache. If $items['settings'] doesn't exist, it's - // because drupal_get_js() was intentionally passed a $javascript argument - // stripped off settings, potentially in order to override how settings get - // output, so in this case, do not add the setting to this output. - if ($scope == 'header' && isset($items['settings'])) { - $items['settings']['data'][] = $setting; - } - - // Loop through the JavaScript to construct the rendered output. - $element = array( - '#tag' => 'script', - '#value' => '', - '#attributes' => array( - 'type' => 'text/javascript', - ), - ); - foreach ($items as $item) { - $query_string = empty($item['version']) ? $default_query_string : $js_version_string . $item['version']; - - switch ($item['type']) { - case 'setting': - $js_element = $element; - $js_element['#value_prefix'] = $embed_prefix; - $js_element['#value'] = 'jQuery.extend(Drupal.settings, ' . drupal_json_encode(drupal_array_merge_deep_array($item['data'])) . ");"; - $js_element['#value_suffix'] = $embed_suffix; - $output .= theme('html_tag', array('element' => $js_element)); - break; - - case 'inline': - $js_element = $element; - if ($item['defer']) { - $js_element['#attributes']['defer'] = 'defer'; - } - $js_element['#value_prefix'] = $embed_prefix; - $js_element['#value'] = $item['data']; - $js_element['#value_suffix'] = $embed_suffix; - $processed[$index++] = theme('html_tag', array('element' => $js_element)); - break; - - case 'file': - $js_element = $element; - if (!$item['preprocess'] || !$preprocess_js) { - if ($item['defer']) { - $js_element['#attributes']['defer'] = 'defer'; - } - $query_string_separator = (strpos($item['data'], '?') !== FALSE) ? '&' : '?'; - $js_element['#attributes']['src'] = file_create_url($item['data']) . $query_string_separator . ($item['cache'] ? $query_string : REQUEST_TIME); - $processed[$index++] = theme('html_tag', array('element' => $js_element)); - } - else { - // By increasing the index for each aggregated file, we maintain - // the relative ordering of JS by weight. We also set the key such - // that groups are split by items sharing the same 'group' value and - // 'every_page' flag. While this potentially results in more aggregate - // files, it helps make each one more reusable across a site visit, - // leading to better front-end performance of a website as a whole. - // See drupal_add_js() for details. - $key = 'aggregate_' . $item['group'] . '_' . $item['every_page'] . '_' . $index; - $processed[$key] = ''; - $files[$key][$item['data']] = $item; - } - break; - - case 'external': - $js_element = $element; - // Preprocessing for external JavaScript files is ignored. - if ($item['defer']) { - $js_element['#attributes']['defer'] = 'defer'; - } - $js_element['#attributes']['src'] = $item['data']; - $processed[$index++] = theme('html_tag', array('element' => $js_element)); - break; - } - } - - // Aggregate any remaining JS files that haven't already been output. - if ($preprocess_js && count($files) > 0) { - foreach ($files as $key => $file_set) { - $uri = drupal_build_js_cache($file_set); - // Only include the file if was written successfully. Errors are logged - // using watchdog. - if ($uri) { - $preprocess_file = file_create_url($uri); - $js_element = $element; - $js_element['#attributes']['src'] = $preprocess_file; - $processed[$key] = theme('html_tag', array('element' => $js_element)); - } - } - } - - // Keep the order of JS files consistent as some are preprocessed and others are not. - // Make sure any inline or JS setting variables appear last after libraries have loaded. - return implode('', $processed) . $output; -} - -/** - * Adds attachments to a render() structure. - * - * Libraries, JavaScript, CSS and other types of custom structures are attached - * to elements using the #attached property. The #attached property is an - * associative array, where the keys are the the attachment types and the values - * are the attached data. For example: - * @code - * $build['#attached'] = array( - * 'js' => array(drupal_get_path('module', 'taxonomy') . '/taxonomy.js'), - * 'css' => array(drupal_get_path('module', 'taxonomy') . '/taxonomy.css'), - * ); - * @endcode - * - * '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 - * callback function and each value an array of arguments. For example: - * @code - * $build['#attached']['drupal_add_http_header'] = array( - * array('Content-Type', 'application/rss+xml; charset=utf-8'), - * ); - * @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 - * The structured array describing the data being rendered. - * @param $group - * The default group of JavaScript and CSS being added. This is only applied - * to the stylesheets and JavaScript items that don't have an explicit group - * assigned to them. - * @param $dependency_check - * 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 or more - * 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 - * FALSE if there were any missing library dependencies; TRUE if all library - * dependencies were met. - * - * @see drupal_add_library() - * @see drupal_add_js() - * @see drupal_add_css() - * @see drupal_render() - */ -function drupal_process_attached($elements, $group = JS_DEFAULT, $dependency_check = FALSE, $every_page = NULL) { - // Add defaults to the special attached structures that should be processed differently. - $elements['#attached'] += array( - 'library' => array(), - 'js' => array(), - 'css' => array(), - ); - - // Add the libraries first. - $success = TRUE; - foreach ($elements['#attached']['library'] as $library) { - if (drupal_add_library($library[0], $library[1], $every_page) === FALSE) { - $success = FALSE; - // Exit if the dependency is missing. - if ($dependency_check) { - return $success; - } - } - } - unset($elements['#attached']['library']); - - // Add both the JavaScript and the CSS. - // The parameters for drupal_add_js() and drupal_add_css() require special - // handling. - foreach (array('js', 'css') as $type) { - foreach ($elements['#attached'][$type] as $data => $options) { - // If the value is not an array, it's a filename and passed as first - // (and only) argument. - if (!is_array($options)) { - $data = $options; - $options = NULL; - } - // In some cases, the first parameter ($data) is an array. Arrays can't be - // passed as keys in PHP, so we have to get $data from the value array. - if (is_numeric($data)) { - $data = $options['data']; - unset($options['data']); - } - // Apply the default group if it isn't explicitly given. - if (!isset($options['group'])) { - $options['group'] = $group; - } - // Set the every_page flag if one was passed. - if (isset($every_page)) { - $options['every_page'] = $every_page; - } - call_user_func('drupal_add_' . $type, $data, $options); - } - unset($elements['#attached'][$type]); - } - - // Add additional types of attachments specified in the render() structure. - // Libraries, JavaScript and CSS have been added already, as they require - // special handling. - foreach ($elements['#attached'] as $callback => $options) { - if (function_exists($callback)) { - foreach ($elements['#attached'][$callback] as $args) { - call_user_func_array($callback, $args); - } - } - } - - return $success; -} - -/** - * Adds JavaScript to change the state of an element based on another element. - * - * A "state" means a certain property on a DOM element, such as "visible" or - * "checked". A state can be applied to an element, depending on the state of - * another element on the page. In general, states depend on HTML attributes and - * DOM element properties, which change due to user interaction. - * - * Since states are driven by JavaScript only, it is important to understand - * that all states are applied on presentation only, none of the states force - * any server-side logic, and that they will not be applied for site visitors - * without JavaScript support. All modules implementing states have to make - * sure that the intended logic also works without JavaScript being enabled. - * - * #states is an associative array in the form of: - * @code - * array( - * STATE1 => CONDITIONS_ARRAY1, - * STATE2 => CONDITIONS_ARRAY2, - * ... - * ) - * @endcode - * Each key is the name of a state to apply to the element, such as 'visible'. - * Each value is a list of conditions that denote when the state should be - * applied. - * - * Multiple different states may be specified to act on complex conditions: - * @code - * array( - * 'visible' => CONDITIONS, - * 'checked' => OTHER_CONDITIONS, - * ) - * @endcode - * - * Every condition is a key/value pair, whose key is a jQuery selector that - * denotes another element on the page, and whose value is an array of - * conditions, which must bet met on that element: - * @code - * array( - * 'visible' => array( - * JQUERY_SELECTOR => REMOTE_CONDITIONS, - * JQUERY_SELECTOR => REMOTE_CONDITIONS, - * ... - * ), - * ) - * @endcode - * All conditions must be met for the state to be applied. - * - * Each remote condition is a key/value pair specifying conditions on the other - * element that need to be met to apply the state to the element: - * @code - * array( - * 'visible' => array( - * ':input[name="remote_checkbox"]' => array('checked' => TRUE), - * ), - * ) - * @endcode - * - * For example, to show a textfield only when a checkbox is checked: - * @code - * $form['toggle_me'] = array( - * '#type' => 'checkbox', - * '#title' => t('Tick this box to type'), - * ); - * $form['settings'] = array( - * '#type' => 'textfield', - * '#states' => array( - * // Only show this field when the 'toggle_me' checkbox is enabled. - * 'visible' => array( - * ':input[name="toggle_me"]' => array('checked' => TRUE), - * ), - * ), - * ); - * @endcode - * - * The following states may be applied to an element: - * - enabled - * - disabled - * - required - * - optional - * - visible - * - invisible - * - checked - * - unchecked - * - expanded - * - collapsed - * - * The following states may be used in remote conditions: - * - empty - * - filled - * - checked - * - unchecked - * - expanded - * - collapsed - * - value - * - * The following states exist for both elements and remote conditions, but are - * not fully implemented and may not change anything on the element: - * - relevant - * - irrelevant - * - valid - * - invalid - * - touched - * - untouched - * - readwrite - * - readonly - * - * When referencing select lists and radio buttons in remote conditions, a - * 'value' condition must be used: - * @code - * '#states' => array( - * // Show the settings if 'bar' has been selected for 'foo'. - * 'visible' => array( - * ':input[name="foo"]' => array('value' => 'bar'), - * ), - * ), - * @endcode - * - * @param $elements - * A renderable array element having a #states property as described above. - * - * @see form_example_states_form() - */ -function drupal_process_states(&$elements) { - $elements['#attached']['library'][] = array('system', 'drupal.states'); - $elements['#attached']['js'][] = array( - 'type' => 'setting', - 'data' => array('states' => array('#' . $elements['#id'] => $elements['#states'])), - ); -} - -/** - * Adds multiple JavaScript or CSS files at the same time. - * - * A library defines a set of JavaScript and/or CSS files, optionally using - * settings, and optionally requiring another library. For example, a library - * 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 - * depending module, without having to add all files of the library separately. - * Each library is only loaded once. - * - * @param $module - * The name of the module that registered the library. - * @param $name - * 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 - * TRUE if the library was successfully added; FALSE if the library or one of - * its dependencies could not be added. - * - * @see drupal_get_library() - * @see hook_library_info() - * @see hook_library_info_alter() - */ -function drupal_add_library($module, $name, $every_page = NULL) { - $added = &drupal_static(__FUNCTION__, array()); - - // Only process the library if it exists and it was not added already. - if (!isset($added[$module][$name])) { - if ($library = drupal_get_library($module, $name)) { - // Add all components within the library. - $elements['#attached'] = array( - 'library' => $library['dependencies'], - 'js' => $library['js'], - 'css' => $library['css'], - ); - $added[$module][$name] = drupal_process_attached($elements, JS_LIBRARY, TRUE, $every_page); - } - else { - // Requested library does not exist. - $added[$module][$name] = FALSE; - } - } - - return $added[$module][$name]; -} - -/** - * Retrieves information for a JavaScript/CSS library. - * - * Library information is statically cached. Libraries are keyed by module for - * several reasons: - * - Libraries are not unique. Multiple modules might ship with the same library - * in a different version or variant. This registry cannot (and does not - * attempt to) prevent library conflicts. - * - Modules implementing and thereby depending on a library that is registered - * by another module can only rely on that module's library. - * - Two (or more) modules can still register the same library and use it - * without conflicts in case the libraries are loaded on certain pages only. - * - * @param $module - * The name of a module that registered a library. - * @param $name - * (optional) The name of a registered library to retrieve. By default, all - * libraries registered by $module are returned. - * - * @return - * 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 hook_library_info() - * @see hook_library_info_alter() - * - * @todo The purpose of drupal_get_*() is completely different to other page - * requisite API functions; find and use a different name. - */ -function drupal_get_library($module, $name = NULL) { - $libraries = &drupal_static(__FUNCTION__, array()); - - if (!isset($libraries[$module])) { - // Retrieve all libraries associated with the module. - $module_libraries = module_invoke($module, 'library_info'); - if (empty($module_libraries)) { - $module_libraries = array(); - } - // Allow modules to alter the module's registered libraries. - drupal_alter('library_info', $module_libraries, $module); - - foreach ($module_libraries as $key => $data) { - if (is_array($data)) { - // Add default elements to allow for easier processing. - $module_libraries[$key] += array('dependencies' => array(), 'js' => array(), 'css' => array()); - foreach ($module_libraries[$key]['js'] as $file => $options) { - $module_libraries[$key]['js'][$file]['version'] = $module_libraries[$key]['version']; - } - } - } - $libraries[$module] = $module_libraries; - } - if (isset($name)) { - if (!isset($libraries[$module][$name])) { - $libraries[$module][$name] = FALSE; - } - return $libraries[$module][$name]; - } - return $libraries[$module]; -} - -/** - * Assist in adding the tableDrag JavaScript behavior to a themed table. - * - * 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 - * 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 - * in place of a form that contains parent relationships, the form must be - * themed into a table. The table must have an id attribute set. If using - * theme_table(), the id may be set as such: - * @code - * $output = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'my-module-table'))); - * return $output; - * @endcode - * - * In the theme function for the form, a special class must be added to each - * form element within the same column, "grouping" them together. - * - * In a situation where a single weight column is being sorted in the table, the - * classes could be added like this (in the theme function): - * @code - * $form['my_elements'][$delta]['weight']['#attributes']['class'] = array('my-elements-weight'); - * @endcode - * - * Each row of the table must also have a class of "draggable" in order to enable the - * drag handles: - * @code - * $row = array(...); - * $rows[] = array( - * 'data' => $row, - * 'class' => array('draggable'), - * ); - * @endcode - * - * When tree relationships are present, the two additional classes - * 'tabledrag-leaf' and 'tabledrag-root' can be used to refine the behavior: - * - Rows with the 'tabledrag-leaf' class cannot have child rows. - * - Rows with the 'tabledrag-root' class cannot be nested under a parent row. - * - * Calling drupal_add_tabledrag() would then be written as such: - * @code - * drupal_add_tabledrag('my-module-table', 'order', 'sibling', 'my-elements-weight'); - * @endcode - * - * 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 - * must also be added to differentiate the groups. - * @code - * $form['my_elements'][$region][$delta]['weight']['#attributes']['class'] = array('my-elements-weight', 'my-elements-weight-' . $region); - * @endcode - * - * $group is still 'my-element-weight', and the additional $subgroup variable - * will be passed in as 'my-elements-weight-' . $region. This also means that - * you'll need to call drupal_add_tabledrag() once for every region added. - * - * @code - * foreach ($regions as $region) { - * drupal_add_tabledrag('my-module-table', 'order', 'sibling', 'my-elements-weight', 'my-elements-weight-' . $region); - * } - * @endcode - * - * In a situation where tree relationships are present, adding multiple - * subgroups is not necessary, because the table will contain indentations that - * provide enough information about the sibling and parent relationships. - * See theme_menu_overview_form() for an example creating a table containing - * parent relationships. - * - * Please note that this function should be called from the theme layer, such as - * in a .tpl.php file, theme_ function, or in a template_preprocess function, - * not in a form declaration. Though the same JavaScript could be added to the - * page using drupal_add_js() directly, this function helps keep template files - * clean and readable. It also prevents tabledrag.js from being added twice - * accidentally. - * - * @param $table_id - * String containing the target table's id attribute. If the table does not - * have an id, one will need to be set, such as <table id="my-module-table">. - * @param $action - * String describing the action to be done on the form item. Either 'match' - * 'depth', or 'order'. Match is typically used for parent relationships. - * Order is typically used to set weights on other form elements with the same - * group. Depth updates the target element with the current indentation. - * @param $relationship - * String describing where the $action variable should be performed. Either - * 'parent', 'sibling', 'group', or 'self'. Parent will only look for fields - * up the tree. Sibling will look for fields in the same group in rows above - * and below it. Self affects the dragged row itself. Group affects the - * dragged row, plus any children below it (the entire dragged group). - * @param $group - * A class name applied on all related form elements for this action. - * @param $subgroup - * (optional) If the group has several subgroups within it, this string should - * contain the class name identifying fields in the same subgroup. - * @param $source - * (optional) If the $action is 'match', this string should contain the class - * name identifying what field will be used as the source value when matching - * the value in $subgroup. - * @param $hidden - * (optional) The column containing the field elements may be entirely hidden - * from view dynamically when the JavaScript is loaded. Set to FALSE if the - * column should not be hidden. - * @param $limit - * (optional) Limit the maximum amount of parenting in this table. - * @see block-admin-display-form.tpl.php - * @see theme_menu_overview_form() - */ -function drupal_add_tabledrag($table_id, $action, $relationship, $group, $subgroup = NULL, $source = NULL, $hidden = TRUE, $limit = 0) { - $js_added = &drupal_static(__FUNCTION__, FALSE); - if (!$js_added) { - // Add the table drag JavaScript to the page before the module JavaScript - // to ensure that table drag behaviors are registered before any module - // uses it. - drupal_add_library('system', 'jquery.cookie'); - drupal_add_js('core/misc/tabledrag.js', array('weight' => -1)); - $js_added = TRUE; - } - - // If a subgroup or source isn't set, assume it is the same as the group. - $target = isset($subgroup) ? $subgroup : $group; - $source = isset($source) ? $source : $target; - $settings['tableDrag'][$table_id][$group][] = array( - 'target' => $target, - 'source' => $source, - 'relationship' => $relationship, - 'action' => $action, - 'hidden' => $hidden, - 'limit' => $limit, - ); - drupal_add_js($settings, 'setting'); -} - -/** - * Aggregates JavaScript files into a cache file in the files directory. - * - * The file name for the JavaScript cache file is generated from the hash of - * the aggregated contents of the files in $files. This forces proxies and - * browsers to download new JavaScript when the JavaScript changes. - * - * The cache file name is retrieved on a page load via a lookup variable that - * contains an associative array. The array key is the hash of the names in - * $files while the value is the cache file name. The cache file is generated - * in two cases. First, if there is no file name value for the key, which will - * happen if a new file name has been added to $files or after the lookup - * variable is emptied to force a rebuild of the cache. Second, the cache - * file is generated if it is missing on disk. Old cache files are not deleted - * immediately when the lookup variable is emptied, but are deleted after a set - * period by drupal_delete_file_if_stale(). This ensures that files referenced - * by a cached page will still be available. - * - * @param $files - * An array of JavaScript files to aggregate and compress into one file. - * - * @return - * The URI of the cache file, or FALSE if the file could not be saved. - */ -function drupal_build_js_cache($files) { - $contents = ''; - $uri = ''; - $map = variable_get('drupal_js_cache_files', array()); - $key = hash('sha256', serialize($files)); - if (isset($map[$key])) { - $uri = $map[$key]; - } - - if (empty($uri) || !file_exists($uri)) { - // Build aggregate JS file. - foreach ($files as $path => $info) { - if ($info['preprocess']) { - // Append a ';' and a newline after each JS file to prevent them from running together. - $contents .= file_get_contents($path) . ";\n"; - } - } - // Prefix filename to prevent blocking by firewalls which reject files - // starting with "ad*". - $filename = 'js_' . drupal_hash_base64($contents) . '.js'; - // Create the js/ within the files folder. - $jspath = 'public://js'; - $uri = $jspath . '/' . $filename; - // Create the JS file. - file_prepare_directory($jspath, FILE_CREATE_DIRECTORY); - if (!file_exists($uri) && !file_unmanaged_save_data($contents, $uri, FILE_EXISTS_REPLACE)) { - return FALSE; - } - // If JS gzip compression is enabled, clean URLs are enabled (which means - // that rewrite rules are working) and the zlib extension is available then - // create a gzipped version of this file. This file is served conditionally - // to browsers that accept gzip using .htaccess rules. - if (variable_get('js_gzip_compression', TRUE) && variable_get('clean_url', 0) && extension_loaded('zlib')) { - if (!file_exists($uri . '.gz') && !file_unmanaged_save_data(gzencode($contents, 9, FORCE_GZIP), $uri . '.gz', FILE_EXISTS_REPLACE)) { - return FALSE; - } - } - $map[$key] = $uri; - variable_set('drupal_js_cache_files', $map); - } - return $uri; -} - -/** - * Deletes old cached JavaScript files and variables. - */ -function drupal_clear_js_cache() { - variable_del('javascript_parsed'); - variable_del('drupal_js_cache_files'); - file_scan_directory('public://js', '/.*/', array('callback' => 'drupal_delete_file_if_stale')); -} - -/** - * Converts a PHP variable into its JavaScript equivalent. - * - * We use HTML-safe strings, i.e. with <, > and & escaped. - * - * @see drupal_json_decode() - * @ingroup php_wrappers - */ -function drupal_json_encode($var) { - // json_encode() does not escape <, > and &, so we do it with str_replace(). - return str_replace(array('<', '>', '&'), array('\u003c', '\u003e', '\u0026'), json_encode($var)); -} - -/** - * Converts an HTML-safe JSON string into its PHP equivalent. - * - * @see drupal_json_encode() - * @ingroup php_wrappers - */ -function drupal_json_decode($var) { - return json_decode($var, TRUE); -} - -/** - * Return data in JSON format. - * - * This function should be used for JavaScript callback functions returning - * data in JSON format. It sets the header for JavaScript output. - * - * @param $var - * (optional) If set, the variable will be converted to JSON and output. - */ -function drupal_json_output($var = NULL) { - // We are returning JSON, so tell the browser. - drupal_add_http_header('Content-Type', 'application/json'); - - if (isset($var)) { - echo drupal_json_encode($var); - } -} - -/** - * Get a salt useful for hardening against SQL injection. - * - * @return - * A salt based on information in settings.php, not in the database. - */ -function drupal_get_hash_salt() { - global $drupal_hash_salt, $databases; - // If the $drupal_hash_salt variable is empty, a hash of the serialized - // database credentials is used as a fallback salt. - return empty($drupal_hash_salt) ? hash('sha256', serialize($databases)) : $drupal_hash_salt; -} - -/** - * Ensure the private key variable used to generate tokens is set. - * - * @return - * The private key. - */ -function drupal_get_private_key() { - if (!($key = variable_get('drupal_private_key', 0))) { - $key = drupal_hash_base64(drupal_random_bytes(55)); - variable_set('drupal_private_key', $key); - } - return $key; -} - -/** - * Generate a token based on $value, the current user session and private key. - * - * @param $value - * An additional value to base the token on. - */ -function drupal_get_token($value = '') { - return drupal_hmac_base64($value, session_id() . drupal_get_private_key() . drupal_get_hash_salt()); -} - -/** - * Validate a token based on $value, the current user session and private key. - * - * @param $token - * The token to be validated. - * @param $value - * An additional value to base the token on. - * @param $skip_anonymous - * Set to true to skip token validation for anonymous users. - * @return - * 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. - */ -function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) { - global $user; - return (($skip_anonymous && $user->uid == 0) || ($token == drupal_get_token($value))); -} - -function _drupal_bootstrap_full() { - static $called = FALSE; - - if ($called) { - return; - } - $called = TRUE; - require_once DRUPAL_ROOT . '/' . variable_get('path_inc', 'core/includes/path.inc'); - require_once DRUPAL_ROOT . '/core/includes/theme.inc'; - require_once DRUPAL_ROOT . '/core/includes/pager.inc'; - require_once DRUPAL_ROOT . '/' . variable_get('menu_inc', 'core/includes/menu.inc'); - require_once DRUPAL_ROOT . '/core/includes/tablesort.inc'; - require_once DRUPAL_ROOT . '/core/includes/file.inc'; - require_once DRUPAL_ROOT . '/core/includes/unicode.inc'; - require_once DRUPAL_ROOT . '/core/includes/image.inc'; - require_once DRUPAL_ROOT . '/core/includes/form.inc'; - require_once DRUPAL_ROOT . '/core/includes/mail.inc'; - require_once DRUPAL_ROOT . '/core/includes/actions.inc'; - require_once DRUPAL_ROOT . '/core/includes/ajax.inc'; - require_once DRUPAL_ROOT . '/core/includes/token.inc'; - require_once DRUPAL_ROOT . '/core/includes/errors.inc'; - - // Detect string handling method - unicode_check(); - // Undo magic quotes - fix_gpc_magic(); - // Load all enabled modules - module_load_all(); - // Make sure all stream wrappers are registered. - file_get_stream_wrappers(); - - $test_info = &$GLOBALS['drupal_test_info']; - if (!empty($test_info['in_child_site'])) { - // Running inside the simpletest child site, log fatal errors to test - // specific file directory. - ini_set('log_errors', 1); - ini_set('error_log', 'public://error.log'); - } - - // Initialize $_GET['q'] prior to invoking hook_init(). - drupal_path_initialize(); - - // Let all modules take action before the menu system handles the request. - // We do not want this while running update.php. - if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') { - // Prior to invoking hook_init(), initialize the theme (potentially a custom - // one for this page), so that: - // - Modules with hook_init() implementations that call theme() or - // theme_get_registry() don't initialize the incorrect theme. - // - The theme can have hook_*_alter() implementations affect page building - // (e.g., hook_form_alter(), hook_node_view_alter(), hook_page_alter()), - // ahead of when rendering starts. - menu_set_custom_theme(); - drupal_theme_initialize(); - module_invoke_all('init'); - } -} - -/** - * Store the current page in the cache. - * - * 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 - * is unzipped in the relatively rare event that the page is requested by a - * client without gzip support. - * - * Page compression requires the PHP zlib extension - * (http://php.net/manual/en/ref.zlib.php). - * - * @see drupal_page_header() - */ -function drupal_page_set_cache() { - global $base_root; - - if (drupal_page_is_cacheable()) { - $cache = (object) array( - 'cid' => $base_root . request_uri(), - 'data' => array( - 'path' => $_GET['q'], - 'body' => ob_get_clean(), - 'title' => drupal_get_title(), - 'headers' => array(), - ), - 'expire' => CACHE_TEMPORARY, - 'created' => REQUEST_TIME, - ); - - // Restore preferred header names based on the lower-case names returned - // by drupal_get_http_header(). - $header_names = _drupal_set_preferred_header_name(); - foreach (drupal_get_http_header() as $name_lower => $value) { - $cache->data['headers'][$header_names[$name_lower]] = $value; - if ($name_lower == 'expires') { - // Use the actual timestamp from an Expires header if available. - $cache->expire = strtotime($value); - } - } - - if ($cache->data['body']) { - if (variable_get('page_compression', TRUE) && extension_loaded('zlib')) { - $cache->data['body'] = gzencode($cache->data['body'], 9, FORCE_GZIP); - } - cache('page')->set($cache->cid, $cache->data, $cache->expire); - } - return $cache; - } -} - -/** - * Executes a cron run when called. - * - * Do not call this function from test, use $this->cronRun() instead. - * - * @return - * Returns TRUE if ran successfully - */ -function drupal_cron_run() { - // Allow execution to continue even if the request gets canceled. - @ignore_user_abort(TRUE); - - // Prevent session information from being saved while cron is running. - drupal_save_session(FALSE); - - // Force the current user to anonymous to ensure consistent permissions on - // cron runs. - $original_user = $GLOBALS['user']; - $GLOBALS['user'] = drupal_anonymous_user(); - - // Try to allocate enough time to run all the hook_cron implementations. - drupal_set_time_limit(240); - - $return = FALSE; - // Grab the defined cron queues. - $queues = module_invoke_all('cron_queue_info'); - drupal_alter('cron_queue_info', $queues); - - // Try to acquire cron lock. - if (!lock_acquire('cron', 240.0)) { - // Cron is still running normally. - watchdog('cron', 'Attempting to re-run cron while it is already running.', array(), WATCHDOG_WARNING); - } - else { - // Make sure every queue exists. There is no harm in trying to recreate an - // existing queue. - foreach ($queues as $queue_name => $info) { - DrupalQueue::get($queue_name)->createQueue(); - } - // Register shutdown callback - drupal_register_shutdown_function('drupal_cron_cleanup'); - - // Iterate through the modules calling their cron handlers (if any): - 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 - variable_set('cron_last', REQUEST_TIME); - watchdog('cron', 'Cron run completed.', array(), WATCHDOG_NOTICE); - - // Release cron lock. - lock_release('cron'); - - // Return TRUE so other functions can check if it did run successfully - $return = TRUE; - } - - foreach ($queues as $queue_name => $info) { - $function = $info['worker callback']; - $end = time() + (isset($info['time']) ? $info['time'] : 15); - $queue = DrupalQueue::get($queue_name); - while (time() < $end && ($item = $queue->claimItem())) { - $function($item->data); - $queue->deleteItem($item); - } - } - // Restore the user. - $GLOBALS['user'] = $original_user; - drupal_save_session(TRUE); - - return $return; -} - -/** - * Shutdown function for cron cleanup. - */ -function drupal_cron_cleanup() { - // See if the semaphore is still locked. - if (variable_get('cron_semaphore', FALSE)) { - watchdog('cron', 'Cron run exceeded the time limit and was aborted.', array(), WATCHDOG_WARNING); - - // Release cron semaphore - variable_del('cron_semaphore'); - } -} - -/** - * Returns information about system object files (modules, themes, etc.). - * - * This function is used to find all or some system object files (module files, - * theme files, etc.) that exist on the site. It searches in several locations, - * depending on what type of object you are looking for. For instance, if you - * are looking for modules and call: - * @code - * drupal_system_listing("/\.module$/", "modules", 'name', 0); - * @endcode - * this function will search the site-wide modules directory (i.e., /modules/), - * your install profile's directory (i.e., - * /profiles/your_site_profile/modules/), the all-sites directory (i.e., - * /sites/all/modules/), and your site-specific directory (i.e., - * /sites/your_site_dir/modules/), in that order, and return information about - * all of the files ending in .module in those directories. - * - * The information is returned in an associative array, which can be keyed on - * the file name ($key = 'filename'), the file name without the extension ($key - * = 'name'), or the full file stream URI ($key = 'uri'). If you use a key of - * 'filename' or 'name', files found later in the search will take precedence - * over files found earlier (unless they belong to a module or theme not - * compatible with Drupal core); if you choose a key of 'uri', you will get all - * files found. - * - * @param string $mask - * The preg_match() regular expression for the files to find. - * @param string $directory - * The subdirectory name in which the files are found. For example, - * 'core/modules' will search in sub-directories of the /core/modules - * directory, sub-directories of /sites/all/modules/, etc. - * @param string $key - * The key to be used for the associative array returned. Possible values are - * 'uri', for the file's URI; 'filename', for the basename of the file; and - * 'name' for the name of the file without the extension. If you choose 'name' - * or 'filename', only the highest-precedence file will be returned. - * @param int $min_depth - * Minimum depth of directories to return files from, relative to each - * directory searched. For instance, a minimum depth of 2 would find modules - * inside /core/modules/node/tests, but not modules directly in - * /core/modules/node. - * - * @return array - * An associative array of file objects, keyed on the chosen key. Each element - * in the array is an object containing file information, with properties: - * - 'uri': Full URI of the file. - * - 'filename': File name. - * - 'name': Name of file without the extension. - */ -function drupal_system_listing($mask, $directory, $key = 'name', $min_depth = 1) { - $config = conf_path(); - $files = array(); - - // Search for the directory in core. - $searchdir = array('core/' . $directory); - - // The 'profiles' directory contains pristine collections of modules and - // themes as provided by a distribution. It is pristine in the same way that - // the 'core/modules' directory is pristine for core; users should avoid - // any modification by using the sites/all or sites/<domain> directories. - $profile = drupal_get_profile(); - if (file_exists("profiles/$profile/$directory")) { - $searchdir[] = "profiles/$profile/$directory"; - } - - // Always search sites/all/* as well as the global directories - $searchdir[] = 'sites/all/' . $directory; - - if (file_exists("$config/$directory")) { - $searchdir[] = "$config/$directory"; - } - - // Get current list of items - if (!function_exists('file_scan_directory')) { - require_once DRUPAL_ROOT . '/core/includes/file.inc'; - } - foreach ($searchdir as $dir) { - $files_to_add = file_scan_directory($dir, $mask, array('key' => $key, 'min_depth' => $min_depth)); - - // Duplicate files found in later search directories take precedence over - // earlier ones, so we want them to overwrite keys in our resulting - // $files array. - // The exception to this is if the later file is from a module or theme not - // compatible with Drupal core. This may occur during upgrades of Drupal - // core when new modules exist in core while older contrib modules with the - // same name exist in a directory such as sites/all/modules/. - 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 - // new resource on the list for merging. - if (file_exists($info_file = dirname($file->uri) . '/' . $file->name . '.info')) { - // Get the .info file for the module or theme this file belongs to. - $info = drupal_parse_info_file($info_file); - - // If the module or theme is incompatible with Drupal core, remove it - // from the array for the current search directory, so it is not - // overwritten when merged with the $files array. - if (isset($info['core']) && $info['core'] != DRUPAL_CORE_COMPATIBILITY) { - unset($files_to_add[$file_key]); - } - } - } - $files = array_merge($files, $files_to_add); - } - - return $files; -} - -/** - * Set the main page content value for later use. - * - * 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 - * displayed. - * - * @param $content - * A string or renderable array representing the body of the page. - * @return - * If called without $content, a renderable array representing the body of - * the page. - */ -function drupal_set_page_content($content = NULL) { - $content_block = &drupal_static(__FUNCTION__, NULL); - $main_content_display = &drupal_static('system_main_content_added', FALSE); - - if (!empty($content)) { - $content_block = (is_array($content) ? $content : array('main' => array('#markup' => $content))); - } - else { - // Indicate that the main content has been requested. We assume that - // the module requesting the content will be adding it to the page. - // A module can indicate that it does not handle the content by setting - // the static variable back to FALSE after calling this function. - $main_content_display = TRUE; - return $content_block; - } -} - -/** - * #pre_render callback to render #browsers into #prefix and #suffix. - * - * @param $elements - * A render array with a '#browsers' property. The '#browsers' property can - * contain any or all of the following keys: - * - 'IE': If FALSE, the element is not rendered by Internet Explorer. If - * TRUE, the element is rendered by Internet Explorer. Can also be a string - * containing an expression for Internet Explorer to evaluate as part of a - * conditional comment. For example, this can be set to 'lt IE 7' for the - * element to be rendered in Internet Explorer 6, but not in Internet - * Explorer 7 or higher. Defaults to TRUE. - * - '!IE': If FALSE, the element is not rendered by browsers other than - * Internet Explorer. If TRUE, the element is rendered by those browsers. - * Defaults to TRUE. - * Examples: - * - To render an element in all browsers, '#browsers' can be left out or set - * to array('IE' => TRUE, '!IE' => TRUE). - * - To render an element in Internet Explorer only, '#browsers' can be set - * to array('!IE' => FALSE). - * - To render an element in Internet Explorer 6 only, '#browsers' can be set - * to array('IE' => 'lt IE 7', '!IE' => FALSE). - * - To render an element in Internet Explorer 8 and higher and in all other - * browsers, '#browsers' can be set to array('IE' => 'gte IE 8'). - * - * @return - * The passed-in element with markup for conditional comments potentially - * added to '#prefix' and '#suffix'. - */ -function drupal_pre_render_conditional_comments($elements) { - $browsers = isset($elements['#browsers']) ? $elements['#browsers'] : array(); - $browsers += array( - 'IE' => TRUE, - '!IE' => TRUE, - ); - - // If rendering in all browsers, no need for conditional comments. - if ($browsers['IE'] === TRUE && $browsers['!IE']) { - return $elements; - } - - // Determine the conditional comment expression for Internet Explorer to - // evaluate. - if ($browsers['IE'] === TRUE) { - $expression = 'IE'; - } - elseif ($browsers['IE'] === FALSE) { - $expression = '!IE'; - } - else { - $expression = $browsers['IE']; - } - - // Wrap the element's potentially existing #prefix and #suffix properties with - // conditional comment markup. The conditional comment expression is evaluated - // by Internet Explorer only. To control the rendering by other browsers, - // either the "downlevel-hidden" or "downlevel-revealed" technique must be - // used. See http://en.wikipedia.org/wiki/Conditional_comment for details. - $elements += array( - '#prefix' => '', - '#suffix' => '', - ); - if (!$browsers['!IE']) { - // "downlevel-hidden". - $elements['#prefix'] = "\n<!--[if $expression]>\n" . $elements['#prefix']; - $elements['#suffix'] .= "<![endif]-->\n"; - } - else { - // "downlevel-revealed". - $elements['#prefix'] = "\n<!--[if $expression]><!-->\n" . $elements['#prefix']; - $elements['#suffix'] .= "<!--<![endif]-->\n"; - } - - return $elements; -} - -/** - * #pre_render callback to render a link into #markup. - * - * Doing so during pre_render gives modules a chance to alter the link parts. - * - * @param $elements - * A structured array whose keys form the arguments to l(): - * - #title: The link text to pass as argument to l(). - * - #href: The URL path component to pass as argument to l(). - * - #options: (optional) An array of options to pass to l(). - * - * @return - * The passed-in elements containing a rendered link in '#markup'. - */ -function drupal_pre_render_link($element) { - // By default, link options to pass to l() are normally set in #options. - $element += array('#options' => array()); - // However, within the scope of renderable elements, #attributes is a valid - // way to specify attributes, too. Take them into account, but do not override - // attributes from #options. - if (isset($element['#attributes'])) { - $element['#options'] += array('attributes' => array()); - $element['#options']['attributes'] += $element['#attributes']; - } - - // This #pre_render callback can be invoked from inside or outside of a Form - // API context, and depending on that, a HTML ID may be already set in - // different locations. #options should have precedence over Form API's #id. - // #attributes have been taken over into #options above already. - if (isset($element['#options']['attributes']['id'])) { - $element['#id'] = $element['#options']['attributes']['id']; - } - elseif (isset($element['#id'])) { - $element['#options']['attributes']['id'] = $element['#id']; - } - - // Conditionally invoke ajax_pre_render_element(), if #ajax is set. - if (isset($element['#ajax']) && !isset($element['#ajax_processed'])) { - // If no HTML ID was found above, automatically create one. - if (!isset($element['#id'])) { - $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 (!isset($element['#ajax']['path'])) { - $element['#ajax']['path'] = $element['#href']; - $element['#ajax']['options'] = $element['#options']; - } - $element = ajax_pre_render_element($element); - } - - $element['#markup'] = l($element['#title'], $element['#href'], $element['#options']); - 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. - * - * This needs to be a #pre_render callback, because eventually assigned - * #theme_wrappers will expect the element's rendered content in #children. - * Note that if also a #theme is defined for the element, then the result of - * the theme callback will override #children. - * - * @see drupal_render() - * - * @param $elements - * A structured array using the #markup key. - * - * @return - * The passed-in elements, but #markup appended to #children. - */ -function drupal_pre_render_markup($elements) { - $elements['#children'] = $elements['#markup']; - return $elements; -} - -/** - * Renders the page, including all theming. - * - * @param $page - * A string or array representing the content of a page. The array consists of - * the following keys: - * - #type: Value is always 'page'. This pushes the theming through page.tpl.php (required). - * - #show_messages: Suppress drupal_get_message() items. Used by Batch API (optional). - * - * @see hook_page_alter() - * @see element_info() - */ -function drupal_render_page($page) { - $main_content_display = &drupal_static('system_main_content_added', FALSE); - - // Allow menu callbacks to return strings or arbitrary arrays to render. - // If the array returned is not of #type page directly, we need to fill - // in the page with defaults. - if (is_string($page) || (is_array($page) && (!isset($page['#type']) || ($page['#type'] != 'page')))) { - drupal_set_page_content($page); - $page = element_info('page'); - } - - // Modules can add elements to $page as needed in hook_page_build(). - foreach (module_implements('page_build') as $module) { - $function = $module . '_page_build'; - $function($page); - } - // Modules alter the $page as needed. Blocks are populated into regions like - // 'sidebar_first', 'footer', etc. - drupal_alter('page', $page); - - // If no module has taken care of the main content, add it to the page now. - // This allows the site to still be usable even if no modules that - // control page regions (for example, the Block module) are enabled. - if (!$main_content_display) { - $page['content']['system_main'] = drupal_set_page_content(); - } - - return drupal_render($page); -} - -/** - * Renders HTML given a structured array tree. - * - * Recursively iterates over each of the array elements, generating HTML code. - * - * Renderable arrays have two kinds of key/value pairs: properties and - * 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 - * any children, it is the responsibility of the theme function to render - * these children. For elements that are not allowed to have any children, - * e.g. buttons or textfields, the theme function can be used to render the - * element itself. If #theme is not present and the element has children, they - * are rendered and concatenated into a string by drupal_render_children(). - * - * 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 - * markup around the rendered children; e.g., fieldsets add the required markup - * for a fieldset around their rendered child elements. All wrapper theme - * functions have to include the element's #children property in their output, - * as it contains the output of the previous theme functions and the rendered - * children. - * - * For example, for the form element type, by default only the #theme_wrappers - * property is set, which adds the form markup around the rendered child - * elements of the form. This allows you to set the #theme property on a - * specific form to a custom theme function, giving you complete control over - * the placement of the form's children while not at all having to deal with - * the form markup itself. - * - * drupal_render() can optionally cache the rendered output of elements to - * improve performance. To use drupal_render() caching, set the element's #cache - * property to an associative array with one or several of the following keys: - * - 'keys': An array of one or more keys that identify the element. If 'keys' - * is set, the cache ID is created automatically from these keys. See - * drupal_render_cid_create(). - * - 'granularity' (optional): Define the cache granularity using binary - * combinations of the cache granularity constants, e.g. DRUPAL_CACHE_PER_USER - * to cache for each user separately or - * 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 - * theme and language. - * - 'cid': Specify the cache ID directly. Either 'keys' or 'cid' is required. - * If 'cid' is set, 'keys' and 'granularity' are ignored. Use only if you - * have special requirements. - * - 'expire': Set to one of the cache lifetime constants. - * - 'bin': Specify a cache bin to cache the element in. Defaults to 'cache'. - * - * This function is usually called from within another function, like - * drupal_get_form() or a theme function. Elements are sorted internally - * using uasort(). Since this is expensive, when passing already sorted - * elements to drupal_render(), for example from a database query, set - * $elements['#sorted'] = TRUE to avoid sorting them a second time. - * - * drupal_render() flags each element with a '#printed' status to indicate that - * the element has been rendered, which allows individual elements of a given - * array to be rendered independently and prevents them from being rendered - * more than once on subsequent calls to drupal_render() (e.g., as part of a - * larger array). If the same array or array element is passed more than once - * to drupal_render(), it simply returns a NULL value. - * - * @param $elements - * The structured array describing the data to be rendered. - * @return - * The rendered HTML. - */ -function drupal_render(&$elements) { - // Early-return nothing if user does not have access. - if (empty($elements) || (isset($elements['#access']) && !$elements['#access'])) { - return; - } - - // Do not print elements twice. - if (!empty($elements['#printed'])) { - return; - } - - // Try to fetch the element's markup from cache and return. - if (isset($elements['#cache']) && $cached_output = drupal_render_cache_get($elements)) { - return $cached_output; - } - - // If #markup is set, ensure #type is set. This allows to specify just #markup - // on an element without setting #type. - if (isset($elements['#markup']) && !isset($elements['#type'])) { - $elements['#type'] = 'markup'; - } - - // If the default values for this element have not been loaded yet, populate - // them. - if (isset($elements['#type']) && empty($elements['#defaults_loaded'])) { - $elements += element_info($elements['#type']); - } - - // Make any final changes to the element before it is rendered. This means - // that the $element or the children can be altered or corrected before the - // element is rendered into the final text. - if (isset($elements['#pre_render'])) { - foreach ($elements['#pre_render'] as $function) { - if (function_exists($function)) { - $elements = $function($elements); - } - } - } - - // Allow #pre_render to abort rendering. - if (!empty($elements['#printed'])) { - return; - } - - // Get the children of the element, sorted by weight. - $children = element_children($elements, TRUE); - - // Initialize this element's #children, unless a #pre_render callback already - // preset #children. - if (!isset($elements['#children'])) { - $elements['#children'] = ''; - } - // Call the element's #theme function if it is set. Then any children of the - // element have to be rendered there. - if (isset($elements['#theme'])) { - $elements['#children'] = theme($elements['#theme'], $elements); - } - // If #theme was not set and the element has children, render them now. - // This is the same process as drupal_render_children() but is inlined - // for speed. - if ($elements['#children'] == '') { - foreach ($children as $key) { - $elements['#children'] .= drupal_render($elements[$key]); - } - } - - // Let the theme functions in #theme_wrappers add markup around the rendered - // children. - if (isset($elements['#theme_wrappers'])) { - foreach ($elements['#theme_wrappers'] as $theme_wrapper) { - $elements['#children'] = theme($theme_wrapper, $elements); - } - } - - // Filter the outputted content and make any last changes before the - // content is sent to the browser. The changes are made on $content - // which allows the output'ed text to be filtered. - if (isset($elements['#post_render'])) { - foreach ($elements['#post_render'] as $function) { - if (function_exists($function)) { - $elements['#children'] = $function($elements['#children'], $elements); - } - } - } - - // Add any JavaScript state information associated with the element. - if (!empty($elements['#states'])) { - drupal_process_states($elements); - } - - // Add additional libraries, CSS, JavaScript an other custom - // attached data associated with this element. - if (!empty($elements['#attached'])) { - drupal_process_attached($elements); - } - - $prefix = isset($elements['#prefix']) ? $elements['#prefix'] : ''; - $suffix = isset($elements['#suffix']) ? $elements['#suffix'] : ''; - $output = $prefix . $elements['#children'] . $suffix; - - // Cache the processed element if #cache is set. - if (isset($elements['#cache'])) { - drupal_render_cache_set($output, $elements); - } - - $elements['#printed'] = TRUE; - return $output; -} - -/** - * Render children of an element and concatenate them. - * - * This renders all children of an element using drupal_render() and then - * joins them together into a single string. - * - * @param $element - * The structured array whose children shall be rendered. - * @param $children_keys - * If the keys of the element's children are already known, they can be passed - * in to save another run of element_children(). - */ -function drupal_render_children(&$element, $children_keys = NULL) { - if ($children_keys === NULL) { - $children_keys = element_children($element); - } - $output = ''; - foreach ($children_keys as $key) { - if (!empty($element[$key])) { - $output .= drupal_render($element[$key]); - } - } - return $output; -} - -/** - * Render an element. - * - * This function renders an element using drupal_render(). The top level - * element is shown with show() before rendering, so it will always be rendered - * even if hide() had been previously used on it. - * - * @param $element - * The element to be rendered. - * - * @return - * The rendered element. - * - * @see drupal_render() - * @see show() - * @see hide() - */ -function render(&$element) { - if (is_array($element)) { - show($element); - return drupal_render($element); - } - else { - // Safe-guard for inappropriate use of render() on flat variables: return - // the variable as-is. - return $element; - } -} - -/** - * Hide 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 show() - */ -function hide(&$element) { - $element['#printed'] = TRUE; - return $element; -} - -/** - * Show a hidden element for later rendering. - * - * You can also use render($element), which shows the element while rendering - * it. - * - * 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 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 hide() - */ -function show(&$element) { - $element['#printed'] = FALSE; - return $element; -} - -/** - * Get the rendered output of a renderable element from cache. - * - * @see drupal_render() - * @see drupal_render_cache_set() - * - * @param $elements - * A renderable array. - * @return - * A markup string containing the rendered content of the element, or FALSE - * if no cached copy of the element is available. - */ -function drupal_render_cache_get($elements) { - if (!in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD')) || !$cid = drupal_render_cid_create($elements)) { - return FALSE; - } - $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache'; - - if (!empty($cid) && $cache = cache($bin)->get($cid)) { - // Add additional libraries, JavaScript, CSS and other data attached - // to this element. - if (isset($cache->data['#attached'])) { - drupal_process_attached($cache->data); - } - // Return the rendered output. - return $cache->data['#markup']; - } - return FALSE; -} - -/** - * Cache the rendered output of a renderable 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 - * The rendered output string of $elements. - * @param $elements - * A renderable array. - */ -function drupal_render_cache_set(&$markup, $elements) { - // Create the cache ID for the element. - if (!in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD')) || !$cid = drupal_render_cid_create($elements)) { - return FALSE; - } - - // Cache implementations are allowed to modify the markup, to support - // replacing markup with edge-side include commands. The supporting cache - // backend will store the markup in some other key (like - // $data['#real-value']) and return an include command instead. When the - // ESI command is executed by the content accelerator, the real value can - // be retrieved and used. - $data['#markup'] = &$markup; - // Persist attached data associated with this element. - $attached = drupal_render_collect_attached($elements, TRUE); - if ($attached) { - $data['#attached'] = $attached; - } - $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache'; - $expire = isset($elements['#cache']['expire']) ? $elements['#cache']['expire'] : CACHE_PERMANENT; - cache($bin)->set($cid, $data, $expire); -} - -/** - * Collect #attached for an element and all child elements into a single array. - * - * 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; - } -} - -/** - * Prepare 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 - * call this function. Executing the query and formatting results should happen - * in a #pre_render callback. - * - * @param $query - * A select query object as returned by db_select(). - * @param $function - * The name of the function doing this caching. A _pre_render suffix will be - * added to this string and is also part of the cache key in - * drupal_render_cache_set() and drupal_render_cache_get(). - * @param $expire - * The cache expire time, passed eventually to cache_set(). - * @param $granularity - * One or more granularity constants passed to drupal_render_cid_parts(). - * - * @return - * A renderable array with the following keys and values: - * - #query: The passed-in $query. - * - #pre_render: $function with a _pre_render suffix. - * - #cache: An associative array prepared for drupal_render_cache_set(). - */ -function drupal_render_cache_by_query($query, $function, $expire = CACHE_TEMPORARY, $granularity = NULL) { - $cache_keys = array_merge(array($function), drupal_render_cid_parts($granularity)); - $query->preExecute(); - $cache_keys[] = hash('sha256', serialize(array((string) $query, $query->getArguments()))); - return array( - '#query' => $query, - '#pre_render' => array($function . '_pre_render'), - '#cache' => array( - 'keys' => $cache_keys, - 'expire' => $expire, - ), - ); -} - -/** - * Helper function for building cache ids. - * - * @param $granularity - * One or more cache granularity constants, e.g. DRUPAL_CACHE_PER_USER to cache - * for each user separately or DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE to - * cache separately for each page and role. - * - * @return - * An array of cache ID parts, always containing the active theme. If the - * locale module is enabled it also contains the active language. If - * $granularity was passed in, more parts are added. - */ -function drupal_render_cid_parts($granularity = NULL) { - global $theme, $base_root, $user; - - $cid_parts[] = $theme; - // If Locale is enabled but we have only one language we do not need it as cid - // part. - if (drupal_multilingual()) { - foreach (language_types_configurable() as $language_type) { - $cid_parts[] = $GLOBALS[$language_type]->language; - } - } - - if (!empty($granularity)) { - // 'PER_ROLE' and 'PER_USER' are mutually exclusive. 'PER_USER' can be a - // resource drag for sites with many users, so when a module is being - // equivocal, we favor the less expensive 'PER_ROLE' pattern. - if ($granularity & DRUPAL_CACHE_PER_ROLE) { - $cid_parts[] = 'r.' . implode(',', array_keys($user->roles)); - } - elseif ($granularity & DRUPAL_CACHE_PER_USER) { - $cid_parts[] = "u.$user->uid"; - } - - if ($granularity & DRUPAL_CACHE_PER_PAGE) { - $cid_parts[] = $base_root . request_uri(); - } - } - - return $cid_parts; -} - -/** - * Create the cache ID for a renderable element. - * - * 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'] - * and, optionally, the #cache['granularity'] properties. - * - * @param $elements - * A renderable array. - * - * @return - * The cache ID string, or FALSE if the element may not be cached. - */ -function drupal_render_cid_create($elements) { - if (isset($elements['#cache']['cid'])) { - return $elements['#cache']['cid']; - } - elseif (isset($elements['#cache']['keys'])) { - $granularity = isset($elements['#cache']['granularity']) ? $elements['#cache']['granularity'] : NULL; - // Merge in additional cache ID parts based provided by drupal_render_cid_parts(). - $cid_parts = array_merge($elements['#cache']['keys'], drupal_render_cid_parts($granularity)); - return implode(':', $cid_parts); - } - return FALSE; -} - -/** - * Function used by uasort to sort structured arrays by weight. - */ -function element_sort($a, $b) { - $a_weight = (is_array($a) && isset($a['#weight'])) ? $a['#weight'] : 0; - $b_weight = (is_array($b) && isset($b['#weight'])) ? $b['#weight'] : 0; - if ($a_weight == $b_weight) { - return 0; - } - return ($a_weight < $b_weight) ? -1 : 1; -} - -/** - * Array sorting callback; sorts elements by title. - */ -function element_sort_by_title($a, $b) { - $a_title = (is_array($a) && isset($a['#title'])) ? $a['#title'] : ''; - $b_title = (is_array($b) && isset($b['#title'])) ? $b['#title'] : ''; - return strnatcasecmp($a_title, $b_title); -} - -/** - * Retrieve the default properties for the defined element type. - * - * @param $type - * An element type as defined by hook_element_info(). - */ -function element_info($type) { - // Use the advanced drupal_static() pattern, since this is called very often. - static $drupal_static_fast; - if (!isset($drupal_static_fast)) { - $drupal_static_fast['cache'] = &drupal_static(__FUNCTION__); - } - $cache = &$drupal_static_fast['cache']; - - if (!isset($cache)) { - $cache = module_invoke_all('element_info'); - foreach ($cache as $element_type => $info) { - $cache[$element_type]['#type'] = $element_type; - } - // Allow modules to alter the element type defaults. - drupal_alter('element_info', $cache); - } - - return isset($cache[$type]) ? $cache[$type] : array(); -} - -/** - * Retrieve 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 drupal_sort_weight($a, $b) { - $a_weight = (is_array($a) && isset($a['weight'])) ? $a['weight'] : 0; - $b_weight = (is_array($b) && isset($b['weight'])) ? $b['weight'] : 0; - if ($a_weight == $b_weight) { - return 0; - } - return ($a_weight < $b_weight) ? -1 : 1; -} - -/** - * 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']); -} - -/** - * Check if the key is a property. - */ -function element_property($key) { - return $key[0] == '#'; -} - -/** - * Get properties of a structured array element. Properties begin with '#'. - */ -function element_properties($element) { - return array_filter(array_keys((array) $element), 'element_property'); -} - -/** - * Check if the key is a child. - */ -function element_child($key) { - return !isset($key[0]) || $key[0] != '#'; -} - -/** - * 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 - * The element array whose children are to be identified. - * @param $sort - * Boolean to indicate whether the children should be sorted by weight. - * @return - * The array keys of the element's children. - */ -function element_children(&$elements, $sort = FALSE) { - // Do not attempt to sort elements which have already been sorted. - $sort = isset($elements['#sorted']) ? !$elements['#sorted'] : $sort; - - // Filter out properties from the element, leaving only children. - $children = array(); - $sortable = FALSE; - foreach ($elements as $key => $value) { - if ($key === '' || $key[0] !== '#') { - if (is_array($value)) { - $children[$key] = $value; - if (isset($value['#weight'])) { - $sortable = TRUE; - } - } - // Only trigger an error if the value is not null. - // @see http://drupal.org/node/1283892 - elseif (isset($value)) { - trigger_error(t('"@key" is an invalid render array key', array('@key' => $key)), E_USER_ERROR); - } - } - } - // Sort the children if necessary. - if ($sort && $sortable) { - uasort($children, 'element_sort'); - // Put the sorted children back into $elements in the correct order, to - // preserve sorting if the same element is passed through - // element_children() twice. - foreach ($children as $key => $child) { - unset($elements[$key]); - $elements[$key] = $child; - } - $elements['#sorted'] = TRUE; - } - - return array_keys($children); -} - -/** - * Returns the visible children of an element. - * - * @param $elements - * The parent element. - * @return - * The array keys of the element's visible children. - */ -function element_get_visible_children(array $elements) { - $visible_children = array(); - - foreach (element_children($elements) as $key) { - $child = $elements[$key]; - - // Skip un-accessible children. - if (isset($child['#access']) && !$child['#access']) { - continue; - } - - // Skip value and hidden elements, since they are not rendered. - if (isset($child['#type']) && in_array($child['#type'], array('value', 'hidden'))) { - continue; - } - - $visible_children[$key] = $child; - } - - return array_keys($visible_children); -} - -/** - * Sets HTML attributes based on element properties. - * - * @param $element - * The renderable element to process. - * @param $map - * An associative array whose keys are element property names and whose values - * are the HTML attribute names to set for corresponding the property; e.g., - * array('#propertyname' => 'attributename'). If both names are identical - * except for the leading '#', then an attribute name value is sufficient and - * no property name needs to be specified. - */ -function element_set_attributes(array &$element, array $map) { - foreach ($map as $property => $attribute) { - // If the key is numeric, the attribute name needs to be taken over. - if (is_int($property)) { - $property = '#' . $attribute; - } - // Do not overwrite already existing attributes. - if (isset($element[$property]) && !isset($element['#attributes'][$attribute])) { - $element['#attributes'][$attribute] = $element[$property]; - } - } -} - -/** - * Sets a value in a nested array with variable depth. - * - * This helper function should be used when the depth of the array element you - * are changing may vary (that is, the number of parent keys is variable). It - * is primarily used for form structures and renderable arrays. - * - * Example: - * @code - * // Assume you have a 'signature' element somewhere in a form. It might be: - * $form['signature_settings']['signature'] = array( - * '#type' => 'text_format', - * '#title' => t('Signature'), - * ); - * // Or, it might be further nested: - * $form['signature_settings']['user']['signature'] = array( - * '#type' => 'text_format', - * '#title' => t('Signature'), - * ); - * @endcode - * - * To deal with the situation, the code needs to figure out the route to the - * element, given an array of parents that is either - * @code array('signature_settings', 'signature') @endcode in the first case or - * @code array('signature_settings', 'user', 'signature') @endcode in the second - * case. - * - * Without this helper function the only way to set the signature element in one - * line would be using eval(), which should be avoided: - * @code - * // Do not do this! Avoid eval(). - * eval('$form[\'' . implode("']['", $parents) . '\'] = $element;'); - * @endcode - * - * Instead, use this helper function: - * @code - * drupal_array_set_nested_value($form, $parents, $element); - * @endcode - * - * However if the number of array parent keys is static, the value should always - * be set directly rather than calling this function. For instance, for the - * first example we could just do: - * @code - * $form['signature_settings']['signature'] = $element; - * @endcode - * - * @param $array - * A reference to the array to modify. - * @param $parents - * An array of parent keys, starting with the outermost key. - * @param $value - * 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() - */ -function drupal_array_set_nested_value(array &$array, array $parents, $value, $force = FALSE) { - $ref = &$array; - foreach ($parents as $parent) { - // PHP auto-creates container arrays and NULL entries without error if $ref - // 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 = $value; -} - -/** - * Retrieves a value from a nested array with variable depth. - * - * This helper function should be used when the depth of the array element being - * retrieved may vary (that is, the number of parent keys is variable). It is - * primarily used for form structures and renderable arrays. - * - * Without this helper function the only way to get a nested array value with - * variable depth in one line would be using eval(), which should be avoided: - * @code - * // Do not do this! Avoid eval(). - * // May also throw a PHP notice, if the variable array keys do not exist. - * eval('$value = $array[\'' . implode("']['", $parents) . "'];"); - * @endcode - * - * Instead, use this helper function: - * @code - * $value = drupal_array_get_nested_value($form, $parents); - * @endcode - * - * The return value will be NULL, regardless of whether the actual value is NULL - * or whether the requested key does not exist. If it is required to know - * whether the nested array key actually exists, pass a third argument that is - * altered by reference: - * @code - * $key_exists = NULL; - * $value = drupal_array_get_nested_value($form, $parents, $key_exists); - * if ($key_exists) { - * // ... do something with $value ... - * } - * @endcode - * - * However if the number of array parent keys is static, the value should always - * be retrieved directly rather than calling this function. For instance: - * @code - * $value = $form['signature_settings']['signature']; - * @endcode - * - * @param $array - * The array from which to get the value. - * @param $parents - * An array of parent keys of the value, starting with the outermost key. - * @param $key_exists - * (optional) If given, an already defined variable that is altered by - * reference. - * - * @return - * The requested nested value. Possibly NULL if the value is NULL or not all - * nested parent keys exist. $key_exists is altered by reference and is a - * Boolean that indicates whether all nested parent keys exist (TRUE) or not - * (FALSE). This allows to distinguish between the two possibilities when NULL - * is returned. - * - * @see drupal_array_set_nested_value() - */ -function drupal_array_get_nested_value(array &$array, array $parents, &$key_exists = NULL) { - $ref = &$array; - foreach ($parents as $parent) { - if (is_array($ref) && array_key_exists($parent, $ref)) { - $ref = &$ref[$parent]; - } - else { - $key_exists = FALSE; - return NULL; - } - } - $key_exists = TRUE; - return $ref; -} - -/** - * Determines whether a nested array with variable depth contains all of the requested keys. - * - * 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 - * drupal_array_set_nested_value() for details. It is primarily used for form - * structures and renderable arrays. - * - * If it is required to also get the value of the checked nested key, use - * drupal_array_get_nested_value() instead. - * - * If the number of array parent keys is static, this helper function is - * unnecessary and the following code can be used instead: - * @code - * $value_exists = isset($form['signature_settings']['signature']); - * $key_exists = array_key_exists('signature', $form['signature_settings']); - * @endcode - * - * @param $array - * The array with the value to check for. - * @param $parents - * An array of parent keys of the value, starting with the outermost key. - * - * @return - * TRUE if all the parent keys exist, FALSE otherwise. - * - * @see drupal_array_get_nested_value() - */ -function drupal_array_nested_key_exists(array $array, array $parents) { - // Although this function is similar to PHP's array_key_exists(), its - // arguments should be consistent with drupal_array_get_nested_value(). - $key_exists = NULL; - drupal_array_get_nested_value($array, $parents, $key_exists); - return $key_exists; -} - -/** - * Provide theme registration for themes across .inc files. - */ -function drupal_common_theme() { - return array( - // theme.inc - 'html' => array( - 'render element' => 'page', - 'template' => 'html', - ), - 'page' => array( - 'render element' => 'page', - 'template' => 'page', - ), - 'region' => array( - 'render element' => 'elements', - 'template' => 'region', - ), - 'status_messages' => array( - 'variables' => array('display' => NULL), - ), - 'link' => array( - 'variables' => array('text' => NULL, 'path' => NULL, 'options' => array()), - ), - 'links' => array( - 'variables' => array('links' => NULL, 'attributes' => array('class' => array('links')), 'heading' => array()), - ), - 'image' => array( - // HTML 4 and XHTML 1.0 always require an alt attribute. The HTML 5 draft - // allows the alt attribute to be omitted in some cases. Therefore, - // default the alt attribute to an empty string, but allow code calling - // theme('image') to pass explicit NULL for it to be omitted. Usually, - // neither omission nor an empty string satisfies accessibility - // requirements, so it is strongly encouraged for code calling - // theme('image') to pass a meaningful value for the alt variable. - // - http://www.w3.org/TR/REC-html40/struct/objects.html#h-13.8 - // - http://www.w3.org/TR/xhtml1/dtds.html - // - http://dev.w3.org/html5/spec/Overview.html#alt - // The title attribute is optional in all cases, so it is omitted by - // default. - 'variables' => array('path' => NULL, 'width' => NULL, 'height' => NULL, 'alt' => '', 'title' => NULL, 'attributes' => array()), - ), - 'breadcrumb' => array( - 'variables' => array('breadcrumb' => NULL), - ), - 'help' => array( - 'variables' => array(), - ), - 'table' => array( - 'variables' => array('header' => NULL, 'rows' => NULL, 'attributes' => array(), 'caption' => NULL, 'colgroups' => array(), 'sticky' => TRUE, 'empty' => ''), - ), - 'tablesort_indicator' => array( - 'variables' => array('style' => NULL), - ), - 'mark' => array( - 'variables' => array('type' => MARK_NEW), - ), - 'item_list' => array( - 'variables' => array('items' => array(), 'title' => '', 'type' => 'ul', 'attributes' => array()), - ), - 'more_help_link' => array( - 'variables' => array('url' => NULL), - ), - 'feed_icon' => array( - 'variables' => array('url' => NULL, 'title' => NULL), - ), - 'more_link' => array( - 'variables' => array('url' => NULL, 'title' => NULL) - ), - 'username' => array( - 'variables' => array('account' => NULL), - ), - 'progress_bar' => array( - 'variables' => array('percent' => NULL, 'message' => NULL), - ), - 'indentation' => array( - 'variables' => array('size' => 1), - ), - 'html_tag' => array( - 'render element' => 'element', - ), - // from theme.maintenance.inc - 'maintenance_page' => array( - 'variables' => array('content' => NULL, 'show_messages' => TRUE), - 'template' => 'maintenance-page', - ), - 'update_page' => array( - 'variables' => array('content' => NULL, 'show_messages' => TRUE), - ), - 'install_page' => array( - 'variables' => array('content' => NULL), - ), - 'task_list' => array( - 'variables' => array('items' => NULL, 'active' => NULL), - ), - 'authorize_message' => array( - 'variables' => array('message' => NULL, 'success' => TRUE), - ), - 'authorize_report' => array( - 'variables' => array('messages' => array()), - ), - // from pager.inc - 'pager' => array( - 'variables' => array('tags' => array(), 'element' => 0, 'parameters' => array(), 'quantity' => 9), - ), - 'pager_first' => array( - 'variables' => array('text' => NULL, 'element' => 0, 'parameters' => array()), - ), - 'pager_previous' => array( - 'variables' => array('text' => NULL, 'element' => 0, 'interval' => 1, 'parameters' => array()), - ), - 'pager_next' => array( - 'variables' => array('text' => NULL, 'element' => 0, 'interval' => 1, 'parameters' => array()), - ), - 'pager_last' => array( - 'variables' => array('text' => NULL, 'element' => 0, 'parameters' => array()), - ), - 'pager_link' => array( - 'variables' => array('text' => NULL, 'page_new' => NULL, 'element' => NULL, 'parameters' => array(), 'attributes' => array()), - ), - // from menu.inc - 'menu_link' => array( - 'render element' => 'element', - ), - 'menu_tree' => array( - 'render element' => 'tree', - ), - 'menu_local_task' => array( - 'render element' => 'element', - ), - 'menu_local_action' => array( - 'render element' => 'element', - ), - 'menu_local_tasks' => array( - 'variables' => array('primary' => array(), 'secondary' => array()), - ), - // from form.inc - 'select' => array( - 'render element' => 'element', - ), - 'fieldset' => array( - 'render element' => 'element', - ), - 'radio' => array( - 'render element' => 'element', - ), - 'radios' => array( - 'render element' => 'element', - ), - 'date' => array( - 'render element' => 'element', - ), - 'exposed_filters' => array( - 'render element' => 'form', - ), - 'checkbox' => array( - 'render element' => 'element', - ), - 'checkboxes' => array( - 'render element' => 'element', - ), - 'button' => array( - 'render element' => 'element', - ), - 'image_button' => array( - 'render element' => 'element', - ), - 'hidden' => array( - 'render element' => 'element', - ), - 'textfield' => array( - 'render element' => 'element', - ), - 'form' => array( - 'render element' => 'element', - ), - 'textarea' => array( - 'render element' => 'element', - ), - 'password' => array( - 'render element' => 'element', - ), - 'file' => array( - 'render element' => 'element', - ), - 'tableselect' => array( - 'render element' => 'element', - ), - 'form_element' => array( - 'render element' => 'element', - ), - 'form_required_marker' => array( - 'render element' => 'element', - ), - 'form_element_label' => array( - 'render element' => 'element', - ), - 'vertical_tabs' => array( - 'render element' => 'element', - ), - 'container' => array( - 'render element' => 'element', - ), - ); -} - -/** - * @ingroup schemaapi - * @{ - */ - -/** - * Creates all tables in a module's hook_schema() implementation. - * - * Note: This function does not pass the module's schema through - * hook_schema_alter(). The module's tables will be created exactly as the - * module defines them. - * - * @param $module - * The module for which the tables will be created. - */ -function drupal_install_schema($module) { - $schema = drupal_get_schema_unprocessed($module); - _drupal_schema_initialize($schema, $module, FALSE); - - foreach ($schema as $name => $table) { - db_create_table($name, $table); - } -} - -/** - * Remove all tables that a module defines in its hook_schema(). - * - * Note: This function does not pass the module's schema through - * hook_schema_alter(). The module's tables will be created exactly as the - * module defines them. - * - * @param $module - * The module for which the tables will be removed. - * @return - * An array of arrays with the following key/value pairs: - * - success: a boolean indicating whether the query succeeded. - * - query: the SQL query(s) executed, passed through check_plain(). - */ -function drupal_uninstall_schema($module) { - $schema = drupal_get_schema_unprocessed($module); - _drupal_schema_initialize($schema, $module, FALSE); - - foreach ($schema as $table) { - if (db_table_exists($table['name'])) { - db_drop_table($table['name']); - } - } -} - -/** - * Returns the unprocessed and unaltered version of a module's schema. - * - * Use this function only if you explicitly need the original - * specification of a schema, as it was defined in a module's - * hook_schema(). No additional default values will be set, - * hook_schema_alter() is not invoked and these unprocessed - * definitions won't be cached. - * - * This function can be used to retrieve a schema specification in - * hook_schema(), so it allows you to derive your tables from existing - * specifications. - * - * It is also used by drupal_install_schema() and - * drupal_uninstall_schema() to ensure that a module's tables are - * created exactly as specified without any changes introduced by a - * module that implements hook_schema_alter(). - * - * @param $module - * The module to which the table belongs. - * @param $table - * The name of the table. If not given, the module's complete schema - * is returned. - */ -function drupal_get_schema_unprocessed($module, $table = NULL) { - // Load the .install file to get hook_schema. - module_load_install($module); - $schema = module_invoke($module, 'schema'); - - if (isset($table) && isset($schema[$table])) { - return $schema[$table]; - } - elseif (!empty($schema)) { - return $schema; - } - return array(); -} - -/** - * Fill in required default values for table definitions returned by hook_schema(). - * - * @param $schema - * The schema definition array as it was returned by the module's - * hook_schema(). - * @param $module - * The module for which hook_schema() was invoked. - * @param $remove_descriptions - * (optional) Whether to additionally remove 'description' keys of all tables - * and fields to improve performance of serialize() and unserialize(). - * Defaults to TRUE. - */ -function _drupal_schema_initialize(&$schema, $module, $remove_descriptions = TRUE) { - // Set the name and module key for all tables. - foreach ($schema as $name => &$table) { - if (empty($table['module'])) { - $table['module'] = $module; - } - if (!isset($table['name'])) { - $table['name'] = $name; - } - if ($remove_descriptions) { - unset($table['description']); - foreach ($table['fields'] as &$field) { - unset($field['description']); - } - } - } -} - -/** - * Retrieve a list of fields from a table schema. The list is suitable for use in a SQL query. - * - * @param $table - * The name of the table from which to retrieve fields. - * @param - * An optional prefix to to all fields. - * - * @return An array of fields. - **/ -function drupal_schema_fields_sql($table, $prefix = NULL) { - $schema = drupal_get_schema($table); - $fields = array_keys($schema['fields']); - if ($prefix) { - $columns = array(); - foreach ($fields as $field) { - $columns[] = "$prefix.$field"; - } - return $columns; - } - else { - return $fields; - } -} - -/** - * Saves (inserts or updates) a record to the database based upon the schema. - * - * @param $table - * The name of the table; this must be defined by a hook_schema() - * implementation. - * @param $record - * An object or array representing the record to write, passed in by - * reference. If inserting a new record, values not provided in $record will - * 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 - * To indicate that this is a new record to be inserted, omit this argument. - * If this is an update, this argument specifies the primary keys' field - * names. If there is only 1 field in the key, you may pass in a string; if - * there are multiple fields in the key, pass in an array. - * - * @return - * If the record insert or update failed, returns FALSE. If it succeeded, - * returns SAVED_NEW or SAVED_UPDATED, depending on the operation performed. - */ -function drupal_write_record($table, &$record, $primary_keys = array()) { - // Standardize $primary_keys to an array. - if (is_string($primary_keys)) { - $primary_keys = array($primary_keys); - } - - $schema = drupal_get_schema($table); - if (empty($schema)) { - return FALSE; - } - - $object = (object) $record; - $fields = array(); - - // Go through the schema to determine fields to write. - foreach ($schema['fields'] as $field => $info) { - if ($info['type'] == 'serial') { - // Skip serial types if we are updating. - if (!empty($primary_keys)) { - continue; - } - // Track serial field so we can helpfully populate them after the query. - // NOTE: Each table should come with one serial field only. - $serial = $field; - } - - // Skip field if it is in $primary_keys as it is unnecessary to update a - // field to the value it is already set to. - if (in_array($field, $primary_keys)) { - continue; - } - - if (!property_exists($object, $field)) { - // Skip fields that are not provided, default values are already known - // by the database. - continue; - } - - // Build array of fields to update or insert. - if (empty($info['serialize'])) { - $fields[$field] = $object->$field; - } - else { - $fields[$field] = serialize($object->$field); - } - - // Type cast to proper datatype, except when the value is NULL and the - // column allows this. - // - // MySQL PDO silently casts e.g. FALSE and '' to 0 when inserting the value - // into an integer column, but PostgreSQL PDO does not. Also type cast NULL - // when the column does not allow this. - if (isset($object->$field) || !empty($info['not null'])) { - if ($info['type'] == 'int' || $info['type'] == 'serial') { - $fields[$field] = (int) $fields[$field]; - } - elseif ($info['type'] == 'float') { - $fields[$field] = (float) $fields[$field]; - } - else { - $fields[$field] = (string) $fields[$field]; - } - } - } - - if (empty($fields)) { - return; - } - - // Build the SQL. - if (empty($primary_keys)) { - // We are doing an insert. - $options = array('return' => Database::RETURN_INSERT_ID); - if (isset($serial) && isset($fields[$serial])) { - // If the serial column has been explicitly set with an ID, then we don't - // require the database to return the last insert id. - if ($fields[$serial]) { - $options['return'] = Database::RETURN_AFFECTED; - } - // If a serial column does exist with no value (i.e. 0) then remove it as - // the database will insert the correct value for us. - else { - unset($fields[$serial]); - } - } - $query = db_insert($table, $options)->fields($fields); - $return = SAVED_NEW; - } - else { - $query = db_update($table)->fields($fields); - foreach ($primary_keys as $key) { - $query->condition($key, $object->$key); - } - $return = SAVED_UPDATED; - } - - // Execute the SQL. - if ($query_return = $query->execute()) { - if (isset($serial)) { - // If the database was not told to return the last insert id, it will be - // because we already know it. - if (isset($options) && $options['return'] != Database::RETURN_INSERT_ID) { - $object->$serial = $fields[$serial]; - } - else { - $object->$serial = $query_return; - } - } - } - // If we have a single-field primary key but got no insert ID, the - // query failed. Note that we explicitly check for FALSE, because - // a valid update query which doesn't change any values will return - // zero (0) affected rows. - elseif ($query_return === FALSE && count($primary_keys) == 1) { - $return = FALSE; - } - - // If we are inserting, populate empty fields with default values. - if (empty($primary_keys)) { - foreach ($schema['fields'] as $field => $info) { - if (isset($info['default']) && !property_exists($object, $field)) { - $object->$field = $info['default']; - } - } - } - - // If we began with an array, convert back. - if (is_array($record)) { - $record = (array) $object; - } - - return $return; -} - -/** - * @} End of "ingroup schemaapi". - */ - -/** - * Parses Drupal module and theme .info files. - * - * Info files are NOT for placing arbitrary theme and module-specific settings. - * Use variable_get() and variable_set() for that. - * - * Information stored in a module .info file: - * - name: The real name of the module for display purposes. - * - description: A brief description of the module. - * - dependencies: An array of shortnames of other modules this module requires. - * - package: The name of the package of modules this module belongs to. - * - * See forum.info for an example of a module .info file. - * - * Information stored in a theme .info file: - * - name: The real name of the theme for display purposes. - * - description: Brief description. - * - screenshot: Path to screenshot relative to the theme's .info file. - * - engine: Theme engine; typically phptemplate. - * - base: Name of a base theme, if applicable; e.g., base = zen. - * - regions: Listed regions; e.g., region[left] = Left sidebar. - * - features: Features available; e.g., features[] = logo. - * - stylesheets: Theme stylesheets; e.g., stylesheets[all][] = my-style.css. - * - scripts: Theme scripts; e.g., scripts[] = my-script.js. - * - * See bartik.info for an example of a theme .info file. - * - * @param $filename - * The file we are parsing. Accepts file with relative or absolute path. - * - * @return - * The info array. - * - * @see drupal_parse_info_format() - */ -function drupal_parse_info_file($filename) { - $info = &drupal_static(__FUNCTION__, array()); - - if (!isset($info[$filename])) { - if (!file_exists($filename)) { - $info[$filename] = array(); - } - else { - $data = file_get_contents($filename); - $info[$filename] = drupal_parse_info_format($data); - } - } - return $info[$filename]; -} - -/** - * Parse data in Drupal's .info format. - * - * Data should be in an .ini-like format to specify values. White-space - * generally doesn't matter, except inside values: - * @code - * key = value - * key = "value" - * key = 'value' - * key = "multi-line - * value" - * key = 'multi-line - * value' - * key - * = - * 'value' - * @endcode - * - * Arrays are created using a HTTP GET alike syntax: - * @code - * key[] = "numeric array" - * key[index] = "associative array" - * key[index][] = "nested numeric array" - * key[index][index] = "nested associative array" - * @endcode - * - * PHP constants are substituted in, but only when used as the entire value. - * Comments should start with a semi-colon at the beginning of a line. - * - * @param $data - * A string to parse. - * @return - * The info array. - * - * @see drupal_parse_info_file() - */ -function drupal_parse_info_format($data) { - $info = array(); - $constants = get_defined_constants(); - - if (preg_match_all(' - @^\s* # Start at the beginning of a line, ignoring leading whitespace - ((?: - [^=;\[\]]| # Key names cannot contain equal signs, semi-colons or square brackets, - \[[^\[\]]*\] # unless they are balanced and not nested - )+?) - \s*=\s* # Key/value pairs are separated by equal signs (ignoring white-space) - (?: - ("(?:[^"]|(?<=\\\\)")*")| # Double-quoted string, which may contain slash-escaped quotes/slashes - (\'(?:[^\']|(?<=\\\\)\')*\')| # Single-quoted string, which may contain slash-escaped quotes/slashes - ([^\r\n]*?) # Non-quoted string - )\s*$ # Stop at the next end of a line, ignoring trailing whitespace - @msx', $data, $matches, PREG_SET_ORDER)) { - foreach ($matches as $match) { - // Fetch the key and value string - $i = 0; - foreach (array('key', 'value1', 'value2', 'value3') as $var) { - $$var = isset($match[++$i]) ? $match[$i] : ''; - } - $value = stripslashes(substr($value1, 1, -1)) . stripslashes(substr($value2, 1, -1)) . $value3; - - // Parse array syntax - $keys = preg_split('/\]?\[/', rtrim($key, ']')); - $last = array_pop($keys); - $parent = &$info; - - // Create nested arrays - foreach ($keys as $key) { - if ($key == '') { - $key = count($parent); - } - if (!isset($parent[$key]) || !is_array($parent[$key])) { - $parent[$key] = array(); - } - $parent = &$parent[$key]; - } - - // Handle PHP constants. - if (isset($constants[$value])) { - $value = $constants[$value]; - } - - // Insert actual value - if ($last == '') { - $last = count($parent); - } - $parent[$last] = $value; - } - } - - return $info; -} - -/** - * Severity levels, as defined in RFC 3164: http://www.ietf.org/rfc/rfc3164.txt. - * - * @return - * Array of the possible severity levels for log messages. - * - * @see watchdog() - * @ingroup logging_severity_levels - */ -function watchdog_severity_levels() { - return array( - WATCHDOG_EMERGENCY => t('emergency'), - WATCHDOG_ALERT => t('alert'), - WATCHDOG_CRITICAL => t('critical'), - WATCHDOG_ERROR => t('error'), - WATCHDOG_WARNING => t('warning'), - WATCHDOG_NOTICE => t('notice'), - WATCHDOG_INFO => t('info'), - WATCHDOG_DEBUG => t('debug'), - ); -} - - -/** - * Explode a string of given tags into an array. - * - * @see drupal_implode_tags() - */ -function drupal_explode_tags($tags) { - // This regexp allows the following types of user input: - // this, "somecompany, llc", "and ""this"" w,o.rks", foo bar - $regexp = '%(?:^|,\ *)("(?>[^"]*)(?>""[^"]* )*"|(?: [^",]*))%x'; - preg_match_all($regexp, $tags, $matches); - $typed_tags = array_unique($matches[1]); - - $tags = array(); - foreach ($typed_tags as $tag) { - // If a user has escaped a term (to demonstrate that it is a group, - // or includes a comma or quote character), we remove the escape - // formatting so to save the term into the database as the user intends. - $tag = trim(str_replace('""', '"', preg_replace('/^"(.*)"$/', '\1', $tag))); - if ($tag != "") { - $tags[] = $tag; - } - } - - return $tags; -} - -/** - * Implode an array of tags into a string. - * - * @see drupal_explode_tags() - */ -function drupal_implode_tags($tags) { - $encoded_tags = array(); - foreach ($tags as $tag) { - // Commas and quotes in tag names are special cases, so encode them. - if (strpos($tag, ',') !== FALSE || strpos($tag, '"') !== FALSE) { - $tag = '"' . str_replace('"', '""', $tag) . '"'; - } - - $encoded_tags[] = $tag; - } - return implode(', ', $encoded_tags); -} - -/** - * Flush all cached data on the site. - * - * 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. - */ -function drupal_flush_all_caches() { - // Change query-strings on css/js files to enforce reload for all users. - _drupal_flush_css_js(); - - registry_rebuild(); - drupal_clear_css_cache(); - drupal_clear_js_cache(); - - // Rebuild the theme data. Note that the module data is rebuilt above, as - // part of registry_rebuild(). - system_rebuild_theme_data(); - drupal_theme_rebuild(); - - node_types_rebuild(); - // node_menu() defines menu items based on node types so it needs to come - // after node types are rebuilt. - menu_rebuild(); - - // Synchronize to catch any actions that were added or removed. - actions_synchronize(); - - // Don't clear cache_form - in-progress form submissions may break. - // Ordered so clearing the page cache will always be the last action. - $core = array('cache', 'path', 'filter', 'bootstrap', 'page'); - $cache_bins = array_merge(module_invoke_all('flush_caches'), $core); - foreach ($cache_bins as $bin) { - cache($bin)->flush(); - } - - // Rebuild the bootstrap module list. We do this here so that developers - // can get new hook_boot() implementations registered without having to - // write a hook_update_N() function. - _system_update_bootstrap_status(); -} - -/** - * Helper function to change query-strings on css/js files. - * - * Changes the character added to all css/js files as dummy query-string, so - * that all browsers are forced to reload fresh files. - */ -function _drupal_flush_css_js() { - // The timestamp is converted to base 36 in order to make it more compact. - variable_set('css_js_query_string', base_convert(REQUEST_TIME, 10, 36)); -} - -/** - * Debug function used for outputting debug information. - * - * The debug information is passed on to trigger_error() after being converted - * to a string using _drupal_debug_message(). - * - * @param $data - * Data to be output. - * @param $label - * Label to prefix the data. - * @param $print_r - * Flag to switch between print_r() and var_export() for data conversion to - * string. Set $print_r to TRUE when dealing with a recursive data structure - * as var_export() will generate an error. - */ -function debug($data, $label = NULL, $print_r = FALSE) { - // Print $data contents to string. - $string = check_plain($print_r ? print_r($data, TRUE) : var_export($data, TRUE)); - - // Display values with pre-formatting to increase readability. - $string = '<pre>' . $string . '</pre>'; - - trigger_error(trim($label ? "$label: $string" : $string)); -} - -/** - * Parse a dependency for comparison by drupal_check_incompatibility(). - * - * @param $dependency - * A dependency string, for example 'foo (>=8.x-4.5-beta5, 3.x)'. - * @return - * An associative array with three keys: - * - 'name' includes the name of the thing to depend on (e.g. 'foo'). - * - 'original_version' contains the original version string (which can be - * used in the UI for reporting incompatibilities). - * - 'versions' is a list of associative arrays, each containing the keys - * 'op' and 'version'. 'op' can be one of: '=', '==', '!=', '<>', '<', - * '<=', '>', or '>='. 'version' is one piece like '4.5-beta3'. - * Callers should pass this structure to drupal_check_incompatibility(). - * - * @see drupal_check_incompatibility() - */ -function drupal_parse_dependency($dependency) { - // We use named subpatterns and support every op that version_compare - // supports. Also, op is optional and defaults to equals. - $p_op = '(?P<operation>!=|==|=|<|<=|>|>=|<>)?'; - // Core version is always optional: 8.x-2.x and 2.x is treated the same. - $p_core = '(?:' . preg_quote(DRUPAL_CORE_COMPATIBILITY) . '-)?'; - $p_major = '(?P<major>\d+)'; - // By setting the minor version to x, branches can be matched. - $p_minor = '(?P<minor>(?:\d+|x)(?:-[A-Za-z]+\d+)?)'; - $value = array(); - $parts = explode('(', $dependency, 2); - $value['name'] = trim($parts[0]); - if (isset($parts[1])) { - $value['original_version'] = ' (' . $parts[1]; - foreach (explode(',', $parts[1]) as $version) { - if (preg_match("/^\s*$p_op\s*$p_core$p_major\.$p_minor/", $version, $matches)) { - $op = !empty($matches['operation']) ? $matches['operation'] : '='; - if ($matches['minor'] == 'x') { - // Drupal considers "2.x" to mean any version that begins with - // "2" (e.g. 2.0, 2.9 are all "2.x"). PHP's version_compare(), - // on the other hand, treats "x" as a string; so to - // version_compare(), "2.x" is considered less than 2.0. This - // means that >=2.x and <2.x are handled by version_compare() - // as we need, but > and <= are not. - if ($op == '>' || $op == '<=') { - $matches['major']++; - } - // Equivalence can be checked by adding two restrictions. - if ($op == '=' || $op == '==') { - $value['versions'][] = array('op' => '<', 'version' => ($matches['major'] + 1) . '.x'); - $op = '>='; - } - } - $value['versions'][] = array('op' => $op, 'version' => $matches['major'] . '.' . $matches['minor']); - } - } - } - return $value; -} - -/** - * Check whether a version is compatible with a given dependency. - * - * @param $v - * The parsed dependency structure from drupal_parse_dependency(). - * @param $current_version - * The version to check against (like 4.2). - * @return - * NULL if compatible, otherwise the original dependency version string that - * caused the incompatibility. - * - * @see drupal_parse_dependency() - */ -function drupal_check_incompatibility($v, $current_version) { - if (!empty($v['versions'])) { - foreach ($v['versions'] as $required_version) { - if ((isset($required_version['op']) && !version_compare($current_version, $required_version['version'], $required_version['op']))) { - return $v['original_version']; - } - } - } -} - -/** - * Performs one or more XML-RPC request(s). - * - * Usage example: - * @code - * $result = xmlrpc('http://example.com/xmlrpc.php', array( - * 'service.methodName' => array($parameter, $second, $third), - * )); - * @endcode - * - * @param $url - * An absolute URL of the XML-RPC endpoint. - * @param $args - * An associative array whose keys are the methods to call and whose values - * are the arguments to pass to the respective method. If multiple methods - * are specified, a system.multicall is performed. - * @param $options - * (optional) An array of options to pass along to drupal_http_request(). - * - * @return - * For one request: - * Either the return value of the method on success, or FALSE. - * If FALSE is returned, see xmlrpc_errno() and xmlrpc_error_msg(). - * For multiple requests: - * An array of results. Each result will either be the result - * returned by the method called, or an xmlrpc_error object if the call - * failed. See xmlrpc_error(). - */ -function xmlrpc($url, $args, $options = array()) { - require_once DRUPAL_ROOT . '/core/includes/xmlrpc.inc'; - return _xmlrpc($url, $args, $options); -} - -/** - * Retrieves a list of all available archivers. - * - * @see hook_archiver_info() - * @see hook_archiver_info_alter() - */ -function archiver_get_info() { - $archiver_info = &drupal_static(__FUNCTION__, array()); - - if (empty($archiver_info)) { - $cache = cache()->get('archiver_info'); - if ($cache === FALSE) { - // Rebuild the cache and save it. - $archiver_info = module_invoke_all('archiver_info'); - drupal_alter('archiver_info', $archiver_info); - uasort($archiver_info, 'drupal_sort_weight'); - cache()->set('archiver_info', $archiver_info); - } - else { - $archiver_info = $cache->data; - } - } - - return $archiver_info; -} - -/** - * Returns a string of supported archive extensions. - * - * @return - * A space-separated string of extensions suitable for use by the file - * validation system. - */ -function archiver_get_extensions() { - $valid_extensions = array(); - foreach (archiver_get_info() as $archive) { - foreach ($archive['extensions'] as $extension) { - foreach (explode('.', $extension) as $part) { - if (!in_array($part, $valid_extensions)) { - $valid_extensions[] = $part; - } - } - } - } - return implode(' ', $valid_extensions); -} - -/** - * Create the appropriate archiver for the specified file. - * - * @param $file - * The full path of the archive file. Note that stream wrapper - * paths are supported, but not remote ones. - * @return - * A newly created instance of the archiver class appropriate - * for the specified file, already bound to that file. - * If no appropriate archiver class was found, will return FALSE. - */ -function archiver_get_archiver($file) { - // Archivers can only work on local paths - $filepath = drupal_realpath($file); - if (!is_file($filepath)) { - throw new Exception(t('Archivers can only operate on local files: %file not supported', array('%file' => $file))); - } - $archiver_info = archiver_get_info(); - - foreach ($archiver_info as $implementation) { - foreach ($implementation['extensions'] as $extension) { - // Because extensions may be multi-part, such as .tar.gz, - // we cannot use simpler approaches like substr() or pathinfo(). - // This method isn't quite as clean but gets the job done. - // Also note that the file may not yet exist, so we cannot rely - // on fileinfo() or other disk-level utilities. - if (strrpos($filepath, '.' . $extension) === strlen($filepath) - strlen('.' . $extension)) { - return new $implementation['class']($filepath); - } - } - } -} - -/** - * Drupal Updater registry. - * - * An Updater is a class that knows how to update various parts of the Drupal - * file system, for example to update modules that have newer releases, or to - * install a new theme. - * - * @return - * Returns the Drupal Updater class registry. - * - * @see hook_updater_info() - * @see hook_updater_info_alter() - */ -function drupal_get_updaters() { - $updaters = &drupal_static(__FUNCTION__); - if (!isset($updaters)) { - $updaters = module_invoke_all('updater_info'); - drupal_alter('updater_info', $updaters); - uasort($updaters, 'drupal_sort_weight'); - } - return $updaters; -} - -/** - * Drupal FileTransfer registry. - * - * @return - * Returns 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; -} diff --git a/core/includes/database/database.inc b/core/includes/database/database.inc deleted file mode 100644 index 77584f90c4a..00000000000 --- a/core/includes/database/database.inc +++ /dev/null @@ -1,3003 +0,0 @@ -<?php - -/** - * @file - * Core systems for the database layer. - * - * Classes required for basic functioning of the database system should be - * placed in this file. All utility functions should also be placed in this - * file only, as they cannot auto-load the way classes can. - */ - -/** - * @defgroup database Database abstraction layer - * @{ - * Allow the use of different database servers using the same code base. - * - * Drupal provides a database abstraction layer to provide developers with - * the ability to support multiple database servers easily. The intent of - * this layer is to preserve the syntax and power of SQL as much as possible, - * but also allow developers a way to leverage more complex functionality in - * a unified way. It also provides a structured interface for dynamically - * constructing queries when appropriate, and enforcing security checks and - * similar good practices. - * - * The system is built atop PHP's PDO (PHP Data Objects) database API and - * inherits much of its syntax and semantics. - * - * Most Drupal database SELECT queries are performed by a call to db_query() or - * db_query_range(). Module authors should also consider using the PagerDefault - * Extender for queries that return results that need to be presented on - * multiple pages, and the Tablesort Extender for generating appropriate queries - * for sortable tables. - * - * For example, one might wish to return a list of the most recent 10 nodes - * authored by a given user. Instead of directly issuing the SQL query - * @code - * SELECT n.nid, n.title, n.created FROM node n WHERE n.uid = $uid LIMIT 0, 10; - * @endcode - * one would instead call the Drupal functions: - * @code - * $result = db_query_range('SELECT n.nid, n.title, n.created - * FROM {node} n WHERE n.uid = :uid', 0, 10, array(':uid' => $uid)); - * foreach ($result as $record) { - * // Perform operations on $node->title, etc. here. - * } - * @endcode - * Curly braces are used around "node" to provide table prefixing via - * DatabaseConnection::prefixTables(). The explicit use of a user ID is pulled - * out into an argument passed to db_query() so that SQL injection attacks - * from user input can be caught and nullified. The LIMIT syntax varies between - * database servers, so that is abstracted into db_query_range() arguments. - * Finally, note the PDO-based ability to iterate over the result set using - * foreach (). - * - * All queries are passed as a prepared statement string. A - * prepared statement is a "template" of a query that omits literal or variable - * values in favor of placeholders. The values to place into those - * placeholders are passed separately, and the database driver handles - * inserting the values into the query in a secure fashion. That means you - * should never quote or string-escape a value to be inserted into the query. - * - * There are two formats for placeholders: named and unnamed. Named placeholders - * are strongly preferred in all cases as they are more flexible and - * self-documenting. Named placeholders should start with a colon ":" and can be - * followed by one or more letters, numbers or underscores. - * - * Named placeholders begin with a colon followed by a unique string. Example: - * @code - * SELECT nid, title FROM {node} WHERE uid=:uid; - * @endcode - * - * ":uid" is a placeholder that will be replaced with a literal value when - * the query is executed. A given placeholder label cannot be repeated in a - * given query, even if the value should be the same. When using named - * placeholders, the array of arguments to the query must be an associative - * array where keys are a placeholder label (e.g., :uid) and the value is the - * corresponding value to use. The array may be in any order. - * - * Unnamed placeholders are simply a question mark. Example: - * @code - * SELECT nid, title FROM {node} WHERE uid=?; - * @endcode - * - * In this case, the array of arguments must be an indexed array of values to - * use in the exact same order as the placeholders in the query. - * - * Note that placeholders should be a "complete" value. For example, when - * running a LIKE query the SQL wildcard character, %, should be part of the - * value, not the query itself. Thus, the following is incorrect: - * @code - * SELECT nid, title FROM {node} WHERE title LIKE :title%; - * @endcode - * It should instead read: - * @code - * SELECT nid, title FROM {node} WHERE title LIKE :title; - * @endcode - * and the value for :title should include a % as appropriate. Again, note the - * lack of quotation marks around :title. Because the value is not inserted - * into the query as one big string but as an explicitly separate value, the - * database server knows where the query ends and a value begins. That is - * considerably more secure against SQL injection than trying to remember - * which values need quotation marks and string escaping and which don't. - * - * INSERT, UPDATE, and DELETE queries need special care in order to behave - * consistently across all different databases. Therefore, they use a special - * object-oriented API for defining a query structurally. For example, rather - * than: - * @code - * INSERT INTO node (nid, title, body) VALUES (1, 'my title', 'my body'); - * @endcode - * one would instead write: - * @code - * $fields = array('nid' => 1, 'title' => 'my title', 'body' => 'my body'); - * db_insert('node')->fields($fields)->execute(); - * @endcode - * This method allows databases that need special data type handling to do so, - * while also allowing optimizations such as multi-insert queries. UPDATE and - * DELETE queries have a similar pattern. - * - * Drupal also supports transactions, including a transparent fallback for - * databases that do not support transactions. To start a new transaction, - * simply call $txn = db_transaction(); in your own code. The transaction will - * remain open for as long as the variable $txn remains in scope. When $txn is - * destroyed, the transaction will be committed. If your transaction is nested - * inside of another then Drupal will track each transaction and only commit - * the outer-most transaction when the last transaction object goes out out of - * scope, that is, all relevant queries completed successfully. - * - * Example: - * @code - * function my_transaction_function() { - * // The transaction opens here. - * $txn = db_transaction(); - * - * try { - * $id = db_insert('example') - * ->fields(array( - * 'field1' => 'mystring', - * 'field2' => 5, - * )) - * ->execute(); - * - * my_other_function($id); - * - * return $id; - * } - * catch (Exception $e) { - * // Something went wrong somewhere, so roll back now. - * $txn->rollback(); - * // Log the exception to watchdog. - * watchdog_exception('type', $e); - * } - * - * // $txn goes out of scope here. Unless the transaction was rolled back, it - * // gets automatically committed here. - * } - * - * function my_other_function($id) { - * // The transaction is still open here. - * - * if ($id % 2 == 0) { - * db_update('example') - * ->condition('id', $id) - * ->fields(array('field2' => 10)) - * ->execute(); - * } - * } - * @endcode - * - * @link http://drupal.org/developing/api/database - */ - - -/** - * Base Database API class. - * - * This class provides a Drupal-specific extension of the PDO database - * abstraction class in PHP. Every database driver implementation must provide a - * concrete implementation of it to support special handling required by that - * database. - * - * @see http://php.net/manual/en/book.pdo.php - */ -abstract class DatabaseConnection extends PDO { - - /** - * The database target this connection is for. - * - * We need this information for later auditing and logging. - * - * @var string - */ - 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. - * - * @var DatabaseLog - */ - protected $logger = NULL; - - /** - * Tracks the number of "layers" of transactions currently active. - * - * On many databases transactions cannot nest. Instead, we track - * nested calls to transactions and collapse them into a single - * transaction. - * - * @var array - */ - protected $transactionLayers = array(); - - /** - * Index of what driver-specific class to use for various operations. - * - * @var array - */ - protected $driverClasses = array(); - - /** - * The name of the Statement class for this connection. - * - * @var string - */ - protected $statementClass = 'DatabaseStatementBase'; - - /** - * Whether this database connection supports transactions. - * - * @var bool - */ - protected $transactionSupport = TRUE; - - /** - * Whether this database connection supports transactional DDL. - * - * Set to FALSE by default because few databases support this feature. - * - * @var bool - */ - protected $transactionalDDLSupport = FALSE; - - /** - * An index used to generate unique temporary table names. - * - * @var integer - */ - protected $temporaryNameIndex = 0; - - /** - * The connection information for this connection object. - * - * @var array - */ - protected $connectionOptions = array(); - - /** - * The schema object for this connection. - * - * @var object - */ - protected $schema = NULL; - - /** - * The prefixes used by this database connection. - * - * @var array - */ - protected $prefixes = array(); - - /** - * List of search values for use in prefixTables(). - * - * @var array - */ - protected $prefixSearch = array(); - - /** - * List of replacement values for use in prefixTables(). - * - * @var array - */ - protected $prefixReplace = array(); - - function __construct($dsn, $username, $password, $driver_options = array()) { - // Initialize and prepare the connection prefix. - $this->setPrefix(isset($this->connectionOptions['prefix']) ? $this->connectionOptions['prefix'] : ''); - - // Because the other methods don't seem to work right. - $driver_options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION; - - // Call PDO::__construct and PDO::setAttribute. - parent::__construct($dsn, $username, $password, $driver_options); - - // Set a specific PDOStatement class if the driver requires that. - if (!empty($this->statementClass)) { - $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array($this->statementClass, array($this))); - } - } - - /** - * Returns the default query options for any given query. - * - * A given query can be customized with a number of option flags in an - * associative array: - * - target: The database "target" against which to execute a query. Valid - * values are "default" or "slave". The system will first try to open a - * connection to a database specified with the user-supplied key. If one - * is not available, it will silently fall back to the "default" target. - * If multiple databases connections are specified with the same target, - * one will be selected at random for the duration of the request. - * - fetch: This element controls how rows from a result set will be - * returned. Legal values include PDO::FETCH_ASSOC, PDO::FETCH_BOTH, - * 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 - * object of that class. The behavior of all other values is defined by PDO. - * See http://php.net/manual/pdostatement.fetch.php - * - return: Depending on the type of query, different return values may be - * meaningful. This directive instructs the system which type of return - * value is desired. The system will generally set the correct value - * automatically, so it is extremely rare that a module developer will ever - * need to specify this value. Setting it incorrectly will likely lead to - * unpredictable results or fatal errors. Legal values include: - * - Database::RETURN_STATEMENT: Return the prepared statement object for - * the query. This is usually only meaningful for SELECT queries, where - * the statement object is how one accesses the result set returned by the - * query. - * - Database::RETURN_AFFECTED: Return the number of rows affected by an - * UPDATE or DELETE query. Be aware that means the number of rows actually - * changed, not the number of rows matched by the WHERE clause. - * - Database::RETURN_INSERT_ID: Return the sequence ID (primary key) - * created by an INSERT statement on a table that contains a serial - * column. - * - Database::RETURN_NULL: Do not return anything, as there is no - * meaningful value to return. That is the case for INSERT queries on - * tables that do not contain a serial column. - * - throw_exception: By default, the database system will catch any errors - * on a query as an Exception, log it, and then rethrow it so that code - * further up the call chain can take an appropriate action. To suppress - * that behavior and simply return NULL on failure, set this option to - * FALSE. - * - * @return - * An array of default query options. - */ - protected function defaultOptions() { - return array( - 'target' => 'default', - 'fetch' => PDO::FETCH_OBJ, - 'return' => Database::RETURN_STATEMENT, - 'throw_exception' => TRUE, - ); - } - - /** - * Returns the connection information for this connection object. - * - * Note that Database::getConnectionInfo() is for requesting information - * about an arbitrary database connection that is defined. This method - * is for requesting the connection information of this specific - * open connection object. - * - * @return - * An array of the connection information. The exact list of - * properties is driver-dependent. - */ - public function getConnectionOptions() { - return $this->connectionOptions; - } - - /** - * Set the list of prefixes used by this database connection. - * - * @param $prefix - * The prefixes, in any of the multiple forms documented in - * default.settings.php. - */ - protected function setPrefix($prefix) { - if (is_array($prefix)) { - $this->prefixes = $prefix + array('default' => ''); - } - else { - $this->prefixes = array('default' => $prefix); - } - - // 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[] = ''; - } - - /** - * Appends a database prefix to all tables in a query. - * - * Queries sent to Drupal should wrap all table names in curly brackets. This - * function searches for this syntax and adds Drupal's table prefix to all - * tables, allowing Drupal to coexist with other systems in the same database - * and/or schema if necessary. - * - * @param $sql - * A string containing a partial or entire SQL query. - * - * @return - * The properly-prefixed string. - */ - public function prefixTables($sql) { - return str_replace($this->prefixSearch, $this->prefixReplace, $sql); - } - - /** - * Find the prefix for a table. - * - * This function is for when you want to know the prefix of a table. This - * is not used in prefixTables due to performance reasons. - */ - public function tablePrefix($table = 'default') { - if (isset($this->prefixes[$table])) { - return $this->prefixes[$table]; - } - else { - return $this->prefixes['default']; - } - } - - /** - * Prepares a query string and returns the prepared statement. - * - * This method caches prepared statements, reusing them when - * possible. It also prefixes tables names enclosed in curly-braces. - * - * @param $query - * The query string as SQL, with curly-braces surrounding the - * table names. - * - * @return DatabaseStatementInterface - * A PDO prepared statement ready for its execute() method. - */ - public function prepareQuery($query) { - $query = $this->prefixTables($query); - - // Call PDO::prepare. - return parent::prepare($query); - } - - /** - * Tells this connection object what its target value is. - * - * This is needed for logging and auditing. It's sloppy to do in the - * constructor because the constructor for child classes has a different - * signature. We therefore also ensure that this function is only ever - * called once. - * - * @param $target - * The target this connection is for. Set to NULL (default) to disable - * logging entirely. - */ - public function setTarget($target = NULL) { - if (!isset($this->target)) { - $this->target = $target; - } - } - - /** - * Returns the target this connection is associated with. - * - * @return - * The target string of this connection. - */ - public function getTarget() { - 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. - * - * @param $logger - * The logging object we want to use. - */ - public function setLogger(DatabaseLog $logger) { - $this->logger = $logger; - } - - /** - * Gets the current logging object for this connection. - * - * @return DatabaseLog - * The current logging object for this connection. If there isn't one, - * NULL is returned. - */ - public function getLogger() { - return $this->logger; - } - - /** - * Creates the appropriate sequence name for a given table and serial field. - * - * This information is exposed to all database drivers, although it is only - * useful on some of them. This method is table prefix-aware. - * - * @param $table - * The table name to use for the sequence. - * @param $field - * The field name to use for the sequence. - * - * @return - * A table prefix-parsed string for the sequence name. - */ - public function makeSequenceName($table, $field) { - 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. - * - * This method provides a central handler for the actual execution of every - * query. All queries executed by Drupal are executed as PDO prepared - * statements. - * - * @param $query - * The query to execute. In most cases this will be a string containing - * an SQL query with placeholders. An already-prepared instance of - * DatabaseStatementInterface may also be passed in order to allow calling - * code to manually bind variables to a query. If a - * DatabaseStatementInterface is passed, the $args array will be ignored. - * It is extremely rare that module code will need to pass a statement - * object to this method. It is used primarily for database drivers for - * databases that require special LOB field handling. - * @param $args - * An array of arguments for the prepared statement. If the prepared - * statement uses ? placeholders, this array must be an indexed array. - * If it contains named placeholders, it must be an associative array. - * @param $options - * An associative array of options to control how the query is run. See - * the documentation for DatabaseConnection::defaultOptions() for details. - * - * @return DatabaseStatementInterface - * This method will return one of: the executed statement, the number of - * rows affected by the query (not the number matched), or the generated - * insert IT of the last query, depending on the value of - * $options['return']. Typically that value will be set by default or a - * query builder and should not be set by a user. If there is an error, - * this method will return NULL and may throw an exception if - * $options['throw_exception'] is TRUE. - * - * @throws PDOException - */ - public function query($query, array $args = array(), $options = array()) { - - // Use default values if not already set. - $options += $this->defaultOptions(); - - try { - // We allow either a pre-bound statement object or a literal string. - // In either case, we want to end up with an executed statement object, - // which we pass to PDOStatement::execute. - if ($query instanceof DatabaseStatementInterface) { - $stmt = $query; - $stmt->execute(NULL, $options); - } - else { - $this->expandArguments($query, $args); - $stmt = $this->prepareQuery($query); - $stmt->execute($args, $options); - } - - // Depending on the type of query we may need to return a different value. - // See DatabaseConnection::defaultOptions() for a description of each - // value. - switch ($options['return']) { - case Database::RETURN_STATEMENT: - return $stmt; - case Database::RETURN_AFFECTED: - return $stmt->rowCount(); - case Database::RETURN_INSERT_ID: - return $this->lastInsertId(); - case Database::RETURN_NULL: - return; - default: - throw new PDOException('Invalid return directive: ' . $options['return']); - } - } - catch (PDOException $e) { - if ($options['throw_exception']) { - // Add additional debug information. - if ($query instanceof DatabaseStatementInterface) { - $e->query_string = $stmt->getQueryString(); - } - else { - $e->query_string = $query; - } - $e->args = $args; - throw $e; - } - return NULL; - } - } - - /** - * Expands out shorthand placeholders. - * - * Drupal supports an alternate syntax for doing arrays of values. We - * therefore need to expand them out into a full, executable query string. - * - * @param $query - * The query string to modify. - * @param $args - * The arguments for the query. - * - * @return - * TRUE if the query was modified, FALSE otherwise. - */ - protected function expandArguments(&$query, &$args) { - $modified = FALSE; - - // If the placeholder value to insert is an array, assume that we need - // to expand it out into a comma-delimited set of placeholders. - foreach (array_filter($args, 'is_array') as $key => $data) { - $new_keys = array(); - foreach ($data as $i => $value) { - // This assumes that there are no other placeholders that use the same - // name. For example, if the array placeholder is defined as :example - // and there is already an :example_2 placeholder, this will generate - // a duplicate key. We do not account for that as the calling code - // is already broken if that happens. - $new_keys[$key . '_' . $i] = $value; - } - - // Update the query with the new placeholders. - // preg_replace is necessary to ensure the replacement does not affect - // placeholders that start with the same exact text. For example, if the - // query contains the placeholders :foo and :foobar, and :foo has an - // array of values, using str_replace would affect both placeholders, - // but using the following preg_replace would only affect :foo because - // it is followed by a non-word character. - $query = preg_replace('#' . $key . '\b#', implode(', ', array_keys($new_keys)), $query); - - // Update the args array with the new placeholders. - unset($args[$key]); - $args += $new_keys; - - $modified = TRUE; - } - - return $modified; - } - - /** - * Gets the driver-specific override class if any for the specified class. - * - * @param string $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 - * The name of the class that should be used for this driver. - */ - public function getDriverClass($class, array $files = array(), $use_autoload = FALSE) { - if (empty($this->driverClasses[$class])) { - $driver = $this->driver(); - $this->driverClasses[$class] = $class . '_' . $driver; - Database::loadDriverFile($driver, $files); - if (!class_exists($this->driverClasses[$class], $use_autoload)) { - $this->driverClasses[$class] = $class; - } - } - return $this->driverClasses[$class]; - } - - /** - * Prepares and returns a SELECT query object. - * - * @param $table - * The base table for this query, that is, the first table in the FROM - * clause. This table will also be used as the "base" table for query_alter - * hook implementations. - * @param $alias - * The alias of the base table of this query. - * @param $options - * An array of options on the query. - * - * @return SelectQueryInterface - * An appropriate SelectQuery object for this database connection. Note that - * it may be a driver-specific subclass of SelectQuery, depending on the - * driver. - * - * @see SelectQuery - */ - public function select($table, $alias = NULL, array $options = array()) { - $class = $this->getDriverClass('SelectQuery', array('query.inc', 'select.inc')); - return new $class($table, $alias, $this, $options); - } - - /** - * Prepares and returns an INSERT query object. - * - * @param $options - * An array of options on the query. - * - * @return InsertQuery - * A new InsertQuery object. - * - * @see InsertQuery - */ - public function insert($table, array $options = array()) { - $class = $this->getDriverClass('InsertQuery', array('query.inc')); - return new $class($this, $table, $options); - } - - /** - * Prepares and returns a MERGE query object. - * - * @param $options - * An array of options on the query. - * - * @return MergeQuery - * A new MergeQuery object. - * - * @see MergeQuery - */ - public function merge($table, array $options = array()) { - $class = $this->getDriverClass('MergeQuery', array('query.inc')); - return new $class($this, $table, $options); - } - - - /** - * Prepares and returns an UPDATE query object. - * - * @param $options - * An array of options on the query. - * - * @return UpdateQuery - * A new UpdateQuery object. - * - * @see UpdateQuery - */ - public function update($table, array $options = array()) { - $class = $this->getDriverClass('UpdateQuery', array('query.inc')); - return new $class($this, $table, $options); - } - - /** - * Prepares and returns a DELETE query object. - * - * @param $options - * An array of options on the query. - * - * @return DeleteQuery - * A new DeleteQuery object. - * - * @see DeleteQuery - */ - public function delete($table, array $options = array()) { - $class = $this->getDriverClass('DeleteQuery', array('query.inc')); - return new $class($this, $table, $options); - } - - /** - * Prepares and returns a TRUNCATE query object. - * - * @param $options - * An array of options on the query. - * - * @return TruncateQuery - * A new TruncateQuery object. - * - * @see TruncateQuery - */ - public function truncate($table, array $options = array()) { - $class = $this->getDriverClass('TruncateQuery', array('query.inc')); - return new $class($this, $table, $options); - } - - /** - * Returns a DatabaseSchema object for manipulating the schema. - * - * This method will lazy-load the appropriate schema library file. - * - * @return DatabaseSchema - * The DatabaseSchema object for this connection. - */ - public function schema() { - if (empty($this->schema)) { - $class = $this->getDriverClass('DatabaseSchema', array('schema.inc')); - if (class_exists($class)) { - $this->schema = new $class($this); - } - } - return $this->schema; - } - - /** - * Escapes a table name string. - * - * Force all table names to be strictly alphanumeric-plus-underscore. - * For some database drivers, it may also wrap the table name in - * database-specific escape characters. - * - * @return - * The sanitized table name string. - */ - public function escapeTable($table) { - return preg_replace('/[^A-Za-z0-9_.]+/', '', $table); - } - - /** - * Escapes a field name string. - * - * Force all field names to be strictly alphanumeric-plus-underscore. - * For some database drivers, it may also wrap the field name in - * database-specific escape characters. - * - * @return - * The sanitized field name string. - */ - public function escapeField($field) { - return preg_replace('/[^A-Za-z0-9_.]+/', '', $field); - } - - /** - * Escapes an alias name string. - * - * Force all alias names to be strictly alphanumeric-plus-underscore. In - * contrast to DatabaseConnection::escapeField() / - * DatabaseConnection::escapeTable(), this doesn't allow the period (".") - * because that is not allowed in aliases. - * - * @return - * The sanitized field name string. - */ - public function escapeAlias($field) { - return preg_replace('/[^A-Za-z0-9_]+/', '', $field); - } - - /** - * Escapes characters that work as wildcard characters in a LIKE pattern. - * - * The wildcard characters "%" and "_" as well as backslash are prefixed with - * a backslash. Use this to do a search for a verbatim string without any - * wildcard behavior. - * - * For example, the following does a case-insensitive query for all rows whose - * name starts with $prefix: - * @code - * $result = db_query( - * 'SELECT * FROM person WHERE name LIKE :pattern', - * array(':pattern' => db_like($prefix) . '%') - * ); - * @endcode - * - * Backslash is defined as escape character for LIKE patterns in - * DatabaseCondition::mapConditionOperator(). - * - * @param $string - * The string to escape. - * - * @return - * The escaped string. - */ - public function escapeLike($string) { - return addcslashes($string, '\%_'); - } - - /** - * Determines if there is an active transaction open. - * - * @return - * TRUE if we're currently in a transaction, FALSE otherwise. - */ - public function inTransaction() { - return ($this->transactionDepth() > 0); - } - - /** - * Determines current transaction depth. - */ - public function transactionDepth() { - return count($this->transactionLayers); - } - - /** - * Returns a new DatabaseTransaction object on this connection. - * - * @param $name - * Optional name of the savepoint. - * - * @see DatabaseTransaction - */ - public function startTransaction($name = '') { - $class = $this->getDriverClass('DatabaseTransaction'); - return new $class($this, $name); - } - - /** - * Rolls back the transaction entirely or to a named savepoint. - * - * This method throws an exception if no transaction is active. - * - * @param $savepoint_name - * The name of the savepoint. The default, 'drupal_transaction', will roll - * the entire transaction back. - * - * @throws DatabaseTransactionNoActiveException - * - * @see DatabaseTransaction::rollback() - */ - public function rollback($savepoint_name = 'drupal_transaction') { - if (!$this->supportsTransactions()) { - return; - } - if (!$this->inTransaction()) { - throw new DatabaseTransactionNoActiveException(); - } - // A previous rollback to an earlier savepoint may mean that the savepoint - // in question has already been rolled back. - if (!in_array($savepoint_name, $this->transactionLayers)) { - return; - } - - // We need to find the point we're rolling back to, all other savepoints - // 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)) { - if ($savepoint == $savepoint_name) { - // If it is the last the transaction in the stack, then it is not a - // savepoint, it is the transaction itself so we will need to roll back - // the transaction rather than a savepoint. - if (empty($this->transactionLayers)) { - break; - } - $this->query('ROLLBACK TO SAVEPOINT ' . $savepoint); - $this->popCommittableTransactions(); - if ($rolled_back_other_active_savepoints) { - throw new DatabaseTransactionOutOfOrderException(); - } - return; - } - else { - $rolled_back_other_active_savepoints = TRUE; - } - } - parent::rollBack(); - if ($rolled_back_other_active_savepoints) { - throw new DatabaseTransactionOutOfOrderException(); - } - } - - /** - * Increases the depth of transaction nesting. - * - * If no transaction is already active, we begin a new transaction. - * - * @throws DatabaseTransactionNameNonUniqueException - * - * @see DatabaseTransaction - */ - public function pushTransaction($name) { - if (!$this->supportsTransactions()) { - return; - } - if (isset($this->transactionLayers[$name])) { - throw new DatabaseTransactionNameNonUniqueException($name . " is already in use."); - } - // If we're already in a transaction then we want to create a savepoint - // rather than try to create another transaction. - if ($this->inTransaction()) { - $this->query('SAVEPOINT ' . $name); - } - else { - parent::beginTransaction(); - } - $this->transactionLayers[$name] = $name; - } - - /** - * Decreases the depth of transaction nesting. - * - * If we pop off the last transaction layer, then we either commit or roll - * back the transaction as necessary. If no transaction is active, we return - * because the transaction may have manually been rolled back. - * - * @param $name - * The name of the savepoint - * - * @throws DatabaseTransactionNoActiveException - * @throws DatabaseTransactionCommitFailedException - * - * @see DatabaseTransaction - */ - public function popTransaction($name) { - if (!$this->supportsTransactions()) { - return; - } - if (!isset($this->transactionLayers[$name])) { - throw new DatabaseTransactionNoActiveException(); - } - - // Mark this layer as committable. - $this->transactionLayers[$name] = FALSE; - $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. - unset($this->transactionLayers[$name]); - if (empty($this->transactionLayers)) { - if (!parent::commit()) { - throw new DatabaseTransactionCommitFailedException(); - } - } - else { - $this->query('RELEASE SAVEPOINT ' . $name); - } - } - } - - /** - * Runs a limited-range query on this database object. - * - * Use this as a substitute for ->query() when a subset of the query is to be - * returned. User-supplied arguments to the query should be passed in as - * separate parameters so that they can be properly escaped to avoid SQL - * injection attacks. - * - * @param $query - * A string containing an SQL query. - * @param $args - * An array of values to substitute into the query at placeholder markers. - * @param $from - * The first result row to return. - * @param $count - * The maximum number of result rows to return. - * @param $options - * An array of options on the query. - * - * @return DatabaseStatementInterface - * A database query result resource, or NULL if the query was not executed - * correctly. - */ - abstract public function queryRange($query, $from, $count, array $args = array(), array $options = array()); - - /** - * Generates a temporary table name. - * - * @return - * A table name. - */ - protected function generateTemporaryTableName() { - return "db_temporary_" . $this->temporaryNameIndex++; - } - - /** - * Runs a SELECT query and stores its results in a temporary table. - * - * Use this as a substitute for ->query() when the results need to stored - * in a temporary table. Temporary tables exist for the duration of the page - * request. User-supplied arguments to the query should be passed in as - * separate parameters so that they can be properly escaped to avoid SQL - * injection attacks. - * - * Note that if you need to know how many results were returned, you should do - * a SELECT COUNT(*) on the temporary table afterwards. - * - * @param $query - * A string containing a normal SELECT SQL query. - * @param $args - * An array of values to substitute into the query at placeholder markers. - * @param $options - * An associative array of options to control how the query is run. See - * the documentation for DatabaseConnection::defaultOptions() for details. - * - * @return - * The name of the temporary table. - */ - abstract function queryTemporary($query, array $args = array(), array $options = array()); - - /** - * Returns the type of database driver. - * - * This is not necessarily the same as the type of the database itself. For - * instance, there could be two MySQL drivers, mysql and mysql_mock. This - * function would return different values for each, but both would return - * "mysql" for databaseType(). - */ - abstract public function driver(); - - /** - * Returns the version of the database server. - */ - public function version() { - return $this->getAttribute(PDO::ATTR_SERVER_VERSION); - } - - /** - * Determines if this driver supports transactions. - * - * @return - * TRUE if this connection supports transactions, FALSE otherwise. - */ - public function supportsTransactions() { - return $this->transactionSupport; - } - - /** - * Determines if this driver supports transactional DDL. - * - * DDL queries are those that change the schema, such as ALTER queries. - * - * @return - * TRUE if this connection supports transactions for DDL queries, FALSE - * otherwise. - */ - public function supportsTransactionalDDL() { - return $this->transactionalDDLSupport; - } - - /** - * Returns the name of the PDO driver for this connection. - */ - abstract public function databaseType(); - - - /** - * Gets any special processing requirements for the condition operator. - * - * Some condition types require special processing, such as IN, because - * the value data they pass in is not a simple value. This is a simple - * overridable lookup function. Database connections should define only - * those operators they wish to be handled differently than the default. - * - * @param $operator - * The condition operator, such as "IN", "BETWEEN", etc. Case-sensitive. - * - * @return - * The extra handling directives for the specified operator, or NULL. - * - * @see DatabaseCondition::compile() - */ - abstract public function mapConditionOperator($operator); - - /** - * Throws an exception to deny direct access to transaction commits. - * - * We do not want to allow users to commit transactions at any time, only - * by destroying the transaction object or allowing it to go out of scope. - * A direct commit bypasses all of the safety checks we've built on top of - * PDO's transaction routines. - * - * @throws DatabaseTransactionExplicitCommitNotAllowedException - * - * @see DatabaseTransaction - */ - public function commit() { - throw new DatabaseTransactionExplicitCommitNotAllowedException(); - } - - /** - * Retrieves an unique id from a given sequence. - * - * Use this function if for some reason you can't use a serial field. For - * example, MySQL has no ways of reading of the current value of a sequence - * and PostgreSQL can not advance the sequence to be larger than a given - * value. Or sometimes you just need a unique integer. - * - * @param $existing_id - * After a database import, it might be that the sequences table is behind, - * so by passing in the maximum existing id, it can be assured that we - * never issue the same id. - * - * @return - * An integer number larger than any number returned by earlier calls and - * also larger than the $existing_id if one was passed in. - */ - abstract public function nextId($existing_id = 0); -} - -/** - * Primary front-controller for the database system. - * - * This class is uninstantiatable and un-extendable. It acts to encapsulate - * all control and shepherding of database connections into a single location - * without the use of globals. - */ -abstract class Database { - - /** - * Flag to indicate a query call should simply return NULL. - * - * This is used for queries that have no reasonable return value anyway, such - * as INSERT statements to a table without a serial primary key. - */ - const RETURN_NULL = 0; - - /** - * Flag to indicate a query call should return the prepared statement. - */ - const RETURN_STATEMENT = 1; - - /** - * Flag to indicate a query call should return the number of affected rows. - */ - const RETURN_AFFECTED = 2; - - /** - * Flag to indicate a query call should return the "last insert id". - */ - const RETURN_INSERT_ID = 3; - - /** - * An nested array of all active connections. It is keyed by database name - * and target. - * - * @var array - */ - static protected $connections = array(); - - /** - * A processed copy of the database connection information from settings.php. - * - * @var array - */ - static protected $databaseInfo = NULL; - - /** - * A list of key/target credentials to simply ignore. - * - * @var array - */ - static protected $ignoreTargets = array(); - - /** - * The key of the currently active database connection. - * - * @var string - */ - static protected $activeKey = 'default'; - - /** - * An array of active query log objects. - * - * Every connection has one and only one logger object for all targets and - * logging keys. - * - * array( - * '$db_key' => DatabaseLog object. - * ); - * - * @var array - */ - static protected $logs = array(); - - /** - * Starts logging a given logging key on the specified connection. - * - * @param $logging_key - * The logging key to log. - * @param $key - * The database connection key for which we want to log. - * - * @return DatabaseLog - * The query log object. Note that the log object does support richer - * methods than the few exposed through the Database class, so in some - * cases it may be desirable to access it directly. - * - * @see DatabaseLog - */ - final public static function startLog($logging_key, $key = 'default') { - if (empty(self::$logs[$key])) { - self::$logs[$key] = new DatabaseLog($key); - - // Every target already active for this connection key needs to have the - // logging object associated with it. - if (!empty(self::$connections[$key])) { - foreach (self::$connections[$key] as $connection) { - $connection->setLogger(self::$logs[$key]); - } - } - } - - self::$logs[$key]->start($logging_key); - return self::$logs[$key]; - } - - /** - * Retrieves the queries logged on for given logging key. - * - * This method also ends logging for the specified key. To get the query log - * to date without ending the logger request the logging object by starting - * it again (which does nothing to an open log key) and call methods on it as - * desired. - * - * @param $logging_key - * The logging key to log. - * @param $key - * The database connection key for which we want to log. - * - * @return array - * The query log for the specified logging key and connection. - * - * @see DatabaseLog - */ - final public static function getLog($logging_key, $key = 'default') { - if (empty(self::$logs[$key])) { - return NULL; - } - $queries = self::$logs[$key]->get($logging_key); - self::$logs[$key]->end($logging_key); - return $queries; - } - - /** - * Gets the connection object for the specified database key and target. - * - * @param $target - * The database target name. - * @param $key - * The database connection key. Defaults to NULL which means the active key. - * - * @return DatabaseConnection - * The corresponding connection object. - */ - final public static function getConnection($target = 'default', $key = NULL) { - if (!isset($key)) { - // By default, we want the active connection, set in setActiveConnection. - $key = self::$activeKey; - } - // If the requested target does not exist, or if it is ignored, we fall back - // to the default target. The target is typically either "default" or - // "slave", indicating to use a slave SQL server if one is available. If - // it's not available, then the default/master server is the correct server - // to use. - if (!empty(self::$ignoreTargets[$key][$target]) || !isset(self::$databaseInfo[$key][$target])) { - $target = 'default'; - } - - if (!isset(self::$connections[$key][$target])) { - // If necessary, a new connection is opened. - self::$connections[$key][$target] = self::openConnection($key, $target); - } - return self::$connections[$key][$target]; - } - - /** - * Determines if there is an active connection. - * - * Note that this method will return FALSE if no connection has been - * established yet, even if one could be. - * - * @return - * TRUE if there is at least one database connection established, FALSE - * otherwise. - */ - final public static function isActiveConnection() { - return !empty(self::$activeKey) && !empty(self::$connections) && !empty(self::$connections[self::$activeKey]); - } - - /** - * Sets the active connection to the specified key. - * - * @return - * The previous database connection key. - */ - final public static function setActiveConnection($key = 'default') { - if (empty(self::$databaseInfo)) { - self::parseConnectionInfo(); - } - - if (!empty(self::$databaseInfo[$key])) { - $old_key = self::$activeKey; - self::$activeKey = $key; - return $old_key; - } - } - - /** - * Process the configuration file for database information. - */ - final public static function parseConnectionInfo() { - global $databases; - - $database_info = is_array($databases) ? $databases : array(); - foreach ($database_info as $index => $info) { - foreach ($database_info[$index] as $target => $value) { - // If there is no "driver" property, then we assume it's an array of - // possible connections for this target. Pick one at random. That allows - // us to have, for example, multiple slave servers. - if (empty($value['driver'])) { - $database_info[$index][$target] = $database_info[$index][$target][mt_rand(0, count($database_info[$index][$target]) - 1)]; - } - - // Parse the prefix information. - if (!isset($database_info[$index][$target]['prefix'])) { - // Default to an empty prefix. - $database_info[$index][$target]['prefix'] = array( - 'default' => '', - ); - } - elseif (!is_array($database_info[$index][$target]['prefix'])) { - // Transform the flat form into an array form. - $database_info[$index][$target]['prefix'] = array( - 'default' => $database_info[$index][$target]['prefix'], - ); - } - } - } - - if (!is_array(self::$databaseInfo)) { - self::$databaseInfo = $database_info; - } - - // Merge the new $database_info into the existing. - // array_merge_recursive() cannot be used, as it would make multiple - // database, user, and password keys in the same database array. - else { - foreach ($database_info as $database_key => $database_values) { - foreach ($database_values as $target => $target_values) { - self::$databaseInfo[$database_key][$target] = $target_values; - } - } - } - } - - /** - * Adds database connection information for a given key/target. - * - * This method allows the addition of new connection credentials at runtime. - * Under normal circumstances the preferred way to specify database - * credentials is via settings.php. However, this method allows them to be - * added at arbitrary times, such as during unit tests, when connecting to - * admin-defined third party databases, etc. - * - * If the given key/target pair already exists, this method will be ignored. - * - * @param $key - * The database key. - * @param $target - * The database target name. - * @param $info - * The database connection information, as it would be defined in - * settings.php. Note that the structure of this array will depend on the - * database driver it is connecting to. - */ - public static function addConnectionInfo($key, $target, $info) { - if (empty(self::$databaseInfo[$key][$target])) { - self::$databaseInfo[$key][$target] = $info; - } - } - - /** - * Gets information on the specified database connection. - * - * @param $connection - * The connection key for which we want information. - */ - final public static function getConnectionInfo($key = 'default') { - if (empty(self::$databaseInfo)) { - self::parseConnectionInfo(); - } - - if (!empty(self::$databaseInfo[$key])) { - return self::$databaseInfo[$key]; - } - } - - /** - * Rename a connection and its corresponding connection information. - * - * @param $old_key - * The old connection key. - * @param $new_key - * The new connection key. - * @return - * TRUE in case of success, FALSE otherwise. - */ - final public static function renameConnection($old_key, $new_key) { - if (empty(self::$databaseInfo)) { - self::parseConnectionInfo(); - } - - if (!empty(self::$databaseInfo[$old_key]) && empty(self::$databaseInfo[$new_key])) { - // Migrate the database connection information. - self::$databaseInfo[$new_key] = self::$databaseInfo[$old_key]; - unset(self::$databaseInfo[$old_key]); - - // Migrate over the DatabaseConnection object if it exists. - if (isset(self::$connections[$old_key])) { - self::$connections[$new_key] = self::$connections[$old_key]; - unset(self::$connections[$old_key]); - } - - return TRUE; - } - else { - return FALSE; - } - } - - /** - * Remove a connection and its corresponding connection information. - * - * @param $key - * The connection key. - * @return - * TRUE in case of success, FALSE otherwise. - */ - final public static function removeConnection($key) { - if (isset(self::$databaseInfo[$key])) { - unset(self::$databaseInfo[$key]); - unset(self::$connections[$key]); - return TRUE; - } - else { - return FALSE; - } - } - - /** - * Opens a connection to the server specified by the given key and target. - * - * @param $key - * The database connection key, as specified in settings.php. The default is - * "default". - * @param $target - * The database target to open. - * - * @throws DatabaseConnectionNotDefinedException - * @throws DatabaseDriverNotSpecifiedException - */ - final protected static function openConnection($key, $target) { - if (empty(self::$databaseInfo)) { - self::parseConnectionInfo(); - } - - // If the requested database does not exist then it is an unrecoverable - // error. - if (!isset(self::$databaseInfo[$key])) { - throw new DatabaseConnectionNotDefinedException('The specified database connection is not defined: ' . $key); - } - - if (!$driver = self::$databaseInfo[$key][$target]['driver']) { - throw new DatabaseDriverNotSpecifiedException('Driver not specified for this database connection: ' . $key); - } - - // We cannot rely on the registry yet, because the registry requires an - // open database connection. - $driver_class = 'DatabaseConnection_' . $driver; - require_once DRUPAL_ROOT . '/core/includes/database/' . $driver . '/database.inc'; - $new_connection = new $driver_class(self::$databaseInfo[$key][$target]); - $new_connection->setTarget($target); - $new_connection->setKey($key); - - // If we have any active logging objects for this connection key, we need - // to associate them with the connection we just opened. - if (!empty(self::$logs[$key])) { - $new_connection->setLogger(self::$logs[$key]); - } - - return $new_connection; - } - - /** - * Closes a connection to the server specified by the given key and target. - * - * @param $target - * The database target name. Defaults to NULL meaning that all target - * connections will be closed. - * @param $key - * The database connection key. Defaults to NULL which means the active key. - */ - public static function closeConnection($target = NULL, $key = NULL) { - // Gets the active connection by default. - if (!isset($key)) { - $key = self::$activeKey; - } - // To close the connection, we need to unset the static variable. - if (isset($target)) { - unset(self::$connections[$key][$target]); - } - else { - unset(self::$connections[$key]); - } - } - - /** - * Instructs the system to temporarily ignore a given key/target. - * - * At times we need to temporarily disable slave queries. To do so, call this - * method with the database key and the target to disable. That database key - * will then always fall back to 'default' for that key, even if it's defined. - * - * @param $key - * The database connection key. - * @param $target - * The target of the specified key to ignore. - */ - public static function ignoreTarget($key, $target) { - 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; - } - } - } - } -} - -/** - * Exception for when popTransaction() is called with no active transaction. - */ -class DatabaseTransactionNoActiveException extends Exception { } - -/** - * Exception thrown when a savepoint or transaction name occurs twice. - */ -class DatabaseTransactionNameNonUniqueException extends Exception { } - -/** - * Exception thrown when a commit() function fails. - */ -class DatabaseTransactionCommitFailedException extends Exception { } - -/** - * Exception to deny attempts to explicitly manage transactions. - * - * This exception will be thrown when the PDO connection commit() is called. - * Code should never call this method directly. - */ -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. - * - * There are many ways that a merge query could be malformed. They should all - * throw this exception and set an appropriately descriptive message. - */ -class InvalidMergeQueryException extends Exception {} - -/** - * Exception thrown if an insert query specifies a field twice. - * - * It is not allowed to specify a field as default and insert field, this - * exception is thrown if that is the case. - */ -class FieldsOverlapException extends Exception {} - -/** - * Exception thrown if an insert query doesn't specify insert or default fields. - */ -class NoFieldsException extends Exception {} - -/** - * Exception thrown if an undefined database connection is requested. - */ -class DatabaseConnectionNotDefinedException extends Exception {} - -/** - * Exception thrown if no driver is specified for a database connection. - */ -class DatabaseDriverNotSpecifiedException extends Exception {} - - -/** - * A wrapper class for creating and managing database transactions. - * - * Not all databases or database configurations support transactions. For - * example, MySQL MyISAM tables do not. It is also easy to begin a transaction - * and then forget to commit it, which can lead to connection errors when - * another transaction is started. - * - * This class acts as a wrapper for transactions. To begin a transaction, - * simply instantiate it. When the object goes out of scope and is destroyed - * it will automatically commit. It also will check to see if the specified - * connection supports transactions. If not, it will simply skip any transaction - * commands, allowing user-space code to proceed normally. The only difference - * is that rollbacks won't actually do anything. - * - * In the vast majority of cases, you should not instantiate this class - * directly. Instead, call ->startTransaction(), from the appropriate connection - * object. - */ -class DatabaseTransaction { - - /** - * The connection object for this transaction. - * - * @var DatabaseConnection - */ - protected $connection; - - /** - * A boolean value to indicate whether this transaction has been rolled back. - * - * @var Boolean - */ - protected $rolledBack = FALSE; - - /** - * The name of the transaction. - * - * This is used to label the transaction savepoint. It will be overridden to - * 'drupal_transaction' if there is no transaction depth. - */ - protected $name; - - public function __construct(DatabaseConnection &$connection, $name = NULL) { - $this->connection = &$connection; - // If there is no transaction depth, then no transaction has started. Name - // the transaction 'drupal_transaction'. - if (!$depth = $connection->transactionDepth()) { - $this->name = 'drupal_transaction'; - } - // Within transactions, savepoints are used. Each savepoint requires a - // name. So if no name is present we need to create one. - elseif (!$name) { - $this->name = 'savepoint_' . $depth; - } - else { - $this->name = $name; - } - $this->connection->pushTransaction($this->name); - } - - public function __destruct() { - // If we rolled back then the transaction would have already been popped. - if (!$this->rolledBack) { - $this->connection->popTransaction($this->name); - } - } - - /** - * Retrieves the name of the transaction or savepoint. - */ - public function name() { - return $this->name; - } - - /** - * Rolls back the current transaction. - * - * This is just a wrapper method to rollback whatever transaction stack we are - * currently in, which is managed by the connection object itself. Note that - * logging (preferable with watchdog_exception()) needs to happen after a - * transaction has been rolled back or the log messages will be rolled back - * too. - * - * @see DatabaseConnection::rollback() - * @see watchdog_exception() - */ - public function rollback() { - $this->rolledBack = TRUE; - $this->connection->rollback($this->name); - } -} - -/** - * Represents a prepared statement. - * - * 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 - * that will work the same way between versions older than 5.2.6 and later - * versions. See http://bugs.php.net/bug.php?id=42452 for more details. - * - * Child implementations should either extend PDOStatement: - * @code - * class DatabaseStatement_oracle extends PDOStatement implements DatabaseStatementInterface {} - * @endcode - * or define their own class. If defining their own class, they will also have - * to implement either the Iterator or IteratorAggregate interface before - * DatabaseStatementInterface: - * @code - * class DatabaseStatement_oracle implements Iterator, DatabaseStatementInterface {} - * @endcode - */ -interface DatabaseStatementInterface extends Traversable { - - /** - * Executes a prepared statement - * - * @param $args - * An array of values with as many elements as there are bound parameters in - * the SQL statement being executed. - * @param $options - * An array of options for this query. - * - * @return - * TRUE on success, or FALSE on failure. - */ - public function execute($args = array(), $options = array()); - - /** - * Gets the query string of this statement. - * - * @return - * The query string, in its form with placeholders. - */ - public function getQueryString(); - - /** - * Returns the number of rows affected by the last SQL statement. - * - * @return - * The number of rows affected by the last DELETE, INSERT, or UPDATE - * statement executed. - */ - public function rowCount(); - - /** - * Sets the default fetch mode for this statement. - * - * See http://php.net/manual/en/pdo.constants.php for the definition of the - * constants used. - * - * @param $mode - * One of the PDO::FETCH_* constants. - * @param $a1 - * An option depending of the fetch mode specified by $mode: - * - for PDO::FETCH_COLUMN, the index of the column to fetch - * - for PDO::FETCH_CLASS, the name of the class to create - * - for PDO::FETCH_INTO, the object to add the data to - * @param $a2 - * If $mode is PDO::FETCH_CLASS, the optional arguments to pass to the - * constructor. - */ - // public function setFetchMode($mode, $a1 = NULL, $a2 = array()); - - /** - * Fetches the next row from a result set. - * - * See http://php.net/manual/en/pdo.constants.php for the definition of the - * constants used. - * - * @param $mode - * One of the PDO::FETCH_* constants. - * Default to what was specified by setFetchMode(). - * @param $cursor_orientation - * Not implemented in all database drivers, don't use. - * @param $cursor_offset - * Not implemented in all database drivers, don't use. - * - * @return - * A result, formatted according to $mode. - */ - // public function fetch($mode = NULL, $cursor_orientation = NULL, $cursor_offset = NULL); - - /** - * Returns a single field from the next record of a result set. - * - * @param $index - * The numeric index of the field to return. Defaults to the first field. - * - * @return - * A single field from the next record, or FALSE if there is no next record. - */ - public function fetchField($index = 0); - - /** - * Fetches the next row and returns it as an object. - * - * The object will be of the class specified by DatabaseStatementInterface::setFetchMode() - * or stdClass if not specified. - */ - // public function fetchObject(); - - /** - * Fetches the next row and returns it as an associative array. - * - * This method corresponds to PDOStatement::fetchObject(), but for associative - * arrays. For some reason PDOStatement does not have a corresponding array - * helper method, so one is added. - * - * @return - * An associative array, or FALSE if there is no next row. - */ - public function fetchAssoc(); - - /** - * Returns an array containing all of the result set rows. - * - * @param $mode - * One of the PDO::FETCH_* constants. - * @param $column_index - * If $mode is PDO::FETCH_COLUMN, the index of the column to fetch. - * @param $constructor_arguments - * If $mode is PDO::FETCH_CLASS, the arguments to pass to the constructor. - * - * @return - * An array of results. - */ - // function fetchAll($mode = NULL, $column_index = NULL, array $constructor_arguments); - - /** - * Returns an entire single column of a result set as an indexed array. - * - * Note that this method will run the result set to the end. - * - * @param $index - * The index of the column number to fetch. - * - * @return - * An indexed array, or an empty array if there is no result set. - */ - public function fetchCol($index = 0); - - /** - * Returns the entire result set as a single associative array. - * - * This method is only useful for two-column result sets. It will return an - * associative array where the key is one column from the result set and the - * value is another field. In most cases, the default of the first two columns - * is appropriate. - * - * Note that this method will run the result set to the end. - * - * @param $key_index - * The numeric index of the field to use as the array key. - * @param $value_index - * The numeric index of the field to use as the array value. - * - * @return - * An associative array, or an empty array if there is no result set. - */ - public function fetchAllKeyed($key_index = 0, $value_index = 1); - - /** - * Returns the result set as an associative array keyed by the given field. - * - * If the given key appears multiple times, later records will overwrite - * earlier ones. - * - * @param $key - * The name of the field on which to index the array. - * @param $fetch - * The fetchmode to use. If set to PDO::FETCH_ASSOC, PDO::FETCH_NUM, or - * PDO::FETCH_BOTH the returned value with be an array of arrays. For any - * other value it will be an array of objects. By default, the fetch mode - * set for the query will be used. - * - * @return - * An associative array, or an empty array if there is no result set. - */ - public function fetchAllAssoc($key, $fetch = NULL); -} - -/** - * Default implementation of DatabaseStatementInterface. - * - * PDO allows us to extend the PDOStatement class to provide additional - * functionality beyond that offered by default. We do need extra - * functionality. By default, this class is not driver-specific. If a given - * driver needs to set a custom statement class, it may do so in its - * constructor. - * - * @see http://us.php.net/pdostatement - */ -class DatabaseStatementBase extends PDOStatement implements DatabaseStatementInterface { - - /** - * Reference to the database connection object for this statement. - * - * The name $dbh is inherited from PDOStatement. - * - * @var DatabaseConnection - */ - public $dbh; - - protected function __construct($dbh) { - $this->dbh = $dbh; - $this->setFetchMode(PDO::FETCH_OBJ); - } - - public function execute($args = array(), $options = array()) { - if (isset($options['fetch'])) { - if (is_string($options['fetch'])) { - // Default to an object. Note: db fields will be added to the object - // before the constructor is run. If you need to assign fields after - // the constructor is run, see http://drupal.org/node/315092. - $this->setFetchMode(PDO::FETCH_CLASS, $options['fetch']); - } - else { - $this->setFetchMode($options['fetch']); - } - } - - $logger = $this->dbh->getLogger(); - if (!empty($logger)) { - $query_start = microtime(TRUE); - } - - $return = parent::execute($args); - - if (!empty($logger)) { - $query_end = microtime(TRUE); - $logger->log($this, $args, $query_end - $query_start); - } - - return $return; - } - - public function getQueryString() { - return $this->queryString; - } - - public function fetchCol($index = 0) { - return $this->fetchAll(PDO::FETCH_COLUMN, $index); - } - - public function fetchAllAssoc($key, $fetch = NULL) { - $return = array(); - if (isset($fetch)) { - if (is_string($fetch)) { - $this->setFetchMode(PDO::FETCH_CLASS, $fetch); - } - else { - $this->setFetchMode($fetch); - } - } - - foreach ($this as $record) { - $record_key = is_object($record) ? $record->$key : $record[$key]; - $return[$record_key] = $record; - } - - return $return; - } - - public function fetchAllKeyed($key_index = 0, $value_index = 1) { - $return = array(); - $this->setFetchMode(PDO::FETCH_NUM); - foreach ($this as $record) { - $return[$record[$key_index]] = $record[$value_index]; - } - return $return; - } - - public function fetchField($index = 0) { - // Call PDOStatement::fetchColumn to fetch the field. - return $this->fetchColumn($index); - } - - public function fetchAssoc() { - // Call PDOStatement::fetch to fetch the row. - return $this->fetch(PDO::FETCH_ASSOC); - } -} - -/** - * Empty implementation of a database statement. - * - * This class satisfies the requirements of being a database statement/result - * object, but does not actually contain data. It is useful when developers - * need to safely return an "empty" result set without connecting to an actual - * database. Calling code can then treat it the same as if it were an actual - * result set that happens to contain no records. - * - * @see SearchQuery - */ -class DatabaseStatementEmpty implements Iterator, DatabaseStatementInterface { - - public function execute($args = array(), $options = array()) { - return FALSE; - } - - public function getQueryString() { - return ''; - } - - public function rowCount() { - return 0; - } - - public function setFetchMode($mode, $a1 = NULL, $a2 = array()) { - return; - } - - public function fetch($mode = NULL, $cursor_orientation = NULL, $cursor_offset = NULL) { - return NULL; - } - - public function fetchField($index = 0) { - return NULL; - } - - public function fetchObject() { - return NULL; - } - - public function fetchAssoc() { - return NULL; - } - - function fetchAll($mode = NULL, $column_index = NULL, array $constructor_arguments = array()) { - return array(); - } - - public function fetchCol($index = 0) { - return array(); - } - - public function fetchAllKeyed($key_index = 0, $value_index = 1) { - return array(); - } - - public function fetchAllAssoc($key, $fetch = NULL) { - return array(); - } - - /* Implementations of Iterator. */ - - public function current() { - return NULL; - } - - public function key() { - return NULL; - } - - public function rewind() { - // Nothing to do: our DatabaseStatement can't be rewound. - } - - public function next() { - // Do nothing, since this is an always-empty implementation. - } - - public function valid() { - return FALSE; - } -} - -/** - * The following utility functions are simply convenience wrappers. - * - * They should never, ever have any database-specific code in them. - */ - -/** - * Executes an arbitrary query string against the active database. - * - * Use this function for SELECT queries if it is just a simple query string. - * If the caller or other modules need to change the query, use db_select() - * instead. - * - * Do not use this function for INSERT, UPDATE, or DELETE queries. Those should - * be handled via db_insert(), db_update() and db_delete() respectively. - * - * @param $query - * The prepared statement query to run. Although it will accept both named and - * unnamed placeholders, named placeholders are strongly preferred as they are - * more self-documenting. - * @param $args - * An array of values to substitute into the query. If the query uses named - * placeholders, this is an associative array in any order. If the query uses - * unnamed placeholders (?), this is an indexed array and the order must match - * the order of placeholders in the query string. - * @param $options - * An array of options to control how the query operates. - * - * @return DatabaseStatementInterface - * A prepared statement object, already executed. - * - * @see DatabaseConnection::defaultOptions() - */ -function db_query($query, array $args = array(), array $options = array()) { - if (empty($options['target'])) { - $options['target'] = 'default'; - } - - return Database::getConnection($options['target'])->query($query, $args, $options); -} - -/** - * Executes a query against the active database, restricted to a range. - * - * @param $query - * The prepared statement query to run. Although it will accept both named and - * unnamed placeholders, named placeholders are strongly preferred as they are - * more self-documenting. - * @param $from - * The first record from the result set to return. - * @param $count - * The number of records to return from the result set. - * @param $args - * An array of values to substitute into the query. If the query uses named - * placeholders, this is an associative array in any order. If the query uses - * unnamed placeholders (?), this is an indexed array and the order must match - * the order of placeholders in the query string. - * @param $options - * An array of options to control how the query operates. - * - * @return DatabaseStatementInterface - * A prepared statement object, already executed. - * - * @see DatabaseConnection::defaultOptions() - */ -function db_query_range($query, $from, $count, array $args = array(), array $options = array()) { - if (empty($options['target'])) { - $options['target'] = 'default'; - } - - return Database::getConnection($options['target'])->queryRange($query, $from, $count, $args, $options); -} - -/** - * Executes a query string and saves the result set to a temporary table. - * - * The execution of the query string happens against the active database. - * - * @param $query - * The prepared statement query to run. Although it will accept both named and - * unnamed placeholders, named placeholders are strongly preferred as they are - * more self-documenting. - * @param $args - * An array of values to substitute into the query. If the query uses named - * placeholders, this is an associative array in any order. If the query uses - * unnamed placeholders (?), this is an indexed array and the order must match - * the order of placeholders in the query string. - * @param $options - * An array of options to control how the query operates. - * - * @return - * The name of the temporary table. - * - * @see DatabaseConnection::defaultOptions() - */ -function db_query_temporary($query, array $args = array(), array $options = array()) { - if (empty($options['target'])) { - $options['target'] = 'default'; - } - - return Database::getConnection($options['target'])->queryTemporary($query, $args, $options); -} - -/** - * Returns a new InsertQuery object for the active database. - * - * @param $table - * The table into which to insert. - * @param $options - * An array of options to control how the query operates. - * - * @return InsertQuery - * A new InsertQuery object for this connection. - */ -function db_insert($table, array $options = array()) { - if (empty($options['target']) || $options['target'] == 'slave') { - $options['target'] = 'default'; - } - return Database::getConnection($options['target'])->insert($table, $options); -} - -/** - * Returns a new MergeQuery object for the active database. - * - * @param $table - * The table into which to merge. - * @param $options - * An array of options to control how the query operates. - * - * @return MergeQuery - * A new MergeQuery object for this connection. - */ -function db_merge($table, array $options = array()) { - if (empty($options['target']) || $options['target'] == 'slave') { - $options['target'] = 'default'; - } - return Database::getConnection($options['target'])->merge($table, $options); -} - -/** - * Returns a new UpdateQuery object for the active database. - * - * @param $table - * The table to update. - * @param $options - * An array of options to control how the query operates. - * - * @return UpdateQuery - * A new UpdateQuery object for this connection. - */ -function db_update($table, array $options = array()) { - if (empty($options['target']) || $options['target'] == 'slave') { - $options['target'] = 'default'; - } - return Database::getConnection($options['target'])->update($table, $options); -} - -/** - * Returns a new DeleteQuery object for the active database. - * - * @param $table - * The table from which to delete. - * @param $options - * An array of options to control how the query operates. - * - * @return DeleteQuery - * A new DeleteQuery object for this connection. - */ -function db_delete($table, array $options = array()) { - if (empty($options['target']) || $options['target'] == 'slave') { - $options['target'] = 'default'; - } - return Database::getConnection($options['target'])->delete($table, $options); -} - -/** - * Returns a new TruncateQuery object for the active database. - * - * @param $table - * The table from which to delete. - * @param $options - * An array of options to control how the query operates. - * - * @return TruncateQuery - * A new TruncateQuery object for this connection. - */ -function db_truncate($table, array $options = array()) { - if (empty($options['target']) || $options['target'] == 'slave') { - $options['target'] = 'default'; - } - return Database::getConnection($options['target'])->truncate($table, $options); -} - -/** - * Returns a new SelectQuery object for the active database. - * - * @param $table - * The base table for this query. May be a string or another SelectQuery - * object. If a query object is passed, it will be used as a subselect. - * @param $alias - * The alias for the base table of this query. - * @param $options - * An array of options to control how the query operates. - * - * @return SelectQuery - * A new SelectQuery object for this connection. - */ -function db_select($table, $alias = NULL, array $options = array()) { - if (empty($options['target'])) { - $options['target'] = 'default'; - } - return Database::getConnection($options['target'])->select($table, $alias, $options); -} - -/** - * Returns a new transaction object for the active database. - * - * @param string $name - * Optional name of the transaction. - * @param array $options - * An array of options to control how the transaction operates: - * - target: The database target name. - * - * @return DatabaseTransaction - * A new DatabaseTransaction object for this connection. - */ -function db_transaction($name = NULL, array $options = array()) { - if (empty($options['target'])) { - $options['target'] = 'default'; - } - return Database::getConnection($options['target'])->startTransaction($name); -} - -/** - * Sets a new active database. - * - * @param $key - * The key in the $databases array to set as the default database. - * - * @return - * The key of the formerly active database. - */ -function db_set_active($key = 'default') { - return Database::setActiveConnection($key); -} - -/** - * Restricts a dynamic table name to safe characters. - * - * Only keeps alphanumeric and underscores. - * - * @param $table - * The table name to escape. - * - * @return - * The escaped table name as a string. - */ -function db_escape_table($table) { - return Database::getConnection()->escapeTable($table); -} - -/** - * Restricts a dynamic column or constraint name to safe characters. - * - * Only keeps alphanumeric and underscores. - * - * @param $field - * The field name to escape. - * - * @return - * The escaped field name as a string. - */ -function db_escape_field($field) { - return Database::getConnection()->escapeField($field); -} - -/** - * Escapes characters that work as wildcard characters in a LIKE pattern. - * - * The wildcard characters "%" and "_" as well as backslash are prefixed with - * a backslash. Use this to do a search for a verbatim string without any - * wildcard behavior. - * - * For example, the following does a case-insensitive query for all rows whose - * name starts with $prefix: - * @code - * $result = db_query( - * 'SELECT * FROM person WHERE name LIKE :pattern', - * array(':pattern' => db_like($prefix) . '%') - * ); - * @endcode - * - * Backslash is defined as escape character for LIKE patterns in - * DatabaseCondition::mapConditionOperator(). - * - * @param $string - * The string to escape. - * - * @return - * The escaped string. - */ -function db_like($string) { - return Database::getConnection()->escapeLike($string); -} - -/** - * Retrieves the name of the currently active database driver. - * - * @return - * The name of the currently active database driver. - */ -function db_driver() { - return Database::getConnection()->driver(); -} - -/** - * Closes the active database connection. - * - * @param $options - * An array of options to control which connection is closed. Only the target - * key has any meaning in this case. - */ -function db_close(array $options = array()) { - if (empty($options['target'])) { - $options['target'] = NULL; - } - Database::closeConnection($options['target']); -} - -/** - * Retrieves a unique id. - * - * Use this function if for some reason you can't use a serial field. Using a - * serial field is preferred, and InsertQuery::execute() returns the value of - * the last ID inserted. - * - * @param $existing_id - * After a database import, it might be that the sequences table is behind, so - * by passing in a minimum ID, it can be assured that we never issue the same - * ID. - * - * @return - * An integer number larger than any number returned before for this sequence. - */ -function db_next_id($existing_id = 0) { - return Database::getConnection()->nextId($existing_id); -} - -/** - * Returns a new DatabaseCondition, set to "OR" all conditions together. - * - * @return DatabaseCondition - */ -function db_or() { - return new DatabaseCondition('OR'); -} - -/** - * Returns a new DatabaseCondition, set to "AND" all conditions together. - * - * @return DatabaseCondition - */ -function db_and() { - return new DatabaseCondition('AND'); -} - -/** - * Returns a new DatabaseCondition, set to "XOR" all conditions together. - * - * @return DatabaseCondition - */ -function db_xor() { - return new DatabaseCondition('XOR'); -} - -/** - * Returns a new DatabaseCondition, set to the specified conjunction. - * - * Internal API function call. The db_and(), db_or(), and db_xor() - * functions are preferred. - * - * @param $conjunction - * The conjunction to use for query conditions (AND, OR or XOR). - * @return DatabaseCondition - */ -function db_condition($conjunction) { - return new DatabaseCondition($conjunction); -} - -/** - * @} End of "defgroup database". - */ - - -/** - * @ingroup schemaapi - * @{ - */ - -/** - * Creates a new table from a Drupal table definition. - * - * @param $name - * The name of the table to create. - * @param $table - * A Schema API table definition array. - */ -function db_create_table($name, $table) { - return Database::getConnection()->schema()->createTable($name, $table); -} - -/** - * Returns an array of field names from an array of key/index column specifiers. - * - * This is usually an identity function but if a key/index uses a column prefix - * specification, this function extracts just the name. - * - * @param $fields - * An array of key/index column specifiers. - * - * @return - * An array of field names. - */ -function db_field_names($fields) { - return Database::getConnection()->schema()->fieldNames($fields); -} - -/** - * Checks if an index exists in the given table. - * - * @param $table - * The name of the table in drupal (no prefixing). - * @param $name - * The name of the index in drupal (no prefixing). - * - * @return - * TRUE if the given index exists, otherwise FALSE. - */ -function db_index_exists($table, $name) { - return Database::getConnection()->schema()->indexExists($table, $name); -} - -/** - * Checks if a table exists. - * - * @param $table - * The name of the table in drupal (no prefixing). - * - * @return - * TRUE if the given table exists, otherwise FALSE. - */ -function db_table_exists($table) { - return Database::getConnection()->schema()->tableExists($table); -} - -/** - * Checks if a column exists in the given table. - * - * @param $table - * The name of the table in drupal (no prefixing). - * @param $field - * The name of the field. - * - * @return - * TRUE if the given column exists, otherwise FALSE. - */ -function db_field_exists($table, $field) { - return Database::getConnection()->schema()->fieldExists($table, $field); -} - -/** - * Finds all tables that are like the specified base table name. - * - * @param $table_expression - * An SQL expression, for example "simpletest%" (without the quotes). - * BEWARE: this is not prefixed, the caller should take care of that. - * - * @return - * Array, both the keys and the values are the matching tables. - */ -function db_find_tables($table_expression) { - return Database::getConnection()->schema()->findTables($table_expression); -} - -function _db_create_keys_sql($spec) { - return Database::getConnection()->schema()->createKeysSql($spec); -} - -/** - * Renames a table. - * - * @param $table - * The table to be renamed. - * @param $new_name - * The new name for the table. - */ -function db_rename_table($table, $new_name) { - return Database::getConnection()->schema()->renameTable($table, $new_name); -} - -/** - * Drops a table. - * - * @param $table - * The table to be dropped. - */ -function db_drop_table($table) { - return Database::getConnection()->schema()->dropTable($table); -} - -/** - * Adds a new field to a table. - * - * @param $table - * Name of the table to be altered. - * @param $field - * Name of the field to be added. - * @param $spec - * The field specification array, as taken from a schema definition. The - * specification may also contain the key 'initial'; the newly-created field - * will be set to the value of the key in all rows. This is most useful for - * creating NOT NULL columns with no default value in existing tables. - * @param $keys_new - * Optional keys and indexes specification to be created on the table along - * with adding the field. The format is the same as a table specification, but - * without the 'fields' element. If you are adding a type 'serial' field, you - * MUST specify at least one key or index including it in this array. See - * db_change_field() for more explanation why. - * - * @see db_change_field() - */ -function db_add_field($table, $field, $spec, $keys_new = array()) { - return Database::getConnection()->schema()->addField($table, $field, $spec, $keys_new); -} - -/** - * Drops a field. - * - * @param $table - * The table to be altered. - * @param $field - * The field to be dropped. - */ -function db_drop_field($table, $field) { - return Database::getConnection()->schema()->dropField($table, $field); -} - -/** - * Sets the default value for a field. - * - * @param $table - * The table to be altered. - * @param $field - * The field to be altered. - * @param $default - * Default value to be set. NULL for 'default NULL'. - */ -function db_field_set_default($table, $field, $default) { - return Database::getConnection()->schema()->fieldSetDefault($table, $field, $default); -} - -/** - * Sets a field to have no default value. - * - * @param $table - * The table to be altered. - * @param $field - * The field to be altered. - */ -function db_field_set_no_default($table, $field) { - return Database::getConnection()->schema()->fieldSetNoDefault($table, $field); -} - -/** - * Adds a primary key to a database table. - * - * @param $table - * Name of the table to be altered. - * @param $fields - * Array of fields for the primary key. - */ -function db_add_primary_key($table, $fields) { - return Database::getConnection()->schema()->addPrimaryKey($table, $fields); -} - -/** - * Drops the primary key of a database table. - * - * @param $table - * Name of the table to be altered. - */ -function db_drop_primary_key($table) { - return Database::getConnection()->schema()->dropPrimaryKey($table); -} - -/** - * Adds a unique key. - * - * @param $table - * The table to be altered. - * @param $name - * The name of the key. - * @param $fields - * An array of field names. - */ -function db_add_unique_key($table, $name, $fields) { - return Database::getConnection()->schema()->addUniqueKey($table, $name, $fields); -} - -/** - * Drops a unique key. - * - * @param $table - * The table to be altered. - * @param $name - * The name of the key. - */ -function db_drop_unique_key($table, $name) { - return Database::getConnection()->schema()->dropUniqueKey($table, $name); -} - -/** - * Adds an index. - * - * @param $table - * The table to be altered. - * @param $name - * The name of the index. - * @param $fields - * An array of field names. - */ -function db_add_index($table, $name, $fields) { - return Database::getConnection()->schema()->addIndex($table, $name, $fields); -} - -/** - * Drops an index. - * - * @param $table - * The table to be altered. - * @param $name - * The name of the index. - */ -function db_drop_index($table, $name) { - return Database::getConnection()->schema()->dropIndex($table, $name); -} - -/** - * Changes a field definition. - * - * IMPORTANT NOTE: To maintain database portability, you have to explicitly - * recreate all indices and primary keys that are using the changed field. - * - * That means that you have to drop all affected keys and indexes with - * db_drop_{primary_key,unique_key,index}() before calling db_change_field(). - * To recreate the keys and indices, pass the key definitions as the optional - * $keys_new argument directly to db_change_field(). - * - * For example, suppose you have: - * @code - * $schema['foo'] = array( - * 'fields' => array( - * 'bar' => array('type' => 'int', 'not null' => TRUE) - * ), - * 'primary key' => array('bar') - * ); - * @endcode - * and you want to change foo.bar to be type serial, leaving it as the primary - * key. The correct sequence is: - * @code - * db_drop_primary_key('foo'); - * db_change_field('foo', 'bar', 'bar', - * array('type' => 'serial', 'not null' => TRUE), - * array('primary key' => array('bar'))); - * @endcode - * - * The reasons for this are due to the different database engines: - * - * On PostgreSQL, changing a field definition involves adding a new field and - * dropping an old one which causes any indices, primary keys and sequences - * (from serial-type fields) that use the changed field to be dropped. - * - * On MySQL, all type 'serial' fields must be part of at least one key or index - * as soon as they are created. You cannot use - * db_add_{primary_key,unique_key,index}() for this purpose because the ALTER - * TABLE command will fail to add the column without a key or index - * specification. The solution is to use the optional $keys_new argument to - * create the key or index at the same time as field. - * - * You could use db_add_{primary_key,unique_key,index}() in all cases unless you - * are converting a field to be type serial. You can use the $keys_new argument - * in all cases. - * - * @param $table - * Name of the table. - * @param $field - * Name of the field to change. - * @param $field_new - * New name for the field (set to the same as $field if you don't want to - * change the name). - * @param $spec - * The field specification for the new field. - * @param $keys_new - * Optional keys and indexes specification to be created on the table along - * with changing the field. The format is the same as a table specification - * but without the 'fields' element. - */ -function db_change_field($table, $field, $field_new, $spec, $keys_new = array()) { - return Database::getConnection()->schema()->changeField($table, $field, $field_new, $spec, $keys_new); -} - -/** - * @} End of "ingroup schemaapi". - */ - -/** - * Sets a session variable specifying the lag time for ignoring a slave server. - */ -function db_ignore_slave() { - $connection_info = Database::getConnectionInfo(); - // Only set ignore_slave_server if there are slave servers being used, which - // is assumed if there are more than one. - if (count($connection_info) > 1) { - // Five minutes is long enough to allow the slave to break and resume - // interrupted replication without causing problems on the Drupal site from - // the old data. - $duration = variable_get('maximum_replication_lag', 300); - // Set session variable with amount of time to delay before using slave. - $_SESSION['ignore_slave_server'] = REQUEST_TIME + $duration; - } -} diff --git a/core/includes/database/log.inc b/core/includes/database/log.inc deleted file mode 100644 index ec27ef8e633..00000000000 --- a/core/includes/database/log.inc +++ /dev/null @@ -1,159 +0,0 @@ -<?php - -/** - * @file - * Logging classes for the database layer. - */ - -/** - * Database query logger. - * - * We log queries in a separate object rather than in the connection object - * because we want to be able to see all queries sent to a given database, not - * database target. If we logged the queries in each connection object we - * would not be able to track what queries went to which target. - * - * Every connection has one and only one logging object on it for all targets - * and logging keys. - */ -class DatabaseLog { - - /** - * Cache of logged queries. This will only be used if the query logger is enabled. - * - * The structure for the logging array is as follows: - * - * array( - * $logging_key = array( - * array(query => '', args => array(), caller => '', target => '', time => 0), - * array(query => '', args => array(), caller => '', target => '', time => 0), - * ), - * ); - * - * @var array - */ - protected $queryLog = array(); - - /** - * The connection key for which this object is logging. - * - * @var string - */ - protected $connectionKey = 'default'; - - /** - * Constructor. - * - * @param $key - * The database connection key for which to enable logging. - */ - public function __construct($key = 'default') { - $this->connectionKey = $key; - } - - /** - * Begin logging queries to the specified connection and logging key. - * - * If the specified logging key is already running this method does nothing. - * - * @param $logging_key - * The identification key for this log request. By specifying different - * logging keys we are able to start and stop multiple logging runs - * simultaneously without them colliding. - */ - public function start($logging_key) { - if (empty($this->queryLog[$logging_key])) { - $this->clear($logging_key); - } - } - - /** - * Retrieve the query log for the specified logging key so far. - * - * @param $logging_key - * The logging key to fetch. - * @return - * An indexed array of all query records for this logging key. - */ - public function get($logging_key) { - return $this->queryLog[$logging_key]; - } - - /** - * Empty the query log for the specified logging key. - * - * This method does not stop logging, it simply clears the log. To stop - * logging, use the end() method. - * - * @param $logging_key - * The logging key to empty. - */ - public function clear($logging_key) { - $this->queryLog[$logging_key] = array(); - } - - /** - * Stop logging for the specified logging key. - * - * @param $logging_key - * The logging key to stop. - */ - public function end($logging_key) { - unset($this->queryLog[$logging_key]); - } - - /** - * Log a query to all active logging keys. - * - * @param $statement - * The prepared statement object to log. - * @param $args - * The arguments passed to the statement object. - * @param $time - * The time in milliseconds the query took to execute. - */ - public function log(DatabaseStatementInterface $statement, $args, $time) { - foreach (array_keys($this->queryLog) as $key) { - $this->queryLog[$key][] = array( - 'query' => $statement->getQueryString(), - 'args' => $args, - 'target' => $statement->dbh->getTarget(), - 'caller' => $this->findCaller(), - 'time' => $time, - ); - } - } - - /** - * Determine the routine that called this query. - * - * We define "the routine that called this query" as the first entry in - * the call stack that is not inside includes/database. That makes the - * climbing logic very simple, and handles the variable stack depth caused - * by the query builders. - * - * @link http://www.php.net/debug_backtrace - * @return - * This method returns a stack trace entry similar to that generated by - * debug_backtrace(). However, it flattens the trace entry and the trace - * entry before it so that we get the function and args of the function that - * called into the database system, not the function and args of the - * database call itself. - */ - public function findCaller() { - $stack = debug_backtrace(); - $stack_count = count($stack); - for ($i = 0; $i < $stack_count; ++$i) { - if (strpos($stack[$i]['file'], 'includes' . DIRECTORY_SEPARATOR . 'database') === FALSE) { - return array( - 'file' => $stack[$i]['file'], - 'line' => $stack[$i]['line'], - 'function' => $stack[$i + 1]['function'], - 'class' => isset($stack[$i + 1]['class']) ? $stack[$i + 1]['class'] : NULL, - 'type' => isset($stack[$i + 1]['type']) ? $stack[$i + 1]['type'] : NULL, - 'args' => $stack[$i + 1]['args'], - ); - } - } - } -} diff --git a/core/includes/database/mysql/database.inc b/core/includes/database/mysql/database.inc deleted file mode 100644 index 7d5d85998db..00000000000 --- a/core/includes/database/mysql/database.inc +++ /dev/null @@ -1,187 +0,0 @@ -<?php - -/** - * @file - * Database interface code for MySQL database servers. - */ - -/** - * @ingroup database - * @{ - */ - -class DatabaseConnection_mysql extends DatabaseConnection { - - /** - * Flag to indicate if we have registered the nextID cleanup function. - * - * @var boolean - */ - protected $shutdownRegistered = FALSE; - - public function __construct(array $connection_options = array()) { - // This driver defaults to transaction support, except if explicitly passed FALSE. - $this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE); - - // MySQL never supports transactional DDL. - $this->transactionalDDLSupport = FALSE; - - $this->connectionOptions = $connection_options; - - // The DSN should use either a socket or a host/port. - if (isset($connection_options['unix_socket'])) { - $dsn = 'mysql:unix_socket=' . $connection_options['unix_socket']; - } - else { - // Default to TCP connection on port 3306. - $dsn = 'mysql:host=' . $connection_options['host'] . ';port=' . (empty($connection_options['port']) ? 3306 : $connection_options['port']); - } - $dsn .= ';dbname=' . $connection_options['database']; - parent::__construct($dsn, $connection_options['username'], $connection_options['password'], array( - // So we don't have to mess around with cursors and unbuffered queries by default. - PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => TRUE, - // Because MySQL's prepared statements skip the query cache, because it's dumb. - PDO::ATTR_EMULATE_PREPARES => TRUE, - // Force column names to lower case. - PDO::ATTR_CASE => PDO::CASE_LOWER, - )); - - // 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' - // for UTF-8. - if (!empty($connection_options['collation'])) { - $this->exec('SET NAMES utf8 COLLATE ' . $connection_options['collation']); - } - else { - $this->exec('SET NAMES utf8'); - } - - // Force MySQL's behavior to conform more closely to SQL standards. - // This allows Drupal to run almost seamlessly on many different - // kinds of database systems. These settings force MySQL to behave - // the same as postgresql, or sqlite in regards to syntax interpretation - // and invalid data handling. See 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. - $this->exec("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'"); - } - - public function queryRange($query, $from, $count, array $args = array(), array $options = array()) { - return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options); - } - - public function queryTemporary($query, array $args = array(), array $options = array()) { - $tablename = $this->generateTemporaryTableName(); - $this->query(preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE {' . $tablename . '} Engine=MEMORY SELECT', $query), $args, $options); - return $tablename; - } - - public function driver() { - return 'mysql'; - } - - public function databaseType() { - return 'mysql'; - } - - public function mapConditionOperator($operator) { - // We don't want to override any of the defaults. - return NULL; - } - - public function nextId($existing_id = 0) { - $new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID)); - // This should only happen after an import or similar event. - if ($existing_id >= $new_id) { - // If we INSERT a value manually into the sequences table, on the next - // INSERT, MySQL will generate a larger value. However, there is no way - // of knowing whether this value already exists in the table. MySQL - // provides an INSERT IGNORE which would work, but that can mask problems - // other than duplicate keys. Instead, we use INSERT ... ON DUPLICATE KEY - // UPDATE in such a way that the UPDATE does not do anything. This way, - // duplicate keys do not generate errors but everything else does. - $this->query('INSERT INTO {sequences} (value) VALUES (:value) ON DUPLICATE KEY UPDATE value = value', array(':value' => $existing_id)); - $new_id = $this->query('INSERT INTO {sequences} () VALUES ()', array(), array('return' => Database::RETURN_INSERT_ID)); - } - if (!$this->shutdownRegistered) { - // Use register_shutdown_function() here to keep the database system - // independent of Drupal. - register_shutdown_function(array($this, 'nextIdDelete')); - $shutdownRegistered = TRUE; - } - return $new_id; - } - - public function nextIdDelete() { - // While we want to clean up the table to keep it up from occupying too - // much storage and memory, we must keep the highest value in the table - // because InnoDB uses an in-memory auto-increment counter as long as the - // server runs. When the server is stopped and restarted, InnoDB - // reinitializes the counter for each table for the first INSERT to the - // table based solely on values from the table so deleting all values would - // be a problem in this case. Also, TRUNCATE resets the auto increment - // counter. - try { - $max_id = $this->query('SELECT MAX(value) FROM {sequences}')->fetchField(); - // We know we are using MySQL here, no need for the slower db_delete(). - $this->query('DELETE FROM {sequences} WHERE value < :value', array(':value' => $max_id)); - } - // During testing, this function is called from shutdown with the - // simpletest prefix stored in $this->connection, and those tables are gone - // by the time shutdown is called so we need to ignore the database - // errors. There is no problem with completely ignoring errors here: if - // these queries fail, the sequence will work just fine, just use a bit - // more database storage and memory. - catch (PDOException $e) { - } - } - - /** - * 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, we keep just the topmost transaction. - $this->transactionLayers = array('drupal_transaction' => 'drupal_transaction'); - } - else { - throw $e; - } - } - } - } - } -} - - -/** - * @} End of "ingroup database". - */ diff --git a/core/includes/database/mysql/install.inc b/core/includes/database/mysql/install.inc deleted file mode 100644 index 75f2ae39050..00000000000 --- a/core/includes/database/mysql/install.inc +++ /dev/null @@ -1,33 +0,0 @@ -<?php - -/** - * @file - * Installation code for MySQL embedded database engine. - */ - -/** - * Specifies installation tasks for MySQL and equivalent databases. - */ -class DatabaseTasks_mysql extends DatabaseTasks { - /** - * The PDO driver name for MySQL and equivalent databases. - * - * @var string - */ - protected $pdoDriver = 'mysql'; - - /** - * Returns a human-readable name string for MySQL and equivalent databases. - */ - public function name() { - return st('MySQL, MariaDB, or equivalent'); - } - - /** - * Returns the minimum version for MySQL. - */ - public function minimumVersion() { - return '5.0.15'; - } -} - diff --git a/core/includes/database/mysql/query.inc b/core/includes/database/mysql/query.inc deleted file mode 100644 index 888b6a5a450..00000000000 --- a/core/includes/database/mysql/query.inc +++ /dev/null @@ -1,107 +0,0 @@ -<?php - -/** - * @ingroup database - * @{ - */ - -/** - * @file - * Query code for MySQL embedded database engine. - */ - - -class InsertQuery_mysql extends InsertQuery { - - public function execute() { - if (!$this->preExecute()) { - return NULL; - } - - // If we're selecting from a SelectQuery, finish building the query and - // pass it back, as any remaining options are irrelevant. - if (empty($this->fromQuery)) { - $max_placeholder = 0; - $values = array(); - foreach ($this->insertValues as $insert_values) { - foreach ($insert_values as $value) { - $values[':db_insert_placeholder_' . $max_placeholder++] = $value; - } - } - } - else { - $values = $this->fromQuery->getArguments(); - } - - $last_insert_id = $this->connection->query((string) $this, $values, $this->queryOptions); - - // Re-initialize the values array so that we can re-use this query. - $this->insertValues = array(); - - return $last_insert_id; - } - - public function __toString() { - // Create a sanitized comment string to prepend to the query. - $comments = $this->connection->makeComment($this->comments); - - // Default fields are always placed first for consistency. - $insert_fields = array_merge($this->defaultFields, $this->insertFields); - - // If we're selecting from a SelectQuery, finish building the query and - // pass it back, as any remaining options are irrelevant. - if (!empty($this->fromQuery)) { - return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery; - } - - $query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES '; - - $max_placeholder = 0; - $values = array(); - if (count($this->insertValues)) { - foreach ($this->insertValues as $insert_values) { - $placeholders = array(); - - // Default fields aren't really placeholders, but this is the most convenient - // way to handle them. - $placeholders = array_pad($placeholders, count($this->defaultFields), 'default'); - - $new_placeholder = $max_placeholder + count($insert_values); - for ($i = $max_placeholder; $i < $new_placeholder; ++$i) { - $placeholders[] = ':db_insert_placeholder_' . $i; - } - $max_placeholder = $new_placeholder; - $values[] = '(' . implode(', ', $placeholders) . ')'; - } - } - else { - // If there are no values, then this is a default-only query. We still need to handle that. - $placeholders = array_fill(0, count($this->defaultFields), 'default'); - $values[] = '(' . implode(', ', $placeholders) . ')'; - } - - $query .= implode(', ', $values); - - return $query; - } -} - -class TruncateQuery_mysql extends TruncateQuery { - public function __toString() { - // TRUNCATE is actually a DDL statement on MySQL, and DDL statements are - // not transactional, and result in an implicit COMMIT. When we are in a - // transaction, fallback to the slower, but transactional, DELETE. - if ($this->connection->inTransaction()) { - // Create a comment string to prepend to the query. - $comments = $this->connection->makeComment($this->comments); - return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '}'; - } - else { - return parent::__toString(); - } - } -} - -/** - * @} End of "ingroup database". - */ diff --git a/core/includes/database/mysql/schema.inc b/core/includes/database/mysql/schema.inc deleted file mode 100644 index 4e88fa169eb..00000000000 --- a/core/includes/database/mysql/schema.inc +++ /dev/null @@ -1,531 +0,0 @@ -<?php - -/** - * @file - * Database schema code for MySQL database servers. - */ - - -/** - * @ingroup schemaapi - * @{ - */ - -class DatabaseSchema_mysql extends DatabaseSchema { - - /** - * Maximum length of a table comment in MySQL. - */ - const COMMENT_MAX_TABLE = 60; - - /** - * Maximum length of a column comment in MySQL. - */ - const COMMENT_MAX_COLUMN = 255; - - /** - * Get information about the table and database name from the prefix. - * - * @return - * A keyed array with information about the database, table name and prefix. - */ - protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) { - $info = array('prefix' => $this->connection->tablePrefix($table)); - if ($add_prefix) { - $table = $info['prefix'] . $table; - } - if (($pos = strpos($table, '.')) !== FALSE) { - $info['database'] = substr($table, 0, $pos); - $info['table'] = substr($table, ++$pos); - } - else { - $db_info = Database::getConnectionInfo(); - $info['database'] = $db_info['default']['database']; - $info['table'] = $table; - } - return $info; - } - - /** - * Build a condition to match a table name against a standard information_schema. - * - * MySQL uses databases like schemas rather than catalogs so when we build - * a condition to query the information_schema.tables, we set the default - * database as the schema unless specified otherwise, and exclude table_catalog - * from the condition criteria. - */ - protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) { - $info = $this->connection->getConnectionOptions(); - - $table_info = $this->getPrefixInfo($table_name, $add_prefix); - - $condition = new DatabaseCondition('AND'); - $condition->condition('table_schema', $table_info['database']); - $condition->condition('table_name', $table_info['table'], $operator); - return $condition; - } - - /** - * Generate SQL to create a new table from a Drupal schema definition. - * - * @param $name - * The name of the table to create. - * @param $table - * A Schema API table definition array. - * @return - * An array of SQL statements to create the table. - */ - protected function createTableSql($name, $table) { - $info = $this->connection->getConnectionOptions(); - - // Provide defaults if needed. - $table += array( - 'mysql_engine' => 'InnoDB', - 'mysql_character_set' => 'utf8', - ); - - $sql = "CREATE TABLE {" . $name . "} (\n"; - - // Add the SQL statement for each field. - foreach ($table['fields'] as $field_name => $field) { - $sql .= $this->createFieldSql($field_name, $this->processField($field)) . ", \n"; - } - - // Process keys & indexes. - $keys = $this->createKeysSql($table); - if (count($keys)) { - $sql .= implode(", \n", $keys) . ", \n"; - } - - // Remove the last comma and space. - $sql = substr($sql, 0, -3) . "\n) "; - - $sql .= 'ENGINE = ' . $table['mysql_engine'] . ' DEFAULT CHARACTER SET ' . $table['mysql_character_set']; - // By default, MySQL uses the default collation for new tables, which is - // 'utf8_general_ci' for utf8. If an alternate collation has been set, it - // needs to be explicitly specified. - // @see DatabaseConnection_mysql - if (!empty($info['collation'])) { - $sql .= ' COLLATE ' . $info['collation']; - } - - // Add table comment. - if (!empty($table['description'])) { - $sql .= ' COMMENT ' . $this->prepareComment($table['description'], self::COMMENT_MAX_TABLE); - } - - return array($sql); - } - - /** - * Create an SQL string for a field to be used in table creation or alteration. - * - * Before passing a field out of a schema definition into this function it has - * to be processed by _db_process_field(). - * - * @param $name - * Name of the field. - * @param $spec - * The field specification, as per the schema data structure format. - */ - protected function createFieldSql($name, $spec) { - $sql = "`" . $name . "` " . $spec['mysql_type']; - - if (in_array($spec['mysql_type'], array('VARCHAR', 'CHAR', 'TINYTEXT', 'MEDIUMTEXT', 'LONGTEXT', 'TEXT')) && isset($spec['length'])) { - $sql .= '(' . $spec['length'] . ')'; - } - elseif (isset($spec['precision']) && isset($spec['scale'])) { - $sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')'; - } - - if (!empty($spec['unsigned'])) { - $sql .= ' unsigned'; - } - - if (isset($spec['not null'])) { - if ($spec['not null']) { - $sql .= ' NOT NULL'; - } - else { - $sql .= ' NULL'; - } - } - - if (!empty($spec['auto_increment'])) { - $sql .= ' auto_increment'; - } - - // $spec['default'] can be NULL, so we explicitly check for the key here. - if (array_key_exists('default', $spec)) { - if (is_string($spec['default'])) { - $spec['default'] = "'" . $spec['default'] . "'"; - } - elseif (!isset($spec['default'])) { - $spec['default'] = 'NULL'; - } - $sql .= ' DEFAULT ' . $spec['default']; - } - - if (empty($spec['not null']) && !isset($spec['default'])) { - $sql .= ' DEFAULT NULL'; - } - - // Add column comment. - if (!empty($spec['description'])) { - $sql .= ' COMMENT ' . $this->prepareComment($spec['description'], self::COMMENT_MAX_COLUMN); - } - - return $sql; - } - - /** - * Set database-engine specific properties for a field. - * - * @param $field - * A field description array, as specified in the schema documentation. - */ - protected function processField($field) { - - if (!isset($field['size'])) { - $field['size'] = 'normal'; - } - - // Set the correct database-engine specific datatype. - // In case one is already provided, force it to uppercase. - if (isset($field['mysql_type'])) { - $field['mysql_type'] = drupal_strtoupper($field['mysql_type']); - } - else { - $map = $this->getFieldTypeMap(); - $field['mysql_type'] = $map[$field['type'] . ':' . $field['size']]; - } - - if (isset($field['type']) && $field['type'] == 'serial') { - $field['auto_increment'] = TRUE; - } - - return $field; - } - - public function getFieldTypeMap() { - // Put :normal last so it gets preserved by array_flip. This makes - // it much easier for modules (such as schema.module) to map - // database types back into schema types. - // $map does not use drupal_static as its value never changes. - static $map = array( - 'varchar:normal' => 'VARCHAR', - 'char:normal' => 'CHAR', - - 'text:tiny' => 'TINYTEXT', - 'text:small' => 'TINYTEXT', - 'text:medium' => 'MEDIUMTEXT', - 'text:big' => 'LONGTEXT', - 'text:normal' => 'TEXT', - - 'serial:tiny' => 'TINYINT', - 'serial:small' => 'SMALLINT', - 'serial:medium' => 'MEDIUMINT', - 'serial:big' => 'BIGINT', - 'serial:normal' => 'INT', - - 'int:tiny' => 'TINYINT', - 'int:small' => 'SMALLINT', - 'int:medium' => 'MEDIUMINT', - 'int:big' => 'BIGINT', - 'int:normal' => 'INT', - - 'float:tiny' => 'FLOAT', - 'float:small' => 'FLOAT', - 'float:medium' => 'FLOAT', - 'float:big' => 'DOUBLE', - 'float:normal' => 'FLOAT', - - 'numeric:normal' => 'DECIMAL', - - 'blob:big' => 'LONGBLOB', - 'blob:normal' => 'BLOB', - ); - return $map; - } - - protected function createKeysSql($spec) { - $keys = array(); - - if (!empty($spec['primary key'])) { - $keys[] = 'PRIMARY KEY (' . $this->createKeysSqlHelper($spec['primary key']) . ')'; - } - if (!empty($spec['unique keys'])) { - foreach ($spec['unique keys'] as $key => $fields) { - $keys[] = 'UNIQUE KEY `' . $key . '` (' . $this->createKeysSqlHelper($fields) . ')'; - } - } - if (!empty($spec['indexes'])) { - foreach ($spec['indexes'] as $index => $fields) { - $keys[] = 'INDEX `' . $index . '` (' . $this->createKeysSqlHelper($fields) . ')'; - } - } - - return $keys; - } - - protected function createKeySql($fields) { - $return = array(); - foreach ($fields as $field) { - if (is_array($field)) { - $return[] = '`' . $field[0] . '`(' . $field[1] . ')'; - } - else { - $return[] = '`' . $field . '`'; - } - } - return implode(', ', $return); - } - - protected function createKeysSqlHelper($fields) { - $return = array(); - foreach ($fields as $field) { - if (is_array($field)) { - $return[] = '`' . $field[0] . '`(' . $field[1] . ')'; - } - else { - $return[] = '`' . $field . '`'; - } - } - return implode(', ', $return); - } - - public function renameTable($table, $new_name) { - if (!$this->tableExists($table)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename %table to %table_new: table %table doesn't exist.", array('%table' => $table, '%table_new' => $new_name))); - } - if ($this->tableExists($new_name)) { - throw new DatabaseSchemaObjectExistsException(t("Cannot rename %table to %table_new: table %table_new already exists.", array('%table' => $table, '%table_new' => $new_name))); - } - - $info = $this->getPrefixInfo($new_name); - return $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO `' . $info['table'] . '`'); - } - - public function dropTable($table) { - if (!$this->tableExists($table)) { - return FALSE; - } - - $this->connection->query('DROP TABLE {' . $table . '}'); - return TRUE; - } - - public function addField($table, $field, $spec, $keys_new = array()) { - if (!$this->tableExists($table)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add field %table.%field: table doesn't exist.", array('%field' => $field, '%table' => $table))); - } - if ($this->fieldExists($table, $field)) { - throw new DatabaseSchemaObjectExistsException(t("Cannot add field %table.%field: field already exists.", array('%field' => $field, '%table' => $table))); - } - - $fixnull = FALSE; - if (!empty($spec['not null']) && !isset($spec['default'])) { - $fixnull = TRUE; - $spec['not null'] = FALSE; - } - $query = 'ALTER TABLE {' . $table . '} ADD '; - $query .= $this->createFieldSql($field, $this->processField($spec)); - if ($keys_sql = $this->createKeysSql($keys_new)) { - $query .= ', ADD ' . implode(', ADD ', $keys_sql); - } - $this->connection->query($query); - if (isset($spec['initial'])) { - $this->connection->update($table) - ->fields(array($field => $spec['initial'])) - ->execute(); - } - if ($fixnull) { - $spec['not null'] = TRUE; - $this->changeField($table, $field, $field, $spec); - } - } - - public function dropField($table, $field) { - if (!$this->fieldExists($table, $field)) { - return FALSE; - } - - $this->connection->query('ALTER TABLE {' . $table . '} DROP `' . $field . '`'); - return TRUE; - } - - public function fieldSetDefault($table, $field, $default) { - if (!$this->fieldExists($table, $field)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field))); - } - - if (!isset($default)) { - $default = 'NULL'; - } - else { - $default = is_string($default) ? "'$default'" : $default; - } - - $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` SET DEFAULT ' . $default); - } - - public function fieldSetNoDefault($table, $field) { - if (!$this->fieldExists($table, $field)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field))); - } - - $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN `' . $field . '` DROP DEFAULT'); - } - - public function indexExists($table, $name) { - // Returns one row for each column in the index. Result is string or FALSE. - // Details at http://dev.mysql.com/doc/refman/5.0/en/show-index.html - $row = $this->connection->query('SHOW INDEX FROM {' . $table . "} WHERE key_name = '$name'")->fetchAssoc(); - return isset($row['key_name']); - } - - public function addPrimaryKey($table, $fields) { - if (!$this->tableExists($table)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add primary key to table %table: table doesn't exist.", array('%table' => $table))); - } - if ($this->indexExists($table, 'PRIMARY')) { - throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table %table: primary key already exists.", array('%table' => $table))); - } - - $this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . $this->createKeySql($fields) . ')'); - } - - public function dropPrimaryKey($table) { - if (!$this->indexExists($table, 'PRIMARY')) { - return FALSE; - } - - $this->connection->query('ALTER TABLE {' . $table . '} DROP PRIMARY KEY'); - return TRUE; - } - - public function addUniqueKey($table, $name, $fields) { - if (!$this->tableExists($table)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add unique key %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name))); - } - if ($this->indexExists($table, $name)) { - throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key %name to table %table: unique key already exists.", array('%table' => $table, '%name' => $name))); - } - - $this->connection->query('ALTER TABLE {' . $table . '} ADD UNIQUE KEY `' . $name . '` (' . $this->createKeySql($fields) . ')'); - } - - public function dropUniqueKey($table, $name) { - if (!$this->indexExists($table, $name)) { - return FALSE; - } - - $this->connection->query('ALTER TABLE {' . $table . '} DROP KEY `' . $name . '`'); - return TRUE; - } - - public function addIndex($table, $name, $fields) { - if (!$this->tableExists($table)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name))); - } - if ($this->indexExists($table, $name)) { - throw new DatabaseSchemaObjectExistsException(t("Cannot add index %name to table %table: index already exists.", array('%table' => $table, '%name' => $name))); - } - - $this->connection->query('ALTER TABLE {' . $table . '} ADD INDEX `' . $name . '` (' . $this->createKeySql($fields) . ')'); - } - - public function dropIndex($table, $name) { - if (!$this->indexExists($table, $name)) { - return FALSE; - } - - $this->connection->query('ALTER TABLE {' . $table . '} DROP INDEX `' . $name . '`'); - return TRUE; - } - - public function changeField($table, $field, $field_new, $spec, $keys_new = array()) { - if (!$this->fieldExists($table, $field)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field %table.%name: field doesn't exist.", array('%table' => $table, '%name' => $field))); - } - if (($field != $field_new) && $this->fieldExists($table, $field_new)) { - throw new DatabaseSchemaObjectExistsException(t("Cannot rename field %table.%name to %name_new: target field already exists.", array('%table' => $table, '%name' => $field, '%name_new' => $field_new))); - } - - $sql = 'ALTER TABLE {' . $table . '} CHANGE `' . $field . '` ' . $this->createFieldSql($field_new, $this->processField($spec)); - if ($keys_sql = $this->createKeysSql($keys_new)) { - $sql .= ', ADD ' . implode(', ADD ', $keys_sql); - } - $this->connection->query($sql); - } - - public function prepareComment($comment, $length = NULL) { - // Work around a bug in some versions of PDO, see http://bugs.php.net/bug.php?id=41125 - $comment = str_replace("'", '’', $comment); - - // Truncate comment to maximum comment length. - if (isset($length)) { - // Add table prefixes before truncating. - $comment = truncate_utf8($this->connection->prefixTables($comment), $length, TRUE, TRUE); - } - - return $this->connection->quote($comment); - } - - /** - * Retrieve a table or column comment. - */ - public function getComment($table, $column = NULL) { - $condition = $this->buildTableNameCondition($table); - if (isset($column)) { - $condition->condition('column_name', $column); - $condition->compile($this->connection, $this); - // Don't use {} around information_schema.columns table. - return $this->connection->query("SELECT column_comment FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField(); - } - $condition->compile($this->connection, $this); - // Don't use {} around information_schema.tables table. - $comment = $this->connection->query("SELECT table_comment FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField(); - // Work-around for MySQL 5.0 bug http://bugs.mysql.com/bug.php?id=11379 - return preg_replace('/; InnoDB free:.*$/', '', $comment); - } - - public function tableExists($table) { - // The information_schema table is very slow to query under MySQL 5.0. - // Instead, we try to select from the table in question. If it fails, - // the most likely reason is that it does not exist. That is dramatically - // faster than using information_schema. - // @link http://bugs.mysql.com/bug.php?id=19588 - // @todo: This override should be removed once we require a version of MySQL - // that has that bug fixed. - try { - $this->connection->queryRange("SELECT 1 FROM {" . $table . "}", 0, 1); - return TRUE; - } - catch (Exception $e) { - return FALSE; - } - } - - public function fieldExists($table, $column) { - // The information_schema table is very slow to query under MySQL 5.0. - // Instead, we try to select from the table and field in question. If it - // fails, the most likely reason is that it does not exist. That is - // dramatically faster than using information_schema. - // @link http://bugs.mysql.com/bug.php?id=19588 - // @todo: This override should be removed once we require a version of MySQL - // that has that bug fixed. - try { - $this->connection->queryRange("SELECT $column FROM {" . $table . "}", 0, 1); - return TRUE; - } - catch (Exception $e) { - return FALSE; - } - } - -} - -/** - * @} End of "ingroup schemaapi". - */ diff --git a/core/includes/database/pgsql/database.inc b/core/includes/database/pgsql/database.inc deleted file mode 100644 index 39b4e9b6960..00000000000 --- a/core/includes/database/pgsql/database.inc +++ /dev/null @@ -1,203 +0,0 @@ -<?php - -/** - * @file - * Database interface code for PostgreSQL database servers. - */ - -/** - * @ingroup database - * @{ - */ - -/** - * The name by which to obtain a lock for retrive the next insert id. - */ -define('POSTGRESQL_NEXTID_LOCK', 1000); - -class DatabaseConnection_pgsql extends DatabaseConnection { - - public function __construct(array $connection_options = array()) { - // This driver defaults to transaction support, except if explicitly passed FALSE. - $this->transactionSupport = !isset($connection_options['transactions']) || ($connection_options['transactions'] !== FALSE); - - // Transactional DDL is always available in PostgreSQL, - // but we'll only enable it if standard transactions are. - $this->transactionalDDLSupport = $this->transactionSupport; - - // Default to TCP connection on port 5432. - if (empty($connection_options['port'])) { - $connection_options['port'] = 5432; - } - - // PostgreSQL in trust mode doesn't require a password to be supplied. - if (empty($connection_options['password'])) { - $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; - - $dsn = 'pgsql:host=' . $connection_options['host'] . ' dbname=' . $connection_options['database'] . ' port=' . $connection_options['port']; - parent::__construct($dsn, $connection_options['username'], $connection_options['password'], array( - // Prepared statements are most effective for performance when queries - // are recycled (used several times). However, if they are not re-used, - // prepared statements become ineffecient. Since most of Drupal's - // prepared queries are not re-used, it should be faster to emulate - // the preparation than to actually ready statements for re-use. If in - // doubt, reset to FALSE and measure performance. - PDO::ATTR_EMULATE_PREPARES => TRUE, - // Convert numeric values to strings when fetching. - PDO::ATTR_STRINGIFY_FETCHES => TRUE, - // Force column names to lower case. - PDO::ATTR_CASE => PDO::CASE_LOWER, - )); - - // Force PostgreSQL to use the UTF-8 character set by default. - $this->exec("SET NAMES 'UTF8'"); - } - - public function query($query, array $args = array(), $options = array()) { - - $options += $this->defaultOptions(); - - // The PDO PostgreSQL driver has a bug which - // doesn't type cast booleans correctly when - // parameters are bound using associative - // arrays. - // See http://bugs.php.net/bug.php?id=48383 - foreach ($args as &$value) { - if (is_bool($value)) { - $value = (int) $value; - } - } - - try { - if ($query instanceof DatabaseStatementInterface) { - $stmt = $query; - $stmt->execute(NULL, $options); - } - else { - $this->expandArguments($query, $args); - $stmt = $this->prepareQuery($query); - $stmt->execute($args, $options); - } - - switch ($options['return']) { - case Database::RETURN_STATEMENT: - return $stmt; - case Database::RETURN_AFFECTED: - return $stmt->rowCount(); - case Database::RETURN_INSERT_ID: - return $this->lastInsertId($options['sequence_name']); - case Database::RETURN_NULL: - return; - default: - throw new PDOException('Invalid return directive: ' . $options['return']); - } - } - catch (PDOException $e) { - if ($options['throw_exception']) { - // Add additional debug information. - if ($query instanceof DatabaseStatementInterface) { - $e->query_string = $stmt->getQueryString(); - } - else { - $e->query_string = $query; - } - $e->args = $args; - throw $e; - } - return NULL; - } - } - - public function queryRange($query, $from, $count, array $args = array(), array $options = array()) { - return $this->query($query . ' LIMIT ' . (int) $count . ' OFFSET ' . (int) $from, $args, $options); - } - - public function queryTemporary($query, array $args = array(), array $options = array()) { - $tablename = $this->generateTemporaryTableName(); - $this->query(preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE {' . $tablename . '} AS SELECT', $query), $args, $options); - return $tablename; - } - - public function driver() { - return 'pgsql'; - } - - public function databaseType() { - return 'pgsql'; - } - - public function mapConditionOperator($operator) { - static $specials; - - // Function calls not allowed in static declarations, thus this method. - if (!isset($specials)) { - $specials = array( - // In PostgreSQL, 'LIKE' is case-sensitive. For case-insensitive LIKE - // statements, we need to use ILIKE instead. - 'LIKE' => array('operator' => 'ILIKE'), - 'NOT LIKE' => array('operator' => 'NOT ILIKE'), - ); - } - - return isset($specials[$operator]) ? $specials[$operator] : NULL; - } - - /** - * Retrive a the next id in a sequence. - * - * PostgreSQL has built in sequences. We'll use these instead of inserting - * and updating a sequences table. - */ - public function nextId($existing = 0) { - - // Retrive the name of the sequence. This information cannot be cached - // because the prefix may change, for example, like it does in simpletests. - $sequence_name = $this->makeSequenceName('sequences', 'value'); - - // When PostgreSQL gets a value too small then it will lock the table, - // retry the INSERT and if it's still too small then alter the sequence. - $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField(); - if ($id > $existing) { - return $id; - } - - // PostgreSQL advisory locks are simply locks to be used by an - // application such as Drupal. This will prevent other Drupal proccesses - // from altering the sequence while we are. - $this->query("SELECT pg_advisory_lock(" . POSTGRESQL_NEXTID_LOCK . ")"); - - // While waiting to obtain the lock, the sequence may have been altered - // so lets try again to obtain an adequate value. - $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField(); - if ($id > $existing) { - $this->query("SELECT pg_advisory_unlock(" . POSTGRESQL_NEXTID_LOCK . ")"); - return $id; - } - - // Reset the sequence to a higher value than the existing id. - $this->query("ALTER SEQUENCE " . $sequence_name . " RESTART WITH " . ($existing + 1)); - - // Retrive the next id. We know this will be as high as we want it. - $id = $this->query("SELECT nextval('" . $sequence_name . "')")->fetchField(); - - $this->query("SELECT pg_advisory_unlock(" . POSTGRESQL_NEXTID_LOCK . ")"); - - return $id; - } -} - -/** - * @} End of "ingroup database". - */ diff --git a/core/includes/database/pgsql/install.inc b/core/includes/database/pgsql/install.inc deleted file mode 100644 index c350634ec40..00000000000 --- a/core/includes/database/pgsql/install.inc +++ /dev/null @@ -1,176 +0,0 @@ -<?php - -/** - * @file - * Install functions for PostgreSQL embedded database engine. - */ - - -// PostgreSQL specific install functions - -class DatabaseTasks_pgsql extends DatabaseTasks { - protected $pdoDriver = 'pgsql'; - - public function __construct() { - $this->tasks[] = array( - 'function' => 'checkEncoding', - 'arguments' => array(), - ); - $this->tasks[] = array( - 'function' => 'checkBinaryOutput', - 'arguments' => array(), - ); - $this->tasks[] = array( - 'function' => 'initializeDatabase', - 'arguments' => array(), - ); - } - - public function name() { - return st('PostgreSQL'); - } - - public function minimumVersion() { - return '8.3'; - } - - /** - * Check encoding is UTF8. - */ - protected function checkEncoding() { - try { - if (db_query('SHOW server_encoding')->fetchField() == 'UTF8') { - $this->pass(st('Database is encoded in UTF-8')); - } - else { - $replacements = array( - '%encoding' => 'UTF8', - '%driver' => $this->name(), - '!link' => '<a href="INSTALL.pgsql.txt">INSTALL.pgsql.txt</a>' - ); - $text = 'The %driver database must use %encoding encoding to work with Drupal.'; - $text .= 'Recreate the database with %encoding encoding. See !link for more details.'; - $this->fail(st($text, $replacements)); - } - } - catch (Exception $e) { - $this->fail(st('Drupal could not determine the encoding of the database was set to UTF-8')); - } - } - - /** - * 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. - */ - function initializeDatabase() { - // We create some functions using global names instead of prefixing them - // like we do with table names. This is so that we don't double up if more - // than one instance of Drupal is running on a single database. We therefore - // avoid trying to create them again in that case. - - try { - // Create functions. - db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric) RETURNS numeric AS - \'SELECT CASE WHEN (($1 > $2) OR ($2 IS NULL)) THEN $1 ELSE $2 END;\' - LANGUAGE \'sql\'' - ); - db_query('CREATE OR REPLACE FUNCTION "greatest"(numeric, numeric, numeric) RETURNS numeric AS - \'SELECT greatest($1, greatest($2, $3));\' - LANGUAGE \'sql\'' - ); - // Don't use {} around pg_proc table. - if (!db_query("SELECT COUNT(*) FROM pg_proc WHERE proname = 'rand'")->fetchField()) { - db_query('CREATE OR REPLACE FUNCTION "rand"() RETURNS float AS - \'SELECT random();\' - LANGUAGE \'sql\'' - ); - } - - db_query('CREATE OR REPLACE FUNCTION "substring_index"(text, text, integer) RETURNS text AS - \'SELECT array_to_string((string_to_array($1, $2)) [1:$3], $2);\' - LANGUAGE \'sql\'' - ); - - // Using || to concatenate in Drupal is not recommeneded because there are - // database drivers for Drupal that do not support the syntax, however - // they do support CONCAT(item1, item2) which we can replicate in - // PostgreSQL. PostgreSQL requires the function to be defined for each - // different argument variation the function can handle. - db_query('CREATE OR REPLACE FUNCTION "concat"(anynonarray, anynonarray) RETURNS text AS - \'SELECT CAST($1 AS text) || CAST($2 AS text);\' - LANGUAGE \'sql\' - '); - db_query('CREATE OR REPLACE FUNCTION "concat"(text, anynonarray) RETURNS text AS - \'SELECT $1 || CAST($2 AS text);\' - LANGUAGE \'sql\' - '); - db_query('CREATE OR REPLACE FUNCTION "concat"(anynonarray, text) RETURNS text AS - \'SELECT CAST($1 AS text) || $2;\' - LANGUAGE \'sql\' - '); - db_query('CREATE OR REPLACE FUNCTION "concat"(text, text) RETURNS text AS - \'SELECT $1 || $2;\' - LANGUAGE \'sql\' - '); - - $this->pass(st('PostgreSQL has initialized itself.')); - } - catch (Exception $e) { - $this->fail(st('Drupal could not be correctly setup with the existing database. Revise any errors.')); - } - } -} - diff --git a/core/includes/database/pgsql/query.inc b/core/includes/database/pgsql/query.inc deleted file mode 100644 index f3783a9ca8f..00000000000 --- a/core/includes/database/pgsql/query.inc +++ /dev/null @@ -1,209 +0,0 @@ -<?php - -/** - * @ingroup database - * @{ - */ - -/** - * @file - * Query code for PostgreSQL embedded database engine. - */ - - -class InsertQuery_pgsql extends InsertQuery { - - public function execute() { - if (!$this->preExecute()) { - return NULL; - } - - $stmt = $this->connection->prepareQuery((string) $this); - - // Fetch the list of blobs and sequences used on that table. - $table_information = $this->connection->schema()->queryTableInformation($this->table); - - $max_placeholder = 0; - $blobs = array(); - $blob_count = 0; - foreach ($this->insertValues as $insert_values) { - foreach ($this->insertFields as $idx => $field) { - if (isset($table_information->blob_fields[$field])) { - $blobs[$blob_count] = fopen('php://memory', 'a'); - fwrite($blobs[$blob_count], $insert_values[$idx]); - rewind($blobs[$blob_count]); - - $stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $blobs[$blob_count], PDO::PARAM_LOB); - - // Pre-increment is faster in PHP than increment. - ++$blob_count; - } - else { - $stmt->bindParam(':db_insert_placeholder_' . $max_placeholder++, $insert_values[$idx]); - } - } - // Check if values for a serial field has been passed. - if (!empty($table_information->serial_fields)) { - foreach ($table_information->serial_fields as $index => $serial_field) { - $serial_key = array_search($serial_field, $this->insertFields); - if ($serial_key !== FALSE) { - $serial_value = $insert_values[$serial_key]; - - // Force $last_insert_id to the specified value. This is only done - // if $index is 0. - if ($index == 0) { - $last_insert_id = $serial_value; - } - // Set the sequence to the bigger value of either the passed - // value or the max value of the column. It can happen that another - // thread calls nextval() which could lead to a serial number being - // used twice. However, trying to insert a value into a serial - // column should only be done in very rare cases and is not thread - // safe by definition. - $this->connection->query("SELECT setval('" . $table_information->sequences[$index] . "', GREATEST(MAX(" . $serial_field . "), :serial_value)) FROM {" . $this->table . "}", array(':serial_value' => (int)$serial_value)); - } - } - } - } - if (!empty($this->fromQuery)) { - // bindParam stores only a reference to the variable that is followed when - // the statement is executed. We pass $arguments[$key] instead of $value - // because the second argument to bindParam is passed by reference and - // the foreach statement assigns the element to the existing reference. - $arguments = $this->fromQuery->getArguments(); - foreach ($arguments as $key => $value) { - $stmt->bindParam($key, $arguments[$key]); - } - } - - // PostgreSQL requires the table name to be specified explicitly - // when requesting the last insert ID, so we pass that in via - // the options array. - $options = $this->queryOptions; - - if (!empty($table_information->sequences)) { - $options['sequence_name'] = $table_information->sequences[0]; - } - // If there are no sequences then we can't get a last insert id. - elseif ($options['return'] == Database::RETURN_INSERT_ID) { - $options['return'] = Database::RETURN_NULL; - } - // Only use the returned last_insert_id if it is not already set. - if (!empty($last_insert_id)) { - $this->connection->query($stmt, array(), $options); - } - else { - $last_insert_id = $this->connection->query($stmt, array(), $options); - } - - // Re-initialize the values array so that we can re-use this query. - $this->insertValues = array(); - - return $last_insert_id; - } - - public function __toString() { - // Create a sanitized comment string to prepend to the query. - $comments = $this->connection->makeComment($this->comments); - - // Default fields are always placed first for consistency. - $insert_fields = array_merge($this->defaultFields, $this->insertFields); - - // If we're selecting from a SelectQuery, finish building the query and - // pass it back, as any remaining options are irrelevant. - if (!empty($this->fromQuery)) { - return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery; - } - - $query = $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES '; - - $max_placeholder = 0; - $values = array(); - if (count($this->insertValues)) { - foreach ($this->insertValues as $insert_values) { - $placeholders = array(); - - // Default fields aren't really placeholders, but this is the most convenient - // way to handle them. - $placeholders = array_pad($placeholders, count($this->defaultFields), 'default'); - - $new_placeholder = $max_placeholder + count($insert_values); - for ($i = $max_placeholder; $i < $new_placeholder; ++$i) { - $placeholders[] = ':db_insert_placeholder_' . $i; - } - $max_placeholder = $new_placeholder; - $values[] = '(' . implode(', ', $placeholders) . ')'; - } - } - else { - // If there are no values, then this is a default-only query. We still need to handle that. - $placeholders = array_fill(0, count($this->defaultFields), 'default'); - $values[] = '(' . implode(', ', $placeholders) . ')'; - } - - $query .= implode(', ', $values); - - return $query; - } -} - -class UpdateQuery_pgsql extends UpdateQuery { - public function execute() { - $max_placeholder = 0; - $blobs = array(); - $blob_count = 0; - - // Because we filter $fields the same way here and in __toString(), the - // placeholders will all match up properly. - $stmt = $this->connection->prepareQuery((string) $this); - - // Fetch the list of blobs and sequences used on that table. - $table_information = $this->connection->schema()->queryTableInformation($this->table); - - // Expressions take priority over literal fields, so we process those first - // and remove any literal fields that conflict. - $fields = $this->fields; - $expression_fields = array(); - foreach ($this->expressionFields as $field => $data) { - if (!empty($data['arguments'])) { - foreach ($data['arguments'] as $placeholder => $argument) { - // We assume that an expression will never happen on a BLOB field, - // which is a fairly safe assumption to make since in most cases - // it would be an invalid query anyway. - $stmt->bindParam($placeholder, $data['arguments'][$placeholder]); - } - } - unset($fields[$field]); - } - - foreach ($fields as $field => $value) { - $placeholder = ':db_update_placeholder_' . ($max_placeholder++); - - if (isset($table_information->blob_fields[$field])) { - $blobs[$blob_count] = fopen('php://memory', 'a'); - fwrite($blobs[$blob_count], $value); - rewind($blobs[$blob_count]); - $stmt->bindParam($placeholder, $blobs[$blob_count], PDO::PARAM_LOB); - ++$blob_count; - } - else { - $stmt->bindParam($placeholder, $fields[$field]); - } - } - - if (count($this->condition)) { - $this->condition->compile($this->connection, $this); - - $arguments = $this->condition->arguments(); - foreach ($arguments as $placeholder => $value) { - $stmt->bindParam($placeholder, $arguments[$placeholder]); - } - } - - $options = $this->queryOptions; - $options['already_prepared'] = TRUE; - $this->connection->query($stmt, $options); - - return $stmt->rowCount(); - } -} diff --git a/core/includes/database/pgsql/schema.inc b/core/includes/database/pgsql/schema.inc deleted file mode 100644 index 9ed8a262032..00000000000 --- a/core/includes/database/pgsql/schema.inc +++ /dev/null @@ -1,617 +0,0 @@ -<?php - -/** - * @file - * Database schema code for PostgreSQL database servers. - */ - -/** - * @ingroup schemaapi - * @{ - */ - -class DatabaseSchema_pgsql extends DatabaseSchema { - - /** - * A cache of information about blob columns and sequences of tables. - * - * This is collected by DatabaseConnection_pgsql->queryTableInformation(), - * by introspecting the database. - * - * @see DatabaseConnection_pgsql->queryTableInformation() - * @var array - */ - protected $tableInformation = array(); - - /** - * Fetch the list of blobs and sequences used on a table. - * - * We introspect the database to collect the information required by insert - * and update queries. - * - * @param $table_name - * The non-prefixed name of the table. - * @return - * An object with two member variables: - * - 'blob_fields' that lists all the blob fields in the table. - * - 'sequences' that lists the sequences used in that table. - */ - public function queryTableInformation($table) { - // Generate a key to reference this table's information on. - $key = $this->connection->prefixTables('{' . $table . '}'); - if (!strpos($key, '.')) { - $key = 'public.' . $key; - } - - if (!isset($this->tableInformation[$key])) { - // Split the key into schema and table for querying. - list($schema, $table_name) = explode('.', $key); - $table_information = (object) array( - 'blob_fields' => array(), - 'sequences' => array(), - ); - // Don't use {} around information_schema.columns table. - $result = $this->connection->query("SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_schema = :schema AND table_name = :table AND (data_type = 'bytea' OR (numeric_precision IS NOT NULL AND column_default LIKE :default))", array( - ':schema' => $schema, - ':table' => $table_name, - ':default' => '%nextval%', - )); - foreach ($result as $column) { - if ($column->data_type == 'bytea') { - $table_information->blob_fields[$column->column_name] = TRUE; - } - elseif (preg_match("/nextval\('([^']+)'/", $column->column_default, $matches)) { - // We must know of any sequences in the table structure to help us - // return the last insert id. If there is more than 1 sequences the - // first one (index 0 of the sequences array) will be used. - $table_information->sequences[] = $matches[1]; - $table_information->serial_fields[] = $column->column_name; - } - } - $this->tableInformation[$key] = $table_information; - } - 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. - * - * @param $name - * The name of the table to create. - * @param $table - * A Schema API table definition array. - * @return - * An array of SQL statements to create the table. - */ - protected function createTableSql($name, $table) { - $sql_fields = array(); - foreach ($table['fields'] as $field_name => $field) { - $sql_fields[] = $this->createFieldSql($field_name, $this->processField($field)); - } - - $sql_keys = array(); - if (isset($table['primary key']) && is_array($table['primary key'])) { - $sql_keys[] = 'PRIMARY KEY (' . implode(', ', $table['primary key']) . ')'; - } - if (isset($table['unique keys']) && is_array($table['unique keys'])) { - foreach ($table['unique keys'] as $key_name => $key) { - $sql_keys[] = 'CONSTRAINT ' . $this->prefixNonTable($name, $key_name, 'key') . ' UNIQUE (' . implode(', ', $key) . ')'; - } - } - - $sql = "CREATE TABLE {" . $name . "} (\n\t"; - $sql .= implode(",\n\t", $sql_fields); - if (count($sql_keys) > 0) { - $sql .= ",\n\t"; - } - $sql .= implode(",\n\t", $sql_keys); - $sql .= "\n)"; - $statements[] = $sql; - - if (isset($table['indexes']) && is_array($table['indexes'])) { - foreach ($table['indexes'] as $key_name => $key) { - $statements[] = $this->_createIndexSql($name, $key_name, $key); - } - } - - // Add table comment. - if (!empty($table['description'])) { - $statements[] = 'COMMENT ON TABLE {' . $name . '} IS ' . $this->prepareComment($table['description']); - } - - // Add column comments. - foreach ($table['fields'] as $field_name => $field) { - if (!empty($field['description'])) { - $statements[] = 'COMMENT ON COLUMN {' . $name . '}.' . $field_name . ' IS ' . $this->prepareComment($field['description']); - } - } - - return $statements; - } - - /** - * Create an SQL string for a field to be used in table creation or - * alteration. - * - * Before passing a field out of a schema definition into this - * function it has to be processed by _db_process_field(). - * - * @param $name - * Name of the field. - * @param $spec - * The field specification, as per the schema data structure format. - */ - protected function createFieldSql($name, $spec) { - $sql = $name . ' ' . $spec['pgsql_type']; - - if (isset($spec['type']) && $spec['type'] == 'serial') { - unset($spec['not null']); - } - - if (in_array($spec['pgsql_type'], array('varchar', 'character', 'text')) && isset($spec['length'])) { - $sql .= '(' . $spec['length'] . ')'; - } - elseif (isset($spec['precision']) && isset($spec['scale'])) { - $sql .= '(' . $spec['precision'] . ', ' . $spec['scale'] . ')'; - } - - if (!empty($spec['unsigned'])) { - $sql .= " CHECK ($name >= 0)"; - } - - if (isset($spec['not null'])) { - if ($spec['not null']) { - $sql .= ' NOT NULL'; - } - else { - $sql .= ' NULL'; - } - } - if (isset($spec['default'])) { - $default = is_string($spec['default']) ? "'" . $spec['default'] . "'" : $spec['default']; - $sql .= " default $default"; - } - - return $sql; - } - - /** - * Set database-engine specific properties for a field. - * - * @param $field - * A field description array, as specified in the schema documentation. - */ - protected function processField($field) { - if (!isset($field['size'])) { - $field['size'] = 'normal'; - } - - // Set the correct database-engine specific datatype. - // In case one is already provided, force it to lowercase. - if (isset($field['pgsql_type'])) { - $field['pgsql_type'] = drupal_strtolower($field['pgsql_type']); - } - else { - $map = $this->getFieldTypeMap(); - $field['pgsql_type'] = $map[$field['type'] . ':' . $field['size']]; - } - - if (!empty($field['unsigned'])) { - // Unsigned datatypes are not supported in PostgreSQL 8.3. In MySQL, - // they are used to ensure a positive number is inserted and it also - // doubles the maximum integer size that can be stored in a field. - // The PostgreSQL schema in Drupal creates a check constraint - // to ensure that a value inserted is >= 0. To provide the extra - // integer capacity, here, we bump up the column field size. - if (!isset($map)) { - $map = $this->getFieldTypeMap(); - } - switch ($field['pgsql_type']) { - case 'smallint': - $field['pgsql_type'] = $map['int:medium']; - break; - case 'int' : - $field['pgsql_type'] = $map['int:big']; - break; - } - } - if (isset($field['type']) && $field['type'] == 'serial') { - unset($field['not null']); - } - return $field; - } - - /** - * This maps a generic data type in combination with its data size - * to the engine-specific data type. - */ - function getFieldTypeMap() { - // Put :normal last so it gets preserved by array_flip. This makes - // it much easier for modules (such as schema.module) to map - // database types back into schema types. - // $map does not use drupal_static as its value never changes. - static $map = array( - 'varchar:normal' => 'varchar', - 'char:normal' => 'character', - - 'text:tiny' => 'text', - 'text:small' => 'text', - 'text:medium' => 'text', - 'text:big' => 'text', - 'text:normal' => 'text', - - 'int:tiny' => 'smallint', - 'int:small' => 'smallint', - 'int:medium' => 'int', - 'int:big' => 'bigint', - 'int:normal' => 'int', - - 'float:tiny' => 'real', - 'float:small' => 'real', - 'float:medium' => 'real', - 'float:big' => 'double precision', - 'float:normal' => 'real', - - 'numeric:normal' => 'numeric', - - 'blob:big' => 'bytea', - 'blob:normal' => 'bytea', - - 'serial:tiny' => 'serial', - 'serial:small' => 'serial', - 'serial:medium' => 'serial', - 'serial:big' => 'bigserial', - 'serial:normal' => 'serial', - ); - return $map; - } - - protected function _createKeySql($fields) { - $return = array(); - foreach ($fields as $field) { - if (is_array($field)) { - $return[] = 'substr(' . $field[0] . ', 1, ' . $field[1] . ')'; - } - else { - $return[] = '"' . $field . '"'; - } - } - return implode(', ', $return); - } - - function renameTable($table, $new_name) { - if (!$this->tableExists($table)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename %table to %table_new: table %table doesn't exist.", array('%table' => $table, '%table_new' => $new_name))); - } - if ($this->tableExists($new_name)) { - throw new DatabaseSchemaObjectExistsException(t("Cannot rename %table to %table_new: table %table_new already exists.", array('%table' => $table, '%table_new' => $new_name))); - } - - // Get the schema and tablename for the old table. - $old_full_name = $this->connection->prefixTables('{' . $table . '}'); - list($old_schema, $old_table_name) = strpos($old_full_name, '.') ? explode('.', $old_full_name) : array('public', $old_full_name); - - // Index names and constraint names are global in PostgreSQL, so we need to - // rename them when renaming the table. - $indexes = $this->connection->query('SELECT indexname FROM pg_indexes WHERE schemaname = :schema AND tablename = :table', array(':schema' => $old_schema, ':table' => $old_table_name)); - foreach ($indexes as $index) { - if (preg_match('/^' . preg_quote($old_full_name) . '_(.*)_idx$/', $index->indexname, $matches)) { - $index_name = $matches[1]; - $this->connection->query('ALTER INDEX ' . $index->indexname . ' RENAME TO {' . $new_name . '}_' . $index_name . '_idx'); - } - } - - // Now rename the table. - // Ensure the new table name does not include schema syntax. - $prefixInfo = $this->getPrefixInfo($new_name); - $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO ' . $prefixInfo['table']); - } - - public function dropTable($table) { - if (!$this->tableExists($table)) { - return FALSE; - } - - $this->connection->query('DROP TABLE {' . $table . '}'); - return TRUE; - } - - public function addField($table, $field, $spec, $new_keys = array()) { - if (!$this->tableExists($table)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add field %table.%field: table doesn't exist.", array('%field' => $field, '%table' => $table))); - } - if ($this->fieldExists($table, $field)) { - throw new DatabaseSchemaObjectExistsException(t("Cannot add field %table.%field: field already exists.", array('%field' => $field, '%table' => $table))); - } - - $fixnull = FALSE; - if (!empty($spec['not null']) && !isset($spec['default'])) { - $fixnull = TRUE; - $spec['not null'] = FALSE; - } - $query = 'ALTER TABLE {' . $table . '} ADD COLUMN '; - $query .= $this->createFieldSql($field, $this->processField($spec)); - $this->connection->query($query); - if (isset($spec['initial'])) { - $this->connection->update($table) - ->fields(array($field => $spec['initial'])) - ->execute(); - } - if ($fixnull) { - $this->connection->query("ALTER TABLE {" . $table . "} ALTER $field SET NOT NULL"); - } - if (isset($new_keys)) { - $this->_createKeys($table, $new_keys); - } - // Add column comment. - if (!empty($spec['description'])) { - $this->connection->query('COMMENT ON COLUMN {' . $table . '}.' . $field . ' IS ' . $this->prepareComment($spec['description'])); - } - } - - public function dropField($table, $field) { - if (!$this->fieldExists($table, $field)) { - return FALSE; - } - - $this->connection->query('ALTER TABLE {' . $table . '} DROP COLUMN "' . $field . '"'); - return TRUE; - } - - public function fieldSetDefault($table, $field, $default) { - if (!$this->fieldExists($table, $field)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field))); - } - - if (!isset($default)) { - $default = 'NULL'; - } - else { - $default = is_string($default) ? "'$default'" : $default; - } - - $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" SET DEFAULT ' . $default); - } - - public function fieldSetNoDefault($table, $field) { - if (!$this->fieldExists($table, $field)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field))); - } - - $this->connection->query('ALTER TABLE {' . $table . '} ALTER COLUMN "' . $field . '" DROP DEFAULT'); - } - - public function indexExists($table, $name) { - // Details http://www.postgresql.org/docs/8.3/interactive/view-pg-indexes.html - $index_name = '{' . $table . '}_' . $name . '_idx'; - return (bool) $this->connection->query("SELECT 1 FROM pg_indexes WHERE indexname = '$index_name'")->fetchField(); - } - - /** - * Helper function: check if a constraint (PK, FK, UK) exists. - * - * @param $table - * The name of the table. - * @param $name - * The name of the constraint (typically 'pkey' or '[constraint]_key'). - */ - protected function constraintExists($table, $name) { - $constraint_name = '{' . $table . '}_' . $name; - return (bool) $this->connection->query("SELECT 1 FROM pg_constraint WHERE conname = '$constraint_name'")->fetchField(); - } - - public function addPrimaryKey($table, $fields) { - if (!$this->tableExists($table)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add primary key to table %table: table doesn't exist.", array('%table' => $table))); - } - if ($this->constraintExists($table, 'pkey')) { - throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table %table: primary key already exists.", array('%table' => $table))); - } - - $this->connection->query('ALTER TABLE {' . $table . '} ADD PRIMARY KEY (' . implode(',', $fields) . ')'); - } - - public function dropPrimaryKey($table) { - if (!$this->constraintExists($table, 'pkey')) { - return FALSE; - } - - $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT ' . $this->prefixNonTable($table, 'pkey')); - return TRUE; - } - - function addUniqueKey($table, $name, $fields) { - if (!$this->tableExists($table)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add unique key %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name))); - } - if ($this->constraintExists($table, $name . '_key')) { - throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key %name to table %table: unique key already exists.", array('%table' => $table, '%name' => $name))); - } - - $this->connection->query('ALTER TABLE {' . $table . '} ADD CONSTRAINT "' . $this->prefixNonTable($table, $name, 'key') . '" UNIQUE (' . implode(',', $fields) . ')'); - } - - public function dropUniqueKey($table, $name) { - if (!$this->constraintExists($table, $name . '_key')) { - return FALSE; - } - - $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $this->prefixNonTable($table, $name, 'key') . '"'); - return TRUE; - } - - public function addIndex($table, $name, $fields) { - if (!$this->tableExists($table)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name))); - } - if ($this->indexExists($table, $name)) { - throw new DatabaseSchemaObjectExistsException(t("Cannot add index %name to table %table: index already exists.", array('%table' => $table, '%name' => $name))); - } - - $this->connection->query($this->_createIndexSql($table, $name, $fields)); - } - - public function dropIndex($table, $name) { - if (!$this->indexExists($table, $name)) { - return FALSE; - } - - $this->connection->query('DROP INDEX ' . $this->prefixNonTable($table, $name, 'idx')); - return TRUE; - } - - public function changeField($table, $field, $field_new, $spec, $new_keys = array()) { - if (!$this->fieldExists($table, $field)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field %table.%name: field doesn't exist.", array('%table' => $table, '%name' => $field))); - } - if (($field != $field_new) && $this->fieldExists($table, $field_new)) { - throw new DatabaseSchemaObjectExistsException(t("Cannot rename field %table.%name to %name_new: target field already exists.", array('%table' => $table, '%name' => $field, '%name_new' => $field_new))); - } - - $spec = $this->processField($spec); - - // We need to typecast the new column to best be able to transfer the data - // Schema_pgsql::getFieldTypeMap() will return possibilities that are not - // 'cast-able' such as 'serial' - so they need to be casted int instead. - if (in_array($spec['pgsql_type'], array('serial', 'bigserial', 'numeric'))) { - $typecast = 'int'; - } - else { - $typecast = $spec['pgsql_type']; - } - - if (in_array($spec['pgsql_type'], array('varchar', 'character', 'text')) && isset($spec['length'])) { - $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); - - 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'))) { - // Type "serial" is known to PostgreSQL, but *only* during table creation, - // not when altering. Because of that, the sequence needs to be created - // and initialized by hand. - $seq = "{" . $table . "}_" . $field_new . "_seq"; - $this->connection->query("CREATE SEQUENCE " . $seq); - // Set sequence to maximal field value to not conflict with existing - // entries. - $this->connection->query("SELECT setval('" . $seq . "', MAX(\"" . $field . '")) FROM {' . $table . "}"); - $this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" SET DEFAULT nextval(\'' . $seq . '\')'); - } - - // Rename the column if necessary. - if ($field != $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. - if (!empty($spec['description'])) { - $this->connection->query('COMMENT ON COLUMN {' . $table . '}."' . $field_new . '" IS ' . $this->prepareComment($spec['description'])); - } - - if (isset($new_keys)) { - $this->_createKeys($table, $new_keys); - } - } - - protected function _createIndexSql($table, $name, $fields) { - $query = 'CREATE INDEX "' . $this->prefixNonTable($table, $name, 'idx') . '" ON {' . $table . '} ('; - $query .= $this->_createKeySql($fields) . ')'; - return $query; - } - - protected function _createKeys($table, $new_keys) { - if (isset($new_keys['primary key'])) { - $this->addPrimaryKey($table, $new_keys['primary key']); - } - if (isset($new_keys['unique keys'])) { - foreach ($new_keys['unique keys'] as $name => $fields) { - $this->addUniqueKey($table, $name, $fields); - } - } - if (isset($new_keys['indexes'])) { - foreach ($new_keys['indexes'] as $name => $fields) { - $this->addIndex($table, $name, $fields); - } - } - } - - /** - * Retrieve a table or column comment. - */ - public function getComment($table, $column = NULL) { - $info = $this->getPrefixInfo($table); - // Don't use {} around pg_class, pg_attribute tables. - if (isset($column)) { - return $this->connection->query('SELECT col_description(oid, attnum) FROM pg_class, pg_attribute WHERE attrelid = oid AND relname = ? AND attname = ?', array($info['table'], $column))->fetchField(); - } - else { - return $this->connection->query('SELECT obj_description(oid, ?) FROM pg_class WHERE relname = ?', array('pg_class', $info['table']))->fetchField(); - } - } -} diff --git a/core/includes/database/pgsql/select.inc b/core/includes/database/pgsql/select.inc deleted file mode 100644 index d1d83828118..00000000000 --- a/core/includes/database/pgsql/select.inc +++ /dev/null @@ -1,108 +0,0 @@ -<?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". - */ - diff --git a/core/includes/database/prefetch.inc b/core/includes/database/prefetch.inc deleted file mode 100644 index 4f2b19d1f3d..00000000000 --- a/core/includes/database/prefetch.inc +++ /dev/null @@ -1,507 +0,0 @@ -<?php - -/** - * @file - * Database interface code for engines that need complete control over their - * result sets. For example, SQLite will prefix some column names by the name - * of the table. We post-process the data, by renaming the column names - * using the same convention as MySQL and PostgreSQL. - */ - -/** - * @ingroup database - * @{ - */ - -/** - * An implementation of DatabaseStatementInterface that prefetches all data. - * - * This class behaves very similar to a PDOStatement but as it always fetches - * every row it is possible to manipulate those results. - */ -class DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface { - - /** - * The query string. - * - * @var string - */ - protected $queryString; - - /** - * Driver-specific options. Can be used by child classes. - * - * @var Array - */ - protected $driverOptions; - - /** - * Reference to the database connection object for this statement. - * - * The name $dbh is inherited from PDOStatement. - * - * @var DatabaseConnection - */ - public $dbh; - - /** - * Main data store. - * - * @var Array - */ - protected $data = array(); - - /** - * The current row, retrieved in PDO::FETCH_ASSOC format. - * - * @var Array - */ - protected $currentRow = NULL; - - /** - * The key of the current row. - * - * @var int - */ - protected $currentKey = NULL; - - /** - * The list of column names in this result set. - * - * @var Array - */ - protected $columnNames = NULL; - - /** - * The number of rows affected by the last query. - * - * @var int - */ - protected $rowCount = NULL; - - /** - * The number of rows in this result set. - * - * @var int - */ - protected $resultRowCount = 0; - - /** - * Holds the current fetch style (which will be used by the next fetch). - * @see PDOStatement::fetch() - * - * @var int - */ - protected $fetchStyle = PDO::FETCH_OBJ; - - /** - * Holds supplementary current fetch options (which will be used by the next fetch). - * - * @var Array - */ - protected $fetchOptions = array( - 'class' => 'stdClass', - 'constructor_args' => array(), - 'object' => NULL, - 'column' => 0, - ); - - /** - * Holds the default fetch style. - * - * @var int - */ - protected $defaultFetchStyle = PDO::FETCH_OBJ; - - /** - * Holds supplementary default fetch options. - * - * @var Array - */ - protected $defaultFetchOptions = array( - 'class' => 'stdClass', - 'constructor_args' => array(), - 'object' => NULL, - 'column' => 0, - ); - - public function __construct(DatabaseConnection $connection, $query, array $driver_options = array()) { - $this->dbh = $connection; - $this->queryString = $query; - $this->driverOptions = $driver_options; - } - - /** - * Executes a prepared statement. - * - * @param $args - * An array of values with as many elements as there are bound parameters in the SQL statement being executed. - * @param $options - * An array of options for this query. - * @return - * TRUE on success, or FALSE on failure. - */ - public function execute($args = array(), $options = array()) { - if (isset($options['fetch'])) { - if (is_string($options['fetch'])) { - // Default to an object. Note: db fields will be added to the object - // before the constructor is run. If you need to assign fields after - // the constructor is run, see http://drupal.org/node/315092. - $this->setFetchMode(PDO::FETCH_CLASS, $options['fetch']); - } - else { - $this->setFetchMode($options['fetch']); - } - } - - $logger = $this->dbh->getLogger(); - if (!empty($logger)) { - $query_start = microtime(TRUE); - } - - // Prepare the query. - $statement = $this->getStatement($this->queryString, $args); - if (!$statement) { - $this->throwPDOException(); - } - - $return = $statement->execute($args); - if (!$return) { - $this->throwPDOException(); - } - - // Fetch all the data from the reply, in order to release any lock - // as soon as possible. - $this->rowCount = $statement->rowCount(); - $this->data = $statement->fetchAll(PDO::FETCH_ASSOC); - // Destroy the statement as soon as possible. See - // DatabaseConnection_sqlite::PDOPrepare() for explanation. - unset($statement); - - $this->resultRowCount = count($this->data); - - if ($this->resultRowCount) { - $this->columnNames = array_keys($this->data[0]); - } - else { - $this->columnNames = array(); - } - - if (!empty($logger)) { - $query_end = microtime(TRUE); - $logger->log($this, $args, $query_end - $query_start); - } - - // Initialize the first row in $this->currentRow. - $this->next(); - - return $return; - } - - /** - * Throw a PDO Exception based on the last PDO error. - */ - protected function throwPDOException() { - $error_info = $this->dbh->errorInfo(); - // We rebuild a message formatted in the same way as PDO. - $exception = new PDOException("SQLSTATE[" . $error_info[0] . "]: General error " . $error_info[1] . ": " . $error_info[2]); - $exception->errorInfo = $error_info; - throw $exception; - } - - /** - * Grab a PDOStatement object from a given query and its arguments. - * - * Some drivers (including SQLite) will need to perform some preparation - * themselves to get the statement right. - * - * @param $query - * The query. - * @param array $args - * An array of arguments. - * @return PDOStatement - * A PDOStatement object. - */ - protected function getStatement($query, &$args = array()) { - return $this->dbh->prepare($query); - } - - /** - * Return the object's SQL query string. - */ - public function getQueryString() { - return $this->queryString; - } - - /** - * @see PDOStatement::setFetchMode() - */ - public function setFetchMode($fetchStyle, $a2 = NULL, $a3 = NULL) { - $this->defaultFetchStyle = $fetchStyle; - switch ($fetchStyle) { - case PDO::FETCH_CLASS: - $this->defaultFetchOptions['class'] = $a2; - if ($a3) { - $this->defaultFetchOptions['constructor_args'] = $a3; - } - break; - case PDO::FETCH_COLUMN: - $this->defaultFetchOptions['column'] = $a2; - break; - case PDO::FETCH_INTO: - $this->defaultFetchOptions['object'] = $a2; - break; - } - - // Set the values for the next fetch. - $this->fetchStyle = $this->defaultFetchStyle; - $this->fetchOptions = $this->defaultFetchOptions; - } - - /** - * Return the current row formatted according to the current fetch style. - * - * This is the core method of this class. It grabs the value at the current - * array position in $this->data and format it according to $this->fetchStyle - * and $this->fetchMode. - * - * @return - * The current row formatted as requested. - */ - public function current() { - if (isset($this->currentRow)) { - switch ($this->fetchStyle) { - case PDO::FETCH_ASSOC: - return $this->currentRow; - case PDO::FETCH_BOTH: - // PDO::FETCH_BOTH returns an array indexed by both the column name - // and the column number. - return $this->currentRow + array_values($this->currentRow); - case PDO::FETCH_NUM: - return array_values($this->currentRow); - case PDO::FETCH_LAZY: - // We do not do lazy as everything is fetched already. Fallback to - // PDO::FETCH_OBJ. - case PDO::FETCH_OBJ: - return (object) $this->currentRow; - case PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE: - $class_name = array_unshift($this->currentRow); - // Deliberate no break. - case PDO::FETCH_CLASS: - if (!isset($class_name)) { - $class_name = $this->fetchOptions['class']; - } - if (count($this->fetchOptions['constructor_args'])) { - $reflector = new ReflectionClass($class_name); - $result = $reflector->newInstanceArgs($this->fetchOptions['constructor_args']); - } - else { - $result = new $class_name(); - } - foreach ($this->currentRow as $k => $v) { - $result->$k = $v; - } - return $result; - case PDO::FETCH_INTO: - foreach ($this->currentRow as $k => $v) { - $this->fetchOptions['object']->$k = $v; - } - return $this->fetchOptions['object']; - case PDO::FETCH_COLUMN: - if (isset($this->columnNames[$this->fetchOptions['column']])) { - return $this->currentRow[$k][$this->columnNames[$this->fetchOptions['column']]]; - } - else { - return; - } - } - } - } - - /* Implementations of Iterator. */ - - public function key() { - return $this->currentKey; - } - - public function rewind() { - // Nothing to do: our DatabaseStatement can't be rewound. - } - - public function next() { - if (!empty($this->data)) { - $this->currentRow = reset($this->data); - $this->currentKey = key($this->data); - unset($this->data[$this->currentKey]); - } - else { - $this->currentRow = NULL; - } - } - - public function valid() { - return isset($this->currentRow); - } - - /* Implementations of DatabaseStatementInterface. */ - - public function rowCount() { - return $this->rowCount; - } - - public function fetch($fetch_style = NULL, $cursor_orientation = PDO::FETCH_ORI_NEXT, $cursor_offset = NULL) { - if (isset($this->currentRow)) { - // Set the fetch parameter. - $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle; - $this->fetchOptions = $this->defaultFetchOptions; - - // Grab the row in the format specified above. - $return = $this->current(); - // Advance the cursor. - $this->next(); - - // Reset the fetch parameters to the value stored using setFetchMode(). - $this->fetchStyle = $this->defaultFetchStyle; - $this->fetchOptions = $this->defaultFetchOptions; - return $return; - } - else { - return FALSE; - } - } - - public function fetchColumn($index = 0) { - if (isset($this->currentRow) && isset($this->columnNames[$index])) { - // We grab the value directly from $this->data, and format it. - $return = $this->currentRow[$this->columnNames[$index]]; - $this->next(); - return $return; - } - else { - return FALSE; - } - } - - public function fetchField($index = 0) { - return $this->fetchColumn($index); - } - - public function fetchObject($class_name = NULL, $constructor_args = array()) { - if (isset($this->currentRow)) { - if (!isset($class_name)) { - // Directly cast to an object to avoid a function call. - $result = (object) $this->currentRow; - } - else { - $this->fetchStyle = PDO::FETCH_CLASS; - $this->fetchOptions = array('constructor_args' => $constructor_args); - // Grab the row in the format specified above. - $result = $this->current(); - // Reset the fetch parameters to the value stored using setFetchMode(). - $this->fetchStyle = $this->defaultFetchStyle; - $this->fetchOptions = $this->defaultFetchOptions; - } - - $this->next(); - - return $result; - } - else { - return FALSE; - } - } - - public function fetchAssoc() { - if (isset($this->currentRow)) { - $result = $this->currentRow; - $this->next(); - return $result; - } - else { - return FALSE; - } - } - - public function fetchAll($fetch_style = NULL, $fetch_column = NULL, $constructor_args = NULL) { - $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle; - $this->fetchOptions = $this->defaultFetchOptions; - if (isset($fetch_column)) { - $this->fetchOptions['column'] = $fetch_column; - } - if (isset($constructor_args)) { - $this->fetchOptions['constructor_args'] = $constructor_args; - } - - $result = array(); - // Traverse the array as PHP would have done. - while (isset($this->currentRow)) { - // Grab the row in the format specified above. - $result[] = $this->current(); - $this->next(); - } - - // Reset the fetch parameters to the value stored using setFetchMode(). - $this->fetchStyle = $this->defaultFetchStyle; - $this->fetchOptions = $this->defaultFetchOptions; - return $result; - } - - public function fetchCol($index = 0) { - if (isset($this->columnNames[$index])) { - $column = $this->columnNames[$index]; - $result = array(); - // Traverse the array as PHP would have done. - while (isset($this->currentRow)) { - $result[] = $this->currentRow[$this->columnNames[$index]]; - $this->next(); - } - return $result; - } - else { - return array(); - } - } - - public function fetchAllKeyed($key_index = 0, $value_index = 1) { - if (!isset($this->columnNames[$key_index]) || !isset($this->columnNames[$value_index])) - return array(); - - $key = $this->columnNames[$key_index]; - $value = $this->columnNames[$value_index]; - - $result = array(); - // Traverse the array as PHP would have done. - while (isset($this->currentRow)) { - $result[$this->currentRow[$key]] = $this->currentRow[$value]; - $this->next(); - } - return $result; - } - - public function fetchAllAssoc($key, $fetch_style = NULL) { - $this->fetchStyle = isset($fetch_style) ? $fetch_style : $this->defaultFetchStyle; - $this->fetchOptions = $this->defaultFetchOptions; - - $result = array(); - // Traverse the array as PHP would have done. - while (isset($this->currentRow)) { - // Grab the row in its raw PDO::FETCH_ASSOC format. - $row = $this->currentRow; - // Grab the row in the format specified above. - $result_row = $this->current(); - $result[$this->currentRow[$key]] = $result_row; - $this->next(); - } - - // Reset the fetch parameters to the value stored using setFetchMode(). - $this->fetchStyle = $this->defaultFetchStyle; - $this->fetchOptions = $this->defaultFetchOptions; - return $result; - } - -} - -/** - * @} End of "ingroup database". - */ - diff --git a/core/includes/database/query.inc b/core/includes/database/query.inc deleted file mode 100644 index c779687679a..00000000000 --- a/core/includes/database/query.inc +++ /dev/null @@ -1,1953 +0,0 @@ -<?php - -/** - * @ingroup database - * @{ - */ - -/** - * @file - * Non-specific Database query code. Used by all engines. - */ - -/** - * Interface for a conditional clause in a query. - */ -interface QueryConditionInterface { - - /** - * Helper function: builds the most common conditional clauses. - * - * This method can take a variable number of parameters. If called with two - * parameters, they are taken as $field and $value with $operator having a - * value of IN if $value is an array and = otherwise. - * - * @param $field - * The name of the field to check. If you would like to add a more complex - * condition involving operators or functions, use where(). - * @param $value - * The value to test the field against. In most cases, this is a scalar. - * For more complex options, it is an array. The meaning of each element in - * the array is dependent on the $operator. - * @param $operator - * The comparison operator, such as =, <, or >=. It also accepts more - * complex options such as IN, LIKE, or BETWEEN. Defaults to IN if $value is - * an array, and = otherwise. - * - * @return QueryConditionInterface - * The called object. - */ - public function condition($field, $value = NULL, $operator = NULL); - - /** - * Adds an arbitrary WHERE clause to the query. - * - * @param $snippet - * A portion of a WHERE clause as a prepared statement. It must use named - * placeholders, not ? placeholders. - * @param $args - * An associative array of arguments. - * - * @return QueryConditionInterface - * The called object. - */ - public function where($snippet, $args = array()); - - /** - * Sets a condition that the specified field be NULL. - * - * @param $field - * The name of the field to check. - * - * @return QueryConditionInterface - * The called object. - */ - public function isNull($field); - - /** - * Sets a condition that the specified field be NOT NULL. - * - * @param $field - * The name of the field to check. - * - * @return QueryConditionInterface - * The called object. - */ - 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. - * - * This method returns by reference. That allows alter hooks to access the - * data structure directly and manipulate it before it gets compiled. - * - * The data structure that is returned is an indexed array of entries, where - * each entry looks like the following: - * @code - * array( - * 'field' => $field, - * 'value' => $value, - * 'operator' => $operator, - * ); - * @endcode - * - * 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 - * array of placeholders for the snippet. - * - * There will also be a single array entry of #conjunction, which is the - * conjunction that will be applied to the array, such as AND. - */ - public function &conditions(); - - /** - * Gets a complete list of all values to insert into the prepared statement. - * - * @return - * An associative array of placeholders and values. - */ - public function arguments(); - - /** - * Compiles the saved conditions for later retrieval. - * - * This method does not return anything, but simply prepares data to be - * retrieved via __toString() and arguments(). - * - * @param $connection - * The database connection for which to compile the conditionals. - * @param $queryPlaceholder - * The query this condition belongs to. If not given, the current query is - * used. - */ - 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(); -} - - -/** - * Interface for a query that can be manipulated via an alter hook. - */ -interface QueryAlterableInterface { - - /** - * Adds a tag to a query. - * - * 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 - * wish to take action. Tags should be all lower-case and contain only - * letters, numbers, and underscore, and start with a letter. That is, they - * should follow the same rules as PHP identifiers in general. - * - * @param $tag - * The tag to add. - * - * @return QueryAlterableInterface - * The called object. - */ - public function addTag($tag); - - /** - * Determines if a given query has a given tag. - * - * @param $tag - * The tag to check. - * - * @return - * TRUE if this query has been marked with this tag, FALSE otherwise. - */ - public function hasTag($tag); - - /** - * Determines if a given query has all specified tags. - * - * @param $tags - * A variable number of arguments, one for each tag to check. - * - * @return - * TRUE if this query has been marked with all specified tags, FALSE - * otherwise. - */ - public function hasAllTags(); - - /** - * Determines if a given query has any specified tag. - * - * @param $tags - * A variable number of arguments, one for each tag to check. - * - * @return - * TRUE if this query has been marked with at least one of the specified - * tags, FALSE otherwise. - */ - public function hasAnyTag(); - - /** - * Adds additional metadata to the query. - * - * Often, a query may need to provide additional contextual data to alter - * hooks. Alter hooks may then use that information to decide if and how - * to take action. - * - * @param $key - * The unique identifier for this piece of metadata. Must be a string that - * follows the same rules as any other PHP identifier. - * @param $object - * The additional data to add to the query. May be any valid PHP variable. - * - * @return QueryAlterableInterface - * The called object. - */ - public function addMetaData($key, $object); - - /** - * Retrieves a given piece of metadata. - * - * @param $key - * The unique identifier for the piece of metadata to retrieve. - * - * @return - * The previously attached metadata object, or NULL if one doesn't exist. - */ - public function getMetaData($key); -} - -/** - * Interface for a query that accepts placeholders. - */ -interface QueryPlaceholderInterface { - - /** - * Returns a unique identifier for this object. - */ - public function uniqueIdentifier(); - - /** - * Returns the next placeholder ID for the query. - * - * @return - * The next available placeholder ID as an integer. - */ - public function nextPlaceholder(); -} - -/** - * Base class for query builders. - * - * Note that query builders use PHP's magic __toString() method to compile the - * query object into a prepared statement. - */ -abstract class Query implements QueryPlaceholderInterface { - - /** - * The connection object on which to run this query. - * - * @var DatabaseConnection - */ - 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. - * - * @var array - */ - protected $queryOptions; - - /** - * A unique identifier for this query object. - */ - protected $uniqueIdentifier; - - /** - * The placeholder counter. - */ - protected $nextPlaceholder = 0; - - /** - * An array of comments that can be prepended to a query. - * - * @var 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) { - $this->uniqueIdentifier = uniqid('', TRUE); - - $this->connection = $connection; - $this->connectionKey = $this->connection->getKey(); - $this->connectionTarget = $this->connection->getTarget(); - - $this->queryOptions = $options; - } - - /** - * 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(); - - /** - * 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. - * - * @return - * A prepared statement query string for this object. - */ - 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() { - return $this->nextPlaceholder++; - } - - /** - * Adds a comment to the query. - * - * By adding a comment to a query, you can more easily find it in your - * query log or the list of active queries on an SQL server. This allows - * for easier debugging and allows you to more easily find where a query - * with a performance problem is being generated. - * - * 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 - * The comment string to be inserted into the query. - * - * @return Query - * The called object. - */ - public function comment($comment) { - $this->comments[] = $comment; - return $this; - } - - /** - * Returns a reference to the comments array for the query. - * - * Because this method returns by reference, alter hooks may edit the comments - * array directly to make their changes. If just adding comments, however, the - * use of comment() is preferred. - * - * Note that this method must be called by reference as well: - * @code - * $comments =& $query->getComments(); - * @endcode - * - * @return - * A reference to the comments array structure. - */ - public function &getComments() { - return $this->comments; - } -} - -/** - * General class for an abstracted INSERT query. - */ -class InsertQuery extends Query { - - /** - * The table on which to insert. - * - * @var string - */ - protected $table; - - /** - * An array of fields on which to insert. - * - * @var array - */ - protected $insertFields = array(); - - /** - * An array of fields that should be set to their database-defined defaults. - * - * @var array - */ - protected $defaultFields = array(); - - /** - * A nested array of values to insert. - * - * $insertValues is an array of arrays. Each sub-array is either an - * associative array whose keys are field names and whose values are field - * values to insert, or a non-associative array of values in the same order - * as $insertFields. - * - * 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(); - - /** - * A SelectQuery object to fetch the rows that should be inserted. - * - * @var SelectQueryInterface - */ - 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()) { - if (!isset($options['return'])) { - $options['return'] = Database::RETURN_INSERT_ID; - } - parent::__construct($connection, $options); - $this->table = $table; - } - - /** - * Adds a set of field->value pairs to be inserted. - * - * 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, - * use the values() method. - * - * @param $fields - * An array of fields on which to insert. This array may be indexed or - * associative. If indexed, the array is taken to be the list of fields. - * If associative, the keys of the array are taken to be the fields and - * the values are taken to be corresponding values to insert. If a - * $values argument is provided, $fields must be indexed. - * @param $values - * An array of fields to insert into the database. The values must be - * specified in the same order as the $fields array. - * - * @return InsertQuery - * The called object. - */ - public function fields(array $fields, array $values = array()) { - if (empty($this->insertFields)) { - if (empty($values)) { - if (!is_numeric(key($fields))) { - $values = array_values($fields); - $fields = array_keys($fields); - } - } - $this->insertFields = $fields; - if (!empty($values)) { - $this->insertValues[] = $values; - } - } - - return $this; - } - - /** - * Adds another set of values to the query to be inserted. - * - * 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 - * in any order as long as the keys of the array match the names of the - * fields. - * - * @param $values - * An array of values to add to the query. - * - * @return InsertQuery - * The called object. - */ - public function values(array $values) { - if (is_numeric(key($values))) { - $this->insertValues[] = $values; - } - else { - // Reorder the submitted values to match the fields array. - foreach ($this->insertFields as $key) { - $insert_values[$key] = $values[$key]; - } - // For consistency, the values array is always numerically indexed. - $this->insertValues[] = array_values($insert_values); - } - return $this; - } - - /** - * Specifies fields for which the database defaults should be used. - * - * 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 - * default values explicitly. In most cases this will not be necessary - * unless you are inserting a row that is all default values, as you cannot - * specify no values in an INSERT query. - * - * Specifying a field both in fields() and in useDefaults() is an error - * and will not execute. - * - * @param $fields - * An array of values for which to use the default values - * specified in the table definition. - * - * @return InsertQuery - * The called object. - */ - public function useDefaults(array $fields) { - $this->defaultFields = $fields; - 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) { - $this->fromQuery = $query; - return $this; - } - - /** - * Executes the insert query. - * - * @return - * The last insert ID of the query, if one exists. If the query - * was given multiple sets of values to insert, the return value is - * undefined. If no fields are specified, this method will do nothing and - * return NULL. That makes it safe to use in multi-insert loops. - */ - public function execute() { - // If validation fails, simply return NULL. Note that validation routines - // in preExecute() may throw exceptions instead. - if (!$this->preExecute()) { - return NULL; - } - - // If we're selecting from a SelectQuery, finish building the query and - // pass it back, as any remaining options are irrelevant. - if (!empty($this->fromQuery)) { - $sql = (string) $this; - // The SelectQuery may contain arguments, load and pass them through. - return $this->connection->query($sql, $this->fromQuery->getArguments(), $this->queryOptions); - } - - $last_insert_id = 0; - - // Each insert happens in its own query in the degenerate case. However, - // we wrap it in a transaction so that it is atomic where possible. On many - // databases, such as SQLite, this is also a notable performance boost. - $transaction = $this->connection->startTransaction(); - - try { - $sql = (string) $this; - foreach ($this->insertValues as $insert_values) { - $last_insert_id = $this->connection->query($sql, $insert_values, $this->queryOptions); - } - } - catch (Exception $e) { - // One of the INSERTs failed, rollback the whole batch. - $transaction->rollback(); - // Rethrow the exception for the calling code. - throw $e; - } - - // Re-initialize the values array so that we can re-use this query. - $this->insertValues = array(); - - // Transaction commits here where $transaction looses scope. - - return $last_insert_id; - } - - /** - * Implements PHP magic __toString method to convert the query to a string. - * - * @return string - * The prepared statement. - */ - public function __toString() { - // Create a sanitized comment string to prepend to the query. - $comments = $this->connection->makeComment($this->comments); - - // Default fields are always placed first for consistency. - $insert_fields = array_merge($this->defaultFields, $this->insertFields); - - if (!empty($this->fromQuery)) { - return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') ' . $this->fromQuery; - } - - // For simplicity, we will use the $placeholders array to inject - // default keywords even though they are not, strictly speaking, - // placeholders for prepared statements. - $placeholders = array(); - $placeholders = array_pad($placeholders, count($this->defaultFields), 'default'); - $placeholders = array_pad($placeholders, count($this->insertFields), '?'); - - return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $insert_fields) . ') VALUES (' . implode(', ', $placeholders) . ')'; - } - - /** - * Preprocesses and validates the query. - * - * @return - * TRUE if the validation was successful, FALSE if not. - * - * @throws FieldsOverlapException - * @throws NoFieldsException - */ - public function preExecute() { - // Confirm that the user did not try to specify an identical - // field and default field. - if (array_intersect($this->insertFields, $this->defaultFields)) { - throw new FieldsOverlapException('You may not specify the same field to have a value and a schema-default value.'); - } - - if (!empty($this->fromQuery)) { - // We have to assume that the used aliases match the insert fields. - // Regular fields are added to the query before expressions, maintain the - // same order for the insert fields. - // This behavior can be overridden by calling fields() manually as only the - // first call to fields() does have an effect. - $this->fields(array_merge(array_keys($this->fromQuery->getFields()), array_keys($this->fromQuery->getExpressions()))); - } - - // Don't execute query without fields. - if (count($this->insertFields) + count($this->defaultFields) == 0) { - throw new NoFieldsException('There are no fields available to insert with.'); - } - - // If no values have been added, silently ignore this query. This can happen - // if values are added conditionally, so we don't want to throw an - // exception. - if (!isset($this->insertValues[0]) && count($this->insertFields) > 0 && empty($this->fromQuery)) { - return FALSE; - } - return TRUE; - } -} - -/** - * General class for an abstracted DELETE operation. - */ -class DeleteQuery extends Query implements QueryConditionInterface { - - /** - * The table from which to delete. - * - * @var string - */ - protected $table; - - /** - * The condition object for this query. - * - * Condition handling is handled via composition. - * - * @var DatabaseCondition - */ - 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()) { - $options['return'] = Database::RETURN_AFFECTED; - parent::__construct($connection, $options); - $this->table = $table; - - $this->condition = new DatabaseCondition('AND'); - } - - /** - * Implements QueryConditionInterface::condition(). - */ - public function condition($field, $value = NULL, $operator = NULL) { - $this->condition->condition($field, $value, $operator); - return $this; - } - - /** - * Implements QueryConditionInterface::isNull(). - */ - public function isNull($field) { - $this->condition->isNull($field); - return $this; - } - - /** - * Implements QueryConditionInterface::isNotNull(). - */ - public function isNotNull($field) { - $this->condition->isNotNull($field); - 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() { - return $this->condition->conditions(); - } - - /** - * Implements QueryConditionInterface::arguments(). - */ - public function arguments() { - return $this->condition->arguments(); - } - - /** - * Implements QueryConditionInterface::where(). - */ - public function where($snippet, $args = array()) { - $this->condition->where($snippet, $args); - return $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() { - $values = array(); - if (count($this->condition)) { - $this->condition->compile($this->connection, $this); - $values = $this->condition->arguments(); - } - - 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() { - // Create a sanitized comment string to prepend to the query. - $comments = $this->connection->makeComment($this->comments); - - $query = $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} '; - - if (count($this->condition)) { - - $this->condition->compile($this->connection, $this); - $query .= "\nWHERE " . $this->condition; - } - - return $query; - } -} - - -/** - * General class for an abstracted TRUNCATE operation. - */ -class TruncateQuery extends Query { - - /** - * The table to truncate. - * - * @var string - */ - 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()) { - $options['return'] = Database::RETURN_AFFECTED; - parent::__construct($connection, $options); - $this->table = $table; - } - - /** - * 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() { - 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() { - // Create a sanitized comment string to prepend to the query. - $comments = $this->connection->makeComment($this->comments); - - return $comments . 'TRUNCATE {' . $this->connection->escapeTable($this->table) . '} '; - } -} - -/** - * General class for an abstracted UPDATE operation. - */ -class UpdateQuery extends Query implements QueryConditionInterface { - - /** - * The table to update. - * - * @var string - */ - protected $table; - - /** - * An array of fields that will be updated. - * - * @var array - */ - protected $fields = array(); - - /** - * An array of values to update to. - * - * @var array - */ - protected $arguments = array(); - - /** - * The condition object for this query. - * - * Condition handling is handled via composition. - * - * @var DatabaseCondition - */ - protected $condition; - - /** - * Array of fields to update to an expression in case of a duplicate record. - * - * This variable is a nested array in the following format: - * @code - * <some field> => array( - * 'condition' => <condition to execute, as a string>, - * 'arguments' => <array of arguments for condition, or NULL for none>, - * ); - * @endcode - * - * @var 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()) { - $options['return'] = Database::RETURN_AFFECTED; - parent::__construct($connection, $options); - $this->table = $table; - - $this->condition = new DatabaseCondition('AND'); - } - - /** - * Implements QueryConditionInterface::condition(). - */ - public function condition($field, $value = NULL, $operator = NULL) { - $this->condition->condition($field, $value, $operator); - return $this; - } - - /** - * Implements QueryConditionInterface::isNull(). - */ - public function isNull($field) { - $this->condition->isNull($field); - return $this; - } - - /** - * Implements QueryConditionInterface::isNotNull(). - */ - public function isNotNull($field) { - $this->condition->isNotNull($field); - 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() { - return $this->condition->conditions(); - } - - /** - * Implements QueryConditionInterface::arguments(). - */ - public function arguments() { - return $this->condition->arguments(); - } - - /** - * Implements QueryConditionInterface::where(). - */ - public function where($snippet, $args = array()) { - $this->condition->where($snippet, $args); - return $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(); - } - - /** - * Adds a set of field->value pairs to be updated. - * - * @param $fields - * An associative array of fields to write into the database. The array keys - * are the field names and the values are the values to which to set them. - * - * @return UpdateQuery - * The called object. - */ - public function fields(array $fields) { - $this->fields = $fields; - return $this; - } - - /** - * Specifies fields to be updated as an expression. - * - * Expression fields are cases such as counter=counter+1. This method takes - * precedence over fields(). - * - * @param $field - * The field to set. - * @param $expression - * The field will be set to the value of this expression. This parameter - * may include named placeholders. - * @param $arguments - * If specified, this is an array of key/value pairs for named placeholders - * corresponding to the expression. - * - * @return UpdateQuery - * The called object. - */ - public function expression($field, $expression, array $arguments = NULL) { - $this->expressionFields[$field] = array( - 'expression' => $expression, - 'arguments' => $arguments, - ); - - return $this; - } - - /** - * Executes the UPDATE query. - * - * @return - * The number of rows affected by the update. - */ - public function execute() { - - // Expressions take priority over literal fields, so we process those first - // and remove any literal fields that conflict. - $fields = $this->fields; - $update_values = array(); - foreach ($this->expressionFields as $field => $data) { - if (!empty($data['arguments'])) { - $update_values += $data['arguments']; - } - unset($fields[$field]); - } - - // Because we filter $fields the same way here and in __toString(), the - // placeholders will all match up properly. - $max_placeholder = 0; - foreach ($fields as $field => $value) { - $update_values[':db_update_placeholder_' . ($max_placeholder++)] = $value; - } - - if (count($this->condition)) { - $this->condition->compile($this->connection, $this); - $update_values = array_merge($update_values, $this->condition->arguments()); - } - - 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() { - // Create a sanitized comment string to prepend to the query. - $comments = $this->connection->makeComment($this->comments); - - // Expressions take priority over literal fields, so we process those first - // and remove any literal fields that conflict. - $fields = $this->fields; - $update_fields = array(); - foreach ($this->expressionFields as $field => $data) { - $update_fields[] = $field . '=' . $data['expression']; - unset($fields[$field]); - } - - $max_placeholder = 0; - foreach ($fields as $field => $value) { - $update_fields[] = $field . '=:db_update_placeholder_' . ($max_placeholder++); - } - - $query = $comments . 'UPDATE {' . $this->connection->escapeTable($this->table) . '} SET ' . implode(', ', $update_fields); - - if (count($this->condition)) { - $this->condition->compile($this->connection, $this); - // There is an implicit string cast on $this->condition. - $query .= "\nWHERE " . $this->condition; - } - - return $query; - } - -} - -/** - * General class for an abstracted MERGE query operation. - * - * An ANSI SQL:2003 compatible database would run the following query: - * - * @code - * MERGE INTO table_name_1 USING table_name_2 ON (condition) - * WHEN MATCHED THEN - * UPDATE SET column1 = value1 [, column2 = value2 ...] - * WHEN NOT MATCHED THEN - * INSERT (column1 [, column2 ...]) VALUES (value1 [, value2 ... - * @endcode - * - * Other databases (most notably MySQL, PostgreSQL and SQLite) will emulate - * this statement by running a SELECT and then INSERT or UPDATE. - * - * By default, the two table names are identical and they are passed into the - * the constructor. table_name_2 can be specified by the - * MergeQuery::conditionTable() method. It can be either a string or a - * subquery. - * - * The condition is built exactly like SelectQuery or UpdateQuery conditions, - * the UPDATE query part is built similarly like an UpdateQuery and finally the - * INSERT query part is built similarly like an InsertQuery. However, both - * UpdateQuery and InsertQuery has a fields method so - * MergeQuery::updateFields() and MergeQuery::insertFields() needs to be called - * instead. MergeQuery::fields() can also be called which calls both of these - * methods as the common case is to use the same column-value pairs for both - * INSERT and UPDATE. However, this is not mandatory. Another convinient - * wrapper is MergeQuery::key() which adds the same column-value pairs to the - * condition and the INSERT query part. - * - * Several methods (key(), fields(), insertFields()) can be called to set a - * key-value pair for the INSERT query part. Subsequent calls for the same - * fields override the earlier ones. The same is true for UPDATE and key(), - * fields() and updateFields(). - */ -class MergeQuery extends Query implements QueryConditionInterface { - /** - * Returned by execute() if an INSERT query has been executed. - */ - const STATUS_INSERT = 1; - - /** - * Returned by execute() if an UPDATE query has been executed. - */ - const STATUS_UPDATE = 2; - - /** - * The table to be used for INSERT and UPDATE. - * - * @var string - */ - protected $table; - - /** - * The table or subquery to be used for the condition. - */ - protected $conditionTable; - - /** - * An array of fields on which to insert. - * - * @var array - */ - protected $insertFields = array(); - - /** - * An array of fields which should be set to their database-defined defaults. - * - * Used on INSERT. - * - * @var array - */ - protected $defaultFields = array(); - - /** - * An array of values to be inserted. - * - * @var string - */ - protected $insertValues = array(); - - /** - * An array of fields that will be updated. - * - * @var array - */ - protected $updateFields = array(); - - /** - * Array of fields to update to an expression in case of a duplicate record. - * - * This variable is a nested array in the following format: - * @code - * <some field> => array( - * 'condition' => <condition to execute, as a string>, - * 'arguments' => <array of arguments for condition, or NULL for none>, - * ); - * @endcode - * - * @var array - */ - protected $expressionFields = array(); - - /** - * Flag indicating whether an UPDATE is necessary. - * - * @var boolean - */ - 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()) { - $options['return'] = Database::RETURN_AFFECTED; - parent::__construct($connection, $options); - $this->table = $table; - $this->conditionTable = $table; - $this->condition = new DatabaseCondition('AND'); - } - - /** - * Sets the table or subquery to be used for the condition. - * - * @param $table - * The table name or the subquery to be used. Use a SelectQuery object to - * pass in a subquery. - * - * @return MergeQuery - * The called object. - */ - protected function conditionTable($table) { - $this->conditionTable = $table; - return $this; - } - - /** - * Adds a set of field->value pairs to be updated. - * - * @param $fields - * An associative array of fields to write into the database. The array keys - * are the field names and the values are the values to which to set them. - * - * @return MergeQuery - * The called object. - */ - public function updateFields(array $fields) { - $this->updateFields = $fields; - $this->needsUpdate = TRUE; - return $this; - } - - /** - * Specifies fields to be updated as an expression. - * - * Expression fields are cases such as counter = counter + 1. This method - * takes precedence over MergeQuery::updateFields() and it's wrappers, - * MergeQuery::key() and MergeQuery::fields(). - * - * @param $field - * The field to set. - * @param $expression - * The field will be set to the value of this expression. This parameter - * may include named placeholders. - * @param $arguments - * If specified, this is an array of key/value pairs for named placeholders - * corresponding to the expression. - * - * @return MergeQuery - * The called object. - */ - public function expression($field, $expression, array $arguments = NULL) { - $this->expressionFields[$field] = array( - 'expression' => $expression, - 'arguments' => $arguments, - ); - $this->needsUpdate = TRUE; - return $this; - } - - /** - * Adds a set of field->value pairs to be inserted. - * - * @param $fields - * An array of fields on which to insert. This array may be indexed or - * associative. If indexed, the array is taken to be the list of fields. - * If associative, the keys of the array are taken to be the fields and - * the values are taken to be corresponding values to insert. If a - * $values argument is provided, $fields must be indexed. - * @param $values - * An array of fields to insert into the database. The values must be - * specified in the same order as the $fields array. - * - * @return MergeQuery - * The called object. - */ - public function insertFields(array $fields, array $values = array()) { - if ($values) { - $fields = array_combine($fields, $values); - } - $this->insertFields = $fields; - return $this; - } - - /** - * Specifies fields for which the database-defaults should be used. - * - * 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 - * default values explicitly. In most cases this will not be necessary - * unless you are inserting a row that is all default values, as you cannot - * specify no values in an INSERT query. - * - * Specifying a field both in fields() and in useDefaults() is an error - * and will not execute. - * - * @param $fields - * An array of values for which to use the default values - * specified in the table definition. - * - * @return MergeQuery - * The called object. - */ - public function useDefaults(array $fields) { - $this->defaultFields = $fields; - return $this; - } - - /** - * Sets common field-value pairs in the INSERT and UPDATE query parts. - * - * This method should only be called once. It may be called either - * with a single associative array or two indexed arrays. If called - * with an associative array, the keys are taken to be the fields - * and the values are taken to be the corresponding values to set. - * If called with two arrays, the first array is taken as the fields - * and the second array is taken as the corresponding values. - * - * @param $fields - * An array of fields to insert, or an associative array of fields and - * values. The keys of the array are taken to be the fields and the values - * are taken to be corresponding values to insert. - * @param $values - * An array of values to set into the database. The values must be - * specified in the same order as the $fields array. - * - * @return MergeQuery - * The called object. - */ - public function fields(array $fields, array $values = array()) { - if ($values) { - $fields = array_combine($fields, $values); - } - foreach ($fields as $key => $value) { - $this->insertFields[$key] = $value; - $this->updateFields[$key] = $value; - } - $this->needsUpdate = TRUE; - return $this; - } - - /** - * 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 - * with a single associative array or two indexed arrays. If called - * with an associative array, the keys are taken to be the fields - * and the values are taken to be the corresponding values to set. - * If called with two arrays, the first array is taken as the fields - * and the second array is taken as the corresponding values. - * - * The fields are copied to the condition of the query and the INSERT part. - * If no other method is called, the UPDATE will become a no-op. - * - * @param $fields - * An array of fields to set, or an associative array of fields and values. - * @param $values - * An array of values to set into the database. The values must be - * specified in the same order as the $fields array. - * - * @return MergeQuery - * The called object. - */ - public function key(array $fields, array $values = array()) { - if ($values) { - $fields = array_combine($fields, $values); - } - foreach ($fields as $key => $value) { - $this->insertFields[$key] = $value; - $this->condition($key, $value); - } - return $this; - } - - /** - * Implements QueryConditionInterface::condition(). - */ - public function condition($field, $value = NULL, $operator = NULL) { - $this->condition->condition($field, $value, $operator); - return $this; - } - - /** - * Implements QueryConditionInterface::isNull(). - */ - public function isNull($field) { - $this->condition->isNull($field); - return $this; - } - - /** - * Implements QueryConditionInterface::isNotNull(). - */ - public function isNotNull($field) { - $this->condition->isNotNull($field); - 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() { - return $this->condition->conditions(); - } - - /** - * Implements QueryConditionInterface::arguments(). - */ - public function arguments() { - return $this->condition->arguments(); - } - - /** - * Implements QueryConditionInterface::where(). - */ - public function where($snippet, $args = array()) { - $this->condition->where($snippet, $args); - return $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 execute() { - // Wrap multiple queries in a transaction, if the database supports it. - $transaction = $this->connection->startTransaction(); - try { - if (!count($this->condition)) { - throw new InvalidMergeQueryException(t('Invalid merge query: no conditions')); - } - $select = $this->connection->select($this->conditionTable) - ->condition($this->condition) - ->forUpdate(); - $select->addExpression('1'); - if (!$select->execute()->fetchField()) { - try { - $insert = $this->connection->insert($this->table)->fields($this->insertFields); - if ($this->defaultFields) { - $insert->useDefaults($this->defaultFields); - } - $insert->execute(); - return MergeQuery::STATUS_INSERT; - } - catch (Exception $e) { - // The insert query failed, maybe it's because a racing insert query - // beat us in inserting the same row. Retry the select query, if it - // returns a row, ignore the error and continue with the update - // query below. - if (!$select->execute()->fetchField()) { - throw $e; - } - } - } - if ($this->needsUpdate) { - $update = $this->connection->update($this->table) - ->fields($this->updateFields) - ->condition($this->condition); - if ($this->expressionFields) { - foreach ($this->expressionFields as $field => $data) { - $update->expression($field, $data['expression'], $data['arguments']); - } - } - $update->execute(); - return MergeQuery::STATUS_UPDATE; - } - } - catch (Exception $e) { - // Something really wrong happened here, bubble up the exception to the - // caller. - $transaction->rollback(); - throw $e; - } - // Transaction commits here where $transaction looses scope. - } -} - -/** - * Generic class for a series of conditions in a query. - */ -class DatabaseCondition implements QueryConditionInterface, Countable { - - /** - * Array of conditions. - * - * @var array - */ - protected $conditions = array(); - - /** - * Array of arguments. - * - * @var 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; - - /** - * 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) { - $this->conditions['#conjunction'] = $conjunction; - } - - /** - * Implements Countable::count(). - * - * Returns the size of this conditional. The size of the conditional is the - * size of its conditional array minus one, because one element is the the - * conjunction. - */ - public function count() { - return count($this->conditions) - 1; - } - - /** - * Implements QueryConditionInterface::condition(). - */ - public function condition($field, $value = NULL, $operator = NULL) { - if (!isset($operator)) { - if (is_array($value)) { - $operator = 'IN'; - } - elseif (!isset($value)) { - $operator = 'IS NULL'; - } - else { - $operator = '='; - } - } - $this->conditions[] = array( - 'field' => $field, - 'value' => $value, - 'operator' => $operator, - ); - - $this->changed = TRUE; - - return $this; - } - - /** - * Implements QueryConditionInterface::where(). - */ - public function where($snippet, $args = array()) { - $this->conditions[] = array( - 'field' => $snippet, - 'value' => $args, - 'operator' => NULL, - ); - $this->changed = TRUE; - - return $this; - } - - /** - * Implements QueryConditionInterface::isNull(). - */ - public function isNull($field) { - return $this->condition($field); - } - - /** - * Implements QueryConditionInterface::isNotNull(). - */ - public function isNotNull($field) { - 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() { - return $this->conditions; - } - - /** - * Implements QueryConditionInterface::arguments(). - */ - public function arguments() { - // If the caller forgot to call compile() first, refuse to run. - if ($this->changed) { - return NULL; - } - return $this->arguments; - } - - /** - * 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(); - $arguments = array(); - - $conditions = $this->conditions; - $conjunction = $conditions['#conjunction']; - unset($conditions['#conjunction']); - foreach ($conditions as $condition) { - if (empty($condition['operator'])) { - // This condition is a literal string, so let it through as is. - $condition_fragments[] = ' (' . $condition['field'] . ') '; - $arguments += $condition['value']; - } - else { - // It's a structured condition, so parse it out accordingly. - // Note that $condition['field'] will only be an object for a dependent - // DatabaseCondition object, not for a dependent subquery. - if ($condition['field'] instanceof QueryConditionInterface) { - // Compile the sub-condition recursively and add it to the list. - $condition['field']->compile($connection, $queryPlaceholder); - $condition_fragments[] = '(' . (string) $condition['field'] . ')'; - $arguments += $condition['field']->arguments(); - } - else { - // For simplicity, we treat all operators as the same data structure. - // In the typical degenerate case, this won't get changed. - $operator_defaults = array( - 'prefix' => '', - 'postfix' => '', - 'delimiter' => '', - 'operator' => $condition['operator'], - 'use_value' => TRUE, - ); - $operator = $connection->mapConditionOperator($condition['operator']); - if (!isset($operator)) { - $operator = $this->mapConditionOperator($condition['operator']); - } - $operator += $operator_defaults; - - $placeholders = array(); - if ($condition['value'] instanceof SelectQueryInterface) { - $condition['value']->compile($connection, $queryPlaceholder); - $placeholders[] = (string) $condition['value']; - $arguments += $condition['value']->arguments(); - // Subqueries are the actual value of the operator, we don't - // need to add another below. - $operator['use_value'] = FALSE; - } - // We assume that if there is a delimiter, then the value is an - // array. If not, it is a scalar. For simplicity, we first convert - // up to an array so that we can build the placeholders in the same way. - elseif (!$operator['delimiter']) { - $condition['value'] = array($condition['value']); - } - if ($operator['use_value']) { - foreach ($condition['value'] as $value) { - $placeholder = ':db_condition_placeholder_' . $queryPlaceholder->nextPlaceholder(); - $arguments[$placeholder] = $value; - $placeholders[] = $placeholder; - } - } - $condition_fragments[] = ' (' . $connection->escapeField($condition['field']) . ' ' . $operator['operator'] . ' ' . $operator['prefix'] . implode($operator['delimiter'], $placeholders) . $operator['postfix'] . ') '; - } - } - } - - $this->changed = FALSE; - $this->stringVersion = implode($conjunction, $condition_fragments); - $this->arguments = $arguments; - } - } - - /** - * 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() { - // If the caller forgot to call compile() first, refuse to run. - if ($this->changed) { - return NULL; - } - return $this->stringVersion; - } - - /** - * PHP magic __clone() method. - * - * Only copies fields that implement QueryConditionInterface. Also sets - * $this->changed to TRUE. - */ - function __clone() { - $this->changed = TRUE; - foreach ($this->conditions as $key => $condition) { - if ($condition['field'] instanceOf QueryConditionInterface) { - $this->conditions[$key]['field'] = clone($condition['field']); - } - } - } - - /** - * Gets any special processing requirements for the condition operator. - * - * Some condition types require special processing, such as IN, because - * the value data they pass in is not a simple value. This is a simple - * overridable lookup function. - * - * @param $operator - * The condition operator, such as "IN", "BETWEEN", etc. Case-sensitive. - * - * @return - * The extra handling directives for the specified operator, or NULL. - */ - protected function mapConditionOperator($operator) { - // $specials does not use drupal_static as its value never changes. - static $specials = array( - 'BETWEEN' => array('delimiter' => ' AND '), - '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 NOT NULL' => array('use_value' => FALSE), - // Use backslash for escaping wildcard characters. - 'LIKE' => array('postfix' => " ESCAPE '\\\\'"), - 'NOT LIKE' => array('postfix' => " ESCAPE '\\\\'"), - // These ones are here for performance reasons. - '=' => array(), - '<' => array(), - '>' => array(), - '>=' => array(), - '<=' => array(), - ); - if (isset($specials[$operator])) { - $return = $specials[$operator]; - } - else { - // We need to upper case because PHP index matches are case sensitive but - // do not need the more expensive drupal_strtoupper because SQL statements are ASCII. - $operator = strtoupper($operator); - $return = isset($specials[$operator]) ? $specials[$operator] : array(); - } - - $return += array('operator' => $operator); - - return $return; - } - -} - -/** - * @} End of "ingroup database". - */ diff --git a/core/includes/database/schema.inc b/core/includes/database/schema.inc deleted file mode 100644 index 27934dcdf04..00000000000 --- a/core/includes/database/schema.inc +++ /dev/null @@ -1,723 +0,0 @@ -<?php - -/** - * @file - * Generic Database schema code. - */ - -require_once __DIR__ . '/query.inc'; - -/** - * @defgroup schemaapi Schema API - * @{ - * API to handle database schemas. - * - * A Drupal schema definition is an array structure representing one or - * more tables and their related keys and indexes. A schema is defined by - * hook_schema(), which usually lives in a modulename.install file. - * - * By implementing hook_schema() and specifying the tables your module - * declares, you can easily create and drop these tables on all - * supported database engines. You don't have to deal with the - * different SQL dialects for table creation and alteration of the - * supported database engines. - * - * hook_schema() should return an array with a key for each table that - * the module defines. - * - * The following keys are defined: - * - 'description': A string in non-markup plain text describing this table - * and its purpose. References to other tables should be enclosed in - * curly-brackets. For example, the node_revisions table - * description field might contain "Stores per-revision title and - * body data for each {node}." - * - 'fields': An associative array ('fieldname' => specification) - * that describes the table's database columns. The specification - * is also an array. The following specification parameters are defined: - * - 'description': A string in non-markup plain text describing this field - * and its purpose. References to other tables should be enclosed in - * curly-brackets. For example, the node table vid field - * description might contain "Always holds the largest (most - * recent) {node_revision}.vid value for this nid." - * - 'type': The generic datatype: 'char', 'varchar', 'text', 'blob', 'int', - * 'float', 'numeric', or 'serial'. Most types just map to the according - * database engine specific datatypes. Use 'serial' for auto incrementing - * fields. This will expand to 'INT 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 - * a serialized string. - * - 'size': The data size: 'tiny', 'small', 'medium', 'normal', - * 'big'. This is a hint about the largest value the field will - * store and determines which of the database engine specific - * datatypes will be used (e.g. on MySQL, TINYINT vs. INT vs. BIGINT). - * 'normal', the default, selects the base type (e.g. on MySQL, - * INT, VARCHAR, BLOB, etc.). - * Not all sizes are available for all data types. See - * DatabaseSchema::getFieldTypeMap() for possible combinations. - * - 'not null': If true, no NULL values will be allowed in this - * database column. Defaults to false. - * - 'default': The field's default value. The PHP type of the - * value matters: '', '0', and 0 are all different. If you - * specify '0' as the default value for a type 'int' field it - * will not work because '0' is a string containing the - * character "zero", not an integer. - * - 'length': The maximal length of a type 'char', 'varchar' or 'text' - * field. Ignored for other field types. - * - 'unsigned': A boolean indicating whether a type 'int', 'float' - * and 'numeric' only is signed or unsigned. Defaults to - * FALSE. Ignored for other field types. - * - 'precision', 'scale': For type 'numeric' fields, indicates - * the precision (total number of significant digits) and scale - * (decimal digits right of the decimal point). Both values are - * mandatory. Ignored for other field types. - * All parameters apart from 'type' are optional except that type - * 'numeric' columns must specify 'precision' and 'scale'. - * - 'primary key': An array of one or more key column specifiers (see below) - * that form the primary key. - * - 'unique keys': An associative array of unique keys ('keyname' => - * specification). Each specification is an array of one or more - * key column specifiers (see below) that form a unique key on the table. - * - 'foreign keys': An associative array of relations ('my_relation' => - * specification). Each specification is an array containing the name of - * the referenced table ('table'), and an array of column mappings - * ('columns'). Column mappings are defined by key pairs ('source_column' => - * 'referenced_column'). - * - 'indexes': An associative array of indexes ('indexname' => - * specification). Each specification is an array of one or more - * key column specifiers (see below) that form an index on the - * table. - * - * A key column specifier is either a string naming a column or an - * array of two elements, column name and length, specifying a prefix - * of the named column. - * - * As an example, here is a SUBSET of the schema definition for - * Drupal's 'node' table. It show four fields (nid, vid, type, and - * title), the primary key on field 'nid', a unique key named 'vid' on - * field 'vid', and two indexes, one named 'nid' on field 'nid' and - * one named 'node_title_type' on the field 'title' and the first four - * bytes of the field 'type': - * - * @code - * $schema['node'] = array( - * 'description' => 'The base table for nodes.', - * 'fields' => array( - * 'nid' => array('type' => 'serial', 'unsigned' => TRUE, 'not null' => TRUE), - * 'vid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE,'default' => 0), - * 'type' => array('type' => 'varchar','length' => 32,'not null' => TRUE, 'default' => ''), - * 'language' => array('type' => 'varchar','length' => 12,'not null' => TRUE,'default' => ''), - * 'title' => array('type' => 'varchar','length' => 255,'not null' => TRUE, 'default' => ''), - * 'uid' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), - * 'status' => array('type' => 'int', 'not null' => TRUE, 'default' => 1), - * 'created' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), - * 'changed' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), - * 'comment' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), - * 'promote' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), - * 'moderate' => array('type' => 'int', 'not null' => TRUE,'default' => 0), - * 'sticky' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), - * 'tnid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0), - * 'translate' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), - * ), - * 'indexes' => array( - * 'node_changed' => array('changed'), - * 'node_created' => array('created'), - * 'node_moderate' => array('moderate'), - * 'node_frontpage' => array('promote', 'status', 'sticky', 'created'), - * 'node_status_type' => array('status', 'type', 'nid'), - * 'node_title_type' => array('title', array('type', 4)), - * 'node_type' => array(array('type', 4)), - * 'uid' => array('uid'), - * 'tnid' => array('tnid'), - * 'translate' => array('translate'), - * ), - * 'unique keys' => array( - * 'vid' => array('vid'), - * ), - * 'foreign keys' => array( - * 'node_revision' => array( - * 'table' => 'node_revision', - * 'columns' => array('vid' => 'vid'), - * ), - * 'node_author' => array( - * 'table' => 'users', - * 'columns' => array('uid' => 'uid'), - * ), - * ), - * 'primary key' => array('nid'), - * ); - * @endcode - * - * @see drupal_install_schema() - */ - -abstract class DatabaseSchema implements QueryPlaceholderInterface { - - protected $connection; - - /** - * The placeholder counter. - */ - protected $placeholder = 0; - - /** - * Definition of prefixInfo array structure. - * - * Rather than redefining DatabaseSchema::getPrefixInfo() for each driver, - * by defining the defaultSchema variable only MySQL has to re-write the - * method. - * - * @see DatabaseSchema::getPrefixInfo() - */ - protected $defaultSchema = 'public'; - - /** - * A unique identifier for this query object. - */ - protected $uniqueIdentifier; - - public function __construct($connection) { - $this->uniqueIdentifier = uniqid('', TRUE); - $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() { - return $this->placeholder++; - } - - /** - * Get information about the table name and schema from the prefix. - * - * @param - * Name of table to look prefix up for. Defaults to 'default' because thats - * default key for prefix. - * @param $add_prefix - * Boolean that indicates whether the given table name should be prefixed. - * - * @return - * A keyed array with information about the schema, table name and prefix. - */ - protected function getPrefixInfo($table = 'default', $add_prefix = TRUE) { - $info = array( - 'schema' => $this->defaultSchema, - 'prefix' => $this->connection->tablePrefix($table), - ); - if ($add_prefix) { - $table = $info['prefix'] . $table; - } - // If the prefix contains a period in it, then that means the prefix also - // contains a schema reference in which case we will change the schema key - // to the value before the period in the prefix. Everything after the dot - // will be prefixed onto the front of the table. - if (($pos = strpos($table, '.')) !== FALSE) { - // Grab everything before the period. - $info['schema'] = substr($table, 0, $pos); - // Grab everything after the dot. - $info['table'] = substr($table, ++$pos); - } - else { - $info['table'] = $table; - } - return $info; - } - - /** - * Create names for indexes, primary keys and constraints. - * - * This prevents using {} around non-table names like indexes and keys. - */ - function prefixNonTable($table) { - $args = func_get_args(); - $info = $this->getPrefixInfo($table); - $args[0] = $info['table']; - return implode('_', $args); - } - - /** - * Build a condition to match a table name against a standard information_schema. - * - * The information_schema is a SQL standard that provides information about the - * database server and the databases, schemas, tables, columns and users within - * it. This makes information_schema a useful tool to use across the drupal - * database drivers and is used by a few different functions. The function below - * describes the conditions to be meet when querying information_schema.tables - * for drupal tables or information associated with drupal tables. Even though - * this is the standard method, not all databases follow standards and so this - * method should be overwritten by a database driver if the database provider - * uses alternate methods. Because information_schema.tables is used in a few - * different functions, a database driver will only need to override this function - * to make all the others work. For example see - * core/includes/databases/mysql/schema.inc. - * - * @param $table_name - * The name of the table in question. - * @param $operator - * The operator to apply on the 'table' part of the condition. - * @param $add_prefix - * Boolean to indicate whether the table name needs to be prefixed. - * - * @return QueryConditionInterface - * A DatabaseCondition object. - */ - protected function buildTableNameCondition($table_name, $operator = '=', $add_prefix = TRUE) { - $info = $this->connection->getConnectionOptions(); - - // Retrive the table name and schema - $table_info = $this->getPrefixInfo($table_name, $add_prefix); - - $condition = new DatabaseCondition('AND'); - $condition->condition('table_catalog', $info['database']); - $condition->condition('table_schema', $table_info['schema']); - $condition->condition('table_name', $table_info['table'], $operator); - return $condition; - } - - /** - * Check if a table exists. - * - * @param $table - * The name of the table in drupal (no prefixing). - * - * @return - * TRUE if the given table exists, otherwise FALSE. - */ - public function tableExists($table) { - $condition = $this->buildTableNameCondition($table); - $condition->compile($this->connection, $this); - // Normally, we would heartily discourage the use of string - // concatenation for conditionals like this however, we - // couldn't use db_select() here because it would prefix - // information_schema.tables and the query would fail. - // Don't use {} around information_schema.tables table. - return (bool) $this->connection->query("SELECT 1 FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchField(); - } - - /** - * Find all tables that are like the specified base table name. - * - * @param $table_expression - * An SQL expression, for example "simpletest%" (without the quotes). - * BEWARE: this is not prefixed, the caller should take care of that. - * - * @return - * Array, both the keys and the values are the matching tables. - */ - public function findTables($table_expression) { - $condition = $this->buildTableNameCondition($table_expression, 'LIKE', FALSE); - - $condition->compile($this->connection, $this); - // Normally, we would heartily discourage the use of string - // concatenation for conditionals like this however, we - // couldn't use db_select() here because it would prefix - // information_schema.tables and the query would fail. - // Don't use {} around information_schema.tables table. - return $this->connection->query("SELECT table_name FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchAllKeyed(0, 0); - } - - /** - * Check if a column exists in the given table. - * - * @param $table - * The name of the table in drupal (no prefixing). - * @param $name - * The name of the column. - * - * @return - * TRUE if the given column exists, otherwise FALSE. - */ - public function fieldExists($table, $column) { - $condition = $this->buildTableNameCondition($table); - $condition->condition('column_name', $column); - $condition->compile($this->connection, $this); - // Normally, we would heartily discourage the use of string - // concatenation for conditionals like this however, we - // couldn't use db_select() here because it would prefix - // information_schema.tables and the query would fail. - // Don't use {} around information_schema.columns table. - return (bool) $this->connection->query("SELECT 1 FROM information_schema.columns WHERE " . (string) $condition, $condition->arguments())->fetchField(); - } - - /** - * Returns a mapping of Drupal schema field names to DB-native field types. - * - * Because different field types do not map 1:1 between databases, Drupal has - * its own normalized field type names. This function returns a driver-specific - * mapping table from Drupal names to the native names for each database. - * - * @return array - * An array of Schema API field types to driver-specific field types. - */ - abstract public function getFieldTypeMap(); - - /** - * Rename a table. - * - * @param $table - * The table to be renamed. - * @param $new_name - * The new name for the table. - * - * @throws DatabaseSchemaObjectDoesNotExistException - * If the specified table doesn't exist. - * @throws DatabaseSchemaObjectExistsException - * If a table with the specified new name already exists. - */ - abstract public function renameTable($table, $new_name); - - /** - * Drop a table. - * - * @param $table - * The table to be dropped. - * - * @return - * TRUE if the table was successfully dropped, FALSE if there was no table - * by that name to begin with. - */ - abstract public function dropTable($table); - - /** - * Add a new field to a table. - * - * @param $table - * Name of the table to be altered. - * @param $field - * Name of the field to be added. - * @param $spec - * The field specification array, as taken from a schema definition. - * The specification may also contain the key 'initial', the newly - * created field will be set to the value of the key in all rows. - * This is most useful for creating NOT NULL columns with no default - * value in existing tables. - * @param $keys_new - * Optional keys and indexes specification to be created on the - * table along with adding the field. The format is the same as a - * table specification but without the 'fields' element. If you are - * adding a type 'serial' field, you MUST specify at least one key - * or index including it in this array. See db_change_field() for more - * explanation why. - * - * @throws DatabaseSchemaObjectDoesNotExistException - * If the specified table doesn't exist. - * @throws DatabaseSchemaObjectExistsException - * If the specified table already has a field by that name. - */ - abstract public function addField($table, $field, $spec, $keys_new = array()); - - /** - * Drop a field. - * - * @param $table - * The table to be altered. - * @param $field - * The field to be dropped. - * - * @return - * TRUE if the field was successfully dropped, FALSE if there was no field - * by that name to begin with. - */ - abstract public function dropField($table, $field); - - /** - * Set the default value for a field. - * - * @param $table - * The table to be altered. - * @param $field - * The field to be altered. - * @param $default - * Default value to be set. NULL for 'default NULL'. - * - * @throws DatabaseSchemaObjectDoesNotExistException - * If the specified table or field doesn't exist. - */ - abstract public function fieldSetDefault($table, $field, $default); - - /** - * Set a field to have no default value. - * - * @param $table - * The table to be altered. - * @param $field - * The field to be altered. - * - * @throws DatabaseSchemaObjectDoesNotExistException - * If the specified table or field doesn't exist. - */ - abstract public function fieldSetNoDefault($table, $field); - - /** - * Checks if an index exists in the given table. - * - * @param $table - * The name of the table in drupal (no prefixing). - * @param $name - * The name of the index in drupal (no prefixing). - * - * @return - * TRUE if the given index exists, otherwise FALSE. - */ - abstract public function indexExists($table, $name); - - /** - * Add a primary key. - * - * @param $table - * The table to be altered. - * @param $fields - * Fields for the primary key. - * - * @throws DatabaseSchemaObjectDoesNotExistException - * If the specified table doesn't exist. - * @throws DatabaseSchemaObjectExistsException - * If the specified table already has a primary key. - */ - abstract public function addPrimaryKey($table, $fields); - - /** - * Drop the primary key. - * - * @param $table - * The table to be altered. - * - * @return - * TRUE if the primary key was successfully dropped, FALSE if there was no - * primary key on this table to begin with. - */ - abstract public function dropPrimaryKey($table); - - /** - * Add a unique key. - * - * @param $table - * The table to be altered. - * @param $name - * The name of the key. - * @param $fields - * An array of field names. - * - * @throws DatabaseSchemaObjectDoesNotExistException - * If the specified table doesn't exist. - * @throws DatabaseSchemaObjectExistsException - * If the specified table already has a key by that name. - */ - abstract public function addUniqueKey($table, $name, $fields); - - /** - * Drop a unique key. - * - * @param $table - * The table to be altered. - * @param $name - * The name of the key. - * - * @return - * TRUE if the key was successfully dropped, FALSE if there was no key by - * that name to begin with. - */ - abstract public function dropUniqueKey($table, $name); - - /** - * Add an index. - * - * @param $table - * The table to be altered. - * @param $name - * The name of the index. - * @param $fields - * An array of field names. - * - * @throws DatabaseSchemaObjectDoesNotExistException - * If the specified table doesn't exist. - * @throws DatabaseSchemaObjectExistsException - * If the specified table already has an index by that name. - */ - abstract public function addIndex($table, $name, $fields); - - /** - * Drop an index. - * - * @param $table - * The table to be altered. - * @param $name - * The name of the index. - * - * @return - * TRUE if the index was successfully dropped, FALSE if there was no index - * by that name to begin with. - */ - abstract public function dropIndex($table, $name); - - /** - * Change a field definition. - * - * IMPORTANT NOTE: To maintain database portability, you have to explicitly - * recreate all indices and primary keys that are using the changed field. - * - * That means that you have to drop all affected keys and indexes with - * db_drop_{primary_key,unique_key,index}() before calling db_change_field(). - * To recreate the keys and indices, pass the key definitions as the - * optional $keys_new argument directly to db_change_field(). - * - * For example, suppose you have: - * @code - * $schema['foo'] = array( - * 'fields' => array( - * 'bar' => array('type' => 'int', 'not null' => TRUE) - * ), - * 'primary key' => array('bar') - * ); - * @endcode - * and you want to change foo.bar to be type serial, leaving it as the - * primary key. The correct sequence is: - * @code - * db_drop_primary_key('foo'); - * db_change_field('foo', 'bar', 'bar', - * array('type' => 'serial', 'not null' => TRUE), - * array('primary key' => array('bar'))); - * @endcode - * - * The reasons for this are due to the different database engines: - * - * On PostgreSQL, changing a field definition involves adding a new field - * and dropping an old one which* causes any indices, primary keys and - * sequences (from serial-type fields) that use the changed field to be dropped. - * - * On MySQL, all type 'serial' fields must be part of at least one key - * or index as soon as they are created. You cannot use - * db_add_{primary_key,unique_key,index}() for this purpose because - * the ALTER TABLE command will fail to add the column without a key - * or index specification. The solution is to use the optional - * $keys_new argument to create the key or index at the same time as - * field. - * - * You could use db_add_{primary_key,unique_key,index}() in all cases - * unless you are converting a field to be type serial. You can use - * the $keys_new argument in all cases. - * - * @param $table - * Name of the table. - * @param $field - * Name of the field to change. - * @param $field_new - * New name for the field (set to the same as $field if you don't want to change the name). - * @param $spec - * The field specification for the new field. - * @param $keys_new - * Optional keys and indexes specification to be created on the - * table along with changing the field. The format is the same as a - * table specification but without the 'fields' element. - * - * @throws DatabaseSchemaObjectDoesNotExistException - * If the specified table or source field doesn't exist. - * @throws DatabaseSchemaObjectExistsException - * If the specified destination field already exists. - */ - abstract public function changeField($table, $field, $field_new, $spec, $keys_new = array()); - - /** - * Create a new table from a Drupal table definition. - * - * @param $name - * The name of the table to create. - * @param $table - * A Schema API table definition array. - * - * @throws DatabaseSchemaObjectExistsException - * If the specified table already exists. - */ - public function createTable($name, $table) { - if ($this->tableExists($name)) { - throw new DatabaseSchemaObjectExistsException(t('Table %name already exists.', array('%name' => $name))); - } - $statements = $this->createTableSql($name, $table); - foreach ($statements as $statement) { - $this->connection->query($statement); - } - } - - /** - * Return an array of field names from an array of key/index column specifiers. - * - * This is usually an identity function but if a key/index uses a column prefix - * specification, this function extracts just the name. - * - * @param $fields - * An array of key/index column specifiers. - * - * @return - * An array of field names. - */ - public function fieldNames($fields) { - $return = array(); - foreach ($fields as $field) { - if (is_array($field)) { - $return[] = $field[0]; - } - else { - $return[] = $field; - } - } - return $return; - } - - /** - * Prepare a table or column comment for database query. - * - * @param $comment - * The comment string to prepare. - * @param $length - * Optional upper limit on the returned string length. - * - * @return - * The prepared comment. - */ - public function prepareComment($comment, $length = NULL) { - return $this->connection->quote($comment); - } -} - -/** - * Exception thrown if an object being created already exists. - * - * For example, this exception should be thrown whenever there is an attempt to - * create a new database table, field, or index that already exists in the - * database schema. - */ -class DatabaseSchemaObjectExistsException extends Exception {} - -/** - * Exception thrown if an object being modified doesn't exist yet. - * - * For example, this exception should be thrown whenever there is an attempt to - * modify a database table, field, or index that does not currently exist in - * the database schema. - */ -class DatabaseSchemaObjectDoesNotExistException extends Exception {} - -/** - * @} End of "defgroup schemaapi". - */ - diff --git a/core/includes/database/select.inc b/core/includes/database/select.inc deleted file mode 100644 index 75047785493..00000000000 --- a/core/includes/database/select.inc +++ /dev/null @@ -1,1630 +0,0 @@ -<?php - -/** - * @ingroup database - * @{ - */ - -require_once __DIR__ . '/query.inc'; - -/** - * Interface for extendable query objects. - * - * "Extenders" follow the "Decorator" OOP design pattern. That is, they wrap - * and "decorate" another object. In our case, they implement the same interface - * as select queries and wrap a select query, to which they delegate almost all - * operations. Subclasses of this class may implement additional methods or - * override existing methods as appropriate. Extenders may also wrap other - * extender objects, allowing for arbitrarily complex "enhanced" queries. - */ -interface QueryExtendableInterface { - - /** - * Enhance this object by wrapping it in an extender object. - * - * @param $extender_name - * The base name of the extending class. The base name will be checked - * against the current database connection to allow driver-specific subclasses - * as well, using the same logic as the query objects themselves. For example, - * PagerDefault_mysql is the MySQL-specific override for PagerDefault. - * @return QueryExtendableInterface - * The extender object, which now contains a reference to this object. - */ - public function extend($extender_name); -} - -/** - * Interface definition for a Select Query object. - */ -interface SelectQueryInterface extends QueryConditionInterface, QueryAlterableInterface, QueryExtendableInterface, QueryPlaceholderInterface { - - /* Alter accessors to expose the query data to alter hooks. */ - - /** - * Returns a reference to the fields array for this query. - * - * Because this method returns by reference, alter hooks may edit the fields - * array directly to make their changes. If just adding fields, however, the - * use of addField() is preferred. - * - * Note that this method must be called by reference as well: - * - * @code - * $fields =& $query->getFields(); - * @endcode - * - * @return - * A reference to the fields array structure. - */ - public function &getFields(); - - /** - * Returns a reference to the expressions array for this query. - * - * Because this method returns by reference, alter hooks may edit the expressions - * array directly to make their changes. If just adding expressions, however, the - * use of addExpression() is preferred. - * - * Note that this method must be called by reference as well: - * - * @code - * $fields =& $query->getExpressions(); - * @endcode - * - * @return - * A reference to the expression array structure. - */ - public function &getExpressions(); - - /** - * Returns a reference to the order by array for this query. - * - * Because this method returns by reference, alter hooks may edit the order-by - * array directly to make their changes. If just adding additional ordering - * fields, however, the use of orderBy() is preferred. - * - * Note that this method must be called by reference as well: - * - * @code - * $fields =& $query->getOrderBy(); - * @endcode - * - * @return - * A reference to the expression array structure. - */ - public function &getOrderBy(); - - /** - * Returns a reference to the group-by array for this query. - * - * Because this method returns by reference, alter hooks may edit the group-by - * array directly to make their changes. If just adding additional grouping - * fields, however, the use of groupBy() is preferred. - * - * Note that this method must be called by reference as well: - * - * @code - * $fields =& $query->getGroupBy(); - * @endcode - * - * @return - * A reference to the group-by array structure. - */ - public function &getGroupBy(); - - /** - * Returns a reference to the tables array for this query. - * - * Because this method returns by reference, alter hooks may edit the tables - * array directly to make their changes. If just adding tables, however, the - * use of the join() methods is preferred. - * - * Note that this method must be called by reference as well: - * - * @code - * $fields =& $query->getTables(); - * @endcode - * - * @return - * A reference to the tables array structure. - */ - public function &getTables(); - - /** - * Returns a reference to the union queries for this query. This include - * queries for UNION, UNION ALL, and UNION DISTINCT. - * - * Because this method returns by reference, alter hooks may edit the tables - * array directly to make their changes. If just adding union queries, - * however, the use of the union() method is preferred. - * - * Note that this method must be called by reference as well: - * - * @code - * $fields =& $query->getUnion(); - * @endcode - * - * @return - * A reference to the union query array structure. - */ - public function &getUnion(); - - /** - * Compiles and returns an associative array of the arguments for this prepared statement. - * - * @param $queryPlaceholder - * When collecting the arguments of a subquery, the main placeholder - * object should be passed as this parameter. - * - * @return - * An associative array of all placeholder arguments for this query. - */ - public function getArguments(QueryPlaceholderInterface $queryPlaceholder = NULL); - - /* Query building operations */ - - /** - * Sets this query to be DISTINCT. - * - * @param $distinct - * TRUE to flag this query DISTINCT, FALSE to disable it. - * @return SelectQueryInterface - * The called object. - */ - public function distinct($distinct = TRUE); - - /** - * Adds a field to the list to be SELECTed. - * - * @param $table_alias - * The name of the table from which the field comes, as an alias. Generally - * you will want to use the return value of join() here to ensure that it is - * valid. - * @param $field - * The name of the field. - * @param $alias - * The alias for this field. If not specified, one will be generated - * automatically based on the $table_alias and $field. The alias will be - * checked for uniqueness, so the requested alias may not be the alias - * that is assigned in all cases. - * @return - * The unique alias that was assigned for this field. - */ - public function addField($table_alias, $field, $alias = NULL); - - /** - * Add multiple fields from the same table to be SELECTed. - * - * This method does not return the aliases set for the passed fields. In the - * majority of cases that is not a problem, as the alias will be the field - * name. However, if you do need to know the alias you can call getFields() - * and examine the result to determine what alias was created. Alternatively, - * simply use addField() for the few fields you care about and this method for - * the rest. - * - * @param $table_alias - * The name of the table from which the field comes, as an alias. Generally - * you will want to use the return value of join() here to ensure that it is - * valid. - * @param $fields - * An indexed array of fields present in the specified table that should be - * included in this query. If not specified, $table_alias.* will be generated - * without any aliases. - * @return SelectQueryInterface - * The called object. - */ - public function fields($table_alias, array $fields = array()); - - /** - * Adds an expression to the list of "fields" to be SELECTed. - * - * An expression can be any arbitrary string that is valid SQL. That includes - * various functions, which may in some cases be database-dependent. This - * method makes no effort to correct for database-specific functions. - * - * @param $expression - * The expression string. May contain placeholders. - * @param $alias - * The alias for this expression. If not specified, one will be generated - * automatically in the form "expression_#". The alias will be checked for - * uniqueness, so the requested alias may not be the alias that is assigned - * in all cases. - * @param $arguments - * Any placeholder arguments needed for this expression. - * @return - * The unique alias that was assigned for this expression. - */ - public function addExpression($expression, $alias = NULL, $arguments = array()); - - /** - * Default Join against another table in the database. - * - * This method is a convenience method for innerJoin(). - * - * @param $table - * The table against which to join. - * @param $alias - * The alias for the table. In most cases this should be the first letter - * of the table, or the first letter of each "word" in the table. - * @param $condition - * The condition on which to join this table. If the join requires values, - * this clause should use a named placeholder and the value or values to - * insert should be passed in the 4th parameter. For the first table joined - * on a query, this value is ignored as the first table is taken as the base - * table. The token %alias can be used in this string to be replaced with - * the actual alias. This is useful when $alias is modified by the database - * system, for example, when joining the same table more than once. - * @param $arguments - * An array of arguments to replace into the $condition of this join. - * @return - * The unique alias that was assigned for this table. - */ - public function join($table, $alias = NULL, $condition = NULL, $arguments = array()); - - /** - * Inner Join against another table in the database. - * - * @param $table - * The table against which to join. - * @param $alias - * The alias for the table. In most cases this should be the first letter - * of the table, or the first letter of each "word" in the table. - * @param $condition - * The condition on which to join this table. If the join requires values, - * this clause should use a named placeholder and the value or values to - * insert should be passed in the 4th parameter. For the first table joined - * on a query, this value is ignored as the first table is taken as the base - * table. The token %alias can be used in this string to be replaced with - * the actual alias. This is useful when $alias is modified by the database - * system, for example, when joining the same table more than once. - * @param $arguments - * An array of arguments to replace into the $condition of this join. - * @return - * The unique alias that was assigned for this table. - */ - public function innerJoin($table, $alias = NULL, $condition = NULL, $arguments = array()); - - /** - * Left Outer Join against another table in the database. - * - * @param $table - * The table against which to join. - * @param $alias - * The alias for the table. In most cases this should be the first letter - * of the table, or the first letter of each "word" in the table. - * @param $condition - * The condition on which to join this table. If the join requires values, - * this clause should use a named placeholder and the value or values to - * insert should be passed in the 4th parameter. For the first table joined - * on a query, this value is ignored as the first table is taken as the base - * table. The token %alias can be used in this string to be replaced with - * the actual alias. This is useful when $alias is modified by the database - * system, for example, when joining the same table more than once. - * @param $arguments - * An array of arguments to replace into the $condition of this join. - * @return - * The unique alias that was assigned for this table. - */ - public function leftJoin($table, $alias = NULL, $condition = NULL, $arguments = array()); - - /** - * Right Outer Join against another table in the database. - * - * @param $table - * The table against which to join. - * @param $alias - * The alias for the table. In most cases this should be the first letter - * of the table, or the first letter of each "word" in the table. - * @param $condition - * The condition on which to join this table. If the join requires values, - * this clause should use a named placeholder and the value or values to - * insert should be passed in the 4th parameter. For the first table joined - * on a query, this value is ignored as the first table is taken as the base - * table. The token %alias can be used in this string to be replaced with - * the actual alias. This is useful when $alias is modified by the database - * system, for example, when joining the same table more than once. - * @param $arguments - * An array of arguments to replace into the $condition of this join. - * @return - * The unique alias that was assigned for this table. - */ - public function rightJoin($table, $alias = NULL, $condition = NULL, $arguments = array()); - - /** - * Join against another table in the database. - * - * This method does the "hard" work of queuing up a table to be joined against. - * In some cases, that may include dipping into the Schema API to find the necessary - * fields on which to join. - * - * @param $type - * The type of join. Typically one one of INNER, LEFT OUTER, and RIGHT OUTER. - * @param $table - * The table against which to join. May be a string or another SelectQuery - * object. If a query object is passed, it will be used as a subselect. - * @param $alias - * The alias for the table. In most cases this should be the first letter - * of the table, or the first letter of each "word" in the table. If omitted, - * one will be dynamically generated. - * @param $condition - * The condition on which to join this table. If the join requires values, - * this clause should use a named placeholder and the value or values to - * insert should be passed in the 4th parameter. For the first table joined - * on a query, this value is ignored as the first table is taken as the base - * table. The token %alias can be used in this string to be replaced with - * the actual alias. This is useful when $alias is modified by the database - * system, for example, when joining the same table more than once. - * @param $arguments - * An array of arguments to replace into the $condition of this join. - * @return - * The unique alias that was assigned for this table. - */ - public function addJoin($type, $table, $alias = NULL, $condition = NULL, $arguments = array()); - - /** - * Orders the result set by a given field. - * - * If called multiple times, the query will order by each specified field in the - * order this method is called. - * - * If the query uses DISTINCT or GROUP BY conditions, fields or expressions - * that are used for the order must be selected to be compatible with some - * databases like PostgreSQL. The PostgreSQL driver can handle simple cases - * automatically but it is suggested to explicitly specify them. Additionally, - * when ordering on an alias, the alias must be added before orderBy() is - * called. - * - * @param $field - * The field on which to order. - * @param $direction - * The direction to sort. Legal values are "ASC" and "DESC". - * @return SelectQueryInterface - * The called object. - */ - public function orderBy($field, $direction = 'ASC'); - - /** - * Orders the result set by a random value. - * - * This may be stacked with other orderBy() calls. If so, the query will order - * by each specified field, including this one, in the order called. Although - * this method may be called multiple times on the same query, doing so - * is not particularly useful. - * - * Note: The method used by most drivers may not scale to very large result - * sets. If you need to work with extremely large data sets, you may create - * your own database driver by subclassing off of an existing driver and - * implementing your own randomization mechanism. See - * - * http://jan.kneschke.de/projects/mysql/order-by-rand/ - * - * for an example of such an alternate sorting mechanism. - * - * @return SelectQueryInterface - * The called object - */ - public function orderRandom(); - - /** - * Restricts a query to a given range in the result set. - * - * If this method is called with no parameters, will remove any range - * directives that have been set. - * - * @param $start - * The first record from the result set to return. If NULL, removes any - * range directives that are set. - * @param $length - * The number of records to return from the result set. - * @return SelectQueryInterface - * The called object. - */ - public function range($start = NULL, $length = NULL); - - /** - * Add another Select query to UNION to this one. - * - * Union queries consist of two or more queries whose - * results are effectively concatenated together. Queries - * will be UNIONed in the order they are specified, with - * this object's query coming first. Duplicate columns will - * be discarded. All forms of UNION are supported, using - * the second '$type' argument. - * - * Note: All queries UNIONed together must have the same - * field structure, in the same order. It is up to the - * caller to ensure that they match properly. If they do - * not, an SQL syntax error will result. - * - * @param $query - * The query to UNION to this query. - * @param $type - * The type of UNION to add to the query. Defaults to plain - * UNION. - * @return SelectQueryInterface - * The called object. - */ - public function union(SelectQueryInterface $query, $type = ''); - - /** - * Groups the result set by the specified field. - * - * @param $field - * The field on which to group. This should be the field as aliased. - * @return SelectQueryInterface - * The called object. - */ - public function groupBy($field); - - /** - * Get the equivalent COUNT query of this query as a new query object. - * - * @return SelectQueryInterface - * A new SelectQuery object with no fields or expressions besides COUNT(*). - */ - public function countQuery(); - - /** - * Indicates if preExecute() has already been called on that object. - * - * @return - * TRUE is this query has already been prepared, FALSE otherwise. - */ - public function isPrepared(); - - /** - * Generic preparation and validation for a SELECT query. - * - * @return - * TRUE if the validation was successful, FALSE if not. - */ - public function preExecute(SelectQueryInterface $query = NULL); - - /** - * Helper function to build most common HAVING conditional clauses. - * - * This method can take a variable number of parameters. If called with two - * parameters, they are taken as $field and $value with $operator having a value - * of IN if $value is an array and = otherwise. - * - * @param $field - * The name of the field to check. If you would like to add a more complex - * condition involving operators or functions, use having(). - * @param $value - * The value to test the field against. In most cases, this is a scalar. For more - * complex options, it is an array. The meaning of each element in the array is - * dependent on the $operator. - * @param $operator - * The comparison operator, such as =, <, or >=. It also accepts more complex - * options such as IN, LIKE, or BETWEEN. Defaults to IN if $value is an array - * = otherwise. - * @return QueryConditionInterface - * The called object. - */ - public function havingCondition($field, $value = NULL, $operator = NULL); - - /** - * Clone magic method. - * - * Select queries have dependent objects that must be deep-cloned. The - * connection object itself, however, should not be cloned as that would - * duplicate the connection itself. - */ - public function __clone(); - - /** - * Add FOR UPDATE to the query. - * - * FOR UPDATE prevents the rows retrieved by the SELECT statement from being - * modified or deleted by other transactions until the current transaction - * ends. Other transactions that attempt UPDATE, DELETE, or SELECT FOR UPDATE - * of these rows will be blocked until the current transaction ends. - * - * @param $set - * IF TRUE, FOR UPDATE will be added to the query, if FALSE then it won't. - * - * @return QueryConditionInterface - * The called object. - */ - public function forUpdate($set = TRUE); -} - -/** - * The base extender class for Select queries. - */ -class SelectQueryExtender implements SelectQueryInterface { - - /** - * The SelectQuery object we are extending/decorating. - * - * @var SelectQueryInterface - */ - protected $query; - - /** - * The connection object on which to run this query. - * - * @var DatabaseConnection - */ - protected $connection; - - /** - * A unique identifier for this query object. - */ - protected $uniqueIdentifier; - - /** - * The placeholder counter. - */ - protected $placeholder = 0; - - public function __construct(SelectQueryInterface $query, DatabaseConnection $connection) { - $this->uniqueIdentifier = uniqid('', TRUE); - $this->query = $query; - $this->connection = $connection; - } - - /** - * Implements QueryPlaceholderInterface::uniqueIdentifier(). - */ - public function uniqueIdentifier() { - return $this->uniqueIdentifier; - } - - /** - * Implements QueryPlaceholderInterface::nextPlaceholder(). - */ - public function nextPlaceholder() { - return $this->placeholder++; - } - - /* Implementations of QueryAlterableInterface. */ - - public function addTag($tag) { - $this->query->addTag($tag); - return $this; - } - - public function hasTag($tag) { - return $this->query->hasTag($tag); - } - - public function hasAllTags() { - return call_user_func_array(array($this->query, 'hasAllTags'), func_get_args()); - } - - public function hasAnyTag() { - return call_user_func_array(array($this->query, 'hasAnyTags'), func_get_args()); - } - - public function addMetaData($key, $object) { - $this->query->addMetaData($key, $object); - return $this; - } - - public function getMetaData($key) { - return $this->query->getMetaData($key); - } - - /* Implementations of QueryConditionInterface for the WHERE clause. */ - - public function condition($field, $value = NULL, $operator = NULL) { - $this->query->condition($field, $value, $operator); - return $this; - } - - public function &conditions() { - return $this->query->conditions(); - } - - public function arguments() { - return $this->query->arguments(); - } - - public function where($snippet, $args = array()) { - $this->query->where($snippet, $args); - return $this; - } - - public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder) { - return $this->query->compile($connection, $queryPlaceholder); - } - - public function compiled() { - return $this->query->compiled(); - } - - /* Implementations of QueryConditionInterface for the HAVING clause. */ - - public function havingCondition($field, $value = NULL, $operator = '=') { - $this->query->condition($field, $value, $operator, $num_args); - return $this; - } - - public function &havingConditions() { - return $this->having->conditions(); - } - - public function havingArguments() { - return $this->having->arguments(); - } - - public function having($snippet, $args = array()) { - $this->query->having($snippet, $args); - return $this; - } - - public function havingCompile(DatabaseConnection $connection) { - return $this->query->havingCompile($connection); - } - - /* Implementations of QueryExtendableInterface. */ - - public function extend($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); - } - - /* Alter accessors to expose the query data to alter hooks. */ - - public function &getFields() { - return $this->query->getFields(); - } - - public function &getExpressions() { - return $this->query->getExpressions(); - } - - public function &getOrderBy() { - return $this->query->getOrderBy(); - } - - public function &getGroupBy() { - return $this->query->getGroupBy(); - } - - public function &getTables() { - return $this->query->getTables(); - } - - public function &getUnion() { - return $this->query->getUnion(); - } - - public function getArguments(QueryPlaceholderInterface $queryPlaceholder = NULL) { - return $this->query->getArguments($queryPlaceholder); - } - - public function isPrepared() { - return $this->query->isPrepared(); - } - - public function preExecute(SelectQueryInterface $query = NULL) { - // If no query object is passed in, use $this. - if (!isset($query)) { - $query = $this; - } - - return $this->query->preExecute($query); - } - - public function execute() { - // By calling preExecute() here, we force it to preprocess the extender - // object rather than just the base query object. That means - // hook_query_alter() gets access to the extended object. - if (!$this->preExecute($this)) { - return NULL; - } - - return $this->query->execute(); - } - - public function distinct($distinct = TRUE) { - $this->query->distinct($distinct); - return $this; - } - - public function addField($table_alias, $field, $alias = NULL) { - return $this->query->addField($table_alias, $field, $alias); - } - - public function fields($table_alias, array $fields = array()) { - $this->query->fields($table_alias, $fields); - return $this; - } - - public function addExpression($expression, $alias = NULL, $arguments = array()) { - return $this->query->addExpression($expression, $alias, $arguments); - } - - public function join($table, $alias = NULL, $condition = NULL, $arguments = array()) { - return $this->query->join($table, $alias, $condition, $arguments); - } - - public function innerJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) { - return $this->query->innerJoin($table, $alias, $condition, $arguments); - } - - public function leftJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) { - return $this->query->leftJoin($table, $alias, $condition, $arguments); - } - - public function rightJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) { - return $this->query->rightJoin($table, $alias, $condition, $arguments); - } - - public function addJoin($type, $table, $alias = NULL, $condition = NULL, $arguments = array()) { - return $this->query->addJoin($type, $table, $alias, $condition, $arguments); - } - - public function orderBy($field, $direction = 'ASC') { - $this->query->orderBy($field, $direction); - return $this; - } - - public function orderRandom() { - $this->query->orderRandom(); - return $this; - } - - public function range($start = NULL, $length = NULL) { - $this->query->range($start, $length); - return $this; - } - - public function union(SelectQueryInterface $query, $type = '') { - $this->query->union($query, $type); - return $this; - } - - public function groupBy($field) { - $this->query->groupBy($field); - return $this; - } - - public function forUpdate($set = TRUE) { - $this->query->forUpdate($set); - return $this; - } - - public function countQuery() { - // Create our new query object that we will mutate into a count query. - $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) { - $this->query->isNull($field); - return $this; - } - - function isNotNull($field) { - $this->query->isNotNull($field); - 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() { - return (string) $this->query; - } - - public function __clone() { - $this->uniqueIdentifier = uniqid('', TRUE); - - // We need to deep-clone the query we're wrapping, which in turn may - // deep-clone other objects. Exciting! - $this->query = clone($this->query); - } - - /** - * Magic override for undefined methods. - * - * If one extender extends another extender, then methods in the inner extender - * will not be exposed on the outer extender. That's because we cannot know - * in advance what those methods will be, so we cannot provide wrapping - * implementations as we do above. Instead, we use this slower catch-all method - * to handle any additional methods. - */ - public function __call($method, $args) { - $return = call_user_func_array(array($this->query, $method), $args); - - // Some methods will return the called object as part of a fluent interface. - // Others will return some useful value. If it's a value, then the caller - // probably wants that value. If it's the called object, then we instead - // return this object. That way we don't "lose" an extender layer when - // chaining methods together. - if ($return instanceof SelectQueryInterface) { - return $this; - } - else { - return $return; - } - } -} - -/** - * Query builder for SELECT statements. - */ -class SelectQuery extends Query implements SelectQueryInterface { - - /** - * The fields to SELECT. - * - * @var array - */ - protected $fields = array(); - - /** - * The expressions to SELECT as virtual fields. - * - * @var array - */ - protected $expressions = array(); - - /** - * The tables against which to JOIN. - * - * This property is a nested array. Each entry is an array representing - * a single table against which to join. The structure of each entry is: - * - * array( - * 'type' => $join_type (one of INNER, LEFT OUTER, RIGHT OUTER), - * 'table' => $table, - * 'alias' => $alias_of_the_table, - * 'condition' => $condition_clause_on_which_to_join, - * 'arguments' => $array_of_arguments_for_placeholders_in_the condition. - * 'all_fields' => TRUE to SELECT $alias.*, FALSE or NULL otherwise. - * ) - * - * If $table is a string, it is taken as the name of a table. If it is - * a SelectQuery object, it is taken as a subquery. - * - * @var array - */ - protected $tables = array(); - - /** - * The fields by which to order this query. - * - * This is an associative array. The keys are the fields to order, and the value - * is the direction to order, either ASC or DESC. - * - * @var array - */ - protected $order = array(); - - /** - * The fields by which to group. - * - * @var array - */ - protected $group = array(); - - /** - * The conditional object for the WHERE clause. - * - * @var DatabaseCondition - */ - protected $where; - - /** - * The conditional object for the HAVING clause. - * - * @var DatabaseCondition - */ - protected $having; - - /** - * Whether or not this query should be DISTINCT - * - * @var boolean - */ - protected $distinct = FALSE; - - /** - * The range limiters for this query. - * - * @var array - */ - protected $range; - - /** - * An array whose elements specify a query to UNION, and the UNION type. The - * 'type' key may be '', 'ALL', or 'DISTINCT' to represent a 'UNION', - * 'UNION ALL', or 'UNION DISTINCT' statement, respectively. - * - * All entries in this array will be applied from front to back, with the - * first query to union on the right of the original query, the second union - * to the right of the first, etc. - * - * @var array - */ - protected $union = array(); - - /** - * Indicates if preExecute() has already been called. - * @var boolean - */ - protected $prepared = FALSE; - - /** - * The FOR UPDATE status - */ - protected $forUpdate = FALSE; - - public function __construct($table, $alias = NULL, DatabaseConnection $connection, $options = array()) { - $options['return'] = Database::RETURN_STATEMENT; - parent::__construct($connection, $options); - $this->where = new DatabaseCondition('AND'); - $this->having = new DatabaseCondition('AND'); - $this->addJoin(NULL, $table, $alias); - } - - /* Implementations of QueryAlterableInterface. */ - - public function addTag($tag) { - $this->alterTags[$tag] = 1; - return $this; - } - - public function hasTag($tag) { - return isset($this->alterTags[$tag]); - } - - public function hasAllTags() { - return !(boolean)array_diff(func_get_args(), array_keys($this->alterTags)); - } - - public function hasAnyTag() { - return (boolean)array_intersect(func_get_args(), array_keys($this->alterTags)); - } - - public function addMetaData($key, $object) { - $this->alterMetaData[$key] = $object; - return $this; - } - - public function getMetaData($key) { - return isset($this->alterMetaData[$key]) ? $this->alterMetaData[$key] : NULL; - } - - /* Implementations of QueryConditionInterface for the WHERE clause. */ - - public function condition($field, $value = NULL, $operator = NULL) { - $this->where->condition($field, $value, $operator); - return $this; - } - - public function &conditions() { - return $this->where->conditions(); - } - - public function 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()) { - $this->where->where($snippet, $args); - return $this; - } - - public function isNull($field) { - $this->where->isNull($field); - return $this; - } - - public function isNotNull($field) { - $this->where->isNotNull($field); - return $this; - } - - public function exists(SelectQueryInterface $select) { - $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. */ - - public function havingCondition($field, $value = NULL, $operator = NULL) { - $this->having->condition($field, $value, $operator); - return $this; - } - - public function &havingConditions() { - return $this->having->conditions(); - } - - public function havingArguments() { - return $this->having->arguments(); - } - - public function having($snippet, $args = array()) { - $this->having->where($snippet, $args); - return $this; - } - - public function havingCompile(DatabaseConnection $connection) { - return $this->having->compile($connection, $this); - } - - /* Implementations of QueryExtendableInterface. */ - - public function extend($extender_name) { - $override_class = $extender_name . '_' . $this->connection->driver(); - if (class_exists($override_class)) { - $extender_name = $override_class; - } - return new $extender_name($this, $this->connection); - } - - public function havingIsNull($field) { - $this->having->isNull($field); - return $this; - } - - public function havingIsNotNull($field) { - $this->having->isNotNull($field); - 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) { - if (isset($set)) { - $this->forUpdate = $set; - } - return $this; - } - - /* Alter accessors to expose the query data to alter hooks. */ - - public function &getFields() { - return $this->fields; - } - - public function &getExpressions() { - return $this->expressions; - } - - public function &getOrderBy() { - return $this->order; - } - - public function &getGroupBy() { - return $this->group; - } - - public function &getTables() { - return $this->tables; - } - - public function &getUnion() { - return $this->union; - } - - public function getArguments(QueryPlaceholderInterface $queryPlaceholder = NULL) { - if (!isset($queryPlaceholder)) { - $queryPlaceholder = $this; - } - $this->compile($this->connection, $queryPlaceholder); - return $this->arguments(); - } - - /** - * Indicates if preExecute() has already been called on that object. - */ - public function isPrepared() { - return $this->prepared; - } - - /** - * Generic preparation and validation for a SELECT query. - * - * @return - * TRUE if the validation was successful, FALSE if not. - */ - public function preExecute(SelectQueryInterface $query = NULL) { - // If no query object is passed in, use $this. - if (!isset($query)) { - $query = $this; - } - - // Only execute this once. - if ($query->isPrepared()) { - return TRUE; - } - - // Modules may alter all queries or only those having a particular tag. - if (isset($this->alterTags)) { - $hooks = array('query'); - foreach ($this->alterTags as $tag => $value) { - $hooks[] = 'query_' . $tag; - } - drupal_alter($hooks, $query); - } - - $this->prepared = TRUE; - - // Now also prepare any sub-queries. - foreach ($this->tables as $table) { - if ($table['table'] instanceof SelectQueryInterface) { - $table['table']->preExecute(); - } - } - - foreach ($this->union as $union) { - $union['query']->preExecute(); - } - - return $this->prepared; - } - - public function execute() { - // If validation fails, simply return NULL. - // Note that validation routines in preExecute() may throw exceptions instead. - if (!$this->preExecute()) { - return NULL; - } - - $args = $this->getArguments(); - return $this->connection->query((string) $this, $args, $this->queryOptions); - } - - public function distinct($distinct = TRUE) { - $this->distinct = $distinct; - return $this; - } - - public function addField($table_alias, $field, $alias = NULL) { - // If no alias is specified, first try the field name itself. - if (empty($alias)) { - $alias = $field; - } - - // If that's already in use, try the table name and field name. - if (!empty($this->fields[$alias])) { - $alias = $table_alias . '_' . $field; - } - - // If that is already used, just add a counter until we find an unused alias. - $alias_candidate = $alias; - $count = 2; - while (!empty($this->fields[$alias_candidate])) { - $alias_candidate = $alias . '_' . $count++; - } - $alias = $alias_candidate; - - $this->fields[$alias] = array( - 'field' => $field, - 'table' => $table_alias, - 'alias' => $alias, - ); - - return $alias; - } - - public function fields($table_alias, array $fields = array()) { - - if ($fields) { - foreach ($fields as $field) { - // We don't care what alias was assigned. - $this->addField($table_alias, $field); - } - } - else { - // We want all fields from this table. - $this->tables[$table_alias]['all_fields'] = TRUE; - } - - return $this; - } - - public function addExpression($expression, $alias = NULL, $arguments = array()) { - if (empty($alias)) { - $alias = 'expression'; - } - - $alias_candidate = $alias; - $count = 2; - while (!empty($this->expressions[$alias_candidate])) { - $alias_candidate = $alias . '_' . $count++; - } - $alias = $alias_candidate; - - $this->expressions[$alias] = array( - 'expression' => $expression, - 'alias' => $alias, - 'arguments' => $arguments, - ); - - return $alias; - } - - public function join($table, $alias = NULL, $condition = NULL, $arguments = array()) { - return $this->addJoin('INNER', $table, $alias, $condition, $arguments); - } - - public function innerJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) { - return $this->addJoin('INNER', $table, $alias, $condition, $arguments); - } - - public function leftJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) { - return $this->addJoin('LEFT OUTER', $table, $alias, $condition, $arguments); - } - - public function rightJoin($table, $alias = NULL, $condition = NULL, $arguments = array()) { - return $this->addJoin('RIGHT OUTER', $table, $alias, $condition, $arguments); - } - - public function addJoin($type, $table, $alias = NULL, $condition = NULL, $arguments = array()) { - - if (empty($alias)) { - if ($table instanceof SelectQueryInterface) { - $alias = 'subquery'; - } - else { - $alias = $table; - } - } - - $alias_candidate = $alias; - $count = 2; - while (!empty($this->tables[$alias_candidate])) { - $alias_candidate = $alias . '_' . $count++; - } - $alias = $alias_candidate; - - if (is_string($condition)) { - $condition = str_replace('%alias', $alias, $condition); - } - - $this->tables[$alias] = array( - 'join type' => $type, - 'table' => $table, - 'alias' => $alias, - 'condition' => $condition, - 'arguments' => $arguments, - ); - - return $alias; - } - - public function orderBy($field, $direction = 'ASC') { - $this->order[$field] = $direction; - return $this; - } - - public function orderRandom() { - $alias = $this->addExpression('RAND()', 'random_field'); - $this->orderBy($alias); - return $this; - } - - public function range($start = NULL, $length = NULL) { - $this->range = func_num_args() ? array('start' => $start, 'length' => $length) : array(); - return $this; - } - - public function union(SelectQueryInterface $query, $type = '') { - // Handle UNION aliasing. - switch ($type) { - // Fold UNION DISTINCT to UNION for better cross database support. - case 'DISTINCT': - case '': - $type = 'UNION'; - break; - - case 'ALL': - $type = 'UNION ALL'; - default: - } - - $this->union[] = array( - 'type' => $type, - 'query' => $query, - ); - - return $this; - } - - public function groupBy($field) { - $this->group[$field] = $field; - return $this; - } - - public function countQuery() { - // Create our new query object that we will mutate into a count query. - $count = clone($this); - - $group_by = $count->getGroupBy(); - - if (!$count->distinct) { - // 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 - // the GROUP BY clause need to be present in the query. - $fields =& $count->getFields(); - foreach (array_keys($fields) as $field) { - if (empty($group_by[$field])) { - unset($fields[$field]); - } - } - $expressions =& $count->getExpressions(); - foreach (array_keys($expressions) as $field) { - if (empty($group_by[$field])) { - unset($expressions[$field]); - } - } - - // Also remove 'all_fields' statements, which are expanded into tablename.* - // when the query is executed. - foreach ($count->tables as $alias => &$table) { - unset($table['all_fields']); - } - } - - // If we've just removed all fields from the query, make sure there is at - // least one so that the query still runs. - $count->addExpression('1'); - - // Ordering a count query is a waste of cycles, and breaks on some - // databases anyway. - $orders = &$count->getOrderBy(); - $orders = array(); - - if ($count->distinct && !empty($group_by)) { - // If the query is distinct and contains a GROUP BY, we need to remove the - // distinct because SQL99 does not support counting on distinct multiple fields. - $count->distinct = FALSE; - } - - $query = $this->connection->select($count); - $query->addExpression('COUNT(*)'); - - return $query; - } - - 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 sanitized comment string to prepend to the query. - $comments = $this->connection->makeComment($this->comments); - - // SELECT - $query = $comments . 'SELECT '; - if ($this->distinct) { - $query .= 'DISTINCT '; - } - - // FIELDS and EXPRESSIONS - $fields = array(); - foreach ($this->tables as $alias => $table) { - if (!empty($table['all_fields'])) { - $fields[] = $this->connection->escapeTable($alias) . '.*'; - } - } - foreach ($this->fields as $alias => $field) { - // Always use the AS keyword for field aliases, as some - // databases require it (e.g., PostgreSQL). - $fields[] = (isset($field['table']) ? $this->connection->escapeTable($field['table']) . '.' : '') . $this->connection->escapeField($field['field']) . ' AS ' . $this->connection->escapeAlias($field['alias']); - } - foreach ($this->expressions as $alias => $expression) { - $fields[] = $expression['expression'] . ' AS ' . $this->connection->escapeAlias($expression['alias']); - } - $query .= implode(', ', $fields); - - - // FROM - We presume all queries have a FROM, as any query that doesn't won't need the query builder anyway. - $query .= "\nFROM "; - foreach ($this->tables as $alias => $table) { - $query .= "\n"; - if (isset($table['join type'])) { - $query .= $table['join type'] . ' JOIN '; - } - - // If the table is a subquery, compile it and integrate it into this query. - if ($table['table'] instanceof SelectQueryInterface) { - // Run preparation steps on this sub-query before converting to string. - $subquery = $table['table']; - $subquery->preExecute(); - $table_string = '(' . (string) $subquery . ')'; - } - else { - $table_string = '{' . $this->connection->escapeTable($table['table']) . '}'; - } - - // Don't use the AS keyword for table aliases, as some - // databases don't support it (e.g., Oracle). - $query .= $table_string . ' ' . $this->connection->escapeTable($table['alias']); - - if (!empty($table['condition'])) { - $query .= ' ON ' . $table['condition']; - } - } - - // WHERE - if (count($this->where)) { - // There is an implicit string cast on $this->condition. - $query .= "\nWHERE " . $this->where; - } - - // GROUP BY - if ($this->group) { - $query .= "\nGROUP BY " . implode(', ', $this->group); - } - - // HAVING - if (count($this->having)) { - // There is an implicit string cast on $this->having. - $query .= "\nHAVING " . $this->having; - } - - // ORDER BY - if ($this->order) { - $query .= "\nORDER BY "; - $fields = array(); - foreach ($this->order as $field => $direction) { - $fields[] = $field . ' ' . $direction; - } - $query .= implode(', ', $fields); - } - - // RANGE - // There is no universal SQL standard for handling range or limit clauses. - // Fortunately, all core-supported databases use the same range syntax. - // Databases that need a different syntax can override this method and - // do whatever alternate logic they need to. - if (!empty($this->range)) { - $query .= "\nLIMIT " . (int) $this->range['length'] . " OFFSET " . (int) $this->range['start']; - } - - // UNION is a little odd, as the select queries to combine are passed into - // this query, but syntactically they all end up on the same level. - if ($this->union) { - foreach ($this->union as $union) { - $query .= ' ' . $union['type'] . ' ' . (string) $union['query']; - } - } - - if ($this->forUpdate) { - $query .= ' FOR UPDATE'; - } - - return $query; - } - - public function __clone() { - // On cloning, also clone the dependent objects. However, we do not - // want to clone the database connection object as that would duplicate the - // connection itself. - - $this->where = clone($this->where); - $this->having = clone($this->having); - foreach ($this->union as $key => $aggregate) { - $this->union[$key]['query'] = clone($aggregate['query']); - } - } -} - -/** - * @} End of "ingroup database". - */ diff --git a/core/includes/database/sqlite/database.inc b/core/includes/database/sqlite/database.inc deleted file mode 100644 index 4cef1641673..00000000000 --- a/core/includes/database/sqlite/database.inc +++ /dev/null @@ -1,511 +0,0 @@ -<?php - -/** - * @file - * Database interface code for SQLite embedded database engine. - */ - -/** - * @ingroup database - * @{ - */ - -include_once DRUPAL_ROOT . '/core/includes/database/prefetch.inc'; - -/** - * Specific SQLite implementation of DatabaseConnection. - */ -class DatabaseConnection_sqlite extends DatabaseConnection { - - /** - * Whether this database connection supports savepoints. - * - * Version of sqlite lower then 3.6.8 can't use savepoints. - * See http://www.sqlite.org/releaselog/3_6_8.html - * - * @var boolean - */ - protected $savepointSupport = FALSE; - - /** - * Whether or not the active transaction (if any) will be rolled back. - * - * @var boolean - */ - protected $willRollback; - - /** - * All databases attached to the current database. This is used to allow - * prefixes to be safely handled without locking the table - * - * @var array - */ - protected $attachedDatabases = array(); - - /** - * Whether or not a table has been dropped this request: the destructor will - * only try to get rid of unnecessary databases if there is potential of them - * being empty. - * - * This variable is set to public because DatabaseSchema_sqlite needs to - * access it. However, it should not be manually set. - * - * @var boolean - */ - var $tableDropped = FALSE; - - public function __construct(array $connection_options = array()) { - // We don't need a specific PDOStatement class here, we simulate it below. - $this->statementClass = NULL; - - // This driver defaults to transaction support, except if explicitly passed FALSE. - $this->transactionSupport = !isset($connection_options['transactions']) || $connection_options['transactions'] !== FALSE; - - $this->connectionOptions = $connection_options; - - parent::__construct('sqlite:' . $connection_options['database'], '', '', array( - // Force column names to lower case. - PDO::ATTR_CASE => PDO::CASE_LOWER, - // Convert numeric values to strings when fetching. - PDO::ATTR_STRINGIFY_FETCHES => TRUE, - )); - - // Attach one database for each registered prefix. - $prefixes = $this->prefixes; - foreach ($prefixes as $table => &$prefix) { - // Empty prefix means query the main database -- no need to attach anything. - if (!empty($prefix)) { - // Only attach the database once. - if (!isset($this->attachedDatabases[$prefix])) { - $this->attachedDatabases[$prefix] = $prefix; - $this->query('ATTACH DATABASE :database AS :prefix', array(':database' => $connection_options['database'] . '-' . $prefix, ':prefix' => $prefix)); - } - - // Add a ., so queries become prefix.table, which is proper syntax for - // querying an attached database. - $prefix .= '.'; - } - } - // Regenerate the prefixes replacement table. - $this->setPrefix($prefixes); - - // Detect support for SAVEPOINT. - $version = $this->query('SELECT sqlite_version()')->fetchField(); - $this->savepointSupport = (version_compare($version, '3.6.8') >= 0); - - // Create functions needed by SQLite. - $this->sqliteCreateFunction('if', array($this, 'sqlFunctionIf')); - $this->sqliteCreateFunction('greatest', array($this, 'sqlFunctionGreatest')); - $this->sqliteCreateFunction('pow', 'pow', 2); - $this->sqliteCreateFunction('length', 'strlen', 1); - $this->sqliteCreateFunction('md5', 'md5', 1); - $this->sqliteCreateFunction('concat', array($this, 'sqlFunctionConcat')); - $this->sqliteCreateFunction('substring', array($this, 'sqlFunctionSubstring'), 3); - $this->sqliteCreateFunction('substring_index', array($this, 'sqlFunctionSubstringIndex'), 3); - $this->sqliteCreateFunction('rand', array($this, 'sqlFunctionRand')); - } - - /** - * Destructor for the SQLite connection. - * - * We prune empty databases on destruct, but only if tables have been - * dropped. This is especially needed when running the test suite, which - * creates and destroy databases several times in a row. - */ - public function __destruct() { - if ($this->tableDropped && !empty($this->attachedDatabases)) { - foreach ($this->attachedDatabases as $prefix) { - // Check if the database is now empty, ignore the internal SQLite tables. - try { - $count = $this->query('SELECT COUNT(*) FROM ' . $prefix . '.sqlite_master WHERE type = :type AND name NOT LIKE :pattern', array(':type' => 'table', ':pattern' => 'sqlite_%'))->fetchField(); - - // We can prune the database file if it doesn't have any tables. - if ($count == 0) { - // Detach the database. - $this->query('DETACH DATABASE :schema', array(':schema' => $prefix)); - // Destroy the database file. - unlink($this->connectionOptions['database'] . '-' . $prefix); - } - } - catch (Exception $e) { - // Ignore the exception and continue. There is nothing we can do here - // to report the error or fail safe. - } - } - } - } - - /** - * SQLite compatibility implementation for the IF() SQL function. - */ - public function sqlFunctionIf($condition, $expr1, $expr2 = NULL) { - return $condition ? $expr1 : $expr2; - } - - /** - * SQLite compatibility implementation for the GREATEST() SQL function. - */ - public function sqlFunctionGreatest() { - $args = func_get_args(); - foreach ($args as $k => $v) { - if (!isset($v)) { - unset($args); - } - } - if (count($args)) { - return max($args); - } - else { - return NULL; - } - } - - /** - * SQLite compatibility implementation for the CONCAT() SQL function. - */ - public function sqlFunctionConcat() { - $args = func_get_args(); - return implode('', $args); - } - - /** - * SQLite compatibility implementation for the SUBSTRING() SQL function. - */ - public function sqlFunctionSubstring($string, $from, $length) { - return substr($string, $from - 1, $length); - } - - /** - * SQLite compatibility implementation for the SUBSTRING_INDEX() SQL function. - */ - public function sqlFunctionSubstringIndex($string, $delimiter, $count) { - // If string is empty, simply return an empty string. - if (empty($string)) { - return ''; - } - $end = 0; - for ($i = 0; $i < $count; $i++) { - $end = strpos($string, $delimiter, $end + 1); - if ($end === FALSE) { - $end = strlen($string); - } - } - return substr($string, 0, $end); - } - - /** - * SQLite compatibility implementation for the RAND() SQL function. - */ - public function sqlFunctionRand($seed = NULL) { - if (isset($seed)) { - mt_srand($seed); - } - return mt_rand() / mt_getrandmax(); - } - - /** - * SQLite-specific implementation of DatabaseConnection::prepare(). - * - * We don't use prepared statements at all at this stage. We just create - * a DatabaseStatement_sqlite object, that will create a PDOStatement - * using the semi-private PDOPrepare() method below. - */ - public function prepare($query, $options = array()) { - return new DatabaseStatement_sqlite($this, $query, $options); - } - - /** - * NEVER CALL THIS FUNCTION: YOU MIGHT DEADLOCK YOUR PHP PROCESS. - * - * This is a wrapper around the parent PDO::prepare method. However, as - * the PDO SQLite driver only closes SELECT statements when the PDOStatement - * destructor is called and SQLite does not allow data change (INSERT, - * UPDATE etc) on a table which has open SELECT statements, you should never - * call this function and keep a PDOStatement object alive as that can lead - * to a deadlock. This really, really should be private, but as - * DatabaseStatement_sqlite needs to call it, we have no other choice but to - * expose this function to the world. - */ - public function PDOPrepare($query, array $options = array()) { - return parent::prepare($query, $options); - } - - public function queryRange($query, $from, $count, array $args = array(), array $options = array()) { - return $this->query($query . ' LIMIT ' . (int) $from . ', ' . (int) $count, $args, $options); - } - - public function queryTemporary($query, array $args = array(), array $options = array()) { - // Generate a new temporary table name and protect it from prefixing. - // SQLite requires that temporary tables to be non-qualified. - $tablename = $this->generateTemporaryTableName(); - $prefixes = $this->prefixes; - $prefixes[$tablename] = ''; - $this->setPrefix($prefixes); - - $this->query(preg_replace('/^SELECT/i', 'CREATE TEMPORARY TABLE ' . $tablename . ' AS SELECT', $query), $args, $options); - return $tablename; - } - - public function driver() { - return 'sqlite'; - } - - public function databaseType() { - return 'sqlite'; - } - - public function mapConditionOperator($operator) { - // We don't want to override any of the defaults. - static $specials = array( - 'LIKE' => array('postfix' => " ESCAPE '\\'"), - 'NOT LIKE' => array('postfix' => " ESCAPE '\\'"), - ); - return isset($specials[$operator]) ? $specials[$operator] : NULL; - } - - public function prepareQuery($query) { - return $this->prepare($this->prefixTables($query)); - } - - public function nextId($existing_id = 0) { - $transaction = $this->startTransaction(); - // We can safely use literal queries here instead of the slower query - // builder because if a given database breaks here then it can simply - // override nextId. However, this is unlikely as we deal with short strings - // and integers and no known databases require special handling for those - // simple cases. If another transaction wants to write the same row, it will - // wait until this transaction commits. - $stmt = $this->query('UPDATE {sequences} SET value = GREATEST(value, :existing_id) + 1', array( - ':existing_id' => $existing_id, - )); - if (!$stmt->rowCount()) { - $this->query('INSERT INTO {sequences} (value) VALUES (:existing_id + 1)', array( - ':existing_id' => $existing_id, - )); - } - // The transaction gets committed when the transaction object gets destroyed - // because it gets out of scope. - return $this->query('SELECT value FROM {sequences}')->fetchField(); - } - - public function rollback($savepoint_name = 'drupal_transaction') { - if ($this->savepointSupport) { - return parent::rollBack($savepoint_name); - } - - if (!$this->inTransaction()) { - throw new DatabaseTransactionNoActiveException(); - } - // A previous rollback to an earlier savepoint may mean that the savepoint - // in question has already been rolled back. - if (!in_array($savepoint_name, $this->transactionLayers)) { - return; - } - - // We need to find the point we're rolling back to, all other savepoints - // before are no longer needed. - while ($savepoint = array_pop($this->transactionLayers)) { - if ($savepoint == $savepoint_name) { - // Mark whole stack of transactions as needed roll back. - $this->willRollback = TRUE; - // If it is the last the transaction in the stack, then it is not a - // savepoint, it is the transaction itself so we will need to roll back - // the transaction rather than a savepoint. - if (empty($this->transactionLayers)) { - break; - } - return; - } - } - if ($this->supportsTransactions()) { - PDO::rollBack(); - } - } - - public function pushTransaction($name) { - if ($this->savepointSupport) { - return parent::pushTransaction($name); - } - if (!$this->supportsTransactions()) { - return; - } - if (isset($this->transactionLayers[$name])) { - throw new DatabaseTransactionNameNonUniqueException($name . " is already in use."); - } - if (!$this->inTransaction()) { - PDO::beginTransaction(); - } - $this->transactionLayers[$name] = $name; - } - - public function popTransaction($name) { - if ($this->savepointSupport) { - return parent::popTransaction($name); - } - if (!$this->supportsTransactions()) { - return; - } - if (!$this->inTransaction()) { - throw new DatabaseTransactionNoActiveException(); - } - - // Commit everything since SAVEPOINT $name. - while($savepoint = array_pop($this->transactionLayers)) { - if ($savepoint != $name) continue; - - // If there are no more layers left then we should commit or rollback. - if (empty($this->transactionLayers)) { - // If there was any rollback() we should roll back whole transaction. - if ($this->willRollback) { - $this->willRollback = FALSE; - PDO::rollBack(); - } - elseif (!PDO::commit()) { - throw new DatabaseTransactionCommitFailedException(); - } - } - else { - break; - } - } - } - -} - -/** - * Specific SQLite implementation of DatabaseConnection. - * - * See DatabaseConnection_sqlite::PDOPrepare() for reasons why we must prefetch - * the data instead of using PDOStatement. - * - * @see DatabaseConnection_sqlite::PDOPrepare() - */ -class DatabaseStatement_sqlite extends DatabaseStatementPrefetch implements Iterator, DatabaseStatementInterface { - - /** - * SQLite specific implementation of getStatement(). - * - * The PDO SQLite layer doesn't replace numeric placeholders in queries - * correctly, and this makes numeric expressions (such as COUNT(*) >= :count) - * fail. We replace numeric placeholders in the query ourselves to work - * around this bug. - * - * See http://bugs.php.net/bug.php?id=45259 for more details. - */ - protected function getStatement($query, &$args = array()) { - if (count($args)) { - // Check if $args is a simple numeric array. - if (range(0, count($args) - 1) === array_keys($args)) { - // In that case, we have unnamed placeholders. - $count = 0; - $new_args = array(); - foreach ($args as $value) { - if (is_float($value) || is_int($value)) { - if (is_float($value)) { - // Force the conversion to float so as not to loose precision - // in the automatic cast. - $value = sprintf('%F', $value); - } - $query = substr_replace($query, $value, strpos($query, '?'), 1); - } - else { - $placeholder = ':db_statement_placeholder_' . $count++; - $query = substr_replace($query, $placeholder, strpos($query, '?'), 1); - $new_args[$placeholder] = $value; - } - } - $args = $new_args; - } - else { - // Else, this is using named placeholders. - foreach ($args as $placeholder => $value) { - if (is_float($value) || is_int($value)) { - if (is_float($value)) { - // Force the conversion to float so as not to loose precision - // in the automatic cast. - $value = sprintf('%F', $value); - } - - // We will remove this placeholder from the query as PDO throws an - // exception if the number of placeholders in the query and the - // arguments does not match. - unset($args[$placeholder]); - // PDO allows placeholders to not be prefixed by a colon. See - // http://marc.info/?l=php-internals&m=111234321827149&w=2 for - // more. - if ($placeholder[0] != ':') { - $placeholder = ":$placeholder"; - } - // When replacing the placeholders, make sure we search for the - // exact placeholder. For example, if searching for - // ':db_placeholder_1', do not replace ':db_placeholder_11'. - $query = preg_replace('/' . preg_quote($placeholder) . '\b/', $value, $query); - } - } - } - } - - return $this->dbh->PDOPrepare($query); - } - - public function execute($args = array(), $options = array()) { - try { - $return = parent::execute($args, $options); - } - catch (PDOException $e) { - if (!empty($e->errorInfo[1]) && $e->errorInfo[1] === 17) { - // The schema has changed. SQLite specifies that we must resend the query. - $return = parent::execute($args, $options); - } - else { - // Rethrow the exception. - throw $e; - } - } - - // In some weird cases, SQLite will prefix some column names by the name - // of the table. We post-process the data, by renaming the column names - // using the same convention as MySQL and PostgreSQL. - $rename_columns = array(); - foreach ($this->columnNames as $k => $column) { - // In some SQLite versions, SELECT DISTINCT(field) will return "(field)" - // instead of "field". - if (preg_match("/^\((.*)\)$/", $column, $matches)) { - $rename_columns[$column] = $matches[1]; - $this->columnNames[$k] = $matches[1]; - $column = $matches[1]; - } - - // Remove "table." prefixes. - if (preg_match("/^.*\.(.*)$/", $column, $matches)) { - $rename_columns[$column] = $matches[1]; - $this->columnNames[$k] = $matches[1]; - } - } - if ($rename_columns) { - // DatabaseStatementPrefetch already extracted the first row, - // put it back into the result set. - if (isset($this->currentRow)) { - $this->data[0] = &$this->currentRow; - } - - // Then rename all the columns across the result set. - foreach ($this->data as $k => $row) { - foreach ($rename_columns as $old_column => $new_column) { - $this->data[$k][$new_column] = $this->data[$k][$old_column]; - unset($this->data[$k][$old_column]); - } - } - - // Finally, extract the first row again. - $this->currentRow = $this->data[0]; - unset($this->data[0]); - } - - return $return; - } -} - -/** - * @} End of "ingroup database". - */ diff --git a/core/includes/database/sqlite/install.inc b/core/includes/database/sqlite/install.inc deleted file mode 100644 index 62cbac381f1..00000000000 --- a/core/includes/database/sqlite/install.inc +++ /dev/null @@ -1,51 +0,0 @@ -<?php - -/** - * @file - * SQLite specific install functions - */ - -class DatabaseTasks_sqlite extends DatabaseTasks { - protected $pdoDriver = 'sqlite'; - - public function name() { - return st('SQLite'); - } - - /** - * Minimum engine version. - * - * @todo: consider upping to 3.6.8 in Drupal 8 to get SAVEPOINT support. - */ - public function minimumVersion() { - return '3.3.7'; - } - - public function getFormOptions($database) { - $form = parent::getFormOptions($database); - - // Remove the options that only apply to client/server style databases. - unset($form['username'], $form['password'], $form['advanced_options']['host'], $form['advanced_options']['port']); - - // Make the text more accurate for SQLite. - $form['database']['#title'] = st('Database file'); - $form['database']['#description'] = st('The absolute path to the file where @drupal data will be stored. This must be writable by the web server and should exist outside of the web root.', array('@drupal' => drupal_install_profile_distribution_name())); - $default_database = conf_path(FALSE, TRUE) . '/files/.ht.sqlite'; - $form['database']['#default_value'] = empty($database['database']) ? $default_database : $database['database']; - return $form; - } - - public function validateDatabaseSettings($database) { - // Perform standard validation. - $errors = parent::validateDatabaseSettings($database); - - // Verify the database is writable. - $db_directory = new SplFileInfo(dirname($database['database'])); - if (!$db_directory->isWritable()) { - $errors[$database['driver'] . '][database'] = st('The directory you specified is not writable by the web server.'); - } - - return $errors; - } -} - diff --git a/core/includes/database/sqlite/query.inc b/core/includes/database/sqlite/query.inc deleted file mode 100644 index 6b8a72f2ab4..00000000000 --- a/core/includes/database/sqlite/query.inc +++ /dev/null @@ -1,160 +0,0 @@ -<?php - -/** - * @file - * Query code for SQLite embedded database engine. - */ - -/** - * @ingroup database - * @{ - */ - -/** - * SQLite specific implementation of InsertQuery. - * - * We ignore all the default fields and use the clever SQLite syntax: - * INSERT INTO table DEFAULT VALUES - * for degenerated "default only" queries. - */ -class InsertQuery_sqlite extends InsertQuery { - - public function execute() { - if (!$this->preExecute()) { - return NULL; - } - if (count($this->insertFields)) { - return parent::execute(); - } - else { - return $this->connection->query('INSERT INTO {' . $this->table . '} DEFAULT VALUES', array(), $this->queryOptions); - } - } - - public function __toString() { - // Create a sanitized comment string to prepend to the query. - $comments = $this->connection->makeComment($this->comments); - - // Produce as many generic placeholders as necessary. - $placeholders = array_fill(0, count($this->insertFields), '?'); - - // If we're selecting from a SelectQuery, finish building the query and - // pass it back, as any remaining options are irrelevant. - if (!empty($this->fromQuery)) { - return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $this->insertFields) . ') ' . $this->fromQuery; - } - - return $comments . 'INSERT INTO {' . $this->table . '} (' . implode(', ', $this->insertFields) . ') VALUES (' . implode(', ', $placeholders) . ')'; - } - -} - -/** - * SQLite specific implementation of UpdateQuery. - * - * SQLite counts all the rows that match the conditions as modified, even if they - * will not be affected by the query. We workaround this by ensuring that - * we don't select those rows. - * - * A query like this one: - * UPDATE test SET name = 'newname' WHERE tid = 1 - * will become: - * UPDATE test SET name = 'newname' WHERE tid = 1 AND name <> 'newname' - */ -class UpdateQuery_sqlite extends UpdateQuery { - /** - * Helper function that removes the fields that are already in a condition. - * - * @param $fields - * The fields. - * @param QueryConditionInterface $condition - * A database condition. - */ - protected function removeFieldsInCondition(&$fields, QueryConditionInterface $condition) { - foreach ($condition->conditions() as $child_condition) { - if ($child_condition['field'] instanceof QueryConditionInterface) { - $this->removeFieldsInCondition($fields, $child_condition['field']); - } - else { - unset($fields[$child_condition['field']]); - } - } - } - - public function execute() { - if (!empty($this->queryOptions['sqlite_return_matched_rows'])) { - return parent::execute(); - } - - // Get the fields used in the update query, and remove those that are already - // in the condition. - $fields = $this->expressionFields + $this->fields; - $this->removeFieldsInCondition($fields, $this->condition); - - // Add the inverse of the fields to the condition. - $condition = new DatabaseCondition('OR'); - foreach ($fields as $field => $data) { - if (is_array($data)) { - // The field is an expression. - $condition->where($field . ' <> ' . $data['expression']); - $condition->isNull($field); - } - elseif (!isset($data)) { - // The field will be set to NULL. - $condition->isNotNull($field); - } - else { - $condition->condition($field, $data, '<>'); - $condition->isNull($field); - } - } - if (count($condition)) { - $condition->compile($this->connection, $this); - $this->condition->where((string) $condition, $condition->arguments()); - } - return parent::execute(); - } - -} - -/** - * SQLite specific implementation of DeleteQuery. - * - * When the WHERE is omitted from a DELETE statement and the table being deleted - * has no triggers, SQLite uses an optimization to erase the entire table content - * without having to visit each row of the table individually. - * - * Prior to SQLite 3.6.5, SQLite does not return the actual number of rows deleted - * by that optimized "truncate" optimization. - */ -class DeleteQuery_sqlite extends DeleteQuery { - public function execute() { - if (!count($this->condition)) { - $total_rows = $this->connection->query('SELECT COUNT(*) FROM {' . $this->connection->escapeTable($this->table) . '}')->fetchField(); - parent::execute(); - return $total_rows; - } - else { - return parent::execute(); - } - } -} - -/** - * SQLite specific implementation of TruncateQuery. - * - * SQLite doesn't support TRUNCATE, but a DELETE query with no condition has - * exactly the effect (it is implemented by DROPing the table). - */ -class TruncateQuery_sqlite extends TruncateQuery { - public function __toString() { - // Create a sanitized comment string to prepend to the query. - $comments = $this->connection->makeComment($this->comments); - - return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} '; - } -} - -/** - * @} End of "ingroup database". - */ diff --git a/core/includes/database/sqlite/schema.inc b/core/includes/database/sqlite/schema.inc deleted file mode 100644 index c5882f12715..00000000000 --- a/core/includes/database/sqlite/schema.inc +++ /dev/null @@ -1,683 +0,0 @@ -<?php - -/** - * @file - * Database schema code for SQLite databases. - */ - - -/** - * @ingroup schemaapi - * @{ - */ - -class DatabaseSchema_sqlite extends DatabaseSchema { - - /** - * Override DatabaseSchema::$defaultSchema - */ - protected $defaultSchema = 'main'; - - public function tableExists($table) { - $info = $this->getPrefixInfo($table); - - // Don't use {} around sqlite_master table. - return (bool) $this->connection->query('SELECT 1 FROM ' . $info['schema'] . '.sqlite_master WHERE type = :type AND name = :name', array(':type' => 'table', ':name' => $info['table']))->fetchField(); - } - - public function fieldExists($table, $column) { - $schema = $this->introspectSchema($table); - return !empty($schema['fields'][$column]); - } - - /** - * Generate SQL to create a new table from a Drupal schema definition. - * - * @param $name - * The name of the table to create. - * @param $table - * A Schema API table definition array. - * @return - * An array of SQL statements to create the table. - */ - public function createTableSql($name, $table) { - $sql = array(); - $sql[] = "CREATE TABLE {" . $name . "} (\n" . $this->createColumsSql($name, $table) . "\n);\n"; - return array_merge($sql, $this->createIndexSql($name, $table)); - } - - /** - * Build the SQL expression for indexes. - */ - protected function createIndexSql($tablename, $schema) { - $sql = array(); - $info = $this->getPrefixInfo($tablename); - if (!empty($schema['unique keys'])) { - foreach ($schema['unique keys'] as $key => $fields) { - $sql[] = 'CREATE UNIQUE INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $key . ' ON ' . $info['table'] . ' (' . $this->createKeySql($fields) . "); \n"; - } - } - if (!empty($schema['indexes'])) { - foreach ($schema['indexes'] as $key => $fields) { - $sql[] = 'CREATE INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $key . ' ON ' . $info['table'] . ' (' . $this->createKeySql($fields) . "); \n"; - } - } - return $sql; - } - - /** - * Build the SQL expression for creating columns. - */ - protected function createColumsSql($tablename, $schema) { - $sql_array = array(); - - // Add the SQL statement for each field. - foreach ($schema['fields'] as $name => $field) { - if (isset($field['type']) && $field['type'] == 'serial') { - if (isset($schema['primary key']) && ($key = array_search($name, $schema['primary key'])) !== FALSE) { - unset($schema['primary key'][$key]); - } - } - $sql_array[] = $this->createFieldSql($name, $this->processField($field)); - } - - // Process keys. - if (!empty($schema['primary key'])) { - $sql_array[] = " PRIMARY KEY (" . $this->createKeySql($schema['primary key']) . ")"; - } - - return implode(", \n", $sql_array); - } - - /** - * Build the SQL expression for keys. - */ - protected function createKeySql($fields) { - $return = array(); - foreach ($fields as $field) { - if (is_array($field)) { - $return[] = $field[0]; - } - else { - $return[] = $field; - } - } - return implode(', ', $return); - } - - /** - * Set database-engine specific properties for a field. - * - * @param $field - * A field description array, as specified in the schema documentation. - */ - protected function processField($field) { - if (!isset($field['size'])) { - $field['size'] = 'normal'; - } - - // Set the correct database-engine specific datatype. - // In case one is already provided, force it to uppercase. - if (isset($field['sqlite_type'])) { - $field['sqlite_type'] = drupal_strtoupper($field['sqlite_type']); - } - else { - $map = $this->getFieldTypeMap(); - $field['sqlite_type'] = $map[$field['type'] . ':' . $field['size']]; - } - - if (isset($field['type']) && $field['type'] == 'serial') { - $field['auto_increment'] = TRUE; - } - - return $field; - } - - /** - * Create an SQL string for a field to be used in table creation or alteration. - * - * Before passing a field out of a schema definition into this function it has - * to be processed by db_processField(). - * - * @param $name - * Name of the field. - * @param $spec - * The field specification, as per the schema data structure format. - */ - protected function createFieldSql($name, $spec) { - if (!empty($spec['auto_increment'])) { - $sql = $name . " INTEGER PRIMARY KEY AUTOINCREMENT"; - if (!empty($spec['unsigned'])) { - $sql .= ' CHECK (' . $name . '>= 0)'; - } - } - else { - $sql = $name . ' ' . $spec['sqlite_type']; - - if (in_array($spec['sqlite_type'], array('VARCHAR', 'TEXT')) && isset($spec['length'])) { - $sql .= '(' . $spec['length'] . ')'; - } - - if (isset($spec['not null'])) { - if ($spec['not null']) { - $sql .= ' NOT NULL'; - } - else { - $sql .= ' NULL'; - } - } - - if (!empty($spec['unsigned'])) { - $sql .= ' CHECK (' . $name . '>= 0)'; - } - - if (isset($spec['default'])) { - if (is_string($spec['default'])) { - $spec['default'] = "'" . $spec['default'] . "'"; - } - $sql .= ' DEFAULT ' . $spec['default']; - } - - if (empty($spec['not null']) && !isset($spec['default'])) { - $sql .= ' DEFAULT NULL'; - } - } - return $sql; - } - - /** - * This maps a generic data type in combination with its data size - * to the engine-specific data type. - */ - public function getFieldTypeMap() { - // Put :normal last so it gets preserved by array_flip. This makes - // it much easier for modules (such as schema.module) to map - // database types back into schema types. - // $map does not use drupal_static as its value never changes. - static $map = array( - 'varchar:normal' => 'VARCHAR', - 'char:normal' => 'CHAR', - - 'text:tiny' => 'TEXT', - 'text:small' => 'TEXT', - 'text:medium' => 'TEXT', - 'text:big' => 'TEXT', - 'text:normal' => 'TEXT', - - 'serial:tiny' => 'INTEGER', - 'serial:small' => 'INTEGER', - 'serial:medium' => 'INTEGER', - 'serial:big' => 'INTEGER', - 'serial:normal' => 'INTEGER', - - 'int:tiny' => 'INTEGER', - 'int:small' => 'INTEGER', - 'int:medium' => 'INTEGER', - 'int:big' => 'INTEGER', - 'int:normal' => 'INTEGER', - - 'float:tiny' => 'FLOAT', - 'float:small' => 'FLOAT', - 'float:medium' => 'FLOAT', - 'float:big' => 'FLOAT', - 'float:normal' => 'FLOAT', - - 'numeric:normal' => 'NUMERIC', - - 'blob:big' => 'BLOB', - 'blob:normal' => 'BLOB', - ); - return $map; - } - - public function renameTable($table, $new_name) { - if (!$this->tableExists($table)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot rename %table to %table_new: table %table doesn't exist.", array('%table' => $table, '%table_new' => $new_name))); - } - if ($this->tableExists($new_name)) { - throw new DatabaseSchemaObjectExistsException(t("Cannot rename %table to %table_new: table %table_new already exists.", array('%table' => $table, '%table_new' => $new_name))); - } - - $schema = $this->introspectSchema($table); - - // SQLite doesn't allow you to rename tables outside of the current - // database. So the syntax '...RENAME TO database.table' would fail. - // So we must determine the full table name here rather than surrounding - // the table with curly braces incase the db_prefix contains a reference - // to a database outside of our existsing database. - $info = $this->getPrefixInfo($new_name); - $this->connection->query('ALTER TABLE {' . $table . '} RENAME TO ' . $info['table']); - - // Drop the indexes, there is no RENAME INDEX command in SQLite. - if (!empty($schema['unique keys'])) { - foreach ($schema['unique keys'] as $key => $fields) { - $this->dropIndex($table, $key); - } - } - if (!empty($schema['indexes'])) { - foreach ($schema['indexes'] as $index => $fields) { - $this->dropIndex($table, $index); - } - } - - // Recreate the indexes. - $statements = $this->createIndexSql($new_name, $schema); - foreach ($statements as $statement) { - $this->connection->query($statement); - } - } - - public function dropTable($table) { - if (!$this->tableExists($table)) { - return FALSE; - } - $this->connection->tableDropped = TRUE; - $this->connection->query('DROP TABLE {' . $table . '}'); - return TRUE; - } - - public function addField($table, $field, $specification, $keys_new = array()) { - if (!$this->tableExists($table)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add field %table.%field: table doesn't exist.", array('%field' => $field, '%table' => $table))); - } - if ($this->fieldExists($table, $field)) { - throw new DatabaseSchemaObjectExistsException(t("Cannot add field %table.%field: field already exists.", array('%field' => $field, '%table' => $table))); - } - - // SQLite doesn't have a full-featured ALTER TABLE statement. It only - // supports adding new fields to a table, in some simple cases. In most - // cases, we have to create a new table and copy the data over. - if (empty($keys_new) && (empty($specification['not null']) || isset($specification['default']))) { - // When we don't have to create new keys and we are not creating a - // NOT NULL column without a default value, we can use the quicker version. - $query = 'ALTER TABLE {' . $table . '} ADD ' . $this->createFieldSql($field, $this->processField($specification)); - $this->connection->query($query); - - // Apply the initial value if set. - if (isset($specification['initial'])) { - $this->connection->update($table) - ->fields(array($field => $specification['initial'])) - ->execute(); - } - } - else { - // We cannot add the field directly. Use the slower table alteration - // method, starting from the old schema. - $old_schema = $this->introspectSchema($table); - $new_schema = $old_schema; - - // Add the new field. - $new_schema['fields'][$field] = $specification; - - // Build the mapping between the old fields and the new fields. - $mapping = array(); - if (isset($specification['initial'])) { - // If we have a initial value, copy it over. - $mapping[$field] = array( - 'expression' => ':newfieldinitial', - 'arguments' => array(':newfieldinitial' => $specification['initial']), - ); - } - else { - // Else use the default of the field. - $mapping[$field] = NULL; - } - - // Add the new indexes. - $new_schema += $keys_new; - - $this->alterTable($table, $old_schema, $new_schema, $mapping); - } - } - - /** - * Create a table with a new schema containing the old content. - * - * As SQLite does not support ALTER TABLE (with a few exceptions) it is - * necessary to create a new table and copy over the old content. - * - * @param $table - * Name of the table to be altered. - * @param $old_schema - * The old schema array for the table. - * @param $new_schema - * The new schema array for the table. - * @param $mapping - * An optional mapping between the fields of the old specification and the - * fields of the new specification. An associative array, whose keys are - * the fields of the new table, and values can take two possible forms: - * - a simple string, which is interpreted as the name of a field of the - * old table, - * - an associative array with two keys 'expression' and 'arguments', - * that will be used as an expression field. - */ - protected function alterTable($table, $old_schema, $new_schema, array $mapping = array()) { - $i = 0; - do { - $new_table = $table . '_' . $i++; - } while ($this->tableExists($new_table)); - - $this->createTable($new_table, $new_schema); - - // Build a SQL query to migrate the data from the old table to the new. - $select = $this->connection->select($table); - - // Complete the mapping. - $possible_keys = array_keys($new_schema['fields']); - $mapping += array_combine($possible_keys, $possible_keys); - - // Now add the fields. - foreach ($mapping as $field_alias => $field_source) { - // Just ignore this field (ie. use it's default value). - if (!isset($field_source)) { - continue; - } - - if (is_array($field_source)) { - $select->addExpression($field_source['expression'], $field_alias, $field_source['arguments']); - } - else { - $select->addField($table, $field_source, $field_alias); - } - } - - // Execute the data migration query. - $this->connection->insert($new_table) - ->from($select) - ->execute(); - - $old_count = $this->connection->query('SELECT COUNT(*) FROM {' . $table . '}')->fetchField(); - $new_count = $this->connection->query('SELECT COUNT(*) FROM {' . $new_table . '}')->fetchField(); - if ($old_count == $new_count) { - $this->dropTable($table); - $this->renameTable($new_table, $table); - } - } - - /** - * Find out the schema of a table. - * - * This function uses introspection methods provided by the database to - * create a schema array. This is useful, for example, during update when - * the old schema is not available. - * - * @param $table - * Name of the table. - * @return - * An array representing the schema, from drupal_get_schema(). - * @see drupal_get_schema() - */ - protected function introspectSchema($table) { - $mapped_fields = array_flip($this->getFieldTypeMap()); - $schema = array( - 'fields' => array(), - 'primary key' => array(), - 'unique keys' => array(), - 'indexes' => array(), - ); - - $info = $this->getPrefixInfo($table); - $result = $this->connection->query('PRAGMA ' . $info['schema'] . '.table_info(' . $info['table'] . ')'); - foreach ($result as $row) { - if (preg_match('/^([^(]+)\((.*)\)$/', $row->type, $matches)) { - $type = $matches[1]; - $length = $matches[2]; - } - else { - $type = $row->type; - $length = NULL; - } - if (isset($mapped_fields[$type])) { - list($type, $size) = explode(':', $mapped_fields[$type]); - $schema['fields'][$row->name] = array( - 'type' => $type, - 'size' => $size, - 'not null' => !empty($row->notnull), - 'default' => trim($row->dflt_value, "'"), - ); - if ($length) { - $schema['fields'][$row->name]['length'] = $length; - } - if ($row->pk) { - $schema['primary key'][] = $row->name; - } - } - else { - new Exception("Unable to parse the column type " . $row->type); - } - } - $indexes = array(); - $result = $this->connection->query('PRAGMA ' . $info['schema'] . '.index_list(' . $info['table'] . ')'); - foreach ($result as $row) { - if (strpos($row->name, 'sqlite_autoindex_') !== 0) { - $indexes[] = array( - 'schema_key' => $row->unique ? 'unique keys' : 'indexes', - 'name' => $row->name, - ); - } - } - foreach ($indexes as $index) { - $name = $index['name']; - // Get index name without prefix. - $index_name = substr($name, strlen($info['table']) + 1); - $result = $this->connection->query('PRAGMA ' . $info['schema'] . '.index_info(' . $name . ')'); - foreach ($result as $row) { - $schema[$index['schema_key']][$index_name][] = $row->name; - } - } - return $schema; - } - - public function dropField($table, $field) { - if (!$this->fieldExists($table, $field)) { - return FALSE; - } - - $old_schema = $this->introspectSchema($table); - $new_schema = $old_schema; - - unset($new_schema['fields'][$field]); - foreach ($new_schema['indexes'] as $index => $fields) { - foreach ($fields as $key => $field_name) { - if ($field_name == $field) { - unset($new_schema['indexes'][$index][$key]); - } - } - // If this index has no more fields then remove it. - if (empty($new_schema['indexes'][$index])) { - unset($new_schema['indexes'][$index]); - } - } - $this->alterTable($table, $old_schema, $new_schema); - return TRUE; - } - - public function changeField($table, $field, $field_new, $spec, $keys_new = array()) { - if (!$this->fieldExists($table, $field)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot change the definition of field %table.%name: field doesn't exist.", array('%table' => $table, '%name' => $field))); - } - if (($field != $field_new) && $this->fieldExists($table, $field_new)) { - throw new DatabaseSchemaObjectExistsException(t("Cannot rename field %table.%name to %name_new: target field already exists.", array('%table' => $table, '%name' => $field, '%name_new' => $field_new))); - } - - $old_schema = $this->introspectSchema($table); - $new_schema = $old_schema; - - // Map the old field to the new field. - if ($field != $field_new) { - $mapping[$field_new] = $field; - } - else { - $mapping = array(); - } - - // Remove the previous definition and swap in the new one. - unset($new_schema['fields'][$field]); - $new_schema['fields'][$field_new] = $spec; - - // Map the former indexes to the new column name. - $new_schema['primary key'] = $this->mapKeyDefinition($new_schema['primary key'], $mapping); - foreach (array('unique keys', 'indexes') as $k) { - foreach ($new_schema[$k] as &$key_definition) { - $key_definition = $this->mapKeyDefinition($key_definition, $mapping); - } - } - - // Add in the keys from $keys_new. - if (isset($keys_new['primary key'])) { - $new_schema['primary key'] = $keys_new['primary key']; - } - foreach (array('unique keys', 'indexes') as $k) { - if (!empty($keys_new[$k])) { - $new_schema[$k] = $keys_new[$k] + $new_schema[$k]; - } - } - - $this->alterTable($table, $old_schema, $new_schema, $mapping); - } - - /** - * Utility method: rename columns in an index definition according to a new mapping. - * - * @param $key_definition - * The key definition. - * @param $mapping - * The new mapping. - */ - protected function mapKeyDefinition(array $key_definition, array $mapping) { - foreach ($key_definition as &$field) { - // The key definition can be an array($field, $length). - if (is_array($field)) { - $field = &$field[0]; - } - if (isset($mapping[$field])) { - $field = $mapping[$field]; - } - } - return $key_definition; - } - - public function addIndex($table, $name, $fields) { - if (!$this->tableExists($table)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add index %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name))); - } - if ($this->indexExists($table, $name)) { - throw new DatabaseSchemaObjectExistsException(t("Cannot add index %name to table %table: index already exists.", array('%table' => $table, '%name' => $name))); - } - - $schema['indexes'][$name] = $fields; - $statements = $this->createIndexSql($table, $schema); - foreach ($statements as $statement) { - $this->connection->query($statement); - } - } - - public function indexExists($table, $name) { - $info = $this->getPrefixInfo($table); - - return $this->connection->query('PRAGMA ' . $info['schema'] . '.index_info(' . $info['table'] . '_' . $name . ')')->fetchField() != ''; - } - - public function dropIndex($table, $name) { - if (!$this->indexExists($table, $name)) { - return FALSE; - } - - $info = $this->getPrefixInfo($table); - - $this->connection->query('DROP INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $name); - return TRUE; - } - - public function addUniqueKey($table, $name, $fields) { - if (!$this->tableExists($table)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add unique key %name to table %table: table doesn't exist.", array('%table' => $table, '%name' => $name))); - } - if ($this->indexExists($table, $name)) { - throw new DatabaseSchemaObjectExistsException(t("Cannot add unique key %name to table %table: unique key already exists.", array('%table' => $table, '%name' => $name))); - } - - $schema['unique keys'][$name] = $fields; - $statements = $this->createIndexSql($table, $schema); - foreach ($statements as $statement) { - $this->connection->query($statement); - } - } - - public function dropUniqueKey($table, $name) { - if (!$this->indexExists($table, $name)) { - return FALSE; - } - - $info = $this->getPrefixInfo($table); - - $this->connection->query('DROP INDEX ' . $info['schema'] . '.' . $info['table'] . '_' . $name); - return TRUE; - } - - public function addPrimaryKey($table, $fields) { - if (!$this->tableExists($table)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot add primary key to table %table: table doesn't exist.", array('%table' => $table))); - } - - $old_schema = $this->introspectSchema($table); - $new_schema = $old_schema; - - if (!empty($new_schema['primary key'])) { - throw new DatabaseSchemaObjectExistsException(t("Cannot add primary key to table %table: primary key already exists.", array('%table' => $table))); - } - - $new_schema['primary key'] = $fields; - $this->alterTable($table, $old_schema, $new_schema); - } - - public function dropPrimaryKey($table) { - $old_schema = $this->introspectSchema($table); - $new_schema = $old_schema; - - if (empty($new_schema['primary key'])) { - return FALSE; - } - - unset($new_schema['primary key']); - $this->alterTable($table, $old_schema, $new_schema); - return TRUE; - } - - public function fieldSetDefault($table, $field, $default) { - if (!$this->fieldExists($table, $field)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot set default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field))); - } - - $old_schema = $this->introspectSchema($table); - $new_schema = $old_schema; - - $new_schema['fields'][$field]['default'] = $default; - $this->alterTable($table, $old_schema, $new_schema); - } - - public function fieldSetNoDefault($table, $field) { - if (!$this->fieldExists($table, $field)) { - throw new DatabaseSchemaObjectDoesNotExistException(t("Cannot remove default value of field %table.%field: field doesn't exist.", array('%table' => $table, '%field' => $field))); - } - - $old_schema = $this->introspectSchema($table); - $new_schema = $old_schema; - - unset($new_schema['fields'][$field]['default']); - $this->alterTable($table, $old_schema, $new_schema); - } - - public function findTables($table_expression) { - // Don't add the prefix, $table_expression already includes the prefix. - $info = $this->getPrefixInfo($table_expression, FALSE); - - // Can't use query placeholders for the schema because the query would have - // to be :prefixsqlite_master, which does not work. - $result = db_query("SELECT name FROM " . $info['schema'] . ".sqlite_master WHERE type = :type AND name LIKE :table_name", array( - ':type' => 'table', - ':table_name' => $info['table'], - )); - return $result->fetchAllKeyed(0, 0); - } -} diff --git a/core/includes/database/sqlite/select.inc b/core/includes/database/sqlite/select.inc deleted file mode 100644 index fb926ef04d3..00000000000 --- a/core/includes/database/sqlite/select.inc +++ /dev/null @@ -1,27 +0,0 @@ -<?php - -/** - * @file - * Select builder for SQLite embedded database engine. - */ - -/** - * @ingroup database - * @{ - */ - -/** - * SQLite specific query builder for SELECT statements. - */ -class SelectQuery_sqlite extends SelectQuery { - public function forUpdate($set = TRUE) { - // SQLite does not support FOR UPDATE so nothing to do. - return $this; - } -} - -/** - * @} End of "ingroup database". - */ - - diff --git a/core/includes/date.inc b/core/includes/date.inc deleted file mode 100644 index 27634f9e39b..00000000000 --- a/core/includes/date.inc +++ /dev/null @@ -1,196 +0,0 @@ -<?php - -/** - * @file - * Initialize the list of date formats and their locales. - */ - -/** - * Provides a default system list of date formats for system_date_formats(). - */ -function system_default_date_formats() { - $formats = array(); - - // Short date formats. - $formats[] = array( - 'type' => 'short', - 'format' => 'Y-m-d H:i', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'short', - 'format' => 'm/d/Y - H:i', - 'locales' => array('en-us'), - ); - $formats[] = array( - 'type' => 'short', - 'format' => 'd/m/Y - H:i', - 'locales' => array('en-gb', 'en-hk', 'en-ie', 'el-gr', 'es-es', 'fr-be', 'fr-fr', 'fr-lu', 'it-it', 'nl-be', 'pt-pt'), - ); - $formats[] = array( - 'type' => 'short', - 'format' => 'Y/m/d - H:i', - 'locales' => array('en-ca', 'fr-ca', 'no-no', 'sv-se'), - ); - $formats[] = array( - 'type' => 'short', - 'format' => 'd.m.Y - H:i', - 'locales' => array('de-ch', 'de-de', 'de-lu', 'fi-fi', 'fr-ch', 'is-is', 'pl-pl', 'ro-ro', 'ru-ru'), - ); - $formats[] = array( - 'type' => 'short', - 'format' => 'm/d/Y - g:ia', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'short', - 'format' => 'd/m/Y - g:ia', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'short', - 'format' => 'Y/m/d - g:ia', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'short', - 'format' => 'M j Y - H:i', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'short', - 'format' => 'j M Y - H:i', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'short', - 'format' => 'Y M j - H:i', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'short', - 'format' => 'M j Y - g:ia', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'short', - 'format' => 'j M Y - g:ia', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'short', - 'format' => 'Y M j - g:ia', - 'locales' => array(), - ); - - // Medium date formats. - $formats[] = array( - 'type' => 'medium', - 'format' => 'D, Y-m-d H:i', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'medium', - 'format' => 'D, m/d/Y - H:i', - 'locales' => array('en-us'), - ); - $formats[] = array( - 'type' => 'medium', - 'format' => 'D, d/m/Y - H:i', - 'locales' => array('en-gb', 'en-hk', 'en-ie', 'el-gr', 'es-es', 'fr-be', 'fr-fr', 'fr-lu', 'it-it', 'nl-be', 'pt-pt'), - ); - $formats[] = array( - 'type' => 'medium', - 'format' => 'D, Y/m/d - H:i', - 'locales' => array('en-ca', 'fr-ca', 'no-no', 'sv-se'), - ); - $formats[] = array( - 'type' => 'medium', - 'format' => 'F j, Y - H:i', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'medium', - 'format' => 'j F, Y - H:i', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'medium', - 'format' => 'Y, F j - H:i', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'medium', - 'format' => 'D, m/d/Y - g:ia', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'medium', - 'format' => 'D, d/m/Y - g:ia', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'medium', - 'format' => 'D, Y/m/d - g:ia', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'medium', - 'format' => 'F j, Y - g:ia', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'medium', - 'format' => 'j F Y - g:ia', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'medium', - 'format' => 'Y, F j - g:ia', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'medium', - 'format' => 'j. F Y - G:i', - 'locales' => array(), - ); - - // Long date formats. - $formats[] = array( - 'type' => 'long', - 'format' => 'l, F j, Y - H:i', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'long', - 'format' => 'l, j F, Y - H:i', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'long', - 'format' => 'l, Y, F j - H:i', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'long', - 'format' => 'l, F j, Y - g:ia', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'long', - 'format' => 'l, j F Y - g:ia', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'long', - 'format' => 'l, Y, F j - g:ia', - 'locales' => array(), - ); - $formats[] = array( - 'type' => 'long', - 'format' => 'l, j. F Y - G:i', - 'locales' => array(), - ); - - return $formats; -} diff --git a/core/includes/errors.inc b/core/includes/errors.inc deleted file mode 100644 index 9d3b92a262a..00000000000 --- a/core/includes/errors.inc +++ /dev/null @@ -1,293 +0,0 @@ -<?php - -/** - * @file - * Functions for error handling - */ - -/** - * Error reporting level: display no errors. - */ -define('ERROR_REPORTING_HIDE', 0); - -/** - * Error reporting level: display errors and warnings. - */ -define('ERROR_REPORTING_DISPLAY_SOME', 1); - -/** - * Error reporting level: display all messages. - */ -define('ERROR_REPORTING_DISPLAY_ALL', 2); - -/** - * Map PHP error constants to watchdog severity levels. - * The error constants are documented at - * http://php.net/manual/en/errorfunc.constants.php - * - * @ingroup logging_severity_levels - */ -function drupal_error_levels() { - $types = array( - E_ERROR => array('Error', WATCHDOG_ERROR), - E_WARNING => array('Warning', WATCHDOG_WARNING), - E_PARSE => array('Parse error', WATCHDOG_ERROR), - E_NOTICE => array('Notice', WATCHDOG_NOTICE), - E_CORE_ERROR => array('Core error', WATCHDOG_ERROR), - E_CORE_WARNING => array('Core warning', WATCHDOG_WARNING), - E_COMPILE_ERROR => array('Compile error', WATCHDOG_ERROR), - E_COMPILE_WARNING => array('Compile warning', WATCHDOG_WARNING), - E_USER_ERROR => array('User error', WATCHDOG_ERROR), - E_USER_WARNING => array('User warning', WATCHDOG_WARNING), - E_USER_NOTICE => array('User notice', WATCHDOG_NOTICE), - E_STRICT => array('Strict warning', WATCHDOG_DEBUG), - E_RECOVERABLE_ERROR => array('Recoverable fatal error', WATCHDOG_ERROR), - E_DEPRECATED => array('Deprecated function', WATCHDOG_DEBUG), - E_USER_DEPRECATED => array('User deprecated function', WATCHDOG_DEBUG), - ); - - return $types; -} - -/** - * Custom PHP error handler. - * - * @param $error_level - * The level of the error raised. - * @param $message - * The error message. - * @param $filename - * The filename that the error was raised in. - * @param $line - * The line number the error was raised at. - * @param $context - * An array that points to the active symbol table at the point the error occurred. - */ -function _drupal_error_handler_real($error_level, $message, $filename, $line, $context) { - if ($error_level & error_reporting()) { - $types = drupal_error_levels(); - list($severity_msg, $severity_level) = $types[$error_level]; - $caller = _drupal_get_last_caller(debug_backtrace()); - - if (!function_exists('filter_xss_admin')) { - require_once DRUPAL_ROOT . '/core/includes/common.inc'; - } - - // We treat recoverable errors as fatal. - _drupal_log_error(array( - '%type' => isset($types[$error_level]) ? $severity_msg : 'Unknown error', - // The standard PHP error handler considers that the error messages - // are HTML. We mimick this behavior here. - '!message' => filter_xss_admin($message), - '%function' => $caller['function'], - '%file' => $caller['file'], - '%line' => $caller['line'], - 'severity_level' => $severity_level, - ), $error_level == E_RECOVERABLE_ERROR); - } -} - -/** - * Decode an exception, especially to retrive the correct caller. - * - * @param $exception - * The exception object that was thrown. - * @return - * An error in the format expected by _drupal_log_error(). - */ -function _drupal_decode_exception($exception) { - $message = $exception->getMessage(); - - $backtrace = $exception->getTrace(); - // Add the line throwing the exception to the backtrace. - array_unshift($backtrace, array('line' => $exception->getLine(), 'file' => $exception->getFile())); - - // For PDOException errors, we try to return the initial caller, - // skipping internal functions of the database layer. - if ($exception instanceof PDOException) { - // The first element in the stack is the call, the second element gives us the caller. - // We skip calls that occurred in one of the classes of the database layer - // or in one of its global functions. - $db_functions = array('db_query', 'db_query_range'); - while (!empty($backtrace[1]) && ($caller = $backtrace[1]) && - ((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE)) || - in_array($caller['function'], $db_functions))) { - // We remove that call. - array_shift($backtrace); - } - if (isset($exception->query_string, $exception->args)) { - $message .= ": " . $exception->query_string . "; " . print_r($exception->args, TRUE); - } - } - $caller = _drupal_get_last_caller($backtrace); - - return array( - '%type' => get_class($exception), - // The standard PHP exception handler considers that the exception message - // is plain-text. We mimick this behavior here. - '!message' => check_plain($message), - '%function' => $caller['function'], - '%file' => $caller['file'], - '%line' => $caller['line'], - 'severity_level' => WATCHDOG_ERROR, - ); -} - -/** - * Render an error message for an exception without any possibility of a further exception occurring. - * - * @param $exception - * The exception object that was thrown. - * @return - * An error message. - */ -function _drupal_render_exception_safe($exception) { - return check_plain(strtr('%type: !message in %function (line %line of %file).', _drupal_decode_exception($exception))); -} - -/** - * Determines whether an error should be displayed. - * - * When in maintenance mode or when error_level is ERROR_REPORTING_DISPLAY_ALL, - * all errors should be displayed. For ERROR_REPORTING_DISPLAY_SOME, $error - * will be examined to determine if it should be displayed. - * - * @param $error - * Optional error to examine for ERROR_REPORTING_DISPLAY_SOME. - * - * @return - * TRUE if an error should be displayed. - */ -function error_displayable($error = NULL) { - $error_level = variable_get('error_level', ERROR_REPORTING_DISPLAY_ALL); - $updating = (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update'); - $all_errors_displayed = ($error_level == ERROR_REPORTING_DISPLAY_ALL); - $error_needs_display = ($error_level == ERROR_REPORTING_DISPLAY_SOME && - isset($error) && $error['%type'] != 'Notice' && $error['%type'] != 'Strict warning'); - - return ($updating || $all_errors_displayed || $error_needs_display); -} - -/** - * Log a PHP error or exception, display an error page in fatal cases. - * - * @param $error - * An array with the following keys: %type, !message, %function, %file, %line - * and severity_level. All the parameters are plain-text, with the exception of - * !message, which needs to be a safe HTML string. - * @param $fatal - * TRUE if the error is fatal. - */ -function _drupal_log_error($error, $fatal = FALSE) { - // Initialize a maintenance theme if the boostrap was not complete. - // Do it early because drupal_set_message() triggers a drupal_theme_initialize(). - if ($fatal && (drupal_get_bootstrap_phase() != DRUPAL_BOOTSTRAP_FULL)) { - unset($GLOBALS['theme']); - if (!defined('MAINTENANCE_MODE')) { - define('MAINTENANCE_MODE', 'error'); - } - drupal_maintenance_theme(); - } - - // When running inside the testing framework, we relay the errors - // to the tested site by the way of HTTP headers. - $test_info = &$GLOBALS['drupal_test_info']; - if (!empty($test_info['in_child_site']) && !headers_sent() && (!defined('SIMPLETEST_COLLECT_ERRORS') || SIMPLETEST_COLLECT_ERRORS)) { - // $number does not use drupal_static as it should not be reset - // as it uniquely identifies each PHP error. - static $number = 0; - $assertion = array( - $error['!message'], - $error['%type'], - array( - 'function' => $error['%function'], - 'file' => $error['%file'], - 'line' => $error['%line'], - ), - ); - header('X-Drupal-Assertion-' . $number . ': ' . rawurlencode(serialize($assertion))); - $number++; - } - - watchdog('php', '%type: !message in %function (line %line of %file).', $error, $error['severity_level']); - - if ($fatal) { - drupal_add_http_header('Status', '500 Service unavailable (with message)'); - } - - if (drupal_is_cli()) { - if ($fatal) { - // When called from CLI, simply output a plain text message. - print html_entity_decode(strip_tags(t('%type: !message in %function (line %line of %file).', $error))). "\n"; - exit; - } - } - - if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') { - if ($fatal) { - // When called from JavaScript, simply output the error message. - print t('%type: !message in %function (line %line of %file).', $error); - exit; - } - } - else { - // Display the message if the current error reporting level allows this type - // of message to be displayed, and unconditionnaly in update.php. - if (error_displayable($error)) { - $class = 'error'; - - // If error type is 'User notice' then treat it as debug information - // instead of an error message, see dd(). - if ($error['%type'] == 'User notice') { - $error['%type'] = 'Debug'; - $class = 'status'; - } - - drupal_set_message(t('%type: !message in %function (line %line of %file).', $error), $class); - } - - if ($fatal) { - drupal_set_title(t('Error')); - // We fallback to a maintenance page at this point, because the page generation - // itself can generate errors. - print theme('maintenance_page', array('content' => t('The website encountered an unexpected error. Please try again later.'))); - exit; - } - } -} - -/** - * Gets the last caller from a backtrace. - * - * @param $backtrace - * A standard PHP backtrace. - * @return - * An associative array with keys 'file', 'line' and 'function'. - */ -function _drupal_get_last_caller($backtrace) { - // Errors that occur inside PHP internal functions do not generate - // information about file and line. Ignore black listed functions. - $blacklist = array('debug', '_drupal_error_handler', '_drupal_exception_handler'); - while (($backtrace && !isset($backtrace[0]['line'])) || - (isset($backtrace[1]['function']) && in_array($backtrace[1]['function'], $blacklist))) { - array_shift($backtrace); - } - - // The first trace is the call itself. - // It gives us the line and the file of the last call. - $call = $backtrace[0]; - - // The second call give us the function where the call originated. - if (isset($backtrace[1])) { - if (isset($backtrace[1]['class'])) { - $call['function'] = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()'; - } - else { - $call['function'] = $backtrace[1]['function'] . '()'; - } - } - else { - $call['function'] = 'main()'; - } - return $call; -} diff --git a/core/includes/file.inc b/core/includes/file.inc deleted file mode 100644 index 6e6611f3acd..00000000000 --- a/core/includes/file.inc +++ /dev/null @@ -1,2451 +0,0 @@ -<?php - -/** - * @file - * API for handling file uploads and server file management. - */ - -/** - * Manually include stream wrapper code. - * - * Stream wrapper code is included here because there are cases where - * File API is needed before a bootstrap, or in an alternate order (e.g. - * maintenance theme). - */ -require_once DRUPAL_ROOT . '/core/includes/stream_wrappers.inc'; - -/** - * @defgroup file File interface - * @{ - * Common file handling functions. - * - * Fields on the file object: - * - fid: File ID - * - uid: The {users}.uid of the user who is associated with the file. - * - filename: Name of the file with no path components. This may differ from - * the basename of the filepath if the file is renamed to avoid overwriting - * an existing file. - * - uri: URI of the file. - * - filemime: The file's MIME type. - * - filesize: The size of the file in bytes. - * - status: A bitmapped field indicating the status of the file. The first 8 - * bits are reserved for Drupal core. The least significant bit indicates - * temporary (0) or permanent (1). Temporary files older than - * DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed during cron runs. - * - timestamp: UNIX timestamp for the date the file was added to the database. - */ - -/** - * Flag used by file_prepare_directory() -- create directory if not present. - */ -define('FILE_CREATE_DIRECTORY', 1); - -/** - * Flag used by file_prepare_directory() -- file permissions may be changed. - */ -define('FILE_MODIFY_PERMISSIONS', 2); - -/** - * Flag for dealing with existing files: Appends number until name is unique. - */ -define('FILE_EXISTS_RENAME', 0); - -/** - * Flag for dealing with existing files: Replace the existing file. - */ -define('FILE_EXISTS_REPLACE', 1); - -/** - * Flag for dealing with existing files: Do nothing and return FALSE. - */ -define('FILE_EXISTS_ERROR', 2); - -/** - * Indicates that the file is permanent and should not be deleted. - * - * Temporary files older than DRUPAL_MAXIMUM_TEMP_FILE_AGE will be removed - * during cron runs, but permanent files will not be removed during the file - * garbage collection process. - */ -define('FILE_STATUS_PERMANENT', 1); - -/** - * Methods to manage a registry of stream wrappers. - */ - -/** - * Drupal stream wrapper registry. - * - * A stream wrapper is an abstraction of a file system that allows Drupal to - * use the same set of methods to access both local files and remote resources. - * - * Provide a facility for managing and querying user-defined stream wrappers - * in PHP. PHP's internal stream_get_wrappers() doesn't return the class - * registered to handle a stream, which we need to be able to find the handler - * for class instantiation. - * - * If a module registers a scheme that is already registered with PHP, the - * existing scheme will be unregistered and replaced with the specified class. - * - * A stream is referenced as "scheme://target". - * - * The optional $filter parameter can be used to retrieve only the stream - * wrappers that are appropriate for particular usage. For example, this returns - * only stream wrappers that use local file storage: - * @code - * $local_stream_wrappers = file_get_stream_wrappers(STEAM_WRAPPERS_LOCAL); - * @endcode - * - * The $filter parameter can only filter to types containing a particular flag. - * In some cases, you may want to filter to types that do not contain a - * particular flag. For example, you may want to retrieve all stream wrappers - * that are not writable, or all stream wrappers that are not local. PHP's - * array_diff_key() function can be used to help with this. For example, this - * returns only stream wrappers that do not use local file storage: - * @code - * $remote_stream_wrappers = array_diff_key(file_get_stream_wrappers(STREAM_WRAPPERS_ALL), file_get_stream_wrappers(STEAM_WRAPPERS_LOCAL)); - * @endcode - * - * @param $filter - * (Optional) Filters out all types except those with an on bit for each on - * bit in $filter. For example, if $filter is STREAM_WRAPPERS_WRITE_VISIBLE, - * which is equal to (STREAM_WRAPPERS_READ | STREAM_WRAPPERS_WRITE | - * STREAM_WRAPPERS_VISIBLE), then only stream wrappers with all three of these - * bits set are returned. Defaults to STREAM_WRAPPERS_ALL, which returns all - * registered stream wrappers. - * - * @return - * An array keyed by scheme, with values containing an array of information - * about the stream wrapper, as returned by hook_stream_wrappers(). If $filter - * is omitted or set to STREAM_WRAPPERS_ALL, the entire Drupal stream wrapper - * registry is returned. Otherwise only the stream wrappers whose 'type' - * bitmask has an on bit for each bit specified in $filter are returned. - * - * @see hook_stream_wrappers() - * @see hook_stream_wrappers_alter() - */ -function file_get_stream_wrappers($filter = STREAM_WRAPPERS_ALL) { - $wrappers_storage = &drupal_static(__FUNCTION__); - - if (!isset($wrappers_storage)) { - $wrappers = module_invoke_all('stream_wrappers'); - foreach ($wrappers as $scheme => $info) { - // Add defaults. - $wrappers[$scheme] += array('type' => STREAM_WRAPPERS_NORMAL); - } - drupal_alter('stream_wrappers', $wrappers); - $existing = stream_get_wrappers(); - foreach ($wrappers as $scheme => $info) { - // We only register classes that implement our interface. - if (in_array('DrupalStreamWrapperInterface', class_implements($info['class']), TRUE)) { - // Record whether we are overriding an existing scheme. - if (in_array($scheme, $existing, TRUE)) { - $wrappers[$scheme]['override'] = TRUE; - stream_wrapper_unregister($scheme); - } - else { - $wrappers[$scheme]['override'] = FALSE; - } - if (($info['type'] & STREAM_WRAPPERS_LOCAL) == STREAM_WRAPPERS_LOCAL) { - stream_wrapper_register($scheme, $info['class']); - } - else { - stream_wrapper_register($scheme, $info['class'], STREAM_IS_URL); - } - } - // Pre-populate the static cache with the filters most typically used. - $wrappers_storage[STREAM_WRAPPERS_ALL][$scheme] = $wrappers[$scheme]; - if (($info['type'] & STREAM_WRAPPERS_WRITE_VISIBLE) == STREAM_WRAPPERS_WRITE_VISIBLE) { - $wrappers_storage[STREAM_WRAPPERS_WRITE_VISIBLE][$scheme] = $wrappers[$scheme]; - } - } - } - - if (!isset($wrappers_storage[$filter])) { - $wrappers_storage[$filter] = array(); - foreach ($wrappers_storage[STREAM_WRAPPERS_ALL] as $scheme => $info) { - // Bit-wise filter. - if (($info['type'] & $filter) == $filter) { - $wrappers_storage[$filter][$scheme] = $info; - } - } - } - - return $wrappers_storage[$filter]; -} - -/** - * Returns the stream wrapper class name for a given scheme. - * - * @param $scheme - * Stream scheme. - * - * @return - * Return string if a scheme has a registered handler, or FALSE. - */ -function file_stream_wrapper_get_class($scheme) { - $wrappers = file_get_stream_wrappers(); - return empty($wrappers[$scheme]) ? FALSE : $wrappers[$scheme]['class']; -} - -/** - * Returns the scheme of a URI (e.g. a stream). - * - * @param $uri - * A stream, referenced as "scheme://target". - * - * @return - * A string containing the name of the scheme, or FALSE if none. For example, - * the URI "public://example.txt" would return "public". - * - * @see file_uri_target() - */ -function file_uri_scheme($uri) { - $position = strpos($uri, '://'); - return $position ? substr($uri, 0, $position) : FALSE; -} - -/** - * Check that the scheme of a stream URI is valid. - * - * Confirms that there is a registered stream handler for the provided scheme - * and that it is callable. This is useful if you want to confirm a valid - * scheme without creating a new instance of the registered handler. - * - * @param $scheme - * A URI scheme, a stream is referenced as "scheme://target". - * - * @return - * Returns TRUE if the string is the name of a validated stream, - * or FALSE if the scheme does not have a registered handler. - */ -function file_stream_wrapper_valid_scheme($scheme) { - // Does the scheme have a registered handler that is callable? - $class = file_stream_wrapper_get_class($scheme); - if (class_exists($class)) { - return TRUE; - } - else { - return FALSE; - } -} - - -/** - * Returns the part of an URI after the schema. - * - * @param $uri - * A stream, referenced as "scheme://target". - * - * @return - * A string containing the target (path), or FALSE if none. - * For example, the URI "public://sample/test.txt" would return - * "sample/test.txt". - * - * @see file_uri_scheme() - */ -function file_uri_target($uri) { - $data = explode('://', $uri, 2); - - // Remove erroneous leading or trailing, forward-slashes and backslashes. - return count($data) == 2 ? trim($data[1], '\/') : FALSE; -} - -/** - * Get the default file stream implementation. - * - * @return - * 'public', 'private' or any other file scheme defined as the default. - */ -function file_default_scheme() { - return variable_get('file_default_scheme', 'public'); -} - -/** - * Normalizes a URI by making it syntactically correct. - * - * A stream is referenced as "scheme://target". - * - * The following actions are taken: - * - Remove trailing slashes from target - * - Trim erroneous leading slashes from target. e.g. ":///" becomes "://". - * - * @param $uri - * String reference containing the URI to normalize. - * - * @return - * The normalized URI. - */ -function file_stream_wrapper_uri_normalize($uri) { - $scheme = file_uri_scheme($uri); - - if ($scheme && file_stream_wrapper_valid_scheme($scheme)) { - $target = file_uri_target($uri); - - if ($target !== FALSE) { - $uri = $scheme . '://' . $target; - } - } - else { - // The default scheme is file:// - $url = 'file://' . $uri; - } - return $uri; -} - -/** - * Returns a reference to the stream wrapper class responsible for a given URI. - * - * The scheme determines the stream wrapper class that should be - * used by consulting the stream wrapper registry. - * - * @param $uri - * A stream, referenced as "scheme://target". - * - * @return - * Returns a new stream wrapper object appropriate for the given URI or FALSE - * if no registered handler could be found. For example, a URI of - * "private://example.txt" would return a new private stream wrapper object - * (DrupalPrivateStreamWrapper). - */ -function file_stream_wrapper_get_instance_by_uri($uri) { - $scheme = file_uri_scheme($uri); - $class = file_stream_wrapper_get_class($scheme); - if (class_exists($class)) { - $instance = new $class(); - $instance->setUri($uri); - return $instance; - } - else { - return FALSE; - } -} - -/** - * Returns a reference to the stream wrapper class responsible for a given scheme. - * - * This helper method returns a stream instance using a scheme. That is, the - * passed string does not contain a "://". For example, "public" is a scheme - * but "public://" is a URI (stream). This is because the later contains both - * a scheme and target despite target being empty. - * - * Note: the instance URI will be initialized to "scheme://" so that you can - * make the customary method calls as if you had retrieved an instance by URI. - * - * @param $scheme - * If the stream was "public://target", "public" would be the scheme. - * - * @return - * Returns a new stream wrapper object appropriate for the given $scheme. - * For example, for the public scheme a stream wrapper object - * (DrupalPublicStreamWrapper). - * FALSE is returned if no registered handler could be found. - */ -function file_stream_wrapper_get_instance_by_scheme($scheme) { - $class = file_stream_wrapper_get_class($scheme); - if (class_exists($class)) { - $instance = new $class(); - $instance->setUri($scheme . '://'); - return $instance; - } - else { - return FALSE; - } -} - -/** - * Creates a web-accessible URL for a stream to an external or local file. - * - * Compatibility: normal paths and stream wrappers. - * @see http://drupal.org/node/515192 - * - * There are two kinds of local files: - * - "managed files", i.e. those stored by a Drupal-compatible stream wrapper. - * These are files that have either been uploaded by users or were generated - * automatically (for example through CSS aggregation). - * - "shipped files", i.e. those outside of the files directory, which ship as - * part of Drupal core or contributed modules or themes. - * - * @param $uri - * The URI to a file for which we need an external URL, or the path to a - * shipped file. - * - * @return - * A string containing a URL that may be used to access the file. - * If the provided string already contains a preceding 'http', 'https', or - * '/', nothing is done and the same string is returned. If a stream wrapper - * could not be found to generate an external URL, then FALSE is returned. - */ -function file_create_url($uri) { - // Allow the URI to be altered, e.g. to serve a file from a CDN or static - // file server. - drupal_alter('file_url', $uri); - - $scheme = file_uri_scheme($uri); - - if (!$scheme) { - // Allow for: - // - root-relative URIs (e.g. /foo.jpg in http://example.com/foo.jpg) - // - protocol-relative URIs (e.g. //bar.jpg, which is expanded to - // http://example.com/bar.jpg by the browser when viewing a page over - // HTTP and to https://example.com/bar.jpg when viewing a HTTPS page) - // Both types of relative URIs are characterized by a leading slash, hence - // we can use a single check. - if (drupal_substr($uri, 0, 1) == '/') { - return $uri; - } - else { - // If this is not a properly formatted stream, then it is a shipped file. - // Therefore, return the urlencoded URI with the base URL prepended. - return $GLOBALS['base_url'] . '/' . drupal_encode_path($uri); - } - } - elseif ($scheme == 'http' || $scheme == 'https') { - // Check for http so that we don't have to implement getExternalUrl() for - // the http wrapper. - return $uri; - } - else { - // Attempt to return an external URL using the appropriate wrapper. - if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) { - return $wrapper->getExternalUrl(); - } - else { - return FALSE; - } - } -} - -/** - * Check that the directory exists and is writable. - * - * Directories need to have execute permissions to be considered a directory by - * FTP servers, etc. - * - * @param $directory - * A string reference containing the name of a directory path or URI. A - * trailing slash will be trimmed from a path. - * @param $options - * A bitmask to indicate if the directory should be created if it does - * not exist (FILE_CREATE_DIRECTORY) or made writable if it is read-only - * (FILE_MODIFY_PERMISSIONS). - * - * @return - * TRUE if the directory exists (or was created) and is writable. FALSE - * otherwise. - */ -function file_prepare_directory(&$directory, $options = FILE_MODIFY_PERMISSIONS) { - if (!file_stream_wrapper_valid_scheme(file_uri_scheme($directory))) { - // Only trim if we're not dealing with a stream. - $directory = rtrim($directory, '/\\'); - } - - // Check if directory exists. - if (!is_dir($directory)) { - // Let mkdir() recursively create directories and use the default directory - // permissions. - if (($options & FILE_CREATE_DIRECTORY) && @drupal_mkdir($directory, NULL, TRUE)) { - return drupal_chmod($directory); - } - return FALSE; - } - // The directory exists, so check to see if it is writable. - $writable = is_writable($directory); - if (!$writable && ($options & FILE_MODIFY_PERMISSIONS)) { - return drupal_chmod($directory); - } - - return $writable; -} - -/** - * If missing, create a .htaccess file in each Drupal files directory. - */ -function file_ensure_htaccess() { - file_save_htaccess('public://', FALSE); - if (variable_get('file_private_path', FALSE)) { - file_save_htaccess('private://', TRUE); - } - file_save_htaccess('temporary://', TRUE); -} - -/** - * Creates an .htaccess file in the given directory. - * - * @param $directory - * The directory. - * @param $private - * FALSE indicates that $directory should be an open and public directory. - * The default is TRUE which indicates a private and protected directory. - */ -function file_save_htaccess($directory, $private = TRUE) { - if (file_uri_scheme($directory)) { - $directory = file_stream_wrapper_uri_normalize($directory); - } - else { - $directory = rtrim($directory, '/\\'); - } - $htaccess_path = $directory . '/.htaccess'; - - if (file_exists($htaccess_path)) { - // Short circuit if the .htaccess file already exists. - return; - } - - if ($private) { - // Private .htaccess file. - $htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nDeny from all\nOptions None\nOptions +FollowSymLinks"; - } - else { - // Public .htaccess file. - $htaccess_lines = "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks"; - } - - // Write the .htaccess file. - if (file_put_contents($htaccess_path, $htaccess_lines)) { - drupal_chmod($htaccess_path, 0444); - } - else { - $variables = array('%directory' => $directory, '!htaccess' => '<br />' . nl2br(check_plain($htaccess_lines))); - watchdog('security', "Security warning: Couldn't write .htaccess file. Please create a .htaccess file in your %directory directory which contains the following lines: <code>!htaccess</code>", $variables, WATCHDOG_ERROR); - } -} - -/** - * Loads file objects from the database. - * - * @param $fids - * An array of file IDs. - * @param $conditions - * (deprecated) An associative array of conditions on the {file_managed} - * 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. - * - * @return - * An array of file objects, indexed by fid. - * - * @see hook_file_load() - * @see file_load() - * @see entity_load() - * @see EntityFieldQuery - * - * @todo Remove $conditions in Drupal 8. - */ -function file_load_multiple($fids = array(), $conditions = array()) { - return entity_load('file', $fids, $conditions); -} - -/** - * Load a file object from the database. - * - * @param $fid - * A file ID. - * - * @return - * A file object. - * - * @see hook_file_load() - * @see file_load_multiple() - */ -function file_load($fid) { - $files = file_load_multiple(array($fid), array()); - return reset($files); -} - -/** - * Save a file object to the database. - * - * If the $file->fid is not set a new record will be added. - * - * @param $file - * A file object returned by file_load(). - * - * @return - * The updated file object. - * - * @see hook_file_insert() - * @see hook_file_update() - */ -function file_save(stdClass $file) { - $file->timestamp = REQUEST_TIME; - $file->filesize = filesize($file->uri); - - // Load the stored entity, if any. - if (!empty($file->fid) && !isset($file->original)) { - $file->original = entity_load_unchanged('file', $file->fid); - } - - module_invoke_all('file_presave', $file); - module_invoke_all('entity_presave', $file, 'file'); - - if (empty($file->fid)) { - drupal_write_record('file_managed', $file); - // Inform modules about the newly added file. - module_invoke_all('file_insert', $file); - module_invoke_all('entity_insert', $file, 'file'); - } - else { - drupal_write_record('file_managed', $file, 'fid'); - // Inform modules that the file has been updated. - module_invoke_all('file_update', $file); - module_invoke_all('entity_update', $file, 'file'); - } - - unset($file->original); - return $file; -} - -/** - * Determines where a file is used. - * - * @param $file - * A file object. - * - * @return - * A nested array with usage data. The first level is keyed by module name, - * the second by object type and the third by the object id. The value - * of the third level contains the usage count. - * - * @see file_usage_add() - * @see file_usage_delete() - */ -function file_usage_list(stdClass $file) { - $result = db_select('file_usage', 'f') - ->fields('f', array('module', 'type', 'id', 'count')) - ->condition('fid', $file->fid) - ->condition('count', 0, '>') - ->execute(); - $references = array(); - foreach ($result as $usage) { - $references[$usage->module][$usage->type][$usage->id] = $usage->count; - } - return $references; -} - -/** - * Records that a module is using a file. - * - * This usage information will be queried during file_delete() to ensure that - * a file is not in use before it is physically removed from disk. - * - * Examples: - * - A module that associates files with nodes, so $type would be - * 'node' and $id would be the node's nid. Files for all revisions are stored - * within a single nid. - * - The User module associates an image with a user, so $type would be 'user' - * and the $id would be the user's uid. - * - * @param $file - * A file object. - * @param $module - * The name of the module using the file. - * @param $type - * The type of the object that contains the referenced file. - * @param $id - * The unique, numeric ID of the object containing the referenced file. - * @param $count - * (optional) The number of references to add to the object. Defaults to 1. - * - * @see file_usage_list() - * @see file_usage_delete() - */ -function file_usage_add(stdClass $file, $module, $type, $id, $count = 1) { - db_merge('file_usage') - ->key(array( - 'fid' => $file->fid, - 'module' => $module, - 'type' => $type, - 'id' => $id, - )) - ->fields(array('count' => $count)) - ->expression('count', 'count + :count', array(':count' => $count)) - ->execute(); -} - -/** - * Removes a record to indicate that a module is no longer using a file. - * - * The file_delete() function is typically called after removing a file usage - * to remove the record from the file_managed table and delete the file itself. - * - * @param $file - * A file object. - * @param $module - * The name of the module using the file. - * @param $type - * (optional) The type of the object that contains the referenced file. May - * be omitted if all module references to a file are being deleted. - * @param $id - * (optional) The unique, numeric ID of the object containing the referenced - * file. May be omitted if all module references to a file are being deleted. - * @param $count - * (optional) The number of references to delete from the object. Defaults to - * 1. 0 may be specified to delete all references to the file within a - * specific object. - * - * @see file_usage_add() - * @see file_usage_list() - * @see file_delete() - */ -function file_usage_delete(stdClass $file, $module, $type = NULL, $id = NULL, $count = 1) { - // Delete rows that have a exact or less value to prevent empty rows. - $query = db_delete('file_usage') - ->condition('module', $module) - ->condition('fid', $file->fid); - if ($type && $id) { - $query - ->condition('type', $type) - ->condition('id', $id); - } - if ($count) { - $query->condition('count', $count, '<='); - } - $result = $query->execute(); - - // If the row has more than the specified count decrement it by that number. - if (!$result && $count > 0) { - $query = db_update('file_usage') - ->condition('module', $module) - ->condition('fid', $file->fid); - if ($type && $id) { - $query - ->condition('type', $type) - ->condition('id', $id); - } - $query->expression('count', 'count - :count', array(':count' => $count)); - $query->execute(); - } -} - -/** - * Copies a file to a new location and adds a file record to the database. - * - * This function should be used when manipulating files that have records - * stored in the database. This is a powerful function that in many ways - * performs like an advanced version of copy(). - * - Checks if $source and $destination are valid and readable/writable. - * - Checks that $source is not equal to $destination; if they are an error - * is reported. - * - If file already exists in $destination either the call will error out, - * replace the file or rename the file based on the $replace parameter. - * - Adds the new file to the files database. If the source file is a - * temporary file, the resulting file will also be a temporary file. See - * file_save_upload() for details on temporary files. - * - * @param $source - * A file object. - * @param $destination - * A string containing the destination that $source should be copied to. - * This must be a stream wrapper URI. - * @param $replace - * Replace behavior when the destination file already exists: - * - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with - * the destination name exists then its database entry will be updated. If - * no database entry is found then a new one will be created. - * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is - * unique. - * - FILE_EXISTS_ERROR - Do nothing and return FALSE. - * - * @return - * File object if the copy is successful, or FALSE in the event of an error. - * - * @see file_unmanaged_copy() - * @see hook_file_copy() - */ -function file_copy(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { - if (!file_valid_uri($destination)) { - if (($realpath = drupal_realpath($source->uri)) !== FALSE) { - watchdog('file', 'File %file (%realpath) could not be copied because the destination %destination is invalid. This is often caused by improper use of file_copy() or a missing stream wrapper.', array('%file' => $source->uri, '%realpath' => $realpath, '%destination' => $destination)); - } - else { - watchdog('file', 'File %file could not be copied because the destination %destination is invalid. This is often caused by improper use of file_copy() or a missing stream wrapper.', array('%file' => $source->uri, '%destination' => $destination)); - } - drupal_set_message(t('The specified file %file could not be copied because the destination is invalid. More information is available in the system log.', array('%file' => $source->uri)), 'error'); - return FALSE; - } - - if ($uri = file_unmanaged_copy($source->uri, $destination, $replace)) { - $file = clone $source; - $file->fid = NULL; - $file->uri = $uri; - $file->filename = basename($uri); - // If we are replacing an existing file re-use its database record. - if ($replace == FILE_EXISTS_REPLACE) { - $existing_files = file_load_multiple(array(), array('uri' => $uri)); - if (count($existing_files)) { - $existing = reset($existing_files); - $file->fid = $existing->fid; - $file->filename = $existing->filename; - } - } - // If we are renaming around an existing file (rather than a directory), - // use its basename for the filename. - elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) { - $file->filename = basename($destination); - } - - $file = file_save($file); - - // Inform modules that the file has been copied. - module_invoke_all('file_copy', $file, $source); - - return $file; - } - return FALSE; -} - -/** - * Determine whether the URI has a valid scheme for file API operations. - * - * There must be a scheme and it must be a Drupal-provided scheme like - * 'public', 'private', 'temporary', or an extension provided with - * hook_stream_wrappers(). - * - * @param $uri - * The URI to be tested. - * - * @return - * TRUE if the URI is allowed. - */ -function file_valid_uri($uri) { - // Assert that the URI has an allowed scheme. Barepaths are not allowed. - $uri_scheme = file_uri_scheme($uri); - if (empty($uri_scheme) || !file_stream_wrapper_valid_scheme($uri_scheme)) { - return FALSE; - } - return TRUE; -} - -/** - * Copies a file to a new location without invoking the file API. - * - * This is a powerful function that in many ways performs like an advanced - * version of copy(). - * - Checks if $source and $destination are valid and readable/writable. - * - Checks that $source is not equal to $destination; if they are an error - * is reported. - * - If file already exists in $destination either the call will error out, - * replace the file or rename the file based on the $replace parameter. - * - * @param $source - * A string specifying the filepath or URI of the source file. - * @param $destination - * A URI containing the destination that $source should be copied to. The - * URI may be a bare filepath (without a scheme) and in that case the default - * scheme (file://) will be used. If this value is omitted, Drupal's default - * files scheme will be used, usually "public://". - * @param $replace - * Replace behavior when the destination file already exists: - * - FILE_EXISTS_REPLACE - Replace the existing file. - * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is - * unique. - * - FILE_EXISTS_ERROR - Do nothing and return FALSE. - * - * @return - * The path to the new file, or FALSE in the event of an error. - * - * @see file_copy() - */ -function file_unmanaged_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { - $original_source = $source; - $original_destination = $destination; - - // Assert that the source file actually exists. - if (!file_exists($source)) { - // @todo Replace drupal_set_message() calls with exceptions instead. - drupal_set_message(t('The specified file %file could not be copied because no file by that name exists. Please check that you supplied the correct filename.', array('%file' => $original_source)), 'error'); - if (($realpath = drupal_realpath($original_source)) !== FALSE) { - watchdog('file', 'File %file (%realpath) could not be copied because it does not exist.', array('%file' => $original_source, '%realpath' => $realpath)); - } - else { - watchdog('file', 'File %file could not be copied because it does not exist.', array('%file' => $original_source)); - } - return FALSE; - } - - // Build a destination URI if necessary. - if (!isset($destination)) { - $destination = file_build_uri(basename($source)); - } - - - // Prepare the destination directory. - if (file_prepare_directory($destination)) { - // The destination is already a directory, so append the source basename. - $destination = file_stream_wrapper_uri_normalize($destination . '/' . basename($source)); - } - else { - // Perhaps $destination is a dir/file? - $dirname = drupal_dirname($destination); - if (!file_prepare_directory($dirname)) { - // The destination is not valid. - watchdog('file', 'File %file could not be copied because the destination directory %destination is not configured correctly.', array('%file' => $original_source, '%destination' => $dirname)); - drupal_set_message(t('The specified file %file could not be copied because the destination directory is not properly configured. This may be caused by a problem with file or directory permissions. More information is available in the system log.', array('%file' => $original_source)), 'error'); - return FALSE; - } - } - - // Determine whether we can perform this operation based on overwrite rules. - $destination = file_destination($destination, $replace); - if ($destination === FALSE) { - drupal_set_message(t('The file %file could not be copied because a file by that name already exists in the destination directory.', array('%file' => $original_source)), 'error'); - watchdog('file', 'File %file could not be copied because a file by that name already exists in the destination directory (%directory)', array('%file' => $original_source, '%destination' => $destination)); - return FALSE; - } - - // Assert that the source and destination filenames are not the same. - $real_source = drupal_realpath($source); - $real_destination = drupal_realpath($destination); - if ($source == $destination || ($real_source !== FALSE) && ($real_source == $real_destination)) { - drupal_set_message(t('The specified file %file was not copied because it would overwrite itself.', array('%file' => $source)), 'error'); - watchdog('file', 'File %file could not be copied because it would overwrite itself.', array('%file' => $source)); - return FALSE; - } - // Make sure the .htaccess files are present. - file_ensure_htaccess(); - // Perform the copy operation. - if (!@copy($source, $destination)) { - watchdog('file', 'The specified file %file could not be copied to %destination.', array('%file' => $source, '%destination' => $destination), WATCHDOG_ERROR); - return FALSE; - } - - // Set the permissions on the new file. - drupal_chmod($destination); - - return $destination; -} - -/** - * Given a relative path, construct a URI into Drupal's default files location. - */ -function file_build_uri($path) { - $uri = file_default_scheme() . '://' . $path; - return file_stream_wrapper_uri_normalize($uri); -} - -/** - * Determines the destination path for a file depending on how replacement of - * existing files should be handled. - * - * @param $destination - * A string specifying the desired final URI or filepath. - * @param $replace - * Replace behavior when the destination file already exists. - * - FILE_EXISTS_REPLACE - Replace the existing file. - * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is - * unique. - * - FILE_EXISTS_ERROR - Do nothing and return FALSE. - * - * @return - * The destination filepath, or FALSE if the file already exists - * and FILE_EXISTS_ERROR is specified. - */ -function file_destination($destination, $replace) { - if (file_exists($destination)) { - switch ($replace) { - case FILE_EXISTS_REPLACE: - // Do nothing here, we want to overwrite the existing file. - break; - - case FILE_EXISTS_RENAME: - $basename = basename($destination); - $directory = drupal_dirname($destination); - $destination = file_create_filename($basename, $directory); - break; - - case FILE_EXISTS_ERROR: - // Error reporting handled by calling function. - return FALSE; - } - } - return $destination; -} - -/** - * Move a file to a new location and update the file's database entry. - * - * Moving a file is performed by copying the file to the new location and then - * deleting the original. - * - Checks if $source and $destination are valid and readable/writable. - * - Performs a file move if $source is not equal to $destination. - * - If file already exists in $destination either the call will error out, - * replace the file or rename the file based on the $replace parameter. - * - Adds the new file to the files database. - * - * @param $source - * A file object. - * @param $destination - * A string containing the destination that $source should be moved to. - * This must be a stream wrapper URI. - * @param $replace - * Replace behavior when the destination file already exists: - * - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with - * the destination name exists then its database entry will be updated and - * file_delete() called on the source file after hook_file_move is called. - * If no database entry is found then the source files record will be - * updated. - * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is - * unique. - * - FILE_EXISTS_ERROR - Do nothing and return FALSE. - * - * @return - * Resulting file object for success, or FALSE in the event of an error. - * - * @see file_unmanaged_move() - * @see hook_file_move() - */ -function file_move(stdClass $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { - if (!file_valid_uri($destination)) { - if (($realpath = drupal_realpath($source->uri)) !== FALSE) { - watchdog('file', 'File %file (%realpath) could not be moved because the destination %destination is invalid. This may be caused by improper use of file_move() or a missing stream wrapper.', array('%file' => $source->uri, '%realpath' => $realpath, '%destination' => $destination)); - } - else { - watchdog('file', 'File %file could not be moved because the destination %destination is invalid. This may be caused by improper use of file_move() or a missing stream wrapper.', array('%file' => $source->uri, '%destination' => $destination)); - } - drupal_set_message(t('The specified file %file could not be moved because the destination is invalid. More information is available in the system log.', array('%file' => $source->uri)), 'error'); - return FALSE; - } - - if ($uri = file_unmanaged_move($source->uri, $destination, $replace)) { - $delete_source = FALSE; - - $file = clone $source; - $file->uri = $uri; - // If we are replacing an existing file re-use its database record. - if ($replace == FILE_EXISTS_REPLACE) { - $existing_files = file_load_multiple(array(), array('uri' => $uri)); - if (count($existing_files)) { - $existing = reset($existing_files); - $delete_source = TRUE; - $file->fid = $existing->fid; - } - } - // If we are renaming around an existing file (rather than a directory), - // use its basename for the filename. - elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) { - $file->filename = basename($destination); - } - - $file = file_save($file); - - // Inform modules that the file has been moved. - module_invoke_all('file_move', $file, $source); - - if ($delete_source) { - // Try a soft delete to remove original if it's not in use elsewhere. - file_delete($source); - } - - return $file; - } - return FALSE; -} - -/** - * Move a file to a new location without calling any hooks or making any - * changes to the database. - * - * @param $source - * A string specifying the filepath or URI of the original file. - * @param $destination - * A string containing the destination that $source should be moved to. - * This must be a stream wrapper URI. If this value is omitted, Drupal's - * default files scheme will be used, usually "public://". - * @param $replace - * Replace behavior when the destination file already exists: - * - FILE_EXISTS_REPLACE - Replace the existing file. - * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is - * unique. - * - FILE_EXISTS_ERROR - Do nothing and return FALSE. - * - * @return - * The URI of the moved file, or FALSE in the event of an error. - * - * @see file_move() - */ -function file_unmanaged_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { - $filepath = file_unmanaged_copy($source, $destination, $replace); - if ($filepath == FALSE || file_unmanaged_delete($source) == FALSE) { - return FALSE; - } - return $filepath; -} - -/** - * Modify a filename as needed for security purposes. - * - * Munging a file name prevents unknown file extensions from masking exploit - * files. When web servers such as Apache decide how to process a URL request, - * they use the file extension. If the extension is not recognized, Apache - * skips that extension and uses the previous file extension. For example, if - * the file being requested is exploit.php.pps, and Apache does not recognize - * the '.pps' extension, it treats the file as PHP and executes it. To make - * this file name safe for Apache and prevent it from executing as PHP, the - * .php extension is "munged" into .php_, making the safe file name - * exploit.php_.pps. - * - * Specifically, this function adds an underscore to all extensions that are - * between 2 and 5 characters in length, internal to the file name, and not - * included in $extensions. - * - * Function behavior is also controlled by the Drupal variable - * 'allow_insecure_uploads'. If 'allow_insecure_uploads' evaluates to TRUE, no - * alterations will be made, if it evaluates to FALSE, the filename is 'munged'. - * - * @param $filename - * File name to modify. - * @param $extensions - * A space-separated list of extensions that should not be altered. - * @param $alerts - * If TRUE, drupal_set_message() will be called to display a message if the - * file name was changed. - * - * @return - * The potentially modified $filename. - */ -function file_munge_filename($filename, $extensions, $alerts = TRUE) { - $original = $filename; - - // Allow potentially insecure uploads for very savvy users and admin - if (!variable_get('allow_insecure_uploads', 0)) { - $whitelist = array_unique(explode(' ', trim($extensions))); - - // Split the filename up by periods. The first part becomes the basename - // the last part the final extension. - $filename_parts = explode('.', $filename); - $new_filename = array_shift($filename_parts); // Remove file basename. - $final_extension = array_pop($filename_parts); // Remove final extension. - - // Loop through the middle parts of the name and add an underscore to the - // end of each section that could be a file extension but isn't in the list - // of allowed extensions. - foreach ($filename_parts as $filename_part) { - $new_filename .= '.' . $filename_part; - if (!in_array($filename_part, $whitelist) && preg_match("/^[a-zA-Z]{2,5}\d?$/", $filename_part)) { - $new_filename .= '_'; - } - } - $filename = $new_filename . '.' . $final_extension; - - if ($alerts && $original != $filename) { - drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $filename))); - } - } - - return $filename; -} - -/** - * Undo the effect of upload_munge_filename(). - * - * @param $filename - * String with the filename to be unmunged. - * - * @return - * An unmunged filename string. - */ -function file_unmunge_filename($filename) { - return str_replace('_.', '.', $filename); -} - -/** - * Create a full file path from a directory and filename. - * - * If a file with the specified name already exists, an alternative will be - * used. - * - * @param $basename - * String filename - * @param $directory - * String containing the directory or parent URI. - * - * @return - * File path consisting of $directory and a unique filename based off - * of $basename. - */ -function file_create_filename($basename, $directory) { - // Strip control characters (ASCII value < 32). Though these are allowed in - // some filesystems, not many applications handle them well. - $basename = preg_replace('/[\x00-\x1F]/u', '_', $basename); - if (substr(PHP_OS, 0, 3) == 'WIN') { - // These characters are not allowed in Windows filenames - $basename = str_replace(array(':', '*', '?', '"', '<', '>', '|'), '_', $basename); - } - - // A URI or path may already have a trailing slash or look like "public://". - if (substr($directory, -1) == '/') { - $separator = ''; - } - else { - $separator = '/'; - } - - $destination = $directory . $separator . $basename; - - if (file_exists($destination)) { - // Destination file already exists, generate an alternative. - $pos = strrpos($basename, '.'); - if ($pos !== FALSE) { - $name = substr($basename, 0, $pos); - $ext = substr($basename, $pos); - } - else { - $name = $basename; - $ext = ''; - } - - $counter = 0; - do { - $destination = $directory . $separator . $name . '_' . $counter++ . $ext; - } while (file_exists($destination)); - } - - return $destination; -} - -/** - * Delete a file and its database record. - * - * If the $force parameter is not TRUE, file_usage_list() will be called to - * determine if the file is being used by any modules. If the file is being - * used the delete will be canceled. - * - * @param $file - * A file object. - * @param $force - * Boolean indicating that the file should be deleted even if the file is - * reported as in use by the file_usage table. - * - * @return mixed - * TRUE for success, FALSE in the event of an error, or an array if the file - * is being used by any modules. - * - * @see file_unmanaged_delete() - * @see file_usage_list() - * @see file_usage_delete() - * @see hook_file_delete() - */ -function file_delete(stdClass $file, $force = FALSE) { - if (!file_valid_uri($file->uri)) { - if (($realpath = drupal_realpath($file->uri)) !== FALSE) { - watchdog('file', 'File %file (%realpath) could not be deleted because it is not a valid URI. This may be caused by improper use of file_delete() or a missing stream wrapper.', array('%file' => $file->uri, '%realpath' => $realpath)); - } - else { - watchdog('file', 'File %file could not be deleted because it is not a valid URI. This may be caused by improper use of file_delete() or a missing stream wrapper.', array('%file' => $file->uri)); - } - drupal_set_message(t('The specified file %file could not be deleted because it is not a valid URI. More information is available in the system log.', array('%file' => $file->uri)), 'error'); - return FALSE; - } - - // If any module still has a usage entry in the file_usage table, the file - // will not be deleted, but file_delete() will return a populated array - // that tests as TRUE. - if (!$force && ($references = file_usage_list($file))) { - return $references; - } - - // Let other modules clean up any references to the deleted file. - module_invoke_all('file_delete', $file); - module_invoke_all('entity_delete', $file, 'file'); - - // Make sure the file is deleted before removing its row from the - // database, so UIs can still find the file in the database. - if (file_unmanaged_delete($file->uri)) { - db_delete('file_managed')->condition('fid', $file->fid)->execute(); - db_delete('file_usage')->condition('fid', $file->fid)->execute(); - return TRUE; - } - return FALSE; -} - -/** - * Delete a file without calling any hooks or making any changes to the - * database. - * - * This function should be used when the file to be deleted does not have an - * entry recorded in the files table. - * - * @param $path - * A string containing a file path or (streamwrapper) URI. - * - * @return - * TRUE for success or path does not exist, or FALSE in the event of an - * error. - * - * @see file_delete() - * @see file_unmanaged_delete_recursive() - */ -function file_unmanaged_delete($path) { - if (is_dir($path)) { - watchdog('file', '%path is a directory and cannot be removed using file_unmanaged_delete().', array('%path' => $path), WATCHDOG_ERROR); - return FALSE; - } - if (is_file($path)) { - return drupal_unlink($path); - } - // Return TRUE for non-existent file, but log that nothing was actually - // deleted, as the current state is the intended result. - if (!file_exists($path)) { - watchdog('file', 'The file %path was not deleted because it does not exist.', array('%path' => $path), WATCHDOG_NOTICE); - return TRUE; - } - // We cannot handle anything other than files and directories. Log an error - // for everything else (sockets, symbolic links, etc). - watchdog('file', 'The file %path is not of a recognized type so it was not deleted.', array('%path' => $path), WATCHDOG_ERROR); - return FALSE; -} - -/** - * Recursively delete all files and directories in the specified filepath. - * - * If the specified path is a directory then the function will call itself - * recursively to process the contents. Once the contents have been removed the - * directory will also be removed. - * - * If the specified path is a file then it will be passed to - * file_unmanaged_delete(). - * - * Note that this only deletes visible files with write permission. - * - * @param $path - * A string containing either an URI or a file or directory path. - * - * @return - * TRUE for success or if path does not exist, FALSE in the event of an - * error. - * - * @see file_unmanaged_delete() - */ -function file_unmanaged_delete_recursive($path) { - if (is_dir($path)) { - $dir = dir($path); - while (($entry = $dir->read()) !== FALSE) { - if ($entry == '.' || $entry == '..') { - continue; - } - $entry_path = $path . '/' . $entry; - file_unmanaged_delete_recursive($entry_path); - } - $dir->close(); - - return drupal_rmdir($path); - } - return file_unmanaged_delete($path); -} - -/** - * Determine total disk space used by a single user or the whole filesystem. - * - * @param $uid - * Optional. A user id, specifying NULL returns the total space used by all - * non-temporary files. - * @param $status - * Optional. The file status to consider. The default is to only - * consider files in status FILE_STATUS_PERMANENT. - * - * @return - * An integer containing the number of bytes used. - */ -function file_space_used($uid = NULL, $status = FILE_STATUS_PERMANENT) { - $query = db_select('file_managed', 'f'); - $query->condition('f.status', $status); - $query->addExpression('SUM(f.filesize)', 'filesize'); - if (isset($uid)) { - $query->condition('f.uid', $uid); - } - return $query->execute()->fetchField(); -} - -/** - * Saves a file upload to a new location. - * - * The file will be added to the {file_managed} table as a temporary file. - * Temporary files are periodically cleaned. To make the file a permanent file, - * assign the status and use file_save() to save the changes. - * - * @param $source - * A string specifying the filepath or URI of the uploaded file to save. - * @param $validators - * An optional, associative array of callback functions used to validate the - * file. See file_validate() for a full discussion of the array format. - * If no extension validator is provided it will default to a limited safe - * list of extensions which is as follows: "jpg jpeg gif png txt - * doc xls pdf ppt pps odt ods odp". To allow all extensions you must - * explicitly set the 'file_validate_extensions' validator to an empty array - * (Beware: this is not safe and should only be allowed for trusted users, if - * at all). - * @param $destination - * A string containing the URI $source should be copied to. - * This must be a stream wrapper URI. If this value is omitted, Drupal's - * temporary files scheme will be used ("temporary://"). - * @param $replace - * Replace behavior when the destination file already exists: - * - FILE_EXISTS_REPLACE: Replace the existing file. - * - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename is - * unique. - * - FILE_EXISTS_ERROR: Do nothing and return FALSE. - * - * @return - * An object containing the file information if the upload succeeded, FALSE - * in the event of an error, or NULL if no file was uploaded. The - * documentation for the "File interface" group, which you can find under - * Related topics, or the header at the top of this file, documents the - * components of a file object. In addition to the standard components, - * this function adds: - * - source: Path to the file before it is moved. - * - destination: Path to the file after it is moved (same as 'uri'). - */ -function file_save_upload($source, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME) { - global $user; - static $upload_cache; - - // Return cached objects without processing since the file will have - // already been processed and the paths in _FILES will be invalid. - if (isset($upload_cache[$source])) { - return $upload_cache[$source]; - } - - // Make sure there's an upload to process. - if (empty($_FILES['files']['name'][$source])) { - return NULL; - } - - // Check for file upload errors and return FALSE if a lower level system - // error occurred. For a complete list of errors: - // See http://php.net/manual/en/features.file-upload.errors.php. - switch ($_FILES['files']['error'][$source]) { - case UPLOAD_ERR_INI_SIZE: - case UPLOAD_ERR_FORM_SIZE: - drupal_set_message(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $_FILES['files']['name'][$source], '%maxsize' => format_size(file_upload_max_size()))), 'error'); - return FALSE; - - case UPLOAD_ERR_PARTIAL: - case UPLOAD_ERR_NO_FILE: - drupal_set_message(t('The file %file could not be saved because the upload did not complete.', array('%file' => $_FILES['files']['name'][$source])), 'error'); - return FALSE; - - case UPLOAD_ERR_OK: - // Final check that this is a valid upload, if it isn't, use the - // default error handler. - if (is_uploaded_file($_FILES['files']['tmp_name'][$source])) { - break; - } - - // Unknown error - default: - drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $_FILES['files']['name'][$source])), 'error'); - return FALSE; - } - - // Begin building file object. - $file = new stdClass(); - $file->uid = $user->uid; - $file->status = 0; - $file->filename = trim(basename($_FILES['files']['name'][$source]), '.'); - $file->uri = $_FILES['files']['tmp_name'][$source]; - $file->filemime = file_get_mimetype($file->filename); - $file->filesize = $_FILES['files']['size'][$source]; - - $extensions = ''; - if (isset($validators['file_validate_extensions'])) { - if (isset($validators['file_validate_extensions'][0])) { - // Build the list of non-munged extensions if the caller provided them. - $extensions = $validators['file_validate_extensions'][0]; - } - else { - // If 'file_validate_extensions' is set and the list is empty then the - // caller wants to allow any extension. In this case we have to remove the - // validator or else it will reject all extensions. - unset($validators['file_validate_extensions']); - } - } - else { - // No validator was provided, so add one using the default list. - // Build a default non-munged safe list for file_munge_filename(). - $extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'; - $validators['file_validate_extensions'] = array(); - $validators['file_validate_extensions'][0] = $extensions; - } - - if (!empty($extensions)) { - // Munge the filename to protect against possible malicious extension hiding - // within an unknown file type (ie: filename.html.foo). - $file->filename = file_munge_filename($file->filename, $extensions); - } - - // Rename potentially executable files, to help prevent exploits (i.e. will - // rename filename.php.foo and filename.php to filename.php.foo.txt and - // filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads' - // evaluates to TRUE. - if (!variable_get('allow_insecure_uploads', 0) && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->filename) && (substr($file->filename, -4) != '.txt')) { - $file->filemime = 'text/plain'; - $file->uri .= '.txt'; - $file->filename .= '.txt'; - // The .txt extension may not be in the allowed list of extensions. We have - // to add it here or else the file upload will fail. - if (!empty($extensions)) { - $validators['file_validate_extensions'][0] .= ' txt'; - drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $file->filename))); - } - } - - // If the destination is not provided, use the temporary directory. - if (empty($destination)) { - $destination = 'temporary://'; - } - - // Assert that the destination contains a valid stream. - $destination_scheme = file_uri_scheme($destination); - if (!$destination_scheme || !file_stream_wrapper_valid_scheme($destination_scheme)) { - drupal_set_message(t('The file could not be uploaded because the destination %destination is invalid.', array('%destination' => $destination)), 'error'); - return FALSE; - } - - $file->source = $source; - // A URI may already have a trailing slash or look like "public://". - if (substr($destination, -1) != '/') { - $destination .= '/'; - } - $file->destination = file_destination($destination . $file->filename, $replace); - // If file_destination() returns FALSE then $replace == FILE_EXISTS_ERROR and - // there's an existing file so we need to bail. - if ($file->destination === FALSE) { - drupal_set_message(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', array('%source' => $source, '%directory' => $destination)), 'error'); - return FALSE; - } - - // Add in our check of the the file name length. - $validators['file_validate_name_length'] = array(); - - // Call the validation functions specified by this function's caller. - $errors = file_validate($file, $validators); - - // Check for errors. - if (!empty($errors)) { - $message = t('The specified file %name could not be uploaded.', array('%name' => $file->filename)); - if (count($errors) > 1) { - $message .= theme('item_list', array('items' => $errors)); - } - else { - $message .= ' ' . array_pop($errors); - } - form_set_error($source, $message); - return FALSE; - } - - // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary - // directory. This overcomes open_basedir restrictions for future file - // operations. - $file->uri = $file->destination; - if (!drupal_move_uploaded_file($_FILES['files']['tmp_name'][$source], $file->uri)) { - form_set_error($source, t('File upload error. Could not move uploaded file.')); - watchdog('file', 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->filename, '%destination' => $file->uri)); - return FALSE; - } - - // Set the permissions on the new file. - drupal_chmod($file->uri); - - // If we are replacing an existing file re-use its database record. - if ($replace == FILE_EXISTS_REPLACE) { - $existing_files = file_load_multiple(array(), array('uri' => $file->uri)); - if (count($existing_files)) { - $existing = reset($existing_files); - $file->fid = $existing->fid; - } - } - - // If we made it this far it's safe to record this file in the database. - if ($file = file_save($file)) { - // Add file to the cache. - $upload_cache[$source] = $file; - return $file; - } - return FALSE; -} - -/** - * Moves an uploaded file to a new location. - * - * PHP's move_uploaded_file() does not properly support streams if safe_mode - * or open_basedir are enabled, so this function fills that gap. - * - * Compatibility: normal paths and stream wrappers. - * @see http://drupal.org/node/515192 - * - * @param $filename - * The filename of the uploaded file. - * @param $uri - * A string containing the destination URI of the file. - * - * @return - * TRUE on success, or FALSE on failure. - * - * @see move_uploaded_file() - * @ingroup php_wrappers - */ -function drupal_move_uploaded_file($filename, $uri) { - $result = @move_uploaded_file($filename, $uri); - // PHP's move_uploaded_file() does not properly support streams if safe_mode - // or open_basedir are enabled so if the move failed, try finding a real path - // and retry the move operation. - if (!$result) { - if ($realpath = drupal_realpath($uri)) { - $result = move_uploaded_file($filename, $realpath); - } - else { - $result = move_uploaded_file($filename, $uri); - } - } - - return $result; -} - -/** - * Check that a file meets the criteria specified by the validators. - * - * After executing the validator callbacks specified hook_file_validate() will - * also be called to allow other modules to report errors about the file. - * - * @param $file - * A Drupal file object. - * @param $validators - * An optional, associative array of callback functions used to validate the - * file. The keys are function names and the values arrays of callback - * parameters which will be passed in after the file object. The - * functions should return an array of error messages; an empty array - * indicates that the file passed validation. The functions will be called in - * the order specified. - * - * @return - * An array containing validation error messages. - * - * @see hook_file_validate() - */ -function file_validate(stdClass &$file, $validators = array()) { - // Call the validation functions specified by this function's caller. - $errors = array(); - foreach ($validators as $function => $args) { - if (function_exists($function)) { - array_unshift($args, $file); - $errors = array_merge($errors, call_user_func_array($function, $args)); - } - } - - // Let other modules perform validation on the new file. - return array_merge($errors, module_invoke_all('file_validate', $file)); -} - -/** - * Check for files with names longer than we can store in the database. - * - * @param $file - * A Drupal file object. - * @return - * An array. If the file name is too long, it will contain an error message. - */ -function file_validate_name_length(stdClass $file) { - $errors = array(); - - if (empty($file->filename)) { - $errors[] = t("The file's name is empty. Please give a name to the file."); - } - if (strlen($file->filename) > 240) { - $errors[] = t("The file's name exceeds the 240 characters limit. Please rename the file and try again."); - } - return $errors; -} - -/** - * Check that the filename ends with an allowed extension. - * - * @param $file - * A Drupal file object. - * @param $extensions - * A string with a space separated list of allowed extensions. - * - * @return - * An array. If the file extension is not allowed, it will contain an error - * message. - * - * @see hook_file_validate() - */ -function file_validate_extensions(stdClass $file, $extensions) { - $errors = array(); - - $regex = '/\.(' . preg_replace('/ +/', '|', preg_quote($extensions)) . ')$/i'; - if (!preg_match($regex, $file->filename)) { - $errors[] = t('Only files with the following extensions are allowed: %files-allowed.', array('%files-allowed' => $extensions)); - } - return $errors; -} - -/** - * Check that the file's size is below certain limits. - * - * This check is not enforced for the user #1. - * - * @param $file - * A Drupal file object. - * @param $file_limit - * An integer specifying the maximum file size in bytes. Zero indicates that - * no limit should be enforced. - * @param $user_limit - * An integer specifying the maximum number of bytes the user is allowed. - * Zero indicates that no limit should be enforced. - * - * @return - * An array. If the file size exceeds limits, it will contain an error - * message. - * - * @see hook_file_validate() - */ -function file_validate_size(stdClass $file, $file_limit = 0, $user_limit = 0) { - global $user; - - $errors = array(); - - // Bypass validation for uid = 1. - if ($user->uid != 1) { - if ($file_limit && $file->filesize > $file_limit) { - $errors[] = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size($file->filesize), '%maxsize' => format_size($file_limit))); - } - - // Save a query by only calling file_space_used() when a limit is provided. - if ($user_limit && (file_space_used($user->uid) + $file->filesize) > $user_limit) { - $errors[] = t('The file is %filesize which would exceed your disk quota of %quota.', array('%filesize' => format_size($file->filesize), '%quota' => format_size($user_limit))); - } - } - return $errors; -} - -/** - * Check that the file is recognized by image_get_info() as an image. - * - * @param $file - * A Drupal file object. - * - * @return - * An array. If the file is not an image, it will contain an error message. - * - * @see hook_file_validate() - */ -function file_validate_is_image(stdClass $file) { - $errors = array(); - - $info = image_get_info($file->uri); - if (!$info || empty($info['extension'])) { - $errors[] = t('Only JPEG, PNG and GIF images are allowed.'); - } - - return $errors; -} - -/** - * Verify that image dimensions are within the specified maximum and minimum. - * - * Non-image files will be ignored. If a image toolkit is available the image - * will be scaled to fit within the desired maximum dimensions. - * - * @param $file - * A Drupal file object. This function may resize the file affecting its - * size. - * @param $maximum_dimensions - * An optional string in the form WIDTHxHEIGHT e.g. '640x480' or '85x85'. If - * an image toolkit is installed the image will be resized down to these - * dimensions. A value of 0 indicates no restriction on size, so resizing - * will be attempted. - * @param $minimum_dimensions - * An optional string in the form WIDTHxHEIGHT. This will check that the - * image meets a minimum size. A value of 0 indicates no restriction. - * - * @return - * An array. If the file is an image and did not meet the requirements, it - * will contain an error message. - * - * @see hook_file_validate() - */ -function file_validate_image_resolution(stdClass $file, $maximum_dimensions = 0, $minimum_dimensions = 0) { - $errors = array(); - - // Check first that the file is an image. - if ($info = image_get_info($file->uri)) { - if ($maximum_dimensions) { - // Check that it is smaller than the given dimensions. - list($width, $height) = explode('x', $maximum_dimensions); - if ($info['width'] > $width || $info['height'] > $height) { - // Try to resize the image to fit the dimensions. - if ($image = image_load($file->uri)) { - image_scale($image, $width, $height); - image_save($image); - $file->filesize = $image->info['file_size']; - drupal_set_message(t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', array('%dimensions' => $maximum_dimensions))); - } - else { - $errors[] = t('The image is too large; the maximum dimensions are %dimensions pixels.', array('%dimensions' => $maximum_dimensions)); - } - } - } - - if ($minimum_dimensions) { - // Check that it is larger than the given dimensions. - list($width, $height) = explode('x', $minimum_dimensions); - if ($info['width'] < $width || $info['height'] < $height) { - $errors[] = t('The image is too small; the minimum dimensions are %dimensions pixels.', array('%dimensions' => $minimum_dimensions)); - } - } - } - - return $errors; -} - -/** - * Save a string to the specified destination and create a database file entry. - * - * @param $data - * A string containing the contents of the file. - * @param $destination - * A string containing the destination URI. This must be a stream wrapper URI. - * If no value is provided, a randomized name will be generated and the file - * will be saved using Drupal's default files scheme, usually "public://". - * @param $replace - * Replace behavior when the destination file already exists: - * - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with - * the destination name exists then its database entry will be updated. If - * no database entry is found then a new one will be created. - * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is - * unique. - * - FILE_EXISTS_ERROR - Do nothing and return FALSE. - * - * @return - * A file object, or FALSE on error. - * - * @see file_unmanaged_save_data() - */ -function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME) { - global $user; - - if (empty($destination)) { - $destination = file_default_scheme() . '://'; - } - if (!file_valid_uri($destination)) { - watchdog('file', 'The data could not be saved because the destination %destination is invalid. This may be caused by improper use of file_save_data() or a missing stream wrapper.', array('%destination' => $destination)); - drupal_set_message(t('The data could not be saved because the destination is invalid. More information is available in the system log.'), 'error'); - return FALSE; - } - - if ($uri = file_unmanaged_save_data($data, $destination, $replace)) { - // Create a file object. - $file = new stdClass(); - $file->fid = NULL; - $file->uri = $uri; - $file->filename = basename($uri); - $file->filemime = file_get_mimetype($file->uri); - $file->uid = $user->uid; - $file->status = FILE_STATUS_PERMANENT; - // If we are replacing an existing file re-use its database record. - if ($replace == FILE_EXISTS_REPLACE) { - $existing_files = file_load_multiple(array(), array('uri' => $uri)); - if (count($existing_files)) { - $existing = reset($existing_files); - $file->fid = $existing->fid; - $file->filename = $existing->filename; - } - } - // If we are renaming around an existing file (rather than a directory), - // use its basename for the filename. - elseif ($replace == FILE_EXISTS_RENAME && is_file($destination)) { - $file->filename = basename($destination); - } - - return file_save($file); - } - return FALSE; -} - -/** - * Save a string to the specified destination without invoking file API. - * - * This function is identical to file_save_data() except the file will not be - * saved to the {file_managed} table and none of the file_* hooks will be - * called. - * - * @param $data - * A string containing the contents of the file. - * @param $destination - * A string containing the destination location. This must be a stream wrapper - * URI. If no value is provided, a randomized name will be generated and the - * file will be saved using Drupal's default files scheme, usually "public://". - * @param $replace - * Replace behavior when the destination file already exists: - * - FILE_EXISTS_REPLACE - Replace the existing file. - * - FILE_EXISTS_RENAME - Append _{incrementing number} until the filename is - * unique. - * - FILE_EXISTS_ERROR - Do nothing and return FALSE. - * - * @return - * A string with the path of the resulting file, or FALSE on error. - * - * @see file_save_data() - */ -function file_unmanaged_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAME) { - // Write the data to a temporary file. - $temp_name = drupal_tempnam('temporary://', 'file'); - if (file_put_contents($temp_name, $data) === FALSE) { - drupal_set_message(t('The file could not be created.'), 'error'); - return FALSE; - } - - // Move the file to its final destination. - return file_unmanaged_move($temp_name, $destination, $replace); -} - -/** - * Transfer file using HTTP to client. - * - * Pipes a file through Drupal to the client. - * - * @param $uri - * String specifying the file URI to transfer. - * @param $headers - * An array of HTTP headers to send along with file. - */ -function file_transfer($uri, $headers) { - if (ob_get_level()) { - ob_end_clean(); - } - - foreach ($headers as $name => $value) { - drupal_add_http_header($name, $value); - } - drupal_send_headers(); - $scheme = file_uri_scheme($uri); - // Transfer file in 1024 byte chunks to save memory usage. - if ($scheme && file_stream_wrapper_valid_scheme($scheme) && $fd = fopen($uri, 'rb')) { - while (!feof($fd)) { - print fread($fd, 1024); - } - fclose($fd); - } - else { - drupal_not_found(); - } - drupal_exit(); -} - -/** - * Menu handler for private file transfers. - * - * Call modules that implement hook_file_download() to find out if a file is - * accessible and what headers it should be transferred with. If one or more - * modules returned headers the download will start with the returned headers. - * If a module returns -1 drupal_access_denied() will be returned. If the file - * exists but no modules responded drupal_access_denied() will be returned. - * If the file does not exist drupal_not_found() will be returned. - * - * @see hook_file_download() - */ -function file_download() { - // Merge remainder of arguments from GET['q'], into relative file path. - $args = func_get_args(); - $scheme = array_shift($args); - $target = implode('/', $args); - $uri = $scheme . '://' . $target; - if (file_stream_wrapper_valid_scheme($scheme) && file_exists($uri)) { - // Let other modules provide headers and controls access to the file. - // module_invoke_all() uses array_merge_recursive() which merges header - // values into a new array. To avoid that and allow modules to override - // headers instead, use array_merge() to merge the returned arrays. - $headers = array(); - foreach (module_implements('file_download') as $module) { - $function = $module . '_file_download'; - $result = $function($uri); - if ($result == -1) { - return drupal_access_denied(); - } - if (isset($result) && is_array($result)) { - $headers = array_merge($headers, $result); - } - } - if (count($headers)) { - file_transfer($uri, $headers); - } - return drupal_access_denied(); - } - return drupal_not_found(); -} - - -/** - * Finds all files that match a given mask in a given directory. - * - * Directories and files beginning with a period are excluded; this - * prevents hidden files and directories (such as SVN working directories) - * from being scanned. - * - * @param $dir - * The base directory or URI to scan, without trailing slash. - * @param $mask - * The preg_match() regular expression of the files to find. - * @param $options - * An associative array of additional options, with the following elements: - * - 'nomask': The preg_match() regular expression of the files to ignore. - * Defaults to '/(\.\.?|CVS)$/'. - * - 'callback': The callback function to call for each match. There is no - * default callback. - * - 'recurse': When TRUE, the directory scan will recurse the entire tree - * starting at the provided directory. Defaults to TRUE. - * - 'key': The key to be used for the returned associative array of files. - * Possible values are 'uri', for the file's URI; 'filename', for the - * basename of the file; and 'name' for the name of the file without the - * extension. Defaults to 'uri'. - * - 'min_depth': Minimum depth of directories to return files from. Defaults - * to 0. - * @param $depth - * Current depth of recursion. This parameter is only used internally and - * should not be passed in. - * - * @return - * An associative array (keyed on the chosen key) of objects with 'uri', - * 'filename', and 'name' members corresponding to the matching files. - */ -function file_scan_directory($dir, $mask, $options = array(), $depth = 0) { - // Merge in defaults. - $options += array( - 'nomask' => '/(\.\.?|CVS)$/', - 'callback' => 0, - 'recurse' => TRUE, - 'key' => 'uri', - 'min_depth' => 0, - ); - - $options['key'] = in_array($options['key'], array('uri', 'filename', 'name')) ? $options['key'] : 'uri'; - $files = array(); - if (is_dir($dir) && $handle = opendir($dir)) { - while (FALSE !== ($filename = readdir($handle))) { - if (!preg_match($options['nomask'], $filename) && $filename[0] != '.') { - $uri = "$dir/$filename"; - $uri = file_stream_wrapper_uri_normalize($uri); - if (is_dir($uri) && $options['recurse']) { - // Give priority to files in this folder by merging them in after any subdirectory files. - $files = array_merge(file_scan_directory($uri, $mask, $options, $depth + 1), $files); - } - elseif ($depth >= $options['min_depth'] && preg_match($mask, $filename)) { - // Always use this match over anything already set in $files with the - // same $$options['key']. - $file = new stdClass(); - $file->uri = $uri; - $file->filename = $filename; - $file->name = pathinfo($filename, PATHINFO_FILENAME); - $key = $options['key']; - $files[$file->$key] = $file; - if ($options['callback']) { - $options['callback']($uri); - } - } - } - } - - closedir($handle); - } - - return $files; -} - -/** - * Determine the maximum file upload size by querying the PHP settings. - * - * @return - * A file size limit in bytes based on the PHP upload_max_filesize and - * post_max_size - */ -function file_upload_max_size() { - static $max_size = -1; - - if ($max_size < 0) { - // Start with post_max_size. - $max_size = parse_size(ini_get('post_max_size')); - - // If upload_max_size is less, then reduce. Except if upload_max_size is - // zero, which indicates no limit. - $upload_max = parse_size(ini_get('upload_max_filesize')); - if ($upload_max > 0 && $upload_max < $max_size) { - $max_size = $upload_max; - } - } - return $max_size; -} - -/** - * Determine an Internet Media Type, or MIME type from a filename. - * - * @param $uri - * A string containing the URI, path, or filename. - * @param $mapping - * An optional map of extensions to their mimetypes, in the form: - * - 'mimetypes': a list of mimetypes, keyed by an identifier, - * - 'extensions': the mapping itself, an associative array in which - * the key is the extension (lowercase) and the value is the mimetype - * identifier. If $mapping is NULL file_mimetype_mapping() is called. - * - * @return - * The internet media type registered for the extension or - * application/octet-stream for unknown extensions. - * - * @see file_default_mimetype_mapping() - */ -function file_get_mimetype($uri, $mapping = NULL) { - if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) { - return $wrapper->getMimeType($uri, $mapping); - } - else { - // getMimeType() is not implementation specific, so we can directly - // call it without an instance. - return DrupalLocalStreamWrapper::getMimeType($uri, $mapping); - } -} - -/** - * Set the permissions on a file or directory. - * - * This function will use the 'file_chmod_directory' and 'file_chmod_file' - * variables for the default modes for directories and uploaded/generated - * files. By default these will give everyone read access so that users - * accessing the files with a user account without the webserver group (e.g. - * via FTP) can read these files, and give group write permissions so webserver - * group members (e.g. a vhost account) can alter files uploaded and owned by - * the webserver. - * - * PHP's chmod does not support stream wrappers so we use our wrapper - * implementation which interfaces with chmod() by default. Contrib wrappers - * may override this behavior in their implementations as needed. - * - * @param $uri - * A string containing a URI file, or directory path. - * @param $mode - * Integer value for the permissions. Consult PHP chmod() documentation for - * more information. - * - * @return - * TRUE for success, FALSE in the event of an error. - * - * @ingroup php_wrappers - */ -function drupal_chmod($uri, $mode = NULL) { - if (!isset($mode)) { - if (is_dir($uri)) { - $mode = variable_get('file_chmod_directory', 0775); - } - else { - $mode = variable_get('file_chmod_file', 0664); - } - } - - // If this URI is a stream, pass it off to the appropriate stream wrapper. - // Otherwise, attempt PHP's chmod. This allows use of drupal_chmod even - // for unmanaged files outside of the stream wrapper interface. - if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) { - if ($wrapper->chmod($mode)) { - return TRUE; - } - } - else { - if (@chmod($uri, $mode)) { - return TRUE; - } - } - - watchdog('file', 'The file permissions could not be set on %uri.', array('%uri' => $uri), WATCHDOG_ERROR); - return FALSE; -} - -/** - * Deletes a file. - * - * PHP's unlink() is broken on Windows, as it can fail to remove a file - * when it has a read-only flag set. - * - * @param $uri - * A URI or pathname. - * @param $context - * Refer to http://php.net/manual/en/ref.stream.php - * - * @return - * Boolean TRUE on success, or FALSE on failure. - * - * @see unlink() - * @ingroup php_wrappers - */ -function drupal_unlink($uri, $context = NULL) { - $scheme = file_uri_scheme($uri); - if ((!$scheme || !file_stream_wrapper_valid_scheme($scheme)) && (substr(PHP_OS, 0, 3) == 'WIN')) { - chmod($uri, 0600); - } - if ($context) { - return unlink($uri, $context); - } - else { - return unlink($uri); - } -} - -/** - * Returns the absolute local filesystem path of a stream URI. - * - * This function was originally written to ease the conversion of 6.x code to - * use 7.x stream wrappers. However, it assumes that every URI may be resolved - * to an absolute local filesystem path, and this assumption fails when stream - * wrappers are used to support remote file storage. Remote stream wrappers - * may implement the realpath method by always returning FALSE. The use of - * drupal_realpath() is discouraged, and is slowly being removed from core - * functions where possible. - * - * Only use this function if you know that the stream wrapper in the URI uses - * the local file system, and you need to pass an absolute path to a function - * that is incompatible with stream URIs. - * - * @param $uri - * A stream wrapper URI or a filesystem path, possibly including one or more - * symbolic links. - * - * @return - * The absolute local filesystem path (with no symbolic links), or FALSE on - * failure. - * - * @see DrupalStreamWrapperInterface::realpath() - * @see http://php.net/manual/function.realpath.php - * @ingroup php_wrappers - * @todo: This function is deprecated, and should be removed wherever possible. - */ -function drupal_realpath($uri) { - // If this URI is a stream, pass it off to the appropriate stream wrapper. - // Otherwise, attempt PHP's realpath. This allows use of drupal_realpath even - // for unmanaged files outside of the stream wrapper interface. - if ($wrapper = file_stream_wrapper_get_instance_by_uri($uri)) { - return $wrapper->realpath(); - } - - return realpath($uri); -} - -/** - * Gets the name of the directory from a given path. - * - * PHP's dirname() does not properly pass streams, so this function fills - * that gap. It is backwards compatible with normal paths and will use - * PHP's dirname() as a fallback. - * - * Compatibility: normal paths and stream wrappers. - * @see http://drupal.org/node/515192 - * - * @param $uri - * A URI or path. - * - * @return - * A string containing the directory name. - * - * @see dirname() - * @ingroup php_wrappers - */ -function drupal_dirname($uri) { - $scheme = file_uri_scheme($uri); - - if ($scheme && file_stream_wrapper_valid_scheme($scheme)) { - return file_stream_wrapper_get_instance_by_scheme($scheme)->dirname($uri); - } - else { - return dirname($uri); - } -} - -/** - * Creates a directory using Drupal's default mode. - * - * PHP's mkdir() does not respect Drupal's default permissions mode. If a mode - * is not provided, this function will make sure that Drupal's is used. - * - * Compatibility: normal paths and stream wrappers. - * @see http://drupal.org/node/515192 - * - * @param $uri - * A URI or pathname. - * @param $mode - * By default the Drupal mode is used. - * @param $recursive - * Default to FALSE. - * @param $context - * Refer to http://php.net/manual/en/ref.stream.php - * - * @return - * Boolean TRUE on success, or FALSE on failure. - * - * @see mkdir() - * @ingroup php_wrappers - */ -function drupal_mkdir($uri, $mode = NULL, $recursive = FALSE, $context = NULL) { - if (!isset($mode)) { - $mode = variable_get('file_chmod_directory', 0775); - } - - if (!isset($context)) { - return mkdir($uri, $mode, $recursive); - } - else { - return mkdir($uri, $mode, $recursive, $context); - } -} - -/** - * Remove a directory. - * - * PHP's rmdir() is broken on Windows, as it can fail to remove a directory - * when it has a read-only flag set. - * - * @param $uri - * A URI or pathname. - * @param $context - * Refer to http://php.net/manual/en/ref.stream.php - * - * @return - * Boolean TRUE on success, or FALSE on failure. - * - * @see rmdir() - * @ingroup php_wrappers - */ -function drupal_rmdir($uri, $context = NULL) { - $scheme = file_uri_scheme($uri); - if ((!$scheme || !file_stream_wrapper_valid_scheme($scheme)) && (substr(PHP_OS, 0, 3) == 'WIN')) { - chmod($uri, 0700); - } - if ($context) { - return rmdir($uri, $context); - } - else { - return rmdir($uri); - } -} - -/** - * Creates a file with a unique filename in the specified directory. - * - * PHP's tempnam() does not return a URI like we want. This function - * will return a URI if given a URI, or it will return a filepath if - * given a filepath. - * - * Compatibility: normal paths and stream wrappers. - * @see http://drupal.org/node/515192 - * - * @param $directory - * The directory where the temporary filename will be created. - * @param $prefix - * The prefix of the generated temporary filename. - * Note: Windows uses only the first three characters of prefix. - * - * @return - * The new temporary filename, or FALSE on failure. - * - * @see tempnam() - * @ingroup php_wrappers - */ -function drupal_tempnam($directory, $prefix) { - $scheme = file_uri_scheme($directory); - - if ($scheme && file_stream_wrapper_valid_scheme($scheme)) { - $wrapper = file_stream_wrapper_get_instance_by_scheme($scheme); - - if ($filename = tempnam($wrapper->getDirectoryPath(), $prefix)) { - return $scheme . '://' . basename($filename); - } - else { - return FALSE; - } - } - else { - // Handle as a normal tempnam() call. - return tempnam($directory, $prefix); - } -} - -/** - * Get the path of system-appropriate temporary directory. - */ -function file_directory_temp() { - $temporary_directory = variable_get('file_temporary_path', NULL); - - if (empty($temporary_directory)) { - $directories = array(); - - // Has PHP been set with an upload_tmp_dir? - if (ini_get('upload_tmp_dir')) { - $directories[] = ini_get('upload_tmp_dir'); - } - - // Operating system specific dirs. - if (substr(PHP_OS, 0, 3) == 'WIN') { - $directories[] = 'c:\\windows\\temp'; - $directories[] = 'c:\\winnt\\temp'; - } - else { - $directories[] = '/tmp'; - } - // PHP may be able to find an alternative tmp directory. - $directories[] = sys_get_temp_dir(); - - foreach ($directories as $directory) { - if (is_dir($directory) && is_writable($directory)) { - $temporary_directory = $directory; - break; - } - } - - if (empty($temporary_directory)) { - // If no directory has been found default to 'files/tmp'. - $temporary_directory = variable_get('file_public_path', conf_path() . '/files') . '/tmp'; - - // Windows accepts paths with either slash (/) or backslash (\), but will - // not accept a path which contains both a slash and a backslash. Since - // the 'file_public_path' variable may have either format, we sanitize - // everything to use slash which is supported on all platforms. - $temporary_directory = str_replace('\\', '/', $temporary_directory); - } - // Save the path of the discovered directory. - variable_set('file_temporary_path', $temporary_directory); - } - - return $temporary_directory; -} - -/** - * Examines a file object and returns appropriate content headers for download. - * - * @param $file - * A file object. - * @return - * An associative array of headers, as expected by file_transfer(). - */ -function file_get_content_headers($file) { - $name = mime_header_encode($file->filename); - $type = mime_header_encode($file->filemime); - // Serve images, text, and flash content for display rather than download. - $inline_types = variable_get('file_inline_types', array('^text/', '^image/', 'flash$')); - $disposition = 'attachment'; - foreach ($inline_types as $inline_type) { - // Exclamation marks are used as delimiters to avoid escaping slashes. - if (preg_match('!' . $inline_type . '!', $file->filemime)) { - $disposition = 'inline'; - } - } - - return array( - 'Content-Type' => $type . '; name="' . $name . '"', - 'Content-Length' => $file->filesize, - 'Content-Disposition' => $disposition . '; filename="' . $name . '"', - 'Cache-Control' => 'private', - ); -} - -/** - * @} End of "defgroup file". - */ diff --git a/core/includes/file.mimetypes.inc b/core/includes/file.mimetypes.inc deleted file mode 100644 index 7468a60af42..00000000000 --- a/core/includes/file.mimetypes.inc +++ /dev/null @@ -1,859 +0,0 @@ -<?php - -/** - * @file - * Provides mimetype mappings. - */ - -/** - * Return an array of MIME extension mappings. - * - * Returns the mapping after modules have altered the default mapping. - * - * @return - * Array of mimetypes correlated to the extensions that relate to them. - * - * @see file_get_mimetype() - */ -function file_mimetype_mapping() { - $mapping = &drupal_static(__FUNCTION__); - if (!isset($mapping)) { - $mapping = file_default_mimetype_mapping(); - // Allow modules to alter the default mapping. - drupal_alter('file_mimetype_mapping', $mapping); - } - return $mapping; -} - -/** - * Default MIME extension mapping. - * - * @return - * Array of mimetypes correlated to the extensions that relate to them. - * - * @see file_get_mimetype() - */ -function file_default_mimetype_mapping() { - return array( - 'mimetypes' => array( - 0 => 'application/andrew-inset', - 1 => 'application/atom', - 2 => 'application/atomcat+xml', - 3 => 'application/atomserv+xml', - 4 => 'application/cap', - 5 => 'application/cu-seeme', - 6 => 'application/dsptype', - 7 => 'application/hta', - 8 => 'application/java-archive', - 9 => 'application/java-serialized-object', - 10 => 'application/java-vm', - 11 => 'application/mac-binhex40', - 12 => 'application/mathematica', - 13 => 'application/msaccess', - 14 => 'application/msword', - 15 => 'application/octet-stream', - 16 => 'application/oda', - 17 => 'application/ogg', - 18 => 'application/pdf', - 19 => 'application/pgp-keys', - 20 => 'application/pgp-signature', - 21 => 'application/pics-rules', - 22 => 'application/postscript', - 23 => 'application/rar', - 24 => 'application/rdf+xml', - 25 => 'application/rss+xml', - 26 => 'application/rtf', - 27 => 'application/smil', - 28 => 'application/vnd.cinderella', - 29 => 'application/vnd.google-earth.kml+xml', - 30 => 'application/vnd.google-earth.kmz', - 31 => 'application/vnd.mozilla.xul+xml', - 32 => 'application/vnd.ms-excel', - 33 => 'application/vnd.ms-excel.addin.macroEnabled.12', - 34 => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', - 35 => 'application/vnd.ms-excel.sheet.macroEnabled.12', - 36 => 'application/vnd.ms-excel.template.macroEnabled.12', - 37 => 'application/vnd.ms-pki.seccat', - 38 => 'application/vnd.ms-pki.stl', - 39 => 'application/vnd.ms-powerpoint', - 40 => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', - 41 => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', - 42 => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', - 43 => 'application/vnd.ms-powerpoint.template.macroEnabled.12', - 44 => 'application/vnd.ms-word.document.macroEnabled.12', - 45 => 'application/vnd.ms-word.template.macroEnabled.12', - 46 => 'application/vnd.ms-xpsdocument', - 47 => 'application/vnd.oasis.opendocument.chart', - 48 => 'application/vnd.oasis.opendocument.database', - 49 => 'application/vnd.oasis.opendocument.formula', - 50 => 'application/vnd.oasis.opendocument.graphics', - 51 => 'application/vnd.oasis.opendocument.graphics-template', - 52 => 'application/vnd.oasis.opendocument.image', - 53 => 'application/vnd.oasis.opendocument.presentation', - 54 => 'application/vnd.oasis.opendocument.presentation-template', - 55 => 'application/vnd.oasis.opendocument.spreadsheet', - 56 => 'application/vnd.oasis.opendocument.spreadsheet-template', - 57 => 'application/vnd.oasis.opendocument.text', - 58 => 'application/vnd.oasis.opendocument.text-master', - 59 => 'application/vnd.oasis.opendocument.text-template', - 60 => 'application/vnd.oasis.opendocument.text-web', - 61 => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', - 62 => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', - 63 => 'application/vnd.openxmlformats-officedocument.presentationml.template', - 64 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', - 65 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', - 66 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', - 67 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', - 68 => 'application/vnd.rim.cod', - 69 => 'application/vnd.smaf', - 70 => 'application/vnd.stardivision.calc', - 71 => 'application/vnd.stardivision.chart', - 72 => 'application/vnd.stardivision.draw', - 73 => 'application/vnd.stardivision.impress', - 74 => 'application/vnd.stardivision.math', - 75 => 'application/vnd.stardivision.writer', - 76 => 'application/vnd.stardivision.writer-global', - 77 => 'application/vnd.sun.xml.calc', - 78 => 'application/vnd.sun.xml.calc.template', - 79 => 'application/vnd.sun.xml.draw', - 80 => 'application/vnd.sun.xml.draw.template', - 81 => 'application/vnd.sun.xml.impress', - 82 => 'application/vnd.sun.xml.impress.template', - 83 => 'application/vnd.sun.xml.math', - 84 => 'application/vnd.sun.xml.writer', - 85 => 'application/vnd.sun.xml.writer.global', - 86 => 'application/vnd.sun.xml.writer.template', - 87 => 'application/vnd.symbian.install', - 88 => 'application/vnd.visio', - 89 => 'application/vnd.wap.wbxml', - 90 => 'application/vnd.wap.wmlc', - 91 => 'application/vnd.wap.wmlscriptc', - 92 => 'application/wordperfect', - 93 => 'application/wordperfect5.1', - 94 => 'application/x-123', - 95 => 'application/x-7z-compressed', - 96 => 'application/x-abiword', - 97 => 'application/x-apple-diskimage', - 98 => 'application/x-bcpio', - 99 => 'application/x-bittorrent', - 100 => 'application/x-cab', - 101 => 'application/x-cbr', - 102 => 'application/x-cbz', - 103 => 'application/x-cdf', - 104 => 'application/x-cdlink', - 105 => 'application/x-chess-pgn', - 106 => 'application/x-cpio', - 107 => 'application/x-debian-package', - 108 => 'application/x-director', - 109 => 'application/x-dms', - 110 => 'application/x-doom', - 111 => 'application/x-dvi', - 112 => 'application/x-flac', - 113 => 'application/x-font', - 114 => 'application/x-freemind', - 115 => 'application/x-futuresplash', - 116 => 'application/x-gnumeric', - 117 => 'application/x-go-sgf', - 118 => 'application/x-graphing-calculator', - 119 => 'application/x-gtar', - 120 => 'application/x-hdf', - 121 => 'application/x-httpd-eruby', - 122 => 'application/x-httpd-php', - 123 => 'application/x-httpd-php-source', - 124 => 'application/x-httpd-php3', - 125 => 'application/x-httpd-php3-preprocessed', - 126 => 'application/x-httpd-php4', - 127 => 'application/x-ica', - 128 => 'application/x-internet-signup', - 129 => 'application/x-iphone', - 130 => 'application/x-iso9660-image', - 131 => 'application/x-java-jnlp-file', - 132 => 'application/x-javascript', - 133 => 'application/x-jmol', - 134 => 'application/x-kchart', - 135 => 'application/x-killustrator', - 136 => 'application/x-koan', - 137 => 'application/x-kpresenter', - 138 => 'application/x-kspread', - 139 => 'application/x-kword', - 140 => 'application/x-latex', - 141 => 'application/x-lha', - 142 => 'application/x-lyx', - 143 => 'application/x-lzh', - 144 => 'application/x-lzx', - 145 => 'application/x-maker', - 146 => 'application/x-mif', - 147 => 'application/x-ms-wmd', - 148 => 'application/x-ms-wmz', - 149 => 'application/x-msdos-program', - 150 => 'application/x-msi', - 151 => 'application/x-netcdf', - 152 => 'application/x-ns-proxy-autoconfig', - 153 => 'application/x-nwc', - 154 => 'application/x-object', - 155 => 'application/x-oz-application', - 156 => 'application/x-pkcs7-certreqresp', - 157 => 'application/x-pkcs7-crl', - 158 => 'application/x-python-code', - 159 => 'application/x-quicktimeplayer', - 160 => 'application/x-redhat-package-manager', - 161 => 'application/x-shar', - 162 => 'application/x-shockwave-flash', - 163 => 'application/x-stuffit', - 164 => 'application/x-sv4cpio', - 165 => 'application/x-sv4crc', - 166 => 'application/x-tar', - 167 => 'application/x-tcl', - 168 => 'application/x-tex-gf', - 169 => 'application/x-tex-pk', - 170 => 'application/x-texinfo', - 171 => 'application/x-trash', - 172 => 'application/x-troff', - 173 => 'application/x-troff-man', - 174 => 'application/x-troff-me', - 175 => 'application/x-troff-ms', - 176 => 'application/x-ustar', - 177 => 'application/x-wais-source', - 178 => 'application/x-wingz', - 179 => 'application/x-x509-ca-cert', - 180 => 'application/x-xcf', - 181 => 'application/x-xfig', - 182 => 'application/x-xpinstall', - 183 => 'application/xhtml+xml', - 184 => 'application/xml', - 185 => 'application/zip', - 186 => 'audio/basic', - 187 => 'audio/midi', - 346 => 'audio/mp4', - 188 => 'audio/mpeg', - 189 => 'audio/ogg', - 190 => 'audio/prs.sid', - 191 => 'audio/x-aiff', - 192 => 'audio/x-gsm', - 193 => 'audio/x-mpegurl', - 194 => 'audio/x-ms-wax', - 195 => 'audio/x-ms-wma', - 196 => 'audio/x-pn-realaudio', - 197 => 'audio/x-realaudio', - 198 => 'audio/x-scpls', - 199 => 'audio/x-sd2', - 200 => 'audio/x-wav', - 201 => 'chemical/x-alchemy', - 202 => 'chemical/x-cache', - 203 => 'chemical/x-cache-csf', - 204 => 'chemical/x-cactvs-binary', - 205 => 'chemical/x-cdx', - 206 => 'chemical/x-cerius', - 207 => 'chemical/x-chem3d', - 208 => 'chemical/x-chemdraw', - 209 => 'chemical/x-cif', - 210 => 'chemical/x-cmdf', - 211 => 'chemical/x-cml', - 212 => 'chemical/x-compass', - 213 => 'chemical/x-crossfire', - 214 => 'chemical/x-csml', - 215 => 'chemical/x-ctx', - 216 => 'chemical/x-cxf', - 217 => 'chemical/x-embl-dl-nucleotide', - 218 => 'chemical/x-galactic-spc', - 219 => 'chemical/x-gamess-input', - 220 => 'chemical/x-gaussian-checkpoint', - 221 => 'chemical/x-gaussian-cube', - 222 => 'chemical/x-gaussian-input', - 223 => 'chemical/x-gaussian-log', - 224 => 'chemical/x-gcg8-sequence', - 225 => 'chemical/x-genbank', - 226 => 'chemical/x-hin', - 227 => 'chemical/x-isostar', - 228 => 'chemical/x-jcamp-dx', - 229 => 'chemical/x-kinemage', - 230 => 'chemical/x-macmolecule', - 231 => 'chemical/x-macromodel-input', - 232 => 'chemical/x-mdl-molfile', - 233 => 'chemical/x-mdl-rdfile', - 234 => 'chemical/x-mdl-rxnfile', - 235 => 'chemical/x-mdl-sdfile', - 236 => 'chemical/x-mdl-tgf', - 237 => 'chemical/x-mmcif', - 238 => 'chemical/x-mol2', - 239 => 'chemical/x-molconn-Z', - 240 => 'chemical/x-mopac-graph', - 241 => 'chemical/x-mopac-input', - 242 => 'chemical/x-mopac-out', - 243 => 'chemical/x-mopac-vib', - 244 => 'chemical/x-ncbi-asn1-ascii', - 245 => 'chemical/x-ncbi-asn1-binary', - 246 => 'chemical/x-ncbi-asn1-spec', - 247 => 'chemical/x-pdb', - 248 => 'chemical/x-rosdal', - 249 => 'chemical/x-swissprot', - 250 => 'chemical/x-vamas-iso14976', - 251 => 'chemical/x-vmd', - 252 => 'chemical/x-xtel', - 253 => 'chemical/x-xyz', - 254 => 'image/gif', - 255 => 'image/ief', - 256 => 'image/jpeg', - 257 => 'image/pcx', - 258 => 'image/png', - 259 => 'image/svg+xml', - 260 => 'image/tiff', - 261 => 'image/vnd.djvu', - 262 => 'image/vnd.microsoft.icon', - 263 => 'image/vnd.wap.wbmp', - 264 => 'image/x-cmu-raster', - 265 => 'image/x-coreldraw', - 266 => 'image/x-coreldrawpattern', - 267 => 'image/x-coreldrawtemplate', - 268 => 'image/x-corelphotopaint', - 269 => 'image/x-jg', - 270 => 'image/x-jng', - 271 => 'image/x-ms-bmp', - 272 => 'image/x-photoshop', - 273 => 'image/x-portable-anymap', - 274 => 'image/x-portable-bitmap', - 275 => 'image/x-portable-graymap', - 276 => 'image/x-portable-pixmap', - 277 => 'image/x-rgb', - 278 => 'image/x-xbitmap', - 279 => 'image/x-xpixmap', - 280 => 'image/x-xwindowdump', - 281 => 'message/rfc822', - 282 => 'model/iges', - 283 => 'model/mesh', - 284 => 'model/vrml', - 285 => 'text/calendar', - 286 => 'text/css', - 287 => 'text/csv', - 288 => 'text/h323', - 289 => 'text/html', - 290 => 'text/iuls', - 291 => 'text/mathml', - 292 => 'text/plain', - 293 => 'text/richtext', - 294 => 'text/scriptlet', - 295 => 'text/tab-separated-values', - 296 => 'text/texmacs', - 297 => 'text/vnd.sun.j2me.app-descriptor', - 298 => 'text/vnd.wap.wml', - 299 => 'text/vnd.wap.wmlscript', - 300 => 'text/x-bibtex', - 301 => 'text/x-boo', - 302 => 'text/x-c++hdr', - 303 => 'text/x-c++src', - 304 => 'text/x-chdr', - 305 => 'text/x-component', - 306 => 'text/x-csh', - 307 => 'text/x-csrc', - 308 => 'text/x-diff', - 309 => 'text/x-dsrc', - 310 => 'text/x-haskell', - 311 => 'text/x-java', - 312 => 'text/x-literate-haskell', - 313 => 'text/x-moc', - 314 => 'text/x-pascal', - 315 => 'text/x-pcs-gcd', - 316 => 'text/x-perl', - 317 => 'text/x-python', - 318 => 'text/x-setext', - 319 => 'text/x-sh', - 320 => 'text/x-tcl', - 321 => 'text/x-tex', - 322 => 'text/x-vcalendar', - 323 => 'text/x-vcard', - 324 => 'video/3gpp', - 325 => 'video/dl', - 326 => 'video/dv', - 327 => 'video/fli', - 328 => 'video/gl', - 329 => 'video/mp4', - 330 => 'video/mpeg', - 331 => 'video/ogg', - 332 => 'video/quicktime', - 333 => 'video/vnd.mpegurl', - 347 => 'video/x-flv', - 334 => 'video/x-la-asf', - 348 => 'video/x-m4v', - 335 => 'video/x-mng', - 336 => 'video/x-ms-asf', - 337 => 'video/x-ms-wm', - 338 => 'video/x-ms-wmv', - 339 => 'video/x-ms-wmx', - 340 => 'video/x-ms-wvx', - 341 => 'video/x-msvideo', - 342 => 'video/x-sgi-movie', - 343 => 'x-conference/x-cooltalk', - 344 => 'x-epoc/x-sisx-app', - 345 => 'x-world/x-vrml', - ), - - // Extensions added to this list MUST be lower-case. - 'extensions' => array( - 'ez' => 0, - 'atom' => 1, - 'atomcat' => 2, - 'atomsrv' => 3, - 'cap' => 4, - 'pcap' => 4, - 'cu' => 5, - 'tsp' => 6, - 'hta' => 7, - 'jar' => 8, - 'ser' => 9, - 'class' => 10, - 'hqx' => 11, - 'nb' => 12, - 'mdb' => 13, - 'dot' => 14, - 'doc' => 14, - 'bin' => 15, - 'oda' => 16, - 'ogx' => 17, - 'pdf' => 18, - 'key' => 19, - 'pgp' => 20, - 'prf' => 21, - 'eps' => 22, - 'ai' => 22, - 'ps' => 22, - 'rar' => 23, - 'rdf' => 24, - 'rss' => 25, - 'rtf' => 26, - 'smi' => 27, - 'smil' => 27, - 'cdy' => 28, - 'kml' => 29, - 'kmz' => 30, - 'xul' => 31, - 'xlb' => 32, - 'xlt' => 32, - 'xls' => 32, - 'xlam' => 33, - 'xlsb' => 34, - 'xlsm' => 35, - 'xltm' => 36, - 'cat' => 37, - 'stl' => 38, - 'pps' => 39, - 'ppt' => 39, - 'ppam' => 40, - 'pptm' => 41, - 'ppsm' => 42, - 'potm' => 43, - 'docm' => 44, - 'dotm' => 45, - 'xps' => 46, - 'odc' => 47, - 'odb' => 48, - 'odf' => 49, - 'odg' => 50, - 'otg' => 51, - 'odi' => 52, - 'odp' => 53, - 'otp' => 54, - 'ods' => 55, - 'ots' => 56, - 'odt' => 57, - 'odm' => 58, - 'ott' => 59, - 'oth' => 60, - 'pptx' => 61, - 'ppsx' => 62, - 'potx' => 63, - 'xlsx' => 64, - 'xltx' => 65, - 'docx' => 66, - 'dotx' => 67, - 'cod' => 68, - 'mmf' => 69, - 'sdc' => 70, - 'sds' => 71, - 'sda' => 72, - 'sdd' => 73, - 'sdw' => 75, - 'sgl' => 76, - 'sxc' => 77, - 'stc' => 78, - 'sxd' => 79, - 'std' => 80, - 'sxi' => 81, - 'sti' => 82, - 'sxm' => 83, - 'sxw' => 84, - 'sxg' => 85, - 'stw' => 86, - 'sis' => 87, - 'vsd' => 88, - 'wbxml' => 89, - 'wmlc' => 90, - 'wmlsc' => 91, - 'wpd' => 92, - 'wp5' => 93, - 'wk' => 94, - '7z' => 95, - 'abw' => 96, - 'dmg' => 97, - 'bcpio' => 98, - 'torrent' => 99, - 'cab' => 100, - 'cbr' => 101, - 'cbz' => 102, - 'cdf' => 103, - 'vcd' => 104, - 'pgn' => 105, - 'cpio' => 106, - 'udeb' => 107, - 'deb' => 107, - 'dir' => 108, - 'dxr' => 108, - 'dcr' => 108, - 'dms' => 109, - 'wad' => 110, - 'dvi' => 111, - 'flac' => 112, - 'pfa' => 113, - 'pfb' => 113, - 'pcf' => 113, - 'gsf' => 113, - 'pcf.z' => 113, - 'mm' => 114, - 'spl' => 115, - 'gnumeric' => 116, - 'sgf' => 117, - 'gcf' => 118, - 'taz' => 119, - 'gtar' => 119, - 'tgz' => 119, - 'hdf' => 120, - 'rhtml' => 121, - 'phtml' => 122, - 'pht' => 122, - 'php' => 122, - 'phps' => 123, - 'php3' => 124, - 'php3p' => 125, - 'php4' => 126, - 'ica' => 127, - 'ins' => 128, - 'isp' => 128, - 'iii' => 129, - 'iso' => 130, - 'jnlp' => 131, - 'js' => 132, - 'jmz' => 133, - 'chrt' => 134, - 'kil' => 135, - 'skp' => 136, - 'skd' => 136, - 'skm' => 136, - 'skt' => 136, - 'kpr' => 137, - 'kpt' => 137, - 'ksp' => 138, - 'kwd' => 139, - 'kwt' => 139, - 'latex' => 140, - 'lha' => 141, - 'lyx' => 142, - 'lzh' => 143, - 'lzx' => 144, - 'maker' => 145, - 'frm' => 145, - 'frame' => 145, - 'fm' => 145, - 'book' => 145, - 'fb' => 145, - 'fbdoc' => 145, - 'mif' => 146, - 'wmd' => 147, - 'wmz' => 148, - 'dll' => 149, - 'bat' => 149, - 'exe' => 149, - 'com' => 149, - 'msi' => 150, - 'nc' => 151, - 'pac' => 152, - 'nwc' => 153, - 'o' => 154, - 'oza' => 155, - 'p7r' => 156, - 'crl' => 157, - 'pyo' => 158, - 'pyc' => 158, - 'qtl' => 159, - 'rpm' => 160, - 'shar' => 161, - 'swf' => 162, - 'swfl' => 162, - 'sitx' => 163, - 'sit' => 163, - 'sv4cpio' => 164, - 'sv4crc' => 165, - 'tar' => 166, - 'gf' => 168, - 'pk' => 169, - 'texi' => 170, - 'texinfo' => 170, - 'sik' => 171, - '~' => 171, - 'bak' => 171, - '%' => 171, - 'old' => 171, - 't' => 172, - 'roff' => 172, - 'tr' => 172, - 'man' => 173, - 'me' => 174, - 'ms' => 175, - 'ustar' => 176, - 'src' => 177, - 'wz' => 178, - 'crt' => 179, - 'xcf' => 180, - 'fig' => 181, - 'xpi' => 182, - 'xht' => 183, - 'xhtml' => 183, - 'xml' => 184, - 'xsl' => 184, - 'zip' => 185, - 'au' => 186, - 'snd' => 186, - 'mid' => 187, - 'midi' => 187, - 'kar' => 187, - 'mpega' => 188, - 'mpga' => 188, - 'm4a' => 188, - 'mp3' => 188, - 'mp2' => 188, - 'ogg' => 189, - 'oga' => 189, - 'spx' => 189, - 'sid' => 190, - 'aif' => 191, - 'aiff' => 191, - 'aifc' => 191, - 'gsm' => 192, - 'm3u' => 193, - 'wax' => 194, - 'wma' => 195, - 'rm' => 196, - 'ram' => 196, - 'ra' => 197, - 'pls' => 198, - 'sd2' => 199, - 'wav' => 200, - 'alc' => 201, - 'cac' => 202, - 'cache' => 202, - 'csf' => 203, - 'cascii' => 204, - 'cbin' => 204, - 'ctab' => 204, - 'cdx' => 205, - 'cer' => 206, - 'c3d' => 207, - 'chm' => 208, - 'cif' => 209, - 'cmdf' => 210, - 'cml' => 211, - 'cpa' => 212, - 'bsd' => 213, - 'csml' => 214, - 'csm' => 214, - 'ctx' => 215, - 'cxf' => 216, - 'cef' => 216, - 'emb' => 217, - 'embl' => 217, - 'spc' => 218, - 'gam' => 219, - 'inp' => 219, - 'gamin' => 219, - 'fchk' => 220, - 'fch' => 220, - 'cub' => 221, - 'gau' => 222, - 'gjf' => 222, - 'gjc' => 222, - 'gal' => 223, - 'gcg' => 224, - 'gen' => 225, - 'hin' => 226, - 'istr' => 227, - 'ist' => 227, - 'dx' => 228, - 'jdx' => 228, - 'kin' => 229, - 'mcm' => 230, - 'mmd' => 231, - 'mmod' => 231, - 'mol' => 232, - 'rd' => 233, - 'rxn' => 234, - 'sdf' => 235, - 'sd' => 235, - 'tgf' => 236, - 'mcif' => 237, - 'mol2' => 238, - 'b' => 239, - 'gpt' => 240, - 'mopcrt' => 241, - 'zmt' => 241, - 'mpc' => 241, - 'dat' => 241, - 'mop' => 241, - 'moo' => 242, - 'mvb' => 243, - 'prt' => 244, - 'aso' => 245, - 'val' => 245, - 'asn' => 246, - 'ent' => 247, - 'pdb' => 247, - 'ros' => 248, - 'sw' => 249, - 'vms' => 250, - 'vmd' => 251, - 'xtel' => 252, - 'xyz' => 253, - 'gif' => 254, - 'ief' => 255, - 'jpeg' => 256, - 'jpe' => 256, - 'jpg' => 256, - 'pcx' => 257, - 'png' => 258, - 'svgz' => 259, - 'svg' => 259, - 'tif' => 260, - 'tiff' => 260, - 'djvu' => 261, - 'djv' => 261, - 'ico' => 262, - 'wbmp' => 263, - 'ras' => 264, - 'cdr' => 265, - 'pat' => 266, - 'cdt' => 267, - 'cpt' => 268, - 'art' => 269, - 'jng' => 270, - 'bmp' => 271, - 'psd' => 272, - 'pnm' => 273, - 'pbm' => 274, - 'pgm' => 275, - 'ppm' => 276, - 'rgb' => 277, - 'xbm' => 278, - 'xpm' => 279, - 'xwd' => 280, - 'eml' => 281, - 'igs' => 282, - 'iges' => 282, - 'silo' => 283, - 'msh' => 283, - 'mesh' => 283, - 'icz' => 285, - 'ics' => 285, - 'css' => 286, - 'csv' => 287, - '323' => 288, - 'html' => 289, - 'htm' => 289, - 'shtml' => 289, - 'uls' => 290, - 'mml' => 291, - 'txt' => 292, - 'pot' => 292, - 'text' => 292, - 'asc' => 292, - 'rtx' => 293, - 'wsc' => 294, - 'sct' => 294, - 'tsv' => 295, - 'ts' => 296, - 'tm' => 296, - 'jad' => 297, - 'wml' => 298, - 'wmls' => 299, - 'bib' => 300, - 'boo' => 301, - 'hpp' => 302, - 'hh' => 302, - 'h++' => 302, - 'hxx' => 302, - 'cxx' => 303, - 'cc' => 303, - 'cpp' => 303, - 'c++' => 303, - 'h' => 304, - 'htc' => 305, - 'csh' => 306, - 'c' => 307, - 'patch' => 308, - 'diff' => 308, - 'd' => 309, - 'hs' => 310, - 'java' => 311, - 'lhs' => 312, - 'moc' => 313, - 'pas' => 314, - 'p' => 314, - 'gcd' => 315, - 'pm' => 316, - 'pl' => 316, - 'py' => 317, - 'etx' => 318, - 'sh' => 319, - 'tk' => 320, - 'tcl' => 320, - 'cls' => 321, - 'ltx' => 321, - 'sty' => 321, - 'tex' => 321, - 'vcs' => 322, - 'vcf' => 323, - '3gp' => 324, - 'dl' => 325, - 'dif' => 326, - 'dv' => 326, - 'fli' => 327, - 'gl' => 328, - 'mp4' => 329, - 'f4v' => 329, - 'f4p' => 329, - 'mpe' => 330, - 'mpeg' => 330, - 'mpg' => 330, - 'ogv' => 331, - 'qt' => 332, - 'mov' => 332, - 'mxu' => 333, - 'lsf' => 334, - 'lsx' => 334, - 'mng' => 335, - 'asx' => 336, - 'asf' => 336, - 'wm' => 337, - 'wmv' => 338, - 'wmx' => 339, - 'wvx' => 340, - 'avi' => 341, - 'movie' => 342, - 'ice' => 343, - 'sisx' => 344, - 'wrl' => 345, - 'vrm' => 345, - 'vrml' => 345, - 'f4a' => 346, - 'f4b' => 346, - 'flv' => 347, - 'm4v' => 348, - ), - ); -} diff --git a/core/includes/filetransfer/filetransfer.inc b/core/includes/filetransfer/filetransfer.inc deleted file mode 100644 index aa7ebe470e2..00000000000 --- a/core/includes/filetransfer/filetransfer.inc +++ /dev/null @@ -1,418 +0,0 @@ -<?php - -/** - * @file - * Base FileTransfer class. - * - * Classes extending this class perform file operations on directories not - * writable by the webserver. To achieve this, the class should connect back - * to the server using some backend (for example FTP or SSH). To keep security, - * the password should always be asked from the user and never stored. For - * safety, all methods operate only inside a "jail", by default the Drupal root. - */ -abstract class FileTransfer { - protected $username; - protected $password; - protected $hostname = 'localhost'; - protected $port; - - /** - * The constructor for the UpdateConnection class. This method is also called - * from the classes that extend this class and override this method. - */ - function __construct($jail) { - $this->jail = $jail; - } - - /** - * Classes that extend this class must override the factory() static method. - * - * @param string $jail - * The full path where all file operations performed by this object will - * be restricted to. This prevents the FileTransfer classes from being - * able to touch other parts of the filesystem. - * @param array $settings - * An array of connection settings for the FileTransfer subclass. If the - * getSettingsForm() method uses any nested settings, the same structure - * will be assumed here. - * @return object - * New instance of the appropriate FileTransfer subclass. - */ - static function factory($jail, $settings) { - throw new FileTransferException('FileTransfer::factory() static method not overridden by FileTransfer subclass.'); - } - - /** - * Implementation of the magic __get() method. - * - * If the connection isn't set to anything, this will call the connect() method - * and set it to and return the result; afterwards, the connection will be - * returned directly without using this method. - */ - function __get($name) { - if ($name == 'connection') { - $this->connect(); - return $this->connection; - } - - if ($name == 'chroot') { - $this->setChroot(); - return $this->chroot; - } - } - - /** - * Connects to the server. - */ - abstract protected function connect(); - - /** - * Copies a directory. - * - * @param $source - * The source path. - * @param $destination - * The destination path. - */ - public final function copyDirectory($source, $destination) { - $source = $this->sanitizePath($source); - $destination = $this->fixRemotePath($destination); - $this->checkPath($destination); - $this->copyDirectoryJailed($source, $destination); - } - - /** - * @see http://php.net/chmod - * - * @param string $path - * @param long $mode - * @param bool $recursive - */ - public final function chmod($path, $mode, $recursive = FALSE) { - if (!in_array('FileTransferChmodInterface', class_implements(get_class($this)))) { - throw new FileTransferException('Unable to change file permissions'); - } - $path = $this->sanitizePath($path); - $path = $this->fixRemotePath($path); - $this->checkPath($path); - $this->chmodJailed($path, $mode, $recursive); - } - - /** - * Creates a directory. - * - * @param $directory - * The directory to be created. - */ - public final function createDirectory($directory) { - $directory = $this->fixRemotePath($directory); - $this->checkPath($directory); - $this->createDirectoryJailed($directory); - } - - /** - * Removes a directory. - * - * @param $directory - * The directory to be removed. - */ - public final function removeDirectory($directory) { - $directory = $this->fixRemotePath($directory); - $this->checkPath($directory); - $this->removeDirectoryJailed($directory); - } - - /** - * Copies a file. - * - * @param $source - * The source file. - * @param $destination - * The destination file. - */ - public final function copyFile($source, $destination) { - $source = $this->sanitizePath($source); - $destination = $this->fixRemotePath($destination); - $this->checkPath($destination); - $this->copyFileJailed($source, $destination); - } - - /** - * Removes a file. - * - * @param $destination - * The destination file to be removed. - */ - public final function removeFile($destination) { - $destination = $this->fixRemotePath($destination); - $this->checkPath($destination); - $this->removeFileJailed($destination); - } - - /** - * Checks that the path is inside the jail and throws an exception if not. - * - * @param $path - * A path to check against the jail. - */ - protected final function checkPath($path) { - $full_jail = $this->chroot . $this->jail; - $full_path = drupal_realpath(substr($this->chroot . $path, 0, strlen($full_jail))); - $full_path = $this->fixRemotePath($full_path, FALSE); - if ($full_jail !== $full_path) { - throw new FileTransferException('@directory is outside of the @jail', NULL, array('@directory' => $path, '@jail' => $this->jail)); - } - } - - /** - * Returns a modified path suitable for passing to the server. - * If a path is a windows path, makes it POSIX compliant by removing the drive letter. - * If $this->chroot has a value, it is stripped from the path to allow for - * chroot'd filetransfer systems. - * - * @param $path - * @param $strip_chroot - * - * @return string - */ - protected final function fixRemotePath($path, $strip_chroot = TRUE) { - $path = $this->sanitizePath($path); - $path = preg_replace('|^([a-z]{1}):|i', '', $path); // Strip out windows driveletter if its there. - if ($strip_chroot) { - if ($this->chroot && strpos($path, $this->chroot) === 0) { - $path = ($path == $this->chroot) ? '' : substr($path, strlen($this->chroot)); - } - } - return $path; - } - - /** - * Changes backslashes to slashes, also removes a trailing slash. - * - * @param string $path - * @return string - */ - function sanitizePath($path) { - $path = str_replace('\\', '/', $path); // Windows path sanitization. - if (substr($path, -1) == '/') { - $path = substr($path, 0, -1); - } - return $path; - } - - /** - * Copies a directory. - * - * We need a separate method to make the $destination is in the jail. - * - * @param $source - * The source path. - * @param $destination - * The destination path. - */ - protected function copyDirectoryJailed($source, $destination) { - if ($this->isDirectory($destination)) { - $destination = $destination . '/' . basename($source); - } - $this->createDirectory($destination); - foreach (new RecursiveIteratorIterator(new SkipDotsRecursiveDirectoryIterator($source), RecursiveIteratorIterator::SELF_FIRST) as $filename => $file) { - $relative_path = substr($filename, strlen($source)); - if ($file->isDir()) { - $this->createDirectory($destination . $relative_path); - } - else { - $this->copyFile($file->getPathName(), $destination . $relative_path); - } - } - } - - /** - * Creates a directory. - * - * @param $directory - * The directory to be created. - */ - abstract protected function createDirectoryJailed($directory); - - /** - * Removes a directory. - * - * @param $directory - * The directory to be removed. - */ - abstract protected function removeDirectoryJailed($directory); - - /** - * Copies a file. - * - * @param $source - * The source file. - * @param $destination - * The destination file. - */ - abstract protected function copyFileJailed($source, $destination); - - /** - * Removes a file. - * - * @param $destination - * The destination file to be removed. - */ - abstract protected function removeFileJailed($destination); - - /** - * Checks if a particular path is a directory - * - * @param $path - * The path to check - * - * @return boolean - */ - abstract public function isDirectory($path); - - /** - * Checks if a particular path is a file (not a directory). - * - * @param $path - * The path to check - * - * @return boolean - */ - abstract public function isFile($path); - - /** - * Returns the chroot property for this connection. - * - * It does this by moving up the tree until it finds itself. If successful, - * it will return the chroot, otherwise FALSE. - * - * @return - * The chroot path for this connection or FALSE. - */ - function findChroot() { - // If the file exists as is, there is no chroot. - $path = __FILE__; - $path = $this->fixRemotePath($path, FALSE); - if ($this->isFile($path)) { - return FALSE; - } - - $path = __DIR__; - $path = $this->fixRemotePath($path, FALSE); - $parts = explode('/', $path); - $chroot = ''; - while (count($parts)) { - $check = implode($parts, '/'); - if ($this->isFile($check . '/' . basename(__FILE__))) { - // Remove the trailing slash. - return substr($chroot, 0, -1); - } - $chroot .= array_shift($parts) . '/'; - } - return FALSE; - } - - /** - * Sets the chroot and changes the jail to match the correct path scheme - * - */ - function setChroot() { - $this->chroot = $this->findChroot(); - $this->jail = $this->fixRemotePath($this->jail); - } - - /** - * Returns a form to collect connection settings credentials. - * - * Implementing classes can either extend this form with fields collecting the - * specific information they need, or override it entirely. - */ - public function getSettingsForm() { - $form['username'] = array( - '#type' => 'textfield', - '#title' => t('Username'), - ); - $form['password'] = array( - '#type' => 'password', - '#title' => t('Password'), - '#description' => t('Your password is not saved in the database and is only used to establish a connection.'), - ); - $form['advanced'] = array( - '#type' => 'fieldset', - '#title' => t('Advanced settings'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, - ); - $form['advanced']['hostname'] = array( - '#type' => 'textfield', - '#title' => t('Host'), - '#default_value' => 'localhost', - '#description' => t('The connection will be created between your web server and the machine hosting the web server files. In the vast majority of cases, this will be the same machine, and "localhost" is correct.'), - ); - $form['advanced']['port'] = array( - '#type' => 'textfield', - '#title' => t('Port'), - '#default_value' => NULL, - ); - return $form; - } -} - -/** - * FileTransferException class. - */ -class FileTransferException extends Exception { - public $arguments; - - function __construct($message, $code = 0, $arguments = array()) { - parent::__construct($message, $code); - $this->arguments = $arguments; - } -} - - -/** - * A FileTransfer Class implementing this interface can be used to chmod files. - */ -interface FileTransferChmodInterface { - - /** - * Changes the permissions of the file / directory specified in $path - * - * @param string $path - * Path to change permissions of. - * @param long $mode - * @see http://php.net/chmod - * @param boolean $recursive - * Pass TRUE to recursively chmod the entire directory specified in $path. - */ - function chmodJailed($path, $mode, $recursive); -} - -/** - * Provides an interface for iterating recursively over filesystem directories. - * - * Manually skips '.' and '..' directories, since no existing method is - * available in PHP 5.2. - * - * @todo Depreciate in favor of RecursiveDirectoryIterator::SKIP_DOTS once PHP - * 5.3 or later is required. - */ -class SkipDotsRecursiveDirectoryIterator extends RecursiveDirectoryIterator { - /** - * Constructs a SkipDotsRecursiveDirectoryIterator - * - * @param $path - * The path of the directory to be iterated over. - */ - function __construct($path) { - parent::__construct($path); - } - - function next() { - parent::next(); - while ($this->isDot()) { - parent::next(); - } - } -} diff --git a/core/includes/filetransfer/ftp.inc b/core/includes/filetransfer/ftp.inc deleted file mode 100644 index 838dc7c1e1e..00000000000 --- a/core/includes/filetransfer/ftp.inc +++ /dev/null @@ -1,144 +0,0 @@ -<?php - -/** - * Base class for FTP implementations. - */ -abstract class FileTransferFTP extends FileTransfer { - - public function __construct($jail, $username, $password, $hostname, $port) { - $this->username = $username; - $this->password = $password; - $this->hostname = $hostname; - $this->port = $port; - parent::__construct($jail); - } - - /** - * Return an object which can implement the FTP protocol. - * - * @param string $jail - * @param array $settings - * @return FileTransferFTP - * The appropriate FileTransferFTP subclass based on the available - * options. If the FTP PHP extension is available, use it. - */ - static function factory($jail, $settings) { - $username = empty($settings['username']) ? '' : $settings['username']; - $password = empty($settings['password']) ? '' : $settings['password']; - $hostname = empty($settings['advanced']['hostname']) ? 'localhost' : $settings['advanced']['hostname']; - $port = empty($settings['advanced']['port']) ? 21 : $settings['advanced']['port']; - - if (function_exists('ftp_connect')) { - $class = 'FileTransferFTPExtension'; - } - else { - throw new FileTransferException('No FTP backend available.'); - } - - return new $class($jail, $username, $password, $hostname, $port); - } - - /** - * Returns the form to configure the FileTransfer class for FTP. - */ - public function getSettingsForm() { - $form = parent::getSettingsForm(); - $form['advanced']['port']['#default_value'] = 21; - return $form; - } -} - -class FileTransferFTPExtension extends FileTransferFTP implements FileTransferChmodInterface { - - public function connect() { - $this->connection = ftp_connect($this->hostname, $this->port); - - if (!$this->connection) { - throw new FileTransferException("Cannot connect to FTP Server, check settings"); - } - if (!ftp_login($this->connection, $this->username, $this->password)) { - throw new FileTransferException("Cannot log in to FTP server. Check username and password"); - } - } - - protected function copyFileJailed($source, $destination) { - if (!@ftp_put($this->connection, $destination, $source, FTP_BINARY)) { - throw new FileTransferException("Cannot move @source to @destination", NULL, array("@source" => $source, "@destination" => $destination)); - } - } - - protected function createDirectoryJailed($directory) { - if (!ftp_mkdir($this->connection, $directory)) { - throw new FileTransferException("Cannot create directory @directory", NULL, array("@directory" => $directory)); - } - } - - protected function removeDirectoryJailed($directory) { - $pwd = ftp_pwd($this->connection); - if (!ftp_chdir($this->connection, $directory)) { - throw new FileTransferException("Unable to change to directory @directory", NULL, array('@directory' => $directory)); - } - $list = @ftp_nlist($this->connection, '.'); - if (!$list) { - $list = array(); - } - foreach ($list as $item){ - if ($item == '.' || $item == '..') { - continue; - } - if (@ftp_chdir($this->connection, $item)){ - ftp_cdup($this->connection); - $this->removeDirectory(ftp_pwd($this->connection) . '/' . $item); - } - else { - $this->removeFile(ftp_pwd($this->connection) . '/' . $item); - } - } - ftp_chdir($this->connection, $pwd); - if (!ftp_rmdir($this->connection, $directory)) { - throw new FileTransferException("Unable to remove to directory @directory", NULL, array('@directory' => $directory)); - } - } - - protected function removeFileJailed($destination) { - if (!ftp_delete($this->connection, $destination)) { - throw new FileTransferException("Unable to remove to file @file", NULL, array('@file' => $destination)); - } - } - - public function isDirectory($path) { - $result = FALSE; - $curr = ftp_pwd($this->connection); - if (@ftp_chdir($this->connection, $path)) { - $result = TRUE; - } - ftp_chdir($this->connection, $curr); - return $result; - } - - public function isFile($path) { - return ftp_size($this->connection, $path) != -1; - } - - function chmodJailed($path, $mode, $recursive) { - if (!ftp_chmod($this->connection, $mode, $path)) { - throw new FileTransferException("Unable to set permissions on %file", NULL, array ('%file' => $path)); - } - if ($this->isDirectory($path) && $recursive) { - $filelist = @ftp_nlist($this->connection, $path); - if (!$filelist) { - //empty directory - returns false - return; - } - foreach ($filelist as $file) { - $this->chmodJailed($file, $mode, $recursive); - } - } - } -} - -if (!function_exists('ftp_chmod')) { - function ftp_chmod($ftp_stream, $mode, $filename) { - return ftp_site($ftp_stream, sprintf('CHMOD %o %s', $mode, $filename)); - } -} diff --git a/core/includes/filetransfer/local.inc b/core/includes/filetransfer/local.inc deleted file mode 100644 index b1259897331..00000000000 --- a/core/includes/filetransfer/local.inc +++ /dev/null @@ -1,76 +0,0 @@ -<?php - -/** - * The local connection class for copying files as the httpd user. - */ -class FileTransferLocal extends FileTransfer implements FileTransferChmodInterface { - - function connect() { - // No-op - } - - static function factory($jail, $settings) { - return new FileTransferLocal($jail); - } - - protected function copyFileJailed($source, $destination) { - if (@!copy($source, $destination)) { - throw new FileTransferException('Cannot copy %source to %destination.', NULL, array('%source' => $source, '%destination' => $destination)); - } - } - - protected function createDirectoryJailed($directory) { - if (!is_dir($directory) && @!mkdir($directory, 0777, TRUE)) { - throw new FileTransferException('Cannot create directory %directory.', NULL, array('%directory' => $directory)); - } - } - - protected function removeDirectoryJailed($directory) { - if (!is_dir($directory)) { - // Programmer error assertion, not something we expect users to see. - throw new FileTransferException('removeDirectoryJailed() called with a path (%directory) that is not a directory.', NULL, array('%directory' => $directory)); - } - foreach (new RecursiveIteratorIterator(new SkipDotsRecursiveDirectoryIterator($directory), RecursiveIteratorIterator::CHILD_FIRST) as $filename => $file) { - if ($file->isDir()) { - if (@!drupal_rmdir($filename)) { - throw new FileTransferException('Cannot remove directory %directory.', NULL, array('%directory' => $filename)); - } - } - elseif ($file->isFile()) { - if (@!drupal_unlink($filename)) { - throw new FileTransferException('Cannot remove file %file.', NULL, array('%file' => $filename)); - } - } - } - if (@!drupal_rmdir($directory)) { - throw new FileTransferException('Cannot remove directory %directory.', NULL, array('%directory' => $directory)); - } - } - - protected function removeFileJailed($file) { - if (@!drupal_unlink($file)) { - throw new FileTransferException('Cannot remove file %file.', NULL, array('%file' => $file)); - } - } - - public function isDirectory($path) { - return is_dir($path); - } - - public function isFile($path) { - return is_file($path); - } - - public function chmodJailed($path, $mode, $recursive) { - if ($recursive && is_dir($path)) { - foreach (new RecursiveIteratorIterator(new SkipDotsRecursiveDirectoryIterator($path), RecursiveIteratorIterator::SELF_FIRST) as $filename => $file) { - if (@!chmod($filename, $mode)) { - throw new FileTransferException('Cannot chmod %path.', NULL, array('%path' => $filename)); - } - } - } - elseif (@!chmod($path, $mode)) { - throw new FileTransferException('Cannot chmod %path.', NULL, array('%path' => $path)); - } - } -} diff --git a/core/includes/filetransfer/ssh.inc b/core/includes/filetransfer/ssh.inc deleted file mode 100644 index 43ec3249ef0..00000000000 --- a/core/includes/filetransfer/ssh.inc +++ /dev/null @@ -1,108 +0,0 @@ -<?php - -/** - * The SSH connection class for the update module. - */ -class FileTransferSSH extends FileTransfer implements FileTransferChmodInterface { - - function __construct($jail, $username, $password, $hostname = "localhost", $port = 22) { - $this->username = $username; - $this->password = $password; - $this->hostname = $hostname; - $this->port = $port; - parent::__construct($jail); - } - - function connect() { - $this->connection = @ssh2_connect($this->hostname, $this->port); - if (!$this->connection) { - throw new FileTransferException('SSH Connection failed to @host:@port', NULL, array('@host' => $this->hostname, '@port' => $this->port)); - } - if (!@ssh2_auth_password($this->connection, $this->username, $this->password)) { - throw new FileTransferException('The supplied username/password combination was not accepted.'); - } - } - - static function factory($jail, $settings) { - $username = empty($settings['username']) ? '' : $settings['username']; - $password = empty($settings['password']) ? '' : $settings['password']; - $hostname = empty($settings['advanced']['hostname']) ? 'localhost' : $settings['advanced']['hostname']; - $port = empty($settings['advanced']['port']) ? 22 : $settings['advanced']['port']; - return new FileTransferSSH($jail, $username, $password, $hostname, $port); - } - - protected function copyFileJailed($source, $destination) { - if (!@ssh2_scp_send($this->connection, $source, $destination)) { - throw new FileTransferException('Cannot copy @source_file to @destination_file.', NULL, array('@source' => $source, '@destination' => $destination)); - } - } - - protected function copyDirectoryJailed($source, $destination) { - if (@!ssh2_exec($this->connection, 'cp -Rp ' . escapeshellarg($source) . ' ' . escapeshellarg($destination))) { - throw new FileTransferException('Cannot copy directory @directory.', NULL, array('@directory' => $source)); - } - } - - protected function createDirectoryJailed($directory) { - if (@!ssh2_exec($this->connection, 'mkdir ' . escapeshellarg($directory))) { - throw new FileTransferException('Cannot create directory @directory.', NULL, array('@directory' => $directory)); - } - } - - protected function removeDirectoryJailed($directory) { - if (@!ssh2_exec($this->connection, 'rm -Rf ' . escapeshellarg($directory))) { - throw new FileTransferException('Cannot remove @directory.', NULL, array('@directory' => $directory)); - } - } - - protected function removeFileJailed($destination) { - if (!@ssh2_exec($this->connection, 'rm ' . escapeshellarg($destination))) { - throw new FileTransferException('Cannot remove @directory.', NULL, array('@directory' => $destination)); - } - } - - /** - * WARNING: This is untested. It is not currently used, but should do the trick. - */ - public function isDirectory($path) { - $directory = escapeshellarg($path); - $cmd = "[ -d {$directory} ] && echo 'yes'"; - if ($output = @ssh2_exec($this->connection, $cmd)) { - if ($output == 'yes') { - return TRUE; - } - return FALSE; - } else { - throw new FileTransferException('Cannot check @path.', NULL, array('@path' => $path)); - } - } - - public function isFile($path) { - $file = escapeshellarg($path); - $cmd = "[ -f {$file} ] && echo 'yes'"; - if ($output = @ssh2_exec($this->connection, $cmd)) { - if ($output == 'yes') { - return TRUE; - } - return FALSE; - } else { - throw new FileTransferException('Cannot check @path.', NULL, array('@path' => $path)); - } - } - - function chmodJailed($path, $mode, $recursive) { - $cmd = sprintf("chmod %s%o %s", $recursive ? '-R ' : '', $mode, escapeshellarg($path)); - if (@!ssh2_exec($this->connection, $cmd)) { - throw new FileTransferException('Cannot change permissions of @path.', NULL, array('@path' => $path)); - } - } - - /** - * Returns the form to configure the FileTransfer class for SSH. - */ - public function getSettingsForm() { - $form = parent::getSettingsForm(); - $form['advanced']['port']['#default_value'] = 22; - return $form; - } -} diff --git a/core/includes/form.inc b/core/includes/form.inc deleted file mode 100644 index 00f95330b6e..00000000000 --- a/core/includes/form.inc +++ /dev/null @@ -1,4464 +0,0 @@ -<?php - -/** - * @defgroup forms Form builder functions - * @{ - * Functions that build an abstract representation of a HTML form. - * - * All modules should declare their form builder functions to be in this - * group and each builder function should reference its validate and submit - * functions using \@see. Conversely, validate and submit functions should - * reference the form builder function using \@see. For examples, of this see - * system_modules_uninstall() or user_pass(), the latter of which has the - * following in its doxygen documentation: - * - * \@ingroup forms - * \@see user_pass_validate(). - * \@see user_pass_submit(). - * - * @} End of "defgroup forms". - */ - -/** - * @defgroup form_api Form generation - * @{ - * Functions to enable the processing and display of HTML forms. - * - * Drupal uses these functions to achieve consistency in its form processing and - * presentation, while simplifying code and reducing the amount of HTML that - * must be explicitly generated by modules. - * - * The primary function used with forms is drupal_get_form(), which is - * used for forms presented interactively to a user. Forms can also be built and - * submitted programmatically without any user input using the - * drupal_form_submit() function. - * - * drupal_get_form() handles retrieving, processing, and displaying a rendered - * HTML form for modules automatically. - * - * Here is an example of how to use drupal_get_form() and a form builder - * function: - * @code - * $form = drupal_get_form('my_module_example_form'); - * ... - * function my_module_example_form($form, &$form_state) { - * $form['submit'] = array( - * '#type' => 'submit', - * '#value' => t('Submit'), - * ); - * return $form; - * } - * function my_module_example_form_validate($form, &$form_state) { - * // Validation logic. - * } - * function my_module_example_form_submit($form, &$form_state) { - * // Submission logic. - * } - * @endcode - * - * Or with any number of additional arguments: - * @code - * $extra = "extra"; - * $form = drupal_get_form('my_module_example_form', $extra); - * ... - * function my_module_example_form($form, &$form_state, $extra) { - * $form['submit'] = array( - * '#type' => 'submit', - * '#value' => $extra, - * ); - * return $form; - * } - * @endcode - * - * The $form argument to form-related functions is a structured array containing - * the elements and properties of the form. For information on the array - * components and format, and more detailed explanations of the Form API - * workflow, see the - * @link http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html Form API reference @endlink - * and the - * @link http://drupal.org/node/37775 Form API documentation section. @endlink - * In addition, there is a set of Form API tutorials in - * @link form_example_tutorial.inc the Form Example Tutorial @endlink which - * provide basics all the way up through multistep forms. - * - * In the form builder, validation, submission, and other form functions, - * $form_state is the primary influence on the processing of the form and is - * passed by reference to most functions, so they use it to communicate with - * the form system and each other. - * - * See drupal_build_form() for documentation of $form_state keys. - */ - -/** - * Wrapper for drupal_build_form() for use when $form_state is not needed. - * - * @param $form_id - * The unique string identifying the desired form. If a function with that - * name exists, it is called to build the form array. Modules that need to - * generate the same form (or very similar forms) using different $form_ids - * can implement hook_forms(), which maps different $form_id values to the - * proper form constructor function. Examples may be found in node_forms(), - * search_forms(), and user_forms(). - * @param ... - * Any additional arguments are passed on to the functions called by - * drupal_get_form(), including the unique form constructor function. For - * example, the node_edit form requires that a node object is passed in here - * when it is called. These are available to implementations of - * hook_form_alter() and hook_form_FORM_ID_alter() as the array - * $form_state['build_info']['args']. - * - * @return - * The form array. - * - * @see drupal_build_form() - */ -function drupal_get_form($form_id) { - $form_state = array(); - - $args = func_get_args(); - // Remove $form_id from the arguments. - array_shift($args); - $form_state['build_info']['args'] = $args; - - return drupal_build_form($form_id, $form_state); -} - -/** - * Build and process a form based on a form id. - * - * The form may also be retrieved from the cache if the form was built in a - * previous page-load. The form is then passed on for processing, validation - * and submission if there is proper input. - * - * @param $form_id - * The unique string identifying the desired form. If a function with that - * name exists, it is called to build the form array. Modules that need to - * generate the same form (or very similar forms) using different $form_ids - * can implement hook_forms(), which maps different $form_id values to the - * proper form constructor function. Examples may be found in node_forms(), - * search_forms(), and user_forms(). - * @param $form_state - * An array which stores information about the form. This is passed as a - * reference so that the caller can use it to examine what in the form changed - * when the form submission process is complete. Furthermore, it may be used - * to store information related to the processed data in the form, which will - * persist across page requests when the 'cache' or 'rebuild' flag is set. - * The following parameters may be set in $form_state to affect how the form - * is rendered: - * - build_info: Internal. An associative array of information stored by Form - * API that is necessary to build and rebuild the form from cache when the - * original context may no longer be available: - * - args: A list of arguments to pass to the form constructor. - * - files: An optional array defining include files that need to be loaded - * for building the form. Each array entry may be the path to a file or - * another array containing values for the parameters 'type', 'module' and - * 'name' as needed by module_load_include(). The files listed here are - * automatically loaded by form_get_cache(). By default the current menu - * router item's 'file' definition is added, if any. Use - * form_load_include() to add include files from a form constructor. - * - rebuild_info: Internal. Similar to 'build_info', but pertaining to - * drupal_rebuild_form(). - * - rebuild: Normally, after the entire form processing is completed and - * submit handlers have run, a form is considered to be done and - * drupal_redirect_form() will redirect the user to a new page using a GET - * request (so a browser refresh does not re-submit the form). However, if - * 'rebuild' has been set to TRUE, then a new copy of the form is - * immediately built and sent to the browser, instead of a redirect. This is - * used for multi-step forms, such as wizards and confirmation forms. - * Normally, $form_state['rebuild'] is set by a submit handler, since it is - * usually logic within a submit handler that determines whether a form is - * done or requires another step. However, a validation handler may already - * set $form_state['rebuild'] to cause the form processing to bypass submit - * handlers and rebuild the form instead, even if there are no validation - * errors. - * - redirect: Used to redirect the form on submission. It may either be a - * string containing the destination URL, or an array of arguments - * compatible with drupal_goto(). See drupal_redirect_form() for complete - * information. - * - no_redirect: If set to TRUE the form will NOT perform a drupal_goto(), - * even if 'redirect' is set. - * - method: The HTTP form method to use for finding the input for this form. - * May be 'post' or 'get'. Defaults to 'post'. Note that 'get' method - * forms do not use form ids so are always considered to be submitted, which - * can have unexpected effects. The 'get' method should only be used on - * forms that do not change data, as that is exclusively the domain of - * 'post.' - * - cache: If set to TRUE the original, unprocessed form structure will be - * cached, which allows the entire form to be rebuilt from cache. A typical - * form workflow involves two page requests; first, a form is built and - * rendered for the user to fill in. Then, the user fills the form in and - * submits it, triggering a second page request in which the form must be - * built and processed. By default, $form and $form_state are built from - * scratch during each of these page requests. Often, it is necessary or - * desired to persist the $form and $form_state variables from the initial - * page request to the one that processes the submission. 'cache' can be set - * to TRUE to do this. A prominent example is an Ajax-enabled form, in which - * ajax_process_form() enables form caching for all forms that include an - * element with the #ajax property. (The Ajax handler has no way to build - * the form itself, so must rely on the cached version.) Note that the - * persistence of $form and $form_state happens automatically for - * (multi-step) forms having the 'rebuild' flag set, regardless of the value - * for 'cache'. - * - no_cache: If set to TRUE the form will NOT be cached, even if 'cache' is - * set. - * - values: An associative array of values submitted to the form. The - * validation functions and submit functions use this array for nearly all - * their decision making. (Note that - * @link http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html/7#tree #tree @endlink - * determines whether the values are a flat array or an array whose structure - * parallels the $form array.) - * - input: The array of values as they were submitted by the user. These are - * raw and unvalidated, so should not be used without a thorough - * understanding of security implications. In almost all cases, code should - * use the data in the 'values' array exclusively. The most common use of - * this key is for multi-step forms that need to clear some of the user - * input when setting 'rebuild'. The values correspond to $_POST or $_GET, - * depending on the 'method' chosen. - * - always_process: If TRUE and the method is GET, a form_id is not - * necessary. This should only be used on RESTful GET forms that do NOT - * write data, as this could lead to security issues. It is useful so that - * searches do not need to have a form_id in their query arguments to - * trigger the search. - * - must_validate: Ordinarily, a form is only validated once, but there are - * times when a form is resubmitted internally and should be validated - * again. Setting this to TRUE will force that to happen. This is most - * likely to occur during Ajax operations. - * - programmed: If TRUE, the form was submitted programmatically, usually - * invoked via drupal_form_submit(). Defaults to FALSE. - * - process_input: Boolean flag. TRUE signifies correct form submission. - * This is always TRUE for programmed forms coming from drupal_form_submit() - * (see 'programmed' key), or if the form_id coming from the $_POST data is - * set and matches the current form_id. - * - submitted: If TRUE, the form has been submitted. Defaults to FALSE. - * - executed: If TRUE, the form was submitted and has been processed and - * executed. Defaults to FALSE. - * - triggering_element: (read-only) The form element that triggered - * submission. This is the same as the deprecated - * $form_state['clicked_button']. It is the element that caused submission, - * which may or may not be a button (in the case of Ajax forms). This key is - * often used to distinguish between various buttons in a submit handler, - * and is also used in Ajax handlers. - * - has_file_element: Internal. If TRUE, there is a file element and Form API - * will set the appropriate 'enctype' HTML attribute on the form. - * - groups: Internal. An array containing references to fieldsets to render - * them within vertical tabs. - * - storage: $form_state['storage'] is not a special key, and no specific - * support is provided for it in the Form API. By tradition it was - * the location where application-specific data was stored for communication - * between the submit, validation, and form builder functions, especially - * in a multi-step-style form. Form implementations may use any key(s) - * within $form_state (other than the keys listed here and other reserved - * ones used by Form API internals) for this kind of storage. The - * recommended way to ensure that the chosen key doesn't conflict with ones - * used by the Form API or other modules is to use the module name as the - * key name or a prefix for the key name. For example, the Node module uses - * $form_state['node'] in node editing forms to store information about the - * node being edited, and this information stays available across successive - * clicks of the "Preview" button as well as when the "Save" button is - * finally clicked. - * - buttons: A list containing copies of all submit and button elements in - * the form. - * - complete_form: A reference to the $form variable containing the complete - * form structure. #process, #after_build, #element_validate, and other - * handlers being invoked on a form element may use this reference to access - * other information in the form the element is contained in. - * - temporary: An array holding temporary data accessible during the current - * page request only. All $form_state properties that are not reserved keys - * (see form_state_keys_no_cache()) persist throughout a multistep form - * sequence. Form API provides this key for modules to communicate - * information across form-related functions during a single page request. - * It may be used to temporarily save data that does not need to or should - * not be cached during the whole form workflow; e.g., data that needs to be - * accessed during the current form build process only. There is no use-case - * for this functionality in Drupal core. - * - wrapper_callback: Modules that wish to pre-populate certain forms with - * common elements, such as back/next/save buttons in multi-step form - * wizards, may define a form builder function name that returns a form - * structure, which is passed on to the actual form builder function. - * Such implementations may either define the 'wrapper_callback' via - * hook_forms() or have to invoke drupal_build_form() (instead of - * drupal_get_form()) on their own in a custom menu callback to prepare - * $form_state accordingly. - * Information on how certain $form_state properties control redirection - * behavior after form submission may be found in drupal_redirect_form(). - * - * @return - * The rendered form. This function may also perform a redirect and hence may - * not return at all, depending upon the $form_state flags that were set. - * - * @see drupal_redirect_form() - */ -function drupal_build_form($form_id, &$form_state) { - // Ensure some defaults; if already set they will not be overridden. - $form_state += form_state_defaults(); - - if (!isset($form_state['input'])) { - $form_state['input'] = $form_state['method'] == 'get' ? $_GET : $_POST; - } - - if (isset($_SESSION['batch_form_state'])) { - // We've been redirected here after a batch processing. The form has - // already been processed, but needs to be rebuilt. See _batch_finished(). - $form_state = $_SESSION['batch_form_state']; - unset($_SESSION['batch_form_state']); - return drupal_rebuild_form($form_id, $form_state); - } - - // If the incoming input contains a form_build_id, we'll check the cache for a - // copy of the form in question. If it's there, we don't have to rebuild the - // form to proceed. In addition, if there is stored form_state data from a - // previous step, we'll retrieve it so it can be passed on to the form - // processing code. - $check_cache = isset($form_state['input']['form_id']) && $form_state['input']['form_id'] == $form_id && !empty($form_state['input']['form_build_id']); - if ($check_cache) { - $form = form_get_cache($form_state['input']['form_build_id'], $form_state); - } - - // If the previous bit of code didn't result in a populated $form object, we - // are hitting the form for the first time and we need to build it from - // scratch. - if (!isset($form)) { - // If we attempted to serve the form from cache, uncacheable $form_state - // keys need to be removed after retrieving and preparing the form, except - // any that were already set prior to retrieving the form. - if ($check_cache) { - $form_state_before_retrieval = $form_state; - } - - $form = drupal_retrieve_form($form_id, $form_state); - drupal_prepare_form($form_id, $form, $form_state); - - // form_set_cache() removes uncacheable $form_state keys defined in - // form_state_keys_no_cache() in order for multi-step forms to work - // properly. This means that form processing logic for single-step forms - // using $form_state['cache'] may depend on data stored in those keys - // during drupal_retrieve_form()/drupal_prepare_form(), but form - // processing should not depend on whether the form is cached or not, so - // $form_state is adjusted to match what it would be after a - // form_set_cache()/form_get_cache() sequence. These exceptions are - // allowed to survive here: - // - always_process: Does not make sense in conjunction with form caching - // in the first place, since passing form_build_id as a GET parameter is - // not desired. - // - temporary: Any assigned data is expected to survives within the same - // page request. - if ($check_cache) { - $uncacheable_keys = array_flip(array_diff(form_state_keys_no_cache(), array('always_process', 'temporary'))); - $form_state = array_diff_key($form_state, $uncacheable_keys); - $form_state += $form_state_before_retrieval; - } - } - - // Now that we have a constructed form, process it. This is where: - // - Element #process functions get called to further refine $form. - // - User input, if any, gets incorporated in the #value property of the - // corresponding elements and into $form_state['values']. - // - Validation and submission handlers are called. - // - If this submission is part of a multistep workflow, the form is rebuilt - // to contain the information of the next step. - // - If necessary, the form and form state are cached or re-cached, so that - // appropriate information persists to the next page request. - // All of the handlers in the pipeline receive $form_state by reference and - // can use it to know or update information about the state of the form. - drupal_process_form($form_id, $form, $form_state); - - // If this was a successful submission of a single-step form or the last step - // of a multi-step form, then drupal_process_form() issued a redirect to - // another page, or back to this page, but as a new request. Therefore, if - // we're here, it means that this is either a form being viewed initially - // before any user input, or there was a validation error requiring the form - // to be re-displayed, or we're in a multi-step workflow and need to display - // the form's next step. In any case, we have what we need in $form, and can - // return it for rendering. - return $form; -} - -/** - * Retrieve default values for the $form_state array. - */ -function form_state_defaults() { - return array( - 'rebuild' => FALSE, - 'rebuild_info' => array(), - 'redirect' => NULL, - // @todo 'args' is usually set, so no other default 'build_info' keys are - // appended via += form_state_defaults(). - 'build_info' => array( - 'args' => array(), - 'files' => array(), - ), - 'temporary' => array(), - 'submitted' => FALSE, - 'executed' => FALSE, - 'programmed' => FALSE, - 'cache'=> FALSE, - 'method' => 'post', - 'groups' => array(), - 'buttons' => array(), - ); -} - -/** - * Constructs a new $form from the information in $form_state. - * - * This is the key function for making multi-step forms advance from step to - * step. It is called by drupal_process_form() when all user input processing, - * including calling validation and submission handlers, for the request is - * finished. If a validate or submit handler set $form_state['rebuild'] to TRUE, - * and if other conditions don't preempt a rebuild from happening, then this - * function is called to generate a new $form, the next step in the form - * workflow, to be returned for rendering. - * - * Ajax form submissions are almost always multi-step workflows, so that is one - * common use-case during which form rebuilding occurs. See ajax_form_callback() - * for more information about creating Ajax-enabled forms. - * - * @param $form_id - * The unique string identifying the desired form. If a function - * with that name exists, it is called to build the form array. - * Modules that need to generate the same form (or very similar forms) - * using different $form_ids can implement hook_forms(), which maps - * different $form_id values to the proper form constructor function. Examples - * may be found in node_forms(), search_forms(), and user_forms(). - * @param $form_state - * A keyed array containing the current state of the form. - * @param $old_form - * (optional) A previously built $form. Used to retain the #build_id and - * #action properties in Ajax callbacks and similar partial form rebuilds. The - * only properties copied from $old_form are the ones which both exist in - * $old_form and for which $form_state['rebuild_info']['copy'][PROPERTY] is - * TRUE. If $old_form is not passed, the entire $form is rebuilt freshly. - * 'rebuild_info' needs to be a separate top-level property next to - * 'build_info', since the contained data must not be cached. - * - * @return - * The newly built form. - * - * @see drupal_process_form() - * @see ajax_form_callback() - */ -function drupal_rebuild_form($form_id, &$form_state, $old_form = NULL) { - $form = drupal_retrieve_form($form_id, $form_state); - - // If only parts of the form will be returned to the browser (e.g., Ajax or - // RIA clients), re-use the old #build_id to not require client-side code to - // manually update the hidden 'build_id' input element. - // Otherwise, a new #build_id is generated, to not clobber the previous - // build's data in the form cache; also allowing the user to go back to an - // earlier build, make changes, and re-submit. - // @see drupal_prepare_form() - if (isset($old_form['#build_id']) && !empty($form_state['rebuild_info']['copy']['#build_id'])) { - $form['#build_id'] = $old_form['#build_id']; - } - else { - $form['#build_id'] = 'form-' . drupal_hash_base64(uniqid(mt_rand(), TRUE) . mt_rand()); - } - - // #action defaults to request_uri(), but in case of Ajax and other partial - // rebuilds, the form is submitted to an alternate URL, and the original - // #action needs to be retained. - if (isset($old_form['#action']) && !empty($form_state['rebuild_info']['copy']['#action'])) { - $form['#action'] = $old_form['#action']; - } - - drupal_prepare_form($form_id, $form, $form_state); - - // Caching is normally done in drupal_process_form(), but what needs to be - // cached is the $form structure before it passes through form_builder(), - // so we need to do it here. - // @todo For Drupal 8, find a way to avoid this code duplication. - if (empty($form_state['no_cache'])) { - form_set_cache($form['#build_id'], $form, $form_state); - } - - // Clear out all group associations as these might be different when - // re-rendering the form. - $form_state['groups'] = array(); - - // Return a fully built form that is ready for rendering. - return form_builder($form_id, $form, $form_state); -} - -/** - * Fetch a form from cache. - */ -function form_get_cache($form_build_id, &$form_state) { - if ($cached = cache('form')->get('form_' . $form_build_id)) { - $form = $cached->data; - - global $user; - if ((isset($form['#cache_token']) && drupal_valid_token($form['#cache_token'])) || (!isset($form['#cache_token']) && !$user->uid)) { - if ($cached = cache('form')->get('form_state_' . $form_build_id)) { - // Re-populate $form_state for subsequent rebuilds. - $form_state = $cached->data + $form_state; - - // If the original form is contained in include files, load the files. - // @see form_load_include() - $form_state['build_info'] += array('files' => array()); - foreach ($form_state['build_info']['files'] as $file) { - if (is_array($file)) { - $file += array('type' => 'inc', 'name' => $file['module']); - module_load_include($file['type'], $file['module'], $file['name']); - } - elseif (file_exists($file)) { - require_once DRUPAL_ROOT . '/' . $file; - } - } - } - return $form; - } - } -} - -/** - * Store a form in the cache. - */ -function form_set_cache($form_build_id, $form, $form_state) { - // 6 hours cache life time for forms should be plenty. - $expire = 21600; - - // Cache form structure. - if (isset($form)) { - if ($GLOBALS['user']->uid) { - $form['#cache_token'] = drupal_get_token(); - } - cache('form')->set('form_' . $form_build_id, $form, REQUEST_TIME + $expire); - } - - // Cache form state. - if ($data = array_diff_key($form_state, array_flip(form_state_keys_no_cache()))) { - cache('form')->set('form_state_' . $form_build_id, $data, REQUEST_TIME + $expire); - } -} - -/** - * Returns an array of $form_state keys that shouldn't be cached. - */ -function form_state_keys_no_cache() { - return array( - // Public properties defined by form constructors and form handlers. - 'always_process', - 'must_validate', - 'rebuild', - 'rebuild_info', - 'redirect', - 'no_redirect', - 'temporary', - // Internal properties defined by form processing. - 'buttons', - 'triggering_element', - 'clicked_button', - 'complete_form', - 'groups', - 'input', - 'method', - 'submit_handlers', - 'submitted', - 'executed', - 'validate_handlers', - 'values', - ); -} - -/** - * Loads an include file and makes sure it is loaded whenever the form is processed. - * - * Example: - * @code - * // Load node.admin.inc from Node module. - * form_load_include($form_state, 'inc', 'node', 'node.admin'); - * @endcode - * - * Use this function instead of module_load_include() from inside a form - * constructor or any form processing logic as it ensures that the include file - * is loaded whenever the form is processed. In contrast to using - * module_load_include() directly, form_load_include() makes sure the include - * file is correctly loaded also if the form is cached. - * - * @param $form_state - * The current state of the form. - * @param $type - * The include file's type (file extension). - * @param $module - * The module to which the include file belongs. - * @param $name - * (optional) The base file name (without the $type extension). If omitted, - * $module is used; i.e., resulting in "$module.$type" by default. - * - * @return - * The filepath of the loaded include file, or FALSE if the include file was - * not found or has been loaded already. - * - * @see module_load_include() - */ -function form_load_include(&$form_state, $type, $module, $name = NULL) { - if (!isset($name)) { - $name = $module; - } - if (!isset($form_state['build_info']['files']["$module:$name.$type"])) { - // Only add successfully included files to the form state. - if ($result = module_load_include($type, $module, $name)) { - $form_state['build_info']['files']["$module:$name.$type"] = array( - 'type' => $type, - 'module' => $module, - 'name' => $name, - ); - return $result; - } - } - return FALSE; -} - -/** - * Retrieves, populates, and processes a form. - * - * This function allows you to supply values for form elements and submit a - * form for processing. Compare to drupal_get_form(), which also builds and - * processes a form, but does not allow you to supply values. - * - * There is no return value, but you can check to see if there are errors - * by calling form_get_errors(). - * - * @param $form_id - * The unique string identifying the desired form. If a function - * with that name exists, it is called to build the form array. - * Modules that need to generate the same form (or very similar forms) - * using different $form_ids can implement hook_forms(), which maps - * different $form_id values to the proper form constructor function. Examples - * may be found in node_forms(), search_forms(), and user_forms(). - * @param $form_state - * A keyed array containing the current state of the form. Most important is - * the $form_state['values'] collection, a tree of data used to simulate the - * incoming $_POST information from a user's form submission. If a key is not - * filled in $form_state['values'], then the default value of the respective - * element is used. To submit an unchecked checkbox or other control that - * browsers submit by not having a $_POST entry, include the key, but set the - * value to NULL. - * @param ... - * Any additional arguments are passed on to the functions called by - * drupal_form_submit(), including the unique form constructor function. - * For example, the node_edit form requires that a node object be passed - * in here when it is called. Arguments that need to be passed by reference - * should not be included here, but rather placed directly in the $form_state - * build info array so that the reference can be preserved. For example, a - * form builder function with the following signature: - * @code - * function mymodule_form($form, &$form_state, &$object) { - * } - * @endcode - * would be called via drupal_form_submit() as follows: - * @code - * $form_state['values'] = $my_form_values; - * $form_state['build_info']['args'] = array(&$object); - * drupal_form_submit('mymodule_form', $form_state); - * @endcode - * For example: - * @code - * // register a new user - * $form_state = array(); - * $form_state['values']['name'] = 'robo-user'; - * $form_state['values']['mail'] = 'robouser@example.com'; - * $form_state['values']['pass']['pass1'] = 'password'; - * $form_state['values']['pass']['pass2'] = 'password'; - * $form_state['values']['op'] = t('Create new account'); - * drupal_form_submit('user_register_form', $form_state); - * @endcode - */ -function drupal_form_submit($form_id, &$form_state) { - if (!isset($form_state['build_info']['args'])) { - $args = func_get_args(); - array_shift($args); - array_shift($args); - $form_state['build_info']['args'] = $args; - } - // Merge in default values. - $form_state += form_state_defaults(); - - // Populate $form_state['input'] with the submitted values before retrieving - // the form, to be consistent with what drupal_build_form() does for - // non-programmatic submissions (form builder functions may expect it to be - // there). - $form_state['input'] = $form_state['values']; - - $form_state['programmed'] = TRUE; - $form = drupal_retrieve_form($form_id, $form_state); - // Programmed forms are always submitted. - $form_state['submitted'] = TRUE; - - // Reset form validation. - $form_state['must_validate'] = TRUE; - form_clear_error(); - - drupal_prepare_form($form_id, $form, $form_state); - drupal_process_form($form_id, $form, $form_state); -} - -/** - * Retrieves the structured array that defines a given form. - * - * @param $form_id - * The unique string identifying the desired form. If a function - * with that name exists, it is called to build the form array. - * Modules that need to generate the same form (or very similar forms) - * using different $form_ids can implement hook_forms(), which maps - * different $form_id values to the proper form constructor function. - * @param $form_state - * A keyed array containing the current state of the form, including the - * additional arguments to drupal_get_form() or drupal_form_submit() in the - * 'args' component of the array. - */ -function drupal_retrieve_form($form_id, &$form_state) { - $forms = &drupal_static(__FUNCTION__); - - // Record the filepath of the include file containing the original form, so - // the form builder callbacks can be loaded when the form is being rebuilt - // from cache on a different path (such as 'system/ajax'). See - // form_get_cache(). - // $menu_get_item() is not available at installation time. - if (!isset($form_state['build_info']['files']['menu']) && !defined('MAINTENANCE_MODE')) { - $item = menu_get_item(); - if (!empty($item['include_file'])) { - // Do not use form_load_include() here, as the file is already loaded. - // Anyway, form_get_cache() is able to handle filepaths too. - $form_state['build_info']['files']['menu'] = $item['include_file']; - } - } - - // We save two copies of the incoming arguments: one for modules to use - // when mapping form ids to constructor functions, and another to pass to - // the constructor function itself. - $args = $form_state['build_info']['args']; - - // We first check to see if there's a function named after the $form_id. - // If there is, we simply pass the arguments on to it to get the form. - if (!function_exists($form_id)) { - // In cases where many form_ids need to share a central constructor function, - // such as the node editing form, modules can implement hook_forms(). It - // maps one or more form_ids to the correct constructor functions. - // - // We cache the results of that hook to save time, but that only works - // for modules that know all their form_ids in advance. (A module that - // adds a small 'rate this comment' form to each comment in a list - // would need a unique form_id for each one, for example.) - // - // So, we call the hook if $forms isn't yet populated, OR if it doesn't - // yet have an entry for the requested form_id. - if (!isset($forms) || !isset($forms[$form_id])) { - $forms = module_invoke_all('forms', $form_id, $args); - } - $form_definition = $forms[$form_id]; - if (isset($form_definition['callback arguments'])) { - $args = array_merge($form_definition['callback arguments'], $args); - } - if (isset($form_definition['callback'])) { - $callback = $form_definition['callback']; - $form_state['build_info']['base_form_id'] = $callback; - } - // In case $form_state['wrapper_callback'] is not defined already, we also - // allow hook_forms() to define one. - if (!isset($form_state['wrapper_callback']) && isset($form_definition['wrapper_callback'])) { - $form_state['wrapper_callback'] = $form_definition['wrapper_callback']; - } - } - - $form = array(); - // Assign a default CSS class name based on $form_id. - // This happens here and not in drupal_prepare_form() in order to allow the - // form constructor function to override or remove the default class. - $form['#attributes']['class'][] = drupal_html_class($form_id); - // Same for the base form ID, if any. - if (isset($form_state['build_info']['base_form_id'])) { - $form['#attributes']['class'][] = drupal_html_class($form_state['build_info']['base_form_id']); - } - - // We need to pass $form_state by reference in order for forms to modify it, - // since call_user_func_array() requires that referenced variables are passed - // explicitly. - $args = array_merge(array($form, &$form_state), $args); - - // When the passed $form_state (not using drupal_get_form()) defines a - // 'wrapper_callback', then it requests to invoke a separate (wrapping) form - // builder function to pre-populate the $form array with form elements, which - // the actual form builder function ($callback) expects. This allows for - // pre-populating a form with common elements for certain forms, such as - // back/next/save buttons in multi-step form wizards. See drupal_build_form(). - if (isset($form_state['wrapper_callback']) && function_exists($form_state['wrapper_callback'])) { - $form = call_user_func_array($form_state['wrapper_callback'], $args); - // Put the prepopulated $form into $args. - $args[0] = $form; - } - - // If $callback was returned by a hook_forms() implementation, call it. - // Otherwise, call the function named after the form id. - $form = call_user_func_array(isset($callback) ? $callback : $form_id, $args); - $form['#form_id'] = $form_id; - - return $form; -} - -/** - * Processes a form submission. - * - * This function is the heart of form API. The form gets built, validated and in - * appropriate cases, submitted and rebuilt. - * - * @param $form_id - * The unique string identifying the current form. - * @param $form - * An associative array containing the structure of the form. - * @param $form_state - * A keyed array containing the current state of the form. This - * includes the current persistent storage data for the form, and - * any data passed along by earlier steps when displaying a - * multi-step form. Additional information, like the sanitized $_POST - * data, is also accumulated here. - */ -function drupal_process_form($form_id, &$form, &$form_state) { - $form_state['values'] = array(); - - // With $_GET, these forms are always submitted if requested. - if ($form_state['method'] == 'get' && !empty($form_state['always_process'])) { - if (!isset($form_state['input']['form_build_id'])) { - $form_state['input']['form_build_id'] = $form['#build_id']; - } - if (!isset($form_state['input']['form_id'])) { - $form_state['input']['form_id'] = $form_id; - } - if (!isset($form_state['input']['form_token']) && isset($form['#token'])) { - $form_state['input']['form_token'] = drupal_get_token($form['#token']); - } - } - - // form_builder() finishes building the form by calling element #process - // functions and mapping user input, if any, to #value properties, and also - // storing the values in $form_state['values']. We need to retain the - // unprocessed $form in case it needs to be cached. - $unprocessed_form = $form; - $form = form_builder($form_id, $form, $form_state); - - // Only process the input if we have a correct form submission. - if ($form_state['process_input']) { - drupal_validate_form($form_id, $form, $form_state); - - // drupal_html_id() maintains a cache of element IDs it has seen, - // so it can prevent duplicates. We want to be sure we reset that - // cache when a form is processed, so scenarios that result in - // the form being built behind the scenes and again for the - // browser don't increment all the element IDs needlessly. - drupal_static_reset('drupal_html_id'); - - if ($form_state['submitted'] && !form_get_errors() && !$form_state['rebuild']) { - // Execute form submit handlers. - form_execute_handlers('submit', $form, $form_state); - - // We'll clear out the cached copies of the form and its stored data - // here, as we've finished with them. The in-memory copies are still - // here, though. - if (!variable_get('cache', 0) && !empty($form_state['values']['form_build_id'])) { - cache('form')->delete('form_' . $form_state['values']['form_build_id']); - cache('form')->delete('form_state_' . $form_state['values']['form_build_id']); - } - - // If batches were set in the submit handlers, we process them now, - // possibly ending execution. We make sure we do not react to the batch - // that is already being processed (if a batch operation performs a - // drupal_form_submit). - if ($batch =& batch_get() && !isset($batch['current_set'])) { - // Store $form_state information in the batch definition. - // We need the full $form_state when either: - // - Some submit handlers were saved to be called during batch - // processing. See form_execute_handlers(). - // - The form is multistep. - // In other cases, we only need the information expected by - // drupal_redirect_form(). - if ($batch['has_form_submits'] || !empty($form_state['rebuild'])) { - $batch['form_state'] = $form_state; - } - else { - $batch['form_state'] = array_intersect_key($form_state, array_flip(array('programmed', 'rebuild', 'storage', 'no_redirect', 'redirect'))); - } - - $batch['progressive'] = !$form_state['programmed']; - batch_process(); - - // Execution continues only for programmatic forms. - // For 'regular' forms, we get redirected to the batch processing - // page. Form redirection will be handled in _batch_finished(), - // after the batch is processed. - } - - // Set a flag to indicate the the form has been processed and executed. - $form_state['executed'] = TRUE; - - // Redirect the form based on values in $form_state. - drupal_redirect_form($form_state); - } - - // Don't rebuild or cache form submissions invoked via drupal_form_submit(). - if (!empty($form_state['programmed'])) { - return; - } - - // If $form_state['rebuild'] has been set and input has been processed - // without validation errors, we are in a multi-step workflow that is not - // yet complete. A new $form needs to be constructed based on the changes - // made to $form_state during this request. Normally, a submit handler sets - // $form_state['rebuild'] if a fully executed form requires another step. - // However, for forms that have not been fully executed (e.g., Ajax - // submissions triggered by non-buttons), there is no submit handler to set - // $form_state['rebuild']. It would not make sense to redisplay the - // identical form without an error for the user to correct, so we also - // rebuild error-free non-executed forms, regardless of - // $form_state['rebuild']. - // @todo D8: Simplify this logic; considering Ajax and non-HTML front-ends, - // along with element-level #submit properties, it makes no sense to have - // divergent form execution based on whether the triggering element has - // #executes_submit_callback set to TRUE. - if (($form_state['rebuild'] || !$form_state['executed']) && !form_get_errors()) { - // Form building functions (e.g., _form_builder_handle_input_element()) - // may use $form_state['rebuild'] to determine if they are running in the - // context of a rebuild, so ensure it is set. - $form_state['rebuild'] = TRUE; - $form = drupal_rebuild_form($form_id, $form_state, $form); - } - } - - // After processing the form, the form builder or a #process callback may - // have set $form_state['cache'] to indicate that the form and form state - // shall be cached. But the form may only be cached if the 'no_cache' property - // is not set to TRUE. Only cache $form as it was prior to form_builder(), - // because form_builder() must run for each request to accommodate new user - // input. Rebuilt forms are not cached here, because drupal_rebuild_form() - // already takes care of that. - if (!$form_state['rebuild'] && $form_state['cache'] && empty($form_state['no_cache'])) { - form_set_cache($form['#build_id'], $unprocessed_form, $form_state); - } -} - -/** - * Prepares a structured form array by adding required elements, - * executing any hook_form_alter functions, and optionally inserting - * a validation token to prevent tampering. - * - * @param $form_id - * A unique string identifying the form for validation, submission, - * theming, and hook_form_alter functions. - * @param $form - * An associative array containing the structure of the form. - * @param $form_state - * A keyed array containing the current state of the form. Passed - * in here so that hook_form_alter() calls can use it, as well. - */ -function drupal_prepare_form($form_id, &$form, &$form_state) { - global $user; - - $form['#type'] = 'form'; - $form_state['programmed'] = isset($form_state['programmed']) ? $form_state['programmed'] : FALSE; - - // Fix the form method, if it is 'get' in $form_state, but not in $form. - if ($form_state['method'] == 'get' && !isset($form['#method'])) { - $form['#method'] = 'get'; - } - - // Generate a new #build_id for this form, if none has been set already. The - // form_build_id is used as key to cache a particular build of the form. For - // multi-step forms, this allows the user to go back to an earlier build, make - // changes, and re-submit. - // @see drupal_build_form() - // @see drupal_rebuild_form() - if (!isset($form['#build_id'])) { - $form['#build_id'] = 'form-' . drupal_hash_base64(uniqid(mt_rand(), TRUE) . mt_rand()); - } - $form['form_build_id'] = array( - '#type' => 'hidden', - '#value' => $form['#build_id'], - '#id' => $form['#build_id'], - '#name' => 'form_build_id', - ); - - // Add a token, based on either #token or form_id, to any form displayed to - // authenticated users. This ensures that any submitted form was actually - // requested previously by the user and protects against cross site request - // forgeries. - // This does not apply to programmatically submitted forms. Furthermore, since - // tokens are session-bound and forms displayed to anonymous users are very - // likely cached, we cannot assign a token for them. - // During installation, there is no $user yet. - if (!empty($user->uid) && !$form_state['programmed']) { - // Form constructors may explicitly set #token to FALSE when cross site - // request forgery is irrelevant to the form, such as search forms. - if (isset($form['#token']) && $form['#token'] === FALSE) { - unset($form['#token']); - } - // Otherwise, generate a public token based on the form id. - else { - $form['#token'] = $form_id; - $form['form_token'] = array( - '#id' => drupal_html_id('edit-' . $form_id . '-form-token'), - '#type' => 'token', - '#default_value' => drupal_get_token($form['#token']), - ); - } - } - - if (isset($form_id)) { - $form['form_id'] = array( - '#type' => 'hidden', - '#value' => $form_id, - '#id' => drupal_html_id("edit-$form_id"), - ); - } - if (!isset($form['#id'])) { - $form['#id'] = drupal_html_id($form_id); - } - - $form += element_info('form'); - $form += array('#tree' => FALSE, '#parents' => array()); - - if (!isset($form['#validate'])) { - // Ensure that modules can rely on #validate being set. - $form['#validate'] = array(); - // Check for a handler specific to $form_id. - if (function_exists($form_id . '_validate')) { - $form['#validate'][] = $form_id . '_validate'; - } - // Otherwise check whether this is a shared form and whether there is a - // handler for the shared $form_id. - elseif (isset($form_state['build_info']['base_form_id']) && function_exists($form_state['build_info']['base_form_id'] . '_validate')) { - $form['#validate'][] = $form_state['build_info']['base_form_id'] . '_validate'; - } - } - - if (!isset($form['#submit'])) { - // Ensure that modules can rely on #submit being set. - $form['#submit'] = array(); - // Check for a handler specific to $form_id. - if (function_exists($form_id . '_submit')) { - $form['#submit'][] = $form_id . '_submit'; - } - // Otherwise check whether this is a shared form and whether there is a - // handler for the shared $form_id. - elseif (isset($form_state['build_info']['base_form_id']) && function_exists($form_state['build_info']['base_form_id'] . '_submit')) { - $form['#submit'][] = $form_state['build_info']['base_form_id'] . '_submit'; - } - } - - // If no #theme has been set, automatically apply theme suggestions. - // theme_form() itself is in #theme_wrappers and not #theme. Therefore, the - // #theme function only has to care for rendering the inner form elements, - // not the form itself. - if (!isset($form['#theme'])) { - $form['#theme'] = array($form_id); - if (isset($form_state['build_info']['base_form_id'])) { - $form['#theme'][] = $form_state['build_info']['base_form_id']; - } - } - - // Invoke hook_form_alter(), hook_form_BASE_FORM_ID_alter(), and - // hook_form_FORM_ID_alter() implementations. - $hooks = array('form'); - if (isset($form_state['build_info']['base_form_id'])) { - $hooks[] = 'form_' . $form_state['build_info']['base_form_id']; - } - $hooks[] = 'form_' . $form_id; - drupal_alter($hooks, $form, $form_state, $form_id); -} - - -/** - * Validates user-submitted form data from the $form_state using - * the validate functions defined in a structured form array. - * - * @param $form_id - * A unique string identifying the form for validation, submission, - * theming, and hook_form_alter functions. - * @param $form - * An associative array containing the structure of the form, which is passed - * by reference. Form validation handlers are able to alter the form structure - * (like #process and #after_build callbacks during form building) in case of - * a validation error. If a validation handler alters the form structure, it - * is responsible for validating the values of changed form elements in - * $form_state['values'] to prevent form submit handlers from receiving - * unvalidated values. - * @param $form_state - * A keyed array containing the current state of the form. The current - * user-submitted data is stored in $form_state['values'], though - * form validation functions are passed an explicit copy of the - * values for the sake of simplicity. Validation handlers can also - * $form_state to pass information on to submit handlers. For example: - * $form_state['data_for_submission'] = $data; - * This technique is useful when validation requires file parsing, - * web service requests, or other expensive requests that should - * not be repeated in the submission step. - */ -function drupal_validate_form($form_id, &$form, &$form_state) { - $validated_forms = &drupal_static(__FUNCTION__, array()); - - if (isset($validated_forms[$form_id]) && empty($form_state['must_validate'])) { - return; - } - - // If the session token was set by drupal_prepare_form(), ensure that it - // matches the current user's session. - if (isset($form['#token'])) { - if (!drupal_valid_token($form_state['values']['form_token'], $form['#token'])) { - $path = current_path(); - $query = drupal_get_query_parameters(); - $url = url($path, array('query' => $query)); - - // Setting this error will cause the form to fail validation. - form_set_error('form_token', t('The form has become outdated. Copy any unsaved work in the form below and then <a href="@link">reload this page</a>.', array('@link' => $url))); - } - } - - _form_validate($form, $form_state, $form_id); - $validated_forms[$form_id] = TRUE; - - // If validation errors are limited then remove any non validated form values, - // so that only values that passed validation are left for submit callbacks. - if (isset($form_state['triggering_element']['#limit_validation_errors']) && $form_state['triggering_element']['#limit_validation_errors'] !== FALSE) { - $values = array(); - foreach ($form_state['triggering_element']['#limit_validation_errors'] as $section) { - // If the section exists within $form_state['values'], even if the value - // is NULL, copy it to $values. - $section_exists = NULL; - $value = drupal_array_get_nested_value($form_state['values'], $section, $section_exists); - if ($section_exists) { - drupal_array_set_nested_value($values, $section, $value); - } - } - // A button's #value does not require validation, so for convenience we - // allow the value of the clicked button to be retained in its normal - // $form_state['values'] locations, even if these locations are not included - // in #limit_validation_errors. - if (isset($form_state['triggering_element']['#button_type'])) { - $button_value = $form_state['triggering_element']['#value']; - - // Like all input controls, the button value may be in the location - // dictated by #parents. If it is, copy it to $values, but do not override - // what may already be in $values. - $parents = $form_state['triggering_element']['#parents']; - if (!drupal_array_nested_key_exists($values, $parents) && drupal_array_get_nested_value($form_state['values'], $parents) === $button_value) { - drupal_array_set_nested_value($values, $parents, $button_value); - } - - // Additionally, form_builder() places the button value in - // $form_state['values'][BUTTON_NAME]. If it's still there, after - // validation handlers have run, copy it to $values, but do not override - // what may already be in $values. - $name = $form_state['triggering_element']['#name']; - if (!isset($values[$name]) && isset($form_state['values'][$name]) && $form_state['values'][$name] === $button_value) { - $values[$name] = $button_value; - } - } - $form_state['values'] = $values; - } -} - -/** - * Redirects the user to a URL after a form has been processed. - * - * After a form was executed, the data in $form_state controls whether the form - * is redirected. By default, we redirect to a new destination page. The path - * of the destination page can be set in $form_state['redirect'], as either a - * string containing the destination or an array of arguments compatible with - * drupal_goto(). If that is not set, the user is redirected to the current - * page to display a fresh, unpopulated copy of the form. - * - * For example, to redirect to 'node': - * @code - * $form_state['redirect'] = 'node'; - * @endcode - * Or to redirect to 'node/123?foo=bar#baz': - * @code - * $form_state['redirect'] = array( - * 'node/123', - * array( - * 'query' => array( - * 'foo' => 'bar', - * ), - * 'fragment' => 'baz', - * ), - * ); - * @endcode - * - * There are several triggers that may prevent a redirection though: - * - If $form_state['redirect'] is FALSE, a form builder function or form - * validation/submit handler does not want a user to be redirected, which - * means that drupal_goto() is not invoked. For most forms, the redirection - * logic will be the same regardless of whether $form_state['redirect'] is - * undefined or FALSE. However, in case it was not defined and the current - * request contains a 'destination' query string, drupal_goto() will redirect - * to that given destination instead. Only setting $form_state['redirect'] to - * FALSE will prevent any redirection. - * - If $form_state['no_redirect'] is TRUE, then the callback that originally - * built the form explicitly disallows any redirection, regardless of the - * redirection value in $form_state['redirect']. For example, ajax_get_form() - * defines $form_state['no_redirect'] when building a form in an Ajax - * callback to prevent any redirection. $form_state['no_redirect'] should NOT - * be altered by form builder functions or form validation/submit handlers. - * - If $form_state['programmed'] is TRUE, the form submission was usually - * invoked via drupal_form_submit(), so any redirection would break the script - * that invoked drupal_form_submit(). - * - If $form_state['rebuild'] is TRUE, the form needs to be rebuilt without - * redirection. - * - * @param $form_state - * A keyed array containing the current state of the form. - * - * @see drupal_process_form() - * @see drupal_build_form() - */ -function drupal_redirect_form($form_state) { - // Skip redirection for form submissions invoked via drupal_form_submit(). - if (!empty($form_state['programmed'])) { - return; - } - // Skip redirection if rebuild is activated. - if (!empty($form_state['rebuild'])) { - return; - } - // Skip redirection if it was explicitly disallowed. - if (!empty($form_state['no_redirect'])) { - return; - } - // Only invoke drupal_goto() if redirect value was not set to FALSE. - if (!isset($form_state['redirect']) || $form_state['redirect'] !== FALSE) { - if (isset($form_state['redirect'])) { - if (is_array($form_state['redirect'])) { - call_user_func_array('drupal_goto', $form_state['redirect']); - } - else { - // This function can be called from the installer, which guarantees - // that $redirect will always be a string, so catch that case here - // and use the appropriate redirect function. - $function = drupal_installation_attempted() ? 'install_goto' : 'drupal_goto'; - $function($form_state['redirect']); - } - } - drupal_goto($_GET['q']); - } -} - -/** - * Performs validation on form elements. First ensures required fields are - * completed, #maxlength is not exceeded, and selected options were in the - * list of options given to the user. Then calls user-defined validators. - * - * @param $elements - * An associative array containing the structure of the form. - * @param $form_state - * A keyed array containing the current state of the form. The current - * user-submitted data is stored in $form_state['values'], though - * form validation functions are passed an explicit copy of the - * values for the sake of simplicity. Validation handlers can also - * $form_state to pass information on to submit handlers. For example: - * $form_state['data_for_submission'] = $data; - * This technique is useful when validation requires file parsing, - * web service requests, or other expensive requests that should - * not be repeated in the submission step. - * @param $form_id - * A unique string identifying the form for validation, submission, - * theming, and hook_form_alter functions. - */ -function _form_validate(&$elements, &$form_state, $form_id = NULL) { - // Also used in the installer, pre-database setup. - $t = get_t(); - - // Recurse through all children. - foreach (element_children($elements) as $key) { - if (isset($elements[$key]) && $elements[$key]) { - _form_validate($elements[$key], $form_state); - } - } - - // Validate the current input. - if (!isset($elements['#validated']) || !$elements['#validated']) { - // The following errors are always shown. - if (isset($elements['#needs_validation'])) { - // Verify that the value is not longer than #maxlength. - if (isset($elements['#maxlength']) && drupal_strlen($elements['#value']) > $elements['#maxlength']) { - form_error($elements, $t('!name cannot be longer than %max characters but is currently %length characters long.', array('!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'], '%max' => $elements['#maxlength'], '%length' => drupal_strlen($elements['#value'])))); - } - - if (isset($elements['#options']) && isset($elements['#value'])) { - if ($elements['#type'] == 'select') { - $options = form_options_flatten($elements['#options']); - } - else { - $options = $elements['#options']; - } - if (is_array($elements['#value'])) { - $value = in_array($elements['#type'], array('checkboxes', 'tableselect')) ? array_keys($elements['#value']) : $elements['#value']; - foreach ($value as $v) { - if (!isset($options[$v])) { - form_error($elements, $t('An illegal choice has been detected. Please contact the site administrator.')); - watchdog('form', 'Illegal choice %choice in !name element.', array('%choice' => $v, '!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR); - } - } - } - // Non-multiple select fields always have a value in HTML. If the user - // does not change the form, it will be the value of the first option. - // Because of this, form validation for the field will almost always - // pass, even if the user did not select anything. To work around this - // browser behavior, required select fields without a #default_value get - // an additional, first empty option. In case the submitted value is - // identical to the empty option's value, we reset the element's value - // to NULL to trigger the regular #required handling below. - // @see form_process_select() - elseif ($elements['#type'] == 'select' && !$elements['#multiple'] && $elements['#required'] && !isset($elements['#default_value']) && $elements['#value'] === $elements['#empty_value']) { - $elements['#value'] = NULL; - form_set_value($elements, NULL, $form_state); - } - elseif (!isset($options[$elements['#value']])) { - form_error($elements, $t('An illegal choice has been detected. Please contact the site administrator.')); - watchdog('form', 'Illegal choice %choice in %name element.', array('%choice' => $elements['#value'], '%name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']), WATCHDOG_ERROR); - } - } - } - - // While this element is being validated, it may be desired that some calls - // to form_set_error() be suppressed and not result in a form error, so - // that a button that implements low-risk functionality (such as "Previous" - // or "Add more") that doesn't require all user input to be valid can still - // have its submit handlers triggered. The triggering element's - // #limit_validation_errors property contains the information for which - // errors are needed, and all other errors are to be suppressed. The - // #limit_validation_errors property is ignored if submit handlers will run, - // but the element doesn't have a #submit property, because it's too large a - // security risk to have any invalid user input when executing form-level - // submit handlers. - if (isset($form_state['triggering_element']['#limit_validation_errors']) && ($form_state['triggering_element']['#limit_validation_errors'] !== FALSE) && !($form_state['submitted'] && !isset($form_state['triggering_element']['#submit']))) { - form_set_error(NULL, '', $form_state['triggering_element']['#limit_validation_errors']); - } - // If submit handlers won't run (due to the submission having been triggered - // by an element whose #executes_submit_callback property isn't TRUE), then - // it's safe to suppress all validation errors, and we do so by default, - // which is particularly useful during an Ajax submission triggered by a - // non-button. An element can override this default by setting the - // #limit_validation_errors property. For button element types, - // #limit_validation_errors defaults to FALSE (via system_element_info()), - // so that full validation is their default behavior. - elseif (isset($form_state['triggering_element']) && !isset($form_state['triggering_element']['#limit_validation_errors']) && !$form_state['submitted']) { - form_set_error(NULL, '', array()); - } - // As an extra security measure, explicitly turn off error suppression if - // one of the above conditions wasn't met. Since this is also done at the - // end of this function, doing it here is only to handle the rare edge case - // where a validate handler invokes form processing of another form. - else { - drupal_static_reset('form_set_error:limit_validation_errors'); - } - - // Make sure a value is passed when the field is required. - if (isset($elements['#needs_validation']) && $elements['#required']) { - // A simple call to empty() will not cut it here as some fields, like - // checkboxes, can return a valid value of '0'. Instead, check the - // length if it's a string, and the item count if it's an array. - // An unchecked checkbox has a #value of integer 0, different than string - // '0', which could be a valid value. - $is_empty_multiple = (!count($elements['#value'])); - $is_empty_string = (is_string($elements['#value']) && drupal_strlen(trim($elements['#value'])) == 0); - $is_empty_value = ($elements['#value'] === 0); - if ($is_empty_multiple || $is_empty_string || $is_empty_value) { - // Although discouraged, a #title is not mandatory for form elements. In - // case there is no #title, we cannot set a form error message. - // Instead of setting no #title, form constructors are encouraged to set - // #title_display to 'invisible' to improve accessibility. - if (isset($elements['#title'])) { - form_error($elements, $t('!name field is required.', array('!name' => $elements['#title']))); - } - else { - form_error($elements); - } - } - } - - // Call user-defined form level validators. - if (isset($form_id)) { - form_execute_handlers('validate', $elements, $form_state); - } - // Call any element-specific validators. These must act on the element - // #value data. - elseif (isset($elements['#element_validate'])) { - foreach ($elements['#element_validate'] as $function) { - $function($elements, $form_state, $form_state['complete_form']); - } - } - $elements['#validated'] = TRUE; - } - - // Done validating this element, so turn off error suppression. - // _form_validate() turns it on again when starting on the next element, if - // it's still appropriate to do so. - drupal_static_reset('form_set_error:limit_validation_errors'); -} - -/** - * A helper function used to execute custom validation and submission - * handlers for a given form. Button-specific handlers are checked - * first. If none exist, the function falls back to form-level handlers. - * - * @param $type - * The type of handler to execute. 'validate' or 'submit' are the - * defaults used by Form API. - * @param $form - * An associative array containing the structure of the form. - * @param $form_state - * A keyed array containing the current state of the form. If the user - * submitted the form by clicking a button with custom handler functions - * defined, those handlers will be stored here. - */ -function form_execute_handlers($type, &$form, &$form_state) { - $return = FALSE; - // If there was a button pressed, use its handlers. - if (isset($form_state[$type . '_handlers'])) { - $handlers = $form_state[$type . '_handlers']; - } - // Otherwise, check for a form-level handler. - elseif (isset($form['#' . $type])) { - $handlers = $form['#' . $type]; - } - else { - $handlers = array(); - } - - foreach ($handlers as $function) { - // Check if a previous _submit handler has set a batch, but make sure we - // do not react to a batch that is already being processed (for instance - // if a batch operation performs a drupal_form_submit()). - if ($type == 'submit' && ($batch =& batch_get()) && !isset($batch['id'])) { - // Some previous submit handler has set a batch. To ensure correct - // execution order, store the call in a special 'control' batch set. - // See _batch_next_set(). - $batch['sets'][] = array('form_submit' => $function); - $batch['has_form_submits'] = TRUE; - } - else { - $function($form, $form_state); - } - $return = TRUE; - } - return $return; -} - -/** - * Files an error against a form element. - * - * When a validation error is detected, the validator calls form_set_error() to - * indicate which element needs to be changed and provide an error message. This - * causes the Form API to not execute the form submit handlers, and instead to - * re-display the form to the user with the corresponding elements rendered with - * an 'error' CSS class (shown as red by default). - * - * The standard form_set_error() behavior can be changed if a button provides - * the #limit_validation_errors property. Multistep forms not wanting to - * validate the whole form can set #limit_validation_errors on buttons to - * limit validation errors to only certain elements. For example, pressing the - * "Previous" button in a multistep form should not fire validation errors just - * because the current step has invalid values. If #limit_validation_errors is - * set on a clicked button, the button must also define a #submit property - * (may be set to an empty array). Any #submit handlers will be executed even if - * there is invalid input, so extreme care should be taken with respect to any - * actions taken by them. This is typically not a problem with buttons like - * "Previous" or "Add more" that do not invoke persistent storage of the - * submitted form values. Do not use the #limit_validation_errors property on - * buttons that trigger saving of form values to the database. - * - * The #limit_validation_errors property is a list of "sections" within - * $form_state['values'] that must contain valid values. Each "section" is an - * array with the ordered set of keys needed to reach that part of - * $form_state['values'] (i.e., the #parents property of the element). - * - * Example 1: Allow the "Previous" button to function, regardless of whether any - * user input is valid. - * - * @code - * $form['actions']['previous'] = array( - * '#type' => 'submit', - * '#value' => t('Previous'), - * '#limit_validation_errors' => array(), // No validation. - * '#submit' => array('some_submit_function'), // #submit required. - * ); - * @endcode - * - * Example 2: Require some, but not all, user input to be valid to process the - * submission of a "Previous" button. - * - * @code - * $form['actions']['previous'] = array( - * '#type' => 'submit', - * '#value' => t('Previous'), - * '#limit_validation_errors' => array( - * array('step1'), // Validate $form_state['values']['step1']. - * array('foo', 'bar'), // Validate $form_state['values']['foo']['bar']. - * ), - * '#submit' => array('some_submit_function'), // #submit required. - * ); - * @endcode - * - * This will require $form_state['values']['step1'] and everything within it - * (for example, $form_state['values']['step1']['choice']) to be valid, so - * calls to form_set_error('step1', $message) or - * form_set_error('step1][choice', $message) will prevent the submit handlers - * from running, and result in the error message being displayed to the user. - * However, calls to form_set_error('step2', $message) and - * form_set_error('step2][groupX][choiceY', $message) will be suppressed, - * resulting in the message not being displayed to the user, and the submit - * handlers will run despite $form_state['values']['step2'] and - * $form_state['values']['step2']['groupX']['choiceY'] containing invalid - * values. Errors for an invalid $form_state['values']['foo'] will be - * suppressed, but errors flagging invalid values for - * $form_state['values']['foo']['bar'] and everything within it will be - * flagged and submission prevented. - * - * Partial form validation is implemented by suppressing errors rather than by - * skipping the input processing and validation steps entirely, because some - * forms have button-level submit handlers that call Drupal API functions that - * assume that certain data exists within $form_state['values'], and while not - * doing anything with that data that requires it to be valid, PHP errors - * would be triggered if the input processing and validation steps were fully - * skipped. - * @see http://drupal.org/node/370537 - * @see http://drupal.org/node/763376 - * - * @param $name - * The name of the form element. If the #parents property of your form - * element is array('foo', 'bar', 'baz') then you may set an error on 'foo' - * or 'foo][bar][baz'. Setting an error on 'foo' sets an error for every - * element where the #parents array starts with 'foo'. - * @param $message - * The error message to present to the user. - * @param $limit_validation_errors - * Internal use only. The #limit_validation_errors property of the clicked - * button, if it exists. - * - * @return - * Return value is for internal use only. To get a list of errors, use - * form_get_errors() or form_get_error(). - */ -function form_set_error($name = NULL, $message = '', $limit_validation_errors = NULL) { - $form = &drupal_static(__FUNCTION__, array()); - $sections = &drupal_static(__FUNCTION__ . ':limit_validation_errors'); - if (isset($limit_validation_errors)) { - $sections = $limit_validation_errors; - } - - if (isset($name) && !isset($form[$name])) { - $record = TRUE; - if (isset($sections)) { - // #limit_validation_errors is an array of "sections" within which user - // input must be valid. If the element is within one of these sections, - // the error must be recorded. Otherwise, it can be suppressed. - // #limit_validation_errors can be an empty array, in which case all - // errors are suppressed. For example, a "Previous" button might want its - // submit action to be triggered even if none of the submitted values are - // valid. - $record = FALSE; - foreach ($sections as $section) { - // Exploding by '][' reconstructs the element's #parents. If the - // reconstructed #parents begin with the same keys as the specified - // section, then the element's values are within the part of - // $form_state['values'] that the clicked button requires to be valid, - // so errors for this element must be recorded. As the exploded array - // will all be strings, we need to cast every value of the section - // array to string. - if (array_slice(explode('][', $name), 0, count($section)) === array_map('strval', $section)) { - $record = TRUE; - break; - } - } - } - if ($record) { - $form[$name] = $message; - if ($message) { - drupal_set_message($message, 'error'); - } - } - } - - return $form; -} - -/** - * Clear all errors against all form elements made by form_set_error(). - */ -function form_clear_error() { - drupal_static_reset('form_set_error'); -} - -/** - * Return an associative array of all errors. - */ -function form_get_errors() { - $form = form_set_error(); - if (!empty($form)) { - return $form; - } -} - -/** - * Returns the error message filed against the given form element. - * - * Form errors higher up in the form structure override deeper errors as well as - * errors on the element itself. - */ -function form_get_error($element) { - $form = form_set_error(); - $parents = array(); - foreach ($element['#parents'] as $parent) { - $parents[] = $parent; - $key = implode('][', $parents); - if (isset($form[$key])) { - return $form[$key]; - } - } -} - -/** - * Flag an element as having an error. - */ -function form_error(&$element, $message = '') { - form_set_error(implode('][', $element['#parents']), $message); -} - -/** - * Walk through the structured form array, adding any required properties to - * each element and mapping the incoming input data to the proper elements. - * Also, execute any #process handlers attached to a specific element. - * - * This is one of the three primary functions that recursively iterates a form - * array. This one does it for completing the form building process. The other - * two are _form_validate() (invoked via drupal_validate_form() and used to - * invoke validation logic for each element) and drupal_render() (for rendering - * each element). Each of these three pipelines provides ample opportunity for - * modules to customize what happens. For example, during this function's life - * cycle, the following functions get called for each element: - * - $element['#value_callback']: A function that implements how user input is - * mapped to an element's #value property. This defaults to a function named - * 'form_type_TYPE_value' where TYPE is $element['#type']. - * - $element['#process']: An array of functions called after user input has - * been mapped to the element's #value property. These functions can be used - * to dynamically add child elements: for example, for the 'date' element - * type, one of the functions in this array is form_process_date(), which adds - * the individual 'year', 'month', 'day', etc. child elements. These functions - * can also be used to set additional properties or implement special logic - * other than adding child elements: for example, for the 'fieldset' element - * type, one of the functions in this array is form_process_fieldset(), which - * adds the attributes and JavaScript needed to make the fieldset collapsible - * if the #collapsible property is set. The #process functions are called in - * preorder traversal, meaning they are called for the parent element first, - * then for the child elements. - * - $element['#after_build']: An array of functions called after form_builder() - * is done with its processing of the element. These are called in postorder - * traversal, meaning they are called for the child elements first, then for - * the parent element. - * There are similar properties containing callback functions invoked by - * _form_validate() and drupal_render(), appropriate for those operations. - * - * Developers are strongly encouraged to integrate the functionality needed by - * their form or module within one of these three pipelines, using the - * appropriate callback property, rather than implementing their own recursive - * traversal of a form array. This facilitates proper integration between - * multiple modules. For example, module developers are familiar with the - * relative order in which hook_form_alter() implementations and #process - * functions run. A custom traversal function that affects the building of a - * form is likely to not integrate with hook_form_alter() and #process in the - * expected way. Also, deep recursion within PHP is both slow and memory - * intensive, so it is best to minimize how often it's done. - * - * As stated above, each element's #process functions are executed after its - * #value has been set. This enables those functions to execute conditional - * logic based on the current value. However, all of form_builder() runs before - * drupal_validate_form() is called, so during #process function execution, the - * element's #value has not yet been validated, so any code that requires - * validated values must reside within a submit handler. - * - * As a security measure, user input is used for an element's #value only if the - * element exists within $form, is not disabled (as per the #disabled property), - * and can be accessed (as per the #access property, except that forms submitted - * using drupal_form_submit() bypass #access restrictions). When user input is - * ignored due to #disabled and #access restrictions, the element's default - * value is used. - * - * Because of the preorder traversal, where #process functions of an element run - * before user input for its child elements is processed, and because of the - * Form API security of user input processing with respect to #access and - * #disabled described above, this generally means that #process functions - * should not use an element's (unvalidated) #value to affect the #disabled or - * #access of child elements. Use-cases where a developer may be tempted to - * implement such conditional logic usually fall into one of two categories: - * - Where user input from the current submission must affect the structure of a - * form, including properties like #access and #disabled that affect how the - * next submission needs to be processed, a multi-step workflow is needed. - * This is most commonly implemented with a submit handler setting persistent - * data within $form_state based on *validated* values in - * $form_state['values'] and setting $form_state['rebuild']. The form building - * functions must then be implemented to use the $form_state data to rebuild - * the form with the structure appropriate for the new state. - * - Where user input must affect the rendering of the form without affecting - * its structure, the necessary conditional rendering logic should reside - * within functions that run during the rendering phase (#pre_render, #theme, - * #theme_wrappers, and #post_render). - * - * @param $form_id - * A unique string identifying the form for validation, submission, - * theming, and hook_form_alter functions. - * @param $element - * An associative array containing the structure of the current element. - * @param $form_state - * A keyed array containing the current state of the form. In this - * context, it is used to accumulate information about which button - * was clicked when the form was submitted, as well as the sanitized - * $_POST data. - */ -function form_builder($form_id, &$element, &$form_state) { - // Initialize as unprocessed. - $element['#processed'] = FALSE; - - // Use element defaults. - if (isset($element['#type']) && empty($element['#defaults_loaded']) && ($info = element_info($element['#type']))) { - // Overlay $info onto $element, retaining preexisting keys in $element. - $element += $info; - $element['#defaults_loaded'] = TRUE; - } - // Assign basic defaults common for all form elements. - $element += array( - '#required' => FALSE, - '#attributes' => array(), - '#title_display' => 'before', - ); - - // Special handling if we're on the top level form element. - if (isset($element['#type']) && $element['#type'] == 'form') { - if (!empty($element['#https']) && variable_get('https', FALSE) && - !url_is_external($element['#action'])) { - global $base_root; - - // Not an external URL so ensure that it is secure. - $element['#action'] = str_replace('http://', 'https://', $base_root) . $element['#action']; - } - - // Store a reference to the complete form in $form_state prior to building - // the form. This allows advanced #process and #after_build callbacks to - // perform changes elsewhere in the form. - $form_state['complete_form'] = &$element; - - // Set a flag if we have a correct form submission. This is always TRUE for - // programmed forms coming from drupal_form_submit(), or if the form_id coming - // from the POST data is set and matches the current form_id. - if ($form_state['programmed'] || (!empty($form_state['input']) && (isset($form_state['input']['form_id']) && ($form_state['input']['form_id'] == $form_id)))) { - $form_state['process_input'] = TRUE; - } - else { - $form_state['process_input'] = FALSE; - } - - // All form elements should have an #array_parents property. - $element['#array_parents'] = array(); - } - - if (!isset($element['#id'])) { - $element['#id'] = drupal_html_id('edit-' . implode('-', $element['#parents'])); - } - // Handle input elements. - if (!empty($element['#input'])) { - _form_builder_handle_input_element($form_id, $element, $form_state); - } - // Allow for elements to expand to multiple elements, e.g., radios, - // checkboxes and files. - if (isset($element['#process']) && !$element['#processed']) { - foreach ($element['#process'] as $process) { - $element = $process($element, $form_state, $form_state['complete_form']); - } - $element['#processed'] = TRUE; - } - - // We start off assuming all form elements are in the correct order. - $element['#sorted'] = TRUE; - - // Recurse through all child elements. - $count = 0; - foreach (element_children($element) as $key) { - // Prior to checking properties of child elements, their default properties - // need to be loaded. - if (isset($element[$key]['#type']) && empty($element[$key]['#defaults_loaded']) && ($info = element_info($element[$key]['#type']))) { - $element[$key] += $info; - $element[$key]['#defaults_loaded'] = TRUE; - } - - // Don't squash an existing tree value. - if (!isset($element[$key]['#tree'])) { - $element[$key]['#tree'] = $element['#tree']; - } - - // Deny access to child elements if parent is denied. - if (isset($element['#access']) && !$element['#access']) { - $element[$key]['#access'] = FALSE; - } - - // Make child elements inherit their parent's #disabled and #allow_focus - // values unless they specify their own. - foreach (array('#disabled', '#allow_focus') as $property) { - if (isset($element[$property]) && !isset($element[$key][$property])) { - $element[$key][$property] = $element[$property]; - } - } - - // Don't squash existing parents value. - if (!isset($element[$key]['#parents'])) { - // Check to see if a tree of child elements is present. If so, - // continue down the tree if required. - $element[$key]['#parents'] = $element[$key]['#tree'] && $element['#tree'] ? array_merge($element['#parents'], array($key)) : array($key); - } - // Ensure #array_parents follows the actual form structure. - $array_parents = $element['#array_parents']; - $array_parents[] = $key; - $element[$key]['#array_parents'] = $array_parents; - - // Assign a decimal placeholder weight to preserve original array order. - if (!isset($element[$key]['#weight'])) { - $element[$key]['#weight'] = $count/1000; - } - else { - // If one of the child elements has a weight then we will need to sort - // later. - unset($element['#sorted']); - } - $element[$key] = form_builder($form_id, $element[$key], $form_state); - $count++; - } - - // The #after_build flag allows any piece of a form to be altered - // after normal input parsing has been completed. - if (isset($element['#after_build']) && !isset($element['#after_build_done'])) { - foreach ($element['#after_build'] as $function) { - $element = $function($element, $form_state); - } - $element['#after_build_done'] = TRUE; - } - - // If there is a file element, we need to flip a flag so later the - // form encoding can be set. - if (isset($element['#type']) && $element['#type'] == 'file') { - $form_state['has_file_element'] = TRUE; - } - - // Final tasks for the form element after form_builder() has run for all other - // elements. - if (isset($element['#type']) && $element['#type'] == 'form') { - // If there is a file element, we set the form encoding. - if (isset($form_state['has_file_element'])) { - $element['#attributes']['enctype'] = 'multipart/form-data'; - } - - // If a form contains a single textfield, and the ENTER key is pressed - // within it, Internet Explorer submits the form with no POST data - // identifying any submit button. Other browsers submit POST data as though - // the user clicked the first button. Therefore, to be as consistent as we - // can be across browsers, if no 'triggering_element' has been identified - // yet, default it to the first button. - if (!$form_state['programmed'] && !isset($form_state['triggering_element']) && !empty($form_state['buttons'])) { - $form_state['triggering_element'] = $form_state['buttons'][0]; - } - - // If the triggering element specifies "button-level" validation and submit - // handlers to run instead of the default form-level ones, then add those to - // the form state. - foreach (array('validate', 'submit') as $type) { - if (isset($form_state['triggering_element']['#' . $type])) { - $form_state[$type . '_handlers'] = $form_state['triggering_element']['#' . $type]; - } - } - - // If the triggering element executes submit handlers, then set the form - // state key that's needed for those handlers to run. - if (!empty($form_state['triggering_element']['#executes_submit_callback'])) { - $form_state['submitted'] = TRUE; - } - - // Special processing if the triggering element is a button. - if (isset($form_state['triggering_element']['#button_type'])) { - // Because there are several ways in which the triggering element could - // have been determined (including from input variables set by JavaScript - // or fallback behavior implemented for IE), and because buttons often - // have their #name property not derived from their #parents property, we - // can't assume that input processing that's happened up until here has - // resulted in $form_state['values'][BUTTON_NAME] being set. But it's - // common for forms to have several buttons named 'op' and switch on - // $form_state['values']['op'] during submit handler execution. - $form_state['values'][$form_state['triggering_element']['#name']] = $form_state['triggering_element']['#value']; - - // @todo Legacy support. Remove in Drupal 8. - $form_state['clicked_button'] = $form_state['triggering_element']; - } - } - return $element; -} - -/** - * Populate the #value and #name properties of input elements so they - * can be processed and rendered. - */ -function _form_builder_handle_input_element($form_id, &$element, &$form_state) { - if (!isset($element['#name'])) { - $name = array_shift($element['#parents']); - $element['#name'] = $name; - if ($element['#type'] == 'file') { - // To make it easier to handle $_FILES in file.inc, we place all - // file fields in the 'files' array. Also, we do not support - // nested file names. - $element['#name'] = 'files[' . $element['#name'] . ']'; - } - elseif (count($element['#parents'])) { - $element['#name'] .= '[' . implode('][', $element['#parents']) . ']'; - } - array_unshift($element['#parents'], $name); - } - - // Setting #disabled to TRUE results in user input being ignored, regardless - // of how the element is themed or whether JavaScript is used to change the - // control's attributes. However, it's good UI to let the user know that input - // is not wanted for the control. HTML supports two attributes for this: - // http://www.w3.org/TR/html401/interact/forms.html#h-17.12. If a form wants - // to start a control off with one of these attributes for UI purposes only, - // but still allow input to be processed if it's sumitted, it can set the - // desired attribute in #attributes directly rather than using #disabled. - // However, developers should think carefully about the accessibility - // implications of doing so: if the form expects input to be enterable under - // some condition triggered by JavaScript, how would someone who has - // JavaScript disabled trigger that condition? Instead, developers should - // consider whether a multi-step form would be more appropriate (#disabled can - // be changed from step to step). If one still decides to use JavaScript to - // affect when a control is enabled, then it is best for accessibility for the - // control to be enabled in the HTML, and disabled by JavaScript on document - // ready. - if (!empty($element['#disabled'])) { - if (!empty($element['#allow_focus'])) { - $element['#attributes']['readonly'] = 'readonly'; - } - else { - $element['#attributes']['disabled'] = 'disabled'; - } - } - - // With JavaScript or other easy hacking, input can be submitted even for - // elements with #access=FALSE or #disabled=TRUE. For security, these must - // not be processed. Forms that set #disabled=TRUE on an element do not - // expect input for the element, and even forms submitted with - // drupal_form_submit() must not be able to get around this. Forms that set - // #access=FALSE on an element usually allow access for some users, so forms - // submitted with drupal_form_submit() may bypass access restriction and be - // treated as high-privilege users instead. - $process_input = empty($element['#disabled']) && ($form_state['programmed'] || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access']))); - - // Set the element's #value property. - if (!isset($element['#value']) && !array_key_exists('#value', $element)) { - $value_callback = !empty($element['#value_callback']) ? $element['#value_callback'] : 'form_type_' . $element['#type'] . '_value'; - if ($process_input) { - // Get the input for the current element. NULL values in the input need to - // be explicitly distinguished from missing input. (see below) - $input_exists = NULL; - $input = drupal_array_get_nested_value($form_state['input'], $element['#parents'], $input_exists); - // For browser-submitted forms, the submitted values do not contain values - // for certain elements (empty multiple select, unchecked checkbox). - // During initial form processing, we add explicit NULL values for such - // elements in $form_state['input']. When rebuilding the form, we can - // distinguish elements having NULL input from elements that were not part - // of the initially submitted form and can therefore use default values - // for the latter, if required. Programmatically submitted forms can - // submit explicit NULL values when calling drupal_form_submit(), so we do - // not modify $form_state['input'] for them. - if (!$input_exists && !$form_state['rebuild'] && !$form_state['programmed']) { - // Add the necessary parent keys to $form_state['input'] and sets the - // element's input value to NULL. - drupal_array_set_nested_value($form_state['input'], $element['#parents'], NULL); - $input_exists = TRUE; - } - // If we have input for the current element, assign it to the #value - // property, optionally filtered through $value_callback. - if ($input_exists) { - if (function_exists($value_callback)) { - $element['#value'] = $value_callback($element, $input, $form_state); - } - if (!isset($element['#value']) && isset($input)) { - $element['#value'] = $input; - } - } - // Mark all posted values for validation. - if (isset($element['#value']) || (!empty($element['#required']))) { - $element['#needs_validation'] = TRUE; - } - } - // Load defaults. - if (!isset($element['#value'])) { - // Call #type_value without a second argument to request default_value handling. - if (function_exists($value_callback)) { - $element['#value'] = $value_callback($element, FALSE, $form_state); - } - // Final catch. If we haven't set a value yet, use the explicit default value. - // Avoid image buttons (which come with garbage value), so we only get value - // for the button actually clicked. - if (!isset($element['#value']) && empty($element['#has_garbage_value'])) { - $element['#value'] = isset($element['#default_value']) ? $element['#default_value'] : ''; - } - } - } - - // Determine which element (if any) triggered the submission of the form and - // keep track of all the clickable buttons in the form for - // form_state_values_clean(). Enforce the same input processing restrictions - // as above. - if ($process_input) { - // Detect if the element triggered the submission via Ajax. - if (_form_element_triggered_scripted_submission($element, $form_state)) { - $form_state['triggering_element'] = $element; - } - - // If the form was submitted by the browser rather than via Ajax, then it - // can only have been triggered by a button, and we need to determine which - // button within the constraints of how browsers provide this information. - if (isset($element['#button_type'])) { - // All buttons in the form need to be tracked for - // form_state_values_clean() and for the form_builder() code that handles - // a form submission containing no button information in $_POST. - $form_state['buttons'][] = $element; - if (_form_button_was_clicked($element, $form_state)) { - $form_state['triggering_element'] = $element; - } - } - } - - // Set the element's value in $form_state['values'], but only, if its key - // does not exist yet (a #value_callback may have already populated it). - if (!drupal_array_nested_key_exists($form_state['values'], $element['#parents'])) { - form_set_value($element, $element['#value'], $form_state); - } -} - -/** - * Helper function to handle the convoluted logic of button click detection. - * - * This detects button or non-button controls that trigger a form submission via - * Ajax or some other scriptable environment. These environments can set the - * special input key '_triggering_element_name' to identify the triggering - * element. If the name alone doesn't identify the element uniquely, the input - * key '_triggering_element_value' may also be set to require a match on element - * value. An example where this is needed is if there are several buttons all - * named 'op', and only differing in their value. - */ -function _form_element_triggered_scripted_submission($element, &$form_state) { - if (!empty($form_state['input']['_triggering_element_name']) && $element['#name'] == $form_state['input']['_triggering_element_name']) { - if (empty($form_state['input']['_triggering_element_value']) || $form_state['input']['_triggering_element_value'] == $element['#value']) { - return TRUE; - } - } - return FALSE; -} - -/** - * Helper function to handle the convoluted logic of button click detection. - * - * This detects button controls that trigger a form submission by being clicked - * and having the click processed by the browser rather than being captured by - * JavaScript. Essentially, it detects if the button's name and value are part - * of the POST data, but with extra code to deal with the convoluted way in - * which browsers submit data for image button clicks. - * - * This does not detect button clicks processed by Ajax (that is done in - * _form_element_triggered_scripted_submission()) and it does not detect form - * submissions from Internet Explorer in response to an ENTER key pressed in a - * textfield (form_builder() has extra code for that). - * - * Because this function contains only part of the logic needed to determine - * $form_state['triggering_element'], it should not be called from anywhere - * other than within the Form API. Form validation and submit handlers needing - * to know which button was clicked should get that information from - * $form_state['triggering_element']. - */ -function _form_button_was_clicked($element, &$form_state) { - // First detect normal 'vanilla' button clicks. Traditionally, all - // standard buttons on a form share the same name (usually 'op'), - // and the specific return value is used to determine which was - // clicked. This ONLY works as long as $form['#name'] puts the - // value at the top level of the tree of $_POST data. - if (isset($form_state['input'][$element['#name']]) && $form_state['input'][$element['#name']] == $element['#value']) { - return TRUE; - } - // When image buttons are clicked, browsers do NOT pass the form element - // value in $_POST. Instead they pass an integer representing the - // coordinates of the click on the button image. This means that image - // buttons MUST have unique $form['#name'] values, but the details of - // their $_POST data should be ignored. - elseif (!empty($element['#has_garbage_value']) && isset($element['#value']) && $element['#value'] !== '') { - return TRUE; - } - return FALSE; -} - -/** - * Removes internal Form API elements and buttons from submitted form values. - * - * This function can be used when a module wants to store all submitted form - * values, for example, by serializing them into a single database column. In - * such cases, all internal Form API values and all form button elements should - * not be contained, and this function allows to remove them before the module - * proceeds to storage. Next to button elements, the following internal values - * are removed: - * - form_id - * - form_token - * - form_build_id - * - op - * - * @param $form_state - * A keyed array containing the current state of the form, including - * submitted form values; altered by reference. - */ -function form_state_values_clean(&$form_state) { - // Remove internal Form API values. - unset($form_state['values']['form_id'], $form_state['values']['form_token'], $form_state['values']['form_build_id'], $form_state['values']['op']); - - // Remove button values. - // form_builder() collects all button elements in a form. We remove the button - // value separately for each button element. - foreach ($form_state['buttons'] as $button) { - // Remove this button's value from the submitted form values by finding - // the value corresponding to this button. - // We iterate over the #parents of this button and move a reference to - // each parent in $form_state['values']. For example, if #parents is: - // array('foo', 'bar', 'baz') - // then the corresponding $form_state['values'] part will look like this: - // array( - // 'foo' => array( - // 'bar' => array( - // 'baz' => 'button_value', - // ), - // ), - // ) - // We start by (re)moving 'baz' to $last_parent, so we are able unset it - // at the end of the iteration. Initially, $values will contain a - // reference to $form_state['values'], but in the iteration we move the - // reference to $form_state['values']['foo'], and finally to - // $form_state['values']['foo']['bar'], which is the level where we can - // unset 'baz' (that is stored in $last_parent). - $parents = $button['#parents']; - $values = &$form_state['values']; - $last_parent = array_pop($parents); - foreach ($parents as $parent) { - $values = &$values[$parent]; - } - unset($values[$last_parent]); - } -} - -/** - * Helper function to determine the value for an image button form element. - * - * @param $form - * The form element whose value is being populated. - * @param $input - * The incoming input to populate the form element. If this is FALSE, - * the element's default value should be returned. - * @param $form_state - * A keyed array containing the current state of the form. - * @return - * The data that will appear in the $form_state['values'] collection - * for this element. Return nothing to use the default. - */ -function form_type_image_button_value($form, $input, $form_state) { - if ($input !== FALSE) { - if (!empty($input)) { - // If we're dealing with Mozilla or Opera, we're lucky. It will - // return a proper value, and we can get on with things. - return $form['#return_value']; - } - else { - // Unfortunately, in IE we never get back a proper value for THIS - // form element. Instead, we get back two split values: one for the - // X and one for the Y coordinates on which the user clicked the - // button. We'll find this element in the #post data, and search - // in the same spot for its name, with '_x'. - $input = $form_state['input']; - foreach (explode('[', $form['#name']) as $element_name) { - // chop off the ] that may exist. - if (substr($element_name, -1) == ']') { - $element_name = substr($element_name, 0, -1); - } - - if (!isset($input[$element_name])) { - if (isset($input[$element_name . '_x'])) { - return $form['#return_value']; - } - return NULL; - } - $input = $input[$element_name]; - } - return $form['#return_value']; - } - } -} - -/** - * Helper function to determine the value for a checkbox form element. - * - * @param $form - * The form element whose value is being populated. -* @param $input - * The incoming input to populate the form element. If this is FALSE, - * the element's default value should be returned. - * @return - * The data that will appear in the $element_state['values'] collection - * for this element. Return nothing to use the default. - */ -function form_type_checkbox_value($element, $input = FALSE) { - if ($input === FALSE) { - // Use #default_value as the default value of a checkbox, except change - // NULL to 0, because _form_builder_handle_input_element() would otherwise - // replace NULL with empty string, but an empty string is a potentially - // valid value for a checked checkbox. - return isset($element['#default_value']) ? $element['#default_value'] : 0; - } - else { - // Checked checkboxes are submitted with a value (possibly '0' or ''): - // http://www.w3.org/TR/html401/interact/forms.html#successful-controls. - // For checked checkboxes, browsers submit the string version of - // #return_value, but we return the original #return_value. For unchecked - // checkboxes, browsers submit nothing at all, but - // _form_builder_handle_input_element() detects this, and calls this - // function with $input=NULL. Returning NULL from a value callback means to - // use the default value, which is not what is wanted when an unchecked - // checkbox is submitted, so we use integer 0 as the value indicating an - // unchecked checkbox. Therefore, modules must not use integer 0 as a - // #return_value, as doing so results in the checkbox always being treated - // as unchecked. The string '0' is allowed for #return_value. The most - // common use-case for setting #return_value to either 0 or '0' is for the - // first option within a 0-indexed array of checkboxes, and for this, - // form_process_checkboxes() uses the string rather than the integer. - return isset($input) ? $element['#return_value'] : 0; - } -} - -/** - * Helper function to determine the value for a checkboxes form element. - * - * @param $element - * The form element whose value is being populated. - * @param $input - * The incoming input to populate the form element. If this is FALSE, - * the element's default value should be returned. - * @return - * The data that will appear in the $element_state['values'] collection - * for this element. Return nothing to use the default. - */ -function form_type_checkboxes_value($element, $input = FALSE) { - if ($input === FALSE) { - $value = array(); - $element += array('#default_value' => array()); - foreach ($element['#default_value'] as $key) { - $value[$key] = $key; - } - return $value; - } - elseif (is_array($input)) { - // Programmatic form submissions use NULL to indicate that a checkbox - // should be unchecked; see drupal_form_submit(). We therefore remove all - // NULL elements from the array before constructing the return value, to - // simulate the behavior of web browsers (which do not send unchecked - // checkboxes to the server at all). This will not affect non-programmatic - // form submissions, since all values in $_POST are strings. - foreach ($input as $key => $value) { - if (!isset($value)) { - unset($input[$key]); - } - } - return drupal_map_assoc($input); - } - else { - return array(); - } -} - -/** - * Helper function to determine the value for a tableselect form element. - * - * @param $element - * The form element whose value is being populated. - * @param $input - * The incoming input to populate the form element. If this is FALSE, - * the element's default value should be returned. - * @return - * The data that will appear in the $element_state['values'] collection - * for this element. Return nothing to use the default. - */ -function form_type_tableselect_value($element, $input = FALSE) { - // If $element['#multiple'] == FALSE, then radio buttons are displayed and - // the default value handling is used. - if (isset($element['#multiple']) && $element['#multiple']) { - // Checkboxes are being displayed with the default value coming from the - // keys of the #default_value property. This differs from the checkboxes - // element which uses the array values. - if ($input === FALSE) { - $value = array(); - $element += array('#default_value' => array()); - foreach ($element['#default_value'] as $key => $flag) { - if ($flag) { - $value[$key] = $key; - } - } - return $value; - } - else { - return is_array($input) ? drupal_map_assoc($input) : array(); - } - } -} - -/** - * Helper function to determine the value for a password_confirm form - * element. - * - * @param $element - * The form element whose value is being populated. - * @param $input - * The incoming input to populate the form element. If this is FALSE, - * the element's default value should be returned. - * @return - * The data that will appear in the $element_state['values'] collection - * for this element. Return nothing to use the default. - */ -function form_type_password_confirm_value($element, $input = FALSE) { - if ($input === FALSE) { - $element += array('#default_value' => array()); - return $element['#default_value'] + array('pass1' => '', 'pass2' => ''); - } -} - -/** - * Helper function to determine the value for a select form element. - * - * @param $element - * The form element whose value is being populated. - * @param $input - * The incoming input to populate the form element. If this is FALSE, - * the element's default value should be returned. - * @return - * The data that will appear in the $element_state['values'] collection - * for this element. Return nothing to use the default. - */ -function form_type_select_value($element, $input = FALSE) { - if ($input !== FALSE) { - if (isset($element['#multiple']) && $element['#multiple']) { - // If an enabled multi-select submits NULL, it means all items are - // unselected. A disabled multi-select always submits NULL, and the - // default value should be used. - if (empty($element['#disabled'])) { - return (is_array($input)) ? drupal_map_assoc($input) : array(); - } - else { - return (isset($element['#default_value']) && is_array($element['#default_value'])) ? $element['#default_value'] : array(); - } - } - // Non-multiple select elements may have an empty option preprended to them - // (see form_process_select()). When this occurs, usually #empty_value is - // an empty string, but some forms set #empty_value to integer 0 or some - // other non-string constant. PHP receives all submitted form input as - // strings, but if the empty option is selected, set the value to match the - // empty value exactly. - elseif (isset($element['#empty_value']) && $input === (string) $element['#empty_value']) { - return $element['#empty_value']; - } - else { - return $input; - } - } -} - -/** - * Helper function to determine the value for a textfield form element. - * - * @param $element - * The form element whose value is being populated. - * @param $input - * The incoming input to populate the form element. If this is FALSE, - * the element's default value should be returned. - * @return - * The data that will appear in the $element_state['values'] collection - * for this element. Return nothing to use the default. - */ -function form_type_textfield_value($element, $input = FALSE) { - if ($input !== FALSE && $input !== NULL) { - // Equate $input to the form value to ensure it's marked for - // validation. - return str_replace(array("\r", "\n"), '', $input); - } -} - -/** - * Helper function to determine the value for form's token value. - * - * @param $element - * The form element whose value is being populated. - * @param $input - * The incoming input to populate the form element. If this is FALSE, - * the element's default value should be returned. - * @return - * The data that will appear in the $element_state['values'] collection - * for this element. Return nothing to use the default. - */ -function form_type_token_value($element, $input = FALSE) { - if ($input !== FALSE) { - return (string) $input; - } -} - -/** - * Change submitted form values during form validation. - * - * Use this function to change the submitted value of a form element in a form - * validation function, so that the changed value persists in $form_state - * through to the submission handlers. - * - * Note that form validation functions are specified in the '#validate' - * component of the form array (the value of $form['#validate'] is an array of - * validation function names). If the form does not originate in your module, - * you can implement hook_form_FORM_ID_alter() to add a validation function - * to $form['#validate']. - * - * @param $element - * The form element that should have its value updated; in most cases you can - * just pass in the element from the $form array, although the only component - * that is actually used is '#parents'. If constructing yourself, set - * $element['#parents'] to be an array giving the path through the form - * array's keys to the element whose value you want to update. For instance, - * if you want to update the value of $form['elem1']['elem2'], which should be - * stored in $form_state['values']['elem1']['elem2'], you would set - * $element['#parents'] = array('elem1','elem2'). - * @param $value - * The new value for the form element. - * @param $form_state - * Form state array where the value change should be recorded. - */ -function form_set_value($element, $value, &$form_state) { - drupal_array_set_nested_value($form_state['values'], $element['#parents'], $value, TRUE); -} - -/** - * Allows PHP array processing of multiple select options with the same value. - * - * Used for form select elements which need to validate HTML option groups - * and multiple options which may return the same value. Associative PHP arrays - * cannot handle these structures, since they share a common key. - * - * @param $array - * The form options array to process. - * - * @return - * An array with all hierarchical elements flattened to a single array. - */ -function form_options_flatten($array) { - // Always reset static var when first entering the recursion. - drupal_static_reset('_form_options_flatten'); - return _form_options_flatten($array); -} - -/** - * Helper function for form_options_flatten(). - * - * Iterates over arrays which may share common values and produces a flat - * array that has removed duplicate keys. Also handles cases where objects - * are passed as array values. - */ -function _form_options_flatten($array) { - $return = &drupal_static(__FUNCTION__); - - foreach ($array as $key => $value) { - if (is_object($value)) { - _form_options_flatten($value->option); - } - elseif (is_array($value)) { - _form_options_flatten($value); - } - else { - $return[$key] = 1; - } - } - - return $return; -} - -/** - * Processes a select list form element. - * - * This process callback is mandatory for select fields, since all user agents - * automatically preselect the first available option of single (non-multiple) - * select lists. - * - * @param $element - * The form element to process. Properties used: - * - #multiple: (optional) Indicates whether one or more options can be - * selected. Defaults to FALSE. - * - #default_value: Must be NULL or not set in case there is no value for the - * element yet, in which case a first default option is inserted by default. - * Whether this first option is a valid option depends on whether the field - * is #required or not. - * - #required: (optional) Whether the user needs to select an option (TRUE) - * or not (FALSE). Defaults to FALSE. - * - #empty_option: (optional) The label to show for the first default option. - * By default, the label is automatically set to "- Please select -" for a - * required field and "- None -" for an optional field. - * - #empty_value: (optional) The value for the first default option, which is - * used to determine whether the user submitted a value or not. - * - If #required is TRUE, this defaults to '' (an empty string). - * - If #required is not TRUE and this value isn't set, then no extra option - * is added to the select control, leaving the control in a slightly - * illogical state, because there's no way for the user to select nothing, - * since all user agents automatically preselect the first available - * option. But people are used to this being the behavior of select - * controls. - * @todo Address the above issue in Drupal 8. - * - If #required is not TRUE and this value is set (most commonly to an - * empty string), then an extra option (see #empty_option above) - * representing a "non-selection" is added with this as its value. - * - * @see _form_validate() - */ -function form_process_select($element) { - // #multiple select fields need a special #name. - if ($element['#multiple']) { - $element['#attributes']['multiple'] = 'multiple'; - $element['#attributes']['name'] = $element['#name'] . '[]'; - } - // A non-#multiple select needs special handling to prevent user agents from - // preselecting the first option without intention. #multiple select lists do - // not get an empty option, as it would not make sense, user interface-wise. - else { - $required = $element['#required']; - // If the element is required and there is no #default_value, then add an - // empty option that will fail validation, so that the user is required to - // make a choice. Also, if there's a value for #empty_value or - // #empty_option, then add an option that represents emptiness. - if (($required && !isset($element['#default_value'])) || isset($element['#empty_value']) || isset($element['#empty_option'])) { - $element += array( - '#empty_value' => '', - '#empty_option' => $required ? t('- Select -') : t('- None -'), - ); - // The empty option is prepended to #options and purposively not merged - // to prevent another option in #options mistakenly using the same value - // as #empty_value. - $empty_option = array($element['#empty_value'] => $element['#empty_option']); - $element['#options'] = $empty_option + $element['#options']; - } - } - return $element; -} - -/** - * Returns HTML for a select form element. - * - * It is possible to group options together; to do this, change the format of - * $options to an associative array in which the keys are group labels, and the - * values are associative arrays in the normal $options format. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #title, #value, #options, #description, #extra, - * #multiple, #required, #name, #attributes, #size. - * - * @ingroup themeable - */ -function theme_select($variables) { - $element = $variables['element']; - element_set_attributes($element, array('id', 'name', 'size')); - _form_set_class($element, array('form-select')); - - return '<select' . drupal_attributes($element['#attributes']) . '>' . form_select_options($element) . '</select>'; -} - -/** - * Converts a select form element's options array into an HTML. - * - * @param $element - * An associative array containing the properties of the element. - * @param $choices - * Mixed: Either an associative array of items to list as choices, or an - * object with an 'option' member that is an associative array. This - * parameter is only used internally and should not be passed. - * @return - * An HTML string of options for the select form element. - */ -function form_select_options($element, $choices = NULL) { - if (!isset($choices)) { - $choices = $element['#options']; - } - // array_key_exists() accommodates the rare event where $element['#value'] is NULL. - // isset() fails in this situation. - $value_valid = isset($element['#value']) || array_key_exists('#value', $element); - $value_is_array = $value_valid && is_array($element['#value']); - $options = ''; - foreach ($choices as $key => $choice) { - if (is_array($choice)) { - $options .= '<optgroup label="' . $key . '">'; - $options .= form_select_options($element, $choice); - $options .= '</optgroup>'; - } - elseif (is_object($choice)) { - $options .= form_select_options($element, $choice->option); - } - else { - $key = (string) $key; - if ($value_valid && (!$value_is_array && (string) $element['#value'] === $key || ($value_is_array && in_array($key, $element['#value'])))) { - $selected = ' selected="selected"'; - } - else { - $selected = ''; - } - $options .= '<option value="' . check_plain($key) . '"' . $selected . '>' . check_plain($choice) . '</option>'; - } - } - return $options; -} - -/** - * Traverses a select element's #option array looking for any values - * that hold the given key. Returns an array of indexes that match. - * - * This function is useful if you need to modify the options that are - * already in a form element; for example, to remove choices which are - * not valid because of additional filters imposed by another module. - * One example might be altering the choices in a taxonomy selector. - * To correctly handle the case of a multiple hierarchy taxonomy, - * #options arrays can now hold an array of objects, instead of a - * direct mapping of keys to labels, so that multiple choices in the - * selector can have the same key (and label). This makes it difficult - * to manipulate directly, which is why this helper function exists. - * - * This function does not support optgroups (when the elements of the - * #options array are themselves arrays), and will return FALSE if - * arrays are found. The caller must either flatten/restore or - * manually do their manipulations in this case, since returning the - * index is not sufficient, and supporting this would make the - * "helper" too complicated and cumbersome to be of any help. - * - * As usual with functions that can return array() or FALSE, do not - * forget to use === and !== if needed. - * - * @param $element - * The select element to search. - * @param $key - * The key to look for. - * @return - * An array of indexes that match the given $key. Array will be - * empty if no elements were found. FALSE if optgroups were found. - */ -function form_get_options($element, $key) { - $keys = array(); - foreach ($element['#options'] as $index => $choice) { - if (is_array($choice)) { - return FALSE; - } - elseif (is_object($choice)) { - if (isset($choice->option[$key])) { - $keys[] = $index; - } - } - elseif ($index == $key) { - $keys[] = $index; - } - } - return $keys; -} - -/** - * Returns HTML for a fieldset form element and its children. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #attributes, #children, #collapsed, #collapsible, - * #description, #id, #title, #value. - * - * @ingroup themeable - */ -function theme_fieldset($variables) { - $element = $variables['element']; - element_set_attributes($element, array('id')); - _form_set_class($element, array('form-wrapper')); - - $output = '<fieldset' . drupal_attributes($element['#attributes']) . '>'; - if (!empty($element['#title'])) { - // Always wrap fieldset legends in a SPAN for CSS positioning. - $output .= '<legend><span class="fieldset-legend">' . $element['#title'] . '</span></legend>'; - } - $output .= '<div class="fieldset-wrapper">'; - if (!empty($element['#description'])) { - $output .= '<div class="fieldset-description">' . $element['#description'] . '</div>'; - } - $output .= $element['#children']; - if (isset($element['#value'])) { - $output .= $element['#value']; - } - $output .= '</div>'; - $output .= "</fieldset>\n"; - return $output; -} - -/** - * Returns HTML for a radio button form element. - * - * Note: The input "name" attribute needs to be sanitized before output, which - * is currently done by passing all attributes to drupal_attributes(). - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #required, #return_value, #value, #attributes, #title, - * #description - * - * @ingroup themeable - */ -function theme_radio($variables) { - $element = $variables['element']; - $element['#attributes']['type'] = 'radio'; - element_set_attributes($element, array('id', 'name', '#return_value' => 'value')); - - if (isset($element['#return_value']) && $element['#value'] !== FALSE && $element['#value'] == $element['#return_value']) { - $element['#attributes']['checked'] = 'checked'; - } - _form_set_class($element, array('form-radio')); - - return '<input' . drupal_attributes($element['#attributes']) . ' />'; -} - -/** - * Returns HTML for a set of radio button form elements. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #title, #value, #options, #description, #required, - * #attributes, #children. - * - * @ingroup themeable - */ -function theme_radios($variables) { - $element = $variables['element']; - $attributes = array(); - if (isset($element['#id'])) { - $attributes['id'] = $element['#id']; - } - $attributes['class'] = 'form-radios'; - if (!empty($element['#attributes']['class'])) { - $attributes['class'] .= ' ' . implode(' ', $element['#attributes']['class']); - } - return '<div' . drupal_attributes($attributes) . '>' . (!empty($element['#children']) ? $element['#children'] : '') . '</div>'; -} - -/** - * Expand a password_confirm field into two text boxes. - */ -function form_process_password_confirm($element) { - $element['pass1'] = array( - '#type' => 'password', - '#title' => t('Password'), - '#value' => empty($element['#value']) ? NULL : $element['#value']['pass1'], - '#required' => $element['#required'], - '#attributes' => array('class' => array('password-field')), - ); - $element['pass2'] = array( - '#type' => 'password', - '#title' => t('Confirm password'), - '#value' => empty($element['#value']) ? NULL : $element['#value']['pass2'], - '#required' => $element['#required'], - '#attributes' => array('class' => array('password-confirm')), - ); - $element['#element_validate'] = array('password_confirm_validate'); - $element['#tree'] = TRUE; - - if (isset($element['#size'])) { - $element['pass1']['#size'] = $element['pass2']['#size'] = $element['#size']; - } - - return $element; -} - -/** - * Validate password_confirm element. - */ -function password_confirm_validate($element, &$element_state) { - $pass1 = trim($element['pass1']['#value']); - $pass2 = trim($element['pass2']['#value']); - if (!empty($pass1) || !empty($pass2)) { - if (strcmp($pass1, $pass2)) { - form_error($element, t('The specified passwords do not match.')); - } - } - elseif ($element['#required'] && !empty($element_state['input'])) { - form_error($element, t('Password field is required.')); - } - - // Password field must be converted from a two-element array into a single - // string regardless of validation results. - form_set_value($element['pass1'], NULL, $element_state); - form_set_value($element['pass2'], NULL, $element_state); - form_set_value($element, $pass1, $element_state); - - return $element; - -} - -/** - * Returns HTML for a date selection form element. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #title, #value, #options, #description, #required, - * #attributes. - * - * @ingroup themeable - */ -function theme_date($variables) { - $element = $variables['element']; - - $attributes = array(); - if (isset($element['#id'])) { - $attributes['id'] = $element['#id']; - } - if (!empty($element['#attributes']['class'])) { - $attributes['class'] = (array) $element['#attributes']['class']; - } - $attributes['class'][] = 'container-inline'; - - return '<div' . drupal_attributes($attributes) . '>' . drupal_render_children($element) . '</div>'; -} - -/** - * Roll out a single date element. - */ -function form_process_date($element) { - // Default to current date - if (empty($element['#value'])) { - $element['#value'] = array( - 'day' => format_date(REQUEST_TIME, 'custom', 'j'), - 'month' => format_date(REQUEST_TIME, 'custom', 'n'), - 'year' => format_date(REQUEST_TIME, 'custom', 'Y'), - ); - } - - $element['#tree'] = TRUE; - - // Determine the order of day, month, year in the site's chosen date format. - $format = variable_get('date_format_short', 'm/d/Y - H:i'); - $sort = array(); - $sort['day'] = max(strpos($format, 'd'), strpos($format, 'j')); - $sort['month'] = max(strpos($format, 'm'), strpos($format, 'M')); - $sort['year'] = strpos($format, 'Y'); - asort($sort); - $order = array_keys($sort); - - // Output multi-selector for date. - foreach ($order as $type) { - switch ($type) { - case 'day': - $options = drupal_map_assoc(range(1, 31)); - $title = t('Day'); - break; - - case 'month': - $options = drupal_map_assoc(range(1, 12), 'map_month'); - $title = t('Month'); - break; - - case 'year': - $options = drupal_map_assoc(range(1900, 2050)); - $title = t('Year'); - break; - } - - $element[$type] = array( - '#type' => 'select', - '#title' => $title, - '#title_display' => 'invisible', - '#value' => $element['#value'][$type], - '#attributes' => $element['#attributes'], - '#options' => $options, - ); - } - - return $element; -} - -/** - * Validates the date type to stop dates like February 30, 2006. - */ -function date_validate($element) { - if (!checkdate($element['#value']['month'], $element['#value']['day'], $element['#value']['year'])) { - form_error($element, t('The specified date is invalid.')); - } -} - -/** - * Helper function for usage with drupal_map_assoc to display month names. - */ -function map_month($month) { - $months = &drupal_static(__FUNCTION__, array( - 1 => 'Jan', - 2 => 'Feb', - 3 => 'Mar', - 4 => 'Apr', - 5 => 'May', - 6 => 'Jun', - 7 => 'Jul', - 8 => 'Aug', - 9 => 'Sep', - 10 => 'Oct', - 11 => 'Nov', - 12 => 'Dec', - )); - return t($months[$month]); -} - -/** - * If no default value is set for weight select boxes, use 0. - */ -function weight_value(&$form) { - if (isset($form['#default_value'])) { - $form['#value'] = $form['#default_value']; - } - else { - $form['#value'] = 0; - } -} - -/** - * Roll out a single radios element to a list of radios, - * using the options array as index. - */ -function form_process_radios($element) { - if (count($element['#options']) > 0) { - $weight = 0; - foreach ($element['#options'] as $key => $choice) { - // Maintain order of options as defined in #options, in case the element - // defines custom option sub-elements, but does not define all option - // sub-elements. - $weight += 0.001; - - $element += array($key => array()); - // Generate the parents as the autogenerator does, so we will have a - // unique id for each radio button. - $parents_for_id = array_merge($element['#parents'], array($key)); - $element[$key] += array( - '#type' => 'radio', - '#title' => $choice, - // The key is sanitized in drupal_attributes() during output from the - // theme function. - '#return_value' => $key, - '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : NULL, - '#attributes' => $element['#attributes'], - '#parents' => $element['#parents'], - '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)), - '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, - '#weight' => $weight, - ); - } - } - return $element; -} - -/** - * Returns HTML for a checkbox form element. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #title, #value, #return_value, #description, #required, - * #attributes. - * - * @ingroup themeable - */ -function theme_checkbox($variables) { - $element = $variables['element']; - $t = get_t(); - $element['#attributes']['type'] = 'checkbox'; - element_set_attributes($element, array('id', 'name', '#return_value' => 'value')); - - // Unchecked checkbox has #value of integer 0. - if (!empty($element['#checked'])) { - $element['#attributes']['checked'] = 'checked'; - } - _form_set_class($element, array('form-checkbox')); - - return '<input' . drupal_attributes($element['#attributes']) . ' />'; -} - -/** - * Returns HTML for a set of checkbox form elements. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #children, #attributes. - * - * @ingroup themeable - */ -function theme_checkboxes($variables) { - $element = $variables['element']; - $attributes = array(); - if (isset($element['#id'])) { - $attributes['id'] = $element['#id']; - } - $attributes['class'][] = 'form-checkboxes'; - if (!empty($element['#attributes']['class'])) { - $attributes['class'] = array_merge($attributes['class'], $element['#attributes']['class']); - } - return '<div' . drupal_attributes($attributes) . '>' . (!empty($element['#children']) ? $element['#children'] : '') . '</div>'; -} - -/** - * Add form_element theming to an element if title or description is set. - * - * This is used as a pre render function for checkboxes and radios. - */ -function form_pre_render_conditional_form_element($element) { - // Set the element's title attribute to show #title as a tooltip, if needed. - if (isset($element['#title']) && $element['#title_display'] == 'attribute') { - $element['#attributes']['title'] = $element['#title']; - if (!empty($element['#required'])) { - // Append an indication that this field is required. - $element['#attributes']['title'] .= ' (' . $t('Required') . ')'; - } - } - - if (isset($element['#title']) || isset($element['#description'])) { - $element['#theme_wrappers'][] = 'form_element'; - } - return $element; -} - -/** - * Sets the #checked property of a checkbox element. - */ -function form_process_checkbox($element, $form_state) { - $value = $element['#value']; - $return_value = $element['#return_value']; - // On form submission, the #value of an available and enabled checked - // checkbox is #return_value, and the #value of an available and enabled - // unchecked checkbox is integer 0. On not submitted forms, and for - // checkboxes with #access=FALSE or #disabled=TRUE, the #value is - // #default_value (integer 0 if #default_value is NULL). Most of the time, - // a string comparison of #value and #return_value is sufficient for - // determining the "checked" state, but a value of TRUE always means checked - // (even if #return_value is 'foo'), and a value of FALSE or integer 0 always - // means unchecked (even if #return_value is '' or '0'). - if ($value === TRUE || $value === FALSE || $value === 0) { - $element['#checked'] = (bool) $value; - } - else { - // Compare as strings, so that 15 is not considered equal to '15foo', but 1 - // is considered equal to '1'. This cast does not imply that either #value - // or #return_value is expected to be a string. - $element['#checked'] = ((string) $value === (string) $return_value); - } - return $element; -} - -function form_process_checkboxes($element) { - $value = is_array($element['#value']) ? $element['#value'] : array(); - $element['#tree'] = TRUE; - if (count($element['#options']) > 0) { - if (!isset($element['#default_value']) || $element['#default_value'] == 0) { - $element['#default_value'] = array(); - } - $weight = 0; - foreach ($element['#options'] as $key => $choice) { - // Integer 0 is not a valid #return_value, so use '0' instead. - // @see form_type_checkbox_value(). - // @todo For Drupal 8, cast all integer keys to strings for consistency - // with form_process_radios(). - if ($key === 0) { - $key = '0'; - } - // Maintain order of options as defined in #options, in case the element - // defines custom option sub-elements, but does not define all option - // sub-elements. - $weight += 0.001; - - $element += array($key => array()); - $element[$key] += array( - '#type' => 'checkbox', - '#title' => $choice, - '#return_value' => $key, - '#default_value' => isset($value[$key]) ? $key : NULL, - '#attributes' => $element['#attributes'], - '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, - '#weight' => $weight, - ); - } - } - return $element; -} - -/** - * Processes a form actions container element. - * - * @param $element - * An associative array containing the properties and children of the - * form actions container. - * @param $form_state - * The $form_state array for the form this element belongs to. - * - * @return - * The processed element. - */ -function form_process_actions($element, &$form_state) { - $element['#attributes']['class'][] = 'form-actions'; - return $element; -} - -/** - * Processes a container element. - * - * @param $element - * An associative array containing the properties and children of the - * container. - * @param $form_state - * The $form_state array for the form this element belongs to. - * @return - * The processed element. - */ -function form_process_container($element, &$form_state) { - // Generate the ID of the element if it's not explicitly given. - if (!isset($element['#id'])) { - $element['#id'] = drupal_html_id(implode('-', $element['#parents']) . '-wrapper'); - } - return $element; -} - -/** - * Returns HTML to wrap child elements in a container. - * - * Used for grouped form items. Can also be used as a #theme_wrapper for any - * renderable element, to surround it with a <div> and add attributes such as - * classes or an HTML id. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #id, #attributes, #children. - * - * @ingroup themeable - */ -function theme_container($variables) { - $element = $variables['element']; - - // Special handling for form elements. - if (isset($element['#array_parents'])) { - // Assign an html ID. - if (!isset($element['#attributes']['id'])) { - $element['#attributes']['id'] = $element['#id']; - } - // Add the 'form-wrapper' class. - $element['#attributes']['class'][] = 'form-wrapper'; - } - - return '<div' . drupal_attributes($element['#attributes']) . '>' . $element['#children'] . '</div>'; -} - -/** - * Returns HTML for a table with radio buttons or checkboxes. - * - * An example of per-row options: - * @code - * $options = array(); - * $options[0]['title'] = "A red row" - * $options[0]['#attributes'] = array ('class' => array('red-row')); - * $options[1]['title'] = "A blue row" - * $options[1]['#attributes'] = array ('class' => array('blue-row')); - * - * $form['myselector'] = array ( - * '#type' => 'tableselect', - * '#title' => 'My Selector' - * '#options' => $options, - * ); - * @endcode - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties and children of - * the tableselect element. Properties used: #header, #options, #empty, - * and #js_select. The #options property is an array of selection options; - * each array element of #options is an array of properties. These - * properties can include #attributes, which is added to the - * table row's HTML attributes; see theme_table(). - * - * @ingroup themeable - */ -function theme_tableselect($variables) { - $element = $variables['element']; - $rows = array(); - $header = $element['#header']; - if (!empty($element['#options'])) { - // Generate a table row for each selectable item in #options. - foreach (element_children($element) as $key) { - $row = array(); - - $row['data'] = array(); - if (isset($element['#options'][$key]['#attributes'])) { - $row += $element['#options'][$key]['#attributes']; - } - // Render the checkbox / radio element. - $row['data'][] = drupal_render($element[$key]); - - // As theme_table only maps header and row columns by order, create the - // correct order by iterating over the header fields. - foreach ($element['#header'] as $fieldname => $title) { - $row['data'][] = $element['#options'][$key][$fieldname]; - } - $rows[] = $row; - } - // Add an empty header or a "Select all" checkbox to provide room for the - // checkboxes/radios in the first table column. - if ($element['#js_select']) { - // Add a "Select all" checkbox. - drupal_add_js('core/misc/tableselect.js'); - array_unshift($header, array('class' => array('select-all'))); - } - else { - // Add an empty header when radio buttons are displayed or a "Select all" - // checkbox is not desired. - array_unshift($header, ''); - } - } - return theme('table', array('header' => $header, 'rows' => $rows, 'empty' => $element['#empty'], 'attributes' => $element['#attributes'])); -} - -/** - * Create the correct amount of checkbox or radio elements to populate the table. - * - * @param $element - * An associative array containing the properties and children of the - * tableselect element. - * @return - * The processed element. - */ -function form_process_tableselect($element) { - - if ($element['#multiple']) { - $value = is_array($element['#value']) ? $element['#value'] : array(); - } - else { - // Advanced selection behaviour make no sense for radios. - $element['#js_select'] = FALSE; - } - - $element['#tree'] = TRUE; - - if (count($element['#options']) > 0) { - if (!isset($element['#default_value']) || $element['#default_value'] === 0) { - $element['#default_value'] = array(); - } - - // Create a checkbox or radio for each item in #options in such a way that - // the value of the tableselect element behaves as if it had been of type - // checkboxes or radios. - foreach ($element['#options'] as $key => $choice) { - // Do not overwrite manually created children. - if (!isset($element[$key])) { - if ($element['#multiple']) { - $title = ''; - if (!empty($element['#options'][$key]['title']['data']['#title'])) { - $title = t('Update @title', array( - '@title' => $element['#options'][$key]['title']['data']['#title'], - )); - } - $element[$key] = array( - '#type' => 'checkbox', - '#title' => $title, - '#title_display' => 'invisible', - '#return_value' => $key, - '#default_value' => isset($value[$key]) ? $key : NULL, - '#attributes' => $element['#attributes'], - ); - } - else { - // Generate the parents as the autogenerator does, so we will have a - // unique id for each radio button. - $parents_for_id = array_merge($element['#parents'], array($key)); - $element[$key] = array( - '#type' => 'radio', - '#title' => '', - '#return_value' => $key, - '#default_value' => ($element['#default_value'] == $key) ? $key : NULL, - '#attributes' => $element['#attributes'], - '#parents' => $element['#parents'], - '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)), - '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, - ); - } - if (isset($element['#options'][$key]['#weight'])) { - $element[$key]['#weight'] = $element['#options'][$key]['#weight']; - } - } - } - } - else { - $element['#value'] = array(); - } - return $element; -} - -/** - * Processes a machine-readable name form element. - * - * @param $element - * The form element to process. Properties used: - * - #machine_name: An associative array containing: - * - exists: A function name to invoke for checking whether a submitted - * machine name value already exists. The submitted value is passed as - * argument. In most cases, an existing API or menu argument loader - * function can be re-used. The callback is only invoked, if the submitted - * value differs from the element's #default_value. - * - source: (optional) The #array_parents of the form element containing - * the human-readable name (i.e., as contained in the $form structure) to - * use as source for the machine name. Defaults to array('name'). - * - label: (optional) A text to display as label for the machine name value - * after the human-readable name form element. Defaults to "Machine name". - * - replace_pattern: (optional) A regular expression (without delimiters) - * matching disallowed characters in the machine name. Defaults to - * '[^a-z0-9_]+'. - * - replace: (optional) A character to replace disallowed characters in the - * machine name via JavaScript. Defaults to '_' (underscore). When using a - * different character, 'replace_pattern' needs to be set accordingly. - * - error: (optional) A custom form error message string to show, if the - * machine name contains disallowed characters. - * - #maxlength: (optional) Should be set to the maximum allowed length of the - * machine name. Defaults to 64. - * - #disabled: (optional) Should be set to TRUE in case an existing machine - * name must not be changed after initial creation. - */ -function form_process_machine_name($element, &$form_state) { - // Apply default form element properties. - $element += array( - '#title' => t('Machine-readable name'), - '#description' => t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'), - '#machine_name' => array(), - ); - // A form element that only wants to set one #machine_name property (usually - // 'source' only) would leave all other properties undefined, if the defaults - // were defined in hook_element_info(). Therefore, we apply the defaults here. - $element['#machine_name'] += array( - 'source' => array('name'), - 'target' => '#' . $element['#id'], - 'label' => t('Machine name'), - 'replace_pattern' => '[^a-z0-9_]+', - 'replace' => '_', - ); - - // The source element defaults to array('name'), but may have been overidden. - if (empty($element['#machine_name']['source'])) { - return $element; - } - - // Retrieve the form element containing the human-readable name from the - // complete form in $form_state. By reference, because we need to append - // a #field_suffix that will hold the live preview. - $key_exists = NULL; - $source = drupal_array_get_nested_value($form_state['complete_form'], $element['#machine_name']['source'], $key_exists); - if (!$key_exists) { - return $element; - } - - // Append a field suffix to the source form element, which will contain - // the live preview of the machine name. - $suffix_id = $source['#id'] . '-machine-name-suffix'; - $source += array('#field_suffix' => ''); - $source['#field_suffix'] .= ' <small id="' . $suffix_id . '"> </small>'; - - $parents = array_merge($element['#machine_name']['source'], array('#field_suffix')); - drupal_array_set_nested_value($form_state['complete_form'], $parents, $source['#field_suffix']); - - $element['#machine_name']['suffix'] = '#' . $suffix_id; - - $js_settings = array( - 'type' => 'setting', - 'data' => array( - 'machineName' => array( - '#' . $source['#id'] => $element['#machine_name'], - ), - ), - ); - $element['#attached']['js'][] = 'core/misc/machine-name.js'; - $element['#attached']['js'][] = $js_settings; - - return $element; -} - -/** - * Form element validation handler for #type 'machine_name'. - * - * Note that #maxlength is validated by _form_validate() already. - */ -function form_validate_machine_name(&$element, &$form_state) { - // Verify that the machine name not only consists of replacement tokens. - if (preg_match('@^' . $element['#machine_name']['replace'] . '+$@', $element['#value'])) { - form_error($element, t('The machine-readable name must contain unique characters.')); - } - - // Verify that the machine name contains no disallowed characters. - if (preg_match('@' . $element['#machine_name']['replace_pattern'] . '@', $element['#value'])) { - if (!isset($element['#machine_name']['error'])) { - // Since a hyphen is the most common alternative replacement character, - // a corresponding validation error message is supported here. - if ($element['#machine_name']['replace'] == '-') { - form_error($element, t('The machine-readable name must contain only lowercase letters, numbers, and hyphens.')); - } - // Otherwise, we assume the default (underscore). - else { - form_error($element, t('The machine-readable name must contain only lowercase letters, numbers, and underscores.')); - } - } - else { - form_error($element, $element['#machine_name']['error']); - } - } - - // Verify that the machine name is unique. - if ($element['#default_value'] !== $element['#value']) { - $function = $element['#machine_name']['exists']; - if ($function($element['#value'], $element, $form_state)) { - form_error($element, t('The machine-readable name is already in use. It must be unique.')); - } - } -} - -/** - * Adds fieldsets to the specified group or adds group members to this - * fieldset. - * - * @param $element - * An associative array containing the properties and children of the - * fieldset. Note that $element must be taken by reference here, so processed - * child elements are taken over into $form_state. - * @param $form_state - * The $form_state array for the form this fieldset belongs to. - * @return - * The processed element. - */ -function form_process_fieldset(&$element, &$form_state) { - $parents = implode('][', $element['#parents']); - - // Each fieldset forms a new group. The #type 'vertical_tabs' basically only - // injects a new fieldset. - $form_state['groups'][$parents]['#group_exists'] = TRUE; - $element['#groups'] = &$form_state['groups']; - - // Process vertical tabs group member fieldsets. - if (isset($element['#group'])) { - // Add this fieldset to the defined group (by reference). - $group = $element['#group']; - $form_state['groups'][$group][] = &$element; - } - - // Contains form element summary functionalities. - $element['#attached']['library'][] = array('system', 'drupal.form'); - - // The .form-wrapper class is required for #states to treat fieldsets like - // containers. - if (!isset($element['#attributes']['class'])) { - $element['#attributes']['class'] = array(); - } - - // Collapsible fieldsets - if (!empty($element['#collapsible'])) { - $element['#attached']['library'][] = array('system', 'drupal.collapse'); - $element['#attributes']['class'][] = 'collapsible'; - if (!empty($element['#collapsed'])) { - $element['#attributes']['class'][] = 'collapsed'; - } - } - - return $element; -} - -/** - * Adds members of this group as actual elements for rendering. - * - * @param $element - * An associative array containing the properties and children of the - * fieldset. - * - * @return - * The modified element with all group members. - */ -function form_pre_render_fieldset($element) { - // Fieldsets may be rendered outside of a Form API context. - if (!isset($element['#parents']) || !isset($element['#groups'])) { - return $element; - } - // Inject group member elements belonging to this group. - $parents = implode('][', $element['#parents']); - $children = element_children($element['#groups'][$parents]); - if (!empty($children)) { - foreach ($children as $key) { - // Break references and indicate that the element should be rendered as - // group member. - $child = (array) $element['#groups'][$parents][$key]; - $child['#group_fieldset'] = TRUE; - // Inject the element as new child element. - $element[] = $child; - - $sort = TRUE; - } - // Re-sort the element's children if we injected group member elements. - if (isset($sort)) { - $element['#sorted'] = FALSE; - } - } - - if (isset($element['#group'])) { - $group = $element['#group']; - // If this element belongs to a group, but the group-holding element does - // not exist, we need to render it (at its original location). - if (!isset($element['#groups'][$group]['#group_exists'])) { - // Intentionally empty to clarify the flow; we simply return $element. - } - // If we injected this element into the group, then we want to render it. - elseif (!empty($element['#group_fieldset'])) { - // Intentionally empty to clarify the flow; we simply return $element. - } - // Otherwise, this element belongs to a group and the group exists, so we do - // not render it. - elseif (element_children($element['#groups'][$group])) { - $element['#printed'] = TRUE; - } - } - - return $element; -} - -/** - * Creates a group formatted as vertical tabs. - * - * @param $element - * An associative array containing the properties and children of the - * fieldset. - * @param $form_state - * The $form_state array for the form this vertical tab widget belongs to. - * @return - * The processed element. - */ -function form_process_vertical_tabs($element, &$form_state) { - // Inject a new fieldset as child, so that form_process_fieldset() processes - // this fieldset like any other fieldset. - $element['group'] = array( - '#type' => 'fieldset', - '#theme_wrappers' => array(), - '#parents' => $element['#parents'], - ); - - // The JavaScript stores the currently selected tab in this hidden - // field so that the active tab can be restored the next time the - // form is rendered, e.g. on preview pages or when form validation - // fails. - $name = implode('__', $element['#parents']); - if (isset($form_state['values'][$name . '__active_tab'])) { - $element['#default_tab'] = $form_state['values'][$name . '__active_tab']; - } - $element[$name . '__active_tab'] = array( - '#type' => 'hidden', - '#default_value' => $element['#default_tab'], - '#attributes' => array('class' => array('vertical-tabs-active-tab')), - ); - - return $element; -} - -/** - * Returns HTML for an element's children fieldsets as vertical tabs. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties and children of the - * fieldset. Properties used: #children. - * - * @ingroup themeable - */ -function theme_vertical_tabs($variables) { - $element = $variables['element']; - // Add required JavaScript and Stylesheet. - drupal_add_library('system', 'drupal.vertical-tabs'); - - $output = '<h2 class="element-invisible">' . t('Vertical Tabs') . '</h2>'; - $output .= '<div class="vertical-tabs-panes">' . $element['#children'] . '</div>'; - return $output; -} - -/** - * Returns HTML for a submit button form element. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #attributes, #button_type, #name, #value. - * - * @ingroup themeable - */ -function theme_submit($variables) { - return theme('button', $variables['element']); -} - -/** - * Returns HTML for a button form element. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #attributes, #button_type, #name, #value. - * - * @ingroup themeable - */ -function theme_button($variables) { - $element = $variables['element']; - $element['#attributes']['type'] = 'submit'; - element_set_attributes($element, array('id', 'name', 'value')); - - $element['#attributes']['class'][] = 'form-' . $element['#button_type']; - if (!empty($element['#attributes']['disabled'])) { - $element['#attributes']['class'][] = 'form-button-disabled'; - } - - return '<input' . drupal_attributes($element['#attributes']) . ' />'; -} - -/** - * Returns HTML for an image button form element. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #attributes, #button_type, #name, #value, #title, #src. - * - * @ingroup themeable - */ -function theme_image_button($variables) { - $element = $variables['element']; - $element['#attributes']['type'] = 'image'; - element_set_attributes($element, array('id', 'name', 'value')); - - $element['#attributes']['src'] = file_create_url($element['#src']); - if (!empty($element['#title'])) { - $element['#attributes']['alt'] = $element['#title']; - $element['#attributes']['title'] = $element['#title']; - } - - $element['#attributes']['class'][] = 'form-' . $element['#button_type']; - if (!empty($element['#attributes']['disabled'])) { - $element['#attributes']['class'][] = 'form-button-disabled'; - } - - return '<input' . drupal_attributes($element['#attributes']) . ' />'; -} - -/** - * Returns HTML for a hidden form element. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #name, #value, #attributes. - * - * @ingroup themeable - */ -function theme_hidden($variables) { - $element = $variables['element']; - $element['#attributes']['type'] = 'hidden'; - element_set_attributes($element, array('name', 'value')); - return '<input' . drupal_attributes($element['#attributes']) . " />\n"; -} - -/** - * Returns HTML for a textfield form element. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #title, #value, #description, #size, #maxlength, - * #placeholder, #required, #attributes, #autocomplete_path. - * - * @ingroup themeable - */ -function theme_textfield($variables) { - $element = $variables['element']; - $element['#attributes']['type'] = 'text'; - element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder')); - _form_set_class($element, array('form-text')); - - $extra = ''; - if ($element['#autocomplete_path'] && drupal_valid_path($element['#autocomplete_path'])) { - drupal_add_library('system', 'drupal.autocomplete'); - $element['#attributes']['class'][] = 'form-autocomplete'; - - $attributes = array(); - $attributes['type'] = 'hidden'; - $attributes['id'] = $element['#attributes']['id'] . '-autocomplete'; - $attributes['value'] = url($element['#autocomplete_path'], array('absolute' => TRUE)); - $attributes['disabled'] = 'disabled'; - $attributes['class'][] = 'autocomplete'; - $extra = '<input' . drupal_attributes($attributes) . ' />'; - } - - $output = '<input' . drupal_attributes($element['#attributes']) . ' />'; - - return $output . $extra; -} - -/** - * Returns HTML for a form. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #action, #method, #attributes, #children - * - * @ingroup themeable - */ -function theme_form($variables) { - $element = $variables['element']; - if (isset($element['#action'])) { - $element['#attributes']['action'] = drupal_strip_dangerous_protocols($element['#action']); - } - element_set_attributes($element, array('method', 'id')); - if (empty($element['#attributes']['accept-charset'])) { - $element['#attributes']['accept-charset'] = "UTF-8"; - } - // Anonymous DIV to satisfy XHTML compliance. - return '<form' . drupal_attributes($element['#attributes']) . '><div>' . $element['#children'] . '</div></form>'; -} - -/** - * Returns HTML for a textarea form element. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #title, #value, #description, #rows, #cols, - * #placeholder, #required, #attributes - * - * @ingroup themeable - */ -function theme_textarea($variables) { - $element = $variables['element']; - element_set_attributes($element, array('id', 'name', 'rows', 'cols', 'placeholder')); - _form_set_class($element, array('form-textarea')); - - $wrapper_attributes = array( - 'class' => array('form-textarea-wrapper'), - ); - - // Add resizable behavior. - if (!empty($element['#resizable'])) { - drupal_add_library('system', 'drupal.textarea'); - $wrapper_attributes['class'][] = 'resizable'; - } - - $output = '<div' . drupal_attributes($wrapper_attributes) . '>'; - $output .= '<textarea' . drupal_attributes($element['#attributes']) . '>' . check_plain($element['#value']) . '</textarea>'; - $output .= '</div>'; - return $output; -} - -/** - * Returns HTML for a password form element. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #title, #value, #description, #size, #maxlength, - * #placeholder, #required, #attributes. - * - * @ingroup themeable - */ -function theme_password($variables) { - $element = $variables['element']; - $element['#attributes']['type'] = 'password'; - element_set_attributes($element, array('id', 'name', 'size', 'maxlength', 'placeholder')); - _form_set_class($element, array('form-text')); - - return '<input' . drupal_attributes($element['#attributes']) . ' />'; -} - -/** - * Expand weight elements into selects. - */ -function form_process_weight($element) { - for ($n = (-1 * $element['#delta']); $n <= $element['#delta']; $n++) { - $weights[$n] = $n; - } - $element['#options'] = $weights; - $element['#type'] = 'select'; - $element['#is_weight'] = TRUE; - $element += element_info('select'); - return $element; -} - -/** - * Returns HTML for a file upload form element. - * - * For assistance with handling the uploaded file correctly, see the API - * provided by file.inc. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #title, #name, #size, #description, #required, - * #attributes. - * - * @ingroup themeable - */ -function theme_file($variables) { - $element = $variables['element']; - $element['#attributes']['type'] = 'file'; - element_set_attributes($element, array('id', 'name', 'size')); - _form_set_class($element, array('form-file')); - - return '<input' . drupal_attributes($element['#attributes']) . ' />'; -} - -/** - * Returns HTML for a form element. - * - * Each form element is wrapped in a DIV container having the following CSS - * classes: - * - form-item: Generic for all form elements. - * - form-type-#type: The internal element #type. - * - form-item-#name: The internal form element #name (usually derived from the - * $form structure and set via form_builder()). - * - form-disabled: Only set if the form element is #disabled. - * - * In addition to the element itself, the DIV contains a label for the element - * based on the optional #title_display property, and an optional #description. - * - * The optional #title_display property can have these values: - * - before: The label is output before the element. This is the default. - * The label includes the #title and the required marker, if #required. - * - after: The label is output after the element. For example, this is used - * for radio and checkbox #type elements as set in system_element_info(). - * If the #title is empty but the field is #required, the label will - * contain only the required marker. - * - invisible: Labels are critical for screen readers to enable them to - * properly navigate through forms but can be visually distracting. This - * property hides the label for everyone except screen readers. - * - attribute: Set the title attribute on the element to create a tooltip - * but output no label element. This is supported only for checkboxes - * and radios in form_pre_render_conditional_form_element(). It is used - * where a visual label is not needed, such as a table of checkboxes where - * the row and column provide the context. The tooltip will include the - * title and required marker. - * - * If the #title property is not set, then the label and any required marker - * will not be output, regardless of the #title_display or #required values. - * This can be useful in cases such as the password_confirm element, which - * creates children elements that have their own labels and required markers, - * but the parent element should have neither. Use this carefully because a - * field without an associated label can cause accessibility challenges. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #title, #title_display, #description, #id, #required, - * #children, #type, #name. - * - * @ingroup themeable - */ -function theme_form_element($variables) { - $element = &$variables['element']; - // This is also used in the installer, pre-database setup. - $t = get_t(); - - // This function is invoked as theme wrapper, but the rendered form element - // may not necessarily have been processed by form_builder(). - $element += array( - '#title_display' => 'before', - ); - - // Add element #id for #type 'item'. - if (isset($element['#markup']) && !empty($element['#id'])) { - $attributes['id'] = $element['#id']; - } - // Add element's #type and #name as class to aid with JS/CSS selectors. - $attributes['class'] = array('form-item'); - if (!empty($element['#type'])) { - $attributes['class'][] = 'form-type-' . strtr($element['#type'], '_', '-'); - } - if (!empty($element['#name'])) { - $attributes['class'][] = 'form-item-' . strtr($element['#name'], array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); - } - // Add a class for disabled elements to facilitate cross-browser styling. - if (!empty($element['#attributes']['disabled'])) { - $attributes['class'][] = 'form-disabled'; - } - $output = '<div' . drupal_attributes($attributes) . '>' . "\n"; - - // If #title is not set, we don't display any label or required marker. - if (!isset($element['#title'])) { - $element['#title_display'] = 'none'; - } - $prefix = isset($element['#field_prefix']) ? '<span class="field-prefix">' . $element['#field_prefix'] . '</span> ' : ''; - $suffix = isset($element['#field_suffix']) ? ' <span class="field-suffix">' . $element['#field_suffix'] . '</span>' : ''; - - switch ($element['#title_display']) { - case 'before': - case 'invisible': - $output .= ' ' . theme('form_element_label', $variables); - $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n"; - break; - - case 'after': - $output .= ' ' . $prefix . $element['#children'] . $suffix; - $output .= ' ' . theme('form_element_label', $variables) . "\n"; - break; - - case 'none': - case 'attribute': - // Output no label and no required marker, only the children. - $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n"; - break; - } - - if (!empty($element['#description'])) { - $output .= '<div class="description">' . $element['#description'] . "</div>\n"; - } - - $output .= "</div>\n"; - - return $output; -} - -/** - * Returns HTML for a marker for required form elements. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * - * @ingroup themeable - */ -function theme_form_required_marker($variables) { - // This is also used in the installer, pre-database setup. - $t = get_t(); - $attributes = array( - 'class' => 'form-required', - 'title' => $t('This field is required.'), - ); - return '<abbr' . drupal_attributes($attributes) . '>*</abbr>'; -} - -/** - * Returns HTML for a form element label and required marker. - * - * Form element labels include the #title and a #required marker. The label is - * associated with the element itself by the element #id. Labels may appear - * before or after elements, depending on theme_form_element() and #title_display. - * - * This function will not be called for elements with no labels, depending on - * #title_display. For elements that have an empty #title and are not required, - * this function will output no label (''). For required elements that have an - * empty #title, this will output the required marker alone within the label. - * The label will use the #id to associate the marker with the field that is - * required. That is especially important for screenreader users to know - * which field is required. - * - * @param $variables - * An associative array containing: - * - element: An associative array containing the properties of the element. - * Properties used: #required, #title, #id, #value, #description. - * - * @ingroup themeable - */ -function theme_form_element_label($variables) { - $element = $variables['element']; - // This is also used in the installer, pre-database setup. - $t = get_t(); - - // If title and required marker are both empty, output no label. - if (empty($element['#title']) && empty($element['#required'])) { - return ''; - } - - // If the element is required, a required marker is appended to the label. - $required = !empty($element['#required']) ? theme('form_required_marker', array('element' => $element)) : ''; - - $title = filter_xss_admin($element['#title']); - - $attributes = array(); - // Style the label as class option to display inline with the element. - if ($element['#title_display'] == 'after') { - $attributes['class'] = 'option'; - } - // Show label only to screen readers to avoid disruption in visual flows. - elseif ($element['#title_display'] == 'invisible') { - $attributes['class'] = 'element-invisible'; - } - - if (!empty($element['#id'])) { - $attributes['for'] = $element['#id']; - } - - // The leading whitespace helps visually separate fields from inline labels. - return ' <label' . drupal_attributes($attributes) . '>' . $t('!title !required', array('!title' => $title, '!required' => $required)) . "</label>\n"; -} - -/** - * Sets a form element's class attribute. - * - * Adds 'required' and 'error' classes as needed. - * - * @param $element - * The form element. - * @param $name - * Array of new class names to be added. - */ -function _form_set_class(&$element, $class = array()) { - if (!empty($class)) { - if (!isset($element['#attributes']['class'])) { - $element['#attributes']['class'] = array(); - } - $element['#attributes']['class'] = array_merge($element['#attributes']['class'], $class); - } - // This function is invoked from form element theme functions, but the - // rendered form element may not necessarily have been processed by - // form_builder(). - if (!empty($element['#required'])) { - $element['#attributes']['class'][] = 'required'; - } - if (isset($element['#parents']) && form_get_error($element)) { - $element['#attributes']['class'][] = 'error'; - } -} - -/** - * Helper form element validator: integer. - */ -function element_validate_integer($element, &$form_state) { - $value = $element['#value']; - if ($value !== '' && (!is_numeric($value) || intval($value) != $value)) { - form_error($element, t('%name must be an integer.', array('%name' => $element['#title']))); - } -} - -/** - * Helper form element validator: integer > 0. - */ -function element_validate_integer_positive($element, &$form_state) { - $value = $element['#value']; - if ($value !== '' && (!is_numeric($value) || intval($value) != $value || $value <= 0)) { - form_error($element, t('%name must be a positive integer.', array('%name' => $element['#title']))); - } -} - -/** - * Helper form element validator: number. - */ -function element_validate_number($element, &$form_state) { - $value = $element['#value']; - if ($value != '' && !is_numeric($value)) { - form_error($element, t('%name must be a number.', array('%name' => $element['#title']))); - } -} - -/** - * @} End of "defgroup form_api". - */ - -/** - * @defgroup batch Batch operations - * @{ - * Create and process batch operations. - * - * Functions allowing forms processing to be spread out over several page - * requests, thus ensuring that the processing does not get interrupted - * because of a PHP timeout, while allowing the user to receive feedback - * on the progress of the ongoing operations. - * - * The API is primarily designed to integrate nicely with the Form API - * workflow, but can also be used by non-Form API scripts (like update.php) - * or even simple page callbacks (which should probably be used sparingly). - * - * Example: - * @code - * $batch = array( - * 'title' => t('Exporting'), - * 'operations' => array( - * array('my_function_1', array($account->uid, 'story')), - * array('my_function_2', array()), - * ), - * 'finished' => 'my_finished_callback', - * 'file' => 'path_to_file_containing_myfunctions', - * ); - * batch_set($batch); - * // Only needed if not inside a form _submit handler. - * // Setting redirect in batch_process. - * batch_process('node/1'); - * @endcode - * - * Note: if the batch 'title', 'init_message', 'progress_message', or - * 'error_message' could contain any user input, it is the responsibility of - * the code calling batch_set() to sanitize them first with a function like - * check_plain() or filter_xss(). Furthermore, if the batch operation - * returns any user input in the 'results' or 'message' keys of $context, - * it must also sanitize them first. - * - * Sample batch operations: - * @code - * // Simple and artificial: load a node of a given type for a given user - * function my_function_1($uid, $type, &$context) { - * // The $context array gathers batch context information about the execution (read), - * // as well as 'return values' for the current operation (write) - * // The following keys are provided : - * // 'results' (read / write): The array of results gathered so far by - * // the batch processing, for the current operation to append its own. - * // 'message' (write): A text message displayed in the progress page. - * // The following keys allow for multi-step operations : - * // 'sandbox' (read / write): An array that can be freely used to - * // store persistent data between iterations. It is recommended to - * // use this instead of $_SESSION, which is unsafe if the user - * // continues browsing in a separate window while the batch is processing. - * // 'finished' (write): A float number between 0 and 1 informing - * // the processing engine of the completion level for the operation. - * // 1 (or no value explicitly set) means the operation is finished - * // and the batch processing can continue to the next operation. - * - * $node = node_load(array('uid' => $uid, 'type' => $type)); - * $context['results'][] = $node->nid . ' : ' . check_plain($node->title); - * $context['message'] = check_plain($node->title); - * } - * - * // More advanced example: multi-step operation - load all nodes, five by five - * function my_function_2(&$context) { - * if (empty($context['sandbox'])) { - * $context['sandbox']['progress'] = 0; - * $context['sandbox']['current_node'] = 0; - * $context['sandbox']['max'] = db_query('SELECT COUNT(DISTINCT nid) FROM {node}')->fetchField(); - * } - * $limit = 5; - * $result = db_select('node') - * ->fields('node', array('nid')) - * ->condition('nid', $context['sandbox']['current_node'], '>') - * ->orderBy('nid') - * ->range(0, $limit) - * ->execute(); - * foreach ($result as $row) { - * $node = node_load($row->nid, NULL, TRUE); - * $context['results'][] = $node->nid . ' : ' . check_plain($node->title); - * $context['sandbox']['progress']++; - * $context['sandbox']['current_node'] = $node->nid; - * $context['message'] = check_plain($node->title); - * } - * if ($context['sandbox']['progress'] != $context['sandbox']['max']) { - * $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; - * } - * } - * @endcode - * - * Sample 'finished' callback: - * @code - * function batch_test_finished($success, $results, $operations) { - * // The 'success' parameter means no fatal PHP errors were detected. All - * // other error management should be handled using 'results'. - * if ($success) { - * $message = format_plural(count($results), 'One post processed.', '@count posts processed.'); - * } - * else { - * $message = t('Finished with an error.'); - * } - * drupal_set_message($message); - * // Providing data for the redirected page is done through $_SESSION. - * foreach ($results as $result) { - * $items[] = t('Loaded node %title.', array('%title' => $result)); - * } - * $_SESSION['my_batch_results'] = $items; - * } - * @endcode - */ - -/** - * Opens a new batch. - * - * @param $batch - * An array defining the batch. The following keys can be used -- only - * 'operations' is required, and batch_init() provides default values for - * the messages. - * - 'operations': Array of function calls to be performed. - * Example: - * @code - * array( - * array('my_function_1', array($arg1)), - * array('my_function_2', array($arg2_1, $arg2_2)), - * ) - * @endcode - * - 'title': Title for the progress page. Only safe strings should be passed. - * Defaults to t('Processing'). - * - 'init_message': Message displayed while the processing is initialized. - * Defaults to t('Initializing.'). - * - 'progress_message': Message displayed while processing the batch. - * Available placeholders are @current, @remaining, @total, @percentage, - * @estimate and @elapsed. Defaults to t('Completed @current of @total.'). - * - 'error_message': Message displayed if an error occurred while processing - * the batch. Defaults to t('An error has occurred.'). - * - 'finished': Name of a function to be executed after the batch has - * completed. This should be used to perform any result massaging that - * may be needed, and possibly save data in $_SESSION for display after - * final page redirection. - * - 'file': Path to the file containing the definitions of the - * 'operations' and 'finished' functions, for instance if they don't - * reside in the main .module file. The path should be relative to - * base_path(), and thus should be built using drupal_get_path(). - * - 'css': Array of paths to CSS files to be used on the progress page. - * - 'url_options': options passed to url() when constructing redirect - * URLs for the batch. - * - * Operations are added as new batch sets. Batch sets are used to ensure - * clean code independence, ensuring that several batches submitted by - * different parts of the code (core / contrib modules) can be processed - * correctly while not interfering or having to cope with each other. Each - * batch set gets to specify his own UI messages, operates on its own set - * of operations and results, and triggers its own 'finished' callback. - * Batch sets are processed sequentially, with the progress bar starting - * fresh for every new set. - */ -function batch_set($batch_definition) { - if ($batch_definition) { - $batch =& batch_get(); - - // Initialize the batch if needed. - if (empty($batch)) { - $batch = array( - 'sets' => array(), - 'has_form_submits' => FALSE, - ); - } - - // Base and default properties for the batch set. - // Use get_t() to allow batches at install time. - $t = get_t(); - $init = array( - 'sandbox' => array(), - 'results' => array(), - 'success' => FALSE, - 'start' => 0, - 'elapsed' => 0, - ); - $defaults = array( - 'title' => $t('Processing'), - 'init_message' => $t('Initializing.'), - 'progress_message' => $t('Completed @current of @total.'), - 'error_message' => $t('An error has occurred.'), - 'css' => array(), - ); - $batch_set = $init + $batch_definition + $defaults; - - // Tweak init_message to avoid the bottom of the page flickering down after - // init phase. - $batch_set['init_message'] .= '<br/> '; - - // The non-concurrent workflow of batch execution allows us to save - // numberOfItems() queries by handling our own counter. - $batch_set['total'] = count($batch_set['operations']); - $batch_set['count'] = $batch_set['total']; - - // Add the set to the batch. - if (empty($batch['id'])) { - // The batch is not running yet. Simply add the new set. - $batch['sets'][] = $batch_set; - } - else { - // The set is being added while the batch is running. Insert the new set - // right after the current one to ensure execution order, and store its - // operations in a queue. - $index = $batch['current_set'] + 1; - $slice1 = array_slice($batch['sets'], 0, $index); - $slice2 = array_slice($batch['sets'], $index); - $batch['sets'] = array_merge($slice1, array($batch_set), $slice2); - _batch_populate_queue($batch, $index); - } - } -} - -/** - * Processes the batch. - * - * Unless the batch has been marked with 'progressive' = FALSE, the function - * issues a drupal_goto and thus ends page execution. - * - * This function is generally not needed in form submit handlers; - * Form API takes care of batches that were set during form submission. - * - * @param $redirect - * (optional) Path to redirect to when the batch has finished processing. - * @param $url - * (optional - should only be used for separate scripts like update.php) - * URL of the batch processing page. - * @param $redirect_callback - * (optional) Specify a function to be called to redirect to the progressive - * processing page. By default drupal_goto() will be used to redirect to a - * page which will do the progressive page. Specifying another function will - * allow the progressive processing to be processed differently. - */ -function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = 'drupal_goto') { - $batch =& batch_get(); - - drupal_theme_initialize(); - - if (isset($batch)) { - // Add process information - $process_info = array( - 'current_set' => 0, - 'progressive' => TRUE, - 'url' => $url, - 'url_options' => array(), - 'source_url' => $_GET['q'], - 'redirect' => $redirect, - 'theme' => $GLOBALS['theme_key'], - 'redirect_callback' => $redirect_callback, - ); - $batch += $process_info; - - // The batch is now completely built. Allow other modules to make changes - // to the batch so that it is easier to reuse batch processes in other - // environments. - drupal_alter('batch', $batch); - - // Assign an arbitrary id: don't rely on a serial column in the 'batch' - // table, since non-progressive batches skip database storage completely. - $batch['id'] = db_next_id(); - - // Move operations to a job queue. Non-progressive batches will use a - // memory-based queue. - foreach ($batch['sets'] as $key => $batch_set) { - _batch_populate_queue($batch, $key); - } - - // Initiate processing. - if ($batch['progressive']) { - // Now that we have a batch id, we can generate the redirection link in - // the generic error message. - $t = get_t(); - $batch['error_message'] = $t('Please continue to <a href="@error_url">the error page</a>', array('@error_url' => url($url, array('query' => array('id' => $batch['id'], 'op' => 'finished'))))); - - // Clear the way for the drupal_goto() redirection to the batch processing - // page, by saving and unsetting the 'destination', if there is any. - if (isset($_GET['destination'])) { - $batch['destination'] = $_GET['destination']; - unset($_GET['destination']); - } - - // Store the batch. - db_insert('batch') - ->fields(array( - 'bid' => $batch['id'], - 'timestamp' => REQUEST_TIME, - 'token' => drupal_get_token($batch['id']), - 'batch' => serialize($batch), - )) - ->execute(); - - // Set the batch number in the session to guarantee that it will stay alive. - $_SESSION['batches'][$batch['id']] = TRUE; - - // Redirect for processing. - $function = $batch['redirect_callback']; - if (function_exists($function)) { - $function($batch['url'], array('query' => array('op' => 'start', 'id' => $batch['id']))); - } - } - else { - // Non-progressive execution: bypass the whole progressbar workflow - // and execute the batch in one pass. - require_once DRUPAL_ROOT . '/core/includes/batch.inc'; - _batch_process(); - } - } -} - -/** - * Retrieves the current batch. - */ -function &batch_get() { - // Not drupal_static(), because Batch API operates at a lower level than most - // use-cases for resetting static variables, and we specifically do not want a - // global drupal_static_reset() resetting the batch information. Functions - // that are part of the Batch API and need to reset the batch information may - // call batch_get() and manipulate the result by reference. Functions that are - // not part of the Batch API can also do this, but shouldn't. - static $batch = array(); - return $batch; -} - -/** - * Populates a job queue with the operations of a batch set. - * - * Depending on whether the batch is progressive or not, the BatchQueue or - * BatchMemoryQueue handler classes will be used. - * - * @param $batch - * The batch array. - * @param $set_id - * The id of the set to process. - * @return - * The name and class of the queue are added by reference to the batch set. - */ -function _batch_populate_queue(&$batch, $set_id) { - $batch_set = &$batch['sets'][$set_id]; - - if (isset($batch_set['operations'])) { - $batch_set += array( - 'queue' => array( - 'name' => 'drupal_batch:' . $batch['id'] . ':' . $set_id, - 'class' => $batch['progressive'] ? 'BatchQueue' : 'BatchMemoryQueue', - ), - ); - - $queue = _batch_queue($batch_set); - $queue->createQueue(); - foreach ($batch_set['operations'] as $operation) { - $queue->createItem($operation); - } - - unset($batch_set['operations']); - } -} - -/** - * Returns a queue object for a batch set. - * - * @param $batch_set - * The batch set. - * @return - * The queue object. - */ -function _batch_queue($batch_set) { - static $queues; - - // The class autoloader is not available when running update.php, so make - // sure the files are manually included. - if (!isset($queues)) { - $queues = array(); - require_once DRUPAL_ROOT . '/core/modules/system/system.queue.inc'; - require_once DRUPAL_ROOT . '/core/includes/batch.queue.inc'; - } - - if (isset($batch_set['queue'])) { - $name = $batch_set['queue']['name']; - $class = $batch_set['queue']['class']; - - if (!isset($queues[$class][$name])) { - $queues[$class][$name] = new $class($name); - } - return $queues[$class][$name]; - } -} - -/** - * @} End of "defgroup batch". - */ diff --git a/core/includes/gettext.inc b/core/includes/gettext.inc deleted file mode 100644 index 4d2ed9ebc74..00000000000 --- a/core/includes/gettext.inc +++ /dev/null @@ -1,1105 +0,0 @@ -<?php - -/** - * @file - * Gettext parsing and generating API. - * - * @todo Decouple these functions from Locale API and put to gettext_ namespace. - */ - -/** - * @defgroup locale-api-import-export Translation import/export API. - * @{ - * Functions to import and export translations. - * - * These functions provide the ability to import translations from - * external files and to export translations and translation templates. - */ - -/** - * Parses Gettext Portable Object file information and inserts into database - * - * @param $file - * Drupal file object corresponding to the PO file to import. - * @param $langcode - * Language code. - * @param $mode - * Should existing translations be replaced LOCALE_IMPORT_KEEP or - * LOCALE_IMPORT_OVERWRITE. - */ -function _locale_import_po($file, $langcode, $mode) { - // Try to allocate enough time to parse and import the data. - drupal_set_time_limit(240); - - // Check if we have the language already in the database. - if (!language_load($langcode)) { - drupal_set_message(t('The language selected for import is not supported.'), 'error'); - return FALSE; - } - - // Get strings from file (returns on failure after a partial import, or on success) - $status = _locale_import_read_po('db-store', $file, $mode, $langcode); - if ($status === FALSE) { - // Error messages are set in _locale_import_read_po(). - return FALSE; - } - - // Get status information on import process. - list($header_done, $additions, $updates, $deletes, $skips) = _locale_import_one_string('db-report'); - - if (!$header_done) { - drupal_set_message(t('The translation file %filename appears to have a missing or malformed header.', array('%filename' => $file->filename)), 'error'); - } - - // Clear cache and force refresh of JavaScript translations. - _locale_invalidate_js($langcode); - cache()->deletePrefix('locale:'); - - // Rebuild the menu, strings may have changed. - menu_rebuild(); - - drupal_set_message(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes))); - watchdog('locale', 'Imported %file into %locale: %number new strings added, %update updated and %delete removed.', array('%file' => $file->filename, '%locale' => $langcode, '%number' => $additions, '%update' => $updates, '%delete' => $deletes)); - if ($skips) { - if (module_exists('dblog')) { - $skip_message = format_plural($skips, 'A translation string was skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.', '@count translation strings were skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.', array('@url' => url('admin/reports/dblog'))); - } - else { - $skip_message = format_plural($skips, 'A translation string was skipped because of disallowed or malformed HTML. See the log for details.', '@count translation strings were skipped because of disallowed or malformed HTML. See the log for details.'); - } - drupal_set_message($skip_message, 'error'); - watchdog('locale', '@count disallowed HTML string(s) in %file', array('@count' => $skips, '%file' => $file->uri), WATCHDOG_WARNING); - } - return TRUE; -} - -/** - * Parses Gettext Portable Object file into an array - * - * @param $op - * Storage operation type: db-store or mem-store. - * @param $file - * Drupal file object corresponding to the PO file to import. - * @param $mode - * Should existing translations be replaced LOCALE_IMPORT_KEEP or - * LOCALE_IMPORT_OVERWRITE. - * @param $lang - * Language code. - */ -function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL) { - - // The file will get closed by PHP on returning from this function. - $fd = fopen($file->uri, 'rb'); - if (!$fd) { - _locale_import_message('The translation import failed because the file %filename could not be read.', $file); - return FALSE; - } - - /* - * The parser context. Can be: - * - 'COMMENT' (#) - * - 'MSGID' (msgid) - * - 'MSGID_PLURAL' (msgid_plural) - * - 'MSGCTXT' (msgctxt) - * - 'MSGSTR' (msgstr or msgstr[]) - * - 'MSGSTR_ARR' (msgstr_arg) - */ - $context = 'COMMENT'; - - // Current entry being read. - $current = array(); - - // Current plurality for 'msgstr[]'. - $plural = 0; - - // Current line. - $lineno = 0; - - while (!feof($fd)) { - // A line should not be longer than 10 * 1024. - $line = fgets($fd, 10 * 1024); - - if ($lineno == 0) { - // The first line might come with a UTF-8 BOM, which should be removed. - $line = str_replace("\xEF\xBB\xBF", '', $line); - } - - $lineno++; - - // Trim away the linefeed. - $line = trim(strtr($line, array("\\\n" => ""))); - - if (!strncmp('#', $line, 1)) { - // Lines starting with '#' are comments. - - if ($context == 'COMMENT') { - // Already in comment token, insert the comment. - $current['#'][] = substr($line, 1); - } - elseif (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) { - // We are currently in string token, close it out. - _locale_import_one_string($op, $current, $mode, $lang, $file); - - // Start a new entry for the comment. - $current = array(); - $current['#'][] = substr($line, 1); - - $context = 'COMMENT'; - } - else { - // A comment following any other token is a syntax error. - _locale_import_message('The translation file %filename contains an error: "msgstr" was expected but not found on line %line.', $file, $lineno); - return FALSE; - } - } - elseif (!strncmp('msgid_plural', $line, 12)) { - // A plural form for the current message. - - if ($context != 'MSGID') { - // A plural form cannot be added to anything else but the id directly. - _locale_import_message('The translation file %filename contains an error: "msgid_plural" was expected but not found on line %line.', $file, $lineno); - return FALSE; - } - - // Remove 'msgid_plural' and trim away whitespace. - $line = trim(substr($line, 12)); - // At this point, $line should now contain only the plural form. - - $quoted = _locale_import_parse_quoted($line); - if ($quoted === FALSE) { - // The plural form must be wrapped in quotes. - _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno); - return FALSE; - } - - // Append the plural form to the current entry. - $current['msgid'] .= "\0" . $quoted; - - $context = 'MSGID_PLURAL'; - } - elseif (!strncmp('msgid', $line, 5)) { - // Starting a new message. - - if (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) { - // We are currently in a message string, close it out. - _locale_import_one_string($op, $current, $mode, $lang, $file); - - // Start a new context for the id. - $current = array(); - } - elseif ($context == 'MSGID') { - // We are currently already in the context, meaning we passed an id with no data. - _locale_import_message('The translation file %filename contains an error: "msgid" is unexpected on line %line.', $file, $lineno); - return FALSE; - } - - // Remove 'msgid' and trim away whitespace. - $line = trim(substr($line, 5)); - // At this point, $line should now contain only the message id. - - $quoted = _locale_import_parse_quoted($line); - if ($quoted === FALSE) { - // The message id must be wrapped in quotes. - _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno); - return FALSE; - } - - $current['msgid'] = $quoted; - $context = 'MSGID'; - } - elseif (!strncmp('msgctxt', $line, 7)) { - // Starting a new context. - - if (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) { - // We are currently in a message, start a new one. - _locale_import_one_string($op, $current, $mode, $lang, $file); - $current = array(); - } - elseif (!empty($current['msgctxt'])) { - // A context cannot apply to another context. - _locale_import_message('The translation file %filename contains an error: "msgctxt" is unexpected on line %line.', $file, $lineno); - return FALSE; - } - - // Remove 'msgctxt' and trim away whitespaces. - $line = trim(substr($line, 7)); - // At this point, $line should now contain the context. - - $quoted = _locale_import_parse_quoted($line); - if ($quoted === FALSE) { - // The context string must be quoted. - _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno); - return FALSE; - } - - $current['msgctxt'] = $quoted; - - $context = 'MSGCTXT'; - } - elseif (!strncmp('msgstr[', $line, 7)) { - // A message string for a specific plurality. - - if (($context != 'MSGID') && ($context != 'MSGCTXT') && ($context != 'MSGID_PLURAL') && ($context != 'MSGSTR_ARR')) { - // Message strings must come after msgid, msgxtxt, msgid_plural, or other msgstr[] entries. - _locale_import_message('The translation file %filename contains an error: "msgstr[]" is unexpected on line %line.', $file, $lineno); - return FALSE; - } - - // Ensure the plurality is terminated. - if (strpos($line, ']') === FALSE) { - _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno); - return FALSE; - } - - // Extract the plurality. - $frombracket = strstr($line, '['); - $plural = substr($frombracket, 1, strpos($frombracket, ']') - 1); - - // Skip to the next whitespace and trim away any further whitespace, bringing $line to the message data. - $line = trim(strstr($line, " ")); - - $quoted = _locale_import_parse_quoted($line); - if ($quoted === FALSE) { - // The string must be quoted. - _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno); - return FALSE; - } - - $current['msgstr'][$plural] = $quoted; - - $context = 'MSGSTR_ARR'; - } - elseif (!strncmp("msgstr", $line, 6)) { - // A string for the an id or context. - - if (($context != 'MSGID') && ($context != 'MSGCTXT')) { - // Strings are only valid within an id or context scope. - _locale_import_message('The translation file %filename contains an error: "msgstr" is unexpected on line %line.', $file, $lineno); - return FALSE; - } - - // Remove 'msgstr' and trim away away whitespaces. - $line = trim(substr($line, 6)); - // At this point, $line should now contain the message. - - $quoted = _locale_import_parse_quoted($line); - if ($quoted === FALSE) { - // The string must be quoted. - _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno); - return FALSE; - } - - $current['msgstr'] = $quoted; - - $context = 'MSGSTR'; - } - elseif ($line != '') { - // Anything that is not a token may be a continuation of a previous token. - - $quoted = _locale_import_parse_quoted($line); - if ($quoted === FALSE) { - // The string must be quoted. - _locale_import_message('The translation file %filename contains a syntax error on line %line.', $file, $lineno); - return FALSE; - } - - // Append the string to the current context. - if (($context == 'MSGID') || ($context == 'MSGID_PLURAL')) { - $current['msgid'] .= $quoted; - } - elseif ($context == 'MSGCTXT') { - $current['msgctxt'] .= $quoted; - } - elseif ($context == 'MSGSTR') { - $current['msgstr'] .= $quoted; - } - elseif ($context == 'MSGSTR_ARR') { - $current['msgstr'][$plural] .= $quoted; - } - else { - // No valid context to append to. - _locale_import_message('The translation file %filename contains an error: there is an unexpected string on line %line.', $file, $lineno); - return FALSE; - } - } - } - - // End of PO file, closed out the last entry. - if (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) { - _locale_import_one_string($op, $current, $mode, $lang, $file); - } - elseif ($context != 'COMMENT') { - _locale_import_message('The translation file %filename ended unexpectedly at line %line.', $file, $lineno); - return FALSE; - } -} - -/** - * Sets an error message occurred during locale file parsing. - * - * @param $message - * The message to be translated. - * @param $file - * Drupal file object corresponding to the PO file to import. - * @param $lineno - * An optional line number argument. - */ -function _locale_import_message($message, $file, $lineno = NULL) { - $vars = array('%filename' => $file->filename); - if (isset($lineno)) { - $vars['%line'] = $lineno; - } - $t = get_t(); - drupal_set_message($t($message, $vars), 'error'); -} - -/** - * Imports a string into the database - * - * @param $op - * Operation to perform: 'db-store', 'db-report', 'mem-store' or 'mem-report'. - * @param $value - * Details of the string stored. - * @param $mode - * Should existing translations be replaced LOCALE_IMPORT_KEEP or - * LOCALE_IMPORT_OVERWRITE. - * @param $lang - * Language to store the string in. - * @param $file - * Object representation of file being imported, only required when op is - * 'db-store'. - */ -function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NULL, $file = NULL) { - $report = &drupal_static(__FUNCTION__, array('additions' => 0, 'updates' => 0, 'deletes' => 0, 'skips' => 0)); - $header_done = &drupal_static(__FUNCTION__ . ':header_done', FALSE); - $strings = &drupal_static(__FUNCTION__ . ':strings', array()); - - switch ($op) { - // Return stored strings - case 'mem-report': - return $strings; - - // Store string in memory (only supports single strings) - case 'mem-store': - $strings[isset($value['msgctxt']) ? $value['msgctxt'] : ''][$value['msgid']] = $value['msgstr']; - return; - - // Called at end of import to inform the user - case 'db-report': - return array($header_done, $report['additions'], $report['updates'], $report['deletes'], $report['skips']); - - // Store the string we got in the database. - case 'db-store': - // We got header information. - if ($value['msgid'] == '') { - $languages = language_list(); - if (($mode != LOCALE_IMPORT_KEEP) || empty($languages[$lang]->plurals)) { - // Since we only need to parse the header if we ought to update the - // plural formula, only run this if we don't need to keep existing - // data untouched or if we don't have an existing plural formula. - $header = _locale_import_parse_header($value['msgstr']); - - // Get the plural formula and update in database. - if (isset($header["Plural-Forms"]) && $p = _locale_import_parse_plural_forms($header["Plural-Forms"], $file->uri)) { - list($nplurals, $plural) = $p; - db_update('languages') - ->fields(array( - 'plurals' => $nplurals, - 'formula' => $plural, - )) - ->condition('language', $lang) - ->execute(); - } - else { - db_update('languages') - ->fields(array( - 'plurals' => 0, - 'formula' => '', - )) - ->condition('language', $lang) - ->execute(); - } - } - $header_done = TRUE; - } - - else { - // Some real string to import. - $comments = _locale_import_shorten_comments(empty($value['#']) ? array() : $value['#']); - - if (strpos($value['msgid'], "\0")) { - // This string has plural versions. - $english = explode("\0", $value['msgid'], 2); - $entries = array_keys($value['msgstr']); - for ($i = 3; $i <= count($entries); $i++) { - $english[] = $english[1]; - } - $translation = array_map('_locale_import_append_plural', $value['msgstr'], $entries); - $english = array_map('_locale_import_append_plural', $english, $entries); - foreach ($translation as $key => $trans) { - if ($key == 0) { - $plid = 0; - } - $plid = _locale_import_one_string_db($report, $lang, isset($value['msgctxt']) ? $value['msgctxt'] : '', $english[$key], $trans, $comments, $mode, $plid, $key); - } - } - - else { - // A simple string to import. - $english = $value['msgid']; - $translation = $value['msgstr']; - _locale_import_one_string_db($report, $lang, isset($value['msgctxt']) ? $value['msgctxt'] : '', $english, $translation, $comments, $mode); - } - } - } // end of db-store operation -} - -/** - * Import one string into the database. - * - * @param $report - * Report array summarizing the number of changes done in the form: - * array(inserts, updates, deletes). - * @param $langcode - * Language code to import string into. - * @param $context - * The context of this string. - * @param $source - * Source string. - * @param $translation - * Translation to language specified in $langcode. - * @param $location - * Location value to save with source string. - * @param $mode - * Import mode to use, LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE. - * @param $plid - * Optional plural ID to use. - * @param $plural - * Optional plural value to use. - * - * @return - * The string ID of the existing string modified or the new string added. - */ -function _locale_import_one_string_db(&$report, $langcode, $context, $source, $translation, $location, $mode, $plid = 0, $plural = 0) { - $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = :context", array(':source' => $source, ':context' => $context))->fetchField(); - - if (!empty($translation)) { - // Skip this string unless it passes a check for dangerous code. - if (!locale_string_is_safe($translation)) { - watchdog('locale', 'Import of string "%string" was skipped because of disallowed or malformed HTML.', array('%string' => $translation), WATCHDOG_ERROR); - $report['skips']++; - $lid = 0; - } - elseif ($lid) { - // We have this source string saved already. - db_update('locales_source') - ->fields(array( - 'location' => $location, - )) - ->condition('lid', $lid) - ->execute(); - - $exists = db_query("SELECT COUNT(lid) FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $langcode))->fetchField(); - - if (!$exists) { - // No translation in this language. - db_insert('locales_target') - ->fields(array( - 'lid' => $lid, - 'language' => $langcode, - 'translation' => $translation, - 'plid' => $plid, - 'plural' => $plural, - )) - ->execute(); - - $report['additions']++; - } - elseif ($mode == LOCALE_IMPORT_OVERWRITE) { - // Translation exists, only overwrite if instructed. - db_update('locales_target') - ->fields(array( - 'translation' => $translation, - 'plid' => $plid, - 'plural' => $plural, - )) - ->condition('language', $langcode) - ->condition('lid', $lid) - ->execute(); - - $report['updates']++; - } - } - else { - // No such source string in the database yet. - $lid = db_insert('locales_source') - ->fields(array( - 'location' => $location, - 'source' => $source, - 'context' => (string) $context, - )) - ->execute(); - - db_insert('locales_target') - ->fields(array( - 'lid' => $lid, - 'language' => $langcode, - 'translation' => $translation, - 'plid' => $plid, - 'plural' => $plural - )) - ->execute(); - - $report['additions']++; - } - } - elseif ($mode == LOCALE_IMPORT_OVERWRITE) { - // Empty translation, remove existing if instructed. - db_delete('locales_target') - ->condition('language', $langcode) - ->condition('lid', $lid) - ->condition('plid', $plid) - ->condition('plural', $plural) - ->execute(); - - $report['deletes']++; - } - - return $lid; -} - -/** - * Parses a Gettext Portable Object file header - * - * @param $header - * A string containing the complete header. - * - * @return - * An associative array of key-value pairs. - */ -function _locale_import_parse_header($header) { - $header_parsed = array(); - $lines = array_map('trim', explode("\n", $header)); - foreach ($lines as $line) { - if ($line) { - list($tag, $contents) = explode(":", $line, 2); - $header_parsed[trim($tag)] = trim($contents); - } - } - return $header_parsed; -} - -/** - * Parses a Plural-Forms entry from a Gettext Portable Object file header - * - * @param $pluralforms - * A string containing the Plural-Forms entry. - * @param $filepath - * A string containing the filepath. - * - * @return - * An array containing the number of plurals and a - * formula in PHP for computing the plural form. - */ -function _locale_import_parse_plural_forms($pluralforms, $filepath) { - // First, delete all whitespace - $pluralforms = strtr($pluralforms, array(" " => "", "\t" => "")); - - // Select the parts that define nplurals and plural - $nplurals = strstr($pluralforms, "nplurals="); - if (strpos($nplurals, ";")) { - $nplurals = substr($nplurals, 9, strpos($nplurals, ";") - 9); - } - else { - return FALSE; - } - $plural = strstr($pluralforms, "plural="); - if (strpos($plural, ";")) { - $plural = substr($plural, 7, strpos($plural, ";") - 7); - } - else { - return FALSE; - } - - // Get PHP version of the plural formula - $plural = _locale_import_parse_arithmetic($plural); - - if ($plural !== FALSE) { - return array($nplurals, $plural); - } - else { - drupal_set_message(t('The translation file %filepath contains an error: the plural formula could not be parsed.', array('%filepath' => $filepath)), 'error'); - return FALSE; - } -} - -/** - * Parses and sanitizes an arithmetic formula into a PHP expression - * - * While parsing, we ensure, that the operators have the right - * precedence and associativity. - * - * @param $string - * A string containing the arithmetic formula. - * - * @return - * The PHP version of the formula. - */ -function _locale_import_parse_arithmetic($string) { - // Operator precedence table - $precedence = array("(" => -1, ")" => -1, "?" => 1, ":" => 1, "||" => 3, "&&" => 4, "==" => 5, "!=" => 5, "<" => 6, ">" => 6, "<=" => 6, ">=" => 6, "+" => 7, "-" => 7, "*" => 8, "/" => 8, "%" => 8); - // Right associativity - $right_associativity = array("?" => 1, ":" => 1); - - $tokens = _locale_import_tokenize_formula($string); - - // Parse by converting into infix notation then back into postfix - // Operator stack - holds math operators and symbols - $operator_stack = array(); - // Element Stack - holds data to be operated on - $element_stack = array(); - - foreach ($tokens as $token) { - $current_token = $token; - - // Numbers and the $n variable are simply pushed into $element_stack - if (is_numeric($token)) { - $element_stack[] = $current_token; - } - elseif ($current_token == "n") { - $element_stack[] = '$n'; - } - elseif ($current_token == "(") { - $operator_stack[] = $current_token; - } - elseif ($current_token == ")") { - $topop = array_pop($operator_stack); - while (isset($topop) && ($topop != "(")) { - $element_stack[] = $topop; - $topop = array_pop($operator_stack); - } - } - elseif (!empty($precedence[$current_token])) { - // If it's an operator, then pop from $operator_stack into $element_stack until the - // precedence in $operator_stack is less than current, then push into $operator_stack - $topop = array_pop($operator_stack); - while (isset($topop) && ($precedence[$topop] >= $precedence[$current_token]) && !(($precedence[$topop] == $precedence[$current_token]) && !empty($right_associativity[$topop]) && !empty($right_associativity[$current_token]))) { - $element_stack[] = $topop; - $topop = array_pop($operator_stack); - } - if ($topop) { - $operator_stack[] = $topop; // Return element to top - } - $operator_stack[] = $current_token; // Parentheses are not needed - } - else { - return FALSE; - } - } - - // Flush operator stack - $topop = array_pop($operator_stack); - while ($topop != NULL) { - $element_stack[] = $topop; - $topop = array_pop($operator_stack); - } - - // Now extract formula from stack - $previous_size = count($element_stack) + 1; - while (count($element_stack) < $previous_size) { - $previous_size = count($element_stack); - for ($i = 2; $i < count($element_stack); $i++) { - $op = $element_stack[$i]; - if (!empty($precedence[$op])) { - $f = ""; - if ($op == ":") { - $f = $element_stack[$i - 2] . "):" . $element_stack[$i - 1] . ")"; - } - elseif ($op == "?") { - $f = "(" . $element_stack[$i - 2] . "?(" . $element_stack[$i - 1]; - } - else { - $f = "(" . $element_stack[$i - 2] . $op . $element_stack[$i - 1] . ")"; - } - array_splice($element_stack, $i - 2, 3, $f); - break; - } - } - } - - // If only one element is left, the number of operators is appropriate - if (count($element_stack) == 1) { - return $element_stack[0]; - } - else { - return FALSE; - } -} - -/** - * Backward compatible implementation of token_get_all() for formula parsing - * - * @param $string - * A string containing the arithmetic formula. - * - * @return - * The PHP version of the formula. - */ -function _locale_import_tokenize_formula($formula) { - $formula = str_replace(" ", "", $formula); - $tokens = array(); - for ($i = 0; $i < strlen($formula); $i++) { - if (is_numeric($formula[$i])) { - $num = $formula[$i]; - $j = $i + 1; - while ($j < strlen($formula) && is_numeric($formula[$j])) { - $num .= $formula[$j]; - $j++; - } - $i = $j - 1; - $tokens[] = $num; - } - elseif ($pos = strpos(" =<>!&|", $formula[$i])) { // We won't have a space - $next = $formula[$i + 1]; - switch ($pos) { - case 1: - case 2: - case 3: - case 4: - if ($next == '=') { - $tokens[] = $formula[$i] . '='; - $i++; - } - else { - $tokens[] = $formula[$i]; - } - break; - case 5: - if ($next == '&') { - $tokens[] = '&&'; - $i++; - } - else { - $tokens[] = $formula[$i]; - } - break; - case 6: - if ($next == '|') { - $tokens[] = '||'; - $i++; - } - else { - $tokens[] = $formula[$i]; - } - break; - } - } - else { - $tokens[] = $formula[$i]; - } - } - return $tokens; -} - -/** - * Modify a string to contain proper count indices - * - * This is a callback function used via array_map() - * - * @param $entry - * An array element. - * @param $key - * Index of the array element. - */ -function _locale_import_append_plural($entry, $key) { - // No modifications for 0, 1 - if ($key == 0 || $key == 1) { - return $entry; - } - - // First remove any possibly false indices, then add new ones - $entry = preg_replace('/(@count)\[[0-9]\]/', '\\1', $entry); - return preg_replace('/(@count)/', "\\1[$key]", $entry); -} - -/** - * Generate a short, one string version of the passed comment array - * - * @param $comment - * An array of strings containing a comment. - * - * @return - * Short one string version of the comment. - */ -function _locale_import_shorten_comments($comment) { - $comm = ''; - while (count($comment)) { - $test = $comm . substr(array_shift($comment), 1) . ', '; - if (strlen($comm) < 130) { - $comm = $test; - } - else { - break; - } - } - return trim(substr($comm, 0, -2)); -} - -/** - * Parses a string in quotes - * - * @param $string - * A string specified with enclosing quotes. - * - * @return - * The string parsed from inside the quotes. - */ -function _locale_import_parse_quoted($string) { - if (substr($string, 0, 1) != substr($string, -1, 1)) { - return FALSE; // Start and end quotes must be the same - } - $quote = substr($string, 0, 1); - $string = substr($string, 1, -1); - if ($quote == '"') { // Double quotes: strip slashes - return stripcslashes($string); - } - elseif ($quote == "'") { // Simple quote: return as-is - return $string; - } - else { - return FALSE; // Unrecognized quote - } -} - -/** - * Generates a structured array of all strings with translations in - * $language, if given. This array can be used to generate an export - * of the string in the database. - * - * @param $language - * Language object to generate the output for, or NULL if generating - * translation template. - */ -function _locale_export_get_strings($language = NULL) { - if (isset($language)) { - $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.translation, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language ORDER BY t.plid, t.plural", array(':language' => $language->language)); - } - else { - $result = db_query("SELECT s.lid, s.source, s.context, s.location, t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid ORDER BY t.plid, t.plural"); - } - $strings = array(); - foreach ($result as $child) { - $string = array( - 'comment' => $child->location, - 'source' => $child->source, - 'context' => $child->context, - 'translation' => isset($child->translation) ? $child->translation : '', - ); - if ($child->plid) { - // Has a parent lid. Since we process in the order of plids, - // we already have the parent in the array, so we can add the - // lid to the next plural version to it. This builds a linked - // list of plurals. - $string['child'] = TRUE; - $strings[$child->plid]['plural'] = $child->lid; - } - $strings[$child->lid] = $string; - } - return $strings; -} - -/** - * Generates the PO(T) file contents for given strings. - * - * @param $language - * Language object to generate the output for, or NULL if generating - * translation template. - * @param $strings - * Array of strings to export. See _locale_export_get_strings() - * on how it should be formatted. - * @param $header - * The header portion to use for the output file. Defaults - * are provided for PO and POT files. - */ -function _locale_export_po_generate($language = NULL, $strings = array(), $header = NULL) { - global $user; - - if (!isset($header)) { - if (isset($language)) { - $header = '# ' . $language->name . ' translation of ' . variable_get('site_name', 'Drupal') . "\n"; - $header .= '# Generated by ' . $user->name . ' <' . $user->mail . ">\n"; - $header .= "#\n"; - $header .= "msgid \"\"\n"; - $header .= "msgstr \"\"\n"; - $header .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n"; - $header .= "\"POT-Creation-Date: " . date("Y-m-d H:iO") . "\\n\"\n"; - $header .= "\"PO-Revision-Date: " . date("Y-m-d H:iO") . "\\n\"\n"; - $header .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n"; - $header .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n"; - $header .= "\"MIME-Version: 1.0\\n\"\n"; - $header .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n"; - $header .= "\"Content-Transfer-Encoding: 8bit\\n\"\n"; - if ($language->formula && $language->plurals) { - $header .= "\"Plural-Forms: nplurals=" . $language->plurals . "; plural=" . strtr($language->formula, array('$' => '')) . ";\\n\"\n"; - } - } - else { - $header = "# LANGUAGE translation of PROJECT\n"; - $header .= "# Copyright (c) YEAR NAME <EMAIL@ADDRESS>\n"; - $header .= "#\n"; - $header .= "msgid \"\"\n"; - $header .= "msgstr \"\"\n"; - $header .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n"; - $header .= "\"POT-Creation-Date: " . date("Y-m-d H:iO") . "\\n\"\n"; - $header .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n"; - $header .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n"; - $header .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n"; - $header .= "\"MIME-Version: 1.0\\n\"\n"; - $header .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n"; - $header .= "\"Content-Transfer-Encoding: 8bit\\n\"\n"; - $header .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n"; - } - } - - $output = $header . "\n"; - - foreach ($strings as $lid => $string) { - // Only process non-children, children are output below their parent. - if (!isset($string['child'])) { - if ($string['comment']) { - $output .= '#: ' . $string['comment'] . "\n"; - } - if (!empty($string['context'])) { - $output .= 'msgctxt ' . _locale_export_string($string['context']); - } - $output .= 'msgid ' . _locale_export_string($string['source']); - if (!empty($string['plural'])) { - $plural = $string['plural']; - $output .= 'msgid_plural ' . _locale_export_string($strings[$plural]['source']); - if (isset($language)) { - $translation = $string['translation']; - for ($i = 0; $i < $language->plurals; $i++) { - $output .= 'msgstr[' . $i . '] ' . _locale_export_string($translation); - if ($plural) { - $translation = _locale_export_remove_plural($strings[$plural]['translation']); - $plural = isset($strings[$plural]['plural']) ? $strings[$plural]['plural'] : 0; - } - else { - $translation = ''; - } - } - } - else { - $output .= 'msgstr[0] ""' . "\n"; - $output .= 'msgstr[1] ""' . "\n"; - } - } - else { - $output .= 'msgstr ' . _locale_export_string($string['translation']); - } - $output .= "\n"; - } - } - return $output; -} - -/** - * Write a generated PO or POT file to the output. - * - * @param $language - * Language object to generate the output for, or NULL if generating - * translation template. - * @param $output - * The PO(T) file to output as a string. See _locale_export_generate_po() - * on how it can be generated. - */ -function _locale_export_po($language = NULL, $output = NULL) { - // Log the export event. - if (isset($language)) { - $filename = $language->language . '.po'; - watchdog('locale', 'Exported %locale translation file: %filename.', array('%locale' => $language->name, '%filename' => $filename)); - } - else { - $filename = 'drupal.pot'; - watchdog('locale', 'Exported translation file: %filename.', array('%filename' => $filename)); - } - // Download the file for the client. - header("Content-Disposition: attachment; filename=$filename"); - header("Content-Type: text/plain; charset=utf-8"); - print $output; - drupal_exit(); -} - -/** - * Print out a string on multiple lines - */ -function _locale_export_string($str) { - $stri = addcslashes($str, "\0..\37\\\""); - $parts = array(); - - // Cut text into several lines - while ($stri != "") { - $i = strpos($stri, "\\n"); - if ($i === FALSE) { - $curstr = $stri; - $stri = ""; - } - else { - $curstr = substr($stri, 0, $i + 2); - $stri = substr($stri, $i + 2); - } - $curparts = explode("\n", _locale_export_wrap($curstr, 70)); - $parts = array_merge($parts, $curparts); - } - - // Multiline string - if (count($parts) > 1) { - return "\"\"\n\"" . implode("\"\n\"", $parts) . "\"\n"; - } - // Single line string - elseif (count($parts) == 1) { - return "\"$parts[0]\"\n"; - } - // No translation - else { - return "\"\"\n"; - } -} - -/** - * Custom word wrapping for Portable Object (Template) files. - */ -function _locale_export_wrap($str, $len) { - $words = explode(' ', $str); - $return = array(); - - $cur = ""; - $nstr = 1; - while (count($words)) { - $word = array_shift($words); - if ($nstr) { - $cur = $word; - $nstr = 0; - } - elseif (strlen("$cur $word") > $len) { - $return[] = $cur . " "; - $cur = $word; - } - else { - $cur = "$cur $word"; - } - } - $return[] = $cur; - - return implode("\n", $return); -} - -/** - * Removes plural index information from a string - */ -function _locale_export_remove_plural($entry) { - return preg_replace('/(@count)\[[0-9]\]/', '\\1', $entry); -} -/** - * @} End of "locale-api-import-export" - */ diff --git a/core/includes/graph.inc b/core/includes/graph.inc deleted file mode 100644 index 416fad6df07..00000000000 --- a/core/includes/graph.inc +++ /dev/null @@ -1,146 +0,0 @@ -<?php - -/** - * @file - * Directed acyclic graph functions. - */ - - -/** - * Perform a depth first sort on a directed acyclic graph. - * - * @param $graph - * A three dimensional associated array, with the first keys being the names - * of the vertices, these can be strings or numbers. The second key is - * 'edges' and the third one are again vertices, each such key representing - * an edge. Values of array elements are copied over. - * - * Example: - * @code - * $graph[1]['edges'][2] = 1; - * $graph[2]['edges'][3] = 1; - * $graph[2]['edges'][4] = 1; - * $graph[3]['edges'][4] = 1; - * @endcode - * - * On return you will also have: - * @code - * $graph[1]['paths'][2] = 1; - * $graph[1]['paths'][3] = 1; - * $graph[2]['reverse_paths'][1] = 1; - * $graph[3]['reverse_paths'][1] = 1; - * @endcode - * - * @return - * The passed-in $graph with more secondary keys filled in: - * - 'paths': Contains a list of vertices than can be reached on a path from - * this vertex. - * - 'reverse_paths': Contains a list of vertices that has a path from them - * to this vertex. - * - 'weight': If there is a path from a vertex to another then the weight of - * the latter is higher. - * - 'component': Vertices in the same component have the same component - * identifier. - * - * @see _drupal_depth_first_search() - */ -function drupal_depth_first_search(&$graph) { - $state = array( - // The order of last visit of the depth first search. This is the reverse - // of the topological order if the graph is acyclic. - 'last_visit_order' => array(), - // The components of the graph. - 'components' => array(), - ); - // Perform the actual sort. - foreach ($graph as $start => $data) { - _drupal_depth_first_search($graph, $state, $start); - } - - // We do such a numbering that every component starts with 0. This is useful - // for module installs as we can install every 0 weighted module in one - // request, and then every 1 weighted etc. - $component_weights = array(); - - foreach ($state['last_visit_order'] as $vertex) { - $component = $graph[$vertex]['component']; - if (!isset($component_weights[$component])) { - $component_weights[$component] = 0; - } - $graph[$vertex]['weight'] = $component_weights[$component]--; - } -} - -/** - * Helper function to perform a depth first sort. - * - * @param $graph - * A three dimensional associated graph array. - * @param $state - * An associative array. The key 'last_visit_order' stores a list of the - * vertices visited. The key components stores list of vertices belonging - * to the same the component. - * @param $start - * An arbitrary vertex where we started traversing the graph. - * @param $component - * The component of the last vertex. - * - * @see drupal_depth_first_search() - */ -function _drupal_depth_first_search(&$graph, &$state, $start, &$component = NULL) { - // Assign new component for each new vertex, i.e. when not called recursively. - if (!isset($component)) { - $component = $start; - } - // Nothing to do, if we already visited this vertex. - if (isset($graph[$start]['paths'])) { - return; - } - // Mark $start as visited. - $graph[$start]['paths'] = array(); - - // Assign $start to the current component. - $graph[$start]['component'] = $component; - $state['components'][$component][] = $start; - - // Visit edges of $start. - if (isset($graph[$start]['edges'])) { - foreach ($graph[$start]['edges'] as $end => $v) { - // Mark that $start can reach $end. - $graph[$start]['paths'][$end] = $v; - - if (isset($graph[$end]['component']) && $component != $graph[$end]['component']) { - // This vertex already has a component, use that from now on and - // reassign all the previously explored vertices. - $new_component = $graph[$end]['component']; - foreach ($state['components'][$component] as $vertex) { - $graph[$vertex]['component'] = $new_component; - $state['components'][$new_component][] = $vertex; - } - unset($state['components'][$component]); - $component = $new_component; - } - // Only visit existing vertices. - if (isset($graph[$end])) { - // Visit the connected vertex. - _drupal_depth_first_search($graph, $state, $end, $component); - - // All vertices reachable by $end are also reachable by $start. - $graph[$start]['paths'] += $graph[$end]['paths']; - } - } - } - - // Now that any other subgraph has been explored, add $start to all reverse - // paths. - foreach ($graph[$start]['paths'] as $end => $v) { - if (isset($graph[$end])) { - $graph[$end]['reverse_paths'][$start] = $v; - } - } - - // Record the order of the last visit. This is the reverse of the - // topological order if the graph is acyclic. - $state['last_visit_order'][] = $start; -} - diff --git a/core/includes/image.inc b/core/includes/image.inc deleted file mode 100644 index 8dc36b995bc..00000000000 --- a/core/includes/image.inc +++ /dev/null @@ -1,442 +0,0 @@ -<?php - -/** - * @file - * API for manipulating images. - */ - -/** - * @defgroup image Image toolkits - * @{ - * Functions for image file manipulations. - * - * Drupal's image toolkits provide an abstraction layer for common image file - * manipulations like scaling, cropping, and rotating. The abstraction frees - * module authors from the need to support multiple image libraries, and it - * allows site administrators to choose the library that's best for them. - * - * PHP includes the GD library by default so a GD toolkit is installed with - * Drupal. Other toolkits like ImageMagick are available from contrib modules. - * GD works well for small images, but using it with larger files may cause PHP - * to run out of memory. In contrast the ImageMagick library does not suffer - * from this problem, but it requires the ISP to have installed additional - * software. - * - * Image toolkits are discovered based on the associated module's - * hook_image_toolkits. Additionally the image toolkit include file - * must be identified in the files array in the module.info file. The - * toolkit must then be enabled using the admin/config/media/image-toolkit - * form. - * - * Only one toolkit may be selected at a time. If a module author wishes to call - * a specific toolkit they can check that it is installed by calling - * image_get_available_toolkits(), and then calling its functions directly. - */ - -/** - * Return a list of available toolkits. - * - * @return - * An array with the toolkit names as keys and the descriptions as values. - */ -function image_get_available_toolkits() { - // hook_image_toolkits returns an array of toolkit names. - $toolkits = module_invoke_all('image_toolkits'); - - $output = array(); - foreach ($toolkits as $name => $info) { - // Only allow modules that aren't marked as unavailable. - if ($info['available']) { - $output[$name] = $info['title']; - } - } - - return $output; -} - -/** - * Retrieve the name of the currently used toolkit. - * - * @return - * String containing the name of the selected toolkit, or FALSE on error. - */ -function image_get_toolkit() { - static $toolkit; - - if (!isset($toolkit)) { - $toolkits = image_get_available_toolkits(); - $toolkit = variable_get('image_toolkit', 'gd'); - if (!isset($toolkits[$toolkit]) || !function_exists('image_' . $toolkit . '_load')) { - // The selected toolkit isn't available so return the first one found. If - // none are available this will return FALSE. - reset($toolkits); - $toolkit = key($toolkits); - } - } - - return $toolkit; -} - -/** - * Invokes the given method using the currently selected toolkit. - * - * @param $method - * A string containing the method to invoke. - * @param $image - * An image object returned by image_load(). - * @param $params - * An optional array of parameters to pass to the toolkit method. - * - * @return - * Mixed values (typically Boolean indicating successful operation). - */ -function image_toolkit_invoke($method, stdClass $image, array $params = array()) { - $function = 'image_' . $image->toolkit . '_' . $method; - if (function_exists($function)) { - array_unshift($params, $image); - return call_user_func_array($function, $params); - } - watchdog('image', 'The selected image handling toolkit %toolkit can not correctly process %function.', array('%toolkit' => $image->toolkit, '%function' => $function), WATCHDOG_ERROR); - return FALSE; -} - -/** - * Get details about an image. - * - * Drupal supports GIF, JPG and PNG file formats when used with the GD - * toolkit, and may support others, depending on which toolkits are - * installed. - * - * @param $filepath - * String specifying the path of the image file. - * @param $toolkit - * An optional image toolkit name to override the default. - * - * @return - * FALSE, if the file could not be found or is not an image. Otherwise, a - * keyed array containing information about the image: - * - "width": Width, in pixels. - * - "height": Height, in pixels. - * - "extension": Commonly used file extension for the image. - * - "mime_type": MIME type ('image/jpeg', 'image/gif', 'image/png'). - * - "file_size": File size in bytes. - */ -function image_get_info($filepath, $toolkit = FALSE) { - $details = FALSE; - if (!is_file($filepath) && !is_uploaded_file($filepath)) { - return $details; - } - - if (!$toolkit) { - $toolkit = image_get_toolkit(); - } - if ($toolkit) { - $image = new stdClass(); - $image->source = $filepath; - $image->toolkit = $toolkit; - $details = image_toolkit_invoke('get_info', $image); - if (isset($details) && is_array($details)) { - $details['file_size'] = filesize($filepath); - } - } - - return $details; -} - -/** - * Scales an image to the exact width and height given. - * - * This function achieves the target aspect ratio by cropping the original image - * equally on both sides, or equally on the top and bottom. This function is - * useful to create uniform sized avatars from larger images. - * - * The resulting image always has the exact target dimensions. - * - * @param $image - * An image object returned by image_load(). - * @param $width - * The target width, in pixels. - * @param $height - * The target height, in pixels. - * - * @return - * TRUE on success, FALSE on failure. - * - * @see image_load() - * @see image_resize() - * @see image_crop() - */ -function image_scale_and_crop(stdClass $image, $width, $height) { - $scale = max($width / $image->info['width'], $height / $image->info['height']); - $x = ($image->info['width'] * $scale - $width) / 2; - $y = ($image->info['height'] * $scale - $height) / 2; - - if (image_resize($image, $image->info['width'] * $scale, $image->info['height'] * $scale)) { - return image_crop($image, $x, $y, $width, $height); - } - return FALSE; -} - -/** - * Scales image dimensions while maintaining aspect ratio. - * - * The resulting dimensions can be smaller for one or both target dimensions. - * - * @param $dimensions - * Dimensions to be modified - an array with components width and height, in - * pixels. - * @param $width - * The target width, in pixels. This value is omitted then the scaling will - * based only on the height value. - * @param $height - * The target height, in pixels. This value is omitted then the scaling will - * based only on the width value. - * @param $upscale - * Boolean indicating that files smaller than the dimensions will be scaled - * up. This generally results in a low quality image. - * - * @return - * TRUE if $dimensions was modified, FALSE otherwise. - * - * @see image_scale() - */ -function image_dimensions_scale(array &$dimensions, $width = NULL, $height = NULL, $upscale = FALSE) { - $aspect = $dimensions['height'] / $dimensions['width']; - - if ($upscale) { - // Set width/height according to aspect ratio if either is empty. - $width = !empty($width) ? $width : $height / $aspect; - $height = !empty($height) ? $height : $width / $aspect; - } - else { - // Set impossibly large values if the width and height aren't set. - $width = !empty($width) ? $width : 9999999; - $height = !empty($height) ? $height : 9999999; - - // Don't scale up. - if (round($width) >= $dimensions['width'] && round($height) >= $dimensions['height']) { - return FALSE; - } - } - - if ($aspect < $height / $width) { - $dimensions['width'] = $width; - $dimensions['height'] = (int) round($width * $aspect); - } - else { - $dimensions['width'] = (int) round($height / $aspect); - $dimensions['height'] = $height; - } - - return TRUE; -} - -/** - * Scales an image while maintaining aspect ratio. - * - * The resulting image can be smaller for one or both target dimensions. - * - * @param $image - * An image object returned by image_load(). - * @param $width - * The target width, in pixels. This value is omitted then the scaling will - * based only on the height value. - * @param $height - * The target height, in pixels. This value is omitted then the scaling will - * based only on the width value. - * @param $upscale - * Boolean indicating that files smaller than the dimensions will be scaled - * up. This generally results in a low quality image. - * - * @return - * TRUE on success, FALSE on failure. - * - * @see image_dimensions_scale() - * @see image_load() - * @see image_scale_and_crop() - */ -function image_scale(stdClass $image, $width = NULL, $height = NULL, $upscale = FALSE) { - $dimensions = $image->info; - - // Scale the dimensions - if they don't change then just return success. - if (!image_dimensions_scale($dimensions, $width, $height, $upscale)) { - return TRUE; - } - - return image_resize($image, $dimensions['width'], $dimensions['height']); -} - -/** - * Resize an image to the given dimensions (ignoring aspect ratio). - * - * @param $image - * An image object returned by image_load(). - * @param $width - * The target width, in pixels. - * @param $height - * The target height, in pixels. - * - * @return - * TRUE on success, FALSE on failure. - * - * @see image_load() - * @see image_gd_resize() - */ -function image_resize(stdClass $image, $width, $height) { - $width = (int) round($width); - $height = (int) round($height); - - return image_toolkit_invoke('resize', $image, array($width, $height)); -} - -/** - * Rotate an image by the given number of degrees. - * - * @param $image - * An image object returned by image_load(). - * @param $degrees - * The number of (clockwise) degrees to rotate the image. - * @param $background - * An hexadecimal integer specifying the background color to use for the - * uncovered area of the image after the rotation. E.g. 0x000000 for black, - * 0xff00ff for magenta, and 0xffffff for white. For images that support - * transparency, this will default to transparent. Otherwise it will - * be white. - * - * @return - * TRUE on success, FALSE on failure. - * - * @see image_load() - * @see image_gd_rotate() - */ -function image_rotate(stdClass $image, $degrees, $background = NULL) { - return image_toolkit_invoke('rotate', $image, array($degrees, $background)); -} - -/** - * Crop an image to the rectangle specified by the given rectangle. - * - * @param $image - * An image object returned by image_load(). - * @param $x - * The top left coordinate, in pixels, of the crop area (x axis value). - * @param $y - * The top left coordinate, in pixels, of the crop area (y axis value). - * @param $width - * The target width, in pixels. - * @param $height - * The target height, in pixels. - * - * @return - * TRUE on success, FALSE on failure. - * - * @see image_load() - * @see image_scale_and_crop() - * @see image_gd_crop() - */ -function image_crop(stdClass $image, $x, $y, $width, $height) { - $aspect = $image->info['height'] / $image->info['width']; - if (empty($height)) $height = $width / $aspect; - if (empty($width)) $width = $height * $aspect; - - $width = (int) round($width); - $height = (int) round($height); - - return image_toolkit_invoke('crop', $image, array($x, $y, $width, $height)); -} - -/** - * Convert an image to grayscale. - * - * @param $image - * An image object returned by image_load(). - * - * @return - * TRUE on success, FALSE on failure. - * - * @see image_load() - * @see image_gd_desaturate() - */ -function image_desaturate(stdClass $image) { - return image_toolkit_invoke('desaturate', $image); -} - - -/** - * Load an image file and return an image object. - * - * Any changes to the file are not saved until image_save() is called. - * - * @param $file - * Path to an image file. - * @param $toolkit - * An optional, image toolkit name to override the default. - * - * @return - * An image object or FALSE if there was a problem loading the file. The - * image object has the following properties: - * - 'source' - The original file path. - * - 'info' - The array of information returned by image_get_info() - * - 'toolkit' - The name of the image toolkit requested when the image was - * loaded. - * Image toolkits may add additional properties. The caller is advised not to - * monkey about with them. - * - * @see image_save() - * @see image_get_info() - * @see image_get_available_toolkits() - * @see image_gd_load() - */ -function image_load($file, $toolkit = FALSE) { - if (!$toolkit) { - $toolkit = image_get_toolkit(); - } - if ($toolkit) { - $image = new stdClass(); - $image->source = $file; - $image->info = image_get_info($file, $toolkit); - if (isset($image->info) && is_array($image->info)) { - $image->toolkit = $toolkit; - if (image_toolkit_invoke('load', $image)) { - return $image; - } - } - } - return FALSE; -} - -/** - * Close the image and save the changes to a file. - * - * @param $image - * An image object returned by image_load(). The object's 'info' property - * will be updated if the file is saved successfully. - * @param $destination - * Destination path where the image should be saved. If it is empty the - * original image file will be overwritten. - * - * @return - * TRUE on success, FALSE on failure. - * - * @see image_load() - * @see image_gd_save() - */ -function image_save(stdClass $image, $destination = NULL) { - if (empty($destination)) { - $destination = $image->source; - } - if ($return = image_toolkit_invoke('save', $image, array($destination))) { - // Clear the cached file size and refresh the image information. - clearstatcache(); - $image->info = image_get_info($destination, $image->toolkit); - - if (drupal_chmod($destination)) { - return $return; - } - } - return FALSE; -} - -/** - * @} End of "defgroup image". - */ diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc deleted file mode 100644 index 66426fbf888..00000000000 --- a/core/includes/install.core.inc +++ /dev/null @@ -1,1859 +0,0 @@ -<?php - -/** - * @file - * API functions for installing Drupal. - */ - -/** - * Global flag to indicate that a task should not be run during the current - * installation request. - * - * This can be used to skip running an installation task when certain - * conditions are met, even though the task may still show on the list of - * installation tasks presented to the user. For example, the Drupal installer - * uses this flag to skip over the database configuration form when valid - * database connection information is already available from settings.php. It - * also uses this flag to skip language import tasks when the installation is - * being performed in English. - */ -define('INSTALL_TASK_SKIP', 1); - -/** - * Global flag to indicate that a task should be run on each installation - * request that reaches it. - * - * This is primarily used by the Drupal installer for bootstrap-related tasks. - */ -define('INSTALL_TASK_RUN_IF_REACHED', 2); - -/** - * Global flag to indicate that a task should be run on each installation - * request that reaches it, until the database is set up and we are able to - * record the fact that it already ran. - * - * This is the default method for running tasks and should be used for most - * tasks that occur after the database is set up; these tasks will then run - * once and be marked complete once they are successfully finished. For - * example, the Drupal installer uses this flag for the batch installation of - * modules on the new site, and also for the configuration form that collects - * basic site information and sets up the site maintenance account. - */ -define('INSTALL_TASK_RUN_IF_NOT_COMPLETED', 3); - -/** - * Installs Drupal either interactively or via an array of passed-in settings. - * - * The Drupal installation happens in a series of steps, which may be spread - * out over multiple page requests. Each request begins by trying to determine - * the last completed installation step (also known as a "task"), if one is - * available from a previous request. Control is then passed to the task - * handler, which processes the remaining tasks that need to be run until (a) - * an error is thrown, (b) a new page needs to be displayed, or (c) the - * installation finishes (whichever happens first). - * - * @param $settings - * An optional array of installation settings. Leave this empty for a normal, - * interactive, browser-based installation intended to occur over multiple - * page requests. Alternatively, if an array of settings is passed in, the - * installer will attempt to use it to perform the installation in a single - * page request (optimized for the command line) and not send any output - * intended for the web browser. See install_state_defaults() for a list of - * elements that are allowed to appear in this array. - * - * @see install_state_defaults() - */ -function install_drupal($settings = array()) { - global $install_state; - // Initialize the installation state with the settings that were passed in, - // as well as a boolean indicating whether or not this is an interactive - // installation. - $interactive = empty($settings); - $install_state = $settings + array('interactive' => $interactive) + install_state_defaults(); - try { - // Begin the page request. This adds information about the current state of - // the Drupal installation to the passed-in array. - install_begin_request($install_state); - // Based on the installation state, run the remaining tasks for this page - // request, and collect any output. - $output = install_run_tasks($install_state); - } - catch (Exception $e) { - // When an installation error occurs, either send the error to the web - // browser or pass on the exception so the calling script can use it. - if ($install_state['interactive']) { - install_display_output($e->getMessage(), $install_state); - } - else { - throw $e; - } - } - // All available tasks for this page request are now complete. Interactive - // installations can send output to the browser or redirect the user to the - // next page. - if ($install_state['interactive']) { - if ($install_state['parameters_changed']) { - // Redirect to the correct page if the URL parameters have changed. - install_goto(install_redirect_url($install_state)); - } - elseif (isset($output)) { - // Display a page only if some output is available. Otherwise it is - // possible that we are printing a JSON page and theme output should - // not be shown. - install_display_output($output, $install_state); - } - } -} - -/** - * Returns an array of default settings for the global installation state. - * - * The installation state is initialized with these settings at the beginning - * of each page request. They may evolve during the page request, but they are - * initialized again once the next request begins. - * - * Non-interactive Drupal installations can override some of these default - * settings by passing in an array to the installation script, most notably - * 'parameters' (which contains one-time parameters such as 'profile' and - * 'locale' that are normally passed in via the URL) and 'forms' (which can - * be used to programmatically submit forms during the installation; the keys - * of each element indicate the name of the installation task that the form - * submission is for, and the values are used as the $form_state['values'] - * array that is passed on to the form submission via drupal_form_submit()). - * - * @see drupal_form_submit() - */ -function install_state_defaults() { - $defaults = array( - // The current task being processed. - 'active_task' => NULL, - // The last task that was completed during the previous installation - // request. - 'completed_task' => NULL, - // This becomes TRUE only when Drupal's system module is installed. - 'database_tables_exist' => FALSE, - // An array of forms to be programmatically submitted during the - // installation. The keys of each element indicate the name of the - // installation task that the form submission is for, and the values are - // used as the $form_state['values'] array that is passed on to the form - // submission via drupal_form_submit(). - 'forms' => array(), - // This becomes TRUE only at the end of the installation process, after - // all available tasks have been completed and Drupal is fully installed. - // It is used by the installer to store correct information in the database - // about the completed installation, as well as to inform theme functions - // that all tasks are finished (so that the task list can be displayed - // correctly). - 'installation_finished' => FALSE, - // Whether or not this installation is interactive. By default this will - // be set to FALSE if settings are passed in to install_drupal(). - 'interactive' => TRUE, - // An array of available languages for the installation. - 'locales' => array(), - // An array of parameters for the installation, pre-populated by the URL - // or by the settings passed in to install_drupal(). This is primarily - // used to store 'profile' (the name of the chosen installation profile) - // and 'locale' (the name of the chosen installation language), since - // these settings need to persist from page request to page request before - // the database is available for storage. - 'parameters' => array(), - // Whether or not the parameters have changed during the current page - // request. For interactive installations, this will trigger a page - // redirect. - 'parameters_changed' => FALSE, - // An array of information about the chosen installation profile. This will - // be filled in based on the profile's .info file. - 'profile_info' => array(), - // An array of available installation profiles. - 'profiles' => array(), - // An array of server variables that will be substituted into the global - // $_SERVER array via drupal_override_server_variables(). Used by - // non-interactive installations only. - 'server' => array(), - // This becomes TRUE only when a valid database connection can be - // established. - 'settings_verified' => FALSE, - // Installation tasks can set this to TRUE to force the page request to - // end (even if there is no themable output), in the case of an interactive - // installation. This is needed only rarely; for example, it would be used - // by an installation task that prints JSON output rather than returning a - // themed page. The most common example of this is during batch processing, - // but the Drupal installer automatically takes care of setting this - // parameter properly in that case, so that individual installation tasks - // which implement the batch API do not need to set it themselves. - 'stop_page_request' => FALSE, - // Installation tasks can set this to TRUE to indicate that the task should - // be run again, even if it normally wouldn't be. This can be used, for - // example, if a single task needs to be spread out over multiple page - // requests, or if it needs to perform some validation before allowing - // itself to be marked complete. The most common examples of this are batch - // processing and form submissions, but the Drupal installer automatically - // takes care of setting this parameter properly in those cases, so that - // individual installation tasks which implement the batch API or form API - // do not need to set it themselves. - 'task_not_complete' => FALSE, - // A list of installation tasks which have already been performed during - // the current page request. - 'tasks_performed' => array(), - ); - return $defaults; -} - -/** - * Begin an installation request, modifying the installation state as needed. - * - * This function performs commands that must run at the beginning of every page - * request. It throws an exception if the installation should not proceed. - * - * @param $install_state - * An array of information about the current installation state. This is - * modified with information gleaned from the beginning of the page request. - */ -function install_begin_request(&$install_state) { - // Add any installation parameters passed in via the URL. - $install_state['parameters'] += $_GET; - - // Validate certain core settings that are used throughout the installation. - if (!empty($install_state['parameters']['profile'])) { - $install_state['parameters']['profile'] = preg_replace('/[^a-zA-Z_0-9]/', '', $install_state['parameters']['profile']); - } - if (!empty($install_state['parameters']['locale'])) { - $install_state['parameters']['locale'] = preg_replace('/[^a-zA-Z_0-9\-]/', '', $install_state['parameters']['locale']); - } - - // Allow command line scripts to override server variables used by Drupal. - require_once DRUPAL_ROOT . '/core/includes/bootstrap.inc'; - if (!$install_state['interactive']) { - drupal_override_server_variables($install_state['server']); - } - - // 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 no - // installation be permitted using such a prefix. - if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], "simpletest") !== FALSE) { - header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden'); - exit; - } - - drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION); - - // This must go after drupal_bootstrap(), which unsets globals! - global $conf; - - require_once DRUPAL_ROOT . '/core/modules/system/system.install'; - require_once DRUPAL_ROOT . '/core/includes/common.inc'; - require_once DRUPAL_ROOT . '/core/includes/file.inc'; - require_once DRUPAL_ROOT . '/core/includes/install.inc'; - require_once DRUPAL_ROOT . '/' . variable_get('path_inc', 'core/includes/path.inc'); - - // Load module basics (needed for hook invokes). - include_once DRUPAL_ROOT . '/core/includes/module.inc'; - include_once DRUPAL_ROOT . '/core/includes/session.inc'; - - // Set up $language, so t() caller functions will still work. - drupal_language_initialize(); - - require_once DRUPAL_ROOT . '/core/includes/ajax.inc'; - $module_list['system']['filename'] = 'core/modules/system/system.module'; - $module_list['entity']['filename'] = 'core/modules/entity/entity.module'; - $module_list['user']['filename'] = 'core/modules/user/user.module'; - module_list(TRUE, FALSE, FALSE, $module_list); - drupal_load('module', 'system'); - drupal_load('module', 'entity'); - drupal_load('module', 'user'); - - // Load the cache infrastructure using a "fake" cache implementation that - // does not attempt to write to the database. We need this during the initial - // part of the installer because the database is not available yet. We - // continue to use it even when the database does become available, in order - // to preserve consistency between interactive and command-line installations - // (the latter complete in one page request and therefore are forced to - // continue using the cache implementation they started with) and also - // because any data put in the cache during the installer is inherently - // suspect, due to the fact that Drupal is not fully set up yet. - require_once DRUPAL_ROOT . '/core/includes/cache.inc'; - require_once DRUPAL_ROOT . '/core/includes/cache-install.inc'; - $conf['cache_default_class'] = 'DrupalFakeCache'; - - // Prepare for themed output. We need to run this at the beginning of the - // page request to avoid a different theme accidentally getting set. (We also - // need to run it even in the case of command-line installations, to prevent - // any code in the installer that happens to initialize the theme system from - // accessing the database before it is set up yet.) - drupal_maintenance_theme(); - - // Check existing settings.php. - $install_state['settings_verified'] = install_verify_settings(); - - if ($install_state['settings_verified']) { - // Initialize the database system. Note that the connection - // won't be initialized until it is actually requested. - require_once DRUPAL_ROOT . '/core/includes/database/database.inc'; - - // Verify the last completed task in the database, if there is one. - $task = install_verify_completed_task(); - } - else { - $task = NULL; - - // Since previous versions of Drupal stored database connection information - // in the 'db_url' variable, we should never let an installation proceed if - // this variable is defined and the settings file was not verified above - // (otherwise we risk installing over an existing site whose settings file - // has not yet been updated). - if (!empty($GLOBALS['db_url'])) { - throw new Exception(install_already_done_error()); - } - } - - // Modify the installation state as appropriate. - $install_state['completed_task'] = $task; - $install_state['database_tables_exist'] = !empty($task); -} - -/** - * Runs all tasks for the current installation request. - * - * In the case of an interactive installation, all tasks will be attempted - * until one is reached that has output which needs to be displayed to the - * user, or until a page redirect is required. Otherwise, tasks will be - * attempted until the installation is finished. - * - * @param $install_state - * An array of information about the current installation state. This is - * passed along to each task, so it can be modified if necessary. - * - * @return - * HTML output from the last completed task. - */ -function install_run_tasks(&$install_state) { - do { - // Obtain a list of tasks to perform. The list of tasks itself can be - // dynamic (e.g., some might be defined by the installation profile, - // which is not necessarily known until the earlier tasks have run), - // so we regenerate the remaining tasks based on the installation state, - // each time through the loop. - $tasks_to_perform = install_tasks_to_perform($install_state); - // Run the first task on the list. - reset($tasks_to_perform); - $task_name = key($tasks_to_perform); - $task = array_shift($tasks_to_perform); - $install_state['active_task'] = $task_name; - $original_parameters = $install_state['parameters']; - $output = install_run_task($task, $install_state); - $install_state['parameters_changed'] = ($install_state['parameters'] != $original_parameters); - // Store this task as having been performed during the current request, - // and save it to the database as completed, if we need to and if the - // database is in a state that allows us to do so. Also mark the - // installation as 'done' when we have run out of tasks. - if (!$install_state['task_not_complete']) { - $install_state['tasks_performed'][] = $task_name; - $install_state['installation_finished'] = empty($tasks_to_perform); - if ($install_state['database_tables_exist'] && ($task['run'] == INSTALL_TASK_RUN_IF_NOT_COMPLETED || $install_state['installation_finished'])) { - variable_set('install_task', $install_state['installation_finished'] ? 'done' : $task_name); - } - } - // Stop when there are no tasks left. In the case of an interactive - // installation, also stop if we have some output to send to the browser, - // the URL parameters have changed, or an end to the page request was - // specifically called for. - $finished = empty($tasks_to_perform) || ($install_state['interactive'] && (isset($output) || $install_state['parameters_changed'] || $install_state['stop_page_request'])); - } while (!$finished); - return $output; -} - -/** - * Runs an individual installation task. - * - * @param $task - * An array of information about the task to be run. - * @param $install_state - * An array of information about the current installation state. This is - * passed in by reference so that it can be modified by the task. - * - * @return - * The output of the task function, if there is any. - */ -function install_run_task($task, &$install_state) { - $function = $task['function']; - - if ($task['type'] == 'form') { - require_once DRUPAL_ROOT . '/core/includes/form.inc'; - if ($install_state['interactive']) { - // For interactive forms, build the form and ensure that it will not - // redirect, since the installer handles its own redirection only after - // marking the form submission task complete. - $form_state = array( - // We need to pass $install_state by reference in order for forms to - // modify it, since the form API will use it in call_user_func_array(), - // which requires that referenced variables be passed explicitly. - 'build_info' => array('args' => array(&$install_state)), - 'no_redirect' => TRUE, - ); - $form = drupal_build_form($function, $form_state); - // If a successful form submission did not occur, the form needs to be - // rendered, which means the task is not complete yet. - if (empty($form_state['executed'])) { - $install_state['task_not_complete'] = TRUE; - return drupal_render($form); - } - // Otherwise, return nothing so the next task will run in the same - // request. - return; - } - else { - // For non-interactive forms, submit the form programmatically with the - // values taken from the installation state. Throw an exception if any - // errors were encountered. - $form_state = array( - 'values' => !empty($install_state['forms'][$function]) ? $install_state['forms'][$function] : array(), - // We need to pass $install_state by reference in order for forms to - // modify it, since the form API will use it in call_user_func_array(), - // which requires that referenced variables be passed explicitly. - 'build_info' => array('args' => array(&$install_state)), - ); - drupal_form_submit($function, $form_state); - $errors = form_get_errors(); - if (!empty($errors)) { - throw new Exception(implode("\n", $errors)); - } - } - } - - elseif ($task['type'] == 'batch') { - // Start a new batch based on the task function, if one is not running - // already. - $current_batch = variable_get('install_current_batch'); - if (!$install_state['interactive'] || !$current_batch) { - $batch = $function($install_state); - if (empty($batch)) { - // If the task did some processing and decided no batch was necessary, - // there is nothing more to do here. - return; - } - batch_set($batch); - // For interactive batches, we need to store the fact that this batch - // task is currently running. Otherwise, we need to make sure the batch - // will complete in one page request. - if ($install_state['interactive']) { - variable_set('install_current_batch', $function); - } - else { - $batch =& batch_get(); - $batch['progressive'] = FALSE; - } - // Process the batch. For progressive batches, this will redirect. - // Otherwise, the batch will complete. - batch_process(install_redirect_url($install_state), install_full_redirect_url($install_state)); - } - // If we are in the middle of processing this batch, keep sending back - // any output from the batch process, until the task is complete. - elseif ($current_batch == $function) { - include_once DRUPAL_ROOT . '/core/includes/batch.inc'; - $output = _batch_page(); - // The task is complete when we try to access the batch page and receive - // FALSE in return, since this means we are at a URL where we are no - // longer requesting a batch ID. - if ($output === FALSE) { - // Return nothing so the next task will run in the same request. - variable_del('install_current_batch'); - return; - } - else { - // We need to force the page request to end if the task is not - // complete, since the batch API sometimes prints JSON output - // rather than returning a themed page. - $install_state['task_not_complete'] = $install_state['stop_page_request'] = TRUE; - return $output; - } - } - } - - else { - // For normal tasks, just return the function result, whatever it is. - return $function($install_state); - } -} - -/** - * Returns a list of tasks to perform during the current installation request. - * - * Note that the list of tasks can change based on the installation state as - * the page request evolves (for example, if an installation profile hasn't - * been selected yet, we don't yet know which profile tasks need to be run). - * - * @param $install_state - * An array of information about the current installation state. - * - * @return - * A list of tasks to be performed, with associated metadata. - */ -function install_tasks_to_perform($install_state) { - // Start with a list of all currently available tasks. - $tasks = install_tasks($install_state); - foreach ($tasks as $name => $task) { - // Remove any tasks that were already performed or that never should run. - // Also, if we started this page request with an indication of the last - // task that was completed, skip that task and all those that come before - // it, unless they are marked as always needing to run. - if ($task['run'] == INSTALL_TASK_SKIP || in_array($name, $install_state['tasks_performed']) || (!empty($install_state['completed_task']) && empty($completed_task_found) && $task['run'] != INSTALL_TASK_RUN_IF_REACHED)) { - unset($tasks[$name]); - } - if (!empty($install_state['completed_task']) && $name == $install_state['completed_task']) { - $completed_task_found = TRUE; - } - } - return $tasks; -} - -/** - * Returns a list of all tasks the installer currently knows about. - * - * This function will return tasks regardless of whether or not they are - * intended to run on the current page request. However, the list can change - * based on the installation state (for example, if an installation profile - * hasn't been selected yet, we don't yet know which profile tasks will be - * available). - * - * @param $install_state - * An array of information about the current installation state. - * - * @return - * A list of tasks, with associated metadata. - */ -function install_tasks($install_state) { - // Determine whether translation import tasks will need to be performed. - $needs_translations = count($install_state['locales']) > 1 && !empty($install_state['parameters']['locale']) && $install_state['parameters']['locale'] != 'en'; - - // Start with the core installation tasks that run before handing control - // to the install profile. - $tasks = array( - 'install_select_profile' => array( - 'display_name' => st('Choose profile'), - 'display' => count($install_state['profiles']) != 1, - 'run' => INSTALL_TASK_RUN_IF_REACHED, - ), - 'install_select_locale' => array( - 'display_name' => st('Choose language'), - 'run' => INSTALL_TASK_RUN_IF_REACHED, - ), - 'install_load_profile' => array( - 'run' => INSTALL_TASK_RUN_IF_REACHED, - ), - 'install_verify_requirements' => array( - 'display_name' => st('Verify requirements'), - ), - 'install_settings_form' => array( - 'display_name' => st('Set up database'), - 'type' => 'form', - 'run' => $install_state['settings_verified'] ? INSTALL_TASK_SKIP : INSTALL_TASK_RUN_IF_NOT_COMPLETED, - ), - 'install_system_module' => array( - ), - 'install_bootstrap_full' => array( - 'run' => INSTALL_TASK_RUN_IF_REACHED, - ), - 'install_profile_modules' => array( - 'display_name' => count($install_state['profiles']) == 1 ? st('Install site') : st('Install profile'), - 'type' => 'batch', - ), - 'install_import_locales' => array( - 'display_name' => st('Set up translations'), - 'display' => $needs_translations, - 'type' => 'batch', - 'run' => $needs_translations ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP, - ), - 'install_configure_form' => array( - 'display_name' => st('Configure site'), - 'type' => 'form', - ), - ); - - // Now add any tasks defined by the installation profile. - if (!empty($install_state['parameters']['profile'])) { - $function = $install_state['parameters']['profile'] . '_install_tasks'; - if (function_exists($function)) { - $result = $function($install_state); - if (is_array($result)) { - $tasks += $result; - } - } - } - - // Finish by adding the remaining core tasks. - $tasks += array( - 'install_import_locales_remaining' => array( - 'display_name' => st('Finish translations'), - 'display' => $needs_translations, - 'type' => 'batch', - 'run' => $needs_translations ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP, - ), - 'install_finished' => array( - 'display_name' => st('Finished'), - ), - ); - - // Allow the installation profile to modify the full list of tasks. - if (!empty($install_state['parameters']['profile'])) { - $profile_file = DRUPAL_ROOT . '/profiles/' . $install_state['parameters']['profile'] . '/' . $install_state['parameters']['profile'] . '.profile'; - if (is_file($profile_file)) { - include_once $profile_file; - $function = $install_state['parameters']['profile'] . '_install_tasks_alter'; - if (function_exists($function)) { - $function($tasks, $install_state); - } - } - } - - // Fill in default parameters for each task before returning the list. - foreach ($tasks as $task_name => &$task) { - $task += array( - 'display_name' => NULL, - 'display' => !empty($task['display_name']), - 'type' => 'normal', - 'run' => INSTALL_TASK_RUN_IF_NOT_COMPLETED, - 'function' => $task_name, - ); - } - return $tasks; -} - -/** - * Returns a list of tasks that should be displayed to the end user. - * - * The output of this function is a list suitable for sending to - * theme_task_list(). - * - * @param $install_state - * An array of information about the current installation state. - * - * @return - * A list of tasks, with keys equal to the machine-readable task name and - * values equal to the name that should be displayed. - * - * @see theme_task_list() - */ -function install_tasks_to_display($install_state) { - $displayed_tasks = array(); - foreach (install_tasks($install_state) as $name => $task) { - if ($task['display']) { - $displayed_tasks[$name] = $task['display_name']; - } - } - return $displayed_tasks; -} - -/** - * Returns the URL that should be redirected to during an installation request. - * - * The output of this function is suitable for sending to install_goto(). - * - * @param $install_state - * An array of information about the current installation state. - * - * @return - * The URL to redirect to. - * - * @see install_full_redirect_url() - */ -function install_redirect_url($install_state) { - return 'core/install.php?' . drupal_http_build_query($install_state['parameters']); -} - -/** - * Returns the complete URL redirected to during an installation request. - * - * @param $install_state - * An array of information about the current installation state. - * - * @return - * The complete URL to redirect to. - * - * @see install_redirect_url() - */ -function install_full_redirect_url($install_state) { - global $base_url; - return $base_url . '/' . install_redirect_url($install_state); -} - -/** - * Displays themed installer output and ends the page request. - * - * Installation tasks should use drupal_set_title() to set the desired page - * title, but otherwise this function takes care of theming the overall page - * output during every step of the installation. - * - * @param $output - * The content to display on the main part of the page. - * @param $install_state - * An array of information about the current installation state. - */ -function install_display_output($output, $install_state) { - drupal_page_header(); - // Only show the task list if there is an active task; otherwise, the page - // request has ended before tasks have even been started, so there is nothing - // meaningful to show. - if (isset($install_state['active_task'])) { - // Let the theming function know when every step of the installation has - // been completed. - $active_task = $install_state['installation_finished'] ? NULL : $install_state['active_task']; - drupal_add_region_content('sidebar_first', theme('task_list', array('items' => install_tasks_to_display($install_state), 'active' => $active_task))); - } - print theme('install_page', array('content' => $output)); - exit; -} - -/** - * Installation task; verify the requirements for installing Drupal. - * - * @param $install_state - * An array of information about the current installation state. - * - * @return - * A themed status report, or an exception if there are requirement errors. - * If there are only requirement warnings, a themed status report is shown - * initially, but the user is allowed to bypass it by providing 'continue=1' - * in the URL. Otherwise, no output is returned, so that the next task can be - * run in the same page request. - */ -function install_verify_requirements(&$install_state) { - // Check the installation requirements for Drupal and this profile. - $requirements = install_check_requirements($install_state); - - // Verify existence of all required modules. - $requirements += drupal_verify_profile($install_state); - - // Check the severity of the requirements reported. - $severity = drupal_requirements_severity($requirements); - - // If there are errors, always display them. If there are only warnings, skip - // them if the user has provided a URL parameter acknowledging the warnings - // and indicating a desire to continue anyway. See drupal_requirements_url(). - if ($severity == REQUIREMENT_ERROR || ($severity == REQUIREMENT_WARNING && empty($install_state['parameters']['continue']))) { - if ($install_state['interactive']) { - drupal_set_title(st('Requirements problem')); - $status_report = theme('status_report', array('requirements' => $requirements)); - $status_report .= st('Check the messages and <a href="!url">proceed with the installation</a>.', array('!url' => check_url(drupal_requirements_url($severity)))); - return $status_report; - } - else { - // Throw an exception showing any unmet requirements. - $failures = array(); - foreach ($requirements as $requirement) { - // Skip warnings altogether for non-interactive installations; these - // proceed in a single request so there is no good opportunity (and no - // good method) to warn the user anyway. - if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) { - $failures[] = $requirement['title'] . ': ' . $requirement['value'] . "\n\n" . $requirement['description']; - } - } - if (!empty($failures)) { - throw new Exception(implode("\n\n", $failures)); - } - } - } -} - -/** - * Installation task; install the Drupal system module. - * - * @param $install_state - * An array of information about the current installation state. - */ -function install_system_module(&$install_state) { - // Install system.module. - drupal_install_system(); - - // Enable the user module so that sessions can be recorded during the - // upcoming bootstrap step. - module_enable(array('user'), FALSE); - - // Save the list of other modules to install for the upcoming tasks. - // variable_set() can be used now that system.module is installed. - $modules = $install_state['profile_info']['dependencies']; - - // The install profile is also a module, which needs to be installed - // after all the dependencies have been installed. - $modules[] = drupal_get_profile(); - - variable_set('install_profile_modules', array_diff($modules, array('system'))); - $install_state['database_tables_exist'] = TRUE; -} - -/** - * Verify and return the last installation task that was completed. - * - * @return - * The last completed task, if there is one. An exception is thrown if Drupal - * is already installed. - */ -function install_verify_completed_task() { - try { - if ($result = db_query("SELECT value FROM {variable} WHERE name = :name", array('name' => 'install_task'))) { - $task = unserialize($result->fetchField()); - } - } - // Do not trigger an error if the database query fails, since the database - // might not be set up yet. - catch (Exception $e) { - } - if (isset($task)) { - if ($task == 'done') { - throw new Exception(install_already_done_error()); - } - return $task; - } -} - -/** - * Verifies the existing settings in settings.php. - */ -function install_verify_settings() { - global $databases; - - // Verify existing settings (if any). - if (!empty($databases) && install_verify_pdo()) { - $database = $databases['default']['default']; - drupal_static_reset('conf_path'); - $settings_file = './' . conf_path(FALSE) . '/settings.php'; - $errors = install_database_errors($database, $settings_file); - if (empty($errors)) { - return TRUE; - } - } - return FALSE; -} - -/** - * Verify PDO library. - */ -function install_verify_pdo() { - // PDO was moved to PHP core in 5.2.0, but the old extension (targeting 5.0 - // and 5.1) is still available from PECL, and can still be built without - // errors. To verify that the correct version is in use, we check the - // PDO::ATTR_DEFAULT_FETCH_MODE constant, which is not available in the - // PECL extension. - return extension_loaded('pdo') && defined('PDO::ATTR_DEFAULT_FETCH_MODE'); -} - -/** - * Installation task; define a form to configure and rewrite settings.php. - * - * @param $form_state - * An associative array containing the current state of the form. - * @param $install_state - * An array of information about the current installation state. - * - * @return - * The form API definition for the database configuration form. - */ -function install_settings_form($form, &$form_state, &$install_state) { - global $databases; - $profile = $install_state['parameters']['profile']; - $install_locale = $install_state['parameters']['locale']; - - drupal_static_reset('conf_path'); - $conf_path = './' . conf_path(FALSE); - $settings_file = $conf_path . '/settings.php'; - $database = isset($databases['default']['default']) ? $databases['default']['default'] : array(); - - drupal_set_title(st('Database configuration')); - - $drivers = drupal_get_database_types(); - $drivers_keys = array_keys($drivers); - - $form['driver'] = array( - '#type' => 'radios', - '#title' => st('Database type'), - '#required' => TRUE, - '#default_value' => !empty($database['driver']) ? $database['driver'] : current($drivers_keys), - '#description' => st('The type of database your @drupal data will be stored in.', array('@drupal' => drupal_install_profile_distribution_name())), - ); - if (count($drivers) == 1) { - $form['driver']['#disabled'] = TRUE; - $form['driver']['#description'] .= ' ' . st('Your PHP configuration only supports a single database type, so it has been automatically selected.'); - } - - // Add driver specific configuration options. - foreach ($drivers as $key => $driver) { - $form['driver']['#options'][$key] = $driver->name(); - - $form['settings'][$key] = $driver->getFormOptions($database); - $form['settings'][$key]['#prefix'] = '<h2 class="js-hide">' . st('@driver_name settings', array('@driver_name' => $driver->name())) . '</h2>'; - $form['settings'][$key]['#type'] = 'container'; - $form['settings'][$key]['#tree'] = TRUE; - $form['settings'][$key]['advanced_options']['#parents'] = array($key); - $form['settings'][$key]['#states'] = array( - 'visible' => array( - ':input[name=driver]' => array('value' => $key), - ) - ); - } - - $form['actions'] = array('#type' => 'actions'); - $form['actions']['save'] = array( - '#type' => 'submit', - '#value' => st('Save and continue'), - '#limit_validation_errors' => array( - array('driver'), - array(isset($form_state['input']['driver']) ? $form_state['input']['driver'] : current($drivers_keys)), - ), - '#submit' => array('install_settings_form_submit'), - ); - - $form['errors'] = array(); - $form['settings_file'] = array('#type' => 'value', '#value' => $settings_file); - - return $form; -} - -/** - * Form API validate for install_settings form. - */ -function install_settings_form_validate($form, &$form_state) { - $driver = $form_state['values']['driver']; - $database = $form_state['values'][$driver]; - $database['driver'] = $driver; - - // TODO: remove when PIFR will be updated to use 'db_prefix' instead of - // 'prefix' in the database settings form. - $database['prefix'] = $database['db_prefix']; - unset($database['db_prefix']); - - $form_state['storage']['database'] = $database; - $errors = install_database_errors($database, $form_state['values']['settings_file']); - foreach ($errors as $name => $message) { - form_set_error($name, $message); - } -} - -/** - * Checks a database connection and returns any errors. - */ -function install_database_errors($database, $settings_file) { - global $databases; - $errors = array(); - - // Check database type. - $database_types = drupal_get_database_types(); - $driver = $database['driver']; - if (!isset($database_types[$driver])) { - $errors['driver'] = st("In your %settings_file file you have configured @drupal to use a %driver server, however your PHP installation currently does not support this database type.", array('%settings_file' => $settings_file, '@drupal' => drupal_install_profile_distribution_name(), '%driver' => $driver)); - } - else { - // Run driver specific validation - $errors += $database_types[$driver]->validateDatabaseSettings($database); - - // Run tasks associated with the database type. Any errors are caught in the - // calling function. - $databases['default']['default'] = $database; - // Just changing the global doesn't get the new information processed. - // We tell tell the Database class to re-parse $databases. - Database::parseConnectionInfo(); - - try { - db_run_tasks($driver); - } - catch (DatabaseTaskException $e) { - // These are generic errors, so we do not have any specific key of the - // database connection array to attach them to; therefore, we just put - // them in the error array with standard numeric keys. - $errors[$driver . '][0'] = $e->getMessage(); - } - } - return $errors; -} - -/** - * Form API submit for install_settings form. - */ -function install_settings_form_submit($form, &$form_state) { - global $install_state; - - // Update global settings array and save. - $settings['databases'] = array( - 'value' => array('default' => array('default' => $form_state['storage']['database'])), - 'required' => TRUE, - ); - $settings['drupal_hash_salt'] = array( - 'value' => drupal_hash_base64(drupal_random_bytes(55)), - 'required' => TRUE, - ); - drupal_rewrite_settings($settings); - // Indicate that the settings file has been verified, and check the database - // for the last completed task, now that we have a valid connection. This - // last step is important since we want to trigger an error if the new - // database already has Drupal installed. - $install_state['settings_verified'] = TRUE; - $install_state['completed_task'] = install_verify_completed_task(); -} - -/** - * Finds all .profile files. - */ -function install_find_profiles() { - return file_scan_directory('./profiles', '/\.profile$/', array('key' => 'name')); -} - -/** - * Installation task; select which profile to install. - * - * @param $install_state - * An array of information about the current installation state. The chosen - * profile will be added here, if it was not already selected previously, as - * will a list of all available profiles. - * - * @return - * For interactive installations, a form allowing the profile to be selected, - * if the user has a choice that needs to be made. Otherwise, an exception is - * thrown if a profile cannot be chosen automatically. - */ -function install_select_profile(&$install_state) { - $install_state['profiles'] += install_find_profiles(); - if (empty($install_state['parameters']['profile'])) { - // Try to find a profile. - $profile = _install_select_profile($install_state['profiles']); - if (empty($profile)) { - // We still don't have a profile, so display a form for selecting one. - // Only do this in the case of interactive installations, since this is - // not a real form with submit handlers (the database isn't even set up - // yet), rather just a convenience method for setting parameters in the - // URL. - if ($install_state['interactive']) { - include_once DRUPAL_ROOT . '/core/includes/form.inc'; - drupal_set_title(st('Select an installation profile')); - $form = drupal_get_form('install_select_profile_form', $install_state['profiles']); - return drupal_render($form); - } - else { - throw new Exception(install_no_profile_error()); - } - } - else { - $install_state['parameters']['profile'] = $profile; - } - } -} - -/** - * Helper function for automatically selecting an installation profile from a - * list or from a selection passed in via $_POST. - */ -function _install_select_profile($profiles) { - if (sizeof($profiles) == 0) { - throw new Exception(install_no_profile_error()); - } - // Don't need to choose profile if only one available. - if (sizeof($profiles) == 1) { - $profile = array_pop($profiles); - // TODO: is this right? - require_once DRUPAL_ROOT . '/' . $profile->uri; - return $profile->name; - } - else { - foreach ($profiles as $profile) { - if (!empty($_POST['profile']) && ($_POST['profile'] == $profile->name)) { - return $profile->name; - } - } - } -} - -/** - * Form API array definition for the profile selection form. - * - * @param $form_state - * Array of metadata about state of form processing. - * @param $profile_files - * Array of .profile files, as returned from file_scan_directory(). - */ -function install_select_profile_form($form, &$form_state, $profile_files) { - $profiles = array(); - $names = array(); - - foreach ($profile_files as $profile) { - // TODO: is this right? - include_once DRUPAL_ROOT . '/' . $profile->uri; - - $details = install_profile_info($profile->name); - // Don't show hidden profiles. This is used by to hide the testing profile, - // which only exists to speed up test runs. - if ($details['hidden'] === TRUE) { - continue; - } - $profiles[$profile->name] = $details; - - // Determine the name of the profile; default to file name if defined name - // is unspecified. - $name = isset($details['name']) ? $details['name'] : $profile->name; - $names[$profile->name] = $name; - } - - // Display radio buttons alphabetically by human-readable name, but always - // put the core profiles first (if they are present in the filesystem). - natcasesort($names); - if (isset($names['minimal'])) { - // If the expert ("Minimal") core profile is present, put it in front of - // any non-core profiles rather than including it with them alphabetically, - // since the other profiles might be intended to group together in a - // particular way. - $names = array('minimal' => $names['minimal']) + $names; - } - if (isset($names['standard'])) { - // If the default ("Standard") core profile is present, put it at the very - // top of the list. This profile will have its radio button pre-selected, - // so we want it to always appear at the top. - $names = array('standard' => $names['standard']) + $names; - } - - foreach ($names as $profile => $name) { - $form['profile'][$name] = array( - '#type' => 'radio', - '#value' => 'standard', - '#return_value' => $profile, - '#title' => $name, - '#description' => isset($profiles[$profile]['description']) ? $profiles[$profile]['description'] : '', - '#parents' => array('profile'), - ); - } - $form['actions'] = array('#type' => 'actions'); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => st('Save and continue'), - ); - return $form; -} - -/** - * Find all .po files useful for the installer. - */ -function install_find_locales() { - $files = install_find_locale_files(); - // English does not need a translation file. - array_unshift($files, (object) array('name' => 'en')); - foreach ($files as $key => $file) { - // Strip off the file name component before the language code. - $files[$key]->langcode = preg_replace('!^(.+\.)?([^\.]+)$!', '\2', $file->name); - // Language codes cannot exceed 12 characters to fit into the {languages} - // table. - if (strlen($files[$key]->langcode) > 12) { - unset($files[$key]); - } - } - return $files; -} - -/** - * Find installer translations either for a specific langcode or all languages. - */ -function install_find_locale_files($langcode = NULL) { - $directory = variable_get('locale_translate_file_directory', conf_path() . '/files/translations'); - $files = file_scan_directory($directory, '!install\.' . (!empty($langcode) ? '\.' . preg_quote($langcode, '!') : '[^\.]+') . '\.po$!', array('recurse' => FALSE)); - return $files; -} - -/** - * Installation task; select which locale to use for the current profile. - * - * @param $install_state - * An array of information about the current installation state. The chosen - * locale will be added here, if it was not already selected previously, as - * will a list of all available locales. - * - * @return - * For interactive installations, a form or other page output allowing the - * locale to be selected or providing information about locale selection, if - * a locale has not been chosen. Otherwise, an exception is thrown if a - * locale cannot be chosen automatically. - */ -function install_select_locale(&$install_state) { - // Find all available locales. - $profilename = $install_state['parameters']['profile']; - $locales = install_find_locales($profilename); - $install_state['locales'] += $locales; - - if (!empty($_POST['locale'])) { - foreach ($locales as $locale) { - if ($_POST['locale'] == $locale->langcode) { - $install_state['parameters']['locale'] = $locale->langcode; - return; - } - } - } - - if (empty($install_state['parameters']['locale'])) { - // If only the built-in (English) language is available, and we are - // performing an interactive installation, inform the user that the - // installer can be localized. Otherwise we assume the user knows what he - // is doing. - if (count($locales) == 1) { - if ($install_state['interactive']) { - drupal_set_title(st('Choose language')); - if (!empty($install_state['parameters']['localize'])) { - $output = '<p>Follow these steps to translate Drupal into your language:</p>'; - $output .= '<ol>'; - $output .= '<li>Download a translation from the <a href="http://localize.drupal.org/download" target="_blank">translation server</a>.</li>'; - $output .= '<li>Place it into the following directory: -<pre> -/profiles/' . $profilename . '/translations/ -</pre></li>'; - $output .= '</ol>'; - $output .= '<p>For more information on installing Drupal in different languages, visit the <a href="http://drupal.org/localize" target="_blank">drupal.org handbook page</a>.</p>'; - $output .= '<p>How should the installation continue?</p>'; - $output .= '<ul>'; - $output .= '<li><a href="install.php?profile=' . $profilename . '">Reload the language selection page after adding translations</a></li>'; - $output .= '<li><a href="install.php?profile=' . $profilename . '&locale=en">Continue installation in English</a></li>'; - $output .= '</ul>'; - } - else { - include_once DRUPAL_ROOT . '/core/includes/form.inc'; - $elements = drupal_get_form('install_select_locale_form', $locales, $profilename); - $output = drupal_render($elements); - } - return $output; - } - // One language, but not an interactive installation. Assume the user - // knows what he is doing. - $locale = current($locales); - $install_state['parameters']['locale'] = $locale->name; - return; - } - else { - // Allow profile to pre-select the language, skipping the selection. - $function = $profilename . '_profile_details'; - if (function_exists($function)) { - $details = $function(); - if (isset($details['language'])) { - foreach ($locales as $locale) { - if ($details['language'] == $locale->name) { - $install_state['parameters']['locale'] = $locale->name; - return; - } - } - } - } - - // We still don't have a locale, so display a form for selecting one. - // Only do this in the case of interactive installations, since this is - // not a real form with submit handlers (the database isn't even set up - // yet), rather just a convenience method for setting parameters in the - // URL. - if ($install_state['interactive']) { - drupal_set_title(st('Choose language')); - include_once DRUPAL_ROOT . '/core/includes/form.inc'; - $elements = drupal_get_form('install_select_locale_form', $locales, $profilename); - return drupal_render($elements); - } - else { - throw new Exception(st('Sorry, you must select a language to continue the installation.')); - } - } - } -} - -/** - * Form API array definition for language selection. - */ -function install_select_locale_form($form, &$form_state, $locales, $profilename) { - include_once DRUPAL_ROOT . '/core/includes/standard.inc'; - $languages = standard_language_list(); - foreach ($locales as $locale) { - $name = $locale->langcode; - if (isset($languages[$name])) { - $name = $languages[$name][0] . (isset($languages[$name][1]) ? ' ' . st('(@language)', array('@language' => $languages[$name][1])) : ''); - } - $form['locale'][$locale->langcode] = array( - '#type' => 'radio', - '#return_value' => $locale->langcode, - '#default_value' => $locale->langcode == 'en' ? 'en' : '', - '#title' => $name . ($locale->langcode == 'en' ? ' ' . st('(built-in)') : ''), - '#parents' => array('locale') - ); - } - if (count($locales) == 1) { - $form['help'] = array( - '#markup' => '<p><a href="install.php?profile=' . $profilename . '&localize=true">' . st('Learn how to install Drupal in other languages') . '</a></p>', - ); - } - $form['actions'] = array('#type' => 'actions'); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => st('Save and continue'), - ); - return $form; -} - -/** - * Indicates that there are no profiles available. - */ -function install_no_profile_error() { - drupal_set_title(st('No profiles available')); - return st('We were unable to find any installation profiles. Installation profiles tell us what modules to enable and what schema to install in the database. A profile is necessary to continue with the installation process.'); -} - -/** - * Indicates that Drupal has already been installed. - */ -function install_already_done_error() { - global $base_url; - - drupal_set_title(st('Drupal already installed')); - return st('<ul><li>To start over, you must empty your existing database.</li><li>To install to a different database, edit the appropriate <em>settings.php</em> file in the <em>sites</em> folder.</li><li>To upgrade an existing installation, proceed to the <a href="@base-url/update.php">update script</a>.</li><li>View your <a href="@base-url">existing site</a>.</li></ul>', array('@base-url' => $base_url)); -} - -/** - * Installation task; load information about the chosen profile. - * - * @param $install_state - * An array of information about the current installation state. The loaded - * profile information will be added here, or an exception will be thrown if - * the profile cannot be loaded. - */ -function install_load_profile(&$install_state) { - $profile_file = DRUPAL_ROOT . '/profiles/' . $install_state['parameters']['profile'] . '/' . $install_state['parameters']['profile'] . '.profile'; - if (is_file($profile_file)) { - include_once $profile_file; - $install_state['profile_info'] = install_profile_info($install_state['parameters']['profile'], $install_state['parameters']['locale']); - } - else { - throw new Exception(st('Sorry, the profile you have chosen cannot be loaded.')); - } -} - -/** - * Installation task; perform a full bootstrap of Drupal. - * - * @param $install_state - * An array of information about the current installation state. - */ -function install_bootstrap_full(&$install_state) { - drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); -} - -/** - * Installation task; install required modules via a batch process. - * - * @param $install_state - * An array of information about the current installation state. - * - * @return - * The batch definition. - */ -function install_profile_modules(&$install_state) { - $modules = variable_get('install_profile_modules', array()); - $files = system_rebuild_module_data(); - variable_del('install_profile_modules'); - - // Always install required modules first. Respect the dependencies between - // the modules. - $required = array(); - $non_required = array(); - // Although the profile module is marked as required, it needs to go after - // every dependency, including non-required ones. So clear its required - // flag for now to allow it to install late. - $files[$install_state['parameters']['profile']]->info['required'] = FALSE; - // Add modules that other modules depend on. - foreach ($modules as $module) { - if ($files[$module]->requires) { - $modules = array_merge($modules, array_keys($files[$module]->requires)); - } - } - $modules = array_unique($modules); - foreach ($modules as $module) { - if (!empty($files[$module]->info['required'])) { - $required[$module] = $files[$module]->sort; - } - else { - $non_required[$module] = $files[$module]->sort; - } - } - arsort($required); - arsort($non_required); - - $operations = array(); - foreach ($required + $non_required as $module => $weight) { - $operations[] = array('_install_module_batch', array($module, $files[$module]->info['name'])); - } - $batch = array( - 'operations' => $operations, - 'title' => st('Installing @drupal', array('@drupal' => drupal_install_profile_distribution_name())), - 'error_message' => st('The installation has encountered an error.'), - 'finished' => '_install_profile_modules_finished', - ); - return $batch; -} - -/** - * Installation task; import languages via a batch process. - * - * @param $install_state - * An array of information about the current installation state. - * - * @return - * The batch definition, if there are language files to import. - */ -function install_import_locales(&$install_state) { - include_once DRUPAL_ROOT . '/core/includes/locale.inc'; - include_once drupal_get_path('module', 'locale') . '/locale.bulk.inc'; - $install_locale = $install_state['parameters']['locale']; - - include_once DRUPAL_ROOT . '/core/includes/standard.inc'; - $predefined = standard_language_list(); - if (!isset($predefined[$install_locale])) { - // Drupal does not know about this language, so we prefill its values with - // our best guess. The user will be able to edit afterwards. - $language = (object) array( - 'language' => $install_locale, - 'name' => $install_locale, - 'default' => TRUE, - ); - locale_language_save($language); - } - else { - // A known predefined language, details will be filled in properly. - $language = (object) array( - 'language' => $install_locale, - 'default' => TRUE, - ); - locale_language_save($language); - } - - // Collect files to import for this language. - $batch = locale_translate_batch_import_files($install_locale); - if (!empty($batch)) { - return $batch; - } -} - -/** - * Installation task; configure settings for the new site. - * - * @param $form_state - * An associative array containing the current state of the form. - * @param $install_state - * An array of information about the current installation state. - * - * @return - * The form API definition for the site configuration form. - */ -function install_configure_form($form, &$form_state, &$install_state) { - if (variable_get('site_name', FALSE) || variable_get('site_mail', FALSE)) { - // Site already configured: This should never happen, means re-running the - // installer, possibly by an attacker after the 'install_task' variable got - // accidentally blown somewhere. Stop it now. - throw new Exception(install_already_done_error()); - } - - drupal_set_title(st('Configure site')); - - // Warn about settings.php permissions risk - $settings_dir = conf_path(); - $settings_file = $settings_dir . '/settings.php'; - // Check that $_POST is empty so we only show this message when the form is - // first displayed, not on the next page after it is submitted. (We do not - // want to repeat it multiple times because it is a general warning that is - // not related to the rest of the installation process; it would also be - // especially out of place on the last page of the installer, where it would - // distract from the message that the Drupal installation has completed - // successfully.) - if (empty($_POST) && (!drupal_verify_install_file(DRUPAL_ROOT . '/' . $settings_file, FILE_EXIST|FILE_READABLE|FILE_NOT_WRITABLE) || !drupal_verify_install_file(DRUPAL_ROOT . '/' . $settings_dir, FILE_NOT_WRITABLE, 'dir'))) { - drupal_set_message(st('All necessary changes to %dir and %file have been made, so you should remove write permissions to them now in order to avoid security risks. If you are unsure how to do so, consult the <a href="@handbook_url">online handbook</a>.', array('%dir' => $settings_dir, '%file' => $settings_file, '@handbook_url' => 'http://drupal.org/server-permissions')), 'warning'); - } - - drupal_add_js(drupal_get_path('module', 'system') . '/system.js'); - // Add JavaScript time zone detection. - drupal_add_js('core/misc/timezone.js'); - // We add these strings as settings because JavaScript translation does not - // work on install time. - drupal_add_js(array('copyFieldValue' => array('edit-site-mail' => array('edit-account-mail'))), 'setting'); - drupal_add_js('jQuery(function () { Drupal.cleanURLsInstallCheck(); });', 'inline'); - // Add JS to show / hide the 'Email administrator about site updates' elements - drupal_add_js('jQuery(function () { Drupal.hideEmailAdministratorCheckbox() });', 'inline'); - // Build menu to allow clean URL check. - menu_rebuild(); - - // Cache a fully-built schema. This is necessary for any invocation of - // index.php because: (1) setting cache table entries requires schema - // information, (2) that occurs during bootstrap before any module are - // loaded, so (3) if there is no cached schema, drupal_get_schema() will - // try to generate one but with no loaded modules will return nothing. - // - // This logically could be done during the 'install_finished' task, but the - // clean URL check requires it now. - drupal_get_schema(NULL, TRUE); - - // Return the form. - return _install_configure_form($form, $form_state, $install_state); -} - -/** - * Installation task; finish importing files at end of installation. - * - * @param $install_state - * An array of information about the current installation state. - * - * @return - * The batch definition, if there are language files to import. - * - * @todo - * This currently does the same as the first import step. Need to revisit - * once we have l10n_update functionality integrated. See - * http://drupal.org/node/1191488. - */ -function install_import_locales_remaining(&$install_state) { - include_once drupal_get_path('module', 'locale') . '/locale.bulk.inc'; - return locale_translate_batch_import_files($install_state['parameters']['locale']); -} - -/** - * Installation task; perform final steps and display a 'finished' page. - * - * @param $install_state - * An array of information about the current installation state. - * - * @return - * A message informing the user that the installation is complete. - */ -function install_finished(&$install_state) { - drupal_set_title(st('@drupal installation complete', array('@drupal' => drupal_install_profile_distribution_name())), PASS_THROUGH); - $messages = drupal_set_message(); - $output = '<p>' . st('Congratulations, you installed @drupal!', array('@drupal' => drupal_install_profile_distribution_name())) . '</p>'; - $output .= '<p>' . (isset($messages['error']) ? st('Review the messages above before visiting <a href="@url">your new site</a>.', array('@url' => url(''))) : st('<a href="@url">Visit your new site</a>.', array('@url' => url('')))) . '</p>'; - - // Flush all caches to ensure that any full bootstraps during the installer - // do not leave stale cached data, and that any content types or other items - // registered by the install profile are registered correctly. - drupal_flush_all_caches(); - - // Remember the profile which was used. - variable_set('install_profile', drupal_get_profile()); - - // Install profiles are always loaded last - db_update('system') - ->fields(array('weight' => 1000)) - ->condition('type', 'module') - ->condition('name', drupal_get_profile()) - ->execute(); - - // Cache a fully-built schema. - drupal_get_schema(NULL, TRUE); - - // Run cron to populate update status tables (if available) so that users - // will be warned if they've installed an out of date Drupal version. - // Will also trigger indexing of profile-supplied content or feeds. - drupal_cron_run(); - - return $output; -} - -/** - * Batch callback for batch installation of modules. - */ -function _install_module_batch($module, $module_name, &$context) { - // Install and enable the module right away, so that the module will be - // loaded by drupal_bootstrap in subsequent batch requests, and other - // modules possibly depending on it can safely perform their installation - // steps. - module_enable(array($module), FALSE); - $context['results'][] = $module; - $context['message'] = st('Installed %module module.', array('%module' => $module_name)); -} - -/** - * 'Finished' callback for module installation batch. - */ -function _install_profile_modules_finished($success, $results, $operations) { - // Flush all caches to complete the module installation process. Subsequent - // installation tasks will now have full access to the profile's modules. - drupal_flush_all_caches(); -} - -/** - * Checks installation requirements and reports any errors. - */ -function install_check_requirements($install_state) { - $profile = $install_state['parameters']['profile']; - - // Check the profile requirements. - $requirements = drupal_check_profile($profile); - - // If Drupal is not set up already, we need to create a settings file. - if (!$install_state['settings_verified']) { - $writable = FALSE; - $conf_path = './' . conf_path(FALSE, TRUE); - $settings_file = $conf_path . '/settings.php'; - $default_settings_file = './sites/default/default.settings.php'; - $file = $conf_path; - $exists = FALSE; - // Verify that the directory exists. - if (drupal_verify_install_file($conf_path, FILE_EXIST, 'dir')) { - // Check if a settings.php file already exists. - $file = $settings_file; - if (drupal_verify_install_file($settings_file, FILE_EXIST)) { - // If it does, make sure it is writable. - $writable = drupal_verify_install_file($settings_file, FILE_READABLE|FILE_WRITABLE); - $exists = TRUE; - } - } - - // If default.settings.php does not exist, or is not readable, throw an - // error. - if (!drupal_verify_install_file($default_settings_file, FILE_EXIST|FILE_READABLE)) { - $requirements['default settings file exists'] = array( - 'title' => st('Default settings file'), - 'value' => st('The default settings file does not exist.'), - 'severity' => REQUIREMENT_ERROR, - 'description' => st('The @drupal installer requires that the %default-file file not be modified in any way from the original download.', array('@drupal' => drupal_install_profile_distribution_name(), '%default-file' => $default_settings_file)), - ); - } - // Otherwise, if settings.php does not exist yet, we can try to copy - // default.settings.php to create it. - elseif (!$exists) { - $copied = drupal_verify_install_file($conf_path, FILE_EXIST|FILE_WRITABLE, 'dir') && @copy($default_settings_file, $settings_file); - if ($copied) { - // If the new settings file has the same owner as default.settings.php, - // this means default.settings.php is owned by the webserver user. - // This is an inherent security weakness because it allows a malicious - // webserver process to append arbitrary PHP code and then execute it. - // However, it is also a common configuration on shared hosting, and - // there is nothing Drupal can do to prevent it. In this situation, - // having settings.php also owned by the webserver does not introduce - // any additional security risk, so we keep the file in place. - if (fileowner($default_settings_file) === fileowner($settings_file)) { - $writable = drupal_verify_install_file($settings_file, FILE_READABLE|FILE_WRITABLE); - $exists = TRUE; - } - // If settings.php and default.settings.php have different owners, this - // probably means the server is set up "securely" (with the webserver - // running as its own user, distinct from the user who owns all the - // Drupal PHP files), although with either a group or world writable - // sites directory. Keeping settings.php owned by the webserver would - // therefore introduce a security risk. It would also cause a usability - // problem, since site owners who do not have root access to the file - // system would be unable to edit their settings file later on. We - // therefore must delete the file we just created and force the - // administrator to log on to the server and create it manually. - else { - $deleted = @drupal_unlink($settings_file); - // We expect deleting the file to be successful (since we just - // created it ourselves above), but if it fails somehow, we set a - // variable so we can display a one-time error message to the - // administrator at the bottom of the requirements list. We also try - // to make the file writable, to eliminate any conflicting error - // messages in the requirements list. - $exists = !$deleted; - if ($exists) { - $settings_file_ownership_error = TRUE; - $writable = drupal_verify_install_file($settings_file, FILE_READABLE|FILE_WRITABLE); - } - } - } - } - - // If settings.php does not exist, throw an error. - if (!$exists) { - $requirements['settings file exists'] = array( - 'title' => st('Settings file'), - 'value' => st('The settings file does not exist.'), - 'severity' => REQUIREMENT_ERROR, - 'description' => st('The @drupal installer requires that you create a settings file as part of the installation process. Copy the %default_file file to %file. More details about installing Drupal are available in <a href="@install_txt">INSTALL.txt</a>.', array('@drupal' => drupal_install_profile_distribution_name(), '%file' => $file, '%default_file' => $default_settings_file, '@install_txt' => base_path() . 'core/INSTALL.txt')), - ); - } - else { - $requirements['settings file exists'] = array( - 'title' => st('Settings file'), - 'value' => st('The %file file exists.', array('%file' => $file)), - ); - // If settings.php is not writable, throw an error. - if (!$writable) { - $requirements['settings file writable'] = array( - 'title' => st('Settings file'), - 'value' => st('The settings file is not writable.'), - 'severity' => REQUIREMENT_ERROR, - 'description' => st('The @drupal installer requires write permissions to %file during the installation process. If you are unsure how to grant file permissions, consult the <a href="@handbook_url">online handbook</a>.', array('@drupal' => drupal_install_profile_distribution_name(), '%file' => $file, '@handbook_url' => 'http://drupal.org/server-permissions')), - ); - } - else { - $requirements['settings file'] = array( - 'title' => st('Settings file'), - 'value' => st('The settings file is writable.'), - ); - } - if (!empty($settings_file_ownership_error)) { - $requirements['settings file ownership'] = array( - 'title' => st('Settings file'), - 'value' => st('The settings file is owned by the web server.'), - 'severity' => REQUIREMENT_ERROR, - 'description' => st('The @drupal installer failed to create a settings file with proper file ownership. Log on to your web server, remove the existing %file file, and create a new one by copying the %default_file file to %file. More details about installing Drupal are available in <a href="@install_txt">INSTALL.txt</a>. If you have problems with the file permissions on your server, consult the <a href="@handbook_url">online handbook</a>.', array('@drupal' => drupal_install_profile_distribution_name(), '%file' => $file, '%default_file' => $default_settings_file, '@install_txt' => base_path() . 'core/INSTALL.txt', '@handbook_url' => 'http://drupal.org/server-permissions')), - ); - } - } - } - return $requirements; -} - -/** - * Forms API array definition for site configuration. - */ -function _install_configure_form($form, &$form_state, &$install_state) { - include_once DRUPAL_ROOT . '/core/includes/locale.inc'; - - $form['site_information'] = array( - '#type' => 'fieldset', - '#title' => st('Site information'), - '#collapsible' => FALSE, - ); - $form['site_information']['site_name'] = array( - '#type' => 'textfield', - '#title' => st('Site name'), - '#required' => TRUE, - '#weight' => -20, - ); - $form['site_information']['site_mail'] = array( - '#type' => 'textfield', - '#title' => st('Site e-mail address'), - '#default_value' => ini_get('sendmail_from'), - '#description' => st("Automated e-mails, such as registration information, will be sent from this address. Use an address ending in your site's domain to help prevent these e-mails from being flagged as spam."), - '#required' => TRUE, - '#weight' => -15, - ); - $form['admin_account'] = array( - '#type' => 'fieldset', - '#title' => st('Site maintenance account'), - '#collapsible' => FALSE, - ); - - $form['admin_account']['account']['#tree'] = TRUE; - $form['admin_account']['account']['name'] = array('#type' => 'textfield', - '#title' => st('Username'), - '#maxlength' => USERNAME_MAX_LENGTH, - '#description' => st('Spaces are allowed; punctuation is not allowed except for periods, hyphens, and underscores.'), - '#required' => TRUE, - '#weight' => -10, - '#attributes' => array('class' => array('username')), - ); - - $form['admin_account']['account']['mail'] = array('#type' => 'textfield', - '#title' => st('E-mail address'), - '#maxlength' => EMAIL_MAX_LENGTH, - '#required' => TRUE, - '#weight' => -5, - ); - $form['admin_account']['account']['pass'] = array( - '#type' => 'password_confirm', - '#required' => TRUE, - '#size' => 25, - '#weight' => 0, - ); - - $form['server_settings'] = array( - '#type' => 'fieldset', - '#title' => st('Server settings'), - '#collapsible' => FALSE, - ); - - $countries = country_get_list(); - $form['server_settings']['site_default_country'] = array( - '#type' => 'select', - '#title' => st('Default country'), - '#empty_value' => '', - '#default_value' => variable_get('site_default_country', NULL), - '#options' => $countries, - '#description' => st('Select the default country for the site.'), - '#weight' => 0, - ); - - $form['server_settings']['date_default_timezone'] = array( - '#type' => 'select', - '#title' => st('Default time zone'), - '#default_value' => date_default_timezone_get(), - '#options' => system_time_zones(), - '#description' => st('By default, dates in this site will be displayed in the chosen time zone.'), - '#weight' => 5, - '#attributes' => array('class' => array('timezone-detect')), - ); - - $form['server_settings']['clean_url'] = array( - '#type' => 'hidden', - '#default_value' => 0, - '#attributes' => array('id' => 'edit-clean-url', 'class' => array('install')), - ); - - $form['update_notifications'] = array( - '#type' => 'fieldset', - '#title' => st('Update notifications'), - '#collapsible' => FALSE, - ); - $form['update_notifications']['update_status_module'] = array( - '#type' => 'checkboxes', - '#options' => array( - 1 => st('Check for updates automatically'), - 2 => st('Receive e-mail notifications'), - ), - '#default_value' => array(1, 2), - '#description' => st('The system will notify you when updates and important security releases are available for installed components. Anonymous information about your site is sent to <a href="@drupal">Drupal.org</a>.', array('@drupal' => 'http://drupal.org')), - '#weight' => 15, - ); - - $form['actions'] = array('#type' => 'actions'); - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => st('Save and continue'), - '#weight' => 15, - ); - - return $form; -} - -/** - * Forms API validate for the site configuration form. - */ -function install_configure_form_validate($form, &$form_state) { - if ($error = user_validate_name($form_state['values']['account']['name'])) { - form_error($form['admin_account']['account']['name'], $error); - } - if ($error = user_validate_mail($form_state['values']['account']['mail'])) { - form_error($form['admin_account']['account']['mail'], $error); - } - if ($error = user_validate_mail($form_state['values']['site_mail'])) { - form_error($form['site_information']['site_mail'], $error); - } -} - -/** - * Forms API submit for the site configuration form. - */ -function install_configure_form_submit($form, &$form_state) { - global $user; - - variable_set('site_name', $form_state['values']['site_name']); - variable_set('site_mail', $form_state['values']['site_mail']); - variable_set('date_default_timezone', $form_state['values']['date_default_timezone']); - variable_set('site_default_country', $form_state['values']['site_default_country']); - - // Enable update.module if this option was selected. - if ($form_state['values']['update_status_module'][1]) { - module_enable(array('update'), FALSE); - - // Add the site maintenance account's email address to the list of - // addresses to be notified when updates are available, if selected. - if ($form_state['values']['update_status_module'][2]) { - variable_set('update_notify_emails', array($form_state['values']['account']['mail'])); - } - } - - // We precreated user 1 with placeholder values. Let's save the real values. - $account = user_load(1); - $merge_data = array('init' => $form_state['values']['account']['mail'], 'roles' => !empty($account->roles) ? $account->roles : array(), 'status' => 1); - user_save($account, array_merge($form_state['values']['account'], $merge_data)); - // Load global $user and perform final login tasks. - $user = user_load(1); - user_login_finalize(); - - if (isset($form_state['values']['clean_url'])) { - variable_set('clean_url', $form_state['values']['clean_url']); - } - - // Record when this install ran. - variable_set('install_time', $_SERVER['REQUEST_TIME']); -} diff --git a/core/includes/install.inc b/core/includes/install.inc deleted file mode 100644 index 514d89c3554..00000000000 --- a/core/includes/install.inc +++ /dev/null @@ -1,1307 +0,0 @@ -<?php - -/** - * Indicates that a module has not been installed yet. - */ -define('SCHEMA_UNINSTALLED', -1); - -/** - * Indicates that a module has been installed. - */ -define('SCHEMA_INSTALLED', 0); - -/** - * Requirement severity -- Informational message only. - */ -define('REQUIREMENT_INFO', -1); - -/** - * Requirement severity -- Requirement successfully met. - */ -define('REQUIREMENT_OK', 0); - -/** - * Requirement severity -- Warning condition; proceed but flag warning. - */ -define('REQUIREMENT_WARNING', 1); - -/** - * Requirement severity -- Error condition; abort installation. - */ -define('REQUIREMENT_ERROR', 2); - -/** - * File permission check -- File exists. - */ -define('FILE_EXIST', 1); - -/** - * File permission check -- File is readable. - */ -define('FILE_READABLE', 2); - -/** - * File permission check -- File is writable. - */ -define('FILE_WRITABLE', 4); - -/** - * File permission check -- File is executable. - */ -define('FILE_EXECUTABLE', 8); - -/** - * File permission check -- File does not exist. - */ -define('FILE_NOT_EXIST', 16); - -/** - * File permission check -- File is not readable. - */ -define('FILE_NOT_READABLE', 32); - -/** - * File permission check -- File is not writable. - */ -define('FILE_NOT_WRITABLE', 64); - -/** - * File permission check -- File is not executable. - */ -define('FILE_NOT_EXECUTABLE', 128); - -/** - * Initialize the update system by loading all installed module's .install files. - */ -function drupal_load_updates() { - foreach (drupal_get_installed_schema_version(NULL, FALSE, TRUE) as $module => $schema_version) { - if ($schema_version > -1) { - module_load_install($module); - } - } -} - -/** - * Returns an array of available schema versions for a module. - * - * @param $module - * A module name. - * @return - * If the module has updates, an array of available updates sorted by version. - * Otherwise, FALSE. - */ -function drupal_get_schema_versions($module) { - $updates = &drupal_static(__FUNCTION__, NULL); - if (!isset($updates[$module])) { - $updates = array(); - - foreach (module_list() as $loaded_module) { - $updates[$loaded_module] = array(); - } - - // Prepare regular expression to match all possible defined hook_update_N(). - $regexp = '/^(?P<module>.+)_update_(?P<version>\d+)$/'; - $functions = get_defined_functions(); - // Narrow this down to functions ending with an integer, since all - // hook_update_N() functions end this way, and there are other - // possible functions which match '_update_'. We use preg_grep() here - // instead of foreaching through all defined functions, since the loop - // through all PHP functions can take significant page execution time - // and this function is called on every administrative page via - // system_requirements(). - foreach (preg_grep('/_\d+$/', $functions['user']) as $function) { - // If this function is a module update function, add it to the list of - // module updates. - if (preg_match($regexp, $function, $matches)) { - $updates[$matches['module']][] = $matches['version']; - } - } - // Ensure that updates are applied in numerical order. - foreach ($updates as &$module_updates) { - sort($module_updates, SORT_NUMERIC); - } - } - return empty($updates[$module]) ? FALSE : $updates[$module]; -} - -/** - * Returns the currently installed schema version for a module. - * - * @param $module - * A module name. - * @param $reset - * Set to TRUE after modifying the system table. - * @param $array - * Set to TRUE if you want to get information about all modules in the - * system. - * @return - * The currently installed schema version, or SCHEMA_UNINSTALLED if the - * module is not installed. - */ -function drupal_get_installed_schema_version($module, $reset = FALSE, $array = FALSE) { - static $versions = array(); - - if ($reset) { - $versions = array(); - } - - if (!$versions) { - $versions = array(); - $result = db_query("SELECT name, schema_version FROM {system} WHERE type = :type", array(':type' => 'module')); - foreach ($result as $row) { - $versions[$row->name] = $row->schema_version; - } - } - - if ($array) { - return $versions; - } - else { - return isset($versions[$module]) ? $versions[$module] : SCHEMA_UNINSTALLED; - } -} - -/** - * Update the installed version information for a module. - * - * @param $module - * A module name. - * @param $version - * The new schema version. - */ -function drupal_set_installed_schema_version($module, $version) { - db_update('system') - ->fields(array('schema_version' => $version)) - ->condition('name', $module) - ->execute(); - - // Reset the static cache of module schema versions. - drupal_get_installed_schema_version(NULL, TRUE); -} - -/** - * Loads the install profile, extracting its defined distribution name. - * - * @return - * The distribution name defined in the profile's .info file. Defaults to - * "Drupal" if none is explicitly provided by the install profile. - * - * @see install_profile_info() - */ -function drupal_install_profile_distribution_name() { - // During installation, the profile information is stored in the global - // installation state (it might not be saved anywhere yet). - if (drupal_installation_attempted()) { - global $install_state; - return $install_state['profile_info']['distribution_name']; - } - // At all other times, we load the profile via standard methods. - else { - $profile = drupal_get_profile(); - $info = system_get_info('module', $profile); - return $info['distribution_name']; - } -} - -/** - * Auto detect the base_url with PHP predefined variables. - * - * @param $file - * The name of the file calling this function so we can strip it out of - * the URI when generating the base_url. - * @return - * The auto-detected $base_url that should be configured in settings.php - */ -function drupal_detect_baseurl($file = 'core/install.php') { - $proto = $_SERVER['HTTPS'] ? 'https://' : 'http://'; - $host = $_SERVER['SERVER_NAME']; - $port = ($_SERVER['SERVER_PORT'] == 80 ? '' : ':' . $_SERVER['SERVER_PORT']); - $uri = preg_replace("/\?.*/", '', $_SERVER['REQUEST_URI']); - $dir = str_replace("/$file", '', $uri); - - return "$proto$host$port$dir"; -} - -/** - * Detect all supported databases that are compiled into PHP. - * - * @return - * An array of database types compiled into PHP. - */ -function drupal_detect_database_types() { - $databases = drupal_get_database_types(); - - foreach ($databases as $driver => $installer) { - $databases[$driver] = $installer->name(); - } - - return $databases; -} - -/** - * Return all supported database installer objects that are compiled into PHP. - * - * @return - * An array of database installer objects compiled into PHP. - */ -function drupal_get_database_types() { - $databases = array(); - - // We define a driver as a directory in /core/includes/database that in turn - // contains a database.inc file. That allows us to drop in additional drivers - // without modifying the installer. - // Because we have no registry yet, we need to also include the install.inc - // file for the driver explicitly. - require_once DRUPAL_ROOT . '/core/includes/database/database.inc'; - foreach (file_scan_directory(DRUPAL_ROOT . '/core/includes/database', '/^[a-z]*$/i', array('recurse' => FALSE)) as $file) { - if (file_exists($file->uri . '/database.inc') && file_exists($file->uri . '/install.inc')) { - $drivers[$file->filename] = $file->uri; - } - } - - foreach ($drivers as $driver => $file) { - $installer = db_installer_object($driver); - if ($installer->installable()) { - $databases[$driver] = $installer; - } - } - - // Usability: unconditionally put the MySQL driver on top. - if (isset($databases['mysql'])) { - $mysql_database = $databases['mysql']; - unset($databases['mysql']); - $databases = array('mysql' => $mysql_database) + $databases; - } - - return $databases; -} - -/** - * Database installer structure. - * - * Defines basic Drupal requirements for databases. - */ -abstract class DatabaseTasks { - - /** - * Structure that describes each task to run. - * - * @var array - * - * Each value of the tasks array is an associative array defining the function - * to call (optional) and any arguments to be passed to the function. - */ - protected $tasks = array( - array( - 'function' => 'checkEngineVersion', - 'arguments' => array(), - ), - array( - 'arguments' => array( - 'CREATE TABLE {drupal_install_test} (id int NULL)', - 'Drupal can use CREATE TABLE database commands.', - 'Failed to <strong>CREATE</strong> a test table on your database server with the command %query. The server reports the following message: %error.<p>Are you sure the configured username has the necessary permissions to create tables in the database?</p>', - TRUE, - ), - ), - array( - 'arguments' => array( - 'INSERT INTO {drupal_install_test} (id) VALUES (1)', - 'Drupal can use INSERT database commands.', - 'Failed to <strong>INSERT</strong> a value into a test table on your database server. We tried inserting a value with the command %query and the server reported the following error: %error.', - ), - ), - array( - 'arguments' => array( - 'UPDATE {drupal_install_test} SET id = 2', - 'Drupal can use UPDATE database commands.', - 'Failed to <strong>UPDATE</strong> a value in a test table on your database server. We tried updating a value with the command %query and the server reported the following error: %error.', - ), - ), - array( - 'arguments' => array( - 'DELETE FROM {drupal_install_test}', - 'Drupal can use DELETE database commands.', - 'Failed to <strong>DELETE</strong> a value from a test table on your database server. We tried deleting a value with the command %query and the server reported the following error: %error.', - ), - ), - array( - 'arguments' => array( - 'DROP TABLE {drupal_install_test}', - 'Drupal can use DROP TABLE database commands.', - 'Failed to <strong>DROP</strong> a test table from your database server. We tried dropping a table with the command %query and the server reported the following error %error.', - ), - ), - ); - - /** - * Results from tasks. - * - * @var array - */ - protected $results = array(); - - /** - * Ensure the PDO driver is supported by the version of PHP in use. - */ - protected function hasPdoDriver() { - return in_array($this->pdoDriver, PDO::getAvailableDrivers()); - } - - /** - * Assert test as failed. - */ - protected function fail($message) { - $this->results[$message] = FALSE; - } - - /** - * Assert test as a pass. - */ - protected function pass($message) { - $this->results[$message] = TRUE; - } - - /** - * Check whether Drupal is installable on the database. - */ - public function installable() { - return $this->hasPdoDriver() && empty($this->error); - } - - /** - * Return the human-readable name of the driver. - */ - abstract public function name(); - - /** - * Return the minimum required version of the engine. - * - * @return - * A version string. If not NULL, it will be checked against the version - * reported by the Database engine using version_compare(). - */ - public function minimumVersion() { - return NULL; - } - - /** - * Run database tasks and tests to see if Drupal can run on the database. - */ - public function runTasks() { - // We need to establish a connection before we can run tests. - if ($this->connect()) { - foreach ($this->tasks as $task) { - if (!isset($task['function'])) { - $task['function'] = 'runTestQuery'; - } - if (method_exists($this, $task['function'])) { - // Returning false is fatal. No other tasks can run. - if (FALSE === call_user_func_array(array($this, $task['function']), $task['arguments'])) { - break; - } - } - else { - throw new DatabaseTaskException(st("Failed to run all tasks against the database server. The task %task wasn't found.", array('%task' => $task['function']))); - } - } - } - // Check for failed results and compile message - $message = ''; - foreach ($this->results as $result => $success) { - if (!$success) { - $message .= '<p class="error">' . $result . '</p>'; - } - } - if (!empty($message)) { - $message = '<p>In order for Drupal to work, and to continue with the installation process, you must resolve all issues reported below. For more help with configuring your database server, see the <a href="http://drupal.org/getting-started/install">installation handbook</a>. If you are unsure what any of this means you should probably contact your hosting provider.</p>' . $message; - throw new DatabaseTaskException($message); - } - } - - /** - * Check if we can connect to the database. - */ - protected function connect() { - try { - // This doesn't actually test the connection. - db_set_active(); - // Now actually do a check. - Database::getConnection(); - $this->pass('Drupal can CONNECT to the database ok.'); - } - catch (Exception $e) { - $this->fail(st('Failed to connect to your database server. The server reports the following message: %error.<ul><li>Is the database server running?</li><li>Does the database exist, and have you entered the correct database name?</li><li>Have you entered the correct username and password?</li><li>Have you entered the correct database hostname?</li></ul>', array('%error' => $e->getMessage()))); - return FALSE; - } - return TRUE; - } - - /** - * Run SQL tests to ensure the database can execute commands with the current user. - */ - protected function runTestQuery($query, $pass, $fail, $fatal = FALSE) { - try { - db_query($query); - $this->pass(st($pass)); - } - catch (Exception $e) { - $this->fail(st($fail, array('%query' => $query, '%error' => $e->getMessage(), '%name' => $this->name()))); - return !$fatal; - } - } - - /** - * Check the engine version. - */ - protected function checkEngineVersion() { - if ($this->minimumVersion() && version_compare(Database::getConnection()->version(), $this->minimumVersion(), '<')) { - $this->fail(st("The database version %version is less than the minimum required version %minimum_version.", array('%version' => Database::getConnection()->version(), '%minimum_version' => $this->minimumVersion()))); - } - } - - /** - * Return driver specific configuration options. - * - * @param $database - * An array of driver specific configuration options. - * - * @return - * The options form array. - */ - public function getFormOptions($database) { - $form['database'] = array( - '#type' => 'textfield', - '#title' => st('Database name'), - '#default_value' => empty($database['database']) ? '' : $database['database'], - '#size' => 45, - '#required' => TRUE, - '#description' => st('The name of the database your @drupal data will be stored in. It must exist on your server before @drupal can be installed.', array('@drupal' => drupal_install_profile_distribution_name())), - ); - - $form['username'] = array( - '#type' => 'textfield', - '#title' => st('Database username'), - '#default_value' => empty($database['username']) ? '' : $database['username'], - '#required' => TRUE, - '#size' => 45, - ); - - $form['password'] = array( - '#type' => 'password', - '#title' => st('Database password'), - '#default_value' => empty($database['password']) ? '' : $database['password'], - '#required' => FALSE, - '#size' => 45, - ); - - $form['advanced_options'] = array( - '#type' => 'fieldset', - '#title' => st('Advanced options'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#description' => st("These options are only necessary for some sites. If you're not sure what you should enter here, leave the default settings or check with your hosting provider."), - '#weight' => 10, - ); - - $profile = drupal_get_profile(); - $db_prefix = ($profile == 'standard') ? 'drupal_' : $profile . '_'; - $form['advanced_options']['db_prefix'] = array( - '#type' => 'textfield', - '#title' => st('Table prefix'), - '#default_value' => '', - '#size' => 45, - '#description' => st('If more than one application will be sharing this database, enter a table prefix such as %prefix for your @drupal site here.', array('@drupal' => drupal_install_profile_distribution_name(), '%prefix' => $db_prefix)), - '#weight' => 10, - ); - - $form['advanced_options']['host'] = array( - '#type' => 'textfield', - '#title' => st('Database host'), - '#default_value' => empty($database['host']) ? 'localhost' : $database['host'], - '#size' => 45, - // Hostnames can be 255 characters long. - '#maxlength' => 255, - '#required' => TRUE, - '#description' => st('If your database is located on a different server, change this.'), - ); - - $form['advanced_options']['port'] = array( - '#type' => 'textfield', - '#title' => st('Database port'), - '#default_value' => empty($database['port']) ? '' : $database['port'], - '#size' => 45, - // The maximum port number is 65536, 5 digits. - '#maxlength' => 5, - '#description' => st('If your database server is listening to a non-standard port, enter its number.'), - ); - - return $form; - } - - /** - * Validates driver specific configuration settings. - * - * Checks to ensure correct basic database settings and that a proper - * connection to the database can be established. - * - * @param $database - * An array of driver specific configuration options. - * - * @return - * An array of driver configuration errors, keyed by form element name. - */ - public function validateDatabaseSettings($database) { - $errors = array(); - - // Verify the table prefix. - if (!empty($database['prefix']) && is_string($database['prefix']) && !preg_match('/^[A-Za-z0-9_.]+$/', $database['prefix'])) { - $errors[$database['driver'] . '][advanced_options][db_prefix'] = st('The database table prefix you have entered, %prefix, is invalid. The table prefix can only contain alphanumeric characters, periods, or underscores.', array('%prefix' => $database['prefix'])); - } - - // Verify the database port. - if (!empty($database['port']) && !is_numeric($database['port'])) { - $errors[$database['driver'] . '][advanced_options][port'] = st('Database port must be a number.'); - } - - return $errors; - } - -} - -/** - * Exception thrown if the database installer fails. - */ -class DatabaseTaskException extends Exception { -} - -/** - * Replace values in settings.php with values in the submitted array. - * - * @param $settings - * An array of settings that need to be updated. - */ -function drupal_rewrite_settings($settings = array()) { - $default_settings = 'sites/default/default.settings.php'; - drupal_static_reset('conf_path'); - $settings_file = conf_path(FALSE) . '/settings.php'; - - // Build list of setting names and insert the values into the global namespace. - $keys = array(); - foreach ($settings as $setting => $data) { - $GLOBALS[$setting] = $data['value']; - $keys[] = $setting; - } - - $buffer = NULL; - $first = TRUE; - if ($fp = fopen(DRUPAL_ROOT . '/' . $default_settings, 'r')) { - // Step line by line through settings.php. - while (!feof($fp)) { - $line = fgets($fp); - if ($first && substr($line, 0, 5) != '<?php') { - $buffer = "<?php\n\n"; - } - $first = FALSE; - // Check for constants. - if (substr($line, 0, 7) == 'define(') { - preg_match('/define\(\s*[\'"]([A-Z_-]+)[\'"]\s*,(.*?)\);/', $line, $variable); - if (in_array($variable[1], $keys)) { - $setting = $settings[$variable[1]]; - $buffer .= str_replace($variable[2], " '" . $setting['value'] . "'", $line); - unset($settings[$variable[1]]); - unset($settings[$variable[2]]); - } - else { - $buffer .= $line; - } - } - // Check for variables. - elseif (substr($line, 0, 1) == '$') { - preg_match('/\$([^ ]*) /', $line, $variable); - if (in_array($variable[1], $keys)) { - // Write new value to settings.php in the following format: - // $'setting' = 'value'; // 'comment' - $setting = $settings[$variable[1]]; - $buffer .= '$' . $variable[1] . " = " . var_export($setting['value'], TRUE) . ";" . (!empty($setting['comment']) ? ' // ' . $setting['comment'] . "\n" : "\n"); - unset($settings[$variable[1]]); - } - else { - $buffer .= $line; - } - } - else { - $buffer .= $line; - } - } - fclose($fp); - - // Add required settings that were missing from settings.php. - foreach ($settings as $setting => $data) { - if ($data['required']) { - $buffer .= "\$$setting = " . var_export($data['value'], TRUE) . ";\n"; - } - } - - $fp = fopen(DRUPAL_ROOT . '/' . $settings_file, 'w'); - if ($fp && fwrite($fp, $buffer) === FALSE) { - throw new Exception(st('Failed to modify %settings. Verify the file permissions.', array('%settings' => $settings_file))); - } - } - else { - throw new Exception(st('Failed to open %settings. Verify the file permissions.', array('%settings' => $default_settings))); - } -} - -/** - * Verify an install profile for installation. - * - * @param $install_state - * An array of information about the current installation state. - * @return - * The list of modules to install. - */ -function drupal_verify_profile($install_state) { - $profile = $install_state['parameters']['profile']; - $locale = $install_state['parameters']['locale']; - - include_once DRUPAL_ROOT . '/core/includes/file.inc'; - include_once DRUPAL_ROOT . '/core/includes/common.inc'; - - $profile_file = DRUPAL_ROOT . "/profiles/$profile/$profile.profile"; - - if (!isset($profile) || !file_exists($profile_file)) { - throw new Exception(install_no_profile_error()); - } - $info = $install_state['profile_info']; - - // Get a list of modules that exist in Drupal's assorted subdirectories. - $present_modules = array(); - foreach (drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.module$/', 'modules', 'name', 0) as $present_module) { - $present_modules[] = $present_module->name; - } - - // The install profile is also a module, which needs to be installed after all the other dependencies - // have been installed. - $present_modules[] = drupal_get_profile(); - - // Verify that all of the profile's required modules are present. - $missing_modules = array_diff($info['dependencies'], $present_modules); - - $requirements = array(); - - if (count($missing_modules)) { - $modules = array(); - foreach ($missing_modules as $module) { - $modules[] = '<span class="admin-missing">' . drupal_ucfirst($module) . '</span>'; - } - $requirements['required_modules'] = array( - 'title' => st('Required modules'), - 'value' => st('Required modules not found.'), - 'severity' => REQUIREMENT_ERROR, - 'description' => st('The following modules are required but were not found. Move them into the appropriate modules subdirectory, such as <em>sites/all/modules</em>. Missing modules: !modules', array('!modules' => implode(', ', $modules))), - ); - } - return $requirements; -} - -/** - * Callback to install the system module. - * - * Separated from the installation of other modules so core system - * functions can be made available while other modules are installed. - */ -function drupal_install_system() { - $system_path = drupal_get_path('module', 'system'); - require_once DRUPAL_ROOT . '/' . $system_path . '/system.install'; - module_invoke('system', 'install'); - - $system_versions = drupal_get_schema_versions('system'); - $system_version = $system_versions ? max($system_versions) : SCHEMA_INSTALLED; - db_insert('system') - ->fields(array('filename', 'name', 'type', 'owner', 'status', 'schema_version', 'bootstrap')) - ->values(array( - 'filename' => $system_path . '/system.module', - 'name' => 'system', - 'type' => 'module', - 'owner' => '', - 'status' => 1, - 'schema_version' => $system_version, - 'bootstrap' => 0, - )) - ->execute(); - system_rebuild_module_data(); -} - -/** - * Uninstalls a given list of modules. - * - * @param $module_list - * The modules to uninstall. - * @param $uninstall_dependents - * If TRUE, the function will check that all modules which depend on the - * passed-in module list either are already uninstalled or contained in the - * list, and it will ensure that the modules are uninstalled in the correct - * order. This incurs a significant performance cost, so use FALSE if you - * know $module_list is already complete and in the correct order. - * - * @return - * FALSE if one or more dependent modules are missing from the list, TRUE - * otherwise. - */ -function drupal_uninstall_modules($module_list = array(), $uninstall_dependents = TRUE) { - if ($uninstall_dependents) { - // Get all module data so we can find dependents and sort. - $module_data = system_rebuild_module_data(); - // Create an associative array with weights as values. - $module_list = array_flip(array_values($module_list)); - - $profile = drupal_get_profile(); - while (list($module) = each($module_list)) { - if (!isset($module_data[$module]) || drupal_get_installed_schema_version($module) == SCHEMA_UNINSTALLED) { - // This module doesn't exist or is already uninstalled, skip it. - unset($module_list[$module]); - continue; - } - $module_list[$module] = $module_data[$module]->sort; - - // If the module has any dependents which are not already uninstalled and - // not included in the passed-in list, abort. It is not safe to uninstall - // them automatically because uninstalling a module is a destructive - // operation. - foreach (array_keys($module_data[$module]->required_by) as $dependent) { - if (!isset($module_list[$dependent]) && drupal_get_installed_schema_version($dependent) != SCHEMA_UNINSTALLED && $dependent != $profile) { - return FALSE; - } - } - } - - // Sort the module list by pre-calculated weights. - asort($module_list); - $module_list = array_keys($module_list); - } - - foreach ($module_list as $module) { - // Uninstall the module. - module_load_install($module); - module_invoke($module, 'uninstall'); - drupal_uninstall_schema($module); - - watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO); - drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED); - } - - if (!empty($module_list)) { - // Call hook_module_uninstall to let other modules act - module_invoke_all('modules_uninstalled', $module_list); - } - - return TRUE; -} - -/** - * Verify the state of the specified file. - * - * @param $file - * The file to check for. - * @param $mask - * An optional bitmask created from various FILE_* constants. - * @param $type - * The type of file. Can be file (default), dir, or link. - * @return - * TRUE on success or FALSE on failure. A message is set for the latter. - */ -function drupal_verify_install_file($file, $mask = NULL, $type = 'file') { - $return = TRUE; - // Check for files that shouldn't be there. - if (isset($mask) && ($mask & FILE_NOT_EXIST) && file_exists($file)) { - return FALSE; - } - // Verify that the file is the type of file it is supposed to be. - if (isset($type) && file_exists($file)) { - $check = 'is_' . $type; - if (!function_exists($check) || !$check($file)) { - $return = FALSE; - } - } - - // Verify file permissions. - if (isset($mask)) { - $masks = array(FILE_EXIST, FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE); - foreach ($masks as $current_mask) { - if ($mask & $current_mask) { - switch ($current_mask) { - case FILE_EXIST: - if (!file_exists($file)) { - if ($type == 'dir') { - drupal_install_mkdir($file, $mask); - } - if (!file_exists($file)) { - $return = FALSE; - } - } - break; - case FILE_READABLE: - if (!is_readable($file) && !drupal_install_fix_file($file, $mask)) { - $return = FALSE; - } - break; - case FILE_WRITABLE: - if (!is_writable($file) && !drupal_install_fix_file($file, $mask)) { - $return = FALSE; - } - break; - case FILE_EXECUTABLE: - if (!is_executable($file) && !drupal_install_fix_file($file, $mask)) { - $return = FALSE; - } - break; - case FILE_NOT_READABLE: - if (is_readable($file) && !drupal_install_fix_file($file, $mask)) { - $return = FALSE; - } - break; - case FILE_NOT_WRITABLE: - if (is_writable($file) && !drupal_install_fix_file($file, $mask)) { - $return = FALSE; - } - break; - case FILE_NOT_EXECUTABLE: - if (is_executable($file) && !drupal_install_fix_file($file, $mask)) { - $return = FALSE; - } - break; - } - } - } - } - return $return; -} - -/** - * Create a directory with specified permissions. - * - * @param $file - * The name of the directory to create; - * @param $mask - * The permissions of the directory to create. - * @param $message - * (optional) Whether to output messages. Defaults to TRUE. - * @return - * TRUE/FALSE whether or not the directory was successfully created. - */ -function drupal_install_mkdir($file, $mask, $message = TRUE) { - $mod = 0; - $masks = array(FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE); - foreach ($masks as $m) { - if ($mask & $m) { - switch ($m) { - case FILE_READABLE: - $mod |= 0444; - break; - case FILE_WRITABLE: - $mod |= 0222; - break; - case FILE_EXECUTABLE: - $mod |= 0111; - break; - } - } - } - - if (@drupal_mkdir($file, $mod)) { - return TRUE; - } - else { - return FALSE; - } -} - -/** - * Attempt to fix file permissions. - * - * The general approach here is that, because we do not know the security - * setup of the webserver, we apply our permission changes to all three - * digits of the file permission (i.e. user, group and all). - * - * To ensure that the values behave as expected (and numbers don't carry - * from one digit to the next) we do the calculation on the octal value - * using bitwise operations. This lets us remove, for example, 0222 from - * 0700 and get the correct value of 0500. - * - * @param $file - * The name of the file with permissions to fix. - * @param $mask - * The desired permissions for the file. - * @param $message - * (optional) Whether to output messages. Defaults to TRUE. - * @return - * TRUE/FALSE whether or not we were able to fix the file's permissions. - */ -function drupal_install_fix_file($file, $mask, $message = TRUE) { - // If $file does not exist, fileperms() issues a PHP warning. - if (!file_exists($file)) { - return FALSE; - } - - $mod = fileperms($file) & 0777; - $masks = array(FILE_READABLE, FILE_WRITABLE, FILE_EXECUTABLE, FILE_NOT_READABLE, FILE_NOT_WRITABLE, FILE_NOT_EXECUTABLE); - - // FILE_READABLE, FILE_WRITABLE, and FILE_EXECUTABLE permission strings - // can theoretically be 0400, 0200, and 0100 respectively, but to be safe - // we set all three access types in case the administrator intends to - // change the owner of settings.php after installation. - foreach ($masks as $m) { - if ($mask & $m) { - switch ($m) { - case FILE_READABLE: - if (!is_readable($file)) { - $mod |= 0444; - } - break; - case FILE_WRITABLE: - if (!is_writable($file)) { - $mod |= 0222; - } - break; - case FILE_EXECUTABLE: - if (!is_executable($file)) { - $mod |= 0111; - } - break; - case FILE_NOT_READABLE: - if (is_readable($file)) { - $mod &= ~0444; - } - break; - case FILE_NOT_WRITABLE: - if (is_writable($file)) { - $mod &= ~0222; - } - break; - case FILE_NOT_EXECUTABLE: - if (is_executable($file)) { - $mod &= ~0111; - } - break; - } - } - } - - // chmod() will work if the web server is running as owner of the file. - // If PHP safe_mode is enabled the currently executing script must also - // have the same owner. - if (@chmod($file, $mod)) { - return TRUE; - } - else { - return FALSE; - } -} - -/** - * Send the user to a different installer page. - * - * This issues an on-site HTTP redirect. Messages (and errors) are erased. - * - * @param $path - * An installer path. - */ -function install_goto($path) { - global $base_url; - include_once DRUPAL_ROOT . '/core/includes/common.inc'; - header('Location: ' . $base_url . '/' . $path); - header('Cache-Control: no-cache'); // Not a permanent redirect. - drupal_exit(); -} - -/** - * Returns the URL of the current script, with modified query parameters. - * - * This function can be called by low-level scripts (such as install.php and - * update.php) and returns the URL of the current script. Existing query - * parameters are preserved by default, but new ones can optionally be merged - * in. - * - * This function is used when the script must maintain certain query parameters - * over multiple page requests in order to work correctly. In such cases (for - * example, update.php, which requires the 'continue=1' parameter to remain in - * the URL throughout the update process if there are any requirement warnings - * that need to be bypassed), using this function to generate the URL for links - * to the next steps of the script ensures that the links will work correctly. - * - * @param $query - * (optional) An array of query parameters to merge in to the existing ones. - * - * @return - * The URL of the current script, with query parameters modified by the - * passed-in $query. The URL is not sanitized, so it still needs to be run - * through check_url() if it will be used as an HTML attribute value. - * - * @see drupal_requirements_url() - */ -function drupal_current_script_url($query = array()) { - $uri = $_SERVER['SCRIPT_NAME']; - $query = array_merge(drupal_get_query_parameters(), $query); - if (!empty($query)) { - $uri .= '?' . drupal_http_build_query($query); - } - return $uri; -} - -/** - * Returns a URL for proceeding to the next page after a requirements problem. - * - * This function can be called by low-level scripts (such as install.php and - * update.php) and returns a URL that can be used to attempt to proceed to the - * next step of the script. - * - * @param $severity - * The severity of the requirements problem, as returned by - * drupal_requirements_severity(). - * - * @return - * A URL for attempting to proceed to the next step of the script. The URL is - * not sanitized, so it still needs to be run through check_url() if it will - * be used as an HTML attribute value. - * - * @see drupal_current_script_url() - */ -function drupal_requirements_url($severity) { - $query = array(); - // If there are no errors, only warnings, append 'continue=1' to the URL so - // the user can bypass this screen on the next page load. - if ($severity == REQUIREMENT_WARNING) { - $query['continue'] = 1; - } - return drupal_current_script_url($query); -} - -/** - * Functional equivalent of t(), used when some systems are not available. - * - * Used during the install process, when database, theme, and localization - * system is possibly not yet available. - * - * 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 get_t() - * @ingroup sanitization - */ -function st($string, array $args = array(), array $options = array()) { - static $locale_strings = NULL; - global $install_state; - - if (empty($options['context'])) { - $options['context'] = ''; - } - - if (!isset($locale_strings)) { - $locale_strings = array(); - if (isset($install_state['parameters']['profile']) && isset($install_state['parameters']['locale'])) { - // If the given locale was selected, there should be at least one .po file - // with its name ending in install.{$install_state['parameters']['locale']}.po - // This might or might not be the entire filename. It is also possible - // that multiple files end with the same extension, even if unlikely. - $locale_files = install_find_locale_files($install_state['parameters']['locale']); - if (!empty($locale_files)) { - require_once DRUPAL_ROOT . '/core/includes/gettext.inc'; - foreach ($locale_files as $locale_file) { - _locale_import_read_po('mem-store', $locale_file); - } - $locale_strings = _locale_import_one_string('mem-report'); - } - } - } - - require_once DRUPAL_ROOT . '/core/includes/theme.inc'; - // Transform arguments before inserting them - foreach ($args as $key => $value) { - switch ($key[0]) { - // Escaped only - case '@': - $args[$key] = check_plain($value); - break; - // Escaped and placeholder - case '%': - default: - $args[$key] = '<em>' . check_plain($value) . '</em>'; - break; - // Pass-through - case '!': - } - } - return strtr((!empty($locale_strings[$options['context']][$string]) ? $locale_strings[$options['context']][$string] : $string), $args); -} - -/** - * Check an install profile's requirements. - * - * @param $profile - * Name of install profile to check. - * @return - * Array of the install profile's requirements. - */ -function drupal_check_profile($profile) { - include_once DRUPAL_ROOT . '/core/includes/file.inc'; - - $profile_file = DRUPAL_ROOT . "/profiles/$profile/$profile.profile"; - - if (!isset($profile) || !file_exists($profile_file)) { - throw new Exception(install_no_profile_error()); - } - - $info = install_profile_info($profile); - - // Collect requirement testing results. - $requirements = array(); - foreach ($info['dependencies'] as $module) { - module_load_install($module); - $function = $module . '_requirements'; - if (function_exists($function)) { - $requirements = array_merge($requirements, $function('install')); - } - } - return $requirements; -} - -/** - * Extract highest severity from requirements array. - * - * @param $requirements - * An array of requirements, in the same format as is returned by - * hook_requirements(). - * @return - * The highest severity in the array. - */ -function drupal_requirements_severity(&$requirements) { - $severity = REQUIREMENT_OK; - foreach ($requirements as $requirement) { - if (isset($requirement['severity'])) { - $severity = max($severity, $requirement['severity']); - } - } - return $severity; -} - -/** - * Check a module's requirements. - * - * @param $module - * Machine name of module to check. - * @return - * TRUE/FALSE depending on the requirements are in place. - */ -function drupal_check_module($module) { - module_load_install($module); - if (module_hook($module, 'requirements')) { - // Check requirements - $requirements = module_invoke($module, 'requirements', 'install'); - if (is_array($requirements) && drupal_requirements_severity($requirements) == REQUIREMENT_ERROR) { - // Print any error messages - foreach ($requirements as $requirement) { - if (isset($requirement['severity']) && $requirement['severity'] == REQUIREMENT_ERROR) { - $message = $requirement['description']; - if (isset($requirement['value']) && $requirement['value']) { - $message .= ' (' . t('Currently using !item !version', array('!item' => $requirement['title'], '!version' => $requirement['value'])) . ')'; - } - drupal_set_message($message, 'error'); - } - } - return FALSE; - } - } - return TRUE; -} - -/** - * Retrieve info about an install profile from its .info file. - * - * The information stored in a profile .info file is similar to that stored in - * a normal Drupal module .info file. For example: - * - name: The real name of the install profile for display purposes. - * - description: A brief description of the profile. - * - dependencies: An array of shortnames of other modules this install profile requires. - * - * Additional, less commonly-used information that can appear in a profile.info - * file but not in a normal Drupal module .info file includes: - * - distribution_name: The name of the Drupal distribution that is being - * installed, to be shown throughout the installation process. Defaults to - * 'Drupal'. - * - * Note that this function does an expensive file system scan to get info file - * information for dependencies. If you only need information from the info - * file itself, use system_get_info(). - * - * Example of .info file: - * @code - * name = Minimal - * description = Start fresh, with only a few modules enabled. - * dependencies[] = block - * dependencies[] = dblog - * @endcode - * - * @param $profile - * Name of profile. - * @param $locale - * Name of locale used (if any). - * - * @return - * The info array. - */ -function install_profile_info($profile, $locale = 'en') { - $cache = &drupal_static(__FUNCTION__, array()); - - if (!isset($cache[$profile])) { - // Set defaults for module info. - $defaults = array( - 'dependencies' => array(), - 'description' => '', - 'distribution_name' => 'Drupal', - 'version' => NULL, - 'hidden' => FALSE, - 'php' => DRUPAL_MINIMUM_PHP, - ); - $info = drupal_parse_info_file("profiles/$profile/$profile.info") + $defaults; - $info['dependencies'] = array_unique(array_merge( - drupal_required_modules(), - $info['dependencies'], - ($locale != 'en' && !empty($locale) ? array('locale') : array())) - ); - - // drupal_required_modules() includes the current profile as a dependency. - // Since a module can't depend on itself we remove that element of the array. - array_shift($info['dependencies']); - - $cache[$profile] = $info; - } - return $cache[$profile]; -} - -/** - * Ensures the environment for a Drupal database on a predefined connection. - * - * This will run tasks that check that Drupal can perform all of the functions - * on a database, that Drupal needs. Tasks include simple checks like CREATE - * TABLE to database specific functions like stored procedures and client - * encoding. - */ -function db_run_tasks($driver) { - db_installer_object($driver)->runTasks(); - return TRUE; -} - -/** - * Returns a database installer object. - * - * @param $driver - * The name of the driver. - */ -function db_installer_object($driver) { - Database::loadDriverFile($driver, array('install.inc')); - $task_class = 'DatabaseTasks_' . $driver; - return new $task_class(); -} diff --git a/core/includes/language.inc b/core/includes/language.inc deleted file mode 100644 index 685a4d44a00..00000000000 --- a/core/includes/language.inc +++ /dev/null @@ -1,468 +0,0 @@ -<?php - -/** - * @file - * Multiple language handling functionality. - */ - -/** - * No language negotiation. The default language is used. - */ -define('LANGUAGE_NEGOTIATION_DEFAULT', 'language-default'); - -/** - * Return all the defined language types. - * - * @return - * An array of language type names. The name will be used as the global - * variable name the language value will be stored in. - */ -function language_types_info() { - $language_types = &drupal_static(__FUNCTION__); - - if (!isset($language_types)) { - $language_types = module_invoke_all('language_types_info'); - // Let other modules alter the list of language types. - drupal_alter('language_types_info', $language_types); - } - - return $language_types; -} - -/** - * Return only the configurable language types. - * - * A language type maybe configurable or fixed. A fixed language type is a type - * whose negotiation values are unchangeable and defined while defining the - * language type itself. - * - * @param $stored - * Optional. By default retrieves values from the 'language_types' variable to - * avoid unnecessary hook invocations. - * If set to FALSE retrieves values from the actual language type definitions. - * This allows to react to alterations performed on the definitions by modules - * installed after the 'language_types' variable is set. - * - * @return - * An array of language type names. - */ -function language_types_configurable($stored = TRUE) { - $configurable = &drupal_static(__FUNCTION__); - - if ($stored && !isset($configurable)) { - $types = variable_get('language_types', drupal_language_types()); - $configurable = array_keys(array_filter($types)); - } - - if (!$stored) { - $result = array(); - foreach (language_types_info() as $type => $info) { - if (!isset($info['fixed'])) { - $result[] = $type; - } - } - return $result; - } - - return $configurable; -} - -/** - * Disable the given language types. - * - * @param $types - * An array of language types. - */ -function language_types_disable($types) { - $enabled_types = variable_get('language_types', drupal_language_types()); - - foreach ($types as $type) { - unset($enabled_types[$type]); - } - - variable_set('language_types', $enabled_types); -} - -/** - * Updates the language type configuration. - */ -function language_types_set() { - // Ensure that we are getting the defined language negotiation information. An - // invocation of module_enable() or module_disable() could outdate the cached - // information. - drupal_static_reset('language_types_info'); - drupal_static_reset('language_negotiation_info'); - - // Determine which language types are configurable and which not by checking - // whether the 'fixed' key is defined. Non-configurable (fixed) language types - // have their language negotiation settings stored there. - $defined_providers = language_negotiation_info(); - foreach (language_types_info() as $type => $info) { - if (isset($info['fixed'])) { - $language_types[$type] = FALSE; - $negotiation = array(); - foreach ($info['fixed'] as $weight => $id) { - if (isset($defined_providers[$id])) { - $negotiation[$id] = $weight; - } - } - language_negotiation_set($type, $negotiation); - } - else { - $language_types[$type] = TRUE; - } - } - - // Save language types. - variable_set('language_types', $language_types); - - // Ensure that subsequent calls of language_types_configurable() return the - // updated language type information. - drupal_static_reset('language_types_configurable'); -} - -/** - * Check if a language provider is enabled. - * - * This has two possible behaviors: - * - If $provider_id is given return its ID if enabled, FALSE otherwise. - * - If no ID is passed the first enabled language provider is returned. - * - * @param $type - * The language negotiation type. - * @param $provider_id - * The language provider ID. - * - * @return - * The provider ID if it is enabled, FALSE otherwise. - */ -function language_negotiation_get($type, $provider_id = NULL) { - $negotiation = variable_get("language_negotiation_$type", array()); - - if (empty($negotiation)) { - return empty($provider_id) ? LANGUAGE_NEGOTIATION_DEFAULT : FALSE; - } - - if (empty($provider_id)) { - return key($negotiation); - } - - if (isset($negotiation[$provider_id])) { - return $provider_id; - } - - return FALSE; -} - -/** - * Check if the given language provider is enabled for any configurable language - * type. - * - * @param $provider_id - * The language provider ID. - * - * @return - * TRUE if there is at least one language type for which the give language - * provider is enabled, FALSE otherwise. - */ -function language_negotiation_get_any($provider_id) { - foreach (language_types_configurable() as $type) { - if (language_negotiation_get($type, $provider_id)) { - return TRUE; - } - } - - return FALSE; -} - -/** - * Return the language switch links for the given language. - * - * @param $type - * The language negotiation type. - * @param $path - * The internal path the switch links will be relative to. - * - * @return - * A keyed array of links ready to be themed. - */ -function language_negotiation_get_switch_links($type, $path) { - $links = FALSE; - $negotiation = variable_get("language_negotiation_$type", array()); - - foreach ($negotiation as $id => $provider) { - if (isset($provider['callbacks']['switcher'])) { - if (isset($provider['file'])) { - require_once DRUPAL_ROOT . '/' . $provider['file']; - } - - $callback = $provider['callbacks']['switcher']; - $result = $callback($type, $path); - - if (!empty($result)) { - // Allow modules to provide translations for specific links. - drupal_alter('language_switch_links', $result, $type, $path); - $links = (object) array('links' => $result, 'provider' => $id); - break; - } - } - } - - return $links; -} - -/** - * Updates language configuration to remove any language provider that is no longer defined. - */ -function language_negotiation_purge() { - // Ensure that we are getting the defined language negotiation information. An - // invocation of module_enable() or module_disable() could outdate the cached - // information. - drupal_static_reset('language_negotiation_info'); - drupal_static_reset('language_types_info'); - - $defined_providers = language_negotiation_info(); - foreach (language_types_info() as $type => $type_info) { - $weight = 0; - $negotiation = array(); - foreach (variable_get("language_negotiation_$type", array()) as $id => $provider) { - if (isset($defined_providers[$id])) { - $negotiation[$id] = $weight++; - } - } - language_negotiation_set($type, $negotiation); - } -} - -/** - * Save a list of language providers. - * - * @param $type - * The language negotiation type. - * @param $language_providers - * An array of language provider weights keyed by id. - * @see language_provider_weight() - */ -function language_negotiation_set($type, $language_providers) { - // Save only the necessary fields. - $provider_fields = array('callbacks', 'file', 'cache'); - - $negotiation = array(); - $providers_weight = array(); - $defined_providers = language_negotiation_info(); - $default_types = language_types_configurable(FALSE); - - // Initialize the providers weight list. - foreach ($language_providers as $id => $provider) { - $providers_weight[$id] = language_provider_weight($provider); - } - - // Order providers list by weight. - asort($providers_weight); - - foreach ($providers_weight as $id => $weight) { - if (isset($defined_providers[$id])) { - $provider = $defined_providers[$id]; - // If the provider does not express any preference about types, make it - // available for any configurable type. - $types = array_flip(isset($provider['types']) ? $provider['types'] : $default_types); - // Check if the provider is defined and has the right type. - if (isset($types[$type])) { - $provider_data = array(); - foreach ($provider_fields as $field) { - if (isset($provider[$field])) { - $provider_data[$field] = $provider[$field]; - } - } - $negotiation[$id] = $provider_data; - } - } - } - - variable_set("language_negotiation_$type", $negotiation); -} - -/** - * Return all the defined language providers. - * - * @return - * An array of language providers. - */ -function language_negotiation_info() { - $language_providers = &drupal_static(__FUNCTION__); - - if (!isset($language_providers)) { - // Collect all the module-defined language negotiation providers. - $language_providers = module_invoke_all('language_negotiation_info'); - - // Add the default language provider. - $language_providers[LANGUAGE_NEGOTIATION_DEFAULT] = array( - 'callbacks' => array('language' => 'language_from_default'), - 'weight' => 10, - 'name' => t('Default language'), - 'description' => t('Use the default site language (@language_name).', array('@language_name' => language_default()->name)), - 'config' => 'admin/config/regional/language', - ); - - // Let other modules alter the list of language providers. - drupal_alter('language_negotiation_info', $language_providers); - } - - return $language_providers; -} - -/** - * Helper function used to cache the language providers results. - * - * @param $provider_id - * The language provider ID. - * @param $provider - * The language provider to be invoked. If not passed it will be explicitly - * loaded through language_negotiation_info(). - * - * @return - * The language provider's return value. - */ -function language_provider_invoke($provider_id, $provider = NULL) { - $results = &drupal_static(__FUNCTION__); - - if (!isset($results[$provider_id])) { - global $user; - - // Get languages grouped by status and select only the enabled ones. - $languages = language_list('enabled'); - $languages = $languages[1]; - - if (!isset($provider)) { - $providers = language_negotiation_info(); - $provider = $providers[$provider_id]; - } - - if (isset($provider['file'])) { - require_once DRUPAL_ROOT . '/' . $provider['file']; - } - - // If the language provider has no cache preference or this is satisfied - // we can execute the callback. - $cache = !isset($provider['cache']) || $user->uid || $provider['cache'] == variable_get('cache', 0); - $callback = isset($provider['callbacks']['language']) ? $provider['callbacks']['language'] : FALSE; - $langcode = $cache && function_exists($callback) ? $callback($languages) : FALSE; - $results[$provider_id] = isset($languages[$langcode]) ? $languages[$langcode] : FALSE; - } - - return $results[$provider_id]; -} - -/** - * Return the passed language provider weight or a default value. - * - * @param $provider - * A language provider data structure. - * - * @return - * A numeric weight. - */ -function language_provider_weight($provider) { - $default = is_numeric($provider) ? $provider : 0; - return isset($provider['weight']) && is_numeric($provider['weight']) ? $provider['weight'] : $default; -} - -/** - * Choose a language for the given type based on language negotiation settings. - * - * @param $type - * The language type. - * - * @return - * The negotiated language object. - */ -function language_initialize($type) { - // Execute the language providers in the order they were set up and return the - // first valid language found. - $negotiation = variable_get("language_negotiation_$type", array()); - - foreach ($negotiation as $id => $provider) { - $language = language_provider_invoke($id, $provider); - if ($language) { - return $language; - } - } - - // If no other language was found use the default one. - return language_default(); -} - -/** - * Default language provider. - * - * @return - * The default language code. - */ -function language_from_default() { - return language_default()->language; -} - -/** - * Split the given path into prefix and actual path. - * - * Parse the given path and return the language object identified by the - * prefix and the actual path. - * - * @param $path - * The path to split. - * @param $languages - * An array of valid languages. - * - * @return - * An array composed of: - * - A language object corresponding to the identified prefix on success, - * FALSE otherwise. - * - The path without the prefix on success, the given path otherwise. - */ -function language_url_split_prefix($path, $languages) { - $args = empty($path) ? array() : explode('/', $path); - $prefix = array_shift($args); - - // Search prefix within enabled languages. - foreach ($languages as $language) { - if (!empty($language->prefix) && $language->prefix == $prefix) { - // Rebuild $path with the language removed. - return array($language, implode('/', $args)); - } - } - - return array(FALSE, $path); -} - -/** - * Return the possible fallback languages ordered by language weight. - * - * @param - * The language type. - * - * @return - * An array of language codes. - */ -function language_fallback_get_candidates($type = LANGUAGE_TYPE_CONTENT) { - $fallback_candidates = &drupal_static(__FUNCTION__); - - if (!isset($fallback_candidates)) { - $fallback_candidates = array(); - - // Get languages ordered by weight. - // Use array keys to avoid duplicated entries. - foreach (language_list('weight') as $languages) { - foreach ($languages as $language) { - $fallback_candidates[$language->language] = NULL; - } - } - - $fallback_candidates = array_keys($fallback_candidates); - $fallback_candidates[] = LANGUAGE_NONE; - - // Let other modules hook in and add/change candidates. - drupal_alter('language_fallback_candidates', $fallback_candidates); - } - - return $fallback_candidates; -} diff --git a/core/includes/locale.inc b/core/includes/locale.inc deleted file mode 100644 index 6222bda70b3..00000000000 --- a/core/includes/locale.inc +++ /dev/null @@ -1,977 +0,0 @@ -<?php - -/** - * @file - * Administration functions for locale.module. - */ - -/** - * The language is determined using a URL language indicator: - * path prefix or domain according to the configuration. - */ -define('LOCALE_LANGUAGE_NEGOTIATION_URL', 'locale-url'); - -/** - * The language is set based on the browser language settings. - */ -define('LOCALE_LANGUAGE_NEGOTIATION_BROWSER', 'locale-browser'); - -/** - * The language is determined using the current interface language. - */ -define('LOCALE_LANGUAGE_NEGOTIATION_INTERFACE', 'locale-interface'); - -/** - * If no URL language is available language is determined using an already - * detected one. - */ -define('LOCALE_LANGUAGE_NEGOTIATION_URL_FALLBACK', 'locale-url-fallback'); - -/** - * The language is set based on the user language settings. - */ -define('LOCALE_LANGUAGE_NEGOTIATION_USER', 'locale-user'); - -/** - * The language is set based on the request/session parameters. - */ -define('LOCALE_LANGUAGE_NEGOTIATION_SESSION', 'locale-session'); - -/** - * Regular expression pattern used to localize JavaScript strings. - */ -define('LOCALE_JS_STRING', '(?:(?:\'(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+'); - -/** - * Regular expression pattern used to match simple JS object literal. - * - * This pattern matches a basic JS object, but will fail on an object with - * nested objects. Used in JS file parsing for string arg processing. - */ -define('LOCALE_JS_OBJECT', '\{.*?\}'); - -/** - * Regular expression to match an object containing a key 'context'. - * - * Pattern to match a JS object containing a 'context key' with a string value, - * which is captured. Will fail if there are nested objects. - */ -define('LOCALE_JS_OBJECT_CONTEXT', ' - \{ # match object literal start - .*? # match anything, non-greedy - (?: # match a form of "context" - \'context\' - | - "context" - | - context - ) - \s*:\s* # match key-value separator ":" - (' . LOCALE_JS_STRING . ') # match context string - .*? # match anything, non-greedy - \} # match end of object literal -'); - -/** - * Translation import mode overwriting all existing translations - * if new translated version available. - */ -define('LOCALE_IMPORT_OVERWRITE', 0); - -/** - * Translation import mode keeping existing translations and only - * inserting new strings. - */ -define('LOCALE_IMPORT_KEEP', 1); - -/** - * URL language negotiation: use the path prefix as URL language - * indicator. - */ -define('LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX', 0); - -/** - * URL language negotiation: use the domain as URL language - * indicator. - */ -define('LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN', 1); - -/** - * @defgroup locale-languages-negotiation Language negotiation options - * @{ - * Functions for language negotiation. - * - * There are functions that provide the ability to identify the - * language. This behavior can be controlled by various options. - */ - -/** - * Identifies the language from the current interface language. - * - * @return - * The current interface language code. - */ -function locale_language_from_interface() { - global $language; - return isset($language->language) ? $language->language : FALSE; -} - -/** - * Identify language from the Accept-language HTTP header we got. - * - * We perform browser accept-language parsing only if page cache is disabled, - * otherwise we would cache a user-specific preference. - * - * @param $languages - * An array of language objects for enabled languages ordered by weight. - * - * @return - * A valid language code on success, FALSE otherwise. - */ -function locale_language_from_browser($languages) { - if (empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { - return FALSE; - } - - // The Accept-Language header contains information about the language - // preferences configured in the user's browser / operating system. - // RFC 2616 (section 14.4) defines the Accept-Language header as follows: - // Accept-Language = "Accept-Language" ":" - // 1#( language-range [ ";" "q" "=" qvalue ] ) - // language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" ) - // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5" - $browser_langcodes = array(); - if (preg_match_all('@([a-zA-Z-]+|\*)(?:;q=([0-9.]+))?(?:$|\s*,\s*)@', trim($_SERVER['HTTP_ACCEPT_LANGUAGE']), $matches, PREG_SET_ORDER)) { - foreach ($matches as $match) { - // We can safely use strtolower() here, tags are ASCII. - // RFC2616 mandates that the decimal part is no more than three digits, - // so we multiply the qvalue by 1000 to avoid floating point comparisons. - $langcode = strtolower($match[1]); - $qvalue = isset($match[2]) ? (float) $match[2] : 1; - $browser_langcodes[$langcode] = (int) ($qvalue * 1000); - } - } - - // We should take pristine values from the HTTP headers, but Internet Explorer - // from version 7 sends only specific language tags (eg. fr-CA) without the - // corresponding generic tag (fr) unless explicitly configured. In that case, - // we assume that the lowest value of the specific tags is the value of the - // generic language to be as close to the HTTP 1.1 spec as possible. - // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 and - // http://blogs.msdn.com/b/ie/archive/2006/10/17/accept-language-header-for-internet-explorer-7.aspx - asort($browser_langcodes); - foreach ($browser_langcodes as $langcode => $qvalue) { - $generic_tag = strtok($langcode, '-'); - if (!isset($browser_langcodes[$generic_tag])) { - $browser_langcodes[$generic_tag] = $qvalue; - } - } - - // Find the enabled language with the greatest qvalue, following the rules - // of RFC 2616 (section 14.4). If several languages have the same qvalue, - // prefer the one with the greatest weight. - $best_match_langcode = FALSE; - $max_qvalue = 0; - foreach ($languages as $langcode => $language) { - // Language tags are case insensitive (RFC2616, sec 3.10). - $langcode = strtolower($langcode); - - // If nothing matches below, the default qvalue is the one of the wildcard - // language, if set, or is 0 (which will never match). - $qvalue = isset($browser_langcodes['*']) ? $browser_langcodes['*'] : 0; - - // Find the longest possible prefix of the browser-supplied language - // ('the language-range') that matches this site language ('the language tag'). - $prefix = $langcode; - do { - if (isset($browser_langcodes[$prefix])) { - $qvalue = $browser_langcodes[$prefix]; - break; - } - } - while ($prefix = substr($prefix, 0, strrpos($prefix, '-'))); - - // Find the best match. - if ($qvalue > $max_qvalue) { - $best_match_langcode = $language->language; - $max_qvalue = $qvalue; - } - } - - return $best_match_langcode; -} - -/** - * Identify language from the user preferences. - * - * @param $languages - * An array of valid language objects. - * - * @return - * A valid language code on success, FALSE otherwise. - */ -function locale_language_from_user($languages) { - // User preference (only for logged users). - global $user; - - if ($user->uid) { - return $user->language; - } - - // No language preference from the user. - return FALSE; -} - -/** - * Identify language from a request/session parameter. - * - * @param $languages - * An array of valid language objects. - * - * @return - * A valid language code on success, FALSE otherwise. - */ -function locale_language_from_session($languages) { - $param = variable_get('locale_language_negotiation_session_param', 'language'); - - // Request parameter: we need to update the session parameter only if we have - // an authenticated user. - if (isset($_GET[$param]) && isset($languages[$langcode = $_GET[$param]])) { - global $user; - if ($user->uid) { - $_SESSION[$param] = $langcode; - } - return $langcode; - } - - // Session parameter. - if (isset($_SESSION[$param])) { - return $_SESSION[$param]; - } - - return FALSE; -} - -/** - * Identify language via URL prefix or domain. - * - * @param $languages - * An array of valid language objects. - * - * @return - * A valid language code on success, FALSE otherwise. - */ -function locale_language_from_url($languages) { - $language_url = FALSE; - - if (!language_negotiation_get_any(LOCALE_LANGUAGE_NEGOTIATION_URL)) { - return $language_url; - } - - switch (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX)) { - case LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX: - // $_GET['q'] might not be available at this time, because - // path initialization runs after the language bootstrap phase. - list($language, $_GET['q']) = language_url_split_prefix(isset($_GET['q']) ? $_GET['q'] : NULL, $languages); - if ($language !== FALSE) { - $language_url = $language->language; - } - break; - - case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN: - foreach ($languages as $language) { - // Skip check if the language doesn't have a domain. - if ($language->domain) { - // Only compare the domains not the protocols or ports. - // Remove protocol and add http:// so parse_url works - $host = 'http://' . str_replace(array('http://', 'https://'), '', $language->domain); - $host = parse_url($host, PHP_URL_HOST); - if ($_SERVER['HTTP_HOST'] == $host) { - $language_url = $language->language; - break; - } - } - } - break; - } - - return $language_url; -} - -/** - * Determines the language to be assigned to URLs when none is detected. - * - * The language negotiation process has a fallback chain that ends with the - * default language provider. Each built-in language type has a separate - * initialization: - * - Interface language, which is the only configurable one, always gets a valid - * value. If no request-specific language is detected, the default language - * will be used. - * - Content language merely inherits the interface language by default. - * - URL language is detected from the requested URL and will be used to rewrite - * URLs appearing in the page being rendered. If no language can be detected, - * there are two possibilities: - * - If the default language has no configured path prefix or domain, then the - * default language is used. This guarantees that (missing) URL prefixes are - * preserved when navigating through the site. - * - If the default language has a configured path prefix or domain, a - * requested URL having an empty prefix or domain is an anomaly that must be - * fixed. This is done by introducing a prefix or domain in the rendered - * page matching the detected interface language. - * - * @param $languages - * (optional) An array of valid language objects. This is passed by - * language_provider_invoke() to every language provider callback, but it is - * not actually needed here. Defaults to NULL. - * @param $language_type - * (optional) The language type to fall back to. Defaults to the interface - * language. - * - * @return - * A valid language code. - */ -function locale_language_url_fallback($language = NULL, $language_type = LANGUAGE_TYPE_INTERFACE) { - $default = language_default(); - $prefix = (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX) == LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX); - - // If the default language is not configured to convey language information, - // a missing URL language information indicates that URL language should be - // the default one, otherwise we fall back to an already detected language. - if (($prefix && empty($default->prefix)) || (!$prefix && empty($default->domain))) { - return $default->language; - } - else { - return $GLOBALS[$language_type]->language; - } -} - -/** - * Return the URL language switcher block. Translation links may be provided by - * other modules. - */ -function locale_language_switcher_url($type, $path) { - $languages = language_list('enabled'); - $links = array(); - - foreach ($languages[1] as $language) { - $links[$language->language] = array( - 'href' => $path, - 'title' => $language->name, - 'language' => $language, - 'attributes' => array('class' => array('language-link')), - ); - } - - return $links; -} - -/** - * Return the session language switcher block. - */ -function locale_language_switcher_session($type, $path) { - drupal_add_css(drupal_get_path('module', 'locale') . '/locale.css'); - - $param = variable_get('locale_language_negotiation_session_param', 'language'); - $language_query = isset($_SESSION[$param]) ? $_SESSION[$param] : $GLOBALS[$type]->language; - - $languages = language_list('enabled'); - $links = array(); - - $query = $_GET; - unset($query['q']); - - foreach ($languages[1] as $language) { - $langcode = $language->language; - $links[$langcode] = array( - 'href' => $path, - 'title' => $language->name, - 'attributes' => array('class' => array('language-link')), - 'query' => $query, - ); - if ($language_query != $langcode) { - $links[$langcode]['query'][$param] = $langcode; - } - else { - $links[$langcode]['attributes']['class'][] = ' session-active'; - } - } - - return $links; -} - -/** - * Rewrite URLs for the URL language provider. - */ -function locale_language_url_rewrite_url(&$path, &$options) { - static $drupal_static_fast; - if (!isset($drupal_static_fast)) { - $drupal_static_fast['languages'] = &drupal_static(__FUNCTION__); - } - $languages = &$drupal_static_fast['languages']; - - if (!isset($languages)) { - $languages = language_list('enabled'); - $languages = array_flip(array_keys($languages[1])); - } - - // Language can be passed as an option, or we go for current URL language. - if (!isset($options['language'])) { - global $language_url; - $options['language'] = $language_url; - } - // We allow only enabled languages here. - elseif (!isset($languages[$options['language']->language])) { - unset($options['language']); - return; - } - - if (isset($options['language'])) { - switch (variable_get('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX)) { - case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN: - if ($options['language']->domain) { - // Ask for an absolute URL with our modified base_url. - $options['absolute'] = TRUE; - $options['base_url'] = $options['language']->domain; - } - break; - - case LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX: - if (!empty($options['language']->prefix)) { - $options['prefix'] = $options['language']->prefix . '/'; - } - break; - } - } -} - -/** - * Rewrite URLs for the Session language provider. - */ -function locale_language_url_rewrite_session(&$path, &$options) { - static $query_rewrite, $query_param, $query_value; - - // The following values are not supposed to change during a single page - // request processing. - if (!isset($query_rewrite)) { - global $user; - if (!$user->uid) { - $languages = language_list('enabled'); - $languages = $languages[1]; - $query_param = check_plain(variable_get('locale_language_negotiation_session_param', 'language')); - $query_value = isset($_GET[$query_param]) ? check_plain($_GET[$query_param]) : NULL; - $query_rewrite = isset($languages[$query_value]) && language_negotiation_get_any(LOCALE_LANGUAGE_NEGOTIATION_SESSION); - } - else { - $query_rewrite = FALSE; - } - } - - // If the user is anonymous, the user language provider is enabled, and the - // corresponding option has been set, we must preserve any explicit user - // language preference even with cookies disabled. - if ($query_rewrite) { - if (is_string($options['query'])) { - $options['query'] = drupal_get_query_array($options['query']); - } - if (!isset($options['query'][$query_param])) { - $options['query'][$query_param] = $query_value; - } - } -} - -/** - * @} End of "locale-languages-negotiation" - */ - -/** - * Check that a string is safe to be added or imported as a translation. - * - * This test can be used to detect possibly bad translation strings. It should - * not have any false positives. But it is only a test, not a transformation, - * as it destroys valid HTML. We cannot reliably filter translation strings - * on import because some strings are irreversibly corrupted. For example, - * a & in the translation would get encoded to &amp; by filter_xss() - * before being put in the database, and thus would be displayed incorrectly. - * - * The allowed tag list is like filter_xss_admin(), but omitting div and img as - * not needed for translation and likely to cause layout issues (div) or a - * possible attack vector (img). - */ -function locale_string_is_safe($string) { - return decode_entities($string) == decode_entities(filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'ins', 'kbd', 'li', 'ol', 'p', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var'))); -} - -/** - * API function to add or update a language. - * - * @param $language - * Language object with properties corresponding to 'languages' table columns. - */ -function locale_language_save($language) { - $language->is_new = !(bool) db_query_range('SELECT 1 FROM {languages} WHERE language = :language', 0, 1, array(':language' => $language->language))->fetchField(); - // Default prefix on language code if not provided otherwise. - if (!isset($language->prefix)) { - $language->prefix = $language->language; - } - - // If name was not set, we add a predefined language. - if (!isset($language->name)) { - include_once DRUPAL_ROOT . '/core/includes/standard.inc'; - $predefined = standard_language_list(); - $language->name = $predefined[$language->language][0]; - $language->direction = isset($predefined[$language->language][2]) ? $predefined[$language->language][2] : LANGUAGE_LTR; - } - - // Set to enabled for the default language and unless specified otherwise. - if (!empty($language->default) || !isset($language->enabled)) { - $language->enabled = TRUE; - } - // Let other modules modify $language before saved. - module_invoke_all('locale_language_presave', $language); - - // Save the record and inform others about the change. - if ($language->is_new) { - drupal_write_record('languages', $language); - module_invoke_all('locale_language_insert', $language); - watchdog('locale', 'The %language (%langcode) language has been created.', array('%language' => $language->name, '%langcode' => $language->language)); - } - else { - drupal_write_record('languages', $language, array('language')); - module_invoke_all('locale_language_update', $language); - watchdog('locale', 'The %language (%langcode) language has been updated.', array('%language' => $language->name, '%langcode' => $language->language)); - } - - if (!empty($language->default)) { - // Set the new version of this language as default in a variable. - $default_language = language_default(); - variable_set('language_default', $language); - } - - // Update language count based on enabled language count. - variable_set('language_count', db_query('SELECT COUNT(language) FROM {languages} WHERE enabled = 1')->fetchField()); - - // Kill the static cache in language_list(). - drupal_static_reset('language_list'); - - // @todo move these two cache clears out. See http://drupal.org/node/1293252 - // Changing the language settings impacts the interface. - cache_clear_all('*', 'cache_page', TRUE); - // Force JavaScript translation file re-creation for the modified language. - _locale_invalidate_js($language->language); - - return $language; -} - -/** - * Parses a JavaScript file, extracts strings wrapped in Drupal.t() and - * Drupal.formatPlural() and inserts them into the database. - */ -function _locale_parse_js_file($filepath) { - global $language; - - // The file path might contain a query string, so make sure we only use the - // actual file. - $parsed_url = drupal_parse_url($filepath); - $filepath = $parsed_url['path']; - // Load the JavaScript file. - $file = file_get_contents($filepath); - - // Match all calls to Drupal.t() in an array. - // Note: \s also matches newlines with the 's' modifier. - preg_match_all('~ - [^\w]Drupal\s*\.\s*t\s* # match "Drupal.t" with whitespace - \(\s* # match "(" argument list start - (' . LOCALE_JS_STRING . ')\s* # capture string argument - (?:,\s*' . LOCALE_JS_OBJECT . '\s* # optionally capture str args - (?:,\s*' . LOCALE_JS_OBJECT_CONTEXT . '\s*) # optionally capture context - ?)? # close optional args - [,\)] # match ")" or "," to finish - ~sx', $file, $t_matches); - - // Match all Drupal.formatPlural() calls in another array. - preg_match_all('~ - [^\w]Drupal\s*\.\s*formatPlural\s* # match "Drupal.formatPlural" with whitespace - \( # match "(" argument list start - \s*.+?\s*,\s* # match count argument - (' . LOCALE_JS_STRING . ')\s*,\s* # match singular string argument - ( # capture plural string argument - (?: # non-capturing group to repeat string pieces - (?: - \' # match start of single-quoted string - (?:\\\\\'|[^\'])* # match any character except unescaped single-quote - @count # match "@count" - (?:\\\\\'|[^\'])* # match any character except unescaped single-quote - \' # match end of single-quoted string - | - " # match start of double-quoted string - (?:\\\\"|[^"])* # match any character except unescaped double-quote - @count # match "@count" - (?:\\\\"|[^"])* # match any character except unescaped double-quote - " # match end of double-quoted string - ) - (?:\s*\+\s*)? # match "+" with possible whitespace, for str concat - )+ # match multiple because we supports concatenating strs - )\s* # end capturing of plural string argument - (?:,\s*' . LOCALE_JS_OBJECT . '\s* # optionally capture string args - (?:,\s*' . LOCALE_JS_OBJECT_CONTEXT . '\s*)? # optionally capture context - )? - [,\)] - ~sx', $file, $plural_matches); - - $matches = array(); - - // Add strings from Drupal.t(). - foreach ($t_matches[1] as $key => $string) { - $matches[] = array( - 'string' => $string, - 'context' => $t_matches[2][$key], - ); - } - - // Add string from Drupal.formatPlural(). - foreach ($plural_matches[1] as $key => $string) { - $matches[] = array( - 'string' => $string, - 'context' => $plural_matches[3][$key], - ); - - // If there is also a plural version of this string, add it to the strings array. - if (isset($plural_matches[2][$key])) { - $matches[] = array( - 'string' => $plural_matches[2][$key], - 'context' => $plural_matches[3][$key], - ); - } - } - - // Loop through all matches and process them. - foreach ($matches as $key => $match) { - - // Remove the quotes and string concatenations from the string and context. - $string = implode('', preg_split('~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s', substr($match['string'], 1, -1))); - $context = implode('', preg_split('~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s', substr($match['context'], 1, -1))); - - $source = db_query("SELECT lid, location FROM {locales_source} WHERE source = :source AND context = :context", array(':source' => $string, ':context' => $context))->fetchObject(); - if ($source) { - // We already have this source string and now have to add the location - // to the location column, if this file is not yet present in there. - $locations = preg_split('~\s*;\s*~', $source->location); - - if (!in_array($filepath, $locations)) { - $locations[] = $filepath; - $locations = implode('; ', $locations); - - // Save the new locations string to the database. - db_update('locales_source') - ->fields(array( - 'location' => $locations, - )) - ->condition('lid', $source->lid) - ->execute(); - } - } - else { - // We don't have the source string yet, thus we insert it into the database. - db_insert('locales_source') - ->fields(array( - 'location' => $filepath, - 'source' => $string, - 'context' => $context, - )) - ->execute(); - } - } -} - -/** - * Force the JavaScript translation file(s) to be refreshed. - * - * This function sets a refresh flag for a specified language, or all - * languages except English, if none specified. JavaScript translation - * files are rebuilt (with locale_update_js_files()) the next time a - * request is served in that language. - * - * @param $langcode - * The language code for which the file needs to be refreshed. - * - * @return - * New content of the 'javascript_parsed' variable. - */ -function _locale_invalidate_js($langcode = NULL) { - $parsed = variable_get('javascript_parsed', array()); - - if (empty($langcode)) { - // Invalidate all languages. - $languages = language_list(); - if (!locale_translate_english()) { - unset($languages['en']); - } - foreach ($languages as $lcode => $data) { - $parsed['refresh:' . $lcode] = 'waiting'; - } - } - else { - // Invalidate single language. - $parsed['refresh:' . $langcode] = 'waiting'; - } - - variable_set('javascript_parsed', $parsed); - return $parsed; -} - -/** - * (Re-)Creates the JavaScript translation file for a language. - * - * @param $language - * The language, the translation file should be (re)created for. - */ -function _locale_rebuild_js($langcode = NULL) { - if (!isset($langcode)) { - global $language; - } - else { - // Get information about the locale. - $languages = language_list(); - $language = $languages[$langcode]; - } - - // Construct the array for JavaScript translations. - // Only add strings with a translation to the translations array. - $result = db_query("SELECT s.lid, s.source, s.context, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location LIKE '%.js%'", array(':language' => $language->language)); - - $translations = array(); - foreach ($result as $data) { - $translations[$data->context][$data->source] = $data->translation; - } - - // Construct the JavaScript file, if there are translations. - $data_hash = NULL; - $data = $status = ''; - if (!empty($translations)) { - - $data = "Drupal.locale = { "; - - if (!empty($language->formula)) { - $data .= "'pluralFormula': function (\$n) { return Number({$language->formula}); }, "; - } - - $data .= "'strings': " . drupal_json_encode($translations) . " };"; - $data_hash = drupal_hash_base64($data); - } - - // Construct the filepath where JS translation files are stored. - // There is (on purpose) no front end to edit that variable. - $dir = 'public://' . variable_get('locale_js_directory', 'languages'); - - // Delete old file, if we have no translations anymore, or a different file to be saved. - $changed_hash = $language->javascript != $data_hash; - if (!empty($language->javascript) && (!$data || $changed_hash)) { - file_unmanaged_delete($dir . '/' . $language->language . '_' . $language->javascript . '.js'); - $language->javascript = ''; - $status = 'deleted'; - } - - // Only create a new file if the content has changed or the original file got - // lost. - $dest = $dir . '/' . $language->language . '_' . $data_hash . '.js'; - if ($data && ($changed_hash || !file_exists($dest))) { - // Ensure that the directory exists and is writable, if possible. - file_prepare_directory($dir, FILE_CREATE_DIRECTORY); - - // Save the file. - if (file_unmanaged_save_data($data, $dest)) { - $language->javascript = $data_hash; - // If we deleted a previous version of the file and we replace it with a - // new one we have an update. - if ($status == 'deleted') { - $status = 'updated'; - } - // If the file did not exist previously and the data has changed we have - // a fresh creation. - elseif ($changed_hash) { - $status = 'created'; - } - // If the data hash is unchanged the translation was lost and has to be - // rebuilt. - else { - $status = 'rebuilt'; - } - } - else { - $language->javascript = ''; - $status = 'error'; - } - } - - // Save the new JavaScript hash (or an empty value if the file just got - // deleted). Act only if some operation was executed that changed the hash - // code. - if ($status && $changed_hash) { - db_update('languages') - ->fields(array( - 'javascript' => $language->javascript, - )) - ->condition('language', $language->language) - ->execute(); - - // Update the default language variable if the default language has been altered. - // This is necessary to keep the variable consistent with the database - // version of the language and to prevent checking against an outdated hash. - $default_langcode = language_default()->language; - drupal_static_reset('language_list'); - if ($default_langcode == $language->language) { - $default = language_load($default_langcode); - variable_set('language_default', $default); - } - } - - // Log the operation and return success flag. - switch ($status) { - case 'updated': - watchdog('locale', 'Updated JavaScript translation file for the language %language.', array('%language' => $language->name)); - return TRUE; - case 'rebuilt': - watchdog('locale', 'JavaScript translation file %file.js was lost.', array('%file' => $language->javascript), WATCHDOG_WARNING); - // Proceed to the 'created' case as the JavaScript translation file has - // been created again. - case 'created': - watchdog('locale', 'Created JavaScript translation file for the language %language.', array('%language' => $language->name)); - return TRUE; - case 'deleted': - watchdog('locale', 'Removed JavaScript translation file for the language %language because no translations currently exist for that language.', array('%language' => $language->name)); - return TRUE; - case 'error': - watchdog('locale', 'An error occurred during creation of the JavaScript translation file for the language %language.', array('%language' => $language->name), WATCHDOG_ERROR); - return FALSE; - default: - // No operation needed. - return TRUE; - } -} - -/** - * @defgroup locale-api-predefined List of predefined languages - * @{ - * API to provide a list of predefined languages. - */ - -/** - * Prepares the language code list for a select form item with only the unsupported ones - */ -function _locale_prepare_predefined_list() { - include_once DRUPAL_ROOT . '/core/includes/standard.inc'; - $languages = language_list(); - $predefined = standard_language_list(); - foreach ($predefined as $key => $value) { - if (isset($languages[$key])) { - unset($predefined[$key]); - continue; - } - $predefined[$key] = t($value[0]); - } - asort($predefined); - return $predefined; -} - -/** - * @} End of "locale-api-languages-predefined" - */ - -/** - * Get list of all predefined and custom countries. - * - * @return - * An array of all country code => country name pairs. - */ -function country_get_list() { - include_once DRUPAL_ROOT . '/core/includes/standard.inc'; - $countries = standard_country_list(); - // Allow other modules to modify the country list. - drupal_alter('countries', $countries); - return $countries; -} - -/** - * Save locale specific date formats to the database. - * - * @param $langcode - * Language code, can be 2 characters, e.g. 'en' or 5 characters, e.g. - * 'en-CA'. - * @param $type - * Date format type, e.g. 'short', 'medium'. - * @param $format - * The date format string. - */ -function locale_date_format_save($langcode, $type, $format) { - $locale_format = array(); - $locale_format['language'] = $langcode; - $locale_format['type'] = $type; - $locale_format['format'] = $format; - - $is_existing = (bool) db_query_range('SELECT 1 FROM {date_format_locale} WHERE language = :langcode AND type = :type', 0, 1, array(':langcode' => $langcode, ':type' => $type))->fetchField(); - if ($is_existing) { - $keys = array('type', 'language'); - drupal_write_record('date_format_locale', $locale_format, $keys); - } - else { - drupal_write_record('date_format_locale', $locale_format); - } -} - -/** - * Select locale date format details from database. - * - * @param $languages - * An array of language codes. - * - * @return - * An array of date formats. - */ -function locale_get_localized_date_format($languages) { - $formats = array(); - - // Get list of different format types. - $format_types = system_get_date_types(); - $short_default = variable_get('date_format_short', 'm/d/Y - H:i'); - - // Loop through each language until we find one with some date formats - // configured. - foreach ($languages as $language) { - $date_formats = system_date_format_locale($language); - if (!empty($date_formats)) { - // We have locale-specific date formats, so check for their types. If - // we're missing a type, use the default setting instead. - foreach ($format_types as $type => $type_info) { - // If format exists for this language, use it. - if (!empty($date_formats[$type])) { - $formats['date_format_' . $type] = $date_formats[$type]; - } - // Otherwise get default variable setting. If this is not set, default - // to the short format. - else { - $formats['date_format_' . $type] = variable_get('date_format_' . $type, $short_default); - } - } - - // Return on the first match. - return $formats; - } - } - - // No locale specific formats found, so use defaults. - $system_types = array('short', 'medium', 'long'); - // Handle system types separately as they have defaults if no variable exists. - $formats['date_format_short'] = $short_default; - $formats['date_format_medium'] = variable_get('date_format_medium', 'D, m/d/Y - H:i'); - $formats['date_format_long'] = variable_get('date_format_long', 'l, F j, Y - H:i'); - - // For non-system types, get the default setting, otherwise use the short - // format. - foreach ($format_types as $type => $type_info) { - if (!in_array($type, $system_types)) { - $formats['date_format_' . $type] = variable_get('date_format_' . $type, $short_default); - } - } - - return $formats; -} diff --git a/core/includes/lock.inc b/core/includes/lock.inc deleted file mode 100644 index 7dd8db30a16..00000000000 --- a/core/includes/lock.inc +++ /dev/null @@ -1,274 +0,0 @@ -<?php - -/** - * @file - * A database-mediated implementation of a locking mechanism. - */ - -/** - * @defgroup lock Locking mechanisms - * @{ - * Functions to coordinate long-running operations across requests. - * - * In most environments, multiple Drupal page requests (a.k.a. threads or - * processes) will execute in parallel. This leads to potential conflicts or - * race conditions when two requests execute the same code at the same time. A - * common example of this is a rebuild like menu_rebuild() where we invoke many - * hook implementations to get and process data from all active modules, and - * then delete the current data in the database to insert the new afterwards. - * - * This is a cooperative, advisory lock system. Any long-running operation - * that could potentially be attempted in parallel by multiple requests should - * try to acquire a lock before proceeding. By obtaining a lock, one request - * notifies any other requests that a specific operation is in progress which - * must not be executed in parallel. - * - * To use this API, pick a unique name for the lock. A sensible choice is the - * name of the function performing the operation. A very simple example use of - * this API: - * @code - * function mymodule_long_operation() { - * if (lock_acquire('mymodule_long_operation')) { - * // Do the long operation here. - * // ... - * lock_release('mymodule_long_operation'); - * } - * } - * @endcode - * - * If a function acquires a lock it should always release it when the - * operation is complete by calling lock_release(), as in the example. - * - * A function that has acquired a lock may attempt to renew a lock (extend the - * duration of the lock) by calling lock_acquire() again during the operation. - * Failure to renew a lock is indicative that another request has acquired - * the lock, and that the current operation may need to be aborted. - * - * If a function fails to acquire a lock it may either immediately return, or - * it may call lock_wait() if the rest of the current page request requires - * that the operation in question be complete. After lock_wait() returns, - * the function may again attempt to acquire the lock, or may simply allow the - * page request to proceed on the assumption that a parallel request completed - * the operation. - * - * lock_acquire() and lock_wait() will automatically break (delete) a lock - * whose duration has exceeded the timeout specified when it was acquired. - * - * Alternative implementations of this API (such as APC) may be substituted - * by setting the 'lock_inc' variable to an alternate include filepath. Since - * this is an API intended to support alternative implementations, code using - * this API should never rely upon specific implementation details (for example - * no code should look for or directly modify a lock in the {semaphore} table). - */ - -/** - * Initialize the locking system. - */ -function lock_initialize() { - global $locks; - - $locks = array(); -} - -/** - * Helper function to get this request's unique id. - */ -function _lock_id() { - // Do not use drupal_static(). This identifier refers to the current - // client request, and must not be changed under any circumstances - // else the shutdown handler may fail to release our locks. - static $lock_id; - - if (!isset($lock_id)) { - // Assign a unique id. - $lock_id = uniqid(mt_rand(), TRUE); - // We only register a shutdown function if a lock is used. - drupal_register_shutdown_function('lock_release_all', $lock_id); - } - return $lock_id; -} - -/** - * Acquire (or renew) a lock, but do not block if it fails. - * - * @param $name - * The name of the lock. - * @param $timeout - * A number of seconds (float) before the lock expires (minimum of 0.001). - * - * @return - * TRUE if the lock was acquired, FALSE if it failed. - */ -function lock_acquire($name, $timeout = 30.0) { - global $locks; - - // Insure that the timeout is at least 1 ms. - $timeout = max($timeout, 0.001); - $expire = microtime(TRUE) + $timeout; - if (isset($locks[$name])) { - // Try to extend the expiration of a lock we already acquired. - $success = (bool) db_update('semaphore') - ->fields(array('expire' => $expire)) - ->condition('name', $name) - ->condition('value', _lock_id()) - ->execute(); - if (!$success) { - // The lock was broken. - unset($locks[$name]); - } - return $success; - } - else { - // Optimistically try to acquire the lock, then retry once if it fails. - // The first time through the loop cannot be a retry. - $retry = FALSE; - // We always want to do this code at least once. - do { - try { - db_insert('semaphore') - ->fields(array( - 'name' => $name, - 'value' => _lock_id(), - 'expire' => $expire, - )) - ->execute(); - // We track all acquired locks in the global variable. - $locks[$name] = TRUE; - // We never need to try again. - $retry = FALSE; - } - catch (PDOException $e) { - // Suppress the error. If this is our first pass through the loop, - // then $retry is FALSE. In this case, the insert must have failed - // meaning some other request acquired the lock but did not release it. - // We decide whether to retry by checking lock_may_be_available() - // Since this will break the lock in case it is expired. - $retry = $retry ? FALSE : lock_may_be_available($name); - } - // We only retry in case the first attempt failed, but we then broke - // an expired lock. - } while ($retry); - } - return isset($locks[$name]); -} - -/** - * Check if lock acquired by a different process may be available. - * - * If an existing lock has expired, it is removed. - * - * @param $name - * The name of the lock. - * - * @return - * TRUE if there is no lock or it was removed, FALSE otherwise. - */ -function lock_may_be_available($name) { - $lock = db_query('SELECT expire, value FROM {semaphore} WHERE name = :name', array(':name' => $name))->fetchAssoc(); - if (!$lock) { - return TRUE; - } - $expire = (float) $lock['expire']; - $now = microtime(TRUE); - if ($now > $expire) { - // We check two conditions to prevent a race condition where another - // request acquired the lock and set a new expire time. We add a small - // number to $expire to avoid errors with float to string conversion. - return (bool) db_delete('semaphore') - ->condition('name', $name) - ->condition('value', $lock['value']) - ->condition('expire', 0.0001 + $expire, '<=') - ->execute(); - } - return FALSE; -} - -/** - * Wait for a lock to be available. - * - * This function may be called in a request that fails to acquire a desired - * lock. This will block further execution until the lock is available or the - * specified delay in seconds is reached. This should not be used with locks - * that are acquired very frequently, since the lock is likely to be acquired - * again by a different request while waiting. - * - * @param $name - * The name of the lock. - * @param $delay - * The maximum number of seconds to wait, as an integer. - * - * @return - * TRUE if the lock holds, FALSE if it is available. - */ -function lock_wait($name, $delay = 30) { - // Pause the process for short periods between calling - // lock_may_be_available(). This prevents hitting the database with constant - // database queries while waiting, which could lead to performance issues. - // However, if the wait period is too long, there is the potential for a - // large number of processes to be blocked waiting for a lock, especially - // if the item being rebuilt is commonly requested. To address both of these - // concerns, begin waiting for 25ms, then add 25ms to the wait period each - // time until it reaches 500ms. After this point polling will continue every - // 500ms until $delay is reached. - - // $delay is passed in seconds, but we will be using usleep(), which takes - // microseconds as a parameter. Multiply it by 1 million so that all - // further numbers are equivalent. - $delay = (int) $delay * 1000000; - - // Begin sleeping at 25ms. - $sleep = 25000; - while ($delay > 0) { - // This function should only be called by a request that failed to get a - // lock, so we sleep first to give the parallel request a chance to finish - // and release the lock. - usleep($sleep); - // After each sleep, increase the value of $sleep until it reaches - // 500ms, to reduce the potential for a lock stampede. - $delay = $delay - $sleep; - $sleep = min(500000, $sleep + 25000, $delay); - if (lock_may_be_available($name)) { - // No longer need to wait. - return FALSE; - } - } - // The caller must still wait longer to get the lock. - return TRUE; -} - -/** - * Release a lock previously acquired by lock_acquire(). - * - * This will release the named lock if it is still held by the current request. - * - * @param $name - * The name of the lock. - */ -function lock_release($name) { - global $locks; - - unset($locks[$name]); - db_delete('semaphore') - ->condition('name', $name) - ->condition('value', _lock_id()) - ->execute(); -} - -/** - * Release all previously acquired locks. - */ -function lock_release_all($lock_id = NULL) { - global $locks; - - $locks = array(); - if (empty($lock_id)) { - $lock_id = _lock_id(); - } - db_delete('semaphore') - ->condition('value', $lock_id) - ->execute(); -} - -/** - * @} End of "defgroup lock". - */ diff --git a/core/includes/mail.inc b/core/includes/mail.inc deleted file mode 100644 index 7272df972e2..00000000000 --- a/core/includes/mail.inc +++ /dev/null @@ -1,586 +0,0 @@ -<?php - -/** - * @file - * API functions for processing and sending e-mail. - */ - -/** - * Auto-detect appropriate line endings for e-mails. - * - * $conf['mail_line_endings'] will override this setting. - */ -define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER_SOFTWARE'], 'Win32') !== FALSE ? "\r\n" : "\n"); - -/** - * Compose and optionally send an e-mail message. - * - * Sending an e-mail works with defining an e-mail template (subject, text - * and possibly e-mail headers) and the replacement values to use in the - * appropriate places in the template. Processed e-mail templates are - * requested from hook_mail() from the module sending the e-mail. Any module - * can modify the composed e-mail message array using hook_mail_alter(). - * Finally drupal_mail_system()->mail() sends the e-mail, which can - * be reused if the exact same composed e-mail is to be sent to multiple - * recipients. - * - * Finding out what language to send the e-mail with needs some consideration. - * If you send e-mail to a user, her preferred language should be fine, so - * use user_preferred_language(). If you send email based on form values - * filled on the page, there are two additional choices if you are not - * sending the e-mail to a user on the site. You can either use the language - * used to generate the page ($language global variable) or the site default - * language. See language_default(). The former is good if sending e-mail to - * the person filling the form, the later is good if you send e-mail to an - * address previously set up (like contact addresses in a contact form). - * - * Taking care of always using the proper language is even more important - * when sending e-mails in a row to multiple users. Hook_mail() abstracts - * whether the mail text comes from an administrator setting or is - * static in the source code. It should also deal with common mail tokens, - * only receiving $params which are unique to the actual e-mail at hand. - * - * An example: - * - * @code - * function example_notify($accounts) { - * foreach ($accounts as $account) { - * $params['account'] = $account; - * // example_mail() will be called based on the first drupal_mail() parameter. - * drupal_mail('example', 'notice', $account->mail, user_preferred_language($account), $params); - * } - * } - * - * function example_mail($key, &$message, $params) { - * $data['user'] = $params['account']; - * $options['language'] = $message['language']; - * user_mail_tokens($variables, $data, $options); - * switch($key) { - * case 'notice': - * $langcode = $message['language']->language; - * $message['subject'] = t('Notification from !site', $variables, array('langcode' => $langcode)); - * $message['body'][] = t("Dear !username\n\nThere is new content available on the site.", $variables, array('langcode' => $langcode)); - * break; - * } - * } - * @endcode - * - * @param $module - * A module name to invoke hook_mail() on. The {$module}_mail() hook will be - * called to complete the $message structure which will already contain common - * defaults. - * @param $key - * A key to identify the e-mail sent. The final e-mail id for e-mail altering - * will be {$module}_{$key}. - * @param $to - * The e-mail address or addresses where the message will be sent to. The - * formatting of this string must comply with RFC 2822. Some examples are: - * - user@example.com - * - user@example.com, anotheruser@example.com - * - User <user@example.com> - * - User <user@example.com>, Another User <anotheruser@example.com> - * @param $language - * Language object to use to compose the e-mail. - * @param $params - * Optional parameters to build the e-mail. - * @param $from - * Sets From to this value, if given. - * @param $send - * Send the message directly, without calling drupal_mail_system()->mail() - * manually. - * - * @return - * The $message array structure containing all details of the - * message. If already sent ($send = TRUE), then the 'result' element - * will contain the success indicator of the e-mail, failure being already - * written to the watchdog. (Success means nothing more than the message being - * accepted at php-level, which still doesn't guarantee it to be delivered.) - */ -function drupal_mail($module, $key, $to, $language, $params = array(), $from = NULL, $send = TRUE) { - $default_from = variable_get('site_mail', ini_get('sendmail_from')); - - // Bundle up the variables into a structured array for altering. - $message = array( - 'id' => $module . '_' . $key, - 'module' => $module, - 'key' => $key, - 'to' => $to, - 'from' => isset($from) ? $from : $default_from, - 'language' => $language, - 'params' => $params, - 'subject' => '', - 'body' => array() - ); - - // Build the default headers - $headers = array( - 'MIME-Version' => '1.0', - 'Content-Type' => 'text/plain; charset=UTF-8; format=flowed; delsp=yes', - 'Content-Transfer-Encoding' => '8Bit', - 'X-Mailer' => 'Drupal' - ); - if ($default_from) { - // To prevent e-mail from looking like spam, the addresses in the Sender and - // Return-Path headers should have a domain authorized to use the originating - // SMTP server. - $headers['From'] = $headers['Sender'] = $headers['Return-Path'] = $default_from; - } - if ($from) { - $headers['From'] = $from; - } - $message['headers'] = $headers; - - // Build the e-mail (get subject and body, allow additional headers) by - // invoking hook_mail() on this module. We cannot use module_invoke() as - // we need to have $message by reference in hook_mail(). - if (function_exists($function = $module . '_mail')) { - $function($key, $message, $params); - } - - // Invoke hook_mail_alter() to allow all modules to alter the resulting e-mail. - drupal_alter('mail', $message); - - // Retrieve the responsible implementation for this message. - $system = drupal_mail_system($module, $key); - - // Format the message body. - $message = $system->format($message); - - // Optionally send e-mail. - if ($send) { - $message['result'] = $system->mail($message); - - // Log errors - if (!$message['result']) { - watchdog('mail', 'Error sending e-mail (from %from to %to).', array('%from' => $message['from'], '%to' => $message['to']), WATCHDOG_ERROR); - drupal_set_message(t('Unable to send e-mail. Contact the site administrator if the problem persists.'), 'error'); - } - } - - return $message; -} - -/** - * Returns an object that implements the MailSystemInterface. - * - * Allows for one or more custom mail backends to format and send mail messages - * composed using drupal_mail(). - * - * An implementation needs to implement the following methods: - * - format: Allows to preprocess, format, and postprocess a mail - * message before it is passed to the sending system. By default, all messages - * may contain HTML and are converted to plain-text by the DefaultMailSystem - * implementation. For example, an alternative implementation could override - * the default implementation and additionally sanitize the HTML for usage in - * a MIME-encoded e-mail, but still invoking the DefaultMailSystem - * implementation to generate an alternate plain-text version for sending. - * - mail: Sends a message through a custom mail sending engine. - * By default, all messages are sent via PHP's mail() function by the - * DefaultMailSystem implementation. - * - * The selection of a particular implementation is controlled via the variable - * 'mail_system', which is a keyed array. The default implementation - * is the class whose name is the value of 'default-system' key. A more specific - * match first to key and then to module will be used in preference to the - * default. To specificy a different class for all mail sent by one module, set - * the class name as the value for the key corresponding to the module name. To - * specificy a class for a particular message sent by one module, set the class - * name as the value for the array key that is the message id, which is - * "${module}_${key}". - * - * For example to debug all mail sent by the user module by logging it to a - * file, you might set the variable as something like: - * - * @code - * array( - * 'default-system' => 'DefaultMailSystem', - * 'user' => 'DevelMailLog', - * ); - * @endcode - * - * Finally, a different system can be specified for a specific e-mail ID (see - * the $key param), such as one of the keys used by the contact module: - * - * @code - * array( - * 'default-system' => 'DefaultMailSystem', - * 'user' => 'DevelMailLog', - * 'contact_page_autoreply' => 'DrupalDevNullMailSend', - * ); - * @endcode - * - * Other possible uses for system include a mail-sending class that actually - * sends (or duplicates) each message to SMS, Twitter, instant message, etc, or - * a class that queues up a large number of messages for more efficient bulk - * sending or for sending via a remote gateway so as to reduce the load - * on the local server. - * - * @param $module - * The module name which was used by drupal_mail() to invoke hook_mail(). - * @param $key - * A key to identify the e-mail sent. The final e-mail ID for the e-mail - * alter hook in drupal_mail() would have been {$module}_{$key}. - * - * @return MailSystemInterface - */ -function drupal_mail_system($module, $key) { - $instances = &drupal_static(__FUNCTION__, array()); - - $id = $module . '_' . $key; - - $configuration = variable_get('mail_system', array('default-system' => 'DefaultMailSystem')); - - // Look for overrides for the default class, starting from the most specific - // id, and falling back to the module name. - if (isset($configuration[$id])) { - $class = $configuration[$id]; - } - elseif (isset($configuration[$module])) { - $class = $configuration[$module]; - } - else { - $class = $configuration['default-system']; - } - - if (empty($instances[$class])) { - $interfaces = class_implements($class); - if (isset($interfaces['MailSystemInterface'])) { - $instances[$class] = new $class(); - } - else { - throw new Exception(t('Class %class does not implement interface %interface', array('%class' => $class, '%interface' => 'MailSystemInterface'))); - } - } - return $instances[$class]; -} - -/** - * An interface for pluggable mail back-ends. - */ -interface MailSystemInterface { - /** - * Format a message composed by drupal_mail() prior sending. - * - * @param $message - * A message array, as described in hook_mail_alter(). - * - * @return - * The formatted $message. - */ - public function format(array $message); - - /** - * Send a message composed by drupal_mail(). - * - * @param $message - * Message array with at least the following elements: - * - id: A unique identifier of the e-mail type. Examples: 'contact_user_copy', - * 'user_password_reset'. - * - to: The mail address or addresses where the message will be sent to. - * The formatting of this string must comply with RFC 2822. Some examples: - * - user@example.com - * - user@example.com, anotheruser@example.com - * - User <user@example.com> - * - User <user@example.com>, Another User <anotheruser@example.com> - * - subject: Subject of the e-mail to be sent. This must not contain any - * newline characters, or the mail may not be sent properly. - * - body: Message to be sent. Accepts both CRLF and LF line-endings. - * E-mail bodies must be wrapped. You can use drupal_wrap_mail() for - * smart plain text wrapping. - * - headers: Associative array containing all additional mail headers not - * defined by one of the other parameters. PHP's mail() looks for Cc - * and Bcc headers and sends the mail to addresses in these headers too. - * - * @return - * TRUE if the mail was successfully accepted for delivery, otherwise FALSE. - */ - public function mail(array $message); -} - -/** - * Perform format=flowed soft wrapping for mail (RFC 3676). - * - * We use delsp=yes wrapping, but only break non-spaced languages when - * absolutely necessary to avoid compatibility issues. - * - * We deliberately use LF rather than CRLF, see drupal_mail(). - * - * @param $text - * The plain text to process. - * @param $indent (optional) - * A string to indent the text with. Only '>' characters are repeated on - * subsequent wrapped lines. Others are replaced by spaces. - */ -function drupal_wrap_mail($text, $indent = '') { - // Convert CRLF into LF. - $text = str_replace("\r", '', $text); - // See if soft-wrapping is allowed. - $clean_indent = _drupal_html_to_text_clean($indent); - $soft = strpos($clean_indent, ' ') === FALSE; - // Check if the string has line breaks. - if (strpos($text, "\n") !== FALSE) { - // Remove trailing spaces to make existing breaks hard. - $text = preg_replace('/ +\n/m', "\n", $text); - // Wrap each line at the needed width. - $lines = explode("\n", $text); - array_walk($lines, '_drupal_wrap_mail_line', array('soft' => $soft, 'length' => strlen($indent))); - $text = implode("\n", $lines); - } - else { - // Wrap this line. - _drupal_wrap_mail_line($text, 0, array('soft' => $soft, 'length' => strlen($indent))); - } - // Empty lines with nothing but spaces. - $text = preg_replace('/^ +\n/m', "\n", $text); - // Space-stuff special lines. - $text = preg_replace('/^(>| |From)/m', ' $1', $text); - // Apply indentation. We only include non-'>' indentation on the first line. - $text = $indent . substr(preg_replace('/^/m', $clean_indent, $text), strlen($indent)); - - return $text; -} - -/** - * Transform an HTML string into plain text, preserving the structure of the - * markup. Useful for preparing the body of a node to be sent by e-mail. - * - * The output will be suitable for use as 'format=flowed; delsp=yes' text - * (RFC 3676) and can be passed directly to drupal_mail() for sending. - * - * We deliberately use LF rather than CRLF, see drupal_mail(). - * - * This function provides suitable alternatives for the following tags: - * <a> <em> <i> <strong> <b> <br> <p> <blockquote> <ul> <ol> <li> <dl> <dt> - * <dd> <h1> <h2> <h3> <h4> <h5> <h6> <hr> - * - * @param $string - * The string to be transformed. - * @param $allowed_tags (optional) - * If supplied, a list of tags that will be transformed. If omitted, all - * all supported tags are transformed. - * - * @return - * The transformed string. - */ -function drupal_html_to_text($string, $allowed_tags = NULL) { - // Cache list of supported tags. - static $supported_tags; - if (empty($supported_tags)) { - $supported_tags = array('a', 'em', 'i', 'strong', 'b', 'br', 'p', 'blockquote', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr'); - } - - // Make sure only supported tags are kept. - $allowed_tags = isset($allowed_tags) ? array_intersect($supported_tags, $allowed_tags) : $supported_tags; - - // Make sure tags, entities and attributes are well-formed and properly nested. - $string = _filter_htmlcorrector(filter_xss($string, $allowed_tags)); - - // Apply inline styles. - $string = preg_replace('!</?(em|i)((?> +)[^>]*)?>!i', '/', $string); - $string = preg_replace('!</?(strong|b)((?> +)[^>]*)?>!i', '*', $string); - - // Replace inline <a> tags with the text of link and a footnote. - // 'See <a href="http://drupal.org">the Drupal site</a>' becomes - // 'See the Drupal site [1]' with the URL included as a footnote. - _drupal_html_to_mail_urls(NULL, TRUE); - $pattern = '@(<a[^>]+?href="([^"]*)"[^>]*?>(.+?)</a>)@i'; - $string = preg_replace_callback($pattern, '_drupal_html_to_mail_urls', $string); - $urls = _drupal_html_to_mail_urls(); - $footnotes = ''; - if (count($urls)) { - $footnotes .= "\n"; - for ($i = 0, $max = count($urls); $i < $max; $i++) { - $footnotes .= '[' . ($i + 1) . '] ' . $urls[$i] . "\n"; - } - } - - // Split tags from text. - $split = preg_split('/<([^>]+?)>/', $string, -1, PREG_SPLIT_DELIM_CAPTURE); - // Note: PHP ensures the array consists of alternating delimiters and literals - // and begins and ends with a literal (inserting $null as required). - - $tag = FALSE; // Odd/even counter (tag or no tag) - $casing = NULL; // Case conversion function - $output = ''; - $indent = array(); // All current indentation string chunks - $lists = array(); // Array of counters for opened lists - foreach ($split as $value) { - $chunk = NULL; // Holds a string ready to be formatted and output. - - // Process HTML tags (but don't output any literally). - if ($tag) { - list($tagname) = explode(' ', strtolower($value), 2); - switch ($tagname) { - // List counters - case 'ul': - array_unshift($lists, '*'); - break; - case 'ol': - array_unshift($lists, 1); - break; - case '/ul': - case '/ol': - array_shift($lists); - $chunk = ''; // Ensure blank new-line. - break; - - // Quotation/list markers, non-fancy headers - case 'blockquote': - // Format=flowed indentation cannot be mixed with lists. - $indent[] = count($lists) ? ' "' : '>'; - break; - case 'li': - $indent[] = isset($lists[0]) && is_numeric($lists[0]) ? ' ' . $lists[0]++ . ') ' : ' * '; - break; - case 'dd': - $indent[] = ' '; - break; - case 'h3': - $indent[] = '.... '; - break; - case 'h4': - $indent[] = '.. '; - break; - case '/blockquote': - if (count($lists)) { - // Append closing quote for inline quotes (immediately). - $output = rtrim($output, "> \n") . "\"\n"; - $chunk = ''; // Ensure blank new-line. - } - // Fall-through - case '/li': - case '/dd': - array_pop($indent); - break; - case '/h3': - case '/h4': - array_pop($indent); - case '/h5': - case '/h6': - $chunk = ''; // Ensure blank new-line. - break; - - // Fancy headers - case 'h1': - $indent[] = '======== '; - $casing = 'drupal_strtoupper'; - break; - case 'h2': - $indent[] = '-------- '; - $casing = 'drupal_strtoupper'; - break; - case '/h1': - case '/h2': - $casing = NULL; - // Pad the line with dashes. - $output = _drupal_html_to_text_pad($output, ($tagname == '/h1') ? '=' : '-', ' '); - array_pop($indent); - $chunk = ''; // Ensure blank new-line. - break; - - // Horizontal rulers - case 'hr': - // Insert immediately. - $output .= drupal_wrap_mail('', implode('', $indent)) . "\n"; - $output = _drupal_html_to_text_pad($output, '-'); - break; - - // Paragraphs and definition lists - case '/p': - case '/dl': - $chunk = ''; // Ensure blank new-line. - break; - } - } - // Process blocks of text. - else { - // Convert inline HTML text to plain text; not removing line-breaks or - // white-space, since that breaks newlines when sanitizing plain-text. - $value = trim(decode_entities($value)); - if (drupal_strlen($value)) { - $chunk = $value; - } - } - - // See if there is something waiting to be output. - if (isset($chunk)) { - // Apply any necessary case conversion. - if (isset($casing)) { - $chunk = $casing($chunk); - } - // Format it and apply the current indentation. - $output .= drupal_wrap_mail($chunk, implode('', $indent)) . MAIL_LINE_ENDINGS; - // Remove non-quotation markers from indentation. - $indent = array_map('_drupal_html_to_text_clean', $indent); - } - - $tag = !$tag; - } - - return $output . $footnotes; -} - -/** - * Helper function for array_walk in drupal_wrap_mail(). - * - * Wraps words on a single line. - */ -function _drupal_wrap_mail_line(&$line, $key, $values) { - // Use soft-breaks only for purely quoted or unindented text. - $line = wordwrap($line, 77 - $values['length'], $values['soft'] ? " \n" : "\n"); - // Break really long words at the maximum width allowed. - $line = wordwrap($line, 996 - $values['length'], $values['soft'] ? " \n" : "\n"); -} - -/** - * Helper function for drupal_html_to_text(). - * - * Keeps track of URLs and replaces them with placeholder tokens. - */ -function _drupal_html_to_mail_urls($match = NULL, $reset = FALSE) { - global $base_url, $base_path; - static $urls = array(), $regexp; - - if ($reset) { - // Reset internal URL list. - $urls = array(); - } - else { - if (empty($regexp)) { - $regexp = '@^' . preg_quote($base_path, '@') . '@'; - } - if ($match) { - list(, , $url, $label) = $match; - // Ensure all URLs are absolute. - $urls[] = strpos($url, '://') ? $url : preg_replace($regexp, $base_url . '/', $url); - return $label . ' [' . count($urls) . ']'; - } - } - return $urls; -} - -/** - * Helper function for drupal_wrap_mail() and drupal_html_to_text(). - * - * Replace all non-quotation markers from a given piece of indentation with spaces. - */ -function _drupal_html_to_text_clean($indent) { - return preg_replace('/[^>]/', ' ', $indent); -} - -/** - * Helper function for drupal_html_to_text(). - * - * Pad the last line with the given character. - */ -function _drupal_html_to_text_pad($text, $pad, $prefix = '') { - // Remove last line break. - $text = substr($text, 0, -1); - // Calculate needed padding space and add it. - if (($p = strrpos($text, "\n")) === FALSE) { - $p = -1; - } - $n = max(0, 79 - (strlen($text) - $p) - strlen($prefix)); - // Add prefix and padding, and restore linebreak. - return $text . $prefix . str_repeat($pad, $n) . "\n"; -} diff --git a/core/includes/menu.inc b/core/includes/menu.inc deleted file mode 100644 index f23eb0d4dff..00000000000 --- a/core/includes/menu.inc +++ /dev/null @@ -1,3798 +0,0 @@ -<?php - -/** - * @file - * API for the Drupal menu system. - */ - -/** - * @defgroup menu Menu system - * @{ - * Define the navigation menus, and route page requests to code based on URLs. - * - * The Drupal menu system drives both the navigation system from a user - * perspective and the callback system that Drupal uses to respond to URLs - * passed from the browser. For this reason, a good understanding of the - * menu system is fundamental to the creation of complex modules. As a note, - * this is related to, but separate from menu.module, which allows menus - * (which in this context are hierarchical lists of links) to be customized from - * the Drupal administrative interface. - * - * Drupal's menu system follows a simple hierarchy defined by paths. - * Implementations of hook_menu() define menu items and assign them to - * paths (which should be unique). The menu system aggregates these items - * and determines the menu hierarchy from the paths. For example, if the - * paths defined were a, a/b, e, a/b/c/d, f/g, and a/b/h, the menu system - * would form the structure: - * - a - * - a/b - * - a/b/c/d - * - a/b/h - * - e - * - f/g - * Note that the number of elements in the path does not necessarily - * determine the depth of the menu item in the tree. - * - * When responding to a page request, the menu system looks to see if the - * path requested by the browser is registered as a menu item with a - * callback. If not, the system searches up the menu tree for the most - * complete match with a callback it can find. If the path a/b/i is - * requested in the tree above, the callback for a/b would be used. - * - * The found callback function is called with any arguments specified - * in the "page arguments" attribute of its menu item. The - * attribute must be an array. After these arguments, any remaining - * components of the path are appended as further arguments. In this - * way, the callback for a/b above could respond to a request for - * a/b/i differently than a request for a/b/j. - * - * For an illustration of this process, see page_example.module. - * - * Access to the callback functions is also protected by the menu system. - * The "access callback" with an optional "access arguments" of each menu - * item is called before the page callback proceeds. If this returns TRUE, - * then access is granted; if FALSE, then access is denied. Default local task - * menu items (see next paragraph) may omit this attribute to use the value - * provided by the parent item. - * - * In the default Drupal interface, you will notice many links rendered as - * tabs. These are known in the menu system as "local tasks", and they are - * rendered as tabs by default, though other presentations are possible. - * Local tasks function just as other menu items in most respects. It is - * convention that the names of these tasks should be short verbs if - * possible. In addition, a "default" local task should be provided for - * each set. When visiting a local task's parent menu item, the default - * local task will be rendered as if it is selected; this provides for a - * normal tab user experience. This default task is special in that it - * links not to its provided path, but to its parent item's path instead. - * The default task's path is only used to place it appropriately in the - * menu hierarchy. - * - * Everything described so far is stored in the menu_router table. The - * menu_links table holds the visible menu links. By default these are - * derived from the same hook_menu definitions, however you are free to - * add more with menu_link_save(). - */ - -/** - * @defgroup menu_flags Menu flags - * @{ - * Flags for use in the "type" attribute of menu items. - */ - -/** - * Internal menu flag -- menu item is the root of the menu tree. - */ -define('MENU_IS_ROOT', 0x0001); - -/** - * Internal menu flag -- menu item is visible in the menu tree. - */ -define('MENU_VISIBLE_IN_TREE', 0x0002); - -/** - * Internal menu flag -- menu item is visible in the breadcrumb. - */ -define('MENU_VISIBLE_IN_BREADCRUMB', 0x0004); - -/** - * Internal menu flag -- menu item links back to its parent. - */ -define('MENU_LINKS_TO_PARENT', 0x0008); - -/** - * Internal menu flag -- menu item can be modified by administrator. - */ -define('MENU_MODIFIED_BY_ADMIN', 0x0020); - -/** - * Internal menu flag -- menu item was created by administrator. - */ -define('MENU_CREATED_BY_ADMIN', 0x0040); - -/** - * Internal menu flag -- menu item is a local task. - */ -define('MENU_IS_LOCAL_TASK', 0x0080); - -/** - * Internal menu flag -- menu item is a local action. - */ -define('MENU_IS_LOCAL_ACTION', 0x0100); - -/** - * @} End of "Menu flags". - */ - -/** - * @defgroup menu_item_types Menu item types - * @{ - * Definitions for various menu item types. - * - * Menu item definitions provide one of these constants, which are shortcuts for - * combinations of @link menu_flags Menu flags @endlink. - */ - -/** - * Menu type -- A "normal" menu item that's shown in menu and breadcrumbs. - * - * Normal menu items show up in the menu tree and can be moved/hidden by - * the administrator. Use this for most menu items. It is the default value if - * no menu item type is specified. - */ -define('MENU_NORMAL_ITEM', MENU_VISIBLE_IN_TREE | MENU_VISIBLE_IN_BREADCRUMB); - -/** - * Menu type -- A hidden, internal callback, typically used for API calls. - * - * Callbacks simply register a path so that the correct function is fired - * when the URL is accessed. They do not appear in menus or breadcrumbs. - */ -define('MENU_CALLBACK', 0x0000); - -/** - * Menu type -- A normal menu item, hidden until enabled by an administrator. - * - * Modules may "suggest" menu items that the administrator may enable. They act - * just as callbacks do until enabled, at which time they act like normal items. - * Note for the value: 0x0010 was a flag which is no longer used, but this way - * the values of MENU_CALLBACK and MENU_SUGGESTED_ITEM are separate. - */ -define('MENU_SUGGESTED_ITEM', MENU_VISIBLE_IN_BREADCRUMB | 0x0010); - -/** - * Menu type -- A task specific to the parent item, usually rendered as a tab. - * - * Local tasks are menu items that describe actions to be performed on their - * parent item. An example is the path "node/52/edit", which performs the - * "edit" task on "node/52". - */ -define('MENU_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_VISIBLE_IN_BREADCRUMB); - -/** - * Menu type -- The "default" local task, which is initially active. - * - * Every set of local tasks should provide one "default" task, that links to the - * same path as its parent when clicked. - */ -define('MENU_DEFAULT_LOCAL_TASK', MENU_IS_LOCAL_TASK | MENU_LINKS_TO_PARENT | MENU_VISIBLE_IN_BREADCRUMB); - -/** - * Menu type -- An action specific to the parent, usually rendered as a link. - * - * Local actions are menu items that describe actions on the parent item such - * as adding a new user, taxonomy term, etc. - */ -define('MENU_LOCAL_ACTION', MENU_IS_LOCAL_TASK | MENU_IS_LOCAL_ACTION | MENU_VISIBLE_IN_BREADCRUMB); - -/** - * @} End of "Menu item types". - */ - -/** - * @defgroup menu_context_types Menu context types - * @{ - * Flags for use in the "context" attribute of menu router items. - */ - -/** - * Internal menu flag: Invisible local task. - * - * This flag may be used for local tasks like "Delete", so custom modules and - * themes can alter the default context and expose the task by altering menu. - */ -define('MENU_CONTEXT_NONE', 0x0000); - -/** - * Internal menu flag: Local task should be displayed in page context. - */ -define('MENU_CONTEXT_PAGE', 0x0001); - -/** - * Internal menu flag: Local task should be displayed inline. - */ -define('MENU_CONTEXT_INLINE', 0x0002); - -/** - * @} End of "Menu context types". - */ - -/** - * @defgroup menu_status_codes Menu status codes - * @{ - * Status codes for menu callbacks. - */ - -/** - * Internal menu status code -- Menu item was found. - */ -define('MENU_FOUND', 1); - -/** - * Internal menu status code -- Menu item was not found. - */ -define('MENU_NOT_FOUND', 2); - -/** - * Internal menu status code -- Menu item access is denied. - */ -define('MENU_ACCESS_DENIED', 3); - -/** - * Internal menu status code -- Menu item inaccessible because site is offline. - */ -define('MENU_SITE_OFFLINE', 4); - -/** - * Internal menu status code -- Everything is working fine. - */ -define('MENU_SITE_ONLINE', 5); - -/** - * @} End of "Menu status codes". - */ - -/** - * @defgroup menu_tree_parameters Menu tree parameters - * @{ - * Parameters for a menu tree. - */ - - /** - * The maximum number of path elements for a menu callback - */ -define('MENU_MAX_PARTS', 9); - - -/** - * The maximum depth of a menu links tree - matches the number of p columns. - */ -define('MENU_MAX_DEPTH', 9); - - -/** - * @} End of "Menu tree parameters". - */ - -/** - * Returns the ancestors (and relevant placeholders) for any given path. - * - * For example, the ancestors of node/12345/edit are: - * - node/12345/edit - * - node/12345/% - * - node/%/edit - * - node/%/% - * - node/12345 - * - node/% - * - node - * - * To generate these, we will use binary numbers. Each bit represents a - * part of the path. If the bit is 1, then it represents the original - * value while 0 means wildcard. If the path is node/12/edit/foo - * then the 1011 bitstring represents node/%/edit/foo where % means that - * any argument matches that part. We limit ourselves to using binary - * numbers that correspond the patterns of wildcards of router items that - * actually exists. This list of 'masks' is built in menu_rebuild(). - * - * @param $parts - * An array of path parts, for the above example - * array('node', '12345', 'edit'). - * - * @return - * An array which contains the ancestors and placeholders. Placeholders - * simply contain as many '%s' as the ancestors. - */ -function menu_get_ancestors($parts) { - $number_parts = count($parts); - $ancestors = array(); - $length = $number_parts - 1; - $end = (1 << $number_parts) - 1; - $masks = variable_get('menu_masks', array()); - // Only examine patterns that actually exist as router items (the masks). - foreach ($masks as $i) { - if ($i > $end) { - // Only look at masks that are not longer than the path of interest. - continue; - } - elseif ($i < (1 << $length)) { - // We have exhausted the masks of a given length, so decrease the length. - --$length; - } - $current = ''; - for ($j = $length; $j >= 0; $j--) { - // Check the bit on the $j offset. - if ($i & (1 << $j)) { - // Bit one means the original value. - $current .= $parts[$length - $j]; - } - else { - // Bit zero means means wildcard. - $current .= '%'; - } - // Unless we are at offset 0, add a slash. - if ($j) { - $current .= '/'; - } - } - $ancestors[] = $current; - } - return $ancestors; -} - -/** - * Unserializes menu data, using a map to replace path elements. - * - * The menu system stores various path-related information (such as the 'page - * arguments' and 'access arguments' components of a menu item) in the database - * using serialized arrays, where integer values in the arrays represent - * arguments to be replaced by values from the path. This function first - * unserializes such menu information arrays, and then does the path - * replacement. - * - * The path replacement acts on each integer-valued element of the unserialized - * menu data array ($data) using a map array ($map, which is typically an array - * of path arguments) as a list of replacements. For instance, if there is an - * element of $data whose value is the number 2, then it is replaced in $data - * with $map[2]; non-integer values in $data are left alone. - * - * As an example, an unserialized $data array with elements ('node_load', 1) - * represents instructions for calling the node_load() function. Specifically, - * this instruction says to use the path component at index 1 as the input - * parameter to node_load(). If the path is 'node/123', then $map will be the - * array ('node', 123), and the returned array from this function will have - * elements ('node_load', 123), since $map[1] is 123. This return value will - * indicate specifically that node_load(123) is to be called to load the node - * whose ID is 123 for this menu item. - * - * @param $data - * A serialized array of menu data, as read from the database. - * @param $map - * A path argument array, used to replace integer values in $data; an integer - * value N in $data will be replaced by value $map[N]. Typically, the $map - * array is generated from a call to the arg() function. - * - * @return - * The unserialized $data array, with path arguments replaced. - */ -function menu_unserialize($data, $map) { - if ($data = unserialize($data)) { - foreach ($data as $k => $v) { - if (is_int($v)) { - $data[$k] = isset($map[$v]) ? $map[$v] : ''; - } - } - return $data; - } - else { - return array(); - } -} - - - -/** - * Replaces the statically cached item for a given path. - * - * @param $path - * The path. - * @param $router_item - * The router item. Usually you take a router entry from menu_get_item and - * set it back either modified or to a different path. This lets you modify the - * navigation block, the page title, the breadcrumb and the page help in one - * call. - */ -function menu_set_item($path, $router_item) { - menu_get_item($path, $router_item); -} - -/** - * Get a router item. - * - * @param $path - * The path, for example node/5. The function will find the corresponding - * node/% item and return that. - * @param $router_item - * Internal use only. - * - * @return - * The router item, an associate array corresponding to one row in the - * menu_router table. The value of key map holds the loaded objects. The - * value of key access is TRUE if the current user can access this page. - * The values for key title, page_arguments, access_arguments, and - * theme_arguments will be filled in based on the database values and the - * objects loaded. - */ -function menu_get_item($path = NULL, $router_item = NULL) { - $router_items = &drupal_static(__FUNCTION__); - if (!isset($path)) { - $path = $_GET['q']; - } - if (isset($router_item)) { - $router_items[$path] = $router_item; - } - if (!isset($router_items[$path])) { - // Rebuild if we know it's needed, or if the menu masks are missing which - // occurs rarely, likely due to a race condition of multiple rebuilds. - if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) { - menu_rebuild(); - } - $original_map = arg(NULL, $path); - - // Since there is no limit to the length of $path, use a hash to keep it - // short yet unique. - $cid = 'menu_item:' . hash('sha256', $path); - if ($cached = cache('menu')->get($cid)) { - $router_item = $cached->data; - } - else { - $parts = array_slice($original_map, 0, MENU_MAX_PARTS); - $ancestors = menu_get_ancestors($parts); - $router_item = db_query_range('SELECT * FROM {menu_router} WHERE path IN (:ancestors) ORDER BY fit DESC', 0, 1, array(':ancestors' => $ancestors))->fetchAssoc(); - cache('menu')->set($cid, $router_item); - } - if ($router_item) { - // Allow modules to alter the router item before it is translated and - // checked for access. - drupal_alter('menu_get_item', $router_item, $path, $original_map); - - $map = _menu_translate($router_item, $original_map); - $router_item['original_map'] = $original_map; - if ($map === FALSE) { - $router_items[$path] = FALSE; - return FALSE; - } - if ($router_item['access']) { - $router_item['map'] = $map; - $router_item['page_arguments'] = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts'])); - $router_item['theme_arguments'] = array_merge(menu_unserialize($router_item['theme_arguments'], $map), array_slice($map, $router_item['number_parts'])); - } - } - $router_items[$path] = $router_item; - } - return $router_items[$path]; -} - -/** - * Execute the page callback associated with the current path. - * - * @param $path - * The drupal path whose handler is to be be executed. If set to NULL, then - * the current path is used. - * @param $deliver - * (optional) A boolean to indicate whether the content should be sent to the - * browser using the appropriate delivery callback (TRUE) or whether to return - * the result to the caller (FALSE). - */ -function menu_execute_active_handler($path = NULL, $deliver = TRUE) { - // Check if site is offline. - $page_callback_result = _menu_site_is_offline() ? MENU_SITE_OFFLINE : MENU_SITE_ONLINE; - - // Allow other modules to change the site status but not the path because that - // would not change the global variable. hook_url_inbound_alter() can be used - // to change the path. Code later will not use the $read_only_path variable. - $read_only_path = !empty($path) ? $path : $_GET['q']; - drupal_alter('menu_site_status', $page_callback_result, $read_only_path); - - // Only continue if the site status is not set. - if ($page_callback_result == MENU_SITE_ONLINE) { - if ($router_item = menu_get_item($path)) { - if ($router_item['access']) { - if ($router_item['include_file']) { - require_once DRUPAL_ROOT . '/' . $router_item['include_file']; - } - $page_callback_result = call_user_func_array($router_item['page_callback'], $router_item['page_arguments']); - } - else { - $page_callback_result = MENU_ACCESS_DENIED; - } - } - else { - $page_callback_result = MENU_NOT_FOUND; - } - } - - // Deliver the result of the page callback to the browser, or if requested, - // return it raw, so calling code can do more processing. - if ($deliver) { - $default_delivery_callback = (isset($router_item) && $router_item) ? $router_item['delivery_callback'] : NULL; - drupal_deliver_page($page_callback_result, $default_delivery_callback); - } - else { - return $page_callback_result; - } -} - -/** - * Loads objects into the map as defined in the $item['load_functions']. - * - * @param $item - * A menu router or menu link item - * @param $map - * An array of path arguments (ex: array('node', '5')) - * - * @return - * Returns TRUE for success, FALSE if an object cannot be loaded. - * Names of object loading functions are placed in $item['load_functions']. - * Loaded objects are placed in $map[]; keys are the same as keys in the - * $item['load_functions'] array. - * $item['access'] is set to FALSE if an object cannot be loaded. - */ -function _menu_load_objects(&$item, &$map) { - if ($load_functions = $item['load_functions']) { - // If someone calls this function twice, then unserialize will fail. - if (!is_array($load_functions)) { - $load_functions = unserialize($load_functions); - } - $path_map = $map; - foreach ($load_functions as $index => $function) { - if ($function) { - $value = isset($path_map[$index]) ? $path_map[$index] : ''; - if (is_array($function)) { - // Set up arguments for the load function. These were pulled from - // 'load arguments' in the hook_menu() entry, but they need - // some processing. In this case the $function is the key to the - // load_function array, and the value is the list of arguments. - list($function, $args) = each($function); - $load_functions[$index] = $function; - - // Some arguments are placeholders for dynamic items to process. - foreach ($args as $i => $arg) { - if ($arg === '%index') { - // Pass on argument index to the load function, so multiple - // occurrences of the same placeholder can be identified. - $args[$i] = $index; - } - if ($arg === '%map') { - // Pass on menu map by reference. The accepting function must - // also declare this as a reference if it wants to modify - // the map. - $args[$i] = &$map; - } - if (is_int($arg)) { - $args[$i] = isset($path_map[$arg]) ? $path_map[$arg] : ''; - } - } - array_unshift($args, $value); - $return = call_user_func_array($function, $args); - } - else { - $return = $function($value); - } - // If callback returned an error or there is no callback, trigger 404. - if ($return === FALSE) { - $item['access'] = FALSE; - $map = FALSE; - return FALSE; - } - $map[$index] = $return; - } - } - $item['load_functions'] = $load_functions; - } - return TRUE; -} - -/** - * Check access to a menu item using the access callback - * - * @param $item - * A menu router or menu link item - * @param $map - * An array of path arguments (ex: array('node', '5')) - * - * @return - * $item['access'] becomes TRUE if the item is accessible, FALSE otherwise. - */ -function _menu_check_access(&$item, $map) { - // Determine access callback, which will decide whether or not the current - // user has access to this path. - $callback = empty($item['access_callback']) ? 0 : trim($item['access_callback']); - // Check for a TRUE or FALSE value. - if (is_numeric($callback)) { - $item['access'] = (bool) $callback; - } - else { - $arguments = menu_unserialize($item['access_arguments'], $map); - // As call_user_func_array is quite slow and user_access is a very common - // callback, it is worth making a special case for it. - if ($callback == 'user_access') { - $item['access'] = (count($arguments) == 1) ? user_access($arguments[0]) : user_access($arguments[0], $arguments[1]); - } - elseif (function_exists($callback)) { - $item['access'] = call_user_func_array($callback, $arguments); - } - } -} - -/** - * Localize the router item title using t() or another callback. - * - * Translate the title and description to allow storage of English title - * strings in the database, yet display of them in the language required - * by the current user. - * - * @param $item - * A menu router item or a menu link item. - * @param $map - * The path as an array with objects already replaced. E.g., for path - * node/123 $map would be array('node', $node) where $node is the node - * object for node 123. - * @param $link_translate - * TRUE if we are translating a menu link item; FALSE if we are - * translating a menu router item. - * - * @return - * No return value. - * $item['title'] is localized according to $item['title_callback']. - * If an item's callback is check_plain(), $item['options']['html'] becomes - * TRUE. - * $item['description'] is translated using t(). - * When doing link translation and the $item['options']['attributes']['title'] - * (link title attribute) matches the description, it is translated as well. - */ -function _menu_item_localize(&$item, $map, $link_translate = FALSE) { - $callback = $item['title_callback']; - $item['localized_options'] = $item['options']; - // All 'class' attributes are assumed to be an array during rendering, but - // links stored in the database may use an old string value. - // @todo In order to remove this code we need to implement a database update - // including unserializing all existing link options and running this code - // on them, as well as adding validation to menu_link_save(). - if (isset($item['options']['attributes']['class']) && is_string($item['options']['attributes']['class'])) { - $item['localized_options']['attributes']['class'] = explode(' ', $item['options']['attributes']['class']); - } - // If we are translating the title of a menu link, and its title is the same - // as the corresponding router item, then we can use the title information - // from the router. If it's customized, then we need to use the link title - // itself; can't localize. - // If we are translating a router item (tabs, page, breadcrumb), then we - // can always use the information from the router item. - if (!$link_translate || ($item['title'] == $item['link_title'])) { - // t() is a special case. Since it is used very close to all the time, - // we handle it directly instead of using indirect, slower methods. - if ($callback == 't') { - if (empty($item['title_arguments'])) { - $item['title'] = t($item['title']); - } - else { - $item['title'] = t($item['title'], menu_unserialize($item['title_arguments'], $map)); - } - } - elseif ($callback && function_exists($callback)) { - if (empty($item['title_arguments'])) { - $item['title'] = $callback($item['title']); - } - else { - $item['title'] = call_user_func_array($callback, menu_unserialize($item['title_arguments'], $map)); - } - // Avoid calling check_plain again on l() function. - if ($callback == 'check_plain') { - $item['localized_options']['html'] = TRUE; - } - } - } - elseif ($link_translate) { - $item['title'] = $item['link_title']; - } - - // Translate description, see the motivation above. - if (!empty($item['description'])) { - $original_description = $item['description']; - $item['description'] = t($item['description']); - if ($link_translate && isset($item['options']['attributes']['title']) && $item['options']['attributes']['title'] == $original_description) { - $item['localized_options']['attributes']['title'] = $item['description']; - } - } -} - -/** - * Handles dynamic path translation and menu access control. - * - * When a user arrives on a page such as node/5, this function determines - * what "5" corresponds to, by inspecting the page's menu path definition, - * node/%node. This will call node_load(5) to load the corresponding node - * object. - * - * It also works in reverse, to allow the display of tabs and menu items which - * contain these dynamic arguments, translating node/%node to node/5. - * - * Translation of menu item titles and descriptions are done here to - * allow for storage of English strings in the database, and translation - * to the language required to generate the current page. - * - * @param $router_item - * A menu router item - * @param $map - * An array of path arguments (ex: array('node', '5')) - * @param $to_arg - * Execute $item['to_arg_functions'] or not. Use only if you want to render a - * path from the menu table, for example tabs. - * - * @return - * Returns the map with objects loaded as defined in the - * $item['load_functions']. $item['access'] becomes TRUE if the item is - * accessible, FALSE otherwise. $item['href'] is set according to the map. - * If an error occurs during calling the load_functions (like trying to load - * a non existing node) then this function return FALSE. - */ -function _menu_translate(&$router_item, $map, $to_arg = FALSE) { - if ($to_arg && !empty($router_item['to_arg_functions'])) { - // Fill in missing path elements, such as the current uid. - _menu_link_map_translate($map, $router_item['to_arg_functions']); - } - // The $path_map saves the pieces of the path as strings, while elements in - // $map may be replaced with loaded objects. - $path_map = $map; - if (!empty($router_item['load_functions']) && !_menu_load_objects($router_item, $map)) { - // An error occurred loading an object. - $router_item['access'] = FALSE; - return FALSE; - } - - // Generate the link path for the page request or local tasks. - $link_map = explode('/', $router_item['path']); - if (isset($router_item['tab_root'])) { - $tab_root_map = explode('/', $router_item['tab_root']); - } - if (isset($router_item['tab_parent'])) { - $tab_parent_map = explode('/', $router_item['tab_parent']); - } - for ($i = 0; $i < $router_item['number_parts']; $i++) { - if ($link_map[$i] == '%') { - $link_map[$i] = $path_map[$i]; - } - if (isset($tab_root_map[$i]) && $tab_root_map[$i] == '%') { - $tab_root_map[$i] = $path_map[$i]; - } - if (isset($tab_parent_map[$i]) && $tab_parent_map[$i] == '%') { - $tab_parent_map[$i] = $path_map[$i]; - } - } - $router_item['href'] = implode('/', $link_map); - $router_item['tab_root_href'] = implode('/', $tab_root_map); - $router_item['tab_parent_href'] = implode('/', $tab_parent_map); - $router_item['options'] = array(); - _menu_check_access($router_item, $map); - - // For performance, don't localize an item the user can't access. - if ($router_item['access']) { - _menu_item_localize($router_item, $map); - } - - return $map; -} - -/** - * This function translates the path elements in the map using any to_arg - * helper function. These functions take an argument and return an object. - * See http://drupal.org/node/109153 for more information. - * - * @param $map - * An array of path arguments (ex: array('node', '5')) - * @param $to_arg_functions - * An array of helper function (ex: array(2 => 'menu_tail_to_arg')) - */ -function _menu_link_map_translate(&$map, $to_arg_functions) { - $to_arg_functions = unserialize($to_arg_functions); - foreach ($to_arg_functions as $index => $function) { - // Translate place-holders into real values. - $arg = $function(!empty($map[$index]) ? $map[$index] : '', $map, $index); - if (!empty($map[$index]) || isset($arg)) { - $map[$index] = $arg; - } - else { - unset($map[$index]); - } - } -} - -/** - * Returns path as one string from the argument we are currently at. - */ -function menu_tail_to_arg($arg, $map, $index) { - return implode('/', array_slice($map, $index)); -} - -/** - * Loads path as one string from the argument we are currently at. - * - * To use this load function, you must specify the load arguments - * in the router item as: - * @code - * $item['load arguments'] = array('%map', '%index'); - * @endcode - * - * @see search_menu(). - */ -function menu_tail_load($arg, &$map, $index) { - $arg = implode('/', array_slice($map, $index)); - $map = array_slice($map, 0, $index); - return $arg; -} - -/** - * This function is similar to _menu_translate() but does link-specific - * preparation such as always calling to_arg functions - * - * @param $item - * A menu link. - * @param $translate - * (optional) Whether to try to translate a link containing dynamic path - * argument placeholders (%) based on the menu router item of the current - * path. Defaults to FALSE. Internally used for breadcrumbs. - * - * @return - * Returns the map of path arguments with objects loaded as defined in the - * $item['load_functions']. - * $item['access'] becomes TRUE if the item is accessible, FALSE otherwise. - * $item['href'] is generated from link_path, possibly by to_arg functions. - * $item['title'] is generated from link_title, and may be localized. - * $item['options'] is unserialized; it is also changed within the call here - * to $item['localized_options'] by _menu_item_localize(). - */ -function _menu_link_translate(&$item, $translate = FALSE) { - if (!is_array($item['options'])) { - $item['options'] = unserialize($item['options']); - } - if ($item['external']) { - $item['access'] = 1; - $map = array(); - $item['href'] = $item['link_path']; - $item['title'] = $item['link_title']; - $item['localized_options'] = $item['options']; - } - else { - // Complete the path of the menu link with elements from the current path, - // if it contains dynamic placeholders (%). - $map = explode('/', $item['link_path']); - if (strpos($item['link_path'], '%') !== FALSE) { - // Invoke registered to_arg callbacks. - if (!empty($item['to_arg_functions'])) { - _menu_link_map_translate($map, $item['to_arg_functions']); - } - // Or try to derive the path argument map from the current router item, - // if this $item's path is within the router item's path. This means - // that if we are on the current path 'foo/%/bar/%/baz', then - // menu_get_item() will have translated the menu router item for the - // current path, and we can take over the argument map for a link like - // 'foo/%/bar'. This inheritance is only valid for breadcrumb links. - // @see _menu_tree_check_access() - // @see menu_get_active_breadcrumb() - elseif ($translate && ($current_router_item = menu_get_item())) { - // If $translate is TRUE, then this link is in the active trail. - // Only translate paths within the current path. - if (strpos($current_router_item['path'], $item['link_path']) === 0) { - $count = count($map); - $map = array_slice($current_router_item['original_map'], 0, $count); - $item['original_map'] = $map; - if (isset($current_router_item['map'])) { - $item['map'] = array_slice($current_router_item['map'], 0, $count); - } - // Reset access to check it (for the first time). - unset($item['access']); - } - } - } - $item['href'] = implode('/', $map); - - // Skip links containing untranslated arguments. - if (strpos($item['href'], '%') !== FALSE) { - $item['access'] = FALSE; - return FALSE; - } - // menu_tree_check_access() may set this ahead of time for links to nodes. - if (!isset($item['access'])) { - if (!empty($item['load_functions']) && !_menu_load_objects($item, $map)) { - // An error occurred loading an object. - $item['access'] = FALSE; - return FALSE; - } - _menu_check_access($item, $map); - } - // For performance, don't localize a link the user can't access. - if ($item['access']) { - _menu_item_localize($item, $map, TRUE); - } - } - - // Allow other customizations - e.g. adding a page-specific query string to the - // options array. For performance reasons we only invoke this hook if the link - // has the 'alter' flag set in the options array. - if (!empty($item['options']['alter'])) { - drupal_alter('translated_menu_link', $item, $map); - } - - return $map; -} - -/** - * Get a loaded object from a router item. - * - * menu_get_object() provides access to objects loaded by the current router - * item. For example, on the page node/%node, the router loads the %node object, - * and calling menu_get_object() will return that. Normally, it is necessary to - * specify the type of object referenced, however node is the default. - * The following example tests to see whether the node being displayed is of the - * "story" content type: - * @code - * $node = menu_get_object(); - * $story = $node->type == 'story'; - * @endcode - * - * @param $type - * Type of the object. These appear in hook_menu definitions as %type. Core - * provides aggregator_feed, aggregator_category, contact, filter_format, - * forum_term, menu, menu_link, node, taxonomy_vocabulary, user. See the - * relevant {$type}_load function for more on each. Defaults to node. - * @param $position - * The position of the object in the path, where the first path segment is 0. - * For node/%node, the position of %node is 1, but for comment/reply/%node, - * it's 2. Defaults to 1. - * @param $path - * See menu_get_item() for more on this. Defaults to the current path. - */ -function menu_get_object($type = 'node', $position = 1, $path = NULL) { - $router_item = menu_get_item($path); - if (isset($router_item['load_functions'][$position]) && !empty($router_item['map'][$position]) && $router_item['load_functions'][$position] == $type . '_load') { - return $router_item['map'][$position]; - } -} - -/** - * Renders a menu tree based on the current path. - * - * The tree is expanded based on the current path and dynamic paths are also - * changed according to the defined to_arg functions (for example the 'My - * account' link is changed from user/% to a link with the current user's uid). - * - * @param $menu_name - * The name of the menu. - * - * @return - * A structured array representing the specified menu on the current page, to - * be rendered by drupal_render(). - */ -function menu_tree($menu_name) { - $menu_output = &drupal_static(__FUNCTION__, array()); - - if (!isset($menu_output[$menu_name])) { - $tree = menu_tree_page_data($menu_name); - $menu_output[$menu_name] = menu_tree_output($tree); - } - return $menu_output[$menu_name]; -} - -/** - * Returns a rendered menu tree. - * - * The menu item's LI element is given one of the following classes: - * - expanded: The menu item is showing its submenu. - * - collapsed: The menu item has a submenu which is not shown. - * - leaf: The menu item has no submenu. - * - * @param $tree - * A data structure representing the tree as returned from menu_tree_data. - * - * @return - * A structured array to be rendered by drupal_render(). - */ -function menu_tree_output($tree) { - $build = array(); - $items = array(); - - // Pull out just the menu links we are going to render so that we - // get an accurate count for the first/last classes. - foreach ($tree as $data) { - if ($data['link']['access'] && !$data['link']['hidden']) { - $items[] = $data; - } - } - - $router_item = menu_get_item(); - $num_items = count($items); - foreach ($items as $i => $data) { - $class = array(); - if ($i == 0) { - $class[] = 'first'; - } - if ($i == $num_items - 1) { - $class[] = 'last'; - } - // Set a class for the <li>-tag. Since $data['below'] may contain local - // tasks, only set 'expanded' class if the link also has children within - // the current menu. - if ($data['link']['has_children'] && $data['below']) { - $class[] = 'expanded'; - } - elseif ($data['link']['has_children']) { - $class[] = 'collapsed'; - } - else { - $class[] = 'leaf'; - } - // Set a class if the link is in the active trail. - if ($data['link']['in_active_trail']) { - $class[] = 'active-trail'; - $data['link']['localized_options']['attributes']['class'][] = 'active-trail'; - } - // Normally, l() compares the href of every link with $_GET['q'] and sets - // the active class accordingly. But local tasks do not appear in menu - // trees, so if the current path is a local task, and this link is its - // tab root, then we have to set the class manually. - if ($data['link']['href'] == $router_item['tab_root_href'] && $data['link']['href'] != $_GET['q']) { - $data['link']['localized_options']['attributes']['class'][] = 'active'; - } - - // Allow menu-specific theme overrides. - $element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_'); - $element['#attributes']['class'] = $class; - $element['#title'] = $data['link']['title']; - $element['#href'] = $data['link']['href']; - $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array(); - $element['#below'] = $data['below'] ? menu_tree_output($data['below']) : $data['below']; - $element['#original_link'] = $data['link']; - // Index using the link's unique mlid. - $build[$data['link']['mlid']] = $element; - } - if ($build) { - // Make sure drupal_render() does not re-order the links. - $build['#sorted'] = TRUE; - // Add the theme wrapper for outer markup. - // Allow menu-specific theme overrides. - $build['#theme_wrappers'][] = 'menu_tree__' . strtr($data['link']['menu_name'], '-', '_'); - } - - return $build; -} - -/** - * Get the data structure representing a named menu tree. - * - * Since this can be the full tree including hidden items, the data returned - * may be used for generating an an admin interface or a select. - * - * @param $menu_name - * The named menu links to return - * @param $link - * A fully loaded menu link, or NULL. If a link is supplied, only the - * path to root will be included in the returned tree - as if this link - * represented the current page in a visible menu. - * @param $max_depth - * Optional maximum depth of links to retrieve. Typically useful if only one - * or two levels of a sub tree are needed in conjunction with a non-NULL - * $link, in which case $max_depth should be greater than $link['depth']. - * - * @return - * An tree of menu links in an array, in the order they should be rendered. - */ -function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) { - $tree = &drupal_static(__FUNCTION__, array()); - - // Use $mlid as a flag for whether the data being loaded is for the whole tree. - $mlid = isset($link['mlid']) ? $link['mlid'] : 0; - // Generate a cache ID (cid) specific for this $menu_name, $link, $language, and depth. - $cid = 'links:' . $menu_name . ':all:' . $mlid . ':' . $GLOBALS['language']->language . ':' . (int) $max_depth; - - if (!isset($tree[$cid])) { - // If the static variable doesn't have the data, check {cache_menu}. - $cache = cache('menu')->get($cid); - if ($cache && isset($cache->data)) { - // If the cache entry exists, it contains the parameters for - // menu_build_tree(). - $tree_parameters = $cache->data; - } - // If the tree data was not in the cache, build $tree_parameters. - if (!isset($tree_parameters)) { - $tree_parameters = array( - 'min_depth' => 1, - 'max_depth' => $max_depth, - ); - if ($mlid) { - // The tree is for a single item, so we need to match the values in its - // p columns and 0 (the top level) with the plid values of other links. - $parents = array(0); - for ($i = 1; $i < MENU_MAX_DEPTH; $i++) { - if (!empty($link["p$i"])) { - $parents[] = $link["p$i"]; - } - } - $tree_parameters['expanded'] = $parents; - $tree_parameters['active_trail'] = $parents; - $tree_parameters['active_trail'][] = $mlid; - } - - // Cache the tree building parameters using the page-specific cid. - cache('menu')->set($cid, $tree_parameters); - } - - // Build the tree using the parameters; the resulting tree will be cached - // by _menu_build_tree()). - $tree[$cid] = menu_build_tree($menu_name, $tree_parameters); - } - - return $tree[$cid]; -} - -/** - * Set the path for determining the active trail of the specified menu tree. - * - * This path will also affect the breadcrumbs under some circumstances. - * Breadcrumbs are built using the preferred link returned by - * menu_link_get_preferred(). If the preferred link is inside one of the menus - * specified in calls to menu_tree_set_path(), the preferred link will be - * overridden by the corresponding path returned by menu_tree_get_path(). - * - * Setting this path does not affect the main content; for that use - * menu_set_active_item() instead. - * - * @param $menu_name - * The name of the affected menu tree. - * @param $path - * The path to use when finding the active trail. - */ -function menu_tree_set_path($menu_name, $path = NULL) { - $paths = &drupal_static(__FUNCTION__); - if (isset($path)) { - $paths[$menu_name] = $path; - } - return isset($paths[$menu_name]) ? $paths[$menu_name] : NULL; -} - -/** - * Get the path for determining the active trail of the specified menu tree. - * - * @param $menu_name - * The menu name of the requested tree. - * - * @return - * A string containing the path. If no path has been specified with - * menu_tree_set_path(), NULL is returned. - */ -function menu_tree_get_path($menu_name) { - return menu_tree_set_path($menu_name); -} - -/** - * Get the data structure representing a named menu tree, based on the current page. - * - * The tree order is maintained by storing each parent in an individual - * field, see http://drupal.org/node/141866 for more. - * - * @param $menu_name - * The named menu links to return. - * @param $max_depth - * (optional) The maximum depth of links to retrieve. - * @param $only_active_trail - * (optional) Whether to only return the links in the active trail (TRUE) - * instead of all links on every level of the menu link tree (FALSE). Defaults - * to FALSE. Internally used for breadcrumbs only. - * - * @return - * An array of menu links, in the order they should be rendered. The array - * is a list of associative arrays -- these have two keys, link and below. - * link is a menu item, ready for theming as a link. Below represents the - * submenu below the link if there is one, and it is a subtree that has the - * same structure described for the top-level array. - */ -function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail = FALSE) { - $tree = &drupal_static(__FUNCTION__, array()); - - // Check if the active trail has been overridden for this menu tree. - $active_path = menu_tree_get_path($menu_name); - // Load the menu item corresponding to the current page. - if ($item = menu_get_item($active_path)) { - if (isset($max_depth)) { - $max_depth = min($max_depth, MENU_MAX_DEPTH); - } - // Generate a cache ID (cid) specific for this page. - $cid = 'links:' . $menu_name . ':page:' . $item['href'] . ':' . $GLOBALS['language']->language . ':' . (int) $item['access'] . ':' . (int) $max_depth; - // If we are asked for the active trail only, and $menu_name has not been - // built and cached for this page yet, then this likely means that it - // won't be built anymore, as this function is invoked from - // template_process_page(). So in order to not build a giant menu tree - // that needs to be checked for access on all levels, we simply check - // whether we have the menu already in cache, or otherwise, build a minimum - // tree containing the breadcrumb/active trail only. - // @see menu_set_active_trail() - if (!isset($tree[$cid]) && $only_active_trail) { - $cid .= ':trail'; - } - - if (!isset($tree[$cid])) { - // If the static variable doesn't have the data, check {cache_menu}. - $cache = cache('menu')->get($cid); - if ($cache && isset($cache->data)) { - // If the cache entry exists, it contains the parameters for - // menu_build_tree(). - $tree_parameters = $cache->data; - } - // If the tree data was not in the cache, build $tree_parameters. - if (!isset($tree_parameters)) { - $tree_parameters = array( - 'min_depth' => 1, - 'max_depth' => $max_depth, - ); - // Parent mlids; used both as key and value to ensure uniqueness. - // We always want all the top-level links with plid == 0. - $active_trail = array(0 => 0); - - // If the item for the current page is accessible, build the tree - // parameters accordingly. - if ($item['access']) { - // Find a menu link corresponding to the current path. If $active_path - // is NULL, let menu_link_get_preferred() determine the path. - if ($active_link = menu_link_get_preferred($active_path)) { - // The active link may only be taken into account to build the - // active trail, if it resides in the requested menu. Otherwise, - // we'd needlessly re-run _menu_build_tree() queries for every menu - // on every page. - if ($active_link['menu_name'] == $menu_name) { - // Use all the coordinates, except the last one because there - // can be no child beyond the last column. - for ($i = 1; $i < MENU_MAX_DEPTH; $i++) { - if ($active_link['p' . $i]) { - $active_trail[$active_link['p' . $i]] = $active_link['p' . $i]; - } - } - // If we are asked to build links for the active trail only, skip - // the entire 'expanded' handling. - if ($only_active_trail) { - $tree_parameters['only_active_trail'] = TRUE; - } - } - } - $parents = $active_trail; - - $expanded = variable_get('menu_expanded', array()); - // Check whether the current menu has any links set to be expanded. - if (!$only_active_trail && in_array($menu_name, $expanded)) { - // Collect all the links set to be expanded, and then add all of - // their children to the list as well. - do { - $result = db_select('menu_links', NULL, array('fetch' => PDO::FETCH_ASSOC)) - ->fields('menu_links', array('mlid')) - ->condition('menu_name', $menu_name) - ->condition('expanded', 1) - ->condition('has_children', 1) - ->condition('plid', $parents, 'IN') - ->condition('mlid', $parents, 'NOT IN') - ->execute(); - $num_rows = FALSE; - foreach ($result as $item) { - $parents[$item['mlid']] = $item['mlid']; - $num_rows = TRUE; - } - } while ($num_rows); - } - $tree_parameters['expanded'] = $parents; - $tree_parameters['active_trail'] = $active_trail; - } - // If access is denied, we only show top-level links in menus. - else { - $tree_parameters['expanded'] = $active_trail; - $tree_parameters['active_trail'] = $active_trail; - } - // Cache the tree building parameters using the page-specific cid. - cache('menu')->set($cid, $tree_parameters); - } - - // Build the tree using the parameters; the resulting tree will be cached - // by _menu_build_tree(). - $tree[$cid] = menu_build_tree($menu_name, $tree_parameters); - } - return $tree[$cid]; - } - - return array(); -} - -/** - * Build a menu tree, translate links, and check access. - * - * @param $menu_name - * The name of the menu. - * @param $parameters - * (optional) An associative array of build parameters. Possible keys: - * - expanded: An array of parent link ids to return only menu links that are - * children of one of the plids in this list. If empty, the whole menu tree - * is built, unless 'only_active_trail' is TRUE. - * - active_trail: An array of mlids, representing the coordinates of the - * currently active menu link. - * - only_active_trail: Whether to only return links that are in the active - * trail. This option is ignored, if 'expanded' is non-empty. Internally - * used for breadcrumbs. - * - min_depth: The minimum depth of menu links in the resulting tree. - * Defaults to 1, which is the default to build a whole tree for a menu, i.e. - * excluding menu container itself. - * - max_depth: The maximum depth of menu links in the resulting tree. - * - * @return - * A fully built menu tree. - */ -function menu_build_tree($menu_name, array $parameters = array()) { - // Build the menu tree. - $data = _menu_build_tree($menu_name, $parameters); - // Check access for the current user to each item in the tree. - menu_tree_check_access($data['tree'], $data['node_links']); - return $data['tree']; -} - -/** - * Build a menu tree. - * - * This function may be used build the data for a menu tree only, for example - * to further massage the data manually before further processing happens. - * menu_tree_check_access() needs to be invoked afterwards. - * - * @see menu_build_tree() - */ -function _menu_build_tree($menu_name, array $parameters = array()) { - // Static cache of already built menu trees. - $trees = &drupal_static(__FUNCTION__, array()); - - // Build the cache id; sort parents to prevent duplicate storage and remove - // default parameter values. - if (isset($parameters['expanded'])) { - sort($parameters['expanded']); - } - $tree_cid = 'links:' . $menu_name . ':tree-data:' . $GLOBALS['language']->language . ':' . hash('sha256', serialize($parameters)); - - // If we do not have this tree in the static cache, check {cache_menu}. - if (!isset($trees[$tree_cid])) { - $cache = cache('menu')->get($tree_cid); - if ($cache && isset($cache->data)) { - $trees[$tree_cid] = $cache->data; - } - } - - if (!isset($trees[$tree_cid])) { - // Select the links from the table, and recursively build the tree. We - // LEFT JOIN since there is no match in {menu_router} for an external - // link. - $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC)); - $query->addTag('translatable'); - $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path'); - $query->fields('ml'); - $query->fields('m', array( - 'load_functions', - 'to_arg_functions', - 'access_callback', - 'access_arguments', - 'page_callback', - 'page_arguments', - 'delivery_callback', - 'tab_parent', - 'tab_root', - 'title', - 'title_callback', - 'title_arguments', - 'theme_callback', - 'theme_arguments', - 'type', - 'description', - )); - for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) { - $query->orderBy('p' . $i, 'ASC'); - } - $query->condition('ml.menu_name', $menu_name); - if (!empty($parameters['expanded'])) { - $query->condition('ml.plid', $parameters['expanded'], 'IN'); - } - elseif (!empty($parameters['only_active_trail'])) { - $query->condition('ml.mlid', $parameters['active_trail'], 'IN'); - } - $min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1); - if ($min_depth != 1) { - $query->condition('ml.depth', $min_depth, '>='); - } - if (isset($parameters['max_depth'])) { - $query->condition('ml.depth', $parameters['max_depth'], '<='); - } - - // Build an ordered array of links using the query result object. - $links = array(); - foreach ($query->execute() as $item) { - $links[] = $item; - } - $active_trail = (isset($parameters['active_trail']) ? $parameters['active_trail'] : array()); - $data['tree'] = menu_tree_data($links, $active_trail, $min_depth); - $data['node_links'] = array(); - menu_tree_collect_node_links($data['tree'], $data['node_links']); - - // Cache the data, if it is not already in the cache. - cache('menu')->set($tree_cid, $data); - $trees[$tree_cid] = $data; - } - - return $trees[$tree_cid]; -} - -/** - * Recursive helper function - collect node links. - * - * @param $tree - * The menu tree you wish to collect node links from. - * @param $node_links - * An array in which to store the collected node links. - */ -function menu_tree_collect_node_links(&$tree, &$node_links) { - foreach ($tree as $key => $v) { - if ($tree[$key]['link']['router_path'] == 'node/%') { - $nid = substr($tree[$key]['link']['link_path'], 5); - if (is_numeric($nid)) { - $node_links[$nid][$tree[$key]['link']['mlid']] = &$tree[$key]['link']; - $tree[$key]['link']['access'] = FALSE; - } - } - if ($tree[$key]['below']) { - menu_tree_collect_node_links($tree[$key]['below'], $node_links); - } - } -} - -/** - * Check access and perform other dynamic operations for each link in the tree. - * - * @param $tree - * The menu tree you wish to operate on. - * @param $node_links - * A collection of node link references generated from $tree by - * menu_tree_collect_node_links(). - */ -function menu_tree_check_access(&$tree, $node_links = array()) { - if ($node_links) { - $nids = array_keys($node_links); - $select = db_select('node', 'n'); - $select->addField('n', 'nid'); - $select->condition('n.status', 1); - $select->condition('n.nid', $nids, 'IN'); - $select->addTag('node_access'); - $nids = $select->execute()->fetchCol(); - foreach ($nids as $nid) { - foreach ($node_links[$nid] as $mlid => $link) { - $node_links[$nid][$mlid]['access'] = TRUE; - } - } - } - _menu_tree_check_access($tree); -} - -/** - * Recursive helper function for menu_tree_check_access() - */ -function _menu_tree_check_access(&$tree) { - $new_tree = array(); - foreach ($tree as $key => $v) { - $item = &$tree[$key]['link']; - _menu_link_translate($item); - if ($item['access'] || ($item['in_active_trail'] && strpos($item['href'], '%') !== FALSE)) { - if ($tree[$key]['below']) { - _menu_tree_check_access($tree[$key]['below']); - } - // The weights are made a uniform 5 digits by adding 50000 as an offset. - // After _menu_link_translate(), $item['title'] has the localized link title. - // Adding the mlid to the end of the index insures that it is unique. - $new_tree[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $tree[$key]; - } - } - // Sort siblings in the tree based on the weights and localized titles. - ksort($new_tree); - $tree = $new_tree; -} - -/** - * Builds the data representing a menu tree. - * - * @param $links - * A flat array of menu links that are part of the menu. Each array element - * is an associative array of information about the menu link, containing the - * fields from the {menu_links} table, and optionally additional information - * from the {menu_router} table, if the menu item appears in both tables. - * This array must be ordered depth-first. See _menu_build_tree() for a sample - * query. - * @param $parents - * An array of the menu link ID values that are in the path from the current - * page to the root of the menu tree. - * @param $depth - * The minimum depth to include in the returned menu tree. - * - * @return - * An array of menu links in the form of a tree. Each item in the tree is an - * associative array containing: - * - link: The menu link item from $links, with additional element - * 'in_active_trail' (TRUE if the link ID was in $parents). - * - below: An array containing the sub-tree of this item, where each element - * is a tree item array with 'link' and 'below' elements. This array will be - * empty if the menu item has no items in its sub-tree having a depth - * greater than or equal to $depth. - */ -function menu_tree_data(array $links, array $parents = array(), $depth = 1) { - // Reverse the array so we can use the more efficient array_pop() function. - $links = array_reverse($links); - return _menu_tree_data($links, $parents, $depth); -} - -/** - * Recursive helper function to build the data representing a menu tree. - * - * The function is a bit complex because the rendering of a link depends on - * the next menu link. - */ -function _menu_tree_data(&$links, $parents, $depth) { - $tree = array(); - while ($item = array_pop($links)) { - // We need to determine if we're on the path to root so we can later build - // the correct active trail and breadcrumb. - $item['in_active_trail'] = in_array($item['mlid'], $parents); - // Add the current link to the tree. - $tree[$item['mlid']] = array( - 'link' => $item, - 'below' => array(), - ); - // Look ahead to the next link, but leave it on the array so it's available - // to other recursive function calls if we return or build a sub-tree. - $next = end($links); - // Check whether the next link is the first in a new sub-tree. - if ($next && $next['depth'] > $depth) { - // Recursively call _menu_tree_data to build the sub-tree. - $tree[$item['mlid']]['below'] = _menu_tree_data($links, $parents, $next['depth']); - // Fetch next link after filling the sub-tree. - $next = end($links); - } - // Determine if we should exit the loop and return. - if (!$next || $next['depth'] < $depth) { - break; - } - } - return $tree; -} - -/** - * Preprocesses the rendered tree for theme_menu_tree(). - */ -function template_preprocess_menu_tree(&$variables) { - $variables['tree'] = $variables['tree']['#children']; -} - -/** - * Returns HTML for a wrapper for a menu sub-tree. - * - * @param $variables - * An associative array containing: - * - tree: An HTML string containing the tree's items. - * - * @see template_preprocess_menu_tree() - * @ingroup themeable - */ -function theme_menu_tree($variables) { - return '<ul class="menu">' . $variables['tree'] . '</ul>'; -} - -/** - * Returns HTML for a menu link and submenu. - * - * @param $variables - * An associative array containing: - * - element: Structured array data for a menu link. - * - * @ingroup themeable - */ -function theme_menu_link(array $variables) { - $element = $variables['element']; - $sub_menu = ''; - - if ($element['#below']) { - $sub_menu = drupal_render($element['#below']); - } - $output = l($element['#title'], $element['#href'], $element['#localized_options']); - return '<li' . drupal_attributes($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n"; -} - -/** - * Returns HTML for a single local task link. - * - * @param $variables - * An associative array containing: - * - element: A render element containing: - * - #link: A menu link array with 'title', 'href', and 'localized_options' - * keys. - * - #active: A boolean indicating whether the local task is active. - * - * @ingroup themeable - */ -function theme_menu_local_task($variables) { - $link = $variables['element']['#link']; - $link_text = $link['title']; - - if (!empty($variables['element']['#active'])) { - // Add text to indicate active tab for non-visual users. - $active = '<span class="element-invisible">' . t('(active tab)') . '</span>'; - - // If the link does not contain HTML already, check_plain() it now. - // After we set 'html'=TRUE the link will not be sanitized by l(). - if (empty($link['localized_options']['html'])) { - $link['title'] = check_plain($link['title']); - } - $link['localized_options']['html'] = TRUE; - $link_text = t('!local-task-title!active', array('!local-task-title' => $link['title'], '!active' => $active)); - } - - return '<li' . (!empty($variables['element']['#active']) ? ' class="active"' : '') . '>' . l($link_text, $link['href'], $link['localized_options']) . "</li>\n"; -} - -/** - * Returns HTML for a single local action link. - * - * @param $variables - * An associative array containing: - * - element: A render element containing: - * - #link: A menu link array with 'title', 'href', and 'localized_options' - * keys. - * - * @ingroup themeable - */ -function theme_menu_local_action($variables) { - $link = $variables['element']['#link']; - - $output = '<li>'; - if (isset($link['href'])) { - $output .= l($link['title'], $link['href'], isset($link['localized_options']) ? $link['localized_options'] : array()); - } - elseif (!empty($link['localized_options']['html'])) { - $output .= $link['title']; - } - else { - $output .= check_plain($link['title']); - } - $output .= "</li>\n"; - - return $output; -} - -/** - * Generates elements for the $arg array in the help hook. - */ -function drupal_help_arg($arg = array()) { - // Note - the number of empty elements should be > MENU_MAX_PARTS. - return $arg + array('', '', '', '', '', '', '', '', '', '', '', ''); -} - -/** - * Returns the help associated with the active menu item. - */ -function menu_get_active_help() { - $output = ''; - $router_path = menu_tab_root_path(); - // We will always have a path unless we are on a 403 or 404. - if (!$router_path) { - return ''; - } - - $arg = drupal_help_arg(arg(NULL)); - - foreach (module_implements('help') as $module) { - $function = $module . '_help'; - // Lookup help for this path. - if ($help = $function($router_path, $arg)) { - $output .= $help . "\n"; - } - } - return $output; -} - -/** - * Gets the custom theme for the current page, if there is one. - * - * @param $initialize - * This parameter should only be used internally; it is set to TRUE in order - * to force the custom theme to be initialized for the current page request. - * - * @return - * The machine-readable name of the custom theme, if there is one. - * - * @see menu_set_custom_theme() - */ -function menu_get_custom_theme($initialize = FALSE) { - $custom_theme = &drupal_static(__FUNCTION__); - // Skip this if the site is offline or being installed or updated, since the - // menu system may not be correctly initialized then. - if ($initialize && !_menu_site_is_offline(TRUE) && (!defined('MAINTENANCE_MODE') || (MAINTENANCE_MODE != 'update' && MAINTENANCE_MODE != 'install'))) { - // First allow modules to dynamically set a custom theme for the current - // page. Since we can only have one, the last module to return a valid - // theme takes precedence. - $custom_themes = array_filter(module_invoke_all('custom_theme'), 'drupal_theme_access'); - if (!empty($custom_themes)) { - $custom_theme = array_pop($custom_themes); - } - // If there is a theme callback function for the current page, execute it. - // If this returns a valid theme, it will override any theme that was set - // by a hook_custom_theme() implementation above. - $router_item = menu_get_item(); - if (!empty($router_item['access']) && !empty($router_item['theme_callback']) && function_exists($router_item['theme_callback'])) { - $theme_name = call_user_func_array($router_item['theme_callback'], $router_item['theme_arguments']); - if (drupal_theme_access($theme_name)) { - $custom_theme = $theme_name; - } - } - } - return $custom_theme; -} - -/** - * Sets a custom theme for the current page, if there is one. - */ -function menu_set_custom_theme() { - menu_get_custom_theme(TRUE); -} - -/** - * Return an array containing the names of system-defined (default) menus. - */ -function menu_list_system_menus() { - return array( - 'navigation' => 'Navigation', - 'management' => 'Management', - 'user-menu' => 'User menu', - 'main-menu' => 'Main menu', - ); -} - -/** - * Return an array of links to be rendered as the Main menu. - */ -function menu_main_menu() { - return menu_navigation_links(variable_get('menu_main_links_source', 'main-menu')); -} - -/** - * Return an array of links to be rendered as the Secondary links. - */ -function menu_secondary_menu() { - - // If the secondary menu source is set as the primary menu, we display the - // second level of the primary menu. - if (variable_get('menu_secondary_links_source', 'user-menu') == variable_get('menu_main_links_source', 'main-menu')) { - return menu_navigation_links(variable_get('menu_main_links_source', 'main-menu'), 1); - } - else { - return menu_navigation_links(variable_get('menu_secondary_links_source', 'user-menu'), 0); - } -} - -/** - * Return an array of links for a navigation menu. - * - * @param $menu_name - * The name of the menu. - * @param $level - * Optional, the depth of the menu to be returned. - * - * @return - * An array of links of the specified menu and level. - */ -function menu_navigation_links($menu_name, $level = 0) { - // Don't even bother querying the menu table if no menu is specified. - if (empty($menu_name)) { - return array(); - } - - // Get the menu hierarchy for the current page. - $tree = menu_tree_page_data($menu_name, $level + 1); - - // Go down the active trail until the right level is reached. - while ($level-- > 0 && $tree) { - // Loop through the current level's items until we find one that is in trail. - while ($item = array_shift($tree)) { - if ($item['link']['in_active_trail']) { - // If the item is in the active trail, we continue in the subtree. - $tree = empty($item['below']) ? array() : $item['below']; - break; - } - } - } - - // Create a single level of links. - $router_item = menu_get_item(); - $links = array(); - foreach ($tree as $item) { - if (!$item['link']['hidden']) { - $class = ''; - $l = $item['link']['localized_options']; - $l['href'] = $item['link']['href']; - $l['title'] = $item['link']['title']; - if ($item['link']['in_active_trail']) { - $class = ' active-trail'; - $l['attributes']['class'][] = 'active-trail'; - } - // Normally, l() compares the href of every link with $_GET['q'] and sets - // the active class accordingly. But local tasks do not appear in menu - // trees, so if the current path is a local task, and this link is its - // tab root, then we have to set the class manually. - if ($item['link']['href'] == $router_item['tab_root_href'] && $item['link']['href'] != $_GET['q']) { - $l['attributes']['class'][] = 'active'; - } - // Keyed with the unique mlid to generate classes in theme_links(). - $links['menu-' . $item['link']['mlid'] . $class] = $l; - } - } - return $links; -} - -/** - * Collects the local tasks (tabs), action links, and the root path. - * - * @param $level - * The level of tasks you ask for. Primary tasks are 0, secondary are 1. - * - * @return - * An array containing - * - tabs: Local tasks for the requested level: - * - count: The number of local tasks. - * - output: The themed output of local tasks. - * - actions: Action links for the requested level: - * - count: The number of action links. - * - output: The themed output of action links. - * - root_path: The router path for the current page. If the current page is - * a default local task, then this corresponds to the parent tab. - */ -function menu_local_tasks($level = 0) { - $data = &drupal_static(__FUNCTION__); - $root_path = &drupal_static(__FUNCTION__ . ':root_path', ''); - $empty = array( - 'tabs' => array('count' => 0, 'output' => array()), - 'actions' => array('count' => 0, 'output' => array()), - 'root_path' => &$root_path, - ); - - if (!isset($data)) { - $data = array(); - // Set defaults in case there are no actions or tabs. - $actions = $empty['actions']; - $tabs = array(); - - $router_item = menu_get_item(); - - // If this router item points to its parent, start from the parents to - // compute tabs and actions. - if ($router_item && ($router_item['type'] & MENU_LINKS_TO_PARENT)) { - $router_item = menu_get_item($router_item['tab_parent_href']); - } - - // If we failed to fetch a router item or the current user doesn't have - // access to it, don't bother computing the tabs. - if (!$router_item || !$router_item['access']) { - return $empty; - } - - // Get all tabs (also known as local tasks) and the root page. - $result = db_select('menu_router', NULL, array('fetch' => PDO::FETCH_ASSOC)) - ->fields('menu_router') - ->condition('tab_root', $router_item['tab_root']) - ->condition('context', MENU_CONTEXT_INLINE, '<>') - ->orderBy('weight') - ->orderBy('title') - ->execute(); - $map = $router_item['original_map']; - $children = array(); - $tasks = array(); - $root_path = $router_item['path']; - - foreach ($result as $item) { - _menu_translate($item, $map, TRUE); - if ($item['tab_parent']) { - // All tabs, but not the root page. - $children[$item['tab_parent']][$item['path']] = $item; - } - // Store the translated item for later use. - $tasks[$item['path']] = $item; - } - - // Find all tabs below the current path. - $path = $router_item['path']; - // Tab parenting may skip levels, so the number of parts in the path may not - // equal the depth. Thus we use the $depth counter (offset by 1000 for ksort). - $depth = 1001; - $actions['count'] = 0; - $actions['output'] = array(); - while (isset($children[$path])) { - $tabs_current = array(); - $actions_current = array(); - $next_path = ''; - $tab_count = 0; - $action_count = 0; - foreach ($children[$path] as $item) { - // Local tasks can be normal items too, so bitmask with - // MENU_IS_LOCAL_TASK before checking. - if (!($item['type'] & MENU_IS_LOCAL_TASK)) { - // This item is not a tab, skip it. - continue; - } - if ($item['access']) { - $link = $item; - // The default task is always active. As tabs can be normal items - // too, so bitmask with MENU_LINKS_TO_PARENT before checking. - if (($item['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT) { - // Find the first parent which is not a default local task or action. - for ($p = $item['tab_parent']; ($tasks[$p]['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT; $p = $tasks[$p]['tab_parent']); - // Use the path of the parent instead. - $link['href'] = $tasks[$p]['href']; - // Mark the link as active, if the current path happens to be the - // path of the default local task itself (i.e., instead of its - // tab_parent_href or tab_root_href). Normally, links for default - // local tasks link to their parent, but the path of default local - // tasks can still be accessed directly, in which case this link - // would not be marked as active, since l() only compares the href - // with $_GET['q']. - if ($link['href'] != $_GET['q']) { - $link['localized_options']['attributes']['class'][] = 'active'; - } - $tabs_current[] = array( - '#theme' => 'menu_local_task', - '#link' => $link, - '#active' => TRUE, - ); - $next_path = $item['path']; - $tab_count++; - } - else { - // Actions can be normal items too, so bitmask with - // MENU_IS_LOCAL_ACTION before checking. - if (($item['type'] & MENU_IS_LOCAL_ACTION) == MENU_IS_LOCAL_ACTION) { - // The item is an action, display it as such. - $actions_current[] = array( - '#theme' => 'menu_local_action', - '#link' => $link, - ); - $action_count++; - } - else { - // Otherwise, it's a normal tab. - $tabs_current[] = array( - '#theme' => 'menu_local_task', - '#link' => $link, - ); - $tab_count++; - } - } - } - } - $path = $next_path; - $tabs[$depth]['count'] = $tab_count; - $tabs[$depth]['output'] = $tabs_current; - $actions['count'] += $action_count; - $actions['output'] = array_merge($actions['output'], $actions_current); - $depth++; - } - $data['actions'] = $actions; - // Find all tabs at the same level or above the current one. - $parent = $router_item['tab_parent']; - $path = $router_item['path']; - $current = $router_item; - $depth = 1000; - while (isset($children[$parent])) { - $tabs_current = array(); - $next_path = ''; - $next_parent = ''; - $count = 0; - foreach ($children[$parent] as $item) { - // Skip local actions. - if ($item['type'] & MENU_IS_LOCAL_ACTION) { - continue; - } - if ($item['access']) { - $count++; - $link = $item; - // Local tasks can be normal items too, so bitmask with - // MENU_LINKS_TO_PARENT before checking. - if (($item['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT) { - // Find the first parent which is not a default local task. - for ($p = $item['tab_parent']; ($tasks[$p]['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT; $p = $tasks[$p]['tab_parent']); - // Use the path of the parent instead. - $link['href'] = $tasks[$p]['href']; - if ($item['path'] == $router_item['path']) { - $root_path = $tasks[$p]['path']; - } - } - // We check for the active tab. - if ($item['path'] == $path) { - // Mark the link as active, if the current path is a (second-level) - // local task of a default local task. Since this default local task - // links to its parent, l() will not mark it as active, as it only - // compares the link's href to $_GET['q']. - if ($link['href'] != $_GET['q']) { - $link['localized_options']['attributes']['class'][] = 'active'; - } - $tabs_current[] = array( - '#theme' => 'menu_local_task', - '#link' => $link, - '#active' => TRUE, - ); - $next_path = $item['tab_parent']; - if (isset($tasks[$next_path])) { - $next_parent = $tasks[$next_path]['tab_parent']; - } - } - else { - $tabs_current[] = array( - '#theme' => 'menu_local_task', - '#link' => $link, - ); - } - } - } - $path = $next_path; - $parent = $next_parent; - $tabs[$depth]['count'] = $count; - $tabs[$depth]['output'] = $tabs_current; - $depth--; - } - // Sort by depth. - ksort($tabs); - // Remove the depth, we are interested only in their relative placement. - $tabs = array_values($tabs); - $data['tabs'] = $tabs; - - // Allow modules to alter local tasks or dynamically append further tasks. - drupal_alter('menu_local_tasks', $data, $router_item, $root_path); - } - - if (isset($data['tabs'][$level])) { - return array( - 'tabs' => $data['tabs'][$level], - 'actions' => $data['actions'], - 'root_path' => $root_path, - ); - } - // @todo If there are no tabs, then there still can be actions; for example, - // when added via hook_menu_local_tasks_alter(). - elseif (!empty($data['actions']['output'])) { - return array('actions' => $data['actions']) + $empty; - } - return $empty; -} - -/** - * Retrieve contextual links for a system object based on registered local tasks. - * - * This leverages the menu system to retrieve the first layer of registered - * local tasks for a given system path. All local tasks of the tab type - * MENU_CONTEXT_INLINE are taken into account. - * - * @see hook_menu() - * - * For example, when considering the following registered local tasks: - * - node/%node/view (default local task) with no 'context' defined - * - node/%node/edit with context: MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE - * - node/%node/revisions with context: MENU_CONTEXT_PAGE - * - node/%node/report-as-spam with context: MENU_CONTEXT_INLINE - * - * If the path "node/123" is passed to this function, then it will return the - * links for 'edit' and 'report-as-spam'. - * - * @param $module - * The name of the implementing module. This is used to prefix the key for - * each contextual link, which is transformed into a CSS class during - * rendering by theme_links(). For example, if $module is 'block' and the - * retrieved local task path argument is 'edit', then the resulting CSS class - * will be 'block-edit'. - * @param $parent_path - * The static menu router path of the object to retrieve local tasks for, for - * example 'node' or 'admin/structure/block/manage'. - * @param $args - * A list of dynamic path arguments to append to $parent_path to form the - * fully-qualified menu router path, for example array(123) for a certain - * node or array('system', 'navigation') for a certain block. - * - * @return - * A list of menu router items that are local tasks for the passed-in path. - * - * @see contextual_links_preprocess() - */ -function menu_contextual_links($module, $parent_path, $args) { - static $path_empty = array(); - - $links = array(); - // Performance: In case a previous invocation for the same parent path did not - // return any links, we immediately return here. - if (isset($path_empty[$parent_path])) { - return $links; - } - // Construct the item-specific parent path. - $path = $parent_path . '/' . implode('/', $args); - - // Get the router item for the given parent link path. - $router_item = menu_get_item($path); - if (!$router_item || !$router_item['access']) { - $path_empty[$parent_path] = TRUE; - return $links; - } - $data = &drupal_static(__FUNCTION__, array()); - $root_path = $router_item['path']; - - // Performance: For a single, normalized path (such as 'node/%') we only query - // available tasks once per request. - if (!isset($data[$root_path])) { - // Get all contextual links that are direct children of the router item and - // not of the tab type 'view'. - $data[$root_path] = db_select('menu_router', 'm') - ->fields('m') - ->condition('tab_parent', $router_item['tab_root']) - ->condition('context', MENU_CONTEXT_NONE, '<>') - ->condition('context', MENU_CONTEXT_PAGE, '<>') - ->orderBy('weight') - ->orderBy('title') - ->execute() - ->fetchAllAssoc('path', PDO::FETCH_ASSOC); - } - $parent_length = drupal_strlen($root_path) + 1; - $map = $router_item['original_map']; - foreach ($data[$root_path] as $item) { - // Extract the actual "task" string from the path argument. - $key = drupal_substr($item['path'], $parent_length); - - // Denormalize and translate the contextual link. - _menu_translate($item, $map, TRUE); - if (!$item['access']) { - continue; - } - // All contextual links are keyed by the actual "task" path argument, - // prefixed with the name of the implementing module. - $links[$module . '-' . $key] = $item; - } - - // Allow modules to alter contextual links. - drupal_alter('menu_contextual_links', $links, $router_item, $root_path); - - // Performance: If the current user does not have access to any links for this - // router path and no other module added further links, we assign FALSE here - // to skip the entire process the next time the same router path is requested. - if (empty($links)) { - $path_empty[$parent_path] = TRUE; - } - - return $links; -} - -/** - * Returns the rendered local tasks at the top level. - */ -function menu_primary_local_tasks() { - $links = menu_local_tasks(0); - // Do not display single tabs. - return ($links['tabs']['count'] > 1 ? $links['tabs']['output'] : ''); -} - -/** - * Returns the rendered local tasks at the second level. - */ -function menu_secondary_local_tasks() { - $links = menu_local_tasks(1); - // Do not display single tabs. - return ($links['tabs']['count'] > 1 ? $links['tabs']['output'] : ''); -} - -/** - * Returns the rendered local actions at the current level. - */ -function menu_local_actions() { - $links = menu_local_tasks(); - return $links['actions']['output']; -} - -/** - * Returns the router path, or the path of the parent tab of a default local task. - */ -function menu_tab_root_path() { - $links = menu_local_tasks(); - return $links['root_path']; -} - -/** - * Returns a renderable element for the primary and secondary tabs. - */ -function menu_local_tabs() { - return array( - '#theme' => 'menu_local_tasks', - '#primary' => menu_primary_local_tasks(), - '#secondary' => menu_secondary_local_tasks(), - ); -} - -/** - * Returns HTML for primary and secondary local tasks. - * - * @ingroup themeable - */ -function theme_menu_local_tasks(&$variables) { - $output = ''; - - if (!empty($variables['primary'])) { - $variables['primary']['#prefix'] = '<h2 class="element-invisible">' . t('Primary tabs') . '</h2>'; - $variables['primary']['#prefix'] .= '<ul class="tabs primary">'; - $variables['primary']['#suffix'] = '</ul>'; - $output .= drupal_render($variables['primary']); - } - if (!empty($variables['secondary'])) { - $variables['secondary']['#prefix'] = '<h2 class="element-invisible">' . t('Secondary tabs') . '</h2>'; - $variables['secondary']['#prefix'] .= '<ul class="tabs secondary">'; - $variables['secondary']['#suffix'] = '</ul>'; - $output .= drupal_render($variables['secondary']); - } - - return $output; -} - -/** - * Set (or get) the active menu for the current page - determines the active trail. - */ -function menu_set_active_menu_names($menu_names = NULL) { - $active = &drupal_static(__FUNCTION__); - - if (isset($menu_names) && is_array($menu_names)) { - $active = $menu_names; - } - elseif (!isset($active)) { - $active = variable_get('menu_default_active_menus', array_keys(menu_list_system_menus())); - } - return $active; -} - -/** - * Get the active menu for the current page - determines the active trail. - */ -function menu_get_active_menu_names() { - return menu_set_active_menu_names(); -} - -/** - * Set the active path, which determines which page is loaded. - * - * Note that this may not have the desired effect unless invoked very early - * in the page load, such as during hook_boot, or unless you call - * menu_execute_active_handler() to generate your page output. - * - * @param $path - * A Drupal path - not a path alias. - */ -function menu_set_active_item($path) { - $_GET['q'] = $path; -} - -/** - * Sets the active trail (path to menu tree root) of the current page. - * - * Any trail set by this function will only be used for functionality that calls - * menu_get_active_trail(). Drupal core only uses trails set here for - * breadcrumbs and the page title and not for menu trees or page content. - * Additionally, breadcrumbs set by drupal_set_breadcrumb() will override any - * trail set here. - * - * To affect the trail used by menu trees, use menu_tree_set_path(). To affect - * the page content, use menu_set_active_item() instead. - * - * @param $new_trail - * Menu trail to set; the value is saved in a static variable and can be - * retrieved by menu_get_active_trail(). The format of this array should be - * the same as the return value of menu_get_active_trail(). - * - * @return - * The active trail. See menu_get_active_trail() for details. - */ -function menu_set_active_trail($new_trail = NULL) { - $trail = &drupal_static(__FUNCTION__); - - if (isset($new_trail)) { - $trail = $new_trail; - } - elseif (!isset($trail)) { - $trail = array(); - $trail[] = array( - 'title' => t('Home'), - 'href' => '<front>', - 'link_path' => '', - 'localized_options' => array(), - 'type' => 0, - ); - - // Try to retrieve a menu link corresponding to the current path. If more - // than one exists, the link from the most preferred menu is returned. - $preferred_link = menu_link_get_preferred(); - $current_item = menu_get_item(); - - // There is a link for the current path. - if ($preferred_link) { - // Pass TRUE for $only_active_trail to make menu_tree_page_data() build - // a stripped down menu tree containing the active trail only, in case - // the given menu has not been built in this request yet. - $tree = menu_tree_page_data($preferred_link['menu_name'], NULL, TRUE); - list($key, $curr) = each($tree); - } - // There is no link for the current path. - else { - $preferred_link = $current_item; - $curr = FALSE; - } - - while ($curr) { - $link = $curr['link']; - if ($link['in_active_trail']) { - // Add the link to the trail, unless it links to its parent. - if (!($link['type'] & MENU_LINKS_TO_PARENT)) { - // The menu tree for the active trail may contain additional links - // that have not been translated yet, since they contain dynamic - // argument placeholders (%). Such links are not contained in regular - // menu trees, and have only been loaded for the additional - // translation that happens here, so as to be able to display them in - // the breadcumb for the current page. - // @see _menu_tree_check_access() - // @see _menu_link_translate() - if (strpos($link['href'], '%') !== FALSE) { - _menu_link_translate($link, TRUE); - } - if ($link['access']) { - $trail[] = $link; - } - } - $tree = $curr['below'] ? $curr['below'] : array(); - } - list($key, $curr) = each($tree); - } - // Make sure the current page is in the trail to build the page title, by - // appending either the preferred link or the menu router item for the - // current page. Exclude it if we are on the front page. - $last = end($trail); - if ($last['href'] != $preferred_link['href'] && !drupal_is_front_page()) { - $trail[] = $preferred_link; - } - } - return $trail; -} - -/** - * Lookup the preferred menu link for a given system path. - * - * @param $path - * The path, for example 'node/5'. The function will find the corresponding - * menu link ('node/5' if it exists, or fallback to 'node/%'). - * - * @return - * A fully translated menu link, or NULL if no matching menu link was - * found. The most specific menu link ('node/5' preferred over 'node/%') in - * the most preferred menu (as defined by menu_get_active_menu_names()) is - * returned. - */ -function menu_link_get_preferred($path = NULL) { - $preferred_links = &drupal_static(__FUNCTION__); - - if (!isset($path)) { - $path = $_GET['q']; - } - - if (!isset($preferred_links[$path])) { - $preferred_links[$path] = FALSE; - - // Look for the correct menu link by building a list of candidate paths, - // which are ordered by priority (translated hrefs are preferred over - // untranslated paths). Afterwards, the most relevant path is picked from - // the menus, ordered by menu preference. - $item = menu_get_item($path); - $path_candidates = array(); - // 1. The current item href. - $path_candidates[$item['href']] = $item['href']; - // 2. The tab root href of the current item (if any). - if ($item['tab_parent'] && ($tab_root = menu_get_item($item['tab_root_href']))) { - $path_candidates[$tab_root['href']] = $tab_root['href']; - } - // 3. The current item path (with wildcards). - $path_candidates[$item['path']] = $item['path']; - // 4. The tab root path of the current item (if any). - if (!empty($tab_root)) { - $path_candidates[$tab_root['path']] = $tab_root['path']; - } - - // Retrieve a list of menu names, ordered by preference. - $menu_names = menu_get_active_menu_names(); - - $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC)); - $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path'); - $query->fields('ml'); - // Weight must be taken from {menu_links}, not {menu_router}. - $query->addField('ml', 'weight', 'link_weight'); - $query->fields('m'); - $query->condition('ml.menu_name', $menu_names, 'IN'); - $query->condition('ml.link_path', $path_candidates, 'IN'); - - // Sort candidates by link path and menu name. - $candidates = array(); - foreach ($query->execute() as $candidate) { - $candidate['weight'] = $candidate['link_weight']; - $candidates[$candidate['link_path']][$candidate['menu_name']] = $candidate; - } - - // Pick the most specific link, in the most preferred menu. - foreach ($path_candidates as $link_path) { - if (!isset($candidates[$link_path])) { - continue; - } - foreach ($menu_names as $menu_name) { - if (!isset($candidates[$link_path][$menu_name])) { - continue; - } - $candidate_item = $candidates[$link_path][$menu_name]; - $map = explode('/', $path); - _menu_translate($candidate_item, $map); - if ($candidate_item['access']) { - $preferred_links[$path] = $candidate_item; - } - break 2; - } - } - } - - return $preferred_links[$path]; -} - -/** - * Gets the active trail (path to root menu root) of the current page. - * - * If a trail is supplied to menu_set_active_trail(), that value is returned. If - * a trail is not supplied to menu_set_active_trail(), the path to the current - * page is calculated and returned. The calculated trail is also saved as a - * static value for use by subsequent calls to menu_get_active_trail(). - * - * @return - * Path to menu root of the current page, as an array of menu link items, - * starting with the site's home page. Each link item is an associative array - * with the following components: - * - title: Title of the item. - * - href: Drupal path of the item. - * - localized_options: Options for passing into the l() function. - * - type: A menu type constant, such as MENU_DEFAULT_LOCAL_TASK, or 0 to - * indicate it's not really in the menu (used for the home page item). - */ -function menu_get_active_trail() { - return menu_set_active_trail(); -} - -/** - * Get the breadcrumb for the current page, as determined by the active trail. - * - * @see menu_set_active_trail() - */ -function menu_get_active_breadcrumb() { - $breadcrumb = array(); - - // No breadcrumb for the front page. - if (drupal_is_front_page()) { - return $breadcrumb; - } - - $item = menu_get_item(); - if (!empty($item['access'])) { - $active_trail = menu_get_active_trail(); - - // Allow modules to alter the breadcrumb, if possible, as that is much - // faster than rebuilding an entirely new active trail. - drupal_alter('menu_breadcrumb', $active_trail, $item); - - // Don't show a link to the current page in the breadcrumb trail. - $end = end($active_trail); - if ($item['href'] == $end['href']) { - array_pop($active_trail); - } - - // Remove the tab root (parent) if the current path links to its parent. - // Normally, the tab root link is included in the breadcrumb, as soon as we - // are on a local task or any other child link. However, if we are on a - // default local task (e.g., node/%/view), then we do not want the tab root - // link (e.g., node/%) to appear, as it would be identical to the current - // page. Since this behavior also needs to work recursively (i.e., on - // default local tasks of default local tasks), and since the last non-task - // link in the trail is used as page title (see menu_get_active_title()), - // this condition cannot be cleanly integrated into menu_get_active_trail(). - // menu_get_active_trail() already skips all links that link to their parent - // (commonly MENU_DEFAULT_LOCAL_TASK). In order to also hide the parent link - // itself, we always remove the last link in the trail, if the current - // router item links to its parent. - if (($item['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT) { - array_pop($active_trail); - } - - foreach ($active_trail as $parent) { - $breadcrumb[] = l($parent['title'], $parent['href'], $parent['localized_options']); - } - } - return $breadcrumb; -} - -/** - * Get the title of the current page, as determined by the active trail. - */ -function menu_get_active_title() { - $active_trail = menu_get_active_trail(); - - foreach (array_reverse($active_trail) as $item) { - if (!(bool) ($item['type'] & MENU_IS_LOCAL_TASK)) { - return $item['title']; - } - } -} - -/** - * Get a menu link by its mlid, access checked and link translated for rendering. - * - * This function should never be called from within node_load() or any other - * function used as a menu object load function since an infinite recursion may - * occur. - * - * @param $mlid - * The mlid of the menu item. - * - * @return - * A menu link, with $item['access'] filled and link translated for - * rendering. - */ -function menu_link_load($mlid) { - if (is_numeric($mlid)) { - $query = db_select('menu_links', 'ml'); - $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path'); - $query->fields('ml'); - // Weight should be taken from {menu_links}, not {menu_router}. - $query->addField('ml', 'weight', 'link_weight'); - $query->fields('m'); - $query->condition('ml.mlid', $mlid); - if ($item = $query->execute()->fetchAssoc()) { - $item['weight'] = $item['link_weight']; - _menu_link_translate($item); - return $item; - } - } - return FALSE; -} - -/** - * Clears the cached cached data for a single named menu. - */ -function menu_cache_clear($menu_name = 'navigation') { - $cache_cleared = &drupal_static(__FUNCTION__, array()); - - if (empty($cache_cleared[$menu_name])) { - cache('menu')->deletePrefix('links:' . $menu_name . ':'); - $cache_cleared[$menu_name] = 1; - } - elseif ($cache_cleared[$menu_name] == 1) { - drupal_register_shutdown_function('cache_clear_all', 'links:' . $menu_name . ':', 'cache_menu', TRUE); - $cache_cleared[$menu_name] = 2; - } - - // Also clear the menu system static caches. - menu_reset_static_cache(); -} - -/** - * Clears all cached menu data. This should be called any time broad changes - * might have been made to the router items or menu links. - */ -function menu_cache_clear_all() { - cache('menu')->flush(); - menu_reset_static_cache(); -} - -/** - * Resets the menu system static cache. - */ -function menu_reset_static_cache() { - drupal_static_reset('_menu_build_tree'); - drupal_static_reset('menu_tree'); - drupal_static_reset('menu_tree_all_data'); - drupal_static_reset('menu_tree_page_data'); - drupal_static_reset('menu_load_all'); - drupal_static_reset('menu_link_get_preferred'); -} - -/** - * (Re)populate the database tables used by various menu functions. - * - * This function will clear and populate the {menu_router} table, add entries - * to {menu_links} for new router items, then remove stale items from - * {menu_links}. - * - * @return - * TRUE if the menu was rebuilt, FALSE if another thread was rebuilding - * in parallel and the current thread just waited for completion. - */ -function menu_rebuild() { - if (!lock_acquire('menu_rebuild')) { - // Wait for another request that is already doing this work. - // We choose to block here since otherwise the router item may not - // be available in menu_execute_active_handler() resulting in a 404. - lock_wait('menu_rebuild'); - return FALSE; - } - - $transaction = db_transaction(); - - try { - list($menu, $masks) = menu_router_build(); - _menu_router_save($menu, $masks); - _menu_navigation_links_rebuild($menu); - // Clear the menu, page and block caches. - menu_cache_clear_all(); - _menu_clear_page_cache(); - // Indicate that the menu has been successfully rebuilt. - variable_del('menu_rebuild_needed'); - } - catch (Exception $e) { - $transaction->rollback(); - watchdog_exception('menu', $e); - } - - lock_release('menu_rebuild'); - return TRUE; -} - -/** - * Collect and alter the menu definitions. - */ -function menu_router_build() { - // We need to manually call each module so that we can know which module - // a given item came from. - $callbacks = array(); - foreach (module_implements('menu') as $module) { - $router_items = call_user_func($module . '_menu'); - if (isset($router_items) && is_array($router_items)) { - foreach (array_keys($router_items) as $path) { - $router_items[$path]['module'] = $module; - } - $callbacks = array_merge($callbacks, $router_items); - } - } - // Alter the menu as defined in modules, keys are like user/%user. - drupal_alter('menu', $callbacks); - list($menu, $masks) = _menu_router_build($callbacks); - _menu_router_cache($menu); - - return array($menu, $masks); -} - -/** - * Helper function to store the menu router if we have it in memory. - */ -function _menu_router_cache($new_menu = NULL) { - $menu = &drupal_static(__FUNCTION__); - - if (isset($new_menu)) { - $menu = $new_menu; - } - return $menu; -} - -/** - * Get the menu router. - */ -function menu_get_router() { - // Check first if we have it in memory already. - $menu = _menu_router_cache(); - if (empty($menu)) { - list($menu, $masks) = menu_router_build(); - } - return $menu; -} - -/** - * Builds a link from a router item. - */ -function _menu_link_build($item) { - // Suggested items are disabled by default. - if ($item['type'] == MENU_SUGGESTED_ITEM) { - $item['hidden'] = 1; - } - // Hide all items that are not visible in the tree. - elseif (!($item['type'] & MENU_VISIBLE_IN_TREE)) { - $item['hidden'] = -1; - } - // Note, we set this as 'system', so that we can be sure to distinguish all - // the menu links generated automatically from entries in {menu_router}. - $item['module'] = 'system'; - $item += array( - 'menu_name' => 'navigation', - 'link_title' => $item['title'], - 'link_path' => $item['path'], - 'hidden' => 0, - 'options' => empty($item['description']) ? array() : array('attributes' => array('title' => $item['description'])), - ); - return $item; -} - -/** - * Helper function to build menu links for the items in the menu router. - */ -function _menu_navigation_links_rebuild($menu) { - // Add normal and suggested items as links. - $menu_links = array(); - foreach ($menu as $path => $item) { - if ($item['_visible']) { - $menu_links[$path] = $item; - $sort[$path] = $item['_number_parts']; - } - } - if ($menu_links) { - // Keep an array of processed menu links, to allow menu_link_save() to - // check this for parents instead of querying the database. - $parent_candidates = array(); - // Make sure no child comes before its parent. - array_multisort($sort, SORT_NUMERIC, $menu_links); - - foreach ($menu_links as $key => $item) { - $existing_item = db_select('menu_links') - ->fields('menu_links') - ->condition('link_path', $item['path']) - ->condition('module', 'system') - ->execute()->fetchAssoc(); - if ($existing_item) { - $item['mlid'] = $existing_item['mlid']; - // A change in hook_menu may move the link to a different menu - if (empty($item['menu_name']) || ($item['menu_name'] == $existing_item['menu_name'])) { - $item['menu_name'] = $existing_item['menu_name']; - $item['plid'] = $existing_item['plid']; - } - else { - // It moved to a new menu. Let menu_link_save() try to find a new - // parent based on the path. - unset($item['plid']); - } - $item['has_children'] = $existing_item['has_children']; - $item['updated'] = $existing_item['updated']; - } - if ($existing_item && $existing_item['customized']) { - $parent_candidates[$existing_item['mlid']] = $existing_item; - } - else { - $item = _menu_link_build($item); - menu_link_save($item, $existing_item, $parent_candidates); - $parent_candidates[$item['mlid']] = $item; - unset($menu_links[$key]); - } - } - } - $paths = array_keys($menu); - // Updated and customized items whose router paths are gone need new ones. - $result = db_select('menu_links', NULL, array('fetch' => PDO::FETCH_ASSOC)) - ->fields('menu_links', array( - 'link_path', - 'mlid', - 'router_path', - 'updated', - )) - ->condition(db_or() - ->condition('updated', 1) - ->condition(db_and() - ->condition('router_path', $paths, 'NOT IN') - ->condition('external', 0) - ->condition('customized', 1) - ) - ) - ->execute(); - foreach ($result as $item) { - $router_path = _menu_find_router_path($item['link_path']); - if (!empty($router_path) && ($router_path != $item['router_path'] || $item['updated'])) { - // If the router path and the link path matches, it's surely a working - // item, so we clear the updated flag. - $updated = $item['updated'] && $router_path != $item['link_path']; - db_update('menu_links') - ->fields(array( - 'router_path' => $router_path, - 'updated' => (int) $updated, - )) - ->condition('mlid', $item['mlid']) - ->execute(); - } - } - // Find any item whose router path does not exist any more. - $result = db_select('menu_links') - ->fields('menu_links') - ->condition('router_path', $paths, 'NOT IN') - ->condition('external', 0) - ->condition('updated', 0) - ->condition('customized', 0) - ->orderBy('depth', 'DESC') - ->execute(); - // Remove all such items. Starting from those with the greatest depth will - // minimize the amount of re-parenting done by menu_link_delete(). - foreach ($result as $item) { - _menu_delete_item($item, TRUE); - } -} - -/** - * Clone an array of menu links. - * - * @param $links - * An array of menu links to clone. - * @param $menu_name - * (optional) The name of a menu that the links will be cloned for. If not - * set, the cloned links will be in the same menu as the original set of - * links that were passed in. - * - * @return - * An array of menu links with the same properties as the passed-in array, - * but with the link identifiers removed so that a new link will be created - * when any of them is passed in to menu_link_save(). - * - * @see menu_link_save() - */ -function menu_links_clone($links, $menu_name = NULL) { - foreach ($links as &$link) { - unset($link['mlid']); - unset($link['plid']); - if (isset($menu_name)) { - $link['menu_name'] = $menu_name; - } - } - return $links; -} - -/** - * Returns an array containing all links for a menu. - * - * @param $menu_name - * The name of the menu whose links should be returned. - * - * @return - * An array of menu links. - */ -function menu_load_links($menu_name) { - $links = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC)) - ->fields('ml') - ->condition('ml.menu_name', $menu_name) - // Order by weight so as to be helpful for menus that are only one level - // deep. - ->orderBy('weight') - ->execute() - ->fetchAll(); - - foreach ($links as &$link) { - $link['options'] = unserialize($link['options']); - } - return $links; -} - -/** - * Deletes all links for a menu. - * - * @param $menu_name - * The name of the menu whose links will be deleted. - */ -function menu_delete_links($menu_name) { - $links = menu_load_links($menu_name); - foreach ($links as $link) { - // To speed up the deletion process, we reset some link properties that - // would trigger re-parenting logic in _menu_delete_item() and - // _menu_update_parental_status(). - $link['has_children'] = FALSE; - $link['plid'] = 0; - _menu_delete_item($link); - } -} - -/** - * Delete one or several menu links. - * - * @param $mlid - * A valid menu link mlid or NULL. If NULL, $path is used. - * @param $path - * The path to the menu items to be deleted. $mlid must be NULL. - */ -function menu_link_delete($mlid, $path = NULL) { - if (isset($mlid)) { - _menu_delete_item(db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchAssoc()); - } - else { - $result = db_query("SELECT * FROM {menu_links} WHERE link_path = :link_path", array(':link_path' => $path)); - foreach ($result as $link) { - _menu_delete_item($link); - } - } -} - -/** - * Helper function for menu_link_delete; deletes a single menu link. - * - * @param $item - * Item to be deleted. - * @param $force - * Forces deletion. Internal use only, setting to TRUE is discouraged. - */ -function _menu_delete_item($item, $force = FALSE) { - $item = is_object($item) ? get_object_vars($item) : $item; - if ($item && ($item['module'] != 'system' || $item['updated'] || $force)) { - // Children get re-attached to the item's parent. - if ($item['has_children']) { - $result = db_query("SELECT mlid FROM {menu_links} WHERE plid = :plid", array(':plid' => $item['mlid'])); - foreach ($result as $m) { - $child = menu_link_load($m->mlid); - $child['plid'] = $item['plid']; - menu_link_save($child); - } - } - - // Notify modules we are deleting the item. - module_invoke_all('menu_link_delete', $item); - - db_delete('menu_links')->condition('mlid', $item['mlid'])->execute(); - - // Update the has_children status of the parent. - _menu_update_parental_status($item); - menu_cache_clear($item['menu_name']); - _menu_clear_page_cache(); - } -} - -/** - * Saves a menu link. - * - * After calling this function, rebuild the menu cache using - * menu_cache_clear_all(). - * - * @param $item - * An associative array representing a menu link item, with elements: - * - link_path: (required) The path of the menu item, which should be - * normalized first by calling drupal_get_normal_path() on it. - * - link_title: (required) Title to appear in menu for the link. - * - menu_name: (optional) The machine name of the menu for the link. - * Defaults to 'navigation'. - * - weight: (optional) Integer to determine position in menu. Default is 0. - * - expanded: (optional) Boolean that determines if the item is expanded. - * - options: (optional) An array of options, see l() for more. - * - mlid: (optional) Menu link identifier, the primary integer key for each - * menu link. Can be set to an existing value, or to 0 or NULL - * to insert a new link. - * - plid: (optional) The mlid of the parent. - * - router_path: (optional) The path of the relevant router item. - * @param $existing_item - * Optional, the current record from the {menu_links} table as an array. - * @param $parent_candidates - * Optional array of menu links keyed by mlid. Used by - * _menu_navigation_links_rebuild() only. - * - * @return - * The mlid of the saved menu link, or FALSE if the menu link could not be - * saved. - */ -function menu_link_save(&$item, $existing_item = array(), $parent_candidates = array()) { - drupal_alter('menu_link', $item); - - // This is the easiest way to handle the unique internal path '<front>', - // since a path marked as external does not need to match a router path. - $item['external'] = (url_is_external($item['link_path']) || $item['link_path'] == '<front>') ? 1 : 0; - // Load defaults. - $item += array( - 'menu_name' => 'navigation', - 'weight' => 0, - 'link_title' => '', - 'hidden' => 0, - 'has_children' => 0, - 'expanded' => 0, - 'options' => array(), - 'module' => 'menu', - 'customized' => 0, - 'updated' => 0, - ); - if (isset($item['mlid'])) { - if (!$existing_item) { - $existing_item = db_query('SELECT * FROM {menu_links} WHERE mlid = :mlid', array('mlid' => $item['mlid']))->fetchAssoc(); - } - if ($existing_item) { - $existing_item['options'] = unserialize($existing_item['options']); - } - } - else { - $existing_item = FALSE; - } - - // Try to find a parent link. If found, assign it and derive its menu. - $parent = _menu_link_find_parent($item, $parent_candidates); - if (!empty($parent['mlid'])) { - $item['plid'] = $parent['mlid']; - $item['menu_name'] = $parent['menu_name']; - } - // If no corresponding parent link was found, move the link to the top-level. - else { - $item['plid'] = 0; - } - $menu_name = $item['menu_name']; - - if (!$existing_item) { - $item['mlid'] = db_insert('menu_links') - ->fields(array( - 'menu_name' => $item['menu_name'], - 'plid' => $item['plid'], - 'link_path' => $item['link_path'], - 'hidden' => $item['hidden'], - 'external' => $item['external'], - 'has_children' => $item['has_children'], - 'expanded' => $item['expanded'], - 'weight' => $item['weight'], - 'module' => $item['module'], - 'link_title' => $item['link_title'], - 'options' => serialize($item['options']), - 'customized' => $item['customized'], - 'updated' => $item['updated'], - )) - ->execute(); - } - - // Directly fill parents for top-level links. - if ($item['plid'] == 0) { - $item['p1'] = $item['mlid']; - for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) { - $item["p$i"] = 0; - } - $item['depth'] = 1; - } - // Otherwise, ensure that this link's depth is not beyond the maximum depth - // and fill parents based on the parent link. - else { - if ($item['has_children'] && $existing_item) { - $limit = MENU_MAX_DEPTH - menu_link_children_relative_depth($existing_item) - 1; - } - else { - $limit = MENU_MAX_DEPTH - 1; - } - if ($parent['depth'] > $limit) { - return FALSE; - } - $item['depth'] = $parent['depth'] + 1; - _menu_link_parents_set($item, $parent); - } - // Need to check both plid and menu_name, since plid can be 0 in any menu. - if ($existing_item && ($item['plid'] != $existing_item['plid'] || $menu_name != $existing_item['menu_name'])) { - _menu_link_move_children($item, $existing_item); - } - // Find the router_path. - if (empty($item['router_path']) || !$existing_item || ($existing_item['link_path'] != $item['link_path'])) { - if ($item['external']) { - $item['router_path'] = ''; - } - else { - // Find the router path which will serve this path. - $item['parts'] = explode('/', $item['link_path'], MENU_MAX_PARTS); - $item['router_path'] = _menu_find_router_path($item['link_path']); - } - } - // If every value in $existing_item is the same in the $item, there is no - // reason to run the update queries or clear the caches. We use - // array_intersect_assoc() with the $item as the first parameter because - // $item may have additional keys left over from building a router entry. - // The intersect removes the extra keys, allowing a meaningful comparison. - if (!$existing_item || (array_intersect_assoc($item, $existing_item)) != $existing_item) { - db_update('menu_links') - ->fields(array( - 'menu_name' => $item['menu_name'], - 'plid' => $item['plid'], - 'link_path' => $item['link_path'], - 'router_path' => $item['router_path'], - 'hidden' => $item['hidden'], - 'external' => $item['external'], - 'has_children' => $item['has_children'], - 'expanded' => $item['expanded'], - 'weight' => $item['weight'], - 'depth' => $item['depth'], - 'p1' => $item['p1'], - 'p2' => $item['p2'], - 'p3' => $item['p3'], - 'p4' => $item['p4'], - 'p5' => $item['p5'], - 'p6' => $item['p6'], - 'p7' => $item['p7'], - 'p8' => $item['p8'], - 'p9' => $item['p9'], - 'module' => $item['module'], - 'link_title' => $item['link_title'], - 'options' => serialize($item['options']), - 'customized' => $item['customized'], - )) - ->condition('mlid', $item['mlid']) - ->execute(); - // Check the has_children status of the parent. - _menu_update_parental_status($item); - menu_cache_clear($menu_name); - if ($existing_item && $menu_name != $existing_item['menu_name']) { - menu_cache_clear($existing_item['menu_name']); - } - // Notify modules we have acted on a menu item. - $hook = 'menu_link_insert'; - if ($existing_item) { - $hook = 'menu_link_update'; - } - module_invoke_all($hook, $item); - // Now clear the cache. - _menu_clear_page_cache(); - } - return $item['mlid']; -} - -/** - * Find a possible parent for a given menu link. - * - * Because the parent of a given link might not exist anymore in the database, - * we apply a set of heuristics to determine a proper parent: - * - * - use the passed parent link if specified and existing. - * - else, use the first existing link down the previous link hierarchy - * - else, for system menu links (derived from hook_menu()), reparent - * based on the path hierarchy. - * - * @param $menu_link - * A menu link. - * @param $parent_candidates - * An array of menu links keyed by mlid. - * @return - * A menu link structure of the possible parent or FALSE if no valid parent - * has been found. - */ -function _menu_link_find_parent($menu_link, $parent_candidates = array()) { - $parent = FALSE; - - // This item is explicitely top-level, skip the rest of the parenting. - if (isset($menu_link['plid']) && empty($menu_link['plid'])) { - return $parent; - } - - // If we have a parent link ID, try to use that. - $candidates = array(); - if (isset($menu_link['plid'])) { - $candidates[] = $menu_link['plid']; - } - - // Else, if we have a link hierarchy try to find a valid parent in there. - if (!empty($menu_link['depth']) && $menu_link['depth'] > 1) { - for ($depth = $menu_link['depth'] - 1; $depth >= 1; $depth--) { - $candidates[] = $menu_link['p' . $depth]; - } - } - - foreach ($candidates as $mlid) { - if (isset($parent_candidates[$mlid])) { - $parent = $parent_candidates[$mlid]; - } - else { - $parent = db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $mlid))->fetchAssoc(); - } - if ($parent) { - return $parent; - } - } - - // If everything else failed, try to derive the parent from the path - // hierarchy. This only makes sense for links derived from menu router - // items (ie. from hook_menu()). - if ($menu_link['module'] == 'system') { - $query = db_select('menu_links'); - $query->condition('module', 'system'); - // We always respect the link's 'menu_name'; inheritance for router items is - // ensured in _menu_router_build(). - $query->condition('menu_name', $menu_link['menu_name']); - - // Find the parent - it must be unique. - $parent_path = $menu_link['link_path']; - do { - $parent = FALSE; - $parent_path = substr($parent_path, 0, strrpos($parent_path, '/')); - $new_query = clone $query; - $new_query->condition('link_path', $parent_path); - // Only valid if we get a unique result. - if ($new_query->countQuery()->execute()->fetchField() == 1) { - $parent = $new_query->fields('menu_links')->execute()->fetchAssoc(); - } - } while ($parent === FALSE && $parent_path); - } - - return $parent; -} - -/** - * Helper function to clear the page and block caches at most twice per page load. - */ -function _menu_clear_page_cache() { - $cache_cleared = &drupal_static(__FUNCTION__, 0); - - // Clear the page and block caches, but at most twice, including at - // the end of the page load when there are multiple links saved or deleted. - if ($cache_cleared == 0) { - cache_clear_all(); - // Keep track of which menus have expanded items. - _menu_set_expanded_menus(); - $cache_cleared = 1; - } - elseif ($cache_cleared == 1) { - drupal_register_shutdown_function('cache_clear_all'); - // Keep track of which menus have expanded items. - drupal_register_shutdown_function('_menu_set_expanded_menus'); - $cache_cleared = 2; - } -} - -/** - * Helper function to update a list of menus with expanded items - */ -function _menu_set_expanded_menus() { - $names = db_query("SELECT menu_name FROM {menu_links} WHERE expanded <> 0 GROUP BY menu_name")->fetchCol(); - variable_set('menu_expanded', $names); -} - -/** - * Find the router path which will serve this path. - * - * @param $link_path - * The path for we are looking up its router path. - * - * @return - * A path from $menu keys or empty if $link_path points to a nonexisting - * place. - */ -function _menu_find_router_path($link_path) { - // $menu will only have data during a menu rebuild. - $menu = _menu_router_cache(); - - $router_path = $link_path; - $parts = explode('/', $link_path, MENU_MAX_PARTS); - $ancestors = menu_get_ancestors($parts); - - if (empty($menu)) { - // Not during a menu rebuild, so look up in the database. - $router_path = (string) db_select('menu_router') - ->fields('menu_router', array('path')) - ->condition('path', $ancestors, 'IN') - ->orderBy('fit', 'DESC') - ->range(0, 1) - ->execute()->fetchField(); - } - elseif (!isset($menu[$router_path])) { - // Add an empty router path as a fallback. - $ancestors[] = ''; - foreach ($ancestors as $key => $router_path) { - if (isset($menu[$router_path])) { - // Exit the loop leaving $router_path as the first match. - break; - } - } - // If we did not find the path, $router_path will be the empty string - // at the end of $ancestors. - } - return $router_path; -} - -/** - * Insert, update or delete an uncustomized menu link related to a module. - * - * @param $module - * The name of the module. - * @param $op - * Operation to perform: insert, update or delete. - * @param $link_path - * The path this link points to. - * @param $link_title - * Title of the link to insert or new title to update the link to. - * Unused for delete. - * - * @return - * The insert op returns the mlid of the new item. Others op return NULL. - */ -function menu_link_maintain($module, $op, $link_path, $link_title) { - switch ($op) { - case 'insert': - $menu_link = array( - 'link_title' => $link_title, - 'link_path' => $link_path, - 'module' => $module, - ); - return menu_link_save($menu_link); - break; - case 'update': - $result = db_query("SELECT * FROM {menu_links} WHERE link_path = :link_path AND module = :module AND customized = 0", array(':link_path' => $link_path, ':module' => $module))->fetchAll(PDO::FETCH_ASSOC); - foreach ($result as $link) { - $link['link_title'] = $link_title; - $link['options'] = unserialize($link['options']); - menu_link_save($link); - } - break; - case 'delete': - menu_link_delete(NULL, $link_path); - break; - } -} - -/** - * Find the depth of an item's children relative to its depth. - * - * For example, if the item has a depth of 2, and the maximum of any child in - * the menu link tree is 5, the relative depth is 3. - * - * @param $item - * An array representing a menu link item. - * - * @return - * The relative depth, or zero. - * - */ -function menu_link_children_relative_depth($item) { - $query = db_select('menu_links'); - $query->addField('menu_links', 'depth'); - $query->condition('menu_name', $item['menu_name']); - $query->orderBy('depth', 'DESC'); - $query->range(0, 1); - - $i = 1; - $p = 'p1'; - while ($i <= MENU_MAX_DEPTH && $item[$p]) { - $query->condition($p, $item[$p]); - $p = 'p' . ++$i; - } - - $max_depth = $query->execute()->fetchField(); - - return ($max_depth > $item['depth']) ? $max_depth - $item['depth'] : 0; -} - -/** - * Update the children of a menu link that's being moved. - * - * The menu name, parents (p1 - p6), and depth are updated for all children of - * the link, and the has_children status of the previous parent is updated. - */ -function _menu_link_move_children($item, $existing_item) { - $query = db_update('menu_links'); - - $query->fields(array('menu_name' => $item['menu_name'])); - - $p = 'p1'; - $expressions = array(); - for ($i = 1; $i <= $item['depth']; $p = 'p' . ++$i) { - $expressions[] = array($p, ":p_$i", array(":p_$i" => $item[$p])); - } - $j = $existing_item['depth'] + 1; - while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) { - $expressions[] = array('p' . $i++, 'p' . $j++, array()); - } - while ($i <= MENU_MAX_DEPTH) { - $expressions[] = array('p' . $i++, 0, array()); - } - - $shift = $item['depth'] - $existing_item['depth']; - if ($shift > 0) { - // The order of expressions must be reversed so the new values don't - // overwrite the old ones before they can be used because "Single-table - // UPDATE assignments are generally evaluated from left to right" - // see: http://dev.mysql.com/doc/refman/5.0/en/update.html - $expressions = array_reverse($expressions); - } - foreach ($expressions as $expression) { - $query->expression($expression[0], $expression[1], $expression[2]); - } - - $query->expression('depth', 'depth + :depth', array(':depth' => $shift)); - $query->condition('menu_name', $existing_item['menu_name']); - $p = 'p1'; - for ($i = 1; $i <= MENU_MAX_DEPTH && $existing_item[$p]; $p = 'p' . ++$i) { - $query->condition($p, $existing_item[$p]); - } - - $query->execute(); - - // Check the has_children status of the parent, while excluding this item. - _menu_update_parental_status($existing_item, TRUE); -} - -/** - * Check and update the has_children status for the parent of a link. - */ -function _menu_update_parental_status($item, $exclude = FALSE) { - // If plid == 0, there is nothing to update. - if ($item['plid']) { - // Check if at least one visible child exists in the table. - $query = db_select('menu_links'); - $query->addField('menu_links', 'mlid'); - $query->condition('menu_name', $item['menu_name']); - $query->condition('hidden', 0); - $query->condition('plid', $item['plid']); - $query->range(0, 1); - if ($exclude) { - $query->condition('mlid', $item['mlid'], '<>'); - } - $parent_has_children = ((bool) $query->execute()->fetchField()) ? 1 : 0; - db_update('menu_links') - ->fields(array('has_children' => $parent_has_children)) - ->condition('mlid', $item['plid']) - ->execute(); - } -} - -/** - * Helper function that sets the p1..p9 values for a menu link being saved. - */ -function _menu_link_parents_set(&$item, $parent) { - $i = 1; - while ($i < $item['depth']) { - $p = 'p' . $i++; - $item[$p] = $parent[$p]; - } - $p = 'p' . $i++; - // The parent (p1 - p9) corresponding to the depth always equals the mlid. - $item[$p] = $item['mlid']; - while ($i <= MENU_MAX_DEPTH) { - $p = 'p' . $i++; - $item[$p] = 0; - } -} - -/** - * Helper function to build the router table based on the data from hook_menu. - */ -function _menu_router_build($callbacks) { - // First pass: separate callbacks from paths, making paths ready for - // matching. Calculate fitness, and fill some default values. - $menu = array(); - $masks = array(); - foreach ($callbacks as $path => $item) { - $load_functions = array(); - $to_arg_functions = array(); - $fit = 0; - $move = FALSE; - - $parts = explode('/', $path, MENU_MAX_PARTS); - $number_parts = count($parts); - // We store the highest index of parts here to save some work in the fit - // calculation loop. - $slashes = $number_parts - 1; - // Extract load and to_arg functions. - foreach ($parts as $k => $part) { - $match = FALSE; - // Look for wildcards in the form allowed to be used in PHP functions, - // because we are using these to construct the load function names. - if (preg_match('/^%(|' . DRUPAL_PHP_FUNCTION_PATTERN . ')$/', $part, $matches)) { - if (empty($matches[1])) { - $match = TRUE; - $load_functions[$k] = NULL; - } - else { - if (function_exists($matches[1] . '_to_arg')) { - $to_arg_functions[$k] = $matches[1] . '_to_arg'; - $load_functions[$k] = NULL; - $match = TRUE; - } - if (function_exists($matches[1] . '_load')) { - $function = $matches[1] . '_load'; - // Create an array of arguments that will be passed to the _load - // function when this menu path is checked, if 'load arguments' - // exists. - $load_functions[$k] = isset($item['load arguments']) ? array($function => $item['load arguments']) : $function; - $match = TRUE; - } - } - } - if ($match) { - $parts[$k] = '%'; - } - else { - $fit |= 1 << ($slashes - $k); - } - } - if ($fit) { - $move = TRUE; - } - else { - // If there is no %, it fits maximally. - $fit = (1 << $number_parts) - 1; - } - $masks[$fit] = 1; - $item['_load_functions'] = $load_functions; - $item['to_arg_functions'] = empty($to_arg_functions) ? '' : serialize($to_arg_functions); - $item += array( - 'title' => '', - 'weight' => 0, - 'type' => MENU_NORMAL_ITEM, - 'module' => '', - '_number_parts' => $number_parts, - '_parts' => $parts, - '_fit' => $fit, - ); - $item += array( - '_visible' => (bool) ($item['type'] & MENU_VISIBLE_IN_BREADCRUMB), - '_tab' => (bool) ($item['type'] & MENU_IS_LOCAL_TASK), - ); - if ($move) { - $new_path = implode('/', $item['_parts']); - $menu[$new_path] = $item; - $sort[$new_path] = $number_parts; - } - else { - $menu[$path] = $item; - $sort[$path] = $number_parts; - } - } - array_multisort($sort, SORT_NUMERIC, $menu); - // Apply inheritance rules. - foreach ($menu as $path => $v) { - $item = &$menu[$path]; - if (!$item['_tab']) { - // Non-tab items. - $item['tab_parent'] = ''; - $item['tab_root'] = $path; - } - // If not specified, assign the default tab type for local tasks. - elseif (!isset($item['context'])) { - $item['context'] = MENU_CONTEXT_PAGE; - } - for ($i = $item['_number_parts'] - 1; $i; $i--) { - $parent_path = implode('/', array_slice($item['_parts'], 0, $i)); - if (isset($menu[$parent_path])) { - - $parent = &$menu[$parent_path]; - - // If we have no menu name, try to inherit it from parent items. - if (!isset($item['menu_name'])) { - // If the parent item of this item does not define a menu name (and no - // previous iteration assigned one already), try to find the menu name - // of the parent item in the currently stored menu links. - if (!isset($parent['menu_name'])) { - $menu_name = db_query("SELECT menu_name FROM {menu_links} WHERE router_path = :router_path AND module = 'system'", array(':router_path' => $parent_path))->fetchField(); - if ($menu_name) { - $parent['menu_name'] = $menu_name; - } - } - // If the parent item defines a menu name, inherit it. - if (!empty($parent['menu_name'])) { - $item['menu_name'] = $parent['menu_name']; - } - } - if (!isset($item['tab_parent'])) { - // Parent stores the parent of the path. - $item['tab_parent'] = $parent_path; - } - if (!isset($item['tab_root']) && !$parent['_tab']) { - $item['tab_root'] = $parent_path; - } - // If an access callback is not found for a default local task we use - // the callback from the parent, since we expect them to be identical. - // In all other cases, the access parameters must be specified. - if (($item['type'] == MENU_DEFAULT_LOCAL_TASK) && !isset($item['access callback']) && isset($parent['access callback'])) { - $item['access callback'] = $parent['access callback']; - if (!isset($item['access arguments']) && isset($parent['access arguments'])) { - $item['access arguments'] = $parent['access arguments']; - } - } - // Same for page callbacks. - if (!isset($item['page callback']) && isset($parent['page callback'])) { - $item['page callback'] = $parent['page callback']; - if (!isset($item['page arguments']) && isset($parent['page arguments'])) { - $item['page arguments'] = $parent['page arguments']; - } - if (!isset($item['file path']) && isset($parent['file path'])) { - $item['file path'] = $parent['file path']; - } - if (!isset($item['file']) && isset($parent['file'])) { - $item['file'] = $parent['file']; - if (empty($item['file path']) && isset($item['module']) && isset($parent['module']) && $item['module'] != $parent['module']) { - $item['file path'] = drupal_get_path('module', $parent['module']); - } - } - } - // Same for delivery callbacks. - if (!isset($item['delivery callback']) && isset($parent['delivery callback'])) { - $item['delivery callback'] = $parent['delivery callback']; - } - // Same for theme callbacks. - if (!isset($item['theme callback']) && isset($parent['theme callback'])) { - $item['theme callback'] = $parent['theme callback']; - if (!isset($item['theme arguments']) && isset($parent['theme arguments'])) { - $item['theme arguments'] = $parent['theme arguments']; - } - } - // Same for load arguments: if a loader doesn't have any explict - // arguments, try to find arguments in the parent. - if (!isset($item['load arguments'])) { - foreach ($item['_load_functions'] as $k => $function) { - // This loader doesn't have any explict arguments... - if (!is_array($function)) { - // ... check the parent for a loader at the same position - // using the same function name and defining arguments... - if (isset($parent['_load_functions'][$k]) && is_array($parent['_load_functions'][$k]) && key($parent['_load_functions'][$k]) === $function) { - // ... and inherit the arguments on the child. - $item['_load_functions'][$k] = $parent['_load_functions'][$k]; - } - } - } - } - } - } - if (!isset($item['access callback']) && isset($item['access arguments'])) { - // Default callback. - $item['access callback'] = 'user_access'; - } - if (!isset($item['access callback']) || empty($item['page callback'])) { - $item['access callback'] = 0; - } - if (is_bool($item['access callback'])) { - $item['access callback'] = intval($item['access callback']); - } - - $item['load_functions'] = empty($item['_load_functions']) ? '' : serialize($item['_load_functions']); - $item += array( - 'access arguments' => array(), - 'access callback' => '', - 'page arguments' => array(), - 'page callback' => '', - 'delivery callback' => '', - 'title arguments' => array(), - 'title callback' => 't', - 'theme arguments' => array(), - 'theme callback' => '', - 'description' => '', - 'position' => '', - 'context' => 0, - 'tab_parent' => '', - 'tab_root' => $path, - 'path' => $path, - 'file' => '', - 'file path' => '', - 'include file' => '', - ); - - // Calculate out the file to be included for each callback, if any. - if ($item['file']) { - $file_path = $item['file path'] ? $item['file path'] : drupal_get_path('module', $item['module']); - $item['include file'] = $file_path . '/' . $item['file']; - } - } - - // Sort the masks so they are in order of descending fit. - $masks = array_keys($masks); - rsort($masks); - - return array($menu, $masks); -} - -/** - * Helper function to save data from menu_router_build() to the router table. - */ -function _menu_router_save($menu, $masks) { - // Delete the existing router since we have some data to replace it. - db_truncate('menu_router')->execute(); - - // Prepare insert object. - $insert = db_insert('menu_router') - ->fields(array( - 'path', - 'load_functions', - 'to_arg_functions', - 'access_callback', - 'access_arguments', - 'page_callback', - 'page_arguments', - 'delivery_callback', - 'fit', - 'number_parts', - 'context', - 'tab_parent', - 'tab_root', - 'title', - 'title_callback', - 'title_arguments', - 'theme_callback', - 'theme_arguments', - 'type', - 'description', - 'position', - 'weight', - 'include_file', - )); - - $num_records = 0; - - foreach ($menu as $path => $item) { - // Fill in insert object values. - $insert->values(array( - 'path' => $item['path'], - 'load_functions' => $item['load_functions'], - 'to_arg_functions' => $item['to_arg_functions'], - 'access_callback' => $item['access callback'], - 'access_arguments' => serialize($item['access arguments']), - 'page_callback' => $item['page callback'], - 'page_arguments' => serialize($item['page arguments']), - 'delivery_callback' => $item['delivery callback'], - 'fit' => $item['_fit'], - 'number_parts' => $item['_number_parts'], - 'context' => $item['context'], - 'tab_parent' => $item['tab_parent'], - 'tab_root' => $item['tab_root'], - 'title' => $item['title'], - 'title_callback' => $item['title callback'], - 'title_arguments' => ($item['title arguments'] ? serialize($item['title arguments']) : ''), - 'theme_callback' => $item['theme callback'], - 'theme_arguments' => serialize($item['theme arguments']), - 'type' => $item['type'], - 'description' => $item['description'], - 'position' => $item['position'], - 'weight' => $item['weight'], - 'include_file' => $item['include file'], - )); - - // Execute in batches to avoid the memory overhead of all of those records - // in the query object. - if (++$num_records == 20) { - $insert->execute(); - $num_records = 0; - } - } - // Insert any remaining records. - $insert->execute(); - // Store the masks. - variable_set('menu_masks', $masks); - - return $menu; -} - -/** - * Checks whether the site is in maintenance mode. - * - * This function will log the current user out and redirect to front page - * if the current user has no 'access site in maintenance mode' permission. - * - * @param $check_only - * If this is set to TRUE, the function will perform the access checks and - * return the site offline status, but not log the user out or display any - * messages. - * - * @return - * FALSE if the site is not in maintenance mode, the user login page is - * displayed, or the user has the 'access site in maintenance mode' - * permission. TRUE for anonymous users not being on the login page when the - * site is in maintenance mode. - */ -function _menu_site_is_offline($check_only = FALSE) { - // Check if site is in maintenance mode. - if (variable_get('maintenance_mode', 0)) { - if (user_access('access site in maintenance mode')) { - // Ensure that the maintenance mode message is displayed only once - // (allowing for page redirects) and specifically suppress its display on - // the maintenance mode settings page. - if (!$check_only && $_GET['q'] != 'admin/config/development/maintenance') { - if (user_access('administer site configuration')) { - drupal_set_message(t('Operating in maintenance mode. <a href="@url">Go online.</a>', array('@url' => url('admin/config/development/maintenance'))), 'status', FALSE); - } - else { - drupal_set_message(t('Operating in maintenance mode.'), 'status', FALSE); - } - } - } - else { - return TRUE; - } - } - return FALSE; -} - -/** - * @} End of "defgroup menu". - */ diff --git a/core/includes/module.inc b/core/includes/module.inc deleted file mode 100644 index cca2ef93565..00000000000 --- a/core/includes/module.inc +++ /dev/null @@ -1,1007 +0,0 @@ -<?php - -/** - * @file - * API for loading and interacting with Drupal modules. - */ - -/** - * Load all the modules that have been enabled in the system table. - * - * @param $bootstrap - * Whether to load only the reduced set of modules loaded in "bootstrap mode" - * for cached pages. See bootstrap.inc. - * - * @return - * If $bootstrap is NULL, return a boolean indicating whether all modules - * have been loaded. - */ -function module_load_all($bootstrap = FALSE) { - static $has_run = FALSE; - - if (isset($bootstrap)) { - foreach (module_list(TRUE, $bootstrap) as $module) { - drupal_load('module', $module); - } - // $has_run will be TRUE if $bootstrap is FALSE. - $has_run = !$bootstrap; - } - return $has_run; -} - - -/** - * Returns a list of currently active modules. - * - * Usually, this returns a list of all enabled modules. When called early on in - * the bootstrap, it will return a list of vital modules only (those needed to - * generate cached pages). - * - * All parameters to this function are optional and should generally not be - * changed from their defaults. - * - * @param $refresh - * (optional) Whether to force the module list to be regenerated (such as - * after the administrator has changed the system settings). Defaults to - * FALSE. - * @param $bootstrap_refresh - * (optional) When $refresh is TRUE, setting $bootstrap_refresh to TRUE forces - * the module list to be regenerated using the reduced set of modules loaded - * in "bootstrap mode" for cached pages. Otherwise, setting $refresh to TRUE - * generates the complete list of enabled modules. - * @param $sort - * (optional) By default, modules are ordered by weight and module name. Set - * this option to TRUE to return a module list ordered only by module name. - * @param $fixed_list - * (optional) If an array of module names is provided, this will override the - * module list with the given set of modules. This will persist until the next - * call with $refresh set to TRUE or with a new $fixed_list passed in. This - * parameter is primarily intended for internal use (e.g., in install.php and - * update.php). - * - * @return - * An associative array whose keys and values are the names of the modules in - * the list. - */ -function module_list($refresh = FALSE, $bootstrap_refresh = FALSE, $sort = FALSE, $fixed_list = NULL) { - static $list = array(), $sorted_list; - - if (empty($list) || $refresh || $fixed_list) { - $list = array(); - $sorted_list = NULL; - if ($fixed_list) { - foreach ($fixed_list as $name => $module) { - drupal_get_filename('module', $name, $module['filename']); - $list[$name] = $name; - } - } - else { - if ($refresh) { - // For the $refresh case, make sure that system_list() returns fresh - // data. - drupal_static_reset('system_list'); - } - if ($bootstrap_refresh) { - $list = system_list('bootstrap'); - } - else { - // Not using drupal_map_assoc() here as that requires common.inc. - $list = array_keys(system_list('module_enabled')); - $list = (!empty($list) ? array_combine($list, $list) : array()); - } - } - } - if ($sort) { - if (!isset($sorted_list)) { - $sorted_list = $list; - ksort($sorted_list); - } - return $sorted_list; - } - return $list; -} - -/** - * Build a list of bootstrap modules and enabled modules and themes. - * - * @param $type - * The type of list to return: - * - module_enabled: All enabled modules. - * - bootstrap: All enabled modules required for bootstrap. - * - theme: All themes. - * - * @return - * An associative array of modules or themes, keyed by name. For $type - * 'bootstrap', the array values equal the keys. For $type 'module_enabled' - * or 'theme', the array values are objects representing the respective - * database row, with the 'info' property already unserialized. - * - * @see module_list() - * @see list_themes() - */ -function system_list($type) { - $lists = &drupal_static(__FUNCTION__); - - // For bootstrap modules, attempt to fetch the list from cache if possible. - // if not fetch only the required information to fire bootstrap hooks - // in case we are going to serve the page from cache. - if ($type == 'bootstrap') { - if (isset($lists['bootstrap'])) { - return $lists['bootstrap']; - } - if ($cached = cache('bootstrap')->get('bootstrap_modules')) { - $bootstrap_list = $cached->data; - } - else { - $bootstrap_list = db_query("SELECT name, filename FROM {system} WHERE status = 1 AND bootstrap = 1 AND type = 'module' ORDER BY weight ASC, name ASC")->fetchAllAssoc('name'); - cache('bootstrap')->set('bootstrap_modules', $bootstrap_list); - } - // To avoid a separate database lookup for the filepath, prime the - // drupal_get_filename() static cache for bootstrap modules only. - // The rest is stored separately to keep the bootstrap module cache small. - foreach ($bootstrap_list as $module) { - drupal_get_filename('module', $module->name, $module->filename); - } - // We only return the module names here since module_list() doesn't need - // the filename itself. - $lists['bootstrap'] = array_keys($bootstrap_list); - } - // Otherwise build the list for enabled modules and themes. - elseif (!isset($lists['module_enabled'])) { - if ($cached = cache('bootstrap')->get('system_list')) { - $lists = $cached->data; - } - else { - $lists = array( - 'module_enabled' => array(), - 'theme' => array(), - 'filepaths' => array(), - ); - // The module name (rather than the filename) is used as the fallback - // weighting in order to guarantee consistent behavior across different - // Drupal installations, which might have modules installed in different - // locations in the file system. The ordering here must also be - // consistent with the one used in module_implements(). - $result = db_query("SELECT * FROM {system} WHERE type = 'theme' OR (type = 'module' AND status = 1) ORDER BY weight ASC, name ASC"); - foreach ($result as $record) { - $record->info = unserialize($record->info); - // Build a list of all enabled modules. - if ($record->type == 'module') { - $lists['module_enabled'][$record->name] = $record; - } - // Build a list of themes. - if ($record->type == 'theme') { - $lists['theme'][$record->name] = $record; - } - // Build a list of filenames so drupal_get_filename can use it. - if ($record->status) { - $lists['filepaths'][] = array('type' => $record->type, 'name' => $record->name, 'filepath' => $record->filename); - } - } - cache('bootstrap')->set('system_list', $lists); - } - // To avoid a separate database lookup for the filepath, prime the - // drupal_get_filename() static cache with all enabled modules and themes. - foreach ($lists['filepaths'] as $item) { - drupal_get_filename($item['type'], $item['name'], $item['filepath']); - } - } - - return $lists[$type]; -} - -/** - * Reset all system_list() caches. - */ -function system_list_reset() { - drupal_static_reset('system_list'); - drupal_static_reset('system_rebuild_module_data'); - drupal_static_reset('list_themes'); - cache('bootstrap')->deleteMultiple(array('bootstrap_modules', 'system_list')); -} - -/** - * Find dependencies any level deep and fill in required by information too. - * - * @param $files - * The array of filesystem objects used to rebuild the cache. - * - * @return - * The same array with the new keys for each module: - * - requires: An array with the keys being the modules that this module - * requires. - * - required_by: An array with the keys being the modules that will not work - * without this module. - */ -function _module_build_dependencies($files) { - require_once DRUPAL_ROOT . '/core/includes/graph.inc'; - foreach ($files as $filename => $file) { - $graph[$file->name]['edges'] = array(); - if (isset($file->info['dependencies']) && is_array($file->info['dependencies'])) { - foreach ($file->info['dependencies'] as $dependency) { - $dependency_data = drupal_parse_dependency($dependency); - $graph[$file->name]['edges'][$dependency_data['name']] = $dependency_data; - } - } - } - drupal_depth_first_search($graph); - foreach ($graph as $module => $data) { - $files[$module]->required_by = isset($data['reverse_paths']) ? $data['reverse_paths'] : array(); - $files[$module]->requires = isset($data['paths']) ? $data['paths'] : array(); - $files[$module]->sort = $data['weight']; - } - return $files; -} - -/** - * Determine whether a given module exists. - * - * @param $module - * The name of the module (without the .module extension). - * - * @return - * TRUE if the module is both installed and enabled. - */ -function module_exists($module) { - $list = module_list(); - return isset($list[$module]); -} - -/** - * Load a module's installation hooks. - * - * @param $module - * The name of the module (without the .module extension). - * - * @return - * The name of the module's install file, if successful; FALSE otherwise. - */ -function module_load_install($module) { - // Make sure the installation API is available - include_once DRUPAL_ROOT . '/core/includes/install.inc'; - - return module_load_include('install', $module); -} - -/** - * Load a module include file. - * - * Examples: - * @code - * // Load node.admin.inc from the node module. - * module_load_include('inc', 'node', 'node.admin'); - * // Load content_types.inc from the node module. - * module_load_include('inc', 'node', 'content_types'); - * @endcode - * - * Do not use this function to load an install file, use module_load_install() - * instead. Do not use this function in a global context since it requires - * Drupal to be fully bootstrapped, use require_once DRUPAL_ROOT . '/path/file' - * instead. - * - * @param $type - * The include file's type (file extension). - * @param $module - * The module to which the include file belongs. - * @param $name - * (optional) The base file name (without the $type extension). If omitted, - * $module is used; i.e., resulting in "$module.$type" by default. - * - * @return - * The name of the included file, if successful; FALSE otherwise. - */ -function module_load_include($type, $module, $name = NULL) { - if (!isset($name)) { - $name = $module; - } - - if (function_exists('drupal_get_path')) { - $file = DRUPAL_ROOT . '/' . drupal_get_path('module', $module) . "/$name.$type"; - if (is_file($file)) { - require_once $file; - return $file; - } - } - return FALSE; -} - -/** - * Load an include file for each of the modules that have been enabled in - * the system table. - */ -function module_load_all_includes($type, $name = NULL) { - $modules = module_list(); - foreach ($modules as $module) { - module_load_include($type, $module, $name); - } -} - -/** - * Enables or installs a given list of modules. - * - * Definitions: - * - "Enabling" is the process of activating a module for use by Drupal. - * - "Disabling" is the process of deactivating a module. - * - "Installing" is the process of enabling it for the first time or after it - * has been uninstalled. - * - "Uninstalling" is the process of removing all traces of a module. - * - * Order of events: - * - Gather and add module dependencies to $module_list (if applicable). - * - For each module that is being enabled: - * - Install module schema and update system registries and caches. - * - If the module is being enabled for the first time or had been - * uninstalled, invoke hook_install() and add it to the list of installed - * modules. - * - Invoke hook_enable(). - * - Invoke hook_modules_installed(). - * - Invoke hook_modules_enabled(). - * - * @param $module_list - * An array of module names. - * @param $enable_dependencies - * If TRUE, dependencies will automatically be added and enabled in the - * correct order. This incurs a significant performance cost, so use FALSE - * if you know $module_list is already complete and in the correct order. - * - * @return - * FALSE if one or more dependencies are missing, TRUE otherwise. - * - * @see hook_install() - * @see hook_enable() - * @see hook_modules_installed() - * @see hook_modules_enabled() - */ -function module_enable($module_list, $enable_dependencies = TRUE) { - if ($enable_dependencies) { - // Get all module data so we can find dependencies and sort. - $module_data = system_rebuild_module_data(); - // Create an associative array with weights as values. - $module_list = array_flip(array_values($module_list)); - - while (list($module) = each($module_list)) { - if (!isset($module_data[$module])) { - // This module is not found in the filesystem, abort. - return FALSE; - } - if ($module_data[$module]->status) { - // Skip already enabled modules. - unset($module_list[$module]); - continue; - } - $module_list[$module] = $module_data[$module]->sort; - - // Add dependencies to the list, with a placeholder weight. - // The new modules will be processed as the while loop continues. - foreach (array_keys($module_data[$module]->requires) as $dependency) { - if (!isset($module_list[$dependency])) { - $module_list[$dependency] = 0; - } - } - } - - if (!$module_list) { - // Nothing to do. All modules already enabled. - return TRUE; - } - - // Sort the module list by pre-calculated weights. - arsort($module_list); - $module_list = array_keys($module_list); - } - - // Required for module installation checks. - include_once DRUPAL_ROOT . '/core/includes/install.inc'; - - $modules_installed = array(); - $modules_enabled = array(); - foreach ($module_list as $module) { - // Only process modules that are not already enabled. - $existing = db_query("SELECT status FROM {system} WHERE type = :type AND name = :name", array( - ':type' => 'module', - ':name' => $module)) - ->fetchObject(); - if ($existing->status == 0) { - // Load the module's code. - drupal_load('module', $module); - module_load_install($module); - - // Update the database and module list to reflect the new module. This - // needs to be done first so that the module's hook implementations, - // hook_schema() in particular, can be called while it is being - // installed. - db_update('system') - ->fields(array('status' => 1)) - ->condition('type', 'module') - ->condition('name', $module) - ->execute(); - // Refresh the module list to include it. - system_list_reset(); - module_list(TRUE); - module_implements_reset(); - _system_update_bootstrap_status(); - // Update the registry to include it. - registry_update(); - // Refresh the schema to include it. - drupal_get_schema(NULL, TRUE); - - // Allow modules to react prior to the installation of a module. - module_invoke_all('modules_preinstall', array($module)); - - // Now install the module if necessary. - if (drupal_get_installed_schema_version($module, TRUE) == SCHEMA_UNINSTALLED) { - drupal_install_schema($module); - - // Set the schema version to the number of the last update provided - // by the module. - $versions = drupal_get_schema_versions($module); - $version = $versions ? max($versions) : SCHEMA_INSTALLED; - - // If the module has no current updates, but has some that were - // previously removed, set the version to the value of - // hook_update_last_removed(). - if ($last_removed = module_invoke($module, 'update_last_removed')) { - $version = max($version, $last_removed); - } - drupal_set_installed_schema_version($module, $version); - // Allow the module to perform install tasks. - module_invoke($module, 'install'); - // Record the fact that it was installed. - $modules_installed[] = $module; - watchdog('system', '%module module installed.', array('%module' => $module), WATCHDOG_INFO); - } - - // Allow modules to react prior to the enabling of a module. - module_invoke_all('modules_preenable', array($module)); - - // Enable the module. - module_invoke($module, 'enable'); - - // Record the fact that it was enabled. - $modules_enabled[] = $module; - watchdog('system', '%module module enabled.', array('%module' => $module), WATCHDOG_INFO); - } - } - - // If any modules were newly installed, invoke hook_modules_installed(). - if (!empty($modules_installed)) { - module_invoke_all('modules_installed', $modules_installed); - } - - // If any modules were newly enabled, invoke hook_modules_enabled(). - if (!empty($modules_enabled)) { - module_invoke_all('modules_enabled', $modules_enabled); - } - - return TRUE; -} - -/** - * Disable a given set of modules. - * - * @param $module_list - * An array of module names. - * @param $disable_dependents - * If TRUE, dependent modules will automatically be added and disabled in the - * correct order. This incurs a significant performance cost, so use FALSE - * if you know $module_list is already complete and in the correct order. - */ -function module_disable($module_list, $disable_dependents = TRUE) { - if ($disable_dependents) { - // Get all module data so we can find dependents and sort. - $module_data = system_rebuild_module_data(); - // Create an associative array with weights as values. - $module_list = array_flip(array_values($module_list)); - - $profile = drupal_get_profile(); - while (list($module) = each($module_list)) { - if (!isset($module_data[$module]) || !$module_data[$module]->status) { - // This module doesn't exist or is already disabled, skip it. - unset($module_list[$module]); - continue; - } - $module_list[$module] = $module_data[$module]->sort; - - // Add dependent modules to the list, with a placeholder weight. - // The new modules will be processed as the while loop continues. - foreach ($module_data[$module]->required_by as $dependent => $dependent_data) { - if (!isset($module_list[$dependent]) && $dependent != $profile) { - $module_list[$dependent] = 0; - } - } - } - - // Sort the module list by pre-calculated weights. - asort($module_list); - $module_list = array_keys($module_list); - } - - $invoke_modules = array(); - - foreach ($module_list as $module) { - if (module_exists($module)) { - // Check if node_access table needs rebuilding. - if (!node_access_needs_rebuild() && module_hook($module, 'node_grants')) { - node_access_needs_rebuild(TRUE); - } - - module_load_install($module); - module_invoke($module, 'disable'); - db_update('system') - ->fields(array('status' => 0)) - ->condition('type', 'module') - ->condition('name', $module) - ->execute(); - $invoke_modules[] = $module; - watchdog('system', '%module module disabled.', array('%module' => $module), WATCHDOG_INFO); - } - } - - if (!empty($invoke_modules)) { - // Refresh the module list to exclude the disabled modules. - system_list_reset(); - module_list(TRUE); - module_implements_reset(); - // Invoke hook_modules_disabled before disabling modules, - // so we can still call module hooks to get information. - module_invoke_all('modules_disabled', $invoke_modules); - // Update the registry to remove the newly-disabled module. - registry_update(); - _system_update_bootstrap_status(); - } - - // If there remains no more node_access module, rebuilding will be - // straightforward, we can do it right now. - if (node_access_needs_rebuild() && count(module_implements('node_grants')) == 0) { - node_access_rebuild(); - } -} - -/** - * @defgroup hooks Hooks - * @{ - * Allow modules to interact with the Drupal core. - * - * Drupal's module system is based on the concept of "hooks". A hook is a PHP - * function that is named foo_bar(), where "foo" is the name of the module - * (whose filename is thus foo.module) and "bar" is the name of the hook. Each - * hook has a defined set of parameters and a specified result type. - * - * To extend Drupal, a module need simply implement a hook. When Drupal wishes - * to allow intervention from modules, it determines which modules implement a - * hook and calls that hook in all enabled modules that implement it. - * - * The available hooks to implement are explained here in the Hooks section of - * the developer documentation. The string "hook" is used as a placeholder for - * the module name in the hook definitions. For example, if the module file is - * called example.module, then hook_help() as implemented by that module would - * be defined as example_help(). - * - * The example functions included are not part of the Drupal core, they are - * just models that you can modify. Only the hooks implemented within modules - * are executed when running Drupal. - * - * See also @link themeable the themeable group page. @endlink - */ - -/** - * Determine whether a module implements a hook. - * - * @param $module - * The name of the module (without the .module extension). - * @param $hook - * The name of the hook (e.g. "help" or "menu"). - * - * @return - * TRUE if the module is both installed and enabled, and the hook is - * implemented in that module. - */ -function module_hook($module, $hook) { - $function = $module . '_' . $hook; - if (function_exists($function)) { - return TRUE; - } - // If the hook implementation does not exist, check whether it may live in an - // optional include file registered via hook_hook_info(). - $hook_info = module_hook_info(); - if (isset($hook_info[$hook]['group'])) { - module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']); - if (function_exists($function)) { - return TRUE; - } - } - return FALSE; -} - -/** - * Determine which modules are implementing a hook. - * - * @param $hook - * The name of the hook (e.g. "help" or "menu"). - * @param $sort - * By default, modules are ordered by weight and filename, settings this option - * to TRUE, module list will be ordered by module name. - * - * @return - * An array with the names of the modules which are implementing this hook. - * - * @see module_implements_write_cache() - */ -function module_implements($hook, $sort = FALSE) { - // Use the advanced drupal_static() pattern, since this is called very often. - static $drupal_static_fast; - if (!isset($drupal_static_fast)) { - $drupal_static_fast['implementations'] = &drupal_static(__FUNCTION__); - } - $implementations = &$drupal_static_fast['implementations']; - - // Fetch implementations from cache. - if (empty($implementations)) { - $implementations = cache('bootstrap')->get('module_implements'); - if ($implementations === FALSE) { - $implementations = array(); - } - else { - $implementations = $implementations->data; - } - } - - if (!isset($implementations[$hook])) { - // The hook is not cached, so ensure that whether or not it has - // implementations, that the cache is updated at the end of the request. - $implementations['#write_cache'] = TRUE; - $hook_info = module_hook_info(); - $implementations[$hook] = array(); - $list = module_list(FALSE, FALSE, $sort); - foreach ($list as $module) { - $include_file = isset($hook_info[$hook]['group']) && module_load_include('inc', $module, $module . '.' . $hook_info[$hook]['group']); - // Since module_hook() may needlessly try to load the include file again, - // function_exists() is used directly here. - if (function_exists($module . '_' . $hook)) { - $implementations[$hook][$module] = $include_file ? $hook_info[$hook]['group'] : FALSE; - } - } - // Allow modules to change the weight of specific implementations but avoid - // an infinite loop. - if ($hook != 'module_implements_alter') { - drupal_alter('module_implements', $implementations[$hook], $hook); - } - } - else { - foreach ($implementations[$hook] as $module => $group) { - // If this hook implementation is stored in a lazy-loaded file, so include - // that file first. - if ($group) { - module_load_include('inc', $module, "$module.$group"); - } - // It is possible that a module removed a hook implementation without the - // implementations cache being rebuilt yet, so we check whether the - // function exists on each request to avoid undefined function errors. - // Since module_hook() may needlessly try to load the include file again, - // function_exists() is used directly here. - if (!function_exists($module . '_' . $hook)) { - // Clear out the stale implementation from the cache and force a cache - // refresh to forget about no longer existing hook implementations. - unset($implementations[$hook][$module]); - $implementations['#write_cache'] = TRUE; - } - } - } - - return array_keys($implementations[$hook]); -} - -/** - * Regenerate the stored list of hook implementations. - */ -function module_implements_reset() { - // We maintain a persistent cache of hook implementations in addition to the - // static cache to avoid looping through every module and every hook on each - // request. Benchmarks show that the benefit of this caching outweighs the - // additional database hit even when using the default database caching - // backend and only a small number of modules are enabled. The cost of the - // cache_get() is more or less constant and reduced further when non-database - // caching backends are used, so there will be more significant gains when a - // large number of modules are installed or hooks invoked, since this can - // quickly lead to module_hook() being called several thousand times - // per request. - drupal_static_reset('module_implements'); - cache('bootstrap')->set('module_implements', array()); - drupal_static_reset('module_hook_info'); - drupal_static_reset('drupal_alter'); - cache('bootstrap')->delete('hook_info'); -} - -/** - * Retrieve a list of what hooks are explicitly declared. - */ -function module_hook_info() { - // This function is indirectly invoked from bootstrap_invoke_all(), in which - // case common.inc, subsystems, and modules are not loaded yet, so it does not - // make sense to support hook groups resp. lazy-loaded include files prior to - // full bootstrap. - if (drupal_bootstrap(NULL, FALSE) != DRUPAL_BOOTSTRAP_FULL) { - return array(); - } - $hook_info = &drupal_static(__FUNCTION__); - - if (!isset($hook_info)) { - $hook_info = array(); - $cache = cache('bootstrap')->get('hook_info'); - if ($cache === FALSE) { - // Rebuild the cache and save it. - // We can't use module_invoke_all() here or it would cause an infinite - // loop. - foreach (module_list() as $module) { - $function = $module . '_hook_info'; - if (function_exists($function)) { - $result = $function(); - if (isset($result) && is_array($result)) { - $hook_info = array_merge_recursive($hook_info, $result); - } - } - } - // We can't use drupal_alter() for the same reason as above. - foreach (module_list() as $module) { - $function = $module . '_hook_info_alter'; - if (function_exists($function)) { - $function($hook_info); - } - } - cache('bootstrap')->set('hook_info', $hook_info); - } - else { - $hook_info = $cache->data; - } - } - - return $hook_info; -} - -/** - * Writes the hook implementation cache. - * - * @see module_implements() - */ -function module_implements_write_cache() { - $implementations = &drupal_static('module_implements'); - // Check whether we need to write the cache. We do not want to cache hooks - // which are only invoked on HTTP POST requests since these do not need to be - // optimized as tightly, and not doing so keeps the cache entry smaller. - if (isset($implementations['#write_cache']) && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD')) { - unset($implementations['#write_cache']); - cache('bootstrap')->set('module_implements', $implementations); - } -} - -/** - * Invoke a hook in a particular module. - * - * @param $module - * The name of the module (without the .module extension). - * @param $hook - * The name of the hook to invoke. - * @param ... - * Arguments to pass to the hook implementation. - * - * @return - * The return value of the hook implementation. - */ -function module_invoke($module, $hook) { - $args = func_get_args(); - // Remove $module and $hook from the arguments. - unset($args[0], $args[1]); - if (module_hook($module, $hook)) { - return call_user_func_array($module . '_' . $hook, $args); - } -} - -/** - * Invoke a hook in all enabled modules that implement it. - * - * @param $hook - * The name of the hook to invoke. - * @param ... - * Arguments to pass to the hook. - * - * @return - * An array of return values of the hook implementations. If modules return - * arrays from their implementations, those are merged into one array. - */ -function module_invoke_all($hook) { - $args = func_get_args(); - // Remove $hook from the arguments. - unset($args[0]); - $return = array(); - foreach (module_implements($hook) as $module) { - $function = $module . '_' . $hook; - if (function_exists($function)) { - $result = call_user_func_array($function, $args); - if (isset($result) && is_array($result)) { - $return = array_merge_recursive($return, $result); - } - elseif (isset($result)) { - $return[] = $result; - } - } - } - - return $return; -} - -/** - * @} End of "defgroup hooks". - */ - -/** - * Array of modules required by core. - */ -function drupal_required_modules() { - $files = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info$/', 'modules', 'name', 0); - $required = array(); - - // An install profile is required and one must always be loaded. - $required[] = drupal_get_profile(); - - foreach ($files as $name => $file) { - $info = drupal_parse_info_file($file->uri); - if (!empty($info) && !empty($info['required']) && $info['required']) { - $required[] = $name; - } - } - - return $required; -} - -/** - * Hands off alterable variables to type-specific *_alter implementations. - * - * This dispatch function hands off the passed-in variables to type-specific - * hook_TYPE_alter() implementations in modules. It ensures a consistent - * interface for all altering operations. - * - * A maximum of 2 alterable arguments is supported. In case more arguments need - * to be passed and alterable, modules provide additional variables assigned by - * reference in the last $context argument: - * @code - * $context = array( - * 'alterable' => &$alterable, - * 'unalterable' => $unalterable, - * 'foo' => 'bar', - * ); - * drupal_alter('mymodule_data', $alterable1, $alterable2, $context); - * @endcode - * - * Note that objects are always passed by reference in PHP5. If it is absolutely - * required that no implementation alters a passed object in $context, then an - * object needs to be cloned: - * @code - * $context = array( - * 'unalterable_object' => clone $object, - * ); - * drupal_alter('mymodule_data', $data, $context); - * @endcode - * - * @param $type - * A string describing the type of the alterable $data. 'form', 'links', - * 'node_content', and so on are several examples. Alternatively can be an - * array, in which case hook_TYPE_alter() is invoked for each value in the - * array, ordered first by module, and then for each module, in the order of - * values in $type. For example, when Form API is using drupal_alter() to - * execute both hook_form_alter() and hook_form_FORM_ID_alter() - * implementations, it passes array('form', 'form_' . $form_id) for $type. - * @param $data - * The variable that will be passed to hook_TYPE_alter() implementations to be - * altered. The type of this variable depends on the value of the $type - * argument. For example, when altering a 'form', $data will be a structured - * array. When altering a 'profile', $data will be an object. - * @param $context1 - * (optional) An additional variable that is passed by reference. - * @param $context2 - * (optional) An additional variable that is passed by reference. If more - * context needs to be provided to implementations, then this should be an - * associative array as described above. - */ -function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) { - // Use the advanced drupal_static() pattern, since this is called very often. - static $drupal_static_fast; - if (!isset($drupal_static_fast)) { - $drupal_static_fast['functions'] = &drupal_static(__FUNCTION__); - } - $functions = &$drupal_static_fast['functions']; - - // Most of the time, $type is passed as a string, so for performance, - // normalize it to that. When passed as an array, usually the first item in - // the array is a generic type, and additional items in the array are more - // specific variants of it, as in the case of array('form', 'form_FORM_ID'). - if (is_array($type)) { - $cid = implode(',', $type); - $extra_types = $type; - $type = array_shift($extra_types); - // Allow if statements in this function to use the faster isset() rather - // than !empty() both when $type is passed as a string, or as an array with - // one item. - if (empty($extra_types)) { - unset($extra_types); - } - } - else { - $cid = $type; - } - - // Some alter hooks are invoked many times per page request, so statically - // cache the list of functions to call, and on subsequent calls, iterate - // through them quickly. - if (!isset($functions[$cid])) { - $functions[$cid] = array(); - $hook = $type . '_alter'; - $modules = module_implements($hook); - if (!isset($extra_types)) { - // For the more common case of a single hook, we do not need to call - // function_exists(), since module_implements() returns only modules with - // implementations. - foreach ($modules as $module) { - $functions[$cid][] = $module . '_' . $hook; - } - } - else { - // For multiple hooks, we need $modules to contain every module that - // implements at least one of them. - $extra_modules = array(); - foreach ($extra_types as $extra_type) { - $extra_modules = array_merge($extra_modules, module_implements($extra_type . '_alter')); - } - // If any modules implement one of the extra hooks that do not implement - // the primary hook, we need to add them to the $modules array in their - // appropriate order. - if (array_diff($extra_modules, $modules)) { - // Order the modules by the order returned by module_list(). - $modules = array_intersect(module_list(), array_merge($modules, $extra_modules)); - } - foreach ($modules as $module) { - // Since $modules is a merged array, for any given module, we do not - // know whether it has any particular implementation, so we need a - // function_exists(). - $function = $module . '_' . $hook; - if (function_exists($function)) { - $functions[$cid][] = $function; - } - foreach ($extra_types as $extra_type) { - $function = $module . '_' . $extra_type . '_alter'; - if (function_exists($function)) { - $functions[$cid][] = $function; - } - } - } - } - // Allow the theme to alter variables after the theme system has been - // initialized. - global $theme, $base_theme_info; - if (isset($theme)) { - $theme_keys = array(); - foreach ($base_theme_info as $base) { - $theme_keys[] = $base->name; - } - $theme_keys[] = $theme; - foreach ($theme_keys as $theme_key) { - $function = $theme_key . '_' . $hook; - if (function_exists($function)) { - $functions[$cid][] = $function; - } - if (isset($extra_types)) { - foreach ($extra_types as $extra_type) { - $function = $theme_key . '_' . $extra_type . '_alter'; - if (function_exists($function)) { - $functions[$cid][] = $function; - } - } - } - } - } - } - - foreach ($functions[$cid] as $function) { - $function($data, $context1, $context2); - } -} - diff --git a/core/includes/pager.inc b/core/includes/pager.inc deleted file mode 100644 index a5d3e6be03c..00000000000 --- a/core/includes/pager.inc +++ /dev/null @@ -1,658 +0,0 @@ -<?php - -/** - * @file - * Functions to aid in presenting database results as a set of pages. - */ - - -/** - * Query extender for pager queries. - * - * This is the "default" pager mechanism. It creates a paged query with a fixed - * number of entries per page. - */ -class PagerDefault extends SelectQueryExtender { - - /** - * The highest element we've autogenerated so far. - * - * @var int - */ - static $maxElement = 0; - - /** - * The number of elements per page to allow. - * - * @var int - */ - protected $limit = 10; - - /** - * The unique ID of this pager on this page. - * - * @var int - */ - protected $element = NULL; - - /** - * The count query that will be used for this pager. - * - * @var SelectQueryInterface - */ - protected $customCountQuery = FALSE; - - public function __construct(SelectQueryInterface $query, DatabaseConnection $connection) { - parent::__construct($query, $connection); - - // Add pager tag. Do this here to ensure that it is always added before - // preExecute() is called. - $this->addTag('pager'); - } - - /** - * Override the execute method. - * - * Before we run the query, we need to add pager-based range() instructions - * to it. - */ - public function execute() { - - // Add convenience tag to mark that this is an extended query. We have to - // do this in the constructor to ensure that it is set before preExecute() - // gets called. - if (!$this->preExecute($this)) { - return NULL; - } - - // A NULL limit is the "kill switch" for pager queries. - if (empty($this->limit)) { - return; - } - $this->ensureElement(); - - $total_items = $this->getCountQuery()->execute()->fetchField(); - $current_page = pager_default_initialize($total_items, $this->limit, $this->element); - $this->range($current_page * $this->limit, $this->limit); - - // Now that we've added our pager-based range instructions, run the query normally. - return $this->query->execute(); - } - - /** - * Ensure that there is an element associated with this query. - * If an element was not specified previously, then the value of the - * $maxElement counter is taken, after which the counter is incremented. - * - * After running this method, access $this->element to get the element for this - * query. - */ - protected function ensureElement() { - if (!isset($this->element)) { - $this->element = self::$maxElement++; - } - } - - /** - * Specify the count query object to use for this pager. - * - * You will rarely need to specify a count query directly. If not specified, - * one is generated off of the pager query itself. - * - * @param SelectQueryInterface $query - * The count query object. It must return a single row with a single column, - * which is the total number of records. - */ - public function setCountQuery(SelectQueryInterface $query) { - $this->customCountQuery = $query; - } - - /** - * Retrieve the count query for this pager. - * - * The count query may be specified manually or, by default, taken from the - * query we are extending. - * - * @return SelectQueryInterface - * A count query object. - */ - public function getCountQuery() { - if ($this->customCountQuery) { - return $this->customCountQuery; - } - else { - return $this->query->countQuery(); - } - } - - /** - * Specify the maximum number of elements per page for this query. - * - * The default if not specified is 10 items per page. - * - * @param $limit - * An integer specifying the number of elements per page. If passed a false - * value (FALSE, 0, NULL), the pager is disabled. - */ - public function limit($limit = 10) { - $this->limit = $limit; - return $this; - } - - /** - * Specify the element ID for this pager query. - * - * The element is used to differentiate different pager queries on the same - * page so that they may be operated independently. If you do not specify an - * element, every pager query on the page will get a unique element. If for - * whatever reason you want to explicitly define an element for a given query, - * you may do so here. - * - * Setting the element here also increments the static $maxElement counter, - * which is used for determining the $element when there's none specified. - * - * Note that no collision detection is done when setting an element ID - * explicitly, so it is possible for two pagers to end up using the same ID - * if both are set explicitly. - * - * @param $element - */ - public function element($element) { - $this->element = $element; - if ($element >= self::$maxElement) { - self::$maxElement = $element + 1; - } - return $this; - } -} - -/** - * Returns the current page being requested for display within a pager. - * - * @param $element - * An optional integer to distinguish between multiple pagers on one page. - * - * @return - * The number of the current requested page, within the pager represented by - * $element. This is determined from the URL query parameter $_GET['page'], or - * 0 by default. Note that this number may differ from the actual page being - * displayed. For example, if a search for "example text" brings up three - * pages of results, but a users visits search/node/example+text?page=10, this - * function will return 10, even though the default pager implementation - * adjusts for this and still displays the third page of search results at - * that URL. - * - * @see pager_default_initialize() - */ -function pager_find_page($element = 0) { - $page = isset($_GET['page']) ? $_GET['page'] : ''; - $page_array = explode(',', $page); - if (!isset($page_array[$element])) { - $page_array[$element] = 0; - } - return (int) $page_array[$element]; -} - -/** - * Initializes a pager for theme('pager'). - * - * This function sets up the necessary global variables so that future calls - * to theme('pager') will render a pager that correctly corresponds to the - * items being displayed. - * - * If the items being displayed result from a database query performed using - * Drupal's database API, and if you have control over the construction of the - * database query, you do not need to call this function directly; instead, you - * can simply extend the query object with the 'PagerDefault' extender before - * executing it. For example: - * @code - * $query = db_select('some_table')->extend('PagerDefault'); - * @endcode - * - * However, if you are using a different method for generating the items to be - * paged through, then you should call this function in preparation. - * - * The following example shows how this function can be used in a page callback - * that invokes an external datastore with an SQL-like syntax: - * @code - * // First find the total number of items and initialize the pager. - * $where = "status = 1"; - * $total = mymodule_select("SELECT COUNT(*) FROM data " . $where)->result(); - * $num_per_page = variable_get('mymodule_num_per_page', 10); - * $page = pager_default_initialize($total, $num_per_page); - * - * // Next, retrieve and display the items for the current page. - * $offset = $num_per_page * $page; - * $result = mymodule_select("SELECT * FROM data " . $where . " LIMIT %d, %d", $offset, $num_per_page)->fetchAll(); - * $output = theme('mymodule_results', array('result' => $result)); - * - * // Finally, display the pager controls, and return. - * $output .= theme('pager'); - * return $output; - * @endcode - * - * A second example involves a page callback that invokes an external search - * service where the total number of matching results is provided as part of - * the returned set (so that we do not need a separate query in order to obtain - * this information). Here, we call pager_find_page() to calculate the desired - * offset before the search is invoked: - * @code - * // Perform the query, using the requested offset from pager_find_page(). - * // This comes from a URL parameter, so here we are assuming that the URL - * // parameter corresponds to an actual page of results that will exist - * // within the set. - * $page = pager_find_page(); - * $num_per_page = variable_get('mymodule_num_per_page', 10); - * $offset = $num_per_page * $page; - * $result = mymodule_remote_search($keywords, $offset, $num_per_page); - * - * // Now that we have the total number of results, initialize the pager. - * pager_default_initialize($result->total, $num_per_page); - * - * // Display the search results. - * $output = theme('search_results', array('results' => $result->data, 'type' => 'remote')); - * - * // Finally, display the pager controls, and return. - * $output .= theme('pager'); - * return $output; - * @endcode - * - * @param $total - * The total number of items to be paged. - * @param $limit - * The number of items the calling code will display per page. - * @param $element - * An optional integer to distinguish between multiple pagers on one page. - * - * @return - * The number of the current page, within the pager represented by $element. - * This is determined from the URL query parameter $_GET['page'], or 0 by - * default. However, if a page that does not correspond to the actual range - * of the result set was requested, this function will return the closest - * page actually within the result set. - */ -function pager_default_initialize($total, $limit, $element = 0) { - global $pager_page_array, $pager_total, $pager_total_items, $pager_limits; - - $page = pager_find_page($element); - - // We calculate the total of pages as ceil(items / limit). - $pager_total_items[$element] = $total; - $pager_total[$element] = ceil($pager_total_items[$element] / $limit); - $pager_page_array[$element] = max(0, min($page, ((int) $pager_total[$element]) - 1)); - $pager_limits[$element] = $limit; - return $pager_page_array[$element]; -} - -/** - * Compose a URL query parameter array for pager links. - * - * @return - * A URL query parameter array that consists of all components of the current - * page request except for those pertaining to paging. - */ -function pager_get_query_parameters() { - $query = &drupal_static(__FUNCTION__); - if (!isset($query)) { - $query = drupal_get_query_parameters($_GET, array('q', 'page')); - } - return $query; -} - -/** - * Returns HTML for a query pager. - * - * Menu callbacks that display paged query results should call theme('pager') to - * retrieve a pager control so that users can view other results. Format a list - * of nearby pages with additional query results. - * - * @param $variables - * An associative array containing: - * - tags: An array of labels for the controls in the pager. - * - element: An optional integer to distinguish between multiple pagers on - * one page. - * - parameters: An associative array of query string parameters to append to - * the pager links. - * - quantity: The number of pages in the list. - * - * @ingroup themeable - */ -function theme_pager($variables) { - $tags = $variables['tags']; - $element = $variables['element']; - $parameters = $variables['parameters']; - $quantity = $variables['quantity']; - global $pager_page_array, $pager_total; - - // Calculate various markers within this pager piece: - // Middle is used to "center" pages around the current page. - $pager_middle = ceil($quantity / 2); - // current is the page we are currently paged to - $pager_current = $pager_page_array[$element] + 1; - // first is the first page listed by this pager piece (re quantity) - $pager_first = $pager_current - $pager_middle + 1; - // last is the last page listed by this pager piece (re quantity) - $pager_last = $pager_current + $quantity - $pager_middle; - // max is the maximum page number - $pager_max = $pager_total[$element]; - // End of marker calculations. - - // Prepare for generation loop. - $i = $pager_first; - if ($pager_last > $pager_max) { - // Adjust "center" if at end of query. - $i = $i + ($pager_max - $pager_last); - $pager_last = $pager_max; - } - if ($i <= 0) { - // Adjust "center" if at start of query. - $pager_last = $pager_last + (1 - $i); - $i = 1; - } - // End of generation loop preparation. - - $li_first = theme('pager_first', array('text' => (isset($tags[0]) ? $tags[0] : t('« first')), 'element' => $element, 'parameters' => $parameters)); - $li_previous = theme('pager_previous', array('text' => (isset($tags[1]) ? $tags[1] : t('‹ previous')), 'element' => $element, 'interval' => 1, 'parameters' => $parameters)); - $li_next = theme('pager_next', array('text' => (isset($tags[3]) ? $tags[3] : t('next ›')), 'element' => $element, 'interval' => 1, 'parameters' => $parameters)); - $li_last = theme('pager_last', array('text' => (isset($tags[4]) ? $tags[4] : t('last »')), 'element' => $element, 'parameters' => $parameters)); - - if ($pager_total[$element] > 1) { - if ($li_first) { - $items[] = array( - 'class' => array('pager-first'), - 'data' => $li_first, - ); - } - if ($li_previous) { - $items[] = array( - 'class' => array('pager-previous'), - 'data' => $li_previous, - ); - } - - // When there is more than one page, create the pager list. - if ($i != $pager_max) { - if ($i > 1) { - $items[] = array( - 'class' => array('pager-ellipsis'), - 'data' => '…', - ); - } - // Now generate the actual pager piece. - for (; $i <= $pager_last && $i <= $pager_max; $i++) { - if ($i < $pager_current) { - $items[] = array( - 'class' => array('pager-item'), - 'data' => theme('pager_previous', array('text' => $i, 'element' => $element, 'interval' => ($pager_current - $i), 'parameters' => $parameters)), - ); - } - if ($i == $pager_current) { - $items[] = array( - 'class' => array('pager-current'), - 'data' => $i, - ); - } - if ($i > $pager_current) { - $items[] = array( - 'class' => array('pager-item'), - 'data' => theme('pager_next', array('text' => $i, 'element' => $element, 'interval' => ($i - $pager_current), 'parameters' => $parameters)), - ); - } - } - if ($i < $pager_max) { - $items[] = array( - 'class' => array('pager-ellipsis'), - 'data' => '…', - ); - } - } - // End generation. - if ($li_next) { - $items[] = array( - 'class' => array('pager-next'), - 'data' => $li_next, - ); - } - if ($li_last) { - $items[] = array( - 'class' => array('pager-last'), - 'data' => $li_last, - ); - } - return '<h2 class="element-invisible">' . t('Pages') . '</h2>' . theme('item_list', array( - 'items' => $items, - 'attributes' => array('class' => array('pager')), - )); - } -} - - -/** - * @defgroup pagerpieces Pager pieces - * @{ - * Theme functions for customizing pager elements. - * - * Note that you should NOT modify this file to customize your pager. - */ - -/** - * Returns HTML for the "first page" link in a query pager. - * - * @param $variables - * An associative array containing: - * - text: The name (or image) of the link. - * - element: An optional integer to distinguish between multiple pagers on - * one page. - * - parameters: An associative array of query string parameters to append to - * the pager links. - * - * @ingroup themeable - */ -function theme_pager_first($variables) { - $text = $variables['text']; - $element = $variables['element']; - $parameters = $variables['parameters']; - global $pager_page_array; - $output = ''; - - // If we are anywhere but the first page - if ($pager_page_array[$element] > 0) { - $output = theme('pager_link', array('text' => $text, 'page_new' => pager_load_array(0, $element, $pager_page_array), 'element' => $element, 'parameters' => $parameters)); - } - - return $output; -} - -/** - * Returns HTML for the "previous page" link in a query pager. - * - * @param $variables - * An associative array containing: - * - text: The name (or image) of the link. - * - element: An optional integer to distinguish between multiple pagers on - * one page. - * - interval: The number of pages to move backward when the link is clicked. - * - parameters: An associative array of query string parameters to append to - * the pager links. - * - * @ingroup themeable - */ -function theme_pager_previous($variables) { - $text = $variables['text']; - $element = $variables['element']; - $interval = $variables['interval']; - $parameters = $variables['parameters']; - global $pager_page_array; - $output = ''; - - // If we are anywhere but the first page - if ($pager_page_array[$element] > 0) { - $page_new = pager_load_array($pager_page_array[$element] - $interval, $element, $pager_page_array); - - // If the previous page is the first page, mark the link as such. - if ($page_new[$element] == 0) { - $output = theme('pager_first', array('text' => $text, 'element' => $element, 'parameters' => $parameters)); - } - // The previous page is not the first page. - else { - $output = theme('pager_link', array('text' => $text, 'page_new' => $page_new, 'element' => $element, 'parameters' => $parameters)); - } - } - - return $output; -} - -/** - * Returns HTML for the "next page" link in a query pager. - * - * @param $variables - * An associative array containing: - * - text: The name (or image) of the link. - * - element: An optional integer to distinguish between multiple pagers on - * one page. - * - interval: The number of pages to move forward when the link is clicked. - * - parameters: An associative array of query string parameters to append to - * the pager links. - * - * @ingroup themeable - */ -function theme_pager_next($variables) { - $text = $variables['text']; - $element = $variables['element']; - $interval = $variables['interval']; - $parameters = $variables['parameters']; - global $pager_page_array, $pager_total; - $output = ''; - - // If we are anywhere but the last page - if ($pager_page_array[$element] < ($pager_total[$element] - 1)) { - $page_new = pager_load_array($pager_page_array[$element] + $interval, $element, $pager_page_array); - // If the next page is the last page, mark the link as such. - if ($page_new[$element] == ($pager_total[$element] - 1)) { - $output = theme('pager_last', array('text' => $text, 'element' => $element, 'parameters' => $parameters)); - } - // The next page is not the last page. - else { - $output = theme('pager_link', array('text' => $text, 'page_new' => $page_new, 'element' => $element, 'parameters' => $parameters)); - } - } - - return $output; -} - -/** - * Returns HTML for the "last page" link in query pager. - * - * @param $variables - * An associative array containing: - * - text: The name (or image) of the link. - * - element: An optional integer to distinguish between multiple pagers on - * one page. - * - parameters: An associative array of query string parameters to append to - * the pager links. - * - * @ingroup themeable - */ -function theme_pager_last($variables) { - $text = $variables['text']; - $element = $variables['element']; - $parameters = $variables['parameters']; - global $pager_page_array, $pager_total; - $output = ''; - - // If we are anywhere but the last page - if ($pager_page_array[$element] < ($pager_total[$element] - 1)) { - $output = theme('pager_link', array('text' => $text, 'page_new' => pager_load_array($pager_total[$element] - 1, $element, $pager_page_array), 'element' => $element, 'parameters' => $parameters)); - } - - return $output; -} - - -/** - * Returns HTML for a link to a specific query result page. - * - * @param $variables - * An associative array containing: - * - text: The link text. Also used to figure out the title attribute of the - * link, if it is not provided in $variables['attributes']['title']; in - * this case, $variables['text'] must be one of the standard pager link - * text strings that would be generated by the pager theme functions, such - * as a number or t('« first'). - * - page_new: The first result to display on the linked page. - * - element: An optional integer to distinguish between multiple pagers on - * one page. - * - parameters: An associative array of query string parameters to append to - * the pager link. - * - attributes: An associative array of HTML attributes to apply to the - * pager link. - * - * @see theme_pager() - * - * @ingroup themeable - */ -function theme_pager_link($variables) { - $text = $variables['text']; - $page_new = $variables['page_new']; - $element = $variables['element']; - $parameters = $variables['parameters']; - $attributes = $variables['attributes']; - - $page = isset($_GET['page']) ? $_GET['page'] : ''; - if ($new_page = implode(',', pager_load_array($page_new[$element], $element, explode(',', $page)))) { - $parameters['page'] = $new_page; - } - - $query = array(); - if (count($parameters)) { - $query = drupal_get_query_parameters($parameters, array()); - } - if ($query_pager = pager_get_query_parameters()) { - $query = array_merge($query, $query_pager); - } - - // Set each pager link title - if (!isset($attributes['title'])) { - static $titles = NULL; - if (!isset($titles)) { - $titles = array( - t('« first') => t('Go to first page'), - t('‹ previous') => t('Go to previous page'), - t('next ›') => t('Go to next page'), - t('last »') => t('Go to last page'), - ); - } - if (isset($titles[$text])) { - $attributes['title'] = $titles[$text]; - } - elseif (is_numeric($text)) { - $attributes['title'] = t('Go to page @number', array('@number' => $text)); - } - } - - return l($text, $_GET['q'], array('attributes' => $attributes, 'query' => $query)); -} - -/** - * @} End of "Pager pieces". - */ - -/** - * Helper function - * - * Copies $old_array to $new_array and sets $new_array[$element] = $value - * Fills in $new_array[0 .. $element - 1] = 0 - */ -function pager_load_array($value, $element, $old_array) { - $new_array = $old_array; - // Look for empty elements. - for ($i = 0; $i < $element; $i++) { - if (empty($new_array[$i])) { - // Load found empty element with 0. - $new_array[$i] = 0; - } - } - // Update the changed element. - $new_array[$element] = (int) $value; - return $new_array; -} diff --git a/core/includes/password.inc b/core/includes/password.inc deleted file mode 100644 index a4b96336294..00000000000 --- a/core/includes/password.inc +++ /dev/null @@ -1,289 +0,0 @@ -<?php - -/** - * @file - * Secure password hashing functions for user authentication. - * - * Based on the Portable PHP password hashing framework. - * @see http://www.openwall.com/phpass/ - * - * An alternative or custom version of this password hashing API may be - * used by setting the variable password_inc to the name of the PHP file - * containing replacement user_hash_password(), user_check_password(), and - * user_needs_new_hash() functions. - */ - -/** - * The standard log2 number of iterations for password stretching. This should - * increase by 1 every Drupal version in order to counteract increases in the - * speed and power of computers available to crack the hashes. - */ -define('DRUPAL_HASH_COUNT', 16); - -/** - * The minimum allowed log2 number of iterations for password stretching. - */ -define('DRUPAL_MIN_HASH_COUNT', 7); - -/** - * The maximum allowed log2 number of iterations for password stretching. - */ -define('DRUPAL_MAX_HASH_COUNT', 30); - -/** - * The expected (and maximum) number of characters in a hashed password. - */ -define('DRUPAL_HASH_LENGTH', 55); - -/** - * Returns a string for mapping an int to the corresponding base 64 character. - */ -function _password_itoa64() { - return './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; -} - -/** - * Encode bytes into printable base 64 using the *nix standard from crypt(). - * - * @param $input - * The string containing bytes to encode. - * @param $count - * The number of characters (bytes) to encode. - * - * @return - * Encoded string - */ -function _password_base64_encode($input, $count) { - $output = ''; - $i = 0; - $itoa64 = _password_itoa64(); - do { - $value = ord($input[$i++]); - $output .= $itoa64[$value & 0x3f]; - if ($i < $count) { - $value |= ord($input[$i]) << 8; - } - $output .= $itoa64[($value >> 6) & 0x3f]; - if ($i++ >= $count) { - break; - } - if ($i < $count) { - $value |= ord($input[$i]) << 16; - } - $output .= $itoa64[($value >> 12) & 0x3f]; - if ($i++ >= $count) { - break; - } - $output .= $itoa64[($value >> 18) & 0x3f]; - } while ($i < $count); - - return $output; -} - -/** - * Generates a random base 64-encoded salt prefixed with settings for the hash. - * - * Proper use of salts may defeat a number of attacks, including: - * - The ability to try candidate passwords against multiple hashes at once. - * - The ability to use pre-hashed lists of candidate passwords. - * - The ability to determine whether two users have the same (or different) - * password without actually having to guess one of the passwords. - * - * @param $count_log2 - * Integer that determines the number of iterations used in the hashing - * process. A larger value is more secure, but takes more time to complete. - * - * @return - * A 12 character string containing the iteration count and a random salt. - */ -function _password_generate_salt($count_log2) { - $output = '$S$'; - // Ensure that $count_log2 is within set bounds. - $count_log2 = _password_enforce_log2_boundaries($count_log2); - // We encode the final log2 iteration count in base 64. - $itoa64 = _password_itoa64(); - $output .= $itoa64[$count_log2]; - // 6 bytes is the standard salt for a portable phpass hash. - $output .= _password_base64_encode(drupal_random_bytes(6), 6); - return $output; -} - -/** - * Ensures that $count_log2 is within set bounds. - * - * @param $count_log2 - * Integer that determines the number of iterations used in the hashing - * process. A larger value is more secure, but takes more time to complete. - * - * @return - * Integer within set bounds that is closest to $count_log2. - */ -function _password_enforce_log2_boundaries($count_log2) { - if ($count_log2 < DRUPAL_MIN_HASH_COUNT) { - return DRUPAL_MIN_HASH_COUNT; - } - elseif ($count_log2 > DRUPAL_MAX_HASH_COUNT) { - return DRUPAL_MAX_HASH_COUNT; - } - - return (int) $count_log2; -} - -/** - * Hash a password using a secure stretched hash. - * - * By using a salt and repeated hashing the password is "stretched". Its - * security is increased because it becomes much more computationally costly - * for an attacker to try to break the hash by brute-force computation of the - * hashes of a large number of plain-text words or strings to find a match. - * - * @param $algo - * The string name of a hashing algorithm usable by hash(), like 'sha256'. - * @param $password - * The plain-text password to hash. - * @param $setting - * An existing hash or the output of _password_generate_salt(). Must be - * at least 12 characters (the settings and salt). - * - * @return - * A string containing the hashed password (and salt) or FALSE on failure. - * The return string will be truncated at DRUPAL_HASH_LENGTH characters max. - */ -function _password_crypt($algo, $password, $setting) { - // The first 12 characters of an existing hash are its setting string. - $setting = substr($setting, 0, 12); - - if ($setting[0] != '$' || $setting[2] != '$') { - return FALSE; - } - $count_log2 = _password_get_count_log2($setting); - // Hashes may be imported from elsewhere, so we allow != DRUPAL_HASH_COUNT - if ($count_log2 < DRUPAL_MIN_HASH_COUNT || $count_log2 > DRUPAL_MAX_HASH_COUNT) { - return FALSE; - } - $salt = substr($setting, 4, 8); - // Hashes must have an 8 character salt. - if (strlen($salt) != 8) { - return FALSE; - } - - // Convert the base 2 logarithm into an integer. - $count = 1 << $count_log2; - - // We rely on the hash() function being available in PHP 5.2+. - $hash = hash($algo, $salt . $password, TRUE); - do { - $hash = hash($algo, $hash . $password, TRUE); - } while (--$count); - - $len = strlen($hash); - $output = $setting . _password_base64_encode($hash, $len); - // _password_base64_encode() of a 16 byte MD5 will always be 22 characters. - // _password_base64_encode() of a 64 byte sha512 will always be 86 characters. - $expected = 12 + ceil((8 * $len) / 6); - return (strlen($output) == $expected) ? substr($output, 0, DRUPAL_HASH_LENGTH) : FALSE; -} - -/** - * Parse the log2 iteration count from a stored hash or setting string. - */ -function _password_get_count_log2($setting) { - $itoa64 = _password_itoa64(); - return strpos($itoa64, $setting[3]); -} - -/** - * Hash a password using a secure hash. - * - * @param $password - * A plain-text password. - * @param $count_log2 - * Optional integer to specify the iteration count. Generally used only during - * mass operations where a value less than the default is needed for speed. - * - * @return - * A string containing the hashed password (and a salt), or FALSE on failure. - */ -function user_hash_password($password, $count_log2 = 0) { - if (empty($count_log2)) { - // Use the standard iteration count. - $count_log2 = variable_get('password_count_log2', DRUPAL_HASH_COUNT); - } - return _password_crypt('sha512', $password, _password_generate_salt($count_log2)); -} - -/** - * Check whether a plain text password matches a stored hashed password. - * - * Alternative implementations of this function may use other data in the - * $account object, for example the uid to look up the hash in a custom table - * or remote database. - * - * @param $password - * A plain-text password - * @param $account - * A user object with at least the fields from the {users} table. - * - * @return - * TRUE or FALSE. - */ -function user_check_password($password, $account) { - if (substr($account->pass, 0, 2) == 'U$') { - // This may be an updated password from user_update_7000(). Such hashes - // have 'U' added as the first character and need an extra md5() (see the - // Drupal 7 documentation). - $stored_hash = substr($account->pass, 1); - $password = md5($password); - } - else { - $stored_hash = $account->pass; - } - - $type = substr($stored_hash, 0, 3); - switch ($type) { - case '$S$': - // A normal Drupal 7 password using sha512. - $hash = _password_crypt('sha512', $password, $stored_hash); - break; - case '$H$': - // phpBB3 uses "$H$" for the same thing as "$P$". - case '$P$': - // A phpass password generated using md5. This is an - // imported password or from an earlier Drupal version. - $hash = _password_crypt('md5', $password, $stored_hash); - break; - default: - return FALSE; - } - return ($hash && $stored_hash == $hash); -} - -/** - * Check whether a user's hashed password needs to be replaced with a new hash. - * - * This is typically called during the login process when the plain text - * password is available. A new hash is needed when the desired iteration count - * has changed through a change in the variable password_count_log2 or - * DRUPAL_HASH_COUNT or if the user's password hash was generated in an update - * like user_update_7000() (see the Drupal 7 documentation). - * - * Alternative implementations of this function might use other criteria based - * on the fields in $account. - * - * @param $account - * A user object with at least the fields from the {users} table. - * - * @return - * TRUE or FALSE. - */ -function user_needs_new_hash($account) { - // Check whether this was an updated password. - if ((substr($account->pass, 0, 3) != '$S$') || (strlen($account->pass) != DRUPAL_HASH_LENGTH)) { - return TRUE; - } - // Ensure that $count_log2 is within set bounds. - $count_log2 = _password_enforce_log2_boundaries(variable_get('password_count_log2', DRUPAL_HASH_COUNT)); - // Check whether the iteration count used differs from the standard number. - return (_password_get_count_log2($account->pass) !== $count_log2); -} - diff --git a/core/includes/path.inc b/core/includes/path.inc deleted file mode 100644 index 630b34c4ce0..00000000000 --- a/core/includes/path.inc +++ /dev/null @@ -1,581 +0,0 @@ -<?php - -/** - * @file - * Functions to handle paths in Drupal, including path aliasing. - * - * These functions are not loaded for cached pages, but modules that need - * to use them in hook_boot() or hook exit() can make them available, by - * executing "drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);". - */ - -/** - * Initialize the $_GET['q'] variable to the proper normal path. - */ -function drupal_path_initialize() { - if (!empty($_GET['q'])) { - $_GET['q'] = drupal_get_normal_path($_GET['q']); - } - else { - $_GET['q'] = drupal_get_normal_path(variable_get('site_frontpage', 'node')); - } -} - -/** - * Given an alias, return its Drupal system URL if one exists. Given a Drupal - * system URL return one of its aliases if such a one exists. Otherwise, - * return FALSE. - * - * @param $action - * One of the following values: - * - wipe: delete the alias cache. - * - alias: return an alias for a given Drupal system path (if one exists). - * - source: return the Drupal system URL for a path alias (if one exists). - * @param $path - * The path to investigate for corresponding aliases or system URLs. - * @param $path_language - * Optional language code to search the path with. Defaults to the page language. - * If there's no path defined for that language it will search paths without - * language. - * - * @return - * Either a Drupal system path, an aliased path, or FALSE if no path was - * found. - */ -function drupal_lookup_path($action, $path = '', $path_language = NULL) { - global $language_url; - // Use the advanced drupal_static() pattern, since this is called very often. - static $drupal_static_fast; - if (!isset($drupal_static_fast)) { - $drupal_static_fast['cache'] = &drupal_static(__FUNCTION__); - } - $cache = &$drupal_static_fast['cache']; - - if (!isset($cache)) { - $cache = array( - 'map' => array(), - 'no_source' => array(), - 'whitelist' => NULL, - 'system_paths' => array(), - 'no_aliases' => array(), - 'first_call' => TRUE, - ); - } - - // Retrieve the path alias whitelist. - if (!isset($cache['whitelist'])) { - $cache['whitelist'] = variable_get('path_alias_whitelist', NULL); - if (!isset($cache['whitelist'])) { - $cache['whitelist'] = drupal_path_alias_whitelist_rebuild(); - } - } - - // If no language is explicitly specified we default to the current URL - // language. If we used a language different from the one conveyed by the - // requested URL, we might end up being unable to check if there is a path - // alias matching the URL path. - $path_language = $path_language ? $path_language : $language_url->language; - - if ($action == 'wipe') { - $cache = array(); - $cache['whitelist'] = drupal_path_alias_whitelist_rebuild(); - } - elseif ($cache['whitelist'] && $path != '') { - if ($action == 'alias') { - // During the first call to drupal_lookup_path() per language, load the - // expected system paths for the page from cache. - if (!empty($cache['first_call'])) { - $cache['first_call'] = FALSE; - - $cache['map'][$path_language] = array(); - // Load system paths from cache. - $cid = current_path(); - if ($cached = cache('path')->get($cid)) { - $cache['system_paths'] = $cached->data; - // Now fetch the aliases corresponding to these system paths. - $args = array( - ':system' => $cache['system_paths'], - ':language' => $path_language, - ':language_none' => LANGUAGE_NONE, - ); - // Always get the language-specific alias before the language-neutral - // one. For example 'de' is less than 'und' so the order needs to be - // ASC, while 'xx-lolspeak' is more than 'und' so the order needs to - // be DESC. We also order by pid ASC so that fetchAllKeyed() returns - // the most recently created alias for each source. Subsequent queries - // using fetchField() must use pid DESC to have the same effect. - // For performance reasons, the query builder is not used here. - if ($path_language == LANGUAGE_NONE) { - // Prevent PDO from complaining about a token the query doesn't use. - unset($args[':language']); - $result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language = :language_none ORDER BY pid ASC', $args); - } - elseif ($path_language < LANGUAGE_NONE) { - $result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language IN (:language, :language_none) ORDER BY language ASC, pid ASC', $args); - } - else { - $result = db_query('SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language IN (:language, :language_none) ORDER BY language DESC, pid ASC', $args); - } - $cache['map'][$path_language] = $result->fetchAllKeyed(); - // Keep a record of paths with no alias to avoid querying twice. - $cache['no_aliases'][$path_language] = array_flip(array_diff_key($cache['system_paths'], array_keys($cache['map'][$path_language]))); - } - } - // If the alias has already been loaded, return it. - if (isset($cache['map'][$path_language][$path])) { - return $cache['map'][$path_language][$path]; - } - // Check the path whitelist, if the top_level part before the first / - // is not in the list, then there is no need to do anything further, - // it is not in the database. - elseif (!isset($cache['whitelist'][strtok($path, '/')])) { - return FALSE; - } - // For system paths which were not cached, query aliases individually. - elseif (!isset($cache['no_aliases'][$path_language][$path])) { - $args = array( - ':source' => $path, - ':language' => $path_language, - ':language_none' => LANGUAGE_NONE, - ); - // See the queries above. - if ($path_language == LANGUAGE_NONE) { - unset($args[':language']); - $alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND language = :language_none ORDER BY pid DESC", $args)->fetchField(); - } - elseif ($path_language > LANGUAGE_NONE) { - $alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", $args)->fetchField(); - } - else { - $alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND language IN (:language, :language_none) ORDER BY language ASC, pid DESC", $args)->fetchField(); - } - $cache['map'][$path_language][$path] = $alias; - return $alias; - } - } - // Check $no_source for this $path in case we've already determined that there - // isn't a path that has this alias - elseif ($action == 'source' && !isset($cache['no_source'][$path_language][$path])) { - // Look for the value $path within the cached $map - $source = FALSE; - if (!isset($cache['map'][$path_language]) || !($source = array_search($path, $cache['map'][$path_language]))) { - $args = array( - ':alias' => $path, - ':language' => $path_language, - ':language_none' => LANGUAGE_NONE, - ); - // See the queries above. - if ($path_language == LANGUAGE_NONE) { - unset($args[':language']); - $result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language = :language_none ORDER BY pid DESC", $args); - } - elseif ($path_language > LANGUAGE_NONE) { - $result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", $args); - } - else { - $result = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language IN (:language, :language_none) ORDER BY language ASC, pid DESC", $args); - } - if ($source = $result->fetchField()) { - $cache['map'][$path_language][$source] = $path; - } - else { - // We can't record anything into $map because we do not have a valid - // index and there is no need because we have not learned anything - // about any Drupal path. Thus cache to $no_source. - $cache['no_source'][$path_language][$path] = TRUE; - } - } - return $source; - } - } - - return FALSE; -} - -/** - * Cache system paths for a page. - * - * Cache an array of the system paths available on each page. We assume - * that aliases will be needed for the majority of these paths during - * subsequent requests, and load them in a single query during - * drupal_lookup_path(). - */ -function drupal_cache_system_paths() { - // Check if the system paths for this page were loaded from cache in this - // request to avoid writing to cache on every request. - $cache = &drupal_static('drupal_lookup_path', array()); - if (empty($cache['system_paths']) && !empty($cache['map'])) { - // Generate a cache ID (cid) specifically for this page. - $cid = current_path(); - // The static $map array used by drupal_lookup_path() includes all - // system paths for the page request. - if ($paths = current($cache['map'])) { - $data = array_keys($paths); - $expire = REQUEST_TIME + (60 * 60 * 24); - cache('path')->set($cid, $data, $expire); - } - } -} - -/** - * Given an internal Drupal path, return the alias set by the administrator. - * - * If no path is provided, the function will return the alias of the current - * page. - * - * @param $path - * An internal Drupal path. - * @param $path_language - * An optional language code to look up the path in. - * - * @return - * An aliased path if one was found, or the original path if no alias was - * found. - */ -function drupal_get_path_alias($path = NULL, $path_language = NULL) { - // If no path is specified, use the current page's path. - if ($path == NULL) { - $path = $_GET['q']; - } - $result = $path; - if ($alias = drupal_lookup_path('alias', $path, $path_language)) { - $result = $alias; - } - return $result; -} - -/** - * Given a path alias, return the internal path it represents. - * - * @param $path - * A Drupal path alias. - * @param $path_language - * An optional language code to look up the path in. - * - * @return - * The internal path represented by the alias, or the original alias if no - * internal path was found. - */ -function drupal_get_normal_path($path, $path_language = NULL) { - $original_path = $path; - - // Lookup the path alias first. - if ($source = drupal_lookup_path('source', $path, $path_language)) { - $path = $source; - } - - // Allow other modules to alter the inbound URL. We cannot use drupal_alter() - // here because we need to run hook_url_inbound_alter() in the reverse order - // of hook_url_outbound_alter(). - foreach (array_reverse(module_implements('url_inbound_alter')) as $module) { - $function = $module . '_url_inbound_alter'; - $function($path, $original_path, $path_language); - } - - return $path; -} - -/** - * Check if the current page is the front page. - * - * @return - * Boolean value: TRUE if the current page is the front page; FALSE if otherwise. - */ -function drupal_is_front_page() { - // Use the advanced drupal_static() pattern, since this is called very often. - static $drupal_static_fast; - if (!isset($drupal_static_fast)) { - $drupal_static_fast['is_front_page'] = &drupal_static(__FUNCTION__); - } - $is_front_page = &$drupal_static_fast['is_front_page']; - - if (!isset($is_front_page)) { - // As drupal_path_initialize updates $_GET['q'] with the 'site_frontpage' path, - // we can check it against the 'site_frontpage' variable. - $is_front_page = ($_GET['q'] == variable_get('site_frontpage', 'node')); - } - - return $is_front_page; -} - -/** - * Check if a path matches any pattern in a set of patterns. - * - * @param $path - * The path to match. - * @param $patterns - * String containing a set of patterns separated by \n, \r or \r\n. - * - * @return - * Boolean value: TRUE if the path matches a pattern, FALSE otherwise. - */ -function drupal_match_path($path, $patterns) { - $regexps = &drupal_static(__FUNCTION__); - - if (!isset($regexps[$patterns])) { - // Convert path settings to a regular expression. - // Therefore replace newlines with a logical or, /* with asterisks and the <front> with the frontpage. - $to_replace = array( - '/(\r\n?|\n)/', // newlines - '/\\\\\*/', // asterisks - '/(^|\|)\\\\<front\\\\>($|\|)/' // <front> - ); - $replacements = array( - '|', - '.*', - '\1' . preg_quote(variable_get('site_frontpage', 'node'), '/') . '\2' - ); - $patterns_quoted = preg_quote($patterns, '/'); - $regexps[$patterns] = '/^(' . preg_replace($to_replace, $replacements, $patterns_quoted) . ')$/'; - } - return (bool)preg_match($regexps[$patterns], $path); -} - -/** - * Return the current URL path of the page being viewed. - * - * Examples: - * - http://example.com/node/306 returns "node/306". - * - http://example.com/drupalfolder/node/306 returns "node/306" while - * base_path() returns "/drupalfolder/". - * - http://example.com/path/alias (which is a path alias for node/306) returns - * "node/306" as opposed to the path alias. - * - * This function is not available in hook_boot() so use $_GET['q'] instead. - * However, be careful when doing that because in the case of Example #3 - * $_GET['q'] will contain "path/alias". If "node/306" is needed, calling - * drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL) makes this function available. - * - * @return - * The current Drupal URL path. - * - * @see request_path() - */ -function current_path() { - return $_GET['q']; -} - -/** - * Rebuild the path alias white list. - * - * @param $source - * An optional system path for which an alias is being inserted. - * - * @return - * An array containing a white list of path aliases. - */ -function drupal_path_alias_whitelist_rebuild($source = NULL) { - // When paths are inserted, only rebuild the whitelist if the system path - // has a top level component which is not already in the whitelist. - if (!empty($source)) { - $whitelist = variable_get('path_alias_whitelist', NULL); - if (isset($whitelist[strtok($source, '/')])) { - return $whitelist; - } - } - // For each alias in the database, get the top level component of the system - // path it corresponds to. This is the portion of the path before the first - // '/', if present, otherwise the whole path itself. - $whitelist = array(); - $result = db_query("SELECT DISTINCT SUBSTRING_INDEX(source, '/', 1) AS path FROM {url_alias}"); - foreach ($result as $row) { - $whitelist[$row->path] = TRUE; - } - variable_set('path_alias_whitelist', $whitelist); - return $whitelist; -} - -/** - * Fetch a specific URL alias from the database. - * - * @param $conditions - * A string representing the source, a number representing the pid, or an - * array of query conditions. - * - * @return - * FALSE if no alias was found or an associative array containing the - * following keys: - * - source: The internal system path. - * - alias: The URL alias. - * - pid: Unique path alias identifier. - * - language: The language of the alias. - */ -function path_load($conditions) { - if (is_numeric($conditions)) { - $conditions = array('pid' => $conditions); - } - elseif (is_string($conditions)) { - $conditions = array('source' => $conditions); - } - elseif (!is_array($conditions)) { - return FALSE; - } - $select = db_select('url_alias'); - foreach ($conditions as $field => $value) { - $select->condition($field, $value); - } - return $select - ->fields('url_alias') - ->execute() - ->fetchAssoc(); -} - -/** - * Save a path alias to the database. - * - * @param $path - * An associative array containing the following keys: - * - source: The internal system path. - * - alias: The URL alias. - * - pid: (optional) Unique path alias identifier. - * - language: (optional) The language of the alias. - */ -function path_save(&$path) { - $path += array('pid' => NULL, 'language' => LANGUAGE_NONE); - - // Insert or update the alias. - $status = drupal_write_record('url_alias', $path, (!empty($path['pid']) ? 'pid' : array())); - - // Verify that a record was written. - if ($status) { - if ($status === SAVED_NEW) { - module_invoke_all('path_insert', $path); - } - else { - module_invoke_all('path_update', $path); - } - drupal_clear_path_cache($path['source']); - } -} - -/** - * Delete a URL alias. - * - * @param $criteria - * A number representing the pid or an array of criteria. - */ -function path_delete($criteria) { - if (!is_array($criteria)) { - $criteria = array('pid' => $criteria); - } - $path = path_load($criteria); - $query = db_delete('url_alias'); - foreach ($criteria as $field => $value) { - $query->condition($field, $value); - } - $query->execute(); - module_invoke_all('path_delete', $path); - drupal_clear_path_cache($path['source']); -} - -/** - * Determine whether a path is in the administrative section of the site. - * - * By default, paths are considered to be non-administrative. If a path does not - * match any of the patterns in path_get_admin_paths(), or if it matches both - * administrative and non-administrative patterns, it is considered - * non-administrative. - * - * @param $path - * A Drupal path. - * - * @return - * TRUE if the path is administrative, FALSE otherwise. - * - * @see path_get_admin_paths() - * @see hook_admin_paths() - * @see hook_admin_paths_alter() - */ -function path_is_admin($path) { - $path_map = &drupal_static(__FUNCTION__); - if (!isset($path_map['admin'][$path])) { - $patterns = path_get_admin_paths(); - $path_map['admin'][$path] = drupal_match_path($path, $patterns['admin']); - $path_map['non_admin'][$path] = drupal_match_path($path, $patterns['non_admin']); - } - return $path_map['admin'][$path] && !$path_map['non_admin'][$path]; -} - -/** - * Get a list of administrative and non-administrative paths. - * - * @return array - * An associative array containing the following keys: - * 'admin': An array of administrative paths and regular expressions - * in a format suitable for drupal_match_path(). - * 'non_admin': An array of non-administrative paths and regular expressions. - * - * @see hook_admin_paths() - * @see hook_admin_paths_alter() - */ -function path_get_admin_paths() { - $patterns = &drupal_static(__FUNCTION__); - if (!isset($patterns)) { - $paths = module_invoke_all('admin_paths'); - drupal_alter('admin_paths', $paths); - // Combine all admin paths into one array, and likewise for non-admin paths, - // for easier handling. - $patterns = array(); - $patterns['admin'] = array(); - $patterns['non_admin'] = array(); - foreach ($paths as $path => $enabled) { - if ($enabled) { - $patterns['admin'][] = $path; - } - else { - $patterns['non_admin'][] = $path; - } - } - $patterns['admin'] = implode("\n", $patterns['admin']); - $patterns['non_admin'] = implode("\n", $patterns['non_admin']); - } - return $patterns; -} - -/** - * Checks a path exists and the current user has access to it. - * - * @param $path - * The path to check. - * @param $dynamic_allowed - * Whether paths with menu wildcards (like user/%) should be allowed. - * - * @return - * TRUE if it is a valid path AND the current user has access permission, - * FALSE otherwise. - */ -function drupal_valid_path($path, $dynamic_allowed = FALSE) { - global $menu_admin; - // We indicate that a menu administrator is running the menu access check. - $menu_admin = TRUE; - if ($path == '<front>' || url_is_external($path)) { - $item = array('access' => TRUE); - } - elseif ($dynamic_allowed && preg_match('/\/\%/', $path)) { - // Path is dynamic (ie 'user/%'), so check directly against menu_router table. - if ($item = db_query("SELECT * FROM {menu_router} where path = :path", array(':path' => $path))->fetchAssoc()) { - $item['link_path'] = $form_item['link_path']; - $item['link_title'] = $form_item['link_title']; - $item['external'] = FALSE; - $item['options'] = ''; - _menu_link_translate($item); - } - } - else { - $item = menu_get_item($path); - } - $menu_admin = FALSE; - return $item && $item['access']; -} - -/** - * Clear the path cache. - * - * @param $source - * An optional system path for which an alias is being changed. - */ -function drupal_clear_path_cache($source = NULL) { - // Clear the drupal_lookup_path() static cache. - drupal_static_reset('drupal_lookup_path'); - drupal_path_alias_whitelist_rebuild($source); -} diff --git a/core/includes/registry.inc b/core/includes/registry.inc deleted file mode 100644 index 8961f7a18b6..00000000000 --- a/core/includes/registry.inc +++ /dev/null @@ -1,186 +0,0 @@ -<?php - -/** - * @file - * This file contains the code registry parser engine. - */ - -/** - * @defgroup registry Code registry - * @{ - * The code registry engine. - * - * Drupal maintains an internal registry of all functions or classes in the - * system, allowing it to lazy-load code files as needed (reducing the amount - * of code that must be parsed on each request). - */ - -/** - * Does the work for registry_update(). - */ -function _registry_update() { - - // The registry serves as a central autoloader for all classes, including - // the database query builders. However, the registry rebuild process - // requires write ability to the database, which means having access to the - // query builders that require the registry in order to be loaded. That - // causes a fatal race condition. Therefore we manually include the - // appropriate query builders for the currently active database before the - // registry rebuild process runs. - $connection_info = Database::getConnectionInfo(); - $driver = $connection_info['default']['driver']; - require_once DRUPAL_ROOT . '/core/includes/database/query.inc'; - require_once DRUPAL_ROOT . '/core/includes/database/select.inc'; - require_once DRUPAL_ROOT . '/core/includes/database/' . $driver . '/query.inc'; - - // Get current list of modules and their files. - $modules = db_query("SELECT * FROM {system} WHERE type = 'module'")->fetchAll(); - // Get the list of files we are going to parse. - $files = array(); - foreach ($modules as &$module) { - $module->info = unserialize($module->info); - $dir = dirname($module->filename); - - // Store the module directory for use in hook_registry_files_alter(). - $module->dir = $dir; - - if ($module->status) { - // Add files for enabled modules to the registry. - foreach ($module->info['files'] as $file) { - $files["$dir/$file"] = array('module' => $module->name, 'weight' => $module->weight); - } - } - } - foreach (file_scan_directory('core/includes', '/\.inc$/') as $filename => $file) { - $files["$filename"] = array('module' => '', 'weight' => 0); - } - - $transaction = db_transaction(); - try { - // Allow modules to manually modify the list of files before the registry - // parses them. The $modules array provides the .info file information, which - // includes the list of files registered to each module. Any files in the - // list can then be added to the list of files that the registry will parse, - // or modify attributes of a file. - drupal_alter('registry_files', $files, $modules); - foreach (registry_get_parsed_files() as $filename => $file) { - // Add the hash for those files we have already parsed. - if (isset($files[$filename])) { - $files[$filename]['hash'] = $file['hash']; - } - else { - // Flush the registry of resources in files that are no longer on disc - // or are in files that no installed modules require to be parsed. - db_delete('registry') - ->condition('filename', $filename) - ->execute(); - db_delete('registry_file') - ->condition('filename', $filename) - ->execute(); - } - } - $parsed_files = _registry_parse_files($files); - - $unchanged_resources = array(); - $lookup_cache = array(); - if ($cache = cache('bootstrap')->get('lookup_cache')) { - $lookup_cache = $cache->data; - } - foreach ($lookup_cache as $key => $file) { - // If the file for this cached resource is carried over unchanged from - // the last registry build, then we can safely re-cache it. - if ($file && in_array($file, array_keys($files)) && !in_array($file, $parsed_files)) { - $unchanged_resources[$key] = $file; - } - } - module_implements_reset(); - _registry_check_code(REGISTRY_RESET_LOOKUP_CACHE); - } - catch (Exception $e) { - $transaction->rollback(); - watchdog_exception('registry', $e); - throw $e; - } - - // We have some unchanged resources, warm up the cache - no need to pay - // for looking them up again. - if (count($unchanged_resources) > 0) { - cache('bootstrap')->set('lookup_cache', $unchanged_resources); - } -} - -/** - * Return the list of files in registry_file - */ -function registry_get_parsed_files() { - $files = array(); - // We want the result as a keyed array. - $files = db_query("SELECT * FROM {registry_file}")->fetchAllAssoc('filename', PDO::FETCH_ASSOC); - return $files; -} - -/** - * Parse all files that have changed since the registry was last built, and save their function and class listings. - * - * @param $files - * The list of files to check and parse. - */ -function _registry_parse_files($files) { - $parsed_files = array(); - foreach ($files as $filename => $file) { - if (file_exists($filename)) { - $hash = hash_file('sha256', $filename); - if (empty($file['hash']) || $file['hash'] != $hash) { - // Delete registry entries for this file, so we can insert the new resources. - db_delete('registry') - ->condition('filename', $filename) - ->execute(); - $file['hash'] = $hash; - $parsed_files[$filename] = $file; - } - } - } - foreach ($parsed_files as $filename => $file) { - _registry_parse_file($filename, file_get_contents($filename), $file['module'], $file['weight']); - db_merge('registry_file') - ->key(array('filename' => $filename)) - ->fields(array( - 'hash' => $file['hash'], - )) - ->execute(); - } - return array_keys($parsed_files); -} - -/** - * Parse a file and save its function and class listings. - * - * @param $filename - * Name of the file we are going to parse. - * @param $contents - * Contents of the file we are going to parse as a string. - * @param $module - * (optional) Name of the module this file belongs to. - * @param $weight - * (optional) Weight of the module. - */ -function _registry_parse_file($filename, $contents, $module = '', $weight = 0) { - if (preg_match_all('/^\s*(?:abstract|final)?\s*(class|interface)\s+([a-zA-Z0-9_]+)/m', $contents, $matches)) { - $query = db_insert('registry')->fields(array('name', 'type', 'filename', 'module', 'weight')); - foreach ($matches[2] as $key => $name) { - $query->values(array( - 'name' => $name, - 'type' => $matches[1][$key], - 'filename' => $filename, - 'module' => $module, - 'weight' => $weight, - )); - } - $query->execute(); - } -} - -/** - * @} End of "defgroup registry". - */ - diff --git a/core/includes/session.inc b/core/includes/session.inc deleted file mode 100644 index df70f0e1ac2..00000000000 --- a/core/includes/session.inc +++ /dev/null @@ -1,493 +0,0 @@ -<?php - -/** - * @file - * User session handling functions. - * - * The user-level session storage handlers: - * - _drupal_session_open() - * - _drupal_session_close() - * - _drupal_session_read() - * - _drupal_session_write() - * - _drupal_session_destroy() - * - _drupal_session_garbage_collection() - * are assigned by session_set_save_handler() in bootstrap.inc and are called - * automatically by PHP. These functions should not be called directly. Session - * data should instead be accessed via the $_SESSION superglobal. - */ - -/** - * Session handler assigned by session_set_save_handler(). - * - * This function is used to handle any initialization, such as file paths or - * database connections, that is needed before accessing session data. Drupal - * does not need to initialize anything in this function. - * - * This function should not be called directly. - * - * @return - * This function will always return TRUE. - */ -function _drupal_session_open() { - return TRUE; -} - -/** - * Session handler assigned by session_set_save_handler(). - * - * This function is used to close the current session. Because Drupal stores - * session data in the database immediately on write, this function does - * not need to do anything. - * - * This function should not be called directly. - * - * @return - * This function will always return TRUE. - */ -function _drupal_session_close() { - return TRUE; -} - -/** - * Reads an entire session from the database (internal use only). - * - * Also initializes the $user object for the user associated with the session. - * This function is registered with session_set_save_handler() to support - * database-backed sessions. It is called on every page load when PHP sets - * up the $_SESSION superglobal. - * - * This function is an internal function and must not be called directly. - * Doing so may result in logging out the current user, corrupting session data - * or other unexpected behavior. Session data must always be accessed via the - * $_SESSION superglobal. - * - * @param $sid - * The session ID of the session to retrieve. - * - * @return - * The user's session, or an empty string if no session exists. - */ -function _drupal_session_read($sid) { - global $user, $is_https; - - // Write and Close handlers are called after destructing objects - // since PHP 5.0.5. - // Thus destructors can use sessions but session handler can't use objects. - // So we are moving session closure before destructing objects. - drupal_register_shutdown_function('session_write_close'); - - // Handle the case of first time visitors and clients that don't store - // cookies (eg. web crawlers). - $insecure_session_name = substr(session_name(), 1); - if (!isset($_COOKIE[session_name()]) && !isset($_COOKIE[$insecure_session_name])) { - $user = drupal_anonymous_user(); - return ''; - } - - // Otherwise, if the session is still active, we have a record of the - // client's session in the database. If it's HTTPS then we are either have - // a HTTPS session or we are about to log in so we check the sessions table - // for an anonymous session with the non-HTTPS-only cookie. - if ($is_https) { - $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.ssid = :ssid", array(':ssid' => $sid))->fetchObject(); - if (!$user) { - if (isset($_COOKIE[$insecure_session_name])) { - $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid AND s.uid = 0", array( - ':sid' => $_COOKIE[$insecure_session_name])) - ->fetchObject(); - } - } - } - else { - $user = db_query("SELECT u.*, s.* FROM {users} u INNER JOIN {sessions} s ON u.uid = s.uid WHERE s.sid = :sid", array(':sid' => $sid))->fetchObject(); - } - - // We found the client's session record and they are an authenticated, - // active user. - if ($user && $user->uid > 0 && $user->status == 1) { - // This is done to unserialize the data member of $user. - $user->data = unserialize($user->data); - - // Add roles element to $user. - $user->roles = array(); - $user->roles[DRUPAL_AUTHENTICATED_RID] = 'authenticated user'; - $user->roles += db_query("SELECT r.rid, r.name FROM {role} r INNER JOIN {users_roles} ur ON ur.rid = r.rid WHERE ur.uid = :uid", array(':uid' => $user->uid))->fetchAllKeyed(0, 1); - } - elseif ($user) { - // The user is anonymous or blocked. Only preserve two fields from the - // {sessions} table. - $account = drupal_anonymous_user(); - $account->session = $user->session; - $account->timestamp = $user->timestamp; - $user = $account; - } - else { - // The session has expired. - $user = drupal_anonymous_user(); - $user->session = ''; - } - - // Store the session that was read for comparison in _drupal_session_write(). - $last_read = &drupal_static('drupal_session_last_read'); - $last_read = array( - 'sid' => $sid, - 'value' => $user->session, - ); - - return $user->session; -} - -/** - * Writes an entire session to the database (internal use only). - * - * This function is registered with session_set_save_handler() to support - * database-backed sessions. - * - * This function is an internal function and must not be called directly. - * Doing so may result in corrupted session data or other unexpected behavior. - * Session data must always be accessed via the $_SESSION superglobal. - * - * @param $sid - * The session ID of the session to write to. - * @param $value - * Session data to write as a serialized string. - * - * @return - * Always returns TRUE. - */ -function _drupal_session_write($sid, $value) { - global $user, $is_https; - - // The exception handler is not active at this point, so we need to do it - // manually. - try { - if (!drupal_save_session()) { - // We don't have anything to do if we are not allowed to save the session. - return; - } - - // Check whether $_SESSION has been changed in this request. - $last_read = &drupal_static('drupal_session_last_read'); - $is_changed = !isset($last_read) || $last_read['sid'] != $sid || $last_read['value'] !== $value; - - // For performance reasons, do not update the sessions table, unless - // $_SESSION has changed or more than 180 has passed since the last update. - if ($is_changed || REQUEST_TIME - $user->timestamp > variable_get('session_write_interval', 180)) { - // Either ssid or sid or both will be added from $key below. - $fields = array( - 'uid' => $user->uid, - 'cache' => isset($user->cache) ? $user->cache : 0, - 'hostname' => ip_address(), - 'session' => $value, - 'timestamp' => REQUEST_TIME, - ); - - // Use the session ID as 'sid' and an empty string as 'ssid' by default. - // _drupal_session_read() does not allow empty strings so that's a safe - // default. - $key = array('sid' => $sid, 'ssid' => ''); - // On HTTPS connections, use the session ID as both 'sid' and 'ssid'. - if ($is_https) { - $key['ssid'] = $sid; - // The "secure pages" setting allows a site to simultaneously use both - // secure and insecure session cookies. If enabled and both cookies are - // presented then use both keys. - if (variable_get('https', FALSE)) { - $insecure_session_name = substr(session_name(), 1); - if (isset($_COOKIE[$insecure_session_name])) { - $key['sid'] = $_COOKIE[$insecure_session_name]; - } - } - } - - db_merge('sessions') - ->key($key) - ->fields($fields) - ->execute(); - } - - // Likewise, do not update access time more than once per 180 seconds. - if ($user->uid && REQUEST_TIME - $user->access > variable_get('session_write_interval', 180)) { - db_update('users') - ->fields(array( - 'access' => REQUEST_TIME - )) - ->condition('uid', $user->uid) - ->execute(); - } - - return TRUE; - } - catch (Exception $exception) { - require_once DRUPAL_ROOT . '/core/includes/errors.inc'; - // If we are displaying errors, then do so with no possibility of a further - // uncaught exception being thrown. - if (error_displayable()) { - print '<h1>Uncaught exception thrown in session handler.</h1>'; - print '<p>' . _drupal_render_exception_safe($exception) . '</p><hr />'; - } - return FALSE; - } -} - -/** - * Initializes the session handler, starting a session if needed. - */ -function drupal_session_initialize() { - global $user, $is_https; - - session_set_save_handler('_drupal_session_open', '_drupal_session_close', '_drupal_session_read', '_drupal_session_write', '_drupal_session_destroy', '_drupal_session_garbage_collection'); - - // We use !empty() in the following check to ensure that blank session IDs - // are not valid. - if (!empty($_COOKIE[session_name()]) || ($is_https && variable_get('https', FALSE) && !empty($_COOKIE[substr(session_name(), 1)]))) { - // If a session cookie exists, initialize the session. Otherwise the - // session is only started on demand in drupal_session_commit(), making - // anonymous users not use a session cookie unless something is stored in - // $_SESSION. This allows HTTP proxies to cache anonymous pageviews. - drupal_session_start(); - if (!empty($user->uid) || !empty($_SESSION)) { - drupal_page_is_cacheable(FALSE); - } - } - else { - // Set a session identifier for this request. This is necessary because - // we lazily start sessions at the end of this request, and some - // processes (like drupal_get_token()) needs to know the future - // session ID in advance. - $user = drupal_anonymous_user(); - // Less random sessions (which are much faster to generate) are used for - // anonymous users than are generated in drupal_session_regenerate() when - // a user becomes authenticated. - session_id(drupal_hash_base64(uniqid(mt_rand(), TRUE))); - } - date_default_timezone_set(drupal_get_user_timezone()); -} - -/** - * Forcefully starts a session, preserving already set session data. - * - * @ingroup php_wrappers - */ -function drupal_session_start() { - // Command line clients do not support cookies nor sessions. - if (!drupal_session_started() && !drupal_is_cli()) { - // Save current session data before starting it, as PHP will destroy it. - $session_data = isset($_SESSION) ? $_SESSION : NULL; - - session_start(); - drupal_session_started(TRUE); - - // Restore session data. - if (!empty($session_data)) { - $_SESSION += $session_data; - } - } -} - -/** - * Commits the current session, if necessary. - * - * If an anonymous user already have an empty session, destroy it. - */ -function drupal_session_commit() { - global $user; - - if (!drupal_save_session()) { - // We don't have anything to do if we are not allowed to save the session. - return; - } - - if (empty($user->uid) && empty($_SESSION)) { - // There is no session data to store, destroy the session if it was - // previously started. - if (drupal_session_started()) { - session_destroy(); - } - } - else { - // There is session data to store. Start the session if it is not already - // started. - if (!drupal_session_started()) { - drupal_session_start(); - } - // Write the session data. - session_write_close(); - } -} - -/** - * Returns whether a session has been started. - */ -function drupal_session_started($set = NULL) { - static $session_started = FALSE; - if (isset($set)) { - $session_started = $set; - } - return $session_started && session_id(); -} - -/** - * Called when an anonymous user becomes authenticated or vice-versa. - * - * @ingroup php_wrappers - */ -function drupal_session_regenerate() { - global $user, $is_https; - if ($is_https && variable_get('https', FALSE)) { - $insecure_session_name = substr(session_name(), 1); - if (isset($_COOKIE[$insecure_session_name])) { - $old_insecure_session_id = $_COOKIE[$insecure_session_name]; - } - $params = session_get_cookie_params(); - $session_id = drupal_hash_base64(uniqid(mt_rand(), TRUE) . drupal_random_bytes(55)); - // If a session cookie lifetime is set, the session will expire - // $params['lifetime'] seconds from the current request. If it is not set, - // it will expire when the browser is closed. - $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0; - setcookie($insecure_session_name, $session_id, $expire, $params['path'], $params['domain'], FALSE, $params['httponly']); - $_COOKIE[$insecure_session_name] = $session_id; - } - - if (drupal_session_started()) { - $old_session_id = session_id(); - } - session_id(drupal_hash_base64(uniqid(mt_rand(), TRUE) . drupal_random_bytes(55))); - - if (isset($old_session_id)) { - $params = session_get_cookie_params(); - $expire = $params['lifetime'] ? REQUEST_TIME + $params['lifetime'] : 0; - setcookie(session_name(), session_id(), $expire, $params['path'], $params['domain'], $params['secure'], $params['httponly']); - $fields = array('sid' => session_id()); - if ($is_https) { - $fields['ssid'] = session_id(); - // If the "secure pages" setting is enabled, use the newly-created - // insecure session identifier as the regenerated sid. - if (variable_get('https', FALSE)) { - $fields['sid'] = $session_id; - } - } - db_update('sessions') - ->fields($fields) - ->condition($is_https ? 'ssid' : 'sid', $old_session_id) - ->execute(); - } - elseif (isset($old_insecure_session_id)) { - // If logging in to the secure site, and there was no active session on the - // secure site but a session was active on the insecure site, update the - // insecure session with the new session identifiers. - db_update('sessions') - ->fields(array('sid' => $session_id, 'ssid' => session_id())) - ->condition('sid', $old_insecure_session_id) - ->execute(); - } - else { - // Start the session when it doesn't exist yet. - // Preserve the logged in user, as it will be reset to anonymous - // by _drupal_session_read. - $account = $user; - drupal_session_start(); - $user = $account; - } - date_default_timezone_set(drupal_get_user_timezone()); -} - -/** - * Session handler assigned by session_set_save_handler(). - * - * Cleans up a specific session. - * - * @param $sid - * Session ID. - */ -function _drupal_session_destroy($sid) { - global $user, $is_https; - - // Delete session data. - db_delete('sessions') - ->condition($is_https ? 'ssid' : 'sid', $sid) - ->execute(); - - // Reset $_SESSION and $user to prevent a new session from being started - // in drupal_session_commit(). - $_SESSION = array(); - $user = drupal_anonymous_user(); - - // Unset the session cookies. - _drupal_session_delete_cookie(session_name()); - if ($is_https) { - _drupal_session_delete_cookie(substr(session_name(), 1), TRUE); - } -} - -/** - * Deletes the session cookie. - * - * @param $name - * Name of session cookie to delete. - * @param $force_insecure - * Force cookie to be insecure. - */ -function _drupal_session_delete_cookie($name, $force_insecure = FALSE) { - if (isset($_COOKIE[$name])) { - $params = session_get_cookie_params(); - setcookie($name, '', REQUEST_TIME - 3600, $params['path'], $params['domain'], !$force_insecure && $params['secure'], $params['httponly']); - unset($_COOKIE[$name]); - } -} - -/** - * Ends a specific user's session(s). - * - * @param $uid - * User ID. - */ -function drupal_session_destroy_uid($uid) { - db_delete('sessions') - ->condition('uid', $uid) - ->execute(); -} - -/** - * Session handler assigned by session_set_save_handler(). - * - * Cleans up stalled sessions. - * - * @param $lifetime - * The value of session.gc_maxlifetime, passed by PHP. - * Sessions not updated for more than $lifetime seconds will be removed. - */ -function _drupal_session_garbage_collection($lifetime) { - // Be sure to adjust 'php_value session.gc_maxlifetime' to a large enough - // value. For example, if you want user sessions to stay in your database - // for three weeks before deleting them, you need to set gc_maxlifetime - // to '1814400'. At that value, only after a user doesn't log in after - // three weeks (1814400 seconds) will his/her session be removed. - db_delete('sessions') - ->condition('timestamp', REQUEST_TIME - $lifetime, '<') - ->execute(); - return TRUE; -} - -/** - * Determines whether to save session data of the current request. - * - * This function allows the caller to temporarily disable writing of - * session data, should the request end while performing potentially - * dangerous operations, such as manipulating the global $user object. - * See http://drupal.org/node/218104 for usage. - * - * @param $status - * Disables writing of session data when FALSE, (re-)enables - * writing when TRUE. - * - * @return - * FALSE if writing session data has been disabled. Otherwise, TRUE. - */ -function drupal_save_session($status = NULL) { - $save_session = &drupal_static(__FUNCTION__, TRUE); - if (isset($status)) { - $save_session = $status; - } - return $save_session; -} diff --git a/core/includes/standard.inc b/core/includes/standard.inc deleted file mode 100644 index 38b5d153740..00000000000 --- a/core/includes/standard.inc +++ /dev/null @@ -1,401 +0,0 @@ -<?php - -/** - * @file - * Provides a list of countries and languages based on web standards. - */ - -/** - * Get an array of all country code => country name pairs. - * - * Get an array of all country code => country name pairs as laid out - * in ISO 3166-1 alpha-2. Originally from the location project - * (http://drupal.org/project/location). - * - * @return - * An array of country code => country name pairs. - */ -function standard_country_list() { - static $countries; - - if (isset($countries)) { - return $countries; - } - $t = get_t(); - - $countries = array( - 'AD' => $t('Andorra'), - 'AE' => $t('United Arab Emirates'), - 'AF' => $t('Afghanistan'), - 'AG' => $t('Antigua and Barbuda'), - 'AI' => $t('Anguilla'), - 'AL' => $t('Albania'), - 'AM' => $t('Armenia'), - 'AN' => $t('Netherlands Antilles'), - 'AO' => $t('Angola'), - 'AQ' => $t('Antarctica'), - 'AR' => $t('Argentina'), - 'AS' => $t('American Samoa'), - 'AT' => $t('Austria'), - 'AU' => $t('Australia'), - 'AW' => $t('Aruba'), - 'AX' => $t('Aland Islands'), - 'AZ' => $t('Azerbaijan'), - 'BA' => $t('Bosnia and Herzegovina'), - 'BB' => $t('Barbados'), - 'BD' => $t('Bangladesh'), - 'BE' => $t('Belgium'), - 'BF' => $t('Burkina Faso'), - 'BG' => $t('Bulgaria'), - 'BH' => $t('Bahrain'), - 'BI' => $t('Burundi'), - 'BJ' => $t('Benin'), - 'BL' => $t('Saint Barthélemy'), - 'BM' => $t('Bermuda'), - 'BN' => $t('Brunei'), - 'BO' => $t('Bolivia'), - 'BR' => $t('Brazil'), - 'BS' => $t('Bahamas'), - 'BT' => $t('Bhutan'), - 'BV' => $t('Bouvet Island'), - 'BW' => $t('Botswana'), - 'BY' => $t('Belarus'), - 'BZ' => $t('Belize'), - 'CA' => $t('Canada'), - 'CC' => $t('Cocos (Keeling) Islands'), - 'CD' => $t('Congo (Kinshasa)'), - 'CF' => $t('Central African Republic'), - 'CG' => $t('Congo (Brazzaville)'), - 'CH' => $t('Switzerland'), - 'CI' => $t('Ivory Coast'), - 'CK' => $t('Cook Islands'), - 'CL' => $t('Chile'), - 'CM' => $t('Cameroon'), - 'CN' => $t('China'), - 'CO' => $t('Colombia'), - 'CR' => $t('Costa Rica'), - 'CU' => $t('Cuba'), - 'CV' => $t('Cape Verde'), - 'CX' => $t('Christmas Island'), - 'CY' => $t('Cyprus'), - 'CZ' => $t('Czech Republic'), - 'DE' => $t('Germany'), - 'DJ' => $t('Djibouti'), - 'DK' => $t('Denmark'), - 'DM' => $t('Dominica'), - 'DO' => $t('Dominican Republic'), - 'DZ' => $t('Algeria'), - 'EC' => $t('Ecuador'), - 'EE' => $t('Estonia'), - 'EG' => $t('Egypt'), - 'EH' => $t('Western Sahara'), - 'ER' => $t('Eritrea'), - 'ES' => $t('Spain'), - 'ET' => $t('Ethiopia'), - 'FI' => $t('Finland'), - 'FJ' => $t('Fiji'), - 'FK' => $t('Falkland Islands'), - 'FM' => $t('Micronesia'), - 'FO' => $t('Faroe Islands'), - 'FR' => $t('France'), - 'GA' => $t('Gabon'), - 'GB' => $t('United Kingdom'), - 'GD' => $t('Grenada'), - 'GE' => $t('Georgia'), - 'GF' => $t('French Guiana'), - 'GG' => $t('Guernsey'), - 'GH' => $t('Ghana'), - 'GI' => $t('Gibraltar'), - 'GL' => $t('Greenland'), - 'GM' => $t('Gambia'), - 'GN' => $t('Guinea'), - 'GP' => $t('Guadeloupe'), - 'GQ' => $t('Equatorial Guinea'), - 'GR' => $t('Greece'), - 'GS' => $t('South Georgia and the South Sandwich Islands'), - 'GT' => $t('Guatemala'), - 'GU' => $t('Guam'), - 'GW' => $t('Guinea-Bissau'), - 'GY' => $t('Guyana'), - 'HK' => $t('Hong Kong S.A.R., China'), - 'HM' => $t('Heard Island and McDonald Islands'), - 'HN' => $t('Honduras'), - 'HR' => $t('Croatia'), - 'HT' => $t('Haiti'), - 'HU' => $t('Hungary'), - 'ID' => $t('Indonesia'), - 'IE' => $t('Ireland'), - 'IL' => $t('Israel'), - 'IM' => $t('Isle of Man'), - 'IN' => $t('India'), - 'IO' => $t('British Indian Ocean Territory'), - 'IQ' => $t('Iraq'), - 'IR' => $t('Iran'), - 'IS' => $t('Iceland'), - 'IT' => $t('Italy'), - 'JE' => $t('Jersey'), - 'JM' => $t('Jamaica'), - 'JO' => $t('Jordan'), - 'JP' => $t('Japan'), - 'KE' => $t('Kenya'), - 'KG' => $t('Kyrgyzstan'), - 'KH' => $t('Cambodia'), - 'KI' => $t('Kiribati'), - 'KM' => $t('Comoros'), - 'KN' => $t('Saint Kitts and Nevis'), - 'KP' => $t('North Korea'), - 'KR' => $t('South Korea'), - 'KW' => $t('Kuwait'), - 'KY' => $t('Cayman Islands'), - 'KZ' => $t('Kazakhstan'), - 'LA' => $t('Laos'), - 'LB' => $t('Lebanon'), - 'LC' => $t('Saint Lucia'), - 'LI' => $t('Liechtenstein'), - 'LK' => $t('Sri Lanka'), - 'LR' => $t('Liberia'), - 'LS' => $t('Lesotho'), - 'LT' => $t('Lithuania'), - 'LU' => $t('Luxembourg'), - 'LV' => $t('Latvia'), - 'LY' => $t('Libya'), - 'MA' => $t('Morocco'), - 'MC' => $t('Monaco'), - 'MD' => $t('Moldova'), - 'ME' => $t('Montenegro'), - 'MF' => $t('Saint Martin (French part)'), - 'MG' => $t('Madagascar'), - 'MH' => $t('Marshall Islands'), - 'MK' => $t('Macedonia'), - 'ML' => $t('Mali'), - 'MM' => $t('Myanmar'), - 'MN' => $t('Mongolia'), - 'MO' => $t('Macao S.A.R., China'), - 'MP' => $t('Northern Mariana Islands'), - 'MQ' => $t('Martinique'), - 'MR' => $t('Mauritania'), - 'MS' => $t('Montserrat'), - 'MT' => $t('Malta'), - 'MU' => $t('Mauritius'), - 'MV' => $t('Maldives'), - 'MW' => $t('Malawi'), - 'MX' => $t('Mexico'), - 'MY' => $t('Malaysia'), - 'MZ' => $t('Mozambique'), - 'NA' => $t('Namibia'), - 'NC' => $t('New Caledonia'), - 'NE' => $t('Niger'), - 'NF' => $t('Norfolk Island'), - 'NG' => $t('Nigeria'), - 'NI' => $t('Nicaragua'), - 'NL' => $t('Netherlands'), - 'NO' => $t('Norway'), - 'NP' => $t('Nepal'), - 'NR' => $t('Nauru'), - 'NU' => $t('Niue'), - 'NZ' => $t('New Zealand'), - 'OM' => $t('Oman'), - 'PA' => $t('Panama'), - 'PE' => $t('Peru'), - 'PF' => $t('French Polynesia'), - 'PG' => $t('Papua New Guinea'), - 'PH' => $t('Philippines'), - 'PK' => $t('Pakistan'), - 'PL' => $t('Poland'), - 'PM' => $t('Saint Pierre and Miquelon'), - 'PN' => $t('Pitcairn'), - 'PR' => $t('Puerto Rico'), - 'PS' => $t('Palestinian Territory'), - 'PT' => $t('Portugal'), - 'PW' => $t('Palau'), - 'PY' => $t('Paraguay'), - 'QA' => $t('Qatar'), - 'RE' => $t('Reunion'), - 'RO' => $t('Romania'), - 'RS' => $t('Serbia'), - 'RU' => $t('Russia'), - 'RW' => $t('Rwanda'), - 'SA' => $t('Saudi Arabia'), - 'SB' => $t('Solomon Islands'), - 'SC' => $t('Seychelles'), - 'SD' => $t('Sudan'), - 'SE' => $t('Sweden'), - 'SG' => $t('Singapore'), - 'SH' => $t('Saint Helena'), - 'SI' => $t('Slovenia'), - 'SJ' => $t('Svalbard and Jan Mayen'), - 'SK' => $t('Slovakia'), - 'SL' => $t('Sierra Leone'), - 'SM' => $t('San Marino'), - 'SN' => $t('Senegal'), - 'SO' => $t('Somalia'), - 'SR' => $t('Suriname'), - 'ST' => $t('Sao Tome and Principe'), - 'SV' => $t('El Salvador'), - 'SY' => $t('Syria'), - 'SZ' => $t('Swaziland'), - 'TC' => $t('Turks and Caicos Islands'), - 'TD' => $t('Chad'), - 'TF' => $t('French Southern Territories'), - 'TG' => $t('Togo'), - 'TH' => $t('Thailand'), - 'TJ' => $t('Tajikistan'), - 'TK' => $t('Tokelau'), - 'TL' => $t('Timor-Leste'), - 'TM' => $t('Turkmenistan'), - 'TN' => $t('Tunisia'), - 'TO' => $t('Tonga'), - 'TR' => $t('Turkey'), - 'TT' => $t('Trinidad and Tobago'), - 'TV' => $t('Tuvalu'), - 'TW' => $t('Taiwan'), - 'TZ' => $t('Tanzania'), - 'UA' => $t('Ukraine'), - 'UG' => $t('Uganda'), - 'UM' => $t('United States Minor Outlying Islands'), - 'US' => $t('United States'), - 'UY' => $t('Uruguay'), - 'UZ' => $t('Uzbekistan'), - 'VA' => $t('Vatican'), - 'VC' => $t('Saint Vincent and the Grenadines'), - 'VE' => $t('Venezuela'), - 'VG' => $t('British Virgin Islands'), - 'VI' => $t('U.S. Virgin Islands'), - 'VN' => $t('Vietnam'), - 'VU' => $t('Vanuatu'), - 'WF' => $t('Wallis and Futuna'), - 'WS' => $t('Samoa'), - 'YE' => $t('Yemen'), - 'YT' => $t('Mayotte'), - 'ZA' => $t('South Africa'), - 'ZM' => $t('Zambia'), - 'ZW' => $t('Zimbabwe'), - ); - - // Sort the list. - natcasesort($countries); - - return $countries; -} - -/** - * Some common languages with their English and native names. - * - * Language codes are defined by the W3C language tags document for - * interoperability. Language codes typically have a language and optionally, - * a script or regional variant name. See - * http://www.w3.org/International/articles/language-tags/ for more information. - * - * This list is based on languages available from localize.drupal.org. See - * http://localize.drupal.org/issues for information on how to add languages - * there. - * - * The "Left-to-right marker" comments and the enclosed UTF-8 markers are to - * make otherwise strange looking PHP syntax natural (to not be displayed in - * right to left). See http://drupal.org/node/128866#comment-528929. - * - * @return - * An array of language code to language name information. - * Language name information itself is an array of English and native names. - */ -function standard_language_list() { - return array( - 'af' => array('Afrikaans', 'Afrikaans'), - 'am' => array('Amharic', 'አማርኛ'), - 'ar' => array('Arabic', /* Left-to-right marker "" */ 'العربية', LANGUAGE_RTL), - 'ast' => array('Asturian', 'Asturianu'), - 'az' => array('Azerbaijani', 'Azərbaycanca'), - 'be' => array('Belarusian', 'Беларуская'), - 'bg' => array('Bulgarian', 'Български'), - 'bn' => array('Bengali', 'বাংলা'), - 'bo' => array('Tibetan', 'བོད་སྐད་'), - 'bs' => array('Bosnian', 'Bosanski'), - 'ca' => array('Catalan', 'Català'), - 'cs' => array('Czech', 'Čeština'), - 'cy' => array('Welsh', 'Cymraeg'), - 'da' => array('Danish', 'Dansk'), - 'de' => array('German', 'Deutsch'), - 'dz' => array('Dzongkha', 'རྫོང་ཁ'), - 'el' => array('Greek', 'Ελληνικά'), - 'en' => array('English', 'English'), - 'en-gb' => array('English, British', 'English, British'), - 'eo' => array('Esperanto', 'Esperanto'), - 'es' => array('Spanish', 'Español'), - 'et' => array('Estonian', 'Eesti'), - 'eu' => array('Basque', 'Euskera'), - 'fa' => array('Persian, Farsi', /* Left-to-right marker "" */ 'فارسی', LANGUAGE_RTL), - 'fi' => array('Finnish', 'Suomi'), - 'fil' => array('Filipino', 'Filipino'), - 'fo' => array('Faeroese', 'Føroyskt'), - 'fr' => array('French', 'Français'), - 'ga' => array('Irish', 'Gaeilge'), - 'gd' => array('Scots Gaelic', 'Gàidhlig'), - 'gl' => array('Galician', 'Galego'), - 'gsw-berne' => array('Swiss German', 'Schwyzerdütsch'), - 'gu' => array('Gujarati', 'ગુજરાતી'), - 'he' => array('Hebrew', /* Left-to-right marker "" */ 'עברית', LANGUAGE_RTL), - 'hi' => array('Hindi', 'हिन्दी'), - 'hr' => array('Croatian', 'Hrvatski'), - 'ht' => array('Haitian Creole', 'Kreyòl ayisyen'), - 'hu' => array('Hungarian', 'Magyar'), - 'hy' => array('Armenian', 'Հայերեն'), - 'id' => array('Indonesian', 'Bahasa Indonesia'), - 'is' => array('Icelandic', 'Íslenska'), - 'it' => array('Italian', 'Italiano'), - 'ja' => array('Japanese', '日本語'), - 'jv' => array('Javanese', 'Basa Java'), - 'ka' => array('Georgian', 'ქართული ენა'), - 'kk' => array('Kazakh', 'Қазақ'), - 'kn' => array('Kannada', 'ಕನ್ನಡ'), - 'ko' => array('Korean', '한국어'), - 'ku' => array('Kurdish', 'Kurdî'), - 'ky' => array('Kyrgyz', 'Кыргызча'), - 'lo' => array('Lao', 'ພາສາລາວ'), - 'lt' => array('Lithuanian', 'Lietuvių'), - 'lv' => array('Latvian', 'Latviešu'), - 'mfe' => array('Mauritian Creole', 'Kreol Morisyen'), - 'mg' => array('Malagasy', 'Malagasy'), - 'mi' => array('Maori', 'Māori'), - 'mk' => array('Macedonian', 'Македонски'), - 'ml' => array('Malayalam', 'മലയാളം'), - 'mn' => array('Mongolian', 'монгол'), - 'mr' => array('Marathi', 'मराठी'), - 'mt' => array('Maltese', 'Malti'), - 'my' => array('Burmese', 'ဗမာစကား'), - 'ne' => array('Nepali', 'नेपाली'), - 'nl' => array('Dutch', 'Nederlands'), - 'nb' => array('Norwegian Bokmål', 'Bokmål'), - 'nn' => array('Norwegian Nynorsk', 'Nynorsk'), - 'oc' => array('Occitan', 'Occitan'), - 'or' => array('Oriya', 'ଓଡ଼ିଆ'), - 'pa' => array('Punjabi', 'ਪੰਜਾਬੀ'), - 'pl' => array('Polish', 'Polski'), - 'pt' => array('Portuguese, International', 'Português, Internacional'), - 'pt-pt' => array('Portuguese, Portugal', 'Português, Portugal'), - 'pt-br' => array('Portuguese, Brazil', 'Português, Brasil'), - 'ro' => array('Romanian', 'Română'), - 'ru' => array('Russian', 'Русский'), - 'sco' => array('Scots', 'Scots'), - 'se' => array('Northern Sami', 'Sámi'), - 'si' => array('Sinhala', 'සිංහල'), - 'sk' => array('Slovak', 'Slovenčina'), - 'sl' => array('Slovenian', 'Slovenščina'), - 'sq' => array('Albanian', 'Shqip'), - 'sr' => array('Serbian', 'Српски'), - 'sv' => array('Swedish', 'Svenska'), - 'sw' => array('Swahili', 'Kiswahili'), - 'ta' => array('Tamil', 'தமிழ்'), - 'ta-lk' => array('Tamil, Sri Lanka', 'தமிழ், இலங்கை'), - 'te' => array('Telugu', 'తెలుగు'), - 'th' => array('Thai', 'ภาษาไทย'), - 'ti' => array('Tigrinya', 'ትግርኛ'), - 'tr' => array('Turkish', 'Türkçe'), - 'ug' => array('Uighur', 'Уйғур'), - 'uk' => array('Ukrainian', 'Українська'), - 'ur' => array('Urdu', /* Left-to-right marker "" */ 'اردو', LANGUAGE_RTL), - 'vi' => array('Vietnamese', 'Tiếng Việt'), - 'xx-lolspeak' => array('Lolspeak', 'Lolspeak'), - 'zh-hans' => array('Chinese, Simplified', '简体中文'), - 'zh-hant' => array('Chinese, Traditional', '繁體中文'), - ); -} diff --git a/core/includes/stream_wrappers.inc b/core/includes/stream_wrappers.inc deleted file mode 100644 index 650d94db1ea..00000000000 --- a/core/includes/stream_wrappers.inc +++ /dev/null @@ -1,836 +0,0 @@ -<?php - -/** - * @file - * Drupal stream wrapper interface. - * - * Provides a Drupal interface and classes to implement PHP stream wrappers for - * public, private, and temporary files. - * - * A stream wrapper is an abstraction of a file system that allows Drupal to - * use the same set of methods to access both local files and remote resources. - * - * Note that PHP 5.2 fopen() only supports URIs of the form "scheme://target" - * despite the fact that according to RFC 3986 a URI's scheme component - * delimiter is in general just ":", not "://". Because of this PHP limitation - * and for consistency Drupal will only accept URIs of form "scheme://target". - * - * @see http://www.faqs.org/rfcs/rfc3986.html - * @see http://bugs.php.net/bug.php?id=47070 - */ - -/** - * Stream wrapper bit flags that are the basis for composite types. - * - * Note that 0x0002 is skipped, because it was the value of a constant that has - * since been removed. - */ - -/** - * Stream wrapper bit flag -- a filter that matches all wrappers. - */ -define('STREAM_WRAPPERS_ALL', 0x0000); - -/** - * Stream wrapper bit flag -- refers to a local file system location. - */ -define('STREAM_WRAPPERS_LOCAL', 0x0001); - -/** - * Stream wrapper bit flag -- wrapper is readable (almost always true). - */ -define('STREAM_WRAPPERS_READ', 0x0004); - -/** - * Stream wrapper bit flag -- wrapper is writeable. - */ -define('STREAM_WRAPPERS_WRITE', 0x0008); - -/** - * Stream wrapper bit flag -- exposed in the UI and potentially web accessible. - */ -define('STREAM_WRAPPERS_VISIBLE', 0x0010); - -/** - * Composite stream wrapper bit flags that are usually used as the types. - */ - -/** - * Stream wrapper type flag -- not visible in the UI or accessible via web, - * but readable and writable. E.g. the temporary directory for uploads. - */ -define('STREAM_WRAPPERS_HIDDEN', STREAM_WRAPPERS_READ | STREAM_WRAPPERS_WRITE); - -/** - * Stream wrapper type flag -- hidden, readable and writeable using local files. - */ -define('STREAM_WRAPPERS_LOCAL_HIDDEN', STREAM_WRAPPERS_LOCAL | STREAM_WRAPPERS_HIDDEN); - -/** - * Stream wrapper type flag -- visible, readable and writeable. - */ -define('STREAM_WRAPPERS_WRITE_VISIBLE', STREAM_WRAPPERS_READ | STREAM_WRAPPERS_WRITE | STREAM_WRAPPERS_VISIBLE); - -/** - * Stream wrapper type flag -- visible and read-only. - */ -define('STREAM_WRAPPERS_READ_VISIBLE', STREAM_WRAPPERS_READ | STREAM_WRAPPERS_VISIBLE); - -/** - * Stream wrapper type flag -- the default when 'type' is omitted from - * hook_stream_wrappers(). This does not include STREAM_WRAPPERS_LOCAL, - * because PHP grants a greater trust level to local files (for example, they - * can be used in an "include" statement, regardless of the "allow_url_include" - * setting), so stream wrappers need to explicitly opt-in to this. - */ -define('STREAM_WRAPPERS_NORMAL', STREAM_WRAPPERS_WRITE_VISIBLE); - -/** - * Stream wrapper type flag -- visible, readable and writeable using local files. - */ -define('STREAM_WRAPPERS_LOCAL_NORMAL', STREAM_WRAPPERS_LOCAL | STREAM_WRAPPERS_NORMAL); - -/** - * Generic PHP stream wrapper interface. - * - * @see http://www.php.net/manual/en/class.streamwrapper.php - */ -interface StreamWrapperInterface { - public function stream_open($uri, $mode, $options, &$opened_url); - public function stream_close(); - public function stream_lock($operation); - public function stream_read($count); - public function stream_write($data); - public function stream_eof(); - public function stream_seek($offset, $whence); - public function stream_flush(); - public function stream_tell(); - public function stream_stat(); - public function unlink($uri); - public function rename($from_uri, $to_uri); - public function mkdir($uri, $mode, $options); - public function rmdir($uri, $options); - public function url_stat($uri, $flags); - public function dir_opendir($uri, $options); - public function dir_readdir(); - public function dir_rewinddir(); - public function dir_closedir(); -} - -/** - * Drupal stream wrapper extension. - * - * Extend the StreamWrapperInterface with methods expected by Drupal stream - * wrapper classes. - */ -interface DrupalStreamWrapperInterface extends StreamWrapperInterface { - /** - * Set the absolute stream resource URI. - * - * This allows you to set the URI. Generally is only called by the factory - * method. - * - * @param $uri - * A string containing the URI that should be used for this instance. - */ - function setUri($uri); - - /** - * Returns the stream resource URI. - * - * @return - * Returns the current URI of the instance. - */ - public function getUri(); - - /** - * Returns a web accessible URL for the resource. - * - * This function should return a URL that can be embedded in a web page - * and accessed from a browser. For example, the external URL of - * "youtube://xIpLd0WQKCY" might be - * "http://www.youtube.com/watch?v=xIpLd0WQKCY". - * - * @return - * Returns a string containing a web accessible URL for the resource. - */ - public function getExternalUrl(); - - /** - * Returns the MIME type of the resource. - * - * @param $uri - * The URI, path, or filename. - * @param $mapping - * An optional map of extensions to their mimetypes, in the form: - * - 'mimetypes': a list of mimetypes, keyed by an identifier, - * - 'extensions': the mapping itself, an associative array in which - * the key is the extension and the value is the mimetype identifier. - * - * @return - * Returns a string containing the MIME type of the resource. - */ - public static function getMimeType($uri, $mapping = NULL); - - /** - * Changes permissions of the resource. - * - * PHP lacks this functionality and it is not part of the official stream - * wrapper interface. This is a custom implementation for Drupal. - * - * @param $mode - * Integer value for the permissions. Consult PHP chmod() documentation - * for more information. - * - * @return - * Returns TRUE on success or FALSE on failure. - */ - public function chmod($mode); - - /** - * Returns canonical, absolute path of the resource. - * - * Implementation placeholder. PHP's realpath() does not support stream - * wrappers. We provide this as a default so that individual wrappers may - * implement their own solutions. - * - * @return - * Returns a string with absolute pathname on success (implemented - * by core wrappers), or FALSE on failure or if the registered - * wrapper does not provide an implementation. - */ - public function realpath(); - - /** - * Gets the name of the directory from a given path. - * - * This method is usually accessed through drupal_dirname(), which wraps - * around the normal PHP dirname() function, which does not support stream - * wrappers. - * - * @param $uri - * An optional URI. - * - * @return - * A string containing the directory name, or FALSE if not applicable. - * - * @see drupal_dirname() - */ - public function dirname($uri = NULL); -} - - -/** - * Drupal stream wrapper base class for local files. - * - * This class provides a complete stream wrapper implementation. URIs such as - * "public://example.txt" are expanded to a normal filesystem path such as - * "sites/default/files/example.txt" and then PHP filesystem functions are - * invoked. - * - * DrupalLocalStreamWrapper implementations need to implement at least the - * getDirectoryPath() and getExternalUrl() methods. - */ -abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface { - /** - * Stream context resource. - * - * @var Resource - */ - public $context; - - /** - * A generic resource handle. - * - * @var Resource - */ - public $handle = NULL; - - /** - * Instance URI (stream). - * - * A stream is referenced as "scheme://target". - * - * @var String - */ - protected $uri; - - /** - * Gets the path that the wrapper is responsible for. - * @TODO: Review this method name in D8 per http://drupal.org/node/701358 - * - * @return - * String specifying the path. - */ - abstract function getDirectoryPath(); - - /** - * Base implementation of setUri(). - */ - function setUri($uri) { - $this->uri = $uri; - } - - /** - * Base implementation of getUri(). - */ - function getUri() { - return $this->uri; - } - - /** - * Returns the local writable target of the resource within the stream. - * - * This function should be used in place of calls to realpath() or similar - * functions when attempting to determine the location of a file. While - * functions like realpath() may return the location of a read-only file, this - * method may return a URI or path suitable for writing that is completely - * separate from the URI used for reading. - * - * @param $uri - * Optional URI. - * - * @return - * Returns a string representing a location suitable for writing of a file, - * or FALSE if unable to write to the file such as with read-only streams. - */ - protected function getTarget($uri = NULL) { - if (!isset($uri)) { - $uri = $this->uri; - } - - list($scheme, $target) = explode('://', $uri, 2); - - // Remove erroneous leading or trailing, forward-slashes and backslashes. - return trim($target, '\/'); - } - - /** - * Base implementation of getMimeType(). - */ - static function getMimeType($uri, $mapping = NULL) { - if (!isset($mapping)) { - // The default file map, defined in file.mimetypes.inc is quite big. - // We only load it when necessary. - include_once DRUPAL_ROOT . '/core/includes/file.mimetypes.inc'; - $mapping = file_mimetype_mapping(); - } - - $extension = ''; - $file_parts = explode('.', basename($uri)); - - // Remove the first part: a full filename should not match an extension. - array_shift($file_parts); - - // Iterate over the file parts, trying to find a match. - // For my.awesome.image.jpeg, we try: - // - jpeg - // - image.jpeg, and - // - awesome.image.jpeg - while ($additional_part = array_pop($file_parts)) { - $extension = strtolower($additional_part . ($extension ? '.' . $extension : '')); - if (isset($mapping['extensions'][$extension])) { - return $mapping['mimetypes'][$mapping['extensions'][$extension]]; - } - } - - return 'application/octet-stream'; - } - - /** - * Base implementation of chmod(). - */ - function chmod($mode) { - $output = @chmod($this->getLocalPath(), $mode); - // We are modifying the underlying file here, so we have to clear the stat - // cache so that PHP understands that URI has changed too. - clearstatcache(); - return $output; - } - - /** - * Base implementation of realpath(). - */ - function realpath() { - return $this->getLocalPath(); - } - - /** - * Returns the canonical absolute path of the URI, if possible. - * - * @param string $uri - * (optional) The stream wrapper URI to be converted to a canonical - * absolute path. This may point to a directory or another type of file. - * - * @return string|false - * If $uri is not set, returns the canonical absolute path of the URI - * previously set by the DrupalStreamWrapperInterface::setUri() function. - * If $uri is set and valid for this class, returns its canonical absolute - * path, as determined by the realpath() function. If $uri is set but not - * valid, returns FALSE. - */ - protected function getLocalPath($uri = NULL) { - if (!isset($uri)) { - $uri = $this->uri; - } - $path = $this->getDirectoryPath() . '/' . $this->getTarget($uri); - $realpath = realpath($path); - if (!$realpath) { - // This file does not yet exist. - $realpath = realpath(dirname($path)) . '/' . basename($path); - } - $directory = realpath($this->getDirectoryPath()); - if (!$realpath || !$directory || strpos($realpath, $directory) !== 0) { - return FALSE; - } - return $realpath; - } - - /** - * Support for fopen(), file_get_contents(), file_put_contents() etc. - * - * @param $uri - * A string containing the URI to the file to open. - * @param $mode - * The file mode ("r", "wb" etc.). - * @param $options - * A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS. - * @param $opened_path - * A string containing the path actually opened. - * - * @return - * Returns TRUE if file was opened successfully. - * - * @see http://php.net/manual/en/streamwrapper.stream-open.php - */ - public function stream_open($uri, $mode, $options, &$opened_path) { - $this->uri = $uri; - $path = $this->getLocalPath(); - $this->handle = ($options & STREAM_REPORT_ERRORS) ? fopen($path, $mode) : @fopen($path, $mode); - - if ((bool) $this->handle && $options & STREAM_USE_PATH) { - $opened_path = $path; - } - - return (bool) $this->handle; - } - - /** - * Support for flock(). - * - * @param $operation - * One of the following: - * - LOCK_SH to acquire a shared lock (reader). - * - LOCK_EX to acquire an exclusive lock (writer). - * - LOCK_UN to release a lock (shared or exclusive). - * - LOCK_NB if you don't want flock() to block while locking (not - * supported on Windows). - * - * @return - * Always returns TRUE at the present time. - * - * @see http://php.net/manual/en/streamwrapper.stream-lock.php - */ - public function stream_lock($operation) { - if (in_array($operation, array(LOCK_SH, LOCK_EX, LOCK_UN, LOCK_NB))) { - return flock($this->handle, $operation); - } - - return TRUE; - } - - /** - * Support for fread(), file_get_contents() etc. - * - * @param $count - * Maximum number of bytes to be read. - * - * @return - * The string that was read, or FALSE in case of an error. - * - * @see http://php.net/manual/en/streamwrapper.stream-read.php - */ - public function stream_read($count) { - return fread($this->handle, $count); - } - - /** - * Support for fwrite(), file_put_contents() etc. - * - * @param $data - * The string to be written. - * - * @return - * The number of bytes written (integer). - * - * @see http://php.net/manual/en/streamwrapper.stream-write.php - */ - public function stream_write($data) { - return fwrite($this->handle, $data); - } - - /** - * Support for feof(). - * - * @return - * TRUE if end-of-file has been reached. - * - * @see http://php.net/manual/en/streamwrapper.stream-eof.php - */ - public function stream_eof() { - return feof($this->handle); - } - - /** - * Support for fseek(). - * - * @param $offset - * The byte offset to got to. - * @param $whence - * SEEK_SET, SEEK_CUR, or SEEK_END. - * - * @return - * TRUE on success. - * - * @see http://php.net/manual/en/streamwrapper.stream-seek.php - */ - public function stream_seek($offset, $whence) { - // fseek returns 0 on success and -1 on a failure. - // stream_seek 1 on success and 0 on a failure. - return !fseek($this->handle, $offset, $whence); - } - - /** - * Support for fflush(). - * - * @return - * TRUE if data was successfully stored (or there was no data to store). - * - * @see http://php.net/manual/en/streamwrapper.stream-flush.php - */ - public function stream_flush() { - return fflush($this->handle); - } - - /** - * Support for ftell(). - * - * @return - * The current offset in bytes from the beginning of file. - * - * @see http://php.net/manual/en/streamwrapper.stream-tell.php - */ - public function stream_tell() { - return ftell($this->handle); - } - - /** - * Support for fstat(). - * - * @return - * An array with file status, or FALSE in case of an error - see fstat() - * for a description of this array. - * - * @see http://php.net/manual/en/streamwrapper.stream-stat.php - */ - public function stream_stat() { - return fstat($this->handle); - } - - /** - * Support for fclose(). - * - * @return - * TRUE if stream was successfully closed. - * - * @see http://php.net/manual/en/streamwrapper.stream-close.php - */ - public function stream_close() { - return fclose($this->handle); - } - - /** - * Support for unlink(). - * - * @param $uri - * A string containing the uri to the resource to delete. - * - * @return - * TRUE if resource was successfully deleted. - * - * @see http://php.net/manual/en/streamwrapper.unlink.php - */ - public function unlink($uri) { - $this->uri = $uri; - return drupal_unlink($this->getLocalPath()); - } - - /** - * Support for rename(). - * - * @param $from_uri, - * The uri to the file to rename. - * @param $to_uri - * The new uri for file. - * - * @return - * TRUE if file was successfully renamed. - * - * @see http://php.net/manual/en/streamwrapper.rename.php - */ - public function rename($from_uri, $to_uri) { - return rename($this->getLocalPath($from_uri), $this->getLocalPath($to_uri)); - } - - /** - * Gets the name of the directory from a given path. - * - * This method is usually accessed through drupal_dirname(), which wraps - * around the PHP dirname() function because it does not support stream - * wrappers. - * - * @param $uri - * A URI or path. - * - * @return - * A string containing the directory name. - * - * @see drupal_dirname() - */ - public function dirname($uri = NULL) { - list($scheme, $target) = explode('://', $uri, 2); - $target = $this->getTarget($uri); - $dirname = dirname($target); - - if ($dirname == '.') { - $dirname = ''; - } - - return $scheme . '://' . $dirname; - } - - /** - * Support for mkdir(). - * - * @param $uri - * A string containing the URI to the directory to create. - * @param $mode - * Permission flags - see mkdir(). - * @param $options - * A bit mask of STREAM_REPORT_ERRORS and STREAM_MKDIR_RECURSIVE. - * - * @return - * TRUE if directory was successfully created. - * - * @see http://php.net/manual/en/streamwrapper.mkdir.php - */ - public function mkdir($uri, $mode, $options) { - $this->uri = $uri; - $recursive = (bool) ($options & STREAM_MKDIR_RECURSIVE); - if ($recursive) { - // $this->getLocalPath() fails if $uri has multiple levels of directories - // that do not yet exist. - $localpath = $this->getDirectoryPath() . '/' . $this->getTarget($uri); - } - else { - $localpath = $this->getLocalPath($uri); - } - if ($options & STREAM_REPORT_ERRORS) { - return mkdir($localpath, $mode, $recursive); - } - else { - return @mkdir($localpath, $mode, $recursive); - } - } - - /** - * Support for rmdir(). - * - * @param $uri - * A string containing the URI to the directory to delete. - * @param $options - * A bit mask of STREAM_REPORT_ERRORS. - * - * @return - * TRUE if directory was successfully removed. - * - * @see http://php.net/manual/en/streamwrapper.rmdir.php - */ - public function rmdir($uri, $options) { - $this->uri = $uri; - if ($options & STREAM_REPORT_ERRORS) { - return drupal_rmdir($this->getLocalPath()); - } - else { - return @drupal_rmdir($this->getLocalPath()); - } - } - - /** - * Support for stat(). - * - * @param $uri - * A string containing the URI to get information about. - * @param $flags - * A bit mask of STREAM_URL_STAT_LINK and STREAM_URL_STAT_QUIET. - * - * @return - * An array with file status, or FALSE in case of an error - see fstat() - * for a description of this array. - * - * @see http://php.net/manual/en/streamwrapper.url-stat.php - */ - public function url_stat($uri, $flags) { - $this->uri = $uri; - $path = $this->getLocalPath(); - // Suppress warnings if requested or if the file or directory does not - // exist. This is consistent with PHP's plain filesystem stream wrapper. - if ($flags & STREAM_URL_STAT_QUIET || !file_exists($path)) { - return @stat($path); - } - else { - return stat($path); - } - } - - /** - * Support for opendir(). - * - * @param $uri - * A string containing the URI to the directory to open. - * @param $options - * Unknown (parameter is not documented in PHP Manual). - * - * @return - * TRUE on success. - * - * @see http://php.net/manual/en/streamwrapper.dir-opendir.php - */ - public function dir_opendir($uri, $options) { - $this->uri = $uri; - $this->handle = opendir($this->getLocalPath()); - - return (bool) $this->handle; - } - - /** - * Support for readdir(). - * - * @return - * The next filename, or FALSE if there are no more files in the directory. - * - * @see http://php.net/manual/en/streamwrapper.dir-readdir.php - */ - public function dir_readdir() { - return readdir($this->handle); - } - - /** - * Support for rewinddir(). - * - * @return - * TRUE on success. - * - * @see http://php.net/manual/en/streamwrapper.dir-rewinddir.php - */ - public function dir_rewinddir() { - rewinddir($this->handle); - // We do not really have a way to signal a failure as rewinddir() does not - // have a return value and there is no way to read a directory handler - // without advancing to the next file. - return TRUE; - } - - /** - * Support for closedir(). - * - * @return - * TRUE on success. - * - * @see http://php.net/manual/en/streamwrapper.dir-closedir.php - */ - public function dir_closedir() { - closedir($this->handle); - // We do not really have a way to signal a failure as closedir() does not - // have a return value. - return TRUE; - } -} - -/** - * Drupal public (public://) stream wrapper class. - * - * Provides support for storing publicly accessible files with the Drupal file - * interface. - */ -class DrupalPublicStreamWrapper extends DrupalLocalStreamWrapper { - /** - * Implements abstract public function getDirectoryPath() - */ - public function getDirectoryPath() { - return variable_get('file_public_path', conf_path() . '/files'); - } - - /** - * Overrides getExternalUrl(). - * - * Return the HTML URI of a public file. - */ - function getExternalUrl() { - $path = str_replace('\\', '/', $this->getTarget()); - return $GLOBALS['base_url'] . '/' . self::getDirectoryPath() . '/' . drupal_encode_path($path); - } -} - - -/** - * Drupal private (private://) stream wrapper class. - * - * Provides support for storing privately accessible files with the Drupal file - * interface. - * - * Extends DrupalPublicStreamWrapper. - */ -class DrupalPrivateStreamWrapper extends DrupalLocalStreamWrapper { - /** - * Implements abstract public function getDirectoryPath() - */ - public function getDirectoryPath() { - return variable_get('file_private_path', ''); - } - - /** - * Overrides getExternalUrl(). - * - * Return the HTML URI of a private file. - */ - function getExternalUrl() { - $path = str_replace('\\', '/', $this->getTarget()); - return url('system/files/' . $path, array('absolute' => TRUE)); - } -} - -/** - * Drupal temporary (temporary://) stream wrapper class. - * - * Provides support for storing temporarily accessible files with the Drupal - * file interface. - * - * Extends DrupalPublicStreamWrapper. - */ -class DrupalTemporaryStreamWrapper extends DrupalLocalStreamWrapper { - /** - * Implements abstract public function getDirectoryPath() - */ - public function getDirectoryPath() { - return variable_get('file_temporary_path', file_directory_temp()); - } - - /** - * Overrides getExternalUrl(). - */ - public function getExternalUrl() { - $path = str_replace('\\', '/', $this->getTarget()); - return url('system/temporary/' . $path, array('absolute' => TRUE)); - } -} diff --git a/core/includes/tablesort.inc b/core/includes/tablesort.inc deleted file mode 100644 index 121a1b90934..00000000000 --- a/core/includes/tablesort.inc +++ /dev/null @@ -1,252 +0,0 @@ -<?php - -/** - * @file - * Functions to aid in the creation of sortable tables. - * - * All tables created with a call to theme('table') have the option of having - * column headers that the user can click on to sort the table by that column. - */ - - -/** - * Query extender class for tablesort queries. - */ -class TableSort extends SelectQueryExtender { - - /** - * The array of fields that can be sorted by. - * - * @var array - */ - protected $header = array(); - - public function __construct(SelectQueryInterface $query, DatabaseConnection $connection) { - parent::__construct($query, $connection); - - // Add convenience tag to mark that this is an extended query. We have to - // do this in the constructor to ensure that it is set before preExecute() - // gets called. - $this->addTag('tablesort'); - } - - /** - * Order the query based on a header array. - * - * @see theme_table() - * @param $header - * Table header array. - * @return SelectQueryInterface - * The called object. - */ - public function orderByHeader(Array $header) { - $this->header = $header; - $ts = $this->init(); - if (!empty($ts['sql'])) { - // Based on code from db_escape_table(), but this can also contain a dot. - $field = preg_replace('/[^A-Za-z0-9_.]+/', '', $ts['sql']); - - // Sort order can only be ASC or DESC. - $sort = drupal_strtoupper($ts['sort']); - $sort = in_array($sort, array('ASC', 'DESC')) ? $sort : ''; - $this->orderBy($field, $sort); - } - return $this; - } - - /** - * Initialize the table sort context. - */ - protected function init() { - $ts = $this->order(); - $ts['sort'] = $this->getSort(); - $ts['query'] = $this->getQueryParameters(); - return $ts; - } - - /** - * Determine the current sort direction. - * - * @param $headers - * An array of column headers in the format described in theme_table(). - * @return - * The current sort direction ("asc" or "desc"). - */ - protected function getSort() { - return tablesort_get_sort($this->header); - } - - /** - * Compose a URL query parameter array to append to table sorting requests. - * - * @return - * A URL query parameter array that consists of all components of the current - * page request except for those pertaining to table sorting. - * - * @see tablesort_get_query_parameters() - */ - protected function getQueryParameters() { - return tablesort_get_query_parameters(); - } - - /** - * Determine the current sort criterion. - * - * @param $headers - * An array of column headers in the format described in theme_table(). - * @return - * An associative array describing the criterion, containing the keys: - * - "name": The localized title of the table column. - * - "sql": The name of the database field to sort on. - */ - protected function order() { - return tablesort_get_order($this->header); - } -} - -/** - * Initialize the table sort context. - */ -function tablesort_init($header) { - $ts = tablesort_get_order($header); - $ts['sort'] = tablesort_get_sort($header); - $ts['query'] = tablesort_get_query_parameters(); - return $ts; -} - -/** - * Format a column header. - * - * If the cell in question is the column header for the current sort criterion, - * it gets special formatting. All possible sort criteria become links. - * - * @param $cell - * The cell to format. - * @param $header - * An array of column headers in the format described in theme_table(). - * @param $ts - * The current table sort context as returned from tablesort_init(). - * @return - * A properly formatted cell, ready for _theme_table_cell(). - */ -function tablesort_header($cell, $header, $ts) { - // Special formatting for the currently sorted column header. - if (is_array($cell) && isset($cell['field'])) { - $title = t('sort by @s', array('@s' => $cell['data'])); - if ($cell['data'] == $ts['name']) { - $ts['sort'] = (($ts['sort'] == 'asc') ? 'desc' : 'asc'); - $cell['class'][] = 'active'; - $image = theme('tablesort_indicator', array('style' => $ts['sort'])); - } - else { - // If the user clicks a different header, we want to sort ascending initially. - $ts['sort'] = 'asc'; - $image = ''; - } - $cell['data'] = l($cell['data'] . $image, $_GET['q'], array('attributes' => array('title' => $title), 'query' => array_merge($ts['query'], array('sort' => $ts['sort'], 'order' => $cell['data'])), 'html' => TRUE)); - - unset($cell['field'], $cell['sort']); - } - return $cell; -} - -/** - * Format a table cell. - * - * Adds a class attribute to all cells in the currently active column. - * - * @param $cell - * The cell to format. - * @param $header - * An array of column headers in the format described in theme_table(). - * @param $ts - * The current table sort context as returned from tablesort_init(). - * @param $i - * The index of the cell's table column. - * @return - * A properly formatted cell, ready for _theme_table_cell(). - */ -function tablesort_cell($cell, $header, $ts, $i) { - if (isset($header[$i]['data']) && $header[$i]['data'] == $ts['name'] && !empty($header[$i]['field'])) { - if (is_array($cell)) { - $cell['class'][] = 'active'; - } - else { - $cell = array('data' => $cell, 'class' => array('active')); - } - } - return $cell; -} - -/** - * Compose a URL query parameter array for table sorting links. - * - * @return - * A URL query parameter array that consists of all components of the current - * page request except for those pertaining to table sorting. - */ -function tablesort_get_query_parameters() { - return drupal_get_query_parameters($_GET, array('q', 'sort', 'order')); -} - -/** - * Determine the current sort criterion. - * - * @param $headers - * An array of column headers in the format described in theme_table(). - * @return - * An associative array describing the criterion, containing the keys: - * - "name": The localized title of the table column. - * - "sql": The name of the database field to sort on. - */ -function tablesort_get_order($headers) { - $order = isset($_GET['order']) ? $_GET['order'] : ''; - foreach ($headers as $header) { - if (is_array($header)) { - if (isset($header['data']) && $order == $header['data']) { - $default = $header; - break; - } - - if (empty($default) && isset($header['sort']) && ($header['sort'] == 'asc' || $header['sort'] == 'desc')) { - $default = $header; - } - } - } - - if (!isset($default)) { - $default = reset($headers); - if (!is_array($default)) { - $default = array('data' => $default); - } - } - - $default += array('data' => NULL, 'field' => NULL); - return array('name' => $default['data'], 'sql' => $default['field']); -} - -/** - * Determine the current sort direction. - * - * @param $headers - * An array of column headers in the format described in theme_table(). - * @return - * The current sort direction ("asc" or "desc"). - */ -function tablesort_get_sort($headers) { - if (isset($_GET['sort'])) { - return (strtolower($_GET['sort']) == 'desc') ? 'desc' : 'asc'; - } - // The user has not specified a sort. Use the default for the currently sorted - // header if specified; otherwise use "asc". - else { - // Find out which header is currently being sorted. - $ts = tablesort_get_order($headers); - foreach ($headers as $header) { - if (is_array($header) && isset($header['data']) && $header['data'] == $ts['name'] && isset($header['sort'])) { - return $header['sort']; - } - } - } - return 'asc'; -} diff --git a/core/includes/theme.inc b/core/includes/theme.inc deleted file mode 100644 index 5c38525b3eb..00000000000 --- a/core/includes/theme.inc +++ /dev/null @@ -1,2791 +0,0 @@ -<?php - -/** - * @file - * The theme system, which controls the output of Drupal. - * - * The theme system allows for nearly all output of the Drupal system to be - * customized by user themes. - */ - -/** - * @defgroup content_flags Content markers - * @{ - * Markers used by theme_mark() and node_mark() to designate content. - * @see theme_mark(), node_mark() - */ - -/** - * Mark content as read. - */ -define('MARK_READ', 0); - -/** - * Mark content as being new. - */ -define('MARK_NEW', 1); - -/** - * Mark content as being updated. - */ -define('MARK_UPDATED', 2); - -/** - * @} End of "Content markers". - */ - -/** - * Determines if a theme is available to use. - * - * @param $theme - * Either the name of a theme or a full theme object. - * - * @return - * Boolean TRUE if the theme is enabled or is the site administration theme; - * FALSE otherwise. - */ -function drupal_theme_access($theme) { - if (is_object($theme)) { - return _drupal_theme_access($theme); - } - else { - $themes = list_themes(); - return isset($themes[$theme]) && _drupal_theme_access($themes[$theme]); - } -} - -/** - * Helper function for determining access to a theme. - * - * @see drupal_theme_access() - */ -function _drupal_theme_access($theme) { - $admin_theme = variable_get('admin_theme'); - return !empty($theme->status) || ($admin_theme && $theme->name == $admin_theme); -} - -/** - * Initialize the theme system by loading the theme. - */ -function drupal_theme_initialize() { - global $theme, $user, $theme_key; - - // If $theme is already set, assume the others are set, too, and do nothing - if (isset($theme)) { - return; - } - - drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE); - $themes = list_themes(); - - // Only select the user selected theme if it is available in the - // list of themes that can be accessed. - $theme = !empty($user->theme) && drupal_theme_access($user->theme) ? $user->theme : variable_get('theme_default', 'bartik'); - - // Allow modules to override the theme. Validation has already been performed - // inside menu_get_custom_theme(), so we do not need to check it again here. - $custom_theme = menu_get_custom_theme(); - $theme = !empty($custom_theme) ? $custom_theme : $theme; - - // Store the identifier for retrieving theme settings with. - $theme_key = $theme; - - // Find all our ancestor themes and put them in an array. - $base_theme = array(); - $ancestor = $theme; - while ($ancestor && isset($themes[$ancestor]->base_theme)) { - $ancestor = $themes[$ancestor]->base_theme; - $base_theme[] = $themes[$ancestor]; - } - _drupal_theme_initialize($themes[$theme], array_reverse($base_theme)); - - // Themes can have alter functions, so reset the drupal_alter() cache. - drupal_static_reset('drupal_alter'); - - // Provide the page with information about the theme that's used, so that a - // later Ajax request can be rendered using the same theme. - // @see ajax_base_page_theme() - $setting['ajaxPageState'] = array( - 'theme' => $theme_key, - 'theme_token' => drupal_get_token($theme_key), - ); - drupal_add_js($setting, 'setting'); -} - -/** - * Initialize the theme system given already loaded information. This - * function is useful to initialize a theme when no database is present. - * - * @param $theme - * An object with the following information: - * filename - * The .info file for this theme. The 'path' to - * the theme will be in this file's directory. (Required) - * owner - * The path to the .theme file or the .engine file to load for - * the theme. (Required) - * stylesheet - * The primary stylesheet for the theme. (Optional) - * engine - * The name of theme engine to use. (Optional) - * @param $base_theme - * An optional array of objects that represent the 'base theme' if the - * theme is meant to be derivative of another theme. It requires - * the same information as the $theme object. It should be in - * 'oldest first' order, meaning the top level of the chain will - * be first. - * @param $registry_callback - * The callback to invoke to set the theme registry. - */ -function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callback = '_theme_load_registry') { - global $theme_info, $base_theme_info, $theme_engine, $theme_path; - $theme_info = $theme; - $base_theme_info = $base_theme; - - $theme_path = dirname($theme->filename); - - // Prepare stylesheets from this theme as well as all ancestor themes. - // We work it this way so that we can have child themes override parent - // theme stylesheets easily. - $final_stylesheets = array(); - - // Grab stylesheets from base theme - foreach ($base_theme as $base) { - if (!empty($base->stylesheets)) { - foreach ($base->stylesheets as $media => $stylesheets) { - foreach ($stylesheets as $name => $stylesheet) { - $final_stylesheets[$media][$name] = $stylesheet; - } - } - } - } - - // Add stylesheets used by this theme. - if (!empty($theme->stylesheets)) { - foreach ($theme->stylesheets as $media => $stylesheets) { - foreach ($stylesheets as $name => $stylesheet) { - $final_stylesheets[$media][$name] = $stylesheet; - } - } - } - - // And now add the stylesheets properly - foreach ($final_stylesheets as $media => $stylesheets) { - foreach ($stylesheets as $stylesheet) { - drupal_add_css($stylesheet, array('group' => CSS_THEME, 'every_page' => TRUE, 'media' => $media)); - } - } - - // Do basically the same as the above for scripts - $final_scripts = array(); - - // Grab scripts from base theme - foreach ($base_theme as $base) { - if (!empty($base->scripts)) { - foreach ($base->scripts as $name => $script) { - $final_scripts[$name] = $script; - } - } - } - - // Add scripts used by this theme. - if (!empty($theme->scripts)) { - foreach ($theme->scripts as $name => $script) { - $final_scripts[$name] = $script; - } - } - - // Add scripts used by this theme. - foreach ($final_scripts as $script) { - drupal_add_js($script, array('group' => JS_THEME, 'every_page' => TRUE)); - } - - $theme_engine = NULL; - - // Initialize the theme. - if (isset($theme->engine)) { - // Include the engine. - include_once DRUPAL_ROOT . '/' . $theme->owner; - - $theme_engine = $theme->engine; - if (function_exists($theme_engine . '_init')) { - foreach ($base_theme as $base) { - call_user_func($theme_engine . '_init', $base); - } - call_user_func($theme_engine . '_init', $theme); - } - } - else { - // include non-engine theme files - foreach ($base_theme as $base) { - // Include the theme file or the engine. - if (!empty($base->owner)) { - include_once DRUPAL_ROOT . '/' . $base->owner; - } - } - // and our theme gets one too. - if (!empty($theme->owner)) { - include_once DRUPAL_ROOT . '/' . $theme->owner; - } - } - - if (isset($registry_callback)) { - _theme_registry_callback($registry_callback, array($theme, $base_theme, $theme_engine)); - } -} - -/** - * Get the theme registry. - * - * @param $complete - * Optional boolean to indicate whether to return the complete theme registry - * array or an instance of the ThemeRegistry class. If TRUE, the complete - * theme registry array will be returned. This is useful if you want to - * foreach over the whole registry, use array_* functions or inspect it in a - * debugger. If FALSE, an instance of the ThemeRegistry class will be - * returned, this provides an ArrayObject which allows it to be accessed - * with array syntax and isset(), and should be more lightweight - * than the full registry. Defaults to TRUE. - * - * @return - * The complete theme registry array, or an instance of the ThemeRegistry - * class. - */ -function theme_get_registry($complete = TRUE) { - static $theme_registry = array(); - $key = (int) $complete; - - if (!isset($theme_registry[$key])) { - list($callback, $arguments) = _theme_registry_callback(); - if (!$complete) { - $arguments[] = FALSE; - } - $theme_registry[$key] = call_user_func_array($callback, $arguments); - } - - return $theme_registry[$key]; -} - -/** - * Set the callback that will be used by theme_get_registry() to fetch the registry. - * - * @param $callback - * The name of the callback function. - * @param $arguments - * The arguments to pass to the function. - */ -function _theme_registry_callback($callback = NULL, array $arguments = array()) { - static $stored; - if (isset($callback)) { - $stored = array($callback, $arguments); - } - return $stored; -} - -/** - * Get the theme_registry cache; if it doesn't exist, build it. - * - * @param $theme - * The loaded $theme object as returned by list_themes(). - * @param $base_theme - * An array of loaded $theme objects representing the ancestor themes in - * oldest first order. - * @param $theme_engine - * The name of the theme engine. - * @param $complete - * Whether to load the complete theme registry or an instance of the - * ThemeRegistry class. - * - * @return - * The theme registry array, or an instance of the ThemeRegistry class. - */ -function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL, $complete = TRUE) { - if ($complete) { - // Check the theme registry cache; if it exists, use it. - $cached = cache()->get("theme_registry:$theme->name"); - if (isset($cached->data)) { - $registry = $cached->data; - } - else { - // If not, build one and cache it. - $registry = _theme_build_registry($theme, $base_theme, $theme_engine); - // Only persist this registry if all modules are loaded. This assures a - // complete set of theme hooks. - if (module_load_all(NULL)) { - _theme_save_registry($theme, $registry); - } - } - return $registry; - } - else { - return new ThemeRegistry('theme_registry:runtime:' . $theme->name, 'cache'); - } -} - -/** - * Write the theme_registry cache into the database. - */ -function _theme_save_registry($theme, $registry) { - cache()->set("theme_registry:$theme->name", $registry); -} - -/** - * Force the system to rebuild the theme registry; this should be called - * when modules are added to the system, or when a dynamic system needs - * to add more theme hooks. - */ -function drupal_theme_rebuild() { - cache()->deletePrefix('theme_registry'); -} - -/** - * Builds the run-time theme registry. - * - * Extends DrupalCacheArray to allow the theme registry to be accessed as a - * complete registry, while internally caching only the parts of the registry - * that are actually in use on the site. On cache misses the complete - * theme registry is loaded and used to update the run-time cache. - */ -class ThemeRegistry Extends DrupalCacheArray { - - /** - * Whether the partial registry can be persisted to the cache. - * - * This is only allowed if all modules and the request method is GET. theme() - * should be very rarely called on POST requests and this avoids polluting - * the runtime cache. - */ - protected $persistable; - - /** - * The complete theme registry array. - */ - protected $completeRegistry; - - function __construct($cid, $bin) { - $this->cid = $cid; - $this->bin = $bin; - $this->persistable = module_load_all(NULL) && $_SERVER['REQUEST_METHOD'] == 'GET'; - - $data = array(); - if ($this->persistable && $cached = cache($this->bin)->get($this->cid)) { - $data = $cached->data; - } - else { - $complete_registry = theme_get_registry(); - if ($this->persistable) { - // If there is no runtime cache stored, fetch the full theme registry, - // but then initialize each value to NULL. This allows - // offsetExists() to function correctly on non-registered theme hooks - // without triggering a call to resolveCacheMiss(). - $data = array_fill_keys(array_keys($complete_registry), NULL); - $this->set($this->cid, $data, $this->bin); - $this->completeRegistry = $complete_registry; - } - else { - $data = $complete_registry; - } - } - $this->storage = $data; - } - - public function offsetExists($offset) { - // Since the theme registry allows for theme hooks to be requested that - // are not registered, just check the existence of the key in the registry. - // Use array_key_exists() here since a NULL value indicates that the theme - // hook exists but has not yet been requested. - return array_key_exists($offset, $this->storage); - } - - public function offsetGet($offset) { - // If the offset is set but empty, it is a registered theme hook that has - // not yet been requested. Offsets that do not exist at all were not - // registered in hook_theme(). - if (isset($this->storage[$offset])) { - return $this->storage[$offset]; - } - elseif (array_key_exists($offset, $this->storage)) { - return $this->resolveCacheMiss($offset); - } - } - - public function resolveCacheMiss($offset) { - if (!isset($this->completeRegistry)) { - $this->completeRegistry = theme_get_registry(); - } - $this->storage[$offset] = $this->completeRegistry[$offset]; - if ($this->persistable) { - $this->persist($offset); - } - return $this->storage[$offset]; - } - - public function set($cid, $data, $bin, $lock = TRUE) { - $lock_name = $cid . ':' . $bin; - if (!$lock || lock_acquire($lock_name)) { - if ($cached = cache($bin)->get($cid)) { - // Use array merge instead of union so that filled in values in $data - // overwrite empty values in the current cache. - $data = array_merge($cached->data, $data); - } - cache($bin)->set($cid, $data); - if ($lock) { - lock_release($lock_name); - } - } - } -} - -/** - * Process a single implementation of hook_theme(). - * - * @param $cache - * The theme registry that will eventually be cached; It is an associative - * array keyed by theme hooks, whose values are associative arrays describing - * the hook: - * - 'type': The passed-in $type. - * - 'theme path': The passed-in $path. - * - 'function': The name of the function generating output for this theme - * hook. Either defined explicitly in hook_theme() or, if neither 'function' - * nor 'template' is defined, then the default theme function name is used. - * The default theme function name is the theme hook prefixed by either - * 'theme_' for modules or '$name_' for everything else. If 'function' is - * defined, 'template' is not used. - * - 'template': The filename of the template generating output for this - * theme hook. The template is in the directory defined by the 'path' key of - * hook_theme() or defaults to $path. - * - 'variables': The variables for this theme hook as defined in - * hook_theme(). If there is more than one implementation and 'variables' is - * not specified in a later one, then the previous definition is kept. - * - 'render element': The renderable element for this theme hook as defined - * in hook_theme(). If there is more than one implementation and - * 'render element' is not specified in a later one, then the previous - * definition is kept. - * - 'preprocess functions': See theme() for detailed documentation. - * - 'process functions': See theme() for detailed documentation. - * @param $name - * The name of the module, theme engine, base theme engine, theme or base - * theme implementing hook_theme(). - * @param $type - * One of 'module', 'theme_engine', 'base_theme_engine', 'theme', or - * 'base_theme'. Unlike regular hooks that can only be implemented by modules, - * each of these can implement hook_theme(). _theme_process_registry() is - * called in aforementioned order and new entries override older ones. For - * example, if a theme hook is both defined by a module and a theme, then the - * definition in the theme will be used. - * @param $theme - * The loaded $theme object as returned from list_themes(). - * @param $path - * The directory where $name is. For example, modules/system or - * themes/bartik. - * - * @see theme() - * @see _theme_process_registry() - * @see hook_theme() - * @see list_themes() - */ -function _theme_process_registry(&$cache, $name, $type, $theme, $path) { - $result = array(); - - // Processor functions work in two distinct phases with the process - // functions always being executed after the preprocess functions. - $variable_process_phases = array( - 'preprocess functions' => 'preprocess', - 'process functions' => 'process', - ); - - $hook_defaults = array( - 'variables' => TRUE, - 'render element' => TRUE, - 'pattern' => TRUE, - 'base hook' => TRUE, - ); - - // Invoke the hook_theme() implementation, process what is returned, and - // merge it into $cache. - $function = $name . '_theme'; - if (function_exists($function)) { - $result = $function($cache, $type, $theme, $path); - foreach ($result as $hook => $info) { - // When a theme or engine overrides a module's theme function - // $result[$hook] will only contain key/value pairs for information being - // overridden. Pull the rest of the information from what was defined by - // an earlier hook. - - // Fill in the type and path of the module, theme, or engine that - // implements this theme function. - $result[$hook]['type'] = $type; - $result[$hook]['theme path'] = $path; - - // If function and file are omitted, default to standard naming - // conventions. - if (!isset($info['template']) && !isset($info['function'])) { - $result[$hook]['function'] = ($type == 'module' ? 'theme_' : $name . '_') . $hook; - } - - if (isset($cache[$hook]['includes'])) { - $result[$hook]['includes'] = $cache[$hook]['includes']; - } - - // If the theme implementation defines a file, then also use the path - // that it defined. Otherwise use the default path. This allows - // system.module to declare theme functions on behalf of core .include - // files. - if (isset($info['file'])) { - $include_file = isset($info['path']) ? $info['path'] : $path; - $include_file .= '/' . $info['file']; - include_once DRUPAL_ROOT . '/' . $include_file; - $result[$hook]['includes'][] = $include_file; - } - - // If the default keys are not set, use the default values registered - // by the module. - if (isset($cache[$hook])) { - $result[$hook] += array_intersect_key($cache[$hook], $hook_defaults); - } - - // The following apply only to theming hooks implemented as templates. - if (isset($info['template'])) { - // Prepend the current theming path when none is set. - if (!isset($info['path'])) { - $result[$hook]['template'] = $path . '/' . $info['template']; - } - } - - // Allow variable processors for all theming hooks, whether the hook is - // implemented as a template or as a function. - foreach ($variable_process_phases as $phase_key => $phase) { - // Check for existing variable processors. Ensure arrayness. - if (!isset($info[$phase_key]) || !is_array($info[$phase_key])) { - $info[$phase_key] = array(); - $prefixes = array(); - if ($type == 'module') { - // Default variable processor prefix. - $prefixes[] = 'template'; - // Add all modules so they can intervene with their own variable - // processors. This allows them to provide variable processors even - // if they are not the owner of the current hook. - $prefixes += module_list(); - } - elseif ($type == 'theme_engine' || $type == 'base_theme_engine') { - // Theme engines get an extra set that come before the normally - // named variable processors. - $prefixes[] = $name . '_engine'; - // The theme engine registers on behalf of the theme using the - // theme's name. - $prefixes[] = $theme; - } - else { - // This applies when the theme manually registers their own variable - // processors. - $prefixes[] = $name; - } - foreach ($prefixes as $prefix) { - // Only use non-hook-specific variable processors for theming hooks - // implemented as templates. See theme(). - if (isset($info['template']) && function_exists($prefix . '_' . $phase)) { - $info[$phase_key][] = $prefix . '_' . $phase; - } - if (function_exists($prefix . '_' . $phase . '_' . $hook)) { - $info[$phase_key][] = $prefix . '_' . $phase . '_' . $hook; - } - } - } - // Check for the override flag and prevent the cached variable - // processors from being used. This allows themes or theme engines to - // remove variable processors set earlier in the registry build. - if (!empty($info['override ' . $phase_key])) { - // Flag not needed inside the registry. - unset($result[$hook]['override ' . $phase_key]); - } - elseif (isset($cache[$hook][$phase_key]) && is_array($cache[$hook][$phase_key])) { - $info[$phase_key] = array_merge($cache[$hook][$phase_key], $info[$phase_key]); - } - $result[$hook][$phase_key] = $info[$phase_key]; - } - } - - // Merge the newly created theme hooks into the existing cache. - $cache = $result + $cache; - } - - // Let themes have variable processors even if they didn't register a template. - if ($type == 'theme' || $type == 'base_theme') { - foreach ($cache as $hook => $info) { - // Check only if not registered by the theme or engine. - if (empty($result[$hook])) { - foreach ($variable_process_phases as $phase_key => $phase) { - if (!isset($info[$phase_key])) { - $cache[$hook][$phase_key] = array(); - } - // Only use non-hook-specific variable processors for theming hooks - // implemented as templates. See theme(). - if (isset($info['template']) && function_exists($name . '_' . $phase)) { - $cache[$hook][$phase_key][] = $name . '_' . $phase; - } - if (function_exists($name . '_' . $phase . '_' . $hook)) { - $cache[$hook][$phase_key][] = $name . '_' . $phase . '_' . $hook; - $cache[$hook]['theme path'] = $path; - } - // Ensure uniqueness. - $cache[$hook][$phase_key] = array_unique($cache[$hook][$phase_key]); - } - } - } - } -} - -/** - * Build the theme registry cache. - * - * @param $theme - * The loaded $theme object as returned by list_themes(). - * @param $base_theme - * An array of loaded $theme objects representing the ancestor themes in - * oldest first order. - * @param $theme_engine - * The name of the theme engine. - */ -function _theme_build_registry($theme, $base_theme, $theme_engine) { - $cache = array(); - // First, process the theme hooks advertised by modules. This will - // serve as the basic registry. Since the list of enabled modules is the same - // regardless of the theme used, this is cached in its own entry to save - // building it for every theme. - if ($cached = cache_get('theme_registry:build:modules')) { - $cache = $cached->data; - } - else { - foreach (module_implements('theme') as $module) { - _theme_process_registry($cache, $module, 'module', $module, drupal_get_path('module', $module)); - } - // Only cache this registry if all modules are loaded. - if (module_load_all(NULL)) { - cache_set('theme_registry:build:modules', $cache); - } - } - - // Process each base theme. - foreach ($base_theme as $base) { - // If the base theme uses a theme engine, process its hooks. - $base_path = dirname($base->filename); - if ($theme_engine) { - _theme_process_registry($cache, $theme_engine, 'base_theme_engine', $base->name, $base_path); - } - _theme_process_registry($cache, $base->name, 'base_theme', $base->name, $base_path); - } - - // And then the same thing, but for the theme. - if ($theme_engine) { - _theme_process_registry($cache, $theme_engine, 'theme_engine', $theme->name, dirname($theme->filename)); - } - - // Finally, hooks provided by the theme itself. - _theme_process_registry($cache, $theme->name, 'theme', $theme->name, dirname($theme->filename)); - - // Let modules alter the registry. - drupal_alter('theme_registry', $cache); - - // Optimize the registry to not have empty arrays for functions. - foreach ($cache as $hook => $info) { - foreach (array('preprocess functions', 'process functions') as $phase) { - if (empty($info[$phase])) { - unset($cache[$hook][$phase]); - } - } - } - return $cache; -} - -/** - * Return a list of all currently available themes. - * - * Retrieved from the database, if available and the site is not in maintenance - * mode; otherwise compiled freshly from the filesystem. - * - * @param $refresh - * Whether to reload the list of themes from the database. Defaults to FALSE. - * - * @return - * An associative array of the currently available themes. The keys are the - * names of the themes and the values are objects having the following - * properties: - * - 'filename': The name of the .info file. - * - 'name': The name of the theme. - * - 'status': 1 for enabled, 0 for disabled themes. - * - 'info': The contents of the .info file. - * - 'stylesheets': A two dimensional array, using the first key for the - * 'media' attribute (e.g. 'all'), the second for the name of the file - * (e.g. style.css). The value is a complete filepath - * (e.g. themes/bartik/style.css). - * - 'scripts': An associative array of JavaScripts, using the filename as key - * and the complete filepath as value. - * - 'engine': The name of the theme engine. - * - 'base theme': The name of the base theme. - */ -function list_themes($refresh = FALSE) { - $list = &drupal_static(__FUNCTION__, array()); - - if ($refresh) { - $list = array(); - system_list_reset(); - } - - if (empty($list)) { - $list = array(); - $themes = array(); - // Extract from the database only when it is available. - // Also check that the site is not in the middle of an install or update. - if (!defined('MAINTENANCE_MODE')) { - try { - $themes = system_list('theme'); - } - catch (Exception $e) { - // If the database is not available, rebuild the theme data. - $themes = _system_rebuild_theme_data(); - } - } - else { - // Scan the installation when the database should not be read. - $themes = _system_rebuild_theme_data(); - } - - foreach ($themes as $theme) { - foreach ($theme->info['stylesheets'] as $media => $stylesheets) { - foreach ($stylesheets as $stylesheet => $path) { - $theme->stylesheets[$media][$stylesheet] = $path; - } - } - foreach ($theme->info['scripts'] as $script => $path) { - $theme->scripts[$script] = $path; - } - if (isset($theme->info['engine'])) { - $theme->engine = $theme->info['engine']; - } - if (isset($theme->info['base theme'])) { - $theme->base_theme = $theme->info['base theme']; - } - // Status is normally retrieved from the database. Add zero values when - // read from the installation directory to prevent notices. - if (!isset($theme->status)) { - $theme->status = 0; - } - $list[$theme->name] = $theme; - } - } - - return $list; -} - -/** - * Generates themed output. - * - * All requests for themed output must go through this function. It examines - * the request and routes it to the appropriate theme function or template, by - * checking the theme registry. - * - * The first argument to this function is the name of the theme hook. For - * instance, to theme a table, the theme hook name is 'table'. By default, this - * theme hook could be implemented by a function called 'theme_table' or a - * template file called 'table.tpl.php', but hook_theme() can override the - * default function or template name. - * - * If the implementation is a template file, several functions are called - * before the template file is invoked, to modify the $variables array. These - * fall into the "preprocessing" phase and the "processing" phase, and are - * executed (if they exist), in the following order (note that in the following - * list, HOOK indicates the theme hook name, MODULE indicates a module name, - * THEME indicates a theme name, and ENGINE indicates a theme engine name): - * - template_preprocess(&$variables, $hook): Creates a default set of variables - * for all theme hooks. - * - template_preprocess_HOOK(&$variables): Should be implemented by - * the module that registers the theme hook, to set up default variables. - * - MODULE_preprocess(&$variables, $hook): hook_preprocess() is invoked on all - * implementing modules. - * - MODULE_preprocess_HOOK(&$variables): hook_preprocess_HOOK() is invoked on - * all implementing modules, so that modules that didn't define the theme hook - * can alter the variables. - * - ENGINE_engine_preprocess(&$variables, $hook): Allows the theme engine to - * set necessary variables for all theme hooks. - * - ENGINE_engine_preprocess_HOOK(&$variables): Allows the theme engine to set - * necessary variables for the particular theme hook. - * - THEME_preprocess(&$variables, $hook): Allows the theme to set necessary - * variables for all theme hooks. - * - THEME_preprocess_HOOK(&$variables): Allows the theme to set necessary - * variables specific to the particular theme hook. - * - template_process(&$variables, $hook): Creates a default set of variables - * for all theme hooks. - * - template_process_HOOK(&$variables): This is the first processor specific - * to the theme hook; it should be implemented by the module that registers - * it. - * - MODULE_process(&$variables, $hook): hook_process() is invoked on all - * implementing modules. - * - MODULE_process_HOOK(&$variables): hook_process_HOOK() is invoked on - * on all implementing modules, so that modules that didn't define the theme - * hook can alter the variables. - * - ENGINE_engine_process(&$variables, $hook): Allows the theme engine to set - * necessary variables for all theme hooks. - * - ENGINE_engine_process_HOOK(&$variables): Allows the theme engine to set - * necessary variables for the particular theme hook. - * - ENGINE_process(&$variables, $hook): Allows the theme engine to process the - * variables. - * - ENGINE_process_HOOK(&$variables): Allows the theme engine to process the - * variables specific to the theme hook. - * - THEME_process(&$variables, $hook): Allows the theme to process the - * variables. - * - THEME_process_HOOK(&$variables): Allows the theme to process the - * variables specific to the theme hook. - * - * If the implementation is a function, only the theme-hook-specific preprocess - * and process functions (the ones ending in _HOOK) are called from the - * list above. This is because theme hooks with function implementations - * need to be fast, and calling the non-theme-hook-specific preprocess and - * process functions for them would incur a noticeable performance penalty. - * - * There are two special variables that these preprocess and process functions - * can set: 'theme_hook_suggestion' and 'theme_hook_suggestions'. These will be - * merged together to form a list of 'suggested' alternate theme hooks to use, - * in reverse order of priority. theme_hook_suggestion will always be a higher - * priority than items in theme_hook_suggestions. theme() will use the - * highest priority implementation that exists. If none exists, theme() will - * use the implementation for the theme hook it was called with. These - * suggestions are similar to and are used for similar reasons as calling - * theme() with an array as the $hook parameter (see below). The difference - * is whether the suggestions are determined by the code that calls theme() or - * by a preprocess or process function. - * - * @param $hook - * The name of the theme hook to call. If the name contains a - * double-underscore ('__') and there isn't an implementation for the full - * name, the part before the '__' is checked. This allows a fallback to a more - * generic implementation. For example, if theme('links__node', ...) is - * called, but there is no implementation of that theme hook, then the 'links' - * implementation is used. This process is iterative, so if - * theme('links__contextual__node', ...) is called, theme() checks for the - * following implementations, and uses the first one that exists: - * - links__contextual__node - * - links__contextual - * - links - * This allows themes to create specific theme implementations for named - * objects and contexts of otherwise generic theme hooks. The $hook parameter - * may also be an array, in which case the first theme hook that has an - * implementation is used. This allows for the code that calls theme() to - * explicitly specify the fallback order in a situation where using the '__' - * convention is not desired or is insufficient. - * @param $variables - * An associative array of variables to merge with defaults from the theme - * registry, pass to preprocess and process functions for modification, and - * finally, pass to the function or template implementing the theme hook. - * Alternatively, this can be a renderable array, in which case, its - * properties are mapped to variables expected by the theme hook - * implementations. - * - * @return - * An HTML string representing the themed output. - */ -function theme($hook, $variables = array()) { - static $hooks = NULL; - - // If called before all modules are loaded, we do not necessarily have a full - // theme registry to work with, and therefore cannot process the theme - // request properly. See also _theme_load_registry(). - if (!module_load_all(NULL) && !defined('MAINTENANCE_MODE')) { - throw new Exception(t('theme() may not be called until all modules are loaded.')); - } - - if (!isset($hooks)) { - drupal_theme_initialize(); - $hooks = theme_get_registry(FALSE); - } - - // If an array of hook candidates were passed, use the first one that has an - // implementation. - if (is_array($hook)) { - foreach ($hook as $candidate) { - if (isset($hooks[$candidate])) { - break; - } - } - $hook = $candidate; - } - - // If there's no implementation, check for more generic fallbacks. If there's - // still no implementation, log an error and return an empty string. - if (!isset($hooks[$hook])) { - // Iteratively strip everything after the last '__' delimiter, until an - // implementation is found. - while ($pos = strrpos($hook, '__')) { - $hook = substr($hook, 0, $pos); - if (isset($hooks[$hook])) { - break; - } - } - if (!isset($hooks[$hook])) { - // Only log a message when not trying theme suggestions ($hook being an - // array). - if (!isset($candidate)) { - watchdog('theme', 'Theme key "@key" not found.', array('@key' => $hook), WATCHDOG_WARNING); - } - return ''; - } - } - - $info = $hooks[$hook]; - global $theme_path; - $temp = $theme_path; - // point path_to_theme() to the currently used theme path: - $theme_path = $info['theme path']; - - // Include a file if the theme function or variable processor is held elsewhere. - if (!empty($info['includes'])) { - foreach ($info['includes'] as $include_file) { - include_once DRUPAL_ROOT . '/' . $include_file; - } - } - - // If a renderable array is passed as $variables, then set $variables to - // the arguments expected by the theme function. - if (isset($variables['#theme']) || isset($variables['#theme_wrappers'])) { - $element = $variables; - $variables = array(); - if (isset($info['variables'])) { - foreach (array_keys($info['variables']) as $name) { - if (isset($element["#$name"])) { - $variables[$name] = $element["#$name"]; - } - } - } - else { - $variables[$info['render element']] = $element; - } - } - - // Merge in argument defaults. - if (!empty($info['variables'])) { - $variables += $info['variables']; - } - elseif (!empty($info['render element'])) { - $variables += array($info['render element'] => array()); - } - - // Invoke the variable processors, if any. The processors may specify - // alternate suggestions for which hook's template/function to use. If the - // hook is a suggestion of a base hook, invoke the variable processors of - // the base hook, but retain the suggestion as a high priority suggestion to - // be used unless overridden by a variable processor function. - if (isset($info['base hook'])) { - $base_hook = $info['base hook']; - $base_hook_info = $hooks[$base_hook]; - if (isset($base_hook_info['preprocess functions']) || isset($base_hook_info['process functions'])) { - $variables['theme_hook_suggestion'] = $hook; - $hook = $base_hook; - $info = $base_hook_info; - } - } - if (isset($info['preprocess functions']) || isset($info['process functions'])) { - $variables['theme_hook_suggestions'] = array(); - foreach (array('preprocess functions', 'process functions') as $phase) { - if (!empty($info[$phase])) { - foreach ($info[$phase] as $processor_function) { - if (function_exists($processor_function)) { - // We don't want a poorly behaved process function changing $hook. - $hook_clone = $hook; - $processor_function($variables, $hook_clone); - } - } - } - } - // If the preprocess/process functions specified hook suggestions, and the - // suggestion exists in the theme registry, use it instead of the hook that - // theme() was called with. This allows the preprocess/process step to - // route to a more specific theme hook. For example, a function may call - // theme('node', ...), but a preprocess function can add 'node__article' as - // a suggestion, enabling a theme to have an alternate template file for - // article nodes. Suggestions are checked in the following order: - // - The 'theme_hook_suggestion' variable is checked first. It overrides - // all others. - // - The 'theme_hook_suggestions' variable is checked in FILO order, so the - // last suggestion added to the array takes precedence over suggestions - // added earlier. - $suggestions = array(); - if (!empty($variables['theme_hook_suggestions'])) { - $suggestions = $variables['theme_hook_suggestions']; - } - if (!empty($variables['theme_hook_suggestion'])) { - $suggestions[] = $variables['theme_hook_suggestion']; - } - foreach (array_reverse($suggestions) as $suggestion) { - if (isset($hooks[$suggestion])) { - $info = $hooks[$suggestion]; - break; - } - } - } - - // Generate the output using either a function or a template. - $output = ''; - if (isset($info['function'])) { - if (function_exists($info['function'])) { - $output = $info['function']($variables); - } - } - else { - // Default render function and extension. - $render_function = 'theme_render_template'; - $extension = '.tpl.php'; - - // The theme engine may use a different extension and a different renderer. - global $theme_engine; - if (isset($theme_engine)) { - if ($info['type'] != 'module') { - if (function_exists($theme_engine . '_render_template')) { - $render_function = $theme_engine . '_render_template'; - } - $extension_function = $theme_engine . '_extension'; - if (function_exists($extension_function)) { - $extension = $extension_function(); - } - } - } - - // In some cases, a template implementation may not have had - // template_preprocess() run (for example, if the default implementation is - // a function, but a template overrides that default implementation). In - // these cases, a template should still be able to expect to have access to - // the variables provided by template_preprocess(), so we add them here if - // they don't already exist. We don't want to run template_preprocess() - // twice (it would be inefficient and mess up zebra striping), so we use the - // 'directory' variable to determine if it has already run, which while not - // completely intuitive, is reasonably safe, and allows us to save on the - // overhead of adding some new variable to track that. - if (!isset($variables['directory'])) { - $default_template_variables = array(); - template_preprocess($default_template_variables, $hook); - $variables += $default_template_variables; - } - - // Render the output using the template file. - $template_file = $info['template'] . $extension; - if (isset($info['path'])) { - $template_file = $info['path'] . '/' . $template_file; - } - $output = $render_function($template_file, $variables); - } - - // restore path_to_theme() - $theme_path = $temp; - return $output; -} - -/** - * Return the path to the current themed element. - * - * It can point to the active theme or the module handling a themed implementation. - * For example, when invoked within the scope of a theming call it will depend - * on where the theming function is handled. If implemented from a module, it - * will point to the module. If implemented from the active theme, it will point - * to the active theme. When called outside the scope of a theming call, it will - * always point to the active theme. - */ -function path_to_theme() { - global $theme_path; - - if (!isset($theme_path)) { - drupal_theme_initialize(); - } - - return $theme_path; -} - -/** - * Allow themes and/or theme engines to easily discover overridden theme functions. - * - * @param $cache - * The existing cache of theme hooks to test against. - * @param $prefixes - * An array of prefixes to test, in reverse order of importance. - * - * @return $implementations - * The functions found, suitable for returning from hook_theme; - */ -function drupal_find_theme_functions($cache, $prefixes) { - $implementations = array(); - $functions = get_defined_functions(); - - foreach ($cache as $hook => $info) { - foreach ($prefixes as $prefix) { - // Find theme functions that implement possible "suggestion" variants of - // registered theme hooks and add those as new registered theme hooks. - // The 'pattern' key defines a common prefix that all suggestions must - // start with. The default is the name of the hook followed by '__'. An - // 'base hook' key is added to each entry made for a found suggestion, - // so that common functionality can be implemented for all suggestions of - // the same base hook. To keep things simple, deep hierarchy of - // suggestions is not supported: each suggestion's 'base hook' key - // refers to a base hook, not to another suggestion, and all suggestions - // are found using the base hook's pattern, not a pattern from an - // intermediary suggestion. - $pattern = isset($info['pattern']) ? $info['pattern'] : ($hook . '__'); - if (!isset($info['base hook']) && !empty($pattern)) { - $matches = preg_grep('/^' . $prefix . '_' . $pattern . '/', $functions['user']); - if ($matches) { - foreach ($matches as $match) { - $new_hook = substr($match, strlen($prefix) + 1); - $arg_name = isset($info['variables']) ? 'variables' : 'render element'; - $implementations[$new_hook] = array( - 'function' => $match, - $arg_name => $info[$arg_name], - 'base hook' => $hook, - ); - } - } - } - // Find theme functions that implement registered theme hooks and include - // that in what is returned so that the registry knows that the theme has - // this implementation. - if (function_exists($prefix . '_' . $hook)) { - $implementations[$hook] = array( - 'function' => $prefix . '_' . $hook, - ); - } - } - } - - return $implementations; -} - -/** - * Allow themes and/or theme engines to easily discover overridden templates. - * - * @param $cache - * The existing cache of theme hooks to test against. - * @param $extension - * The extension that these templates will have. - * @param $path - * The path to search. - */ -function drupal_find_theme_templates($cache, $extension, $path) { - $implementations = array(); - - // Collect paths to all sub-themes grouped by base themes. These will be - // used for filtering. This allows base themes to have sub-themes in its - // folder hierarchy without affecting the base themes template discovery. - $theme_paths = array(); - foreach (list_themes() as $theme_info) { - if (!empty($theme_info->base_theme)) { - $theme_paths[$theme_info->base_theme][$theme_info->name] = dirname($theme_info->filename); - } - } - foreach ($theme_paths as $basetheme => $subthemes) { - foreach ($subthemes as $subtheme => $subtheme_path) { - if (isset($theme_paths[$subtheme])) { - $theme_paths[$basetheme] = array_merge($theme_paths[$basetheme], $theme_paths[$subtheme]); - } - } - } - global $theme; - $subtheme_paths = isset($theme_paths[$theme]) ? $theme_paths[$theme] : array(); - - // Escape the periods in the extension. - $regex = '/' . str_replace('.', '\.', $extension) . '$/'; - // Get a listing of all template files in the path to search. - $files = file_scan_directory($path, $regex, array('key' => 'name')); - - // Find templates that implement registered theme hooks and include that in - // what is returned so that the registry knows that the theme has this - // implementation. - foreach ($files as $template => $file) { - // Ignore sub-theme templates for the current theme. - if (strpos($file->uri, str_replace($subtheme_paths, '', $file->uri)) !== 0) { - continue; - } - // Chop off the remaining '.tpl' extension. $template already has the - // rightmost extension removed, but there might still be more, such as with - // .tpl.php, which still has .tpl in $template at this point. - if (($pos = strpos($template, '.tpl')) !== FALSE) { - $template = substr($template, 0, $pos); - } - // Transform - in filenames to _ to match function naming scheme - // for the purposes of searching. - $hook = strtr($template, '-', '_'); - if (isset($cache[$hook])) { - $implementations[$hook] = array( - 'template' => $template, - 'path' => dirname($file->uri), - ); - } - - // Match templates based on the 'template' filename. - foreach ($cache as $hook => $info) { - if (isset($info['template'])) { - $template_candidates = array($info['template'], str_replace($info['theme path'] . '/', '', $info['template'])); - if (in_array($template, $template_candidates)) { - $implementations[$hook] = array( - 'template' => $template, - 'path' => dirname($file->uri), - ); - } - } - } - } - - // Find templates that implement possible "suggestion" variants of registered - // theme hooks and add those as new registered theme hooks. See - // drupal_find_theme_functions() for more information about suggestions and - // the use of 'pattern' and 'base hook'. - $patterns = array_keys($files); - foreach ($cache as $hook => $info) { - $pattern = isset($info['pattern']) ? $info['pattern'] : ($hook . '__'); - if (!isset($info['base hook']) && !empty($pattern)) { - // Transform _ in pattern to - to match file naming scheme - // for the purposes of searching. - $pattern = strtr($pattern, '_', '-'); - - $matches = preg_grep('/^' . $pattern . '/', $patterns); - if ($matches) { - foreach ($matches as $match) { - $file = substr($match, 0, strpos($match, '.')); - // Put the underscores back in for the hook name and register this pattern. - $arg_name = isset($info['variables']) ? 'variables' : 'render element'; - $implementations[strtr($file, '-', '_')] = array( - 'template' => $file, - 'path' => dirname($files[$match]->uri), - $arg_name => $info[$arg_name], - 'base hook' => $hook, - ); - } - } - } - } - return $implementations; -} - -/** - * Retrieve a setting for the current theme or for a given theme. - * - * The final setting is obtained from the last value found in the following - * sources: - * - the default global settings specified in this function - * - the default theme-specific settings defined in any base theme's .info file - * - the default theme-specific settings defined in the theme's .info file - * - the saved values from the global theme settings form - * - the saved values from the theme's settings form - * To only retrieve the default global theme setting, an empty string should be - * given for $theme. - * - * @param $setting_name - * The name of the setting to be retrieved. - * @param $theme - * The name of a given theme; defaults to the current theme. - * - * @return - * The value of the requested setting, NULL if the setting does not exist. - */ -function theme_get_setting($setting_name, $theme = NULL) { - $cache = &drupal_static(__FUNCTION__, array()); - - // If no key is given, use the current theme if we can determine it. - if (!isset($theme)) { - $theme = !empty($GLOBALS['theme_key']) ? $GLOBALS['theme_key'] : ''; - } - - if (empty($cache[$theme])) { - // Set the default values for each global setting. - // To add new global settings, add their default values below, and then - // add form elements to system_theme_settings() in system.admin.inc. - $cache[$theme] = array( - 'default_logo' => 1, - 'logo_path' => '', - 'default_favicon' => 1, - 'favicon_path' => '', - // Use the IANA-registered MIME type for ICO files as default. - 'favicon_mimetype' => 'image/vnd.microsoft.icon', - ); - // Turn on all default features. - $features = _system_default_theme_features(); - foreach ($features as $feature) { - $cache[$theme]['toggle_' . $feature] = 1; - } - - // Get the values for the theme-specific settings from the .info files of - // the theme and all its base themes. - if ($theme) { - $themes = list_themes(); - $theme_object = $themes[$theme]; - - // Create a list which includes the current theme and all its base themes. - if (isset($theme_object->base_themes)) { - $theme_keys = array_keys($theme_object->base_themes); - $theme_keys[] = $theme; - } - else { - $theme_keys = array($theme); - } - foreach ($theme_keys as $theme_key) { - if (!empty($themes[$theme_key]->info['settings'])) { - $cache[$theme] = array_merge($cache[$theme], $themes[$theme_key]->info['settings']); - } - } - } - - // Get the saved global settings from the database. - $cache[$theme] = array_merge($cache[$theme], variable_get('theme_settings', array())); - - if ($theme) { - // Get the saved theme-specific settings from the database. - $cache[$theme] = array_merge($cache[$theme], variable_get('theme_' . $theme . '_settings', array())); - - // If the theme does not support a particular feature, override the global - // setting and set the value to NULL. - if (!empty($theme_object->info['features'])) { - foreach ($features as $feature) { - if (!in_array($feature, $theme_object->info['features'])) { - $cache[$theme]['toggle_' . $feature] = NULL; - } - } - } - - // Generate the path to the logo image. - if ($cache[$theme]['toggle_logo']) { - if ($cache[$theme]['default_logo']) { - $cache[$theme]['logo'] = file_create_url(dirname($theme_object->filename) . '/logo.png'); - } - elseif ($cache[$theme]['logo_path']) { - $cache[$theme]['logo'] = file_create_url($cache[$theme]['logo_path']); - } - } - - // Generate the path to the favicon. - if ($cache[$theme]['toggle_favicon']) { - if ($cache[$theme]['default_favicon']) { - if (file_exists($favicon = dirname($theme_object->filename) . '/favicon.ico')) { - $cache[$theme]['favicon'] = file_create_url($favicon); - } - else { - $cache[$theme]['favicon'] = file_create_url('core/misc/favicon.ico'); - } - } - elseif ($cache[$theme]['favicon_path']) { - $cache[$theme]['favicon'] = file_create_url($cache[$theme]['favicon_path']); - } - else { - $cache[$theme]['toggle_favicon'] = FALSE; - } - } - } - } - - return isset($cache[$theme][$setting_name]) ? $cache[$theme][$setting_name] : NULL; -} - -/** - * Render a system default template, which is essentially a PHP template. - * - * @param $template_file - * The filename of the template to render. - * @param $variables - * A keyed array of variables that will appear in the output. - * - * @return - * The output generated by the template. - */ -function theme_render_template($template_file, $variables) { - extract($variables, EXTR_SKIP); // Extract the variables to a local namespace - ob_start(); // Start output buffering - include DRUPAL_ROOT . '/' . $template_file; // Include the template file - return ob_get_clean(); // End buffering and return its contents -} - -/** - * Enable a given list of themes. - * - * @param $theme_list - * An array of theme names. - */ -function theme_enable($theme_list) { - drupal_clear_css_cache(); - - foreach ($theme_list as $key) { - db_update('system') - ->fields(array('status' => 1)) - ->condition('type', 'theme') - ->condition('name', $key) - ->execute(); - } - - list_themes(TRUE); - menu_rebuild(); - drupal_theme_rebuild(); - - // Invoke hook_themes_enabled() after the themes have been enabled. - module_invoke_all('themes_enabled', $theme_list); -} - -/** - * Disable a given list of themes. - * - * @param $theme_list - * An array of theme names. - */ -function theme_disable($theme_list) { - // Don't disable the default theme. - if ($pos = array_search(variable_get('theme_default', 'bartik'), $theme_list) !== FALSE) { - unset($theme_list[$pos]); - if (empty($theme_list)) { - return; - } - } - - drupal_clear_css_cache(); - - foreach ($theme_list as $key) { - db_update('system') - ->fields(array('status' => 0)) - ->condition('type', 'theme') - ->condition('name', $key) - ->execute(); - } - - list_themes(TRUE); - menu_rebuild(); - drupal_theme_rebuild(); - - // Invoke hook_themes_disabled after the themes have been disabled. - module_invoke_all('themes_disabled', $theme_list); -} - -/** - * @ingroup themeable - * @{ - */ - -/** - * Returns HTML for status and/or error messages, grouped by type. - * - * An invisible heading identifies the messages for assistive technology. - * Sighted users see a colored box. See http://www.w3.org/TR/WCAG-TECHS/H69.html - * for info. - * - * @param $variables - * An associative array containing: - * - display: (optional) Set to 'status' or 'error' to display only messages - * of that type. - */ -function theme_status_messages($variables) { - $display = $variables['display']; - $output = ''; - - $status_heading = array( - 'status' => t('Status message'), - 'error' => t('Error message'), - 'warning' => t('Warning message'), - ); - foreach (drupal_get_messages($display) as $type => $messages) { - $output .= "<div class=\"messages $type\">\n"; - if (!empty($status_heading[$type])) { - $output .= '<h2 class="element-invisible">' . $status_heading[$type] . "</h2>\n"; - } - if (count($messages) > 1) { - $output .= " <ul>\n"; - foreach ($messages as $message) { - $output .= ' <li>' . $message . "</li>\n"; - } - $output .= " </ul>\n"; - } - else { - $output .= $messages[0]; - } - $output .= "</div>\n"; - } - return $output; -} - -/** - * Returns HTML for a link. - * - * All Drupal code that outputs a link should call the l() function. That - * function performs some initial preprocessing, and then, if necessary, calls - * theme('link') for rendering the anchor tag. - * - * To optimize performance for sites that don't need custom theming of links, - * the l() function includes an inline copy of this function, and uses that copy - * if none of the enabled modules or the active theme implement any preprocess - * or process functions or override this theme implementation. - * - * @param $variables - * An associative array containing the keys 'text', 'path', and 'options'. See - * the l() function for information about these variables. - * - * @see l() - */ -function theme_link($variables) { - return '<a href="' . check_plain(url($variables['path'], $variables['options'])) . '"' . drupal_attributes($variables['options']['attributes']) . '>' . ($variables['options']['html'] ? $variables['text'] : check_plain($variables['text'])) . '</a>'; -} - -/** - * Returns HTML for a set of links. - * - * @param $variables - * An associative array containing: - * - links: An associative array of links to be themed. The key for each link - * is used as its css class. Each link should be itself an array, with the - * following elements: - * - title: The link text. - * - href: The link URL. If omitted, the 'title' is shown as a plain text - * item in the links list. - * - html: (optional) Whether or not 'title' is HTML. If set, the title - * will not be passed through check_plain(). - * - attributes: (optional) Attributes for the anchor, or for the <span> tag - * used in its place if no 'href' is supplied. If element 'class' is - * included, it must be an array of one or more class names. - * If the 'href' element is supplied, the entire link array is passed to l() - * as its $options parameter. - * - attributes: A keyed array of attributes for the UL containing the - * list of links. - * - heading: (optional) A heading to precede the links. May be an associative - * array or a string. If it's an array, it can have the following elements: - * - text: The heading text. - * - level: The heading level (e.g. 'h2', 'h3'). - * - class: (optional) An array of the CSS classes for the heading. - * When using a string it will be used as the text of the heading and the - * level will default to 'h2'. Headings should be used on navigation menus - * and any list of links that consistently appears on multiple pages. To - * make the heading invisible use the 'element-invisible' CSS class. Do not - * use 'display:none', which removes it from screen-readers and assistive - * technology. Headings allow screen-reader and keyboard only users to - * navigate to or skip the links. See - * http://juicystudio.com/article/screen-readers-display-none.php and - * http://www.w3.org/TR/WCAG-TECHS/H42.html for more information. - */ -function theme_links($variables) { - $links = $variables['links']; - $attributes = $variables['attributes']; - $heading = $variables['heading']; - global $language_url; - $output = ''; - - if (count($links) > 0) { - $output = ''; - - // Treat the heading first if it is present to prepend it to the - // list of links. - if (!empty($heading)) { - if (is_string($heading)) { - // Prepare the array that will be used when the passed heading - // is a string. - $heading = array( - 'text' => $heading, - // Set the default level of the heading. - 'level' => 'h2', - ); - } - $output .= '<' . $heading['level']; - if (!empty($heading['class'])) { - $output .= drupal_attributes(array('class' => $heading['class'])); - } - $output .= '>' . check_plain($heading['text']) . '</' . $heading['level'] . '>'; - } - - $output .= '<ul' . drupal_attributes($attributes) . '>'; - - $num_links = count($links); - $i = 1; - - foreach ($links as $key => $link) { - $class = array($key); - - // Add first, last and active classes to the list of links to help out themers. - if ($i == 1) { - $class[] = 'first'; - } - if ($i == $num_links) { - $class[] = 'last'; - } - if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '<front>' && drupal_is_front_page())) - && (empty($link['language']) || $link['language']->language == $language_url->language)) { - $class[] = 'active'; - } - $output .= '<li' . drupal_attributes(array('class' => $class)) . '>'; - - if (isset($link['href'])) { - // Pass in $link as $options, they share the same keys. - $output .= l($link['title'], $link['href'], $link); - } - elseif (!empty($link['title'])) { - // Some links are actually not links, but we wrap these in <span> for adding title and class attributes. - if (empty($link['html'])) { - $link['title'] = check_plain($link['title']); - } - $span_attributes = ''; - if (isset($link['attributes'])) { - $span_attributes = drupal_attributes($link['attributes']); - } - $output .= '<span' . $span_attributes . '>' . $link['title'] . '</span>'; - } - - $i++; - $output .= "</li>\n"; - } - - $output .= '</ul>'; - } - - return $output; -} - -/** - * Returns HTML for an image. - * - * @param $variables - * An associative array containing: - * - path: Either the path of the image file (relative to base_path()) or a - * full URL. - * - width: The width of the image (if known). - * - height: The height of the image (if known). - * - alt: The alternative text for text-based browsers. HTML 4 and XHTML 1.0 - * always require an alt attribute. The HTML 5 draft allows the alt - * attribute to be omitted in some cases. Therefore, this variable defaults - * to an empty string, but can be set to NULL for the attribute to be - * omitted. Usually, neither omission nor an empty string satisfies - * accessibility requirements, so it is strongly encouraged for code calling - * theme('image') to pass a meaningful value for this variable. - * - http://www.w3.org/TR/REC-html40/struct/objects.html#h-13.8 - * - http://www.w3.org/TR/xhtml1/dtds.html - * - http://dev.w3.org/html5/spec/Overview.html#alt - * - title: The title text is displayed when the image is hovered in some - * popular browsers. - * - attributes: Associative array of attributes to be placed in the img tag. - */ -function theme_image($variables) { - $attributes = $variables['attributes']; - $attributes['src'] = file_create_url($variables['path']); - - foreach (array('width', 'height', 'alt', 'title') as $key) { - - if (isset($variables[$key])) { - $attributes[$key] = $variables[$key]; - } - } - - return '<img' . drupal_attributes($attributes) . ' />'; -} - -/** - * Returns HTML for a breadcrumb trail. - * - * @param $variables - * An associative array containing: - * - breadcrumb: An array containing the breadcrumb links. - */ -function theme_breadcrumb($variables) { - $breadcrumb = $variables['breadcrumb']; - - if (!empty($breadcrumb)) { - // Provide a navigational heading to give context for breadcrumb links to - // screen-reader users. Make the heading invisible with .element-invisible. - $output = '<h2 class="element-invisible">' . t('You are here') . '</h2>'; - - $output .= '<div class="breadcrumb">' . implode(' » ', $breadcrumb) . '</div>'; - return $output; - } -} - -/** - * Returns HTML for a table. - * - * @param $variables - * An associative array containing: - * - header: An array containing the table headers. Each element of the array - * can be either a localized string or an associative array with the - * following keys: - * - "data": The localized title of the table column. - * - "field": The database field represented in the table column (required - * if user is to be able to sort on this column). - * - "sort": A default sort order for this column ("asc" or "desc"). - * - Any HTML attributes, such as "colspan", to apply to the column header - * cell. - * - rows: An array of table rows. Every row is an array of cells, or an - * associative array with the following keys: - * - "data": an array of cells - * - Any HTML attributes, such as "class", to apply to the table row. - * - "no_striping": a boolean indicating that the row should receive no - * 'even / odd' styling. Defaults to FALSE. - * Each cell can be either a string or an associative array with the - * following keys: - * - "data": The string to display in the table cell. - * - "header": Indicates this cell is a header. - * - Any HTML attributes, such as "colspan", to apply to the table cell. - * Here's an example for $rows: - * @code - * $rows = array( - * // Simple row - * array( - * 'Cell 1', 'Cell 2', 'Cell 3' - * ), - * // Row with attributes on the row and some of its cells. - * array( - * 'data' => array('Cell 1', array('data' => 'Cell 2', 'colspan' => 2)), 'class' => array('funky') - * ) - * ); - * @endcode - * - attributes: An array of HTML attributes to apply to the table tag. - * - caption: A localized string to use for the <caption> tag. - * - colgroups: An array of column groups. Each element of the array can be - * either: - * - An array of columns, each of which is an associative array of HTML - * attributes applied to the COL element. - * - An array of attributes applied to the COLGROUP element, which must - * include a "data" attribute. To add attributes to COL elements, set the - * "data" attribute with an array of columns, each of which is an - * associative array of HTML attributes. - * Here's an example for $colgroup: - * @code - * $colgroup = array( - * // COLGROUP with one COL element. - * array( - * array( - * 'class' => array('funky'), // Attribute for the COL element. - * ), - * ), - * // Colgroup with attributes and inner COL elements. - * array( - * 'data' => array( - * array( - * 'class' => array('funky'), // Attribute for the COL element. - * ), - * ), - * 'class' => array('jazzy'), // Attribute for the COLGROUP element. - * ), - * ); - * @endcode - * These optional tags are used to group and set properties on columns - * within a table. For example, one may easily group three columns and - * apply same background style to all. - * - sticky: Use a "sticky" table header. - * - empty: The message to display in an extra row if table does not have any - * rows. - */ -function theme_table($variables) { - $header = $variables['header']; - $rows = $variables['rows']; - $attributes = $variables['attributes']; - $caption = $variables['caption']; - $colgroups = $variables['colgroups']; - $sticky = $variables['sticky']; - $empty = $variables['empty']; - - // Add sticky headers, if applicable. - if (count($header) && $sticky) { - drupal_add_js('core/misc/tableheader.js'); - // Add 'sticky-enabled' class to the table to identify it for JS. - // This is needed to target tables constructed by this function. - $attributes['class'][] = 'sticky-enabled'; - } - - $output = '<table' . drupal_attributes($attributes) . ">\n"; - - if (isset($caption)) { - $output .= '<caption>' . $caption . "</caption>\n"; - } - - // Format the table columns: - if (count($colgroups)) { - foreach ($colgroups as $number => $colgroup) { - $attributes = array(); - - // Check if we're dealing with a simple or complex column - if (isset($colgroup['data'])) { - foreach ($colgroup as $key => $value) { - if ($key == 'data') { - $cols = $value; - } - else { - $attributes[$key] = $value; - } - } - } - else { - $cols = $colgroup; - } - - // Build colgroup - if (is_array($cols) && count($cols)) { - $output .= ' <colgroup' . drupal_attributes($attributes) . '>'; - $i = 0; - foreach ($cols as $col) { - $output .= ' <col' . drupal_attributes($col) . ' />'; - } - $output .= " </colgroup>\n"; - } - else { - $output .= ' <colgroup' . drupal_attributes($attributes) . " />\n"; - } - } - } - - // Add the 'empty' row message if available. - if (!count($rows) && $empty) { - $header_count = 0; - foreach ($header as $header_cell) { - if (is_array($header_cell)) { - $header_count += isset($header_cell['colspan']) ? $header_cell['colspan'] : 1; - } - else { - $header_count++; - } - } - $rows[] = array(array('data' => $empty, 'colspan' => $header_count, 'class' => array('empty', 'message'))); - } - - // Format the table header: - if (count($header)) { - $ts = tablesort_init($header); - // HTML requires that the thead tag has tr tags in it followed by tbody - // tags. Using ternary operator to check and see if we have any rows. - $output .= (count($rows) ? ' <thead><tr>' : ' <tr>'); - foreach ($header as $cell) { - $cell = tablesort_header($cell, $header, $ts); - $output .= _theme_table_cell($cell, TRUE); - } - // Using ternary operator to close the tags based on whether or not there are rows - $output .= (count($rows) ? " </tr></thead>\n" : "</tr>\n"); - } - else { - $ts = array(); - } - - // Format the table rows: - if (count($rows)) { - $output .= "<tbody>\n"; - $flip = array('even' => 'odd', 'odd' => 'even'); - $class = 'even'; - foreach ($rows as $number => $row) { - $attributes = array(); - - // Check if we're dealing with a simple or complex row - if (isset($row['data'])) { - foreach ($row as $key => $value) { - if ($key == 'data') { - $cells = $value; - } - else { - $attributes[$key] = $value; - } - } - } - else { - $cells = $row; - } - if (count($cells)) { - // Add odd/even class - if (empty($row['no_striping'])) { - $class = $flip[$class]; - $attributes['class'][] = $class; - } - - // Build row - $output .= ' <tr' . drupal_attributes($attributes) . '>'; - $i = 0; - foreach ($cells as $cell) { - $cell = tablesort_cell($cell, $header, $ts, $i++); - $output .= _theme_table_cell($cell); - } - $output .= " </tr>\n"; - } - } - $output .= "</tbody>\n"; - } - - $output .= "</table>\n"; - return $output; -} - -/** - * Returns HTML for a sort icon. - * - * @param $variables - * An associative array containing: - * - style: Set to either 'asc' or 'desc', this determines which icon to show. - */ -function theme_tablesort_indicator($variables) { - if ($variables['style'] == "asc") { - return theme('image', array('path' => 'core/misc/arrow-asc.png', 'width' => 13, 'height' => 13, 'alt' => t('sort ascending'), 'title' => t('sort ascending'))); - } - else { - return theme('image', array('path' => 'core/misc/arrow-desc.png', 'width' => 13, 'height' => 13, 'alt' => t('sort descending'), 'title' => t('sort descending'))); - } -} - -/** - * Returns HTML for a marker for new or updated content. - * - * @param $variables - * An associative array containing: - * - type: Number representing the marker type to display. See MARK_NEW, - * MARK_UPDATED, MARK_READ. - */ -function theme_mark($variables) { - $type = $variables['type']; - global $user; - if ($user->uid) { - if ($type == MARK_NEW) { - return ' <span class="marker">' . t('new') . '</span>'; - } - elseif ($type == MARK_UPDATED) { - return ' <span class="marker">' . t('updated') . '</span>'; - } - } -} - -/** - * Returns HTML for a list or nested list of items. - * - * @param $variables - * An associative array containing: - * - items: A list of items to render. String values are rendered as is. Each - * item can also be an associative array containing: - * - data: The string content of the list item. - * - children: A list of nested child items to render that behave - * identically to 'items', but any non-numeric string keys are treated as - * HTML attributes for the child list that wraps 'children'. - * Any other key/value pairs are used as HTML attributes for the list item - * in 'data'. - * - title: The title of the list. - * - type: The type of list to return (e.g. "ul", "ol"). - * - attributes: The attributes applied to the list element. - */ -function theme_item_list($variables) { - $items = $variables['items']; - $title = $variables['title']; - $type = $variables['type']; - $list_attributes = $variables['attributes']; - - $output = ''; - if ($items) { - $output .= '<' . $type . drupal_attributes($list_attributes) . '>'; - - $num_items = count($items); - $i = 0; - foreach ($items as $key => $item) { - $i++; - $attributes = array(); - - if (is_array($item)) { - $value = ''; - if (isset($item['data'])) { - $value .= $item['data']; - } - $attributes = array_diff_key($item, array('data' => 0, 'children' => 0)); - - // Append nested child list, if any. - if (isset($item['children'])) { - // HTML attributes for the outer list are defined in the 'attributes' - // theme variable, but not inherited by children. For nested lists, - // all non-numeric keys in 'children' are used as list attributes. - $child_list_attributes = array(); - foreach ($item['children'] as $child_key => $child_item) { - if (is_string($child_key)) { - $child_list_attributes[$child_key] = $child_item; - unset($item['children'][$child_key]); - } - } - $value .= theme('item_list', array( - 'items' => $item['children'], - 'type' => $type, - 'attributes' => $child_list_attributes, - )); - } - } - else { - $value = $item; - } - - $attributes['class'][] = ($i % 2 ? 'odd' : 'even'); - if ($i == 1) { - $attributes['class'][] = 'first'; - } - if ($i == $num_items) { - $attributes['class'][] = 'last'; - } - - $output .= '<li' . drupal_attributes($attributes) . '>' . $value . '</li>'; - } - $output .= "</$type>"; - } - - // Only output the list container and title, if there are any list items. - if ($output !== '') { - if ($title !== '') { - $title = '<h3>' . $title . '</h3>'; - } - $output = '<div class="item-list">' . $title . $output . '</div>'; - } - - return $output; -} - -/** - * Returns HTML for a "more help" link. - * - * @param $variables - * An associative array containing: - * - url: The url for the link. - */ -function theme_more_help_link($variables) { - return '<div class="more-help-link">' . l(t('More help'), $variables['url']) . '</div>'; -} - -/** - * Returns HTML for a feed icon. - * - * @param $variables - * An associative array containing: - * - url: An internal system path or a fully qualified external URL of the - * feed. - * - title: A descriptive title of the feed. - */ -function theme_feed_icon($variables) { - $text = t('Subscribe to @feed-title', array('@feed-title' => $variables['title'])); - if ($image = theme('image', array('path' => 'core/misc/feed.png', 'width' => 16, 'height' => 16, 'alt' => $text))) { - return l($image, $variables['url'], array('html' => TRUE, 'attributes' => array('class' => array('feed-icon'), 'title' => $text))); - } -} - -/** - * Returns HTML for a generic HTML tag with attributes. - * - * @param $variables - * An associative array containing: - * - element: An associative array describing the tag: - * - #tag: The tag name to output. Typical tags added to the HTML HEAD: - * - meta: To provide meta information, such as a page refresh. - * - link: To refer to stylesheets and other contextual information. - * - script: To load JavaScript. - * - #attributes: (optional) An array of HTML attributes to apply to the - * tag. - * - #value: (optional) A string containing tag content, such as inline CSS. - * - #value_prefix: (optional) A string to prepend to #value, e.g. a CDATA - * wrapper prefix. - * - #value_suffix: (optional) A string to append to #value, e.g. a CDATA - * wrapper suffix. - */ -function theme_html_tag($variables) { - $element = $variables['element']; - $attributes = isset($element['#attributes']) ? drupal_attributes($element['#attributes']) : ''; - if (!isset($element['#value'])) { - return '<' . $element['#tag'] . $attributes . " />\n"; - } - else { - $output = '<' . $element['#tag'] . $attributes . '>'; - if (isset($element['#value_prefix'])) { - $output .= $element['#value_prefix']; - } - $output .= $element['#value']; - if (isset($element['#value_suffix'])) { - $output .= $element['#value_suffix']; - } - $output .= '</' . $element['#tag'] . ">\n"; - return $output; - } -} - -/** - * Returns HTML for a "more" link, like those used in blocks. - * - * @param $variables - * An associative array containing: - * - url: The url of the main page. - * - title: A descriptive verb for the link, like 'Read more'. - */ -function theme_more_link($variables) { - return '<div class="more-link">' . l(t('More'), $variables['url'], array('attributes' => array('title' => $variables['title']))) . '</div>'; -} - -/** - * Returns HTML for a username, potentially linked to the user's page. - * - * @param $variables - * An associative array containing: - * - account: The user object to format. - * - name: The user's name, sanitized. - * - extra: Additional text to append to the user's name, sanitized. - * - link_path: The path or URL of the user's profile page, home page, or - * other desired page to link to for more information about the user. - * - link_options: An array of options to pass to the l() function's $options - * parameter if linking the user's name to the user's page. - * - attributes_array: An array of attributes to pass to the - * drupal_attributes() function if not linking to the user's page. - * - * @see template_preprocess_username() - * @see template_process_username() - */ -function theme_username($variables) { - if (isset($variables['link_path'])) { - // We have a link path, so we should generate a link using l(). - // Additional classes may be added as array elements like - // $variables['link_options']['attributes']['class'][] = 'myclass'; - $output = l($variables['name'] . $variables['extra'], $variables['link_path'], $variables['link_options']); - } - else { - // Modules may have added important attributes so they must be included - // in the output. Additional classes may be added as array elements like - // $variables['attributes_array']['class'][] = 'myclass'; - $output = '<span' . drupal_attributes($variables['attributes_array']) . '>' . $variables['name'] . $variables['extra'] . '</span>'; - } - return $output; -} - -/** - * Returns HTML for a progress bar. - * - * @param $variables - * An associative array containing: - * - percent: The percentage of the progress. - * - message: A string containing information to be displayed. - */ -function theme_progress_bar($variables) { - $output = '<div id="progress" class="progress">'; - $output .= '<div class="bar"><div class="filled" style="width: ' . $variables['percent'] . '%"></div></div>'; - $output .= '<div class="percentage">' . $variables['percent'] . '%</div>'; - $output .= '<div class="message">' . $variables['message'] . '</div>'; - $output .= '</div>'; - - return $output; -} - -/** - * Returns HTML for an indentation div; used for drag and drop tables. - * - * @param $variables - * An associative array containing: - * - size: Optional. The number of indentations to create. - */ -function theme_indentation($variables) { - $output = ''; - for ($n = 0; $n < $variables['size']; $n++) { - $output .= '<div class="indentation"> </div>'; - } - return $output; -} - -/** - * @} End of "ingroup themeable". - */ - -/** - * Returns HTML output for a single table cell for theme_table(). - * - * @param $cell - * Array of cell information, or string to display in cell. - * @param bool $header - * TRUE if this cell is a table header cell, FALSE if it is an ordinary - * table cell. If $cell is an array with element 'header' set to TRUE, that - * will override the $header parameter. - * - * @return - * HTML for the cell. - */ -function _theme_table_cell($cell, $header = FALSE) { - $attributes = ''; - - if (is_array($cell)) { - $data = isset($cell['data']) ? $cell['data'] : ''; - // Cell's data property can be a string or a renderable array. - if (is_array($data)) { - $data = drupal_render($data); - } - $header |= isset($cell['header']); - unset($cell['data']); - unset($cell['header']); - $attributes = drupal_attributes($cell); - } - else { - $data = $cell; - } - - if ($header) { - $output = "<th$attributes>$data</th>"; - } - else { - $output = "<td$attributes>$data</td>"; - } - - return $output; -} - -/** - * Adds a default set of helper variables for variable processors and templates. - * This comes in before any other preprocess function which makes it possible to - * be used in default theme implementations (non-overridden theme functions). - * - * For more detailed information, see theme(). - * - */ -function template_preprocess(&$variables, $hook) { - global $user; - static $count = array(); - - // Track run count for each hook to provide zebra striping. - // See "template_preprocess_block()" which provides the same feature specific to blocks. - $count[$hook] = isset($count[$hook]) && is_int($count[$hook]) ? $count[$hook] : 1; - $variables['zebra'] = ($count[$hook] % 2) ? 'odd' : 'even'; - $variables['id'] = $count[$hook]++; - - // Tell all templates where they are located. - $variables['directory'] = path_to_theme(); - - // Initialize html class attribute for the current hook. - $variables['classes_array'] = array(drupal_html_class($hook)); - - // Merge in variables that don't depend on hook and don't change during a - // single page request. - // Use the advanced drupal_static() pattern, since this is called very often. - static $drupal_static_fast; - if (!isset($drupal_static_fast)) { - $drupal_static_fast['default_variables'] = &drupal_static(__FUNCTION__); - } - $default_variables = &$drupal_static_fast['default_variables']; - // Global $user object shouldn't change during a page request once rendering - // has started, but if there's an edge case where it does, re-fetch the - // variables appropriate for the new user. - if (!isset($default_variables) || ($user !== $default_variables['user'])) { - $default_variables = _template_preprocess_default_variables(); - } - $variables += $default_variables; -} - -/** - * Returns hook-independent variables to template_preprocess(). - */ -function _template_preprocess_default_variables() { - global $user; - - // Variables that don't depend on a database connection. - $variables = array( - 'attributes_array' => array(), - 'title_attributes_array' => array(), - 'content_attributes_array' => array(), - 'title_prefix' => array(), - 'title_suffix' => array(), - 'user' => $user, - 'db_is_active' => !defined('MAINTENANCE_MODE'), - 'is_admin' => FALSE, - 'logged_in' => FALSE, - ); - - // The user object has no uid property when the database does not exist during - // install. The user_access() check deals with issues when in maintenance mode - // as uid is set but the user.module has not been included. - if (isset($user->uid) && function_exists('user_access')) { - $variables['is_admin'] = user_access('access administration pages'); - $variables['logged_in'] = ($user->uid > 0); - } - - // drupal_is_front_page() might throw an exception. - try { - $variables['is_front'] = drupal_is_front_page(); - } - catch (Exception $e) { - // If the database is not yet available, set default values for these - // variables. - $variables['is_front'] = FALSE; - $variables['db_is_active'] = FALSE; - } - - return $variables; -} - -/** - * A default process function used to alter variables as late as possible. - * - * For more detailed information, see theme(). - * - */ -function template_process(&$variables, $hook) { - // Flatten out classes. - $variables['classes'] = implode(' ', $variables['classes_array']); - - // Flatten out attributes, title_attributes, and content_attributes. - // Because this function can be called very often, and often with empty - // attributes, optimize performance by only calling drupal_attributes() if - // necessary. - $variables['attributes'] = $variables['attributes_array'] ? drupal_attributes($variables['attributes_array']) : ''; - $variables['title_attributes'] = $variables['title_attributes_array'] ? drupal_attributes($variables['title_attributes_array']) : ''; - $variables['content_attributes'] = $variables['content_attributes_array'] ? drupal_attributes($variables['content_attributes_array']) : ''; -} - -/** - * Preprocess variables for html.tpl.php - * - * @see system_elements() - * @see html.tpl.php - */ -function template_preprocess_html(&$variables) { - // Compile a list of classes that are going to be applied to the body element. - // This allows advanced theming based on context (home page, node of certain type, etc.). - // Add a class that tells us whether we're on the front page or not. - $variables['classes_array'][] = $variables['is_front'] ? 'front' : 'not-front'; - // Add a class that tells us whether the page is viewed by an authenticated user or not. - $variables['classes_array'][] = $variables['logged_in'] ? 'logged-in' : 'not-logged-in'; - - // Add information about the number of sidebars. - if (!empty($variables['page']['sidebar_first']) && !empty($variables['page']['sidebar_second'])) { - $variables['classes_array'][] = 'two-sidebars'; - } - elseif (!empty($variables['page']['sidebar_first'])) { - $variables['classes_array'][] = 'one-sidebar sidebar-first'; - } - elseif (!empty($variables['page']['sidebar_second'])) { - $variables['classes_array'][] = 'one-sidebar sidebar-second'; - } - else { - $variables['classes_array'][] = 'no-sidebars'; - } - - // Populate the body classes. - if ($suggestions = theme_get_suggestions(arg(), 'page', '-')) { - foreach ($suggestions as $suggestion) { - if ($suggestion != 'page-front') { - // Add current suggestion to page classes to make it possible to theme - // the page depending on the current page type (e.g. node, admin, user, - // etc.) as well as more specific data like node-12 or node-edit. - $variables['classes_array'][] = drupal_html_class($suggestion); - } - } - } - - // If on an individual node page, add the node type to body classes. - if ($node = menu_get_object()) { - $variables['classes_array'][] = drupal_html_class('node-type-' . $node->type); - } - - // Initializes attributes which are specific to the html and body elements. - $variables['html_attributes_array'] = array(); - $variables['body_attributes_array'] = array(); - - // HTML element attributes. - $variables['html_attributes_array']['xmlns'] = "http://www.w3.org/1999/xhtml"; - $variables['html_attributes_array']['xml:lang'] = $GLOBALS['language']->language; - $variables['html_attributes_array']['dir'] = $GLOBALS['language']->direction ? 'rtl' : 'ltr'; - - // Add favicon. - if (theme_get_setting('toggle_favicon')) { - $favicon = theme_get_setting('favicon'); - $type = theme_get_setting('favicon_mimetype'); - drupal_add_html_head_link(array('rel' => 'shortcut icon', 'href' => drupal_strip_dangerous_protocols($favicon), 'type' => $type)); - } - - // Construct page title. - if (drupal_get_title()) { - $head_title = array( - 'title' => strip_tags(drupal_get_title()), - 'name' => check_plain(variable_get('site_name', 'Drupal')), - ); - } - else { - $head_title = array('name' => check_plain(variable_get('site_name', 'Drupal'))); - if (variable_get('site_slogan', '')) { - $head_title['slogan'] = filter_xss_admin(variable_get('site_slogan', '')); - } - } - $variables['head_title_array'] = $head_title; - $variables['head_title'] = implode(' | ', $head_title); - - // Populate the page template suggestions. - if ($suggestions = theme_get_suggestions(arg(), 'html')) { - $variables['theme_hook_suggestions'] = $suggestions; - } -} - -/** - * Preprocess variables for page.tpl.php - * - * Most themes utilize their own copy of page.tpl.php. The default is located - * inside "modules/system/page.tpl.php". Look in there for the full list of - * variables. - * - * Uses the arg() function to generate a series of page template suggestions - * based on the current path. - * - * Any changes to variables in this preprocessor should also be changed inside - * template_preprocess_maintenance_page() to keep all of them consistent. - * - * @see drupal_render_page() - * @see template_process_page() - * @see page.tpl.php - */ -function template_preprocess_page(&$variables) { - // Move some variables to the top level for themer convenience and template cleanliness. - $variables['show_messages'] = $variables['page']['#show_messages']; - - foreach (system_region_list($GLOBALS['theme']) as $region_key => $region_name) { - if (!isset($variables['page'][$region_key])) { - $variables['page'][$region_key] = array(); - } - } - - // Set up layout variable. - $variables['layout'] = 'none'; - if (!empty($variables['page']['sidebar_first'])) { - $variables['layout'] = 'first'; - } - if (!empty($variables['page']['sidebar_second'])) { - $variables['layout'] = ($variables['layout'] == 'first') ? 'both' : 'second'; - } - - $variables['base_path'] = base_path(); - $variables['front_page'] = url(); - $variables['feed_icons'] = drupal_get_feeds(); - $variables['language'] = $GLOBALS['language']; - $variables['language']->dir = $GLOBALS['language']->direction ? 'rtl' : 'ltr'; - $variables['logo'] = theme_get_setting('logo'); - $variables['main_menu'] = theme_get_setting('toggle_main_menu') ? menu_main_menu() : array(); - $variables['secondary_menu'] = theme_get_setting('toggle_secondary_menu') ? menu_secondary_menu() : array(); - $variables['action_links'] = menu_local_actions(); - $variables['site_name'] = (theme_get_setting('toggle_name') ? filter_xss_admin(variable_get('site_name', 'Drupal')) : ''); - $variables['site_slogan'] = (theme_get_setting('toggle_slogan') ? filter_xss_admin(variable_get('site_slogan', '')) : ''); - $variables['tabs'] = menu_local_tabs(); - - if ($node = menu_get_object()) { - $variables['node'] = $node; - } - - // Populate the page template suggestions. - if ($suggestions = theme_get_suggestions(arg(), 'page')) { - $variables['theme_hook_suggestions'] = $suggestions; - } -} - -/** - * Process variables for page.tpl.php - * - * Perform final addition of variables before passing them into the template. - * To customize these variables, simply set them in an earlier step. - * - * @see template_preprocess_page() - * @see page.tpl.php - */ -function template_process_page(&$variables) { - if (!isset($variables['breadcrumb'])) { - // Build the breadcrumb last, so as to increase the chance of being able to - // re-use the cache of an already rendered menu containing the active link - // for the current page. - // @see menu_tree_page_data() - $variables['breadcrumb'] = theme('breadcrumb', array('breadcrumb' => drupal_get_breadcrumb())); - } - if (!isset($variables['title'])) { - $variables['title'] = drupal_get_title(); - } - - // Generate messages last in order to capture as many as possible for the - // current page. - if (!isset($variables['messages'])) { - $variables['messages'] = $variables['show_messages'] ? theme('status_messages') : ''; - } -} - -/** - * Process variables for html.tpl.php - * - * Perform final addition and modification of variables before passing into - * the template. To customize these variables, call drupal_render() on elements - * in $variables['page'] during THEME_preprocess_page(). - * - * @see template_preprocess_html() - * @see html.tpl.php - */ -function template_process_html(&$variables) { - // Flatten out html_attributes and body_attributes. - $variables['html_attributes'] = drupal_attributes($variables['html_attributes_array']); - $variables['body_attributes'] = drupal_attributes($variables['body_attributes_array']); - - // Render page_top and page_bottom into top level variables. - $variables['page_top'] = drupal_render($variables['page']['page_top']); - $variables['page_bottom'] = drupal_render($variables['page']['page_bottom']); - // Place the rendered HTML for the page body into a top level variable. - $variables['page'] = $variables['page']['#children']; - $variables['page_bottom'] .= drupal_get_js('footer'); - - $variables['head'] = drupal_get_html_head(); - $variables['css'] = drupal_add_css(); - $variables['styles'] = drupal_get_css(); - $variables['scripts'] = drupal_get_js(); -} - -/** - * Generate an array of suggestions from path arguments. - * - * This is typically called for adding to the 'theme_hook_suggestions' or - * 'classes_array' variables from within preprocess functions, when wanting to - * base the additional suggestions on the path of the current page. - * - * @param $args - * An array of path arguments, such as from function arg(). - * @param $base - * A string identifying the base 'thing' from which more specific suggestions - * are derived. For example, 'page' or 'html'. - * @param $delimiter - * The string used to delimit increasingly specific information. The default - * of '__' is appropriate for theme hook suggestions. '-' is appropriate for - * extra classes. - * - * @return - * An array of suggestions, suitable for adding to - * $variables['theme_hook_suggestions'] within a preprocess function or to - * $variables['classes_array'] if the suggestions represent extra CSS classes. - */ -function theme_get_suggestions($args, $base, $delimiter = '__') { - - // Build a list of suggested theme hooks or body classes in order of - // specificity. One suggestion is made for every element of the current path, - // though numeric elements are not carried to subsequent suggestions. For - // example, for $base='page', http://www.example.com/node/1/edit would result - // in the following suggestions and body classes: - // - // page__node page-node - // page__node__% page-node-% - // page__node__1 page-node-1 - // page__node__edit page-node-edit - - $suggestions = array(); - $prefix = $base; - foreach ($args as $arg) { - // Remove slashes or null per SA-CORE-2009-003 and change - (hyphen) to _ - // (underscore). - // - // When we discover templates in @see drupal_find_theme_templates, - // hyphens (-) are converted to underscores (_) before the theme hook - // is registered. We do this because the hyphens used for delimiters - // in hook suggestions cannot be used in the function names of the - // associated preprocess functions. Any page templates designed to be used - // on paths that contain a hyphen are also registered with these hyphens - // converted to underscores so here we must convert any hyphens in path - // arguments to underscores here before fetching theme hook suggestions - // to ensure the templates are appropriately recognized. - $arg = str_replace(array("/", "\\", "\0", '-'), array('', '', '', '_'), $arg); - // The percent acts as a wildcard for numeric arguments since - // asterisks are not valid filename characters on many filesystems. - if (is_numeric($arg)) { - $suggestions[] = $prefix . $delimiter . '%'; - } - $suggestions[] = $prefix . $delimiter . $arg; - if (!is_numeric($arg)) { - $prefix .= $delimiter . $arg; - } - } - if (drupal_is_front_page()) { - // Front templates should be based on root only, not prefixed arguments. - $suggestions[] = $base . $delimiter . 'front'; - } - - return $suggestions; -} - -/** - * The variables array generated here is a mirror of template_preprocess_page(). - * This preprocessor will run its course when theme_maintenance_page() is - * invoked. - * - * An alternate template file of "maintenance-page--offline.tpl.php" can be - * used when the database is offline to hide errors and completely replace the - * content. - * - * The $variables array contains the following arguments: - * - $content - * - * @see maintenance-page.tpl.php - */ -function template_preprocess_maintenance_page(&$variables) { - // Add favicon - if (theme_get_setting('toggle_favicon')) { - $favicon = theme_get_setting('favicon'); - $type = theme_get_setting('favicon_mimetype'); - drupal_add_html_head_link(array('rel' => 'shortcut icon', 'href' => drupal_strip_dangerous_protocols($favicon), 'type' => $type)); - } - - global $theme; - // Retrieve the theme data to list all available regions. - $theme_data = list_themes(); - $regions = $theme_data[$theme]->info['regions']; - - // Get all region content set with drupal_add_region_content(). - foreach (array_keys($regions) as $region) { - // Assign region to a region variable. - $region_content = drupal_get_region_content($region); - isset($variables[$region]) ? $variables[$region] .= $region_content : $variables[$region] = $region_content; - } - - // Setup layout variable. - $variables['layout'] = 'none'; - if (!empty($variables['sidebar_first'])) { - $variables['layout'] = 'first'; - } - if (!empty($variables['sidebar_second'])) { - $variables['layout'] = ($variables['layout'] == 'first') ? 'both' : 'second'; - } - - // Construct page title - if (drupal_get_title()) { - $head_title = array( - 'title' => strip_tags(drupal_get_title()), - 'name' => variable_get('site_name', 'Drupal'), - ); - } - else { - $head_title = array('name' => variable_get('site_name', 'Drupal')); - if (variable_get('site_slogan', '')) { - $head_title['slogan'] = variable_get('site_slogan', ''); - } - } - - // set the default language if necessary - $language = isset($GLOBALS['language']) ? $GLOBALS['language'] : language_default(); - - $variables['head_title_array'] = $head_title; - $variables['head_title'] = implode(' | ', $head_title); - $variables['base_path'] = base_path(); - $variables['front_page'] = url(); - $variables['breadcrumb'] = ''; - $variables['feed_icons'] = ''; - $variables['help'] = ''; - $variables['language'] = $language; - $variables['language']->dir = $language->direction ? 'rtl' : 'ltr'; - $variables['logo'] = theme_get_setting('logo'); - $variables['messages'] = $variables['show_messages'] ? theme('status_messages') : ''; - $variables['main_menu'] = array(); - $variables['secondary_menu'] = array(); - $variables['site_name'] = (theme_get_setting('toggle_name') ? variable_get('site_name', 'Drupal') : ''); - $variables['site_slogan'] = (theme_get_setting('toggle_slogan') ? variable_get('site_slogan', '') : ''); - $variables['tabs'] = ''; - $variables['title'] = drupal_get_title(); - - // Compile a list of classes that are going to be applied to the body element. - $variables['classes_array'][] = 'in-maintenance'; - if (isset($variables['db_is_active']) && !$variables['db_is_active']) { - $variables['classes_array'][] = 'db-offline'; - } - if ($variables['layout'] == 'both') { - $variables['classes_array'][] = 'two-sidebars'; - } - elseif ($variables['layout'] == 'none') { - $variables['classes_array'][] = 'no-sidebars'; - } - else { - $variables['classes_array'][] = 'one-sidebar sidebar-' . $variables['layout']; - } - - // Dead databases will show error messages so supplying this template will - // allow themers to override the page and the content completely. - if (isset($variables['db_is_active']) && !$variables['db_is_active']) { - $variables['theme_hook_suggestion'] = 'maintenance_page__offline'; - } -} - -/** - * The variables array generated here is a mirror of template_process_html(). - * This processor will run its course when theme_maintenance_page() is invoked. - * - * @see maintenance-page.tpl.php - */ -function template_process_maintenance_page(&$variables) { - $variables['head'] = drupal_get_html_head(); - $variables['css'] = drupal_add_css(); - $variables['styles'] = drupal_get_css(); - $variables['scripts'] = drupal_get_js(); -} - -/** - * Preprocess variables for region.tpl.php - * - * Prepare the values passed to the theme_region function to be passed into a - * pluggable template engine. Uses the region name to generate a template file - * suggestions. If none are found, the default region.tpl.php is used. - * - * @see drupal_region_class() - * @see region.tpl.php - */ -function template_preprocess_region(&$variables) { - // Create the $content variable that templates expect. - $variables['content'] = $variables['elements']['#children']; - $variables['region'] = $variables['elements']['#region']; - - $variables['classes_array'][] = drupal_region_class($variables['region']); - $variables['theme_hook_suggestions'][] = 'region__' . $variables['region']; -} - -/** - * Preprocesses variables for theme_username(). - * - * Modules that make any changes to variables like 'name' or 'extra' must insure - * that the final string is safe to include directly in the output by using - * check_plain() or filter_xss(). - * - * @see template_process_username() - */ -function template_preprocess_username(&$variables) { - $account = $variables['account']; - - $variables['extra'] = ''; - if (empty($account->uid)) { - $variables['uid'] = 0; - if (theme_get_setting('toggle_comment_user_verification')) { - $variables['extra'] = ' (' . t('not verified') . ')'; - } - } - else { - $variables['uid'] = (int) $account->uid; - } - - // Set the name to a formatted name that is safe for printing and - // that won't break tables by being too long. Keep an unshortened, - // unsanitized version, in case other preprocess functions want to implement - // their own shortening logic or add markup. If they do so, they must ensure - // that $variables['name'] is safe for printing. - $name = $variables['name_raw'] = format_username($account); - if (drupal_strlen($name) > 20) { - $name = drupal_substr($name, 0, 15) . '...'; - } - $variables['name'] = check_plain($name); - - $variables['profile_access'] = user_access('access user profiles'); - $variables['link_attributes'] = array(); - // Populate link path and attributes if appropriate. - if ($variables['uid'] && $variables['profile_access']) { - // We are linking to a local user. - $variables['link_attributes'] = array('title' => t('View user profile.')); - $variables['link_path'] = 'user/' . $variables['uid']; - } - elseif (!empty($account->homepage)) { - // Like the 'class' attribute, the 'rel' attribute can hold a - // space-separated set of values, so initialize it as an array to make it - // easier for other preprocess functions to append to it. - $variables['link_attributes'] = array('rel' => array('nofollow')); - $variables['link_path'] = $account->homepage; - $variables['homepage'] = $account->homepage; - } - // We do not want the l() function to check_plain() a second time. - $variables['link_options']['html'] = TRUE; - // Set a default class. - $variables['attributes_array'] = array('class' => array('username')); -} - -/** - * Processes variables for theme_username(). - * - * @see template_preprocess_username() - */ -function template_process_username(&$variables) { - // Finalize the link_options array for passing to the l() function. - // This is done in the process phase so that attributes may be added by - // modules or the theme during the preprocess phase. - if (isset($variables['link_path'])) { - // $variables['attributes_array'] contains attributes that should be applied - // regardless of whether a link is being rendered or not. - // $variables['link_attributes'] contains attributes that should only be - // applied if a link is being rendered. Preprocess functions are encouraged - // to use the former unless they want to add attributes on the link only. - // If a link is being rendered, these need to be merged. Some attributes are - // themselves arrays, so the merging needs to be recursive. - $variables['link_options']['attributes'] = array_merge_recursive($variables['link_attributes'], $variables['attributes_array']); - } -} diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc deleted file mode 100644 index fcd87030cb8..00000000000 --- a/core/includes/theme.maintenance.inc +++ /dev/null @@ -1,211 +0,0 @@ -<?php - -/** - * @file - * Theming for maintenance pages. - */ - -/** - * Sets up the theming system for maintenance page. - * - * Used for site installs, updates and when the site is in maintenance mode. - * It also applies when the database is unavailable or bootstrap was not - * complete. Seven is always used for the initial install and update operations. - * In other cases, Bartik is used, but this can be overridden by setting a - * "maintenance_theme" key in the $conf variable in settings.php. - */ -function _drupal_maintenance_theme() { - global $theme, $theme_key, $conf; - - // If $theme is already set, assume the others are set too, and do nothing. - if (isset($theme)) { - return; - } - - require_once DRUPAL_ROOT . '/' . variable_get('path_inc', 'core/includes/path.inc'); - require_once DRUPAL_ROOT . '/core/includes/theme.inc'; - require_once DRUPAL_ROOT . '/core/includes/common.inc'; - require_once DRUPAL_ROOT . '/core/includes/unicode.inc'; - require_once DRUPAL_ROOT . '/core/includes/file.inc'; - require_once DRUPAL_ROOT . '/core/includes/module.inc'; - unicode_check(); - - // Install and update pages are treated differently to prevent theming overrides. - if (defined('MAINTENANCE_MODE') && (MAINTENANCE_MODE == 'install' || MAINTENANCE_MODE == 'update')) { - $custom_theme = (isset($conf['maintenance_theme']) ? $conf['maintenance_theme'] : 'seven'); - } - else { - // The bootstrap was not complete. So we are operating in a crippled - // environment, we need to bootstrap just enough to allow hook invocations - // to work. See _drupal_log_error(). - if (!class_exists('Database', FALSE)) { - require_once DRUPAL_ROOT . '/core/includes/database/database.inc'; - } - - // We use the default theme as the maintenance theme. If a default theme - // isn't specified in the database or in settings.php, we use Bartik. - $custom_theme = variable_get('maintenance_theme', variable_get('theme_default', 'bartik')); - } - - // Ensure that system.module is loaded. - if (!function_exists('_system_rebuild_theme_data')) { - $module_list['system']['filename'] = 'core/modules/system/system.module'; - module_list(TRUE, FALSE, FALSE, $module_list); - drupal_load('module', 'system'); - } - - $themes = list_themes(); - - // list_themes() triggers a drupal_alter() in maintenance mode, but we can't - // let themes alter the .info data until we know a theme's base themes. So - // don't set global $theme until after list_themes() builds its cache. - $theme = $custom_theme; - - // Store the identifier for retrieving theme settings with. - $theme_key = $theme; - - // Find all our ancestor themes and put them in an array. - $base_theme = array(); - $ancestor = $theme; - while ($ancestor && isset($themes[$ancestor]->base_theme)) { - $base_theme[] = $new_base_theme = $themes[$themes[$ancestor]->base_theme]; - $ancestor = $themes[$ancestor]->base_theme; - } - _drupal_theme_initialize($themes[$theme], array_reverse($base_theme), '_theme_load_offline_registry'); - - // These are usually added from system_init() -except maintenance.css. - // When the database is inactive it's not called so we add it here. - $path = drupal_get_path('module', 'system'); - drupal_add_css($path . '/system.base.css'); - drupal_add_css($path . '/system.admin.css'); - drupal_add_css($path . '/system.menus.css'); - drupal_add_css($path . '/system.messages.css'); - drupal_add_css($path . '/system.theme.css'); - drupal_add_css($path . '/system.maintenance.css'); -} - -/** - * This builds the registry when the site needs to bypass any database calls. - */ -function _theme_load_offline_registry($theme, $base_theme = NULL, $theme_engine = NULL) { - return _theme_build_registry($theme, $base_theme, $theme_engine); -} - -/** - * Returns HTML for a list of maintenance tasks to perform. - * - * @param $variables - * An associative array containing: - * - items: An associative array of maintenance tasks. - * - active: The key for the currently active maintenance task. - * - * @ingroup themeable - */ -function theme_task_list($variables) { - $items = $variables['items']; - $active = $variables['active']; - - $done = isset($items[$active]) || $active == NULL; - $output = '<h2 class="element-invisible">Installation tasks</h2>'; - $output .= '<ol class="task-list">'; - - foreach ($items as $k => $item) { - if ($active == $k) { - $class = 'active'; - $status = '(' . t('active') . ')'; - $done = FALSE; - } - else { - $class = $done ? 'done' : ''; - $status = $done ? '(' . t('done') . ')' : ''; - } - $output .= '<li'; - $output .= ($class ? ' class="' . $class . '"' : '') . '>'; - $output .= $item; - $output .= ($status ? '<span class="element-invisible">' . $status . '</span>' : ''); - $output .= '</li>'; - } - $output .= '</ol>'; - return $output; -} - -/** - * Returns HTML for the installation page. - * - * Note: this function is not themeable. - * - * @param $variables - * An associative array containing: - * - content: The page content to show. - */ -function theme_install_page($variables) { - drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); - return theme('maintenance_page', $variables); -} - -/** - * Returns HTML for the update page. - * - * Note: this function is not themeable. - * - * @param $variables - * An associative array containing: - * - content: The page content to show. - * - show_messages: Whether to output status and error messages. - * FALSE can be useful to postpone the messages to a subsequent page. - */ -function theme_update_page($variables) { - drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); - return theme('maintenance_page', $variables); -} - -/** - * Returns HTML for a report of the results from an operation run via authorize.php. - * - * @param $variables - * An associative array containing: - * - messages: An array of result messages. - * - * @ingroup themeable - */ -function theme_authorize_report($variables) { - $messages = $variables['messages']; - $output = ''; - if (!empty($messages)) { - $output .= '<div id="authorize-results">'; - foreach ($messages as $heading => $logs) { - $items = array(); - foreach ($logs as $number => $log_message) { - if ($number === '#abort') { - continue; - } - $items[] = theme('authorize_message', array('message' => $log_message['message'], 'success' => $log_message['success'])); - } - $output .= theme('item_list', array('items' => $items, 'title' => $heading)); - } - $output .= '</div>'; - } - return $output; -} - -/** - * Returns HTML for a single log message from the authorize.php batch operation. - * - * @param $variables - * An associative array containing: - * - message: The log message. - * - success: A boolean indicating failure or success. - * - * @ingroup themeable - */ -function theme_authorize_message($variables) { - $message = $variables['message']; - $success = $variables['success']; - if ($success) { - $item = array('data' => $message, 'class' => array('success')); - } - else { - $item = array('data' => '<strong>' . $message . '</strong>', 'class' => array('failure')); - } - return $item; -} diff --git a/core/includes/token.inc b/core/includes/token.inc deleted file mode 100644 index 7a5fea141c3..00000000000 --- a/core/includes/token.inc +++ /dev/null @@ -1,257 +0,0 @@ -<?php - -/** - * @file - * Drupal placeholder/token replacement system. - * - * API functions for replacing placeholders in text with meaningful values. - * - * For example: When configuring automated emails, an administrator enters - * standard text for the email. Variables like the title of a node and the date - * the email was sent can be entered as placeholders like [node:title] and - * [date:short]. When a Drupal module prepares to send the email, it can call - * the token_replace() function, passing in the text. The token system will - * scan the text for placeholder tokens, give other modules an opportunity to - * replace them with meaningful text, then return the final product to the - * original module. - * - * Tokens follow the form: [$type:$name], where $type is a general class of - * tokens like 'node', 'user', or 'comment' and $name is the name of a given - * placeholder. For example, [node:title] or [node:created:since]. - * - * In addition to raw text containing placeholders, modules may pass in an array - * of objects to be used when performing the replacement. The objects should be - * keyed by the token type they correspond to. For example: - * - * @code - * // Load a node and a user, then replace tokens in the text. - * $text = 'On [date:short], [user:name] read [node:title].'; - * $node = node_load(1); - * $user = user_load(1); - * - * // [date:...] tokens use the current date automatically. - * $data = array('node' => $node, 'user' => $user); - * return token_replace($text, $data); - * @endcode - * - * Some tokens may be chained in the form of [$type:$pointer:$name], where $type - * is a normal token type, $pointer is a reference to another token type, and - * $name is the name of a given placeholder. For example, [node:author:mail]. In - * that example, 'author' is a pointer to the 'user' account that created the - * node, and 'mail' is a placeholder available for any 'user'. - * - * @see token_replace() - * @see hook_tokens() - * @see hook_token_info() - */ - -/** - * Replaces all tokens in a given string with appropriate values. - * - * @param $text - * A string potentially containing replaceable tokens. - * @param $data - * (optional) An array of keyed objects. For simple replacement scenarios - * 'node', 'user', and others are common keys, with an accompanying node or - * user object being the value. Some token types, like 'site', do not require - * any explicit information from $data and can be replaced even if it is - * empty. - * @param $options - * (optional) A keyed array of settings and flags to control the token - * replacement process. Supported options are: - * - language: A language object to be used when generating locale-sensitive - * tokens. - * - callback: A callback function that will be used to post-process the array - * of token replacements after they are generated. For example, a module - * using tokens in a text-only email might provide a callback to strip HTML - * entities from token values before they are inserted into the final text. - * - clear: A boolean flag indicating that tokens should be removed from the - * final text if no replacement value can be generated. - * - sanitize: A boolean flag indicating that tokens should be sanitized for - * display to a web browser. Defaults to TRUE. Developers who set this - * option to FALSE assume responsibility for running filter_xss(), - * check_plain() or other appropriate scrubbing functions before displaying - * data to users. - * - * @return - * Text with tokens replaced. - */ -function token_replace($text, array $data = array(), array $options = array()) { - $replacements = array(); - foreach (token_scan($text) as $type => $tokens) { - $replacements += token_generate($type, $tokens, $data, $options); - if (!empty($options['clear'])) { - $replacements += array_fill_keys($tokens, ''); - } - } - - // Optionally alter the list of replacement values. - if (!empty($options['callback']) && function_exists($options['callback'])) { - $function = $options['callback']; - $function($replacements, $data, $options); - } - - $tokens = array_keys($replacements); - $values = array_values($replacements); - - return str_replace($tokens, $values, $text); -} - -/** - * Builds a list of all token-like patterns that appear in the text. - * - * @param $text - * The text to be scanned for possible tokens. - * - * @return - * An associative array of discovered tokens, grouped by type. - */ -function token_scan($text) { - // Matches tokens with the following pattern: [$type:$name] - // $type and $name may not contain [ ] or whitespace characters. - // $type may not contain : characters, but $name may. - preg_match_all('/ - \[ # [ - pattern start - ([^\s\[\]:]*) # match $type not containing whitespace : [ or ] - : # : - separator - ([^\s\[\]]*) # match $name not containing whitespace [ or ] - \] # ] - pattern end - /x', $text, $matches); - - $types = $matches[1]; - $tokens = $matches[2]; - - // Iterate through the matches, building an associative array containing - // $tokens grouped by $types, pointing to the version of the token found in - // the source text. For example, $results['node']['title'] = '[node:title]'; - $results = array(); - for ($i = 0; $i < count($tokens); $i++) { - $results[$types[$i]][$tokens[$i]] = $matches[0][$i]; - } - - return $results; -} - -/** - * Generates replacement values for a list of tokens. - * - * @param $type - * The type of token being replaced. 'node', 'user', and 'date' are common. - * @param $tokens - * An array of tokens to be replaced, keyed by the literal text of the token - * as it appeared in the source text. - * @param $data - * (optional) An array of keyed objects. For simple replacement scenarios - * 'node', 'user', and others are common keys, with an accompanying node or - * user object being the value. Some token types, like 'site', do not require - * any explicit information from $data and can be replaced even if it is - * empty. - * @param $options - * (optional) A keyed array of settings and flags to control the token - * replacement process. Supported options are: - * - language: A language object to be used when generating locale-sensitive - * tokens. - * - callback: A callback function that will be used to post-process the - * array of token replacements after they are generated. Can be used when - * modules require special formatting of token text, for example URL - * encoding or truncation to a specific length. - * - sanitize: A boolean flag indicating that tokens should be sanitized for - * display to a web browser. Developers who set this option to FALSE assume - * responsibility for running filter_xss(), check_plain() or other - * appropriate scrubbing functions before displaying data to users. - * - * @return - * An associative array of replacement values, keyed by the original 'raw' - * tokens that were found in the source text. For example: - * $results['[node:title]'] = 'My new node'; - * - * @see hook_tokens() - * @see hook_tokens_alter() - */ -function token_generate($type, array $tokens, array $data = array(), array $options = array()) { - $options += array('sanitize' => TRUE); - $replacements = module_invoke_all('tokens', $type, $tokens, $data, $options); - - // Allow other modules to alter the replacements. - $context = array( - 'type' => $type, - 'tokens' => $tokens, - 'data' => $data, - 'options' => $options, - ); - drupal_alter('tokens', $replacements, $context); - - return $replacements; -} - -/** - * Given a list of tokens, returns those that begin with a specific prefix. - * - * Used to extract a group of 'chained' tokens (such as [node:author:name]) from - * the full list of tokens found in text. For example: - * @code - * $data = array( - * 'author:name' => '[node:author:name]', - * 'title' => '[node:title]', - * 'created' => '[node:created]', - * ); - * $results = token_find_with_prefix($data, 'author'); - * $results == array('name' => '[node:author:name]'); - * @endcode - * - * @param $tokens - * A keyed array of tokens, and their original raw form in the source text. - * @param $prefix - * A textual string to be matched at the beginning of the token. - * @param $delimiter - * An optional string containing the character that separates the prefix from - * the rest of the token. Defaults to ':'. - * - * @return - * An associative array of discovered tokens, with the prefix and delimiter - * stripped from the key. - */ -function token_find_with_prefix(array $tokens, $prefix, $delimiter = ':') { - $results = array(); - foreach ($tokens as $token => $raw) { - $parts = explode($delimiter, $token, 2); - if (count($parts) == 2 && $parts[0] == $prefix) { - $results[$parts[1]] = $raw; - } - } - return $results; -} - -/** - * Returns metadata describing supported tokens. - * - * The metadata array contains token type, name, and description data as well as - * an optional pointer indicating that the token chains to another set of tokens. - * For example: - * @code - * $data['types']['node'] = array( - * 'name' => t('Nodes'), - * 'description' => t('Tokens related to node objects.'), - * ); - * $data['tokens']['node']['title'] = array( - * 'name' => t('Title'), - * 'description' => t('The title of the current node.'), - * ); - * $data['tokens']['node']['author'] = array( - * 'name' => t('Author'), - * 'description' => t('The author of the current node.'), - * 'type' => 'user', - * ); - * @endcode - * - * @return - * An associative array of token information, grouped by token type. - */ -function token_info() { - $data = &drupal_static(__FUNCTION__); - if (!isset($data)) { - $data = module_invoke_all('token_info'); - drupal_alter('token_info', $data); - } - return $data; -} diff --git a/core/includes/unicode.entities.inc b/core/includes/unicode.entities.inc deleted file mode 100644 index 3b1fbb691d7..00000000000 --- a/core/includes/unicode.entities.inc +++ /dev/null @@ -1,265 +0,0 @@ -<?php - -/** - * @file - * (X)HTML entities, as defined in HTML 4.01. - * - * @see http://www.w3.org/TR/html401/sgml/entities.html - */ - -$html_entities = array( - 'Á' => 'Á', - 'á' => 'á', - 'Â' => 'Â', - 'â' => 'â', - '´' => '´', - 'Æ' => 'Æ', - 'æ' => 'æ', - 'À' => 'À', - 'à' => 'à', - 'ℵ' => 'ℵ', - 'Α' => 'Α', - 'α' => 'α', - '&' => '&', - '∧' => '∧', - '∠' => '∠', - 'Å' => 'Å', - 'å' => 'å', - '≈' => '≈', - 'Ã' => 'Ã', - 'ã' => 'ã', - 'Ä' => 'Ä', - 'ä' => 'ä', - '„' => '„', - 'Β' => 'Β', - 'β' => 'β', - '¦' => '¦', - '•' => '•', - '∩' => '∩', - 'Ç' => 'Ç', - 'ç' => 'ç', - '¸' => '¸', - '¢' => '¢', - 'Χ' => 'Χ', - 'χ' => 'χ', - 'ˆ' => 'ˆ', - '♣' => '♣', - '≅' => '≅', - '©' => '©', - '↵' => '↵', - '∪' => '∪', - '¤' => '¤', - '†' => '†', - '‡' => '‡', - '↓' => '↓', - '⇓' => '⇓', - '°' => '°', - 'Δ' => 'Δ', - 'δ' => 'δ', - '♦' => '♦', - '÷' => '÷', - 'É' => 'É', - 'é' => 'é', - 'Ê' => 'Ê', - 'ê' => 'ê', - 'È' => 'È', - 'è' => 'è', - '∅' => '∅', - ' ' => ' ', - ' ' => ' ', - 'Ε' => 'Ε', - 'ε' => 'ε', - '≡' => '≡', - 'Η' => 'Η', - 'η' => 'η', - 'Ð' => 'Ð', - 'ð' => 'ð', - 'Ë' => 'Ë', - 'ë' => 'ë', - '€' => '€', - '∃' => '∃', - 'ƒ' => 'ƒ', - '∀' => '∀', - '½' => '½', - '¼' => '¼', - '¾' => '¾', - '⁄' => '⁄', - 'Γ' => 'Γ', - 'γ' => 'γ', - '≥' => '≥', - '↔' => '↔', - '⇔' => '⇔', - '♥' => '♥', - '…' => '…', - 'Í' => 'Í', - 'í' => 'í', - 'Î' => 'Î', - 'î' => 'î', - '¡' => '¡', - 'Ì' => 'Ì', - 'ì' => 'ì', - 'ℑ' => 'ℑ', - '∞' => '∞', - '∫' => '∫', - 'Ι' => 'Ι', - 'ι' => 'ι', - '¿' => '¿', - '∈' => '∈', - 'Ï' => 'Ï', - 'ï' => 'ï', - 'Κ' => 'Κ', - 'κ' => 'κ', - 'Λ' => 'Λ', - 'λ' => 'λ', - '⟨' => '〈', - '«' => '«', - '←' => '←', - '⇐' => '⇐', - '⌈' => '⌈', - '“' => '“', - '≤' => '≤', - '⌊' => '⌊', - '∗' => '∗', - '◊' => '◊', - '‎' => '', - '‹' => '‹', - '‘' => '‘', - '¯' => '¯', - '—' => '—', - 'µ' => 'µ', - '·' => '·', - '−' => '−', - 'Μ' => 'Μ', - 'μ' => 'μ', - '∇' => '∇', - ' ' => ' ', - '–' => '–', - '≠' => '≠', - '∋' => '∋', - '¬' => '¬', - '∉' => '∉', - '⊄' => '⊄', - 'Ñ' => 'Ñ', - 'ñ' => 'ñ', - 'Ν' => 'Ν', - 'ν' => 'ν', - 'Ó' => 'Ó', - 'ó' => 'ó', - 'Ô' => 'Ô', - 'ô' => 'ô', - 'Œ' => 'Œ', - 'œ' => 'œ', - 'Ò' => 'Ò', - 'ò' => 'ò', - '‾' => '‾', - 'Ω' => 'Ω', - 'ω' => 'ω', - 'Ο' => 'Ο', - 'ο' => 'ο', - '⊕' => '⊕', - '∨' => '∨', - 'ª' => 'ª', - 'º' => 'º', - 'Ø' => 'Ø', - 'ø' => 'ø', - 'Õ' => 'Õ', - 'õ' => 'õ', - '⊗' => '⊗', - 'Ö' => 'Ö', - 'ö' => 'ö', - '¶' => '¶', - '∂' => '∂', - '‰' => '‰', - '⊥' => '⊥', - 'Φ' => 'Φ', - 'φ' => 'φ', - 'Π' => 'Π', - 'π' => 'π', - 'ϖ' => 'ϖ', - '±' => '±', - '£' => '£', - '′' => '′', - '″' => '″', - '∏' => '∏', - '∝' => '∝', - 'Ψ' => 'Ψ', - 'ψ' => 'ψ', - '√' => '√', - '⟩' => '〉', - '»' => '»', - '→' => '→', - '⇒' => '⇒', - '⌉' => '⌉', - '”' => '”', - 'ℜ' => 'ℜ', - '®' => '®', - '⌋' => '⌋', - 'Ρ' => 'Ρ', - 'ρ' => 'ρ', - '‏' => '', - '›' => '›', - '’' => '’', - '‚' => '‚', - 'Š' => 'Š', - 'š' => 'š', - '⋅' => '⋅', - '§' => '§', - '­' => '', - 'Σ' => 'Σ', - 'σ' => 'σ', - 'ς' => 'ς', - '∼' => '∼', - '♠' => '♠', - '⊂' => '⊂', - '⊆' => '⊆', - '∑' => '∑', - '¹' => '¹', - '²' => '²', - '³' => '³', - '⊃' => '⊃', - '⊇' => '⊇', - 'ß' => 'ß', - 'Τ' => 'Τ', - 'τ' => 'τ', - '∴' => '∴', - 'Θ' => 'Θ', - 'θ' => 'θ', - 'ϑ' => 'ϑ', - ' ' => ' ', - 'Þ' => 'Þ', - 'þ' => 'þ', - '˜' => '˜', - '×' => '×', - '™' => '™', - 'Ú' => 'Ú', - 'ú' => 'ú', - '↑' => '↑', - '⇑' => '⇑', - 'Û' => 'Û', - 'û' => 'û', - 'Ù' => 'Ù', - 'ù' => 'ù', - '¨' => '¨', - 'ϒ' => 'ϒ', - 'Υ' => 'Υ', - 'υ' => 'υ', - 'Ü' => 'Ü', - 'ü' => 'ü', - '℘' => '℘', - 'Ξ' => 'Ξ', - 'ξ' => 'ξ', - 'Ý' => 'Ý', - 'ý' => 'ý', - '¥' => '¥', - 'ÿ' => 'ÿ', - 'Ÿ' => 'Ÿ', - 'Ζ' => 'Ζ', - 'ζ' => 'ζ', - '‍' => '', - '‌' => '', - '>' => '>', - '<' => '<', - '"' => '"', - // Add apostrophe (XML). - ''' => "'", -); diff --git a/core/includes/unicode.inc b/core/includes/unicode.inc deleted file mode 100644 index 9dde2ca70cc..00000000000 --- a/core/includes/unicode.inc +++ /dev/null @@ -1,601 +0,0 @@ -<?php - -/** - * Indicates an error during check for PHP unicode support. - */ -define('UNICODE_ERROR', -1); - -/** - * Indicates that standard PHP (emulated) unicode support is being used. - */ -define('UNICODE_SINGLEBYTE', 0); - -/** - * Indicates that full unicode support with the PHP mbstring extension is being - * used. - */ -define('UNICODE_MULTIBYTE', 1); - -/** - * Matches Unicode characters that are word boundaries. - * - * @see http://unicode.org/glossary - * - * Characters with the following General_category (gc) property values are used - * as word boundaries. While this does not fully conform to the Word Boundaries - * algorithm described in http://unicode.org/reports/tr29, as PCRE does not - * contain the Word_Break property table, this simpler algorithm has to do. - * - Cc, Cf, Cn, Co, Cs: Other. - * - Pc, Pd, Pe, Pf, Pi, Po, Ps: Punctuation. - * - Sc, Sk, Sm, So: Symbols. - * - Zl, Zp, Zs: Separators. - * - * Non-boundary characters include the following General_category (gc) property - * values: - * - Ll, Lm, Lo, Lt, Lu: Letters. - * - Mc, Me, Mn: Combining Marks. - * - Nd, Nl, No: Numbers. - * - * Note that the PCRE property matcher is not used because we wanted to be - * compatible with Unicode 5.2.0 regardless of the PCRE version used (and any - * bugs in PCRE property tables). - */ -define('PREG_CLASS_UNICODE_WORD_BOUNDARY', - '\x{0}-\x{2F}\x{3A}-\x{40}\x{5B}-\x{60}\x{7B}-\x{A9}\x{AB}-\x{B1}\x{B4}' . - '\x{B6}-\x{B8}\x{BB}\x{BF}\x{D7}\x{F7}\x{2C2}-\x{2C5}\x{2D2}-\x{2DF}' . - '\x{2E5}-\x{2EB}\x{2ED}\x{2EF}-\x{2FF}\x{375}\x{37E}-\x{385}\x{387}\x{3F6}' . - '\x{482}\x{55A}-\x{55F}\x{589}-\x{58A}\x{5BE}\x{5C0}\x{5C3}\x{5C6}' . - '\x{5F3}-\x{60F}\x{61B}-\x{61F}\x{66A}-\x{66D}\x{6D4}\x{6DD}\x{6E9}' . - '\x{6FD}-\x{6FE}\x{700}-\x{70F}\x{7F6}-\x{7F9}\x{830}-\x{83E}' . - '\x{964}-\x{965}\x{970}\x{9F2}-\x{9F3}\x{9FA}-\x{9FB}\x{AF1}\x{B70}' . - '\x{BF3}-\x{BFA}\x{C7F}\x{CF1}-\x{CF2}\x{D79}\x{DF4}\x{E3F}\x{E4F}' . - '\x{E5A}-\x{E5B}\x{F01}-\x{F17}\x{F1A}-\x{F1F}\x{F34}\x{F36}\x{F38}' . - '\x{F3A}-\x{F3D}\x{F85}\x{FBE}-\x{FC5}\x{FC7}-\x{FD8}\x{104A}-\x{104F}' . - '\x{109E}-\x{109F}\x{10FB}\x{1360}-\x{1368}\x{1390}-\x{1399}\x{1400}' . - '\x{166D}-\x{166E}\x{1680}\x{169B}-\x{169C}\x{16EB}-\x{16ED}' . - '\x{1735}-\x{1736}\x{17B4}-\x{17B5}\x{17D4}-\x{17D6}\x{17D8}-\x{17DB}' . - '\x{1800}-\x{180A}\x{180E}\x{1940}-\x{1945}\x{19DE}-\x{19FF}' . - '\x{1A1E}-\x{1A1F}\x{1AA0}-\x{1AA6}\x{1AA8}-\x{1AAD}\x{1B5A}-\x{1B6A}' . - '\x{1B74}-\x{1B7C}\x{1C3B}-\x{1C3F}\x{1C7E}-\x{1C7F}\x{1CD3}\x{1FBD}' . - '\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}' . - '\x{1FFD}-\x{206F}\x{207A}-\x{207E}\x{208A}-\x{208E}\x{20A0}-\x{20B8}' . - '\x{2100}-\x{2101}\x{2103}-\x{2106}\x{2108}-\x{2109}\x{2114}' . - '\x{2116}-\x{2118}\x{211E}-\x{2123}\x{2125}\x{2127}\x{2129}\x{212E}' . - '\x{213A}-\x{213B}\x{2140}-\x{2144}\x{214A}-\x{214D}\x{214F}' . - '\x{2190}-\x{244A}\x{249C}-\x{24E9}\x{2500}-\x{2775}\x{2794}-\x{2B59}' . - '\x{2CE5}-\x{2CEA}\x{2CF9}-\x{2CFC}\x{2CFE}-\x{2CFF}\x{2E00}-\x{2E2E}' . - '\x{2E30}-\x{3004}\x{3008}-\x{3020}\x{3030}\x{3036}-\x{3037}' . - '\x{303D}-\x{303F}\x{309B}-\x{309C}\x{30A0}\x{30FB}\x{3190}-\x{3191}' . - '\x{3196}-\x{319F}\x{31C0}-\x{31E3}\x{3200}-\x{321E}\x{322A}-\x{3250}' . - '\x{3260}-\x{327F}\x{328A}-\x{32B0}\x{32C0}-\x{33FF}\x{4DC0}-\x{4DFF}' . - '\x{A490}-\x{A4C6}\x{A4FE}-\x{A4FF}\x{A60D}-\x{A60F}\x{A673}\x{A67E}' . - '\x{A6F2}-\x{A716}\x{A720}-\x{A721}\x{A789}-\x{A78A}\x{A828}-\x{A82B}' . - '\x{A836}-\x{A839}\x{A874}-\x{A877}\x{A8CE}-\x{A8CF}\x{A8F8}-\x{A8FA}' . - '\x{A92E}-\x{A92F}\x{A95F}\x{A9C1}-\x{A9CD}\x{A9DE}-\x{A9DF}' . - '\x{AA5C}-\x{AA5F}\x{AA77}-\x{AA79}\x{AADE}-\x{AADF}\x{ABEB}' . - '\x{D800}-\x{F8FF}\x{FB29}\x{FD3E}-\x{FD3F}\x{FDFC}-\x{FDFD}' . - '\x{FE10}-\x{FE19}\x{FE30}-\x{FE6B}\x{FEFF}-\x{FF0F}\x{FF1A}-\x{FF20}' . - '\x{FF3B}-\x{FF40}\x{FF5B}-\x{FF65}\x{FFE0}-\x{FFFD}'); - -/** - * Wrapper around _unicode_check(). - */ -function unicode_check() { - list($GLOBALS['multibyte']) = _unicode_check(); -} - -/** - * Perform checks about Unicode support in PHP, and set the right settings if - * needed. - * - * Because Drupal needs to be able to handle text in various encodings, we do - * not support mbstring function overloading. HTTP input/output conversion must - * be disabled for similar reasons. - * - * @param $errors - * Whether to report any fatal errors with form_set_error(). - */ -function _unicode_check() { - // Ensure translations don't break at install time - $t = get_t(); - - // Check for mbstring extension - if (!function_exists('mb_strlen')) { - return array(UNICODE_SINGLEBYTE, $t('Operations on Unicode strings are emulated on a best-effort basis. Install the <a href="@url">PHP mbstring extension</a> for improved Unicode support.', array('@url' => 'http://www.php.net/mbstring'))); - } - - // Check mbstring configuration - if (ini_get('mbstring.func_overload') != 0) { - return array(UNICODE_ERROR, $t('Multibyte string function overloading in PHP is active and must be disabled. Check the php.ini <em>mbstring.func_overload</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring'))); - } - if (ini_get('mbstring.encoding_translation') != 0) { - return array(UNICODE_ERROR, $t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.encoding_translation</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring'))); - } - if (ini_get('mbstring.http_input') != 'pass') { - return array(UNICODE_ERROR, $t('Multibyte string input conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.http_input</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring'))); - } - if (ini_get('mbstring.http_output') != 'pass') { - return array(UNICODE_ERROR, $t('Multibyte string output conversion in PHP is active and must be disabled. Check the php.ini <em>mbstring.http_output</em> setting. Please refer to the <a href="@url">PHP mbstring documentation</a> for more information.', array('@url' => 'http://www.php.net/mbstring'))); - } - - // Set appropriate configuration - mb_internal_encoding('utf-8'); - mb_language('uni'); - return array(UNICODE_MULTIBYTE, ''); -} - -/** - * Return Unicode library status and errors. - */ -function unicode_requirements() { - // Ensure translations don't break at install time - $t = get_t(); - - $libraries = array( - UNICODE_SINGLEBYTE => $t('Standard PHP'), - UNICODE_MULTIBYTE => $t('PHP Mbstring Extension'), - UNICODE_ERROR => $t('Error'), - ); - $severities = array( - UNICODE_SINGLEBYTE => REQUIREMENT_WARNING, - UNICODE_MULTIBYTE => REQUIREMENT_OK, - UNICODE_ERROR => REQUIREMENT_ERROR, - ); - list($library, $description) = _unicode_check(); - - $requirements['unicode'] = array( - 'title' => $t('Unicode library'), - 'value' => $libraries[$library], - ); - if ($description) { - $requirements['unicode']['description'] = $description; - } - - $requirements['unicode']['severity'] = $severities[$library]; - - return $requirements; -} - -/** - * Prepare a new XML parser. - * - * This is a wrapper around xml_parser_create() which extracts the encoding from - * the XML data first and sets the output encoding to UTF-8. This function should - * be used instead of xml_parser_create(), because PHP 4's XML parser doesn't - * check the input encoding itself. "Starting from PHP 5, the input encoding is - * automatically detected, so that the encoding parameter specifies only the - * output encoding." - * - * This is also where unsupported encodings will be converted. Callers should - * take this into account: $data might have been changed after the call. - * - * @param $data - * The XML data which will be parsed later. - * - * @return - * An XML parser object or FALSE on error. - * - * @ingroup php_wrappers - */ -function drupal_xml_parser_create(&$data) { - // Default XML encoding is UTF-8 - $encoding = 'utf-8'; - $bom = FALSE; - - // Check for UTF-8 byte order mark (PHP5's XML parser doesn't handle it). - if (!strncmp($data, "\xEF\xBB\xBF", 3)) { - $bom = TRUE; - $data = substr($data, 3); - } - - // Check for an encoding declaration in the XML prolog if no BOM was found. - if (!$bom && preg_match('/^<\?xml[^>]+encoding="(.+?)"/', $data, $match)) { - $encoding = $match[1]; - } - - // Unsupported encodings are converted here into UTF-8. - $php_supported = array('utf-8', 'iso-8859-1', 'us-ascii'); - if (!in_array(strtolower($encoding), $php_supported)) { - $out = drupal_convert_to_utf8($data, $encoding); - if ($out !== FALSE) { - $encoding = 'utf-8'; - $data = preg_replace('/^(<\?xml[^>]+encoding)="(.+?)"/', '\\1="utf-8"', $out); - } - else { - watchdog('php', 'Could not convert XML encoding %s to UTF-8.', array('%s' => $encoding), WATCHDOG_WARNING); - return FALSE; - } - } - - $xml_parser = xml_parser_create($encoding); - xml_parser_set_option($xml_parser, XML_OPTION_TARGET_ENCODING, 'utf-8'); - return $xml_parser; -} - -/** - * Convert data to UTF-8 - * - * Requires the iconv, GNU recode or mbstring PHP extension. - * - * @param $data - * The data to be converted. - * @param $encoding - * The encoding that the data is in. - * - * @return - * Converted data or FALSE. - */ -function drupal_convert_to_utf8($data, $encoding) { - if (function_exists('iconv')) { - $out = @iconv($encoding, 'utf-8', $data); - } - elseif (function_exists('mb_convert_encoding')) { - $out = @mb_convert_encoding($data, 'utf-8', $encoding); - } - elseif (function_exists('recode_string')) { - $out = @recode_string($encoding . '..utf-8', $data); - } - else { - watchdog('php', 'Unsupported encoding %s. Please install iconv, GNU recode or mbstring for PHP.', array('%s' => $encoding), WATCHDOG_ERROR); - return FALSE; - } - - return $out; -} - -/** - * Truncate a UTF-8-encoded string safely to a number of bytes. - * - * If the end position is in the middle of a UTF-8 sequence, it scans backwards - * until the beginning of the byte sequence. - * - * Use this function whenever you want to chop off a string at an unsure - * location. On the other hand, if you're sure that you're splitting on a - * character boundary (e.g. after using strpos() or similar), you can safely use - * substr() instead. - * - * @param $string - * The string to truncate. - * @param $len - * An upper limit on the returned string length. - * - * @return - * The truncated string. - */ -function drupal_truncate_bytes($string, $len) { - if (strlen($string) <= $len) { - return $string; - } - if ((ord($string[$len]) < 0x80) || (ord($string[$len]) >= 0xC0)) { - return substr($string, 0, $len); - } - // Scan backwards to beginning of the byte sequence. - while (--$len >= 0 && ord($string[$len]) >= 0x80 && ord($string[$len]) < 0xC0); - - return substr($string, 0, $len); -} - -/** - * Truncates a UTF-8-encoded string safely to a number of characters. - * - * @param $string - * The string to truncate. - * @param $max_length - * An upper limit on the returned string length, including trailing ellipsis - * if $add_ellipsis is TRUE. - * @param $wordsafe - * If TRUE, attempt to truncate on a word boundary. Word boundaries are - * spaces, punctuation, and Unicode characters used as word boundaries in - * non-Latin languages; see PREG_CLASS_UNICODE_WORD_BOUNDARY for more - * information. If a word boundary cannot be found that would make the length - * of the returned string fall within length guidelines (see parameters - * $max_length and $min_wordsafe_length), word boundaries are ignored. - * @param $add_ellipsis - * If TRUE, add t('...') to the end of the truncated string (defaults to - * FALSE). The string length will still fall within $max_length. - * @param $min_wordsafe_length - * If $wordsafe is TRUE, the minimum acceptable length for truncation (before - * adding an ellipsis, if $add_ellipsis is TRUE). Has no effect if $wordsafe - * is FALSE. This can be used to prevent having a very short resulting string - * that will not be understandable. For instance, if you are truncating the - * string "See myverylongurlexample.com for more information" to a word-safe - * return length of 20, the only available word boundary within 20 characters - * is after the word "See", which wouldn't leave a very informative string. If - * you had set $min_wordsafe_length to 10, though, the function would realise - * that "See" alone is too short, and would then just truncate ignoring word - * boundaries, giving you "See myverylongurl..." (assuming you had set - * $add_ellipses to TRUE). - * - * @return - * The truncated string. - */ -function truncate_utf8($string, $max_length, $wordsafe = FALSE, $add_ellipsis = FALSE, $min_wordsafe_length = 1) { - $ellipsis = ''; - $max_length = max($max_length, 0); - $min_wordsafe_length = max($min_wordsafe_length, 0); - - if (drupal_strlen($string) <= $max_length) { - // No truncation needed, so don't add ellipsis, just return. - return $string; - } - - if ($add_ellipsis) { - // Truncate ellipsis in case $max_length is small. - $ellipsis = drupal_substr(t('...'), 0, $max_length); - $max_length -= drupal_strlen($ellipsis); - $max_length = max($max_length, 0); - } - - if ($max_length <= $min_wordsafe_length) { - // Do not attempt word-safe if lengths are bad. - $wordsafe = FALSE; - } - - if ($wordsafe) { - $matches = array(); - // Find the last word boundary, if there is one within $min_wordsafe_length - // to $max_length characters. preg_match() is always greedy, so it will - // find the longest string possible. - $found = preg_match('/^(.{' . $min_wordsafe_length . ',' . $max_length . '})[' . PREG_CLASS_UNICODE_WORD_BOUNDARY . ']/u', $string, $matches); - if ($found) { - $string = $matches[1]; - } - else { - $string = drupal_substr($string, 0, $max_length); - } - } - else { - $string = drupal_substr($string, 0, $max_length); - } - - if ($add_ellipsis) { - $string .= $ellipsis; - } - - return $string; -} - -/** - * Encodes MIME/HTTP header values that contain non-ASCII, UTF-8 encoded - * characters. - * - * For example, mime_header_encode('tést.txt') returns "=?UTF-8?B?dMOpc3QudHh0?=". - * - * See http://www.rfc-editor.org/rfc/rfc2047.txt for more information. - * - * Notes: - * - Only encode strings that contain non-ASCII characters. - * - We progressively cut-off a chunk with truncate_utf8(). This is to ensure - * each chunk starts and ends on a character boundary. - * - Using \n as the chunk separator may cause problems on some systems and may - * have to be changed to \r\n or \r. - */ -function mime_header_encode($string) { - if (preg_match('/[^\x20-\x7E]/', $string)) { - $chunk_size = 47; // floor((75 - strlen("=?UTF-8?B??=")) * 0.75); - $len = strlen($string); - $output = ''; - while ($len > 0) { - $chunk = drupal_truncate_bytes($string, $chunk_size); - $output .= ' =?UTF-8?B?' . base64_encode($chunk) . "?=\n"; - $c = strlen($chunk); - $string = substr($string, $c); - $len -= $c; - } - return trim($output); - } - return $string; -} - -/** - * Complement to mime_header_encode - */ -function mime_header_decode($header) { - // First step: encoded chunks followed by other encoded chunks (need to collapse whitespace) - $header = preg_replace_callback('/=\?([^?]+)\?(Q|B)\?([^?]+|\?(?!=))\?=\s+(?==\?)/', '_mime_header_decode', $header); - // Second step: remaining chunks (do not collapse whitespace) - return preg_replace_callback('/=\?([^?]+)\?(Q|B)\?([^?]+|\?(?!=))\?=/', '_mime_header_decode', $header); -} - -/** - * Helper function to mime_header_decode - */ -function _mime_header_decode($matches) { - // Regexp groups: - // 1: Character set name - // 2: Escaping method (Q or B) - // 3: Encoded data - $data = ($matches[2] == 'B') ? base64_decode($matches[3]) : str_replace('_', ' ', quoted_printable_decode($matches[3])); - if (strtolower($matches[1]) != 'utf-8') { - $data = drupal_convert_to_utf8($data, $matches[1]); - } - return $data; -} - -/** - * Decodes all HTML entities (including numerical ones) to regular UTF-8 bytes. - * - * Double-escaped entities will only be decoded once ("&lt;" becomes "<", - * not "<"). Be careful when using this function, as decode_entities can revert - * previous sanitization efforts (<script> will become <script>). - * - * @param $text - * The text to decode entities in. - * - * @return - * The input $text, with all HTML entities decoded once. - */ -function decode_entities($text) { - return html_entity_decode($text, ENT_QUOTES, 'UTF-8'); -} - -/** - * Count the amount of characters in a UTF-8 string. This is less than or - * equal to the byte count. - * - * @ingroup php_wrappers - */ -function drupal_strlen($text) { - global $multibyte; - if ($multibyte == UNICODE_MULTIBYTE) { - return mb_strlen($text); - } - else { - // Do not count UTF-8 continuation bytes. - return strlen(preg_replace("/[\x80-\xBF]/", '', $text)); - } -} - -/** - * Uppercase a UTF-8 string. - * - * @ingroup php_wrappers - */ -function drupal_strtoupper($text) { - global $multibyte; - if ($multibyte == UNICODE_MULTIBYTE) { - return mb_strtoupper($text); - } - else { - // Use C-locale for ASCII-only uppercase - $text = strtoupper($text); - // Case flip Latin-1 accented letters - $text = preg_replace_callback('/\xC3[\xA0-\xB6\xB8-\xBE]/', '_unicode_caseflip', $text); - return $text; - } -} - -/** - * Lowercase a UTF-8 string. - * - * @ingroup php_wrappers - */ -function drupal_strtolower($text) { - global $multibyte; - if ($multibyte == UNICODE_MULTIBYTE) { - return mb_strtolower($text); - } - else { - // Use C-locale for ASCII-only lowercase - $text = strtolower($text); - // Case flip Latin-1 accented letters - $text = preg_replace_callback('/\xC3[\x80-\x96\x98-\x9E]/', '_unicode_caseflip', $text); - return $text; - } -} - -/** - * Helper function for case conversion of Latin-1. - * Used for flipping U+C0-U+DE to U+E0-U+FD and back. - */ -function _unicode_caseflip($matches) { - return $matches[0][0] . chr(ord($matches[0][1]) ^ 32); -} - -/** - * Capitalize the first letter of a UTF-8 string. - * - * @ingroup php_wrappers - */ -function drupal_ucfirst($text) { - // Note: no mbstring equivalent! - return drupal_strtoupper(drupal_substr($text, 0, 1)) . drupal_substr($text, 1); -} - -/** - * Cut off a piece of a string based on character indices and counts. Follows - * the same behavior as PHP's own substr() function. - * - * Note that for cutting off a string at a known character/substring - * location, the usage of PHP's normal strpos/substr is safe and - * much faster. - * - * @ingroup php_wrappers - */ -function drupal_substr($text, $start, $length = NULL) { - global $multibyte; - if ($multibyte == UNICODE_MULTIBYTE) { - return $length === NULL ? mb_substr($text, $start) : mb_substr($text, $start, $length); - } - else { - $strlen = strlen($text); - // Find the starting byte offset. - $bytes = 0; - if ($start > 0) { - // Count all the continuation bytes from the start until we have found - // $start characters or the end of the string. - $bytes = -1; $chars = -1; - while ($bytes < $strlen - 1 && $chars < $start) { - $bytes++; - $c = ord($text[$bytes]); - if ($c < 0x80 || $c >= 0xC0) { - $chars++; - } - } - } - elseif ($start < 0) { - // Count all the continuation bytes from the end until we have found - // abs($start) characters. - $start = abs($start); - $bytes = $strlen; $chars = 0; - while ($bytes > 0 && $chars < $start) { - $bytes--; - $c = ord($text[$bytes]); - if ($c < 0x80 || $c >= 0xC0) { - $chars++; - } - } - } - $istart = $bytes; - - // Find the ending byte offset. - if ($length === NULL) { - $iend = $strlen; - } - elseif ($length > 0) { - // Count all the continuation bytes from the starting index until we have - // found $length characters or reached the end of the string, then - // backtrace one byte. - $iend = $istart - 1; - $chars = -1; - $last_real = FALSE; - while ($iend < $strlen - 1 && $chars < $length) { - $iend++; - $c = ord($text[$iend]); - $last_real = FALSE; - if ($c < 0x80 || $c >= 0xC0) { - $chars++; - $last_real = TRUE; - } - } - // Backtrace one byte if the last character we found was a real character - // and we don't need it. - if ($last_real && $chars >= $length) { - $iend--; - } - } - elseif ($length < 0) { - // Count all the continuation bytes from the end until we have found - // abs($start) characters, then backtrace one byte. - $length = abs($length); - $iend = $strlen; $chars = 0; - while ($iend > 0 && $chars < $length) { - $iend--; - $c = ord($text[$iend]); - if ($c < 0x80 || $c >= 0xC0) { - $chars++; - } - } - // Backtrace one byte if we are not at the beginning of the string. - if ($iend > 0) { - $iend--; - } - } - else { - // $length == 0, return an empty string. - return ''; - } - - return substr($text, $istart, max(0, $iend - $istart + 1)); - } -} diff --git a/core/includes/update.inc b/core/includes/update.inc deleted file mode 100644 index c60d0bc9911..00000000000 --- a/core/includes/update.inc +++ /dev/null @@ -1,730 +0,0 @@ -<?php - -/** - * @file - * Drupal database update API. - * - * This file contains functions to perform database updates for a Drupal - * installation. It is included and used extensively by update.php. - */ - -/** - * Minimum schema version of Drupal 7 required for upgrade to Drupal 8. - * - * Upgrades from Drupal 7 to Drupal 8 require that Drupal 7 be running - * the most recent version, or the upgrade could fail. We can't easily - * check the Drupal 7 version once the update process has begun, so instead - * we check the schema version of system.module in the system table. - */ -define('REQUIRED_D7_SCHEMA_VERSION', '7069'); - -/** - * Disable any items in the {system} table that are not core compatible. - */ -function update_fix_compatibility() { - $incompatible = array(); - $result = db_query("SELECT name, type, status FROM {system} WHERE status = 1 AND type IN ('module','theme')"); - foreach ($result as $row) { - if (update_check_incompatibility($row->name, $row->type)) { - $incompatible[] = $row->name; - } - } - if (!empty($incompatible)) { - db_update('system') - ->fields(array('status' => 0)) - ->condition('name', $incompatible, 'IN') - ->execute(); - } -} - -/** - * Helper function to test compatibility of a module or theme. - */ -function update_check_incompatibility($name, $type = 'module') { - static $themes, $modules; - - // Store values of expensive functions for future use. - if (empty($themes) || empty($modules)) { - // We need to do a full rebuild here to make sure the database reflects any - // code changes that were made in the filesystem before the update script - // was initiated. - $themes = system_rebuild_theme_data(); - $modules = system_rebuild_module_data(); - } - - if ($type == 'module' && isset($modules[$name])) { - $file = $modules[$name]; - } - elseif ($type == 'theme' && isset($themes[$name])) { - $file = $themes[$name]; - } - if (!isset($file) - || !isset($file->info['core']) - || $file->info['core'] != DRUPAL_CORE_COMPATIBILITY - || version_compare(phpversion(), $file->info['php']) < 0) { - return TRUE; - } - return FALSE; -} - -/** - * Performs extra steps required to bootstrap when using a Drupal 7 database. - * - * Users who still have a Drupal 7 database (and are in the process of - * updating to Drupal 8) need extra help before a full bootstrap can be - * achieved. This function does the necessary preliminary work that allows - * the bootstrap to be successful. - * - * No access check has been performed when this function is called, so no - * irreversible changes to the database are made here. - */ -function update_prepare_d8_bootstrap() { - // Allow the database system to work even if the registry has not been - // created yet. - include_once DRUPAL_ROOT . '/core/includes/install.inc'; - include_once DRUPAL_ROOT . '/core/modules/entity/entity.controller.inc'; - drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE); - - // If the site has not updated to Drupal 8 yet, check to make sure that it is - // running an up-to-date version of Drupal 7 before proceeding. Note this has - // to happen AFTER the database bootstraps because of - // drupal_get_installed_schema_version(). - $system_schema = drupal_get_installed_schema_version('system'); - if ($system_schema < 8000) { - $has_required_schema = $system_schema >= REQUIRED_D7_SCHEMA_VERSION; - $requirements = array( - 'drupal 7 version' => array( - 'title' => 'Drupal 7 version', - 'value' => $has_required_schema ? 'You are running a current version of Drupal 7.' : 'You are not running a current version of Drupal 7', - 'severity' => $has_required_schema ? REQUIREMENT_OK : REQUIREMENT_ERROR, - 'description' => $has_required_schema ? '' : 'Please update your Drupal 7 installation to the most recent version before attempting to upgrade to Drupal 8', - ), - ); - } -} - -/** - * Perform Drupal 7.x to 8.x updates that are required for update.php - * to function properly. - * - * This function runs when update.php is run the first time for 8.x, - * even before updates are selected or performed. It is important - * that if updates are not ultimately performed that no changes are - * made which make it impossible to continue using the prior version. - */ -function update_fix_d8_requirements() { - global $conf; - - if (drupal_get_installed_schema_version('system') < 8000 && !variable_get('update_d8_requirements', FALSE)) { - // @todo: Make critical, first-run changes to the database here. - variable_set('update_d8_requirements', TRUE); - } -} - -/** - * Helper function to install a new module in Drupal 8 via hook_update_N(). - */ -function update_module_enable(array $modules) { - foreach ($modules as $module) { - // Check for initial schema and install it. The schema version of a newly - // installed module is always 0. Using 8000 here would be inconsistent - // since $module_update_8000() may involve a schema change, and we want - // to install the schema as it was before any updates were added. - $function = $module . '_schema_0'; - if (function_exists($function)) { - $schema = $function(); - foreach ($schema as $table => $spec) { - db_create_table($table, $spec); - } - } - // Change the schema version from SCHEMA_UNINSTALLED to 0, so any module - // updates since the module's inception are executed in a core upgrade. - db_update('system') - ->condition('type', 'module') - ->condition('name', $module) - ->fields(array('schema_version' => 0)) - ->execute(); - - system_list_reset(); - // @todo: figure out what to do about hook_install() and hook_enable(). - } -} - -/** - * Perform one update and store the results for display on finished page. - * - * If an update function completes successfully, it should return a message - * as a string indicating success, for example: - * @code - * return t('New index added successfully.'); - * @endcode - * - * Alternatively, it may return nothing. In that case, no message - * will be displayed at all. - * - * If it fails for whatever reason, it should throw an instance of - * DrupalUpdateException with an appropriate error message, for example: - * @code - * throw new DrupalUpdateException(t('Description of what went wrong')); - * @endcode - * - * If an exception is thrown, the current update and all updates that depend on - * it will be aborted. The schema version will not be updated in this case, and - * all the aborted updates will continue to appear on update.php as updates - * that have not yet been run. - * - * If an update function needs to be re-run as part of a batch process, it - * should accept the $sandbox array by reference as its first parameter - * and set the #finished property to the percentage completed that it is, as a - * fraction of 1. - * - * @param $module - * The module whose update will be run. - * @param $number - * The update number to run. - * @param $dependency_map - * An array whose keys are the names of all update functions that will be - * performed during this batch process, and whose values are arrays of other - * update functions that each one depends on. - * @param $context - * The batch context array. - * - * @see update_resolve_dependencies() - */ -function update_do_one($module, $number, $dependency_map, &$context) { - $function = $module . '_update_' . $number; - - // If this update was aborted in a previous step, or has a dependency that - // was aborted in a previous step, go no further. - if (!empty($context['results']['#abort']) && array_intersect($context['results']['#abort'], array_merge($dependency_map, array($function)))) { - return; - } - - $ret = array(); - if (function_exists($function)) { - try { - $ret['results']['query'] = $function($context['sandbox']); - $ret['results']['success'] = TRUE; - } - // @TODO We may want to do different error handling for different - // exception types, but for now we'll just log the exception and - // return the message for printing. - catch (Exception $e) { - watchdog_exception('update', $e); - - require_once DRUPAL_ROOT . '/core/includes/errors.inc'; - $variables = _drupal_decode_exception($e); - // The exception message is run through check_plain() by _drupal_decode_exception(). - $ret['#abort'] = array('success' => FALSE, 'query' => t('%type: !message in %function (line %line of %file).', $variables)); - } - } - - if (isset($context['sandbox']['#finished'])) { - $context['finished'] = $context['sandbox']['#finished']; - unset($context['sandbox']['#finished']); - } - - if (!isset($context['results'][$module])) { - $context['results'][$module] = array(); - } - if (!isset($context['results'][$module][$number])) { - $context['results'][$module][$number] = array(); - } - $context['results'][$module][$number] = array_merge($context['results'][$module][$number], $ret); - - if (!empty($ret['#abort'])) { - // Record this function in the list of updates that were aborted. - $context['results']['#abort'][] = $function; - } - - // Record the schema update if it was completed successfully. - if ($context['finished'] == 1 && empty($ret['#abort'])) { - drupal_set_installed_schema_version($module, $number); - } - - $context['message'] = 'Updating ' . check_plain($module) . ' module'; -} - -/** - * @class Exception class used to throw error if a module update fails. - */ -class DrupalUpdateException extends Exception { } - -/** - * Start the database update batch process. - * - * @param $start - * An array whose keys contain the names of modules to be updated during the - * current batch process, and whose values contain the number of the first - * requested update for that module. The actual updates that are run (and the - * order they are run in) will depend on the results of passing this data - * through the update dependency system. - * @param $redirect - * Path to redirect to when the batch has finished processing. - * @param $url - * URL of the batch processing page (should only be used for separate - * scripts like update.php). - * @param $batch - * Optional parameters to pass into the batch API. - * @param $redirect_callback - * (optional) Specify a function to be called to redirect to the progressive - * processing page. - * - * @see update_resolve_dependencies() - */ -function update_batch($start, $redirect = NULL, $url = NULL, $batch = array(), $redirect_callback = 'drupal_goto') { - // During the update, bring the site offline so that schema changes do not - // affect visiting users. - $_SESSION['maintenance_mode'] = variable_get('maintenance_mode', FALSE); - if ($_SESSION['maintenance_mode'] == FALSE) { - variable_set('maintenance_mode', TRUE); - } - - // Resolve any update dependencies to determine the actual updates that will - // be run and the order they will be run in. - $updates = update_resolve_dependencies($start); - - // Store the dependencies for each update function in an array which the - // batch API can pass in to the batch operation each time it is called. (We - // do not store the entire update dependency array here because it is - // potentially very large.) - $dependency_map = array(); - foreach ($updates as $function => $update) { - $dependency_map[$function] = !empty($update['reverse_paths']) ? array_keys($update['reverse_paths']) : array(); - } - - $operations = array(); - foreach ($updates as $update) { - if ($update['allowed']) { - // Set the installed version of each module so updates will start at the - // correct place. (The updates are already sorted, so we can simply base - // this on the first one we come across in the above foreach loop.) - if (isset($start[$update['module']])) { - drupal_set_installed_schema_version($update['module'], $update['number'] - 1); - unset($start[$update['module']]); - } - // Add this update function to the batch. - $function = $update['module'] . '_update_' . $update['number']; - $operations[] = array('update_do_one', array($update['module'], $update['number'], $dependency_map[$function])); - } - } - $batch['operations'] = $operations; - $batch += array( - 'title' => 'Updating', - 'init_message' => 'Starting updates', - 'error_message' => 'An unrecoverable error has occurred. You can find the error message below. It is advised to copy it to the clipboard for reference.', - 'finished' => 'update_finished', - 'file' => 'core/includes/update.inc', - ); - batch_set($batch); - batch_process($redirect, $url, $redirect_callback); -} - -/** - * Finish the update process and store results for eventual display. - * - * After the updates run, all caches are flushed. The update results are - * stored into the session (for example, to be displayed on the update results - * page in update.php). Additionally, if the site was off-line, now that the - * update process is completed, the site is set back online. - * - * @param $success - * Indicate that the batch API tasks were all completed successfully. - * @param $results - * An array of all the results that were updated in update_do_one(). - * @param $operations - * A list of all the operations that had not been completed by the batch API. - * - * @see update_batch() - */ -function update_finished($success, $results, $operations) { - // Clear the caches in case the data has been updated. - drupal_flush_all_caches(); - - $_SESSION['update_results'] = $results; - $_SESSION['update_success'] = $success; - $_SESSION['updates_remaining'] = $operations; - - // Now that the update is done, we can put the site back online if it was - // previously in maintenance mode. - if (isset($_SESSION['maintenance_mode']) && $_SESSION['maintenance_mode'] == FALSE) { - variable_set('maintenance_mode', FALSE); - unset($_SESSION['maintenance_mode']); - } -} - -/** - * Return a list of all the pending database updates. - * - * @return - * An associative array keyed by module name which contains all information - * about database updates that need to be run, and any updates that are not - * going to proceed due to missing requirements. The system module will - * always be listed first. - * - * The subarray for each module can contain the following keys: - * - start: The starting update that is to be processed. If this does not - * exist then do not process any updates for this module as there are - * other requirements that need to be resolved. - * - warning: Any warnings about why this module can not be updated. - * - pending: An array of all the pending updates for the module including - * the update number and the description from source code comment for - * each update function. This array is keyed by the update number. - */ -function update_get_update_list() { - // Make sure that the system module is first in the list of updates. - $ret = array('system' => array()); - - $modules = drupal_get_installed_schema_version(NULL, FALSE, TRUE); - foreach ($modules as $module => $schema_version) { - // Skip uninstalled and incompatible modules. - if ($schema_version == SCHEMA_UNINSTALLED || update_check_incompatibility($module)) { - continue; - } - // Otherwise, get the list of updates defined by this module. - $updates = drupal_get_schema_versions($module); - if ($updates !== FALSE) { - // module_invoke returns NULL for nonexisting hooks, so if no updates - // are removed, it will == 0. - $last_removed = module_invoke($module, 'update_last_removed'); - if ($schema_version < $last_removed) { - $ret[$module]['warning'] = '<em>' . $module . '</em> module can not be updated. Its schema version is ' . $schema_version . '. Updates up to and including ' . $last_removed . ' have been removed in this release. In order to update <em>' . $module . '</em> module, you will first <a href="http://drupal.org/upgrade">need to upgrade</a> to the last version in which these updates were available.'; - continue; - } - - $updates = drupal_map_assoc($updates); - foreach (array_keys($updates) as $update) { - if ($update > $schema_version) { - // The description for an update comes from its Doxygen. - $func = new ReflectionFunction($module . '_update_' . $update); - $description = str_replace(array("\n", '*', '/'), '', $func->getDocComment()); - $ret[$module]['pending'][$update] = "$update - $description"; - if (!isset($ret[$module]['start'])) { - $ret[$module]['start'] = $update; - } - } - } - if (!isset($ret[$module]['start']) && isset($ret[$module]['pending'])) { - $ret[$module]['start'] = $schema_version; - } - } - } - - if (empty($ret['system'])) { - unset($ret['system']); - } - return $ret; -} - -/** - * Resolves dependencies in a set of module updates, and orders them correctly. - * - * This function receives a list of requested module updates and determines an - * appropriate order to run them in such that all update dependencies are met. - * Any updates whose dependencies cannot be met are included in the returned - * array but have the key 'allowed' set to FALSE; the calling function should - * take responsibility for ensuring that these updates are ultimately not - * performed. - * - * In addition, the returned array also includes detailed information about the - * dependency chain for each update, as provided by the depth-first search - * algorithm in drupal_depth_first_search(). - * - * @param $starting_updates - * An array whose keys contain the names of modules with updates to be run - * and whose values contain the number of the first requested update for that - * module. - * - * @return - * An array whose keys are the names of all update functions within the - * provided modules that would need to be run in order to fulfill the - * request, arranged in the order in which the update functions should be - * run. (This includes the provided starting update for each module and all - * subsequent updates that are available.) The values are themselves arrays - * containing all the keys provided by the drupal_depth_first_search() - * algorithm, which encode detailed information about the dependency chain - * for this update function (for example: 'paths', 'reverse_paths', 'weight', - * and 'component'), as well as the following additional keys: - * - 'allowed': A boolean which is TRUE when the update function's - * dependencies are met, and FALSE otherwise. Calling functions should - * inspect this value before running the update. - * - 'missing_dependencies': An array containing the names of any other - * update functions that are required by this one but that are unavailable - * to be run. This array will be empty when 'allowed' is TRUE. - * - 'module': The name of the module that this update function belongs to. - * - 'number': The number of this update function within that module. - * - * @see drupal_depth_first_search() - */ -function update_resolve_dependencies($starting_updates) { - // Obtain a dependency graph for the requested update functions. - $update_functions = update_get_update_function_list($starting_updates); - $graph = update_build_dependency_graph($update_functions); - - // Perform the depth-first search and sort the results. - require_once DRUPAL_ROOT . '/core/includes/graph.inc'; - drupal_depth_first_search($graph); - uasort($graph, 'drupal_sort_weight'); - - foreach ($graph as $function => &$data) { - $module = $data['module']; - $number = $data['number']; - // If the update function is missing and has not yet been performed, mark - // it and everything that ultimately depends on it as disallowed. - if (update_is_missing($module, $number, $update_functions) && !update_already_performed($module, $number)) { - $data['allowed'] = FALSE; - foreach (array_keys($data['paths']) as $dependent) { - $graph[$dependent]['allowed'] = FALSE; - $graph[$dependent]['missing_dependencies'][] = $function; - } - } - elseif (!isset($data['allowed'])) { - $data['allowed'] = TRUE; - $data['missing_dependencies'] = array(); - } - // Now that we have finished processing this function, remove it from the - // graph if it was not part of the original list. This ensures that we - // never try to run any updates that were not specifically requested. - if (!isset($update_functions[$module][$number])) { - unset($graph[$function]); - } - } - - return $graph; -} - -/** - * Returns an organized list of update functions for a set of modules. - * - * @param $starting_updates - * An array whose keys contain the names of modules and whose values contain - * the number of the first requested update for that module. - * - * @return - * An array containing all the update functions that should be run for each - * module, including the provided starting update and all subsequent updates - * that are available. The keys of the array contain the module names, and - * each value is an ordered array of update functions, keyed by the update - * number. - * - * @see update_resolve_dependencies() - */ -function update_get_update_function_list($starting_updates) { - // Go through each module and find all updates that we need (including the - // first update that was requested and any updates that run after it). - $update_functions = array(); - foreach ($starting_updates as $module => $version) { - $update_functions[$module] = array(); - $updates = drupal_get_schema_versions($module); - if ($updates !== FALSE) { - $max_version = max($updates); - if ($version <= $max_version) { - foreach ($updates as $update) { - if ($update >= $version) { - $update_functions[$module][$update] = $module . '_update_' . $update; - } - } - } - } - } - return $update_functions; -} - -/** - * Constructs a graph which encodes the dependencies between module updates. - * - * This function returns an associative array which contains a "directed graph" - * representation of the dependencies between a provided list of update - * functions, as well as any outside update functions that they directly depend - * on but that were not in the provided list. The vertices of the graph - * represent the update functions themselves, and each edge represents a - * requirement that the first update function needs to run before the second. - * For example, consider this graph: - * - * system_update_8000 ---> system_update_8001 ---> system_update_8002 - * - * Visually, this indicates that system_update_8000() must run before - * system_update_8001(), which in turn must run before system_update_8002(). - * - * The function takes into account standard dependencies within each module, as - * shown above (i.e., the fact that each module's updates must run in numerical - * order), but also finds any cross-module dependencies that are defined by - * modules which implement hook_update_dependencies(), and builds them into the - * graph as well. - * - * @param $update_functions - * An organized array of update functions, in the format returned by - * update_get_update_function_list(). - * - * @return - * A multidimensional array representing the dependency graph, suitable for - * passing in to drupal_depth_first_search(), but with extra information - * about each update function also included. Each array key contains the name - * of an update function, including all update functions from the provided - * list as well as any outside update functions which they directly depend - * on. Each value is an associative array containing the following keys: - * - 'edges': A representation of any other update functions that immediately - * depend on this one. See drupal_depth_first_search() for more details on - * the format. - * - 'module': The name of the module that this update function belongs to. - * - 'number': The number of this update function within that module. - * - * @see drupal_depth_first_search() - * @see update_resolve_dependencies() - */ -function update_build_dependency_graph($update_functions) { - // Initialize an array that will define a directed graph representing the - // dependencies between update functions. - $graph = array(); - - // Go through each update function and build an initial list of dependencies. - foreach ($update_functions as $module => $functions) { - $previous_function = NULL; - foreach ($functions as $number => $function) { - // Add an edge to the directed graph representing the fact that each - // update function in a given module must run after the update that - // numerically precedes it. - if ($previous_function) { - $graph[$previous_function]['edges'][$function] = TRUE; - } - $previous_function = $function; - - // Define the module and update number associated with this function. - $graph[$function]['module'] = $module; - $graph[$function]['number'] = $number; - } - } - - // Now add any explicit update dependencies declared by modules. - $update_dependencies = update_retrieve_dependencies(); - foreach ($graph as $function => $data) { - if (!empty($update_dependencies[$data['module']][$data['number']])) { - foreach ($update_dependencies[$data['module']][$data['number']] as $module => $number) { - $dependency = $module . '_update_' . $number; - $graph[$dependency]['edges'][$function] = TRUE; - $graph[$dependency]['module'] = $module; - $graph[$dependency]['number'] = $number; - } - } - } - - return $graph; -} - -/** - * Determines if a module update is missing or unavailable. - * - * @param $module - * The name of the module. - * @param $number - * The number of the update within that module. - * @param $update_functions - * An organized array of update functions, in the format returned by - * update_get_update_function_list(). This should represent all module - * updates that are requested to run at the time this function is called. - * - * @return - * TRUE if the provided module update is not installed or is not in the - * provided list of updates to run; FALSE otherwise. - */ -function update_is_missing($module, $number, $update_functions) { - return !isset($update_functions[$module][$number]) || !function_exists($update_functions[$module][$number]); -} - -/** - * Determines if a module update has already been performed. - * - * @param $module - * The name of the module. - * @param $number - * The number of the update within that module. - * - * @return - * TRUE if the database schema indicates that the update has already been - * performed; FALSE otherwise. - */ -function update_already_performed($module, $number) { - return $number <= drupal_get_installed_schema_version($module); -} - -/** - * Invoke hook_update_dependencies() in all installed modules. - * - * This function is similar to module_invoke_all(), with the main difference - * that it does not require that a module be enabled to invoke its hook, only - * that it be installed. This allows the update system to properly perform - * updates even on modules that are currently disabled. - * - * @return - * An array of return values obtained by merging the results of the - * hook_update_dependencies() implementations in all installed modules. - * - * @see module_invoke_all() - * @see hook_update_dependencies() - */ -function update_retrieve_dependencies() { - $return = array(); - // Get a list of installed modules, arranged so that we invoke their hooks in - // the same order that module_invoke_all() does. - $modules = db_query("SELECT name FROM {system} WHERE type = 'module' AND schema_version <> :schema ORDER BY weight ASC, name ASC", array(':schema' => SCHEMA_UNINSTALLED))->fetchCol(); - foreach ($modules as $module) { - $function = $module . '_update_dependencies'; - if (function_exists($function)) { - $result = $function(); - // Each implementation of hook_update_dependencies() returns a - // multidimensional, associative array containing some keys that - // represent module names (which are strings) and other keys that - // represent update function numbers (which are integers). We cannot use - // array_merge_recursive() to properly merge these results, since it - // treats strings and integers differently. Therefore, we have to - // explicitly loop through the expected array structure here and perform - // the merge manually. - if (isset($result) && is_array($result)) { - foreach ($result as $module => $module_data) { - foreach ($module_data as $update => $update_data) { - foreach ($update_data as $module_dependency => $update_dependency) { - // If there are redundant dependencies declared for the same - // update function (so that it is declared to depend on more than - // one update from a particular module), record the dependency on - // the highest numbered update here, since that automatically - // implies the previous ones. For example, if one module's - // implementation of hook_update_dependencies() required this - // ordering: - // - // system_update_8001 ---> user_update_8000 - // - // but another module's implementation of the hook required this - // one: - // - // system_update_8002 ---> user_update_8000 - // - // we record the second one, since system_update_8001() is always - // guaranteed to run before system_update_8002() anyway (within - // an individual module, updates are always run in numerical - // order). - if (!isset($return[$module][$update][$module_dependency]) || $update_dependency > $return[$module][$update][$module_dependency]) { - $return[$module][$update][$module_dependency] = $update_dependency; - } - } - } - } - } - } - } - - return $return; -} - -/** - * @defgroup update-api-7.x-to-8.x Update versions of API functions - * @{ - * Functions similar to normal API function but not firing hooks. - * - * During update, it is impossible to judge the consequences of firing a hook - * as it might hit a module not yet updated. So simplified versions of some - * core APIs are provided. - */ - -/** - * @} End of "defgroup update-api-7.x-to-8.x" - */ diff --git a/core/includes/updater.inc b/core/includes/updater.inc deleted file mode 100644 index 363c6ebffaa..00000000000 --- a/core/includes/updater.inc +++ /dev/null @@ -1,427 +0,0 @@ -<?php - -/** - * @file - * Classes used for updating various files in the Drupal webroot. These - * classes use a FileTransfer object to actually perform the operations. - * Normally, the FileTransfer is provided when the site owner is redirected to - * authorize.php as part of a multistep process. - */ - -/** - * Interface for a class which can update a Drupal project. - * - * An Updater currently serves the following purposes: - * - It can take a given directory, and determine if it can operate on it. - * - It can move the contents of that directory into the appropriate place - * on the system using FileTransfer classes. - * - It can return a list of "next steps" after an update or install. - * - In the future, it will most likely perform some of those steps as well. - */ -interface DrupalUpdaterInterface { - - /** - * Checks if the project is installed. - * - * @return bool - */ - public function isInstalled(); - - /** - * Returns the system name of the project. - * - * @param string $directory - * A directory containing a project. - */ - public static function getProjectName($directory); - - /** - * @return string - * An absolute path to the default install location. - */ - public function getInstallDirectory(); - - /** - * Determine if the Updater can handle the project provided in $directory. - * - * @todo: Provide something more rational here, like a project spec file. - * - * @param string $directory - * - * @return bool - * TRUE if the project is installed, FALSE if not. - */ - public static function canUpdateDirectory($directory); - - /** - * Actions to run after an install has occurred. - */ - public function postInstall(); - - /** - * Actions to run after an update has occurred. - */ - public function postUpdate(); -} - -/** - * Base class for Updaters used in Drupal. - */ -class Updater { - - /** - * @var string $source Directory to install from. - */ - public $source; - - public function __construct($source) { - $this->source = $source; - $this->name = self::getProjectName($source); - $this->title = self::getProjectTitle($source); - } - - /** - * Return an Updater of the appropriate type depending on the source. - * - * If a directory is provided which contains a module, will return a - * ModuleUpdater. - * - * @param string $source - * Directory of a Drupal project. - * - * @return Updater - */ - public static function factory($source) { - if (is_dir($source)) { - $updater = self::getUpdaterFromDirectory($source); - } - else { - throw new UpdaterException(t('Unable to determine the type of the source directory.')); - } - return new $updater($source); - } - - /** - * Determine which Updater class can operate on the given directory. - * - * @param string $directory - * Extracted Drupal project. - * - * @return string - * The class name which can work with this project type. - */ - public static function getUpdaterFromDirectory($directory) { - // Gets a list of possible implementing classes. - $updaters = drupal_get_updaters(); - foreach ($updaters as $updater) { - $class = $updater['class']; - if (call_user_func(array($class, 'canUpdateDirectory'), $directory)) { - return $class; - } - } - throw new UpdaterException(t('Cannot determine the type of project.')); - } - - /** - * Figure out what the most important (or only) info file is in a directory. - * - * Since there is no enforcement of which info file is the project's "main" - * info file, this will get one with the same name as the directory, or the - * first one it finds. Not ideal, but needs a larger solution. - * - * @param string $directory - * Directory to search in. - * - * @return string - * Path to the info file. - */ - public static function findInfoFile($directory) { - $info_files = file_scan_directory($directory, '/.*\.info$/'); - if (!$info_files) { - return FALSE; - } - foreach ($info_files as $info_file) { - if (drupal_substr($info_file->filename, 0, -5) == basename($directory)) { - // Info file Has the same name as the directory, return it. - return $info_file->uri; - } - } - // Otherwise, return the first one. - $info_file = array_shift($info_files); - return $info_file->uri; - } - - /** - * Get the name of the project directory (basename). - * - * @todo: It would be nice, if projects contained an info file which could - * provide their canonical name. - * - * @param string $directory - * - * @return string - * The name of the project. - */ - public static function getProjectName($directory) { - return basename($directory); - } - - /** - * Return the project name from a Drupal info file. - * - * @param string $directory - * Directory to search for the info file. - * - * @return string - * The title of the project. - */ - public static function getProjectTitle($directory) { - $info_file = self::findInfoFile($directory); - $info = drupal_parse_info_file($info_file); - if (empty($info)) { - throw new UpdaterException(t('Unable to parse info file: %info_file.', array('%info_file' => $info_file))); - } - if (empty($info['name'])) { - throw new UpdaterException(t("The info file (%info_file) does not define a 'name' attribute.", array('%info_file' => $info_file))); - } - return $info['name']; - } - - /** - * Store the default parameters for the Updater. - * - * @param array $overrides - * An array of overrides for the default parameters. - * - * @return array - * An array of configuration parameters for an update or install operation. - */ - protected function getInstallArgs($overrides = array()) { - $args = array( - 'make_backup' => FALSE, - 'install_dir' => $this->getInstallDirectory(), - 'backup_dir' => $this->getBackupDir(), - ); - return array_merge($args, $overrides); - } - - /** - * Updates a Drupal project, returns a list of next actions. - * - * @param FileTransfer $filetransfer - * Object that is a child of FileTransfer. Used for moving files - * to the server. - * @param array $overrides - * An array of settings to override defaults; see self::getInstallArgs(). - * - * @return array - * An array of links which the user may need to complete the update - */ - public function update(&$filetransfer, $overrides = array()) { - try { - // Establish arguments with possible overrides. - $args = $this->getInstallArgs($overrides); - - // Take a Backup. - if ($args['make_backup']) { - $this->makeBackup($args['install_dir'], $args['backup_dir']); - } - - if (!$this->name) { - // This is bad, don't want to delete the install directory. - throw new UpdaterException(t('Fatal error in update, cowardly refusing to wipe out the install directory.')); - } - - // Make sure the installation parent directory exists and is writable. - $this->prepareInstallDirectory($filetransfer, $args['install_dir']); - - // Note: If the project is installed in sites/all, it will not be - // deleted. It will be installed in sites/default as that will override - // the sites/all reference and not break other sites which are using it. - if (is_dir($args['install_dir'] . '/' . $this->name)) { - // Remove the existing installed file. - $filetransfer->removeDirectory($args['install_dir'] . '/' . $this->name); - } - - // Copy the directory in place. - $filetransfer->copyDirectory($this->source, $args['install_dir']); - - // Make sure what we just installed is readable by the web server. - $this->makeWorldReadable($filetransfer, $args['install_dir'] . '/' . $this->name); - - // Run the updates. - // @TODO: decide if we want to implement this. - $this->postUpdate(); - - // For now, just return a list of links of things to do. - return $this->postUpdateTasks(); - } - catch (FileTransferException $e) { - throw new UpdaterFileTransferException(t('File Transfer failed, reason: !reason', array('!reason' => strtr($e->getMessage(), $e->arguments)))); - } - } - - /** - * Installs a Drupal project, returns a list of next actions. - * - * @param FileTransfer $filetransfer - * Object that is a child of FileTransfer. - * @param array $overrides - * An array of settings to override defaults; see self::getInstallArgs(). - * - * @return array - * An array of links which the user may need to complete the install. - */ - public function install(&$filetransfer, $overrides = array()) { - try { - // Establish arguments with possible overrides. - $args = $this->getInstallArgs($overrides); - - // Make sure the installation parent directory exists and is writable. - $this->prepareInstallDirectory($filetransfer, $args['install_dir']); - - // Copy the directory in place. - $filetransfer->copyDirectory($this->source, $args['install_dir']); - - // Make sure what we just installed is readable by the web server. - $this->makeWorldReadable($filetransfer, $args['install_dir'] . '/' . $this->name); - - // Potentially enable something? - // @TODO: decide if we want to implement this. - $this->postInstall(); - // For now, just return a list of links of things to do. - return $this->postInstallTasks(); - } - catch (FileTransferException $e) { - throw new UpdaterFileTransferException(t('File Transfer failed, reason: !reason', array('!reason' => strtr($e->getMessage(), $e->arguments)))); - } - } - - /** - * Make sure the installation parent directory exists and is writable. - * - * @param FileTransfer $filetransfer - * Object which is a child of FileTransfer. - * @param string $directory - * The installation directory to prepare. - */ - public function prepareInstallDirectory(&$filetransfer, $directory) { - // Make the parent dir writable if need be and create the dir. - if (!is_dir($directory)) { - $parent_dir = dirname($directory); - if (!is_writable($parent_dir)) { - @chmod($parent_dir, 0755); - // It is expected that this will fail if the directory is owned by the - // FTP user. If the FTP user == web server, it will succeed. - try { - $filetransfer->createDirectory($directory); - $this->makeWorldReadable($filetransfer, $directory); - } - catch (FileTransferException $e) { - // Probably still not writable. Try to chmod and do it again. - // @todo: Make a new exception class so we can catch it differently. - try { - $old_perms = substr(sprintf('%o', fileperms($parent_dir)), -4); - $filetransfer->chmod($parent_dir, 0755); - $filetransfer->createDirectory($directory); - $this->makeWorldReadable($filetransfer, $directory); - // Put the permissions back. - $filetransfer->chmod($parent_dir, intval($old_perms, 8)); - } - catch (FileTransferException $e) { - $message = t($e->getMessage(), $e->arguments); - $throw_message = t('Unable to create %directory due to the following: %reason', array('%directory' => $install_location, '%reason' => $message)); - throw new UpdaterException($throw_message); - } - } - // Put the parent directory back. - @chmod($parent_dir, 0555); - } - } - } - - /** - * Ensure that a given directory is world readable. - * - * @param FileTransfer $filetransfer - * Object which is a child of FileTransfer. - * @param string $path - * The file path to make world readable. - * @param bool $recursive - * If the chmod should be applied recursively. - */ - public function makeWorldReadable(&$filetransfer, $path, $recursive = TRUE) { - if (!is_executable($path)) { - // Set it to read + execute. - $new_perms = substr(sprintf('%o', fileperms($path)), -4, -1) . "5"; - $filetransfer->chmod($path, intval($new_perms, 8), $recursive); - } - } - - /** - * Perform a backup. - * - * @todo Not implemented. - */ - public function makeBackup(&$filetransfer, $from, $to) { - } - - /** - * Return the full path to a directory where backups should be written. - */ - public function getBackupDir() { - return file_stream_wrapper_get_instance_by_scheme('temporary')->getDirectoryPath(); - } - - /** - * Perform actions after new code is updated. - */ - public function postUpdate() { - } - - /** - * Perform actions after installation. - */ - public function postInstall() { - } - - /** - * Return an array of links to pages that should be visited post operation. - * - * @return array - * Links which provide actions to take after the install is finished. - */ - public function postInstallTasks() { - return array(); - } - - /** - * Return an array of links to pages that should be visited post operation. - * - * @return array - * Links which provide actions to take after the update is finished. - */ - public function postUpdateTasks() { - return array(); - } -} - -/** - * Exception class for the Updater class hierarchy. - * - * This is identical to the base Exception class, we just give it a more - * specific name so that call sites that want to tell the difference can - * specifically catch these exceptions and treat them differently. - */ -class UpdaterException extends Exception { -} - -/** - * Child class of UpdaterException that indicates a FileTransfer exception. - * - * We have to catch FileTransfer exceptions and wrap those in t(), since - * FileTransfer is so low-level that it doesn't use any Drupal APIs and none - * of the strings are translated. - */ -class UpdaterFileTransferException extends UpdaterException { -} diff --git a/core/includes/utility.inc b/core/includes/utility.inc deleted file mode 100644 index 7d82f3292e9..00000000000 --- a/core/includes/utility.inc +++ /dev/null @@ -1,65 +0,0 @@ -<?php - -/** - * @file - * Miscellaneous functions. - */ - -/** - * Drupal-friendly var_export(). - * - * @param $var - * The variable to export. - * @param $prefix - * A prefix that will be added at the beginning of every lines of the output. - * @return - * The variable exported in a way compatible to Drupal's coding standards. - */ -function drupal_var_export($var, $prefix = '') { - if (is_array($var)) { - if (empty($var)) { - $output = 'array()'; - } - else { - $output = "array(\n"; - // Don't export keys if the array is non associative. - $export_keys = array_values($var) != $var; - foreach ($var as $key => $value) { - $output .= ' ' . ($export_keys ? drupal_var_export($key) . ' => ' : '') . drupal_var_export($value, ' ', FALSE) . ",\n"; - } - $output .= ')'; - } - } - elseif (is_bool($var)) { - $output = $var ? 'TRUE' : 'FALSE'; - } - elseif (is_string($var)) { - $line_safe_var = str_replace("\n", '\n', $var); - if (strpos($var, "\n") !== FALSE || strpos($var, "'") !== FALSE) { - // If the string contains a line break or a single quote, use the - // double quote export mode. Encode backslash and double quotes and - // transform some common control characters. - $var = str_replace(array('\\', '"', "\n", "\r", "\t"), array('\\\\', '\"', '\n', '\r', '\t'), $var); - $output = '"' . $var . '"'; - } - else { - $output = "'" . $var . "'"; - } - } - else if (is_object($var) && get_class($var) === 'stdClass') { - // var_export() will export stdClass objects using an undefined - // magic method __set_state() leaving the export broken. This - // workaround avoids this by casting the object as an array for - // export and casting it back to an object when evaluated. - $output .= '(object) ' . drupal_var_export((array) $var, $prefix); - } - else { - $output = var_export($var, TRUE); - } - - if ($prefix) { - $output = str_replace("\n", "\n$prefix", $output); - } - - return $output; -} diff --git a/core/includes/uuid.inc b/core/includes/uuid.inc deleted file mode 100644 index 57f2199d7b8..00000000000 --- a/core/includes/uuid.inc +++ /dev/null @@ -1,154 +0,0 @@ -<?php - -/** - * @file - * Handling of universally unique identifiers. - */ - -/** - * Interface that defines a UUID backend. - */ -interface UuidInterface { - - /** - * Generates a Universally Unique IDentifier (UUID). - * - * @return - * A 32 byte integer represented as a hex string formatted with 4 hypens. - */ - public function generate(); - -} - -/** - * Factory class for UUIDs. - * - * Determines which UUID implementation to use, and uses that to generate - * and validate UUIDs. - */ -class Uuid { - - /** - * Holds the UUID implementation. - */ - protected $plugin; - - /** - * This constructor instantiates the correct UUID object. - */ - public function __construct() { - $class = $this->determinePlugin(); - $this->plugin = new $class(); - } - - /** - * Generates an universally unique identifier. - * - * @see UuidInterface::generate() - */ - public function generate() { - return $this->plugin->generate(); - } - - /** - * Check that a string appears to be in the format of a UUID. - * - * Plugins should not implement validation, since UUIDs should be in a - * consistent format across all plugins. - * - * @param $uuid - * The string to test. - * - * @return - * TRUE if the string is well formed. - */ - public function isValid($uuid) { - return preg_match("/^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/", $uuid); - } - - /** - * Determines the optimal implementation to use for generating UUIDs. - * - * The selection is made based on the enabled PHP extensions with the - * most performant available option chosen. - * - * @return - * The class name for the optimal UUID generator. - */ - protected function determinePlugin() { - static $plugin; - if (!empty($plugin)) { - return $plugin; - } - - $plugin = 'UuidPhp'; - - // Debian/Ubuntu uses the (broken) OSSP extension as their UUID - // implementation. The OSSP implementation is not compatible with the - // PECL functions. - if (function_exists('uuid_create') && !function_exists('uuid_make')) { - $plugin = 'UuidPecl'; - } - // Try to use the COM implementation for Windows users. - elseif (function_exists('com_create_guid')) { - $plugin = 'UuidCom'; - } - return $plugin; - } -} - -/** - * UUID implementation using the PECL extension. - */ -class UuidPecl implements UuidInterface { - public function generate() { - return uuid_create(UUID_TYPE_DEFAULT); - } -} - -/** - * UUID implementation using the Windows internal GUID extension. - * - * @see http://php.net/com_create_guid - */ -class UuidCom implements UuidInterface { - public function generate() { - // Remove {} wrapper and make lower case to keep result consistent. - return drupal_strtolower(trim(com_create_guid(), '{}')); - } -} - -/** - * Generates an UUID v4 using PHP code. - * - * Loosely based on Ruby's UUIDTools generate_random logic. - * - * @see http://uuidtools.rubyforge.org/api/classes/UUIDTools/UUID.html - */ -class UuidPhp implements UuidInterface { - public function generate() { - $hex = substr(hash('sha256', drupal_random_bytes(16)), 0, 32); - - // The field names refer to RFC 4122 section 4.1.2. - $time_low = substr($hex, 0, 8); - $time_mid = substr($hex, 8, 4); - - $time_hi_and_version = base_convert(substr($hex, 12, 4), 16, 10); - $time_hi_and_version &= 0x0FFF; - $time_hi_and_version |= (4 << 12); - - $clock_seq_hi_and_reserved = base_convert(substr($hex, 16, 4), 16, 10); - $clock_seq_hi_and_reserved &= 0x3F; - $clock_seq_hi_and_reserved |= 0x80; - - $clock_seq_low = substr($hex, 20, 2); - $nodes = substr($hex, 20); - - $uuid = sprintf('%s-%s-%04x-%02x%02x-%s', - $time_low, $time_mid, - $time_hi_and_version, $clock_seq_hi_and_reserved, - $clock_seq_low, $nodes); - - return $uuid; - } -} diff --git a/core/includes/xmlrpc.inc b/core/includes/xmlrpc.inc deleted file mode 100644 index 92e5d14f0fc..00000000000 --- a/core/includes/xmlrpc.inc +++ /dev/null @@ -1,625 +0,0 @@ -<?php - -/** - * @file - * Drupal XML-RPC library. - * - * Based on the IXR - The Incutio XML-RPC Library - (c) Incutio Ltd 2002-2005 - * Version 1.7 (beta) - Simon Willison, 23rd May 2005 - * Site: http://scripts.incutio.com/xmlrpc/ - * Manual: http://scripts.incutio.com/xmlrpc/manual.php - * This version is made available under the GNU GPL License - */ - -/** - * Turns a data structure into objects with 'data' and 'type' attributes. - * - * @param $data - * The data structure. - * @param $type - * Optional type to assign to $data. - * - * @return object - * An XML-RPC data object containing the input $data. - */ -function xmlrpc_value($data, $type = FALSE) { - $xmlrpc_value = new stdClass(); - $xmlrpc_value->data = $data; - if (!$type) { - $type = xmlrpc_value_calculate_type($xmlrpc_value); - } - $xmlrpc_value->type = $type; - if ($type == 'struct') { - // Turn all the values in the array into new xmlrpc_values - foreach ($xmlrpc_value->data as $key => $value) { - $xmlrpc_value->data[$key] = xmlrpc_value($value); - } - } - if ($type == 'array') { - for ($i = 0, $j = count($xmlrpc_value->data); $i < $j; $i++) { - $xmlrpc_value->data[$i] = xmlrpc_value($xmlrpc_value->data[$i]); - } - } - return $xmlrpc_value; -} - -/** - * Maps a PHP type to an XML-RPC type. - * - * @param $xmlrpc_value - * Variable whose type should be mapped. - * - * @return string - * The corresponding XML-RPC type. - * - * @see http://www.xmlrpc.com/spec#scalars - */ -function xmlrpc_value_calculate_type($xmlrpc_value) { - // http://www.php.net/gettype: Never use gettype() to test for a certain type - // [...] Instead, use the is_* functions. - if (is_bool($xmlrpc_value->data)) { - return 'boolean'; - } - if (is_double($xmlrpc_value->data)) { - return 'double'; - } - if (is_int($xmlrpc_value->data)) { - return 'int'; - } - if (is_array($xmlrpc_value->data)) { - // empty or integer-indexed arrays are 'array', string-indexed arrays 'struct' - return empty($xmlrpc_value->data) || range(0, count($xmlrpc_value->data) - 1) === array_keys($xmlrpc_value->data) ? 'array' : 'struct'; - } - if (is_object($xmlrpc_value->data)) { - if (isset($xmlrpc_value->data->is_date)) { - return 'date'; - } - if (isset($xmlrpc_value->data->is_base64)) { - return 'base64'; - } - $xmlrpc_value->data = get_object_vars($xmlrpc_value->data); - return 'struct'; - } - // default - return 'string'; -} - -/** - * Generates XML representing the given value. - * - * @param $xmlrpc_value - * A value to be represented in XML. - * - * @return - * XML representation of $xmlrpc_value. - */ -function xmlrpc_value_get_xml($xmlrpc_value) { - switch ($xmlrpc_value->type) { - case 'boolean': - return '<boolean>' . (($xmlrpc_value->data) ? '1' : '0') . '</boolean>'; - - case 'int': - return '<int>' . $xmlrpc_value->data . '</int>'; - - case 'double': - return '<double>' . $xmlrpc_value->data . '</double>'; - - case 'string': - // Note: we don't escape apostrophes because of the many blogging clients - // that don't support numerical entities (and XML in general) properly. - return '<string>' . htmlspecialchars($xmlrpc_value->data) . '</string>'; - - case 'array': - $return = '<array><data>' . "\n"; - foreach ($xmlrpc_value->data as $item) { - $return .= ' <value>' . xmlrpc_value_get_xml($item) . "</value>\n"; - } - $return .= '</data></array>'; - return $return; - - case 'struct': - $return = '<struct>' . "\n"; - foreach ($xmlrpc_value->data as $name => $value) { - $return .= " <member><name>" . check_plain($name) . "</name><value>"; - $return .= xmlrpc_value_get_xml($value) . "</value></member>\n"; - } - $return .= '</struct>'; - return $return; - - case 'date': - return xmlrpc_date_get_xml($xmlrpc_value->data); - - case 'base64': - return xmlrpc_base64_get_xml($xmlrpc_value->data); - } - return FALSE; -} - -/** - * Constructs an object representing an XML-RPC message. - * - * @param $message - * A string containing an XML message. - * - * @return object - * An XML-RPC object containing the message. - * - * @see http://www.xmlrpc.com/spec - */ -function xmlrpc_message($message) { - $xmlrpc_message = new stdClass(); - // The stack used to keep track of the current array/struct - $xmlrpc_message->array_structs = array(); - // The stack used to keep track of if things are structs or array - $xmlrpc_message->array_structs_types = array(); - // A stack as well - $xmlrpc_message->current_struct_name = array(); - $xmlrpc_message->message = $message; - return $xmlrpc_message; -} - -/** - * Parses an XML-RPC message. - * - * If parsing fails, the faultCode and faultString will be added to the message - * object. - * - * @param $xmlrpc_message - * An object generated by xmlrpc_message(). - * - * @return - * TRUE if parsing succeeded; FALSE otherwise. - */ -function xmlrpc_message_parse($xmlrpc_message) { - $xmlrpc_message->_parser = xml_parser_create(); - // Set XML parser to take the case of tags into account. - xml_parser_set_option($xmlrpc_message->_parser, XML_OPTION_CASE_FOLDING, FALSE); - // Set XML parser callback functions - xml_set_element_handler($xmlrpc_message->_parser, 'xmlrpc_message_tag_open', 'xmlrpc_message_tag_close'); - xml_set_character_data_handler($xmlrpc_message->_parser, 'xmlrpc_message_cdata'); - xmlrpc_message_set($xmlrpc_message); - if (!xml_parse($xmlrpc_message->_parser, $xmlrpc_message->message)) { - return FALSE; - } - xml_parser_free($xmlrpc_message->_parser); - - // Grab the error messages, if any. - $xmlrpc_message = xmlrpc_message_get(); - if (!isset($xmlrpc_message->messagetype)) { - return FALSE; - } - elseif ($xmlrpc_message->messagetype == 'fault') { - $xmlrpc_message->fault_code = $xmlrpc_message->params[0]['faultCode']; - $xmlrpc_message->fault_string = $xmlrpc_message->params[0]['faultString']; - } - return TRUE; -} - -/** - * Stores a copy of the most recent XML-RPC message object temporarily. - * - * @param $value - * An XML-RPC message to store, or NULL to keep the last message. - * - * @return object - * The most recently stored message. - * - * @see xmlrpc_message_get() - */ -function xmlrpc_message_set($value = NULL) { - static $xmlrpc_message; - if ($value) { - $xmlrpc_message = $value; - } - return $xmlrpc_message; -} - -/** - * Returns the most recently stored XML-RPC message object. - * - * @return object - * The most recently stored message. - * - * @see xmlrpc_message_set() - */ -function xmlrpc_message_get() { - return xmlrpc_message_set(); -} - -/** - * Handles opening tags for XML parsing in xmlrpc_message_parse(). - */ -function xmlrpc_message_tag_open($parser, $tag, $attr) { - $xmlrpc_message = xmlrpc_message_get(); - $xmlrpc_message->current_tag_contents = ''; - $xmlrpc_message->last_open = $tag; - switch ($tag) { - case 'methodCall': - case 'methodResponse': - case 'fault': - $xmlrpc_message->messagetype = $tag; - break; - - // Deal with stacks of arrays and structs - case 'data': - $xmlrpc_message->array_structs_types[] = 'array'; - $xmlrpc_message->array_structs[] = array(); - break; - - case 'struct': - $xmlrpc_message->array_structs_types[] = 'struct'; - $xmlrpc_message->array_structs[] = array(); - break; - } - xmlrpc_message_set($xmlrpc_message); -} - -/** - * Handles character data for XML parsing in xmlrpc_message_parse(). - */ -function xmlrpc_message_cdata($parser, $cdata) { - $xmlrpc_message = xmlrpc_message_get(); - $xmlrpc_message->current_tag_contents .= $cdata; - xmlrpc_message_set($xmlrpc_message); -} - -/** - * Handles closing tags for XML parsing in xmlrpc_message_parse(). - */ -function xmlrpc_message_tag_close($parser, $tag) { - $xmlrpc_message = xmlrpc_message_get(); - $value_flag = FALSE; - switch ($tag) { - case 'int': - case 'i4': - $value = (int)trim($xmlrpc_message->current_tag_contents); - $value_flag = TRUE; - break; - - case 'double': - $value = (double)trim($xmlrpc_message->current_tag_contents); - $value_flag = TRUE; - break; - - case 'string': - $value = $xmlrpc_message->current_tag_contents; - $value_flag = TRUE; - break; - - case 'dateTime.iso8601': - $value = xmlrpc_date(trim($xmlrpc_message->current_tag_contents)); - // $value = $iso->getTimestamp(); - $value_flag = TRUE; - break; - - case 'value': - // If no type is indicated, the type is string - // We take special care for empty values - if (trim($xmlrpc_message->current_tag_contents) != '' || (isset($xmlrpc_message->last_open) && ($xmlrpc_message->last_open == 'value'))) { - $value = (string) $xmlrpc_message->current_tag_contents; - $value_flag = TRUE; - } - unset($xmlrpc_message->last_open); - break; - - case 'boolean': - $value = (boolean)trim($xmlrpc_message->current_tag_contents); - $value_flag = TRUE; - break; - - case 'base64': - $value = base64_decode(trim($xmlrpc_message->current_tag_contents)); - $value_flag = TRUE; - break; - - // Deal with stacks of arrays and structs - case 'data': - case 'struct': - $value = array_pop($xmlrpc_message->array_structs); - array_pop($xmlrpc_message->array_structs_types); - $value_flag = TRUE; - break; - - case 'member': - array_pop($xmlrpc_message->current_struct_name); - break; - - case 'name': - $xmlrpc_message->current_struct_name[] = trim($xmlrpc_message->current_tag_contents); - break; - - case 'methodName': - $xmlrpc_message->methodname = trim($xmlrpc_message->current_tag_contents); - break; - } - if ($value_flag) { - if (count($xmlrpc_message->array_structs) > 0) { - // Add value to struct or array - if ($xmlrpc_message->array_structs_types[count($xmlrpc_message->array_structs_types) - 1] == 'struct') { - // Add to struct - $xmlrpc_message->array_structs[count($xmlrpc_message->array_structs) - 1][$xmlrpc_message->current_struct_name[count($xmlrpc_message->current_struct_name) - 1]] = $value; - } - else { - // Add to array - $xmlrpc_message->array_structs[count($xmlrpc_message->array_structs) - 1][] = $value; - } - } - else { - // Just add as a parameter - $xmlrpc_message->params[] = $value; - } - } - if (!in_array($tag, array("data", "struct", "member"))) { - $xmlrpc_message->current_tag_contents = ''; - } - xmlrpc_message_set($xmlrpc_message); -} - -/** - * Constructs an object representing an XML-RPC request. - * - * @param $method - * The name of the method to be called. - * @param $args - * An array of parameters to send with the method. - * - * @return object - * An XML-RPC object representing the request. - */ -function xmlrpc_request($method, $args) { - $xmlrpc_request = new stdClass(); - $xmlrpc_request->method = $method; - $xmlrpc_request->args = $args; - $xmlrpc_request->xml = <<<EOD -<?xml version="1.0"?> -<methodCall> -<methodName>{$xmlrpc_request->method}</methodName> -<params> - -EOD; - foreach ($xmlrpc_request->args as $arg) { - $xmlrpc_request->xml .= '<param><value>'; - $v = xmlrpc_value($arg); - $xmlrpc_request->xml .= xmlrpc_value_get_xml($v); - $xmlrpc_request->xml .= "</value></param>\n"; - } - $xmlrpc_request->xml .= '</params></methodCall>'; - return $xmlrpc_request; -} - -/** - * Generates, temporarily saves, and returns an XML-RPC error object. - * - * @param $code - * The error code. - * @param $message - * The error message. - * @param $reset - * TRUE to empty the temporary error storage. Ignored if $code is supplied. - * - * @return object - * An XML-RPC error object representing $code and $message, or the most - * recently stored error object if omitted. - */ -function xmlrpc_error($code = NULL, $message = NULL, $reset = FALSE) { - static $xmlrpc_error; - if (isset($code)) { - $xmlrpc_error = new stdClass(); - $xmlrpc_error->is_error = TRUE; - $xmlrpc_error->code = $code; - $xmlrpc_error->message = $message; - } - elseif ($reset) { - $xmlrpc_error = NULL; - } - return $xmlrpc_error; -} - -/** - * Converts an XML-RPC error object into XML. - * - * @param $xmlrpc_error - * The XML-RPC error object. - * - * @return string - * An XML representation of the error as an XML methodResponse. - */ -function xmlrpc_error_get_xml($xmlrpc_error) { - return <<<EOD -<methodResponse> - <fault> - <value> - <struct> - <member> - <name>faultCode</name> - <value><int>{$xmlrpc_error->code}</int></value> - </member> - <member> - <name>faultString</name> - <value><string>{$xmlrpc_error->message}</string></value> - </member> - </struct> - </value> - </fault> -</methodResponse> - -EOD; -} - -/** - * Converts a PHP or ISO date/time to an XML-RPC object. - * - * @param $time - * A PHP timestamp or an ISO date-time string. - * - * @return object - * An XML-RPC time/date object. - */ -function xmlrpc_date($time) { - $xmlrpc_date = new stdClass(); - $xmlrpc_date->is_date = TRUE; - // $time can be a PHP timestamp or an ISO one - if (is_numeric($time)) { - $xmlrpc_date->year = gmdate('Y', $time); - $xmlrpc_date->month = gmdate('m', $time); - $xmlrpc_date->day = gmdate('d', $time); - $xmlrpc_date->hour = gmdate('H', $time); - $xmlrpc_date->minute = gmdate('i', $time); - $xmlrpc_date->second = gmdate('s', $time); - $xmlrpc_date->iso8601 = gmdate('Ymd\TH:i:s', $time); - } - else { - $xmlrpc_date->iso8601 = $time; - $time = str_replace(array('-', ':'), '', $time); - $xmlrpc_date->year = substr($time, 0, 4); - $xmlrpc_date->month = substr($time, 4, 2); - $xmlrpc_date->day = substr($time, 6, 2); - $xmlrpc_date->hour = substr($time, 9, 2); - $xmlrpc_date->minute = substr($time, 11, 2); - $xmlrpc_date->second = substr($time, 13, 2); - } - return $xmlrpc_date; -} - -/** - * Converts an XML-RPC date-time object into XML. - * - * @param $xmlrpc_date - * The XML-RPC date-time object. - * - * @return string - * An XML representation of the date/time as XML. - */ -function xmlrpc_date_get_xml($xmlrpc_date) { - return '<dateTime.iso8601>' . $xmlrpc_date->year . $xmlrpc_date->month . $xmlrpc_date->day . 'T' . $xmlrpc_date->hour . ':' . $xmlrpc_date->minute . ':' . $xmlrpc_date->second . '</dateTime.iso8601>'; -} - -/** - * Returns an XML-RPC base 64 object. - * - * @param $data - * Base 64 data to store in returned object. - * - * @return object - * An XML-RPC base 64 object. - */ -function xmlrpc_base64($data) { - $xmlrpc_base64 = new stdClass(); - $xmlrpc_base64->is_base64 = TRUE; - $xmlrpc_base64->data = $data; - return $xmlrpc_base64; -} - -/** - * Converts an XML-RPC base 64 object into XML. - * - * @param $xmlrpc_base64 - * The XML-RPC base 64 object. - * - * @return string - * An XML representation of the base 64 data as XML. - */ -function xmlrpc_base64_get_xml($xmlrpc_base64) { - return '<base64>' . base64_encode($xmlrpc_base64->data) . '</base64>'; -} - -/** - * Performs one or more XML-RPC requests. - * - * @param $url - * An absolute URL of the XML-RPC endpoint, e.g., - * http://example.com/xmlrpc.php - * @param $args - * An associative array whose keys are the methods to call and whose values - * are the arguments to pass to the respective method. If multiple methods - * are specified, a system.multicall is performed. - * @param $options - * (optional) An array of options to pass along to drupal_http_request(). - * - * @return - * A single response (single request) or an array of responses (multicall - * request). Each response is the return value of the method, just as if it - * has been a local function call, on success, or FALSE on failure. If FALSE - * is returned, see xmlrpc_errno() and xmlrpc_error_msg() to get more - * information. - */ -function _xmlrpc($url, $args, $options = array()) { - xmlrpc_clear_error(); - if (count($args) > 1) { - $multicall_args = array(); - foreach ($args as $method => $call) { - $multicall_args[] = array('methodName' => $method, 'params' => $call); - } - $method = 'system.multicall'; - $args = array($multicall_args); - } - else { - $method = key($args); - $args = $args[$method]; - } - $xmlrpc_request = xmlrpc_request($method, $args); - // Required options which will replace any that are passed in. - $options['method'] = 'POST'; - $options['headers']['Content-Type'] = 'text/xml'; - $options['data'] = $xmlrpc_request->xml; - $result = drupal_http_request($url, $options); - if ($result->code != 200) { - xmlrpc_error($result->code, $result->error); - return FALSE; - } - $message = xmlrpc_message($result->data); - // Now parse what we've got back - if (!xmlrpc_message_parse($message)) { - // XML error - xmlrpc_error(-32700, t('Parse error. Not well formed')); - return FALSE; - } - // Is the message a fault? - if ($message->messagetype == 'fault') { - xmlrpc_error($message->fault_code, $message->fault_string); - return FALSE; - } - // We now know that the message is well-formed and a non-fault result. - if ($method == 'system.multicall') { - // Return per-method results or error objects. - $return = array(); - foreach ($message->params[0] as $result) { - if (array_keys($result) == array(0)) { - $return[] = $result[0]; - } - else { - $return[] = xmlrpc_error($result['faultCode'], $result['faultString']); - } - } - } - else { - $return = $message->params[0]; - } - return $return; -} - -/** - * Returns the last XML-RPC client error number. - */ -function xmlrpc_errno() { - $error = xmlrpc_error(); - return ($error != NULL ? $error->code : NULL); -} - -/** - * Returns the last XML-RPC client error message. - */ -function xmlrpc_error_msg() { - $error = xmlrpc_error(); - return ($error != NULL ? $error->message : NULL); -} - -/** - * Clears any previously-saved errors. - * - * @see xmlrpc_error() - */ -function xmlrpc_clear_error() { - xmlrpc_error(NULL, NULL, TRUE); -} - diff --git a/core/includes/xmlrpcs.inc b/core/includes/xmlrpcs.inc deleted file mode 100644 index 70c7cdac3b2..00000000000 --- a/core/includes/xmlrpcs.inc +++ /dev/null @@ -1,385 +0,0 @@ -<?php - -/** - * @file - * Provides API for defining and handling XML-RPC requests. - */ - -/** - * Invokes XML-RPC methods on this server. - * - * @param array $callbacks - * Array of external XML-RPC method names with the callbacks they map to. - */ -function xmlrpc_server($callbacks) { - $xmlrpc_server = new stdClass(); - // Define built-in XML-RPC method names - $defaults = array( - 'system.multicall' => 'xmlrpc_server_multicall', - array( - 'system.methodSignature', - 'xmlrpc_server_method_signature', - array('array', 'string'), - 'Returns an array describing the return type and required parameters of a method.', - ), - array( - 'system.getCapabilities', - 'xmlrpc_server_get_capabilities', - array('struct'), - 'Returns a struct describing the XML-RPC specifications supported by this server.', - ), - array( - 'system.listMethods', - 'xmlrpc_server_list_methods', - array('array'), - 'Returns an array of available methods on this server.', - ), - array( - 'system.methodHelp', - 'xmlrpc_server_method_help', - array('string', 'string'), - 'Returns a documentation string for the specified method.', - ), - ); - // We build an array of all method names by combining the built-ins - // with those defined by modules implementing the _xmlrpc hook. - // Built-in methods are overridable. - $callbacks = array_merge($defaults, (array) $callbacks); - drupal_alter('xmlrpc', $callbacks); - foreach ($callbacks as $key => $callback) { - // we could check for is_array($callback) - if (is_int($key)) { - $method = $callback[0]; - $xmlrpc_server->callbacks[$method] = $callback[1]; - $xmlrpc_server->signatures[$method] = $callback[2]; - $xmlrpc_server->help[$method] = $callback[3]; - } - else { - $xmlrpc_server->callbacks[$key] = $callback; - $xmlrpc_server->signatures[$key] = ''; - $xmlrpc_server->help[$key] = ''; - } - } - - $data = file_get_contents('php://input'); - if (!$data) { - print 'XML-RPC server accepts POST requests only.'; - drupal_exit(); - } - $xmlrpc_server->message = xmlrpc_message($data); - if (!xmlrpc_message_parse($xmlrpc_server->message)) { - xmlrpc_server_error(-32700, t('Parse error. Request not well formed.')); - } - if ($xmlrpc_server->message->messagetype != 'methodCall') { - xmlrpc_server_error(-32600, t('Server error. Invalid XML-RPC. Request must be a methodCall.')); - } - if (!isset($xmlrpc_server->message->params)) { - $xmlrpc_server->message->params = array(); - } - xmlrpc_server_set($xmlrpc_server); - $result = xmlrpc_server_call($xmlrpc_server, $xmlrpc_server->message->methodname, $xmlrpc_server->message->params); - - if (is_object($result) && !empty($result->is_error)) { - xmlrpc_server_error($result); - } - // Encode the result - $r = xmlrpc_value($result); - // Create the XML - $xml = ' -<methodResponse> - <params> - <param> - <value>' . xmlrpc_value_get_xml($r) . '</value> - </param> - </params> -</methodResponse> - -'; - // Send it - xmlrpc_server_output($xml); -} - -/** - * Throws an XML-RPC error. - * - * @param $error - * An error object or integer error code. - * @param $message - * (optional) The description of the error. Used only if an integer error - * code was passed in. - */ -function xmlrpc_server_error($error, $message = FALSE) { - if ($message && !is_object($error)) { - $error = xmlrpc_error($error, $message); - } - xmlrpc_server_output(xmlrpc_error_get_xml($error)); -} - -/** - * Sends XML-RPC output to the browser. - * - * @param string $xml - * XML to send to the browser. - */ -function xmlrpc_server_output($xml) { - $xml = '<?xml version="1.0"?>' . "\n" . $xml; - drupal_add_http_header('Content-Length', strlen($xml)); - drupal_add_http_header('Content-Type', 'text/xml'); - echo $xml; - drupal_exit(); -} - -/** - * Stores a copy of an XML-RPC request temporarily. - * - * @param object $xmlrpc_server - * (optional) Request object created by xmlrpc_server(). Omit to leave the - * previous server object saved. - * - * @return - * The latest stored request. - * - * @see xmlrpc_server_get() - */ -function xmlrpc_server_set($xmlrpc_server = NULL) { - static $server; - if (!isset($server)) { - $server = $xmlrpc_server; - } - return $server; -} - -/** - * Retrieves the latest stored XML-RPC request. - * - * @return object - * The stored request. - * - * @see xmlrpc_server_set() - */ -function xmlrpc_server_get() { - return xmlrpc_server_set(); -} - -/** - * Dispatches an XML-RPC request and any parameters to the appropriate handler. - * - * @param object $xmlrpc_server - * Object containing information about this XML-RPC server, the methods it - * provides, their signatures, etc. - * @param string $methodname - * The external XML-RPC method name; e.g., 'system.methodHelp'. - * @param array $args - * Array containing any parameters that are to be sent along with the request. - * - * @return - * The results of the call. - */ -function xmlrpc_server_call($xmlrpc_server, $methodname, $args) { - // Make sure parameters are in an array - if ($args && !is_array($args)) { - $args = array($args); - } - // Has this method been mapped to a Drupal function by us or by modules? - if (!isset($xmlrpc_server->callbacks[$methodname])) { - return xmlrpc_error(-32601, t('Server error. Requested method @methodname not specified.', array("@methodname" => $xmlrpc_server->message->methodname))); - } - $method = $xmlrpc_server->callbacks[$methodname]; - $signature = $xmlrpc_server->signatures[$methodname]; - - // If the method has a signature, validate the request against the signature - if (is_array($signature)) { - $ok = TRUE; - $return_type = array_shift($signature); - // Check the number of arguments - if (count($args) != count($signature)) { - return xmlrpc_error(-32602, t('Server error. Wrong number of method parameters.')); - } - // Check the argument types - foreach ($signature as $key => $type) { - $arg = $args[$key]; - switch ($type) { - case 'int': - case 'i4': - if (is_array($arg) || !is_int($arg)) { - $ok = FALSE; - } - break; - - case 'base64': - case 'string': - if (!is_string($arg)) { - $ok = FALSE; - } - break; - - case 'boolean': - if ($arg !== FALSE && $arg !== TRUE) { - $ok = FALSE; - } - break; - - case 'float': - case 'double': - if (!is_float($arg)) { - $ok = FALSE; - } - break; - - case 'date': - case 'dateTime.iso8601': - if (!$arg->is_date) { - $ok = FALSE; - } - break; - } - if (!$ok) { - return xmlrpc_error(-32602, t('Server error. Invalid method parameters.')); - } - } - } - - if (!function_exists($method)) { - return xmlrpc_error(-32601, t('Server error. Requested function @method does not exist.', array("@method" => $method))); - } - // Call the mapped function - return call_user_func_array($method, $args); -} - -/** - * Dispatches multiple XML-RPC requests. - * - * @param array $methodcalls - * An array of XML-RPC requests to make. Each request is an array with the - * following elements: - * - methodName: Name of the method to invoke. - * - params: Parameters to pass to the method. - * - * @return - * An array of the results of each request. - * - * @see xmlrpc_server_call() - */ -function xmlrpc_server_multicall($methodcalls) { - // See http://www.xmlrpc.com/discuss/msgReader$1208 - $return = array(); - $xmlrpc_server = xmlrpc_server_get(); - foreach ($methodcalls as $call) { - $ok = TRUE; - if (!isset($call['methodName']) || !isset($call['params'])) { - $result = xmlrpc_error(3, t('Invalid syntax for system.multicall.')); - $ok = FALSE; - } - $method = $call['methodName']; - $params = $call['params']; - if ($method == 'system.multicall') { - $result = xmlrpc_error(-32600, t('Recursive calls to system.multicall are forbidden.')); - } - elseif ($ok) { - $result = xmlrpc_server_call($xmlrpc_server, $method, $params); - } - if (is_object($result) && !empty($result->is_error)) { - $return[] = array( - 'faultCode' => $result->code, - 'faultString' => $result->message, - ); - } - else { - $return[] = array($result); - } - } - return $return; -} - -/** - * Lists the methods available on this XML-RPC server. - * - * XML-RPC method system.listMethods maps to this function. - * - * @return array - * Array of the names of methods available on this server. - */ -function xmlrpc_server_list_methods() { - $xmlrpc_server = xmlrpc_server_get(); - return array_keys($xmlrpc_server->callbacks); -} - -/** - * Returns a list of the capabilities of this server. - * - * XML-RPC method system.getCapabilities maps to this function. - * - * @return array - * Array of server capabilities. - * - * @see http://groups.yahoo.com/group/xml-rpc/message/2897 - */ -function xmlrpc_server_get_capabilities() { - return array( - 'xmlrpc' => array( - 'specUrl' => 'http://www.xmlrpc.com/spec', - 'specVersion' => 1, - ), - 'faults_interop' => array( - 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php', - 'specVersion' => 20010516, - ), - 'system.multicall' => array( - 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208', - 'specVersion' => 1, - ), - 'introspection' => array( - 'specUrl' => 'http://scripts.incutio.com/xmlrpc/introspection.html', - 'specVersion' => 1, - ), - ); -} - -/** - * Returns one method signature for a function. - * - * This is the function mapped to the XML-RPC method system.methodSignature. - * - * A method signature is an array of the input and output types of a method. For - * instance, the method signature of this function is array('array', 'string'), - * because it takes an array and returns a string. - * - * @param string $methodname - * Name of method to return a method signature for. - * - * @return array - * An array of arrays of types, each of the arrays representing one method - * signature of the function that $methodname maps to. - */ -function xmlrpc_server_method_signature($methodname) { - $xmlrpc_server = xmlrpc_server_get(); - if (!isset($xmlrpc_server->callbacks[$methodname])) { - return xmlrpc_error(-32601, t('Server error. Requested method @methodname not specified.', array("@methodname" => $methodname))); - } - if (!is_array($xmlrpc_server->signatures[$methodname])) { - return xmlrpc_error(-32601, t('Server error. Requested method @methodname signature not specified.', array("@methodname" => $methodname))); - } - // We array of types - $return = array(); - foreach ($xmlrpc_server->signatures[$methodname] as $type) { - $return[] = $type; - } - return array($return); -} - -/** - * Returns the help for an XML-RPC method. - * - * XML-RPC method system.methodHelp maps to this function. - * - * @param string $method - * Name of method for which we return a help string. - * - * @return string - * Help text for $method. - */ -function xmlrpc_server_method_help($method) { - $xmlrpc_server = xmlrpc_server_get(); - return $xmlrpc_server->help[$method]; -} - |