<?php

/**
 * @version 1.4.7
 */

/*
Plugin Name: Cryptocurrency Price Widget
Plugin URI: https://co-in.io/crypto-price-widget/
Description: Add live cryptocurrency prices with a Gutenberg block or shortcode. No API key required, free setup, and fast browser-side rendering.
Version: 1.4.7
Requires at least: 6.3
Requires PHP: 5.4
Author: CurrencyRate.today
Author URI: https://currencyrate.today/
License: GPLv2 or later
Text Domain: cryptocurrency-price-widget
Domain Path: /languages
*/

// Exit if accessed directly.
if (!defined('ABSPATH')) {
    exit;
} // Exit if accessed directly

define('CRCPW_PATH', plugin_dir_path(__FILE__));
define('CRCPW_URL', plugin_dir_url(__FILE__));
define('CRCPW_VERSION', '1.4.7');
define('CRCPW_PLUGIN_SLUG', 'cryptocurrency-price-widget');
define('CRCPW_CRYPTO_ASSETS_URL', 'https://co-in.io/data/crypto-assets.json');
define('CRCPW_CRYPTO_ASSETS_TRANSIENT', 'crcpw_crypto_assets_v3');

class CRCPW_cryptocurrency_price_widget
{
    protected static $_instance = null;

    private $_flags;
    private $_coins;
    private $_coin_meta;

    public static function get_instance()
    {
        if (!self::$_instance) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }

    public function __construct()
    {
        $this->_flags = require CRCPW_PATH.'data/flags.php';

        $this->CRCPW_maybe_flush_assets_cache();
        $this->_coin_meta = null;
        $this->_coins = null;

        add_shortcode(CRCPW_PLUGIN_SLUG, [$this, 'CRCPW_shortcode']);
        add_filter('widget_custom_html_content', [$this, 'CRCPW_render_shortcode_in_custom_html_widget'], 11);
        add_action('init', [$this, 'CRCPW_register_block']);
        add_action('wp_enqueue_scripts', [$this, 'CRCPW_enqueue_frontend_styles']);
        add_action('admin_menu', [$this, 'CRCPW_add_plugin_page']);
        add_action('admin_enqueue_scripts', [$this, 'CRCPW_admin_settins_scripts']);
        add_action('admin_enqueue_scripts', [$this, 'CRCPW_admin_settins_styles']);
        add_action('wp_ajax_CRCPW_assets_data', [$this, 'CRCPW_assets_data_callback']);
        add_action('wp_ajax_CRCPW_block_preview', [$this, 'CRCPW_block_preview_callback']);
        add_filter('plugin_action_links', [$this, 'CRCPW_plugin_action_links'], 10, 2);
    }

    public function CRCPW_shortcode($attr)
    {
        return $this->CRCPW_render_widget($attr);
    }

    public function CRCPW_render_shortcode_in_custom_html_widget($content)
    {
        if (!is_string($content) || '' === $content) {
            return $content;
        }

        if (false === strpos($content, '['.CRCPW_PLUGIN_SLUG)) {
            return $content;
        }

        if (!shortcode_exists(CRCPW_PLUGIN_SLUG)) {
            return $content;
        }

        if (!function_exists('get_shortcode_regex') || !function_exists('do_shortcode_tag')) {
            return $content;
        }

        $result = preg_replace_callback(
            '/'.get_shortcode_regex([CRCPW_PLUGIN_SLUG]).'/',
            'do_shortcode_tag',
            $content
        );

        return is_string($result) ? $result : $content;
    }

    public function CRCPW_render_block($attributes)
    {
        return $this->CRCPW_render_widget($attributes);
    }

