<?php

namespace CleantalkSP\SpbctWP\UploadDirPreventPhpExecutionModule;

class UploadDirPreventPhpExecution
{
    const HTACCESS_CONTENT = '# BEGIN Security by CleanTalk code execution protection
AddHandler cgi-script .php .php2 .php3 .php4 .php5 .php6 .php7 .phtml .shtml .phar
Options -ExecCGI
# END Security by CleanTalk code execution protection';

    const HTACCESS_FILE = '.htaccess';

    public static $status = [
        'content_exists' => 'Content already exists',
        'failed_to_create_htaccess_file' => 'Failed to create .htaccess file. Please check folder permissions.',
        'failed_to_get_htaccess_file' => 'Failed to get .htaccess file',
        'failed_to_delete_content_on_deactivate' => 'Failed to delete content on deactivate',
        'failed_to_delete_empty_file_on_deactivate' => 'Failed to delete empty file on deactivate',
        'failed_to_add_content_to_htaccess_file' => 'Failed to add content to .htaccess file',
        'deactivated' => 'Deactivated',
        'not_apache' => 'This feature is optimized for Apache servers and may not protect your uploads folder on Nginx.',
    ];

    /*
    * handle
    */
    public static function handle()
    {
        global $spbc;

        self::updateStatuses(null, true);

        if (
            !$spbc->key_is_ok ||
            !$spbc->settings['wp__upload_dir_prevent_php_execution']
        ) {
            self::deactivate();
            return true;
        }

        if (!self::isApache()) {
            self::updateStatuses('not_apache');
        }

        $dir_path = wp_upload_dir()['basedir'] . DIRECTORY_SEPARATOR;
        $htaccess_file = $dir_path . self::HTACCESS_FILE;

        try {
            if (!file_exists($htaccess_file)) {
                $result = self::createHtaccessFile($htaccess_file);
                if ($result !== true) {
                    return ['error' => 'failed_to_create_htaccess_file'];
                }
            } elseif (self::isContentAlreadyExists($htaccess_file)) {
                self::updateStatuses('content_exists');
                return true;
            } else {
                $result = self::addContentToHtaccessFile($htaccess_file);
                if ($result !== true) {
                    return ['error' => 'failed_to_add_content_to_htaccess_file'];
                }
            }
            return true;
        } catch (\Exception $e) {
            self::log($e->getMessage());
            return ['error' => $e->getMessage()];
        }
    }

    /*
    * deactivate
    */
    public static function deactivate()
    {
        global $spbc;

        $htaccess_file = wp_upload_dir()['basedir'] . DIRECTORY_SEPARATOR . self::HTACCESS_FILE;

        try {
            if (file_exists($htaccess_file)) {
                self::deleteContentOnDeactivate($htaccess_file);
            }
        } catch (\Exception $e) {
            self::log($e->getMessage());
            return false;
        }

        self::updateStatuses('deactivated');
    }

    /*
    * delete content on deactivate
    */
    private static function deleteContentOnDeactivate($htaccess_file)
    {
        try {
            $htaccess_content = file_get_contents($htaccess_file);
            if (!is_string($htaccess_content)) {
                throw new \Exception('failed_to_get_htaccess_file');
            }

            $htaccess_content = str_replace(self::HTACCESS_CONTENT, '', $htaccess_content);
            $result = file_put_contents($htaccess_file, $htaccess_content);

            if ($result === false) {
                throw new \Exception('failed_to_delete_content_on_deactivate');
            }

            if (filesize($htaccess_file) === 0) {
                if (!unlink($htaccess_file)) {
                    throw new \Exception('failed_to_delete_empty_file_on_deactivate');
                }
            }
            return true;
        } catch (\Exception $e) {
            self::updateStatuses($e->getMessage());
            return false;
        }
    }

