<?php
/** @noinspection PhpMultipleClassDeclarationsInspection */

/*------------------------------------------------------------------------------
  $Id$

  AbanteCart, Ideal OpenSource Ecommerce Solution
  http://www.AbanteCart.com

  Copyright © 2011-2020 Belavier Commerce LLC

  This source file is subject to Open Software License (OSL 3.0)
  License details is bundled with this package in the file LICENSE.txt.
  It is also available at this URL:
  <http://www.opensource.org/licenses/OSL-3.0>

 UPGRADE NOTE:
   Do not edit or add to this file if you wish to upgrade AbanteCart to newer
   versions in the future. If you wish to customize AbanteCart for your
   needs please refer to http://www.AbanteCart.com for more information.
------------------------------------------------------------------------------*/

if (!defined('DIR_CORE')) {
    header('Location: static_pages/');
}

/**
 * Class AExtensionManager
 *
 * @property ExtensionsApi $extensions
 * @property ADB $db
 * @property ACache $cache
 * @property AConfig $config
 * @property ALanguage $language
 * @property ALoader $load
 * @property ModelToolUpdater $model_tool_updater
 * @property AHtml $html
 * @property AUser $user
 * @property ASession $session
 * @property ALog $log
 * @property AMessage $messages
 * @property ModelSettingStore $model_setting_store
 * */
class AExtensionManager
{
    /**
     * @var Registry
     */
    protected $registry;
    /**
     * @var array
     */
    public $errors = [];
    /**
     * @var array extension type list that manager can to install-uninstall
     */
    protected $extension_types = ['extension', 'extensions', 'payment', 'shipping', 'template'];

    public function __construct()
    {
        if (!IS_ADMIN) { // forbid for non admin calls
            throw new AException (AC_ERR_LOAD, 'Error: permission denied to access extension manager');
        }
        $this->registry = Registry::getInstance();
    }

    public function __get($key)
    {
        return $this->registry->get($key);
    }

    public function __set($key, $value)
    {
        $this->registry->set($key, $value);
    }

    public function getInstalled($type = '')
    {
        return $this->extensions->getInstalled($type);
    }

    public function getExtensionInfo($key = '')
    {
        return $this->extensions->getExtensionInfo($key);
    }

    public function getExtensionsList($data = [], $mode = '')
    {
        return $this->extensions->getExtensionsList($data, $mode);
    }

    /**
     * @param array $data
     *
     * @return int extension_id
     */
    public function add($data)
    {
        if (is_array($data)) {
            // check collision
            $data['type'] = $data['type'] == 'extension' ? 'extensions' : $data['type'];
            $type = ($data['type'] ? $data['type'] : 'extensions');
            $key = $data['key'];
            $status = $data['status'];
            $priority = $data['priority'];
            $version = $data['version'];
            $license_key = $data['license_key'];
            $category = $data['category'];
        } else {
            $key = $data;
            $type = 'extensions';
            $category = $status = $priority = $version = $license_key = '';
        }
        $sql = "SELECT extension_id 
                FROM ".$this->db->table("extensions")." 
                WHERE `key`= '".$this->db->escape($key)."'";
        $res = $this->db->query($sql);
        if ($res->num_rows) {
            return $res->row['extension_id'];
        }

        $this->db->query(
            "INSERT INTO ".$this->db->table("extensions")." 
             SET `type` = '".$this->db->escape($type)."',
                 `key` = '".$this->db->escape($key)."',
                 `category` = '".$this->db->escape($category)."',
                 `status` = '".$this->db->escape($status)."',
                 `priority` = '".$this->db->escape($priority)."',
                 `version` = '".$this->db->escape($version)."',
                 `license_key` = '".$this->db->escape($license_key)."',
                 `support_expiration` = '".$this->db->escape($data['support_expiration'])."',
                 `mp_product_url` = '".$this->db->escape($data['mp_product_url'])."',
                 `date_added` = NOW()"
        );

        $this->cache->remove('extensions');

        return $this->db->getLastId();
    }

