HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/be.naniguide.com/app/Library/UpgradeManager.php
<?php

/**
 * UpgradeManager class.
 *
 * Tool for upgrading the entire system source
 *
 * LICENSE: This product includes software developed at
 * the Acelle Co., Ltd. (http://acellemail.com/).
 *
 * @category   Acelle Library
 *
 * @author     N. Pham <[email protected]>
 * @author     L. Pham <[email protected]>
 * @copyright  Acelle Co., Ltd
 * @license    Acelle Co., Ltd
 *
 * @version    1.0
 *
 * @link       http://acellemail.com
 */

namespace Acelle\Library;

use ZipArchive;
use Illuminate\Support\Facades\Log as LaravelLog; // something wrong, cannot use the default name Log
use Acelle\Library\Facades\Hook;
use Acelle\Model\Language;

class UpgradeManager
{
    protected $source;
    protected $target;

    protected $allTranslationFiles;

    public const META_FILE = 'meta.json';

    /**
     * Constructor, specify the source, target and load the meta information.
     */
    public function __construct()
    {
        $this->source = storage_path('tmp/patch');
        $this->target = base_path();
        $this->allTranslationFiles = $this->findTranslationFiles();

        // deletect all language files
    }

    private function findTranslationFiles()
    {
        $allTranslationFiles = [];
        foreach (Language::all() as $lang) {
            foreach (Hook::execute('add_translation_file') as $source) {
                $path = join_paths($source['translation_folder'], $lang->code, $source['file_name']);
                $allTranslationFiles[] = $path;
            }
        }

        return $allTranslationFiles;
    }

    /**
     * Constructor, specify the source, target and load the meta information.
     */
    public function load($path)
    {
        // Check WRITE permission
        $this->cleanup();

        try {
            // Extract the zip file
            $old = umask(0);
            $zip = new ZipArchive();
            $res = $zip->open($path);
            if ($res === true) {
                $zip->extractTo($this->source);
                $zip->close();
                umask($old);

                // test the patch, throw an exception in case meta.json does not exist
                $this->validate();
            } else {
                umask($old);
                LaravelLog::error('Cannot open zip file '.$path.' with error code '.$res);
                throw new \Exception('Invalid upgrade package');
            }
        } catch (\Exception $e) {
            $this->rm($this->source);
            throw $e;
        }
    }

    /**
     * Read the meta data from a patch package.
     */
    public function getNewVersion()
    {
        if ($this->isNewVersionAvailable()) {
            return $this->getMetaInfo()['version'];
        } else {
            return;
        }
    }

    /**
     * Get last supported version for upgrade.
     */
    public function getLastSupportedVersion()
    {
        if ($this->isNewVersionAvailable()) {
            return $this->getMetaInfo()['last_supported'];
        } else {
            return;
        }
    }

    /**
     * Read the meta data from a patch package.
     */
    public function cleanup()
    {
        // Check WRITE permission
        if (!$this->isWritable($this->source)) {
            throw new \Exception("Cannot write to folder {$this->source}");
        }

        // Clean up the target folder
        $this->rm($this->source);
    }

    /**
     * Read the meta data from a patch package.
     */
    public function validate()
    {
        $meta = $this->getMetaInfo();
        if (version_compare($this->getNewVersion(), $this->getCurrentVersion(), '=')) {
            throw new \Exception(sprintf('The version you uploaded is the same as the current one (%s)', $this->getNewVersion()));
        }

        if (version_compare($this->getNewVersion(), $this->getCurrentVersion(), '<')) {
            throw new \Exception(sprintf('The version you uploaded (%s) is older than the current one (%s)', $this->getNewVersion(), $this->getCurrentVersion()));
        }

        if (version_compare($this->getLastSupportedVersion(), $this->getCurrentVersion(), '>')) {
            throw new \Exception(sprintf('You are on a version that is not supported by this update, last supported version is %s', $this->getLastSupportedVersion()));
        }

        // DRYRUN to see if there is any error
        $this->test();
    }

