You are here

panels_page.module in Panels 6.2

Same filename and directory in other branches
  1. 5.2 panels_page/panels_page.module

panels_page.module

This module is the primary implementation pf the Panels API. It provides Panel pages that are used to create full page layouts. It utilizes numerous .inc files for various segments of its functionality. These includes are lazy-loaded (some through the menu system, some internally) in order to keep code weight to an absolute minimum.

File

panels_page/panels_page.module
View source
<?php

/**
 * @file panels_page.module
 *
 * This module is the primary implementation pf the Panels API. It provides
 * Panel pages that are used to create full page layouts. It utilizes numerous
 * .inc files for various segments of its functionality. These includes are
 * lazy-loaded (some through the menu system, some internally) in order to keep
 * code weight to an absolute minimum.
 */

/**
* Bitvalues used to determine the status of the panels_page override at a given
* path. These are set during menu rebuild and used at render-time to speed
* along calculations somewhat.
*
/**
* Indicates that the item utilizes a dynamic path - that is, it has a
*  wildcard (%) in its path.
*/
define('PANELS_IS_DYNAMIC', 1);

/**
 * Indicates that there is at least one stored loader that the router item at
 * this path has overridden, and that Panels has stored for this router item.
 */
define('PANELS_HAS_FALLBACK_ROUTER', 1 << 1 | PANELS_IS_DYNAMIC);

/**
 * Panels display objects with this bitval set share the provided path with
 * another panel.
 */
define('PANELS_PID_SHARES_PATH', 1 << 2 | PANELS_IS_DYNAMIC);

/**
 * Indicates that the panel page is being loaded from code (i.e., from
 * hook_default_panel_pages).
 */
define('PANELS_DEFAULT', 1 << 3);

/**
 * Indicates that the panel_page is an overridden default.
 */
define('PANELS_OVERRIDDEN', 1 << 4 | PANELS_DEFAULT);

/**
 * Implementation of hook_help().
 */
function panels_page_help($path, $arg) {
  $output = NULL;
  switch ($path) {
    case 'admin/panels/panel-page':
    case 'admin/panels/panel-page/list':
      $output = '<p>';
      $output .= t('You may peruse a list of your current panels layouts and edit them, or click add to create a new page.');
      $output .= '</p>';
      break;
    case 'admin/panels/panel-page/add':
      $output = '<p>';
      $output .= t('Choose a layout for your new page from the list below.');
      $output .= '</p>';
      break;
  }
  return $output;
}

/**
 * Implementation of hook_theme().
 */
function panels_page_theme() {
  $theme = array();
  $theme['panels_page_render_form'] = array(
    'arguments' => array(
      'form',
    ),
  );
  return $theme;
}

/**
 * Implementation of hook_perm().
 */
function panels_page_perm() {
  return array(
    'create panel-pages',
    'access all panel-pages',
  );
}
function panels_page_menu() {
  panels_page_load_include('menu');
  panels_page_load_include('read');
  $items = panels_page_admin_static_menu_items();
  $items = panels_page_admin_dynamic_menu_items($items);
  return $items;
}

/**
 * Implementation of hook_menu_alter().
 *
 * Delegates to an .inc file to reduce code load overhead.
 */
function panels_page_menu_alter(&$callbacks) {
  return _panels_page_menu_alter($callbacks);
}

/*function panels_page_menu_link_alter(&$item, &$menu) {

}*/

/**
 * Menu loader for some panels_page admin screens. Loads the panels_page
 * specified by the name arg, induces a 404 when a bad arg is provided.
 *
 * Use the empty string so that the behavior of callers that don't pass arg(6)
 * will be the same as callers who do pass arg(6), but arg(6) is empty.
 *
 * @param string $name
 * @param string $which
 * @return mixed
 */
