e107inc/e107

View on GitHub
e107_handlers/shortcode_handler.php

Summary

Maintainability
A
3 hrs
Test Coverage
F
57%
<?php
/*
 * e107 website system
 *
 * Copyright (C) 2008-2010 e107 Inc (e107.org)
 * Released under the terms and conditions of the
 * GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
 *
 * e107 Shortcode handler
 *
 * $URL$
 * $Id$
 */

if (!defined('e107_INIT'))
{
    exit;
}

/**
 *
 * @package     e107
 * @subpackage    e107_handlers
 * @version     $Id$
 * @author      e107inc
 *
 * e_parse_shortcode - shortcode parser/manager, former e_shortcode
 * e_shortcode - abstract batch class
 */

    /**
     * @deprecated
     * @param $classFunc
     * @param $codes
     * @param string $path
     * @param bool $force
     * @return e_parse_shortcode
     */
    function register_shortcode($classFunc, $codes, $path = '', $force = false)
    {
        trigger_error('<b>'.__METHOD__.' is deprecated.</b>', E_USER_DEPRECATED); // NO LAN

        return e107::getScParser()->registerShortcode($classFunc, $codes, $path, $force);
    }

    /**
     *  @deprecated
     * @param $className
     * @param $scVarName
     * @param $value
     * @return e_parse_shortcode
     */
    function setScVar($className, $scVarName, $value)
    {
        return e107::getScParser()->setScVar($className, $scVarName, $value);
    }

    /**
     *  @deprecated FIXME: to be removed (once event calendar changed)
     * @param $className
     * @param $scFuncName
     * @param string $param
     * @return bool|mixed
     */
    function callScFunc($className, $scFuncName, $param = '')
    {
        return e107::getScParser()->callScFunc($className, $scFuncName, $param);
    }

    /**
     *  @deprecated FIXME: to be removed
     * @param $class
     * @param bool $force
     * @param null $eVars
     * @return e_shortcode
     */
    function initShortcodeClass($class, $force = false, $eVars = null)
    {
        trigger_error('<b>'.__METHOD__.' is deprecated.</b>', E_USER_DEPRECATED); // NO LAN

        return e107::getScParser()->initShortcodeClass($class,  $force);
    }


/**
 *
 */
class e_parse_shortcode
{
    protected $scList               = array();  // The actual code - added by parsing files or when plugin codes encountered. Array key is the shortcode name.
    protected $parseSCFiles         = true;     // True if individual shortcode files are to be used
    protected $addedCodes           = null;     // Pointer to a class or array to be used on a single call
    protected $registered_codes     = array();  // Shortcodes added by plugins TODO make it private
    protected $scClasses            = array();  // Batch shortcode classes - TODO make it private
    protected $scOverride           = array();  // Array of codes found in override/shortcodes dir
    protected $scBatchOverride      = array();  // Array of codes found in override/shortcodes/batch dir
    protected $ignoreCodes          = array();  // Shortcodes to be ignored and remain unchanged. (ie. {THEME}, {e_PLUGIN} etc. )
    protected $addonOverride        = array();  // Overrides coming from e_shortcode.php
    private   $legacyBatch          = array();  // List of legacy batch file codes. eg. using SC_BEGIN etc.
    private   $legacyBatchFile       = null;
    private   $debug_legacy         = array();

    /** @var e_vars  */
    protected $eVars                = null;
    protected $wrappers             = array();  // Wrappers array for the current parsing cycle, see contact_template.php and $CONTACT_WRAPPER variable
    protected $wrapper              = null;     // current wrapper being processed.
    protected $wrapperDebugDone     = array();  // Flag to avoid repetition of debug info.
    protected $sc_style             = array();  // Former $sc_style global variable. Internally used - performance reasons
    protected $editableCodes        = array(); // Array of editable shortcode data.
    protected $editableActive       = false;
    protected $nowrap               = false;
    private   $mode                 = 'default';

    function __construct()
    {
        // $_SESSION['editable'] = array();

        $this->ignoreCodes = e107::getParser()->getUrlConstants(); // ignore all URL shortcodes. ie. {e_PLUGIN}

        if(e_ADMIN_AREA !== true)
        {
            $this->loadOverrideShortcodes();
            $this->loadThemeShortcodes();
        }

        $this->loadPluginShortcodes();
        $this->loadPluginSCFiles();

        
        //$this->loadCoreShortcodes(); DEPRECATED

        $editableActivePref = e107::getPref('inline_editing',255);

        if(check_class($editableActivePref))
        {
            $this->editableActive = true;
        }

    }

    /**
     * @return void
     */
    public function clearRegistered()
    {
        $this->registered_codes = array();
        $this->scClasses = array();

    }

    /**
     * Register shortcode
     * $classFunc could be function name, class name or object
     * $code could be 'true' when class name/object is passed to automate the
     * registration of shortcode methods
     *
     * @param mixed $classFunc
     * @param mixed $codes
     * @param string $path
     * @param boolean $force override
     * @return e_parse_shortcode
     */
    function registerShortcode($classFunc, $codes, $path = '', $force = false)
    {
        //If codes is set to true, let's go get a list of shortcode methods
        if ($codes === true)
        {
            $codes = array();
            $tmp = get_class_methods($classFunc);
            foreach ($tmp as $c)
            {
                if (strpos($c, 'sc_') === 0)
                {
                    $codes[] = substr($c, 3);
                }
            }
            unset($tmp);
        }

        //Register object feature
        $classObj = null;
        if (is_object($classFunc))
        {
            $classObj = $classFunc;
            $classFunc = get_class($classObj);

        }

        //We only register these shortcodes if they have not already been registered in some manner
        //ie theme or other plugin .sc files
        if (is_array($codes))
        {
            foreach ($codes as $code)
            {
                $code = strtoupper($code);
                if ((!$this->isRegistered($code) || $force == true) && !$this->isOverride($code))
                {
                    $this->registered_codes[$code] = array('type' => 'class', 'path' => $path, 'class' => $classFunc);
                }
            }

            //register object if required
            if (null !== $classObj && (!$this->isScClass($classFunc) || $force == true))
            {
                $this->scClasses[$classFunc] = $classObj;
            }
        }
        else
        {
            $codes = strtoupper($codes);
            if ((!$this->isRegistered($codes) || $force == true) && !$this->isOverride($codes))
            {
                $this->registered_codes[$codes] = array('type' => 'func', 'path' => $path, 'function' => $classFunc);
            }
        }
        return $this;
    }



    /**
     * Add value to already registered SC object
     *
     * @param string $className
     * @param string $scVarName
     * @param mixed $value
     * @return e_parse_shortcode
     */
    public function setScVar($className, $scVarName, $value)
    {
        if ($this->isScClass($className))
        {
            // new way - batch should extend e_shortcode class
            if (method_exists($this->scClasses[$className], 'setScVar'))
            {
                $this->scClasses[$className]->setScVar($scVarName, $value);
            }
            else // Old - DEPRECATED

            {
                $this->scClasses[$className]->$scVarName = $value;
            }
        }
        return $this;
    }

    /**
     * Call function on an already registered SC object
     *
     * @param string $className
     * @param string $scFuncName
     * @param mixed $param - passed to function
     *
     * @return mixed|boolean - NULL if class/method doesn't exist; otherwise whatever the function returns.
     */
    public function callScFunc($className, $scFuncName, $param = '')
    {
        if ($this->isScClass($className))
        {
            return method_exists($this->scClasses[$className], $scFuncName) ? call_user_func(array($this->scClasses[$className], $scFuncName), $param) : null;
        }
        return null;
    }

