<?php

/******************************************************************************
 *
 *	PROJECT: Flynax Classifieds Software
 *	VERSION: 4.X
 *	LICENSE: FL4HLD2A8696 - http://www.flynax.com/license-agreement.html
 *	PRODUCT: Real Estate Agency
 *	DOMAIN: gevimmo.com
 *	FILE: RLSASSCOMPILER.CLASS.PHP
 *
 *	The software is a commercial product delivered under single, non-exclusive,
 *	non-transferable license for one domain or IP address. Therefore distribution,
 *	sale or transfer of the file in whole or in part without permission of Flynax
 *	respective owners is considered to be illegal and breach of Flynax License End
 *	User Agreement.
 *
 *	You are not allowed to remove this information from the file without permission
 *	of Flynax respective owners.
 *
 *	Flynax Classifieds Software 2017 |  All copyrights reserved.
 *
 *	http://www.flynax.com/
 *
 ******************************************************************************/

require_once(RL_PLUGINS . 'sassCompiler' . RL_DS . 'scssphp' . RL_DS . 'scss.inc.php');
use Leafo\ScssPhp\Compiler;

class rlSassCompiler
{
    private $scssFileName = 'style.scss';
    private $scssRtlFileName = 'rtlStyle.scss';
    private $cssFileName = 'style.css';
    private $cssRtlFileName = 'rtl.css';

    protected $cssRoot;
    protected $scssRoot;
    protected $tmpRoot;
    protected $backupRoot;

    protected $templateName;
    protected $cssFile;
    protected $cssRtlFile;

    protected $cssFileHash;
    protected $cssRtlFileHash;

    public function __construct()
    {
        global $config;

        $this->templateName = $config['template'];

        $this->cssRoot = RL_ROOT . 'templates' . RL_DS . $this->templateName . RL_DS . 'css' . RL_DS;
        $this->scssRoot = $this->cssRoot . 'scss' . RL_DS;
        $this->tmpRoot = RL_TMP . 'compile' . RL_DS;
        $this->backupRoot = RL_ROOT . 'backup' . RL_DS . 'plugins' . RL_DS;

        $this->cssFile = $this->cssRoot . $this->cssFileName;
        $this->cssRtlFile = $this->cssRoot . $this->cssRtlFileName;

        $this->cssFileHash = (isset($config[$this->templateName . '_css_hash'])
            ? $config[$this->templateName . '_css_hash'] 
            : $GLOBALS['tpl_settings']['css_hash']);

        $this->cssRtlFileHash = (isset($config[$this->templateName . '_css_rtl_hash'])
            ? $config[$this->templateName . '_css_rtl_hash'] 
            : $GLOBALS['tpl_settings']['css_rtl_hash']);
    }