function panels_page_admin_load() {

  // Lets us tell if this is the first time through the function. Fugly hack to
  // do it this way, but this gets called numerous times by theme(), which makes
  // it the only way to avoid generating superfluous object cache items due to
  // later indirect calls to this function made by access checking within
  // theme(). They entries get get cleared later, but it's still icky, so we
  // head it off here.
  static $first = TRUE;
  panels_page_load_include('write');
  panels_load_include('plugins');
  $args = func_get_args();
  $name = array_shift($args);
  $which = array_shift($args);
  if (function_exists($which) && $first) {
    return $which($name, array_shift($args));
  }
  $first = FALSE;
  $panel_page = panels_page_load($name);
  panels_page_fetch_display($panel_page, $which);
  panels_page_set_current($panel_page);
  return is_object($panel_page) && !empty($panel_page->pid) ? $panel_page : FALSE;
}

/**
 * Menu loader for some panels_page admin screens. Loads the panels_page
 * specified by the name arg from the edit cache, or induces a 404 when a bad
 * arg is provided.
 *
 * Use the empty string so that the behavior of callers that don't pass arg(6)
 * will be identical to callers who do pass arg(6), but arg(6) is empty.
 *
 * @param string $name
 * @param string $which
 * @return mixed
 */
function panels_page_admin_cache_load($name, $which = '') {
  static $panel_page;
  if (empty($panel_page)) {
    if (!empty($_POST)) {
      $panel_page = panels_cache_get('panel_object:panel_page', $name);
    }
    else {
      $panel_page = panels_page_load($name);
      panels_page_fetch_display($panel_page, $which);
      panels_cache_set('panel_object:panel_page', $name, $panel_page);
    }
  }
  if (is_object($panel_page) && !empty($panel_page->pid)) {
    panels_page_set_current($panel_page);
    return $panel_page;
  }
  return FALSE;
}

/**
 * Kluges local tasks to basically work like menu links instead of router items.
 *
 * ...like they should. This function is headdesking's raison d'etre.
 */
function panels_page_concession_to_borked_tabs($name) {
  static $first = TRUE;
  if (!user_access('create panel-pages')) {
    return FALSE;
  }
  if (!$first || panels_page_climb_stack()) {
    $panel_page = panels_page_get_current();
    return $panel_page->name == $name;
  }
  $first = FALSE;
  return TRUE;
}

/**
 * Checks to see if theme() is in the current call stack.
 *
 * Let there be kluge. Horrible, awful, kitten-mass-murdering KLUGE.
 *
 */
function panels_page_climb_stack() {
  $backtrace = debug_backtrace();
  $result = array_filter($backtrace, '_panels_page_climb_stack');
  return !empty($result);
}

/**
 * Kluge's little helper. Kinda like Santa's little helper. Except not.
 */
function _panels_page_climb_stack($call) {
  return $call['function'] == 'theme';
}

/**
 * Check whether the current page request is allowed.
 *
 * Note that this handler is ONLY used by static panel_pages; the rest are all
 * handled internally by the master loader.
 *
 * TODO this is still fairly hackish, and we also really may need to add support
 * for these checks for dynamic panels_pages...
 *
 * @return boolean
 */
function panels_page_access_handler() {
  panels_page_load_include('read');
  $args = func_get_args();
  $name = array_shift($args);
  return (bool) panels_page_access($name);
}

/**
 * Execute the active page rendering callback.
 *
 * This is the unified handler through which ALL (non-admin) panels_page-touched
 * callbacks pass. It takes the callback and arguments calculated by the main
 * brain, panels_page_get_loader_data(), and fires out the callback with its
 * arguments.
 *
 * @return mixed
 */
function panels_page_render_handler() {
  $args = func_get_args();

  // FIXME all of these are wrapped in if statements b/c of the 403/404 possibility
  if ($loader_data = panels_page_master_loader($args)) {
    return call_user_func_array($loader_data->page_callback, $loader_data->page_arguments);
  }
}
function panels_page_static_render_handler() {
  $args = func_get_args();
  if ($loader_data = panels_page_master_loader($args)) {
    return panels_page_render_page_normal($loader_data->panel_page, array());
  }
}
function panels_page_title_handler() {
  $args = func_get_args();
  if ($loader_data = panels_page_master_loader($args)) {
    return $loader_data->fallback ? _menu_item_localize($loader_data->router_item, $loader_data->map) : $loader_data->title;
  }
}

