diff --git a/esi.pages.inc b/esi.pages.inc index 56c4ecd..60f4893 100644 --- a/esi.pages.inc +++ b/esi.pages.inc @@ -7,9 +7,15 @@ /** * Menu callback to handle an ESI component. * - * @see esi_component_load(). + * @see esi_component_load() */ function esi_handle_component($component) { + // Anyone running ESI should have object caching disabled for the page cache. + // Disable it just in case. This is especially important since $_GET['q'] + // gets set synthetically in COMPONENT__restore_context(), and it would be + // very easy to end up with an ESI fragment in the cache for "/node/1". + drupal_page_is_cacheable(FALSE); + // The menu wildcard loader will return NULL for invalid components, so that // the menu-handler will delegate 404 delivery here. if (empty($component)) { @@ -17,13 +23,13 @@ function esi_handle_component($component) { } // Remove the component from the arguments. - $args = array_slice(func_get_args(),1); + $args = array_slice(func_get_args(), 1); // Load in the include file if provided. if (!empty($component['file'])) { $filepath = $component['filepath'] . '/' . $component['file']; if (file_exists($filepath)) { - include_once($filepath); + include_once $filepath; } } @@ -85,7 +91,7 @@ function esi_fast_404() { watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING); // Unlike drupal_fast_404(), the HTML furniture (html tag, head tag, etc) are - // not delivered + // not delivered. echo "\n"; // Unlike normal page-views, invoking hook_exit() is unneccessary overhead. diff --git a/modules/esi_panels/esi_panels.esi.inc b/modules/esi_panels/esi_panels.esi.inc index 2195911..355f757 100644 --- a/modules/esi_panels/esi_panels.esi.inc +++ b/modules/esi_panels/esi_panels.esi.inc @@ -1,58 +1,50 @@ content[$pid]; - + $pane = new stdClass(); $pane->esi_meta = array( - 'display_contexts' => array(), - ); - - if (!empty($pane->configuration['context'])) { - $task_name = array_shift($args); - list($task, $subtask) = _esi_panels__get_task_identifier($task_name); - - $pane->esi_meta['task'] = $task; - $pane->esi_meta['subtask'] = $subtask; - } - - $pane->esi_meta += array( - 'display' => $display, + 'address' => $address, 'theme' => $theme, - 'display_args' => $args, 'url' => base64_decode($url), ); + // Allow other modules to alter the context information here. + // A certain implementation might adjust the addressable string to add extra + // args or do something similar. + // @see hook_esi_block_context_alter() + drupal_alter('esi_panels_pane_context', $pane); + esi_panels__restore_context($pane); return $pane; } - - /** * Restore the original context that was used when a block was displayed. */ @@ -65,100 +57,36 @@ function esi_panels__restore_context(&$pane) { $_SERVER['REQUEST_URI'] = $pane->esi_meta['url']; drupal_static_reset('drupal_get_destination'); - // Load up contexts. Owch. - if (!empty($pane->configuration['context'])) { - ctools_include('context'); - ctools_include('context-task-handler'); - - // Load the task/subtask plugins. - $task = page_manager_get_task($pane->esi_meta['task']); - $subtask = (empty($pane->esi_meta['subtask'])) ? '' : page_manager_get_subtask($task, $pane->esi_meta['subtask']); - - // Use the task-name and the original display arguments to generate the - // arguments that were passed to the context constructor. - // E.g. The node_view task takes "1" and returns node_load(1). - $base_context_arguments = esi_panels__get_base_context_arguments($pane->esi_meta['task'], $pane->esi_meta['subtask'], $pane->esi_meta['display_args']); - $base_contexts = ctools_context_handler_get_task_contexts($task, $subtask, $base_context_arguments); - - - // The base contexts are then typically used in the task plugin as render - // information: - // $output = ctools_context_handler_render($task, '', $contexts, array($node->nid)); - - // Other contexts are usually loaded by the panel_context task_handler - // plugin in the render function panels_panel_context_render(): - // $contexts = ctools_context_handler_get_handler_contexts($base_contexts, $handler); - - // Load the relevant handler - $handlers = page_manager_load_sorted_handlers($task, $subtask ? $subtask['name'] : '', TRUE); - $id = ctools_context_handler_get_render_handler($task, $subtask, $handlers, $base_contexts, $pane->esi_meta['display_args']); - if ($id) { - $handler = $handlers[$id]; - $contexts = ctools_context_handler_get_handler_contexts($base_contexts, $handler); - } - - $pane->esi_meta['display_contexts'] = $contexts; + // Load the actual pane info through the addressable content system. + $pane_meta = $pane->esi_meta; + ctools_include('content'); + if ($pane = ctools_get_addressable_content($pane_meta['address'], 'pane')) { + $pane->esi_meta = $pane_meta; } } /** - * Render the HTML for a single block. + * Render the HTML for a single pane. * Defined in hook_esi_component_info(). * * @see esi_panels_esi_component_info() */ function esi_panels__esi_pane_render($pane) { - // Much of this is from the "standard" display renderer: - // see panels_renderer_standard::prepare(). - ctools_include('content'); - $content_type = ctools_get_content_type($pane->type); - - // Check access control; if the user doesn't have access, simply return an - // empty string. - // ctools_include('context'); - // if ($pane->access && !empty($pane->display_contexts) && ctools_access($pane->access, $pane->display_contexts)) { - // return ''; - // } - $content = ctools_content_render($pane->type, $pane->subtype, $pane->configuration, array(), $pane->esi_meta['display_args'], $pane->esi_meta['display_contexts']); - - if (empty($content)) { - return ''; - } - - foreach (module_implements('panels_pane_content_alter') as $module) { - $function = $module . '_panels_pane_content_alter'; - $function($content, $pane, $pane->esi_meta['display_args'], $pane->esi_meta['display_contexts']); - } - + // Set HTTP headers. esi_panels_set_http_headers($pane); - // Pass long the css_id that is usually available. - if (!empty($pane->css['css_id'])) { - $content->css_id = check_plain($pane->css['css_id']); + // Use addressable content exclusively. + if ($address = $pane->esi_meta['address']) { + ctools_include('content'); + $content = ctools_get_addressable_content($address); } - // Pass long the css_class that is usually available. - if (!empty($pane->css['css_class'])) { - $content->css_class = check_plain($pane->css['css_class']); + // CTools will return an empty string if any of a number of errors occur. + if (empty($content)) { + return ''; } - if (!empty($content->content)) { - if (!empty($pane->style['style'])) { - $style = panels_get_style($pane->style['style']); - - if (isset($style) && isset($style['render pane'])) { - $output = theme($style['render pane'], array('content' => $content, 'pane' => $pane, 'display' => $this->display, 'style' => $style, 'settings' => $pane->style['settings'])); - - // This could be null if no theme function existed. - if (isset($output)) { - return $output; - } - } - } - - // fallback - return theme('panels_pane', array('content' => $content, 'pane' => $pane, 'display' => $pane->esi_meta['display'])); - } + return $content; } /** @@ -174,7 +102,7 @@ function esi_panels_set_http_headers($pane) { // @see hook_esi_block_cache_headers_alter(). drupal_alter('esi_panels_cache_headers', $headers); - foreach($headers as $header) { + foreach ($headers as $header) { drupal_add_http_header($header[0], $header[1]); } } @@ -187,5 +115,4 @@ function esi_panels_set_http_headers($pane) { */ function esi_panels__esi_pane_flush() { // @TODO. - } diff --git a/modules/esi_panels/esi_panels.module b/modules/esi_panels/esi_panels.module index 835a232..3e9d318 100644 --- a/modules/esi_panels/esi_panels.module +++ b/modules/esi_panels/esi_panels.module @@ -87,7 +87,7 @@ function esi_panels_form_panels_panel_context_edit_settings_alter(&$form, &$form } /** - * Implementation of hook_ctools_plugin_directory(). + * Implements hook_ctools_plugin_directory(). */ function esi_panels_ctools_plugin_directory($module, $plugin) { // Safety: go away if CTools is not at an appropriate version. @@ -99,7 +99,7 @@ function esi_panels_ctools_plugin_directory($module, $plugin) { // errors when they're in use. if ($module == 'ctools' && $plugin == 'cache') { return; - // if we did we'd make a plugin/ctools_cache or something. + // If we did we'd make a plugin/ctools_cache or something. } if ($module == 'page_manager' || $module == 'panels' || $module == 'ctools') { @@ -121,7 +121,7 @@ function esi_panels_ctools_plugin_type() { } /** - * Implementation of hook_ctools_plugin_api(). + * Implements hook_ctools_plugin_api(). * * Inform CTools about version information for various plugins implemented by * Panels. @@ -157,45 +157,41 @@ function esi_panels_ctools_plugin_post_alter(&$plugin, &$info) { /** * Build the URL to use for this ESI component. * - * @return String - * The internal URL. Generate a fully-qualified path by running through url(). + * @return string + * The internal URL. Generate a fully-qualified path by running through url(). */ function esi_panels_url($pane, $display) { - // ESI 6.x-1.x and 6.x-2.x used the URL patterns: - // Default: esi/panels_pane/theme:display_id:pane_id - // With context: esi/panels_pane/theme:display_id:pane_id/[base64($_GET['q'])]/task_name/context - - $url = "esi/panels_pane/"; - + // ESI 7 takes advantage of addressable content URLs. + // Addressable content URLs contain context and args. + // Default: esi/panels_pane/theme:addressable_url + // Cache per-role: esi/panels_pane/theme:addressable_url/CACHE=ROLE + // Cache per-user: esi/panels_pane/theme:addressable_url/CACHE=USER + // Cache per-page: esi/panels_pane/theme:addressable_url/[base64($_GET['q'])] + // Cache per-role-page: esi/panels_pane/theme:addressable_url/[base64($_GET['q'])]/CACHE=ROLE + // Cache per-user-page: esi/panels_pane/theme:addressable_url/[base64($_GET['q'])]/CACHE=ROLE global $theme; - $url .= implode(':', array( - $theme, - $pane->did, - $pane->pid, - )); - - // The did and pid are used to identify which pane content_type to load. - - // Other available data to pass into the URL: - // - $display->args Are *always* passed. - // - $display->context A pane can only accept a single context. - // - $display->cache_key The cache key provides the name of the task/subtask. - - if (!empty($pane->configuration['context'])) { - // If the context originates from the *TASK* plugin (which is typical), the - // task name is required in order to generate the task contexts - // ($base_context in panels_panel_context_render()). - // Additional contexts may be supplied directly by the display. - $task_name = _esi_panels__get_taskname($display->cache_key); - - $url .= "/{$task_name}"; + $url = "esi/panels_pane/" . $theme . ":"; + + // If the display reports that it has addressable content, make use of that. + if ($display->renderer_handler && $address = $display->renderer_handler->address) { + $url .= implode('::', array( + $address, + $pane->pid, + )); } - // Add all the display arguments to the end of the URL. - $url .= '/' . implode('/', $display->args); + if ($pane->cache['settings']['override_context']['esi_overridden_context__page']) { + $url .= '/' . base64_encode($_GET['q']); + } - // Always add the current page URL. - $url .= '/' . base64_encode($_GET['q']); + switch ($pane->cache['settings']['override_context']['esi_overridden_context__user']) { + case 1: + $url .= '/CACHE=ROLE'; + break; + case 2: + $url .= '/CACHE=USER'; + break; + } // Allow other modules to alter the ESI URL (or respond to it). // @see hook_esi_block_url_alter(). @@ -206,7 +202,7 @@ function esi_panels_url($pane, $display) { /** * Save the configuration of a panel page. - * @see panels_panel_context_save(). + * @see panels_panel_context_save() */ function esi_panels__panel_context_save(&$handler, $update) { // Override the rendering pipeline if any pane uses ESI. @@ -221,87 +217,23 @@ function esi_panels__panel_context_save(&$handler, $update) { } /** - * Load the arguments which are used to populate the base context of a ctools - * task plugin. - * - * @example - * $args = esi_panels__get_base_context_arguments('node_view', array(1)); - * Returns array(node_load(1)); - * - * @param String $task - * The ctools task. - * @param String $subtask - * The subtask of the ctools task (if applicable). - * @param Array $args - * Arguments to pass to the argument constructor (if applicable). - * - * @return Array - * Array of arguments to pass to the ctools context constructor. - */ -function esi_panels__get_base_context_arguments($task, $subtask = '', $args = array()) { - // A core bug is preventing module_invoke_all() from lazy-loading according - // to the hook_hook_info() definitions. - foreach (module_list(FALSE, FALSE, TRUE) as $module) { - module_load_include('inc', $module, $module . '.esi_panels'); - } - - return module_invoke_all('esi_panels_context_arguments', $task, $subtask, $args); -} - - -/** * Check if any panes are configured to use ESI. * - * @param Object $display - * A panels_display object. + * @param ibject $display + * A panels_display object. * - * @return Boolean + * @return boolean + * Whether any panes use the ESI cache. */ function _esi_panels__display_uses_esi(panels_display $display) { // Iterate each pane. foreach ($display->content as $pid => $pane) { // Any single pane implementing ESI is enough to return TRUE. - if (!empty($pane->cache) && $pane->cache['method'] == 'esi') { + // Allow other cache plugins whose names start with "esi". + if (!empty($pane->cache) && strpos($pane->cache['method'], 'esi') === 0) { return TRUE; } } return FALSE; } - -/** - * Reverse the $display->cache_key encoding to get the task name. - * - * @param String $cache_key - * The cache key used on a display. - * - * @return String - * The task name of the task handler. - */ -function _esi_panels__get_taskname($cache_key) { - // $display->cache_key = 'panel_context:' . $task_name . ':' . $handler->name; - if (preg_match('/^panel_context:([^:]+):.*$/', $cache_key, $matches)) { - return $matches[1]; - } -} - -/** - * Reverse the $display->cache_key encoding to get the task name (and sub-task - * if used). - * - * @param String $task_name - * The task key, as used by a display cache_key. - * - * @return Array - * - 0 => Name of the task. - * - 1 => Name of the subtask (or '' if not set). - */ -function _esi_panels__get_task_identifier($task_name) { - if (strpos('-', $task_name)) { - list ($task, $subtask) = explode('-', $task_name, 2); - return array($task, $subtask); - } - else { - return array($task_name, ''); - } -} diff --git a/modules/esi_panels/plugins/cache/esi.inc b/modules/esi_panels/plugins/cache/esi.inc index 839e703..d5d7bed 100644 --- a/modules/esi_panels/plugins/cache/esi.inc +++ b/modules/esi_panels/plugins/cache/esi.inc @@ -1,10 +1,11 @@ t("ESI"), 'description' => t('ESI caching is a proxy-based cache. Panes are replaced by tags and requested separately by the proxy.'), @@ -26,89 +27,68 @@ $plugin = array( * Set cached content. */ function esi_panels_esi_cache_set_cache($conf, $content, $display, $args, $contexts, $pane = NULL) { - // $content will be a panels_cache_object; $content->content is the actual - // content. - // DO NOTHING! + if (!$conf['use_object_cache']) { + return FALSE; + } + + $cid = esi_panels_esi_cache_get_id($conf, $display, $args, $contexts, $pane); + cache_set($cid, $content); } /** * Get cached content. */ function esi_panels_esi_cache_get_cache($conf, $display, $args, $contexts, $pane = NULL) { - // If this 'cache get' function returns FALSE, panels will invoke and render - // the pane in the normal way. - return FALSE; - - - // @TODO: is this necessary now? - // // Check that the pane is indeed meant to be served by ESI. - // if (empty($pane) || empty($pane->cache['method']) || $pane->cache['method'] != 'esi') { - // return FALSE; - // } - // - // // Panels expects a cache-handler to return an object similar to the object - // // returned by cache_get(). Faking a cached object allows us to return a - // // simple ESI tag instead. - // - // // Fake the cached object. - // $cache = new stdClass; - // $cache->data = new panels_cache_object(); - // $cache->data->content = new stdClass; - // $cache->data->head = NULL; - // $cache->data->css = array(); - // $cache->data->js = array(); - // $cache->data->tokens = array(); - // $cache->data->ready = TRUE; - // - // // Fill in the content with the ESI code - // $cache->data->content->content = 'This is my content, tell me yours.'; - // - // // Add in extras if missing. - // if (!isset($cache->data->content->module)) { - // $cache->data->content->module = isset($pane->type) ? $pane->type : 'esi_panels'; - // } - // if (!isset($cache->data->content->delta)) { - // $cache->data->content->delta = isset($pane->subtype) ? $pane->subtype : 'esi'; - // } - // - // // Return the cache object. - // return $cache->data; -} + if (!$conf['use_object_cache']) { + return FALSE; + } + $cid = esi_panels_esi_cache_get_id($conf, $display, $args, $contexts, $pane); + $cache = cache_get($cid, 'cache'); + if (!$cache) { + return FALSE; + } + if ((time() - $cache->created) > $conf['esi_ttl']) { + return FALSE; + } + + return $cache->data; +} /** * Admin-settings form for configuring the ESI cache on panel panes. */ function esi_panels_esi_cache_settings_form($conf, $display, $pid) { - // drupal_set_message(print_r($display,true)); - // Use the TTL from the config if provided, or a suitable default. - $esi_ttl = isset($conf['esi_ttl']) ? $conf['esi_ttl'] : (int)variable_get('esi_panels_esi_default_ttl', ESI_DEFAULT_TTL); + $esi_ttl = isset($conf['esi_ttl']) ? $conf['esi_ttl'] : (int) variable_get('esi_panels_esi_default_ttl', ESI_DEFAULT_TTL); $pane = (isset($display->content[$pid])) ? $display->content[$pid] : FALSE; - // Load up the pane information to examine the context. - + // Load up the pane information to examine the context. $form['esi_ttl'] = array( - '#title' => t('Cache Maximum Age (TTL)'), + '#title' => t('Cache maximum age (TTL)'), '#type' => 'select', '#options' => esi_max_age_options($esi_ttl), '#default_value' => $esi_ttl, '#description' => t('External page caches (proxy/browser) will not deliver cached paged older than this setting; time to live in short.'), ); - - + // Load up the pane information to examine the context. + $form['use_object_cache'] = array( + '#title' => t('Use object caching'), + '#type' => 'checkbox', + '#default_value' => !empty($conf['use_object_cache']), + '#description' => t('Also use object caching for the content of ESI panes. (Be careful - this may cause cache coherency problems.)'), + ); $form['override_context'] = array( '#title' => t('Override cache context'), '#type' => 'fieldset', '#description' => t('The context requested by the pane (and provided by the parent panel) will be used by default to create separate versions of the pane for each context.'), '#collapsible' => TRUE, - '#collapsed' => empty($conf['esi_override_context']), + '#collapsed' => empty($conf['override_context']['esi_override_context']), ); - // Describe the current panels context: placeholder if there is no context. $form['override_context']['pane_context'] = array( '#type' => 'item', @@ -117,6 +97,7 @@ function esi_panels_esi_cache_settings_form($conf, $display, $pid) { '#suffix' => '', '#markup' => t("This pane doesn't use panels context."), ); + // If there is context, add to the placeholder. if (!empty($pane->configuration['context'])) { // The context is a simple identifier - e.g. "argument_entity_id:node_1". @@ -127,34 +108,17 @@ function esi_panels_esi_cache_settings_form($conf, $display, $pid) { $form['override_context']['pane_context']['#markup'] = t($context->identifier); } - - $form['override_context']['esi_override_context'] = array( '#title' => t('Override cache context'), '#type' => 'checkbox', - '#default_value' => !empty($conf['esi_override_context']), + '#default_value' => !empty($conf['override_context']['esi_override_context']), ); - $form['override_context']['esi_overridden_context__pane_context'] = array( - '#title' => t('Cache per pane-context'), - '#description' => t("The contents of this pane changes depending on the pane context provided by the panel."), - '#type' => 'checkbox', - '#default_value' => !empty($conf['esi_overridden_context__pane_context']), - // If this pane doesn't provide context, this option isn't applicable. - '#disabled' => empty($pane->configuration['context']), - - // @TODO: use states (once control of radios through states is patched). - // '#states' => array( - // 'disabled' => array( // override_context to take. - // ':input[name="settings[override_context][esi_override_context]"]' => array('unchecked' => TRUE), - // ), - // ), - ); $form['override_context']['esi_overridden_context__page'] = array( '#title' => t('Cache per page'), '#description' => t("The contents of this pane changes depending on the page on which it's displayed."), '#type' => 'checkbox', - '#default_value' => !empty($conf['esi_overridden_context__page']), + '#default_value' => !empty($conf['override_context']['esi_overridden_context__page']), // @TODO: use states (once control of radios through states is patched). // '#states' => array( @@ -163,6 +127,7 @@ function esi_panels_esi_cache_settings_form($conf, $display, $pid) { // ), // ), ); + $form['override_context']['esi_overridden_context__user'] = array( '#title' => t('User context'), '#description' => t("How a user or their roles affect the pane."), @@ -172,7 +137,7 @@ function esi_panels_esi_cache_settings_form($conf, $display, $pid) { DRUPAL_CACHE_PER_ROLE => t("Pane changes according to the current user's roles"), DRUPAL_CACHE_PER_USER => ('Pane changes according to the current user'), ), - '#default_value' => isset($conf['esi_overridden_context__user']) ? $conf['esi_overridden_context__user'] : DRUPAL_CACHE_PER_ROLE, + '#default_value' => isset($conf['override_context']['esi_overridden_context__user']) ? $conf['override_context']['esi_overridden_context__user'] : DRUPAL_CACHE_PER_ROLE, // @TODO: use states (once control of radios through states is patched). // '#states' => array( @@ -181,6 +146,56 @@ function esi_panels_esi_cache_settings_form($conf, $display, $pid) { // ), // ), ); - + return $form; } + +/** + * Figure out an id for object caching based upon input and settings. + */ +function esi_panels_esi_cache_get_id($conf, $display, $args, $contexts, $pane) { + $id = 'esi_panels_esi_cache'; + + // This is used in case this is an in-code display, which means did will be something like 'new-1'. + if (isset($display->owner) && isset($display->owner->id)) { + $id .= ':' . $display->owner->id; + } + $id .= ':' . $display->did; + + if ($pane) { + $id .= ':' . $pane->pid; + } + + if (user_access('view pane admin links')) { + $id .= ':admin'; + } + + // @TODO: Fix this to align with how ESI.module works. + switch ($conf['granularity']) { + case 'args': + foreach ($args as $arg) { + $id .= ':' . $arg; + } + break; + + case 'context': + if (!is_array($contexts)) { + $contexts = array($contexts); + } + foreach ($contexts as $context) { + if (isset($context->argument)) { + $id .= ':' . $context->argument; + } + } + } + if (module_exists('locale')) { + global $language; + $id .= ':' . $language->language; + } + + if (!empty($pane->configuration['use_pager'])) { + $id .= ':p' . check_plain($_GET['page']); + } + + return $id; +} diff --git a/modules/esi_panels/plugins/display_renderers/esi_panels_renderer_esi.class.php b/modules/esi_panels/plugins/display_renderers/esi_panels_renderer_esi.class.php index 27b1f9e..098108f 100644 --- a/modules/esi_panels/plugins/display_renderers/esi_panels_renderer_esi.class.php +++ b/modules/esi_panels/plugins/display_renderers/esi_panels_renderer_esi.class.php @@ -7,7 +7,6 @@ * they're bundled together into regions. */ class esi_panels_renderer_esi extends panels_renderer_standard { - /** * Prepare the list of panes to be rendered, accounting for visibility/access * settings and rendering order. @@ -132,7 +131,7 @@ class esi_panels_renderer_esi extends panels_renderer_standard { */ function handle_esi_pane($pane) { $url = url(esi_panels_url($pane, $this->display), array('absolute' => TRUE)); - $render =array( + $render = array( '#type' => 'esi', '#url' => $url, );