    public function CRCPW_block_preview_callback()
    {
        if (!isset($_GET['nonce']) || !check_ajax_referer('crcpw_nonce', 'nonce', false)) {
            wp_die(esc_html__('Invalid nonce.', 'cryptocurrency-price-widget'), '', ['response' => 403]);
        }

        if (!current_user_can('edit_posts')) {
            wp_die(esc_html__('Sorry, you are not allowed to preview this widget.', 'cryptocurrency-price-widget'), '', ['response' => 403]);
        }

        $attributes = [
            'items' => $this->CRCPW_get_preview_request_value('items', 'BTC,ETH,LTC,XMR,DASH'),
            'base' => $this->CRCPW_get_preview_request_value('base', 'USD,EUR,CNY,GBP'),
            'backgroundColor' => $this->CRCPW_get_preview_request_value('backgroundColor', 'fff'),
            'streaming' => $this->CRCPW_get_preview_request_value('streaming', '1'),
            'striped' => $this->CRCPW_get_preview_request_value('striped', '0'),
            'rounded' => $this->CRCPW_get_preview_request_value('rounded', '1'),
            'boxShadow' => $this->CRCPW_get_preview_request_value('boxShadow', '1'),
            'border' => $this->CRCPW_get_preview_request_value('border', '1'),
            'signature' => $this->CRCPW_get_preview_request_value('signature', '1'),
        ];

        nocache_headers();
        header('Content-Type: text/html; charset='.get_option('blog_charset'));

        echo '<!doctype html><html><head><meta charset="'.esc_attr(get_bloginfo('charset')).'"><meta name="viewport" content="width=device-width, initial-scale=1"><style>html,body{margin:0;padding:0;background:transparent;}body{box-sizing:border-box;min-width:220px;padding:12px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;}.crcpw-preview-shell{max-width:760px;}.crcpw-attribution{display:flex;align-items:center;justify-content:flex-end;gap:4px;margin-top:8px;color:#646970;font-size:11px;line-height:1.4;}.crcpw-attribution__label{color:#8c8f94;}.crcpw-attribution__link{color:#3c434a;font-weight:600;text-decoration:none;}.crcpw-attribution__link:hover,.crcpw-attribution__link:focus{color:#135e96;text-decoration:underline;}</style></head><body><div class="crcpw-preview-shell">';
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Outputs the plugin's own sanitized widget loader markup in an authenticated editor preview iframe.
        echo $this->CRCPW_render_widget($attributes);
        echo '</div><script>(function(){var timer;function height(){var shell=document.querySelector(".crcpw-preview-shell"),body=document.body,doc=document.documentElement,styles=body?window.getComputedStyle(body):null,paddingBottom=styles?parseFloat(styles.paddingBottom)||0:0,rect;if(shell&&typeof shell.getBoundingClientRect==="function"){rect=shell.getBoundingClientRect();return Math.ceil(rect.top+rect.height+paddingBottom);}return Math.ceil(Math.max(body?body.scrollHeight:0,body?body.offsetHeight:0,doc?doc.scrollHeight:0));}function send(){var message=JSON.stringify({type:"CRCPW_preview_height",h:height()});if(window.parent&&window.parent!==window){window.parent.postMessage(message,window.location.origin);}if(window.top&&window.top!==window&&window.top!==window.parent){window.top.postMessage(message,window.location.origin);}}function queue(){window.clearTimeout(timer);timer=window.setTimeout(send,80);}window.addEventListener("load",function(){send();window.setTimeout(send,400);window.setTimeout(send,1200);window.setTimeout(send,2500);});if("ResizeObserver" in window){new ResizeObserver(queue).observe(document.body);}if("MutationObserver" in window){new MutationObserver(queue).observe(document.body,{childList:true,subtree:true,attributes:true});}window.setInterval(send,3000);queue();})();</script></body></html>';

        wp_die();
    }

    private function CRCPW_render_widget($attr)
    {
        $provided_attr = array_change_key_case((array) $attr, CASE_LOWER);

        $attr = shortcode_atts(
            [
                'items' => 'BTC,ETH,LTC,XMR,DASH',
                'base' => 'USD,EUR,CNY,GBP',
                'backgroundcolor' => 'fff',
                'streaming' => '1',
                'striped' => '0',
                'rounded' => '1',
                'boxshadow' => '1',
                'border' => '1',
                'signature' => '1',
            ],
            $provided_attr,
            CRCPW_PLUGIN_SLUG
        );

        if (!array_key_exists('striped', $provided_attr)) {
            unset($attr['striped']);
        }

        $attr['items'] = implode(',', $this->CRCPW_normalize_symbol_list($attr['items'], ['BTC', 'ETH', 'LTC', 'XMR', 'DASH']));
        $attr['base'] = implode(',', $this->CRCPW_normalize_symbol_list($attr['base'], ['USD', 'EUR', 'CNY', 'GBP']));
        $attr['backgroundcolor'] = $this->CRCPW_sanitize_hex_value($attr['backgroundcolor'], 'fff');
        $attr['streaming'] = $this->CRCPW_normalize_bool($attr['streaming'], '1');
        $attr['rounded'] = $this->CRCPW_normalize_bool($attr['rounded'], '1');
        $attr['boxshadow'] = $this->CRCPW_normalize_bool($attr['boxshadow'], '1');
        $attr['border'] = $this->CRCPW_normalize_bool($attr['border'], '1');
        $attr['signature'] = $this->CRCPW_normalize_bool($attr['signature'], '1');

        if (isset($attr['striped'])) {
            $attr['striped'] = $this->CRCPW_normalize_bool($attr['striped'], '0');
        }

        $config = [];
        $signature = ('1' === (string) $attr['signature']);
        $items = (string) $attr['items'];
        unset($attr['signature']);

        foreach ($attr as $key => $value) {
            if ('boxshadow' == $key) {
                $key = 'boxShadow';
            } elseif ('backgroundcolor' == $key) {
                $key = 'backgroundColor';
            }
            $config[$key] = (string) $value;
        }

        $lang = $this->CRCPW_get_lang();
        $script_url = 'https://co-in.io/'.('en' != $lang ? $lang.'/' : '').'widget/pricelist.js?items='.rawurlencode($items);
        $json_config = wp_json_encode($config);
        $json_script_url = wp_json_encode(esc_url_raw($script_url));

        $output = '<script>!function(){var e=document.getElementsByTagName("script"),t=e[e.length-1],n=document.createElement("script"),c='.$json_config.';function r(){if("undefined"==typeof crCryptocoinPriceWidget||"function"!=typeof crCryptocoinPriceWidget.init)return;var e=crCryptocoinPriceWidget.init(c);t.parentNode.insertBefore(e,t)}n.src='.$json_script_url.',n.async=!0,n.readyState?n.onreadystatechange=function(){"loaded"!=n.readyState&&"complete"!=n.readyState||(n.onreadystatechange=null,r())}:n.onload=function(){r()},t.parentNode.insertBefore(n,null)}();</script>'.(($signature) ? $this->CRCPW_signature_markup() : '');

        return $output;
    }