/**
 * Wrapper function for the actual panels_page master loader,
 * _panels_page_master_loader().
 *
 * @param array $args
 *   Any additional argument data; varies significantly from one panel_page to
 *   the next.
 * @return array
 *   The $loader_data corresponding to the request data we've passed in.
 */
function panels_page_master_loader($args) {

  // Get the unique name of the panel_page, which is always the arg on top of
  // the $args array stack
  $name = array_shift($args);
  return _panels_page_master_loader($name, $args);
}

/**
 * Determine the render-time behavior of panels_page.
 *
 * This function is basically the brains of the dynamic override system.
 *
 * @param string $name
 * @param array $args
 * @return array $load
 */
function _panels_page_master_loader($name, $args) {
  static $loader_data = array();
  if (!empty($loader_data[$name])) {
    return $loader_data[$name];
  }
  $load = new stdClass();
  $loader_data[$name] =& $load;
  $load->panel_args = array();
  panels_load_include('plugins');
  panels_page_load_include('read');
  $panel_page = panels_page_load($name);
  $panel_page->context = array();

  // Handle static pages then duck out early.
  if (!($panel_page->load_flags & PANELS_IS_DYNAMIC)) {
    if ((bool) panels_page_access($panel_page->name)) {
      panels_page_load_include('render');
      panels_page_fetch_primary_display($panel_page);
      panels_page_prepare_panels_render($load, $panel_page);
    }
    return $load;
  }

  // Construct $panel_page->context and determine if we fall back.
  _panels_page_construct_argument_contexts($load, $panel_page, $args);

  // If we've determined that we're falling back...
  if ($load->fallback === TRUE) {

    // ...then bail out and do it.
    return panels_page_prepare_fallback_render($load, _panels_page_rebuild_menu_map($args, array_keys(explode('/', $panel_page->path), '%')));
  }

  // By now we are 100% certain that a panel_page render should occur, so check
  // the panels_page native access function. If that passes, then include the
  // the render inc file and proceed inside there.
  $load->access = panels_page_access($panel_page->name);
  if (empty($load->access)) {
    return drupal_access_denied();
  }
  panels_page_load_include('render');
  return panels_page_prepare_panels_render($load, $panel_page);
}

/**
 * Extracts context data from provided URLs; helper function for
 * _panels_page_master_loader().
 *
 * @param object $load
 * @param object $panel_page
 * @param array $args
 */
function _panels_page_construct_argument_contexts(&$load, &$panel_page, $args) {

  // At this point, we know we're handling a dynamic/override panels_page.
  // Start off by assuming that we won't fall back.
  $load->fallback = FALSE;

  // TODO Multiple p-pages per path will necessitate more complex logic here
  $load_objects = array();
  foreach ($panel_page->arguments as $id => $argument) {
    $ignore = $argument['default'] == 'ignore';

    // FIXME The directness of this association is questionable
    $load_objects[$id] = array_shift($args);
    $context = !empty($load_objects[$id]) ? panels_argument_get_context($argument, $load_objects[$id]) : PANELS_ARG_IS_BAD;
    if (!is_a($context, 'panels_required_context') && !is_a($context, 'panels_context')) {
      if ($context & PANELS_ARG_USE_FALLBACK) {
        if ($panel_page->load_flags & PANELS_HAS_FALLBACK_ROUTER) {
          $load->fallback = TRUE;
          break;
        }
        else {
          if ($ignore) {
            continue;
          }
        }
      }
      else {
        if ($context & PANELS_ARG_IS_BAD && $ignore) {
          continue;
        }
      }

      // Prep a 404 and bail out if we get this far.
      return _panels_page_not_found($load);
    }
    $panel_page->context[panels_context_id($argument, 'argument')] = $context;
    $load->panel_args[] = $context
      ->get_original_argument();
  }
}
function _panels_page_not_found(&$load) {
  $load->page_callback = 'drupal_not_found';
  $load->page_arguments = array();
  return FALSE;
}

