<?php
/**
 * VersionX
 *
 * Copyright 2011 by Mark Hamstra <hello@markhamstra.com>
 *
 * This file is part of VersionX, a real estate property listings component
 * for MODX Revolution.
 *
 * VersionX is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * VersionX is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * VersionX; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
 * Suite 330, Boston, MA 02111-1307 USA
 *
 * @package versionx
*/

class VersionX {
    public $modx;
    private $chunks;
    private $tvs = array();
    public $config = array();
    public $categoryCache = array();

    public $debug = false;
    public $action = null;
    public $charset = null;


    /**
     * @param \modX $modx
     * @param array $config
     */
    function __construct(modX &$modx,array $config = array()) {
        $this->modx =& $modx;

        $basePath = $this->modx->getOption('versionx.core_path',$config,$this->modx->getOption('core_path').'components/versionx/');
        $assetsUrl = $this->modx->getOption('versionx.assets_url',$config,$this->modx->getOption('assets_url').'components/versionx/');
        $assetsPath = $this->modx->getOption('versionx.assets_path',$config,$this->modx->getOption('assets_path').'components/versionx/');
        $this->config = array_merge(array(
            'base_bath' => $basePath,
            'core_path' => $basePath,
            'model_path' => $basePath.'model/',
            'processors_path' => $basePath.'processors/',
            'elements_path' => $basePath.'elements/',
            'templates_path' => $basePath.'templates/',
            'assets_path' => $assetsPath,
            'js_url' => $assetsUrl.'js/',
            'css_url' => $assetsUrl.'css/',
            'assets_url' => $assetsUrl,
            'connector_url' => $assetsUrl.'connector.php',
        ),$config);

        require_once dirname(dirname(__FILE__)) . '/docs/version.inc.php';
        if (defined('VERSIONX_FULLVERSION')) {
            $this->config['version'] = VERSIONX_FULLVERSION;
        }
        $modelpath = $this->config['model_path'];
        $this->modx->addPackage('versionx',$modelpath);
        $this->modx->lexicon->load('versionx:default');
        
        $this->debug = $this->modx->getOption('versionx.debug',null,false);
        $this->getAction();
    }

