diff options
Diffstat (limited to 'includes')
68 files changed, 960 insertions, 759 deletions
diff --git a/includes/actions.inc b/includes/actions.inc index 0daf8e86d..c2fd4d96c 100644 --- a/includes/actions.inc +++ b/includes/actions.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/ajax.inc b/includes/ajax.inc index 0f652eff8..41c69832a 100644 --- a/includes/ajax.inc +++ b/includes/ajax.inc @@ -1,17 +1,16 @@ <?php -// $Id$ /** * @file - * Functions for use with Drupal's AJAX framework. + * Functions for use with Drupal's Ajax framework. */ /** - * @defgroup ajax AJAX framework + * @defgroup ajax Ajax framework * @{ - * Functions for Drupal's AJAX framework. + * Functions for Drupal's Ajax framework. * - * Drupal's AJAX framework is used to dynamically update parts of a page's HTML + * Drupal's Ajax framework is used to dynamically update parts of a page's HTML * based on data from the server. Upon a specified event, such as a button * click, a callback function is triggered which performs server-side logic and * may return updated markup, which is then replaced on-the-fly with no page @@ -20,33 +19,33 @@ * This framework creates a PHP macro language that allows the server to * instruct JavaScript to perform actions on the client browser. When using * forms, it can be used with the #ajax property. - * The #ajax property can be used to bind events to the AJAX framework. By + * The #ajax property can be used to bind events to the Ajax framework. By * default, #ajax uses 'system/ajax' as its path for submission and thus calls * ajax_form_callback() and a defined #ajax['callback'] function. * However, you may optionally specify a different path to request or a * different callback function to invoke, which can return updated HTML or can - * also return a richer set of @link ajax_commands AJAX framework commands @endlink. + * also return a richer set of @link ajax_commands Ajax framework commands @endlink. * * Standard form handling is as follows: * - A form element has a #ajax property that includes #ajax['callback'] and * omits #ajax['path']. See below about using #ajax['path'] to implement * advanced use-cases that require something other than standard form * handling. - * - On the specified element, AJAX processing is triggered by a change to + * - On the specified element, Ajax processing is triggered by a change to * that element. * - The browser submits an HTTP POST request to the 'system/ajax' Drupal * path. * - The menu page callback for 'system/ajax', ajax_form_callback(), calls * drupal_process_form() to process the form submission and rebuild the * form if necessary. The form is processed in much the same way as if it - * were submitted without AJAX, with the same #process functions and + * were submitted without Ajax, with the same #process functions and * validation and submission handlers called in either case, making it easy - * to create AJAX-enabled forms that degrade gracefully when JavaScript is + * to create Ajax-enabled forms that degrade gracefully when JavaScript is * disabled. * - After form processing is complete, ajax_form_callback() calls the * function named by #ajax['callback'], which returns the form element that * has been updated and needs to be returned to the browser, or - * alternatively, an array of custom AJAX commands. + * alternatively, an array of custom Ajax commands. * - The page delivery callback for 'system/ajax', ajax_deliver(), renders the * element returned by #ajax['callback'], and returns the JSON string * created by ajax_render() to the browser. @@ -57,7 +56,7 @@ * #ajax['callback'], using a JavaScript animation effect specified by * #ajax['effect']. * - * A simple example of basic AJAX use from the + * A simple example of basic Ajax use from the * @link http://drupal.org/project/examples Examples module @endlink follows: * @code * function main_page() { @@ -99,28 +98,28 @@ * } * @endcode * - * In the above example, the 'changethis' element is AJAX-enabled. The default + * In the above example, the 'changethis' element is Ajax-enabled. The default * #ajax['event'] is 'change', so when the 'changethis' element changes, - * an AJAX call is made. The form is submitted and reprocessed, and then the + * an Ajax call is made. The form is submitted and reprocessed, and then the * callback is called. In this case, the form has been automatically * built changing $form['replace_textfield']['#description'], so the callback * just returns that part of the form. * - * To implement AJAX handling in a form, add '#ajax' to the form - * definition of a field. That field will trigger an AJAX event when it is + * To implement Ajax handling in a form, add '#ajax' to the form + * definition of a field. That field will trigger an Ajax event when it is * clicked (or changed, depending on the kind of field). #ajax supports * the following parameters (either 'path' or 'callback' is required at least): * - #ajax['callback']: The callback to invoke to handle the server side of the - * AJAX event, which will receive a $form and $form_state as arguments, and + * Ajax event, which will receive a $form and $form_state as arguments, and * returns a renderable array (most often a form or form fragment), an HTML - * string, or an array of AJAX commands. If returning a renderable array or + * string, or an array of Ajax commands. If returning a renderable array or * a string, the value will replace the original element named in * #ajax['wrapper'], and * theme_status_messages() * will be prepended to that * element. (If the status messages are not wanted, return an array - * of AJAX commands instead.) - * #ajax['wrapper']. If an array of AJAX commands is returned, it will be + * of Ajax commands instead.) + * #ajax['wrapper']. If an array of Ajax commands is returned, it will be * executed by the calling code. * - #ajax['path']: The menu path to use for the request. This is often omitted * and the default is used. This path should map @@ -155,21 +154,21 @@ * More information is available in the * @link http://api.drupal.org/api/drupal/developer--topics--forms_api_reference.html/7 Form API Reference @endlink * - * In addition to using Form API for doing in-form modification, AJAX may be + * In addition to using Form API for doing in-form modification, Ajax may be * enabled by adding classes to buttons and links. By adding the 'use-ajax' - * class to a link, the link will be loaded via an AJAX call. When using this + * class to a link, the link will be loaded via an Ajax call. When using this * method, the href of the link can contain '/nojs/' as part of the path. When - * the AJAX framework makes the request, it will convert this to '/ajax/'. + * the Ajax framework makes the request, it will convert this to '/ajax/'. * The server is then able to easily tell if this request was made through an - * actual AJAX request or in a degraded state, and respond appropriately. + * actual Ajax request or in a degraded state, and respond appropriately. * * Similarly, submit buttons can be given the class 'use-ajax-submit'. The - * form will then be submitted via AJAX to the path specified in the #action. + * form will then be submitted via Ajax to the path specified in the #action. * Like the ajax-submit class above, this path will have '/nojs/' replaced with * '/ajax/' so that the submit handler can tell if the form was submitted * in a degraded state or not. * - * When responding to AJAX requests, the server should do what it needs to do + * When responding to Ajax requests, the server should do what it needs to do * for that request, then create a commands array. This commands array will * be converted to a JSON object and returned to the client, which will then * iterate over the array and process it like a macro language. @@ -189,14 +188,14 @@ * // Add a visual "changed" marker to the '#object-1' element. * $commands[] = ajax_command_changed('#object-1'); * // Menu 'page callback' and #ajax['callback'] functions are supposed to - * // return render arrays. If returning an AJAX commands array, it must be + * // return render arrays. If returning an Ajax commands array, it must be * // encapsulated in a render array structure. * return array('#type' => 'ajax', '#commands' => $commands); * @endcode * - * When returning an AJAX command array, it is often useful to have + * When returning an Ajax command array, it is often useful to have * status messages rendered along with other tasks in the command array. - * In that case the the AJAX commands array may be constructed like this: + * In that case the the Ajax commands array may be constructed like this: * @code * $commands = array(); * $commands[] = ajax_command_replace(NULL, $output); @@ -204,7 +203,7 @@ * return array('#type' => 'ajax', '#commands' => $commands); * @endcode * - * See @link ajax_commands AJAX framework commands @endlink + * See @link ajax_commands Ajax framework commands @endlink */ /** @@ -215,7 +214,7 @@ * functions. */ function ajax_render($commands = array()) { - // AJAX responses aren't rendered with html.tpl.php, so we have to call + // Ajax responses aren't rendered with html.tpl.php, so we have to call // drupal_get_css() and drupal_get_js() here, in order to have new files added // during this request to be loaded by the page. We only want to send back // files that the page hasn't already loaded, so we implement simple diffing @@ -235,7 +234,7 @@ function ajax_render($commands = array()) { // @todo Inline CSS and JS items are indexed numerically. These can't be // reliably diffed with array_diff_key(), since the number can change // due to factors unrelated to the inline content, so for now, we strip - // the inline items from AJAX responses, and can add support for them + // the inline items from Ajax responses, and can add support for them // when drupal_add_css() and drupal_add_js() are changed to using md5() // or some other hash of the inline content. foreach ($items[$type] as $key => $item) { @@ -251,14 +250,14 @@ function ajax_render($commands = array()) { // Settings are handled separately, later in this function, so that changes to // the ajaxPageState setting that occur during drupal_get_css() and // drupal_get_js() get included, and because the jQuery.extend() code produced - // by drupal_get_js() for adding settings isn't appropriate during an AJAX + // by drupal_get_js() for adding settings isn't appropriate during an Ajax // response, because it does not pass TRUE for the "deep" parameter, and // therefore, can clobber existing settings on the page. if (isset($items['js']['settings'])) { unset($items['js']['settings']); } - // Render the HTML to load these files, and add AJAX commands to insert this + // Render the HTML to load these files, and add Ajax commands to insert this // HTML in the page. We pass TRUE as the $skip_alter argument to prevent the // data from being altered again, as we already altered it above. $styles = drupal_get_css($items['css'], TRUE); @@ -287,16 +286,16 @@ function ajax_render($commands = array()) { array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $settings['data']), TRUE)); } - // Allow modules to alter any AJAX response. + // Allow modules to alter any Ajax response. drupal_alter('ajax_render', $commands); return drupal_json_encode($commands); } /** - * Get a form submitted via #ajax during an AJAX callback. + * Get a form submitted via #ajax during an Ajax callback. * - * This will load a form from the form cache used during AJAX operations. It + * This will load a form from the form cache used during Ajax operations. It * pulls the form info from $_POST. * * @return @@ -326,7 +325,7 @@ function ajax_get_form() { // Since some of the submit handlers are run, redirects need to be disabled. $form_state['no_redirect'] = TRUE; - // When a form is rebuilt after AJAX processing, its #build_id and #action + // When a form is rebuilt after Ajax processing, its #build_id and #action // should not change. // @see drupal_rebuild_form() $form_state['rebuild_info']['copy']['#build_id'] = TRUE; @@ -341,10 +340,10 @@ function ajax_get_form() { } /** - * Menu callback; handles AJAX requests for the #ajax Form API property. + * Menu callback; handles Ajax requests for the #ajax Form API property. * * This rebuilds the form from cache and invokes the defined #ajax['callback'] - * to return an AJAX command structure for JavaScript. In case no 'callback' has + * to return an Ajax command structure for JavaScript. In case no 'callback' has * been defined, nothing will happen. * * The Form API #ajax property can be set both for buttons and other input @@ -361,9 +360,9 @@ function ajax_form_callback() { // We need to return the part of the form (or some other content) that needs // to be re-rendered so the browser can update the page with changed content. - // Since this is the generic menu callback used by many AJAX elements, it is + // Since this is the generic menu callback used by many Ajax elements, it is // up to the #ajax['callback'] function of the element (may or may not be a - // button) that triggered the AJAX request to determine what needs to be + // button) that triggered the Ajax request to determine what needs to be // rendered. if (!empty($form_state['triggering_element'])) { $callback = $form_state['triggering_element']['#ajax']['callback']; @@ -374,21 +373,21 @@ function ajax_form_callback() { } /** - * Theme callback for AJAX requests. + * Theme callback for Ajax requests. * - * Many different pages can invoke an AJAX request to system/ajax or another - * generic AJAX path. It is almost always desired for an AJAX response to be + * Many different pages can invoke an Ajax request to system/ajax or another + * generic Ajax path. It is almost always desired for an Ajax response to be * rendered using the same theme as the base page, because most themes are built * with the assumption that they control the entire page, so if the CSS for two * themes are both loaded for a given page, they may conflict with each other. * For example, Bartik is Drupal's default theme, and Seven is Drupal's default * administration theme. Depending on whether the "Use the administration theme * when editing or creating content" checkbox is checked, the node edit form may - * be displayed in either theme, but the AJAX response to the Field module's + * be displayed in either theme, but the Ajax response to the Field module's * "Add another item" button should be rendered using the same theme as the rest * of the page. Therefore, system_menu() sets the 'theme callback' for * 'system/ajax' to this function, and it is recommended that modules - * implementing other generic AJAX paths do the same. + * implementing other generic Ajax paths do the same. */ function ajax_base_page_theme() { if (!empty($_POST['ajax_page_state']['theme']) && !empty($_POST['ajax_page_state']['theme_token'])) { @@ -407,9 +406,9 @@ function ajax_base_page_theme() { } /** - * Package and send the result of a page callback to the browser as an AJAX response. + * Package and send the result of a page callback to the browser as an Ajax response. * - * This function is the equivalent of drupal_deliver_html_page(), but for AJAX + * This function is the equivalent of drupal_deliver_html_page(), but for Ajax * requests. Like that function, it: * - Adds needed HTTP headers. * - Prints rendered output. @@ -425,36 +424,71 @@ function ajax_base_page_theme() { * @see drupal_deliver_html_page() */ function ajax_deliver($page_callback_result) { + // Browsers do not allow JavaScript to read the contents of a user's local + // files. To work around that, the jQuery Form plugin submits forms containing + // a file input element to an IFRAME, instead of using XHR. Browsers do not + // normally expect JSON strings as content within an IFRAME, so the response + // must be customized accordingly. + // @see http://malsup.com/jquery/form/#file-upload + // @see Drupal.ajax.prototype.beforeSend() + $iframe_upload = !empty($_POST['ajax_iframe_upload']); + // Emit a Content-Type HTTP header if none has been added by the page callback // or by a wrapping delivery callback. if (is_null(drupal_get_http_header('Content-Type'))) { - // The standard header for JSON is application/json. - // @see http://www.ietf.org/rfc/rfc4627.txt?number=4627 - // However, browsers do not allow JavaScript to read the contents of a - // user's local files. To work around that, jQuery submits forms containing - // a file input element to an IFRAME, instead of using XHR. - // @see http://malsup.com/jquery/form/#file-upload - // When Internet Explorer receives application/json content in an IFRAME, it - // treats it as a file download and prompts the user to save it. To prevent - // that, we return the content as text/plain. But only for POST requests, - // since jQuery should always use XHR for GET requests and the incorrect - // mime type should not end up in page or proxy server caches. - // @see http://drupal.org/node/995854 - $iframe_upload = !isset($_SERVER['HTTP_X_REQUESTED_WITH']) || $_SERVER['HTTP_X_REQUESTED_WITH'] != 'XMLHttpRequest'; - if ($iframe_upload && $_SERVER['REQUEST_METHOD'] == 'POST') { - drupal_add_http_header('Content-Type', 'text/plain; charset=utf-8'); + if (!$iframe_upload) { + // Standard JSON can be returned to a browser's XHR object, and to + // non-browser user agents. + // @see http://www.ietf.org/rfc/rfc4627.txt?number=4627 + drupal_add_http_header('Content-Type', 'application/json; charset=utf-8'); } else { - drupal_add_http_header('Content-Type', 'application/json; charset=utf-8'); + // Browser IFRAMEs expect HTML. With most other content types, Internet + // Explorer presents the user with a download prompt. + drupal_add_http_header('Content-Type', 'text/html; charset=utf-8'); } } - // Normalize whatever was returned by the page callback to an AJAX commands - // array. + // Print the response. + $commands = ajax_prepare_response($page_callback_result); + $json = ajax_render($commands); + if (!$iframe_upload) { + // Standard JSON can be returned to a browser's XHR object, and to + // non-browser user agents. + print $json; + } + else { + // Browser IFRAMEs expect HTML. Browser extensions, such as Linkification + // and Skype's Browser Highlighter, convert URLs, phone numbers, etc. into + // links. This corrupts the JSON response. Protect the integrity of the + // JSON data by making it the value of a textarea. + // @see http://malsup.com/jquery/form/#file-upload + // @see http://drupal.org/node/1009382 + print '<textarea>' . $json . '</textarea>'; + } + + // Perform end-of-request tasks. + ajax_footer(); +} + +/** + * Converts the return value of a page callback into an Ajax commands array. + * + * @param $page_callback_result + * The result of a page callback. Can be one of: + * - NULL: to indicate no content. + * - An integer menu status constant: to indicate an error condition. + * - A string of HTML content. + * - A renderable array of content. + * + * @return + * An Ajax commands array that can be passed to ajax_render(). + */ +function ajax_prepare_response($page_callback_result) { $commands = array(); if (!isset($page_callback_result)) { // Simply delivering an empty commands array is sufficient. This results - // in the AJAX request being completed, but nothing being done to the page. + // in the Ajax request being completed, but nothing being done to the page. } elseif (is_int($page_callback_result)) { switch ($page_callback_result) { @@ -473,7 +507,7 @@ function ajax_deliver($page_callback_result) { } } elseif (is_array($page_callback_result) && isset($page_callback_result['#type']) && ($page_callback_result['#type'] == 'ajax')) { - // Complex AJAX callbacks can return a result that contains an error message + // Complex Ajax callbacks can return a result that contains an error message // or a specific set of commands to send to the browser. $page_callback_result += element_info('ajax'); $error = $page_callback_result['#error']; @@ -488,7 +522,7 @@ function ajax_deliver($page_callback_result) { } } else { - // Like normal page callbacks, simple AJAX callbacks can return HTML + // Like normal page callbacks, simple Ajax callbacks can return HTML // content, as a string or render array. This HTML is inserted in some // relationship to #ajax['wrapper'], as determined by which jQuery DOM // manipulation method is used. The method used is specified by @@ -497,28 +531,24 @@ function ajax_deliver($page_callback_result) { $html = is_string($page_callback_result) ? $page_callback_result : drupal_render($page_callback_result); $commands[] = ajax_command_insert(NULL, $html); // Add the status messages inside the new content's wrapper element, so that - // on subsequent AJAX requests, it is treated as old content. + // on subsequent Ajax requests, it is treated as old content. $commands[] = ajax_command_prepend(NULL, theme('status_messages')); } - // Unlike the recommendation in http://malsup.com/jquery/form/#file-upload, - // we do not have to wrap the JSON string in a TEXTAREA, because - // drupal_json_encode() returns an HTML-safe JSON string. - print ajax_render($commands); - ajax_footer(); + return $commands; } /** - * Perform end-of-AJAX-request tasks. + * Perform end-of-Ajax-request tasks. * - * This function is the equivalent of drupal_page_footer(), but for AJAX + * This function is the equivalent of drupal_page_footer(), but for Ajax * requests. * * @see drupal_page_footer() */ function ajax_footer() { - // Even for AJAX requests, invoke hook_exit() implementations. There may be - // modules that need very fast AJAX responses, and therefore, run AJAX + // Even for Ajax requests, invoke hook_exit() implementations. There may be + // modules that need very fast Ajax responses, and therefore, run Ajax // requests with an early bootstrap. if (drupal_get_bootstrap_phase() == DRUPAL_BOOTSTRAP_FULL && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')) { module_invoke_all('exit'); @@ -551,10 +581,10 @@ function ajax_process_form($element, &$form_state) { } /** - * Add AJAX information about an element to the page to communicate with JavaScript. + * Add Ajax information about an element to the page to communicate with JavaScript. * * If #ajax['path'] is set on an element, this additional JavaScript is added - * to the page header to attach the AJAX behaviors. See ajax.js for more + * to the page header to attach the Ajax behaviors. See ajax.js for more * information. * * @param $element @@ -591,9 +621,9 @@ function ajax_pre_render_element($element) { case 'image_button': // Use the mousedown instead of the click event because form // submission via pressing the enter key triggers a click event on - // submit inputs, inappropriately triggering AJAX behaviors. + // submit inputs, inappropriately triggering Ajax behaviors. $element['#ajax']['event'] = 'mousedown'; - // Attach an additional event handler so that AJAX behaviors + // Attach an additional event handler so that Ajax behaviors // can be triggered still via keyboard input. $element['#ajax']['keypress'] = TRUE; break; @@ -642,7 +672,7 @@ function ajax_pre_render_element($element) { unset($settings['path'], $settings['options']); // Add special data to $settings['submit'] so that when this element - // triggers an AJAX submission, Drupal's form processing can determine which + // triggers an Ajax submission, Drupal's form processing can determine which // element triggered it. // @see _form_element_triggered_scripted_submission() if (isset($settings['trigger_as'])) { @@ -685,7 +715,7 @@ function ajax_pre_render_element($element) { 'data' => array('ajax' => array($element['#id'] => $settings)), ); - // Indicate that AJAX processing was successful. + // Indicate that Ajax processing was successful. $element['#ajax_processed'] = TRUE; } return $element; @@ -696,16 +726,16 @@ function ajax_pre_render_element($element) { */ /** - * @defgroup ajax_commands AJAX framework commands + * @defgroup ajax_commands Ajax framework commands * @{ - * Functions to create various AJAX commands. + * Functions to create various Ajax commands. * * These functions can be used to create arrays for use with the * ajax_render() function. */ /** - * Creates a Drupal AJAX 'alert' command. + * Creates a Drupal Ajax 'alert' command. * * The 'alert' command instructs the client to display a JavaScript alert * dialog box. @@ -727,7 +757,7 @@ function ajax_command_alert($text) { } /** - * Creates a Drupal AJAX 'insert' command using the method in #ajax['method']. + * Creates a Drupal Ajax 'insert' command using the method in #ajax['method']. * * This command instructs the client to insert the given HTML using whichever * jQuery DOM manipulation method has been specified in the #ajax['method'] @@ -758,7 +788,7 @@ function ajax_command_insert($selector, $html, $settings = NULL) { } /** - * Creates a Drupal AJAX 'insert/replaceWith' command. + * Creates a Drupal Ajax 'insert/replaceWith' command. * * The 'insert/replaceWith' command instructs the client to use jQuery's * replaceWith() method to replace each element matched matched by the given @@ -791,7 +821,7 @@ function ajax_command_replace($selector, $html, $settings = NULL) { } /** - * Creates a Drupal AJAX 'insert/html' command. + * Creates a Drupal Ajax 'insert/html' command. * * The 'insert/html' command instructs the client to use jQuery's html() * method to set the HTML content of each element matched by the given @@ -824,7 +854,7 @@ function ajax_command_html($selector, $html, $settings = NULL) { } /** - * Creates a Drupal AJAX 'insert/prepend' command. + * Creates a Drupal Ajax 'insert/prepend' command. * * The 'insert/prepend' command instructs the client to use jQuery's prepend() * method to prepend the given HTML content to the inside each element matched @@ -857,10 +887,10 @@ function ajax_command_prepend($selector, $html, $settings = NULL) { } /** - * Creates a Drupal AJAX 'insert/append' command. + * Creates a Drupal Ajax 'insert/append' command. * * The 'insert/append' command instructs the client to use jQuery's append() - * method to append the given HTML content to the inside each element matched + * method to append the given HTML content to the inside of each element matched * by the given selector. * * This command is implemented by Drupal.ajax.prototype.commands.insert() @@ -890,7 +920,7 @@ function ajax_command_append($selector, $html, $settings = NULL) { } /** - * Creates a Drupal AJAX 'insert/after' command. + * Creates a Drupal Ajax 'insert/after' command. * * The 'insert/after' command instructs the client to use jQuery's after() * method to insert the given HTML content after each element matched by @@ -923,7 +953,7 @@ function ajax_command_after($selector, $html, $settings = NULL) { } /** - * Creates a Drupal AJAX 'insert/before' command. + * Creates a Drupal Ajax 'insert/before' command. * * The 'insert/before' command instructs the client to use jQuery's before() * method to insert the given HTML content before each of elements matched by @@ -956,7 +986,7 @@ function ajax_command_before($selector, $html, $settings = NULL) { } /** - * Creates a Drupal AJAX 'remove' command. + * Creates a Drupal Ajax 'remove' command. * * The 'remove' command instructs the client to use jQuery's remove() method * to remove each of elements matched by the given selector, and everything @@ -982,7 +1012,7 @@ function ajax_command_remove($selector) { } /** - * Creates a Drupal AJAX 'changed' command. + * Creates a Drupal Ajax 'changed' command. * * This command instructs the client to mark each of the elements matched by the * given selector as 'ajax-changed'. @@ -1009,7 +1039,7 @@ function ajax_command_changed($selector, $asterisk = '') { } /** - * Creates a Drupal AJAX 'css' command. + * Creates a Drupal Ajax 'css' command. * * The 'css' command will instruct the client to use the jQuery css() method * to apply the CSS arguments to elements matched by the given selector. @@ -1037,7 +1067,7 @@ function ajax_command_css($selector, $argument) { } /** - * Creates a Drupal AJAX 'settings' command. + * Creates a Drupal Ajax 'settings' command. * * The 'settings' command instructs the client either to use the given array as * the settings for ajax-loaded content or to extend Drupal.settings with the @@ -1068,7 +1098,7 @@ function ajax_command_settings($argument, $merge = FALSE) { } /** - * Creates a Drupal AJAX 'data' command. + * Creates a Drupal Ajax 'data' command. * * The 'data' command instructs the client to attach the name=value pair of * data to the selector via jQuery's data cache. @@ -1100,7 +1130,7 @@ function ajax_command_data($selector, $name, $value) { } /** - * Creates a Drupal AJAX 'invoke' command. + * Creates a Drupal Ajax 'invoke' command. * * The 'invoke' command will instruct the client to invoke the given jQuery * method with the supplied arguments on the elements matched by the given @@ -1131,7 +1161,7 @@ function ajax_command_invoke($selector, $method, array $arguments = array()) { } /** - * Creates a Drupal AJAX 'restripe' command. + * Creates a Drupal Ajax 'restripe' command. * * The 'restripe' command instructs the client to restripe a table. This is * usually used after a table has been modified by a replace or append command. diff --git a/includes/archiver.inc b/includes/archiver.inc index 5e6ef323f..fec053be6 100644 --- a/includes/archiver.inc +++ b/includes/archiver.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/authorize.inc b/includes/authorize.inc index a1e8a7bdb..3617d7d03 100644 --- a/includes/authorize.inc +++ b/includes/authorize.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -160,7 +159,7 @@ function _authorize_filetransfer_connection_settings($backend) { * Therefore, to properly add defaults, we need to walk through all the * children form elements and process those defaults recursively. * - * @param &$element + * @param $element * Reference to the Form API form element we're operating on. * @param $key * The key for our current form element, if any. diff --git a/includes/batch.inc b/includes/batch.inc index 1510a62e8..7011abfbd 100644 --- a/includes/batch.inc +++ b/includes/batch.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** diff --git a/includes/batch.queue.inc b/includes/batch.queue.inc index ee721463f..846483698 100644 --- a/includes/batch.queue.inc +++ b/includes/batch.queue.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index fb73528ff..b70149cd3 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -9,7 +8,7 @@ /** * The current system version. */ -define('VERSION', '7.1'); +define('VERSION', '7.2'); /** * Core API compatibility. @@ -1222,185 +1221,64 @@ function drupal_unpack($obj, $field = 'data') { /** * Translates a string to the current language or to a given language. * - * All human-readable text that will be displayed on the site or sent to a user - * should be passed through the t() function. This ensures that sites can be - * fully translated into other languages. - * - * Here are some examples of translating static text using t(): - * @code - * if (!$info || !$info['extension']) { - * form_set_error('picture_upload', t('The uploaded file was not an image.')); - * } - * - * $form['submit'] = array( - * '#type' => 'submit', - * '#value' => t('Log in'), - * ); - * @endcode - * - * In addition to translating static text, t() can handle text that should not - * be translated or that might change from time to time (such as link paths) - * and dynamic text from variables, using special "placeholders". There are - * three styles of placeholders: - * - !variable: Indicates that the text should be inserted as-is. This is - * useful for inserting variables into things like e-mail. Example: - * @code - * $message[] = t("If you don't want to receive such e-mails, you can change your settings at !url.", array('!url' => url("user/$account->uid", array('absolute' => TRUE)))); - * @endcode - * - @variable: Indicates that the text should be run through check_plain(), to - * escape HTML characters. Use this for any output that is displayed within a - * Drupal page. Example: - * @code - * drupal_set_title($title = t("@name's blog", array('@name' => format_username($account))), PASS_THROUGH); - * @endcode - * - %variable: Indicates that the string should be HTML-escaped and highlighted - * with drupal_placeholder(), which shows up as <em>emphasized</em>. - * @code - * $message = t('%name-from sent %name-to an e-mail.', array('%name-from' => format_username($user), '%name-to' => format_username($account))); - * @endcode - * - * When using t(), try to put entire paragraphs in one t() call. This makes it - * easier for translators, as it provides context as to what each word refers - * to (and also allows translators to adjust word order, which may not be the - * same in all languages). HTML markup within translation strings is allowed, - * but should be avoided if possible. The exception is embedded links: link - * titles add context for translators and need to be translated, so they should - * be kept in the main string, while link URLs should be generated using - * placeholders. - * - Incorrect HTML in t(): - * @code - * $output .= t('<p>Go to the @contact-page.</p>', array('@contact-page' => l(t('contact page'), 'contact'))); - * @endcode - * - Correct HTML in t(): - * @code - * $output .= '<p>' . t('Go to the <a href="@contact-page">contact page</a>.', array('@contact-page' => url('contact'))) . '</p>'; - * @endcode - * - * Another thing that is helpful is to avoid escaping quotation marks wherever - * possible, because it can be confusing to translation teams. - * - Less desirable quotation mark escaping: - * @code - * $output .= t('Don\'t click me.'); - * @endcode - * - Better way to use quotation marks: - * @code - * $output .= t("Don't click me."); - * @endcode - * - * It is important that all translation uses the t() mechanism, because in - * addition to actually translating the text at run-time, the t() function is - * also used by text-extraction routines to find text that needs to be - * translated, and build databases of text to be translated for translation - * teams. For that reason, you must put the actual string into the t() function, - * in most cases, and not a variable. - * - Incorrect use of a variable in t(): - * @code - * $message = 'An error occurred.'; - * drupal_set_message(t($message), 'error'); - * $output .= t($message); - * @endcode - * - Correct translation of a variable with t(): - * @code - * $message = t('An error occurred.'); - * drupal_set_message($message, 'error'); - * $output .= $message; - * @endcode - * - * The only case in which variables can be passed safely through t() is when - * code-based versions of the same strings will be passed through t() (or - * otherwise extracted) elsewhere. - * - * Also, you cannot use t() early in the bootstrap process, prior to the - * DRUPAL_BOOTSTRAP_LANGUAGE phase. The language variables will not be - * initialized yet, so the string will not be translated into the correct - * language. Examples of places where t() cannot be used include: - * - In a PHP define() statement. - * - In a hook_boot() implementation. - * - * In some cases, modules may include strings in code that can't use t() - * calls. For example, a module may use an external PHP application that - * produces strings that are loaded into variables in Drupal for output. - * In these cases, module authors may include a dummy file that passes the - * relevant strings through t(). This approach will allow the strings to be - * extracted. - * - * Sample external (non-Drupal) code: - * @code - * class Time { - * public $yesterday = 'Yesterday'; - * public $today = 'Today'; - * public $tomorrow = 'Tomorrow'; - * } - * @endcode - * - * Sample dummy file: - * @code - * // Dummy function included in example.potx.inc. - * function example_potx() { - * $strings = array( - * t('Yesterday'), - * t('Today'), - * t('Tomorrow'), - * ); - * // No return value needed, since this is a dummy function. - * } - * @endcode - * - * Having passed strings through t() in a dummy function, it is then - * possible to pass variables through t(): - * @code - * $time = new Time(); - * $output .= t($time->today); - * @endcode - * - * However tempting it is, custom data from user input or other non-code - * sources should not be passed through t(). Doing so leads to the following - * problems and errors: - * - The t() system doesn't support updates to existing strings. When user - * data is updated, the next time it's passed through t(), a new record is - * created instead of an update. The database bloats over time and any - * existing translations are orphaned with each update. - * - The t() system assumes any data it receives is in English. User data may - * be in another language, producing translation errors. - * - The "Built-in interface" text group in the locale system is used to - * produce translations for storage in .po files. When non-code strings are - * passed through t(), they are added to this text group, which is rendered - * inaccurate since it is a mix of actual interface strings and various user - * input strings of uncertain origin. - * Instead, translation of these data can be done through the locale system, - * either directly through hook_local() or through helper functions provided by - * contributed modules. - * - * Incorrect: + * The t() function serves two purposes. First, at run-time it translates + * user-visible text into the appropriate language. Second, various mechanisms + * that figure out what text needs to be translated work off t() -- the text + * inside t() calls is added to the database of strings to be translated. So, + * to enable a fully-translatable site, it is important that all human-readable + * text that will be displayed on the site or sent to a user is passed through + * the t() function, or a related function. See the + * @link http://drupal.org/node/322729 Localization API @endlink pages for + * more information, including recommendations on how to break up or not + * break up strings for translation. + * + * You should never use t() to translate variables, such as calling + * @code t($text); @endcode, unless the text that the variable holds has been + * passed through t() elsewhere (e.g., $text is one of several translated + * literal strings in an array). It is especially important never to call + * @code t($user_text); @endcode, where $user_text is some text that a user + * entered - doing that can lead to cross-site scripting and other security + * problems. However, you can use variable substitution in your string, to put + * variable text such as user names or link URLs into translated text. Variable + * substitution looks like this: * @code - * $item = item_load(); - * $output .= check_plain(t($item['title'])); + * $text = t("@name's blog", array('@name' => format_username($account))); * @endcode + * Basically, you can put variables like @name into your string, and t() will + * substitute their sanitized values at translation time (see $args below or + * the Localization API pages referenced above for details). Translators can + * then rearrange the string as necessary for the language (e.g., in Spanish, + * it might be "blog de @name"). * - * During installation, st() is used in place of t(). Code that may be called - * during installation or during normal operation should use the get_t() - * helper function. + * During the Drupal installation phase, some resources used by t() wil not be + * available to code that needs localization. See st() and get_t() for + * alternatives. * * @param $string * A string containing the English string to translate. * @param $args - * An associative array of replacements to make after translation. Incidences - * of any key in this array are replaced with the corresponding value. Based - * on the first character of the key, the value is escaped and/or themed: - * - !variable: inserted as is - * - @variable: escape plain text to HTML (using check_plain()) - * - %variable: escape text and theme as a placeholder for user-submitted - * content (using check_plain() + drupal_placeholder()) + * An associative array of replacements to make after translation. + * Occurrences in $string of any key in $args are replaced with the + * corresponding value, after sanitization. The sanitization function depends + * on the first character of the key: + * - !variable: Inserted as is. Use this for text that has already been + * sanitized. + * - @variable: Escaped to HTML using check_plain(). Use this for anything + * displayed on a page on the site. + * - %variable: Escaped as a placeholder for user-submitted content using + * drupal_placeholder(), which shows up as <em>emphasized</em> text. * @param $options - * An associative array of additional options, with the following keys: - * - 'langcode' (defaults to the current language) The language code to - * translate to a language other than what is used to display the page. - * - 'context' (defaults to the empty context) The context the source string - * belongs to. + * An associative array of additional options, with the following elements: + * - 'langcode' (defaults to the current language): The language code to + * translate to a language other than what is used to display the page. + * - 'context' (defaults to the empty context): The context the source string + * belongs to. * * @return * The translated string. * + * @see st() + * @see get_t() * @ingroup sanitization */ function t($string, array $args = array(), array $options = array()) { @@ -1547,7 +1425,7 @@ function request_uri() { * The exception that is going to be logged. * @param $message * The message to store in the log. If empty, a text that contains all useful - * information about the passed in exception is used. + * information about the passed-in exception is used. * @param $variables * Array of variables to replace in the message on display. Defaults to the * return value of drupal_decode_exception(). @@ -1643,8 +1521,8 @@ function watchdog($type, $message, $variables = array(), $severity = WATCHDOG_NO * messages without clearing them. * * @param $message - * The message should begin with a capital letter and always ends with a - * period '.'. + * The message to be displayed to the user. For consistency with other + * messages, it should begin with a capital letter and end with a period. * @param $type * The type of the message. One of the following values are possible: * - 'status' @@ -2363,8 +2241,30 @@ function drupal_installation_attempted() { } /** - * Return the name of the localization function. Use in code that needs to - * run both during installation and normal operation. + * Returns the name of the proper localization function. + * + * get_t() exists to support localization for code that might run during + * the installation phase, when some elements of the system might not have + * loaded. + * + * This would include implementations of hook_install(), which could run + * during the Drupal installation phase, and might also be run during + * non-installation time, such as while installing the module from the the + * module administration page. + * + * Example useage: + * @code + * $t = get_t(); + * $translated = $t('translate this'); + * @endcode + * + * Use t() if your code will never run during the Drupal installation phase. + * Use st() if your code will only run during installation and never any other + * time. Use get_t() if your code could run in either circumstance. + * + * @see t() + * @see st() + * @ingroup sanitization */ function get_t() { static $t; @@ -2420,6 +2320,9 @@ function drupal_language_types() { * Return true if there is more than one language enabled. */ function drupal_multilingual() { + // The "language_count" variable stores the number of enabled languages to + // avoid unnecessarily querying the database when building the list of + // enabled languages on monolingual sites. return variable_get('language_count', 1) > 1; } @@ -2491,6 +2394,8 @@ function language_default($property = NULL) { * base_path() returns "/drupalfolder/". * - http://example.com/path/alias (which is a path alias for node/306) returns * "path/alias" as opposed to the internal path. + * - http://example.com/index.php returns an empty string (meaning: front page). + * - http://example.com/index.php?page=1 returns an empty string. * * @return * The requested Drupal URL path. @@ -2512,11 +2417,19 @@ function request_path() { $path = $_GET['q']; } elseif (isset($_SERVER['REQUEST_URI'])) { - // This is a request using a clean URL. Extract the path from REQUEST_URI. + // This request is either a clean URL, or 'index.php', or nonsense. + // Extract the path from REQUEST_URI. $request_path = strtok($_SERVER['REQUEST_URI'], '?'); $base_path_len = strlen(rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/')); // Unescape and strip $base_path prefix, leaving q without a leading slash. $path = substr(urldecode($request_path), $base_path_len + 1); + // If the path equals the script filename, either because 'index.php' was + // explicitly provided in the URL, or because the server added it to + // $_SERVER['REQUEST_URI'] even when it wasn't provided in the URL (some + // versions of Microsoft IIS do this), the front page should be served. + if ($path == basename($_SERVER['PHP_SELF'])) { + $path = ''; + } } else { // This is the front page. diff --git a/includes/cache-install.inc b/includes/cache-install.inc index 545f1a2a2..d9bb0f92e 100644 --- a/includes/cache-install.inc +++ b/includes/cache-install.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -31,7 +30,7 @@ class DrupalFakeCache extends DrupalDatabaseCache implements DrupalCacheInterfac // If there is a database cache, attempt to clear it whenever possible. The // reason for doing this is that the database cache can accumulate data // during installation due to any full bootstraps that may occur at the - // same time (for example, AJAX requests triggered by the installer). If we + // same time (for example, Ajax requests triggered by the installer). If we // didn't try to clear it whenever this function is called, the data in the // cache would become stale; for example, the installer sometimes calls // variable_set(), which updates the {variable} table and then clears the diff --git a/includes/cache.inc b/includes/cache.inc index 21630617d..8666874ac 100644 --- a/includes/cache.inc +++ b/includes/cache.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * Get the cache object for a cache bin. @@ -325,10 +324,15 @@ class DrupalDatabaseCache implements DrupalCacheInterface { try { // Garbage collection necessary when enforcing a minimum cache lifetime. $this->garbageCollection($this->bin); - $query = db_select($this->bin); - $query->fields($this->bin, array('cid', 'data', 'created', 'expire', 'serialized')); - $query->condition($this->bin . '.cid', $cids, 'IN'); - $result = $query->execute(); + + // When serving cached pages, the overhead of using db_select() was found + // to add around 30% overhead to the request. Since $this->bin is a + // variable, this means the call to db_query() here uses a concatenated + // string. This is highly discouraged under any other circumstances, and + // is used here only due to the performance overhead we would incur + // otherwise. When serving an uncached page, the overhead of using + // db_select() is a much smaller proportion of the request. + $result = db_query('SELECT cid, data, created, expire, serialized FROM {' . db_escape_table($this->bin) . '} WHERE cid IN (:cids)', array(':cids' => $cids)); $cache = array(); foreach ($result as $item) { $item = $this->prepareItem($item); diff --git a/includes/common.inc b/includes/common.inc index 03b0de485..5dadb4d16 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -915,7 +914,9 @@ function drupal_http_request($url, array $options = array()) { return $result; } // Parse response headers from the response body. - list($response, $result->data) = explode("\r\n\r\n", $response, 2); + // Be tolerant of malformed HTTP responses that separate header and body with + // \n\n or \r\r instead of \r\n\r\n. + list($response, $result->data) = preg_split("/\r\n\r\n|\n\n|\r\r/", $response, 2); $response = preg_split("/\r\n|\n|\r/", $response); // Parse the response status line. @@ -1050,15 +1051,15 @@ function _fix_gpc_magic_files(&$item, $key) { * Fix double-escaping problems caused by "magic quotes" in some PHP installations. */ function fix_gpc_magic() { - $fixed = &drupal_static(__FUNCTION__, FALSE); + static $fixed = FALSE; if (!$fixed && ini_get('magic_quotes_gpc')) { array_walk($_GET, '_fix_gpc_magic'); array_walk($_POST, '_fix_gpc_magic'); array_walk($_COOKIE, '_fix_gpc_magic'); array_walk($_REQUEST, '_fix_gpc_magic'); array_walk($_FILES, '_fix_gpc_magic_files'); - $fixed = TRUE; } + $fixed = TRUE; } /** @@ -1974,7 +1975,7 @@ function _format_date_callback(array $matches = NULL, $new_langcode = NULL) { /** * Format a username. * - * By default, the passed in object's 'name' property is used if it exists, or + * By default, the passed-in object's 'name' property is used if it exists, or * else, the site-defined value for the 'anonymous' variable. However, a module * may override this by implementing hook_username_alter(&$name, $account). * @@ -2238,7 +2239,7 @@ function drupal_http_header_attributes(array $attributes = array()) { * drupal_attributes(array('title' => t('<script>steal_cookie();</script>'))); * * // The statement below demonstrates dangerous use of drupal_attributes, and - * // will return an onmouseout attribute with javascript code that, when used + * // will return an onmouseout attribute with JavaScript code that, when used * // as attribute in a tag, will cause users to be redirected to another site. * // * // In this case, the 'onmouseout' attribute should not be whitelisted -- @@ -2383,9 +2384,9 @@ function l($text, $path, array $options = array()) { * basis in hook_page_delivery_callback_alter(). * * For example, the same page callback function can be used for an HTML - * version of the page and an AJAX version of the page. The page callback + * version of the page and an Ajax version of the page. The page callback * function just needs to decide what content is to be returned and the - * delivery callback function will send it as an HTML page or an AJAX + * delivery callback function will send it as an HTML page or an Ajax * response, as appropriate. * * In order for page callbacks to be reusable in different delivery formats, @@ -2602,7 +2603,8 @@ function drupal_exit($destination = NULL) { * A linear array. * @param $function * A name of a function to apply to all values before output. - * @result + * + * @return * An associative array. */ function drupal_map_assoc($array, $function = NULL) { @@ -3543,7 +3545,7 @@ function drupal_load_stylesheet_content($contents, $optimize = FALSE) { // There are different conditions for removing leading and trailing // whitespace. // @see http://php.net/manual/en/regexp.reference.subpatterns.php - $contents = preg_replace_callback('< + $contents = preg_replace('< # Strip leading and trailing whitespace. \s*([@{};,])\s* # Strip only leading whitespace from: @@ -3554,7 +3556,10 @@ function drupal_load_stylesheet_content($contents, $optimize = FALSE) { # - Colon: Retain :pseudo-selectors. | ([\(:])\s+ >xS', - '_drupal_load_stylesheet_content', + // Only one of the three capturing groups will match, so its reference + // will contain the wanted value and the references for the + // two non-matching groups will be replaced with empty strings. + '$1$2$3', $contents ); // End the file with a new line. @@ -3569,16 +3574,6 @@ function drupal_load_stylesheet_content($contents, $optimize = FALSE) { } /** - * Helper for drupal_load_stylesheet_content(). - */ -function _drupal_load_stylesheet_content($matches) { - // Discard the full match. - unset($matches[0]); - // Use the non-empty match. - return current(array_filter($matches)); -} - -/** * Loads stylesheets recursively and returns contents with corrected paths. * * This function is used for recursive loading of stylesheets and @@ -3672,17 +3667,17 @@ function drupal_html_class($class) { * blocks, and other content to be output multiple times on the same page, * without breaking (X)HTML validation. * - * For already existing ids, a counter is appended to the id string. Therefore, + * For already existing IDs, a counter is appended to the ID string. Therefore, * JavaScript and CSS code should not rely on any value that was generated by * this function and instead should rely on manually added CSS classes or * similarly reliable constructs. * - * Two consecutive hyphens separate the counter from the original id. To manage - * uniqueness across multiple AJAX requests on the same page, AJAX requests + * Two consecutive hyphens separate the counter from the original ID. To manage + * uniqueness across multiple Ajax requests on the same page, Ajax requests * POST an array of all IDs currently present on the page, which are used to * prime this function's cache upon first invocation. * - * To allow reverse-parsing of ids submitted via AJAX, any multiple consecutive + * To allow reverse-parsing of IDs submitted via Ajax, any multiple consecutive * hyphens in the originally passed $id are replaced with a single hyphen. * * @param $id @@ -3692,10 +3687,10 @@ function drupal_html_class($class) { * The cleaned ID. */ function drupal_html_id($id) { - // If this is an AJAX request, then content returned by this page request will - // be merged with content already on the base page. The HTML ids must be + // If this is an Ajax request, then content returned by this page request will + // be merged with content already on the base page. The HTML IDs must be // unique for the fully merged content. Therefore, initialize $seen_ids to - // take into account ids that are already in use on the base page. + // take into account IDs that are already in use on the base page. $seen_ids_init = &drupal_static(__FUNCTION__ . ':init'); if (!isset($seen_ids_init)) { // Ideally, Drupal would provide an API to persist state information about @@ -3703,7 +3698,7 @@ function drupal_html_id($id) { // function's $seen_ids static variable to that state information in order // to have it properly initialized for this page request. However, no such // page state API exists, so instead, ajax.js adds all of the in-use HTML - // ids to the POST data of AJAX submissions. Direct use of $_POST is + // IDs to the POST data of Ajax submissions. Direct use of $_POST is // normally not recommended as it could open up security risks, but because // the raw POST data is cast to a number before being returned by this // function, this usage is safe. @@ -3750,7 +3745,7 @@ function drupal_html_id($id) { // The counter needs to be appended with a delimiter that does not exist in // the base ID. Requiring a unique delimiter helps ensure that we really do // return unique IDs and also helps us re-create the $seen_ids array during - // AJAX requests. + // Ajax requests. if (isset($seen_ids[$id])) { $id = $id . '--' . ++$seen_ids[$id]; } @@ -3768,7 +3763,7 @@ function drupal_html_id($id) { * page region that is output by the theme (Drupal core already handles this in * the standard template preprocess implementation). Standardizing the class * names in this way allows modules to implement certain features, such as - * drag-and-drop or dynamic AJAX loading, in a theme-independent way. + * drag-and-drop or dynamic Ajax loading, in a theme-independent way. * * @param $region * The name of the page region (for example, 'page_top' or 'content'). @@ -3953,12 +3948,17 @@ function drupal_add_js($data = NULL, $options = NULL) { if (isset($data)) { // Add jquery.js and drupal.js, as well as the basePath setting, the - // first time a Javascript file is added. + // first time a JavaScript file is added. if (empty($javascript)) { + // url() generates the prefix using hook_url_outbound_alter(). Instead of + // running the hook_url_outbound_alter() again here, extract the prefix + // from url(). + url('', array('prefix' => &$prefix)); $javascript = array( 'settings' => array( 'data' => array( array('basePath' => base_path()), + array('pathPrefix' => empty($prefix) ? '' : $prefix), ), 'type' => 'setting', 'scope' => 'header', @@ -3996,7 +3996,7 @@ function drupal_add_js($data = NULL, $options = NULL) { default: // 'file' and 'external' // Local and external files must keep their name as the associative key - // so the same JavaScript file is not be added twice. + // so the same JavaScript file is not added twice. $javascript[$options['data']] = $options; } } @@ -4094,13 +4094,13 @@ function drupal_get_js($scope = 'header', $javascript = NULL, $skip_alter = FALS // page request. $default_query_string = variable_get('css_js_query_string', '0'); - // For inline Javascript to validate as XHTML, all Javascript containing + // For inline JavaScript to validate as XHTML, all JavaScript containing // XHTML needs to be wrapped in CDATA. To make that backwards compatible // with HTML 4, we need to comment out the CDATA-tag. $embed_prefix = "\n<!--//--><![CDATA[//><!--\n"; $embed_suffix = "\n//--><!]]>\n"; - // Since Javascript may look for arguments in the url and act on them, some + // Since JavaScript may look for arguments in the URL and act on them, some // third-party code might require the use of a different query string. $js_version_string = variable_get('drupal_js_version_query_string', 'v='); @@ -4318,7 +4318,7 @@ function drupal_process_attached($elements, $group = JS_DEFAULT, $dependency_che } // Add additional types of attachments specified in the render() structure. - // Libraries, Javascript and CSS have been added already, as they require + // Libraries, JavaScript and CSS have been added already, as they require // special handling. foreach ($elements['#attached'] as $callback => $options) { if (function_exists($callback)) { @@ -4785,7 +4785,7 @@ function drupal_clear_js_cache() { } /** - * Converts a PHP variable into its Javascript equivalent. + * Converts a PHP variable into its JavaScript equivalent. * * We use HTML-safe strings, i.e. with <, > and & escaped. * @@ -4881,12 +4881,12 @@ function drupal_valid_token($token, $value = '', $skip_anonymous = FALSE) { } function _drupal_bootstrap_full() { - $called = &drupal_static(__FUNCTION__); + static $called = FALSE; if ($called) { return; } - $called = 1; + $called = TRUE; require_once DRUPAL_ROOT . '/' . variable_get('path_inc', 'includes/path.inc'); require_once DRUPAL_ROOT . '/includes/theme.inc'; require_once DRUPAL_ROOT . '/includes/pager.inc'; @@ -5235,7 +5235,7 @@ function drupal_set_page_content($content = NULL) { * browsers, '#browsers' can be set to array('IE' => 'gte IE 8'). * * @return - * The passed in element with markup for conditional comments potentially + * The passed-in element with markup for conditional comments potentially * added to '#prefix' and '#suffix'. */ function drupal_pre_render_conditional_comments($elements) { @@ -5297,7 +5297,7 @@ function drupal_pre_render_conditional_comments($elements) { * - #options: (optional) An array of options to pass to l(). * * @return - * The passed in elements containing a rendered link in '#markup'. + * The passed-in elements containing a rendered link in '#markup'. */ function drupal_pre_render_link($element) { // By default, link options to pass to l() are normally set in #options. @@ -5327,7 +5327,7 @@ function drupal_pre_render_link($element) { if (!isset($element['#id'])) { $element['#id'] = $element['#options']['attributes']['id'] = drupal_html_id('ajax-link'); } - // If #ajax['path] was not specified, use the href as AJAX request URL. + // If #ajax['path] was not specified, use the href as Ajax request URL. if (!isset($element['#ajax']['path'])) { $element['#ajax']['path'] = $element['#href']; $element['#ajax']['options'] = $element['#options']; @@ -5444,7 +5444,7 @@ function drupal_pre_render_links($element) { * A structured array using the #markup key. * * @return - * The passed in elements, but #markup appended to #children. + * The passed-in elements, but #markup appended to #children. */ function drupal_pre_render_markup($elements) { $elements['#children'] = $elements['#markup']; @@ -5575,9 +5575,9 @@ function drupal_render(&$elements) { return $cached_output; } - // If #markup is not empty, set #type. This allows to specify just #markup on - // an element without setting #type. - if (!empty($elements['#markup']) && !isset($elements['#type'])) { + // If #markup is set, ensure #type is set. This allows to specify just #markup + // on an element without setting #type. + if (isset($elements['#markup']) && !isset($elements['#type'])) { $elements['#type'] = 'markup'; } @@ -5694,13 +5694,17 @@ function drupal_render_children(&$element, $children_keys = NULL) { } /** - * Render and print an element. + * Render an element. * * This function renders an element using drupal_render(). The top level - * element is always rendered even if hide() had been previously used on it. + * element is shown with show() before rendering, so it will always be rendered + * even if hide() had been previously used on it. * - * Any nested elements are only rendered if they haven't been rendered before - * or if they have been re-enabled with show(). + * @param $element + * The element to be rendered. + * + * @return + * The rendered element. * * @see drupal_render() * @see show() @@ -5721,6 +5725,21 @@ function render(&$element) { /** * Hide an element from later rendering. * + * The first time render() or drupal_render() is called on an element tree, + * as each element in the tree is rendered, it is marked with a #printed flag + * and the rendered children of the element are cached. Subsequent calls to + * render() or drupal_render() will not traverse the child tree of this element + * again: they will just use the cached children. So if you want to hide an + * element, be sure to call hide() on the element before its parent tree is + * rendered for the first time, as it will have no effect on subsequent + * renderings of the parent tree. + * + * @param $element + * The element to be hidden. + * + * @return + * The element. + * * @see render() * @see show() */ @@ -5730,10 +5749,25 @@ function hide(&$element) { } /** - * Show a hidden or already printed element from later rendering. + * Show a hidden element for later rendering. + * + * You can also use render($element), which shows the element while rendering + * it. + * + * The first time render() or drupal_render() is called on an element tree, + * as each element in the tree is rendered, it is marked with a #printed flag + * and the rendered children of the element are cached. Subsequent calls to + * render() or drupal_render() will not traverse the child tree of this element + * again: they will just use the cached children. So if you want to show an + * element, be sure to call show() on the element before its parent tree is + * rendered for the first time, as it will have no effect on subsequent + * renderings of the parent tree. + * + * @param $element + * The element to be shown. * - * Alternatively, render($element) could be used which automatically shows the - * element while rendering it. + * @return + * The element. * * @see render() * @see hide() @@ -5800,8 +5834,9 @@ function drupal_render_cache_set(&$markup, $elements) { // be retrieved and used. $data['#markup'] = &$markup; // Persist attached data associated with this element. - if (isset($elements['#attached'])) { - $data['#attached'] = $elements['#attached']; + $attached = drupal_render_collect_attached($elements, TRUE); + if ($attached) { + $data['#attached'] = $attached; } $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache'; $expire = isset($elements['#cache']['expire']) ? $elements['#cache']['expire'] : CACHE_PERMANENT; @@ -5809,6 +5844,49 @@ function drupal_render_cache_set(&$markup, $elements) { } /** + * Collect #attached for an element and all child elements into a single array. + * + * When caching elements, it is necessary to collect all libraries, JavaScript + * and CSS into a single array, from both the element itself and all child + * elements. This allows drupal_render() to add these back to the page when the + * element is returned from cache. + * + * @param $elements + * The element to collect #attached from. + * @param $return + * Whether to return the attached elements and reset the internal static. + * + * @return + * The #attached array for this element and its descendants. + */ +function drupal_render_collect_attached($elements, $return = FALSE) { + $attached = &drupal_static(__FUNCTION__, array()); + + // Collect all #attached for this element. + if (isset($elements['#attached'])) { + foreach ($elements['#attached'] as $key => $value) { + if (!isset($attached[$key])) { + $attached[$key] = array(); + } + $attached[$key] = array_merge($attached[$key], $value); + } + } + if ($children = element_children($elements)) { + foreach ($children as $child) { + drupal_render_collect_attached($elements[$child]); + } + } + + // If this was the first call to the function, return all attached elements + // and reset the static cache. + if ($return) { + $return = $attached; + $attached = array(); + return $return; + } +} + +/** * Prepare an element for caching based on a query. This smart caching strategy * saves Drupal from querying and rendering to HTML when the underlying query is * unchanged. @@ -5830,7 +5908,7 @@ function drupal_render_cache_set(&$markup, $elements) { * * @return * A renderable array with the following keys and values: - * - #query: The passed in $query. + * - #query: The passed-in $query. * - #pre_render: $function with a _pre_render suffix. * - #cache: An associative array prepared for drupal_render_cache_set(). */ @@ -6237,7 +6315,7 @@ function drupal_array_set_nested_value(array &$array, array $parents, $value, $f * The array from which to get the value. * @param $parents * An array of parent keys of the value, starting with the outermost key. - * @param &$key_exists + * @param $key_exists * (optional) If given, an already defined variable that is altered by * reference. * @@ -6651,30 +6729,27 @@ function drupal_schema_fields_sql($table, $prefix = NULL) { } /** - * Saves a record to the database based upon the schema. - * - * Default values are filled in for missing items, and 'serial' (auto increment) - * types are filled in with IDs. + * Saves (inserts or updates) a record to the database based upon the schema. * * @param $table * The name of the table; this must be defined by a hook_schema() * implementation. * @param $record * An object or array representing the record to write, passed in by - * reference. The function will fill in defaults from the schema and add an - * ID value to serial fields. + * reference. If inserting a new record, values not provided in $record will + * be populated in $record and in the database with the default values from + * the schema, as well as a single serial (auto-increment) field (if present). + * If updating an existing record, only provided values are updated in the + * database, and $record is not modified. * @param $primary_keys - * If this is an update, specify the primary keys' field names. If this is a - * new record, you must not provide this value. If there is only 1 field in - * the key, you may pass in a string; if there are multiple fields in the key, - * pass in an array. + * To indicate that this is a new record to be inserted, omit this argument. + * If this is an update, this argument specifies the primary keys' field + * names. If there is only 1 field in the key, you may pass in a string; if + * there are multiple fields in the key, pass in an array. * * @return - * Failure to write a record will return FALSE. Otherwise SAVED_NEW or - * SAVED_UPDATED is returned depending on the operation performed. The $object - * parameter will contain values for any serial fields defined by the $table. - * For example, $record->nid or $record['nid'] will be populated after - * inserting a new a new node. + * If the record insert or update failed, returns FALSE. If it succeeded, + * returns SAVED_NEW or SAVED_UPDATED, depending on the operation performed. */ function drupal_write_record($table, &$record, $primary_keys = array()) { // Standardize $primary_keys to an array. @@ -6815,7 +6890,7 @@ function drupal_write_record($table, &$record, $primary_keys = array()) { */ /** - * Parse Drupal module and theme info file format. + * Parses Drupal module and theme .info files. * * Info files are NOT for placing arbitrary theme and module-specific settings. * Use variable_get() and variable_set() for that. @@ -6826,35 +6901,42 @@ function drupal_write_record($table, &$record, $primary_keys = array()) { * - dependencies: An array of shortnames of other modules this module requires. * - package: The name of the package of modules this module belongs to. * - * @see forum.info + * See forum.info for an example of a module .info file. * * Information stored in a theme .info file: - * - name: The real name of the theme for display purposes - * - description: Brief description + * - name: The real name of the theme for display purposes. + * - description: Brief description. * - screenshot: Path to screenshot relative to the theme's .info file. * - engine: Theme engine; typically phptemplate. - * - base: Name of a base theme, if applicable, eg: base = zen - * - regions: Listed regions eg: region[left] = Left sidebar - * - features: Features available eg: features[] = logo - * - stylesheets: Theme stylesheets eg: stylesheets[all][] = my-style.css - * - scripts: Theme scripts eg: scripts[] = my-script.css + * - base: Name of a base theme, if applicable; e.g., base = zen. + * - regions: Listed regions; e.g., region[left] = Left sidebar. + * - features: Features available; e.g., features[] = logo. + * - stylesheets: Theme stylesheets; e.g., stylesheets[all][] = my-style.css. + * - scripts: Theme scripts; e.g., scripts[] = my-script.js. * - * @see bartik.info + * See bartik.info for an example of a theme .info file. * * @param $filename * The file we are parsing. Accepts file with relative or absolute path. + * * @return * The info array. * * @see drupal_parse_info_format() */ function drupal_parse_info_file($filename) { - if (!file_exists($filename)) { - return array(); - } + $info = &drupal_static(__FUNCTION__, array()); - $data = file_get_contents($filename); - return drupal_parse_info_format($data); + if (!isset($info[$filename])) { + if (!file_exists($filename)) { + $info[$filename] = array(); + } + else { + $data = file_get_contents($filename); + $info[$filename] = drupal_parse_info_format($data); + } + } + return $info[$filename]; } /** @@ -7319,8 +7401,7 @@ function entity_create_stub_entity($entity_type, $ids) { /** * Load entities from the database. * - * This function should be used whenever you need to load more than one entity - * from the database. The entities are loaded into memory and will not require + * The entities are stored in a static memory cache, and will not require * database access if loaded again during the same page request. * * The actual loading is done through a class that has to implement the @@ -7415,8 +7496,15 @@ function entity_get_controller($entity_type) { * The type of entity, i.e. 'node', 'user'. * @param $entities * The entity objects which are being prepared for view, keyed by object ID. + * @param $langcode + * (optional) A language code to be used for rendering. Defaults to the global + * content language of the current request. */ -function entity_prepare_view($entity_type, $entities) { +function entity_prepare_view($entity_type, $entities, $langcode = NULL) { + if (!isset($langcode)) { + $langcode = $GLOBALS['language_content']->language; + } + // To ensure hooks are only run once per entity, check for an // entity_view_prepared flag and only process items without it. // @todo: resolve this more generally for both entity and field level hooks. @@ -7432,7 +7520,7 @@ function entity_prepare_view($entity_type, $entities) { } if (!empty($prepare)) { - module_invoke_all('entity_prepare_view', $prepare, $entity_type); + module_invoke_all('entity_prepare_view', $prepare, $entity_type, $langcode); } } @@ -7507,7 +7595,7 @@ function entity_label($entity_type, $entity) { $label = FALSE; $info = entity_get_info($entity_type); if (isset($info['label callback']) && function_exists($info['label callback'])) { - $label = $info['label callback']($entity); + $label = $info['label callback']($entity, $entity_type); } elseif (!empty($info['entity keys']['label']) && isset($entity->{$info['entity keys']['label']})) { $label = $entity->{$info['entity keys']['label']}; diff --git a/includes/database/database.inc b/includes/database/database.inc index b384afe3a..4cc1a33d7 100644 --- a/includes/database/database.inc +++ b/includes/database/database.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -321,7 +320,7 @@ abstract class DatabaseConnection extends PDO { * PDO::FETCH_OBJ, PDO::FETCH_NUM, or a string representing the name of a * class. If a string is specified, each record will be fetched into a new * object of that class. The behavior of all other values is defined by PDO. - * See http://www.php.net/PDOStatement-fetch + * See http://php.net/manual/pdostatement.fetch.php * - return: Depending on the type of query, different return values may be * meaningful. This directive instructs the system which type of return * value is desired. The system will generally set the correct value @@ -542,6 +541,63 @@ abstract class DatabaseConnection extends PDO { } /** + * Flatten an array of query comments into a single comment string. + * + * The comment string will be sanitized to avoid SQL injection attacks. + * + * @param $comments + * An array of query comment strings. + * + * @return + * A sanitized comment string. + */ + public function makeComment($comments) { + if (empty($comments)) + return ''; + + // Flatten the array of comments. + $comment = implode('; ', $comments); + + // Sanitize the comment string so as to avoid SQL injection attacks. + return '/* ' . $this->filterComment($comment) . ' */ '; + } + + /** + * Sanitize a query comment string. + * + * Ensure a query comment does not include strings such as "* /" that might + * terminate the comment early. This avoids SQL injection attacks via the + * query comment. The comment strings in this example are separated by a + * space to avoid PHP parse errors. + * + * For example, the comment: + * @code + * db_update('example') + * ->condition('id', $id) + * ->fields(array('field2' => 10)) + * ->comment('Exploit * / DROP TABLE node; --') + * ->execute() + * @endcode + * + * Would result in the following SQL statement being generated: + * @code + * "/ * Exploit * / DROP TABLE node; -- * / UPDATE example SET field2=..." + * @endcode + * + * Unless the comment is sanitised first, the SQL server would drop the + * node table and ignore the rest of the SQL statement. + * + * @param $comment + * A query comment string. + * + * @return + * A sanitized version of the query comment string. + */ + protected function filterComment($comment = '') { + return preg_replace('/(\/\*\s*)|(\s*\*\/)/', '', $comment); + } + + /** * Executes a query string against the database. * * This method provides a central handler for the actual execution of every diff --git a/includes/database/log.inc b/includes/database/log.inc index f28aadb2c..ec27ef8e6 100644 --- a/includes/database/log.inc +++ b/includes/database/log.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/database/mysql/database.inc b/includes/database/mysql/database.inc index a3d711840..262cc6051 100644 --- a/includes/database/mysql/database.inc +++ b/includes/database/mysql/database.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/database/mysql/install.inc b/includes/database/mysql/install.inc index 2f83b2664..75f2ae390 100644 --- a/includes/database/mysql/install.inc +++ b/includes/database/mysql/install.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/database/mysql/query.inc b/includes/database/mysql/query.inc index 584a4a473..888b6a5a4 100644 --- a/includes/database/mysql/query.inc +++ b/includes/database/mysql/query.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @ingroup database @@ -43,8 +42,8 @@ class InsertQuery_mysql extends InsertQuery { } public function __toString() { - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a sanitized comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); // Default fields are always placed first for consistency. $insert_fields = array_merge($this->defaultFields, $this->insertFields); @@ -93,8 +92,8 @@ class TruncateQuery_mysql extends TruncateQuery { // not transactional, and result in an implicit COMMIT. When we are in a // transaction, fallback to the slower, but transactional, DELETE. if ($this->connection->inTransaction()) { - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '}'; } else { diff --git a/includes/database/mysql/schema.inc b/includes/database/mysql/schema.inc index 01933da09..4e88fa169 100644 --- a/includes/database/mysql/schema.inc +++ b/includes/database/mysql/schema.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/database/pgsql/database.inc b/includes/database/pgsql/database.inc index d38b64afe..98b954ffd 100644 --- a/includes/database/pgsql/database.inc +++ b/includes/database/pgsql/database.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -35,6 +34,15 @@ class DatabaseConnection_pgsql extends DatabaseConnection { if (empty($connection_options['password'])) { $connection_options['password'] = NULL; } + // If the password contains a backslash it is treated as an escape character + // http://bugs.php.net/bug.php?id=53217 + // so backslashes in the password need to be doubled up. + // The bug was reported against pdo_pgsql 1.0.2, backslashes in passwords + // will break on this doubling up when the bug is fixed, so check the version + //elseif (phpversion('pdo_pgsql') < 'version_this_was_fixed_in') { + else { + $connection_options['password'] = str_replace('\\', '\\\\', $connection_options['password']); + } $this->connectionOptions = $connection_options; diff --git a/includes/database/pgsql/install.inc b/includes/database/pgsql/install.inc index 0d446af58..c77f4ea78 100644 --- a/includes/database/pgsql/install.inc +++ b/includes/database/pgsql/install.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/database/pgsql/query.inc b/includes/database/pgsql/query.inc index c475a0011..f3783a9ca 100644 --- a/includes/database/pgsql/query.inc +++ b/includes/database/pgsql/query.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @ingroup database @@ -104,8 +103,8 @@ class InsertQuery_pgsql extends InsertQuery { } public function __toString() { - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a sanitized comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); // Default fields are always placed first for consistency. $insert_fields = array_merge($this->defaultFields, $this->insertFields); diff --git a/includes/database/pgsql/schema.inc b/includes/database/pgsql/schema.inc index 30f12ef78..9ed8a2620 100644 --- a/includes/database/pgsql/schema.inc +++ b/includes/database/pgsql/schema.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -528,6 +527,9 @@ class DatabaseSchema_pgsql extends DatabaseSchema { $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $check . '"'); } + // Remove old default. + $this->fieldSetNoDefault($table, $field); + $this->connection->query('ALTER TABLE {' . $table . '} ALTER "' . $field . '" TYPE ' . $typecast . ' USING "' . $field . '"::' . $typecast); if (isset($spec['not null'])) { @@ -562,6 +564,11 @@ class DatabaseSchema_pgsql extends DatabaseSchema { $this->connection->query('ALTER TABLE {' . $table . '} ADD CHECK ("' . $field_new . '" >= 0)'); } + // Add default if necessary. + if (isset($spec['default'])) { + $this->fieldSetDefault($table, $field_new, $spec['default']); + } + // Change description if necessary. if (!empty($spec['description'])) { $this->connection->query('COMMENT ON COLUMN {' . $table . '}."' . $field_new . '" IS ' . $this->prepareComment($spec['description'])); diff --git a/includes/database/pgsql/select.inc b/includes/database/pgsql/select.inc index 98061c90a..d1d838281 100644 --- a/includes/database/pgsql/select.inc +++ b/includes/database/pgsql/select.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/database/prefetch.inc b/includes/database/prefetch.inc index a81ea10f1..f378d3555 100644 --- a/includes/database/prefetch.inc +++ b/includes/database/prefetch.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/database/query.inc b/includes/database/query.inc index de421b30c..23b652f9b 100644 --- a/includes/database/query.inc +++ b/includes/database/query.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @ingroup database @@ -362,6 +361,9 @@ abstract class Query implements QueryPlaceholderInterface { * for easier debugging and allows you to more easily find where a query * with a performance problem is being generated. * + * The comment string will be sanitized to remove * / and other characters + * that may terminate the string early so as to avoid SQL injection attacks. + * * @param $comment * The comment string to be inserted into the query. * @@ -624,9 +626,8 @@ class InsertQuery extends Query { * The prepared statement. */ public function __toString() { - - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a sanitized comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); // Default fields are always placed first for consistency. $insert_fields = array_merge($this->defaultFields, $this->insertFields); @@ -816,9 +817,8 @@ class DeleteQuery extends Query implements QueryConditionInterface { * The prepared statement. */ public function __toString() { - - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a sanitized comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); $query = $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} '; @@ -885,8 +885,8 @@ class TruncateQuery extends Query { * The prepared statement. */ public function __toString() { - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a sanitized comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); return $comments . 'TRUNCATE {' . $this->connection->escapeTable($this->table) . '} '; } @@ -1112,9 +1112,8 @@ class UpdateQuery extends Query implements QueryConditionInterface { * The prepared statement. */ public function __toString() { - - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a sanitized comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); // Expressions take priority over literal fields, so we process those first // and remove any literal fields that conflict. diff --git a/includes/database/schema.inc b/includes/database/schema.inc index e57c94bfb..de1b2f5b9 100644 --- a/includes/database/schema.inc +++ b/includes/database/schema.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/database/select.inc b/includes/database/select.inc index 43f92671e..716c2fc3d 100644 --- a/includes/database/select.inc +++ b/includes/database/select.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @ingroup database @@ -1440,9 +1439,8 @@ class SelectQuery extends Query implements SelectQueryInterface { } public function __toString() { - - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a sanitized comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); // SELECT $query = $comments . 'SELECT '; diff --git a/includes/database/sqlite/database.inc b/includes/database/sqlite/database.inc index 651efa26f..cf3b9551f 100644 --- a/includes/database/sqlite/database.inc +++ b/includes/database/sqlite/database.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/database/sqlite/install.inc b/includes/database/sqlite/install.inc index a8a12410d..62cbac381 100644 --- a/includes/database/sqlite/install.inc +++ b/includes/database/sqlite/install.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/database/sqlite/query.inc b/includes/database/sqlite/query.inc index 7b1af8114..6b8a72f2a 100644 --- a/includes/database/sqlite/query.inc +++ b/includes/database/sqlite/query.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -33,8 +32,8 @@ class InsertQuery_sqlite extends InsertQuery { } public function __toString() { - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a sanitized comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); // Produce as many generic placeholders as necessary. $placeholders = array_fill(0, count($this->insertFields), '?'); @@ -102,7 +101,7 @@ class UpdateQuery_sqlite extends UpdateQuery { } elseif (!isset($data)) { // The field will be set to NULL. - $condition->isNull($field); + $condition->isNotNull($field); } else { $condition->condition($field, $data, '<>'); @@ -149,8 +148,8 @@ class DeleteQuery_sqlite extends DeleteQuery { */ class TruncateQuery_sqlite extends TruncateQuery { public function __toString() { - // Create a comments string to prepend to the query. - $comments = (!empty($this->comments)) ? '/* ' . implode('; ', $this->comments) . ' */ ' : ''; + // Create a sanitized comment string to prepend to the query. + $comments = $this->connection->makeComment($this->comments); return $comments . 'DELETE FROM {' . $this->connection->escapeTable($this->table) . '} '; } diff --git a/includes/database/sqlite/schema.inc b/includes/database/sqlite/schema.inc index 11ecd881a..840ba6b5d 100644 --- a/includes/database/sqlite/schema.inc +++ b/includes/database/sqlite/schema.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/database/sqlite/select.inc b/includes/database/sqlite/select.inc index ee3d84dff..fb926ef04 100644 --- a/includes/database/sqlite/select.inc +++ b/includes/database/sqlite/select.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/date.inc b/includes/date.inc index 75d666bc9..27634f9e3 100644 --- a/includes/date.inc +++ b/includes/date.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/entity.inc b/includes/entity.inc index 5fb36e2dd..a3cdf7417 100644 --- a/includes/entity.inc +++ b/includes/entity.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * Interface for entity controller classes. @@ -409,7 +408,7 @@ class EntityFieldQueryException extends Exception {} * direct access to the collected properties in order to handle alternate * execution routines. We therefore use public properties for simplicity. Note * that code that is simply creating and running a field query should still use - * the appropriate methods add conditions on the query. + * the appropriate methods to add conditions on the query. * * Storage engines are not required to support every type of query. By default, * an EntityFieldQueryException will be raised if an unsupported condition is diff --git a/includes/errors.inc b/includes/errors.inc index 235c16141..3a97b6daa 100644 --- a/includes/errors.inc +++ b/includes/errors.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/file.inc b/includes/file.inc index 4f7a4c3be..6dc7f88b3 100644 --- a/includes/file.inc +++ b/includes/file.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -202,9 +201,8 @@ function file_stream_wrapper_get_class($scheme) { * @see file_uri_target() */ function file_uri_scheme($uri) { - $data = explode('://', $uri, 2); - - return count($data) == 2 ? $data[0] : FALSE; + $position = strpos($uri, '://'); + return $position ? substr($uri, 0, $position) : FALSE; } /** @@ -424,7 +422,7 @@ function file_create_url($uri) { * Directories need to have execute permissions to be considered a directory by * FTP servers, etc. * - * @param &$directory + * @param $directory * A string reference containing the name of a directory path or URI. A * trailing slash will be trimmed from a path. * @param $options diff --git a/includes/file.mimetypes.inc b/includes/file.mimetypes.inc index fe23ba48b..cda03fb92 100644 --- a/includes/file.mimetypes.inc +++ b/includes/file.mimetypes.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/filetransfer/filetransfer.inc b/includes/filetransfer/filetransfer.inc index cd62f1d51..2083da9d3 100644 --- a/includes/filetransfer/filetransfer.inc +++ b/includes/filetransfer/filetransfer.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /* * Base FileTransfer class. diff --git a/includes/filetransfer/ftp.inc b/includes/filetransfer/ftp.inc index ddb0d3eae..838dc7c1e 100644 --- a/includes/filetransfer/ftp.inc +++ b/includes/filetransfer/ftp.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * Base class for FTP implementations. diff --git a/includes/filetransfer/local.inc b/includes/filetransfer/local.inc index d5744c191..b12598973 100644 --- a/includes/filetransfer/local.inc +++ b/includes/filetransfer/local.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * The local connection class for copying files as the httpd user. diff --git a/includes/filetransfer/ssh.inc b/includes/filetransfer/ssh.inc index 56b944ca4..43ec3249e 100644 --- a/includes/filetransfer/ssh.inc +++ b/includes/filetransfer/ssh.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * The SSH connection class for the update module. diff --git a/includes/form.inc b/includes/form.inc index 2ff93e61c..a337b03d1 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @defgroup forms Form builder functions @@ -128,9 +127,9 @@ * - 'triggering_element': (read-only) The form element that triggered * submission. This is the same as the deprecated * $form_state['clicked_button']. It is the element that caused submission, - * which may or may not be a button (in the case of AJAX forms.) This is + * which may or may not be a button (in the case of Ajax forms.) This is * often used to distinguish between various buttons in a submit handler, - * and is also used in AJAX handlers. + * and is also used in Ajax handlers. * - 'cache': The typical form workflow involves two page requests. During the * first page request, a form is built and returned for the user to fill in. * Then the user fills the form in and submits it, triggering a second page @@ -139,9 +138,9 @@ * In some special use-cases, it is necessary or desired to persist the $form * and $form_state variables from the initial page request to the one that * processes the submission. A form builder function can set 'cache' to TRUE - * to do this. One example where this is needed is to handle AJAX submissions, + * to do this. One example where this is needed is to handle Ajax submissions, * so ajax_process_form() sets this for all forms that include an element with - * a #ajax property. (In AJAX, the handler has no way to build the form + * a #ajax property. (In Ajax, the handler has no way to build the form * itself, so must rely on the cached version created on each page load, so * it's a classic example of this use case.) Note that the persistence of * $form and $form_state across successive submissions of a multi-step form @@ -202,7 +201,7 @@ function drupal_get_form($form_id) { * can implement hook_forms(), which maps different $form_id values to the * proper form constructor function. Examples may be found in node_forms(), * search_forms(), and user_forms(). - * @param &$form_state + * @param $form_state * An array which stores information about the form. This is passed as a * reference so that the caller can use it to examine what in the form changed * when the form submission process is complete. Furthermore, it may be used @@ -254,7 +253,7 @@ function drupal_get_form($form_id) { * - must_validate: Ordinarily, a form is only validated once but there are * times when a form is resubmitted internally and should be validated * again. Setting this to TRUE will force that to happen. This is most - * likely to occur during AHAH or AJAX operations. + * likely to occur during AHAH or Ajax operations. * - temporary: An array holding temporary data accessible during the current * page request only. It may be used to temporary save any data that doesn't * need to or shouldn't be cached during the whole form workflow, e.g. data @@ -396,9 +395,9 @@ function form_state_defaults() { * function is called to generate a new $form, the next step in the form * workflow, to be returned for rendering. * - * AJAX form submissions are almost always multi-step workflows, so that is one + * Ajax form submissions are almost always multi-step workflows, so that is one * common use-case during which form rebuilding occurs. See ajax_form_callback() - * for more information about creating AJAX-enabled forms. + * for more information about creating Ajax-enabled forms. * * @param $form_id * The unique string identifying the desired form. If a function @@ -411,7 +410,7 @@ function form_state_defaults() { * A keyed array containing the current state of the form. * @param $old_form * (optional) A previously built $form. Used to retain the #build_id and - * #action properties in AJAX callbacks and similar partial form rebuilds. The + * #action properties in Ajax callbacks and similar partial form rebuilds. The * only properties copied from $old_form are the ones which both exist in * $old_form and for which $form_state['rebuild_info']['copy'][PROPERTY] is * TRUE. If $old_form is not passed, the entire $form is rebuilt freshly. @@ -427,7 +426,7 @@ function form_state_defaults() { function drupal_rebuild_form($form_id, &$form_state, $old_form = NULL) { $form = drupal_retrieve_form($form_id, $form_state); - // If only parts of the form will be returned to the browser (e.g. AJAX or + // If only parts of the form will be returned to the browser (e.g., Ajax or // RIA clients), re-use the old #build_id to not require client-side code to // manually update the hidden 'build_id' input element. // Otherwise, a new #build_id is generated, to not clobber the previous @@ -441,7 +440,7 @@ function drupal_rebuild_form($form_id, &$form_state, $old_form = NULL) { $form['#build_id'] = 'form-' . drupal_hash_base64(uniqid(mt_rand(), TRUE) . mt_rand()); } - // #action defaults to request_uri(), but in case of AJAX and other partial + // #action defaults to request_uri(), but in case of Ajax and other partial // rebuilds, the form is submitted to an alternate URL, and the original // #action needs to be retained. if (isset($old_form['#action']) && !empty($form_state['rebuild_info']['copy']['#action'])) { @@ -882,13 +881,13 @@ function drupal_process_form($form_id, &$form, &$form_state) { // yet complete. A new $form needs to be constructed based on the changes // made to $form_state during this request. Normally, a submit handler sets // $form_state['rebuild'] if a fully executed form requires another step. - // However, for forms that have not been fully executed (e.g., AJAX + // However, for forms that have not been fully executed (e.g., Ajax // submissions triggered by non-buttons), there is no submit handler to set // $form_state['rebuild']. It would not make sense to redisplay the // identical form without an error for the user to correct, so we also // rebuild error-free non-executed forms, regardless of // $form_state['rebuild']. - // @todo D8: Simplify this logic; considering AJAX and non-HTML front-ends, + // @todo D8: Simplify this logic; considering Ajax and non-HTML front-ends, // along with element-level #submit properties, it makes no sense to have // divergent form execution based on whether the triggering element has // #executes_submit_callback set to TRUE. @@ -1146,7 +1145,7 @@ function drupal_validate_form($form_id, &$form, &$form_state) { * - If $form_state['no_redirect'] is TRUE, then the callback that originally * built the form explicitly disallows any redirection, regardless of the * redirection value in $form_state['redirect']. For example, ajax_get_form() - * defines $form_state['no_redirect'] when building a form in an AJAX + * defines $form_state['no_redirect'] when building a form in an Ajax * callback to prevent any redirection. $form_state['no_redirect'] should NOT * be altered by form builder functions or form validation/submit handlers. * - If $form_state['programmed'] is TRUE, the form submission was usually @@ -1286,7 +1285,7 @@ function _form_validate(&$elements, &$form_state, $form_id = NULL) { // If submit handlers won't run (due to the submission having been triggered // by an element whose #executes_submit_callback property isn't TRUE), then // it's safe to suppress all validation errors, and we do so by default, - // which is particularly useful during an AJAX submission triggered by a + // which is particularly useful during an Ajax submission triggered by a // non-button. An element can override this default by setting the // #limit_validation_errors property. For button element types, // #limit_validation_errors defaults to FALSE (via system_element_info()), @@ -1962,12 +1961,12 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) { // form_state_values_clean(). Enforce the same input processing restrictions // as above. if ($process_input) { - // Detect if the element triggered the submission via AJAX. + // Detect if the element triggered the submission via Ajax. if (_form_element_triggered_scripted_submission($element, $form_state)) { $form_state['triggering_element'] = $element; } - // If the form was submitted by the browser rather than via AJAX, then it + // If the form was submitted by the browser rather than via Ajax, then it // can only have been triggered by a button, and we need to determine which // button within the constraints of how browsers provide this information. if (isset($element['#button_type'])) { @@ -1992,7 +1991,7 @@ function _form_builder_handle_input_element($form_id, &$element, &$form_state) { * Helper function to handle the convoluted logic of button click detection. * * This detects button or non-button controls that trigger a form submission via - * AJAX or some other scriptable environment. These environments can set the + * Ajax or some other scriptable environment. These environments can set the * special input key '_triggering_element_name' to identify the triggering * element. If the name alone doesn't identify the element uniquely, the input * key '_triggering_element_value' may also be set to require a match on element @@ -2017,7 +2016,7 @@ function _form_element_triggered_scripted_submission($element, &$form_state) { * of the POST data, but with extra code to deal with the convoluted way in * which browsers submit data for image button clicks. * - * This does not detect button clicks processed by AJAX (that is done in + * This does not detect button clicks processed by Ajax (that is done in * _form_element_triggered_scripted_submission()) and it does not detect form * submissions from Internet Explorer in response to an ENTER key pressed in a * textfield (form_builder() has extra code for that). @@ -2062,7 +2061,7 @@ function _form_button_was_clicked($element, &$form_state) { * - form_build_id * - op * - * @param &$form_state + * @param $form_state * A keyed array containing the current state of the form, including * submitted form values; altered by reference. */ @@ -2492,7 +2491,7 @@ function form_process_select($element) { if (($required && !isset($element['#default_value'])) || isset($element['#empty_value']) || isset($element['#empty_option'])) { $element += array( '#empty_value' => '', - '#empty_option' => $required ? t('- Select - ') : t('- None -'), + '#empty_option' => $required ? t('- Select -') : t('- None -'), ); // The empty option is prepended to #options and purposively not merged // to prevent another option in #options mistakenly using the same value @@ -3378,7 +3377,7 @@ function form_validate_machine_name(&$element, &$form_state) { * Adds fieldsets to the specified group or adds group members to this * fieldset. * - * @param &$element + * @param $element * An associative array containing the properties and children of the * fieldset. Note that $element must be taken by reference here, so processed * child elements are taken over into $form_state. @@ -3724,7 +3723,7 @@ function theme_textarea($variables) { function theme_password($variables) { $element = $variables['element']; $element['#attributes']['type'] = 'password'; - element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength')); + element_set_attributes($element, array('id', 'name', 'size', 'maxlength')); _form_set_class($element, array('form-text')); return '<input' . drupal_attributes($element['#attributes']) . ' />'; @@ -3956,7 +3955,7 @@ function theme_form_element_label($variables) { * * Adds 'required' and 'error' classes as needed. * - * @param &$element + * @param $element * The form element. * @param $name * Array of new class names to be added. @@ -4016,7 +4015,9 @@ function _form_set_class(&$element, $class = array()) { * Note: if the batch 'title', 'init_message', 'progress_message', or * 'error_message' could contain any user input, it is the responsibility of * the code calling batch_set() to sanitize them first with a function like - * check_plain() or filter_xss(). + * check_plain() or filter_xss(). Furthermore, if the batch operation + * returns any user input in the 'results' or 'message' keys of $context, + * it must also sanitize them first. * * Sample batch operations: * @code @@ -4039,8 +4040,8 @@ function _form_set_class(&$element, $class = array()) { * // and the batch processing can continue to the next operation. * * $node = node_load(array('uid' => $uid, 'type' => $type)); - * $context['results'][] = $node->nid . ' : ' . $node->title; - * $context['message'] = $node->title; + * $context['results'][] = $node->nid . ' : ' . check_plain($node->title); + * $context['message'] = check_plain($node->title); * } * * // More advanced example: multi-step operation - load all nodes, five by five @@ -4059,10 +4060,10 @@ function _form_set_class(&$element, $class = array()) { * ->execute(); * foreach ($result as $row) { * $node = node_load($row->nid, NULL, TRUE); - * $context['results'][] = $node->nid . ' : ' . $node->title; + * $context['results'][] = $node->nid . ' : ' . check_plain($node->title); * $context['sandbox']['progress']++; * $context['sandbox']['current_node'] = $node->nid; - * $context['message'] = $node->title; + * $context['message'] = check_plain($node->title); * } * if ($context['sandbox']['progress'] != $context['sandbox']['max']) { * $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max']; diff --git a/includes/graph.inc b/includes/graph.inc index 38ed6fc3a..416fad6df 100644 --- a/includes/graph.inc +++ b/includes/graph.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -33,7 +32,7 @@ * @endcode * * @return - * The passed in $graph with more secondary keys filled in: + * The passed-in $graph with more secondary keys filled in: * - 'paths': Contains a list of vertices than can be reached on a path from * this vertex. * - 'reverse_paths': Contains a list of vertices that has a path from them @@ -75,15 +74,15 @@ function drupal_depth_first_search(&$graph) { /** * Helper function to perform a depth first sort. * - * @param &$graph + * @param $graph * A three dimensional associated graph array. - * @param &$state + * @param $state * An associative array. The key 'last_visit_order' stores a list of the * vertices visited. The key components stores list of vertices belonging * to the same the component. * @param $start * An arbitrary vertex where we started traversing the graph. - * @param &$component + * @param $component * The component of the last vertex. * * @see drupal_depth_first_search() diff --git a/includes/image.inc b/includes/image.inc index 28d63be77..b04943b5e 100644 --- a/includes/image.inc +++ b/includes/image.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/install.core.inc b/includes/install.core.inc index c7b4053d9..a74dfdf0f 100644 --- a/includes/install.core.inc +++ b/includes/install.core.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/install.inc b/includes/install.inc index 68a67bcd6..d22f4f9cb 100644 --- a/includes/install.inc +++ b/includes/install.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * Indicates that a module has not been installed yet. @@ -199,7 +198,7 @@ function drupal_install_profile_distribution_name() { // At all other times, we load the profile via standard methods. else { $profile = drupal_get_profile(); - $info = install_profile_info($profile); + $info = system_get_info('module', $profile); return $info['distribution_name']; } } @@ -548,7 +547,7 @@ abstract class DatabaseTasks { * * @param $database * An array of driver specific configuration options. - * + * * @return * An array of driver configuration errors, keyed by form element name. */ @@ -783,44 +782,12 @@ function drupal_uninstall_modules($module_list = array(), $uninstall_dependents } foreach ($module_list as $module) { - // First, retrieve all the module's menu paths from db. - drupal_load('module', $module); - $paths = module_invoke($module, 'menu'); - // Uninstall the module. module_load_install($module); module_invoke($module, 'uninstall'); drupal_uninstall_schema($module); watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO); - - // Now remove the menu links for all paths declared by this module. - if (!empty($paths)) { - $paths = array_keys($paths); - // Clean out the names of load functions. - foreach ($paths as $index => $path) { - $parts = explode('/', $path, MENU_MAX_PARTS); - foreach ($parts as $k => $part) { - if (preg_match('/^%[a-z_]*$/', $part)) { - $parts[$k] = '%'; - } - } - $paths[$index] = implode('/', $parts); - } - - $result = db_select('menu_links') - ->fields('menu_links') - ->condition('router_path', $paths, 'IN') - ->condition('external', 0) - ->orderBy('depth') - ->execute(); - // Remove all such items. Starting from those with the greatest depth will - // minimize the amount of re-parenting done by menu_link_delete(). - foreach ($result as $item) { - _menu_delete_item($item, TRUE); - } - } - drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED); } @@ -1055,7 +1022,12 @@ function install_goto($path) { * Used during the install process, when database, theme, and localization * system is possibly not yet available. * + * Use t() if your code will never run during the Drupal installation phase. + * Use st() if your code will only run during installation and never any other + * time. Use get_t() if your code could run in either circumstance. + * * @see t() + * @see get_t() * @ingroup sanitization */ function st($string, array $args = array(), array $options = array()) { @@ -1199,6 +1171,10 @@ function drupal_check_module($module) { * installed, to be shown throughout the installation process. Defaults to * 'Drupal'. * + * Note that this function does an expensive file system scan to get info file + * information for dependencies. If you only need information from the info + * file itself, use system_get_info(). + * * Example of .info file: * @code * name = Minimal diff --git a/includes/iso.inc b/includes/iso.inc index ed2bfdd79..dabbefdd5 100644 --- a/includes/iso.inc +++ b/includes/iso.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/language.inc b/includes/language.inc index 908f013fd..b7057f2a1 100644 --- a/includes/language.inc +++ b/includes/language.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -85,6 +84,44 @@ function language_types_disable($types) { } /** + * Updates the language type configuration. + */ +function language_types_set() { + // Ensure that we are getting the defined language negotiation information. An + // invocation of module_enable() or module_disable() could outdate the cached + // information. + drupal_static_reset('language_types_info'); + drupal_static_reset('language_negotiation_info'); + + // Determine which language types are configurable and which not by checking + // whether the 'fixed' key is defined. Non-configurable (fixed) language types + // have their language negotiation settings stored there. + $defined_providers = language_negotiation_info(); + foreach (language_types_info() as $type => $info) { + if (isset($info['fixed'])) { + $language_types[$type] = FALSE; + $negotiation = array(); + foreach ($info['fixed'] as $weight => $id) { + if (isset($defined_providers[$id])) { + $negotiation[$id] = $weight; + } + } + language_negotiation_set($type, $negotiation); + } + else { + $language_types[$type] = TRUE; + } + } + + // Save language types. + variable_set('language_types', $language_types); + + // Ensure that subsequent calls of language_types_configurable() return the + // updated language type information. + drupal_static_reset('language_types_configurable'); +} + +/** * Check if a language provider is enabled. * * This has two possible behaviors: @@ -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) { diff --git a/includes/locale.inc b/includes/locale.inc index d6db7c17c..578b1b3c6 100644 --- a/includes/locale.inc +++ b/includes/locale.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -586,159 +585,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); - $current = array(); - $current["#"][] = substr($line, 1); - $context = "COMMENT"; + + // Start a new entry for the comment. + $current = array(); + $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") || ($context == "MSGSTR_ARR")) { // 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") || ($context == "MSGSTR_ARR")) { // 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 (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) { + // 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; } - } /** diff --git a/includes/lock.inc b/includes/lock.inc index 6dd4b9352..42f1906f2 100644 --- a/includes/lock.inc +++ b/includes/lock.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -188,7 +187,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. @@ -199,12 +198,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; diff --git a/includes/mail.inc b/includes/mail.inc index 2628175ed..d2febed39 100644 --- a/includes/mail.inc +++ b/includes/mail.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -123,8 +122,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; diff --git a/includes/menu.inc b/includes/menu.inc index 0806a31cf..cfd35c794 100644 --- a/includes/menu.inc +++ b/includes/menu.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @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 @@ -125,7 +127,7 @@ define('MENU_IS_LOCAL_ACTION', 0x0100); /** * @defgroup menu_item_types Menu item types * @{ - * Definitions for various menu types. + * Definitions for various menu item types. * * Menu item definitions provide one of these constants, which are shortcuts for * combinations of @link menu_flags Menu flags @endlink. @@ -1042,7 +1044,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']; @@ -2064,7 +2066,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() */ @@ -2319,11 +2321,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; } } @@ -2891,11 +2893,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']); @@ -2904,18 +2907,26 @@ 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. * * @return * The mlid of the saved menu link, or FALSE if the menu link could not be diff --git a/includes/module.inc b/includes/module.inc index aa061e195..779b66826 100644 --- a/includes/module.inc +++ b/includes/module.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -127,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; } @@ -372,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; } @@ -854,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. * @@ -888,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. diff --git a/includes/pager.inc b/includes/pager.inc index 553e279c3..146033f1b 100644 --- a/includes/pager.inc +++ b/includes/pager.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -431,7 +430,7 @@ function theme_pager($variables) { /** * @defgroup pagerpieces Pager pieces * @{ - * Use these pieces to construct your own custom pagers in your theme. + * Theme functions for customizing pager elements. * * Note that you should NOT modify this file to customize your pager. */ diff --git a/includes/password.inc b/includes/password.inc index 1c6672af1..93d34f81f 100644 --- a/includes/password.inc +++ b/includes/password.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/path.inc b/includes/path.inc index 83ebcb0c9..db605370c 100644 --- a/includes/path.inc +++ b/includes/path.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/registry.inc b/includes/registry.inc index 99fbfef63..3fb14fb31 100644 --- a/includes/registry.inc +++ b/includes/registry.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @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; } diff --git a/includes/session.inc b/includes/session.inc index 23af6bd64..2ede2ff89 100644 --- a/includes/session.inc +++ b/includes/session.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/stream_wrappers.inc b/includes/stream_wrappers.inc index 1f837783d..7df1f9dc6 100644 --- a/includes/stream_wrappers.inc +++ b/includes/stream_wrappers.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -384,7 +383,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 diff --git a/includes/tablesort.inc b/includes/tablesort.inc index b2b122f90..121a1b909 100644 --- a/includes/tablesort.inc +++ b/includes/tablesort.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @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) && isset($header['sort'])) { - 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 (isset($header['data']) && $order == $header['data']) { - return array('name' => $header['data'], 'sql' => isset($header['field']) ? $header['field'] : ''); - } + if (is_array($header)) { + if (isset($header['data']) && $order == $header['data']) { + $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 (isset($header['sort'])) { + if (is_array($header) && isset($header['data']) && $header['data'] == $ts['name'] && isset($header['sort'])) { return $header['sort']; } } diff --git a/includes/theme.inc b/includes/theme.inc index c5015865d..3ae50006a 100644 --- a/includes/theme.inc +++ b/includes/theme.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @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, @@ -320,8 +319,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 +362,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 +370,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 +414,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 (!isset($info[$key]) && 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 +482,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. @@ -972,7 +985,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 +995,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 +1269,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 +1302,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; } /** @@ -1756,10 +1760,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'))); } } @@ -1872,7 +1876,7 @@ function theme_more_help_link($variables) { */ function theme_feed_icon($variables) { $text = t('Subscribe to @feed-title', array('@feed-title' => $variables['title'])); - if ($image = theme('image', array('path' => 'misc/feed.png', 'alt' => $text))) { + 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))); } } @@ -2042,6 +2046,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; @@ -2119,6 +2126,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. diff --git a/includes/theme.maintenance.inc b/includes/theme.maintenance.inc index 4950d8278..218a8adaa 100644 --- a/includes/theme.maintenance.inc +++ b/includes/theme.maintenance.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/token.inc b/includes/token.inc index 57c15ea4f..edc8a962f 100644 --- a/includes/token.inc +++ b/includes/token.inc @@ -1,24 +1,23 @@ <?php -// $Id$ /** * @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,22 +54,25 @@ * (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. */ @@ -96,17 +98,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 +133,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 +144,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 +185,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 +193,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 +206,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 +243,7 @@ function token_find_with_prefix(array $tokens, $prefix, $delimiter = ':') { * 'type' => 'user', * ); * @endcode + * * @return * An associative array of token information, grouped by token type. */ diff --git a/includes/unicode.entities.inc b/includes/unicode.entities.inc index 1ef3d4799..3b1fbb691 100644 --- a/includes/unicode.entities.inc +++ b/includes/unicode.entities.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/unicode.inc b/includes/unicode.inc index 812a2f174..9dde2ca70 100644 --- a/includes/unicode.inc +++ b/includes/unicode.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * 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. diff --git a/includes/update.inc b/includes/update.inc index 0019df671..1eb7a1d9c 100644 --- a/includes/update.inc +++ b/includes/update.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/updater.inc b/includes/updater.inc index b49d5d21f..363c6ebff 100644 --- a/includes/updater.inc +++ b/includes/updater.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/utility.inc b/includes/utility.inc index 8bd78904f..254313f73 100644 --- a/includes/utility.inc +++ b/includes/utility.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/xmlrpc.inc b/includes/xmlrpc.inc index 40b0d9d03..92e5d14f0 100644 --- a/includes/xmlrpc.inc +++ b/includes/xmlrpc.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file diff --git a/includes/xmlrpcs.inc b/includes/xmlrpcs.inc index fd5a40733..70c7cdac3 100644 --- a/includes/xmlrpcs.inc +++ b/includes/xmlrpcs.inc @@ -1,5 +1,4 @@ <?php -// $Id$ /** * @file @@ -337,7 +336,7 @@ function xmlrpc_server_get_capabilities() { } /** - * Returns the method signature of a function. + * Returns one method signature for a function. * * This is the function mapped to the XML-RPC method system.methodSignature. * @@ -349,8 +348,8 @@ function xmlrpc_server_get_capabilities() { * Name of method to return a method signature for. * * @return array - * An array of types representing the method signature of the function that - * $methodname maps to. + * An array of arrays of types, each of the arrays representing one method + * signature of the function that $methodname maps to. */ function xmlrpc_server_method_signature($methodname) { $xmlrpc_server = xmlrpc_server_get(); @@ -365,7 +364,7 @@ function xmlrpc_server_method_signature($methodname) { foreach ($xmlrpc_server->signatures[$methodname] as $type) { $return[] = $type; } - return $return; + return array($return); } /** |