<?php

/**
 * File:
 * class.module.php
 *
 * Description:
 *  cApi class
 * 
 * @package Core
 * @subpackage cApi
 * @version $Rev$
 * @since 2.0
 * @author Ortwin Pinke <o.pinke@conlite.org>
 * @copyright (c) 2015, conlite.org
 * @license http://www.gnu.de/documents/gpl.en.html GPL v3 (english version)
 * @license http://www.gnu.de/documents/gpl.de.html GPL v3 (deutsche Version)
 * @link http://www.conlite.org ConLite.org
 * 
 * $Id$
 */
if (!defined('CON_FRAMEWORK')) {
    die('Illegal call');
}

cInclude('includes', 'functions.upl.php');

class cApiModuleCollection extends ItemCollection {

    /**
     * Constructor Function
     * @param none
     */
    public function __construct() {
        global $cfg;
        parent::__construct($cfg["tab"]["mod"], "idmod");
        $this->_setItemClass("cApiModule");
    }

    /**
     * Creates a new communication item
     */
    public function create($name) {
        global $auth, $client;
        $item = parent::createNewItem();

        $item->set("idclient", $client);
        $item->set("name", $name);
        $item->set("author", $auth->auth["uid"]);
        $item->set("created", date("Y-m-d H:i:s"), false);
        $item->store();
        return $item;
    }

    public function delete($iIdMod) {
        /* @var $oMod cApiModule */
        $oMod = $this->_itemClassInstance;
        $oMod->_bNoted = TRUE;
        $oMod->loadByPrimaryKey($iIdMod);
        if ($oMod->isLoaded()) {
            if ($oMod->hasModuleFolder()) {
                $oMod->deleteModuleFolder();
            }
        }
        unset($oMod);
        return parent::delete($iIdMod);
    }

}

/**
 * Module access class
 */
class cApiModule extends Item {

    protected $_error;

    /**
     * Assoziative package structure array
     * @var array
     */
    protected $_packageStructure;
    protected $_bOutputFromFile = false;
    protected $_bInputFromFile = false;

    /**
     * @var array
     */
    private $aUsedTemplates = array();

    /**
     * Configuration Array of ModFileEdit
     * array is set with entries in local config file
     * 
     * @var array 
     */
    private $_aModFileEditConf = array(
        'use' => false,
        'modFolderName' => 'data/modules'
    );

    /**
     *
     * @var string 
     */
    private $_sModAlias;

    /**
     *
     * @var string
     */
    private $_sModPath;
    private $_aModDefaultStruct = array(
        'css', 'js', 'php', 'template', 'image', 'lang', 'xml'
    );

    /**
     * Constructor Function
     * @param  mixed  $mId  Specifies the ID of item to load
     */
    public function __construct($mId = false) {
        $cfg = cRegistry::getConfig();
        $cfgClient = cRegistry::getClientConfig(cRegistry::getClientId());
        
        parent::__construct($cfg["tab"]["mod"], "idmod");

        // Using no filters is just for compatibility reasons.
        // That's why you don't have to stripslashes values if you store them
        // using ->set. You have to add slashes, if you store data directly
        // (data not from a form field)
        $this->setFilters(array(), array());

        $this->_packageStructure = array(
            "jsfiles" => $cfgClient["js"]["path"],
            "tplfiles" => $cfgClient["tpl"]["path"],
            "cssfiles" => $cfgClient["css"]["path"]
                );

        if (isset($cfg['dceModEdit']) && is_array($cfg['dceModEdit'])) {
            $this->_aModFileEditConf['clientPath'] = $cfgClient["path"]["frontend"];
            $this->_aModFileEditConf = array_merge($this->_aModFileEditConf, $cfg['dceModEdit']);
            if (!isset($cfg['dceModEdit']['modPath']) || empty($cfg['dceModEdit']['modPath'])) {
                $this->_aModFileEditConf['modPath'] = $cfgClient["path"]["frontend"]
                        . $this->_aModFileEditConf['modFolderName'] . "/";
            }
        }

        $oClient = new cApiClient(cRegistry::getClientId());
        $aClientProp = $oClient->getPropertiesByType('modfileedit');
        if (count($aClientProp) > 0) {
            $this->_aModFileEditConf = array_merge($this->_aModFileEditConf, $aClientProp);
        }

        if ($mId !== false) {
            $this->loadByPrimaryKey($mId);
        }
    }

