#!/usr/local/psa/bin/sw-engine-pleskrun
<?php
// vim:ft=php ts=4 sts=4 sw=4 et

class PleskCtl_Wrapper
{

    const VERSION = '1.1';

    private $_commands = array(
        'help' => 'Show help and exit',
        'version' => 'Show product version information',
        'db' => 'Database related commands. Open MySQL console by default.',
        'bin' => 'Run the specified Plesk command-line utility (e.g., domain, client)',
        'sbin' => 'Run the specified Plesk internal utility',
        'php' => 'Run a PHP script using the proper PHP interpreter',
        'conf' => 'Open the specified Plesk configuration file in the editor',
        'log' => 'Display the specified Plesk log file',
        'installer' => 'Parallels Installer shortcuts and command completion.',
    );

    private $_commandAliases = array(
        '-v' => 'version',
        '-h' => 'help',
    );

    private $_logFiles = array(
        'maillog' => '/usr/local/psa/var/log/maillog',
        'error_log' => '/var/log/sw-cp-server/error_log',
        'access_log' => '/usr/local/psa/admin/logs/httpsd_access_log',
        'panel.log' => '/usr/local/psa/admin/logs/panel.log',
    );

    public function run($arguments)
    {
        $command = (1 == count($arguments)) ? 'help' : $arguments[1];

        if (!isset($this->_commands[$command])) {
            if (isset($this->_commandAliases[$command])) {
                $command = $this->_commandAliases[$command];
            } else {
                $command = 'help';
            } 
        }

        call_user_func(array($this, '_do' . ucfirst($command)), array_slice($arguments, 2));
    }

    private function _doHelp(array $params = null)
    {
        if (isset($params[0]) && ('-c' == $params[0])) {
            array_shift($params);
            echo $this->_getCompletion($params);
            return;
        }

        if (isset($params[0]) && $this->_commands[$params[0]] && method_exists($this, '_getHelp' . ucfirst($params[0]))) {
            call_user_func(array($this, '_getHelp' . ucfirst($params[0])));
            return;
        }

        echo "Usage: plesk [command]\n\n";

        echo "Commands:\n";

        foreach ($this->_commands as $command => $commandHelp) {
            echo "  $command - $commandHelp\n";
        }
    }

    private function _doVersion()
    {
        $fullVersion = trim(file_get_contents('/usr/local/psa/version'));
        preg_match('/^([\d\.]+)(.*?)([\d\.]+)$/', $fullVersion, $matches);
        $version = $matches[1];
        $buildTarget = trim($matches[2]);
        $buildDate = $this->_dateToHuman($matches[3]);

        $revision = trim(file_get_contents('/usr/local/psa/.revision'));

        $updateData = '';

        if (file_exists('/root/.autoinstaller/microupdates.xml')) {
            $xml = simplexml_load_string(file_get_contents('/root/.autoinstaller/microupdates.xml'));
            $patches = $xml->xpath('/patches/product[@id="plesk" and @version="' . $version . '"]/patch');
            if ($patches) {
                $patch = current($patches);
                $version .= ' Update #' . $patch['version'];
                $updateDate = $this->_isoDateToHuman((string)$patch['installed-at']);
            }
        }

        echo "Product version: $version\n";
        if (!empty($updateDate)) {
            echo "    Update date: $updateDate\n";
        }
        echo "     Build date: $buildDate\n";
        echo "   Build target: $buildTarget\n";
        echo "       Revision: $revision\n";
        echo "   Architecture: " . ((4 == PHP_INT_SIZE) ? '32-bit' : '64-bit') . "\n";
        echo "Wrapper version: " . self::VERSION . "\n";
    }