    private function CRCPW_signature_markup()
    {
        return '<div class="crcpw-attribution"><span class="crcpw-attribution__label">'.esc_html__('Source:', 'cryptocurrency-price-widget').'</span> <a class="crcpw-attribution__link" href="https://currencyrate.today/" target="_blank" rel="noopener noreferrer">CurrencyRate.Today</a></div>';
    }

    private function CRCPW_normalize_symbol_list($value, $default)
    {
        $value = is_array($value) ? $value : explode(',', (string) $value);
        $symbols = [];

        foreach ($value as $symbol) {
            $symbol = $this->CRCPW_normalize_asset_symbol($symbol);

            if ('' !== $symbol) {
                $symbols[] = $symbol;
            }
        }

        return empty($symbols) ? $default : array_values($symbols);
    }

    private function CRCPW_sanitize_hex_value($value, $default)
    {
        $value = strtolower(trim((string) $value));
        $value = ltrim($value, '#');
        $value = preg_replace('/[^a-f0-9]/', '', $value);

        return preg_match('/^([a-f0-9]{3}|[a-f0-9]{6})$/', $value) ? $value : $default;
    }

    private function CRCPW_normalize_bool($value, $default)
    {
        if (is_bool($value)) {
            return $value ? '1' : '0';
        }

        $value = (string) $value;

        if ('true' === strtolower($value)) {
            return '1';
        }

        if ('false' === strtolower($value)) {
            return '0';
        }

        return in_array($value, ['0', '1'], true) ? $value : $default;
    }

    private function CRCPW_normalize_positive_int($value, $default)
    {
        $value = preg_replace('/[^0-9]/', '', (string) $value);

        return '' === $value ? $default : (string) max(1, (int) $value);
    }

    private function CRCPW_get_preview_request_value($key, $default)
    {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The authenticated preview callback verifies the nonce before calling this helper.
        if (!isset($_GET[$key])) {
            return $default;
        }

        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- The authenticated preview callback verifies the nonce before calling this helper.
        return sanitize_text_field(wp_unslash($_GET[$key]));
    }

    private function CRCPW_normalize_asset_symbol($symbol)
    {
        $symbol = trim((string) $symbol);
        $symbol = function_exists('mb_strtoupper') ? mb_strtoupper($symbol, 'UTF-8') : strtoupper($symbol);
        $normalized = preg_replace('/[^\p{L}\p{N}._$-]/u', '', $symbol);

        if (null === $normalized) {
            $normalized = preg_replace('/[^A-Z0-9._$-]/', '', strtoupper($symbol));
        }

        return (string) $normalized;
    }

    public function CRCPW_plugin_action_links($links, $file)
    {
        if ($file == plugin_basename(CRCPW_PATH.'/widget_init.php')) {
            $links[] = '<a href="'.admin_url('admin.php?page='.CRCPW_PLUGIN_SLUG).'">'.__('Settings', 'cryptocurrency-price-widget').'</a>';
        }

        return $links;
    }