    /**
     * same as e_parse_shortcode::callScFunc(), but passes the last argument (array)
     * to the called method as multiple arguments
     *
     * @param string $className
     * @param string $scFuncName
     * @param array $args
     * @return bool|mixed - NULL if class/method doesn't exist; otherwise whatever the function returns.
     * @internal param array $param - arguments passed to function
     *
     */
    protected function callScFuncA($className, $scFuncName, $args = array())
    {
        if ($this->isScClass($className))
        {
            // avoid warnings
            return method_exists($this->scClasses[$className], $scFuncName) ? call_user_func_array(array($this->scClasses[$className], $scFuncName), $args) : null;
        }
        return null;
    }



    /**
     * Create shortcode object - don't forget you still can use e_shortcode.php
     *
     * @param string $class
     * @param boolean $force
     * @return e_shortcode
     */
    public function initShortcodeClass($class, $force = false)
    {
        if (class_exists($class, false) && ($force || !$this->isScClass($class)))
        {
            $this->scClasses[$class] = new $class();

            if($class !== 'theme_shortcodes' && method_exists($this->scClasses[$class], 'init'))
            {
                $this->scClasses[$class]->init();
            }

            if(!empty($this->scClasses[$class]->override))
            {
                $methods = get_class_methods($class);
                foreach($methods as $meth)
                {
                    if(strpos($meth,'sc_') === 0)
                    {
                        $this->addonOverride[$meth] = $class;
                    }
                }
            }

            return $this->scClasses[$class];
        }

        return null;
    }

    /**
     * Get registered SC object
     * Normally you would use the proxy of this method - e107::getScBatch()
     * Global File Override ClassName/Path examples:
     * 1. Core signup shortcodes
     *    - Origin ClassName: signup_shortcodes
     *    - Origin Location: core/shortcodes/batch/signup_shortcodes.php
     *    - File Override ClassName: override_signup_shortcodes
     *    - File Override Location: core/override/shortcodes/batch/signup_shortcodes.php
     *
     * 2. Plugin 'gallery' global shortcode batch (e_shortcode.php)
     *    - Origin ClassName: gallery_shortcodes //FIXME Should be gallery_shortcode? (more below)
     *    - Origin Location: plugins/gallery/e_shortcode.php
     *    - File Override ClassName: override_gallery_shortcodes
     *    - File Override Location: core/override/shortcodes/batch/gallery_shortcodes.php
     *
     * 3. Plugin 'forum' regular shortcode batch
     *    - Origin ClassName: plugin_forum_view_shortcodes //FIXME Should be forum_shortcodes? (more below)
     *    - Origin Location: plugins/forum/shortcodes/batch/view_shortcodes.php
     *    - File Override ClassName: override_plugin_forum_view_shortcodes
     *    - File Override Location: core/override/shortcodes/batch/forum_view_shortcodes.php
     *
     * <code><?php
     * // simple use
     * e107::getScParser()->getScObject('news_shortcodes'); // For Globally Registered shortcodes, including plugins using e_shortcode.php
     *
     * // plugin override - e107_plugins/myplug/shortcodes/batch/news_shortcodes.php -> class plugin_myplug_news_shortcodes
     * e107::getScParser()->getScObject('news_shortcodes', 'myplug', true);
     *
     * // more complex plugin override
     * // e107_plugins/myplug/shortcodes/batch/news2_shortcodes.php -> class plugin_myplug_news2_shortcodes
     * e107::getScParser()->getScObject('news_shortcodes', 'myplug', 'news2_shortcodes');
     * </code>
     * @param string $className
     * @param null $pluginName
     * @param string $overrideClass if true, $className is used
     * @return e_shortcode
     * @internal param string $plugName if true className is used., if string, string value is used.
     */
    public function getScObject($className, $pluginName = null, $overrideClass = null)
    {
        
        /* FIXME Discuss Generic plugin Class naming. (excluding specific calls with $overrideClass. 
        // Defaults should be: 
            e_shortcode.php              = {plugin}_shortcode 
              {plugin}_shortcodes.php     = {plugin}_shortcodes
        */

        $path = null;
        $manualCall = false;
        
        if(trim($className)==""){ return null; }

        $_class_fname = $className;
        if($pluginName === true) //XXX When called manually by a plugin, not e_shortcode.php  eg. $sc = e107::getScBatch('faqs',TRUE); for faqs_shortcode.php with class faqs_shortcode
        {
            // @todo FAQs is incorrect and should use plugin_ as a prefix.
            $pluginName = str_replace("_shortcodes","",$className);    
            $manualCall = true;     
        }
        elseif(is_string($pluginName)) //@fixme - stuck with it.
        {
            $className = 'plugin_'.$pluginName.'_'.str_replace('/', '_', $className);
        }

        $globalOverride = $this->isBatchOverride(str_replace('plugin_', '', $className));
        
        // forced override
        if($overrideClass)
        {
            if(true === $overrideClass)
            {
                $overrideClass = $className;
            }
            // e.g. class plugin_myplug_news_shortcodes
            
            if($pluginName != null)
            {
                $_class_fname = $overrideClass;
                $className = 'plugin_'.$pluginName.'_'.str_replace('/', '_', $overrideClass);
            }
            else
            {
                $className = $overrideClass;    
            }
        }
        
        
        if($className == '_theme__shortcodes') // Check for theme shortcode batch.  - @see header_default.php //XXX Discuss. 
        {
            $className = 'theme_shortcodes';
            $path = THEME.'theme_shortcodes.php';            
        }
        elseif(!$pluginName)
        {
            if(!$globalOverride)
            {
                $path = e_CORE.'shortcodes/batch/'.$_class_fname.'.php';
            }
            else 
            {
                $path = e_CORE.'override/shortcodes/batch/'.$_class_fname.'.php';
                $className = 'override_'.$className;
            }
        }
        else
        {

            if(!$globalOverride)
            {                
                // do nothing if it's e_shortcode batch global
                if($pluginName.'_shortcodes' !== $className || $manualCall == true) // manual call by plugin, not e_shortcode.php
                {            
                    // BC  - required. 
                    $pathBC = e_PLUGIN.$pluginName.'/';
                    $path = (is_readable($pathBC.$_class_fname.'.php') ? $pathBC : e_PLUGIN.$pluginName.'/shortcodes/batch/').$_class_fname.'.php';
                }
            }
            else 
            {
                $path = e_CORE.'override/shortcodes/batch/'.$pluginName.'_'.$_class_fname.'.php';
                $className = 'override_'.$className;
            }
        }

        


        
        // Includes global Shortcode Classes (e_shortcode.php) or already loaded batch 
        if ($this->isScClass($className)) 
        {
            return $this->scClasses[$className];
        }
        
        // If it already exists - don't include it again. 
        if (class_exists($className, false)) // don't allow __autoload()
        {
            // $this->registerClassMethods($className, $path); // XXX Global registration should happen separately - here we want only the object. 
             $this->scClasses[$className] = new $className(); 
            return $this->scClasses[$className];
        }
        
        if (is_readable($path))
        {
            require_once($path);
            //e107::getDebug()->log("Loading Class '".$className."' in <b>".$path."</b>");

            if (class_exists($className, false)) // don't allow __autoload()
            {
                // register instance directly to allow override
                 $this->scClasses[$className] = new $className(); 
                // $this->registerClassMethods($className, $path);  // XXX Global registration should happen separately - here we want only the object. 
                return $this->scClasses[$className];
            }
            elseif(class_exists("plugin_".$className,false)) // v2.1.7 = Fix path issues. getScObject('myplugin',true) should load: plugin_myplugin_shortcodes class.
            {
                $className = "plugin_".$className;
                $this->scClasses[$className] = new $className();
                return $this->scClasses[$className];
            }
            elseif(E107_DBG_BBSC || E107_DBG_SC)
            {
            //     echo "<h3>Couldn't Find Class '".$className."' in <b>".$path."</b></h3>";
                echo "<div class='alert alert-danger'>Couldn't Load: <b>".$path."</b> with class-name:<b> {$className}</b> and pluginName <b>{$pluginName}</b></div>";
            }
            else
            {
                e107::getDebug()->log("Found file: <b>".$path."</b> but couldn't Find Class <b>".$className."</b> OR <b>plugin_".$className."</b> inside.");
            }
        }
        else
        {
            e107::getDebug()->log("File not available: <i>".$path."</i>. Couldn't Find Class '".$className."' in <b>".$path."</b>");
        }

    //    e107::getDebug()->log( "<div class='alert alert-danger'>Couldn't Load: <b>".$path."</b> with class-name:<b> {$className}</b> and pluginName <b>{$pluginName}</b></div>");


        // TODO - throw exception?
        return null;
    }