    private function _doDb(array $params = null)
    {
        $overrideCommand = null;

        $args = '';

        if (isset($params[0])) {
            $subCommand = $params[0];

            if ('tables' == $subCommand) {
                $args = "--skip-column-names --batch --execute 'show tables;'";
            } else if ('show' == $subCommand && isset($params[1])) {
                $args = "--table --execute 'select * from {$params[1]};'";
            } else if ('desc' == $subCommand && isset($params[1])) {
                $args = "--table --execute 'desc {$params[1]};'";
            } else if ('dump' == $subCommand) {
                $overrideCommand = "mysqldump --no-defaults -uadmin";
                $databases = join(' ', array_slice($params, 1));
                $args = "--databases " . (empty($databases) ? "psa" : $databases);
            } else {
                $args = "--table --execute " . escapeshellarg($subCommand);
            }

            $args .= ' | less --quit-if-one-screen --no-init';
        }

        $this->_executeMysql($args, $overrideCommand);
    }

    private function _doPhp(array $params = null)
    {
        $arguments = join(' ', array_map('escapeshellarg', $params));
        system("/usr/local/psa/bin/sw-engine-pleskrun $arguments", $exitCode);
        exit($exitCode);
    }

    private function _doBin(array $params = null)
    {
        if (count($params) < 1) {
            $this->_fatalError("Please specify a command.");
        }

        $command = array_shift($params);
        $arguments = join(' ', array_map('escapeshellarg', $params));
        $fullCommand = "/usr/local/psa/bin/$command";

        if (is_executable($fullCommand)) {
            system("$fullCommand $arguments", $exitCode);
        } else {
            system("/usr/local/psa/bin/sw-engine-pleskrun $fullCommand $arguments", $exitCode);
        }

        exit($exitCode);
    }

    private function _doSbin(array $params = null)
    {
        if (count($params) < 1) {
            $this->_fatalError("Please specify a command.");
        }

        $command = array_shift($params);
        $arguments = join(' ', array_map('escapeshellarg', $params));

        system("/usr/local/psa/admin/sbin/$command $arguments", $exitCode);
        exit($exitCode);
    }

    private function _doConf(array $params = null)
    {
        if (!isset($params[0])) {
            $this->_fatalError('Please specify a configuration file.');
        }

        $config = $params[0];
        $editor = "vi";
        if (isset($_ENV["EDITOR"]) && !empty($_ENV["EDITOR"])) {
            $editor = $_ENV["EDITOR"];
        }
        $this->_procOpen("$editor /usr/local/psa/admin/conf/$config");
    }

    private function _doLog(array $logFiles = null)
    {
        if (count($logFiles) == 0) {
            $this->_fatalError('Please specify a log file.');
        }

        foreach ($logFiles as &$logFile){
            if ($logFile == "--all") {
                 $logFiles = $this->_logFiles;
                 break;
            }
            else if (array_key_exists($logFile, $this->_logFiles)) {
                $logFile = $this->_logFiles[$logFile];
            }
            else {
                $this->_fatalError("Incorrect log file : $logFile");
            }
        }
        $logFilesStr = implode(" ", $logFiles);
        echo "Log files: $logFilesStr\n\n";
        $this->_procOpen("tail -n 20 -F $logFilesStr");
    }

    private function _doInstaller(array $options = null)
    {
        if (count($options) == 1) {
            if ($options[0] == 'install-all-updates') {
                $options = array(
                    '--select-product-id', 'plesk',
                    '--select-release-current',
                    '--upgrade-installed-components',
                );
            } else if ($options[0] == 'install-panel-updates') {
                $options = array(
                    '--select-product-id', 'plesk',
                    '--select-release-current',
                    '--upgrade-installed-components',
                    '--reinstall-patch',
                    '--include-components-from-class', 'vendor=parallels',
                    '--include-components-from-class', 'patched',
                );
            }
        }

        $installer = new PleskCtl_Autoinstaller();
        $commandLine = $installer->buildCommandLine($options);
        echo "Parallels Installer command line: $commandLine\n\n";
        $this->_procOpen($commandLine);
    }

    private function _getCompletionBin(array $args)
    {
        if (count($args) > 1)
            return array();

        return array_map('basename', glob('/usr/local/psa/bin/*'));
    }

    private function _getCompletionSbin(array $args)
    {
        if (count($args) > 1)
            return array();

        return array_map('basename', glob('/usr/local/psa/admin/sbin/*'));
    }

    private function _getCompletionHelp(array $args)
    {
        if (count($args) > 1)
            return array();

        $commands = array_filter(array_keys($this->_commands), function($command) { return ('help' != $command); });
        return $commands;
    }

