YetiForceCompany/YetiForceCRM

View on GitHub
vtlib/Vtiger/PackageImport.php

Summary

Maintainability
F
1 wk
Test Coverage
F
33%
<?php
/* +**********************************************************************************
 * The contents of this file are subject to the vtiger CRM Public License Version 1.0
 * ("License"); You may not use this file except in compliance with the License
 * The Original Code is:  vtiger CRM Open Source
 * The Initial Developer of the Original Code is vtiger.
 * Portions created by vtiger are Copyright (C) vtiger.
 * All Rights Reserved.
 * Contributor(s): YetiForce S.A.
 * ********************************************************************************** */

namespace vtlib;

/**
 * Provides API to import module into vtiger CRM.
 */
class PackageImport extends PackageExport
{
    /**
     * Module Meta XML File (Parsed).
     */
    public $_modulexml;

    /**
     * Module Fields mapped by [modulename][fieldname] which
     * will be used to create customviews.
     */
    public $_modulefields_cache = [];

    /**
     * License of the package.
     */
    public $_licensetext = false;
    public $_errorText = '';
    public $packageType = '';
    public $parameters = [];

    /**
     * Parse the manifest file.
     *
     * @param \App\Zip $zip
     */
    public function __parseManifestFile(\App\Zip $zip)
    {
        if ($content = $zip->getFromName('manifest.xml')) {
            $this->_modulexml = simplexml_load_string($content);
            return true;
        }
        return false;
    }

    /**
     * Get type of package (as specified in manifest).
     *
     * @return false|string
     */
    public function type()
    {
        if (!empty($this->_modulexml) && !empty($this->_modulexml->type)) {
            return (string) $this->_modulexml->type;
        }
        return false;
    }

    /**
     * Get type of package (as specified in manifest).
     */
    public function getTypeName()
    {
        if (!empty($this->_modulexml) && !empty($this->_modulexml->type)) {
            $type = strtolower($this->_modulexml->type);
            switch ($type) {
                case 'extension':
                    $type = 'LBL_EXTENSION_MODULE';
                    break;
                case 'entity':
                    $type = 'LBL_BASE_MODULE';
                    break;
                case 'inventory':
                    $type = 'LBL_INVENTORY_MODULE';
                    break;
                case 'language':
                    $type = 'LBL_LANGUAGE_MODULE';
                    break;
                default:
                    break;
            }

            return $type;
        }
        return '';
    }

    /**
     * XPath evaluation on the root module node.
     *
     * @param string Path expression
     * @param mixed $path
     */
    public function xpath($path)
    {
        return $this->_modulexml->xpath($path);
    }

    /**
     * Are we trying to import language package?
     *
     * @param mixed|null $zipfile
     */
    public function isLanguageType($zipfile = null)
    {
        if (!empty($zipfile) && !$this->checkZip($zipfile)) {
            return false;
        }
        $packagetype = $this->type();
        if ($packagetype) {
            $lcasetype = strtolower($packagetype);
            if ('language' === $lcasetype) {
                return true;
            }
        }
        if ($packagetype) {
            $lcasetype = strtolower($packagetype);
            if ('layout' === $lcasetype) {
                return true;
            }
        }
        return false;
    }

    /**
     * Are we trying to import extension package?
     *
     * @param mixed|null $zipfile
     */
    public function isExtensionType($zipfile = null)
    {
        if (!empty($zipfile) && !$this->checkZip($zipfile)) {
            return false;
        }
        $packagetype = $this->type();
        if ($packagetype) {
            $lcasetype = strtolower($packagetype);
            if ('extension' === $lcasetype) {
                return true;
            }
        }
        return false;
    }

    /**
     * Checks font package type.
     *
     * @param null $zipfile
     *
     * @return bool
     */
    public function isFontType($zipfile = null)
    {
        if (!empty($zipfile) && !$this->checkZip($zipfile)) {
            return false;
        }
        $packagetype = $this->type();
        if ($packagetype) {
            $lcasetype = strtolower($packagetype);
            if ('font' === $lcasetype) {
                return true;
            }
        }
        return false;
    }

    public function isUpdateType($zipfile = null)
    {
        if (!empty($zipfile) && !$this->checkZip($zipfile)) {
            return false;
        }
        $packagetype = $this->type();

        if ($packagetype) {
            $lcasetype = strtolower($packagetype);
            if ('update' === $lcasetype) {
                return true;
            }
        }
        return false;
    }

    /**
     * Are we trying to import language package?
     *
     * @param mixed|null $zipfile
     */
    public function isLayoutType($zipfile = null)
    {
        if (!empty($zipfile) && !$this->checkZip($zipfile)) {
            return false;
        }
        $packagetype = $this->type();

        if ($packagetype) {
            $lcasetype = strtolower($packagetype);
            if ('layout' === $lcasetype) {
                return true;
            }
        }
        return false;
    }

    /**
     * checks whether a package is module bundle or not.
     *
     * @param string $zipfile - path to the zip file
     *
     * @return bool - true if given zipfile is a module bundle and false otherwise
     */
    public function isModuleBundle($zipfile = null)
    {
        // If data is not yet available
        if (!empty($zipfile) && !$this->checkZip($zipfile)) {
            return false;
        }
        return (bool) $this->_modulexml->modulebundle;
    }

    /**
     * @return array module list available in the module bundle
     */
    public function getAvailableModuleInfoFromModuleBundle()
    {
        $list = (array) $this->_modulexml->modulelist;

        return (array) $list['dependent_module'];
    }

    /**
     * Get the license of this package
     * NOTE: checkzip should have been called earlier.
     */
    public function getLicense()
    {
        return $this->_licensetext;
    }

