diff --git includes/ajax.inc includes/ajax.inc index 74b4e73..84dc14e 100644 --- includes/ajax.inc +++ includes/ajax.inc @@ -193,11 +193,24 @@ * functions. */ function ajax_render($commands = array()) { + // Update the page state and determine the new CSS and JavaScript that needs + // to be loaded. If there is no prior page state, something is wrong. + drupal_static_reset('drupal_page_state_id'); + if ($prior_page_state = drupal_get_page_state()) { + $new_page_state = $prior_page_state; + drupal_update_page_state($new_page_state); + $new_css = array_diff_key($new_page_state['css'], $prior_page_state['css']); + $new_js = array_diff_key($new_page_state['js'], $prior_page_state['js']); + unset($new_js['settings']); + } + // Automatically extract any 'settings' added via drupal_add_js() and make // them the first command. $scripts = drupal_add_js(NULL, NULL); if (!empty($scripts['settings'])) { array_unshift($commands, ajax_command_settings(call_user_func_array('array_merge_recursive', $scripts['settings']['data']))); + $commands[0]['styles'] = isset($new_css) ? drupal_get_css($new_css) : ''; + $commands[0]['scripts'] = isset($new_js) ? drupal_get_js('header', $new_js) : ''; } // Allow modules to alter any AJAX response. @@ -526,6 +539,7 @@ function ajax_process_form($element, &$form_state) { $form_state['cache'] = TRUE; } + drupal_page_state_enabled(TRUE); return $element; } diff --git includes/common.inc includes/common.inc index 14e716b..1762f16 100644 --- includes/common.inc +++ includes/common.inc @@ -6794,3 +6794,79 @@ function drupal_get_updaters() { } return $updaters; } + +/** + * + */ +function drupal_page_state_id($new_id = NULL) { + $id = &drupal_static(__FUNCTION__); + if (isset($new_id)) { + $id = $new_id; + } + // Security care must always be taken when using raw $_POST data. + elseif (!isset($id) && isset($_POST['_page_state_id']) && is_string($_POST['_page_state_id']) && preg_match('/^[0-9a-f]{32}$/', $_POST['_page_state_id'])) { + $id = $_POST['_page_state_id']; + } + return $id; +} + +/** + * + */ +function drupal_get_page_state($id = NULL) { + if (!isset($id)) { + $id = drupal_page_state_id(); + } + if ($id && $cached = cache_get('page_state_' . $id, 'cache_form')) { + $page_state = $cached->data; + } + else { + $page_state = array(); + } + return $page_state; +} + +/** + * + */ +function drupal_page_state_enabled($enable = NULL) { + $enabled = &drupal_static(__FUNCTION__); + if (isset($enable)) { + $enabled = $enable; + } + elseif (!isset($enabled)) { + $enabled = (bool) drupal_page_state_id(); + } + return $enabled; +} + +/** + * + */ +function drupal_update_page_state(&$page_state = NULL) { + if (!isset($page_state)) { + $page_state = drupal_get_page_state(); + } + foreach (module_implements('page_state_update') as $module) { + $function = $module . '_page_state_update'; + $function($page_state); + } + + // There can be many users viewing pages that need their page state persisted. + // We don't want each one having a randomly generated id, as that would flood + // the cache bin with many records. By basing the id on the page state + // content, we only need as many records as there are unique states. + $page_state_id = drupal_hash_base64(serialize($page_state)); + + // $page_state serves a similar purpose to $form_state. Therefore, persist it + // in the same cache bin with the same 6 hour life time. + cache_set('page_state_' . $page_state_id, $page_state, 'cache_form', REQUEST_TIME + 21600); + + // The browser will need to know the new page state id. + drupal_add_js(array('pageStateId' => $page_state_id), 'setting'); + + // Future calls to drupal_page_state_id() during this page request should + // retrieve the new id. + drupal_page_state_id($page_state_id); + return $page_state_id; +} diff --git includes/theme.inc includes/theme.inc index ecbded1..b69f7cf 100644 --- includes/theme.inc +++ includes/theme.inc @@ -2443,6 +2443,13 @@ function template_process_maintenance_page(&$variables) { $variables['head'] = drupal_get_html_head(); $variables['css'] = drupal_add_css(); $variables['styles'] = drupal_get_css(); + + // If necessary, persist page state information to enable AJAX functionality. + // This updates a JavaScript variable, so do this before calling + // drupal_get_js(). + if (drupal_page_state_enabled()) { + drupal_update_page_state(); + } $variables['scripts'] = drupal_get_js(); } diff --git misc/ajax.js misc/ajax.js index 553d2cc..5413288 100644 --- misc/ajax.js +++ misc/ajax.js @@ -206,6 +206,9 @@ Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) { // Disable the element that received the change. $(this.element).addClass('progress-disabled').attr('disabled', true); + // Give the server information about the page state. + form_values.push({ name: '_page_state_id', value: Drupal.settings.pageStateId }); + // Prevent duplicate HTML ids in the returned markup. // @see drupal_html_id() $('[id]').each(function () { @@ -418,6 +421,12 @@ Drupal.ajax.prototype.commands = { * Command to set the settings that will be used for other commands in this response. */ settings: function (ajax, response, status) { + if (response.styles) { + $('head').append(response.styles); + } + if (response.scripts) { + $('head').append(response.scripts); + } if (response.merge) { $.extend(true, Drupal.settings, response.settings); } diff --git modules/system/system.module modules/system/system.module index e46aa5c..2eb62bd 100644 --- modules/system/system.module +++ modules/system/system.module @@ -3684,3 +3684,15 @@ function system_admin_paths() { ); return $paths; } + +/** + * Implements hook_page_state_update(). + */ +function system_page_state_update(&$page_state) { + $page_state += array( + 'css' => array(), + 'js' => array(), + ); + $page_state['css'] = array_merge($page_state['css'], drupal_add_css()); + $page_state['js'] = array_merge($page_state['js'], drupal_add_js()); +}