Index: update.php =================================================================== RCS file: /cvs/drupal/drupal/update.php,v retrieving revision 1.265 diff -u -9 -p -r1.265 update.php --- update.php 3 Nov 2008 05:55:55 -0000 1.265 +++ update.php 11 Nov 2008 23:49:44 -0000 @@ -683,18 +683,19 @@ if (empty($op)) { print theme('update_page', '
', FALSE); exit; } install_goto('update.php?op=info'); } update_prepare_d7_bootstrap(); drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL); drupal_maintenance_theme(); +drupal_session_start(); // Turn error reporting back on. From now on, only fatal errors (which are // not passed through the error handler) will cause a message to be printed. ini_set('display_errors', TRUE); // Access check: if (!empty($update_free_access) || $user->uid == 1) { include_once DRUPAL_ROOT . '/includes/install.inc'; Index: includes/batch.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/batch.inc,v retrieving revision 1.28 diff -u -9 -p -r1.28 batch.inc --- includes/batch.inc 10 Nov 2008 05:22:56 -0000 1.28 +++ includes/batch.inc 11 Nov 2008 23:49:44 -0000 @@ -350,18 +350,19 @@ function _batch_finished() { // Let drupal_redirect_form handle redirection logic. $form = isset($batch['form']) ? $batch['form'] : array(); if (empty($_batch['form_state']['rebuild']) && empty($_batch['form_state']['storage'])) { drupal_redirect_form($form, $redirect); } // We get here if $form['#redirect'] was FALSE, or if the form is a // multi-step form. We save the final $form_state value to be retrieved // by drupal_get_form, and we redirect to the originating page. + drupal_session_start(); $_SESSION['batch_form_state'] = $_batch['form_state']; drupal_goto($_batch['source_page']); } } /** * Shutdown function: store the batch data for next request, * or clear the table if the batch is finished. */ Index: includes/bootstrap.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/bootstrap.inc,v retrieving revision 1.247 diff -u -9 -p -r1.247 bootstrap.inc --- includes/bootstrap.inc 11 Nov 2008 22:39:58 -0000 1.247 +++ includes/bootstrap.inc 11 Nov 2008 23:49:44 -0000 @@ -633,35 +633,42 @@ function variable_del($name) { cache_clear_all('variables', 'cache'); unset($conf[$name]); } /** * Retrieve the current page from the cache. * - * Note: we do not serve cached pages when status messages are waiting (from - * a redirected form submission which was completed). + * Note: we do not serve cached pages to authenticated users, or to anonymous + * users when $_SESSION is non-empty (it may contain e.g. status messages from + * a form submission). + * + * @return + * The cache object, if the page was found in the cache; TRUE if the page was + * not found, but output buffering was started in order to possibly cache the + * current request; FALSE if the page was not found, and the current request + * may not be cached (e.g. because it belongs to an authenticated user). */ function page_get_cache() { global $user, $base_root; - $cache = NULL; - - if (!$user->uid && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && count(drupal_set_message()) == 0) { - $cache = cache_get($base_root . request_uri(), 'cache_page'); - - if (empty($cache)) { - ob_start(); - } + if ($user->uid || ($_SERVER['REQUEST_METHOD'] != 'GET' && $_SERVER['REQUEST_METHOD'] != 'HEAD') || !empty($_SESSION)) { + return FALSE; + } + $cache = cache_get($base_root . request_uri(), 'cache_page'); + if ($cache) { + return $cache; + } + else { + ob_start(); + return TRUE; } - - return $cache; } /** * Call all init or exit hooks without including all modules. * * @param $hook * The name of the bootstrap hook we wish to invoke. */ function bootstrap_invoke_all($hook) { @@ -933,18 +940,20 @@ function watchdog($type, $message, $vari * - 'status' * - 'warning' * - 'error' * @param $repeat * If this is FALSE and the message is already set, then the message won't * be repeated. */ function drupal_set_message($message = NULL, $type = 'status', $repeat = TRUE) { if ($message) { + drupal_session_start(); + if (!isset($_SESSION['messages'])) { $_SESSION['messages'] = array(); } if (!isset($_SESSION['messages'][$type])) { $_SESSION['messages'][$type] = array(); } if ($repeat || !in_array($message, $_SESSION['messages'][$type])) { @@ -1071,19 +1080,19 @@ function drupal_bootstrap($phase = NULL) * drupal_bootstrap(). * * @see drupal_bootstrap */ function drupal_get_bootstrap_phase() { return drupal_bootstrap(); } function _drupal_bootstrap($phase) { - global $conf; + global $conf, $user; switch ($phase) { case DRUPAL_BOOTSTRAP_CONFIGURATION: drupal_initialize_variables(); // Start a page timer: timer_start('page'); // Initialize the configuration conf_init(); @@ -1117,48 +1126,68 @@ function _drupal_bootstrap($phase) { header($_SERVER['SERVER_PROTOCOL'] . ' 403 Forbidden'); print 'Sorry, ' . check_plain(ip_address()) . ' has been banned.'; exit(); } break; case DRUPAL_BOOTSTRAP_SESSION: require_once DRUPAL_ROOT . '/' . variable_get('session_inc', 'includes/session.inc'); session_set_save_handler('_sess_open', '_sess_close', '_sess_read', '_sess_write', '_sess_destroy_sid', '_sess_gc'); - session_start(); + // If a session cookie exists, initialize the session. Otherwise the + // session is only started on demand using drupal_session_start(), making + // anonymous users not use a session cookie unless something is stored in + // $_SESSION. This allows HTTP proxies to cache anonymous pageviews. + if (isset($_COOKIE[session_name()])) { + drupal_session_start(); + } + else { + $user = drupal_anonymous_user(); + } break; case DRUPAL_BOOTSTRAP_VARIABLES: // Initialize configuration variables, using values from settings.php if available. $conf = variable_init(isset($conf) ? $conf : array()); break; case DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE: // Load module handling. require_once DRUPAL_ROOT . '/includes/module.inc'; $cache_mode = variable_get('cache', CACHE_DISABLED); // Get the page from the cache. - $cache = $cache_mode == CACHE_DISABLED ? '' : page_get_cache(); + $cache = $cache_mode == CACHE_DISABLED ? FALSE : page_get_cache(); // If the skipping of the bootstrap hooks is not enforced, call hook_boot. if ($cache_mode != CACHE_AGGRESSIVE) { bootstrap_invoke_all('boot'); } // If there is a cached page, display it. - if ($cache) { + if (is_object($cache)) { + // Destroy empty anonymous sessions if possible. + if (drupal_session_start(FALSE) && empty($_SESSION)) { + session_destroy(); + } drupal_page_cache_header($cache); // If the skipping of the bootstrap hooks is not enforced, call hook_exit. if ($cache_mode != CACHE_AGGRESSIVE) { bootstrap_invoke_all('exit'); } // We are done. exit; } // Prepare for non-cached page workflow. drupal_page_header(); + // If the session has not already been started and output buffering is + // not enabled, the session must be started now before the HTTP headers + // are sent. If output buffering is enabled, the session may be started + // at any time using drupal_session_start(). + if ($cache === FALSE) { + drupal_session_start(); + } break; case DRUPAL_BOOTSTRAP_LANGUAGE: drupal_init_language(); break; case DRUPAL_BOOTSTRAP_PATH: require_once DRUPAL_ROOT . '/includes/path.inc'; // Initialize $_GET['q'] prior to loading modules and invoking hook_init(). @@ -1399,25 +1428,31 @@ function drupal_get_schema($table = NULL /** * Confirm that a function is available. * * If the function is already available, this function does nothing. * If the function is not available, it tries to load the file where the * function lives. If the file is not available, it returns false, so that it * can be used as a drop-in replacement for function_exists(). * * @param $function - * The name of the function to check or load. + * The name of the function to check or load, or FALSE to reset internal + * cache. * @return * TRUE if the function is now available, FALSE otherwise. */ function drupal_function_exists($function) { static $checked = array(); + #if ($function === FALSE) { + #$checked = array(); + #return TRUE; + #} + if (defined('MAINTENANCE_MODE')) { return function_exists($function); } if (isset($checked[$function])) { return $checked[$function]; } $checked[$function] = FALSE; Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.824 diff -u -9 -p -r1.824 common.inc --- includes/common.inc 11 Nov 2008 22:39:58 -0000 1.824 +++ includes/common.inc 11 Nov 2008 23:49:44 -0000 @@ -320,21 +320,23 @@ function drupal_goto($path = '', $query // Remove newlines from the URL to avoid header injection attacks. $url = str_replace(array("\n", "\r"), '', $url); // Allow modules to react to the end of the page request before redirecting. // We do not want this while running update.php. if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') { module_invoke_all('exit', $url); } - // Even though session_write_close() is registered as a shutdown function, we - // need all session data written to the database before redirecting. - session_write_close(); + if (drupal_session_start(FALSE)) { + // Even though session_write_close() is registered as a shutdown function, + // we need all session data written to the database before redirecting. + session_write_close(); + } header('Location: ' . $url, TRUE, $http_response_code); // The "Location" header sends a redirect status code to the HTTP daemon. In // some cases this can be wrong, so we make sure none of the code below the // drupal_goto() call gets executed upon redirection. exit(); } @@ -1627,18 +1629,24 @@ function l($text, $path, array $options } /** * Perform end-of-request tasks. * * This function sets the page cache if appropriate, and allows modules to * react to the closing of the page by calling hook_exit(). */ function drupal_page_footer() { + global $user; + + // Destroy empty anonymous sessions if possible. + if (!headers_sent() && drupal_session_start(FALSE) && empty($_SESSION) && !$user->uid) { + session_destroy(); + } if (variable_get('cache', CACHE_DISABLED) != CACHE_DISABLED) { page_set_cache(); } module_invoke_all('exit'); module_implements(MODULE_IMPLEMENTS_WRITE_CACHE); _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE); @@ -2745,19 +2753,19 @@ function _drupal_bootstrap_full() { * The majority of all modern browsers support gzip or both of them. * We thus only deal with the gzip variant and unzip the cache in case * the browser does not accept gzip encoding. * * @see drupal_page_header */ function page_set_cache() { global $user, $base_root; - if (!$user->uid && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && count(drupal_get_messages(NULL, FALSE)) == 0) { + if (!$user->uid && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'HEAD') && empty($_SESSION)) { // This will fail in some cases, see page_get_cache() for the explanation. if ($data = ob_get_contents()) { $cache = TRUE; if (variable_get('page_compression', TRUE) && function_exists('gzencode')) { // We do not store the data in case the zlib mode is deflate. // This should be rarely happening. if (zlib_get_coding_type() == 'deflate') { $cache = FALSE; } Index: includes/session.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/session.inc,v retrieving revision 1.63 diff -u -9 -p -r1.63 session.inc --- includes/session.inc 11 Nov 2008 16:49:37 -0000 1.63 +++ includes/session.inc 11 Nov 2008 23:49:44 -0000 @@ -153,18 +153,31 @@ function _sess_write($key, $value) { )) ->condition('uid', $user->uid) ->execute(); } return TRUE; } /** + * Propagate $_SESSION and set session cookie if not already set. This function + * should be called explicitly before writing to $_SESSION. + */ +function drupal_session_start($start = TRUE) { + static $started = FALSE; + if ($start && !$started) { + $started = TRUE; + session_start(); + } + return $started; +} + +/** * Called when an anonymous user becomes authenticated or vice-versa. */ function drupal_session_regenerate() { $old_session_id = session_id(); session_regenerate_id(); db_update('sessions') ->fields(array( 'sid' => session_id() )) @@ -202,18 +215,19 @@ function drupal_session_count($timestamp * Cleanup a specific session. * * @param string $sid * Session ID. */ function _sess_destroy_sid($sid) { db_delete('sessions') ->condition('sid', $sid) ->execute(); + setcookie(session_name(), '', time() - 3600, ini_get('session.cookie_path'), ini_get('session.cookie_domain'), (bool)ini_get('session.cookie_secure'), (bool)ini_get('session.httponly')); } /** * End a specific user's session(s). * * @param string $uid * User ID. */ function drupal_session_destroy_uid($uid) { Index: modules/simpletest/drupal_web_test_case.php =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/drupal_web_test_case.php,v retrieving revision 1.56 diff -u -9 -p -r1.56 drupal_web_test_case.php --- modules/simpletest/drupal_web_test_case.php 9 Nov 2008 03:07:54 -0000 1.56 +++ modules/simpletest/drupal_web_test_case.php 11 Nov 2008 23:49:44 -0000 @@ -1,17 +1,18 @@ curlConnect(); $url = empty($curl_options[CURLOPT_URL]) ? curl_getinfo($this->ch, CURLINFO_EFFECTIVE_URL) : $curl_options[CURLOPT_URL]; curl_setopt_array($this->ch, $this->curl_options + $curl_options); + $this->_headers = array(); $this->drupalSetContent(curl_exec($this->ch), curl_getinfo($this->ch, CURLINFO_EFFECTIVE_URL)); $this->assertTrue($this->_content !== FALSE, t('!method to !url, response is !length bytes.', array('!method' => !empty($curl_options[CURLOPT_NOBODY]) ? 'HEAD' : (empty($curl_options[CURLOPT_POSTFIELDS]) ? 'GET' : 'POST'), '!url' => $url, '!length' => strlen($this->_content))), t('Browser')); return $this->drupalGetContent(); } /** * Reads headers and registers errors received from the tested site. * * @see _drupal_log_error(). * * @param $ch the cURL handler. * @param $header a header. */ protected function curlHeaderCallback($ch, $header) { - // Errors are being sent via X-Drupal-Assertion-* headers, - // generated by _drupal_log_error() in the exact form required - // by DrupalWebTestCase::error(). - if (preg_match('/^X-Drupal-Assertion-[0-9]+: (.*)$/', $header, $matches)) { - // Call DrupalWebTestCase::error() with the parameters from the header. - call_user_func_array(array(&$this, 'error'), unserialize(urldecode($matches[1]))); + if (preg_match('!^HTTP/1\.!', $header)) { + // First line of response detected. Reset headers from any previous + // requests, e.g. if this request is the result of a HTTP redirect. + $this->_headers = array(); + } + elseif ($header) { + $split = explode(':', $header, 2); + if (sizeof($split) == 2) { + $name = $split[0]; + $value = trim($split[1]); + // Errors are being sent via X-Drupal-Assertion-* headers, generated + // by _drupal_log_error() in the exact form required by + // DrupalWebTestCase::error(). + if (preg_match('/^X-Drupal-Assertion-[0-9]+$/', $name)) { + // Call DrupalWebTestCase::error() with the parameters from the header. + call_user_func_array(array(&$this, 'error'), unserialize(urldecode($value))); + } + if (isset($this->_headers[$name])) { + // Concatenate duplicate headers using comma (RFC 2616, section 4.2). + $this->_headers[$name] .= ',' . $value; + } + else { + $this->_headers[$name] = $value; + } + } } // This is required by cURL. return strlen($header); } /** * Close the cURL handler and unset the handler. */ protected function curlClose() { @@ -1029,26 +1050,23 @@ class DrupalWebTestCase { } /** * Retrieves only the headers for a Drupal path or an absolute path. * * @param $path * Drupal path or URL to load into internal browser * @param $options * Options to be forwarded to url(). - * @return - * The retrieved headers, also available as $this->drupalGetContent() */ function drupalHead($path, $options = array()) { $options['absolute'] = TRUE; - $out = $this->curlExec(array(CURLOPT_HEADER => TRUE, CURLOPT_NOBODY => TRUE, CURLOPT_URL => url($path, $options))); + $out = $this->curlExec(array(CURLOPT_NOBODY => TRUE, CURLOPT_URL => url($path, $options))); $this->refreshVariables(); // Ensure that any changes to variables in the other thread are picked up. - return $out; } /** * Handle form input related to drupalPost(). Ensure that the specified fields * exist and attempt to create POST data in the correct manner for the particular * field type. * * @param $post * Reference to array of post values. @@ -1325,18 +1343,38 @@ class DrupalWebTestCase { /** * Gets the current raw HTML of requested page. */ function drupalGetContent() { return $this->_content; } /** + * Gets the HTTP response headers. + * @return + * The HTTP headers in a name => value array. + */ + function drupalGetHeaders() { + return $this->_headers; + } + + /** + * Gets the value of the specified HTTP response header. + * @param $name + * The HTTP header name. + * @return + * The HTTP header value or FALSE. + */ + function drupalGetHeader($name) { + return isset($this->_headers[$name]) ? $this->_headers[$name] : FALSE; + } + + /** * Sets the raw HTML content. This can be useful when a page has been fetched * outside of the internal browser and assertions need to be made on the * returned page. * * A good example would be when testing drupal_http_request(). After fetching * the page the content can be set and page elements can be checked to ensure * that the function worked properly. */ function drupalSetContent($content, $url = 'internal:') { Index: modules/simpletest/tests/bootstrap.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/bootstrap.test,v retrieving revision 1.4 diff -u -9 -p -r1.4 bootstrap.test --- modules/simpletest/tests/bootstrap.test 2 Nov 2008 10:56:35 -0000 1.4 +++ modules/simpletest/tests/bootstrap.test 11 Nov 2008 23:49:44 -0000 @@ -104,23 +104,22 @@ class BootstrapPageCacheTestCase extends 'description' => t('Enable the page cache, submit a HEAD request and examine headers.'), 'group' => t('Bootstrap') ); } /** * Enable cache and examine HTTP headers. */ function testPageCache() { - global $base_url; - variable_set('cache', 1); + variable_set('cache', CACHE_NORMAL); // Retrieve the front page, which has already been cached by $this->curlConnect(); - $this->drupalHead($base_url); - $this->assertText('ETag: ', t('Verify presence of ETag header indicating that page caching is enabled.')); + $this->drupalGet(''); + $this->assertTrue($this->drupalGetHeader('ETag'), t('An ETag header was sent, indicating that page caching is enabled.')); } } class BootstrapVariableTestCase extends DrupalWebTestCase { /** * Implementation of setUp(). */ Index: modules/simpletest/tests/session.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/session.test,v retrieving revision 1.4 diff -u -9 -p -r1.4 session.test --- modules/simpletest/tests/session.test 15 Sep 2008 15:18:59 -0000 1.4 +++ modules/simpletest/tests/session.test 11 Nov 2008 23:49:44 -0000 @@ -17,18 +17,19 @@ class SessionTestCase extends DrupalWebT 'group' => t('Session') ); } /** * Implementation of setUp(). */ function setUp() { parent::setUp('session_test'); + module_rebuild_cache(); } /** * Tests for drupal_save_session(). */ function testSessionSaveSession() { $this->assertTrue(drupal_save_session(), t('drupal_save_session() correctly returns TRUE when initially called with no arguments.'), t('Session')); $this->assertFalse(drupal_save_session(FALSE), t('drupal_save_session() correctly returns FALSE when called with FALSE.'), t('Session')); $this->assertFalse(drupal_save_session(), t('drupal_save_session() correctly returns FALSE when saving has been disabled.'), t('Session')); @@ -127,10 +128,86 @@ class SessionTestCase extends DrupalWebT $this->curlClose(); // Change cookie file for user. $this->cookie_file = file_directory_temp() . '/cookie.' . $uid . '.txt'; $this->curl_options[CURLOPT_COOKIEFILE] = $this->cookie_file; $this->curl_options[CURLOPT_COOKIESESSION] = TRUE; $this->drupalGet('session-test/get'); $this->assertResponse(200, t('Session test module is correctly enabled.'), t('Session')); } + + /** + * Test that empty anonymous sessions are destroyed. + */ + function testEmptyAnonymousSession() { + // With caching disabled, a session is always started. + $this->drupalGet(''); + $this->assertSessionStarted(TRUE); + $this->assertSessionEmpty(TRUE); + + variable_set('cache', CACHE_NORMAL); + + // During this request the session is destroyed in drupal_page_footer(), + // and the session cookie is unset. + $this->drupalGet(''); + $this->assertSessionStarted(TRUE); + $this->assertSessionEmpty(TRUE); + $this->assertFalse($this->drupalGetHeader('ETag'), t('Page was not cached.')); + // When PHP deletes a cookie, it sends "Set-Cookie: cookiename=deleted; + // expires=..." + $this->assertTrue(preg_match('/SESS\w+=deleted/', $this->drupalGetHeader('Set-Cookie')), t('Session cookie was deleted.')); + + // Verify that the session cookie was actually deleted by checking that a + // session is not started + $this->drupalGet(''); + $this->assertSessionStarted(FALSE); + $this->assertFalse($this->drupalGetHeader('Set-Cookie'), t('New session was not started.')); + + // Setting a value in $_SESSION starts a new session. + $this->drupalGet('session-test/set/' . $this->randomName()); + $this->assertSessionStarted(FALSE); + $this->assertTrue($this->drupalGetHeader('Set-Cookie'), t('New session was started.')); + + // Page caching is disabled when session is non-empty. + $this->drupalGet('session-test/unset'); + $this->assertSessionStarted(TRUE); + $this->assertSessionEmpty(FALSE); + $this->assertFalse($this->drupalGetHeader('ETag'), t('Page was not cached.')); + + // During this request the session is destroyed in _drupal_bootstrap(), + // and the session cookie is unset. + $this->drupalGet(''); + $this->assertSessionStarted(TRUE); + $this->assertSessionEmpty(TRUE); + $this->assertTrue($this->drupalGetHeader('ETag'), t('Page was cached.')); + $this->assertTrue(preg_match('/SESS\w+=deleted/', $this->drupalGetHeader('Set-Cookie')), t('Session cookie was deleted.')); + + // Verify that session was destroyed. + $this->drupalGet(''); + $this->assertSessionStarted(FALSE); + $this->assertFalse($this->drupalGetHeader('Set-Cookie'), t('New session was not started.')); + } + + /** + * Assert whether session was started during bootstrap process. + */ + function assertSessionStarted($started) { + if ($started) { + $this->assertIdentical($this->drupalGetHeader('X-Session-Started'), '1', t('Session was started.')); + } + else { + $this->assertIdentical($this->drupalGetHeader('X-Session-Started'), '0', t('Session was not started.')); + } + } + + /** + * Assert whether $_SESSION is empty at the beginning of the request. + */ + function assertSessionEmpty($empty) { + if ($empty) { + $this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '1', t('Session was empty.')); + } + else { + $this->assertIdentical($this->drupalGetHeader('X-Session-Empty'), '0', t('Session was not empty.')); + } + } } Index: modules/simpletest/tests/session_test.module =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/session_test.module,v retrieving revision 1.3 diff -u -9 -p -r1.3 session_test.module --- modules/simpletest/tests/session_test.module 5 Nov 2008 17:06:18 -0000 1.3 +++ modules/simpletest/tests/session_test.module 11 Nov 2008 23:49:44 -0000 @@ -12,49 +12,74 @@ function session_test_menu() { 'type' => MENU_CALLBACK, ); $items['session-test/set/%'] = array( 'title' => t('Set Session value'), 'page callback' => '_session_test_set', 'page arguments' => array(2), 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); + $items['session-test/unset'] = array( + 'title' => t('Session value'), + 'page callback' => '_session_test_unset', + 'access arguments' => array('access content'), + 'type' => MENU_CALLBACK, + ); $items['session-test/no-set/%'] = array( 'title' => t('Disabled session set value'), 'page callback' => '_session_test_no_set', 'page arguments' => array(2), 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); return $items; } /** + * Implement hook_boot(). + */ +function session_test_boot() { + header('X-Session-Started: ' . intval(drupal_session_start(FALSE))); + header('X-Session-Empty: ' . intval(empty($_SESSION))); +} + +/** * Page callback, prints the stored session value to the screen. */ function _session_test_get() { + drupal_session_start(); if (!empty($_SESSION['session_test_value'])) { return t('The current value of the stored session variable is: %val', array('%val' => $_SESSION['session_test_value'])); } else { return ""; } } /** * Page callback, stores a value in $_SESSION['session_test_value']. */ function _session_test_set($value) { + drupal_session_start(); $_SESSION['session_test_value'] = $value; return t('The current value of the stored session variable has been set to %val', array('%val' => $value)); } /** + * Page callback, unsets $_SESSION['session_test_value']. + */ +function _session_test_unset() { + drupal_session_start(); + unset($_SESSION['session_test_value']); + return t('The stored session variable has been unset'); +} + +/** * Menu callback: turns off session saving and then tries to save a value * anyway. */ function _session_test_no_set($value) { drupal_save_session(FALSE); _session_test_set($value); return t('session saving was disabled, and then %val was set', array('%val' => $value)); }