<?php

class XApp_Shell_Client
{
	/**
	 * List of running processes.
	 *
	 * @var array
	 */
	protected $processList;

	/**
	 * Actual working directory.
	 *
	 * @var string
	 */
	protected $cwd;

	/**
	 * Environment properties.
	 *
	 * @var array
	 */
	protected $env = array();

	/**
	 * Stdout output.
	 *
	 * @var string
	 */
	protected $out;

	/**
	 * Stderr output.
	 *
	 * @var string
	 */
	protected $error;

	/**
	 * Constructor.
	 *
	 * @param string $cwd Working directory
	 * @param array $env Array of environment properties
	 */
	public function __construct($cwd = '', array $env = array())
	{
		$this->setCwd($cwd);
		$this->env = $_ENV;
		$this->setEnv($env);
	}

	/**
	 * Returns a list of processes.
	 *
	 * Works only on Linux.
	 *
	 * @return XApp_Shell_Client
	 */
	public function loadProcessList()
	{
		$output = shell_exec('ps aux');

		$data = explode("\n", $output);

		foreach ($data as $value) {
			$value = preg_replace('/ +/', ' ', $value);
			$list = explode(' ', $value);
			$commands[$list[10]][] = $list[1];
		}

		$this->processList = $commands;

		return $this;
	}

	/**
	 * Checks if there is a process of the given name.
	 *
	 * Works only on Linux.
	 *
	 * @param string $name Process name
	 * @return boolean
	 */
	public function processExists($name)
	{
		return array_key_exists((string) $name, $this->processList);
	}

	/**
	 * Kills all processes of the given name.
	 *
	 * Works only on Linux.
	 *
	 * @param string $name Process name
	 * @return XApp_Shell_Client
	 */
	public function killProcess($name)
	{
		shell_exec('killall -s KILL ' . (string) $name);

		return $this;
	}

	/**
	 * Sets working directory.
	 *
	 * Defaults to null.
	 *
	 * @param string $cwd Working directory
	 * @return XApp_Shell_Client
	 */
	public function setCwd($cwd = '')
	{
		$this->cwd = (string) $cwd;

		return $this;
	}

	/**
	 * Adds one or more environment properties.
	 *
	 * @param array $env Array of properties
	 * @return XApp_Shell_Client
	 */
	public function setEnv(array $env)
	{
		$this->env = array_merge($this->env, $env);

		return $this;
	}

	/**
	 * Removes environment properties.
	 *
	 * @return XApp_Shell_Client
	 */
	public function clearEnv()
	{
		$this->env = $_ENV;

		return $this;
	}

	/**
	 * Executes an external command.
	 *
	 * Captures stdout and stderr.
	 * Throws an exception on status code != 0.
	 *
	 * @param string $cmd Command to execute
	 * @param integer $status Status code
	 * @return XApp_Shell_Client
	 * @throws XApp_Shell_Exception On execution error
	 */
	public function exec($cmd, &$status = null)
	{
		static $descriptorSpec = array(
			0 => array('pipe', 'r'),
			1 => array('pipe', 'w'),
			2 => array('pipe', 'w')
		);

		$env = $this->env;
		if (ini_get('safe_mode')) {
			// If the safe_mode is set on, we have to check which properties we are allowed to set.

			$allowedPrefixes = explode(',', ini_get('safe_mode_allowed_env_vars'));
			$protectedVars = explode(',', ini_get('safe_mode_protected_env_vars'));

			// Throw away protected properties.
			$env = array_diff_key($env, array_fill_keys($protectedVars, true));

			// Throw away properties that do not have the allowed prefix.
			foreach ($env as $name => $value) {
				foreach ($allowedPrefixes as $prefix) {
					// Empty prefix - allow all properties.
					if ($prefix === '') {
						break 2;
					}

					if (substr($name, 0, strlen($prefix)) == $prefix) {
						continue 2;
					}
				}

				unset($env[$name]);
			}
		}

		$cmd = (string) $cmd;
        error_log('running : ' . $cmd);
		$process = proc_open($cmd, $descriptorSpec, $pipes, !empty($this->cwd) ? $this->cwd : null, !empty($env) ? $env : null);

		if (!is_resource($process)) {
			throw new XApp_Shell_Exception('Unable to start shell process.');
		}

		$this->out = stream_get_contents($pipes[1]);
		fclose($pipes[1]);

		$this->error = stream_get_contents($pipes[2]);
		fclose($pipes[2]);

		$status = proc_close($process);

		if ($status !== 0) {
			throw new XApp_Shell_Exception(
				'Command ' . $cmd . ' returned code ' . $status
					. '. Output: ' . $this->error
			);
		}

		return $this;
	}

	/**
	 * Returns stdout contents.
	 *
	 * @return string
	 */
	public function getOut()
	{
		return $this->out;
	}

	/**
	 * Returns stderr contents.
	 *
	 * @return string
	 */
	public function getError()
	{
		return $this->error;
	}
}