    public function getParameters()
    {
        $params = [];
        if (empty($this->_modulexml->parameters)) {
            return $params;
        }
        foreach ($this->_modulexml->parameters->parameter as $parameter) {
            $params[] = $parameter;
        }
        return $params;
    }

    public function initParameters(\App\Request $request)
    {
        $data = [];
        foreach ($request->getAll() as $name => $value) {
            if (false !== strpos($name, 'param_')) {
                $name = str_replace('param_', '', $name);
                $data[$name] = $value;
            }
        }
        $this->parameters = $data;
    }

    /**
     * Check if zipfile is a valid package.
     *
     * @param mixed $zipfile
     */
    public function checkZip($zipfile)
    {
        $manifestFound = $languagefile_found = $layoutfile_found = $updatefile_found = $extensionfile_found = $moduleVersionFound = $fontfile_found = false;
        $moduleName = null;
        $zip = \App\Zip::openFile($zipfile, ['checkFiles' => false]);
        if ($this->__parseManifestFile($zip)) {
            $manifestFound = true;
            $moduleName = (string) $this->_modulexml->name;
            $isModuleBundle = (string) $this->_modulexml->modulebundle;
            if ('true' === $isModuleBundle && (!empty($this->_modulexml))
                    && (!empty($this->_modulexml->dependencies))
                    && (!empty($this->_modulexml->dependencies->vtiger_version))) {
                $languagefile_found = true;
            }
            // Do we need to check the zip further?
            if ($this->isLanguageType()) {
                $languagefile_found = true; // No need to search for module language file.
            }
            if ($this->isLayoutType()) {
                $layoutfile_found = true; // No need to search for module language file.
            }
            if ($this->isExtensionType()) {
                $extensionfile_found = true; // No need to search for module language file.
            }
            if ($this->isUpdateType()) {
                $updatefile_found = true; // No need to search for module language file.
            }
            if ($this->isFontType()) {
                $fontfile_found = true; // No need to search for module language file.
            }
        }
        for ($i = 0; $i < $zip->numFiles; ++$i) {
            $fileName = $zip->getNameIndex($i);
            $matches = [];
            $pattern = '/languages[\/\\\]' . \App\Config::main('default_language') . '[\/\\\]([^\/]+)\.json/';
            preg_match($pattern, $fileName, $matches);
            if (\count($matches) && \in_array($moduleName, $matches)) {
                $languagefile_found = true;
            }
            $settingsPattern = '/languages[\/\\\]' . \App\Config::main('default_language') . '[\/\\\]Settings[\/\\\]([^\/]+)\.json/';
            preg_match($settingsPattern, $fileName, $matches);
            if (\count($matches) && \in_array($moduleName, $matches)) {
                $languagefile_found = true;
            }
        }
        // Verify module language file.
        if (!$fontfile_found && !$updatefile_found && !$layoutfile_found && !$languagefile_found) {
            $errorText = \App\Language::translate('LBL_ERROR_NO_DEFAULT_LANGUAGE', 'Settings:ModuleManager');
            $errorText = str_replace('__DEFAULTLANGUAGE__', \App\Config::main('default_language'), $errorText);
            $this->_errorText = $errorText;
        }
        if (!empty($this->_modulexml)
            && !empty($this->_modulexml->dependencies)
            && !empty($this->_modulexml->dependencies->vtiger_version)) {
            $moduleVersion = (string) $this->_modulexml->dependencies->vtiger_version;
            $versionCheck = \App\Version::compare(\App\Version::get(), $moduleVersion);
            if (false !== $versionCheck && $versionCheck >= 0) {
                $moduleVersionFound = true;
            } else {
                $errorText = \App\Language::translate('LBL_ERROR_VERSION', 'Settings:ModuleManager');
                $errorText = str_replace('__MODULEVERSION__', $moduleVersion, $errorText);
                $errorText = str_replace('__CRMVERSION__', \App\Version::get(), $errorText);
                $this->_errorText = $errorText;
            }
        }
        $validzip = false;
        if ($manifestFound) {
            if ($languagefile_found && $moduleVersionFound) {
                $validzip = true;
            }
            if ($layoutfile_found && $moduleVersionFound) {
                $validzip = true;
            }
            if ($extensionfile_found && $moduleVersionFound) {
                $validzip = true;
            }
            if ($updatefile_found && $moduleVersionFound) {
                $validzip = true;
            }
            if ($fontfile_found) {
                $validzip = true;
            }
            if ($this->isLanguageType() && false !== strpos($this->_modulexml->prefix, '/')) {
                $validzip = false;
                $this->_errorText = \App\Language::translate('LBL_ERROR_NO_VALID_PREFIX', 'Settings:ModuleManager');
            }
            if (!empty($moduleName) && !empty($this->_modulexml->type) && \Settings_ModuleManager_Module_Model::checkModuleName($moduleName) && \in_array(strtolower($this->_modulexml->type), ['entity', 'inventory', 'extension'])) {
                $validzip = false;
                $this->_errorText = \App\Language::translate('LBL_INVALID_MODULE_NAME', 'Settings:ModuleManager');
            }
        }
        if ($validzip && !empty($this->_modulexml->license)) {
            if (!empty($this->_modulexml->license->inline)) {
                $this->_licensetext = (string) $this->_modulexml->license->inline;
            } elseif (!empty($this->_modulexml->license->file)) {
                $licensefile = (string) $this->_modulexml->license->file;
                if ($licenseContent = $zip->getFromName($licensefile)) {
                    $this->_licensetext = $licenseContent;
                } else {
                    $this->_licensetext = "Missing $licensefile!";
                }
            }
        }
        if ($zip) {
            $zip->close();
        }
        return $validzip;
    }

    /**
     * Get module name packaged in the zip file.
     *
     * @param mixed $zipfile
     */
    public function getModuleNameFromZip($zipfile)
    {
        if (!$this->checkZip($zipfile)) {
            return null;
        }
        return (string) $this->_modulexml->name;
    }