    public function createModuleFolder() {
        //echo $this->_aModFileEditConf['modPath'];
        $sPathErrorLog = cRegistry::getConfigValue('path', 'logs').'errorlog.txt';
        
        if (is_writable($this->_aModFileEditConf['clientPath']) && !file_exists($this->_aModFileEditConf['modPath'])) {
            try {
                mkdir($this->_aModFileEditConf['modPath'], 0777, true);
            } catch (Exception $ex) {
                $oWriter = cLogWriter::factory("File", array('destination' => $sPathErrorLog));
                $oLog = new cLog($oWriter);
                $oLog->log($ex->getFile() . " (" . $ex->getLine() . "): " . $ex->getMessage(), cLog::WARN);
            }
        }

        if ($this->_aModFileEditConf['use'] == true && is_writable($this->_aModFileEditConf['modPath'])) {
            if (!is_dir($this->getModulePath())) {
                $this->_oldumask = umask(0);
                try {
                    mkdir($this->getModulePath(), 0777);
                } catch (Exception $ex) {
                    $oWriter = cLogWriter::factory("File", array('destination' => $sPathErrorLog));
                    $oLog = new cLog($oWriter);
                    $oLog->log($ex->getFile() . " (" . $ex->getLine() . "): " . $ex->getMessage(), cLog::WARN);
                }

                if (is_writable($this->getModulePath())) {
                    return $this->_createModuleStruct();
                }
                umask($this->_oldumask);
            }
        } else {
            $oWriter = cLogWriter::factory("File", array('destination' => $sPathErrorLog));
            $oLog = new cLog($oWriter);
            $oLog->log(__FILE__ . " (" . __LINE__ . "): " . 'Error: Cannot create mod path '.$this->getModulePath(), cLog::WARN);
        }
        return FALSE;
    }

    public function deleteModuleFolder() {
        if ($this->_aModFileEditConf['use'] == TRUE && is_writable($this->_aModFileEditConf['modPath'])) {
            if (is_dir($this->getModulePath())) {
                return $this->_recursiveRemoveDirectory($this->getModulePath());
            }
        }
        return FALSE;
    }

    public function getModuleAlias() {
        return $this->_sModAlias;
    }

    public function hasModuleFolder() {
        $sOldPath = $this->_aModFileEditConf['modPath'] . uplCreateFriendlyName($this->get("name"));
        return (file_exists($this->getModulePath()) || file_exists($sOldPath));
    }

    public function getModulePath() {
        if (empty($this->_sModPath)) {
            $this->_setModulePath();
        }
        return $this->_sModPath;
    }

    protected function _setModulePath() {
        $this->_sModPath = $this->_aModFileEditConf['modPath'] . $this->getModuleAlias() . "/";
    }

    /**
     * Returns the translated name of the module if a translation exists.
     *
     * @param none
     * @return string Translated module name or original
     */
    public function getTranslatedName() {
        global $lang;

        // If we're not loaded, return
        if ($this->virgin == true) {
            return false;
        }

        $modname = $this->getProperty("translated-name", $lang);

        if ($modname === false) {
            return $this->get("name");
        } else {
            return $modname;
        }
    }

    /**
     * Sets the translated name of the module
     *
     * @param $name string Translated name of the module
     * @return none
     */
    public function setTranslatedName($name) {
        global $lang;
        $this->setProperty("translated-name", $lang, $name);
    }

    /**
     * Parses the module for mi18n strings and returns them in an array
     *
     * @return array Found strings for this module
     */
    public function parseModuleForStrings() {
        global $cfg;
        if ($this->virgin == true) {
            return false;
        }

        // Fetch the code, append input to output
        $code = $this->get("output");
        $code .= $this->get("input");

        // Initialize array
        $strings = array();

        // Split the code into mi18n chunks
        $varr = preg_split('/mi18n([\s]*)\(([\s]*)"/', $code, -1);

        if (count($varr) > 1) {
            foreach ($varr as $key => $value) {
                // Search first closing
                $closing = strpos($value, '")');

                if ($closing === false) {
                    $closing = strpos($value, '" )');
                }

                if ($closing !== false) {
                    $value = substr($value, 0, $closing) . '")';
                }

                // Append mi18n again
                $varr[$key] = 'mi18n("' . $value;

                // Parse for the mi18n stuff
                preg_match_all('/mi18n([\s]*)\("(.*)"\)/', $varr[$key], $results);

                // Append to strings array if there are any results
                if (is_array($results[1]) && count($results[2]) > 0) {
                    $strings = array_merge($strings, $results[2]);
                }

                // Unset the results for the next run
                unset($results);
            }
        }

        // adding dynamically new module translations by content types
        // this function was introduced with contenido 4.8.13
        // checking if array is set to prevent crashing the module translation page
        if (is_array($cfg['translatable_content_types']) && count($cfg['translatable_content_types']) > 0) {
            // iterate over all defines cms content types
            foreach ($cfg['translatable_content_types'] as $sContentType) {
                // check if the content type exists and include his class file
                if (file_exists($cfg['contenido']['path'] . "classes/class." . strtolower($sContentType) . ".php")) {
                    cInclude("classes", "class." . strtolower($sContentType) . ".php");
                    // if the class exists, has the method "addModuleTranslations"
                    // and the current module contains this cms content type we
                    // add the additional translations for the module
                    if (class_exists($sContentType) && method_exists($sContentType, 'addModuleTranslations') && preg_match('/' . strtoupper($sContentType) . '\[\d+\]/', $code)) {

                        $strings = call_user_func(array($sContentType, 'addModuleTranslations'), $strings);
                    }
                }
            }
        }

        // Make the strings unique
        return array_unique($strings);
    }

