#!/usr/bin/env php
<?php

/**
 * This file is part of Gush.
 *
 * (c) Luis Cordova <cordoval@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 *
 * Gush installer, heavily inspired by composer installer script
 * credits to http://getcomposer.org
 */

process($argv);

/**
 * processes the installer
 */
function process($argv)
{
    $check      = in_array('--check', $argv);
    $help       = in_array('--help', $argv);
    $force      = in_array('--force', $argv);
    $quiet      = in_array('--quiet', $argv);
    $installDir = false;

    // --no-ansi wins over --ansi
    if (in_array('--no-ansi', $argv)) {
        define('USE_ANSI', false);
    } elseif (in_array('--ansi', $argv)) {
        define('USE_ANSI', true);
    } else {
        // On Windows, default to no ANSI, except in ANSICON and ConEmu.
        // Everywhere else, default to ANSI if stdout is a terminal.
        define('USE_ANSI',
        (DIRECTORY_SEPARATOR == '\\')
            ? (false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI'))
            : (function_exists('posix_isatty') && posix_isatty(1))
        );
    }

    foreach ($argv as $key => $val) {
        if (0 === strpos($val, '--install-dir')) {
            if (13 === strlen($val) && isset($argv[$key+1])) {
                $installDir = trim($argv[$key+1]);
            } else {
                $installDir = trim(substr($val, 14));
            }
        }
    }

    if ($help) {
        displayHelp();
        exit(0);
    }

    $ok = checkPlatform($quiet);

    if (false !== $installDir && !is_dir($installDir)) {
        out("The defined install dir ({$installDir}) does not exist.", 'info');
        $ok = false;
    }

    if ($check) {
        exit($ok ? 0 : 1);
    }

    if ($ok || $force) {
        installGush($installDir, $quiet);
        exit(0);
    }

    exit(1);
}

/**
 * displays the help
 */
function displayHelp()
{
    echo <<<EOF
Gush Installer
--------------
Options
--help               this help
--check              for checking environment only
--force              forces the installation
--ansi               force ANSI color output
--no-ansi            disable ANSI color output
--install-dir="..."  accepts a target installation directory

EOF;
}

/**
 * check the platform for possible issues on running gush
 */
function checkPlatform($quiet)
{
    $errors = array();
    $warnings = array();

    $iniPath = php_ini_loaded_file();
    $displayIniMessage = false;
    if ($iniPath) {
        $iniMessage = PHP_EOL.PHP_EOL.'The php.ini used by your command-line PHP is: ' . $iniPath;
    } else {
        $iniMessage = PHP_EOL.PHP_EOL.'A php.ini file does not exist. You will have to create one.';
    }
    $iniMessage .= PHP_EOL.'If you can not modify the ini file, you can also run `php -d option=value` to modify ini values on the fly. You can use -d multiple times.';

    if (ini_get('detect_unicode')) {
        $errors['unicode'] = 'On';
    }

    if (extension_loaded('suhosin')) {
        $suhosin = ini_get('suhosin.executor.include.whitelist');
        $suhosinBlacklist = ini_get('suhosin.executor.include.blacklist');
        if (false === stripos($suhosin, 'phar') && (!$suhosinBlacklist || false !== stripos($suhosinBlacklist, 'phar'))) {
            $errors['suhosin'] = $suhosin;
        }
    }

    if (!function_exists('json_decode')) {
        $errors['json'] = true;
    }

    if (!extension_loaded('Phar')) {
        $errors['phar'] = true;
    }

    if (!extension_loaded('filter')) {
        $errors['filter'] = true;
    }

    if (!extension_loaded('hash')) {
        $errors['hash'] = true;
    }

    if (!extension_loaded('ctype')) {
        $errors['ctype'] = true;
    }

    if (!ini_get('allow_url_fopen')) {
        $errors['allow_url_fopen'] = true;
    }

    if (extension_loaded('ionCube Loader') && ioncube_loader_iversion() < 40009) {
        $errors['ioncube'] = ioncube_loader_version();
    }

    if (version_compare(PHP_VERSION, '5.4', '<')) {
        $errors['php'] = PHP_VERSION;
    }

    if (version_compare(PHP_VERSION, '5.4', '<')) {
        $warnings['php'] = PHP_VERSION;
    }

    if (!extension_loaded('openssl')) {
        $warnings['openssl'] = true;
    }

    if (ini_get('apc.enable_cli')) {
        $warnings['apc_cli'] = true;
    }

    ob_start();
    phpinfo(INFO_GENERAL);
    $phpinfo = ob_get_clean();
    if (preg_match('{Configure Command(?: *</td><td class="v">| *=> *)(.*?)(?:</td>|$)}m', $phpinfo, $match)) {
        $configure = $match[1];

        if (false !== strpos($configure, '--enable-sigchild')) {
            $warnings['sigchild'] = true;
        }

        if (false !== strpos($configure, '--with-curlwrappers')) {
            $warnings['curlwrappers'] = true;
        }
    }

    if (!empty($errors)) {
        out("Some settings on your machine make Gush unable to work properly.", 'error');

        out('Make sure that you fix the issues listed below and run this script again:', 'error');
        foreach ($errors as $error => $current) {
            switch ($error) {
                case 'json':
                    $text = PHP_EOL."The json extension is missing.".PHP_EOL;
                    $text .= "Install it or recompile php without --disable-json";
                    break;

                case 'phar':
                    $text = PHP_EOL."The phar extension is missing.".PHP_EOL;
                    $text .= "Install it or recompile php without --disable-phar";
                    break;

                case 'filter':
                    $text = PHP_EOL."The filter extension is missing.".PHP_EOL;
                    $text .= "Install it or recompile php without --disable-filter";
                    break;

                case 'hash':
                    $text = PHP_EOL."The hash extension is missing.".PHP_EOL;
                    $text .= "Install it or recompile php without --disable-hash";
                    break;

                case 'ctype':
                    $text = PHP_EOL."The ctype extension is missing.".PHP_EOL;
                    $text .= "Install it or recompile php without --disable-ctype";
                    break;

                case 'unicode':
                    $text = PHP_EOL."The detect_unicode setting must be disabled.".PHP_EOL;
                    $text .= "Add the following to the end of your `php.ini`:".PHP_EOL;
                    $text .= "    detect_unicode = Off";
                    $displayIniMessage = true;
                    break;

                case 'suhosin':
                    $text = PHP_EOL."The suhosin.executor.include.whitelist setting is incorrect.".PHP_EOL;
                    $text .= "Add the following to the end of your `php.ini` or suhosin.ini (Example path [for Debian]: /etc/php5/cli/conf.d/suhosin.ini):".PHP_EOL;
                    $text .= "    suhosin.executor.include.whitelist = phar ".$current;
                    $displayIniMessage = true;
                    break;

                case 'php':
                    $text = PHP_EOL."Your PHP ({$current}) is too old, you must upgrade to PHP 5.4 or higher.";
                    break;

                case 'allow_url_fopen':
                    $text = PHP_EOL."The allow_url_fopen setting is incorrect.".PHP_EOL;
                    $text .= "Add the following to the end of your `php.ini`:".PHP_EOL;
                    $text .= "    allow_url_fopen = On";
                    $displayIniMessage = true;
                    break;

                case 'ioncube':
                    $text = PHP_EOL."Your ionCube Loader extension ($current) is incompatible with Phar files.".PHP_EOL;
                    $text .= "Upgrade to ionCube 4.0.9 or higher or remove this line (path may be different) from your `php.ini` to disable it:".PHP_EOL;
                    $text .= "    zend_extension = /usr/lib/php5/20090626+lfs/ioncube_loader_lin_5.3.so";
                    $displayIniMessage = true;
                    break;
            }
            if ($displayIniMessage) {
                $text .= $iniMessage;
            }
            out($text, 'info');
        }

        out('');

        return false;
    }

    if (!empty($warnings)) {
        out("Some settings on your machine may cause stability issues with Gush.", 'error');

        out('If you encounter issues, try to change the following:', 'error');
        foreach ($warnings as $warning => $current) {
            switch ($warning) {
                case 'apc_cli':
                    $text = PHP_EOL."The apc.enable_cli setting is incorrect.".PHP_EOL;
                    $text .= "Add the following to the end of your `php.ini`:".PHP_EOL;
                    $text .= "    apc.enable_cli = Off";
                    $displayIniMessage = true;
                    break;

                case 'sigchild':
                    $text = PHP_EOL."PHP was compiled with --enable-sigchild which can cause issues on some platforms.".PHP_EOL;
                    $text .= "Recompile it without this flag if possible, see also:".PHP_EOL;
                    $text .= "    https://bugs.php.net/bug.php?id=22999";
                    break;

                case 'curlwrappers':
                    $text = PHP_EOL."PHP was compiled with --with-curlwrappers which will cause issues with HTTP authentication and GitHub.".PHP_EOL;
                    $text .= "Recompile it without this flag if possible";
                    break;

                case 'openssl':
                    $text = PHP_EOL."The openssl extension is missing, which will reduce the security and stability of Gush.".PHP_EOL;
                    $text .= "If possible you should enable it or recompile php with --with-openssl";
                    break;

                case 'php':
                    $text = PHP_EOL."Your PHP ({$current}) is quite old, upgrading to PHP 5.4 or higher is recommended.".PHP_EOL;
                    $text .= "Gush works with 5.4+ for most people, but there might be edge case issues.";
                    break;
            }
            if ($displayIniMessage) {
                $text .= $iniMessage;
            }
            out($text, 'info');
        }

        out('');

        return true;
    }

    if (!$quiet) {
        out("All settings correct for using Gush", 'success');
    }

    return true;
}

/**
 * installs gush to the current working directory
 */
function installGush($installDir, $quiet)
{
    $installPath = (is_dir($installDir) ? rtrim($installDir, '/').'/' : '') . 'gush.phar';
    $installDir = realpath($installDir) ? realpath($installDir) : getcwd();
    $file       = $installDir.DIRECTORY_SEPARATOR.'gush.phar';

    if (is_readable($file)) {
        @unlink($file);
    }

    $retries = 3;
    while ($retries--) {
        if (!$quiet) {
            out("Downloading...", 'info');
        }

        $source = (extension_loaded('openssl') ? 'http' : 'http').'://gushphp.org/gush.phar';

        $errorHandler = new ErrorHandler();
        set_error_handler(array($errorHandler, 'handleError'));

        $fh = fopen($file, 'w');
        if (!$fh) {
            out('Could not create file '.$file.': '.$errorHandler->message, 'error');
        }
        if (!fwrite($fh, file_get_contents($source, false, getStreamContext($source)))) {
            out('Download failed: '.$errorHandler->message, 'error');
        }
        fclose($fh);

        restore_error_handler();
        if ($errorHandler->message) {
            continue;
        }

        try {
            // test the phar validity
            $phar = new Phar($file);
            // free the variable to unlock the file
            unset($phar);
            break;
        } catch (Exception $e) {
            if (!$e instanceof UnexpectedValueException && !$e instanceof PharException) {
                throw $e;
            }
            unlink($file);
            if ($retries) {
                if (!$quiet) {
                    out('The download is corrupt, retrying...', 'error');
                }
            } else {
                out('The download is corrupt ('.$e->getMessage().'), aborting.', 'error');
                exit(1);
            }
        }
    }

    if ($errorHandler->message) {
        out('The download failed repeatedly, aborting.', 'error');
        exit(1);
    }

    chmod($file, 0755);

    if (!$quiet) {
        out(PHP_EOL."Gush successfully installed to: " . $file, 'success', false);
        out(PHP_EOL."Use it: php $installPath", 'info');
    }
}

/**
 * colorize output
 */
function out($text, $color = null, $newLine = true)
{
    $styles = array(
        'success' => "\033[0;32m%s\033[0m",
        'error' => "\033[31;31m%s\033[0m",
        'info' => "\033[33;33m%s\033[0m"
    );

    $format = '%s';

    if (isset($styles[$color]) && USE_ANSI) {
        $format = $styles[$color];
    }

    if ($newLine) {
        $format .= PHP_EOL;
    }

    printf($format, $text);
}

/**
 * function copied from Composer\Util\StreamContextFactory::getContext
 *
 * Any changes should be applied there as well, or backported here.
 *
 * @param string $url URL the context is to be used for
 * @return resource Default context
 * @throws \RuntimeException if https proxy required and OpenSSL uninstalled
 */
function getStreamContext($url)
{
    $options = array('http' => array());

    // Handle system proxy
    if (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy'])) {
        // Some systems seem to rely on a lowercased version instead...
        $proxy = parse_url(!empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']);
    }

    if (!empty($proxy)) {
        $proxyURL = isset($proxy['scheme']) ? $proxy['scheme'] . '://' : '';
        $proxyURL .= isset($proxy['host']) ? $proxy['host'] : '';

        if (isset($proxy['port'])) {
            $proxyURL .= ":" . $proxy['port'];
        } elseif ('http://' == substr($proxyURL, 0, 7)) {
            $proxyURL .= ":80";
        } elseif ('https://' == substr($proxyURL, 0, 8)) {
            $proxyURL .= ":443";
        }

        // http(s):// is not supported in proxy
        $proxyURL = str_replace(array('http://', 'https://'), array('tcp://', 'ssl://'), $proxyURL);

        if (0 === strpos($proxyURL, 'ssl:') && !extension_loaded('openssl')) {
            throw new \RuntimeException('You must enable the openssl extension to use a proxy over https');
        }

        $options['http'] = array(
            'proxy'           => $proxyURL,
        );

        // enabled request_fulluri unless it is explicitly disabled
        switch (parse_url($url, PHP_URL_SCHEME)) {
            case 'http': // default request_fulluri to true
                $reqFullUriEnv = getenv('HTTP_PROXY_REQUEST_FULLURI');
                if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) {
                    $options['http']['request_fulluri'] = true;
                }
                break;
            case 'https': // default request_fulluri to true
                $reqFullUriEnv = getenv('HTTPS_PROXY_REQUEST_FULLURI');
                if ($reqFullUriEnv === false || $reqFullUriEnv === '' || (strtolower($reqFullUriEnv) !== 'false' && (bool) $reqFullUriEnv)) {
                    $options['http']['request_fulluri'] = true;
                }
                break;
        }

        if (isset($proxy['user'])) {
            $auth = urldecode($proxy['user']);
            if (isset($proxy['pass'])) {
                $auth .= ':' . urldecode($proxy['pass']);
            }
            $auth = base64_encode($auth);

            $options['http']['header'] = "Proxy-Authorization: Basic {$auth}\r\n";
        }
    }

    return stream_context_create($options);
}

class ErrorHandler
{
    public $message = '';

    public function handleError($code, $msg)
    {
        if ($this->message) {
            $this->message .= "\n";
        }
        $this->message .= preg_replace('{^copy\(.*?\): }', '', $msg);
    }
}
