Current File : //var/www/vinorea/modules/ipexportimport/controllers/front/import.php
<?php
/**
 *
 * NOTICE OF LICENSE
 *
 *  @author    SmartPresta <tehran.alishov@gmail.com>
 *  @copyright 2024 SmartPresta
 *  @license   Commercial License
 */

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

require_once dirname(__FILE__) . '/../../classes/EIAHelper.php';
require_once dirname(__FILE__) . '/../../classes/EIAImportCronHistory.php';

class IpExportImportImportModuleFrontController extends ModuleFrontController
{
    private $now;
    private $importDir;
    private $url;
    public $eIHelper;
    private $cookies = array();

    public function init()
    {
        if (Configuration::getGlobalValue('IPIA_SCHDL_ENABLE') && (Configuration::getGlobalValue('IPIA_SCHDL_USE_URL') || Configuration::getGlobalValue('IPIA_SCHDL_USE_FTP'))) {
            if (Tools::getValue('token') === md5(Configuration::getGlobalValue('IPEIA_SECURE_KEY'))) {
                error_reporting(E_ERROR | E_PARSE);
                ini_set('max_execution_time', 0);
                ini_set('memory_limit', '-1');
                $this->import();
                die;
            } else {
                http_response_code(403);
                die($this->module->l('Token is incorrect.'));
            }
        } else {
            http_response_code(401);
            die($this->module->l('No schedule is enabled in the module.'));
        }
    }