    /**
     * @param string $ctx Context name
     * @return bool
     */
    public function initialize($ctx = 'web') {
        switch ($ctx) {
            case 'mgr':
                $this->modx->regClientStartupHTMLBlock('
                <script type="text/javascript">
                    Ext.onReady(function() {
                        VersionX.config = '.$this->modx->toJSON($this->config).';
                        VersionX.action = '.$this->action.';
                    });
                </script>

                <style type="text/css">
                    .ext-gecko .x-form-text, .ext-ie8 .x-form-text {padding-top: 0;}
                    .vx-added .x-form-item-label { color: green; }
                    .vx-changed .x-form-item-label { color: #dd6600; }
                    .vx-removed .x-form-item-label { color: #ff0000; }
                </style>');

                $this->modx->regClientStartupScript($this->config['js_url'].'mgr/versionx.class.js');
                $this->modx->regClientStartupScript($this->config['js_url'].'mgr/common/json2.js');
            break;
        }
        return true;
    }

    /**
    * Gets a Chunk and caches it; also falls back to file-based templates
    * for easier debugging.
    *
    * @access public
    * @param string $name The name of the Chunk
    * @param array $properties The properties for the Chunk
    * @return string The processed content of the Chunk
    * @author Shaun "splittingred" McCormick
    */
    public function getChunk($name,$properties = array()) {
        $chunk = null;
        if (!isset($this->chunks[$name])) {
            $chunk = $this->_getTplChunk($name);
            if (empty($chunk)) {
                $chunk = $this->modx->getObject('modChunk',array('name' => $name),true);
                if ($chunk == false) return false;
            }
            $this->chunks[$name] = $chunk->getContent();
        } else {
            $o = $this->chunks[$name];
            $chunk = $this->modx->newObject('modChunk');
            $chunk->setContent($o);
        }
        $chunk->setCacheable(false);
        return $chunk->process($properties);
    }

    /**
    * Returns a modChunk object from a template file.
    *
    * @access private
    * @param string $name The name of the Chunk. Will parse to name.chunk.tpl
    * @param string $postFix The postfix to append to the name
    * @return modChunk/boolean Returns the modChunk object if found, otherwise false.
    * @author Shaun "splittingred" McCormick
    */
    private function _getTplChunk($name,$postFix = '.tpl') {
        $chunk = false;
        $f = $this->config['elements_path'].'chunks/'.strtolower($name).$postFix;
        if (file_exists($f)) {
            $o = file_get_contents($f);
            /* @var modChunk $chunk */
            $chunk = $this->modx->newObject('modChunk');
            $chunk->set('name',$name);
            $chunk->setContent($o);
        }
        return $chunk;
    }

    /**
     * Creates a new version of a Resource.
     *
     * @param int|modResource|modStaticResource $resource
     * @param string $mode
     * @return bool
     *
     */
    public function newResourceVersion($resource, $mode = 'upd') {
        if ($resource instanceof modResource) {
            // We're retrieving the resource again to clean up raw post data we don't want.
            $resource = $this->modx->getObject('modResource',$resource->get('id'));
        } else {
            $resource = $this->modx->getObject('modResource',(int)$resource);
        }

        $rArray = $resource->toArray();

        /* @var vxResource $version */
        $version = $this->modx->newObject('vxResource');

        $v = array(
            'content_id' => $rArray['id'],
            'user' => $this->modx->user->get('id'),
            'mode' => $mode,
            'title' => $rArray[$this->modx->getOption('resource_tree_node_name',null,'pagetitle')],
            'context_key' => $rArray['context_key'],
            'class' => $rArray['class_key'],
            'content' => $resource->get('content'),
        );

        $version->fromArray($v);

        unset ($rArray['id'],$rArray['content']);
        $version->set('fields',$rArray);

        if (method_exists($resource,'getTemplateVars'))
            $tvs = $resource->getTemplateVars();
        else
            $tvs = call_user_func(array($this,'getTemplateVars'),$resource);

        $tvArray = array();
        /* @var modTemplateVar $tv */
        foreach ($tvs as $tv) {
            $tvArray[] = $tv->get(array('id','value'));
        }
        $version->set('tvs',$tvArray);

        if($this->checkLastVersion('vxResource', $version, $this->debug)) {
            return $version->save();
        }
        return true;
    }


    /**
     * Creates a new version of a Template.
     *
     * @param int|\modTemplate $template
     * @param string $mode
     * @return bool
     *
     */
    public function newTemplateVersion($template, $mode = 'upd') {
        if ($template instanceof modTemplate) {
            /* Fetch it again to prevent getting stuck with raw post data */
            $template = $this->modx->getObject('modTemplate', $template->get('id'));
        } else {
            $template = $this->modx->getObject('modTemplate', (int)$template);
        }
        $tArray = $template->toArray();

        /* @var vxTemplate $version */
        $version = $this->modx->newObject('vxTemplate');

        $v = array(
            'content_id' => $tArray['id'],
            'user' => $this->modx->user->get('id'),
            'mode' => $mode,
        );

        $version->fromArray(array_merge($v,$tArray));

        if($this->checkLastVersion('vxTemplate', $version)) {
            return $version->save();
        }
        return true;
    }


    /**
     * Creates a new version of a Template Variable.
     *
     * @param int|\modTemplateVar $tv
     * @param string $mode
     * @return bool
     *
     */
    public function newTemplateVarVersion($tv, $mode = 'upd') {
        if ($tv instanceof modTemplateVar) {
            /* Fetch it again to prevent getting stuck with raw post data */
            $tv = $this->modx->getObject('modTemplateVar', $tv->get('id'));
        } else {
            $tv = $this->modx->getObject('modTemplateVar', (int)$tv);
        }

        $tArray = $tv->toArray();

        /* @var modTemplateVar $version */
        $version = $this->modx->newObject('vxTemplateVar');

        $v = array(
            'content_id' => $tArray['id'],
            'user' => $this->modx->user->get('id'),
            'mode' => $mode,
        );

        $version->fromArray(array_merge($v,$tArray));

        if($this->checkLastVersion('vxTemplateVar', $version)) {
            return $version->save();
        }
        return true;
    }
    /**
     * Create a new version of a Chunk.
     *
     * @param int|\modChunk $chunk
     * @param string $mode
     * @return bool
     */
    public function newChunkVersion($chunk, $mode = 'upd') {
        if ($chunk instanceof modChunk) {
            /* Fetch it again to prevent getting stuck with raw post data */
            $chunk = $this->modx->getObject('modChunk', $chunk->get('id'));
        } else {
            $chunk = $this->modx->getObject('modChunk', (int)$chunk);
        }

        // prevents resource groups from failing in MODX versions prior to 2.2.14 (see github #8992 + fix)
        if (!($chunk instanceof modChunk)) {
            return false;
        }

        $cArray = $chunk->toArray();

        /* @var vxChunk $version */
        $version = $this->modx->newObject('vxChunk');
        
        $v = array(
            'content_id' => $cArray['id'],
            'user' => $this->modx->user->get('id'),
            'mode' => $mode,
        );

        $version->fromArray(array_merge($v,$cArray));

        if($this->checkLastVersion('vxChunk', $version)) {
            return $version->save();
        }
        return true;
    }

    /**
     * Creates a new version of a Snippet.
     *
     * @param int|\modSnippet $snippet
     * @param string $mode
     * @return bool
     */
    public function newSnippetVersion($snippet, $mode = 'upd') {
        if ($snippet instanceof modSnippet) {
            /* Fetch it again to prevent getting stuck with raw post data */
            $snippet = $this->modx->getObject('modSnippet', $snippet->get('id'));
        } else {
            $snippet = $this->modx->getObject('modSnippet', (int)$snippet);
        }

        $sArray = $snippet->toArray();

        /* @var vxSnippet $version */
        $version = $this->modx->newObject('vxSnippet');

        $v = array(
            'content_id' => $sArray['id'],
            'user' => $this->modx->user->get('id'),
            'mode' => $mode,
        );

        $version->fromArray(array_merge($v,$sArray));

        if($this->checkLastVersion('vxSnippet', $version)) {
            return $version->save();
        }
        return true;
    }

    /**
     * Creates a new version of a Plugin.
     *
     * @param int|\modPlugin $plugin
     * @param string $mode
     * @return bool
     */
    public function newPluginVersion($plugin, $mode = 'upd') {
        if ($plugin instanceof modPlugin) {
            /* Fetch it again to prevent getting stuck with raw post data */
            $plugin = $this->modx->getObject('modPlugin', $plugin->get('id'));
        } else {
            $plugin = $this->modx->getObject('modPlugin', (int)$plugin);
        }
        $pArray = $plugin->toArray();

        /* @var vxPlugin $version */
        $version = $this->modx->newObject('vxPlugin');

        $v = array(
            'content_id' => $pArray['id'],
            'user' => $this->modx->user->get('id'),
            'mode' => $mode,
        );

        $version->fromArray(array_merge($v,$pArray));

        if($this->checkLastVersion('vxPlugin', $version)) {
            return $version->save();
        }
        return true;
    }

    /**
     * Gets & prepares version details for output.
     *
     * @param string $class
     * @param int $id
     * @param bool $json
     * @param string $prefix
     * @return bool|array
     */
    public function getVersionDetails($class = 'vxResource',$id = 0, $json = false, $prefix = '') {
        $v = $this->modx->getObject($class,$id);
        /* @var xPDOObject $v */
        if ($v instanceof $class) {
            $vArray = $v->toArray();
            $vArray['mode'] = $this->modx->lexicon('versionx.mode.'.$vArray['mode']);

            /* Class specific processing */
            switch ($class) {
                case 'vxResource':
                    $vArray = array_merge($vArray,$vArray['fields']);

                    if ($vArray['parent'] != 0) {
                        /* @var modResource $parent */
                        $parent = $this->modx->getObject('modResource',$vArray['parent']);
                        if ($parent instanceof modResource) $vArray['parent'] = $parent->get('pagetitle') .' ('.$vArray['parent'].')';
                    }

                    /* Process content type */
                    /* @var modContentType $ct */
                    $ct = $this->modx->getObject('modContentType',$vArray['content_type']);
                    if ($ct instanceof modContentType)
                        $vArray['content_type'] = $ct->get('name');

                    $vArray['content'] = $this->_prepareCodeView($vArray['content']);

                    if ($vArray['content_dispo'] == 1) $vArray['content_dispo'] = $this->modx->lexicon('attachment');
                    else $vArray['content_dispo'] = $this->modx->lexicon('inline');

                    /* Process boolean values */
                    $vArray['published'] = (intval($vArray['published'])) ? $this->modx->lexicon('yes') : $this->modx->lexicon('no');
                    $vArray['hidemenu'] = (intval($vArray['hidemenu'])) ? $this->modx->lexicon('yes') : $this->modx->lexicon('no');
                    $vArray['isfolder'] = (intval($vArray['isfolder'])) ? $this->modx->lexicon('yes') : $this->modx->lexicon('no');
                    $vArray['richtext'] = (intval($vArray['richtext'])) ? $this->modx->lexicon('yes') : $this->modx->lexicon('no');
                    $vArray['searchable'] = (intval($vArray['searchable'])) ? $this->modx->lexicon('yes') : $this->modx->lexicon('no');
                    $vArray['cacheable'] = (intval($vArray['cacheable'])) ? $this->modx->lexicon('yes') : $this->modx->lexicon('no');
                    $vArray['deleted'] = (intval($vArray['deleted'])) ? $this->modx->lexicon('yes') : $this->modx->lexicon('no');

                    /* Process time stamps */
                    $df = $this->modx->config['manager_date_format'].' '.$this->modx->config['manager_time_format'];
                    $vArray['saved'] = ($vArray['saved'] != 0) ? date($df,strtotime($vArray['saved'])) : '';
                    $vArray['publishedon'] = ($vArray['publishedon'] != 0) ? date($df,strtotime($vArray['publishedon'])) : '';
                    $vArray['pub_date'] = ($vArray['pub_date'] != 0) ? date($df,strtotime($vArray['pub_date'])) : '';
                    $vArray['unpub_date'] = ($vArray['unpub_date'] != 0) ? date($df,strtotime($vArray['unpub_date'])) : '';

                    /* Get TV captions */
                    $tvArray = array();
                    foreach ($vArray['tvs'] as $tv) {
                        if (!isset($this->tvs[$tv['id']]) || empty($this->tvs[$tv['id']])) {
                            /* @var modTemplateVar $tvObj */
                            $tvObj = $this->modx->getObject('modTemplateVar',$tv['id']);
                            if ($tvObj instanceof modTemplateVar) {
                                $caption = $tvObj->get('caption');
                                if (empty($caption)) $caption = $tvObj->get('name');
                                $this->tvs[$tv['id']] = $caption;
                            } else {
                                $this->tvs[$tv['id']] = 'tv'.$tv['id'];
                            }
                        }
                        $tvArray[] = array_merge($tv,array('caption' => $this->tvs[$tv['id']]));
                    }
                    $vArray['tvs'] = $tvArray;
                    break;
                case 'vxTemplateVar':
                    $vArray['category'] = $this->getCategory($vArray['category']);
                    if (is_array($vArray['input_properties'])) {
                        foreach ($vArray['input_properties'] as $key => $value) {
                            if ($decoded = $this->modx->fromJSON($value)) {
                                $vArray['input_properties'][$key] = $decoded;
                            }
                        }
                    }
                    if (is_array($vArray['output_properties'])) {
                        foreach ($vArray['output_properties'] as $key => $value) {
                            if ($decoded = $this->modx->fromJSON($value)) {
                                $vArray['output_properties'][$key] = $decoded;
                            }
                        }
                    }
                    break;

                case 'vxTemplate':
                    $vArray['content'] =  $this->_prepareCodeView($vArray['content']);
                    $vArray['category'] = $this->getCategory($vArray['category']);
                    break;

                case 'vxChunk':
                    $vArray['snippet'] =  $this->_prepareCodeView($vArray['snippet']);
                    $vArray['category'] = $this->getCategory($vArray['category']);
                    break;

                case 'vxSnippet':
                    $vArray['snippet'] =  $this->_prepareCodeView($vArray['snippet']);
                    $vArray['category'] = $this->getCategory($vArray['category']);
                    break;

                case 'vxPlugin':
                    $vArray['plugincode'] =  $this->_prepareCodeView($vArray['plugincode']);
                    $vArray['category'] = $this->getCategory($vArray['category']);
                    break;
            }

            /* @var modUserProfile $up */
            $up = $this->modx->getObject('modUserProfile',array('internalKey' => $vArray['user']));
            if ($up instanceof modUserProfile) $vArray['user'] = $up->get('fullname');

            if (!empty($prefix)) {
                $ta = array();
                foreach ($vArray as $tk => $tv) {
                    $ta[$prefix.$tk] = $tv;
                }
                $vArray = $ta;
            }
            if ($json) return $this->modx->toJSON($vArray);
            return $vArray;
        }
        return false;
    }

    /**
     * @param $string
     *
     * @return string
     */
    private function _prepareCodeView($string) {
        $lines = explode("\n",$string);
        foreach ($lines as $idx => $line) {
            $pos = 0;
            while( substr($line, $pos, 1) == ' ') {
                $pos++;
            }
            $lines[$idx] = str_repeat('&nbsp;', $pos) . $this->htmlent(substr($line, $pos));
        }

        $lines = implode('<br />', $lines);
        return $lines;
    }

    /**
     * Provides backwards compatibility for MODX 2.0.8 (min. supported version)
     * Only use if modResource::getTemplateVars is not available. 
     * This function is identical to modResource::getTemplateVars in the MODX model. 
     * 
     * @static
     * @param modResource $resource
     * @return array|null
     */
    private static function getTemplateVars(modResource &$resource) {
        /* @var xPDOQuery $c */
        $c = $resource->xpdo->newQuery('modTemplateVar');
        $c->query['distinct'] = 'DISTINCT';
        $c->select($resource->xpdo->getSelectColumns('modTemplateVar', 'modTemplateVar'));
        if ($resource->isNew()) {
            $c->select(array(
                'modTemplateVar.default_text AS value',
                '0 AS resourceId'
            ));
        } else {
            $c->select(array(
                'IF(ISNULL(tvc.value),modTemplateVar.default_text,tvc.value) AS value',
                $resource->get('id').' AS resourceId'
            ));
        }
        $c->innerJoin('modTemplateVarTemplate','tvtpl',array(
            'tvtpl.tmplvarid = modTemplateVar.id',
            'tvtpl.templateid' => $resource->get('template'),
        ));
        if (!$resource->isNew()) {
            $c->leftJoin('modTemplateVarResource','tvc',array(
                'tvc.tmplvarid = modTemplateVar.id',
                'tvc.contentid' => $resource->get('id'),
            ));
        }
        $c->sortby('tvtpl.rank,modTemplateVar.rank');
        return $resource->xpdo->getCollection('modTemplateVar', $c);
    }

    /**
     * Checks the last saved version (if any).
     * Returns true if there is no earlier version, or something is different.
     * So if this returns true: go ahead and save the version.
     * If this returns false: nothing changed, don't bother.
     *
     * @param string $class
     * @param \xPDOObject $version
     *
     * @return bool
     */
    protected function checkLastVersion($class = 'vxResource', xPDOObject $version) {
        /* Get last version to make sure we've got some changes to save */
        $c = $this->modx->newQuery($class);
        $c->where(array('content_id' => $version->get('content_id')));
        $c->sortby('version_id','DESC');
        $c->limit(1);

        $lastVersion = $this->modx->getCollection($class,$c);
        $lastVersion = !empty($lastVersion) ? array_shift($lastVersion) : array();
        /* @var vxResource $lastVersion */

        /* If there's no earlier version, we can go ahead and
         return true to indicate we need to save the version */
        if (!($lastVersion instanceof $class)) { 
            if ($this->debug) $this->modx->log(xPDO::LOG_LEVEL_ERROR,"[VersionX] Saving a {$class} for ID {$version->get('content_id')}: No earlier version found.");
            return true;
        }

        $newVersionArray = $version->toArray();
        $lastVersionArray = $lastVersion->toArray();

        /* Get rid of excluded vars for the specific object. */
        $exclude = call_user_func(array($class,'getExcludeFields'));
        if ($this->debug) $this->modx->log(modX::LOG_LEVEL_ERROR,'[VersionX checkLastVersion] Exclude fields: ' . print_r($exclude, true));
        foreach ($exclude as $key => $value) {
            if (is_array($value)) {
                foreach ($value as $subfield) {
                    if (isset($newVersionArray[$key]) && isset($newVersionArray[$key][$subfield])) {
                        unset ($newVersionArray[$key][$subfield]);
                    }
                    if (isset($lastVersionArray[$key]) && isset($lastVersionArray[$key][$subfield])) {
                        unset ($lastVersionArray[$key][$subfield]);
                    }
                }
            } else {
                if (isset($lastVersionArray[$value])) { unset($lastVersionArray[$value]); }
                if (isset($newVersionArray[$value])) { unset($newVersionArray[$value]); }
            }
        }

        $newVersionFlat = $this->flattenArray($newVersionArray);
        $lastVersionFlat = $this->flattenArray($lastVersionArray);

        if ($this->debug) {
            $this->modx->log(modX::LOG_LEVEL_ERROR,'[VersionX checkLastVersion] New: ' . print_r($newVersionArray, true));
            $this->modx->log(modX::LOG_LEVEL_ERROR,'[VersionX checkLastVersion] New Flattened: ' . $newVersionFlat);
            $this->modx->log(modX::LOG_LEVEL_ERROR,'[VersionX checkLastVersion] Last: ' . print_r($lastVersionArray, true));
            $this->modx->log(modX::LOG_LEVEL_ERROR,'[VersionX checkLastVersion] Last Flattened: ' . $newVersionFlat);
        }

        /* If the flattened arrays don't match there's a difference and we return true to indicate we need to save. */
        if ($newVersionFlat != $lastVersionFlat) {
            return true;
        }

        if ($this->debug) $this->modx->log(xPDO::LOG_LEVEL_ERROR,"[VersionX] Not saving a {$class} for ID {$version->get('content_id')}: No changes found.");
        /* If we got here, there was a last version but it seemed nothing changes.
        Return false to indicate to NOT save a new version. */
        return false;
    }

    /**
     * Flattens an array recursively.
     * @param array $array
     *
     * @return array|string
     */
    public function flattenArray(array $array = array()) {
        if (!is_array($array)) return (string)$array;

        $string = array();
        foreach ($array as $key => $value) {
            if (is_array($value)) {
                $value = '{' . $this->flattenArray($value) .'}';
            }
            if (!empty($value)) {
                $string[] = $key . ':' . $value;
            }
        }
        $string = implode(',',$string);
        return $string;
    }

    /**
     * Outputs the JavaScript needed to add a tab to the panels.
     *
     * @param string $class
     */
    public function outputVersionsTab ($class = 'vxResource') {
        if (!class_exists($class)) {
            $path = $this->config['model_path'].'versionx/'.strtolower($class).'.class.php';
            if (file_exists($path)) {
                require_once ($path);
            } 
            if (!class_exists($class)) {
                $this->modx->log(modX::LOG_LEVEL_ERROR,'[VersionX::outputVersionsTab] Error loading class '.$class);
                return;
            }
        }
        $langs = $this->_getLangs();
        $action = $this->getAction();
        $jsurl = $this->config['js_url'].'mgr/';
        
        /* Load class & set inVersion to true, indicating we're not looking at the VersionX controller. */
        $this->modx->regClientStartupScript($jsurl.'versionx.class.js');
        $this->modx->regClientStartupHTMLBlock('
            <script type="text/javascript">
                VersionX.config = '.$this->modx->toJSON($this->config).';
                VersionX.inVersion = true;
                VersionX.action = '.$action.';
                '.$langs.'
            </script>
        ');
        
        /* Get the different individual JS to add to the page */
        $tabjs = call_user_func(array($class,'getTabJavascript'));
        if (is_array($tabjs)) {
            foreach ($tabjs as $js) {
                $this->modx->regClientStartupScript($jsurl.$js);
            }
        }
        
        /* Get the template and register it */
        $tplName = call_user_func(array($class,'getTabTpl'));
        $tplFile = $this->config['templates_path'] . $tplName . '.tpl';
        if (file_exists($tplFile)) {
            $tpl = file_get_contents($tplFile);
            if (!empty($tpl)) {
                $this->modx->regClientStartupHTMLBlock($tpl);
            }
        }
        
        
    }

    /**
     * Gets language strings for use on non-VersionX controllers.
     * @return string
     */
    public function _getLangs() {
        $entries = $this->modx->lexicon->loadCache('versionx');
		$langs = 'Ext.applyIf(MODx.lang,' . $this->modx->toJSON($entries) . ');';
        return $langs;
    }

    /**
     * Gets the action ID for the VersionX controller.
     * 
     * @return int
     */
    public function getAction() {
        if (!$this->action) {
            /* @var modAction $action */
            $action = $this->modx->getObject('modAction',array(
                'namespace' => 'versionx',
                'controller' => 'controllers/index',
            ));
            if ($action) {
                $this->action = $action->get('id');
            } else {
                $this->action = "0";
            }
        }
        return $this->action;
    }

    /**
     * @param $id
     *
     * @return string
     */
    public function getCategory($id) {
        if (!$id || $id == 0) return '';
        if (isset($this->categoryCache[$id])) {
            return $this->categoryCache[$id];
        }
        /* @var modCategory $category */
        $category = $this->modx->getObject('modCategory',(int)$id);
        if ($category) {
            return $this->categoryCache[$id] = $category->get('category') . " ($id)";
        } else {
            return (string)$id;
        }
    }

    /**
     * Runs htmlentities() on the string with the proper character encoding.
     * @param string $string
     * @return string
     */
    public function htmlent($string = '') {
        if ($this->charset === null) {
            $this->charset = $this->modx->getOption('modx_charset', null, 'UTF-8');
        }
        return htmlentities($string, ENT_QUOTES | ENT_SUBSTITUTE, $this->charset);
    }

}