    /**
     * Add options page.
     */
    public function CRCPW_add_plugin_page()
    {
        add_menu_page(
            'Settings Admin',
            'CR Crypto Price',
            'manage_options',
            CRCPW_PLUGIN_SLUG,
            [$this, 'CRCPW_admin_settins_page'],
            'data:image/svg+xml;base64,'.base64_encode('<svg height="512" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m407.55338 3.81206c-47.16019 8.43203-78.32965 43.80881-78.58404 89.19436-.11049 19.64007 8.69559 70.63414 13.10248 75.86644 7.01634 8.33316 18.70681 7.67447 24.39209-1.37516 2.52079-4.01378 2.29595-8.5386-1.38245-27.84611-8.40265-44.10541-7.39921-62.03262 4.46471-79.75825 25.04092-37.41705 82.16352-34.8696 101.4395 4.52353 15.63098 31.94721 6.58336 100.50897-19.83745 150.33204-7.65104 14.42703-4.86557 25.03544 7.26174 27.65993 9.15041 1.97864 14.0391-2.36513 22.50085-19.99959 27.49362-57.29209 35.665-125.82176 19.55479-163.99765-15.55646-36.86365-56.74229-61.06577-92.91223-54.59954m-162.10688 80.57872c-6.07329.38263-11.94487 1.20568-13.04852 1.83098-6.67715 3.77624-224.13873 223.71779-226.39614 228.97705-3.56663 8.30877-3.41245 20.4156.3636 28.58827 3.67455 7.95438 153.4537 158.17471 161.42594 161.90345 10.21937 4.77905 22.5985 4.56847 32.04827-.54442 3.10153-1.67819 40.75285-38.65996 116.59511-114.51999l112.01476-112.04186 1.2347-7.52296c4.10625-24.9982-6.28014-159.08764-13.20398-170.46516-3.04371-5.00118-26.21267-11.10018-42.16742-11.10018-1.37603 0-1.05097 10.67389.71564 23.51647l1.60473 11.65744 5.46301 5.46085c26.87049 26.8523 7.20135 71.3352-30.74033 69.5222-29.22683-1.39571-47.11394-31.55431-34.73866-58.56967l2.50666-5.46984-2.06597-13.77989c-1.13577-7.57945-2.13021-18.83628-2.20859-25.01618l-.1439-11.23757-3.51267-.5059c-6.23389-.90008-54.35897-1.39956-65.74623-.68309m54.49002 92.65988 4.21803 4.29498-7.02919 6.97212-7.02919 6.97084 18.80703 18.81446 18.80703 18.81446-5.47714 5.54175-5.47585 5.54175-18.83401-18.80162-18.83401-18.8029-6.81719 6.76026-6.81848 6.76155-4.457-4.53894-4.457-4.53766 19.03573-19.04429c10.46991-10.47359 19.28498-19.04301 19.58948-19.04301s2.45142 1.9337 4.77178 4.29626m-44.45438 50.97995c18.23144 4.58773 20.95523 23.02341 5.88828 39.84766l-5.68399 6.3481 7.68444 7.72455 7.68316 7.72455-5.30754 5.23744c-2.91909 2.8813-5.74053 5.23872-6.27115 5.23872-1.34905 0-45.64668-44.37377-45.64668-45.72582 0-1.70258 21.22632-21.99107 25.13728-24.02621 3.27755-1.70644 7.88744-3.1458 10.80396-3.37307.72206-.0565 3.29168.39547 5.71226 1.00409m-16.88624 18.90819-5.19448 4.96908 6.52426 6.60747 6.52426 6.60747 5.27029-5.46984c6.59749-6.84757 6.96623-9.96769 1.72164-14.56955-5.27671-4.62882-8.48488-4.22821-14.84596 1.85538m-24.12357 37.65074 7.26431 21.91917 8.09815 8.13414 8.09686 8.13414-5.53367 5.46214-5.53367 5.46085-8.29729-8.2497-8.29729-8.2497-21.55781-7.14675c-25.94029-8.60023-24.29702-7.24176-17.22029-14.23956l5.82661-5.76131 13.4558 5.56101c7.40178 3.05849 13.77572 5.44416 14.16502 5.30035s-1.82186-6.48677-4.91182-14.09704l-5.61975-13.8351 5.41804-5.48268c7.08572-7.16986 6.30456-8.0815 14.64682 17.09004m-47.13321 31.67244c7.97995 3.00071 13.6755 11.73063 13.20012 20.23199l-.33662 6.01811 13.80142 4.43622c15.95218 5.1283 15.39586 4.36303 8.27931 11.38523l-5.52211 5.45058-11.33587-3.72874c-13.28621-4.37074-13.43011-4.38358-16.45069-1.54722-3.31352 3.10985-3.04243 3.83146 3.95079 10.54421 10.19239 9.78408 9.90844 8.64132 3.69896 14.92522l-5.41418 5.47883-22.85675-22.82696c-12.57057-12.55367-22.85546-23.32258-22.85546-23.92863 0-.60733 4.9478-5.92566 10.99668-11.82051 14.51191-14.14326 22.29786-17.83348 30.8444-14.61834m-17.3038 19.13931-5.4553 5.15655 5.72896 5.87815c6.84289 7.02091 6.77994 7.01193 12.09647 1.59986 5.99877-6.10927 6.49471-9.72502 1.95805-14.25882-5.00305-4.9999-7.63562-4.70201-14.32819 1.62426m-25.25805 27.33893c7.75254 3.35381 7.85018 3.88282 1.85912 9.9754l-5.38592 5.47498-3.37262-1.98892c-8.13412-4.79574-18.72095 4.90103-15.01813 13.75678 3.47284 8.30748 17.28196 21.12309 23.92057 22.20036 7.63305 1.23778 14.88965-9.44382 10.94914-16.11677l-1.99017-3.37178 5.19962-5.13087c6.3007-6.2197 5.93068-6.19915 9.26861-.5059 9.20052 15.6892-5.78678 38.7614-25.17583 38.75883-16.3826-.00385-36.67358-20.37965-36.67358-36.8277 0-18.22767 20.60705-33.06686 36.41919-26.22442" fill="#000000" fill-rule="evenodd"/></svg>')
        );
    }