    public function import()
    {
        $this->importDir = dirname(__FILE__) . '/../../import/';
        
        $this->eIHelper = new EIAHelper($this->module);

        $dt = new DateTime();
        $this->now = $dt->format('YmdHis');
        $this->url = $this->context->link->getModuleLink('ipexportimport', 'importByGroups', array('token' => md5(Configuration::getGlobalValue('IPEIA_SECURE_KEY'))));
        
        if (Configuration::getGlobalValue('IPIA_SCHDL_USE_URL') == '1') {
            $urls_limit = '';
            if (Tools::isSubmit('url_ids')) {
                $urls_limit = ' AND id_ipimport_url IN (' . pSQL(Tools::getValue('url_ids')) . ') ';
            }

            $sql = 'SELECT 
                    i.id_ipimport id,
                    ie.`template`,
                    url,
                    entity,
                    IFNULL(configuration,
                        (
                            SELECT configuration
                            FROM ' . _DB_PREFIX_ . 'ipexport
                            WHERE `name` = "catalog_default")) configuration
                FROM ' . _DB_PREFIX_ . 'ipimport_url ie
                LEFT JOIN ' . _DB_PREFIX_ . 'ipimport i ON ie.template = i.`name`
                WHERE ie.active = 1 ' . $urls_limit;
            $result = Db::getInstance()->executeS($sql);
            foreach ($result as &$res) {
                $remote_file_url = $res['url'];
                if (filter_var($remote_file_url, FILTER_VALIDATE_URL) === false) {
                    continue;
                }
                $remote_file = pathinfo($remote_file_url);
                $local_file = $this->now . '-' . $remote_file['basename'];

                if (file_put_contents($this->importDir . $res['entity'] . '/' . $local_file, fopen($remote_file_url, "r"))) {
                    $conf = json_decode($res['configuration'], true)[$res['entity']];
                    parse_str($conf, $parsed_conf);
                    if (($newFile = $this->convertIfNotCsv($this->importDir . $res['entity'] . '/' . $local_file, $parsed_conf['csv_separator'], '"'))) {
                        $local_file = $newFile;
                    }
                    $data = $this->importNow(null, 0, 5, -1, '{}',  $res['entity'], $local_file, $conf);
                    // Insert the imported file to the DB
                    if (!empty($data['errors'])) {
                        foreach ($data['errors'] as $error) {
                            $message = $error . '<br />';
                            echo $message;
                            $this->saveCronHistory(false, $message, 'URL', $res['entity']);
                        }
                    } elseif (!empty($data['isFinished'])) {
                        $message = sprintf($this->module->l('"%s" imported.'), $remote_file_url) . '<br />';
                        echo $message;
                        $this->saveCronHistory(true, $message, 'URL', $res['entity']);
                    } elseif (is_string($data)) {
                        $message = $data . '<br />';
                        echo $message;
                        $this->saveCronHistory(false, $message, 'URL', $res['entity']);
                    }
                }
            }
        }

        if (Configuration::getGlobalValue('IPIA_SCHDL_USE_FTP') == '1') {
            $ftps_limit = '';
            if (Tools::isSubmit('ftp_ids')) {
                $ftps_limit = ' AND id_ipimport_ftp IN (' . pSQL(Tools::getValue('ftp_ids')) . ') ';
            }

            $sql = 'SELECT 
                    i.id_ipimport id,
                    ie.`template`,
                    type,
                    url,
                    port,
                    username,
                    password,
                    entities,
                    IFNULL(configuration,
                        (
                            SELECT configuration
                            FROM ' . _DB_PREFIX_ . 'ipexport
                            WHERE `name` = "catalog_default")) configuration
                FROM ' . _DB_PREFIX_ . 'ipimport_ftp ie
                LEFT JOIN ' . _DB_PREFIX_ . 'ipimport i ON ie.template = i.`name`
                WHERE ie.active = 1 ' . $ftps_limit;
            $result = Db::getInstance()->executeS($sql);
            foreach ($result as &$res) {
                foreach (explode(',', $res['entities']) as $val) {
                    $val = explode(':', $val);
                    if ($val[1] == '1') {
                        $files = $this->downloadFromFTP(
                            $res['type'],
                            $res['url'],
                            $res['port'],
                            $res['username'],
                            $res['password'],
                            $val[2],
                            $val[0]
                        );
                        if ($files && is_array($files)) {
                            $conf = json_decode($res['configuration'], true)[$val[0]];
                            parse_str($conf, $parsed_conf);
                            foreach ($files as $file) {
                                if (($newFile = $this->convertIfNotCsv($this->importDir . $val[0] . '/' . $file, $parsed_conf['csv_separator'], '"'))) {
                                    $file = $newFile;
                                }
                                $data = $this->importNow(null, 0, 5, -1, '{}', $val[0], $file, $conf);
                                // Insert the imported file to the DB
                                if (!empty($data['errors'])) {
                                    foreach ($data['errors'] as $error) {
                                        $message = $error . '<br />';
                                        echo $message;
                                        $this->saveCronHistory(false, $message, 'FTP', $val[0]);
                                    }
                                } elseif (!empty($data['isFinished'])) {
                                    Db::getInstance()->execute('INSERT IGNORE INTO ' . _DB_PREFIX_ . 'ipimport_history
                                        VALUES (NULL, "' . $val[0] . '", "' . $file . '", "' . date('Y-m-d H:i:s') . '");');
                                    $message = sprintf($this->module->l('%s -> "%s" imported.'), $val[0], $file) . '<br />';
                                    echo $message;
                                    $this->saveCronHistory(true, $message, 'FTP', $val[0]);
                                } elseif (is_string($data)) {
                                    $message = $data . '<br />';
                                    echo $message;
                                    $this->saveCronHistory(false, $message, 'FTP', $val[0]);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    public function downloadFromFTP($type, $url, $port, $username, $password, $remote_folder, $entity)
    {
        if (!$remote_folder) {
            return false;
//            $remote_folder = 'public_ftp';
        }
        $password = html_entity_decode($password);

        if ($type === 'sftp') {
            set_include_path(dirname(__FILE__) . '/../../vendor/phpseclib');
            include_once('Net/SFTP.php');
            if (!$port) {
                $port = 22;
            }
            $sftp = new Net_SFTP($url, $port);
            if ($sftp->login($username, $password)) {
                $files = [];
                $remote_folder_path_info = pathinfo($remote_folder);
                if (isset($remote_folder_path_info['extension'])) { // is file
                    $path_parts = $remote_folder_path_info;
                    if (!is_numeric(Tools::substr($path_parts['basename'], 0, 14))) {
                        $path_parts['basename'] = $this->now . '-' . $path_parts['basename'];
                    }
                    if (Db::getInstance()->getValue('SELECT id_ipimport_history id FROM ' . _DB_PREFIX_ . 'ipimport_history
                                        WHERE `entity` = "' . $entity . '" AND `file` = "' . $path_parts['basename'] . '"')) {
                    } elseif (file_exists($this->importDir . $entity . '/' . $path_parts['basename'])) {
                        $files[] = $path_parts['basename'];
                    } else {
                        if ((bool) $sftp->get($remote_folder . '/' . $path_parts['basename'], $this->importDir . $entity . '/' . $path_parts['basename'])) {
                            $files[] = $path_parts['basename'];
                        } else {
                            $message = sprintf($this->module->l('Could not download file "%s" via SFTP.'), $remote_folder . '/' . $path_parts['basename']) . '<br />';
                            echo $message;
                            $this->saveCronHistory(false, $message, 'SFTP', $entity);
                        }
                    }
                } else {
                    foreach ($sftp->nlist($remote_folder) as $content) {
                        $path_parts = pathinfo($content);
                        if ($path_parts['basename'] == '.' || $path_parts['basename'] == '..' || !$path_parts['extension']) {
                            continue;
                        }
                        if (!is_numeric(Tools::substr($path_parts['basename'], 0, 14))) {
                            $path_parts['basename'] = $this->now . '-' . $path_parts['basename'];
                        }
                        if (Db::getInstance()->getValue('SELECT id_ipimport_history id FROM ' . _DB_PREFIX_ . 'ipimport_history
                                        WHERE `entity` = "' . $entity . '" AND `file` = "' . $path_parts['basename'] . '"')) {
                            continue;
                        } elseif (file_exists($this->importDir . $entity . '/' . $path_parts['basename'])) {
                            $files[] = $path_parts['basename'];
                        } else {
                            if ((bool) $sftp->get($remote_folder . '/' . $content, $this->importDir . $entity . '/' . $path_parts['basename'])) {
                                $files[] = $path_parts['basename'];
                            } else {
                                $message = sprintf($this->module->l('Could not download file "%s" via SFTP.'), $remote_folder . '/' . $content) . '<br />';
                                echo $message;
                                $this->saveCronHistory(false, $message, 'SFTP', $entity);
                            }
                        }
                    }
                }
                return $files;
            } else {
                $message = $this->module->l('Cannot log in using SFTP.') . '<br />';
                echo $message;
                $this->saveCronHistory(false, $message, 'SFTP', '');
                return false;
            }
        } else {
            if (!$port) {
                $port = 21;
            }
            // open an FTP/FTPS connection
            if ($type === 'ftps') {
                $connId = ftp_ssl_connect($url, (int) $port);
            } else {
                $connId = ftp_connect($url, (int) $port);
            }
            if ($connId) {
                // login to FTP server
                if (!@ftp_login($connId, $username, $password)) {
                    $message = $this->module->l('Could not log in using FTP. Make sure login and/or password is correct.') . '<br />';
                    echo $message;
                    $this->saveCronHistory(false, $message, 'FTP', '');
                    return false;
                }

                ftp_pasv($connId, true);
                $files = [];
                $remote_folder_path_info = pathinfo($remote_folder);
                if (isset($remote_folder_path_info['extension'])) { // is file
                    $path_parts = pathinfo($remote_folder_path_info);
                    if (!is_numeric(Tools::substr($path_parts['basename'], 0, 14))) {
                        $path_parts['basename'] = $this->now . '-' . $path_parts['basename'];
                    }
                    if (Db::getInstance()->getValue('SELECT id_ipimport_history id FROM ' . _DB_PREFIX_ . 'ipimport_history
                                        WHERE `entity` = "' . $entity . '" AND `file` = "' . $path_parts['basename'] . '"')) {
                    } elseif (file_exists($this->importDir . $entity . '/' . $path_parts['basename'])) {
                        $files[] = $path_parts['basename'];
                    } else {
                        if (ftp_get($connId, $this->importDir . $entity . '/' . $path_parts['basename'], $path_parts['basename'], FTP_BINARY)) {
                            $files[] = $path_parts['basename'];
                        } else {
                            $message = sprintf($this->module->l('Could not download file "%s" via FTP.'), $path_parts['basename']) . '<br />';
                            echo $message;
                            $this->saveCronHistory(false, $message, 'FTP', $entity);
                        }
                    }
                } else {
                    if (!($contents = ftp_nlist($connId, $remote_folder))) {
                        ftp_pasv($connId, false);
                        $contents = ftp_nlist($connId, $remote_folder);
                    }
                    if (!$contents) {
                        $message = $this->module->l('Could not read the FTP directory.') . '<br />';
                        echo $message;
                        $this->saveCronHistory(false, $message, 'FTP', $entity);
                        return false;
                    }

                    foreach ($contents as $content) {
                        $path_parts = pathinfo($content);
                        if ($path_parts['basename'] == '.' || $path_parts['basename'] == '..' || !$path_parts['extension']) {
                            continue;
                        }
                        if (!is_numeric(Tools::substr($path_parts['basename'], 0, 14))) {
                            $path_parts['basename'] = $this->now . '-' . $path_parts['basename'];
                        }
                        if (Db::getInstance()->getValue('SELECT id_ipimport_history id FROM ' . _DB_PREFIX_ . 'ipimport_history
                                        WHERE `entity` = "' . $entity . '" AND `file` = "' . $path_parts['basename'] . '"')) {
                            continue;
                        } elseif (file_exists($this->importDir . $entity . '/' . $path_parts['basename'])) {
                            $files[] = $path_parts['basename'];
                        } else {
                            if (ftp_get($connId, $this->importDir . $entity . '/' . $path_parts['basename'], $content, FTP_BINARY)) {
                                $files[] = $path_parts['basename'];
                            } else {
                                $message = sprintf($this->module->l('Could not download file "%s" via FTP.'), $content) . '<br />';
                                echo $message;
                                $this->saveCronHistory(false, $message, 'FTP', $entity);
                            }
                        }
                    }
                }
                ftp_close($connId);
                return $files;
            } else {
                $message = $this->module->l('Could not connect using FTP.') . '<br />';
                echo $message;
                $this->saveCronHistory(false, $message, 'FTP', '');
                return false;
            }
        }
    }
    
    private function curlResponseHeaderCallback($ch, $headerLine)
    {
        if (preg_match('/^Set-Cookie:\s*([^;]*)/mi', $headerLine, $matches) == 1 && !in_array($matches[1], $this->cookies)) {
            $this->cookies[] = $matches[1];
        }
        return strlen($headerLine); // Needed by curl
    }

    public function importNow($ch, $offset, $limit, $total, $crossStepsVariables, $entity, $filename, $config, $moreStep = 0)
    {
        if ($ch === null) {
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $this->url);
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
            curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, "curlResponseHeaderCallback"));
        }

        $startingTime = microtime(true);

        $query = $config . '&' . http_build_query([
                'ajax' => 1,
                'action' => 'import',
                'offset' => $offset,
                'limit' => $limit,
                'entity' => $entity,
                'filename' => $filename,
                'crossStepsVars' => $crossStepsVariables,
                'moreStep' => $moreStep
        ]);

        curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Cookie: ' . implode('; ', $this->cookies)));
//        curl_setopt($ch, CURLOPT_HTTPHEADER, array("Cookie: XDEBUG_SESSION=netbeans-xdebug"));
        
        $result = curl_exec($ch);
        if (curl_errno($ch)) {
            echo curl_error($ch) . "<br> \n";
        }
        $server_output = json_decode($result, true);
        
        if (!$server_output) {
            curl_close($ch);
            return $result;
        }
        
        if (!empty($server_output['errors'])) {
            curl_close($ch);
            return $server_output;
        }

        if ($server_output['totalCount']) {
            $total = $server_output['totalCount'];
        }

        if (isset($server_output['isFinished']) && !$server_output['isFinished'] || !empty($server_output['oneMoreStep'])) {
            $previousDelay = microtime(true) - $startingTime;
            $targetDelay = 5 * 1000 * 1000; // try to keep around 5 seconds by call
            // acceleration will be limited to 4 to avoid newLimit to increase too fast (NEVER reach 30 seconds by call!).
            $acceleration = min(4, ($targetDelay / $previousDelay));
            // keep between 5 to 100 elements to process in one call
            $newLimit = min(100, max(5, floor($limit * $acceleration)));
            $newOffset = !empty($server_output['isFinished']) ? 0 : $offset + $limit;
            return $this->importNow(
                            $ch,
                            $newOffset,
                            $newLimit,
                            $total,
                            json_encode($server_output['crossStepsVariables'], JSON_UNESCAPED_SLASHES), // or just $server_output['crossStepsVariables']?
                            $entity,
                            $filename,
                            $config,
                            (int) $server_output['oneMoreStep']
            );
        } else {
            curl_close($ch);
            return $server_output;
        }
    }
    
    public function convertIfNotCsv($file, $separator, $encloser)
    {
        $targetFile = '';
        $fileExtension = Tools::strtolower(pathinfo($file, PATHINFO_EXTENSION));
        if (in_array($fileExtension, ['xls', 'xlsx', 'ods'])) {
            $targetFile = $this->eIHelper->convertExcelToCsv($file, $separator, $encloser);
        } elseif ($fileExtension === 'xml') {
            $targetFile = $this->eIHelper->convertXmlToCsv($file, $separator, $encloser);
        } elseif ($fileExtension === 'json') {
            $targetFile = $this->eIHelper->convertJsonToCsv($file, $separator, $encloser);
        } else {
            throw new Exception('Invalid file type');
        }
        
        return pathinfo($targetFile, PATHINFO_FILENAME) . ".$fileExtension";
    }
    
    public function saveCronHistory($success, $message, $type, $entities)
    {
        $cronHistory = new EIAImportCronHistory();
        $cronHistory->type = $type;
        $cronHistory->entities = $entities;
        $cronHistory->success = (int) $success;
        $cronHistory->message = $message;
        $cronHistory->date_add = date('Y-m-d H:i:s');
        $cronHistory->add();
    }
}