    /**
     * Read the meta data from a patch package.
     */
    private function getMetaInfo()
    {
        $path = join_paths($this->source, self::META_FILE);
        if (!file_exists($path)) {
            // Clean up invalid package (without meta file)
            // Then raise an exception
            // So that user will only see this message once
            $this->cleanup();

            // Ooops!
            throw new \Exception('NOMETA: Unknown package format (deleted)');
        }

        return json_decode(file_get_contents($path), true);
    }

    /**
     * Actually run the upgrade process.
     */
    public function run($test = false)
    {
        $this->refreshConfig();

        $old = umask(0);

        try {
            if ($test) {
                LaravelLog::info('Start upgrading (test)');
            } else {
                LaravelLog::info('Start upgrading');
            }

            if (!$test) {
                // set umask(0) and back up the current umask
                $old = umask(0);
            }

            $errors = [];
            $meta = $this->getMetaInfo();
            $updates = $meta['updated'];
            $deletes = $meta['deleted'];
            $packages = $meta['packages'];
            $dirs = $meta['dirs'];
            $langFiles = [];

            // new or updated entire directories
            foreach ($dirs as $dir) {
                $source = join_paths($this->source, $dir);
                $target = join_paths($this->target, $dir);
                if ($test) {
                    if (!$this->isWritable($target)) {
                        $errors[] = $target;
                    }
                } else {
                    LaravelLog::info("REPLACE DIRECTORY {$dir}");
                    $this->rm($target);
                    $this->copy($source, $target);
                }
            }

            // new or updated files
            foreach ($updates as $file) {
                $source = join_paths($this->source, $file);
                $target = join_paths($this->target, $file);

                // File is deleted in build.sh script
                if (!\Illuminate\Support\Facades\File::exists($source)) {
                    continue;
                }

                if ($test) {
                    if (!$this->isWritable($target)) {
                        $errors[] = $target;
                    }
                } else {
                    if ($this->isTranslationFile($target)) {
                        // Skipped, translation file shall be handled by Language::dump() later on
                    } else {
                        LaravelLog::info('REPLACE file: '.$file);
                        $this->copy($source, $target);
                    }
                }
            }

            // deleted files
            foreach ($deletes as $file) {
                $target = join_paths($this->target, $file);

                if ($test) {
                    if (!$this->isWritable($target)) {
                        $errors[] = $target;
                    }
                } else {
                    LaravelLog::info("DELETE file {$file}");
                    @unlink($target);
                }
            }

            // new or updated packages
            foreach ($packages as $dir) {
                $source = join_paths($this->source, 'vendor', $dir);
                $target = join_paths($this->target, 'vendor', $dir);
                if ($test) {
                    if (!$this->isWritable($target)) {
                        $errors[] = $target;
                    }
                } else {
                    LaravelLog::info("REPLACE package {$dir}");
                    $this->rm($target);
                    $this->copy($source, $target);
                }
            }

            // just finish for test mode
            if ($test) {
                return $errors;
            }

            // cleanup
            LaravelLog::info('Cleaning up');
            $this->cleanup();

            if (!$test) {
                // restore the umask
                umask($old);
            }

            return true;
        } catch (\Exception $e) {
            if (!$test) {
                // restore the umask
                umask($old);
            }
            throw $e;
        }
    }

    public function refreshConfig()
    {
        try {
            // DO NOT clear cache here, otherwise, it will wipe out all list statistics
            \Artisan::call('config:clear');
            \Artisan::call('route:clear');
            \Artisan::call('view:clear');
        } catch (\Throwable $e) {
            // something wrong here, just ignore
        }

        // GO AFTER ARTISAN CALL
        LaravelLog::info('DELETE cached config files');
        $files = [
            'bootstrap/cache/packages.php',
            'bootstrap/cache/config.php',
            'bootstrap/cache/services.php',
            'bootstrap/cache/compiled.php'
        ];

        foreach ($files as $file) {
            $path = base_path($file);
            if (file_exists($path)) {
                $this->rm($path);
            }
        }
    }