    /**
     * Checks if the module is in use
     * @return bool    Specifies if the module is in use
     */
    public function moduleInUse($module, $bSetData = false) {
        global $cfg;

        $db = new DB_ConLite();

        $sql = "SELECT
                    c.idmod, c.idtpl, t.name
                FROM
                " . $cfg["tab"]["container"] . " as c,
                " . $cfg["tab"]["tpl"] . " as t
                WHERE
                    c.idmod = '" . Contenido_Security::toInteger($module) . "' AND
                    t.idtpl=c.idtpl
                GROUP BY c.idtpl
                ORDER BY t.name";
        $db->query($sql);

        if ($db->nf() == 0) {
            return false;
        } else {
            $i = 0;
            // save the datas of used templates in array
            if ($bSetData === true) {
                while ($db->next_record()) {
                    $this->aUsedTemplates[$i]['tpl_name'] = $db->f('name');
                    $this->aUsedTemplates[$i]['tpl_id'] = (int) $db->f('idmod');
                    $i++;
                }
            }

            return true;
        }
    }

    /**
     * Get the informations of used templates
     * @return array template data
     */
    public function getUsedTemplates() {
        return $this->aUsedTemplates;
    }

    /**
     * Checks if the module is a pre-4.3 module
     * @return boolean true if this module is an old one
     * 
     * @deprecated since version 2.0
     */
    public function isOldModule() {
        // Keywords to scan
        $scanKeywords = array('$cfgTab', 'idside', 'idsidelang');

        $input = $this->get("input");
        $output = $this->get("output");

        foreach ($scanKeywords as $keyword) {
            if (strstr($input, $keyword)) {
                return true;
            }
            if (strstr($output, $keyword)) {
                return true;
            }
        }
    }

    public function getField($field) {
        $value = parent::getField($field);

        switch ($field) {
            case "name":
                if ($value == "") {
                    $value = i18n("- Unnamed Module -");
                }
        }
        return ($value);
    }

    public function store($bJustStore = false) {
        global $cfg;
        /* dceModFileEdit (c)2009-2011 www.dceonline.de */
        if ($this->_aModFileEditConf['use'] == true && ($this->_aModFileEditConf['allModsFromFile'] == true || in_array($this->get('idmod'), $this->_aModFileEditConf['modsFromFile']))) {
            $this->modifiedValues['output'] = true;
            $this->modifiedValues['input'] = true;
        }
        /* End dceModFileEdit (c)2009-2011 www.dceonline.de */
        if ($bJustStore) {
            // Just store changes, e.g. if specifying the mod package
            parent::store();
        } else {
            cInclude("includes", "functions.con.php");

            parent::store();

            conGenerateCodeForAllArtsUsingMod($this->get("idmod"));

            if ($this->_shouldStoreToFile()) {
                if ($this->_makeFileDirectoryStructure()) {
                    $sRootPath = $cfg['path']['contenido'] . $cfg['path']['modules'] . $this->get("idclient") . "/";
                    file_put_contents($sRootPath . $this->get("idmod") . ".xml", $this->export($this->get("idmod") . ".xml", true));
                }
            }
        }
    }

    public function getModFileEditConf() {
        return $this->_aModFileEditConf;
    }

    protected function _recursiveRemoveDirectory($directory) {
        foreach (glob("{$directory}/*") as $file) {
            if (is_dir($file)) {
                $this->_recursiveRemoveDirectory($file);
            } else {
                unlink($file);
            }
        }
        return rmdir($directory);
    }

    protected function _makeFileDirectoryStructure() {
        global $cfg;

        $sRootPath = $cfg['path']['contenido'] . $cfg['path']['modules'];
        if (!is_dir($sRootPath)) {
            @mkdir($sRootPath);
        }

        $sRootPath = $cfg['path']['contenido'] . $cfg['path']['modules'] . $this->get("idclient") . "/";
        if (!is_dir($sRootPath)) {
            @mkdir($sRootPath);
        }

        if (is_dir($sRootPath)) {
            return true;
        } else {
            return false;
        }
    }

    protected function _shouldStoreToFile() {
        if (getSystemProperty("modules", "storeasfiles") == "true") {
            return true;
        } else {
            return false;
        }
    }

    /**
     * @return mixed
     */
    public function getError()
    {
        return $this->_error;
    }

    protected function _shouldLoadFromFiles() {
        if (getSystemProperty("modules", "loadfromfiles") == "true") {
            return true;
        } else {
            return false;
        }
    }