    /**
     * Compile SCSS to CSS.
     * @param bool $ignoreCssChanges - pass true if you want to override existing styles.
     * @param bool $ignoreBackup - pass true if you don't want to backup original styles.
     * @return string - message
     */
    protected function compileStyles($ignoreCssChanges = false, $ignoreBackup = false)
    {
        global $lang;

        if (version_compare(PHP_VERSION, '5.4') < 0) {
            return array(
                'status' => 'error',
                'message' => $lang['sc_php_version_invalid']
            );
        }

        if (!isset($GLOBALS['tpl_settings']['sass_styles'])
            && !$GLOBALS['tpl_settings']['sass_styles']
            && version_compare($GLOBALS['config']['rl_version'], '4.5') <= 0
        ) {
            return array(
                'status' => 'error',
                'message' => $lang['sc_software_version_invalid']
            );
        }

        // Check for changes in original css files.
        if (!$ignoreCssChanges) {

            $compareResults = $this->compareHashes();
            if ($compareResults) {

                $errors = array();
                foreach ($compareResults as $file) {
                    if ($file['error']) {
                        $errors[] = $file['name'];
                    }
                }

                if (count($errors) > 0) {
                    return array(
                        'status' => 'error',
                        'reason' => 'modified',
                        'message' => str_replace('[fileNames]', implode(',', $errors), $lang['sc_files_changed'])
                    );
                }
            } else {
                return array(
                    'status' => 'error',
                    'message' => $lang['sc_hash_compare_failed_no_files']
                );
            }
        }

        if (!$ignoreBackup) {

            $compiledFiles = array();
            $this->removeTmpFiles();

            // Compile {$this->scssFileName} file to css and write it in tmp folder.
            $compiledFiles[] = $this->compileScssToCss($this->scssRoot . $this->scssFileName, $this->cssFileName);
            // Compile {$this->scssRtlFileName} file to css and write it in tmp folder.
            $compiledFiles[] = $this->compileScssToCss($this->scssRoot . $this->scssRtlFileName, $this->cssRtlFileName);

            // Return error messages if compilation was unsuccessful.
            $errors = array();
            foreach ($compiledFiles as $file) {
                if ($file['error']) {
                    $errors[] = $file['name'];
                }
            }

            if (count($errors) > 0) {
                return array(
                    'status' => 'error',
                    'message' => str_replace('[fileNames]', implode(',', $errors), $lang['sc_compilation_failed'])
                );
            }
        }

        if (!$ignoreBackup) {

            // Backup {$this->cssFile} and {$this->cssRtlFile}
            $isBackUpped = $this->backupOriginalStyles();
            if (!$isBackUpped) {
                return array(
                    'status' => 'error',
                    'reason' => 'backup',
                    'message' => $lang['sc_backup_failed']
                );
            }
        }

        $compilationSuccess = $this->moveCompiledToTemplate();

        if ($compilationSuccess) {

            $this->updateHashes();
            return array(
                'status' => 'ok',
                'message' => $lang['sc_compilation_success']
            );
        }

        return array(
            'status' => 'error',
            'message' => str_replace('[pathToCss]', $this->cssRoot, $lang['sc_cant_replace_styles'])
        );

    }

    /**
     * Compile Scss to css and put it in tmp folder.
     * @param string - $scssFile
     * @param string - $cssFileName
     * @throws Exception
     * @return array - compiled file and bool error
     */
    private function compileScssToCss($scssFile, $cssFileName)
    {
        $scssCompiler = new Compiler();
        // Add import path to recognize where to find import files.
        $scssCompiler->setFormatter('Leafo\ScssPhp\Formatter\Expanded');
        $scssCompiler->addImportPath($this->scssRoot);

        if (file_exists($scssFile)) {

            $ct = file_get_contents($scssFile);
            $css = $scssCompiler->compile($ct);

            if (!$css) {
                throw new Exception("Compilation is unsuccessful.");
            }

            if (is_writable($this->tmpRoot) && file_put_contents($this->tmpRoot . $cssFileName, $css)) {
                return array(
                    'name' => $cssFileName,
                    'error' => false
                );
            }
        }

        return array(
            'name' => $cssFileName,
            'error' => true
        );
    }

