Skip to content
panels_page.module 13.8 KiB
Newer Older
 * 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.
 *
 * TODO Reduce the number of include loads calls...it's amateurish.
/**
 * 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);
function panels_page_help($path, $arg) {
  $output = NULL;
    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');
  $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
function panels_page_admin_load($name, $which = '') {
  $panel_page = panels_page_load($name);
  panels_page_fetch_display($panel_page, $which);
  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 = '') {
  panels_page_load_include('write');
  panels_load_include('plugins');

  if (!empty($_POST)) {
    // FIXME With the way the menu system currently works, this is likely to be
    // fired AT LEAST six times per admin page request. BOLLOCKS. Time to
    // figure out yet another fracking caching method...maybe just right here?
    $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);
  }
  return is_object($panel_page) && !empty($panel_page->pid) ? $panel_page : FALSE;
/**
 * 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() {
  $name = array_shift($args);
  $access = panels_page_access($name);
  return !empty($access);
 * 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 404/500 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
function _panels_page_master_loader($name, $args) {
  if (!empty($loader_data[$name])) {
    return $loader_data[$name];
  $panel_page = panels_page_load($name);
  // 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_fetch_primary_display($panel_page);
      panels_page_prepare_panels_render($load, $panel_page, $args);
  // 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, bail out and do it.
  if ($load->fallback === TRUE) {
    return panels_page_prepare_fallback_render($load,
      _panels_page_rebuild_menu_map(
        explode('/', $_GET['q']),
        array_keys(explode('/', $panel_page->path), '%'),
        $args_clone));
  }

  // 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, $args);
}

/**
 * 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.
  // TODO Multiple p-pages per path will necessitate more complex logic here
Sam Boyer's avatar
Sam Boyer committed
  $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;
      // TODO Are there other cases? If not, this else is an unnecessary overspecification
      else if ($context & PANELS_ARG_IS_BAD && $ignore) {
      // Prep a 404 and bail out if we get this far.
      $load->page_callback = 'drupal_not_found';
      $load->page_arguments = array();
      return FALSE;
    }
    $panel_page->context[panels_argument_context_id($argument)] = $context;
  }
Sam Boyer's avatar
Sam Boyer committed
/**
 * Rebuild a drupal menu system-style $map using data passed in to the panels
 * callback handlers from the menu system.
Sam Boyer's avatar
Sam Boyer committed
 *
 * @param array $map
 *   A incomplete menu map - it has only $_GET['q'] data initially - that this
Sam Boyer's avatar
Sam Boyer committed
 * @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.
Sam Boyer's avatar
Sam Boyer committed
 */
Sam Boyer's avatar
Sam Boyer committed
function _panels_page_rebuild_menu_map($map, $load_objects, $positions) {
  foreach ($positions as $key => $position) {
    $map[$position] = $load_objects[$key];
  }
Sam Boyer's avatar
Sam Boyer committed
}

function panels_page_prepare_fallback_render(&$load, $map) {
Sam Boyer's avatar
Sam Boyer committed
  // Lifted from menu_get_item().
  list($ancestors, $placeholders) = menu_get_ancestors(explode('/', $quickref->path));
  if ($router_item = db_fetch_array(db_query_range('SELECT * FROM {panels_page_menu_store} WHERE path IN ('. implode (',', $placeholders) .') ORDER BY fit DESC', $ancestors, 0, 1))) {
    // FIXME Ideally we could skip some of this, much has already been loaded.
    $map = _menu_translate($router_item, $original_map);
    if ($router_item['access']) {
      $load->map = $map;
      $load->page_arguments = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts']));
Sam Boyer's avatar
Sam Boyer committed

  $load->page_callback = $router_item['page callback'];
  $load->router_item   = $router_item;
/**
 * 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
 * properly called and loaded.
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)) {
      $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') {
    $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_page($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.
 */
function panels_page_sanitize($page) {
  foreach (array('arguments', 'displays', 'contexts', 'relationships', 'switcher_options') as $id) {
    if (!isset($page->$id) || !is_array($page->$id)) {