1:   2:   3:   4:   5:   6:   7:   8:   9:  10:  11:  12:  13:  14:  15:  16:  17:  18:  19:  20:  21:  22:  23:  24:  25:  26:  27:  28:  29:  30:  31:  32:  33:  34:  35:  36:  37:  38:  39:  40:  41:  42:  43:  44:  45:  46:  47:  48:  49:  50:  51:  52:  53:  54:  55:  56:  57:  58:  59:  60:  61:  62:  63:  64:  65:  66:  67:  68:  69:  70:  71:  72:  73:  74:  75:  76:  77:  78:  79:  80:  81:  82:  83:  84:  85:  86:  87:  88:  89:  90:  91:  92:  93:  94:  95:  96:  97:  98:  99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 
<?php
namespace Omeka\Stdlib;

use Zend\Log\LoggerInterface;

class Cli
{
    /**
     * @var LoggerInterface
     */
    protected $logger;

    /**
     * @var string
     */
    protected $executeStrategy;

    /**
     * @param LoggerInterface $logger
     * @param string $executeStrategy
     */
    public function __construct(LoggerInterface $logger, $executeStrategy)
    {
        $this->logger = $logger;
        $this->executeStrategy = $executeStrategy;
    }

    /**
     * Get a command path.
     *
     * Returns the path to the provided command or boolean false if the command
     * is not found.
     *
     * @param string $command
     * @return string|false
     */
    public function getCommandPath($command)
    {
        $command = sprintf('command -v %s', escapeshellarg($command));
        return $this->execute($command);
    }

    /**
     * Verfy that a command exists and is executable.
     *
     * @param string $commandDir The command's directory or the command path if
     *     $command is not passed
     * @param string $command
     * @return string|false The command path if valid, false otherwise
     */
    public function validateCommand($commandDir, $command = null)
    {
        $commandDir = realpath($commandDir);
        if (false === $commandDir) {
            return false;
        }
        if (null === $command) {
            $commandPath = $commandDir;
        } else {
            if (!@is_dir($commandDir)) {
                return false;
            }
            $commandPath = sprintf('%s/%s', $commandDir, $command);
        }
        if (!@is_file($commandPath) || !@is_executable($commandPath)) {
            return false;
        }
        return $commandPath;
    }

    /**
     * Execute a command.
     *
     * Expects arguments to be properly escaped.
     *
     * @param string $command An executable command
     * @return string|false The command's standard output or false on error
     */
    public function execute($command)
    {
        switch ($this->executeStrategy) {
            case 'proc_open':
                $output = $this->procOpen($command);
                break;
            case 'exec':
            default:
                $output = $this->exec($command);
                break;
        }

        return $output;
    }

    /**
     * Execute command using PHP's exec function.
     *
     * @link http://php.net/manual/en/function.exec.php
     * @param string $command
     * @return string|false
     */
    public function exec($command)
    {
        exec($command, $output, $exitCode);
        if (0 !== $exitCode) {
            $this->logger->err(sprintf('Command "%s" failed with status code %s.', $command, $exitCode));
            return false;
        }
        return implode(PHP_EOL, $output);
    }

    /**
     * Execute command using PHP's proc_open function.
     *
     * For servers that allow proc_open. Logs standard error.
     *
     * @link http://php.net/manual/en/function.proc-open.php
     * @param string $command
     * @return string|false
     */
    public function procOpen($command)
    {
        $descriptorSpec = [
            0 => ['pipe', 'r'], // STDIN
            1 => ['pipe', 'w'], // STDOUT
            2 => ['pipe', 'w'], // STDERR
        ];

        $proc = proc_open($command, $descriptorSpec, $pipes, getcwd());
        if (!is_resource($proc)) {
            return false;
        }

        $input = stream_get_contents($pipes[0]);
        $output = stream_get_contents($pipes[1]);
        $errors = stream_get_contents($pipes[2]);
        foreach ($pipes as $pipe) {
            fclose($pipe);
        }

        $exitCode = proc_close($proc);
        if (0 !== $exitCode) {
            // Log standard error
            $this->logger->err($errors);
            $this->logger->err(sprintf('Command "%s" failed with status code %s.', $command, $exitCode));
            return false;
        }
        return trim($output);
    }
}