    private function CRCPW_is_settings_admin_screen($hook_suffix)
    {
        return current_user_can('manage_options')
            && 'toplevel_page_'.CRCPW_PLUGIN_SLUG === (string) $hook_suffix;
    }

    public function CRCPW_admin_settins_scripts($hook_suffix = '')
    {
        if (!$this->CRCPW_is_settings_admin_screen($hook_suffix)) {
            return;
        }

        wp_enqueue_script('jquery-ui-autocomplete');
        wp_register_script('CRCPW-admin-token-picker', CRCPW_URL.'assets/admin/js/token-picker.js', ['jquery', 'jquery-ui-autocomplete'], CRCPW_VERSION, true);
        wp_register_script('CRCPW-admin-settings', CRCPW_URL.'assets/admin/js/settings.js', ['jquery', 'CRCPW-admin-token-picker', 'wp-color-picker'], CRCPW_VERSION, true);

        wp_enqueue_script('CRCPW-admin-token-picker');
        wp_enqueue_script('CRCPW-admin-settings');
        wp_enqueue_script('wp-color-picker');
    }

    public function CRCPW_admin_settins_styles($hook_suffix = '')
    {
        if (!$this->CRCPW_is_settings_admin_screen($hook_suffix)) {
            return;
        }

        wp_register_style('CRCPW-admin-style', CRCPW_URL.'assets/admin/css/style.css', null, CRCPW_VERSION, 'all');
        wp_enqueue_style('CRCPW-admin-style');
        wp_enqueue_style('wp-color-picker');
    }

    public function CRCPW_enqueue_frontend_styles()
    {
        wp_enqueue_style('CRCPW-frontend-style', CRCPW_URL.'assets/css/frontend.css', [], CRCPW_VERSION, 'all');
    }

    public function CRCPW_admin_settins_page()
    {
        require_once CRCPW_PATH.'includes/crcpw-admin-settings.php';
    }

    public function CRCPW_register_block()
    {
        if (!function_exists('register_block_type')) {
            return;
        }

        wp_register_script(
            'CRCPW-price-widget-block-editor',
            CRCPW_URL.'blocks/price-widget/editor.js',
            ['wp-blocks', 'wp-element', 'wp-components', 'wp-block-editor', 'wp-i18n', 'wp-data'],
            CRCPW_VERSION,
            true
        );

        wp_localize_script(
            'CRCPW-price-widget-block-editor',
            'CRCPWBlockSettings',
            [
                'ajaxUrl' => admin_url('admin-ajax.php'),
                'nonce' => wp_create_nonce('crcpw_nonce'),
                'previewUrlBase' => esc_url_raw(admin_url('admin-ajax.php')),
                'settingsUrl' => admin_url('admin.php?page='.CRCPW_PLUGIN_SLUG),
                'wordpressPluginUrl' => 'https://wordpress.org/plugins/cryptocurrency-price-widget/',
                'wordpressSupportUrl' => 'https://wordpress.org/support/plugin/cryptocurrency-price-widget/',
                'wordpressReviewUrl' => 'https://wordpress.org/support/plugin/cryptocurrency-price-widget/reviews/#new-post',
                'currencyRateUrl' => 'https://currencyrate.today/',
                'coInUrl' => 'https://co-in.io/',
                'fiatIconBase' => esc_url_raw(CRCPW_URL.'assets/fiat/128/'),
                'cryptoIconBase' => esc_url_raw('https://www.cryptocompare.com'),
                'defaultBase' => ['USD', 'EUR', 'CNY', 'GBP'],
                'defaultItems' => ['BTC', 'ETH', 'LTC', 'XMR', 'DASH'],
                'defaultBaseMeta' => $this->CRCPW_get_default_flag_meta(['USD', 'EUR', 'CNY', 'GBP']),
                'defaultItemsMeta' => $this->CRCPW_get_default_coin_meta(['BTC', 'ETH', 'LTC', 'XMR', 'DASH']),
            ]
        );

        wp_register_style(
            'CRCPW-price-widget-block-editor',
            CRCPW_URL.'blocks/price-widget/editor.css',
            ['wp-edit-blocks'],
            CRCPW_VERSION
        );

        register_block_type(
            CRCPW_PATH.'blocks/price-widget',
            [
                'editor_script' => 'CRCPW-price-widget-block-editor',
                'editor_style' => 'CRCPW-price-widget-block-editor',
                'render_callback' => [$this, 'CRCPW_render_block'],
            ]
        );
    }