    /**
     * returns the name of the module.
     *
     * @return string - name of the module as given in manifest file
     */
    public function getModuleName()
    {
        return (string) $this->_modulexml->name;
    }

    /**
     * Cache the field instance for re-use.
     *
     * @param mixed $moduleInstance
     * @param mixed $fieldname
     * @param mixed $fieldInstance
     */
    public function __AddModuleFieldToCache($moduleInstance, $fieldname, $fieldInstance)
    {
        $this->_modulefields_cache["$moduleInstance->name"]["$fieldname"] = $fieldInstance;
    }

    /**
     * Get field instance from cache.
     *
     * @param mixed $moduleInstance
     * @param mixed $fieldname
     */
    public function __GetModuleFieldFromCache($moduleInstance, $fieldname)
    {
        return $this->_modulefields_cache["$moduleInstance->name"]["$fieldname"];
    }

    /**
     * Initialize Import.
     *
     * @param mixed $zipfile
     * @param mixed $overwrite
     */
    public function initImport($zipfile, $overwrite = true)
    {
        $module = $this->getModuleNameFromZip($zipfile);
        if (null !== $module) {
            $defaultLayout = \Vtiger_Viewer::getDefaultLayoutName();
            $zip = \App\Zip::openFile($zipfile, ['checkFiles' => false]);
            if ($zip->statName("$module.png")) {
                $zip->unzipFile("$module.png", "layouts/$defaultLayout/images/$module.png");
            }
            $zip->unzip([
                // Templates folder
                'templates' => "layouts/$defaultLayout/modules/$module",
                'public_resources' => "public_html/layouts/$defaultLayout/modules/$module/resources",
                // Cron folder
                'cron' => "cron/modules/$module",
                // Config
                'config' => 'config/Modules',
                // Modules folder
                'modules' => 'modules',
                // Settings folder
                'settings/modules' => "modules/Settings/$module",
                // Settings templates folder
                'settings/templates' => "layouts/$defaultLayout/modules/Settings/$module",
                'settings/public_resources' => "public_html/layouts/$defaultLayout/modules/Settings/$module/resources",
                //module images
                'images' => "layouts/$defaultLayout/images/$module",
                'updates' => 'cache/updates',
                'layouts' => 'layouts',
                'languages' => 'languages',
            ]);
        }
        return $module;
    }

    public function getTemporaryFilePath($filepath = false)
    {
        return 'cache/' . $filepath;
    }

    /**
     * Get dependent version.
     *
     * @return string
     */
    public function getDependentVtigerVersion(): string
    {
        return $this->_modulexml->dependencies->vtiger_version ?? '';
    }

    /**
     * Get dependent Maximum version.
     */
    public function getDependentMaxVtigerVersion()
    {
        return $this->_modulexml->dependencies->vtiger_max_version;
    }

    /**
     * Get package version.
     */
    public function getVersion()
    {
        return $this->_modulexml->version;
    }

    /**
     * Get package author name.
     */
    public function getAuthorName()
    {
        return $this->_modulexml->authorname;
    }

    /**
     * Get package author phone number.
     */
    public function getAuthorPhone()
    {
        return $this->_modulexml->authorphone;
    }

    /**
     * Get package author phone email.
     */
    public function getAuthorEmail()
    {
        return $this->_modulexml->authoremail;
    }

    /**
     * Get package author phone email.
     */
    public function getDescription()
    {
        return $this->_modulexml->description;
    }

    /**
     * Get premium.
     *
     * @return int
     */
    public function getPremium(): int
    {
        return (int) $this->_modulexml->premium;
    }

    public function getUpdateInfo()
    {
        return [
            'from' => $this->_modulexml->from_version,
            'to' => $this->_modulexml->to_version,
        ];
    }

    /**
     * Import Module from zip file.
     *
     * @param string Zip file name
     * @param bool True for overwriting existing module
     * @param mixed $zipfile
     * @param mixed $overwrite
     */
    public function import($zipfile, $overwrite = false)
    {
        $module = $this->getModuleNameFromZip($zipfile);
        if (null !== $module) {
            $zip = \App\Zip::openFile($zipfile, ['checkFiles' => false]);
            // If data is not yet available
            if (empty($this->_modulexml)) {
                $this->__parseManifestFile($zip);
            }
            $buildModuleArray = [];
            $installSequenceArray = [];
            $moduleBundle = (bool) $this->_modulexml->modulebundle;
            if (true === $moduleBundle) {
                $moduleList = (array) $this->_modulexml->modulelist;
                foreach ($moduleList as $moduleInfos) {
                    foreach ($moduleInfos as $moduleInfo) {
                        $moduleInfo = (array) $moduleInfo;
                        $buildModuleArray[] = $moduleInfo;
                        $installSequenceArray[] = $moduleInfo['install_sequence'];
                    }
                }
                sort($installSequenceArray);
                $zip->unzip($this->getTemporaryFilePath());
                foreach ($installSequenceArray as $sequence) {
                    foreach ($buildModuleArray as $moduleInfo) {
                        if ($moduleInfo['install_sequence'] == $sequence) {
                            $this->import($this->getTemporaryFilePath($moduleInfo['filepath']), $overwrite);
                        }
                    }
                }
            } else {
                $this->packageType = strtolower($this->_modulexml->type);
                switch ((string) $this->_modulexml->type) {
                    case 'update':
                        Functions::recurseDelete('cache/updates');
                        $zip = \App\Zip::openFile($zipfile, ['checkFiles' => false]);
                        $zip->extract('cache/updates');
                        $this->importUpdate();
                        break;
                    case 'font':
                        $this->importFont($zipfile);
                        break;
                    default:
                        $this->initImport($zipfile, $overwrite);
                        // Call module import function
                        $this->importModule();
                        break;
                }
            }
        }
        return $module;
    }

