Newer
Older
Earl Miles
committed
<?php
// $Id$
/**
* @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.
Sam Boyer
committed
*
* TODO Reduce the number of include loads calls...it's amateurish.
Earl Miles
committed
*/
Sam Boyer
committed
/**
* 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.
Sam Boyer
committed
*
Sam Boyer
committed
/**
* Indicates that the item utilizes a dynamic path - that is, it has a
* wildcard (%) in its path.
Sam Boyer
committed
*/
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);
Sam Boyer
committed
* Panels display objects with this bitval set share the provided path with
* another panel.
define('PANELS_PID_SHARES_PATH', 1 << 2 | PANELS_IS_DYNAMIC);
Earl Miles
committed
/**
* Implementation of hook_help().
*/
Sam Boyer
committed
function panels_page_help($path, $arg) {
Sam Boyer
committed
switch ($path) {
Earl Miles
committed
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;
}
Sam Boyer
committed
/**
* Implementation of hook_theme().
*/
function panels_page_theme() {
$theme = array();
$theme['panels_page_render_form'] = array(
'arguments' => array('form'),
);
return $theme;
}
Earl Miles
committed
/**
* 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');
Sam Boyer
committed
panels_page_load_include('read');
$items = panels_page_admin_static_menu_items();
$items = panels_page_admin_dynamic_menu_items($items);
return $items;
}
Earl Miles
committed
/**
Sam Boyer
committed
* Implementation of hook_menu_alter().
*
* Delegates to an .inc file to reduce code load overhead.
Earl Miles
committed
*/
function panels_page_menu_alter(&$callbacks) {
return _panels_page_menu_alter($callbacks);
}
/*function panels_page_menu_link_alter(&$item, &$menu) {
}*/
/**
Sam Boyer
committed
* 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.
*
Sam Boyer
committed
* @param string $name
* @param string $which
Sam Boyer
committed
* @return mixed
*/
Sam Boyer
committed
function panels_page_admin_load($name, $which = '') {
Sam Boyer
committed
panels_page_load_include('write');
panels_load_include('plugins');
Sam Boyer
committed
Sam Boyer
committed
$panel_page = panels_page_load($name);
Sam Boyer
committed
panels_page_fetch_display($panel_page, $which);
Sam Boyer
committed
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.
*
Sam Boyer
committed
* @param string $name
* @param string $which
Sam Boyer
committed
* @return mixed
*/
function panels_page_admin_cache_load($name, $which = '') {
Sam Boyer
committed
panels_page_load_include('write');
panels_load_include('plugins');
Sam Boyer
committed
if (!empty($_POST)) {
// FIXME With the way the menu system currently works, this is likely to be
Sam Boyer
committed
// fired AT LEAST six times per admin page request. BOLLOCKS. Time to
// figure out yet another fracking caching method...maybe just right here?
Sam Boyer
committed
$panel_page = panels_cache_get('panel_object:panel_page', $name);
}
else {
$panel_page = panels_page_load($name);
panels_page_fetch_display($panel_page, $which);
Sam Boyer
committed
panels_cache_set('panel_object:panel_page', $name, $panel_page);
}
return is_object($panel_page) && !empty($panel_page->pid) ? $panel_page : FALSE;
Earl Miles
committed
}
/**
* 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.
*
Sam Boyer
committed
* 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() {
Sam Boyer
committed
panels_page_load_include('read');
$args = func_get_args();
Sam Boyer
committed
$name = array_shift($args);
$access = panels_page_access($name);
return !empty($access);
}
/**
* Execute the active page rendering callback.
*
Sam Boyer
committed
* 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();
Sam Boyer
committed
// FIXME all of these are wrapped in if statements b/c of the 404/500 possibility
Sam Boyer
committed
if ($loader_data = panels_page_master_loader($args)) {
Sam Boyer
committed
return call_user_func_array($loader_data->page_callback, $loader_data->page_arguments);
}
}
function panels_page_static_render_handler() {
$args = func_get_args();
Sam Boyer
committed
if ($loader_data = panels_page_master_loader($args)) {
Sam Boyer
committed
return panels_page_render_page_normal($loader_data->panel_page, array());
}
}
function panels_page_title_handler() {
$args = func_get_args();
Sam Boyer
committed
if ($loader_data = panels_page_master_loader($args)) {
Sam Boyer
committed
return $loader_data->fallback ? _menu_item_localize($loader_data->router_item, $loader_data->map) : $loader_data->title;
}
}
Sam Boyer
committed
/**
* 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.
*/
Sam Boyer
committed
function panels_page_master_loader($args) {
Sam Boyer
committed
// Get the unique name of the panel_page, which is always the arg on top of
// the $args array stack
$name = array_shift($args);
Sam Boyer
committed
return _panels_page_master_loader($name, $args);
Sam Boyer
committed
}
* Determine the render-time behavior of panels_page.
*
* This function is basically the brains of the dynamic override system.
*
Sam Boyer
committed
* @param string $name
* @param array $args
* @return array $loader_data
Sam Boyer
committed
function _panels_page_master_loader($name, $args) {
static $loader_data = array();
Sam Boyer
committed
if (!empty($loader_data[$name])) {
return $loader_data[$name];
Sam Boyer
committed
$load = new stdClass();
Sam Boyer
committed
$loader_data[$name] = &$load;
Sam Boyer
committed
panels_load_include('plugins');
Sam Boyer
committed
panels_page_load_include('read');
Sam Boyer
committed
$panel_page = panels_page_load($name);
Sam Boyer
committed
// Handle static pages then duck out early.
Sam Boyer
committed
if (!($panel_page->load_flags & PANELS_IS_DYNAMIC)) {
Sam Boyer
committed
if ((bool) panels_page_access($panel_page->name)) {
Sam Boyer
committed
panels_page_load_include('render');
panels_page_fetch_primary_display($panel_page);
Sam Boyer
committed
panels_page_prepare_panels_render($load, $panel_page, $args);
Sam Boyer
committed
}
return $load;
Sam Boyer
committed
Sam Boyer
committed
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
// 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) {
Sam Boyer
committed
// At this point, we know we're handling a dynamic/override panels_page.
// Start off by assuming that we won't fall back.
Sam Boyer
committed
$load->fallback = FALSE;
Sam Boyer
committed
// TODO Multiple p-pages per path will necessitate more complex logic here
Sam Boyer
committed
foreach ($panel_page->arguments as $id => $argument) {
Sam Boyer
committed
$ignore = ($argument['default'] == 'ignore');
Sam Boyer
committed
// FIXME The directness of this association is questionable
$load_objects[$id] = array_shift($args);
Sam Boyer
committed
$context = !empty($load_objects[$id]) ? panels_argument_get_context($argument, $load_objects[$id]) : PANELS_ARG_IS_BAD;
Sam Boyer
committed
if (!is_a($context, 'panels_required_context') && !is_a($context, 'panels_context')) {
Sam Boyer
committed
if ($context & PANELS_ARG_USE_FALLBACK) {
Sam Boyer
committed
if ($panel_page->load_flags & PANELS_HAS_FALLBACK_ROUTER) {
$load->fallback = TRUE;
break;
}
else if ($ignore) {
continue;
}
Sam Boyer
committed
}
// TODO Are there other cases? If not, this else is an unnecessary overspecification
else if ($context & PANELS_ARG_IS_BAD && $ignore) {
Sam Boyer
committed
continue;
}
Sam Boyer
committed
// Prep a 404 and bail out if we get this far.
$load->page_callback = 'drupal_not_found';
$load->page_arguments = array();
return FALSE;
Sam Boyer
committed
}
$panel_page->context[panels_argument_context_id($argument)] = $context;
}
}
* 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
Sam Boyer
committed
* 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.
Sam Boyer
committed
* @return array $map
* The rebuilt menu map.
function _panels_page_rebuild_menu_map($map, $load_objects, $positions) {
foreach ($positions as $key => $position) {
$map[$position] = $load_objects[$key];
}
return $map;
Sam Boyer
committed
function panels_page_prepare_fallback_render(&$load, $map) {
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))) {
Sam Boyer
committed
// FIXME Ideally we could skip some of this, much has already been loaded.
$map = _menu_translate($router_item, $original_map);
if ($router_item['access']) {
Sam Boyer
committed
$load->map = $map;
$load->page_arguments = array_merge(menu_unserialize($router_item['page_arguments'], $map), array_slice($map, $router_item['number_parts']));
}
}
Sam Boyer
committed
$load->page_callback = $router_item['page callback'];
$load->router_item = $router_item;
Sam Boyer
committed
return $load;
Earl Miles
committed
/**
* 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.
Earl Miles
committed
*/
function panels_page_get_current() {
Sam Boyer
committed
// Take advantage of our .inc organization to know if it's at all possible
Sam Boyer
committed
// that there's a current page to be retrieved.
if (!function_exists('panels_page_set_current')) {
return FALSE;
Earl Miles
committed
}
Sam Boyer
committed
$fubar = NULL; // PHP4 compatibility
return panels_page_set_current($fubar);
Earl Miles
committed
}
/**
* 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);
}
/**
Sam Boyer
committed
* 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.
* TODO move me to an inc!
Earl Miles
committed
*/
Sam Boyer
committed
function panels_page_load_include($include) {
Sam Boyer
committed
panels_load_include($include, 'panels_page/panels_page.');
Earl Miles
committed
}
/**
* 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)) {
Sam Boyer
committed
panels_page_load_include('read');
Earl Miles
committed
$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') {
Sam Boyer
committed
panels_page_load_include('write');
Earl Miles
committed
$code = "/**\n";
$code .= " * Implementation of hook_default_panel_pages()\n";
$code .= " */\n";
$code .= "function " . $name . "_default_panel_pages() {\n";
foreach ($panels as $panel => $truth) {
Sam Boyer
committed
$code .= panels_page_export_page($all_panels[$panel], ' ');
Earl Miles
committed
$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) {
Earl Miles
committed
if (!isset($page->$id) || !is_array($page->$id)) {