/**
 * Rebuild a drupal menu system-style $map using data passed in to the panels
 * callback handlers from the menu system.
 *
 * @param array $map
 *   A incomplete menu map - it has only $_GET['q'] data initially - that this
 *   function will rebuild.
 * @param array $load_objects
 *   The set of menu-loader-returned objects provided by the menu system to the
 *   panels callback handlers.
 * @param array $positions
 *   The positions within the path map (i.e., arg(0), arg(1), etc...) that the
 *   loader objects correspond to.
 * @return array $map
 *   The rebuilt menu map.
 */
function _panels_page_rebuild_menu_map($load_objects, $positions) {
  $map = explode('/', $_GET['q']);
  foreach ($positions as $key => $position) {
    $map[$position] = $load_objects[$key];
  }
  return $map;
}

/**
 * Prepare the fallback router, update the menu cache with it, then load up
 * our loader item and initiate fallback.
 *
 * Partially lifted from menu_get_item().
 *
 * @param object $load
 * @param array $map
 * @return object $load
 */
function panels_page_prepare_fallback_render(&$load, $map) {
  $original_map = arg(NULL, $_GET['q']);
  $default_menu = menu_get_item($_GET['q']);
  $parts = array_slice($original_map, 0, MENU_MAX_PARTS);
  list($ancestors, $placeholders) = menu_get_ancestors($parts);
  if (!($router_item = db_fetch_array(db_query_range('SELECT * FROM {panels_page_router_store} WHERE path IN (' . implode(',', $placeholders) . ') ORDER BY fit DESC', $ancestors, 0, 1)))) {
    return _panels_page_not_found($load);
  }

  // FIXME calling _menu_translate would result in an incomplete load
  // $map = _menu_translate($router_item, $original_map);
  $load->map = $map;
  $load->page_arguments = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts']));
  $load->page_callback = $router_item['page_callback'];
  $load->router_item = $router_item;

  // If we did not override the default menu callback, stop here.
  if ($router_item['page_callback'] != $default_menu['page_callback']) {
    return $load;
  }

  // Fix for lack of the _menu_translate() function.  We crib some of that function here.
  _menu_check_access($router_item, $map);

  // For performance, don't localize an item the user can't access.
  if ($router_item['access']) {
    _menu_item_localize($router_item, $map);
  }
  menu_set_item($_GET['q'], $router_item);
  return $load;
}

/**
 * Figure out if a panel is the current page; mostly useful in theming.
 *
 * This function will return NULL until panels_page_set_current() has been
 * called and loaded with data.
 */
function panels_page_get_current() {

  // Take advantage of our .inc organization to know if it's at all possible
  // that there's a current page to be retrieved.
  if (!function_exists('panels_page_set_current')) {
    return FALSE;
  }
  $fubar = NULL;

  // PHP4 compatibility
  return panels_page_set_current($fubar);
}

/**
 * Theme function to render our panel as a form.
 *
 * We need to do this so that the entire display is inside the form.
 */
function theme_panels_page_render_form($form) {
  $form['#children'] = panels_render_display($form['#display']);
  return theme('form', $form);
}

/**
 * Wrapper for panels_load_include() that specifically targets panels_page
 * include files.
 *
 * @param string $include
 *   The name of the panels_page include file, without the .inc extension.
 */
function panels_page_load_include($include) {
  panels_load_include($include, 'panels_page/panels_page.');
}

/**
 * Implementation of hook_panels_exportables().
 */