    /**
     * Import Module.
     */
    public function importModule()
    {
        $moduleName = (string) $this->_modulexml->name;
        $tabLabel = $this->_modulexml->label;
        $tabVersion = $this->_modulexml->version;
        $isextension = false;
        $moduleType = 0;
        if (!empty($this->_modulexml->type)) {
            $this->packageType = strtolower($this->_modulexml->type);
            if ('extension' == $this->packageType || 'language' == $this->packageType) {
                $isextension = true;
            }
            if ('inventory' == $this->packageType) {
                $moduleType = 1;
            }
        }

        $vtigerMinVersion = $this->_modulexml->dependencies->vtiger_version;
        $vtigerMaxVersion = $this->_modulexml->dependencies->vtiger_max_version;

        $moduleInstance = new Module();
        $moduleInstance->name = $moduleName;
        $moduleInstance->label = $tabLabel;
        $moduleInstance->isentitytype = (true !== $isextension);
        $moduleInstance->version = (!$tabVersion) ? 0 : $tabVersion;
        $moduleInstance->minversion = (!$vtigerMinVersion) ? false : $vtigerMinVersion;
        $moduleInstance->maxversion = (!$vtigerMaxVersion) ? false : $vtigerMaxVersion;
        $moduleInstance->type = $moduleType;
        $moduleInstance->premium = $this->getPremium();

        $moduleInstance->save();
        $moduleInstance->initWebservice();
        $this->moduleInstance = $moduleInstance;

        $this->importTables($this->_modulexml);
        $this->importBlocks($this->_modulexml, $moduleInstance);
        $this->importInventory();
        $this->importCustomViews($this->_modulexml, $moduleInstance);
        $this->importSharingAccess($this->_modulexml, $moduleInstance);
        $this->importEvents($this->_modulexml, $moduleInstance);
        $this->importActions($this->_modulexml, $moduleInstance);
        $this->importRelatedLists($this->_modulexml, $moduleInstance);
        $this->importCustomLinks($this->_modulexml, $moduleInstance);
        $this->importCronTasks($this->_modulexml);
        Module::fireEvent($moduleInstance->name, Module::EVENT_MODULE_POSTINSTALL);
        register_shutdown_function(function () {
            try {
                chdir(ROOT_DIRECTORY);
                (new \App\BatchMethod(['method' => '\App\UserPrivilegesFile::recalculateAll', 'params' => []]))->save();
            } catch (\Throwable $e) {
                \App\Log::error($e->getMessage() . PHP_EOL . $e->__toString());
                throw $e;
            }
        });
    }

    /**
     * Import Tables of the module.
     *
     * @param mixed $modulenode
     */
    public function importTables($modulenode)
    {
        if (empty($modulenode->tables) || empty($modulenode->tables->table)) {
            return;
        }
        $db = \App\Db::getInstance();
        $db->createCommand()->checkIntegrity(false)->execute();

        // Import the table via queries
        foreach ($modulenode->tables->table as $tablenode) {
            $tableName = $tablenode->name;
            $sql = (string) $tablenode->sql; // Convert to string format
            // Avoid executing SQL that will DELETE or DROP table data
            if (Utils::isCreateSql($sql)) {
                if (!Utils::checkTable($tableName)) {
                    \App\Log::trace("SQL: $sql ... ", __METHOD__);
                    $db->createCommand($sql)->execute();
                    \App\Log::trace('DONE', __METHOD__);
                }
            } else {
                if (Utils::isDestructiveSql($sql)) {
                    \App\Log::trace("SQL: $sql ... SKIPPED", __METHOD__);
                } else {
                    \App\Log::trace("SQL: $sql ... ", __METHOD__);
                    $db->createCommand($sql)->execute();
                    \App\Log::trace('DONE', __METHOD__);
                }
            }
        }
        $db->createCommand()->checkIntegrity(true)->execute();
    }

    /**
     * Import Blocks of the module.
     *
     * @param mixed $modulenode
     * @param mixed $moduleInstance
     */
    public function importBlocks($modulenode, $moduleInstance)
    {
        if (empty($modulenode->blocks) || empty($modulenode->blocks->block)) {
            return;
        }
        foreach ($modulenode->blocks->block as $blocknode) {
            $blockInstance = $this->importBlock($modulenode, $moduleInstance, $blocknode);
            $this->importFields($blocknode, $blockInstance, $moduleInstance);
        }
    }

    /**
     * Import Block of the module.
     *
     * @param mixed $modulenode
     * @param mixed $moduleInstance
     * @param mixed $blocknode
     */
    public function importBlock($modulenode, $moduleInstance, $blocknode)
    {
        $blocklabel = $blocknode->blocklabel;
        $blockInstance = new Block();
        $blockInstance->label = $blocklabel;
        if (isset($blocknode->sequence, $blocknode->display_status)) {
            $blockInstance->sequence = (string) ($blocknode->sequence);
            if ($blockInstance->sequence = '') {
                $blockInstance->sequence = null;
            }
            $blockInstance->showtitle = (string) ($blocknode->show_title);
            $blockInstance->visible = (string) ($blocknode->visible);
            $blockInstance->increateview = (string) ($blocknode->create_view);
            $blockInstance->ineditview = (string) ($blocknode->edit_view);
            $blockInstance->indetailview = (string) ($blocknode->detail_view);
            $blockInstance->display_status = (string) ($blocknode->display_status);
            $blockInstance->iscustom = (string) ($blocknode->iscustom);
            $blockInstance->islist = (string) ($blocknode->islist);
        } else {
            $blockInstance->display_status = null;
        }
        $moduleInstance->addBlock($blockInstance);

        return $blockInstance;
    }