    /*
    * add content to .htaccess file
    */
    private static function addContentToHtaccessFile($htaccess_file)
    {
        try {
            $htaccess_content = file_get_contents($htaccess_file);
            if (!is_string($htaccess_content)) {
                throw new \Exception('failed_to_get_htaccess_file');
            }

            $htaccess_content .= "\n" . self::HTACCESS_CONTENT;
            $result = file_put_contents($htaccess_file, $htaccess_content);
            if ($result === false) {
                throw new \Exception('failed_to_add_content_to_htaccess_file');
            }
            return true;
        } catch (\Exception $e) {
            self::updateStatuses($e->getMessage());
            return false;
        }
    }

    /*
    * check if content already exists in .htaccess file
    */
    private static function isContentAlreadyExists($htaccess_file)
    {
        try {
            $htaccess_content = file_get_contents($htaccess_file);
            if (!is_string($htaccess_content)) {
                throw new \Exception('failed_to_get_htaccess_file');
            }

            if (strpos($htaccess_content, self::HTACCESS_CONTENT) === false) {
                return false;
            }

            return true;
        } catch (\Exception $e) {
            self::updateStatuses($e->getMessage());
            return false;
        }
    }

    /*
    * create .htaccess file
    */
    private static function createHtaccessFile($htaccess_file)
    {
        try {
            $result = @file_put_contents($htaccess_file, self::HTACCESS_CONTENT);
            if ($result === false) {
                throw new \Exception('failed_to_create_htaccess_file');
            }
        } catch (\Exception $e) {
            self::updateStatuses($e->getMessage());
            return false;
        }
    }

    private static function log($message)
    {
        global $spbc;

        $spbc->data['upload_dir_prevent_php_execution_status'] = $message;
        $spbc->save('data');
    }

    /**
     * Checks if the server is Apache
     * @return bool
     */
    private static function isApache()
    {
        return isset($_SERVER['SERVER_SOFTWARE']) && stripos($_SERVER['SERVER_SOFTWARE'], 'apache') !== false;
    }

    /**
     * Returns description for the uploads PHP execution prevention setting,
     * including warning messages for specific statuses.
     *
     * @param string|null $statuses
     * @return string
     */
    public static function getDescription($statuses = null)
    {
        $description = __('Prohibits the execution of PHP files in the uploads directory (wp-content/uploads/) by creating a security file .htaccess is only in the root of the folder. This reduces the risk of malicious scripts being uploaded and executed. This option is enabled by default.', 'security-malware-firewall');
        $show_statuses = ['failed_to_create_htaccess_file', 'not_apache'];

        if (is_array($statuses) && count($statuses)) {
            $description .= '<ul class="spbc_settings_status_list">';
            foreach ($show_statuses as $status_key) {
                if (in_array($status_key, $statuses, true) && isset(self::$status[$status_key])) {
                    $description .= '<li class="spbc_settings_description spbc_settings_description_status_php_execution">'
                        . esc_html(__(
                            self::$status[$status_key],
                            'security-malware-firewall'
                        ))
                        . '</li>';
                }
            }
            $description .= '</ul>';
        }
        return $description;
    }

    /**
     * Add or clear statuses for upload_dir_prevent_php_execution
     *
     * @param string|null $status_key Key status from self::STATUS
     * @param bool $clear Clear all statuses (true) or add new one (false)
     */
    public static function updateStatuses($status_key = null, $clear = false)
    {
        global $spbc;

        // Initialize status array
        if (
            empty($spbc->data['upload_dir_prevent_php_execution_status']) ||
            !is_array($spbc->data['upload_dir_prevent_php_execution_status'])
        ) {
            $spbc->data['upload_dir_prevent_php_execution_status'] = [];
        }

        if ($clear) {
            $spbc->data['upload_dir_prevent_php_execution_status'] = [];
        } elseif ($status_key && isset(self::$status[$status_key])) {
            if (!in_array($status_key, $spbc->data['upload_dir_prevent_php_execution_status'], true)) {
                $spbc->data['upload_dir_prevent_php_execution_status'][] = $status_key;
            }
        }

        $spbc->save('data');
    }
}
