March 7, 2011

Backporting EE Views

EE1 I prefer to keep software up-to-date, especially when there are so many security fixes. Sometimes the best options are taken away and you must do bad things. Well, there is a saying about lemons and pimm’s making a great summer drink.

Recently I was tasked with updating some custom module code on an older EE 1.6.x series website. The module has been updated to take advantage of new EE features, like the addition of CI views. Without views the whole module produced a blank screen in the CP. The code below provides this functionality in older EE versions.

Nuts and Bolts

Save the file at the bottom of this article in your module. Then before you call $DSP->view make sure you override it with the following code:

global $DSP;
// this check is only needed if not in the mcp file
if ( ! class_exists('Display') )
{
    require PATH_CP.'cp.display'.EXT;
    $DSP = new Display();
}
if ( ! method_exists($DSP, 'view') )
{
    require PATH_MOD.'yourmod/display_view'.EXT;
    $DSP = new Display_view($DSP, PATH_MOD.'yourmod/views/');
}

Display View Class

/**
 * Display_view class wraps EE Display class and provides
 * DSP->view method to the control panel for older versions of EE
 *
 * Save as display_view.php in your module
 *
 * @author Jeremy Messenger <jlmessengertech+eeview@gmail.com>
 * @license the view function is based on CodeIgniter and
 *  may fall under it's licensing terms, all other code is
 *  public domain and is 'AS IS' without warranty.
 * @copyright 2011 Jeremy Messenger
 */
class Display_view
{
    // path to view files, set to the current addon's path
    var $view_path      = '';
    // array of cached view variables
    var $cached_vars    = array();

    var $old_display = NULL;

    function Display_view($old_display, $view_path)
    {
        $this->old_display = $old_display;
        $this->view_path = $view_path;
    }

    // wrap the old display instance
    function __call($name, $args) {
        return call_user_func_array(array($this->old_display, $name), $args);}
    function __set($name, $value) {
        $this->old_display->$name = $value;}
    function __get($name) {
        return $this->old_display->$name;}
    function __isset($name) {
        return isset($this->old_display->$name);}
    function __unset($name) {
        unset($this->old_display->$name);}

    // add view method in newer version of EE
    function view($view, $vars = array(), $return = FALSE, $path = '')
    {
        global $FNS, $LANG, $LOC, $PREFS, $REGX, $SESS;

        // Set the path to the requested file
        if ($path == '')
        {
            $ext = pathinfo($view, PATHINFO_EXTENSION);
            $file = ($ext == '') ? $view.EXT : $view;
            $path = $this->view_path.$file;
        }
        else
        {
            $x = explode('/', $path);
            $file = end($x);
        }

        if ( ! file_exists($path))
        {
            trigger_error('Unable to load the requested file: '.$file);
            return FALSE;
        }

        /*
         * Extract and cache variables
         *
         * You can either set variables using the dedicated
         * $this->load_vars() function or via the second
         * parameter of this function. We'll merge the two types
         * and cache them so that views that are embedded within
         * other views can have access to these variables.
         */ 
        if (is_array($vars))
        {
            $this->cached_vars = array_merge($this->cached_vars, $vars);
        }
        extract($this->cached_vars);

        /*
         * Buffer the output
         *
         * We buffer the output for two reasons:
         * 1. Speed. You get a significant speed boost.
         * 2. So that the final rendered template can be
         * post-processed by the output class.  Why do we
         * need post processing?  For one thing, in order to
         * show the elapsed page load time.  Unless we can
         * intercept the content right before it's sent to the
         * browser and then stop the timer it won't be accurate.
         */
        ob_start();

        // If the PHP installation does not support short tags we'll
        // do a little string replacement, changing the short tags
        // to standard PHP echo statements.
        if ((bool) @ini_get('short_open_tag') === FALSE)
        {
            echo eval('?>'.preg_replace("/;*\s*\?>/", "; ?>",
                str_replace('<?=', '<?php echo ', file_get_contents($path))));
        }
        else
        {
            // include() vs include_once() allows for multiple views with the same name
            include($path);
        }

        // Return the file data if requested
        if ($return === TRUE)
        {       
            $buffer = ob_get_contents();
            ob_end_clean();
            return $buffer;
        }

        /*
         * Flush the buffer... or buff the flusher?
         *
         * In order to permit views to be nested within
         * other views, we need to flush the content back out
         * whenever we are beyond the first level of output
         * buffering so that it can be seen and included properly
         * by the first included template and any subsequent
         * ones. Oy!
         */
        if (ob_get_level() > 1)
        {
            ob_end_flush();
        }
        else
        {
            $buffer = ob_get_contents();
            ob_end_clean();
            return $buffer;
        }
    }
}