    /**
     * Move original styles from template/css to backup/plugins.
     * @return bool - backed up or not
     */
    private function backupOriginalStyles()
    {
        if (is_writable($this->backupRoot)) {

            $GLOBALS['reefless']->loadClass('Zip');
            $pluginBackupPath = $this->backupRoot . 'styles-' . date("d_m_Y_g_H_s") . '.zip';

            $GLOBALS['rlZip']->addFile(file_get_contents($this->cssFile), $this->cssFileName);
            $GLOBALS['rlZip']->addFile(file_get_contents($this->cssRtlFile), $this->cssRtlFileName);

            $file = fopen($pluginBackupPath, "wb");
            fwrite($file, $GLOBALS['rlZip']->getZippedfile());
            fclose($file);

            if (file_exists($pluginBackupPath)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Move compiled files to template dir.
     * @return bool - moved or not
     */
    private function moveCompiledToTemplate()
    {
        if (is_writable($this->cssRoot)) {

            $cssReplaced = rename($this->tmpRoot . $this->cssFileName, $this->cssRoot . $this->cssFileName);
            $sccRtlRemplaced = rename($this->tmpRoot . $this->cssRtlFileName, $this->cssRoot . $this->cssRtlFileName);

            if ($cssReplaced && $sccRtlRemplaced) {
                return true;
            }
        }
        return false;
    }

    /**
     * Create or update hash values for template.
     */
    private function updateHashes()
    {
        $cssConfigKey = $this->templateName . '_css_hash';
        $cssRtlConfigKey = $this->templateName . '_css_rtl_hash';

        if (!isset($GLOBALS['config'][$this->templateName . '_css_hash']) &&
            !isset($GLOBALS['config'][$this->templateName . '_css_rtl_hash'])
        ) {
            $insertSql = "INSERT INTO " . RL_DBPREFIX . "config (`Key`, `Default`, `Values`, `Type`, `Data_type`, `Plugin`) VALUES ";
            $insertSql .= "('{$cssConfigKey}', '', '', 'text', 'text', 'sassCompiler'), ";
            $insertSql .= "('{$cssRtlConfigKey}', '', '', 'text', 'text', 'sassCompiler')";
            $GLOBALS['rlDb']->query($insertSql);
        }

        $styleHash = md5_file($this->cssRoot . $this->cssFileName);
        $styleRtlHash = md5_file($this->cssRoot . $this->cssRtlFileName);

        $sql = "UPDATE " . RL_DBPREFIX . "config SET `Default` = ";
        $sql .= "(CASE WHEN `Key` = '{$cssConfigKey}' THEN '{$styleHash}' WHEN `Key` = '{$cssRtlConfigKey}' THEN '{$styleRtlHash}' END) ";
        $sql .= "WHERE `Key` IN ('{$cssConfigKey}', '{$cssRtlConfigKey}')";
        $GLOBALS['rlDb']->query($sql);
    }

    /**
     * Compare md5 hash of compiled files with existing.
     * @return array|bool - compared files list, error message or false if no compiled files
     */
    private function compareHashes()
    {
        if (file_exists($this->cssRoot . $this->cssFileName) && file_exists($this->cssRoot . $this->cssRtlFileName)) {
            $results = array();
            $styleHash = md5_file($this->cssRoot . $this->cssFileName);
            $styleRtlHash = md5_file($this->cssRoot . $this->cssRtlFileName);

            if ($styleHash == $this->cssFileHash) {
                $results[] = array(
                    'name' => $this->cssFileName,
                    'error' => false
                );
            } else {
                $results[] = array(
                    'name' => $this->cssFileName,
                    'error' => true
                );
            }

            if ($styleRtlHash == $this->cssRtlFileHash) {
                $results[] = array(
                    'name' => $this->cssRtlFileName,
                    'error' => false
                );
            } else {
                $results[] = array(
                    'name' => $this->cssRtlFileName,
                    'error' => true
                );
            }

            return $results;
        }
        return false;
    }

    /**
     * Remove compiled files from tmp folder.
     */
    private function removeTmpFiles()
    {

        if (!$GLOBALS['tpl_settings']['sass_styles']) {
            return false;
        }

        if (file_exists($this->tmpRoot . $this->cssFileName)) {
            unlink($this->tmpRoot . $this->cssFileName);
        }

        if (file_exists($this->tmpRoot . $this->cssRtlFileName)) {
            unlink($this->tmpRoot . $this->cssRtlFileName);
        }
    }

    /**
     * Display button on the refresh page.
     * @hook apTplControlsForm
     **/
    public function hookApTplControlsForm()
    {
        if ($GLOBALS['tpl_settings']['sass_styles']) {
            $GLOBALS['rlSmarty']->display(RL_PLUGINS . 'sassCompiler' . RL_DS . 'compile_button.tpl');
        }
    }

    /**
     * Compile ajax.
     * @hook apAjaxRequest
     **/
    public function hookApAjaxRequest()
    {
        global $out;

        switch ($_REQUEST['item']) {
            case 'sc_clear_tmp':
                $this->removeTmpFiles();
                $out = array();
                break;
            case 'sc_compile':

                $ignoreChanges = $_REQUEST['ignore_changes'] == 'true' ?: false;
                $ignoreBackup = $_REQUEST['ignore_backup'] == 'true' ?: false;

                $out = $this->compileStyles($ignoreChanges, $ignoreBackup);
                break;
        }
    }
}