    /**
     * Import Fields of the module.
     *
     * @param mixed $blocknode
     * @param mixed $blockInstance
     * @param mixed $moduleInstance
     */
    public function importFields($blocknode, $blockInstance, $moduleInstance)
    {
        if (empty($blocknode->fields) || empty($blocknode->fields->field)) {
            return;
        }

        foreach ($blocknode->fields->field as $fieldnode) {
            $this->importField($blocknode, $blockInstance, $moduleInstance, $fieldnode);
        }
    }

    /**
     * Import Field of the module.
     *
     * @param mixed $blocknode
     * @param mixed $blockInstance
     * @param mixed $moduleInstance
     * @param mixed $fieldnode
     */
    public function importField($blocknode, $blockInstance, $moduleInstance, $fieldnode)
    {
        $fieldInstance = new Field();
        $fieldInstance->name = (string) $fieldnode->fieldname;
        $fieldInstance->label = (string) $fieldnode->fieldlabel;
        $fieldInstance->table = (string) $fieldnode->tablename;
        $fieldInstance->column = (string) $fieldnode->columnname;
        $fieldInstance->uitype = (int) $fieldnode->uitype;
        $fieldInstance->generatedtype = (int) $fieldnode->generatedtype;
        $fieldInstance->readonly = (int) $fieldnode->readonly;
        $fieldInstance->presence = (int) $fieldnode->presence;
        $fieldInstance->defaultvalue = (string) $fieldnode->defaultvalue;
        $fieldInstance->maximumlength = (string) $fieldnode->maximumlength;
        $fieldInstance->sequence = (int) $fieldnode->sequence;
        $fieldInstance->quickcreate = (int) $fieldnode->quickcreate;
        $fieldInstance->quicksequence = (int) $fieldnode->quickcreatesequence;
        $fieldInstance->typeofdata = (string) $fieldnode->typeofdata;
        $fieldInstance->displaytype = (int) $fieldnode->displaytype;
        $fieldInstance->info_type = (string) $fieldnode->info_type;

        if (!empty($fieldnode->fieldparams)) {
            $fieldInstance->fieldparams = (string) $fieldnode->fieldparams;
        }

        if (!empty($fieldnode->helpinfo)) {
            $fieldInstance->helpinfo = (string) $fieldnode->helpinfo;
        }

        if (isset($fieldnode->masseditable)) {
            $fieldInstance->masseditable = (int) $fieldnode->masseditable;
        }

        if (isset($fieldnode->columntype) && !empty($fieldnode->columntype)) {
            $fieldInstance->columntype = (string) ($fieldnode->columntype);
        }

        if (!empty($fieldnode->tree_template)) {
            $templateid = $fieldInstance->setTreeTemplate($fieldnode->tree_template, $moduleInstance);
            $fieldInstance->fieldparams = $templateid;
        }
        if (!empty($fieldnode->numberInfo)) {
            $numberInfo = $fieldnode->numberInfo;
            \App\Fields\RecordNumber::getInstance($moduleInstance->id)->set('tabid', $moduleInstance->id)->set('prefix', $numberInfo->prefix)->set('leading_zeros', $numberInfo->leading_zeros)->set('postfix', $numberInfo->postfix)->set('start_id', $numberInfo->start_id)->set('cur_id', $numberInfo->cur_id)->set('reset_sequence', $numberInfo->reset_sequence)->set('cur_sequence', $numberInfo->cur_sequence)->save();
        }

        $blockInstance->addField($fieldInstance);

        // Set the field as entity identifier if marked.
        if (!empty($fieldnode->entityidentifier)) {
            $moduleInstance->entityidfield = $fieldnode->entityidentifier->entityidfield;
            $moduleInstance->entityidcolumn = $fieldnode->entityidentifier->entityidcolumn;
            $moduleInstance->setEntityIdentifier($fieldInstance);
        }

        // Check picklist values associated with field if any.
        if (!empty($fieldnode->picklistvalues) && !empty($fieldnode->picklistvalues->picklistvalue)) {
            $picklistvalues = [];
            foreach ($fieldnode->picklistvalues->picklistvalue as $picklistvaluenode) {
                $picklistvalues[] = $picklistvaluenode;
            }
            $fieldInstance->setPicklistValues($picklistvalues);
        }

        // Check related modules associated with this field
        if (!empty($fieldnode->relatedmodules) && !empty($fieldnode->relatedmodules->relatedmodule)) {
            $relatedmodules = [];
            foreach ($fieldnode->relatedmodules->relatedmodule as $relatedmodulenode) {
                $relatedmodules[] = $relatedmodulenode;
            }
            $fieldInstance->setRelatedModules($relatedmodules);
        }

        // Set summary field if marked in xml
        if (!empty($fieldnode->summaryfield)) {
            $fieldInstance->setSummaryField($fieldnode->summaryfield);
        }
        $this->__AddModuleFieldToCache($moduleInstance, $fieldnode->fieldname, $fieldInstance);

        return $fieldInstance;
    }

    /**
     * Import Custom views of the module.
     *
     * @param mixed $modulenode
     * @param mixed $moduleInstance
     */
    public function importCustomViews($modulenode, $moduleInstance)
    {
        if (empty($modulenode->customviews) || empty($modulenode->customviews->customview)) {
            return;
        }
        foreach ($modulenode->customviews->customview as $customviewnode) {
            $this->importCustomView($modulenode, $moduleInstance, $customviewnode);
        }
    }

