[ Index ]

PHP Cross Reference of Drupal 6 (yi-drupal)

title

Body

[close]

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

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


Generated: Mon Jul 9 18:01:44 2012 Cross-referenced by PHPXref 0.7