123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709 |
- <?php
- /**
- * @file
- */
- /**
- * The standard render pipeline for a Panels display object.
- *
- * Given a fully-loaded panels_display object, this class will turn its
- * combination of layout, panes, and styles into HTML, invoking caching
- * appropriately along the way. Interacting with the renderer externally is
- * very simple - just pass it the display object and call the render() method:
- *
- * @code
- * // given that $display is a fully loaded Panels display object
- * $renderer = panels_get_renderer_handler('standard', $display)
- * $html_output = $renderer->render();
- * @endcode
- *
- * Internally, the render pipeline is divided into two phases, prepare and
- * render:
- * - The prepare phase transforms the skeletal data on the provided
- * display object into a structure that is expected by the render phase.
- * It is divided into a series of discrete sub-methods and operates
- * primarily by passing parameters, all with the intention of making
- * subclassing easier.
- * - The render phase relies primarily on data stored in the renderer object's
- * properties, presumably set in the prepare phase. It iterates through the
- * rendering of each pane, pane styling, placement in panel regions, region
- * styling, and finally the arrangement of rendered regions in the layout.
- * Caching, if in use, is triggered per pane, or on the entire display.
- *
- * In short: prepare builds conf, render renders conf. Subclasses should respect
- * this separation of responsibilities by adhering to these loose guidelines,
- * given a loaded display object:
- * - If your renderer needs to modify the datastructure representing what is
- * to be rendered (panes and their conf, styles, caching, etc.), it should
- * use the prepare phase.
- * - If your renderer needs to modify the manner in which that renderable
- * datastructure data is rendered, it should use the render phase.
- *
- * In the vast majority of use cases, this standard renderer will be sufficient
- * and need not be switched out/subclassed; style and/or layout plugins can
- * accommodate nearly every use case. If you think you might need a custom
- * renderer, consider the following criteria/examples:
- * - Some additional markup needs to be added to EVERY SINGLE panel.
- * - Given a full display object, just render one pane.
- * - Show a Panels admin interface.
- *
- * The system is almost functionally identical to the old procedural approach,
- * with some exceptions (@see panels_renderer_legacy for details). The approach
- * here differs primarily in its friendliness to tweaking in subclasses.
- */
- /**
- *
- */
- class panels_renderer_standard {
- /**
- * The fully-loaded Panels display object that is to be rendered. "Fully
- * loaded" is defined as:
- * 1. Having been produced by panels_load_displays(), whether or this page
- * request or at some time in the past and the object was exported.
- * 2. Having had some external code attach context data ($display->context),
- * in the exact form expected by panes. Context matching is delicate,
- * typically relying on exact string matches, so special attention must
- * be taken.
- *
- * @var panels_display
- */
- var $display;
- /**
- * An associative array of loaded plugins. Used primarily as a central
- * location for storing plugins that require additional loading beyond
- * reading the plugin definition, which is already statically cached by
- * ctools_get_plugins(). An example is layout plugins, which can optionally
- * have a callback that determines the set of panel regions available at
- * runtime.
- *
- * @var array
- */
- var $plugins = array();
- /**
- * A multilevel array of rendered data. The first level of the array
- * indicates the type of rendered data, typically with up to three keys:
- * 'layout', 'regions', and 'panes'. The relevant rendered data is stored as
- * the value for each of these keys as it is generated:
- * - 'panes' are an associative array of rendered output, keyed on pane id.
- * - 'regions' are an associative array of rendered output, keyed on region
- * name.
- * - 'layout' is the whole of the rendered output.
- *
- * @var array
- */
- var $rendered = array();
- /**
- * A multilevel array of data prepared for rendering. The first level of the
- * array indicates the type of prepared data. The standard renderer populates
- * and uses two top-level keys, 'panes' and 'regions':
- * - 'panes' are an associative array of pane objects to be rendered, keyed
- * on pane id and sorted into proper rendering order.
- * - 'regions' are an associative array of regions, keyed on region name,
- * each of which is itself an indexed array of pane ids in the order in
- * which those panes appear in that region.
- *
- * @var array
- */
- var $prepared = array();
- /**
- * Boolean state variable, indicating whether or not the prepare() method has
- * been run.
- *
- * This state is checked in panels_renderer_standard::render_layout() to
- * determine whether the prepare method should be automatically triggered.
- *
- * @var bool
- */
- var $prep_run = FALSE;
- /**
- * The plugin that defines this handler.
- */
- var $plugin = FALSE;
- /**
- * TRUE if this renderer is rendering in administrative mode
- * which will allow layouts to have extra functionality.
- *
- * @var bool
- */
- var $admin = FALSE;
- /**
- * Where to add standard meta information. There are three possibilities:
- * - standard: Put the meta information in the normal location. Default.
- * - inline: Put the meta information directly inline. This will
- * not work for javascript.
- *
- * @var string
- */
- var $meta_location = 'standard';
- /**
- * Include rendered HTML prior to the layout.
- *
- * @var string
- */
- var $prefix = '';
- /**
- * Include rendered HTML after the layout.
- *
- * @var string
- */
- var $suffix = '';
- /**
- * Boolean flag indicating whether to render the layout even if all rendered
- * regions are blank. If FALSE, the layout renders as an empty string (without
- * prefix or suffix) if not in administrative mode.
- *
- * @var bool
- */
- var $show_empty_layout = TRUE;
- /**
- * Receive and store the display object to be rendered.
- *
- * This is a psuedo-constructor that should typically be called immediately
- * after object construction.
- *
- * @param array $plugin
- * The definition of the renderer plugin.
- * @param panels_display $display
- * The panels display object to be rendered.
- */
- function init($plugin, &$display) {
- $this->plugin = $plugin;
- $layout = panels_get_layout($display->layout);
- $this->display = &$display;
- $this->plugins['layout'] = $layout;
- if (!isset($layout['regions'])) {
- $this->plugins['layout']['regions'] = panels_get_regions($layout, $display);
- }
- if (empty($this->plugins['layout'])) {
- watchdog('panels', "Layout: @layout couldn't been found, maybe the theme is disabled.", array('@layout' => $display->layout));
- }
- }
- /**
- * Get the Panels storage oparation for a given renderer AJAX method.
- *
- * @param string $method
- * The method name.
- *
- * @return string
- * The Panels storage op.
- */
- function get_panels_storage_op_for_ajax($method) {
- return 'read';
- }
- /**
- * Prepare the attached display for rendering.
- *
- * This is the outermost prepare method. It calls several sub-methods as part
- * of the overall preparation process. This compartmentalization is intended
- * to ease the task of modifying renderer behavior in child classes.
- *
- * If you override this method, it is important that you either call this
- * method via parent::prepare(), or manually set $this->prep_run = TRUE.
- *
- * @param mixed $external_settings
- * An optional parameter allowing external code to pass in additional
- * settings for use in the preparation process. Not used in the default
- * renderer, but included for interface consistency.
- */
- function prepare($external_settings = NULL) {
- $this->prepare_panes($this->display->content);
- $this->prepare_regions($this->display->panels, $this->display->panel_settings);
- $this->prep_run = TRUE;
- }
- /**
- * Prepare the list of panes to be rendered, accounting for visibility/access
- * settings and rendering order.
- *
- * This method represents the standard approach for determining the list of
- * panes to be rendered that is compatible with all parts of the Panels
- * architecture. It first applies visibility & access checks, then sorts panes
- * into their proper rendering order, and returns the result as an array.
- *
- * Inheriting classes should override this method if that renderer needs to
- * regularly make additions to the set of panes that will be rendered.
- *
- * @param array $panes
- * An associative array of pane data (stdClass objects), keyed on pane id.
- *
- * @return array
- * An associative array of panes to be rendered, keyed on pane id and sorted
- * into proper rendering order.
- */
- function prepare_panes($panes) {
- ctools_include('content');
- // Use local variables as writing to them is very slightly faster.
- $first = $normal = $last = array();
- // Prepare the list of panes to be rendered.
- foreach ($panes as $pid => $pane) {
- if (empty($this->admin)) {
- // TODO remove in 7.x and ensure the upgrade path weeds out any stragglers; it's been long enough.
- $pane->shown = !empty($pane->shown);
- // Guarantee this field exists.
- // If this pane is not visible to the user, skip out and do the next one.
- if (!$pane->shown || !panels_pane_access($pane, $this->display)) {
- continue;
- }
- }
- // If the pane's subtype is unique, get it so that
- // hook_ctools_content_subtype_alter() and/or
- // hook_ctools_block_info() will be called.
- if ($pane->type != $pane->subtype) {
- $content_type = ctools_content_get_subtype($pane->type, $pane->subtype);
- }
- else {
- $content_type = ctools_get_content_type($pane->type);
- }
- // If this pane wants to render last, add it to the $last array. We allow
- // this because some panes need to be rendered after other panes,
- // primarily so they can do things like the leftovers of forms.
- if (!empty($content_type['render last'])) {
- $last[$pid] = $pane;
- }
- // If it wants to render first, add it to the $first array. This is used
- // by panes that need to do some processing before other panes are
- // rendered.
- elseif (!empty($content_type['render first'])) {
- $first[$pid] = $pane;
- }
- // Otherwise, render it in the normal order.
- else {
- $normal[$pid] = $pane;
- }
- }
- $this->prepared['panes'] = $first + $normal + $last;
- // Allow other modules the alter the prepared panes array.
- drupal_alter('panels_panes_prepared', $this->prepared['panes'], $this);
- return $this->prepared['panes'];
- }
- /**
- * Prepare the list of regions to be rendered.
- *
- * This method is primarily about properly initializing the style plugin that
- * will be used to render the region. This is crucial as regions cannot be
- * rendered without a style plugin (in keeping with Panels' philosophy of
- * hardcoding none of its output), but for most regions no style has been
- * explicitly set. The logic here is what accommodates that situation:
- * - If a region has had its style explicitly set, then we fetch that plugin
- * and continue.
- * - If the region has no explicit style, but a style was set at the display
- * level, then inherit the style from the display.
- * - If neither the region nor the dispay have explicitly set styles, then
- * fall back to the hardcoded 'default' style, a very minimal style.
- *
- * The other important task accomplished by this method is ensuring that even
- * regions without any panes are still properly prepared for the rendering
- * process. This is essential because the way Panels loads display objects
- * (@see panels_load_displays) results only in a list of regions that
- * contain panes - not necessarily all the regions defined by the layout
- * plugin, which can only be determined by asking the plugin at runtime. This
- * method consults that retrieved list of regions and prepares all of those,
- * ensuring none are inadvertently skipped.
- *
- * @param array $region_pane_list
- * An associative array of pane ids, keyed on the region to which those pids
- * are assigned. In the default case, this is $display->panels.
- * @param array $settings
- * All known region style settings, including both the top-level display's
- * settings (if any) and all region-specific settings (if any).
- *
- * @return array
- * An array of regions prepared for rendering.
- */
- function prepare_regions($region_pane_list, $settings) {
- // Initialize defaults to be used for regions without their own explicit
- // settings. Use display settings if they exist, else hardcoded defaults.
- $default = array(
- 'style' => panels_get_style(!empty($settings['style']) ? $settings['style'] : 'default'),
- 'style settings' => isset($settings['style_settings']['default']) ? $settings['style_settings']['default'] : array(),
- );
- $regions = array();
- if (empty($settings)) {
- // No display/panel region settings exist, init all with the defaults.
- foreach ($this->plugins['layout']['regions'] as $region_id => $title) {
- // Ensure this region has at least an empty panes array.
- $panes = !empty($region_pane_list[$region_id]) ? $region_pane_list[$region_id] : array();
- $regions[$region_id] = $default;
- $regions[$region_id]['pids'] = $panes;
- }
- }
- else {
- // Some settings exist; iterate through each region and set individually.
- foreach ($this->plugins['layout']['regions'] as $region_id => $title) {
- // Ensure this region has at least an empty panes array.
- $panes = !empty($region_pane_list[$region_id]) ? $region_pane_list[$region_id] : array();
- if (empty($settings[$region_id]['style']) || $settings[$region_id]['style'] == -1) {
- $regions[$region_id] = $default;
- }
- else {
- $regions[$region_id]['style'] = panels_get_style($settings[$region_id]['style']);
- $regions[$region_id]['style settings'] = isset($settings['style_settings'][$region_id]) ? $settings['style_settings'][$region_id] : array();
- }
- $regions[$region_id]['pids'] = $panes;
- }
- }
- $this->prepared['regions'] = $regions;
- return $this->prepared['regions'];
- }
- /**
- * Build inner content, then hand off to layout-specified theme function for
- * final render step.
- *
- * This is the outermost method in the Panels render pipeline. It calls the
- * inner methods, which return a content array, which is in turn passed to the
- * theme function specified in the layout plugin.
- *
- * @return string
- * Themed & rendered HTML output.
- */
- function render() {
- // Let the display refer back to the renderer.
- $this->display->renderer_handler = $this;
- // Attach out-of-band data first.
- $this->add_meta();
- if (empty($this->display->cache['method']) || !empty($this->display->skip_cache)) {
- return $this->render_layout();
- }
- else {
- $cache = panels_get_cached_content($this->display, $this->display->args, $this->display->context);
- if ($cache === FALSE) {
- $cache = new panels_cache_object();
- $cache->set_content($this->render_layout());
- panels_set_cached_content($cache, $this->display, $this->display->args, $this->display->context);
- }
- return $cache->content;
- }
- }
- /**
- * Perform display/layout-level render operations.
- *
- * This method triggers all the inner pane/region rendering processes, passes
- * that to the layout plugin's theme callback, and returns the rendered HTML.
- *
- * If display-level caching is enabled and that cache is warm, this method
- * will not be called.
- *
- * @return string
- * The HTML string representing the entire rendered, themed panel.
- */
- function render_layout() {
- if (empty($this->prep_run)) {
- $this->prepare();
- }
- $this->render_panes();
- $this->render_regions();
- if ($this->admin && !empty($this->plugins['layout']['admin theme'])) {
- $theme = $this->plugins['layout']['admin theme'];
- }
- else {
- $theme = $this->plugins['layout']['theme'];
- }
- // Determine whether to render layout.
- $show = TRUE;
- if (!$this->show_empty_layout && !$this->admin) {
- $show = FALSE;
- // Render layout if any region is not empty.
- foreach ($this->rendered['regions'] as $region) {
- if (is_string($region) && $region !== '') {
- $show = TRUE;
- break;
- }
- }
- }
- if (!$show) {
- return;
- }
- $this->rendered['layout'] = theme($theme, array('css_id' => check_plain($this->display->css_id), 'content' => $this->rendered['regions'], 'settings' => $this->display->layout_settings, 'display' => $this->display, 'layout' => $this->plugins['layout'], 'renderer' => $this));
- return $this->prefix . $this->rendered['layout'] . $this->suffix;
- }
- /**
- * Attach out-of-band page metadata (e.g., CSS and JS).
- *
- * This must be done before render, because panels-within-panels must have
- * their CSS added in the right order: inner content before outer content.
- */
- function add_meta() {
- global $theme;
- if (!empty($this->plugins['layout']['css'])) {
- // Do not use the path_to_theme() function, because it returns the
- // $GLOBALS['theme_path'] value, which may be overriden in the theme()
- // function when the theme hook defines the key 'theme path'.
- $theme_path = isset($theme) ? drupal_get_path('theme', $theme) : '';
- $css = $this->plugins['layout']['css'];
- if (!is_array($css)) {
- $css = array($css);
- }
- // Load each of the CSS files defined in this layout.
- foreach ($css as $file) {
- if (!empty($theme_path) && file_exists($theme_path . '/' . $file)) {
- $this->add_css($theme_path . '/' . $file);
- }
- else {
- $this->add_css($this->plugins['layout']['path'] . '/' . $file);
- }
- }
- }
- if ($this->admin && isset($this->plugins['layout']['admin css'])) {
- $admin_css = $this->plugins['layout']['admin css'];
- if (!is_array($admin_css)) {
- $admin_css = array($admin_css);
- }
- foreach ($admin_css as $file) {
- $this->add_css($this->plugins['layout']['path'] . '/' . $file);
- }
- }
- }
- /**
- * Add CSS information to the renderer.
- *
- * To facilitate previews over Views, CSS can now be added in a manner
- * that does not necessarily mean just using drupal_add_css. Therefore,
- * during the panel rendering process, this method can be used to add
- * css and make certain that ti gets to the proper location.
- *
- * The arguments should exactly match drupal_add_css().
- *
- * @see drupal_add_css
- */
- function add_css($filename) {
- switch ($this->meta_location) {
- case 'standard':
- drupal_add_css($filename);
- break;
- case 'inline':
- $url = base_path() . $filename;
- $this->prefix .= '<link type="text/css" rel="stylesheet" href="' . file_create_url($url) . '" />' . "\n";
- break;
- }
- }
- /**
- * Render all prepared panes, first by dispatching to their plugin's render
- * callback, then handing that output off to the pane's style plugin.
- *
- * @return array
- * The array of rendered panes, keyed on pane pid.
- */
- function render_panes() {
- drupal_alter('panels_prerender_panes', $this);
- ctools_include('content');
- // First, render all the panes into little boxes.
- $this->rendered['panes'] = array();
- foreach ($this->prepared['panes'] as $pid => $pane) {
- $content = $this->render_pane($pane);
- if ($content) {
- $this->rendered['panes'][$pid] = $content;
- }
- }
- return $this->rendered['panes'];
- }
- /**
- * Render a pane using its designated style.
- *
- * This method also manages 'title pane' functionality, where the title from
- * an individual pane can be bubbled up to take over the title for the entire
- * display.
- *
- * @param object $pane
- * A Panels pane object, as loaded from the database.
- */
- function render_pane(&$pane) {
- module_invoke_all('panels_pane_prerender', $pane);
- $content = $this->render_pane_content($pane);
- if ($this->display->hide_title == PANELS_TITLE_PANE && !empty($this->display->title_pane) && $this->display->title_pane == $pane->pid) {
- // If the user selected to override the title with nothing, and selected
- // this as the title pane, assume the user actually wanted the original
- // title to bubble up to the top but not actually be used on the pane.
- if (empty($content->title) && !empty($content->original_title)) {
- $this->display->stored_pane_title = $content->original_title;
- }
- else {
- $this->display->stored_pane_title = !empty($content->title) ? $content->title : '';
- }
- }
- 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' => $this->display));
- }
- }
- /**
- * Render the interior contents of a single pane.
- *
- * This method retrieves pane content and produces a ready-to-render content
- * object. It also manages pane-specific caching.
- *
- * @param object $pane
- * A Panels pane object, as loaded from the database.
- *
- * @return stdClass $content
- * A renderable object, containing a subject, content, etc. Based on the
- * renderable objects used by the block system.
- */
- function render_pane_content(&$pane) {
- ctools_include('context');
- // TODO finally safe to remove this check?
- if (!is_array($this->display->context)) {
- watchdog('panels', 'renderer::render_pane_content() hit with a non-array for the context', $this->display, WATCHDOG_DEBUG);
- $this->display->context = array();
- }
- $caching = !empty($pane->cache['method']) && empty($this->display->skip_cache);
- if ($caching && ($cache = panels_get_cached_content($this->display, $this->display->args, $this->display->context, $pane))) {
- $content = $cache->content;
- }
- else {
- if ($caching) {
- // This is created before rendering so that calls to drupal_add_js
- // and drupal_add_css will be captured.
- $cache = new panels_cache_object();
- }
- $content = ctools_content_render($pane->type, $pane->subtype, $pane->configuration, array(), $this->display->args, $this->display->context);
- foreach (module_implements('panels_pane_content_alter') as $module) {
- $function = $module . '_panels_pane_content_alter';
- $function($content, $pane, $this->display->args, $this->display->context, $this, $this->display);
- }
- if ($caching && isset($cache)) {
- $cache->set_content($content);
- panels_set_cached_content($cache, $this->display, $this->display->args, $this->display->context, $pane);
- $content = $cache->content;
- }
- }
- // If there's content, check if we've css configuration to add.
- if (!empty($content)) {
- // Pass long the css_id that is usually available.
- if (!empty($pane->css['css_id'])) {
- $id = ctools_context_keyword_substitute($pane->css['css_id'], array(), $this->display->context);
- $content->css_id = check_plain($id);
- }
- // Pass long the css_class that is usually available.
- if (!empty($pane->css['css_class'])) {
- $class = ctools_context_keyword_substitute($pane->css['css_class'], array(), $this->display->context, array('css safe' => TRUE));
- $content->css_class = check_plain($class);
- }
- }
- return $content;
- }
- /**
- * Render all prepared regions, placing already-rendered panes into their
- * appropriate positions therein.
- *
- * @return array
- * An array of rendered panel regions, keyed on the region name.
- */
- function render_regions() {
- drupal_alter('panels_prerender_regions', $this);
- $this->rendered['regions'] = array();
- // Loop through all panel regions, put all panes that belong to the current
- // region in an array, then render the region. Primarily this ensures that
- // the panes are arranged in the proper order.
- $content = array();
- foreach ($this->prepared['regions'] as $region_id => $conf) {
- $region_panes = array();
- foreach ($conf['pids'] as $pid) {
- // Only include panes for region rendering if they had some output.
- if (!empty($this->rendered['panes'][$pid])) {
- $region_panes[$pid] = $this->rendered['panes'][$pid];
- }
- }
- $this->rendered['regions'][$region_id] = $this->render_region($region_id, $region_panes);
- }
- return $this->rendered['regions'];
- }
- /**
- * Render a single panel region.
- *
- * Primarily just a passthrough to the panel region rendering callback
- * specified by the style plugin that is attached to the current panel region.
- *
- * @param $region_id
- * The ID of the panel region being rendered
- * @param $panes
- * An array of panes that are assigned to the panel that's being rendered.
- *
- * @return string
- * The rendered, HTML string output of the passed-in panel region.
- */
- function render_region($region_id, $panes) {
- $style = $this->prepared['regions'][$region_id]['style'];
- $style_settings = $this->prepared['regions'][$region_id]['style settings'];
- // Retrieve the pid (can be a panel page id, a mini panel id, etc.), this
- // might be used (or even necessary) for some panel display styles.
- // TODO: Got to fix this to use panel page name instead of pid, since pid is
- // no longer guaranteed. This needs an API to be able to set the final id.
- $owner_id = 0;
- if (isset($this->display->owner) && is_object($this->display->owner) && isset($this->display->owner->id)) {
- $owner_id = $this->display->owner->id;
- }
- $output = theme($style['render region'], array('display' => $this->display, 'owner_id' => $owner_id, 'panes' => $panes, 'settings' => $style_settings, 'region_id' => $region_id, 'style' => $style));
- return $output;
- }
- }
|