    /**
     * Import Custom View of the module.
     *
     * @param mixed $modulenode
     * @param mixed $moduleInstance
     * @param mixed $customviewnode
     */
    public function importCustomView($modulenode, $moduleInstance, $customviewnode)
    {
        $filterInstance = new Filter();
        $filterInstance->name = $customviewnode->viewname;
        $filterInstance->isdefault = $customviewnode->setdefault;
        $filterInstance->inmetrics = $customviewnode->setmetrics;
        $filterInstance->presence = $customviewnode->presence;
        $filterInstance->privileges = $customviewnode->privileges;
        $filterInstance->featured = $customviewnode->featured;
        $filterInstance->sequence = $customviewnode->sequence;
        $filterInstance->description = $customviewnode->description;
        $filterInstance->sort = $customviewnode->sort;
        $moduleInstance->addFilter($filterInstance);
        foreach ($customviewnode->fields->field as $fieldnode) {
            if ((string) $fieldnode->modulename === $moduleInstance->name) {
                $fieldInstance = $this->__GetModuleFieldFromCache($moduleInstance, $fieldnode->fieldname);
            } else {
                $fieldInstance = Field::getInstance((string) $fieldnode->fieldname, Module::getInstance((string) $fieldnode->modulename));
            }
            if ($sourceFieldName = (string) $fieldnode->sourcefieldname ?? '') {
                $fieldInstance->sourcefieldname = $sourceFieldName;
            }
            $filterInstance->addField($fieldInstance, $fieldnode->columnindex);
        }
        if (!empty($customviewnode->rules)) {
            $filterInstance->addRule(\App\Json::decode($customviewnode->rules));
        }
    }

    /**
     * Import Sharing Access of the module.
     *
     * @param mixed $modulenode
     * @param mixed $moduleInstance
     */
    public function importSharingAccess($modulenode, $moduleInstance)
    {
        if (empty($modulenode->sharingaccess)) {
            return;
        }

        if (!empty($modulenode->sharingaccess->default)) {
            foreach ($modulenode->sharingaccess->default as $defaultnode) {
                $moduleInstance->setDefaultSharing($defaultnode);
            }
        }
    }

    /**
     * Import Events of the module.
     *
     * @param mixed $modulenode
     * @param mixed $moduleInstance
     */
    public function importEvents($modulenode, $moduleInstance)
    {
        if (empty($modulenode->eventHandlers) || empty($modulenode->eventHandlers->event)) {
            return;
        }
        $moduleId = \App\Module::getModuleId($moduleInstance->name);
        foreach ($modulenode->eventHandlers->event as &$eventNode) {
            \App\EventHandler::registerHandler($eventNode->eventName, $eventNode->className, $eventNode->includeModules, $eventNode->excludeModules, $eventNode->priority, $eventNode->isActive, $moduleId);
        }
    }

    /**
     * Import actions of the module.
     *
     * @param mixed $modulenode
     * @param mixed $moduleInstance
     */
    public function importActions($modulenode, $moduleInstance)
    {
        if (empty($modulenode->actions) || empty($modulenode->actions->action)) {
            return;
        }
        foreach ($modulenode->actions->action as $actionnode) {
            $this->importAction($modulenode, $moduleInstance, $actionnode);
        }
    }

    /**
     * Import action of the module.
     *
     * @param mixed $modulenode
     * @param mixed $moduleInstance
     * @param mixed $actionnode
     */
    public function importAction($modulenode, $moduleInstance, $actionnode)
    {
        $actionstatus = (string) $actionnode->status;
        if ('enabled' === $actionstatus) {
            $moduleInstance->enableTools((string) $actionnode->name);
        } else {
            $moduleInstance->disableTools((string) $actionnode->name);
        }
    }

    /**
     * Import related lists of the module.
     *
     * @param mixed $modulenode
     * @param mixed $moduleInstance
     */
    public function importRelatedLists($modulenode, $moduleInstance)
    {
        if (!empty($modulenode->relatedlists) && !empty($modulenode->relatedlists->relatedlist)) {
            foreach ($modulenode->relatedlists->relatedlist as $relatedlistnode) {
                $this->importRelatedlist($modulenode, $moduleInstance, $relatedlistnode);
            }
        }
        if (!empty($modulenode->inrelatedlists) && !empty($modulenode->inrelatedlists->inrelatedlist)) {
            foreach ($modulenode->inrelatedlists->inrelatedlist as $inRelatedListNode) {
                $this->importInRelatedlist($modulenode, $moduleInstance, $inRelatedListNode);
            }
        }
    }

    /**
     * Import related list of the module.
     *
     * @param mixed $modulenode
     * @param mixed $moduleInstance
     * @param mixed $relatedlistnode
     */
    public function importRelatedlist($modulenode, $moduleInstance, $relatedlistnode)
    {
        $relModuleInstance = Module::getInstance($relatedlistnode->relatedmodule);
        $label = $relatedlistnode->label;
        $actions = false;
        if (!empty($relatedlistnode->actions) && !empty($relatedlistnode->actions->action)) {
            $actions = [];
            foreach ($relatedlistnode->actions->action as $actionnode) {
                $actions[] = "$actionnode";
            }
        }
        $fields = [];
        if (!empty($relatedlistnode->fields)) {
            foreach ($relatedlistnode->fields->field as $fieldNode) {
                $fields[] = "$fieldNode";
            }
        }
        if ($relModuleInstance) {
            $moduleInstance->setRelatedList($relModuleInstance, "$label", $actions, "$relatedlistnode->function", "$relatedlistnode->field_name", $fields);
        }
        return $relModuleInstance;
    }

    public function importInRelatedlist($modulenode, $moduleInstance, $inRelatedListNode)
    {
        $inRelModuleInstance = Module::getInstance($inRelatedListNode->inrelatedmodule);
        $label = $inRelatedListNode->label;
        $actions = false;
        if (!empty($inRelatedListNode->actions) && !empty($inRelatedListNode->actions->action)) {
            $actions = [];
            foreach ($inRelatedListNode->actions->action as $actionnode) {
                $actions[] = "$actionnode";
            }
        }
        $fields = [];
        if (!empty($inRelatedListNode->fields)) {
            foreach ($inRelatedListNode->fields->field as $fieldNode) {
                $fields[] = "$fieldNode";
            }
        }
        if ($inRelModuleInstance) {
            $inRelModuleInstance->setRelatedList($moduleInstance, "$label", $actions, "$inRelatedListNode->function", "$inRelatedListNode->field_name", $fields);
        }
        return $inRelModuleInstance;
    }