    /**
     * Register any shortcode from the override/shortcodes/ directory
     *
     * @return e_parse_shortcode
     */
    protected function loadOverrideShortcodes()
    {
        if (e107::getPref('sc_override'))
        {
            $tmp = explode(',', e107::getPref('sc_override'));
            foreach ($tmp as $code)
            {
                $code = strtoupper(trim($code));
                $this->registered_codes[$code]['type'] = 'override';
                $this->registered_codes[$code]['path'] = e_CORE.'override/shortcodes/single/';
                $this->registered_codes[$code]['function'] = 'override_'.strtolower($code).'_shortcode'; 
                $this->scOverride[] = $code;
            }
        }
        if (e107::getPref('sc_batch_override'))
        {
            $tmp = explode(',', e107::getPref('sc_batch_override'));
            foreach ($tmp as $code)
            {
                //$code = strtoupper(trim($code));
                //$this->registered_codes[$code]['type'] = 'override';
                $this->scBatchOverride[] = $code;
            }
        }
        return $this;
    }

    /**
     * Register any shortcodes that were registered by the theme
     * $register_sc[] = 'MY_THEME_CODE'
     *
     * @return e_parse_shortcode
     */
    public function loadThemeShortcodes($theme=null)
    {
        global $register_sc;

        $themePath = ($theme === null) ? defset('THEME') : e_THEME.$theme.'/';

        if(empty($themePath))
        {
            return null;
        }

        if(file_exists($themePath."theme_shortcodes.php"))
        {
            $classFunc = 'theme_shortcodes';
            $path = $themePath."theme_shortcodes.php";
            include_once($path);
            $this->registerClassMethods($classFunc, $path, false);
        }

    
        if (isset($register_sc) && is_array($register_sc)) // legacy load.
        {
            foreach ($register_sc as $code)
            {
                if (!$this->isRegistered($code))
                {
                    $code = strtoupper($code);
                    $this->registered_codes[$code]['type'] = 'theme';
                }
            }
        }
        
        return $this;
    }

    /**
     * Register all .sc files found in plugin directories (via pref)
     *
     * @return e_parse_shortcode
     */
    protected function loadPluginSCFiles()
    {
        $pref = e107::getPref('shortcode_list');
        $prefl = e107::getPref('shortcode_legacy_list');

        // new shortcodes - functions, shortcode/single/*.php
        if ($pref)
        {
            foreach ($pref as $path => $namearray)
            {
                foreach ($namearray as $code => $uclass)
                {
                    $code = strtoupper($code);
                    if (!$this->isRegistered($code))
                    {
                        if($this->isOverride($code))
                        {
                            $this->registered_codes[$code]['type'] = 'override';
                            $this->registered_codes[$code]['function'] = 'override_'.strtolower($code).'_shortcode';
                            $this->registered_codes[$code]['path'] = e_CORE.'override/shortcodes/single/';
                            $this->registered_codes[$code]['perms'] = $uclass; 
                            continue;
                        }
                        $this->registered_codes[$code]['type'] = 'plugin';
                        $this->registered_codes[$code]['function'] = strtolower($code).'_shortcode';
                        $this->registered_codes[$code]['path'] = e_PLUGIN.$path.'/shortcodes/single/';
                        $this->registered_codes[$code]['perms'] = $uclass; 
                    }
                }
            }
        }

        // legacy .sc - plugin root
        if ($prefl)
        {
            foreach ($prefl as $path => $namearray)
            {
                foreach ($namearray as $code => $uclass)
                {
                    // XXX old? investigate
                    if ($code == 'shortcode_config')
                    {
                        include_once(e_PLUGIN.$path.'/shortcode_config.php');
                    }
                    else
                    {
                        $code = strtoupper($code); 
                        if (!$this->isRegistered($code))
                        {
                            $this->registered_codes[$code]['type'] = 'plugin_legacy';
                            $this->registered_codes[$code]['path'] = $path;
                            $this->registered_codes[$code]['perms'] = $uclass; 
                        }
                    }
                }
            }
        }

        return $this;
    }

    /**
     * Register Plugin Shortcode Batch files (e_shortcode.php) for use site-wide.
     * Equivalent to multiple .sc files in the plugin's folder.
     *
     * @return e_parse_shortcode
     */
    protected function loadPluginShortcodes()
    {
        $pref = e107::getPref('e_shortcode_list');

        if (!$pref)
        {
            return $this;
        }
        
        foreach ($pref as $key => $val)
        {
            $globalOverride = $this->isBatchOverride($key.'_shortcodes');
            if($globalOverride)
            {
                $path = e_CORE.'override/shortcodes/batch/'.$key.'_shortcodes.php';
                $classFunc = 'override_'.$key.'_shortcodes';
            }
            else
            {
                $path = e_PLUGIN.$key.'/e_shortcode.php';
                $classFunc = $key.'_shortcodes';
            }
            
            if (!include_once($path))
            {
                // try to switch back to the batch origin in case it's an override
                if($globalOverride)
                {
                    $path = e_PLUGIN.$key.'/e_shortcode.php';
                    $classFunc = $key.'_shortcodes';
                    if (!include_once($path))
                    {
                        continue;
                    }
                }
                else continue;
            }
            
            $this->registerClassMethods($classFunc, $path, false);
        }
        return $this;
    }