    private function _getCompletionLog(array $args)
    {
        return array_merge(array_keys($this->_logFiles), array("--all"));
    }

    private function _getCompletionDb(array $args)
    {
        if (count($args) > 1) {
            /* Just printing possible completion options and returning empty completion array will result in operational completion. */
            if (($args[0] == 'show' || $args[0] == 'desc') && count($args) == 2) {
                $this->_doDb(array('tables'));
            } else if ($args[0] == 'dump') {
                $this->_executeMysql("--skip-column-names --batch --execute 'show databases;'");
            }

            return array();
        }

        return array('tables', 'show', 'desc', 'dump');
    }

    private function _getCompletionConf(array $args)
    {
        if (count($args) > 1)
            return array();

        return array('panel.ini', 'php.ini');
    }

    private function _getCompletionInstaller(array $args)
    {
        if (count($args) > 1 && strncmp($args[0], '-', 1) != 0)
            return array();

        $shortcuts = array();
        if (count($args) <= 1)
            $shortcuts = array('install-all-updates', 'install-panel-updates');

        $installer = new PleskCtl_Autoinstaller();
        return array_merge($shortcuts, $installer->getCompletion($args));
    }

    private function _getSubCommandsCompletion($command, array $params)
    {
        $subCommands = array();

        if (method_exists($this, '_getCompletion' . ucfirst($command))) {
            $subCommands = call_user_func(array($this, '_getCompletion' . ucfirst($command)), $params);
        }

        return $subCommands;
    }

    private function _getCompletion(array $params)
    {
        /* Input will contain previous options and the unfinished one. 
         * Output should be a string with possible completions. They don't need 
         * to have the same prefix - filtering would be done by caller. E.g.:
         * [ 'log', 'pan' ]                              => 'panel.log'
         * [ 'bin', '' ]                                 => 'domain client subscription ...'
         * [ 'sbin', 'usermng', '' ]                     => ''
         * [ 'installer', '--all-versions', '--source' ] => '--source'
         */
        if (count($params) >= 2) {
            $command = $params[0];
            array_shift($params);
            $subCommands = $this->_getSubCommandsCompletion($command, $params);
            return join(' ', $subCommands);
        }

        $commands = array_keys($this->_commands);
        return join(' ', $commands);
    }

    private function _getHelpPhp()
    {
        echo "Usage: plesk php filename.php\n\n";
        echo "Run filename.php using the proper PHP interpreter.\n";
    }

    private function _getHelpVersion()
    {
        echo "Usage: plesk version\n\n";
        echo wordwrap("Show product version information. Version, build date, revision and architecture will be shown.\n");
    }

    private function _getHelpBin()
    {
        echo "Usage: plesk bin utility\n\n";
        echo "Run the specified Plesk command-line utility (e.g., domain, client).\n";
        echo "This applies to public CLI utilities located at /usr/local/psa/bin directory.\n";
    }

    private function _getHelpSbin()
    {
        echo "Usage: plesk sbin utility\n\n";
        echo "Run the specified Plesk internal utility.\n";
        echo "This applies to utilities located at /usr/local/psa/admin/sbin directory.\n";
    }

    private function _getHelpDb()
    {
        echo "Usage: plesk db [command|sql]\n\n";
        echo "Execute database specific command.\n";
        echo "Open MySQL console if no particular command was supplied.\n";
        echo "Run an SQL query if 'sql' is provided instead of command.\n\n";
        echo "Additional commands:\n";
        echo "  tables - Show the list of existing database tables\n";
        echo "  dump [databases] - Show a dump of the specified Plesk database(s) \n";
        echo "                     (the psa database dump is shown by default)\n";
        echo "  desc <table> - Show a structure of the specified database table\n";
        echo "  show <table> - Show content of the specified database table\n";
    }

    private function _getHelpConf()
    {
        echo "Usage: plesk conf <name>\n\n";
        echo "Open the specified Plesk configuration file in the editor.\n\n";
        echo "Available configuration files:\n";
        echo "  panel.ini - Plesk internal settings\n";
        echo "  php.ini - PHP interpreter settings (for panel web interface only)\n";
    }