    /**
     * Import custom links of the module.
     *
     * @param mixed $modulenode
     * @param mixed $moduleInstance
     */
    public function importCustomLinks($modulenode, $moduleInstance)
    {
        if (empty($modulenode->customlinks) || empty($modulenode->customlinks->customlink)) {
            return;
        }

        foreach ($modulenode->customlinks->customlink as $customlinknode) {
            $handlerInfo = null;
            if (isset($customlinknode->handler_path)) {
                $handlerInfo = [];
                $handlerInfo = ['path' => "$customlinknode->handler_path",
                    'class' => "$customlinknode->handler_class",
                    'method' => "$customlinknode->handler", ];
            }
            $moduleInstance->addLink(
                "$customlinknode->linktype", "$customlinknode->linklabel", "$customlinknode->linkurl", "$customlinknode->linkicon", "$customlinknode->sequence", $handlerInfo
            );
        }
    }

    /**
     * Import cron jobs of the module.
     *
     * @param mixed $modulenode
     */
    public function importCronTasks($modulenode)
    {
        if (empty($modulenode->crons) || empty($modulenode->crons->cron)) {
            return;
        }
        foreach ($modulenode->crons->cron as $cronTask) {
            if (empty($cronTask->status)) {
                $cronTask->status = Cron::$STATUS_DISABLED;
            } else {
                $cronTask->status = Cron::$STATUS_ENABLED;
            }
            if ((empty($cronTask->sequence))) {
                $cronTask->sequence = Cron::nextSequence();
            }
            Cron::register("$cronTask->name", "$cronTask->handler", "$cronTask->frequency", "$modulenode->name", "$cronTask->status", "$cronTask->sequence", "$cronTask->description");
        }
    }

    public function importUpdate()
    {
        $dirName = 'cache/updates/updates';
        $db = \App\Db::getInstance();
        $startTime = microtime(true);
        file_put_contents('cache/logs/update.log', PHP_EOL . ((string) $this->_modulexml->label) . ' - ' . date('Y-m-d H:i:s'), FILE_APPEND);
        ob_start();
        if (file_exists($dirName . '/init.php')) {
            require_once $dirName . '/init.php';
            $updateInstance = new \YetiForceUpdate($this->_modulexml);
            $updateInstance->package = $this;
            $result = $updateInstance->preupdate();
            file_put_contents('cache/logs/update.log', PHP_EOL . ' | ' . ob_get_clean(), FILE_APPEND);
            ob_start();
            if (false !== $result) {
                $updateInstance->update();
                if ($updateInstance->filesToDelete) {
                    foreach ($updateInstance->filesToDelete as $path) {
                        Functions::recurseDelete($path);
                    }
                }
                if (method_exists($updateInstance, 'afterDelete')) {
                    $updateInstance->afterDelete();
                }
                Functions::recurseCopy($dirName . '/files', '');
                if (method_exists($updateInstance, 'afterCopy')) {
                    $updateInstance->afterCopy();
                }
                if ($content = ob_get_clean()) {
                    file_put_contents('cache/logs/update.log', $content, FILE_APPEND);
                }
                ob_start();
                $result = $updateInstance->postupdate();
            }
        } else {
            Functions::recurseCopy($dirName . '/files', '');
            $result = true;
        }
        $db->createCommand()->insert('yetiforce_updates', [
            'user' => \Users_Record_Model::getCurrentUserModel()->get('user_name'),
            'name' => (string) $this->_modulexml->label,
            'from_version' => (string) $this->_modulexml->from_version,
            'to_version' => (string) $this->_modulexml->to_version,
            'result' => $result,
            'time' => date('Y-m-d H:i:s'),
        ])->execute();
        if ($result) {
            $db->createCommand()->update('vtiger_version', ['current_version' => (string) $this->_modulexml->to_version])->execute();
        }
        Functions::recurseDelete($dirName);
        register_shutdown_function(function () {
            try {
                Functions::recurseDelete('cache/templates_c');
            } catch (\Exception $e) {
                \App\Log::error($e->getMessage() . PHP_EOL . $e->__toString());
            }
        });
        \App\Module::createModuleMetaFile();
        \App\Cache::clear();
        \App\Cache::clearOpcache();
        Functions::recurseDelete('app_data/LanguagesUpdater.json');
        Functions::recurseDelete('app_data/SystemUpdater.json');
        Functions::recurseDelete('app_data/cron.php');
        Functions::recurseDelete('app_data/ConfReport_AllErrors.php');
        Functions::recurseDelete('app_data/shop.php');
        file_put_contents('cache/logs/update.log', PHP_EOL . date('Y-m-d H:i:s') . ' (' . round(microtime(true) - $startTime, 2) . ') | ' . ob_get_clean(), FILE_APPEND);
    }