    /**
     * Function gets parent extensions id and text id from extension dependencies table
     *
     * @param string $extension_txt_id
     *
     * @return array
     */
    public function getParentsExtensionTextId($extension_txt_id)
    {
        $info = $this->extensions->getExtensionInfo($extension_txt_id);
        $extension_id = (int) $info['extension_id'];
        if (!$extension_id) {
            return [];
        }

        $result = $this->db->query(
            "SELECT e.key, ed.extension_parent_id, e.status
            FROM ".$this->db->table("extension_dependencies")." ed
            LEFT JOIN ".$this->db->table("extensions")." e 
                ON ed.extension_parent_id = e.extension_id
            WHERE ed.extension_id = '".$extension_id."'"
        );
        return $result->rows;
    }

    /**
     * @param string $parent_extension_txt_id
     *
     * @return array
     */
    public function getChildrenExtensions($parent_extension_txt_id)
    {
        $info = $this->extensions->getExtensionInfo($parent_extension_txt_id);
        $extension_id = (int) $info['extension_id'];
        if (!$extension_id) {
            return [];
        }

        $result = $this->db->query(
            "SELECT e.*
            FROM ".$this->db->table("extension_dependencies")." ed
            LEFT JOIN ".$this->db->table("extensions")." e 
                ON ed.extension_id = e.extension_id
            WHERE ed.extension_parent_id = '".$extension_id."'"
        );

        return $result->rows;
    }

    /**
     * @param string $extension_txt_id
     * @param string $extension_parent_txt_id
     *
     * @return bool
     */
    public function addDependant($extension_txt_id, $extension_parent_txt_id)
    {
        $info = $this->extensions->getExtensionInfo($extension_parent_txt_id);
        $extension_parent_id = (int) $info['extension_id'];
        $info = $this->extensions->getExtensionInfo($extension_txt_id);
        $extension_id = (int) $info['extension_id'];
        if (!$extension_id || !$extension_parent_id) {
            return false;
        }

        $result = $this->db->query(
            "SELECT *
            FROM ".$this->db->table("extension_dependencies")." 
            WHERE extension_id = '".$extension_id."' 
                AND extension_parent_id = '".$extension_parent_id."'"
        );
        if (!$result->num_rows) {
            $sql = "INSERT INTO ".$this->db->table("extension_dependencies")." 
                        (extension_id, extension_parent_id )
                    VALUES ('".$extension_id."', '".$extension_parent_id."')";
            $this->db->query($sql);
        }
        $this->cache->remove('extensions');
        return true;
    }

    /**
     * function delete extension dependants from table by given id's
     *
     * @param string $extension_txt_id
     * @param string $extension_parent_txt_id
     *
     * @return bool
     */
    public function deleteDependant($extension_txt_id = '', $extension_parent_txt_id = '')
    {
        $info = $this->extensions->getExtensionInfo($extension_parent_txt_id);
        $extension_parent_id = $info ? (int) $info['extension_id'] : 0;

        $info = $this->extensions->getExtensionInfo($extension_txt_id);
        $extension_id = $info ? (int) $info['extension_id'] : 0;

        if (!$extension_id && !$extension_parent_id) {
            return false;
        }

        $sql = "DELETE FROM ".$this->db->table("extension_dependencies")."
                WHERE ";
        $where = [];
        if ($extension_id) {
            $where[] = "extension_id = '".$extension_id."'";
        }
        if ($extension_parent_id) {
            $where[] = "extension_parent_id = '".$extension_parent_id."'";
        }
        $sql .= implode(' AND ', $where);
        $this->db->query($sql);

        $this->cache->remove('extensions');

        return true;
    }

