Skip to content
Snippets Groups Projects

Compare revisions

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

Source

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

Target

Select target project
  • rklusman2/UNL-CMS
  • yzha1/UNL-CMS
  • pear/UNL-CMS
  • bbieber2/UNL-CMS
  • tsteiner2/UNL-CMS
  • erasmussen2/UNL-CMS
  • UNL-Information-Services/UNL-CMS
7 results
Select Git revision
  • develop
  • issue-424
  • master
  • master-no-logins
  • svn-staging
  • svn-testing
  • svn-trunk
  • topics/add-contribution-info
  • 2010-11-11
  • 2010-12-15
  • 2011-01-11
  • 2011-01-18
  • 2011-01-26
  • 2011-02-10
  • 2011-02-23
  • 2011-03-09
  • 2011-03-15
  • 2011-03-30
  • 2011-04-05
  • 2011-05-03
  • 2011-05-12
  • 2011-06-16
  • 2011-06-21
  • 2011-06-23
  • 2011-06-29
  • 2011-06-30
  • 2011-07-11
  • 2011-07-18
  • 2011-07-20
  • 2011-07-21
  • 2011-07-28
  • 2011-08-03
  • 2011-08-05
  • 2011-08-15
  • 2011-08-17
  • 2011-08-29
  • 2011-08-30
  • 2011-09-19
  • 2011-10-03
  • 2011-10-06
  • 2011-10-27
  • 2011-11-01
  • 2011-11-08
  • 2011-11-08.2
  • 2011-11-14
  • 2011-11-17
  • 2011-12-05
  • 2011-12-16
  • 2012-01-12
  • 2012-01-13
  • 2012-02-07
  • 2012-03-01
  • 2012-04-02
  • 2012-04-03
  • 2012-04-18
  • 20120207
  • 7.1
  • 7.10
  • 7.11
  • 7.12
  • 7.13
  • 7.14
  • 7.15
  • 7.16
  • 7.16.1
  • 7.17
  • 7.18
  • 7.19
  • 7.2
  • 7.3
  • 7.3.1
  • 7.4
  • 7.5
  • 7.5.1
  • 7.6
  • 7.6.1
  • 7.7
  • 7.7.1
  • 7.7.2
  • 7.7.3
  • 7.8
  • 7.9