    /**
     * Import inventory fields of the module.
     */
    public function importInventory()
    {
        if (1 !== $this->moduleInstance->type) {
            return false;
        }
        $module = (string) $this->moduleInstance->name;
        $inventory = \Vtiger_Inventory_Model::getInstance($module);
        $inventory->createInventoryTables();
        if (empty($this->_modulexml->inventory) || empty($this->_modulexml->inventory->fields->field)) {
            return false;
        }
        foreach ($this->_modulexml->inventory->fields->field as $fieldNode) {
            $fieldModel = $inventory->getFieldCleanInstance((string) $fieldNode->invtype);
            $fieldModel->setDefaultDataConfig();
            $fields = ['label', 'defaultValue', 'block', 'displayType', 'params', 'colSpan', 'columnName', 'sequence'];
            foreach ($fields as $name) {
                switch ($name) {
                    case 'label':
                        $value = \App\Purifier::purifyByType((string) $fieldNode->label, 'Text');
                        $fieldModel->set($name, $value);
                        break;
                    case 'defaultValue':
                        $value = \App\Purifier::purifyByType((string) $fieldNode->defaultvalue, 'Text');
                        $fieldModel->set($name, $value);
                        break;
                    case 'block':
                        $blockId = (int) $fieldNode->block;
                        if (!\in_array($blockId, $fieldModel->getBlocks())) {
                            throw new \App\Exceptions\IllegalValue("ERR_NOT_ALLOWED_VALUE||{$name}||" . $blockId, 406);
                        }
                        $fieldModel->set($name, $blockId);
                        break;
                    case 'displayType':
                        $displayType = (int) $fieldNode->displaytype;
                        if (!\in_array($displayType, $fieldModel->displayTypeBase())) {
                            throw new \App\Exceptions\IllegalValue("ERR_NOT_ALLOWED_VALUE||{$name}||" . $displayType, 406);
                        }
                        $fieldModel->set($name, $displayType);
                        break;
                    case 'params':
                        $value = \App\Purifier::purifyByType((string) $fieldNode->params, 'Text');
                        $fieldModel->set($name, $value);
                        break;
                    case 'colSpan':
                        $fieldModel->set($name, (int) $fieldNode->colspan);
                        break;
                    case 'columnName':
                        $fieldModel->set($name, \App\Purifier::purifyByType((string) $fieldNode->columnname, 'Alnum'));
                        break;
                    case 'sequence':
                        $fieldModel->set($name, (int) $fieldNode->sequence);
                        break;
                    default:
                        break;
                }
            }
            $inventory->saveField($fieldModel);
        }
    }

    /**
     * Import font package.
     *
     * @param string $zipfile
     *
     * @throws \App\Exceptions\AppException
     */
    public function importFont($zipfile)
    {
        $fontsDir = ROOT_DIRECTORY . '/public_html/layouts/resources/fonts';
        $zip = \App\Zip::openFile($zipfile, ['onlyExtensions' => ['ttf', 'txt', 'woff']]);
        $files = $zip->unzip(['fonts' => $fontsDir]);
        $fonts = \App\Json::read($fontsDir . '/fonts.json');
        $tempFonts = [];
        foreach ($fonts as $font) {
            $tempFonts[$font['family']][$font['weight']][$font['style']] = $font['file'];
        }
        foreach ($files as $key => &$file) {
            $file = \str_replace('fonts/', '', $file);
            if (empty($file)) {
                unset($files[$key]);
            }
        }
        $files = \array_flip($files);
        $missing = [];
        if (!empty($this->_modulexml->fonts->font)) {
            foreach ($this->_modulexml->fonts->font as $font) {
                if (!isset($files[(string) $font->file])) {
                    $missing[] = (string) $font->file;
                }
                if (!isset($tempFonts[(string) $font->family][(string) $font->weight][(string) $font->style])) {
                    $fonts[] = [
                        'family' => (string) $font->family,
                        'weight' => (string) $font->weight,
                        'style' => (string) $font->style,
                        'file' => (string) $font->file,
                    ];
                }
            }
        }
        if ($missing) {
            $this->_errorText = \App\Language::translate('LBL_ERROR_MISSING_FILES', 'Settings:ModuleManager') . ' ' . \implode(',', $missing);
        }
        $css = [];
        foreach ($fonts as $key => $font) {
            if (!\file_exists("$fontsDir/{$font['file']}")) {
                unset($fonts[$key]);
            } else {
                $woff = pathinfo($font['file'], PATHINFO_FILENAME) . '.woff';
                $fontCss = "@font-face {\n";
                $fontCss .= "    font-family: '{$font['family']}';\n";
                $fontCss .= "    font-style: {$font['style']};\n";
                $fontCss .= "    font-weight: {$font['weight']};\n";
                $fontCss .= "    src: local('{$font['family']}'), url({$woff}) format('woff');\n";
                $fontCss .= '}';
                $css[] = $fontCss;
            }
        }
        $css[] = '@font-face {
            font-family: \'DejaVu Sans\';
            font-style: normal;
            font-weight: 100;
            src: local(\'DejaVu Sans\'), url(\'DejaVuSans-ExtraLight.woff\') format(\'woff\');
        }
        @font-face {
            font-family: \'DejaVu Sans\';
            font-style: normal;
            font-weight: 400;
            src: local(\'DejaVu Sans\'), url(\'DejaVuSans.woff\') format(\'woff\');
        }
        @font-face {
            font-family: \'DejaVu Sans\';
            font-style: normal;
            font-weight: 700;
            src: local(\'DejaVu Sans\'), url(\'DejaVuSans-Bold.woff\') format(\'woff\');
        }
        @font-face {
            font-family: \'DejaVu Sans\';
            font-style: italic;
            font-weight: 700;
            src: local(\'DejaVu Sans\'), url(\'DejaVuSans-BoldOblique.woff\') format(\'woff\');
        }
        @font-face {
            font-family: \'DejaVu Sans\';
            font-style: italic;
            font-weight: 400;
            src: local(\'DejaVu Sans\'), url(\'DejaVuSans-Oblique.woff\') format(\'woff\');
        }
        * {
            font-family: \'DejaVu Sans\';
        }';
        file_put_contents($fontsDir . '/fonts.css', implode("\n", $css));
        \App\Json::save($fontsDir . '/fonts.json', array_values($fonts));
    }
}