<?php

class PartKeeprRequirements extends SymfonyRequirements
{
    /**
     * Constructor that initializes the requirements.
     */
    public function __construct()
    {
        parent::__construct();

        $this->addRequirement(
            ini_get('allow_url_fopen') || function_exists('curl_init'),
            sprintf('No way to remotely load files detected'),
            sprintf('Either set <strong>allow_url_fopen=true</strong> or install the cURL extension')
        );

        $this->addRequirement(
            function_exists('imagecreate'),
            sprintf('GD library not found'),
            sprintf('Install the GD library extension')
        );

        $this->addRequirement(
            function_exists('imagettftext'),
            sprintf('GD library has no FreeType support'),
            sprintf('Install the FreeType extension and make sure GD extension can use it')
        );

        $this->addRequirement(
            function_exists('ldap_connect'),
            sprintf('LDAP library not found'),
            sprintf('Install the LDAP library extension')
        );

        $this->addRequirement(
            function_exists('curl_init'),
            sprintf('CURL library not found'),
            sprintf('Install the CURL library extension')
        );

        $this->addPhpIniRequirement(
            'memory_limit',
            $this->getBytesIniSetting('memory_limit') > 128000000,
            false,
            'Memory Limit too small',
            sprintf(
                'The php.ini memory_limit directive must be set to 128MB or higher. Your limit is set to %s',
                ini_get('memory_limit')
            )
        );

        $this->checkWritable(realpath(dirname(__FILE__).'/../data/'));
        $this->checkWritable(realpath(dirname(__FILE__).'/../app/'));
        $this->checkWritable(realpath(dirname(__FILE__).'/../web/'));

        $this->addRecommendation(
            function_exists('apc_fetch'),
            sprintf('PHP APCu cache not found'),
            sprintf('For best performance install the PHP APCu cache')
        );

        $this->addPhpIniRecommendation(
            'max_execution_time',
            ini_get('max_execution_time') > 30,
            true,
            sprintf('Maximum Execution Time might be too low'),
            sprintf(
                'Your maximum execution time is set to %d seconds, which might be too low for low-end systems. If you encounter problems, please increase the value.',
                ini_get('max_execution_time')
            )
        );

        $this->addRequirement(
            function_exists('mb_convert_case'),
            sprintf('The PHP function mb_convert_case does not exist.'),
            sprintf('Please compile PHP with the mbstring functions in case you are using Gentoo, or install php-mbstring on RedHat, Fedora or CentOS.')
        );

        $this->addRequirement(
            function_exists('bcscale'),
            sprintf('BCMath library not found'),
            sprintf('Install the BCMath library extension')
        );

        if (ini_get('opcache.enable')) {
            if (version_compare(phpversion(), '7.0', '<')) {
                $this->addPhpIniRequirement(
                    'opcache.save_comments',
                    1,
                    false,
                    'opcache.save_comments must be on',
                    sprintf('The php.ini opcache.save_comments directive must be set to 1.')
                );

                $this->addPhpIniRequirement(
                    'opcache.load_comments',
                    1,
                    false,
                    'opcache.load_comments must be on',
                    sprintf('The php.ini opcache.load_comments directive must be set to 1.')
                );
            }
        }
    }

    /**
     * Checks if a path is writable. If not, generates a requirement.
     *
     * @param $path string The path to check
     */
    protected function checkWritable($path)
    {
        try {
            $this->isWritableRecursive($path);
        } catch (\Exception $e) {
            $this->addRequirement(
                false,
                sprintf(
                    'Directory %s and all subfolders/files must be writable.<br/><br/>If you already set the subfolders and files writable, you might have SELinux installed. Read <a href="https://wiki.partkeepr.org/wiki/SELinux" target="_blank">the PartKeepr SELinux Wiki Article</a> for further information.<br/>',
                    $path
                ),
                $e->getMessage()
            );
        }
    }

    /**
     * Returns a php.ini setting with parsed byte values.
     *
     * Example: If you specify memory_limit=64M, this method will return 67108864 bytes.
     *
     * @param $setting string The php.ini setting
     *
     * @return int The byts
     */
    protected function getBytesIniSetting($setting)
    {
        return (int) $this->returnBytes(ini_get($setting));
    }

    /**
     * Parses a value with g,m or k modifiers and returns the effective bytes.
     *
     * @param $val string The value to parse
     *
     * @return int|string The bytes
     */
    protected function returnBytes($val)
    {
        $val = trim($val);
        $last = strtolower($val[strlen($val) - 1]);
        $vali = (int) substr($val, 0, -1);
        switch ($last) {
            // The 'G' modifier is available since PHP 5.1.0
            case 'g':
                $vali *= 1073741824;
                break;
            case 'm':
                $vali *= 1048576;
                break;
            case 'k':
                $vali *= 1024;
                break;
            default:
                $vali = (int) $val;
        }

        return $vali;
    }

    /**
     * Checks if the given directory and all contained files within it is writable by the current user.
     *
     * @param string $dir The directory to check
     *
     * @throws \Exception If the directory is not writable
     *
     * @return bool True if the path is writable
     */
    protected function isWritableRecursive($dir)
    {
        if (!is_writable($dir)) {
            throw new \Exception($dir.' is not writable.');
        }

        $folder = opendir($dir);
        while ($file = readdir($folder)) {
            if ($file != '.' && $file != '..') {
                if (!is_writable($dir.'/'.$file)) {
                    closedir($folder);

                    throw new \Exception($dir.'/'.$file.' is not writable.');
                } else {
                    // Skip hidden directories
                    if ((is_dir($dir.'/'.$file)) && ($file[0] == '.')) {
                        continue;
                    }
                    if (is_dir($dir.'/'.$file)) {
                        if (!$this->isWritableRecursive($dir.'/'.$file)) {
                            closedir($folder);

                            throw new \Exception($dir.'/'.$file.'is not writable.');
                        }
                    }
                }
            }
        }

        return true;
    }
}