    /**
     * Common Auto-Register function for class methods.
     * @param $class
     * @param $path
     * @param bool $force
     * @return e_parse_shortcode
     */
    protected function registerClassMethods($class, $path, $force = false)
    {
        $tmp = get_class_methods($class);
        $className = is_object($class) ? get_class($class) : $class;

        foreach ($tmp as $c)
        {
            if (strpos($c, 'sc_') === 0)
            {
                $sc_func = substr($c, 3);
                $code = strtoupper($sc_func);
                if ($force || !$this->isRegistered($code))
                {
                    $this->registered_codes[$code] = array('type' => 'class', 'path' => $path, 'class' => $className);
                    $this->initShortcodeClass($className);
                //    if (class_exists($className, false))
                //    {
                    //    $this->scClasses[$className] = new $className(); // Required. Test with e107::getScBatch($className)    
                    //    echo "CLASS=:".$className;            
                //    }
                }
            }
        }
        

        return $this;
    }


    /**
     * @param $code
     * @return bool
     */
    function isRegistered($code)
    {
        return array_key_exists($code, $this->registered_codes);
    }


    /**
     * @param $className
     * @param $object
     * @return $this
     */
    public function resetScClass($className, $object)
    {
        if(null === $object)
        {
            unset($this->scClasses[$className]);
        }
        elseif ($this->isScClass($className))
        {
            $this->scClasses[$className] = $object;
        }
        return $this;
    }

    /**
     * @param $className
     * @return bool
     */
    function isScClass($className)
    {
        return isset($this->scClasses[$className]);
    }

    /**
     * @param $code
     * @return bool
     */
    function isOverride($code)
    {
        return in_array($code, $this->scOverride);
    }

    /**
     * @param $name
     * @return bool
     */
    function isBatchOverride($name)
    {
        return in_array($name, $this->scBatchOverride);
    }

    /**
     * Backward Compatibility for $sc_style wrapper
     * @param mixed $extraCodes
     */
    private function mergeLegacyWrappers($extraCodes=null)
    {
        global $sc_style; //legacy, will be removed soon, use the non-global $SC_STYLE instead

        if(is_array($extraCodes) && isset($extraCodes['_WRAPPER_'])) // v2.x array wrapper.
        {
            return null;
        }

        if(isset($sc_style) && is_array($sc_style)/* && !isset($extraCodes['_WRAPPER_'])*/)
        {
            $this->sc_style = array_merge($sc_style, $this->sc_style);
        }
    }

    /**
     *    Parse the shortcodes in some text
     *
     *    @param string $text - the text containing the shortcodes
     *    @param boolean $useSCFiles - if TRUE, all currently registered shortcodes can be used.
     *                                - if FALSE, only those passed are used.
     *    @param array|object|null $extraCodes - if passed, defines additional shortcodes:
     *            - if an object or an array, the shortcodes defined by the class of the object are available for this parsing only.
     *    @param array|object|null $eVars - if defined, details values to be substituted for shortcodes. Array key (lower case) is shortcode name (upper case)
     *
     *    @return string with shortcodes substituted
     */
    function parseCodes($text, $useSCFiles = true, $extraCodes = null, $eVars = null)
    {
        $saveParseSCFiles = $this->parseSCFiles; // In case of nested call
        $this->parseSCFiles = $useSCFiles;
        $saveVars = $this->eVars; // In case of nested call
        $saveCodes = $this->addedCodes;
        $this->eVars = $eVars;
        $this->addedCodes = NULL;
        
        // former $sc_style - do it once here and not on every doCode loop - performance

        $this->sc_style = e107::scStyle();     //FIXME - BC Problems and conflicts.

        /* --------- BUG TEST Scenario -------------- 
         * Front-end Theme: Bootstrap
         * MENU-1 contains '{NAVIGATION=side}' on top and chatbox_menu below
         * URL to use: /usersettings.php - 'Signature' input should be enabled. 
         * Expected Result: 
         *         1) {NAVIGATION=side} wrapped with $SC_WRAPPER ie. enclosed in box. 
         *         2) Internal styling of chatbox_class not to be damaged by what happens globally ie. the text 'Display name' should not appear in the chatbox
         *         3) Usersettings Signature box to appear wrapped in BC $sc_style pre/post - ie. should appear at the bottom of the html table.(not at the top) 
         *         4) Existing Chatbox Styling (v1.x) not broken (ie. test with v1 theme).         
         *      - All of the above to occur without changes to usersetting_template.php - since its logic is that of v1.x templates. 
         * 
         * Things that may help: 
         * Modify e107::getScBatch() so that it never registers shortcodes globally;  ie. no overriding of existing shortcodes with it, as it is a replacement for non-global shortcode declaration in v1 
         * ONLY globally register shortcodes when they are declared in e_shortcode.php - this is consistent with the logic of e_xxxx which affect e107 Outside of the plugin/sript. (gallery plugin follows this logic)
         * 
         */

        $this->mergeLegacyWrappers($extraCodes); // XXX Commenting this out will fix #2 above.

        //object support
        
        if (is_object($extraCodes))
        {

            $this->addedCodes = &$extraCodes;

        //    e107::getDebug()->log("Codes".print_a($this->addedCodes,true));
            
            // TEMPLATEID_WRAPPER support - see contact template
            // must be registered in e_shortcode object (batch) via () method before parsing
            // Do it only once per parsing cylcle and not on every doCode() loop - performance
            if(method_exists($this->addedCodes, 'wrapper'))
            {
                $tmpWrap = e107::templateWrapper($this->addedCodes->wrapper());
                $this->wrapper = $this->addedCodes->getWrapperID();

                if(!empty($tmpWrap)) // FIX for #3 above.
                {
                    $this->wrappers = array_merge($this->wrappers,$tmpWrap);
                }
                elseif(!empty($this->wrapper))  // if there's a wrapper id but no wrappers assigned to it, clear the wrappers array.
                {
                    $this->wrappers = array();
                }
            }

            if($this->editableActive && method_exists($this->addedCodes, 'editable'))
            {
                $this->editableCodes = $this->addedCodes->editable();

                if(isset($this->editableCodes['perms']) && getperms($this->editableCodes['perms']))
                {
                    // TODO use Library Manager...
                    e107::js('footer', '{e_WEB}js/jquery.contenteditable.js', 'jquery', 2);
                    $token = defset('e_TOKEN','token-missing');
                    $_SESSION['editable'][$token] = $this->editableCodes;

                    e107::js('footer-inline', '$(".e-editable-front").each(function ()
                    {

                        var sc   = $(this).attr("data-edit-sc");
                        var id      = $(this).attr("data-edit-id");
                        var token    = "'.$token.'";
                        var box     = $(this).parent("div, span"); 
                        var container = this; 

                        $(this).contentEditable({
                            "placeholder" : "",
                            
                              "onFocusIn" : function(element){
                                var $input = $("<span id=\"e-editable-front-controls\"><span class=\"e-editable-front-save\" ><i class=\"fa fa-fw fa-save\"></i></span><span class=\"e-editable-front-cancel\" ><i class=\"fa fa-fw fa-ban\"></i></span></span>"); 
                               $input.appendTo($(box)).hide().fadeIn(300);
                                $(container).addClass("active");
                             
                            },
                            "onFocusOut" : function(element){
                            //   $(".e-editable-front-save").remove();
                            }
                            
                          
                        });
                        
                        
                        $(box).on("click",".e-editable-front-cancel",function () 
                        {
                            console.log("Cancelled");
                            $(container).removeClass("active");
                            $("#e-editable-front-controls").fadeOut(300, function() { $("#e-editable-front-controls").remove(); });     
                        }); 
                        
                        $(box).on("click",".e-editable-front-save",function () 
                        {
                            $("#e-editable-front-controls").html("<i class=\"fa fa-fw fa-spin fa-spinner\"></i>");
                        
                            $(container).removeClass("active");
                            
                            var edited_content = $(container).html();

                            $.post("'.e_WEB_ABS.'js/inline.php",{ content : edited_content, sc: sc, id: id, token: token }, function (data)
                            {
                                console.log(data);
                                try
                                {
                                    var d = $.parseJSON(data);
                                } 
                                catch(e)
                                {
                                    // Not JSON.
                                //    return;
                                }

                                console.log(d);
                                     
                                if(d.msg)
                                {
    
                                    if(d.status == "ok")
                                    {
                                        $("#e-editable-front-controls").html("<i class=\"fa fa-fw fa-check\"></i>");    
                                    }
                                            
                                    if(d.status == "error")
                                    {
                                        $("#e-editable-front-controls").html("<i class=\"fa fa-fw fa-cross\"></i>");    
                                    }            
                                        
                                }    
                                else
                                {
                                    $("#e-editable-front-controls").html("<i class=\"fa fa-fw fa-cross\"></i>");    
                                }
                                
                                $("#e-editable-front-controls").fadeOut(2000, function() { $(this).remove(); });     
    
                            }) 
                            
                        }); 
                                        
                        

                    });
                    
                




                    ');
                }
            }


            /*
            $classname = get_class($extraCodes);

            //register once
            if (!$this->isScClass($classname))
            {
                $this->registerShortcode($extraCodes, true);        // Register class if not already registered
            }

            //always overwrite object
            $this->scClasses[$classname] = $extraCodes;
            */

            // auto-register eVars if possible - call it manually?
            // $this->callScFunc($classname, 'setParserVars', $this->eVars);
        }
        elseif (is_array($extraCodes)) // Array value contains the contents of a .sc file which is then parsed. ie. return " whatever "; 
        {
            $this->addedCodes = &$extraCodes;

            if(isset($extraCodes['_WRAPPER_']))
            {
                $tmpWrap = e107::templateWrapper($extraCodes['_WRAPPER_']);
                $this->wrappers = array_merge($this->wrappers,$tmpWrap);
                $this->wrapper = $extraCodes['_WRAPPER_'];
                unset($extraCodes['_WRAPPER_']);
            }

            /*
            foreach ($extraCodes as $sc => $code)
            {
                $this->scList[$sc] = $code;
            }
            */
            
        //    print_a($this);
        }

        $ret = preg_replace_callback('#\{([A-Z][^\x02]*?\S)\}#', array(&$this, 'doCode'), (string) $text); // must always start with uppercase letter
        // $ret = preg_replace_callback('#\{(\S[^\x02]*?\S)\}#', array(&$this, 'doCode'), $text);
        $this->parseSCFiles = $saveParseSCFiles; // Restore previous value
        $this->addedCodes = $saveCodes;
        $this->eVars = $saveVars; // restore eVars
        $this->debug_legacy = null;
    

            //    $this->sc_style = array();     //XXX Adding this will also fix #2 above. 

        
        return $ret;
    }