    private function CRCPW_maybe_flush_assets_cache()
    {
        $stored_version = get_option('crcpw_crypto_assets_version');

        if (CRCPW_VERSION === $stored_version) {
            return;
        }

        delete_transient(CRCPW_CRYPTO_ASSETS_TRANSIENT);
        update_option('crcpw_crypto_assets_version', CRCPW_VERSION, false);
    }

    private function CRCPW_get_coin_meta()
    {
        if (is_array($this->_coin_meta)) {
            return $this->_coin_meta;
        }

        $this->_coin_meta = $this->CRCPW_build_coin_meta($this->CRCPW_get_crypto_assets_payload());
        $this->_coins = $this->CRCPW_build_coin_icon_map($this->_coin_meta);

        return $this->_coin_meta;
    }

    private function CRCPW_get_crypto_assets_payload()
    {
        $cached = get_transient(CRCPW_CRYPTO_ASSETS_TRANSIENT);

        if ($this->CRCPW_is_valid_crypto_assets_payload($cached)) {
            return $cached;
        }

        $remote = $this->CRCPW_fetch_remote_crypto_assets();

        if ($this->CRCPW_is_valid_crypto_assets_payload($remote)) {
            $remote = $this->CRCPW_normalize_crypto_assets_payload($remote);
            set_transient(CRCPW_CRYPTO_ASSETS_TRANSIENT, $remote, DAY_IN_SECONDS);

            return $remote;
        }

        $fallback = $this->CRCPW_get_fallback_crypto_assets();

        if ($this->CRCPW_is_valid_crypto_assets_payload($fallback)) {
            return $this->CRCPW_normalize_crypto_assets_payload($fallback);
        }

        return [];
    }

    private function CRCPW_fetch_remote_crypto_assets()
    {
        $response = wp_remote_get(
            CRCPW_CRYPTO_ASSETS_URL,
            [
                'timeout' => 15,
            ]
        );

        if (is_wp_error($response)) {
            return [];
        }

        if (200 !== (int) wp_remote_retrieve_response_code($response)) {
            return [];
        }

        $data = json_decode(wp_remote_retrieve_body($response), true);

        return is_array($data) ? $data : [];
    }

    private function CRCPW_get_fallback_crypto_assets()
    {
        $file = CRCPW_PATH.'assets/crypto-assets.json';

        if (!file_exists($file)) {
            return [];
        }

        $data = json_decode((string) file_get_contents($file), true);

        return is_array($data) ? $data : [];
    }

    private function CRCPW_is_valid_crypto_assets_payload($data)
    {
        return is_array($data)
            && 3 === (int) ($data['version'] ?? 0)
            && 'CoinLore' === ($data['source']['runtime_provider'] ?? '')
            && !empty($data['symbols'])
            && (isset($data['assets_by_symbol']) || isset($data['assets']));
    }

    private function CRCPW_normalize_crypto_assets_payload($data)
    {
        $assets = [];

        if (!empty($data['assets_by_symbol']) && is_array($data['assets_by_symbol'])) {
            foreach ($data['assets_by_symbol'] as $symbol => $asset) {
                $asset = $this->CRCPW_normalize_crypto_asset_row($symbol, $asset);

                if (!empty($asset)) {
                    $assets[$asset['symbol']] = $asset;
                }
            }
        }

        if (empty($assets) && !empty($data['assets']) && is_array($data['assets'])) {
            foreach ($data['assets'] as $asset) {
                $symbol = isset($asset['symbol']) ? $asset['symbol'] : '';
                $asset = $this->CRCPW_normalize_crypto_asset_row($symbol, $asset);

                if (!empty($asset)) {
                    $assets[$asset['symbol']] = $asset;
                }
            }
        }

        return [
            'version' => 3,
            'source' => [
                'runtime_provider' => 'CoinLore',
            ],
            'asset_count' => count($assets),
            'duplicate_symbol_count' => (int) ($data['duplicate_symbol_count'] ?? 0),
            'symbols' => array_keys($assets),
            'assets_by_symbol' => $assets,
        ];
    }