    /**
     *  Parse import xml file, stores data in global variable (-> event handler functions)
     *
     * @param string $sFile Filename including path of import xml file
     * @param string $sType Import type, "module" or "package"
     * @return bool Returns true, if file has been parsed
     */
    private function _parseImportFile($sFile, $sType = "module", $sEncoding = "ISO-8859-1") {
        global $_mImport;

        $oParser = new clXmlParser($sEncoding);

        if ($sType == "module") {
            $oParser->setEventHandlers(array("/module/name" => "cHandler_ModuleData",
                "/module/description" => "cHandler_ModuleData",
                "/module/type" => "cHandler_ModuleData",
                "/module/input" => "cHandler_ModuleData",
                "/module/output" => "cHandler_ModuleData"));
        } else {
            $aHandler = array("/modulepackage/guid" => "cHandler_ModuleData",
                #"/modulepackage/repository_guid"    => "cHandler_ModuleData",
                "/modulepackage/module/name" => "cHandler_ModuleData",
                "/modulepackage/module/description" => "cHandler_ModuleData",
                "/modulepackage/module/type" => "cHandler_ModuleData",
                "/modulepackage/module/input" => "cHandler_ModuleData",
                "/modulepackage/module/output" => "cHandler_ModuleData",
                "/modulepackage/module/input" => "cHandler_ModuleData");

            // Add file handler (e.g. js, css, templates)
            foreach ($this->_packageStructure As $sFileType => $sFilePath) {
                // Note, that $aHandler["/modulepackage/" . $sFileType] and using
                // a handler which uses the node name (here: FileType) doesn't work,
                // as the event handler for the filetype node will be fired
                // after the node has been successfully parsed, not before.
                // So, we have a little redundancy here, but maybe we need
                // this in the future.
                $aHandler["/modulepackage/" . $sFileType . "/area"] = "cHandler_ItemArea";
                $aHandler["/modulepackage/" . $sFileType . "/name"] = "cHandler_ItemName";
                $aHandler["/modulepackage/" . $sFileType . "/content"] = "cHandler_ItemData";
            }

            // Layouts
            $aHandler["/modulepackage/layouts/area"] = "cHandler_ItemArea";
            $aHandler["/modulepackage/layouts/name"] = "cHandler_ItemName";
            $aHandler["/modulepackage/layouts/description"] = "cHandler_ItemData";
            $aHandler["/modulepackage/layouts/content"] = "cHandler_ItemData";

            // Translations
            $aHandler["/modulepackage/translations/language"] = "cHandler_ItemArea";
            $aHandler["/modulepackage/translations/string/original"] = "cHandler_ItemName";
            $aHandler["/modulepackage/translations/string/translation"] = "cHandler_Translation";

            $oParser->setEventHandlers($aHandler);
        }

        if ($oParser->parseFile($sFile)) {
            return true;
        } else {
            $this->_error = $oParser->error;
            return false;
        }
    }

    /**
     * Imports the a module from a XML file
     * Uses xmlparser and callbacks
     *
     * @param string    $file     Filename of data file (full path)
     */
    public function import($sFile) {
        global $_mImport;

        if ($this->_parseImportFile($sFile, "module")) {
            $bStore = false;
            foreach ($_mImport["module"] as $key => $value) {
                if ($this->get($key) != $value) {
                    $this->set($key, addslashes($value));
                    $bStore = true;
                }
            }

            if ($bStore == true) {
                $this->store();
            }
            return true;
        } else {
            return false;
        }
    }

    /**
     * Exports the specified module strings to a file
     *
     * @param $filename string Filename to return
     * @param $return    boolean if false, the result is immediately sent to the browser
     */
    public function export($filename, $return = false) {
        $tree = new XmlTree('1.0', 'ISO-8859-1');
        $root = & $tree->addRoot('module');

        $root->appendChild("name", clHtmlSpecialChars($this->get("name")));
        $root->appendChild("description", clHtmlSpecialChars($this->get("description")));
        $root->appendChild("type", clHtmlSpecialChars($this->get("type")));
        $root->appendChild("input", clHtmlSpecialChars($this->get("input")));
        $root->appendChild("output", clHtmlSpecialChars($this->get("output")));

        if ($return == false) {
            ob_end_clean();
            header("Content-Type: text/xml");
            header("Etag: " . md5(mt_rand()));
            header("Content-Disposition: attachment;filename=\"$filename\"");
            $tree->dump(false);
        } else {
            return stripslashes($tree->dump(true));
        }
    }

    public function getPackageOverview($sFile) {
        global $_mImport;

        if ($this->_parseImportFile($sFile, "package")) {
            $aData = array();
            $aData["guid"] = $_mImport["module"]["guid"];
            $aData["repository_guid"] = $_mImport["module"]["repository_guid"];
            $aData["name"] = $_mImport["module"]["name"];

            // Files
            foreach ($this->_packageStructure as $sFileType => $sFilePath) {
                if (is_array($_mImport["items"][$sFileType])) {
                    $aData[$sFileType] = array_keys($_mImport["items"][$sFileType]);
                }
            }

            // Layouts
            if (is_array($_mImport["items"]["layouts"])) {
                $aData["layouts"] = array_keys($_mImport["items"]["layouts"]);
            }

            // Translation languages
            if (is_array($_mImport["translations"])) {
                $aData["translations"] = array_keys($_mImport["translations"]);
            }

            return $aData;
        } else {
            return false;
        }
    }