    /**
     * Save extension settings into database
     *
     * @param string $extension_txt_id
     * @param array $data
     *
     * @return bool
     * @throws AException
     */
    public function editSetting($extension_txt_id, $data)
    {
        if (empty($data)) {
            return false;
        }
        if (empty($extension_txt_id)) {
            $error = new AError (
                "Error: Can't edit setting because field \"extension_txt_id\" is empty. Settings array: "
                .implode(",", array_keys($data))
            );
            $error->toLog()->toDebug();
            return false;
        }
        // parameters that placed in extension table
        $masks = ['status', 'version', 'date_installed', 'priority', 'license_key'];
        $keys = array_keys($data);
        unset($keys['store_id']);
        // check if settings required and it is not status
        $ext = new ExtensionUtils($extension_txt_id, (int) $data['store_id']);
        if (isset($data['one_field']) && !isset($data[$extension_txt_id."_status"])) {
            $validate = $ext->validateSettings($data);
            if (!$validate['result']) { // check is all required settings are set
                if (!isset($validate['errors'])) {
                    $this->errors[] = "Can't save setting because value is empty. ";
                } else {
                    $this->load->language($extension_txt_id.'/'.$extension_txt_id);
                    foreach ($validate['errors'] as $field_id => $error_text) {
                        $this->errors[] = $error_text
                            ? $error_text
                            : $this->language->get($field_id.'_validation_error');
                    }
                }
                return false;
            }
        }
        unset($data['one_field']); //remove sign to prevent writing into settings table
        $this->db->query(
            "DELETE FROM ".$this->db->table("settings")." 
            WHERE `group` = '".$this->db->escape($extension_txt_id)."'
                AND `key` IN ('".implode("', '", $keys)."')
                AND `store_id` = '".(int) $data['store_id']."' "
        );

        foreach ($data as $key => $value) {
            $setting_name = str_replace($extension_txt_id."_", '', $key);
            //check if setting is multi-value (array) and save serialized value.
            if (is_array($value)) {
                //validate values in array. If setting is array of all members = 0 save only single value of 0
                //This is to match standard post format in regular form submit
                foreach ($value as &$v) {
                    //remove empty value from multiselectbox with empty set of options
                    $v = $v == "''" ? null : $v;
                }
                $concat = implode('', $value);
                if (preg_match('/[^0]/', $concat)) {
                    $value = serialize($value);
                } else {
                    $value = 0;
                }
            }
            // status check
            if ($setting_name == 'status') {
                //when try to enable extension
                if ($value == 1) { // check is parent extension enabled
                    $validate = $ext->validateSettings($data); // check is all required settings are set and valid
                    if (!$validate['result']) {
                        $value = 0; // disable extension
                        if (!isset($validate['errors'])) {
                            $error = "Cannot enable extension \"".$extension_txt_id
                                ."\". Please fill all required fields on settings edit page. ";
                            $this->messages->saveError('App Error', $error);
                            $this->errors[] = $error;
                            $error = new AError ($error);
                            $error->toLog()->toDebug();
                        } else {
                            $this->load->language($extension_txt_id.'/'.$extension_txt_id);
                            foreach ($validate['errors'] as $field_id => $error_text) {
                                $error =
                                    $error_text ? $error_text : $this->language->get($field_id.'_validation_error');
                                $this->messages->saveError('App Error: '.$field_id, $error);
                                $this->errors[] = $error;
                                $error = new AError ($error);
                                $error->toLog()->toDebug();
                            }
                        }
                    } else {
                        // if all fine with required fields - check children
                        $parents = $this->getParentsExtensionTextId($extension_txt_id);
                        $enabled = $this->extensions->getEnabledExtensions();
                        foreach ($parents as $parent) {
                            if (!in_array($parent['key'], $enabled)) {
                                $error =
                                    "Cannot enable extension \"".$extension_txt_id."\". It's depends on extension \""
                                    .$parent['key']."\" which not enabled. ";
                                $this->messages->saveError('Extension App Error', $error);
                                $this->errors[] = $error;
                                $error = new AError ($error);
                                $error->toLog()->toDebug();
                                //prevents enabling
                                $value = 0;
                                break;
                            }
                        }
                    }
                } else { // When try to disable disable dependants too
                    if ($this->isExtensionInstalled($extension_txt_id)) {
                        $children_keys = [];
                        $children = $this->getChildrenExtensions($extension_txt_id);

                        foreach ($children as $child) {
                            if ($this->config->get($child['key']."_status") == 1) {
                                $children_keys[] = $this->db->escape($child['key']);
                            }
                        }
                        if ($children_keys) {
                            foreach ($children_keys as $child) {
                                $sql = "UPDATE ".$this->db->table("settings")." 
                                        SET `value` = 0
                                        WHERE `group` = '".$child."'
                                        AND `key`= '".$child."_status'";
                                $this->db->query($sql);
                            }
                            $sql = "UPDATE ".$this->db->table("extensions")." 
                                    SET `".$setting_name."` = '".$this->db->escape($value)."'
                                    WHERE  `key` IN ('".implode("','", $children_keys)."')";
                            $this->db->query($sql);
                        }
                    }
                }
            }

            //Special case.
            //Check that we have single mode RL with ID
            if (has_value($data[$key."_resource_id"]) && !has_value($value)) {
                //save ID if resource path is missing
                $value = $data[$key."_resource_id"];
            }
            //skip saving ???

            // now re-insert settings
            $this->db->query(
                "INSERT INTO ".$this->db->table("settings")." 
                SET `store_id` = '".(int) $data['store_id']."',
                  `group` = '".$this->db->escape($extension_txt_id)."',
                  `key` = '".$this->db->escape($key)."',
                  `value` = '".$this->db->escape($value)."'"
            );
            if (in_array($setting_name, $masks)) {
                $sql = "UPDATE ".$this->db->table("extensions")." 
                        SET `".$setting_name."` = '".$this->db->escape($value)."'
                        WHERE  `key` = '".$this->db->escape($extension_txt_id)."'";
                $this->db->query($sql);
            }
        }
        // update date of changes in extension list
        $sql = "UPDATE ".$this->db->table("extensions")." 
                SET `date_modified` = NOW()
                WHERE  `key` = '".$this->db->escape($extension_txt_id)."'";
        $this->db->query($sql);
        $this->cache->remove('admin_menu');
        $this->cache->remove('settings');
        $this->cache->remove('extensions');
        return true;
    }

