<?php
// Copyright 1999-2023. Plesk International GmbH. All Rights Reserved.

class PathProcessor
{
    /**
     * @var array
     */
    private $_replacement = array();

    /**
     * @param array $replacement
     */
    public function __construct(array $replacement)
    {
        foreach ($replacement as $from => $to) {
            if (strtolower($from) != strtolower($to)) {
                $this->_replacement[$from] = $to;
            }
        }
    }

    /**
     * @return bool
     */
    public function isRequired()
    {
        return count($this->_replacement) > 0;
    }

    /**
     * @param string $content
     * @return bool
     */
    public function updateString(&$content)
    {
        $ret = false;
        foreach ($this->_replacement as $from => $to) {
            if (is_string($content)) {
                $offset = 0;
                while (false !== ($start = stripos($content, $from, $offset))) {
                    if ($start !== stripos($content, $to, $start)) {
                        $end = $start + strlen($from);
                        $content = substr($content, 0, $start) . $to . substr($content, $end);
                        $ret = true;
                    }
                    $offset = $start + strlen($to);
                }
            }
        }

        return $ret;
    }

    /**
     * @param string $content
     * @return bool
     */
    public function updateSerializedArray(&$content)
    {
        $arr = unserialize($content);
        if ($this->_updateArray($arr)) {
            $content = serialize($arr);
            return true;
        }

        return false;
    }

    /**
     * @param array $arr
     * @return bool
     */
    private function _updateArray(&$arr)
    {
        if (!is_array($arr)) {
            return false;
        }

        $ret = false;
        foreach ($arr as $key => $value) {
            if (is_array($arr[$key])) {
                if ($this->_updateArray($arr[$key])) {
                    $ret = true;
                }
            }
            else {
                if ($this->updateString($value)) {
                    $arr[$key] = $value;
                    $ret = true;
                }

                $savedKey = $key;
                if ($this->updateString($key)) {
                    unset($arr[$savedKey]);
                    $arr[$key] = $value;
                    $ret = true;
                }
            }
        }

        return $ret;
    }
}

class HordeFile
{
    /**
     * @var string
     */
    private $_file;

    public function __construct($file)
    {
        $this->_file = $file;
    }

    /**
     * @return bool
     */
    public function isKnownType()
    {
        return $this->_isReg() || $this->_isIni();
    }

    /**
     * @param PathProcessor $processor
     * @throws Exception
     */
    public function process(PathProcessor $processor)
    {
        if (!$this->isKnownType()) {
            return;
        }

        echo "INFO: Processing file '{$this->_file}'..." . PHP_EOL;

        $content = file_get_contents($this->_file);
        if (false === $content) {
            throw new Exception("Unable to get file '{$this->_file}' content");
        }

        if ($this->_isReg()) {
            $bSave = $this->_processReg($content, $processor);
        }
        else {
            $bSave = $this->_processIni($content, $processor);
        }

        if ($bSave) {
            if (false === file_put_contents($this->_file, $content)) {
                throw new Exception("Unable to save file '{$this->_file}'");
            }
            echo "INFO: File '{$this->_file}' was processed" . PHP_EOL;
        }
        else {
            echo "INFO: File '{$this->_file}' was skipped" . PHP_EOL;
        }
    }

    /**
     * @param string $content
     * @param PathProcessor $processor
     * @return bool
     */
    private function _processReg(&$content, PathProcessor $processor)
    {
        return $processor->updateSerializedArray($content);
    }

    /**
     * @param string $content
     * @param PathProcessor $processor
     * @return bool
     */
    private function _processIni(&$content, PathProcessor $processor)
    {
        $pos = strpos($content, "\n");
        if (false === $pos) {
            return $processor->updateSerializedArray($content);
        }
        else {
            $serializedArray = substr($content, $pos + 1);
            if ($processor->updateSerializedArray($serializedArray)) {
                $content = substr($content, 0, $pos + 1) . $serializedArray;
                return true;
            }
        }

        return false;
    }