    private function CRCPW_normalize_crypto_asset_row($symbol, $asset)
    {
        $symbol = $this->CRCPW_normalize_asset_symbol($symbol);

        if ('' === $symbol || !is_array($asset)) {
            return [];
        }

        $coinlore = isset($asset['providers']['coinlore']) && is_array($asset['providers']['coinlore'])
            ? $asset['providers']['coinlore']
            : [];

        return [
            'symbol' => $symbol,
            'name' => (string) ($asset['name'] ?? ($coinlore['name'] ?? '')),
            'icon' => [
                'path' => (string) ($asset['icon']['path'] ?? ''),
                'url_64' => (string) ($asset['icon']['url_64'] ?? ''),
            ],
            'providers' => [
                'coinlore' => [
                    'id' => (string) ($coinlore['id'] ?? ''),
                    'name' => (string) ($coinlore['name'] ?? ($asset['name'] ?? '')),
                ],
            ],
        ];
    }

    private function CRCPW_build_coin_meta($data)
    {
        $assets = [];

        if (!empty($data['assets_by_symbol']) && is_array($data['assets_by_symbol'])) {
            foreach ($data['assets_by_symbol'] as $symbol => $asset) {
                $meta = $this->CRCPW_build_coin_meta_row($symbol, $asset);

                if (!empty($meta)) {
                    $assets[$meta['symbol']] = $meta;
                }
            }
        }

        if (empty($assets) && !empty($data['assets']) && is_array($data['assets'])) {
            foreach ($data['assets'] as $asset) {
                $symbol = isset($asset['symbol']) ? $asset['symbol'] : '';
                $meta = $this->CRCPW_build_coin_meta_row($symbol, $asset);

                if (!empty($meta)) {
                    $assets[$meta['symbol']] = $meta;
                }
            }
        }

        return $assets;
    }

    private function CRCPW_build_coin_meta_row($symbol, $asset)
    {
        $symbol = $this->CRCPW_normalize_asset_symbol($symbol);

        if ('' === $symbol || !is_array($asset)) {
            return [];
        }

        $icon_path = (string) ($asset['icon']['path'] ?? '');
        $icon_url = (string) ($asset['icon']['url_64'] ?? '');

        if ('' === $icon_url && '' !== $icon_path) {
            $icon_url = 'https://www.cryptocompare.com'.$icon_path.'?width=64';
        }

        return [
            'symbol' => $symbol,
            'name' => (string) ($asset['providers']['coinlore']['name'] ?? ($asset['name'] ?? '')),
            'icon_path' => $icon_path,
            'icon_url' => $icon_url,
        ];
    }

    private function CRCPW_build_coin_icon_map($assets)
    {
        $icons = [];

        foreach ($assets as $symbol => $asset) {
            $icons[$symbol] = (string) ($asset['icon_path'] ?? '');
        }

        return $icons;
    }

    private function CRCPW_search_coin_results($query)
    {
        $results = [];
        $coin_meta = $this->CRCPW_get_coin_meta();

        foreach ($coin_meta as $symbol => $asset) {
            $name = (string) ($asset['name'] ?? '');

            if (
                !empty($query)
                && false === stripos($symbol, $query)
                && ('' === $name || false === stripos($name, $query))
            ) {
                continue;
            }

            $results[] = [
                'id' => $symbol,
                'text' => (string) ($asset['icon_path'] ?? ''),
                'fiat' => 0,
                'name' => $name,
                'icon_url' => (string) ($asset['icon_url'] ?? ''),
            ];
        }

        return $results;
    }

    private function CRCPW_search_flag_results($query)
    {
        $results = [];

        foreach ($this->_flags as $symbol => $path) {
            if (!empty($query) && false === stripos($symbol, $query)) {
                continue;
            }

            $results[] = [
                'id' => $symbol,
                'text' => $path,
                'fiat' => 1,
                'name' => '',
                'icon_url' => '',
            ];
        }

        return $results;
    }

    private function CRCPW_get_coin_option($symbol)
    {
        $symbol = $this->CRCPW_normalize_asset_symbol($symbol);
        $coin_meta = $this->CRCPW_get_coin_meta();

        return $coin_meta[$symbol] ?? [
            'symbol' => $symbol,
            'name' => '',
            'icon_path' => '',
            'icon_url' => '',
        ];
    }