    /**
     * method deletes all settings of extension with language definitions
     *
     * @param string $group - extension text id
     */
    public function deleteSetting($group)
    {
        $this->db->query(
            "DELETE FROM ".$this->db->table("settings")." 
            WHERE `group` = '".$this->db->escape($group)."';"
        );
        $this->db->query(
            "DELETE FROM ".$this->db->table("language_definitions")." 
            WHERE `block` = '".$this->db->escape($group)."_".$this->db->escape($group)."';"
        );
        $this->cache->remove('settings');
        $this->cache->remove('extensions');
        $this->cache->remove('localization');
    }

    /**
     * extension install actions, db queries, copying files etc
     *
     * @param string $name
     * @param DomNode| DOMElement|SimpleXMLElement $config
     *
     * @return bool|null
     * @throws AException
     */
    public function install($name, $config)
    {
        $ext = new ExtensionUtils($name);
        // gets extension_id for install.php
        $extension_info = $this->getExtensionsList(['search' => $name]);
        $validate = $this->validateCoreVersion($extension_info->row['key'], $config);
        $errors = $ext->getError();

        if ($errors) {
            $this->session->data['error'] = implode("<br>", $errors);
        }
        if (!$validate) {
            $error = new AError ($errors);
            $error->toLog()->toDebug();

            return false;
        }

        //install default settings
        $default_settings = $ext->getDefaultSettings();
        $settings = [
            $name.'_status'         => 0,
            $name.'_layout'         => (string) $config->layout,
            $name.'_priority'       => (string) $config->priority,
            $name.'_date_installed' => date("Y-m-d H:i:s", time()),
        ];

        $settings = array_merge($settings, $default_settings);

        //write info about install into install log
        $install_upgrade_history = new ADataset('install_upgrade_history', 'admin');
        $install_upgrade_history->addRows(
            [
                'date_added'  => date("Y-m-d H:i:s", time()),
                'name'        => $name,
                'version'     => $settings[$name.'_version'],
                'backup_file' => '',
                'backup_date' => '',
                'type'        => 'install',
                'user'        => $this->user->getUsername(),
            ]
        );

        // add dependencies into database for required extensions only
        if (isset($config->dependencies->item)) {
            foreach ($config->dependencies->item as $item) {
                if ((boolean) $item['required']) {
                    $this->addDependant($name, (string) $item);
                }
            }
        }

        // running sql install script if it exists
        if (isset($config->install->sql)) {
            $file = DIR_EXT.str_replace('../', '', $name).'/'.(string) $config->install->sql;
            if (is_file($file)) {
                $this->db->performSql($file);
            }
        }
        // running php install script if it exists
        if (isset($config->install->trigger)) {
            $file = DIR_EXT.str_replace('../', '', $name).'/'.(string) $config->install->trigger;
            if (is_file($file)) {
                include($file);
            }
        }

        // refresh data about updates
        $this->load->model('tool/updater');
        $this->model_tool_updater->check4updates(true);

        //save default settings for all stores
        $this->load->model('setting/store');
        $stores = $this->model_setting_store->getStores();
        foreach ($stores as $store) {
            $settings['store_id'] = $store['store_id'];
            $this->editSetting($name, $settings);
        }
        return null;
    }

