<?php

namespace MatthiasWeb\RealMediaLibrary\Vendor\MatthiasWeb\Utils;

use Exception;
use WP_REST_Response;
// @codeCoverageIgnoreStart
\defined('ABSPATH') or die('No script kiddies please!');
// Avoid direct file request
// @codeCoverageIgnoreEnd
/**
 * Create a base REST Service needed for boilerplate development. Please do not remove it.
 */
class Service
{
    private $core;
    const NOTICE_CORRUPT_REST_API_ID = 'notice-corrupt-rest-api';
    const SECURITY_PLUGINS_BLOCKING_REST_API = ['better-wp-security', 'all-in-one-wp-security-and-firewall', 'sucuri-scanner', 'anti-spam', 'wp-cerber', 'wp-simple-firewall', 'wp-hide-security-enhancer', 'bulletproof-security', 'disable-json-api', 'ninjafirewall', 'hide-my-wp', 'perfmatters', 'swift-performance', 'clearfy', 'password-protected', 'wp-rest-api-controller'];
    /**
     * C'tor.
     *
     * @param PluginReceiver $core
     * @codeCoverageIgnore
     */
    private function __construct($core)
    {
        $this->core = $core;
    }
    /**
     * Register endpoints.
     */
    public function rest_api_init()
    {
        $namespace = Service::getNamespace($this->getCore());
        \register_rest_route($namespace, '/plugin', ['methods' => 'GET', 'callback' => [$this, 'routePlugin'], 'permission_callback' => '__return_true']);
    }
    /**
     * Response for /plugin route.
     */
    public function routePlugin()
    {
        return new WP_REST_Response($this->getCore()->getPluginData());
    }
    /**
     * Get core instance.
     *
     * @return PluginReceiver
     * @codeCoverageIgnore
     */
    public function getCore()
    {
        return $this->core;
    }
    /**
     * Enable obfuscate for REST API routes. This is useful when an ad-blocker blocks a route.
     *
     * @param array $queryVars
     * @see https://regex101.com/r/fx1BfD/1
     */
    public function request($queryVars)
    {
        $offset = self::getObfuscateOffset();
        if (!empty($offset) && isset($queryVars['rest_route']) && \preg_match('/(.*\\/)([01])(\\w{32})[\\/](.*)$/m', $queryVars['rest_route'], $match) && $match[3] === $offset) {
            $beforeOffset = $match[1];
            $mode = $match[2];
            $afterOffset = $match[4];
            if ($mode === '1') {
                $afterOffset = self::deObfuscatePath($offset, $afterOffset);
            }
            $queryVars['rest_route'] = self::deObfuscatePath($offset, $beforeOffset) . $afterOffset;
        }
        return $queryVars;
    }
    /**
     * Show a notice for `corruptRestApi.tsx`.
     */
    public function admin_notices()
    {
        if (!isset($GLOBALS[self::NOTICE_CORRUPT_REST_API_ID])) {
            $GLOBALS[self::NOTICE_CORRUPT_REST_API_ID] = \true;
            $securityPlugins = $this->getSecurityPlugins();
            echo \sprintf('<div id="notice-corrupt-rest-api" class="hidden notice notice-warning" style="display:none;"><p>%s</p><ul style="list-style:circle;margin-left:30px;"></ul><p>%s</p><p>%s</p><textarea readonly="readonly" style="width:100%%;margin-bottom:5px;" rows="4"></textarea></div>', \sprintf(
                // translators:
                \__('<strong>An unexpected network error has occurred!</strong> One or more WordPress plugins tried to call the WordPress REST API, which failed. Most likely a <strong>security plugin%s</strong>, a web <strong>server configuration</strong> or active <strong>ad-blocker extension</strong> installed on your browser disabled the REST API. Please make sure that the following REST API namespaces are reachable to use your plugin without problems:', 'devowl-wp-utils'),
                \count($securityPlugins) > 0 ? ' (' . \join(', ', $securityPlugins) . ')' : ''
            ), \sprintf(
                // translators:
                \__('What is the WordPress REST API and how to enable it? %1$sLearn more%2$s.', 'devowl-wp-utils'),
                '<a href="' . \esc_attr(\__('https://devowl.io/knowledge-base/wordpress-rest-api-does-not-respond/', 'devowl-wp-utils')) . '" target="_blank" rel="noreferrer">',
                '</a>'
            ), \__('In the text box below you will find a technical listing of the last 10 failed network requests:', 'devowl-wp-utils'));
        }
    }
    /**
     * Get all active security plugins which can limit the WP REST API.
     *
     * @return string[]
     */
    public function getSecurityPlugins()
    {
        $result = [];
        $plugins = \get_option('active_plugins');
        // @codeCoverageIgnoreStart
        if (!\defined('PHPUNIT_FILE')) {
            require_once ABSPATH . '/wp-admin/includes/plugin.php';
        }
        // @codeCoverageIgnoreEnd
        foreach ($plugins as $pluginFile) {
            foreach (self::SECURITY_PLUGINS_BLOCKING_REST_API as $slug) {
                if (\strpos($pluginFile, $slug, 0) === 0) {
                    $result[] = \get_plugin_data(\constant('WP_PLUGIN_DIR') . '/' . $pluginFile)['Name'];
                }
            }
        }
        return $result;
    }
    /**
     * Get the wp-json URL for a defined REST service.
     *
     * @param string $instance The plugin class instance, so we can determine the slug from
     * @param string $namespace The prefix for REST service
     * @param string $endpoint The path appended to the prefix
     * @return string Example: https://wordpress.org/wp-json
     */
    public static function getUrl($instance, $namespace = null, $endpoint = '')
    {
        $path = ($namespace === null ? Service::getNamespace($instance) : $namespace) . '/' . $endpoint;
        return \rest_url($path);
    }
    /**
     * Get the default namespace of this plugin generated from the slug.
     *
     * @param mixed $instance The plugin class instance, so we can determine the slug from
     * @param string $version The version used for this namespace
     * @return string
     */
    public static function getNamespace($instance, $version = 'v1')
    {
        $slug = $instance->getPluginConstant(Constants::PLUGIN_CONST_SLUG);
        $slug = \preg_replace('/-(lite|pro)$/', '', $slug);
        return $slug . '/' . $version;
    }
    /**
     * Get the (backend) API URL for the current running environment for another container.
     *
     * @param string $serviceName E.g. commerce
     * @codeCoverageIgnore Ignored due to the fact that it depends on too much server variables
     */
    public static function getExternalContainerUrl($serviceName)
    {
        $host = 'devowl.io';
        // In our development / review environment we always have prefixed our WordPress container as `wordpress.` subdomain
        if (\defined('DEVOWL_WP_DEV') && \constant('DEVOWL_WP_DEV')) {
            $host = \parse_url(\site_url(), \PHP_URL_HOST);
            $host = \substr($host, \strlen('WordPress.'));
        }
        // Internal hook
        return \apply_filters('DevOwl/Utils/Service/ExternalContainerUrl', \sprintf('https://%s.%s', $serviceName, $host), $serviceName, $host);
    }
    /**
     * Get the offset for the obfuscate mechanism. This can be a string or a number. This allows
     * uniqueness of obfuscated strings between WordPress instances.
     *
     * @param mixed $instance The plugin class instance, so we can determine the slug from
     */
    public static function getObfuscateOffset()
    {
        $salt = Utils::getNonceSalt();
        if (empty($salt)) {
            return '';
        }
        /**
         * This filter allows to enable or disable the obfuscated `wp-json/...` pathes.
         *
         * Currently, the obfuscating is disabled by default for the following plugins:
         *
         * ### WP Cerber (https://wordpress.org/plugins/wp-cerber/)
         *
         * Technically, WP Cerber uses `$_REQUEST['rest_route']` instead of
         * https://github.com/WordPress/WordPress/blob/fe6b65c44b929ee5583f6146d92388ef43061f9a/wp-includes/rest-api.php#L390,
         * which is the default WordPress API to do so. Also, the `cerber_access_control()` function, which blocks the REST API requests,
         * is executed at `init` time, instead of https://developer.wordpress.org/reference/hooks/rest_api_init/ or even better:
         * https://developer.wordpress.org/reference/hooks/rest_authentication_errors/
         *
         * ### mqTranslate (https://wordpress.org/plugins/mqtranslate/)
         *
         * It modifies the way how the `request` hook works to rewrite translation URLs. As it is an outdated plugin, we simply deactivate
         * the obfuscation for this plugin.
         *
         * @hook DevOwl/Utils/RestObfuscatePath
         * @example <caption>Disable REST obfuscation</caption>
         * <?php
         * add_filter('DevOwl/Utils/RestObfuscatePath', '__return_false');
         * @param {boolean} $enabled
         * @since 1.13.3
         */
        $enableObfuscation = \apply_filters('DevOwl/Utils/RestObfuscatePath', !\defined('CERBER_PLUGIN_ID') && !\is_plugin_active('mqtranslate/mqtranslate.php'));
        if (!$enableObfuscation) {
            return '';
        }
        // As the salt should not be exposed to public, but the obfuscate-offset will be,
        // we take only the first x characters and hash it. It should be enough for uniqueness.
        return \md5(\substr($salt, 0, 6));
    }
    /**
     * A helper function which obfuscates a given path string with a given offset.
     *
     * @param string|number $offset
     * @param string $str
     */
    public static function deObfuscatePath($offset, $str)
    {
        try {
            $offsetEveryXCharacters = 4;
            $offsetBase64 = \strtolower(\base64_encode($offset));
            $result = \explode('/', $str);
            foreach ($result as &$part) {
                if (!empty($part)) {
                    $equalCharacters = \intval(\substr($part, -1));
                    $part = \substr_replace($part, '', -1) . \str_repeat('=', $equalCharacters);
                    $part = self::base32Decode(\strtoupper($part));
                    $part = \preg_replace_callback('/\\/(\\d+)\\//', function ($m) use($offsetBase64) {
                        return $offsetBase64[\intval($m[1])] ?? '';
                    }, $part);
                    $newPart = '';
                    for ($i = 0; $i < \strlen($part); $i += $offsetEveryXCharacters + 1) {
                        for ($j = 0; $j < $offsetEveryXCharacters; $j++) {
                            $newPart .= $part[$i + $j] ?? '';
                        }
                    }
                    $part = $newPart;
                }
            }
            return \join('/', $result);
        } catch (Exception $e) {
            return $str;
        }
    }
    /**
     * Decode a given base32-decoded string.
     *
     * @param string $str
     */
    private static function base32Decode($str)
    {
        // Define the base32 decoding table
        // prettier-ignore
        $base32Table = ['A' => 0, 'B' => 1, 'C' => 2, 'D' => 3, 'E' => 4, 'F' => 5, 'G' => 6, 'H' => 7, 'I' => 8, 'J' => 9, 'K' => 10, 'L' => 11, 'M' => 12, 'N' => 13, 'O' => 14, 'P' => 15, 'Q' => 16, 'R' => 17, 'S' => 18, 'T' => 19, 'U' => 20, 'V' => 21, 'W' => 22, 'X' => 23, 'Y' => 24, 'Z' => 25, '2' => 26, '3' => 27, '4' => 28, '5' => 29, '6' => 30, '7' => 31];
        $str = \rtrim($str, '=');
        $bits = 0;
        $value = 0;
        $output = '';
        for ($i = 0; $i < \strlen($str); $i++) {
            $char = $str[$i];
            $charValue = $base32Table[$char];
            $value = $value << 5 | $charValue;
            $bits += 5;
            if ($bits >= 8) {
                $output .= \chr($value >> $bits - 8 & 0xff);
                $bits -= 8;
            }
        }
        return \base64_decode($output);
    }
    /**
     * Get a new instance of Service.
     *
     * @param PluginReceiver $core
     * @return Service
     * @codeCoverageIgnore Instance getter
     */
    public static function instance($core)
    {
        return new Service($core);
    }
}