    /**
     * Imports a module package from a XML file Uses xmlparser and callbacks
     *
     * @param string    $sFile         Filename of data file (including path)
     * @param array        $aOptions    Optional. An array of arrays specifying, how the items
     *                                 of the xml file will be imported. If specified, has to
     *                                 contain an array of this structure:
     *
     * $aOptions["items"][<filetype>][<clHtmlSpecialChars(filename)>]                = "skip", "append" or "overwrite";
     * $aOptions["translations"][<PackageLanguage>]    = <AssignedIDLang>;
     *
     *                                 If a file is not mentioned in the $aOptions["items"][<filetype>]
     *                                 array, it is new and will be imported.
     *
     *                                 If a <PackageLang> is not found in $aOptions["translations"],
     *                                 then the translations for this language will be ignored
     *
     * @return bool Returns true, if import has been successfully finished
     */
    public function importPackage($sFile, $aOptions = array()) {
        global $_mImport, $client;

        cInclude("includes", "functions.file.php");
        cInclude("includes", "functions.lay.php"); // You won't believe the code in there (or what is missing in class.layout.php...)
        // Ensure correct options structure
        foreach ($this->_packageStructure as $sFileType => $sFilePath) {
            if (!is_array($aOptions["items"][$sFileType])) {
                $aOptions["items"][$sFileType] = array();
            }
        }

        // Layouts
        if (!is_array($aOptions["items"]["layouts"])) {
            $aOptions["items"]["layouts"] = array();
        }

        // Translations
        if (!is_array($aOptions["translations"])) {
            $aOptions["translations"] = array();
        }

        // Parse file
        if ($this->_parseImportFile($sFile, "package")) {
            // Import data
            // Module
            foreach ($_mImport["module"] as $sKey => $sData) {
                if ($this->get($sKey) != $sData) {
                    $this->set($sKey, addslashes($sData));
                    $bStore = true;
                }
            }

            if ($bStore == true) {
                $this->store();
            }

            // Files
            foreach ($this->_packageStructure as $sFileType => $sFilePath) {
                if (is_array($_mImport["items"][$sFileType])) {
                    foreach ($_mImport["items"][$sFileType] as $sFileName => $aContent) {
                        if (!array_key_exists(clHtmlSpecialChars($sFileName), $aOptions["items"][$sFileType]) || $aOptions["items"][$sFileType][clHtmlSpecialChars($sFileName)] == "overwrite") {
                            if (!file_exists($sFilePath . $sFileName)) {
                                createFile($sFileName, $sFilePath);
                            }
                            fileEdit($sFileName, $aContent["content"], $sFilePath);
                        } else if ($aOptions["items"][$sFileType][clHtmlSpecialChars($sFileName)] == "append") {
                            $sOriginalContent = getFileContent($sFileName, $sFilePath);
                            fileEdit($sFileName, $sOriginalContent . $aContent["content"], $sFilePath);
                        }
                    }
                }
            }

            // Layouts
            if (is_array($_mImport["items"]["layouts"])) {
                foreach ($_mImport["items"]["layouts"] as $sLayout => $aContent) {
                    if (!array_key_exists(clHtmlSpecialChars($sLayout), $aOptions["items"]["layouts"]) || $aOptions["items"]["layouts"][clHtmlSpecialChars($sLayout)] == "overwrite") {
                        $oLayouts = new cApiLayoutCollection;
                        $oLayouts->setWhere("idclient", $client);
                        $oLayouts->setWhere("name", $sLayout);
                        $oLayouts->query();

                        if (!$oLayout = $oLayouts->next()) {
                            layEditLayout(false, addslashes($sLayout), addslashes($aContent["description"]), addslashes($aContent["content"]));
                        } else {
                            layEditLayout($oLayout->get($oLayout->primaryKey), addslashes($sLayout), addslashes($aContent["description"]), addslashes($aContent["content"]));
                        }
                    } elseif ($aOptions["items"]["layouts"][clHtmlSpecialChars($sLayout)] == "append") {
                        $oLayouts = new cApiLayoutCollection;
                        $oLayouts->setWhere("idclient", $client);
                        $oLayouts->setWhere("name", $sLayout);
                        $oLayouts->query();

                        if (!$oLayout = $oLayouts->next()) {
                            layEditLayout(false, addslashes($sLayout), addslashes($aContent["description"]), addslashes($aContent["content"]));
                        } else {
                            layEditLayout($oLayout->get($oLayout->primaryKey), addslashes($sLayout), addslashes($oLayout->get("description") . $aContent["description"]), addslashes($oLayout->get("code") . $aContent["content"]));
                        }
                    }
                }
            }

            // Translations
            if (is_array($_mImport["translations"])) {
                $oTranslations = new cApiModuleTranslationCollection();
                $iID = $this->get($this->primaryKey);

                foreach ($_mImport["translations"] as $sPackageLang => $aTranslations) {
                    if (array_key_exists($sPackageLang, $aOptions["translations"])) {
                        foreach ($_mImport["translations"][$sPackageLang] as $sOriginal => $sTranslation) {
                            $oTranslations->create($iID, $aOptions["translations"][$sPackageLang], $sOriginal, $sTranslation);
                        }
                    }
                }
            }
            return true;
        } else {
            return false;
        }
    }