    /**
     * @param string $name
     * @param SimpleXMLElement|stdClass $config
     *
     * @return bool|null
     * @throws AException
     */
    public function uninstall($name, $config)
    {
        // check dependencies
        $validate = $this->checkDependants($name);
        if (!$validate) {
            $this->session->data['error'] = implode("<br>", $this->errors);
            return false;
        }

        //write info about install into install log
        $info = $this->extensions->getExtensionInfo($name);

        if ($info['type'] == 'payment' && $this->config->get($name.'_status')) {
            $this->load->language('extension/extensions');
            $this->session->data['error'] = $this->language->get('error_payment_uninstall');
            return false;
        }

        $install_upgrade_history = new ADataset('install_upgrade_history', 'admin');
        $install_upgrade_history->addRows(
            [
                'date_added'  => date("Y-m-d H:i:s", time()),
                'name'        => $name,
                'version'     => $info['version'],
                'backup_file' => '',
                'backup_date' => '',
                'type'        => 'uninstall',
                'user'        => $this->user->getUsername(),
            ]
        );

        if (isset($config->uninstall->sql)) {
            $file = DIR_EXT.str_replace('../', '', $name).'/'.(string) $config->uninstall->sql;
            if (is_file($file)) {
                $this->db->performSql($file);
            }
        }
        // running php uninstall script if it exists
        if (isset($config->uninstall->trigger)) {
            $file = DIR_EXT.str_replace('../', '', $name).'/'.(string) $config->uninstall->trigger;
            if (is_file($file)) {
                include($file);
            }
        }

        //set status to off
        $this->editSetting($name, ['status' => 0]);
        //uninstall settings
        $this->deleteSetting($name);
        return null;
    }