    /**
     * @return bool
     */
    private function _isReg()
    {
        return 0 == strcasecmp(self::_getExtension($this->_file), 'reg');
    }

    /**
     * @return bool
     */
    private function _isIni()
    {
        return 0 == strcasecmp(basename($this->_file), 'pear.ini');
    }

    /**
     * @return string
     */
    private function _getExtension()
    {
        return substr(strrchr(basename($this->_file), '.'), 1);
    }
}

class HordeDir
{
    /**
     * @var string
     */
    private $_path;

    /**
     * @param string $path
     */
    public function __construct($path)
    {
        $this->_path = $path;
    }

    /**
     * @param PathProcessor $processor
     * @throws Exception
     */
    public function process(PathProcessor $processor)
    {
        $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::SKIP_DOTS;
        $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->_path, $flags));

        $ret = true;
        foreach ($it as $filePath => $fileInfo) {
            try {
                if ($fileInfo->isFile()) {
                    $file = new HordeFile($filePath);
                    if ($file->isKnownType()) {
                        $file->process($processor);
                    }
                }
            }
            catch (Exception $e) {
                echo "ERROR: " . $e->getMessage() . PHP_EOL;
                $ret = false;
            }
        }

        if (!$ret) {
            throw new Exception("Unable to process directory '{$this->_path}'");
        }
    }
}

abstract class HordePostInstallProcessor
{
    const PLESK_INSTALL_DIR = 'plesk-install-dir';
    const REPLACE_PLESK_INSTALL_DIR = 'replace-plesk-install-dir';

    public static function run()
    {
        $opts = self::_getOptions();
        $replacement = array(
            $opts[self::REPLACE_PLESK_INSTALL_DIR] => $opts[self::PLESK_INSTALL_DIR],
        );

        $processor = new PathProcessor($replacement);
        if (!$processor->isRequired()) {
            echo "INFO: Processing is not required" . PHP_EOL;
            return true;
        }

        $pathList = array(
            'pear\pear.ini',
            'pear\pear\.registry',
        );

        $ret = true;
        foreach ($pathList as $path) {
            $fullPath = $opts[self::PLESK_INSTALL_DIR] . 'Webmail\horde\\' . $path;
            try {
                if (is_dir($fullPath)) {
                    $hordeDir = new HordeDir($fullPath);
                    $hordeDir->process($processor);
                }
                elseif (is_file($fullPath)) {
                    $hordeFile = new HordeFile($fullPath);
                    $hordeFile->process($processor);
                }
                else {
                    throw new Exception("Unable to process path '{$fullPath}'");
                }
            }
            catch (Exception $e) {
                echo "ERROR: " . $e->getMessage() . PHP_EOL;
                $ret = false;
            }
        }

        return $ret;
    }

    /**
     * @return array
     * @throws Exception
     */
    private static function _getOptions()
    {
        $optsDesc = array(
            self::PLESK_INSTALL_DIR . ':',
            self::REPLACE_PLESK_INSTALL_DIR . ':',
        );

        $opts = getopt('', $optsDesc);
        if (false === $opts) {
            throw new Exception("Unable to get input parameters");
        }

        foreach ($optsDesc as $opt) {
            $opt = strstr($opt, ':', true);
            if (!isset($opts[$opt])) {
                throw new Exception("Missing required parameter --{$opt}");
            }
        }

        foreach ($opts as $key => $value) {
            $val = rtrim(trim($value), '\/');
            if (!$val) {
                throw new Exception("Invalid input parameter --{$key} value");
            }
            $opts[$key] = $val . '\\';
        }

        return $opts;
    }
}

try {
    if (!HordePostInstallProcessor::run()) {
        exit(1);
    }
}
catch (Exception $e) {
    echo 'ERROR: ' . $e->getMessage() . PHP_EOL;
    exit(1);
}

exit(0);
