array('title', 'id', 'image', 'link'), ); $theme['panels_layout_icon'] = array( 'arguments' => array('id', 'image', 'title' => NULL), ); $theme['panels_pane'] = array( 'arguments' => array('output' => array(), 'pane' => array(), 'display' => array()), 'path' => drupal_get_path('module', 'panels') . '/templates', 'template' => 'panels-pane', ); $theme['panels_common_content_list'] = array( 'arguments' => array('display'), 'file' => 'includes/common.inc', ); $theme['panels_render_display_form'] = array( 'arguments' => array('form' => NULL), ); $theme['panels_dashboard'] = array( 'arguments' => array(), 'path' => drupal_get_path('module', 'panels') . '/templates', 'file' => '../includes/callbacks.inc', 'template' => 'panels-dashboard', ); // Register layout and style themes on behalf of all of these items. ctools_include('plugins', 'panels'); // No need to worry about files; the plugin has to already be loaded for us // to even know what the theme function is, so files will be auto included. $layouts = panels_get_layouts(); foreach ($layouts as $name => $data) { foreach (array('theme', 'admin theme') as $callback) { if (!empty($data[$callback])) { $theme[$data[$callback]] = array( 'arguments' => array('css_id' => NULL, 'content' => NULL, 'settings' => NULL, 'display' => NULL, 'layout' => NULL, 'renderer' => NULL), 'path' => $data['path'], ); // if no theme function exists, assume template. if (!function_exists("theme_$data[theme]")) { $theme[$data[$callback]]['template'] = str_replace('_', '-', $data[$callback]); $theme[$data[$callback]]['file'] = $data['file']; // for preprocess. } } } } $styles = panels_get_styles(); foreach ($styles as $name => $data) { if (!empty($data['render pane'])) { $theme[$data['render pane']] = array( 'arguments' => array('output' => NULL, 'pane' => NULL, 'display' => NULL), ); } // If we're in legacy mode, include the old callback key for legacy styles. if (variable_get('panels_legacy_rendering_mode', TRUE)) { if (!empty($data['render panel'])) { $theme[$data['render panel']] = array( 'arguments' => array('display' => NULL, 'panel_id' => NULL, 'panes' => NULL, 'settings' => NULL), ); } } if (!empty($data['render region'])) { $theme[$data['render region']] = array( 'arguments' => array('display' => NULL, 'region_id' => NULL, 'panes' => NULL, 'settings' => NULL), ); } if (!empty($data['hook theme'])) { if (is_array($data['hook theme'])) { $theme += $data['hook theme']; } else if (function_exists($data['hook theme'])) { $data['hook theme']($theme, $data); } } } return $theme; } /** * Implementation of hook_menu */ function panels_menu() { // Safety: go away if CTools is not at an appropriate version. if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) { return array(); } $items = array(); // Base AJAX router callback. $items['panels/ajax'] = array( 'access arguments' => array('access content'), 'page callback' => 'panels_ajax_router', 'type' => MENU_CALLBACK, ); $admin_base = array( 'file' => 'includes/callbacks.inc', 'access arguments' => array('use panels dashboard'), ); // Provide a nice location for a panels admin panel. $items['admin/build/panels'] = array( 'title' => 'Panels', 'page callback' => 'panels_admin_page', 'description' => 'Administer items related to the Panels module.', ) + $admin_base; $items['admin/build/panels/dashboard'] = array( 'title' => 'Dashboard', 'page callback' => 'panels_admin_page', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ) + $admin_base; $items['admin/build/panels/settings'] = array( 'title' => 'Settings', 'page callback' => 'drupal_get_form', 'page arguments' => array('panels_admin_settings_page'), 'type' => MENU_LOCAL_TASK, ) + $admin_base; $items['admin/build/panels/settings/general'] = array( 'title' => 'General', 'page callback' => 'drupal_get_form', 'page arguments' => array('panels_admin_settings_page'), 'access arguments' => array('administer page manager'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10, ) + $admin_base; if (module_exists('page_manager')) { $items['admin/build/panels/settings/panel-page'] = array( 'title' => 'Panel pages', 'page callback' => 'panels_admin_panel_context_page', 'type' => MENU_LOCAL_TASK, 'weight' => -10, ) + $admin_base; } ctools_include('plugins', 'panels'); $layouts = panels_get_layouts(); foreach ($layouts as $name => $data) { if (!empty($data['hook menu'])) { if (is_array($data['hook menu'])) { $items += $data['hook menu']; } else if (function_exists($data['hook menu'])) { $data['hook menu']($items, $data); } } } return $items; } /** * Menu loader function to load a cache item for Panels AJAX. * * This load all of the includes needed to perform AJAX, and loads the * cache object and makes sure it is valid. */ function panels_edit_cache_load($cache_key) { ctools_include('display-edit', 'panels'); ctools_include('plugins', 'panels'); ctools_include('ajax'); ctools_include('modal'); ctools_include('context'); return panels_edit_cache_get($cache_key); } /** * Implementation of hook_init() */ function panels_init() { // Safety: go away if CTools is not at an appropriate version. if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) { return; } ctools_add_css('panels', 'panels'); ctools_add_js('panels', 'panels'); } /** * Implementation of hook_perm */ function panels_perm() { return array( 'view all panes', 'view pane admin links', 'administer pane visibility', 'administer pane access', 'administer advanced pane settings', 'administer panels layouts', 'use panels caching features', 'use panels dashboard', ); } /** * Implementation of hook_flush_caches(). * * We implement this so that we can be sure our legacy rendering state setting * in $conf is updated whenever caches are cleared. */ function panels_flush_caches() { $legacy = panels_get_legacy_state(); $legacy->determineStatus(); } // --------------------------------------------------------------------------- // CTools hooks // // These aren't core Drupal hooks but they are just as important. /** * Implementation of hook_ctools_plugin_directory() to let the system know * we implement task and task_handler plugins. */ function panels_ctools_plugin_directory($module, $plugin) { // Safety: go away if CTools is not at an appropriate version. if (!module_invoke('ctools', 'api_version', PANELS_REQUIRED_CTOOLS_API)) { return; } if ($module == 'page_manager' || $module == 'panels' || $module == 'ctools') { return 'plugins/' . $plugin; } } /** * Inform CTools that the layout plugin can be loaded from themes. */ function panels_ctools_plugin_layouts() { return array( 'load themes' => TRUE, 'use hooks' => TRUE, 'process' => 'panels_layout_process', ); } /** * Inform CTools that the style plugin can be loaded from themes. */ function panels_ctools_plugin_styles() { return array( 'load themes' => TRUE, 'use hooks' => TRUE, 'process' => 'panels_plugin_styles_process', ); } /** * Implementation of hook_ctools_plugin_api(). * * Inform CTools about version information for various plugins implemented by * Panels. * * @param string $owner * The system name of the module owning the API about which information is * being requested. * @param string $api * The name of the API about which information is being requested. */ function panels_ctools_plugin_api($owner, $api) { if ($owner == 'panels' && $api == 'styles') { // As of 6.x-3.6, Panels has a slightly new system for style plugins. return array('version' => 2.0); } } /** * Perform additional processing on a style plugin. * * Currently this is only being used to apply versioning information to style * plugins in order to ensure the legacy renderer passes the right type of * parameters to a style plugin in a hybrid environment of both new and old * plugins. * * @see _ctools_process_data() * * @param array $plugin * The style plugin that is being processed. * @param array $info * The style plugin type info array. */ function panels_plugin_styles_process(&$plugin, $info) { $compliant_modules = ctools_plugin_api_info('panels', 'styles', 2.0, 2.0); $plugin['version'] = empty($compliant_modules[$plugin['module']]) ? 1.0 : $compliant_modules[$plugin['module']]['version']; } // --------------------------------------------------------------------------- // Panels display editing /** * @defgroup mainapi Functions comprising the main panels API * @{ */ /** * Main API entry point to edit a panel display. * * Sample implementations utiltizing the the complex $destination behavior can be found * in panels_page_edit_content() and, in a separate contrib module, OG Blueprints * (http://drupal.org/project/og_blueprints), og_blueprints_blueprint_edit(). * * @ingroup mainapi * * @param object $display instanceof panels_display \n * A fully loaded panels $display object, as returned from panels_load_display(). * Merely passing a did is NOT sufficient. \n * Note that 'fully loaded' means the $display must already be loaded with any contexts * the caller wishes to have set for the display. * @param mixed $destination \n * The redirect destination that the user should be taken to on form submission or * cancellation. With panels_edit, $destination has complex effects on the return * values of panels_edit() once the form has been submitted. See the explanation of * the return value below to understand the different types of values returned by panels_edit() * at different stages of FAPI. Under most circumstances, simply passing in * drupal_get_destination() is all that's necessary. * @param array $content_types \n * An associative array of allowed content types, typically as returned from * panels_common_get_allowed_types(). Note that context partially governs available content types, * so you will want to create any relevant contexts using panels_create_context() or * panels_create_context_empty() to make sure all the appropriate content types are available. * * @return * Because the functions called by panels_edit() invoke the form API, this function * returns different values depending on the stage of form submission we're at. In Drupal 5, * the phase of form submission is indicated by the contents of $_POST['op']. Here's what you'll * get at different stages: * -# If !$_POST['op']: then we're on on the initial passthrough and the form is being * rendered, so it's the $form itself that's being returned. Because negative margins, * a common CSS technique, bork the display editor's ajax drag-and-drop, it's important * that the $output be printed, not returned. Use this syntax in the caller function: \n * print theme('page', panels_edit($display, $destination, $content_types), FALSE); \n * -# If $_POST['op'] == t('Cancel'): form submission has been cancelled. If empty($destination) == FALSE, * then there is no return value and the panels API takes care of redirecting to $destination. * If empty($destination) == TRUE, then there's still no return value, but the caller function * has to take care of form redirection. * -# If $_POST['op'] == ('Save'): the form has been submitted successfully and has run through * panels_edit_display_submit(). $output depends on the value of $destination: * - If empty($destination) == TRUE: $output contains the modified $display * object, and no redirection will occur. This option is useful if the caller * needs to perform additional operations on or with the modified $display before * the page request is complete. Using hook_form_alter() to add an additional submit * handler is typically the preferred method for something like this, but there * are certain use cases where that is infeasible and $destination = NULL should * be used instead. If this method is employed, the caller will need to handle form * redirection. Note that having $_REQUEST['destination'] set, whether via * drupal_get_destination() or some other method, will NOT interfere with this * functionality; consequently, you can use drupal_get_destination() to safely store * your desired redirect in the caller function, then simply use drupal_goto() once * panels_edit() has done its business. * - If empty($destination) == FALSE: the form will redirect to the URL string * given in $destination and NO value will be returned. */ function panels_edit($display, $destination = NULL, $content_types = NULL, $title = FALSE) { ctools_include('display-edit', 'panels'); ctools_include('ajax'); ctools_include('plugins', 'panels'); return _panels_edit($display, $destination, $content_types, $title); } /** * API entry point for selecting a layout for a given display. * * Layout selection is nothing more than a list of radio items encompassing the available * layouts for this display, as defined by .inc files in the panels/layouts subdirectory. * The only real complexity occurs when a user attempts to change the layout of a display * that has some content in it. * * @param object $display instanceof panels_display \n * A fully loaded panels $display object, as returned from panels_load_display(). * Merely passing a did is NOT sufficient. * @param string $finish * A string that will be used for the text of the form submission button. If no value is provided, * then the form submission button will default to t('Save'). * @param mixed $destination * Basic usage is a string containing the URL that the form should redirect to upon submission. * For a discussion of advanced usages, see panels_edit(). * @param mixed $allowed_layouts * Allowed layouts has three different behaviors that depend on which of three value types * are passed in by the caller: * #- if $allowed_layouts instanceof panels_allowed_layouts (includes subclasses): the most * complex use of the API. The caller is passing in a loaded panels_allowed_layouts object * that the client module previously created and stored somewhere using a custom storage * mechanism. * #- if is_string($allowed_layouts): the string will be used in a call to variable_get() which * will call the $allowed_layouts . '_allowed_layouts' var. If the data was stored properly * in the system var, the $allowed_layouts object will be unserialized and recreated. * @see panels_common_set_allowed_layouts() * #- if is_null($allowed_layouts): the default behavior, which also provides backwards * compatibility for implementations of the Panels2 API written before beta4. In this case, * a dummy panels_allowed_layouts object is created which does not restrict any layouts. * Subsequent behavior is indistinguishable from pre-beta4 behavior. * * @return * Can return nothing, or a modified $display object, or a redirection string; return values for the * panels_edit* family of functions are quite complex. See panels_edit() for detailed discussion. * @see panels_edit() */ function panels_edit_layout($display, $finish, $destination = NULL, $allowed_layouts = NULL) { ctools_include('display-layout', 'panels'); ctools_include('plugins', 'panels'); return _panels_edit_layout($display, $finish, $destination, $allowed_layouts); } // --------------------------------------------------------------------------- // panels database functions /** * Forms the basis of a panel display * */ class panels_display { var $args = array(); var $content = array(); var $panels = array(); var $incoming_content = NULL; var $css_id = NULL; var $context = array(); var $did = 'new'; var $renderer; function panels_display() { // Set the default renderer to either the legacy or the standard renderer, // depending on the legacy rendering state $this->renderer = variable_get('panels_legacy_rendering_mode', TRUE) ? 'legacy' : 'standard'; } function add_pane(&$pane, $location = NULL) { // If no location specified, use what's set in the pane. if (empty($location)) { $location = $pane->panel; } else { $pane->panel = $location; } // Get a temporary pid for this pane. $pane->pid = "new-" . $this->next_new_pid(); // Add the pane to the approprate spots. $this->content[$pane->pid] = &$pane; $this->panels[$location][] = $pane->pid; } function duplicate_pane($pid, $location = FALSE) { $pane = $this->clone_pane($pid); $this->add_pane($pane, $location); } function clone_pane($pid) { $pane = drupal_clone($this->content[$pid]); return $pane; } function next_new_pid() { // We don't use static vars to record the next new pid because // temporary pids can last for years in exports and in caching // during editing. $id = array(0); foreach (array_keys($this->content) as $pid) { if (!is_numeric($pid)) { $id[] = substr($pid, 4); } } $next_id = max($id); return ++$next_id; } /** * Get the title from a display. * * The display must have already been rendered, or the setting to set the * display's title from a pane's title will not have worked. * * @return * The title to use. If NULL, this means to let any default title that may be in use * pass through. i.e, do not actually set the title. */ function get_title() { switch ($this->hide_title) { case PANELS_TITLE_NONE: return ''; case PANELS_TITLE_PANE: return isset($this->stored_pane_title) ? $this->stored_pane_title : ''; case PANELS_TITLE_FIXED: case FALSE; // For old exported panels that are not in the database. if (!empty($this->title)) { return filter_xss_admin(ctools_context_keyword_substitute($this->title, array(), $this->context)); } return NULL; } } /** * Render this panels display. * * After checking to ensure the designated layout plugin is valid, a * display renderer object is spawned and runs its rendering logic. * * @param mixed $renderer * An instantiated display renderer object, or the name of a display * renderer plugin+class to be fetched. Defaults to NULL. When NULL, the * predesignated display renderer will be used. */ function render($renderer = NULL) { $layout = panels_get_layout($this->layout); if (!$layout) { return NULL; } // If we were not given a renderer object, load it. if (!is_object($renderer)) { // If the renderer was not specified, default to $this->renderer // which is either standard or was already set for us. $renderer = panels_get_renderer_handler(!empty($renderer) ? $renderer : $this->renderer, $this); if (!$renderer) { return NULL; } } $output = ''; // Let modules act just prior to render. foreach (module_implements('panels_pre_render') as $module) { $function = $module . '_panels_pre_render'; $output .= $function($display, $renderer); } $output .= $renderer->render(); // Let modules act just after render. foreach (module_implements('panels_post_render') as $module) { $function = $module . '_panels_post_render'; $output .= $function($display, $renderer); } return $output; } } /** * }@ End of 'defgroup mainapi', although other functions are specifically added later */ /** * Creates a new display, setting the ID to our magic new id. */ function panels_new_display() { ctools_include('export'); $display = ctools_export_new_object('panels_display', FALSE); $display->did = 'new'; return $display; } /** * Create a new pane. * * @todo -- use schema API for some of this? */ function panels_new_pane($type, $subtype, $set_defaults = FALSE) { ctools_include('export'); $pane = ctools_export_new_object('panels_pane', FALSE); $pane->pid = 'new'; $pane->type = $type; $pane->subtype = $subtype; if ($set_defaults) { $content_type = ctools_get_content_type($type); $pane->configuration = ctools_content_get_defaults($content_type, $subtype); } return $pane; } /** * Load and fill the requested $display object(s). * * Helper function primarily for for panels_load_display(). * * @param array $dids * An indexed array of dids to be loaded from the database. * * @return $displays * An array of displays, keyed by their display dids. * * @todo schema API can drasticly simplify this code. */ function panels_load_displays($dids) { $displays = array(); if (empty($dids) || !is_array($dids)) { return $displays; } $result = db_query("SELECT * FROM {panels_display} WHERE did IN (" . db_placeholders($dids) . ")", $dids); ctools_include('export'); while ($obj = db_fetch_object($result)) { $displays[$obj->did] = ctools_export_unpack_object('panels_display', $obj); // Modify the hide_title field to go from a bool to an int if necessary. } $result = db_query("SELECT * FROM {panels_pane} WHERE did IN (" . db_placeholders($dids) . ") ORDER BY did, panel, position", $dids); while ($obj = db_fetch_object($result)) { $pane = ctools_export_unpack_object('panels_pane', $obj); $displays[$pane->did]->panels[$pane->panel][] = $pane->pid; $displays[$pane->did]->content[$pane->pid] = $pane; } return $displays; } /** * Load a single display. * * @ingroup mainapi * * @param int $did * The display id (did) of the display to be loaded. * * @return object $display instanceof panels_display \n * Returns a partially-loaded panels_display object. $display objects returned from * from this function have only the following data: * - $display->did (the display id) * - $display->name (the 'name' of the display, where applicable - it often isn't) * - $display->layout (a string with the system name of the display's layout) * - $display->panel_settings (custom layout style settings contained in an associative array; NULL if none) * - $display->layout_settings (panel size and configuration settings for Flexible layouts; NULL if none) * - $display->css_id (the special css_id that has been assigned to this display, if any; NULL if none) * - $display->content (an array of pane objects, keyed by pane id (pid)) * - $display->panels (an associative array of panel regions, each an indexed array of pids in the order they appear in that region) * - $display->cache (any relevant data from panels_simple_cache) * - $display->args * - $display->incoming_content * * While all of these members are defined, $display->context is NEVER defined in the returned $display; * it must be set using one of the ctools_context_create() functions. */ function panels_load_display($did) { $displays = panels_load_displays(array($did)); if (!empty($displays)) { return array_shift($displays); } } /** * Save a display object. * * @ingroup mainapi * * Note a new $display only receives a real did once it is run through this function. * Until then, it uses a string placeholder, 'new', in place of a real did. The same * applies to all new panes (whether on a new $display or not); in addition, * panes have sequential numbers appended, of the form 'new-1', 'new-2', etc. * * @param object $display instanceof panels_display \n * The display object to be saved. Passed by reference so the caller need not use * the return value for any reason except convenience. * * @return object $display instanceof panels_display \n */ function panels_save_display(&$display) { $update = (isset($display->did) && is_numeric($display->did)) ? array('did') : array(); drupal_write_record('panels_display', $display, $update); $pids = array(); if ($update) { // Get a list of all panes currently in the database for this display so we can know if there // are panes that need to be deleted. (i.e, aren't currently in our list of panes). $result = db_query("SELECT pid FROM {panels_pane} WHERE did = %d", $display->did); while ($pane = db_fetch_object($result)) { $pids[$pane->pid] = $pane->pid; } } // update all the panes ctools_include('plugins', 'panels'); ctools_include('content'); foreach ($display->panels as $id => $panes) { $position = 0; $new_panes = array(); foreach ((array) $panes as $pid) { if (!isset($display->content[$pid])) { continue; } $pane = $display->content[$pid]; $type = ctools_get_content_type($pane->type); $pane->position = $position++; $pane->did = $display->did; $old_pid = $pane->pid; drupal_write_record('panels_pane', $pane, is_numeric($pid) ? array('pid') : array()); if ($pane->pid != $old_pid) { // and put it back so our pids and positions can be used unset($display->content[$id]); $display->content[$pane->pid] = $pane; // If the title pane was one of our panes that just got its ID changed, // we need to change it in the database, too. if (isset($display->title_pane) && $display->title_pane == $old_pid) { $display->title_pane = $pane->pid; // Do a simple update query to write it so we don't have to rewrite // the whole record. We can't just save writing the whole record here // because it was needed to get the did. Chicken, egg, more chicken. db_query("UPDATE {panels_display} SET title_pane = %d WHERE did = %d", $pane->pid, $display->did); } } // re-add this to the list of content for this panel. $new_panes[] = $pane->pid; // Remove this from the list of panes scheduled for deletion. if (isset($pids[$pane->pid])) { unset($pids[$pane->pid]); } } $display->panels[$id] = $new_panes; } if (!empty($pids)) { db_query("DELETE FROM {panels_pane} WHERE pid IN (" . db_placeholders($pids) . ")", $pids); } // Clear any cached content for this display. panels_clear_cached_content($display); // Allow other modules to take action when a display is saved. module_invoke_all('panels_display_save', $display); // Log the change to watchdog, using the same style as node.module $watchdog_args = array('%did' => $display->did); if (!empty($display->title)) { $watchdog_args['%title'] = $display->title; watchdog('content', 'Panels: saved display "%title" with display id %did', $watchdog_args, WATCHDOG_NOTICE); } else { watchdog('content', 'Panels: saved display with id %did', $watchdog_args, WATCHDOG_NOTICE); } // to be nice, even tho we have a reference. return $display; } /** * Delete a display. */ function panels_delete_display($display) { if (is_object($display)) { $did = $display->did; } else { $did = $display; } db_query("DELETE FROM {panels_display} WHERE did = %d", $did); db_query("DELETE FROM {panels_pane} WHERE did = %d", $did); } /** * Exports the provided display into portable code. * * This function is primarily intended as a mechanism for cloning displays. * It generates an exact replica (in code) of the provided $display, with * the exception that it replaces all ids (dids and pids) with 'new-*' values. * Only once panels_save_display() is called on the code version of $display will * the exported display written to the database and permanently saved. * * @see panels_page_export() or _panels_page_fetch_display() for sample implementations. * * @ingroup mainapi * * @param object $display instanceof panels_display \n * This export function does no loading of additional data about the provided * display. Consequently, the caller should make sure that all the desired data * has been loaded into the $display before calling this function. * @param string $prefix * A string prefix that is prepended to each line of exported code. This is primarily * used for prepending a double space when exporting so that the code indents and lines up nicely. * * @return string $output * The passed-in $display expressed as code, ready to be imported. Import by running * eval($output) in the caller function; doing so will create a new $display variable * with all the exported values. Note that if you have already defined a $display variable in * the same scope as where you eval(), your existing $display variable WILL be overwritten. */ function panels_export_display($display, $prefix = '') { ctools_include('export'); $output = ctools_export_object('panels_display', $display, $prefix); // Initialize empty properties. $output .= $prefix . '$display->content = array()' . ";\n"; $output .= $prefix . '$display->panels = array()' . ";\n"; $panels = array(); $title_pid = 0; if (!empty($display->content)) { $pid_counter = 0; $region_counters = array(); foreach ($display->content as $pane) { $pid = 'new-' . ++$pid_counter; if ($pane->pid == $display->title_pane) { $title_pid = $pid; } $pane->pid = $pid; $output .= ctools_export_object('panels_pane', $pane, $prefix . ' '); $output .= "$prefix " . '$display->content[\'' . $pane->pid . '\'] = $pane' . ";\n"; if (!isset($region_counters[$pane->panel])) { $region_counters[$pane->panel] = 0; } $output .= "$prefix " . '$display->panels[\'' . $pane->panel . '\'][' . $region_counters[$pane->panel]++ .'] = \'' . $pane->pid . "';\n"; } } $output .= $prefix . '$display->hide_title = '; switch ($display->hide_title) { case PANELS_TITLE_FIXED: $output .= 'PANELS_TITLE_FIXED'; break; case PANELS_TITLE_NONE: $output .= 'PANELS_TITLE_NONE'; break; case PANELS_TITLE_PANE: $output .= 'PANELS_TITLE_PANE'; break; } $output .= ";\n"; $output .= $prefix . '$display->title_pane =' . " '$title_pid';\n"; return $output; } /** * Render a display by loading the content into an appropriate * array and then passing through to panels_render_layout. * * if $incoming_content is NULL, default content will be applied. Use * an empty string to indicate no content. * @ingroup hook_invocations */ function panels_render_display(&$display, $renderer = NULL) { ctools_include('display-render', 'panels'); ctools_include('plugins', 'panels'); ctools_include('context'); if (!empty($display->context)) { if ($form_context = ctools_context_get_form($display->context)) { $form_context->form['#theme'] = 'panels_render_display_form'; $form_context->form['#display'] = &$display; $form_context->form['#form_context_id'] = $form_context->id; return drupal_render_form($form_context->form_id, $form_context->form); } } return $display->render($renderer); } /** * Theme function to render our panel as a form. * * When rendering a display as a form, the entire display needs to be * inside the