    /**
     * Callback looks up and substitutes a shortcode
     * @param $matches
     * @return bool|int|mixed|string
     */
    function doCode($matches)
    {
        // e107::getDebug()->log($matches[1]);
        // print_a($matches);

        if(in_array($matches[0],$this->ignoreCodes)) // Ignore all {e_PLUGIN}, {THEME} etc. otherwise it will just return blank for these items. 
        {
            return $matches[0];    
        }

        // XXX remove all globals, $sc_style removed
        global $pref, $e107cache, $menu_pref, $parm, $sql;
        
        $parmArray          = false;
        $fullShortcodeKey   = null;
        $noDebugLog            = false;

        if ($this->eVars)
        {
            if ($this->eVars->isVar($matches[1]))
            {
                $match1 = $matches[1]; // php7 fix.
            //    e107::getDebug()->log("Using eVars ".$match1);
                return $this->eVars->$match1;
            }
        }
        if (strpos($matches[1], E_NL) !== false)
        {
            return $matches[0];
        }


        if(preg_match('/^([A-Z_]*\d?):(.*)/', $matches[1], $newMatch))
        {
            $fullShortcodeKey = $newMatch[0];
            $code = $newMatch[1];
            $parmStr = trim($newMatch[2]);
            // fix for #3161: htmlized shortcode parameters ...
            $parmStr = str_ireplace('&amp;', '&', $parmStr);
            $debugParm = $parmStr;
            parse_str($parmStr,$parm);
            $parmArray = true;
        }
        elseif (strpos($matches[1], '='))
        {
            list($code, $parm) = explode('=', $matches[1], 2);
        }
        else
        {
            $code = $matches[1];
            $parm = '';
        }
        //look for the $sc_mode
        if (strpos($code, '|'))
        {
            list($code, $sc_mode) = explode("|", $code, 2);
            $code = trim($code);
            $sc_mode = trim($sc_mode);
        }
        else
        {
            $sc_mode = '';
        }
        
        if($parmArray == false)
        {
            $parm = trim($parm);
            $parm = str_replace(array('[[', ']]'), array('{', '}'), $parm);
        }

        if(empty($parm))
        {
            $parm = array();
        }

        
        if (E107_DBG_BBSC || E107_DBG_SC || E107_DBG_TIMEDETAILS)
        {
            e107::getDebug()->logTime("SC ".$code);
        }

        if (E107_DBG_SC && ADMIN)
        {
            
            $dbg = "<strong>";
            $dbg .= '{';
            $dbg .= $code;
            $dbg .=($parm) ? '='.htmlentities($parm) : "";
            $dbg .= '}';
            $dbg .= "</strong>";
        //    echo $dbg;
            return $dbg;
            //    trigger_error('starting shortcode {'.$code.'}', E_USER_ERROR);    // no longer useful - use ?[debug=bbsc]
        }

        $scCode = '';
        $scFile = '';
        $_path = '';
        $ret = '';
        $_method = 'sc_'.strtolower($code);


        // Display e_shortcode.php override info.
        if((E107_DBG_BBSC || E107_DBG_SC) && isset($this->addonOverride[$_method]) && is_object($this->addedCodes) && method_exists($this->addedCodes, $_method))
        {
            $debugArr = array('class_original'=>get_class($this->addedCodes), 'class_override'=>$this->addonOverride[$_method], 'function'=>$_method);
            e107::getDebug()->logCode(4, $code, null, print_a($debugArr,true));
            $noDebugLog = true;
        }


        if (!isset($this->addonOverride[$_method]) && is_object($this->addedCodes) && method_exists($this->addedCodes, $_method)) //It is class-based batch shortcode.  Class already loaded; call the method
        {
            
            $ret = $this->addedCodes->$_method($parm, $sc_mode);

            if(E107_DBG_BBSC || E107_DBG_SC || E107_DBG_TIMEDETAILS)
            {
                $_class = get_class($this->addedCodes); // "(class loaded)"; // debug. 
                $_function = $_method;
                $_path = "(already loaded)";

            }
        }
        elseif(is_array($this->addedCodes) && array_key_exists($code, $this->addedCodes)) // Its array-based shortcode. Load the code for evaluation later.
        {

        //    $ret = $this->addedCodes[$code];

        //$this->legacyBatch
            if(in_array($code,$this->legacyBatch))
            {
                $_type = 'legacy batch';
                $_path = $this->legacyBatchFile;
                $scCode =  $this->addedCodes[$code];
            }
            else
            {
                $_type = 'array';
                $_path = "(direct to parser)";
                $ret = $this->addedCodes[$code];

            }



        }
        elseif (array_key_exists($code, $this->scList)) // Check to see if we've already loaded the .sc file contents
        {
            
            $scCode = $this->scList[$code];
            $_path = "(loaded earlier)"; // debug. 
        }
        else
        {
            //.sc file not yet loaded, or shortcode is new function type
            if ($this->parseSCFiles == true)
            {
                
                if (array_key_exists($code, $this->registered_codes))
                {
                    //shortcode is registered, let's proceed.
                    if (isset($this->registered_codes[$code]['perms']))
                    {
                        if (!check_class($this->registered_codes[$code]['perms']))
                        {
                            return '';
                        }
                    }

                    switch ($this->registered_codes[$code]['type'])
                    {
                        case 'class':
                            //It is batch shortcode.  Load the class and call the method
                            $_class     = $this->registered_codes[$code]['class'];
                            $_method     = 'sc_'.strtolower($code);

                            if (!$this->isScClass($_class))
                            {
                                if (!class_exists($_class) && $this->registered_codes[$code]['path'])
                                {
                                    include_once($this->registered_codes[$code]['path']);
                                }
                                $this->initShortcodeClass($_class, false);
                                if(!$this->isScClass($_class))
                                {
                                    return '';
                                }

                                // egister passed eVars object on init - call it manually?
                                // $this->callScFunc($_class, 'setVars', $this->var);
                            }

                            // FIXME - register passed eVars object - BAD solution - called on EVERY sc method call
                            // XXX - removal candidate - I really think it should be done manually (outside the parser)
                            // via e107::getScBatch(name)->setParserVars($eVars);
                            // $this->callScFunc($_class, 'setParserVars', $this->eVars);
                            $wrapper = $this->callScFunc($_class, 'wrapper', null);

                            $ret = $this->callScFuncA($_class, $_method, array($parm, $sc_mode));
                            
                            /*if (method_exists($this->scClasses[$_class], $_method))
                            {
                                $ret = $this->scClasses[$_class]->$_method($parm, $sc_mode);
                            }
                            else
                            {
                                echo $_class.'::'.$_method.' NOT FOUND!<br />';
                            }*/

                            break;
                        
                        case 'override':
                        case 'func':
                        case 'plugin':
                            //It is a function, so include the file and call the function
                            $_function = $this->registered_codes[$code]['function'];
                            if (!function_exists($_function) && $this->registered_codes[$code]['path'])
                            {
                                include_once($this->registered_codes[$code]['path'].strtolower($code).'.php');

                            }
                            
                            if (function_exists($_function))
                            {
                                $ret = call_user_func($_function, $parm, $sc_mode);
                            }
                            break;

                        case 'plugin_legacy':
                            $scFile = e_PLUGIN.strtolower($this->registered_codes[$code]['path']).'/'.strtolower($code).'.sc';
                            break;

                        // case 'override':
                            // $scFile = e_CORE.'override/shortcodes/'.strtolower($code).'.sc';
                            // break;

                        case 'theme':
                            $scFile = THEME.strtolower($code).'.sc';
                            break;

                    }
                }
                else
                {
                    // Code is not registered, let's look for .sc or .php file
                    // .php file takes precedence over .sc file
                    $codeLower = strtolower($code);
                    if (is_readable(e_CORE.'shortcodes/single/'.$codeLower.'.php'))
                    {
                        $_function = $codeLower.'_shortcode';
                        $_class = ($codeLower === 'sitelinks_alt') ? 'sitelinks_alt' : null; // all others are plain functions.
                        $_path = e_CORE.'shortcodes/single/'.$codeLower.'.php';

                        include_once($_path);


                        if (is_string($_class) && class_exists($_class, false)) // prevent __autoload - performance
                        {
                            // SecretR - fix array(parm, sc_mode) causing parm to become an array, see issue 424
                            if(!method_exists($_class, $_function))
                            {
                                trigger_error("Method ".$_function." doesn't exist in class ".$_class." within file ".$_path);
                            }
                            else
                            {
                                $ret = call_user_func(array($_class, $_function), $parm, $sc_mode);
                            }
                        
                        }
                        elseif (function_exists($_function))
                        {
                            $ret = call_user_func($_function, $parm, $sc_mode);
                        }
                    }
                    else
                    {
                        $scFile = e_CORE.'shortcodes/single/'.$codeLower.'.sc';
                        $_path = $scFile;
                    }
                }

                if(!empty($scFile))
                {
                    $_type = 'file';

                    if(file_exists($scFile))
                    {
                        $scCode = file_get_contents($scFile);
                        $this->scList[$code] = $scCode;
                        $_path = $scFile;

                    }
                    else
                    {
                        $_path .= $scFile." MISSING!";
                    }
                }

            }

            if (!isset($scCode))
            {
                if (E107_DBG_BBSC)
                {
                    trigger_error('shortcode not found:{'.$code.'}', E_USER_ERROR);
                }
                return $matches[0];
            }

        //    if (E107_DBG_SC && $scFile)
        //    {
                //    echo (isset($scFile)) ? "<br />sc_file= ".str_replace(e_CORE.'shortcodes/single/', '', $scFile).'<br />' : '';
                //    echo "<br />sc= <b>$code</b>";
        //    }
        }

        if ($scCode) // legacy shortode to be evaluated.
        {

            try
            {
                $ret = @eval($scCode);
            }
            catch (Throwable $t) {    // Executed only in PHP 7, will not match in PHP 5.x

                $error              = $this->debug_legacy;
                $error['code']      = $code;
                $error['problem']   = $scCode;

                e107::getDebug()->logCode(-2, $code, null, print_a($error,true));
                trigger_error("Couldn't parse {".$code."} legacy shortcode at line ".$t->getLine().".\n". $t->getMessage(), E_USER_NOTICE);
            }

            if($ret === false && E107_DEBUG_LEVEL > 0 ) // Error in Code.
            {

                $error              = $this->debug_legacy;
                $error['code']      = $code;
                $error['problem']   = $scCode;
                $error['error']     = error_get_last();

                e107::getDebug()->logCode(-2, $code, null, print_a($error,true));

            }
        }

        if(E107_DBG_BBSC && !empty($this->wrapper) && $this->wrapperDebugDone[$this->wrapper]==false)
        {
            list($wrapTmpl, $wrapID1, $wrapID2) = explode('/',$this->wrapper,3);

            $wrapActive = strtoupper($wrapTmpl)."_WRAPPER";

            if(!empty($wrapID1))
            {
                $wrapActive .= "['".$wrapID1."']";
            }

            if(!empty($wrapID2))
            {
                $wrapActive .= "['".$wrapID2."']";
            }

        //    e107::getMessage()->addDebug("Active Wrapper: \$".$wrapActive);
            $this->wrapperDebugDone[$this->wrapper] = true;
            global $db_debug;

            $db_debug->logCode(3, $this->wrapper,null,  '<pre>To use, add to the file '.$wrapTmpl.'_template.php:<br />$'.$wrapActive.'[\'SHORTCODE_NAME\'] = "(html before){---}(html after)";</pre>');
        }

        if (isset($ret) && ($ret != '' || is_numeric($ret)))
        {
            $ret = $this->makeEditable($ret, $code);
            if (!$this->nowrap == $code)
            {
                $ret = $this->makeWrapper($ret, $code, $fullShortcodeKey, $sc_mode);
            }
        }


        //if (E107_DBG_SC || E107_DBG_TIMEDETAILS)
        //{
        //    $sql->db_Mark_Time("(After SC {$code})");
        //}
        
        if (($noDebugLog != true) && (E107_DBG_BBSC || E107_DBG_SC || E107_DBG_TIMEDETAILS))
        {
            global $db_debug;
            
            $other = array();

            if(!empty($_type))
            {
                $other['type'] = $_type;
            }
            
            if(!empty($_class))
            {
                $other['class'] = $_class;
            }
            if(!empty($_function))
            {
                $other['function'] = $_function;    
            }
            if(!empty($_path))
            {
                $other['path'] = str_replace('../','',$_path);        
            }

            if($this->debug_legacy)
            {
                $other = $this->debug_legacy;
            }

            if(!empty($this->wrappers[$code]))
            {
                $other['wrapper'] = str_replace(array("\n","\t")," ",$this->wrappers[$code]);
            }
            elseif(!empty($this->wrappers[$fullShortcodeKey]) )
            {
                $other['wrapper'] = $this->wrappers[$fullShortcodeKey];
            }


            $info = (isset($this->registered_codes[$code])) ? print_a($this->registered_codes[$code],true) : print_a($other,true);
            
            $tmp = isset($debugParm) ? $debugParm : $parm;

            $db_debug->logCode(2, $code, $tmp, $info);


            
        }

        if($this->mode === 'schema' && !empty($ret))
        {
            $ret = e107::getParser()->stripAttributes($ret, ['href']);
            $ret = str_replace('"','',$ret);
        }

        return isset($ret) ? $ret: '';

    }