    /**
     * Exports the specified module and attached files to a file
     *
     * @param string    $sPackageFileName    Filename to return
     * @param bool        $bReturn            if false, the result is immediately sent to the browser
     */
    public function exportPackage($sPackageFileName, $bReturn = false) {
        global $cfgClient, $client;

        cInclude("includes", "functions.file.php");

        $oTree = new XmlTree('1.0', 'ISO-8859-1');
        $oRoot = & $oTree->addRoot('modulepackage');

        $oRoot->appendChild("package_guid", $this->get("package_guid"));
        $oRoot->appendChild("package_data", $this->get("package_data")); // This is serialized and more or less informal data

        $aData = unserialize($this->get("package_data"));
        if (!is_array($aData)) {
            $aData = array();
            $aData["repository_guid"] = "";
            $aData["jsfiles"] = array();
            $aData["tplfiles"] = array();
            $aData["cssfiles"] = array();
            $aData["layouts"] = array();
            $aData["translations"] = array();
        }

        // Export basic module
        $oNodeModule = & $oRoot->appendChild("module");
        $oNodeModule->appendChild("name", clHtmlSpecialChars($this->get("name")));
        $oNodeModule->appendChild("description", clHtmlSpecialChars($this->get("description")));
        $oNodeModule->appendChild("type", clHtmlSpecialChars($this->get("type")));
        $oNodeModule->appendChild("input", clHtmlSpecialChars($this->get("input")));
        $oNodeModule->appendChild("output", clHtmlSpecialChars($this->get("output")));

        // Export files (e.g. js, css, templates)
        foreach ($this->_packageStructure As $sFileType => $sFilePath) {
            $oNodeFiles = & $oRoot->appendChild($sFileType);
            if (count($aData[$sFileType]) > 0) {
                foreach ($aData[$sFileType] as $sFileName) {
                    if (is_readable($sFilePath . $sFileName)) {
                        $sContent = getFileContent($sFileName, $sFilePath);
                        $oNodeFiles->appendChild("area", clHtmlSpecialChars($sFileType));
                        $oNodeFiles->appendChild("name", clHtmlSpecialChars($sFileName));
                        $oNodeFiles->appendChild("content", clHtmlSpecialChars($sContent));
                    }
                }
            }
        }
        unset($sContent);

        // Export layouts
        $oNodeLayouts = & $oRoot->appendChild("layouts");

        $oLayouts = new cApiLayoutCollection;
        $oLayouts->setWhere("idclient", $client);
        $oLayouts->query();

        while ($oLayout = $oLayouts->next()) {
            if (in_array($oLayout->get($oLayout->primaryKey), $aData["layouts"])) {
                $oNodeLayouts->appendChild("area", "layouts");
                $oNodeLayouts->appendChild("name", clHtmlSpecialChars($oLayout->get("name")));
                $oNodeLayouts->appendChild("description", clHtmlSpecialChars($oLayout->get("description")));
                $oNodeLayouts->appendChild("content", clHtmlSpecialChars($oLayout->get("code")));
            }
        }
        unset($oLayout);
        unset($oLayouts);

        // Export translations
        $oLangs = new cApiLanguageCollection();
        $oLangs->setOrder("idlang");
        $oLangs->query();

        if ($oLangs->count() > 0) {
            $iIDMod = $this->get($this->primaryKey);
            while ($oLang = $oLangs->next()) {
                $iID = $oLang->get($oLang->primaryKey);

                if (in_array($iID, $aData["translations"])) {
                    $oNodeTrans = & $oRoot->appendChild("translations");
                    // This is nice, but it doesn't help so much,
                    // as this data is available too late on import ...
                    $oNodeTrans->setNodeAttribs(array("origin-language-id" => $iID,
                        "origin-language-name" => clHtmlSpecialChars($oLang->get("name"))));
                    // ... so we store the important information with the data
                    $oNodeTrans->appendChild("language", clHtmlSpecialChars($oLang->get("name")));

                    $oTranslations = new cApiModuleTranslationCollection;
                    $oTranslations->setWhere("idmod", $iIDMod);
                    $oTranslations->setWhere("idlang", $iID);
                    $oTranslations->query();

                    while ($oTranslation = $oTranslations->next()) {
                        $oNodeString = & $oNodeTrans->appendChild("string");
                        $oNodeString->appendChild("original", clHtmlSpecialChars($oTranslation->get("original")));
                        $oNodeString->appendChild("translation", clHtmlSpecialChars($oTranslation->get("translation")));
                    }
                }
            }
        }
        unset($oLangs);
        unset($oLang);

        if ($bReturn == false) {
            ob_end_clean();
            header("Content-Type: text/xml");
            header("Etag: " . md5(mt_rand()));
            header("Content-Disposition: attachment;filename=\"$sPackageFileName\"");
            $oTree->dump(false);
        } else {
            return stripslashes($oTree->dump(true));
        }
    }

    /* dceModFileEdit (c)2009-2012 www.dceonline.de */

    /**
     * Overridden parent method for hooking dceModFileEdit
     * 
     * @return void
     */
    protected function _onLoad() {
        $this->_sModAlias = strtolower(uplCreateFriendlyName($this->get('name')));
        $this->_sModAliasOld = uplCreateFriendlyName($this->get('name'));
        $this->_setOutputFromPhpFile();
        $this->_setInputFromPhpFile();
    }

    /**
     * Use a PHP-file, if present, for module output
     * 
     * @return boolean
     */
    private function _setOutputFromPhpFile() {
        // dceModEdit not enabled or not present
        if ($this->_aModFileEditConf['use'] !== true) {
            return false;
        }
        return $this->_setFieldFromFile('output', $this->_sModAlias . "_output.php");
    }

    /**
     * Use a PHP-file, if present, for module input
     * 
     * @return boolean
     */
    private function _setInputFromPhpFile() {
        cInclude('includes', 'functions.upl.php');
        // dceModEdit not enabled or not present
        if ($this->_aModFileEditConf['use'] !== true) {
            return false;
        }
        return $this->_setFieldFromFile('input', $this->_sModAlias . "_input.php");
    }