function panels_page_panels_exportables($op = 'list', $panels = NULL, $name = 'foo') {
  static $all_panels = NULL;
  if ($op == 'list') {
    if (empty($all_panels)) {
      panels_page_load_include('read');
      $all_panels = panels_page_load_all();
    }
    foreach ($all_panels as $name => $panel) {
      $return[$name] = check_plain($name) . ' (' . check_plain(panels_page_get_title($panel)) . ')';
    }
    return $return;
  }
  if ($op == 'export') {
    panels_page_load_include('write');
    $code = "/**\n";
    $code .= " * Implementation of hook_default_panel_pages()\n";
    $code .= " */\n";
    $code .= "function " . $name . "_default_panel_pages() {\n";
    foreach ($panels as $panel => $truth) {
      $code .= panels_page_export($all_panels[$panel], '  ');
      $code .= '  $pages[\'' . check_plain($panel) . '\'] = $page;' . "\n\n\n";
    }
    $code .= "  return \$pages;\n";
    $code .= "}\n";
    return $code;
  }
}

/**
 * Sanitize a panel with safe, empty values.
 *
 * Used by various panels_page CRUD-type functions.
 */
function panels_page_sanitize($page) {
  foreach (array(
    'arguments',
    'displays',
    'contexts',
    'relationships',
    'switcher_options',
  ) as $id) {
    if (!isset($page->{$id}) || !is_array($page->{$id})) {
      $page->{$id} = array();
    }
  }
  return $page;
}

Functions

Namesort descending Description
panels_page_access_handler Check whether the current page request is allowed.
panels_page_admin_cache_load Menu loader for some panels_page admin screens. Loads the panels_page specified by the name arg from the edit cache, or induces a 404 when a bad arg is provided.
panels_page_admin_load Menu loader for some panels_page admin screens. Loads the panels_page specified by the name arg, induces a 404 when a bad arg is provided.
panels_page_climb_stack Checks to see if theme() is in the current call stack.
panels_page_concession_to_borked_tabs Kluges local tasks to basically work like menu links instead of router items.
panels_page_get_current Figure out if a panel is the current page; mostly useful in theming.
panels_page_help Implementation of hook_help().
panels_page_load_include Wrapper for panels_load_include() that specifically targets panels_page include files.
panels_page_master_loader Wrapper function for the actual panels_page master loader, _panels_page_master_loader().
panels_page_menu
panels_page_menu_alter Implementation of hook_menu_alter().
panels_page_panels_exportables Implementation of hook_panels_exportables().
panels_page_perm Implementation of hook_perm().
panels_page_prepare_fallback_render Prepare the fallback router, update the menu cache with it, then load up our loader item and initiate fallback.
panels_page_render_handler Execute the active page rendering callback.
panels_page_sanitize Sanitize a panel with safe, empty values.
panels_page_static_render_handler
panels_page_theme Implementation of hook_theme().
panels_page_title_handler
theme_panels_page_render_form Theme function to render our panel as a form.
_panels_page_climb_stack Kluge's little helper. Kinda like Santa's little helper. Except not.
_panels_page_construct_argument_contexts Extracts context data from provided URLs; helper function for _panels_page_master_loader().
_panels_page_master_loader Determine the render-time behavior of panels_page.
_panels_page_not_found
_panels_page_rebuild_menu_map Rebuild a drupal menu system-style $map using data passed in to the panels callback handlers from the menu system.

Constants

Namesort descending Description
PANELS_DEFAULT Indicates that the panel page is being loaded from code (i.e., from hook_default_panel_pages).
PANELS_HAS_FALLBACK_ROUTER Indicates that there is at least one stored loader that the router item at this path has overridden, and that Panels has stored for this router item.
PANELS_IS_DYNAMIC Bitvalues used to determine the status of the panels_page override at a given path. These are set during menu rebuild and used at render-time to speed along calculations somewhat.
PANELS_OVERRIDDEN Indicates that the panel_page is an overridden default.
PANELS_PID_SHARES_PATH Panels display objects with this bitval set share the provided path with another panel.