    /**
     * @param $mode
     * @return void
     */
    public function setMode($mode)
    {
        $this->mode = (string) $mode;
    }


    /**
     * Add Wrapper to Shortcode (when detected)
     * @param mixed $ret
     * @param string $code
     * @param string $fullShortcodeKey
     * @param string $sc_mode
     * // Wrapper support - see contact_template.php
     * @return string
     */
    private function makeWrapper($ret, $code, $fullShortcodeKey, $sc_mode)
    {
        $pre = '';
        $post = '';

        if(!empty($fullShortcodeKey) && !empty($this->wrappers[$fullShortcodeKey]) ) // eg: $NEWS_WRAPPER['view']['item']['NEWSIMAGE: item=1']
        {
            list($pre, $post) = explode("{---}", $this->wrappers[$fullShortcodeKey], 2);
        }
        elseif(isset($this->wrappers[$code]) && !empty($this->wrappers[$code])) // eg: $NEWS_WRAPPER['view']['item']['NEWSIMAGE']
        {
            list($pre, $post) = explode("{---}", $this->wrappers[$code], 2);
        }
        else
        {
            //if $sc_mode exists, we need it to parse $sc_style
            if($sc_mode)
            {
                $code = $code.'|'.$sc_mode;
            }

            if (is_array($this->sc_style) && array_key_exists($code, $this->sc_style))
            {

                // old way - pre/post keys
                if(is_array($this->sc_style[$code]))
                {
                    if (isset($this->sc_style[$code]['pre']))
                    {
                        $pre = $this->sc_style[$code]['pre'];
                    }
                    if (isset($this->sc_style[$code]['post']))
                    {
                        $post = $this->sc_style[$code]['post'];
                    }
                }
                else // new way - same format as wrapper
                {
                    list($pre, $post) = explode("{---}", $this->sc_style[$code], 2);
                }

            }

        }

        if(strpos($pre, '{') !== false) // shortcode found in wrapper
        {
            $this->nowrap = $code;
            $pre = $this->parseCodes($pre, true, $this->addedCodes);
            $this->nowrap = false;
        }

        if(strpos($post, '{') !== false) // shortcode found in wrapper
        {
            $this->nowrap = $code;
            $post = $this->parseCodes($post, true, $this->addedCodes);
            $this->nowrap = false;
        }


        return $pre.$ret.$post;

    }