    /**
     * Merge .env files.
     */
    public function mergeEnv($updatefile)
    {
        if (!file_exists($updatefile)) {
            return;
        }

        $updatedb = file($updatefile);

        foreach ($updatedb as $updaterecord) {
            if (empty(trim($updaterecord))) {
                continue;
            }

            if (preg_match('/^\s*#/', $updaterecord)) {
                // skip commented line with leading #
                continue;
            }

            list($updatekey, $updatevalue) = explode('=', $updaterecord, 2);
            $updatevalue = stripslashes(trim(trim($updatevalue), '"'));
            \Acelle\Helpers\write_env($updatekey, $updatevalue, $overwrite = false);
        }
    }

    /**
     * Test the upgrade process (DRY-RUN).
     */
    public function test()
    {
        return $this->run(true);
    }

    /**
     * Get current app version.
     */
    public function getCurrentVersion()
    {
        return trim(file_get_contents(base_path('VERSION')));
    }

    /**
     * Check if new version is available.
     */
    public function isNewVersionAvailable()
    {
        return file_exists($this->source);
    }

    /**
     * Check if an existing file is writable or a new path can be created.
     *
     * @input string file path
     * @output boolean
     */
    private function isWritable($path)
    {
        if (is_writable($path)) {
            return true;
        } elseif (!file_exists($path) && $this->canCreateFile($path)) {
            return true;
        } else {
            // file exists but not writable
            // file not exist nor creatable
            return false;
        }
    }

    /**
     * Check if the specified path can be created.
     *
     * @output boolean
     */
    private function canCreateFile($path)
    {
        $a = explode(DIRECTORY_SEPARATOR, $path);
        $parent = null;
        for ($i = 0; $i < sizeof($a); $i += 1) {
            $tmppath = implode(DIRECTORY_SEPARATOR, array_slice($a, 0, $i));

            if (empty($tmppath)) {
                continue;
            }

            try {
                if (!file_exists($tmppath)) {
                    break;
                } else {
                    $parent = $tmppath;
                }
            } catch (\Exception $ex) {
                LaravelLog::warning($path.' not in open_basedir: '.ini_get('open_basedir'));
                $parent = $tmppath;
            }
        }

        return is_writable($parent);
    }

    /**
     * Delete a directory recursively.
     */
    private function rm($src)
    {
        if (!file_exists($src)) {
            return;
        }

        if (!is_dir($src)) {
            unlink($src);

            return;
        }

        $dir = opendir($src);
        while (false !== ($file = readdir($dir))) {
            if (($file != '.') && ($file != '..')) {
                $full = $src.'/'.$file;
                if (is_dir($full)) {
                    $this->rm($full);
                } else {
                    unlink($full);
                }
            }
        }
        closedir($dir);
        rmdir($src);
    }

    /**
     * Copy file or directory to a destination (always REMOVE and REPLACE the destination).
     * Make sure parent directories are always created, similarly to "cp -p"
     *
     */
    public function copy($src, $dst)
    {
        if (\Illuminate\Support\Facades\File::exists($dst)) {
            // Delete the file or link or directory
            if (is_link($dst) || is_file($dst)) {
                // To preserve $dst's permission
                // \Illuminate\Support\Facades\File::delete($dst);
            } else {
                \Illuminate\Support\Facades\File::deleteDirectory($dst);
            }
        } else {
            // Make sure the PARENT directory exists
            $dirname = pathinfo($dst)['dirname'];
            if (!\Illuminate\Support\Facades\File::exists($dirname)) {
                \Illuminate\Support\Facades\File::makeDirectory($dirname, 0777, true, true);
            }
        }

        // if source is a file, just copy it
        if (\Illuminate\Support\Facades\File::isFile($src)) {
            \Illuminate\Support\Facades\File::copy($src, $dst);
        } else {
            \Illuminate\Support\Facades\File::copyDirectory($src, $dst);
        }
    }

    /**
     * Check if the provided file is a language file.
     */
    public function isTranslationFile($path) // absolute path
    {
        return in_array($path, $this->allTranslationFiles);
    }
}