    private function _getHelpLog()
    {
        echo "Usage: plesk log <name> ...\n\n";
        echo "Display the specified Plesk log file using the 'tail -f' utility.\n\n";
        echo "Available log files:\n";
        echo "  error_log - Plesk errors\n";
        echo "  access_log - Web server access log\n";
        echo "  panel.log - Plesk events log\n";
        echo "  maillog - Mail server log file\n";
        echo "  --all - Monitor all available log files\n";
    }

    private function _getHelpInstaller()
    {
        echo "Usage: plesk installer [shortcut | options]\n\n";
        echo wordwrap("Run Parallels Installer with given options or a predefined set of options via a shortcut.\n\n");
        echo "Available shortcuts:\n";
        echo "  install-all-updates - Install all available updates within current Plesk version,\n";
        echo "                        including updates for system components such as MySQL or PHP.\n";
        echo "  install-panel-updates - Install only Plesk updates. Reinstall patch if latest \n";
        echo "                          update is already installed. System components such as \n";
        echo "                          MySQL and PHP will not be updated unless it is required\n";
        echo "                          for proper Plesk functioning.\n";
    }

    private function _dateToHuman($rawDate)
    {
        $offset = strlen($rawDate) - 9;
        return '20' . substr($rawDate, $offset, 2) . '/' // year
            . substr($rawDate, $offset + 2, 2) . '/' // month
            . substr($rawDate, $offset + 4, 2) . ' ' // day
            . substr($rawDate, $offset + 7, 2) . ':00'; // time
    }

    private function _isoDateToHuman($isoDate)
    {
        return substr($isoDate, 0, 4) . '/' . substr($isoDate, 4, 2) . '/' . substr($isoDate, 6, 2) . ' ' . substr($isoDate, 9, 2) . ':' . substr($isoDate, 11, 2);
    }

    private function _fatalError($message)
    {
        echo "Error: $message\n";
        exit(1);
    }

    private function _procOpen($command)
    {
        $descriptorSpec = array(0 => STDIN, 1 => STDOUT, 2 => STDERR);
        $process = proc_open($command, $descriptorSpec, $pipes);
        proc_close($process);
    }

    private function _executeMysql($commandArgs, $baseCommandLine = null)
    {
        $password = trim(file_get_contents('/etc/psa/.psa.shadow'));
        putenv("MYSQL_PWD=$password");
        $command = (empty($baseCommandLine) ? "mysql --no-defaults -uadmin psa" : $baseCommandLine) . " " . $commandArgs;
        $this->_procOpen($command);
        putenv("MYSQL_PWD=");
    }

}