    /**
     * Add Editable Container to shortcode (when detected)
     * @param string $text
     * @param string $code
     * @return string
     */
    private function makeEditable($text, $code)
    {
        if($this->editableActive === false)
        {
            return $text; // unchanged.
        }


        $lcode = strtolower($code);

        if(empty($code)
        || empty($this->editableCodes)
        || empty($this->editableCodes['shortcodes'][$lcode])
        || !isset($this->editableCodes['perms'])
        || empty($this->editableCodes['table'])
        || empty($this->editableCodes['pid'])
        || !getperms($this->editableCodes['perms'])
        || empty($this->editableCodes['shortcodes'][$lcode]['field'])
        )
        {

            return $text;
        }

        if(!empty($this->editableCodes['vars']))
        {
            $varID = $this->editableCodes['vars'];
            $var = $this->addedCodes->getScVar($varID);
        }
        else
        {
            $var = $this->addedCodes->getVars();
        }


        $container  = empty($this->editableCodes['shortcodes'][$lcode]['container']) ? 'span' : $this->editableCodes['shortcodes'][$lcode]['container'];
        $pid        = $this->editableCodes['pid'];
        $id         = intval($var[$pid]);

        $attributes = "title='".LAN_EDIT."' contenteditable='true' class='e-editable-front' data-edit-id='".$id."' data-edit-sc='".$lcode."' ";

        $ret = ($container == 'div') ?  "<div>" :  "<span>";
        $ret .= ($container == 'div') ? "<div ".$attributes." >".$text."</div>" : "<span  ".$attributes."  >".$text."</span>";

    //    $ret .= "<span class='input-group-btn'>";
    //    $ret .= '<span id="'.$lcode."-".$id.'" class="e-editable-front-save" ><i class="fa fa-fw fa-save"></i></span>';
    //    $ret .= "</span>";
        $ret .= ($container == 'div') ?  "</div>" :  "</span>";

        return $ret;

    }