    private function _displayNoteFromFile($bIsOldPath = FALSE) {
        if (isset($this->_bNoted) && $this->_bNoted === true) {
            return;
        }
        global $frame, $area;
        if ($frame == 4 && $area == 'mod_edit') {
            $sAddMess = '';
            if ($bIsOldPath) {
                $sAddMess .= "<br>" . i18n("Using old CamelCase for name of modulefolder. You may lowercase the name for modulefolder");
            }
            $oNote = new Contenido_Notification();
            $oNote->displayNotification('warning', i18n("Module uses Output- and/or InputFromFile. Editing and Saving may not be possible in backend.") . $sAddMess);
            $this->_bNoted = true;
        }
    }

    /**
     * read file and set an object field
     * 
     * @param string $sFile 
     * @param string $sField
     * @return boolean
     */
    private function _setFieldFromFile($sField, $sFile) {
        $bIsOldPath = TRUE;
        $sFile = strtolower($sFile);
        if (FALSE === strstr($sFile, $this->_aModFileEditConf['modPath'])) {
            $sFile = $this->_aModFileEditConf['modPath'] . $sFile;
        }
        // check for new struct since CL 2.0
        if (is_dir($this->_aModFileEditConf['modPath'] . $this->_sModAlias) && is_writable($this->_aModFileEditConf['modPath'] . $this->_sModAlias) && file_exists($this->_aModFileEditConf['modPath'] .
                        $this->_sModAlias . "/php/" . $this->_sModAlias .
                        "_" . $sField . ".php")) {
            $sFile = $this->_aModFileEditConf['modPath'] .
                    $this->_sModAlias . "/php/" . $this->_sModAlias .
                    "_" . $sField . ".php";
            $bIsOldPath = FALSE;
        }

        if (is_file($sFile) && is_readable($sFile)) {
            $iFileSize = (int) filesize($sFile);
            if ($iFileSize > 0 && $fh = fopen($sFile, 'r')) {
                $this->set($sField, fread($fh, $iFileSize), false);
                fclose($fh);
                switch ($sField) {
                    case "output":
                        $this->_bOutputFromFile = true;
                        break;
                    case "input":
                        $this->_bInputFromFile = true;
                    default:
                        break;
                }
                $this->_displayNoteFromFile($bIsOldPath);
                return true;
            }
        } else {
            switch ($sField) {
                case "output":
                    $this->_bOutputFromFile = false;
                    break;
                case "input":
                    $this->_bInputFromFile = false;
                default:
                    break;
            }
        }
        return false;
    }

    public function isLoadedFromFile($sWhat = "all") {
        switch ($sWhat) {
            case "all":
                return (($this->_bOutputFromFile || $this->_bInputFromFile) ? TRUE : FALSE);
                break;
            case "output":
                return $this->_bOutputFromFile;
            case "input":
                return $this->_bInputFromFile;
            default:
                return false;
        }
    }

    /* End dceModFileEdit (c)2009-2012 www.dceonline.de */

    private function _createModuleStruct() {
        $bDone = FALSE;
        if ($this->_aModFileEditConf['use'] == TRUE && is_writable($this->_sModPath)) {
            $bDone = TRUE;
            foreach ($this->_aModDefaultStruct as $sFolder) {
                $bDone = $bDone && mkdir($this->_sModPath . $sFolder);
            }
        }
        $this->_createModulePhpFiles();
        umask($this->_oldumask);
        return $bDone;
    }

    private function _createModulePhpFiles() {
        $sPath = $this->_sModPath . "php/";
        $aFileTpl = array(
            'output' => "<?php\n\n?>",
            'input' => "?><?php\n\n?><?php"
        );

        if (is_writable($sPath)) {
            $sOutputFile = $sPath . $this->_sModAlias . "_output.php";
            $sInputFile = $sPath . $this->_sModAlias . "_input.php";
            if (!file_exists($sOutputFile)) {
                $rFileHandler = fopen($sOutputFile, "w");
                if (is_resource($rFileHandler)) {
                    if ($this->getField('output') == "") {
                        fwrite($rFileHandler, $aFileTpl['output']);
                    } else {
                        fwrite($rFileHandler, $this->getField('output'));
                    }
                    fclose($rFileHandler);
                }
            }
            if (!file_exists($sInputFile)) {
                $rFileHandler = fopen($sInputFile, "w");
                if (is_resource($rFileHandler)) {
                    if ($this->getField('input') == "") {
                        fwrite($rFileHandler, $aFileTpl['input']);
                    } else {
                        fwrite($rFileHandler, $this->getField('input'));
                    }
                    fclose($rFileHandler);
                }
            }
        }
    }

}

// end class

class cApiModuleTranslationCollection extends ItemCollection {

    protected $_error;
    
    protected $f_obj;

    /**
     * Constructor Function
     * @param none
     */
    public function __construct() {
        global $cfg;
        parent::__construct($cfg["tab"]["mod_translations"], "idmodtranslation");
        $this->_setItemClass("cApiModuleTranslation");
    }