    /**
     * @param string $extension_txt_id
     *
     * @return bool
     * @throws AException
     */
    public function delete($extension_txt_id)
    {
        if (!trim($extension_txt_id)) {
            $this->log->write('Error! Abantecart tried to delete by empty extension_txt_id');
            return false;
        }

        $info = $this->extensions->getExtensionInfo($extension_txt_id);
        $install_upgrade_history = new ADataset('install_upgrade_history', 'admin');
        $install_upgrade_history->addRows(
            [
                'date_added'  => date("Y-m-d H:i:s", time()),
                'name'        => $extension_txt_id,
                'version'     => $info['version'],
                'backup_file' => '',
                'backup_date' => '',
                'type'        => 'delete',
                'user'        => $this->user->getUsername(),
            ]
        );
        $this->db->query(
            "DELETE FROM ".$this->db->table("extensions")." 
            WHERE `type` = '".$info['type']."' 
                AND `key` = '".$this->db->escape($extension_txt_id)."'"
        );
        $this->deleteDependant($extension_txt_id);

        $this->session->data['package_info']['ftp'] = false;
        $pmanager = new APackageManager();
        $result = $pmanager->removeDir(DIR_EXT.$extension_txt_id);

        if (!$result) {
            $message = "Error: Can't to delete file or directory: '"
                .DIR_EXT.$extension_txt_id
                ."'. No file permissions, change permissions to 777 with your FTP access";
            $this->session->data['error'] = $message;
        }

        // refresh data about updates
        $this->load->model('tool/updater');
        $this->model_tool_updater->check4updates(true);
        $this->cache->remove('extensions');
        return true;
    }

    /**
     * @param string $extension_txt_id
     *
     * @return bool
     */
    public function validate($extension_txt_id)
    {
        $result = $this->validateFreeSpace();
        if (!$result) {
            return false;
        }
        $result = $this->isExtensionInstalled($extension_txt_id);
        if ($result) {
            return false;
        }
        /** @var SimpleXMLElement|stdClass $config */
        $config = getExtensionConfigXml($extension_txt_id);

        $result = $this->validateCoreVersion($extension_txt_id, $config);
        if (!$result) {
            return false;
        }
        $result = $this->validatePhpModules($extension_txt_id, $config);
        if (!$result) {
            return false;
        }
        $result = $this->validatePhpVersion($extension_txt_id, $config);
        if (!$result) {
            return false;
        }
        $result = $this->validateDependencies($extension_txt_id, $config);
        if (!$result) {
            return false;
        }
        return true;
    }

    /**
     *  is dependencies present
     *
     * @param string $extension_txt_id
     * @param SimpleXMLElement|stdClass $config
     *
     * @return bool
     */
    public function validateDependencies($extension_txt_id, $config)
    {
        $extensions = $this->extensions->getEnabledExtensions();
        $all_extensions = $this->extensions->getExtensionsList();
        $versions = [];
        foreach ($all_extensions->rows as $ext) {
            $versions[$ext['key']] = $ext['version'];
        }
        if (!isset($config->dependencies->item)) {
            return true;
        }
        foreach ($config->dependencies->item as $item) {
            $required = (boolean) $item['required'];
            $version = (string) $item['version'];
            $prior_version = (string) $item['prior_version'];

            $item = (string) $item;
            // check existing of required
            if ($required && !in_array($item, $extensions)) {
                $this->errors[] = sprintf(
                    '<b>%s</b> extension cannot be installed: <b>%s</b> extension required and must be installed and enabled!',
                    $extension_txt_id, $item
                );
            }
            // if extension installed - check version that need
            if ($version) {
                if ($required
                    && (!versionCompare($version, $versions[$item], '>=')
                        || !versionCompare(
                            $prior_version, $versions[$item], '<='
                        ))) {
                    $this->errors[] = sprintf(
                        '<b>%s</b> extension cannot be installed: <b>%s</b> extension versions <b>'.$prior_version.' - '
                        .$version.'</b> are required', $extension_txt_id, $item
                    );
                }
            }
            if (sizeof($this->errors) > 0) {
                return false;
            }
        }
        return true;
    }

    /**
     *  is dependants installed?
     *
     * @param string $extension_txt_id
     *
     * @return bool
     */
    public function checkDependants($extension_txt_id)
    {
        $extensions = $this->extensions->getInstalled('exts');
        foreach ($extensions as $extension) {
            if ($extension == $extension_txt_id) {
                continue;
            }
            /**
             * @var SimpleXMLElement|stdClass $config
             */
            $config = getExtensionConfigXml($extension);
            if (!isset($config->dependencies->item)) {
                continue;
            }
            foreach ($config->dependencies->item as $item) {
                $required = (boolean) $item['required'];
                $item = (string) $item;
                if ($item == $extension_txt_id && $required) {
                    $this->errors[] = sprintf(
                        '<b>%s</b> extension cannot be uninstalled: <b>%s</b> extension depends on it. Please uninstall it first.',
                        $extension_txt_id, $extension
                    );
                    return false;
                }
            }
        }

        return true;
    }

