[ Index ]

PHP Cross Reference of Drupal 6 (gatewave)

title

Body

[close]

/sites/all/modules/panels/plugins/display_renderers/ -> panels_renderer_standard.class.php (source)

   1  <?php
   2  // $Id: panels_renderer_standard.class.php,v 1.1.2.32 2011/01/12 23:48:44 merlinofchaos Exp $
   3  
   4  /**
   5   * The standard render pipeline for a Panels display object.
   6   *
   7   * Given a fully-loaded panels_display object, this class will turn its
   8   * combination of layout, panes, and styles into HTML, invoking caching
   9   * appropriately along the way. Interacting with the renderer externally is
  10   * very simple - just pass it the display object and call the render() method:
  11   *
  12   * @code
  13   *   // given that $display is a fully loaded Panels display object
  14   *   $renderer = panels_get_renderer_handler('standard', $display)
  15   *   $html_output = $renderer->render();
  16   * @endcode
  17   *
  18   * Internally, the render pipeline is divided into two phases, prepare and
  19   * render:
  20   *   - The prepare phase transforms the skeletal data on the provided
  21   *     display object into a structure that is expected by the render phase.
  22   *     It is divided into a series of discrete sub-methods and operates
  23   *     primarily by passing parameters, all with the intention of making
  24   *     subclassing easier.
  25   *   - The render phase relies primarily on data stored in the renderer object's
  26   *     properties, presumably set in the prepare phase. It iterates through the
  27   *     rendering of each pane, pane styling, placement in panel regions, region
  28   *     styling, and finally the arrangement of rendered regions in the layout.
  29   *     Caching, if in use, is triggered per pane, or on the entire display.
  30   *
  31   * In short: prepare builds conf, render renders conf. Subclasses should respect
  32   * this separation of responsibilities by adhering to these loose guidelines,
  33   * given a loaded display object:
  34   *   - If your renderer needs to modify the datastructure representing what is
  35   *     to be rendered (panes and their conf, styles, caching, etc.), it should
  36   *     use the prepare phase.
  37   *   - If your renderer needs to modify the manner in which that renderable
  38   *     datastructure data is rendered, it should use the render phase.
  39   *
  40   * In the vast majority of use cases, this standard renderer will be sufficient
  41   * and need not be switched out/subclassed; style and/or layout plugins can
  42   * accommodate nearly every use case. If you think you might need a custom
  43   * renderer, consider the following criteria/examples:
  44   *   - Some additional markup needs to be added to EVERY SINGLE panel.
  45   *   - Given a full display object, just render one pane.
  46   *   - Show a Panels admin interface.
  47   *
  48   * The system is almost functionally identical to the old procedural approach,
  49   * with some exceptions (@see panels_renderer_legacy for details). The approach
  50   * here differs primarily in its friendliness to tweaking in subclasses.
  51   */
  52  class panels_renderer_standard {
  53    /**
  54     * The fully-loaded Panels display object that is to be rendered. "Fully
  55     * loaded" is defined as:
  56     *   1. Having been produced by panels_load_displays(), whether or this page
  57     *      request or at some time in the past and the object was exported.
  58     *   2. Having had some external code attach context data ($display->context),
  59     *      in the exact form expected by panes. Context matching is delicate,
  60     *      typically relying on exact string matches, so special attention must
  61     *      be taken.
  62     *
  63     * @var panels_display
  64     */
  65    var $display;
  66  
  67    /**
  68     * An associative array of loaded plugins. Used primarily as a central
  69     * location for storing plugins that require additional loading beyond
  70     * reading the plugin definition, which is already statically cached by
  71     * ctools_get_plugins(). An example is layout plugins, which can optionally
  72     * have a callback that determines the set of panel regions available at
  73     * runtime.
  74     *
  75     * @var array
  76     */
  77    var $plugins = array();
  78  
  79    /**
  80     * A multilevel array of rendered data. The first level of the array
  81     * indicates the type of rendered data, typically with up to three keys:
  82     * 'layout', 'regions', and 'panes'. The relevant rendered data is stored as
  83     * the value for each of these keys as it is generated:
  84     *  - 'panes' are an associative array of rendered output, keyed on pane id.
  85     *  - 'regions' are an associative array of rendered output, keyed on region
  86     *    name.
  87     *  - 'layout' is the whole of the rendered output.
  88     *
  89     * @var array
  90     */
  91    var $rendered = array();
  92  
  93    /**
  94     * A multilevel array of data prepared for rendering. The first level of the
  95     * array indicates the type of prepared data. The standard renderer populates
  96     * and uses two top-level keys, 'panes' and 'regions':
  97     *  - 'panes' are an associative array of pane objects to be rendered, keyed
  98     *    on pane id and sorted into proper rendering order.
  99     *  - 'regions' are an associative array of regions, keyed on region name,
 100     *    each of which is itself an indexed array of pane ids in the order in
 101     *    which those panes appear in that region.
 102     *
 103     * @var array
 104     */
 105    var $prepared = array();
 106  
 107    /**
 108     * Boolean state variable, indicating whether or not the prepare() method has
 109     * been run.
 110     *
 111     * This state is checked in panels_renderer_standard::render_layout() to
 112     * determine whether the prepare method should be automatically triggered.
 113     *
 114     * @var bool
 115     */
 116    var $prep_run = FALSE;
 117  
 118    /**
 119     * The plugin that defines this handler.
 120     */
 121    var $plugin = FALSE;
 122  
 123    /**
 124     * TRUE if this renderer is rendering in administrative mode
 125     * which will allow layouts to have extra functionality.
 126     *
 127     * @var bool
 128     */
 129    var $admin = FALSE;
 130  
 131    /**
 132     * Where to add standard meta information. There are three possibilities:
 133     * - standard: Put the meta information in the normal location. Default.
 134     * - inline: Put the meta information directly inline. This will
 135     *   not work for javascript.
 136     *
 137     * @var string
 138     */
 139    var $meta_location = 'standard';
 140  
 141    /**
 142     * Include rendered HTML prior to the layout.
 143     *
 144     * @var string
 145     */
 146    var $prefix = '';
 147  
 148    /**
 149     * Include rendered HTML after the layout.
 150     *
 151     * @var string
 152     */
 153    var $suffix = '';
 154  
 155    /**
 156     * Receive and store the display object to be rendered.
 157     *
 158     * This is a psuedo-constructor that should typically be called immediately
 159     * after object construction.
 160     *
 161     * @param array $plugin
 162     *   The definition of the renderer plugin.
 163     * @param panels_display $display
 164     *   The panels display object to be rendered.
 165     */
 166    function init($plugin, &$display) {
 167      $this->plugin = $plugin;
 168      $layout = panels_get_layout($display->layout);
 169      $this->display = &$display;
 170      $this->plugins['layout'] = $layout;
 171      if (!isset($layout['panels'])) {
 172        $this->plugins['layout']['panels'] = panels_get_regions($layout, $display);
 173      }
 174  
 175      if (empty($this->plugins['layout'])) {
 176        watchdog('panels', "Layout: @layout couldn't been found, maybe the theme is disabled.", array('@layout' => $display->layout));
 177      }
 178    }
 179  
 180    /**
 181     * Prepare the attached display for rendering.
 182     *
 183     * This is the outermost prepare method. It calls several sub-methods as part
 184     * of the overall preparation process. This compartmentalization is intended
 185     * to ease the task of modifying renderer behavior in child classes.
 186     *
 187     * If you override this method, it is important that you either call this
 188     * method via parent::prepare(), or manually set $this->prep_run = TRUE.
 189     *
 190     * @param mixed $external_settings
 191     *  An optional parameter allowing external code to pass in additional
 192     *  settings for use in the preparation process. Not used in the default
 193     *  renderer, but included for interface consistency.
 194     */
 195    function prepare($external_settings = NULL) {
 196      $this->prepare_panes($this->display->content);
 197      $this->prepare_regions($this->display->panels, $this->display->panel_settings);
 198      $this->prep_run = TRUE;
 199    }
 200  
 201    /**
 202     * Prepare the list of panes to be rendered, accounting for visibility/access
 203     * settings and rendering order.
 204     *
 205     * This method represents the standard approach for determining the list of
 206     * panes to be rendered that is compatible with all parts of the Panels
 207     * architecture. It first applies visibility & access checks, then sorts panes
 208     * into their proper rendering order, and returns the result as an array.
 209     *
 210     * Inheriting classes should override this method if that renderer needs to
 211     * regularly make additions to the set of panes that will be rendered.
 212     *
 213     * @param array $panes
 214     *  An associative array of pane data (stdClass objects), keyed on pane id.
 215     * @return array
 216     *  An associative array of panes to be rendered, keyed on pane id and sorted
 217     *  into proper rendering order.
 218     */
 219    function prepare_panes($panes) {
 220      ctools_include('content');
 221      // Use local variables as writing to them is very slightly faster
 222      $first = $normal = $last = array();
 223  
 224      // Prepare the list of panes to be rendered
 225      foreach ($panes as $pid => $pane) {
 226        if (empty($this->admin)) {
 227          // TODO remove in 7.x and ensure the upgrade path weeds out any stragglers; it's been long enough
 228          $pane->shown = !empty($pane->shown); // guarantee this field exists.
 229          // If this pane is not visible to the user, skip out and do the next one
 230          if (!$pane->shown || !panels_pane_access($pane, $this->display)) {
 231            continue;
 232          }
 233        }
 234  
 235        $content_type = ctools_get_content_type($pane->type);
 236  
 237        // If this pane wants to render last, add it to the $last array. We allow
 238        // this because some panes need to be rendered after other panes,
 239        // primarily so they can do things like the leftovers of forms.
 240        if (!empty($content_type['render last'])) {
 241          $last[$pid] = $pane;
 242        }
 243        // If it wants to render first, add it to the $first array. This is used
 244        // by panes that need to do some processing before other panes are
 245        // rendered.
 246        else if (!empty($content_type['render first'])) {
 247          $first[$pid] = $pane;
 248        }
 249        // Otherwise, render it in the normal order.
 250        else {
 251          $normal[$pid] = $pane;
 252        }
 253      }
 254      $this->prepared['panes'] = $first + $normal + $last;
 255      return $this->prepared['panes'];
 256    }
 257  
 258    /**
 259     * Prepare the list of regions to be rendered.
 260     *
 261     * This method is primarily about properly initializing the style plugin that
 262     * will be used to render the region. This is crucial as regions cannot be
 263     * rendered without a style plugin (in keeping with Panels' philosophy of
 264     * hardcoding none of its output), but for most regions no style has been
 265     * explicitly set. The logic here is what accommodates that situation:
 266     *  - If a region has had its style explicitly set, then we fetch that plugin
 267     *    and continue.
 268     *  - If the region has no explicit style, but a style was set at the display
 269     *    level, then inherit the style from the display.
 270     *  - If neither the region nor the dispay have explicitly set styles, then
 271     *    fall back to the hardcoded 'default' style, a very minimal style.
 272     *
 273     * The other important task accomplished by this method is ensuring that even
 274     * regions without any panes are still properly prepared for the rendering
 275     * process. This is essential because the way Panels loads display objects
 276     * (@see panels_load_displays) results only in a list of regions that
 277     * contain panes - not necessarily all the regions defined by the layout
 278     * plugin, which can only be determined by asking the plugin at runtime. This
 279     * method consults that retrieved list of regions and prepares all of those,
 280     * ensuring none are inadvertently skipped.
 281     *
 282     * @param array $region_pane_list
 283     *   An associative array of pane ids, keyed on the region to which those pids
 284     *   are assigned. In the default case, this is $display->panels.
 285     * @param array $settings
 286     *   All known region style settings, including both the top-level display's
 287     *   settings (if any) and all region-specific settings (if any).
 288     * @return array
 289     *   An array of regions prepared for rendering.
 290     */
 291    function prepare_regions($region_pane_list, $settings) {
 292      // Initialize defaults to be used for regions without their own explicit
 293      // settings. Use display settings if they exist, else hardcoded defaults.
 294      $default = array(
 295        'style' => panels_get_style(!empty($settings['style']) ? $settings['style'] : 'default'),
 296        'style settings' => isset($settings['style_settings']['default']) ? $settings['style_settings']['default'] : array(),
 297      );
 298  
 299      $regions = array();
 300      if (empty($settings)) {
 301        // No display/panel region settings exist, init all with the defaults.
 302        foreach ($this->plugins['layout']['panels'] as $region_id => $title) {
 303          // Ensure this region has at least an empty panes array.
 304          $panes = !empty($region_pane_list[$region_id]) ? $region_pane_list[$region_id] : array();
 305  
 306          $regions[$region_id] = $default;
 307          $regions[$region_id]['pids'] = $panes;
 308        }
 309      }
 310      else {
 311        // Some settings exist; iterate through each region and set individually.
 312        foreach ($this->plugins['layout']['panels'] as $region_id => $title) {
 313          // Ensure this region has at least an empty panes array.
 314          $panes = !empty($region_pane_list[$region_id]) ? $region_pane_list[$region_id] : array();
 315  
 316          if (empty($settings[$region_id]['style']) || $settings[$region_id]['style'] == -1) {
 317            $regions[$region_id] = $default;
 318          }
 319          else {
 320            $regions[$region_id]['style'] = panels_get_style($settings[$region_id]['style']);
 321            $regions[$region_id]['style settings'] = isset($settings['style_settings'][$region_id]) ? $settings['style_settings'][$region_id] : array();
 322          }
 323          $regions[$region_id]['pids'] = $panes;
 324        }
 325      }
 326  
 327      $this->prepared['regions'] = $regions;
 328      return $this->prepared['regions'];
 329    }
 330  
 331    /**
 332     * Build inner content, then hand off to layout-specified theme function for
 333     * final render step.
 334     *
 335     * This is the outermost method in the Panels render pipeline. It calls the
 336     * inner methods, which return a content array, which is in turn passed to the
 337     * theme function specified in the layout plugin.
 338     *
 339     * @return string
 340     *  Themed & rendered HTML output.
 341     */
 342    function render() {
 343      // Attach out-of-band data first.
 344      $this->add_meta();
 345  
 346      if (empty($this->display->cache['method']) || !empty($this->display->skip_cache)) {
 347        return $this->render_layout();
 348      }
 349      else {
 350        $cache = panels_get_cached_content($this->display, $this->display->args, $this->display->context);
 351        if ($cache === FALSE) {
 352          $cache = new panels_cache_object();
 353          $cache->set_content($this->render_layout());
 354          panels_set_cached_content($cache, $this->display, $this->display->args, $this->display->context);
 355        }
 356        return $cache->content;
 357      }
 358    }
 359  
 360    /**
 361     * Perform display/layout-level render operations.
 362     *
 363     * This method triggers all the inner pane/region rendering processes, passes
 364     * that to the layout plugin's theme callback, and returns the rendered HTML.
 365     *
 366     * If display-level caching is enabled and that cache is warm, this method
 367     * will not be called.
 368     *
 369     * @return string
 370     *   The HTML string representing the entire rendered, themed panel.
 371     */
 372    function render_layout() {
 373      if (empty($this->prep_run)) {
 374        $this->prepare();
 375      }
 376      $this->render_panes();
 377      $this->render_regions();
 378  
 379      if ($this->admin && !empty($this->plugins['layout']['admin theme'])) {
 380        $theme = $this->plugins['layout']['admin theme'];
 381      }
 382      else {
 383        $theme = $this->plugins['layout']['theme'];
 384      }
 385      $this->rendered['layout'] = theme($theme, check_plain($this->display->css_id), $this->rendered['regions'], $this->display->layout_settings, $this->display, $this->plugins['layout'], $this);
 386      return $this->prefix . $this->rendered['layout'] . $this->suffix;
 387    }
 388  
 389    /**
 390     * Attach out-of-band page metadata (e.g., CSS and JS).
 391     *
 392     * This must be done before render, because panels-within-panels must have
 393     * their CSS added in the right order: inner content before outer content.
 394     */
 395    function add_meta() {
 396      if (!empty($this->plugins['layout']['css'])) {
 397        if (file_exists(path_to_theme() . '/' . $this->plugins['layout']['css'])) {
 398          $this->add_css(path_to_theme() . '/' . $this->plugins['layout']['css']);
 399        }
 400        else {
 401          $this->add_css($this->plugins['layout']['path'] . '/' . $this->plugins['layout']['css']);
 402        }
 403      }
 404  
 405      if ($this->admin && isset($this->plugins['layout']['admin css'])) {
 406        $this->add_css($this->plugins['layout']['path'] . '/' . $this->plugins['layout']['admin css']);
 407      }
 408    }
 409  
 410    /**
 411     * Add CSS information to the renderer.
 412     *
 413     * To facilitate previews over Views, CSS can now be added in a manner
 414     * that does not necessarily mean just using drupal_add_css. Therefore,
 415     * during the panel rendering process, this method can be used to add
 416     * css and make certain that ti gets to the proper location.
 417     *
 418     * The arguments should exactly match drupal_add_css().
 419     *
 420     * @see drupal_add_css
 421     */
 422    function add_css($filename, $type = 'module', $media = 'all', $preprocess = TRUE) {
 423      $path = file_create_path($filename);
 424      switch ($this->meta_location) {
 425        case 'standard':
 426          if ($path) {
 427            // Use CTools CSS add because it can handle temporary CSS in private
 428            // filesystem.
 429            ctools_include('css');
 430            ctools_css_add_css($filename, $type, $media, $preprocess);
 431          }
 432          else {
 433            drupal_add_css($filename, $type, $media, $preprocess);
 434          }
 435          break;
 436        case 'inline':
 437          if ($path) {
 438            $url = file_create_url($filename);
 439          }
 440          else {
 441            $url = base_path() . $filename;
 442          }
 443  
 444          $this->prefix .= '<link type="text/css" rel="stylesheet" media="' . $media . '" href="' . $url . '" />'."\n";
 445          break;
 446      }
 447    }
 448  
 449    /**
 450     * Render all prepared panes, first by dispatching to their plugin's render
 451     * callback, then handing that output off to the pane's style plugin.
 452     *
 453     * @return array
 454     *   The array of rendered panes, keyed on pane pid.
 455     */
 456    function render_panes() {
 457      ctools_include('content');
 458  
 459      // First, render all the panes into little boxes.
 460      $this->rendered['panes'] = array();
 461      foreach ($this->prepared['panes'] as $pid => $pane) {
 462        $content = $this->render_pane($pane);
 463        if ($content) {
 464          $this->rendered['panes'][$pid] = $content;
 465        }
 466      }
 467      return $this->rendered['panes'];
 468    }
 469  
 470    /**
 471     * Render a pane using its designated style.
 472     *
 473     * This method also manages 'title pane' functionality, where the title from
 474     * an individual pane can be bubbled up to take over the title for the entire
 475     * display.
 476     *
 477     * @param stdClass $pane
 478     *  A Panels pane object, as loaded from the database.
 479     */
 480    function render_pane(&$pane) {
 481      $content = $this->render_pane_content($pane);
 482      if ($this->display->hide_title == PANELS_TITLE_PANE && !empty($this->display->title_pane) && $this->display->title_pane == $pane->pid) {
 483  
 484        // If the user selected to override the title with nothing, and selected
 485        // this as the title pane, assume the user actually wanted the original
 486        // title to bubble up to the top but not actually be used on the pane.
 487        if (empty($content->title) && !empty($content->original_title)) {
 488          $this->display->stored_pane_title = $content->original_title;
 489        }
 490        else {
 491          $this->display->stored_pane_title = !empty($content->title) ? $content->title : '';
 492        }
 493      }
 494  
 495      if (!empty($content->content)) {
 496        if (!empty($pane->style['style'])) {
 497          $style = panels_get_style($pane->style['style']);
 498  
 499          if (isset($style) && isset($style['render pane'])) {
 500            $output = theme($style['render pane'], $content, $pane, $this->display, $style);
 501  
 502            // This could be null if no theme function existed.
 503            if (isset($output)) {
 504              return $output;
 505            }
 506          }
 507        }
 508  
 509        // fallback
 510        return theme('panels_pane', $content, $pane, $this->display);
 511      }
 512    }
 513  
 514    /**
 515     * Render the interior contents of a single pane.
 516     *
 517     * This method retrieves pane content and produces a ready-to-render content
 518     * object. It also manages pane-specific caching.
 519     *
 520     * @param stdClass $pane
 521     *   A Panels pane object, as loaded from the database.
 522     * @return stdClass $content
 523     *   A renderable object, containing a subject, content, etc. Based on the
 524     *   renderable objects used by the block system.
 525     */
 526    function render_pane_content(&$pane) {
 527      ctools_include('context');
 528      // TODO finally safe to remove this check?
 529      if (!is_array($this->display->context)) {
 530        watchdog('panels', 'renderer::render_pane_content() hit with a non-array for the context', $this->display, WATCHDOG_DEBUG);
 531        $this->display->context = array();
 532      }
 533  
 534      $content = FALSE;
 535      $caching = !empty($pane->cache['method']) && empty($this->display->skip_cache);
 536      if ($caching && ($cache = panels_get_cached_content($this->display, $this->display->args, $this->display->context, $pane))) {
 537        $content = $cache->content;
 538      }
 539      else {
 540        $content = ctools_content_render($pane->type, $pane->subtype, $pane->configuration, array(), $this->display->args, $this->display->context);
 541        foreach (module_implements('panels_pane_content_alter') as $module) {
 542          $function = $module . '_panels_pane_content_alter';
 543          $function($content, $pane, $this->display->args, $this->display->context);
 544        }
 545        if ($caching) {
 546          $cache = new panels_cache_object();
 547          $cache->set_content($content);
 548          panels_set_cached_content($cache, $this->display, $this->display->args, $this->display->context, $pane);
 549          $content = $cache->content;
 550        }
 551      }
 552  
 553      // Pass long the css_id that is usually available.
 554      if (!empty($pane->css['css_id'])) {
 555        $content->css_id = check_plain($pane->css['css_id']);
 556      }
 557  
 558      // Pass long the css_class that is usually available.
 559      if (!empty($pane->css['css_class'])) {
 560        $content->css_class = check_plain($pane->css['css_class']);
 561      }
 562  
 563      return $content;
 564    }
 565  
 566    /**
 567     * Render all prepared regions, placing already-rendered panes into their
 568     * appropriate positions therein.
 569     *
 570     * @return array
 571     *   An array of rendered panel regions, keyed on the region name.
 572     */
 573    function render_regions() {
 574      $this->rendered['regions'] = array();
 575  
 576      // Loop through all panel regions, put all panes that belong to the current
 577      // region in an array, then render the region. Primarily this ensures that
 578      // the panes are arranged in the proper order.
 579      $content = array();
 580      foreach ($this->prepared['regions'] as $region_id => $conf) {
 581        $region_panes = array();
 582        foreach ($conf['pids'] as $pid) {
 583          // Only include panes for region rendering if they had some output.
 584          if (!empty($this->rendered['panes'][$pid])) {
 585            $region_panes[$pid] = $this->rendered['panes'][$pid];
 586          }
 587        }
 588        $this->rendered['regions'][$region_id] = $this->render_region($region_id, $region_panes);
 589      }
 590  
 591      return $this->rendered['regions'];
 592    }
 593  
 594    /**
 595     * Render a single panel region.
 596     *
 597     * Primarily just a passthrough to the panel region rendering callback
 598     * specified by the style plugin that is attached to the current panel region.
 599     *
 600     * @param $region_id
 601     *   The ID of the panel region being rendered
 602     * @param $panes
 603     *   An array of panes that are assigned to the panel that's being rendered.
 604     *
 605     * @return string
 606     *   The rendered, HTML string output of the passed-in panel region.
 607     */
 608    function render_region($region_id, $panes) {
 609      $style = $this->prepared['regions'][$region_id]['style'];
 610      $style_settings = $this->prepared['regions'][$region_id]['style settings'];
 611  
 612      // Retrieve the pid (can be a panel page id, a mini panel id, etc.), this
 613      // might be used (or even necessary) for some panel display styles.
 614      // TODO: Got to fix this to use panel page name instead of pid, since pid is
 615      // no longer guaranteed. This needs an API to be able to set the final id.
 616      $owner_id = 0;
 617      if (isset($this->display->owner) && is_object($this->display->owner) && isset($this->display->owner->id)) {
 618        $owner_id = $this->display->owner->id;
 619      }
 620  
 621      return theme($style['render region'], $this->display, $owner_id, $panes, $style_settings, $region_id, $style);
 622    }
 623  }


Generated: Thu Mar 24 11:18:33 2011 Cross-referenced by PHPXref 0.7