82 results
Show changes
Showing
with 1880 additions and 654 deletions
<?php
/**
* @file
* Provides a helper to properly encode HTML-safe JSON prior to PHP 5.3.0.
*/
/**
* Encodes a PHP variable to HTML-safe JSON for PHP versions below 5.3.0.
*
* @see drupal_json_encode()
*/
function drupal_json_encode_helper($var) {
switch (gettype($var)) {
case 'boolean':
return $var ? 'true' : 'false'; // Lowercase necessary!
case 'integer':
case 'double':
return $var;
case 'resource':
case 'string':
// Always use Unicode escape sequences (\u0022) over JSON escape
// sequences (\") to prevent browsers interpreting these as
// special characters.
$replace_pairs = array(
// ", \ and U+0000 - U+001F must be escaped according to RFC 4627.
'\\' => '\u005C',
'"' => '\u0022',
"\x00" => '\u0000',
"\x01" => '\u0001',
"\x02" => '\u0002',
"\x03" => '\u0003',
"\x04" => '\u0004',
"\x05" => '\u0005',
"\x06" => '\u0006',
"\x07" => '\u0007',
"\x08" => '\u0008',
"\x09" => '\u0009',
"\x0a" => '\u000A',
"\x0b" => '\u000B',
"\x0c" => '\u000C',
"\x0d" => '\u000D',
"\x0e" => '\u000E',
"\x0f" => '\u000F',
"\x10" => '\u0010',
"\x11" => '\u0011',
"\x12" => '\u0012',
"\x13" => '\u0013',
"\x14" => '\u0014',
"\x15" => '\u0015',
"\x16" => '\u0016',
"\x17" => '\u0017',
"\x18" => '\u0018',
"\x19" => '\u0019',
"\x1a" => '\u001A',
"\x1b" => '\u001B',
"\x1c" => '\u001C',
"\x1d" => '\u001D',
"\x1e" => '\u001E',
"\x1f" => '\u001F',
// Prevent browsers from interpreting these as as special.
"'" => '\u0027',
'<' => '\u003C',
'>' => '\u003E',
'&' => '\u0026',
// Prevent browsers from interpreting the solidus as special and
// non-compliant JSON parsers from interpreting // as a comment.
'/' => '\u002F',
// While these are allowed unescaped according to ECMA-262, section
// 15.12.2, they cause problems in some JSON parsers.
"\xe2\x80\xa8" => '\u2028', // U+2028, Line Separator.
"\xe2\x80\xa9" => '\u2029', // U+2029, Paragraph Separator.
);
return '"' . strtr($var, $replace_pairs) . '"';
case 'array':
// Arrays in JSON can't be associative. If the array is empty or if it
// has sequential whole number keys starting with 0, it's not associative
// so we can go ahead and convert it as an array.
if (empty($var) || array_keys($var) === range(0, sizeof($var) - 1)) {
$output = array();
foreach ($var as $v) {
$output[] = drupal_json_encode_helper($v);
}
return '[ ' . implode(', ', $output) . ' ]';
}
// Otherwise, fall through to convert the array as an object.
case 'object':
$output = array();
foreach ($var as $k => $v) {
$output[] = drupal_json_encode_helper(strval($k)) . ':' . drupal_json_encode_helper($v);
}
return '{' . implode(', ', $output) . '}';
default:
return 'null';
}
}
<?php
// $Id: language.inc,v 1.30 2010/05/12 08:26:14 dries Exp $
/**
* @file
......@@ -84,6 +83,44 @@ function language_types_disable($types) {
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.
*
......@@ -174,6 +211,28 @@ function language_negotiation_get_switch_links($type, $path) {
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.
......@@ -181,7 +240,8 @@ function language_negotiation_get_switch_links($type, $path) {
* @param $type
* The language negotiation type.
* @param $language_providers
* An array of language provider ids.
* 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.
......@@ -190,7 +250,7 @@ function language_negotiation_set($type, $language_providers) {
$negotiation = array();
$providers_weight = array();
$defined_providers = language_negotiation_info();
$default_types = language_types_configurable();
$default_types = language_types_configurable(FALSE);
// Initialize the providers weight list.
foreach ($language_providers as $id => $provider) {
......
<?php
// $Id: locale.inc,v 1.260 2010/10/06 13:38:39 dries Exp $
/**
* @file
......@@ -22,6 +21,12 @@ define('LOCALE_LANGUAGE_NEGOTIATION_BROWSER', 'locale-browser');
*/
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.
*/
......@@ -37,6 +42,36 @@ define('LOCALE_LANGUAGE_NEGOTIATION_SESSION', 'locale-session');
*/
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.
......@@ -64,6 +99,10 @@ 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.
*/
/**
......@@ -84,39 +123,82 @@ function locale_language_from_interface() {
* otherwise we would cache a user-specific preference.
*
* @param $languages
* An array of valid language objects.
* 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) {
// Specified by the user via the browser's Accept Language setting
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_langs = array();
$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 (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$browser_accept = explode(",", $_SERVER['HTTP_ACCEPT_LANGUAGE']);
foreach ($browser_accept as $langpart) {
// The language part is either a code or a code with a quality.
// We cannot do anything with a * code, so it is skipped.
// If the quality is missing, it is assumed to be 1 according to the RFC.
if (preg_match("!([a-z-]+)(;q=([0-9\\.]+))?!", trim($langpart), $found)) {
$browser_langs[$found[1]] = (isset($found[3]) ? (float) $found[3] : 1.0);
}
// 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, '-')));
// Order the codes by quality
arsort($browser_langs);
// Try to find the first preferred language we have
foreach ($browser_langs as $langcode => $q) {
if (isset($languages[$langcode])) {
return $langcode;
// Find the best match.
if ($qvalue > $max_qvalue) {
$best_match_langcode = $language->language;
$max_qvalue = $qvalue;
}
}
return FALSE;
return $best_match_langcode;
}
/**
......@@ -198,18 +280,71 @@ function locale_language_from_url($languages) {
case LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN:
foreach ($languages as $language) {
$host = parse_url($language->domain, PHP_URL_HOST);
if ($host && ($_SERVER['HTTP_HOST'] == $host)) {
// 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.
......@@ -268,11 +403,27 @@ function locale_language_switcher_session($type, $path) {
* 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)) {
......@@ -351,8 +502,11 @@ function locale_string_is_safe($string) {
}
/**
* @defgroup locale-api-add Language addition API.
* @defgroup locale-api-add Language addition API
* @{
* Add a language.
*
* The language addition API is used to create languages and store them.
*/
/**
......@@ -429,8 +583,12 @@ function locale_add_language($langcode, $name = NULL, $native = NULL, $direction
*/
/**
* @defgroup locale-api-import Translation import API.
* @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.
*/
/**
......@@ -506,159 +664,250 @@ function _locale_import_po($file, $langcode, $mode, $group = NULL) {
*/
function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL, $group = 'default') {
$fd = fopen($file->uri, "rb"); // File will get closed by PHP on return
// 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;
}
$context = "COMMENT"; // Parser context: COMMENT, MSGID, MSGID_PLURAL, MSGSTR and MSGSTR_ARR
$current = array(); // Current entry being read
$plural = 0; // Current plural form
$lineno = 0; // Current line
/*
* 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)) {
$line = fgets($fd, 10*1024); // A line should not be this long
// 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)) { // A comment
if ($context == "COMMENT") { // Already in comment context: add
$current["#"][] = substr($line, 1);
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")) { // End current entry, start a new one
elseif (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) {
// We are currently in string token, close it out.
_locale_import_one_string($op, $current, $mode, $lang, $file, $group);
// Start a new entry for the comment.
$current = array();
$current["#"][] = substr($line, 1);
$context = "COMMENT";
$current['#'][] = substr($line, 1);
$context = 'COMMENT';
}
else { // Parse error
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)) {
if ($context != "MSGID") { // Must be plural form for current entry
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;
}
$current["msgid"] = $current["msgid"] . "\0" . $quoted;
$context = "MSGID_PLURAL";
// Append the plural form to the current entry.
$current['msgid'] .= "\0" . $quoted;
$context = 'MSGID_PLURAL';
}
elseif (!strncmp("msgid", $line, 5)) {
if ($context == "MSGSTR") { // End current entry, start a new one
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, $group);
// Start a new context for the id.
$current = array();
}
elseif ($context == "MSGID") { // Already in this context? Parse error
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";
$current['msgid'] = $quoted;
$context = 'MSGID';
}
elseif (!strncmp("msgctxt", $line, 7)) {
if ($context == "MSGSTR") { // End current entry, start a new one
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, $group);
$current = array();
}
elseif (!empty($current["msgctxt"])) { // Already in this context? Parse error
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";
$current['msgctxt'] = $quoted;
$context = 'MSGCTXT';
}
elseif (!strncmp("msgstr[", $line, 7)) {
if (($context != "MSGID") && ($context != "MSGCTXT") && ($context != "MSGID_PLURAL") && ($context != "MSGSTR_ARR")) { // Must come after msgid, msgxtxt, msgid_plural, or msgstr[]
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;
}
if (strpos($line, "]") === 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;
}
$frombracket = strstr($line, "[");
$plural = substr($frombracket, 1, strpos($frombracket, "]") - 1);
// 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";
$current['msgstr'][$plural] = $quoted;
$context = 'MSGSTR_ARR';
}
elseif (!strncmp("msgstr", $line, 6)) {
if (($context != "MSGID") && ($context != "MSGCTXT")) { // Should come just after a msgid or msgctxt block
// 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";
$current['msgstr'] = $quoted;
$context = 'MSGSTR';
}
elseif ($line != "") {
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;
}
if (($context == "MSGID") || ($context == "MSGID_PLURAL")) {
$current["msgid"] .= $quoted;
// Append the string to the current context.
if (($context == 'MSGID') || ($context == 'MSGID_PLURAL')) {
$current['msgid'] .= $quoted;
}
elseif ($context == "MSGCTXT") {
$current["msgctxt"] .= $quoted;
elseif ($context == 'MSGCTXT') {
$current['msgctxt'] .= $quoted;
}
elseif ($context == "MSGSTR") {
$current["msgstr"] .= $quoted;
elseif ($context == 'MSGSTR') {
$current['msgstr'] .= $quoted;
}
elseif ($context == "MSGSTR_ARR") {
$current["msgstr"][$plural] .= $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, flush last entry
if (!empty($current) && !empty($current['msgstr'])) {
// End of PO file, closed out the last entry.
if (($context == 'MSGSTR') || ($context == 'MSGSTR_ARR')) {
_locale_import_one_string($op, $current, $mode, $lang, $file, $group);
}
elseif ($context != "COMMENT") {
elseif ($context != 'COMMENT') {
_locale_import_message('The translation file %filename ended unexpectedly at line %line.', $file, $lineno);
return FALSE;
}
}
/**
......@@ -1205,7 +1454,7 @@ function _locale_import_parse_quoted($string) {
}
}
/**
* @} End of "locale-api-import"
* @} End of "locale-api-import-export"
*/
/**
......@@ -1224,26 +1473,78 @@ function _locale_parse_js_file($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*\(\s*(' . LOCALE_JS_STRING . ')\s*[,\)]~s', $file, $t_matches);
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*\(\s*.+?\s*,\s*(' . LOCALE_JS_STRING . ')\s*,\s*((?:(?:\'(?:\\\\\'|[^\'])*@count(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*@count(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+)\s*[,\)]~s', $file, $plural_matches);
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],
);
}
// Loop through all matches and process them.
$all_matches = array_merge($plural_matches[1], $t_matches[1]);
foreach ($all_matches as $key => $string) {
$strings = array($string);
// 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])) {
$strings[] = $plural_matches[2][$key];
$matches[] = array(
'string' => $plural_matches[2][$key],
'context' => $plural_matches[3][$key],
);
}
}
foreach ($strings as $key => $string) {
foreach ($matches as $key => $match) {
// Remove the quotes and string concatenations from the string.
$string = implode('', preg_split('~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s', substr($string, 1, -1)));
$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 textgroup = 'default'", array(':source' => $string))->fetchObject();
$source = db_query("SELECT lid, location FROM {locales_source} WHERE source = :source AND context = :context AND textgroup = 'default'", 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.
......@@ -1268,17 +1569,16 @@ function _locale_parse_js_file($filepath) {
->fields(array(
'location' => $filepath,
'source' => $string,
'context' => '',
'context' => $context,
'textgroup' => 'default',
))
->execute();
}
}
}
}
/**
* @defgroup locale-api-export Translation (template) export API.
* @addtogroup locale-api-import-export
* @{
*/
......@@ -1515,12 +1815,16 @@ function _locale_export_remove_plural($entry) {
return preg_replace('/(@count)\[[0-9]\]/', '\\1', $entry);
}
/**
* @} End of "locale-api-export"
* @} End of "locale-api-import-export"
*/
/**
* @defgroup locale-api-seek String search functions.
* @defgroup locale-api-seek Translation search API
* @{
* Functions to search in translation files.
*
* These functions provide the functionality to search for specific
* translations.
*/
/**
......@@ -1689,11 +1993,11 @@ function _locale_rebuild_js($langcode = NULL) {
// Construct the array for JavaScript translations.
// Only add strings with a translation to the translations array.
$result = db_query("SELECT s.lid, s.source, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location LIKE '%.js%' AND s.textgroup = :textgroup AND t.translation IS NOT NULL", array(':language' => $language->language, ':textgroup' => 'default'));
$result = db_query("SELECT s.lid, s.source, s.context, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location LIKE '%.js%' AND s.textgroup = :textgroup", array(':language' => $language->language, ':textgroup' => 'default'));
$translations = array();
foreach ($result as $data) {
$translations[$data->source] = $data->translation;
$translations[$data->context][$data->source] = $data->translation;
}
// Construct the JavaScript file, if there are translations.
......@@ -1825,6 +2129,7 @@ function _locale_translate_language_list($translation, $limit_language) {
/**
* @defgroup locale-api-predefined List of predefined languages
* @{
* API to provide a list of predefined languages.
*/
/**
......@@ -1859,6 +2164,10 @@ function _locale_prepare_predefined_list() {
/**
* @defgroup locale-autoimport Automatic interface translation import
* @{
* Functions to create batches for importing translations.
*
* These functions can be used to import translations for installed
* modules.
*/
/**
......@@ -1984,7 +2293,7 @@ function _locale_batch_import($filepath, &$context) {
// The filename is either {langcode}.po or {prefix}.{langcode}.po, so
// we can extract the language code to use for the import from the end.
if (preg_match('!(/|\.)([^\./]+)\.po$!', $filepath, $langcode)) {
$file = (object) array('filename' => basename($filepath), 'uri' => $filepath);
$file = (object) array('filename' => drupal_basename($filepath), 'uri' => $filepath);
_locale_import_read_po('db-store', $file, LOCALE_IMPORT_KEEP, $langcode[2]);
$context['results'][] = $filepath;
}
......
<?php
// $Id: lock.inc,v 1.5 2010/07/16 11:19:38 dries Exp $
/**
* @file
......@@ -7,8 +6,10 @@
*/
/**
* @defgroup lock Functions to coordinate long-running operations across requests.
* @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
......@@ -73,7 +74,10 @@ function lock_initialize() {
* Helper function to get this request's unique id.
*/
function _lock_id() {
$lock_id = &drupal_static(__FUNCTION__);
// 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.
......@@ -186,7 +190,7 @@ function lock_may_be_available($name) {
* 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 during the sleep().
* again by a different request while waiting.
*
* @param $name
* The name of the lock.
......@@ -197,12 +201,32 @@ function lock_may_be_available($name) {
* TRUE if the lock holds, FALSE if it is available.
*/
function lock_wait($name, $delay = 30) {
$delay = (int) $delay;
while ($delay--) {
// 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.
sleep(1);
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;
......
<?php
// $Id: mail.inc,v 1.34 2010/07/28 02:19:56 dries Exp $
/**
* @file
......@@ -58,6 +57,12 @@ define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER
* user_mail_tokens($variables, $data, $options);
* switch($key) {
* case 'notice':
* // If the recipient can receive such notices by instant-message, do
* // not send by email.
* if (example_im_send($key, $message, $params)) {
* $message['send'] = FALSE;
* break;
* }
* $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));
......@@ -66,6 +71,19 @@ define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER
* }
* @endcode
*
* Another example, which uses drupal_mail() to format a message for sending
* later:
*
* @code
* $params = array('current_conditions' => $data);
* $to = 'user@example.com';
* $message = drupal_mail('example', 'notice', $to, $language, $params, FALSE);
* // Only add to the spool if sending was not canceled.
* if ($message['send']) {
* example_spool_message($message);
* }
* @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
......@@ -87,8 +105,10 @@ define('MAIL_LINE_ENDINGS', isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER
* @param $from
* Sets From to this value, if given.
* @param $send
* Send the message directly, without calling drupal_mail_system()->mail()
* manually.
* If TRUE, drupal_mail() will call drupal_mail_system()->mail() to deliver
* the message, and store the result in $message['result']. Modules
* implementing hook_mail_alter() may cancel sending by setting
* $message['send'] to FALSE.
*
* @return
* The $message array structure containing all details of the
......@@ -109,6 +129,7 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N
'from' => isset($from) ? $from : $default_from,
'language' => $language,
'params' => $params,
'send' => TRUE,
'subject' => '',
'body' => array()
);
......@@ -123,8 +144,8 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N
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. Errors-To is redundant, but shouldn't hurt.
$headers['From'] = $headers['Sender'] = $headers['Return-Path'] = $headers['Errors-To'] = $default_from;
// SMTP server.
$headers['From'] = $headers['Sender'] = $headers['Return-Path'] = $default_from;
}
if ($from) {
$headers['From'] = $from;
......@@ -149,14 +170,22 @@ function drupal_mail($module, $key, $to, $language, $params = array(), $from = N
// Optionally send e-mail.
if ($send) {
// The original caller requested sending. Sending was canceled by one or
// more hook_mail_alter() implementations. We set 'result' to NULL, because
// FALSE indicates an error in sending.
if (empty($message['send'])) {
$message['result'] = NULL;
}
// Sending was originally requested and was not canceled.
else {
$message['result'] = $system->mail($message);
// Log errors
// 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;
}
......@@ -431,7 +460,7 @@ function drupal_html_to_text($string, $allowed_tags = NULL) {
$indent[] = count($lists) ? ' "' : '>';
break;
case 'li':
$indent[] = is_numeric($lists[0]) ? ' ' . $lists[0]++ . ') ' : ' * ';
$indent[] = isset($lists[0]) && is_numeric($lists[0]) ? ' ' . $lists[0]++ . ') ' : ' * ';
break;
case 'dd':
$indent[] = ' ';
......@@ -510,7 +539,7 @@ function drupal_html_to_text($string, $allowed_tags = NULL) {
$chunk = $casing($chunk);
}
// Format it and apply the current indentation.
$output .= drupal_wrap_mail($chunk, implode('', $indent));
$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);
}
......
<?php
// $Id: menu.inc,v 1.416 2010/10/21 11:56:17 dries Exp $
/**
* @file
......@@ -14,7 +13,10 @@
* 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.
* 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
......@@ -49,8 +51,9 @@
* 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. Menu items may
* omit this attribute to use the value provided by an ancestor item.
* 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
......@@ -124,8 +127,10 @@ define('MENU_IS_LOCAL_ACTION', 0x0100);
/**
* @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 the above flags.
* combinations of @link menu_flags Menu flags @endlink.
*/
/**
......@@ -250,7 +255,7 @@ define('MENU_SITE_ONLINE', 5);
/**
* @defgroup menu_tree_parameters Menu tree parameters
* @{
* Menu tree
* Parameters for a menu tree.
*/
/**
......@@ -269,6 +274,20 @@ define('MENU_MAX_DEPTH', 9);
* @} End of "Menu tree parameters".
*/
/**
* Reserved key to identify the most specific menu link for a given path.
*
* The value of this constant is a hash of the constant name. We use the hash
* so that the reserved key is over 32 characters in length and will not
* collide with allowed menu names:
* @code
* sha1('MENU_PREFERRED_LINK') = 1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91
* @endcode
*
* @see menu_link_get_preferred()
*/
define('MENU_PREFERRED_LINK', '1cf698d64d1aa4b83907cf6ed55db3a7f8e92c91');
/**
* Returns the ancestors (and relevant placeholders) for any given path.
*
......@@ -335,25 +354,39 @@ function menu_get_ancestors($parts) {
}
/**
* The menu system uses serialized arrays stored in the database for
* arguments. However, often these need to change according to the
* current path. This function unserializes such an array and does the
* necessary change.
* 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.
*
* Integer values are mapped according to the $map parameter. For
* example, if unserialize($data) is array('view', 1) and $map is
* array('node', '12345') then 'view' will not be changed because
* it is not an integer, but 1 will as it is an integer. As $map[1]
* is '12345', 1 will be replaced with '12345'. So the result will
* be array('node_load', '12345').
* 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.
*
* @param @data
* A serialized array.
* @param @map
* An array of potential replacements.
* 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 $data array unserialized and mapped.
* The unserialized $data array, with path arguments replaced.
*/
function menu_unserialize($data, $map) {
if ($data = unserialize($data)) {
......@@ -412,9 +445,12 @@ function menu_get_item($path = NULL, $router_item = NULL) {
$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);
$parts = array_slice($original_map, 0, MENU_MAX_PARTS);
$ancestors = menu_get_ancestors($parts);
// Since there is no limit to the length of $path, use a hash to keep it
// short yet unique.
......@@ -423,10 +459,16 @@ function menu_get_item($path = NULL, $router_item = NULL) {
$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_set($cid, $router_item, 'cache_menu');
}
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) {
......@@ -467,11 +509,6 @@ function menu_execute_active_handler($path = NULL, $deliver = TRUE) {
// Only continue if the site status is not set.
if ($page_callback_result == MENU_SITE_ONLINE) {
// Rebuild if we know it's needed, or if the menu masks are missing which
// occurs rarely, likely due to a race condition of multiple rebuilds.
if (variable_get('menu_rebuild_needed', FALSE) || !variable_get('menu_masks', array())) {
menu_rebuild();
}
if ($router_item = menu_get_item($path)) {
if ($router_item['access']) {
if ($router_item['include_file']) {
......@@ -517,8 +554,8 @@ function menu_execute_active_handler($path = NULL, $deliver = TRUE) {
function _menu_load_objects(&$item, &$map) {
if ($load_functions = $item['load_functions']) {
// If someone calls this function twice, then unserialize will fail.
if ($load_functions_unserialized = unserialize($load_functions)) {
$load_functions = $load_functions_unserialized;
if (!is_array($load_functions)) {
$load_functions = unserialize($load_functions);
}
$path_map = $map;
foreach ($load_functions as $index => $function) {
......@@ -783,10 +820,30 @@ function _menu_link_map_translate(&$map, $to_arg_functions) {
}
}
/**
* 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
......@@ -1001,7 +1058,7 @@ function menu_tree_output($tree) {
}
// Allow menu-specific theme overrides.
$element['#theme'] = 'menu_link__' . $data['link']['menu_name'];
$element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_');
$element['#attributes']['class'] = $class;
$element['#title'] = $data['link']['title'];
$element['#href'] = $data['link']['href'];
......@@ -1090,6 +1147,45 @@ function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) {
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.
*
......@@ -1115,8 +1211,10 @@ function menu_tree_all_data($menu_name, $link = NULL, $max_depth = NULL) {
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()) {
if ($item = menu_get_item($active_path)) {
if (isset($max_depth)) {
$max_depth = min($max_depth, MENU_MAX_DEPTH);
}
......@@ -1155,8 +1253,9 @@ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail =
// 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_link = menu_link_get_preferred()) {
// 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, $menu_name)) {
// 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
......@@ -1240,6 +1339,8 @@ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail =
* 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.
* - conditions: An associative array of custom database select query
* condition key/value pairs; see _menu_build_tree() for the actual query.
*
* @return
* A fully built menu tree.
......@@ -1323,6 +1424,12 @@ function _menu_build_tree($menu_name, array $parameters = array()) {
if (isset($parameters['max_depth'])) {
$query->condition('ml.depth', $parameters['max_depth'], '<=');
}
// Add custom query conditions, if any were passed.
if (isset($parameters['conditions'])) {
foreach ($parameters['conditions'] as $column => $value) {
$query->condition($column, $value);
}
}
// Build an ordered array of links using the query result object.
$links = array();
......@@ -1416,18 +1523,30 @@ function _menu_tree_check_access(&$tree) {
}
/**
* Build the data representing a menu tree.
* Builds the data representing a menu tree.
*
* @param $links
* An array of links (associative arrays) ordered by p1..p9.
* 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 plid values that represent the path from the current page
* to the root of the menu tree.
* 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 of any link in the $links array.
* The minimum depth to include in the returned menu tree.
*
* @return
* See menu_tree_page_data for a description of the data structure.
* 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.
......@@ -1627,9 +1746,9 @@ function menu_get_custom_theme($initialize = FALSE) {
if (!empty($custom_themes)) {
$custom_theme = array_pop($custom_themes);
}
// Otherwise, execute the theme callback function for the current page, if
// there is one, in order to determine the custom theme to set.
if (!isset($custom_theme)) {
// 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']);
......@@ -1638,7 +1757,6 @@ function menu_get_custom_theme($initialize = FALSE) {
}
}
}
}
return $custom_theme;
}
......@@ -2024,7 +2142,7 @@ function menu_local_tasks($level = 0) {
* 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.
* A list of menu router items that are local tasks for the passed-in path.
*
* @see contextual_links_preprocess()
*/
......@@ -2128,22 +2246,35 @@ function menu_tab_root_path() {
}
/**
* Returns renderable local tasks.
* 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() {
$output = array();
function theme_menu_local_tasks(&$variables) {
$output = '';
if ($primary = menu_primary_local_tasks()) {
$primary['#prefix'] = '<h2 class="element-invisible">' . t('Primary tabs') . '</h2><ul class="tabs primary">';
$primary['#suffix'] = '</ul>';
$output[] = $primary;
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 ($secondary = menu_secondary_local_tasks()) {
$secondary['#prefix'] = '<h2 class="element-invisible">' . t('Secondary tabs') . '</h2><ul class="tabs secondary">';
$secondary['#suffix'] = '</ul>';
$output[] = $secondary;
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;
......@@ -2151,6 +2282,13 @@ function theme_menu_local_tasks() {
/**
* Set (or get) the active menu for the current page - determines the active trail.
*
* @return
* An array of menu machine names, in order of preference. The
* 'menu_default_active_menus' variable may be used to assert a menu order
* different from the order of creation, or to prevent a particular menu from
* being used at all in the active trail.
* E.g., $conf['menu_default_active_menus'] = array('navigation', 'main-menu')
*/
function menu_set_active_menu_names($menu_names = NULL) {
$active = &drupal_static(__FUNCTION__);
......@@ -2174,38 +2312,36 @@ function menu_get_active_menu_names() {
/**
* Set the active path, which determines which page is loaded.
*
* @param $path
* A Drupal path - not a path alias.
*
* 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 or gets the active trail (path to menu tree root) of the current page.
* 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, or NULL to use previously-set or calculated trail. If
* supplying a trail, use the same format as the return value (see below).
* 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
* 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).
* If $new_trail is supplied, the value is saved in a static variable and
* returned. If $new_trail is not supplied, and there is a saved value from
* a previous call, the saved value is returned. If $new_trail is not supplied
* and there is no saved value, the path to the current page is calculated,
* saved as the static value, and returned.
* The active trail. See menu_get_active_trail() for details.
*/
function menu_set_active_trail($new_trail = NULL) {
$trail = &drupal_static(__FUNCTION__);
......@@ -2266,11 +2402,11 @@ function menu_set_active_trail($new_trail = NULL) {
}
list($key, $curr) = each($tree);
}
// Make sure the current page is in the trail (needed for the page title),
// if the link's type allows it to be shown in the breadcrumb. Also exclude
// it if we are on the front page.
// 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'] && ($preferred_link['type'] & MENU_VISIBLE_IN_BREADCRUMB) == MENU_VISIBLE_IN_BREADCRUMB && !drupal_is_front_page()) {
if ($last['href'] != $preferred_link['href'] && !drupal_is_front_page()) {
$trail[] = $preferred_link;
}
}
......@@ -2283,23 +2419,30 @@ function menu_set_active_trail($new_trail = NULL) {
* @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/%').
* @param $selected_menu
* The name of a menu used to restrict the search for a preferred menu link.
* If not specified, all the menus returned by menu_get_active_menu_names()
* will be used.
*
* @return
* A fully translated menu link, or NULL if not matching menu link was
* A fully translated menu link, or FALSE 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) {
function menu_link_get_preferred($path = NULL, $selected_menu = NULL) {
$preferred_links = &drupal_static(__FUNCTION__);
if (!isset($path)) {
$path = $_GET['q'];
}
if (!isset($preferred_links[$path])) {
$preferred_links[$path] = FALSE;
if (empty($selected_menu)) {
// Use an illegal menu name as the key for the preferred menu link.
$selected_menu = MENU_PREFERRED_LINK;
}
if (!isset($preferred_links[$path])) {
// 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
......@@ -2321,48 +2464,70 @@ function menu_link_get_preferred($path = NULL) {
// Retrieve a list of menu names, ordered by preference.
$menu_names = menu_get_active_menu_names();
// Put the selected menu at the front of the list.
array_unshift($menu_names, $selected_menu);
$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->fields('m', array_diff(drupal_schema_fields_sql('menu_router'), array('weight')));
$query->condition('ml.menu_name', $menu_names, 'IN');
$query->addField('ml', 'weight', 'link_weight');
$query->fields('m');
$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;
// Add any menus not already in the menu name search list.
if (!in_array($candidate['menu_name'], $menu_names)) {
$menu_names[] = $candidate['menu_name'];
}
}
// Pick the most specific link, in the most preferred menu.
// Store the most specific link for each menu. Also save the most specific
// link of the most preferred menu in $preferred_link.
foreach ($path_candidates as $link_path) {
if (!isset($candidates[$link_path])) {
continue;
}
if (isset($candidates[$link_path])) {
foreach ($menu_names as $menu_name) {
if (!isset($candidates[$link_path][$menu_name])) {
continue;
}
if (empty($preferred_links[$path][$menu_name]) && isset($candidates[$link_path][$menu_name])) {
$candidate_item = $candidates[$link_path][$menu_name];
$map = explode('/', $path);
_menu_translate($candidate_item, $map);
if ($candidate_item['access']) {
$preferred_links[$path] = $candidate_item;
$preferred_links[$path][$menu_name] = $candidate_item;
if (empty($preferred_links[$path][MENU_PREFERRED_LINK])) {
// Store the most specific link.
$preferred_links[$path][MENU_PREFERRED_LINK] = $candidate_item;
}
}
}
}
break 2;
}
}
}
return $preferred_links[$path];
return isset($preferred_links[$path][$selected_menu]) ? $preferred_links[$path][$selected_menu] : FALSE;
}
/**
* Gets the active trail (path to root menu root) of the current page.
*
* See menu_set_active_trail() for details of return value.
* 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();
......@@ -2452,9 +2617,11 @@ function menu_link_load($mlid) {
$query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
$query->fields('ml');
// Weight should be taken from {menu_links}, not {menu_router}.
$query->fields('m', array_diff(drupal_schema_fields_sql('menu_router'), array('weight')));
$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;
}
......@@ -2637,19 +2804,15 @@ function _menu_navigation_links_rebuild($menu) {
}
}
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 $item) {
foreach ($menu_links as $key => $item) {
$existing_item = db_select('menu_links')
->fields('menu_links', array(
'mlid',
'menu_name',
'plid',
'customized',
'has_children',
'updated',
))
->fields('menu_links')
->condition('link_path', $item['path'])
->condition('module', 'system')
->execute()->fetchAssoc();
......@@ -2668,9 +2831,14 @@ function _menu_navigation_links_rebuild($menu) {
$item['has_children'] = $existing_item['has_children'];
$item['updated'] = $existing_item['updated'];
}
if (!$existing_item || !$existing_item['customized']) {
if ($existing_item && $existing_item['customized']) {
$parent_candidates[$existing_item['mlid']] = $existing_item;
}
else {
$item = _menu_link_build($item);
menu_link_save($item);
menu_link_save($item, $existing_item, $parent_candidates);
$parent_candidates[$item['mlid']] = $item;
unset($menu_links[$key]);
}
}
}
......@@ -2834,11 +3002,12 @@ function _menu_delete_item($item, $force = FALSE) {
menu_link_save($child);
}
}
db_delete('menu_links')->condition('mlid', $item['mlid'])->execute();
// Notify modules we have deleted the item.
// 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']);
......@@ -2847,24 +3016,37 @@ function _menu_delete_item($item, $force = FALSE) {
}
/**
* Save a menu link.
* Saves a menu link.
*
* After calling this function, rebuild the menu cache using
* menu_cache_clear_all().
*
* @param $item
* An array representing a menu link item. The only mandatory keys are
* link_path and link_title. Possible keys are:
* - menu_name: Default is navigation
* - weight: Default is 0
* - expanded: Whether the item is expanded.
* - options: An array of options, see l() for more.
* - mlid: Set to an existing value, or 0 or NULL to insert a new link.
* - plid: The mlid of the parent.
* - router_path: The path of the relevant router 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) {
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>',
......@@ -2883,49 +3065,20 @@ function menu_link_save(&$item) {
'customized' => 0,
'updated' => 0,
);
$existing_item = FALSE;
if (isset($item['mlid'])) {
if ($existing_item = db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $item['mlid']))->fetchAssoc()) {
$existing_item['options'] = unserialize($existing_item['options']);
}
}
// If we have a parent link ID, we use it to inherit 'menu_name' and 'depth'.
if (isset($item['plid'])) {
if ($item['plid']) {
$parent = db_query("SELECT * FROM {menu_links} WHERE mlid = :mlid", array(':mlid' => $item['plid']))->fetchAssoc();
if (!$existing_item) {
$existing_item = db_query('SELECT * FROM {menu_links} WHERE mlid = :mlid', array('mlid' => $item['mlid']))->fetchAssoc();
}
// If the parent link ID is zero, then this link lives at the top-level.
else {
$parent = FALSE;
if ($existing_item) {
$existing_item['options'] = unserialize($existing_item['options']);
}
}
// Otherwise, try to find a valid parent link for this link.
else {
$query = db_select('menu_links');
// Only links derived from router items should have module == 'system', and
// we want to find the parent even if it's in a different menu.
if ($item['module'] == 'system') {
$query->condition('module', 'system');
$existing_item = FALSE;
}
// We always respect the link's 'menu_name'; inheritance for router items is
// ensured in _menu_router_build().
$query->condition('menu_name', $item['menu_name']);
// Find the parent - it must be unique.
$parent_path = $item['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);
}
// If a parent link was found, assign it and derive its menu.
// 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'];
......@@ -3046,6 +3199,85 @@ function menu_link_save(&$item) {
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.
*/
......@@ -3305,8 +3537,7 @@ function _menu_router_build($callbacks) {
$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.
// See http://php.net/manual/en/language.functions.php for reference.
if (preg_match('/^%(|[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)$/', $part, $matches)) {
if (preg_match('/^%(|' . DRUPAL_PHP_FUNCTION_PATTERN . ')$/', $part, $matches)) {
if (empty($matches[1])) {
$match = TRUE;
$load_functions[$k] = NULL;
......@@ -3342,7 +3573,7 @@ function _menu_router_build($callbacks) {
$fit = (1 << $number_parts) - 1;
}
$masks[$fit] = 1;
$item['load_functions'] = empty($load_functions) ? '' : serialize($load_functions);
$item['_load_functions'] = $load_functions;
$item['to_arg_functions'] = empty($to_arg_functions) ? '' : serialize($to_arg_functions);
$item += array(
'title' => '',
......@@ -3445,6 +3676,21 @@ function _menu_router_build($callbacks) {
$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'])) {
......@@ -3458,6 +3704,7 @@ function _menu_router_build($callbacks) {
$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' => '',
......
<?php
// $Id: module.inc,v 1.202 2010/10/03 02:04:55 dries Exp $
/**
* @file
......@@ -32,27 +31,39 @@ function module_load_all($bootstrap = FALSE) {
/**
* Collect a list of all loaded modules. During the bootstrap, return only
* vital modules. See bootstrap.inc
* 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
* Whether to force the module list to be regenerated (such as after the
* administrator has changed the system settings).
* @param $bootstrap
* Whether to return the reduced set of modules loaded in "bootstrap mode"
* for cached pages. See bootstrap.inc.
* (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
* 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.
* (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) Override the module list with the given modules. Stays until the
* next call with $refresh = TRUE.
* (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 all loaded
* modules.
* An associative array whose keys and values are the names of the modules in
* the list.
*/
function module_list($refresh = FALSE, $bootstrap = FALSE, $sort = FALSE, $fixed_list = NULL) {
function module_list($refresh = FALSE, $bootstrap_refresh = FALSE, $sort = FALSE, $fixed_list = NULL) {
static $list = array(), $sorted_list;
if (empty($list) || $refresh || $fixed_list) {
......@@ -70,7 +81,7 @@ function module_list($refresh = FALSE, $bootstrap = FALSE, $sort = FALSE, $fixed
// data.
drupal_static_reset('system_list');
}
if ($bootstrap) {
if ($bootstrap_refresh) {
$list = system_list('bootstrap');
}
else {
......@@ -115,6 +126,9 @@ function system_list($type) {
// if not fetch only the required information to fire bootstrap hooks
// in case we are going to serve the page from cache.
if ($type == 'bootstrap') {
if (isset($lists['bootstrap'])) {
return $lists['bootstrap'];
}
if ($cached = cache_get('bootstrap_modules', 'cache_bootstrap')) {
$bootstrap_list = $cached->data;
}
......@@ -181,6 +195,7 @@ function system_list($type) {
*/
function system_list_reset() {
drupal_static_reset('system_list');
drupal_static_reset('system_rebuild_module_data');
drupal_static_reset('list_themes');
cache_clear_all('bootstrap_modules', 'cache_bootstrap');
cache_clear_all('system_list', 'cache_bootstrap');
......@@ -235,12 +250,18 @@ function module_exists($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 . '/includes/install.inc';
module_load_include('install', $module);
return module_load_include('install', $module);
}
/**
......@@ -264,11 +285,14 @@ function module_load_install($module) {
* @param $module
* The module to which the include file belongs.
* @param $name
* Optionally, specify the base file name (without the $type extension).
* If not set, $module is used.
* (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 (empty($name)) {
if (!isset($name)) {
$name = $module;
}
......@@ -350,7 +374,7 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
// Add dependencies to the list, with a placeholder weight.
// The new modules will be processed as the while loop continues.
foreach ($module_data[$module]->info['dependencies'] as $dependency) {
foreach (array_keys($module_data[$module]->requires) as $dependency) {
if (!isset($module_list[$dependency])) {
$module_list[$dependency] = 0;
}
......@@ -466,6 +490,7 @@ function module_disable($module_list, $disable_dependents = TRUE) {
// 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.
......@@ -477,7 +502,7 @@ function module_disable($module_list, $disable_dependents = TRUE) {
// 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]) && !strstr($module_data[$dependent]->filename, '.profile')) {
if (!isset($module_list[$dependent]) && $dependent != $profile) {
$module_list[$dependent] = 0;
}
}
......@@ -514,6 +539,7 @@ function module_disable($module_list, $disable_dependents = TRUE) {
system_list_reset();
module_list(TRUE);
module_implements('', FALSE, TRUE);
entity_info_cache_clear();
// Invoke hook_modules_disabled before disabling modules,
// so we can still call module hooks to get information.
module_invoke_all('modules_disabled', $invoke_modules);
......@@ -569,7 +595,20 @@ function module_disable($module_list, $disable_dependents = TRUE) {
* implemented in that module.
*/
function module_hook($module, $hook) {
return function_exists($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;
}
/**
......@@ -637,7 +676,9 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) {
$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']);
if (module_hook($module, $hook)) {
// 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;
}
}
......@@ -655,9 +696,11 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) {
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 module_hook() on
// each request to avoid undefined function errors.
if (!module_hook($module, $hook)) {
// 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]);
......@@ -673,9 +716,17 @@ function module_implements($hook, $sort = FALSE, $reset = FALSE) {
* Retrieve a list of what hooks are explicitly declared.
*/
function module_hook_info() {
$hook_info = &drupal_static(__FUNCTION__, array());
// 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 (empty($hook_info)) {
if (!isset($hook_info)) {
$hook_info = array();
$cache = cache_get('hook_info', 'cache_bootstrap');
if ($cache === FALSE) {
// Rebuild the cache and save it.
......@@ -736,15 +787,15 @@ function module_implements_write_cache() {
* @return
* The return value of the hook implementation.
*/
function module_invoke() {
function module_invoke($module, $hook) {
$args = func_get_args();
$module = $args[0];
$hook = $args[1];
// 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.
*
......@@ -757,9 +808,9 @@ function module_invoke() {
* 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() {
function module_invoke_all($hook) {
$args = func_get_args();
$hook = $args[0];
// Remove $hook from the arguments.
unset($args[0]);
$return = array();
foreach (module_implements($hook) as $module) {
......@@ -786,7 +837,7 @@ function module_invoke_all() {
* Array of modules required by core.
*/
function drupal_required_modules() {
$files = drupal_system_listing('/\.info$/', 'modules', 'name', 0);
$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.
......@@ -805,7 +856,7 @@ function drupal_required_modules() {
/**
* Hands off alterable variables to type-specific *_alter implementations.
*
* This dispatch function hands off the passed in variables to type-specific
* 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.
*
......@@ -839,14 +890,14 @@ function drupal_required_modules() {
* 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
* @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
* @param $context1
* (optional) An additional variable that is passed by reference.
* @param &$context2
* @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.
......@@ -902,10 +953,24 @@ function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
}
// 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.
// appropriate order. module_implements() can only return ordered
// implementations of a single hook. To get the ordered implementations
// of multiple hooks, we mimic the module_implements() logic of first
// ordering by module_list(), and then calling
// drupal_alter('module_implements').
if (array_diff($extra_modules, $modules)) {
// Order the modules by the order returned by module_list().
// Merge the arrays and order by module_list().
$modules = array_intersect(module_list(), array_merge($modules, $extra_modules));
// Since module_implements() already took care of loading the necessary
// include files, we can safely pass FALSE for the array values.
$implementations = array_fill_keys($modules, FALSE);
// Let modules adjust the order solely based on the primary hook. This
// ensures the same module order regardless of whether this if block
// runs. Calling drupal_alter() recursively in this way does not result
// in an infinite loop, because this call is for a single $type, so we
// won't end up in this code block again.
drupal_alter('module_implements', $implementations, $hook);
$modules = array_keys($implementations);
}
foreach ($modules as $module) {
// Since $modules is a merged array, for any given module, we do not
......@@ -953,4 +1018,3 @@ function drupal_alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
$function($data, $context1, $context2);
}
}
<?php
// $Id: pager.inc,v 1.85 2010/10/08 05:07:53 webchick Exp $
/**
* @file
......@@ -20,7 +19,7 @@ class PagerDefault extends SelectQueryExtender {
*
* @var int
*/
static protected $maxElement = 0;
static $maxElement = 0;
/**
* The number of elements per page to allow.
......@@ -201,7 +200,7 @@ function pager_find_page($element = 0) {
* 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 peformed using
* 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
......@@ -431,8 +430,9 @@ function theme_pager($variables) {
/**
* @defgroup pagerpieces Pager pieces
* @{
* Use these pieces to construct your own custom pagers in your theme. Note that
* you should NOT modify this file to customize your pager.
* Theme functions for customizing pager elements.
*
* Note that you should NOT modify this file to customize your pager.
*/
/**
......@@ -574,13 +574,20 @@ function theme_pager_last($variables) {
*
* @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 a pager
* anchor tag.
* - attributes: An associative array of HTML attributes to apply to the
* pager link.
*
* @see theme_pager()
*
* @ingroup themeable
*/
......
<?php
// $Id: password.inc,v 1.8 2010/05/04 15:47:03 dries Exp $
/**
* @file
......@@ -19,7 +18,7 @@
* increase by 1 every Drupal version in order to counteract increases in the
* speed and power of computers available to crack the hashes.
*/
define('DRUPAL_HASH_COUNT', 14);
define('DRUPAL_HASH_COUNT', 15);
/**
* The minimum allowed log2 number of iterations for password stretching.
......@@ -99,17 +98,37 @@ function _password_base64_encode($input, $count) {
*/
function _password_generate_salt($count_log2) {
$output = '$S$';
// Minimum log2 iterations is DRUPAL_MIN_HASH_COUNT.
$count_log2 = max($count_log2, DRUPAL_MIN_HASH_COUNT);
// Maximum log2 iterations is DRUPAL_MAX_HASH_COUNT.
// 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[min($count_log2, DRUPAL_MAX_HASH_COUNT)];
$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.
*
......@@ -261,7 +280,8 @@ function user_needs_new_hash($account) {
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) != variable_get('password_count_log2', DRUPAL_HASH_COUNT));
return (_password_get_count_log2($account->pass) !== $count_log2);
}
<?php
// $Id: path.inc,v 1.71 2010/08/09 00:13:06 dries Exp $
/**
* @file
......@@ -14,12 +13,12 @@
* 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'));
// Ensure $_GET['q'] is set before calling drupal_normal_path(), to support
// path caching with hook_url_inbound_alter().
if (empty($_GET['q'])) {
$_GET['q'] = variable_get('site_frontpage', 'node');
}
$_GET['q'] = drupal_get_normal_path($_GET['q']);
}
/**
......@@ -94,13 +93,30 @@ function drupal_lookup_path($action, $path = '', $path_language = NULL) {
if ($cached = cache_get($cid, 'cache_path')) {
$cache['system_paths'] = $cached->data;
// Now fetch the aliases corresponding to these system paths.
// We order by ASC and overwrite array keys to ensure the correct
// alias is used when there are multiple aliases per path.
$cache['map'][$path_language] = db_query("SELECT source, alias FROM {url_alias} WHERE source IN (:system) AND language IN (:language, :language_none) ORDER BY language ASC, pid ASC", array(
$args = array(
':system' => $cache['system_paths'],
':language' => $path_language,
':language_none' => LANGUAGE_NONE,
))->fetchAllKeyed();
);
// 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])));
}
......@@ -117,12 +133,22 @@ function drupal_lookup_path($action, $path = '', $path_language = NULL) {
}
// For system paths which were not cached, query aliases individually.
elseif (!isset($cache['no_aliases'][$path_language][$path])) {
// Get the most fitting result falling back with alias without language
$alias = db_query("SELECT alias FROM {url_alias} WHERE source = :source AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", array(
$args = array(
':source' => $path,
':language' => $path_language,
':language_none' => LANGUAGE_NONE,
))->fetchField();
);
// 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;
}
......@@ -133,12 +159,23 @@ function drupal_lookup_path($action, $path = '', $path_language = NULL) {
// 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]))) {
// Get the most fitting result falling back with alias without language
if ($source = db_query("SELECT source FROM {url_alias} WHERE alias = :alias AND language IN (:language, :language_none) ORDER BY language DESC, pid DESC", array(
$args = array(
':alias' => $path,
':language' => $path_language,
':language_none' => LANGUAGE_NONE))
->fetchField()) {
':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 {
......
<?php
// $Id: registry.inc,v 1.34 2010/09/17 14:45:46 dries Exp $
/**
* @file
......@@ -98,7 +97,8 @@ function _registry_update() {
_registry_check_code(REGISTRY_RESET_LOOKUP_CACHE);
}
catch (Exception $e) {
$transaction->rollback('registry', $e->getMessage(), array(), WATCHDOG_ERROR);
$transaction->rollback();
watchdog_exception('registry', $e);
throw $e;
}
......@@ -183,4 +183,3 @@ function _registry_parse_file($filename, $contents, $module = '', $weight = 0) {
/**
* @} End of "defgroup registry".
*/
<?php
// $Id: session.inc,v 1.90 2010/10/15 04:15:41 webchick Exp $
/**
* @file
......@@ -50,21 +49,23 @@ function _drupal_session_close() {
}
/**
* Session handler assigned by session_set_save_handler().
* Reads an entire session from the database (internal use only).
*
* This function will be called by PHP to retrieve the current user's
* session data, which is stored in the database. It also loads the
* current user's appropriate roles into the user object.
* 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 should not be called directly. Session data should
* instead be accessed via 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
* Session ID.
* The session ID of the session to retrieve.
*
* @return
* Either an array of the session data, or an empty string, if no data
* was found or the user is anonymous.
* The user's session, or an empty string if no session exists.
*/
function _drupal_session_read($sid) {
global $user, $is_https;
......@@ -100,6 +101,10 @@ function _drupal_session_read($sid) {
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();
}
$session_data = @gzinflate($user->session);
if ($session_data !== FALSE) {
$user->session = $session_data;
}
// We found the client's session record and they are an authenticated,
// active user.
......@@ -137,21 +142,22 @@ function _drupal_session_read($sid) {
}
/**
* Session handler assigned by session_set_save_handler().
* Writes an entire session to the database (internal use only).
*
* This function will be called by PHP to store the current user's
* session, which Drupal saves to the database.
* This function is registered with session_set_save_handler() to support
* database-backed sessions.
*
* This function should not be called directly. Session data should
* instead be accessed via the $_SESSION superglobal.
* 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
* Session ID.
* The session ID of the session to write to.
* @param $value
* Serialized array of the session data.
* Session data to write as a serialized string.
*
* @return
* This function will always return TRUE.
* Always returns TRUE.
*/
function _drupal_session_write($sid, $value) {
global $user, $is_https;
......@@ -176,24 +182,26 @@ function _drupal_session_write($sid, $value) {
'uid' => $user->uid,
'cache' => isset($user->cache) ? $user->cache : 0,
'hostname' => ip_address(),
'session' => $value,
'session' => gzdeflate($value, 9),
'timestamp' => REQUEST_TIME,
);
// The "secure pages" setting allows a site to simultaneously use both
// secure and insecure session cookies. If enabled and both cookies are
// presented then use both keys. If not enabled but on HTTPS then use the
// PHP session id as 'ssid'. If on HTTP then use the PHP session id as
// 'sid'.
// 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 (variable_get('https', FALSE) && isset($_COOKIE[$insecure_session_name])) {
if (isset($_COOKIE[$insecure_session_name])) {
$key['sid'] = $_COOKIE[$insecure_session_name];
}
}
else {
$key['sid'] = $sid;
}
db_merge('sessions')
......@@ -234,7 +242,9 @@ function drupal_session_initialize() {
session_set_save_handler('_drupal_session_open', '_drupal_session_close', '_drupal_session_read', '_drupal_session_write', '_drupal_session_destroy', '_drupal_session_garbage_collection');
if (isset($_COOKIE[session_name()]) || ($is_https && variable_get('https', FALSE) && isset($_COOKIE[substr(session_name(), 1)]))) {
// 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
......
<?php
// $Id: stream_wrappers.inc,v 1.21 2010/10/21 12:09:41 dries Exp $
/**
* @file
......@@ -318,7 +317,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
}
$extension = '';
$file_parts = explode('.', basename($uri));
$file_parts = explode('.', drupal_basename($uri));
// Remove the first part: a full filename should not match an extension.
array_shift($file_parts);
......@@ -342,7 +341,11 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
* Base implementation of chmod().
*/
function chmod($mode) {
return @chmod($this->getLocalPath(), $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;
}
/**
......@@ -353,10 +356,18 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
}
/**
* Return the local filesystem path.
* Returns the canonical absolute path of the URI, if possible.
*
* @param $uri
* Optional URI, supplied when doing a move or rename.
* @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)) {
......@@ -366,7 +377,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
$realpath = realpath($path);
if (!$realpath) {
// This file does not yet exist.
$realpath = realpath(dirname($path)) . '/' . basename($path);
$realpath = realpath(dirname($path)) . '/' . drupal_basename($path);
}
$directory = realpath($this->getDirectoryPath());
if (!$realpath || !$directory || strpos($realpath, $directory) !== 0) {
......@@ -384,7 +395,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
* The file mode ("r", "wb" etc.).
* @param $options
* A bit mask of STREAM_USE_PATH and STREAM_REPORT_ERRORS.
* @param &$opened_path
* @param $opened_path
* A string containing the path actually opened.
*
* @return
......@@ -398,7 +409,7 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
$this->handle = ($options & STREAM_REPORT_ERRORS) ? fopen($path, $mode) : @fopen($path, $mode);
if ((bool) $this->handle && $options & STREAM_USE_PATH) {
$opened_url = $path;
$opened_path = $path;
}
return (bool) $this->handle;
......@@ -671,11 +682,14 @@ abstract class DrupalLocalStreamWrapper implements DrupalStreamWrapperInterface
*/
public function url_stat($uri, $flags) {
$this->uri = $uri;
if ($flags & STREAM_URL_STAT_QUIET) {
return @stat($this->getLocalPath());
$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($this->getLocalPath());
return stat($path);
}
}
......
<?php
// $Id: tablesort.inc,v 1.59 2010/10/15 04:34:15 webchick Exp $
/**
* @file
......@@ -74,18 +73,7 @@ class TableSort extends SelectQueryExtender {
* The current sort direction ("asc" or "desc").
*/
protected function getSort() {
if (isset($_GET['sort'])) {
return ($_GET['sort'] == 'desc') ? 'desc' : 'asc';
}
// User has not specified a sort. Use default if specified; otherwise use "asc".
else {
foreach ($this->header as $header) {
if (is_array($header) && array_key_exists('sort', $header)) {
return $header['sort'];
}
}
}
return 'asc';
return tablesort_get_sort($this->header);
}
/**
......@@ -112,32 +100,7 @@ class TableSort extends SelectQueryExtender {
* - "sql": The name of the database field to sort on.
*/
protected function order() {
$order = isset($_GET['order']) ? $_GET['order'] : '';
foreach ($this->header as $header) {
if (isset($header['data']) && $order == $header['data']) {
return array('name' => $header['data'], 'sql' => isset($header['field']) ? $header['field'] : '');
}
if (isset($header['sort']) && ($header['sort'] == 'asc' || $header['sort'] == 'desc')) {
$default = array('name' => $header['data'], 'sql' => isset($header['field']) ? $header['field'] : '');
}
}
if (isset($default)) {
return $default;
}
else {
// The first column specified is initial 'order by' field unless otherwise specified
$headers = array_values($this->header);
$header = $headers[0];
if (is_array($header)) {
$header += array('data' => NULL, 'field' => NULL);
return array('name' => $header['data'], 'sql' => $header['field']);
}
else {
return array('name' => $header);
}
}
return tablesort_get_order($this->header);
}
}
......@@ -239,29 +202,27 @@ function tablesort_get_query_parameters() {
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']) {
return array('name' => $header['data'], 'sql' => isset($header['field']) ? $header['field'] : '');
$default = $header;
break;
}
if (isset($header['sort']) && ($header['sort'] == 'asc' || $header['sort'] == 'desc')) {
$default = array('name' => $header['data'], 'sql' => isset($header['field']) ? $header['field'] : '');
}
if (empty($default) && isset($header['sort']) && ($header['sort'] == 'asc' || $header['sort'] == 'desc')) {
$default = $header;
}
if (isset($default)) {
return $default;
}
else {
// The first column specified is the initial 'order by' field unless otherwise specified.
$first = current($headers);
if (is_array($first)) {
$first += array('data' => NULL, 'field' => NULL);
return array('name' => $first['data'], 'sql' => $first['field']);
}
else {
return array('name' => $first, 'sql' => '');
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']);
}
/**
......@@ -274,12 +235,15 @@ function tablesort_get_order($headers) {
*/
function tablesort_get_sort($headers) {
if (isset($_GET['sort'])) {
return ($_GET['sort'] == 'desc') ? 'desc' : 'asc';
return (strtolower($_GET['sort']) == 'desc') ? 'desc' : 'asc';
}
// User has not specified a sort. Use default if specified; otherwise use "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) && array_key_exists('sort', $header)) {
if (is_array($header) && isset($header['data']) && $header['data'] == $ts['name'] && isset($header['sort'])) {
return $header['sort'];
}
}
......
<?php
// $Id: theme.inc,v 1.620 2010/10/21 19:31:39 dries Exp $
/**
* @file
......@@ -104,7 +103,7 @@ function drupal_theme_initialize() {
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.
// later Ajax request can be rendered using the same theme.
// @see ajax_base_page_theme()
$setting['ajaxPageState'] = array(
'theme' => $theme_key,
......@@ -238,18 +237,33 @@ function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callb
/**
* 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 theme registry array if it has been stored in memory, NULL otherwise.
* The complete theme registry array, or an instance of the ThemeRegistry
* class.
*/
function theme_get_registry() {
static $theme_registry = NULL;
function theme_get_registry($complete = TRUE) {
static $theme_registry = array();
$key = (int) $complete;
if (!isset($theme_registry)) {
if (!isset($theme_registry[$key])) {
list($callback, $arguments) = _theme_registry_callback();
$theme_registry = call_user_func_array($callback, $arguments);
if (!$complete) {
$arguments[] = FALSE;
}
$theme_registry[$key] = call_user_func_array($callback, $arguments);
}
return $theme_registry;
return $theme_registry[$key];
}
/**
......@@ -269,21 +283,28 @@ function _theme_registry_callback($callback = NULL, array $arguments = array())
}
/**
* Get the theme_registry cache from the database; if it doesn't exist, build it.
* 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
* @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) {
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.
$cache = cache_get("theme_registry:$theme->name", 'cache');
if (isset($cache->data)) {
$registry = $cache->data;
$cached = cache_get("theme_registry:$theme->name");
if (isset($cached->data)) {
$registry = $cached->data;
}
else {
// If not, build one and cache it.
......@@ -296,6 +317,10 @@ function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL)
}
return $registry;
}
else {
return new ThemeRegistry('theme_registry:runtime:' . $theme->name, 'cache');
}
}
/**
* Write the theme_registry cache into the database.
......@@ -313,6 +338,116 @@ function drupal_theme_rebuild() {
cache_clear_all('theme_registry', 'cache', TRUE);
}
/**
* 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_get($this->cid, $this->bin)) {
$data = $cached->data;
}
else {
// 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 = $this->initializeRegistry();
if ($this->persistable) {
$this->set($data);
}
}
$this->storage = $data;
}
/**
* Initializes the full theme registry.
*
* @return
* An array with the keys of the full theme registry, but the values
* initialized to NULL.
*/
function initializeRegistry() {
$this->completeRegistry = theme_get_registry();
return array_fill_keys(array_keys($this->completeRegistry), NULL);
}
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($data, $lock = TRUE) {
$lock_name = $this->cid . ':' . $this->bin;
if (!$lock || lock_acquire($lock_name)) {
if ($cached = cache_get($this->cid, $this->bin)) {
// 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);
}
else {
$registry = $this->initializeRegistry();
$data = array_merge($registry, $data);
}
cache_set($this->cid, $data, $this->bin);
if ($lock) {
lock_release($lock_name);
}
}
}
}
/**
* Process a single implementation of hook_theme().
*
......@@ -320,8 +455,8 @@ function drupal_theme_rebuild() {
* 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.
* - '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.
......@@ -363,7 +498,6 @@ function drupal_theme_rebuild() {
*/
function _theme_process_registry(&$cache, $name, $type, $theme, $path) {
$result = array();
$function = $name . '_theme';
// Processor functions work in two distinct phases with the process
// functions always being executed after the preprocess functions.
......@@ -372,24 +506,43 @@ function _theme_process_registry(&$cache, $name, $type, $theme, $path) {
'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 left out, default to standard naming
// 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 a path is set in the info, use what was set. Otherwise use the
// default path. This is mostly so system.module can declare theme
// functions on behalf of core .include files.
// All files are included to be safe. Conditionally included
// files can prevent them from getting registered.
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'];
......@@ -397,14 +550,10 @@ function _theme_process_registry(&$cache, $name, $type, $theme, $path) {
$result[$hook]['includes'][] = $include_file;
}
// If these keys are left unspecified within overridden entries returned
// by hook_theme(), carry them forward from the prior entry. This is so
// that themes don't need to specify this information, since the module
// that registered the theme hook already has.
foreach (array('variables', 'render element', 'pattern', 'base hook') as $key) {
if (!array_key_exists($key, $info) && isset($cache[$hook][$key])) {
$result[$hook][$key] = $cache[$hook][$key];
}
// 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.
......@@ -469,7 +618,7 @@ function _theme_process_registry(&$cache, $name, $type, $theme, $path) {
}
// Merge the newly created theme hooks into the existing cache.
$cache = array_merge($cache, $result);
$cache = $result + $cache;
}
// Let themes have variable processors even if they didn't register a template.
......@@ -499,23 +648,34 @@ function _theme_process_registry(&$cache, $name, $type, $theme, $path) {
}
/**
* Rebuild the theme registry cache.
* 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
* @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.
// 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) {
......@@ -633,8 +793,9 @@ function list_themes($refresh = FALSE) {
* 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 request and routes it to the appropriate
* @link themeable theme function or template @endlink, 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
......@@ -734,6 +895,8 @@ function list_themes($refresh = FALSE) {
*
* @return
* An HTML string representing the themed output.
*
* @see themeable
*/
function theme($hook, $variables = array()) {
static $hooks = NULL;
......@@ -747,7 +910,7 @@ function theme($hook, $variables = array()) {
if (!isset($hooks)) {
drupal_theme_initialize();
$hooks = theme_get_registry();
$hooks = theme_get_registry(FALSE);
}
// If an array of hook candidates were passed, use the first one that has an
......@@ -972,7 +1135,7 @@ function drupal_find_theme_functions($cache, $prefixes) {
// 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 heirarchy 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
......@@ -982,7 +1145,7 @@ function drupal_find_theme_functions($cache, $prefixes) {
$matches = preg_grep('/^' . $prefix . '_' . $pattern . '/', $functions['user']);
if ($matches) {
foreach ($matches as $match) {
$new_hook = str_replace($prefix . '_', '', $match);
$new_hook = substr($match, strlen($prefix) + 1);
$arg_name = isset($info['variables']) ? 'variables' : 'render element';
$implementations[$new_hook] = array(
'function' => $match,
......@@ -1256,15 +1419,8 @@ function theme_enable($theme_list) {
menu_rebuild();
drupal_theme_rebuild();
// Notify locale module about new themes being enabled, so translations can
// be imported. This might start a batch, and only return to the redirect
// path after that.
module_invoke('locale', 'system_update', $theme_list);
// Invoke hook_themes_enabled after the themes have been enabled.
// Invoke hook_themes_enabled() after the themes have been enabled.
module_invoke_all('themes_enabled', $theme_list);
return;
}
/**
......@@ -1296,10 +1452,8 @@ function theme_disable($theme_list) {
menu_rebuild();
drupal_theme_rebuild();
// Invoke hook_themes_enabled after the themes have been enabled.
// Invoke hook_themes_disabled after the themes have been disabled.
module_invoke_all('themes_disabled', $theme_list);
return;
}
/**
......@@ -1375,32 +1529,35 @@ function theme_link($variables) {
*
* @param $variables
* An associative array containing:
* - links: A keyed 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
* keys:
* - title: the link text
* - href: the link URL. If omitted, the 'title' is shown as a plain text
* - 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) set this to TRUE if 'title' is HTML so it will be
* escaped.
* Array items are passed on to the l() function's $options parameter when
* creating the link.
* - attributes: A keyed array of attributes.
* - heading: An optional keyed array or a string for a heading to precede the
* links. When using an array the following keys can be used:
* - text: the heading text
* - level: the heading level (e.g. 'h2', 'h3')
* - class: (optional) an array of the CSS classes for the heading
* - 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.
* 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'];
......@@ -1754,10 +1911,10 @@ function theme_table($variables) {
*/
function theme_tablesort_indicator($variables) {
if ($variables['style'] == "asc") {
return theme('image', array('path' => 'misc/arrow-asc.png', 'alt' => t('sort ascending'), 'title' => t('sort ascending')));
return theme('image', array('path' => 'misc/arrow-asc.png', 'width' => 13, 'height' => 13, 'alt' => t('sort ascending'), 'title' => t('sort ascending')));
}
else {
return theme('image', array('path' => 'misc/arrow-desc.png', 'alt' => t('sort descending'), 'title' => t('sort descending')));
return theme('image', array('path' => 'misc/arrow-desc.png', 'width' => 13, 'height' => 13, 'alt' => t('sort descending'), 'title' => t('sort descending')));
}
}
......@@ -1814,6 +1971,7 @@ function theme_item_list($variables) {
foreach ($items as $i => $item) {
$attributes = array();
$children = array();
$data = '';
if (is_array($item)) {
foreach ($item as $key => $value) {
if ($key == 'data') {
......@@ -1864,12 +2022,13 @@ function theme_more_help_link($variables) {
*
* @param $variables
* An associative array containing:
* - url: The url of the feed.
* - 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' => 'misc/feed.png', 'alt' => $text))) {
if ($image = theme('image', array('path' => '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)));
}
}
......@@ -1894,11 +2053,12 @@ function theme_feed_icon($variables) {
*/
function theme_html_tag($variables) {
$element = $variables['element'];
$attributes = isset($element['#attributes']) ? drupal_attributes($element['#attributes']) : '';
if (!isset($element['#value'])) {
return '<' . $element['#tag'] . drupal_attributes($element['#attributes']) . " />\n";
return '<' . $element['#tag'] . $attributes . " />\n";
}
else {
$output = '<' . $element['#tag'] . drupal_attributes($element['#attributes']) . '>';
$output = '<' . $element['#tag'] . $attributes . '>';
if (isset($element['#value_prefix'])) {
$output .= $element['#value_prefix'];
}
......@@ -1960,6 +2120,8 @@ function theme_username($variables) {
/**
* Returns HTML for a progress bar.
*
* Note that the core Batch API uses this only for non-JavaScript batch jobs.
*
* @param $variables
* An associative array containing:
* - percent: The percentage of the progress.
......@@ -2039,6 +2201,9 @@ function _theme_table_cell($cell, $header = FALSE) {
* 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;
......@@ -2074,7 +2239,7 @@ function template_preprocess(&$variables, $hook) {
}
/**
* Returns hook-independant variables to template_preprocess().
* Returns hook-independent variables to template_preprocess().
*/
function _template_preprocess_default_variables() {
global $user;
......@@ -2116,6 +2281,9 @@ function _template_preprocess_default_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.
......@@ -2192,14 +2360,18 @@ function template_preprocess_html(&$variables) {
// Construct page title.
if (drupal_get_title()) {
$head_title = array(strip_tags(drupal_get_title()), check_plain(variable_get('site_name', 'Drupal')));
$head_title = array(
'title' => strip_tags(drupal_get_title()),
'name' => check_plain(variable_get('site_name', 'Drupal')),
);
}
else {
$head_title = array(check_plain(variable_get('site_name', 'Drupal')));
$head_title = array('name' => check_plain(variable_get('site_name', 'Drupal')));
if (variable_get('site_slogan', '')) {
$head_title[] = filter_xss_admin(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.
......@@ -2250,13 +2422,12 @@ function template_preprocess_page(&$variables) {
$variables['language'] = $GLOBALS['language'];
$variables['language']->dir = $GLOBALS['language']->direction ? 'rtl' : 'ltr';
$variables['logo'] = theme_get_setting('logo');
$variables['messages'] = $variables['show_messages'] ? theme('status_messages') : '';
$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'] = theme('menu_local_tasks');
$variables['tabs'] = menu_local_tabs();
if ($node = menu_get_object()) {
$variables['node'] = $node;
......@@ -2288,6 +2459,12 @@ function template_process_page(&$variables) {
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') : '';
}
}
/**
......@@ -2352,8 +2529,19 @@ function theme_get_suggestions($args, $base, $delimiter = '__') {
$suggestions = array();
$prefix = $base;
foreach ($args as $arg) {
// Remove slashes or null per SA-CORE-2009-003.
$arg = str_replace(array("/", "\\", "\0"), '', $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)) {
......@@ -2417,18 +2605,22 @@ function template_preprocess_maintenance_page(&$variables) {
// Construct page title
if (drupal_get_title()) {
$head_title = array(strip_tags(drupal_get_title()), variable_get('site_name', 'Drupal'));
$head_title = array(
'title' => strip_tags(drupal_get_title()),
'name' => variable_get('site_name', 'Drupal'),
);
}
else {
$head_title = array(variable_get('site_name', 'Drupal'));
$head_title = array('name' => variable_get('site_name', 'Drupal'));
if (variable_get('site_slogan', '')) {
$head_title[] = 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();
......@@ -2445,7 +2637,6 @@ function template_preprocess_maintenance_page(&$variables) {
$variables['site_slogan'] = (theme_get_setting('toggle_slogan') ? variable_get('site_slogan', '') : '');
$variables['tabs'] = '';
$variables['title'] = drupal_get_title();
$variables['closure'] = '';
// Compile a list of classes that are going to be applied to the body element.
$variables['classes_array'][] = 'in-maintenance';
......
<?php
// $Id: theme.maintenance.inc,v 1.67 2010/09/19 18:10:41 dries Exp $
/**
* @file
......@@ -41,7 +40,6 @@ function _drupal_maintenance_theme() {
// to work. See _drupal_log_error().
if (!class_exists('Database', FALSE)) {
require_once DRUPAL_ROOT . '/includes/database/database.inc';
spl_autoload_register('db_autoload');
}
// We use the default theme as the maintenance theme. If a default theme
......@@ -176,13 +174,14 @@ function theme_authorize_report($variables) {
if (!empty($messages)) {
$output .= '<div id="authorize-results">';
foreach ($messages as $heading => $logs) {
$output .= '<h3>' . check_plain($heading) . '</h3>';
$items = array();
foreach ($logs as $number => $log_message) {
if ($number === '#abort') {
continue;
}
$output .= theme('authorize_message', array('message' => $log_message['message'], 'success' => $log_message['success']));
$items[] = theme('authorize_message', array('message' => $log_message['message'], 'success' => $log_message['success']));
}
$output .= theme('item_list', array('items' => $items, 'title' => $heading));
}
$output .= '</div>';
}
......@@ -200,14 +199,13 @@ function theme_authorize_report($variables) {
* @ingroup themeable
*/
function theme_authorize_message($variables) {
$output = '';
$message = $variables['message'];
$success = $variables['success'];
if ($success) {
$output .= '<li class="success">' . $message . '</li>';
$item = array('data' => $message, 'class' => array('success'));
}
else {
$output .= '<li class="failure"><strong>' . t('Failed') . ':</strong> ' . $message . '</li>';
$item = array('data' => '<strong>' . $message . '</strong>', 'class' => array('failure'));
}
return $output;
return $item;
}
<?php
// $Id: token.inc,v 1.10 2010/10/18 01:13:07 dries Exp $
/**
* @file
* Drupal placeholder/token replacement system.
*
* Provides a set of extensible API functions for replacing placeholders in text
* with meaningful values.
* 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.
* 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].
* 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
......@@ -38,8 +37,8 @@
* 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'.
* 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()
......@@ -47,7 +46,7 @@
*/
/**
* Replace all tokens in a given string with appropriate values.
* Replaces all tokens in a given string with appropriate values.
*
* @param $text
* A string potentially containing replaceable tokens.
......@@ -55,28 +54,36 @@
* (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.
* 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
* 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.
* 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()) {
$text_tokens = token_scan($text);
if (empty($text_tokens)) {
return $text;
}
$replacements = array();
foreach (token_scan($text) as $type => $tokens) {
foreach ($text_tokens as $type => $tokens) {
$replacements += token_generate($type, $tokens, $data, $options);
if (!empty($options['clear'])) {
$replacements += array_fill_keys($tokens, '');
......@@ -96,17 +103,25 @@ function token_replace($text, array $data = array(), array $options = array()) {
}
/**
* Build a list of all token-like patterns that appear in the 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:$token]
// $type and $token may not contain white spaces.
preg_match_all('/\[([^\s\]:]*):([^\s\]]*)\]/', $text, $matches);
// 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];
......@@ -123,7 +138,7 @@ function token_scan($text) {
}
/**
* Generate replacement values for a list of tokens.
* Generates replacement values for a list of tokens.
*
* @param $type
* The type of token being replaced. 'node', 'user', and 'date' are common.
......@@ -134,20 +149,22 @@ function token_scan($text) {
* (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.
* 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
* - 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
* - 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:
......@@ -173,7 +190,7 @@ function token_generate($type, array $tokens, array $data = array(), array $opti
}
/**
* Given a list of tokens, return those that begin with a specific prefix.
* 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:
......@@ -181,7 +198,7 @@ function token_generate($type, array $tokens, array $data = array(), array $opti
* $data = array(
* 'author:name' => '[node:author:name]',
* 'title' => '[node:title]',
* 'created' => '[node:author:name]',
* 'created' => '[node:created]',
* );
* $results = token_find_with_prefix($data, 'author');
* $results == array('name' => '[node:author:name]');
......@@ -194,6 +211,7 @@ function token_generate($type, array $tokens, array $data = array(), array $opti
* @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.
......@@ -230,6 +248,7 @@ function token_find_with_prefix(array $tokens, $prefix, $delimiter = ':') {
* 'type' => 'user',
* );
* @endcode
*
* @return
* An associative array of token information, grouped by token type.
*/
......
<?php
// $Id: unicode.entities.inc,v 1.2 2009/04/26 15:14:55 dries Exp $
/**
* @file
......
<?php
// $Id: unicode.inc,v 1.47 2010/08/11 10:58:22 dries Exp $
/**
* Indicates an error during check for PHP unicode support.
......@@ -170,7 +169,7 @@ function unicode_requirements() {
* 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
* @param $data
* The XML data which will be parsed later.
*
* @return
......@@ -221,7 +220,7 @@ function drupal_xml_parser_create(&$data) {
* @param $data
* The data to be converted.
* @param $encoding
* The encoding that the data is in
* The encoding that the data is in.
*
* @return
* Converted data or FALSE.
......@@ -290,10 +289,10 @@ function drupal_truncate_bytes($string, $len) {
* non-Latin languages; see PREG_CLASS_UNICODE_WORD_BOUNDARY for more
* information. If a word boundary cannot be found that would make the length
* of the returned string fall within length guidelines (see parameters
* $max_return_length and $min_wordsafe_length), word boundaries are ignored.
* $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_return_length.
* 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
......
<?php
/**
* Do so special setup for UNL specific features.
*/
function unl_bootstrap() {
unl_bootstrap_short_hostname_redirect();
unl_bootstrap_multisite_without_symlinks();
unl_bootstrap_proxy_pass_support();
unl_bootstrap_mobile_internal_redirect();
}
/**
* Check that the hostname resolves to an IP Address.
* If it doesn't redirect to <hostname>.unl.edu.
*/
function unl_bootstrap_short_hostname_redirect() {
// Don't do a redirect when using the command line.
if (PHP_SAPI == 'cli') {
return;
}
$hostname = $_SERVER['HTTP_HOST'];
if (gethostbynamel($hostname)) {
// The provided host name is just fine.
return;
}
// Otherwise, try adding .unl.edu.
$hostname .= '.unl.edu';
if (gethostbynamel($hostname)) {
// If its a valid domain, redirect to it.
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
$uri = 'https://';
} else {
$uri = 'http://';
}
$uri .= $hostname . $_SERVER['REQUEST_URI'];
header('Location: ' . $uri);
exit;
}
}
/**
* Enable the set up of multiple sites without making symbolics links.
* Instead, a few entries in .htaccess and sites.php will be all that is needed.
*/
function unl_bootstrap_multisite_without_symlinks() {
$original_script_name = $_SERVER['SCRIPT_NAME'];
$php_file = basename($original_script_name);
......@@ -37,3 +83,91 @@ function unl_bootstrap() {
conf_path(TRUE, TRUE);
}
/**
* Fix some paths when used through a ProxyPass
*/
function unl_bootstrap_proxy_pass_support() {
if (isset($_SERVER['HTTP_X_FORWARDED_HOST']) && isset($_SERVER['HTTP_X_FORWARDED_PATH'])) {
$GLOBALS['base_url'] = 'http://' . $_SERVER['HTTP_X_FORWARDED_HOST'] . $_SERVER['HTTP_X_FORWARDED_PATH'];
$GLOBALS['cookie_domain'] = $_SERVER['HTTP_X_FORWARDED_HOST'];
$_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_FORWARDED_PATH'] . '/' . request_path();
}
}
/**
* If this site should be served with a mobile theme, do an internal redirect
* so that the page cache can store this mobile themed page separate from
* the normally themed page.
*/
function unl_bootstrap_mobile_internal_redirect() {
if (unl_bootstrap_is_mobile_user() && !isset($_GET['format'])) {
$_GET['format'] = 'mobile';
$_SERVER['QUERY_STRING'] = http_build_query($_GET);
$_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_URL'] . '?' . $_SERVER['QUERY_STRING'];
}
}
/**
* Check if the user is using a "supported" mobile user agent
*
* @return bool
*/
function unl_bootstrap_is_mobile_user() {
if (isset($_SERVER['X-UNL-Mobile'])) {
// If Varnish set the X-UNL-Mobile header, use it instead of checking again.
return $_SERVER['X-UNL-Mobile'] == 'Yes';
}
if (!isset($_SERVER['HTTP_ACCEPT'], $_SERVER['HTTP_USER_AGENT'])) {
// We have no vars to check
return false;
}
if (isset($_COOKIE['wdn_mobile'])
&& $_COOKIE['wdn_mobile'] == 'no') {
// The user has a cookie set, requesting no mobile views
return false;
}
if ( // Check the http_accept and user agent and see
preg_match('/text\/vnd\.wap\.wml|application\/vnd\.wap\.xhtml\+xml/i', $_SERVER['HTTP_ACCEPT'])
||
(preg_match('/'.
'sony|symbian|nokia|samsung|mobile|windows ce|epoc|opera mini|' .
'nitro|j2me|midp-|cldc-|netfront|mot|up\.browser|up\.link|audiovox|' .
'blackberry|ericsson,|panasonic|philips|sanyo|sharp|sie-|' .
'portalmmm|blazer|avantgo|danger|palm|series60|palmsource|pocketpc|' .
'smartphone|rover|ipaq|au-mic|alcatel|ericy|vodafone\/|wap1\.|wap2\.|iPhone|Android' .
'/i', $_SERVER['HTTP_USER_AGENT'])
) && !preg_match('/ipad/i', $_SERVER['HTTP_USER_AGENT'])) {
return true;
}
return false;
}
/**
* This will be called during update.php's bootstrap to remove any
* shared table prefixes from the database config.
* This allows the same updates to be run on all sites, even if
* they would normally be applied to the same table.
*/
function unl_bootstrap_update() {
foreach ($GLOBALS['databases'] as $key1 => $databases) {
foreach ($databases as $key2 => $database) {
if ($key2 == 'slave') {
foreach ($database as $key3 => $slave) {
if (is_array($slave['prefix'])) {
$GLOBALS['databases'][$key1][$key2][$key3]['prefix'] = $slave['prefix']['default'];
}
}
}
else {
if (is_array($database['prefix'])) {
$GLOBALS['databases'][$key1][$key2]['prefix'] = $database['prefix']['default'];
}
}
}
}
}