class PleskCtl_Autoinstaller
{
    private $_options = array(
        /* Only public non-obsolete options valid for Linux are listed here. */
        '--source'                           => array('flag' => false),
        '--target'                           => array('flag' => false),
        '--proxy-host'                       => array('flag' => false),
        '--proxy-port'                       => array('flag' => false),
        '--proxy-user'                       => array('flag' => false),
        '--proxy-password'                   => array('flag' => false),
        '--query-status'                     => array('flag' => true ),
        '--check-updates'                    => array('flag' => true ),
        '--show-all-releases'                => array('flag' => true ),
        '--show-os-list'                     => array('flag' => true ),
        '--show-releases'                    => array('flag' => true ),
        '--show-installation-types'          => array('flag' => true , 'min' => '3.8.0'),
        '--show-components'                  => array('flag' => true ),
        '--show-packages'                    => array('flag' => true ),
        '--mirror-os'                        => array('flag' => false),
        '--installation-type'                => array('flag' => false, 'min' => '3.8.0'),
        '--install-component'                => array('flag' => false),
        '--install-everything'               => array('flag' => true ),
        '--upgrade-installed-components'     => array('flag' => true , 'min' => '3.2.0'),
        '--include-components-from-class'    => array('flag' => false, 'min' => '3.15.4'),
        '--remove-component'                 => array('flag' => false, 'min' => '3.13.0'),
        '--remove-everything'                => array('flag' => true , 'min' => '3.13.0'),
        '--select-product-id'                => array('flag' => false),
        '--select-release-id'                => array('flag' => false),
        '--select-release-latest'            => array('flag' => true ),
        '--select-release-current'           => array('flag' => true ),
        '--enable-xml-output'                => array('flag' => true ),
        '--override-os-name'                 => array('flag' => false),
        '--override-os-vendor'               => array('flag' => false),
        '--override-os-version'              => array('flag' => false),
        '--override-os-arch'                 => array('flag' => false),
        '--override-environment'             => array('flag' => false),
        '--disable-major-updates'            => array('flag' => true , 'min' => '3.5.2', 'max' => '3.12.9'),
        '--web-interface'                    => array('flag' => true , 'min' => '3.4.0'),
        '--ssl-cert'                         => array('flag' => false, 'min' => '3.4.0'),
        '--without-ssl'                      => array('flag' => true , 'min' => '3.4.0'),
        '--console-interface'                => array('flag' => true , 'min' => '3.5.0'),
        '--disable-browser'                  => array('flag' => true , 'min' => '3.5.1'),
        '--with-ssl'                         => array('flag' => true , 'min' => '3.5.1'),
        '--reinstall-patch'                  => array('flag' => true , 'min' => '3.5.1'),
        '--download-retry-count'             => array('flag' => false, 'min' => '3.15.15'),
        '--no-clear'                         => array('flag' => true ),
        '--truncate-log'                     => array('flag' => true ),
        '--separate-logs'                    => array('flag' => true ),
        '--no-daemon'                        => array('flag' => true ),
        '--notify-email'                     => array('flag' => false),
        '--ignore-key-errors'                => array('flag' => true ),
        '--skip-components-check'            => array('flag' => true , 'min' => '3.11.0'),
        '--version'                          => array('flag' => true ),
        '--print-version-of-this-binary'     => array('flag' => true , 'min' => '3.7.0'),
        '--skip-cleanup'                     => array('flag' => true , 'min' => '3.10.0'),
        '--help'                             => array('flag' => true ),
        '--debug'                            => array('flag' => true ),
        '--no-space-check'                   => array('flag' => true ),
        '--enable-feedback'                  => array('flag' => true , 'min' => '3.14.0'),
        '--disable-feedback'                 => array('flag' => true , 'min' => '3.14.0'),
        '--tier'                             => array('flag' => false, 'min' => '3.11.0'),
        '--all-versions'                     => array('flag' => true , 'min' => '3.11.0'),
    );

    private function _getBinaryPath()
    {
        if (isset($_ENV["PLESK_INSTALLER"]) && is_executable($_ENV["PLESK_INSTALLER"]))
            return $_ENV["PLESK_INSTALLER"];

        return "/usr/local/psa/admin/sbin/autoinstaller";
    }

    private function _getBinaryVersion()
    {
        $installer = $this->_getBinaryPath();
        return trim(exec("$installer --print-version-of-this-binary 2>/dev/null"));
    }

    private function _getAvailableOptions()
    {
        $currentVersion = $this->_getBinaryVersion();
        if (empty($currentVersion))
            return array();

        $allowedOptions = array();
        foreach ($this->_options as $key => $attr) {
            if ((!isset($attr['min']) || version_compare($attr['min'], $currentVersion, '<=')) &&
                (!isset($attr['max']) || version_compare($attr['max'], $currentVersion, '>=')))
            {
                $allowedOptions[] = $key;
            }
        }

        return $allowedOptions;
    }

    public function buildCommandLine(array $options = null)
    {
        $installer = $this->_getBinaryPath();
        $optionsStr = join(' ', array_map('escapeshellarg', $options));
        return "$installer $optionsStr";
    }

    public function getCompletion(array $args)
    {
        if (count($args) > 1) {
            $previousOption = $args[count($args) - 2];
            if (isset($this->_options[$previousOption]) && !@$this->_options[$previousOption]['flag'])
                return array(); /* Previous word is an option requiring argument. We don't complete arguments. */
        }

        return $this->_getAvailableOptions();
    }
}

$wrapper = new PleskCtl_Wrapper();
$wrapper->run($argv);