    /**
     * Creates a new module translation item
     */
    public function create($idmod, $idlang, $original, $translation = false) {
        // Check if the original already exists. If it does,
        // update the translation if passed
        $mod = new cApiModuleTranslation();
        $sorg = $mod->_inFilter($original);

        $this->select("idmod = '$idmod' AND idlang = '$idlang' AND original = '$sorg'");

        if ($item = $this->next()) {
            if ($translation !== false) {
                $item->set("translation", $translation);
                $item->store();
            }
            return $item;
        } else {
            $item = parent::createNewItem();
            $item->set("idmod", $idmod);
            $item->set("idlang", $idlang);
            $item->set("original", $original);
            $item->set("translation", $translation);
            $item->store();
            return $item;
        }
    }

    /**
     * Fetches a translation
     *
     * @param $module int Module ID
     * @param $lang   int Language ID
     * @param $string string String to lookup
     */
    public function fetchTranslation($module, $lang, $string) {
        // If the f_obj does not exist, create one
        if (!is_object($this->f_obj)) {
            $this->f_obj = new cApiModuleTranslation();
        }

        // Create original string
        $sorg = $this->_itemClassInstance->_inFilter($string);

        // Look up
        $this->select("idmod = '$module' AND idlang='$lang' AND original = '$sorg'");

        if ($t = $this->next()) {
            $translation = $t->get("translation");

            if ($translation != "") {
                return $translation;
            } else {
                return $string;
            }
        } else {
            return $string;
        }
    }

    public function import($idmod, $idlang, $file) {
        global $_mImport;

        $parser = new clXmlParser("ISO-8859-1");

        $parser->setEventHandlers(array("/module/translation/string/original" => "cHandler_ItemName",
            "/module/translation/string/translation" => "cHandler_Translation"));

        $_mImport["current_item_area"] = "current"; // Pre-specification, as this won't be set from the XML file (here)

        if ($parser->parseFile($file)) {
            foreach ($_mImport["translations"]["current"] as $sOriginal => $sTranslation) {
                $this->create($idmod, $idlang, $sOriginal, $sTranslation);
            }

            return true;
        } else {
            $this->_error = $parser->error;
            return false;
        }
    }

    /**
     * Exports the specified module strings to a file
     *
     * @param $idmod    int Module ID
     * @param $idlang   int Language ID
     * @param $filename string Filename to return
     * @param $return    boolean if false, the result is immediately sent to the browser
     */
    public function export($idmod, $idlang, $filename, $return = false) {
        $langobj = new cApiLanguage($idlang);

        #$langstring = $langobj->get("name") . ' ('.$idlang.')';

        $translations = new cApiModuleTranslationCollection;
        $translations->select("idmod = '$idmod' AND idlang='$idlang'");

        $tree = new XmlTree('1.0', 'ISO-8859-1');
        $root = & $tree->addRoot('module');

        $translation = & $root->appendChild('translation');
        $translation->setNodeAttribs(array("origin-language-id" => $idlang,
            "origin-language-name" => $langobj->get("name")));

        while ($otranslation = $translations->next()) {
            $string = &$translation->appendChild("string");

            $string->appendChild("original", clHtmlSpecialChars($otranslation->get("original")));
            $string->appendChild("translation", clHtmlSpecialChars($otranslation->get("translation")));
        }

        if ($return == false) {
            header("Content-Type: text/xml");
            header("Etag: " . md5(mt_rand()));
            header("Content-Disposition: attachment;filename=\"$filename\"");
            $tree->dump(false);
        } else {
            return $tree->dump(true);
        }
    }

}

/**
 * Module access class
 */
class cApiModuleTranslation extends Item {

    /**
     * Constructor Function
     * @param $loaditem Item to load
     */
    public function __construct($loaditem = false) {
        global $cfg;
        parent::__construct($cfg["tab"]["mod_translations"], "idmodtranslation");
        if ($loaditem !== false) {
            $this->loadByPrimaryKey($loaditem);
        }
    }

    public function useInFilter($sValue) {
        return $this->_inFilter($sValue);
    }

}

function cHandler_ModuleData($sName, $aAttribs, $sContent) {
    global $_mImport;
    $_mImport["module"][$sName] = $sContent;
}

// The following three functions references all file data (e.g. for css,
// js and template files) and layout data
// Note, that first the type is specified (from the "area" information
// in the xml file).
// Second, filename is specified based on "name" node content.
// Third, file content is stored using type, name and node content.
// You will have to specify individual handler functions, if one of
// the file areas may store additional data (e.g. a description)
function cHandler_ItemArea($sName, $aAttribs, $sContent) {
    global $_mImport;
    $_mImport["current_item_area"] = $sContent;
}

function cHandler_ItemName($sName, $aAttribs, $sContent) {
    global $_mImport;
    $_mImport["current_item_name"] = $sContent;
}

function cHandler_ItemData($sName, $aAttribs, $sContent) {
    global $_mImport;
    $_mImport["items"][$_mImport["current_item_area"]][$_mImport["current_item_name"]][$sName] = $sContent;
}

// Separate language area, as someone may specify "cssfiles" or something
// as language name, funny guy...
function cHandler_Translation($sName, $aAttribs, $sContent) {
    global $_mImport;
    $_mImport["translations"][$_mImport["current_item_area"]][$_mImport["current_item_name"]] = $sContent;
}