    private function CRCPW_get_default_flag_meta($symbols)
    {
        $items = [];

        foreach ($symbols as $symbol) {
            $symbol = $this->CRCPW_normalize_asset_symbol($symbol);
            $items[] = [
                'id' => $symbol,
                'text' => (string) ($this->_flags[$symbol] ?? ''),
                'fiat' => 1,
                'name' => '',
                'icon_url' => '',
            ];
        }

        return $items;
    }

    private function CRCPW_get_default_coin_meta($symbols)
    {
        $items = [];
        $default_meta = [
            'BTC' => [
                'name' => 'Bitcoin',
                'icon_path' => '/media/37746251/btc.png',
                'icon_url' => 'https://www.cryptocompare.com/media/37746251/btc.png?width=64',
            ],
            'ETH' => [
                'name' => 'Ethereum',
                'icon_path' => '/media/37746238/eth.png',
                'icon_url' => 'https://www.cryptocompare.com/media/37746238/eth.png?width=64',
            ],
            'LTC' => [
                'name' => 'Litecoin',
                'icon_path' => '/media/37746243/ltc.png',
                'icon_url' => 'https://www.cryptocompare.com/media/37746243/ltc.png?width=64',
            ],
            'XMR' => [
                'name' => 'Monero',
                'icon_path' => '/media/37746883/xmr.png',
                'icon_url' => 'https://www.cryptocompare.com/media/37746883/xmr.png?width=64',
            ],
            'DASH' => [
                'name' => 'Dash',
                'icon_path' => '/media/37746893/dash.png',
                'icon_url' => 'https://www.cryptocompare.com/media/37746893/dash.png?width=64',
            ],
        ];

        foreach ($symbols as $symbol) {
            $symbol = $this->CRCPW_normalize_asset_symbol($symbol);
            $coin = $default_meta[$symbol] ?? [
                'name' => '',
                'icon_path' => '',
                'icon_url' => '',
            ];
            $items[] = [
                'id' => $symbol,
                'text' => (string) ($coin['icon_path'] ?? ''),
                'fiat' => 0,
                'name' => (string) ($coin['name'] ?? ''),
                'icon_url' => (string) ($coin['icon_url'] ?? ''),
            ];
        }

        return $items;
    }

    public function CRCPW_get_lang()
    {
        $sl = trim(substr(get_bloginfo('language'), 0, 2));

        return (in_array($sl, ['en', 'es', 'ru', 'de', 'fr', 'pt'])) ? $sl : 'en';
    }

    /*
    * Callback functions
    */
    public function CRCPW_assets_data_callback()
    {
        if (!isset($_GET['nonce']) || !check_ajax_referer('crcpw_nonce', 'nonce', false)) {
            wp_send_json_error(['message' => __('Invalid nonce.', 'cryptocurrency-price-widget')], 400);
            wp_die();
        }
    
        $page = isset($_GET['page']) ? absint(wp_unslash($_GET['page'])) : 0;
        $type = isset($_GET['type']) ? sanitize_key(wp_unslash($_GET['type'])) : '';
        $q = isset($_GET['q']) ? sanitize_text_field(wp_unslash($_GET['q'])) : null;
    
        if (empty($type)) {
            wp_send_json_error(['message' => __('Missing type parameter.', 'cryptocurrency-price-widget')], 400);
            wp_die();
        }
    
        switch ($type) {
            case 'flags':
                $results = $this->CRCPW_search_flag_results($q);
                break;
            case 'coins':
                $results = $this->CRCPW_search_coin_results($q);
                break;
            default:
                wp_send_json_error(['message' => __('Invalid type parameter.', 'cryptocurrency-price-widget')], 400);
                wp_die();
        }
    
        wp_send_json($this->_CRCPW_result_assets_data_callback($results, $page), 200);
    }

    private function _CRCPW_result_assets_data_callback($results, $page)
    {
        $page = $page > 1 ? ($page - 1) : 0;
        $i = 0;
        $limit = 100;
        $count = count($results);
        $results = array_slice($results, $page * $limit, $limit);

        $more = ($count > (($page + 1) * $limit));

        return [
            'results' => $results,
            'pagination' => [
                'more' => $more,
            ],
        ];
    }
}

function CRCPW_load_plugin_textdomain()
{
    load_plugin_textdomain('cryptocurrency-price-widget', false, basename(dirname(__FILE__)).'/languages/');
}
add_action('plugins_loaded', 'CRCPW_load_plugin_textdomain');

$GLOBALS['CRCPW_cryptocurrency_price_widget'] = CRCPW_cryptocurrency_price_widget::get_instance();