    /**
     * @param $fname
     * @param string $type
     * @return array
     */
    function parse_scbatch($fname, $type = 'file')
    {
    //    global $e107cache, $eArrayStorage;
        $cur_shortcodes = array();
        if ($type == 'file')
        {
            $batch_cachefile = 'nomd5_scbatch_'.md5($fname);
            //            $cache_filename = $e107cache->cache_fname("nomd5_{$batchfile_md5}");
            $sc_cache = e107::getCache()->retrieve_sys($batch_cachefile);
            if (!$sc_cache)
            {
                $sc_batch = file($fname);
            }
            else
            {
                $cur_shortcodes = e107::unserialize($sc_cache);
                $sc_batch = "";
            }
        }
        else
        {
            $sc_batch = $fname;
        }
        
        $this->debug_legacy = array('type'=>$type, 'path'=> str_replace(e_ROOT,"",$fname));

        if ($sc_batch)
        {
            $cur_sc = '';
            foreach ($sc_batch as $line)
            {
                if (trim($line) == 'SC_END')
                {
                    $cur_sc = '';
                }
                if ($cur_sc)
                {
                    $cur_shortcodes[$cur_sc] .= $line;
                }
                if (preg_match('#^SC_BEGIN (\w*).*#', $line, $matches))
                {
                    $cur_sc = $matches[1];
                    $cur_shortcodes[$cur_sc] = varset($cur_shortcodes[$cur_sc], '');
                }
            }
            if ($type == 'file')
            {
                $sc_cache = e107::serialize($cur_shortcodes, false);
                e107::getCache()->set_sys($batch_cachefile, $sc_cache);
            }
        }

        foreach (array_keys($cur_shortcodes) as $cur_sc)
        {
            if (array_key_exists($cur_sc, $this->registered_codes))
            {
                if ($this->registered_codes[$cur_sc]['type'] == 'plugin')
                {
                    $scFile = e_PLUGIN.strtolower($this->registered_codes[$cur_sc]['path']).'/'.strtolower($cur_sc).'.sc';
                }
                else
                {
                    $scFile = THEME.strtolower($cur_sc).'.sc';
                }
                if (is_readable($scFile))
                {
                    $cur_shortcodes[$cur_sc] = file_get_contents($scFile);
                }
            }
        }


        $this->legacyBatch = array_keys($cur_shortcodes);
        $this->legacyBatchFile = str_replace(e_ROOT, '', $fname);

        return $cur_shortcodes;
    }
}


/**
 *
 */
class e_shortcode
{
    /**
     * Stores passed to shortcode handler array data
     * Usage: $this->var['someKey']
     * Assigned via setVars() and addVars() methods
     * @var array
     */
    protected $var = array(); // value available to each shortcode. 
    
    protected $mode = 'view'; // or edit. Used within shortcodes for form elements vs values only.   

    protected $wrapper = null; // holds template/key value of the currently used wrapper (if any) - see contact_template.php for an example.     

    protected $override = false;

    protected $editable = null;
    /**
     * Storage for shortcode values
     * @var e_vars
     */
    protected $scVars = null;

    /**
     * e_shortcode constructor.
     */
    public function __construct($eVars = null)
    {
        $this->scVars = !empty($eVars) ? $eVars : new e_vars();
    }
    
    /**
     * Startup code for child class
     */
    public function init() {}


    /**
     * Breadcrumb calculations should occur in here.
     */
    public function breadcrumb() {}


    /**
     * Sets wrapper id (to be retrieved from the registry while parsing)
     * Example e107::getScBatch('contact')->wrapper('contact/form');
     * which results in using the $CONTACT_WRAPPER['form'] wrapper in the parsing phase
     * Template cannot be loaded via include, only by getTemplate or getCoreTemplate
     * e107::getScBatch() must be used also.
     * @param string $id
     * @return $this|null
     */
    public function wrapper($id = null)
    {
        if(null === $id) return $this->wrapper;
        
        if(false === $id) $id = null;
        $this->wrapper = $id;

        return $this;
    }


    /**
     * @return null
     */
    public function getWrapperID()
    {
        return $this->wrapper;
    }


    /**
     * @param $data
     * @return $this|null
     */
    public function editable($data=null)
    {
        if(null === $data) return $this->editable;
        $this->editable = $data;

        return $this;
    }


    /**
     * Set external array data to be used in the batch
     * Use setVars() - preferred. 
     *  //XXX will soon become private. Use setVars() instead. 
     * @param array $eVars
     * @return e_shortcode
     */
    public function setParserVars($eVars)
    {
        $this->var = $eVars;
        return $this;
    }

    /**
     * Alias of setParserVars - Preferred use by Plugins.
     * Sets the value of $sc->var
     * @param $eVars
     * @return e_shortcode
     */
    public function setVars($eVars) // Alias of setParserVars();
    {
        return $this->setParserVars($eVars);    
    }
    
    /**
     * Add array to current parser array data
     * @param array $array
     * @return e_shortcode
     */
    public function addParserVars($array) 
    {
        if(!is_array($array)) return $this;
        $this->var = array_merge($this->var, $array);
        return $this;
    }
    
    /**
     * Alias of addParserVars()
     * @param array $array
     * @return e_shortcode
     */
    public function addVars($array) 
    {
        return $this->addParserVars($array);
    }

    /**
     * Get external simple parser object
     *
     * @return array
     */
    public function getParserVars()
    {
        return $this->var;
    }

    /**
     * Alias of getParserVars()
     *
     * @return array
     */
    public function getVars()
    {
        return $this->getParserVars();
    }
    
    /**
     * Batch mod
     * @param string mod
     * @return e_shortcode
     */
    public function setMode($mode)
    {
        $this->mode = ($mode == 'edit') ? 'edit' : 'view';
        return $this;            
    }
    
    /**
     * Add shortcode value
     * <code>e107::getScBatch('class_name')->setScVar('some_property', $some_value);</code>
     *
     * @param string $name
     * @param mixed $value
     * @return e_shortcode
     */
    public function setScVar($name, $value)
    {
        $this->scVars->$name = $value;
        return $this;
    }
    
    /**
     * Add shortcode values
     * <code>e107::getScBatch('class_name')->addScVars(array('some_property', $some_value));</code>
     *
     * @param array $vars
     * @return e_shortcode
     */
    public function addScVars($vars)
    {
        $this->scVars->addVars($vars);
        return $this;
    }

    /**
     * Retrieve shortcode value
     * <code>$some_value = e107::getScBatch('class_name')->getScVar('some_property');</code>
     *
     * @param string $name
     * @return mixed
     */
    public function getScVar($name)
    {
        return $this->scVars->$name;
    }
    
    /**
     * Retrieve all shortcode values
     * <code>$some_value = e107::getScBatch('class_name')->getScVars();</code>
     *
     * @return array
     */
    public function getScVars()
    {
        return $this->scVars->getVars();
    }

    /**
     * Check if shortcode variable is set
     * <code>if(e107::getScBatch('class_name')->issetScVar('some_property'))
     * {
     *         //do something
     * }</code>
     *
     * @param string $name
     * @return boolean
     */
    public function issetScVar($name)
    {
        return isset($this->scVars->$name);
    }

    /**
     * Unset shortcode value
     * <code>e107::getScBatch('class_name')->unsetScVar('some_property');</code>
     *
     * @param string $name
     * @return e_shortcode
     */
    public function unsetScVar($name)
    {
        $this->scVars->$name = null;
        unset($this->scVars->$name);
        return $this;
    }
    
    /**
     * Empty scvar object data
     * @return e_shortcode
     */
    public function emptyScVars()
    {
        $this->scVars->emptyVars();
        return $this;
    }

    /**
     * Magic setter - bind to eVars object
     *
     * @param string $name
     * @param mixed $value
     */
    public function __set($name, $value)
    {
        $this->setScVar($name, $value);
    }

    /**
     * Magic getter - bind to eVars object
     *
     * @param string $name
     * @return mixed value or null if key not found
     */
    public function __get($name)
    {
        return $this->getScVar($name);
    }

    /**
     * Magic method - bind to eVars object
     * NOTE: works on PHP 5.1.0+
     *
     * @param string $name
     * @return boolean
     */
    public function __isset($name)
    {
        return $this->issetScVar($name);
    }

    /**
     * Magic method - bind to eVars object
     * NOTE: works on PHP 5.1.0+
     *
     * @param string $name
     */
    public function __unset($name)
    {
        $this->unsetScVar($name);
    }
}