    /**
     *  check free space
     * //TODO: is really needed?
     */
    public function validateFreeSpace()
    {
        return true;
    }

    /**
     *  is extension already installed ( extension upgrade )
     *
     * @param string $extension_txt_id
     *
     * @return bool
     */
    public function isExtensionInstalled($extension_txt_id)
    {
        $installed = $this->config->get($extension_txt_id.'_status');
        return $installed === null ? false : true;
    }

    /**
     *  is extension support current core version
     *
     * @param string $extension_txt_id
     * @param SimpleXMLElement|stdClass $config
     *
     * @return bool
     */
    public function validateCoreVersion($extension_txt_id, $config)
    {
        if (!isset($config->cartversions->item)) {
            $this->errors[] = 'Error: config file of extension does not contain any '
                .'information about versions of AbanteCart where it can be run.';

            return false;
        }
        $cart_versions = [];
        foreach ($config->cartversions->item as $item) {
            $version = (string) $item;
            $cart_versions[] = $version;
        }
        // check is cart version presents on extension cart version list
        foreach ($cart_versions as $version) {
            $result = versionCompare(VERSION, $version, '>=');
            if ($result) {
                return true;
            }
        }
        // if not - seek cart earlier version then current cart version in the list
        foreach ($cart_versions as $version) {
            $result = versionCompare($version, VERSION, '<');
            if ($result) {
                $error_text = 'Extension <b>%s</b> written for earlier version '
                    .'of Abantecart (v.%s) lower that you have. ';
                $error_text .= 'Probably all will be OK.';
                $error_text = sprintf($error_text, $extension_txt_id, implode(', ', $cart_versions));
                $this->session->data['error'] = $error_text;
                $this->messages->saveWarning($extension_txt_id.' extension warning', $error_text);
                return true;
            }
        }

        $error_text = '<b>%s</b> extension cannot be installed. AbanteCart version incompatibility. ';
        $error_text .= sizeof($cart_versions) > 1
            ? 'Versions <b>%s</b> are required.'
            : 'Version <b>%s</b> is required.';
        $this->errors[] = sprintf(
            $error_text,
            $extension_txt_id,
            implode(', ', $cart_versions)
        );
        return false;
    }


    /**
     *  is hosting support all php modules used by extension
     */
    /**
     * @param string $extension_txt_id
     * @param SimpleXMLElement|stdClass $config
     *
     * @return bool
     */
    public function validatePhpModules($extension_txt_id, $config)
    {
        if (!isset($config->phpmodules->item)) {
            return true;
        }
        foreach ($config->phpmodules->item as $item) {
            $item = (string) $item;
            if (!extension_loaded($item)) {
                $this->errors[] = sprintf(
                    '<b>%s</b> extension cannot be installed: <b>%s</b> php module required',
                    $extension_txt_id,
                    $item
                );
                return false;
            }
        }
        return true;
    }

    /**
     * @param string $extension_txt_id
     * @param SimpleXMLElement|stdClass $config
     *
     * @return bool
     */
    public function validatePhpVersion($extension_txt_id, $config)
    {
        if (!isset($config->phpminversion)) {
            return true;
        }
        $phpMinVersion = (string)$config->phpminversion ?: MIN_PHP_VERSION;
        if (version_compare(phpversion(), $phpMinVersion, '<') == true) {
            $this->errors[] = sprintf(
                                '<b>%s</b> extension cannot be installed: <b>%s</b> php version required',
                                $extension_txt_id,
                                $phpMinVersion
                            );
            return false;
        }
        return true;
    }
}