<?php

namespace DevOwl\RealCookieBanner;

use DevOwl\CacheInvalidate\ExcludeAssets;
use DevOwl\CookieConsentManagement\CookieConsentManagement;
use DevOwl\CookieConsentManagement\settings\Settings;
use DevOwl\Customize\AbstractCustomizePanel;
use DevOwl\Customize\Core as CustomizeCore;
use DevOwl\DeliverAnonymousAsset\AnonymousAssetBuilder;
use DevOwl\HeadlessContentBlocker\matcher\ScriptInlineMatcher;
use DevOwl\Multilingual\AbstractLanguagePlugin;
use DevOwl\RealQueue\Core as RealQueue;
use DevOwl\RealCookieBanner\base\Core as BaseCore;
use DevOwl\RealCookieBanner\comp\ComingSoonPlugins;
use DevOwl\RealCookieBanner\comp\DatabaseUpgrades;
use DevOwl\RealCookieBanner\comp\language\Hooks;
use DevOwl\RealCookieBanner\comp\migration\DashboardTileMigrationMajor2;
use DevOwl\RealCookieBanner\comp\migration\DashboardTileMigrationMajor3;
use DevOwl\RealCookieBanner\comp\migration\DashboardTileMigrationMajor4;
use DevOwl\RealCookieBanner\comp\migration\DashboardTileMigrationMajor5;
use DevOwl\RealCookieBanner\comp\migration\DashboardTileTcfV2IllegalUsage;
use DevOwl\RealCookieBanner\comp\migration\DbConsentV2;
use DevOwl\RealCookieBanner\comp\TemplatesPluginIntegrations;
use DevOwl\RealCookieBanner\comp\ThirdPartyNotices;
use DevOwl\RealCookieBanner\lite\Core as LiteCore;
use DevOwl\RealCookieBanner\lite\settings\TcfVendorConfiguration;
use DevOwl\RealCookieBanner\lite\tcf\TcfVendorListNormalizer;
use DevOwl\RealCookieBanner\overrides\interfce\IOverrideCore;
use DevOwl\RealCookieBanner\rest\Templates;
use DevOwl\RealCookieBanner\rest\Config;
use DevOwl\RealCookieBanner\rest\Import;
use DevOwl\RealCookieBanner\settings\Blocker;
use DevOwl\RealCookieBanner\view\Banner;
use DevOwl\RealCookieBanner\view\ConfigPage;
use DevOwl\RealCookieBanner\settings\General;
use DevOwl\RealCookieBanner\settings\Consent;
use DevOwl\RealCookieBanner\settings\Cookie;
use DevOwl\RealCookieBanner\settings\CookieGroup;
use DevOwl\RealCookieBanner\settings\Multisite;
use DevOwl\RealCookieBanner\rest\Consent as RestConsent;
use DevOwl\RealCookieBanner\rest\Stats as RestStats;
use DevOwl\RealCookieBanner\rest\Scanner as RestScanner;
use DevOwl\RealCookieBanner\scanner\AutomaticScanStarter;
use DevOwl\RealCookieBanner\scanner\Scanner;
use DevOwl\RealCookieBanner\settings\BannerLink;
use DevOwl\RealCookieBanner\settings\CookiePolicy;
use DevOwl\RealCookieBanner\settings\CountryBypass;
use DevOwl\RealCookieBanner\settings\GoogleConsentMode;
use DevOwl\RealCookieBanner\settings\Reset;
use DevOwl\RealCookieBanner\settings\Revision;
use DevOwl\RealCookieBanner\settings\TCF;
use DevOwl\RealCookieBanner\templates\TemplateConsumers;
use DevOwl\RealCookieBanner\view\Blocker as ViewBlocker;
use DevOwl\RealCookieBanner\view\checklist\ActivateBanner;
use DevOwl\RealCookieBanner\view\checklist\AddCookie;
use DevOwl\RealCookieBanner\view\checklist\SaveSettings;
use DevOwl\RealCookieBanner\view\shortcode\LinkShortcode;
use DevOwl\RealCookieBanner\view\shortcode\HistoryUuidsShortcode;
use DevOwl\RealCookieBanner\view\Scanner as ViewScanner;
use DevOwl\RealCookieBanner\view\navmenu\NavMenuLinks;
use DevOwl\RealCookieBanner\view\Notices;
use DevOwl\RealCookieBanner\view\shortcode\CookiePolicyShortcode;
use DevOwl\RealUtils\Core as RealUtilsCore;
use MatthiasWeb\Utils\FixInvalidJsonInDb;
use MatthiasWeb\Utils\Service;
use MatthiasWeb\Utils\ServiceNoStore;

// @codeCoverageIgnoreStart
defined('ABSPATH') or die('No script kiddies please!'); // Avoid direct file request
// @codeCoverageIgnoreEnd

/**
 * Singleton core class which handles the main system for plugin. It includes
 * registering of the autoload, all hooks (actions & filters) (see BaseCore class).
 */
class Core extends BaseCore implements IOverrideCore {
    use LiteCore;
    use CustomizeCore;

    /**
     * Add the minimum required capability so a user can manage cookies to all users,
     * which also have the `manage_options` capability.
     */
    const ADD_MANAGE_MIN_CAPABILITY_TO_ALL_USERS_WITH = 'manage_options';

    /**
     * The minimal required capability so a user can manage cookies.
     */
    const MANAGE_MIN_CAPABILITY = 'manage_real_cookie_banner';

    /**
     * Singleton instance.
     *
     * @var Core
     */
    private static $me = null;

    /**
     * The config page.
     *
     * @var ConfigPage
     */
    private $configPage;

    /**
     * The banner.
     *
     * @var Banner
     */
    private $banner;

    /**
     * The blocker.
     *
     * @var ViewBlocker
     */
    private $blocker;

    /**
     * An unique id for this page request. This is e. g. needed for unique overlay id.
     *
     * @var string
     */
    private $pageRequestUuid4;

    /**
     * See AbstractLanguagePlugin.
     *
     * @var AbstractLanguagePlugin
     */
    private $compLanguage;

    /**
     * See AdInitiator.
     *
     * @var AdInitiator
     */
    private $adInitiator;

    /**
     * See RpmInitiator.
     *
     * @var RpmInitiator
     */
    private $rpmInitiator;

    /**
     * See AnonymousAssetBuilder.
     *
     * @var AnonymousAssetBuilder
     */
    private $anonymousAssetBuilder;

    /**
     * See TcfVendorListNormalizer.
     *
     * @var TcfVendorListNormalizer
     */
    private $tcfVendorListNormalizer;

    /**
     * See ExcludeAssets.
     *
     * @var ExcludeAssets
     */
    private $excludeAssets;

    /**
     * See Notices.
     *
     * @var Notices
     */
    private $notices;

    /**
     * See Scanner.
     *
     * @var Scanner
     */
    private $scanner;

    /**
     * See RealQueue.
     *
     * @var RealQueue
     */
    private $realQueue;

    /**
     * See CookieConsentManagement.
     *
     * @var CookieConsentManagement
     */
    private $cookieConsentManagement;

    /**
     * Application core constructor.
     */
    protected function __construct() {
        parent::__construct();

        // This line is needed so all singleton instances are correctly configured with `@devowl-wp/cookie-consent-management`
        // e.g. by setting the `Settings` instance.
        $this->getCookieConsentManagement();

        // The Uuid4 must start with a non-number character to work with CSS selectors
        $this->pageRequestUuid4 = 'a' . wp_generate_uuid4();
        $this->blocker = ViewBlocker::instance();
        $this->scanner = Scanner::instance();
        $this->realQueue = new RealQueue($this);
        $this->configPage = ConfigPage::instance();
        $this->banner = Banner::instance();
        $this->notices = new Notices($this);
        $automaticScanStarter = AutomaticScanStarter::instance();
        $templatesPluginIntegrations = TemplatesPluginIntegrations::getInstance();

        $this->realQueue->addCapability(self::MANAGE_MIN_CAPABILITY);

        // Create multilingual instance
        $path = untrailingslashit(plugin_dir_path(RCB_FILE)) . $this->getPluginData('DomainPath');
        $mo = trailingslashit($path) . RCB_TD . '-%s.mo';
        $this->compLanguage = AbstractLanguagePlugin::determineImplementation(RCB_TD, $mo, Localization::class);
        Localization::enableWordPressDotOrgLanguagePacksDownload(RCB_SLUG, RCB_SLUG_LITE);

        // Enable `no-store` for our relevant WP REST API endpoints
        ServiceNoStore::hook('/' . Service::getNamespace($this));
        ServiceNoStore::hook('/wp/v2/rcb-'); // Custom Post Types

        // Fix technicalDefinitions hosts modified by migration tools
        (new FixInvalidJsonInDb())->fixMetadataBySingleMetaKey(Cookie::META_NAME_TECHNICAL_DEFINITIONS);

        // Official Consent API
        add_filter('Consent/Block/HTML', [$this->getBlocker(), 'replace']);

        add_action('http_api_debug', [$this->getNotices(), 'http_api_debug'], 10, 5);
        add_action('init', [$templatesPluginIntegrations, 'init'], 0);
        add_action('init', [ComingSoonPlugins::getInstance(), 'init'], 11);
        add_action('init', [ThirdPartyNotices::getInstance(), 'init']);
        add_action('init', [$this->getScanner(), 'probablyReduceCurrentUserPermissions'], 0);
        add_action('init', [$this->getScanner(), 'outputBlogId'], 0);
        add_action('init', [$this, 'registerPostTypes'], 0); // E.g. WooCommerce does not know at 10 priority about the custom post types
        add_action(
            'RCB/Templates/FalsePositivePlugin',
            [$templatesPluginIntegrations, 'templates_plugin_false_positives'],
            10,
            4
        );
        add_action('activated_plugin', [$this->getActivator(), 'anyPluginToggledState']);
        add_action('deactivated_plugin', [$this->getActivator(), 'anyPluginToggledState']);
        add_action('activated_plugin', [$this->getNotices(), 'anyPluginToggledState'], 10, 2);
        add_action('deactivated_plugin', [$this->getNotices(), 'anyPluginToggledState'], 10, 2);
        add_action('admin_notices', [$this->getNotices(), 'admin_notice_scanner_rerun_after_plugin_toggle']);
        add_action('admin_notices', [$this->getNotices(), 'admin_notice_service_using_template_which_got_deleted']);
        add_action('admin_notices', [$this->getNotices(), 'admin_notice_tcf_too_much_vendors']);
        add_action('admin_notices', [$this->getNotices(), 'admin_notices_services_with_empty_privacy_policy']);
        add_action('admin_notices', [$this->getNotices(), 'admin_notices_scanner_explicit_external_url_coverage']);
        add_action('admin_notices', [
            $this->getNotices(),
            'admin_notices_check_saving_consent_via_rest_api_endpoint_working',
        ]);
        add_action('admin_notices', [$this->getNotices(), 'admin_notice_services_with_updated_templates']);
        add_action('admin_notices', [$this->getNotices(), 'admin_notice_services_with_successor_templates']);
        add_action('admin_notices', [
            $this->getNotices(),
            'admin_notice_services_with_google_consent_mode_adjustments',
        ]);
        add_action('admin_init', [$automaticScanStarter, 'probablyAddClientJob']);
        add_action('DevOwl/RealProductManager/LicenseActivation/StatusChanged/' . RCB_SLUG, [
            $automaticScanStarter,
            'probablyAddClientJob',
        ]);
        add_action('admin_init', [$this, 'registerSettings'], 1);
        add_action('rest_api_init', [$this, 'registerSettings'], 1);
        add_filter('rest_request_after_callbacks', [$this->getBlocker(), 'skipContentBlockerOnRestAPIEndpoint'], 10, 3);
        add_action('plugins_loaded', [Localization::class, 'multilingual'], 1);
        add_action('wp', [$this->getAssets(), 'createHashedAssets']);
        add_action('login_init', [$this->getAssets(), 'createHashedAssets']);
        add_action(
            'plugins_loaded',
            [$this->getBlocker(), 'registerOutputBuffer'],
            ViewBlocker::OB_START_PLUGINS_LOADED_PRIORITY
        );
        add_filter('pre_http_request', [$this->getBlocker(), 'pre_http_request'], 10, 3);
        add_action('DevOwl/Utils/NewVersionInstallation/' . RCB_SLUG, [TCF::getInstance(), 'new_version_installation']);
        add_action('DevOwl/Utils/NewVersionInstallation/' . RCB_SLUG, [new DatabaseUpgrades(), 'apply']);

        add_filter('RCB/Blocker/Enabled', [$this->getScanner(), 'force_blocker_enabled']);
        add_filter('customize_save_response', [$this, 'customize_save_response'], 10, 1);
        add_filter('option_' . Consent::SETTING_COOKIE_DURATION, [Consent::getInstance(), 'option_cookie_duration']);
        add_filter('option_' . Consent::SETTING_CONSENT_DURATION, [Consent::getInstance(), 'option_consent_duration']);
        add_action(
            'update_option_' . Consent::SETTING_CONSENT_DURATION,
            [Consent::getInstance(), 'update_option_consent_transient_deletion'],
            10,
            2
        );
        add_action('update_option_' . General::SETTING_BANNER_ACTIVE, [
            $this->getNotices(),
            'update_option_banner_active',
        ]);
        add_filter(
            'pre_update_option',
            function ($value, $optionName, $oldValue) {
                // See https://wordpress.stackexchange.com/a/330290/83335
                if (
                    $value !== $oldValue &&
                    in_array(
                        $optionName,
                        [General::SETTING_SET_COOKIES_VIA_MANAGER, GoogleConsentMode::SETTING_GCM_ENABLED],
                        true
                    )
                ) {
                    TemplateConsumers::getInstance()->forceRecalculation();
                }
                return $value;
            },
            10,
            3
        );

        // Cache relevant hooks
        add_filter('RCB/Customize/Updated', [Cache::getInstance(), 'customize_updated']);
        add_filter('RCB/Settings/Updated', [Cache::getInstance(), 'settings_updated']);
        add_filter('RCB/Settings/Updated', [SaveSettings::class, 'settings_updated']);
        add_filter('RCB/Settings/Updated', [ActivateBanner::class, 'settings_updated'], 10, 2);
        add_filter('RCB/Settings/Updated', [UserConsent::getInstance(), 'settings_updated']);
        add_filter('RCB/Revision/Hash', [Cache::getInstance(), 'revision_hash']);
        add_action('DevOwl/RealProductManager/LicenseActivation/StatusChanged/' . RCB_SLUG, [
            Cache::getInstance(),
            'invalidate',
        ]);
        add_action('DevOwl/RealProductManager/LicenseActivation/StatusChanged/' . RCB_SLUG, [
            TemplateConsumers::getInstance(),
            'licenseStatusChanged',
        ]);

        // Compatibility hooks (Blocker)
        add_filter('ez_buffered_final_content', [$this->getBlocker(), 'replace'], 1); // Ezoic CDN compatibility
        add_filter('rocket_buffer', [$this->getBlocker(), 'replace'], 1); // WP Rocket Lazy loading compatibility
        add_filter('wp-meteor-frontend-rewrite', [$this->getBlocker(), 'replace']); // WP Meteor
        add_filter('ionos_performancemodify_output', [$this->getBlocker(), 'replace']); // IONOS Performance plugin
        add_filter('ionos_performance_modify_output', [$this->getBlocker(), 'replace']); // IONOS Performance plugin (v2)
        add_filter('facetwp_facet_html', [$this->getBlocker(), 'replace']); // Facet WP facets
        add_filter('wp_grid_builder/async/render_response', [$this->getBlocker(), 'replace']); // WP Grid builder refresh ajax action
        add_filter('autoptimize_filter_html_before_minify', [$this->getBlocker(), 'replace']); // Autoptimize
        add_filter('autoptimize_filter_css_exclude', [$this->getBlocker(), 'autoptimize_filter_css_exclude']);
        add_filter('avf_exclude_assets', [$this->getBlocker(), 'avf_exclude_assets']);
        add_filter('sgo_js_minify_exclude', [$this->getBlocker(), 'sgo_js_minify_exclude']);
        add_filter('sgo_css_minify_exclude', [$this->getBlocker(), 'sgo_css_minify_exclude']);
        add_filter('litespeed_buffer_before', [$this->getBlocker(), 'replace'], 1); // LiteSpeed Cache
        add_filter('litespeed_ccss_url', [$this->getBlocker(), 'modifyUrlToSkipContentBlocker']); // LiteSpeed Cache Critical CSS
        add_filter('litespeed_ucss_url', [$this->getBlocker(), 'modifyUrlToSkipContentBlocker']); // LiteSpeed Cache Unique CSS
        add_filter('us_grid_listing_post', [$this->getBlocker(), 'replace']); // Impreza Lazy Loading posts
        add_filter('woocommerce_update_order_review_fragments', [$this->getBlocker(), 'replace']); // WooCommerce
        add_filter('fl_builder_render_js', [ScriptInlineMatcher::class, 'makeInlineScriptSkippable']); // Beaver Builder and inline scripts (https://docs.wpbeaverbuilder.com/beaver-builder/developer/how-to-tips/load-css-and-javascript-inline/)
        add_action(
            'shutdown',
            function () {
                // [Theme Comp] Themify
                if (has_action('shutdown', ['TFCache', 'tf_cache_end']) !== false) {
                    // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Themify buffer merge returns filtered HTML document body.
                    echo $this->getBlocker()->replace(ob_get_clean());
                }
            },
            0
        );
        add_action('init', [$this->getBlocker(), 'registerOutputBuffer'], 11); // SiteGround Optimizer (https://wordpress.org/support/topic/allow-to-modify-html-in-output-buffer/)
        add_action('shutdown', [$this->getBlocker(), 'closeOutputBuffer'], 11); // "

        add_shortcode(LinkShortcode::TAG, [LinkShortcode::class, 'render']);
        add_shortcode(HistoryUuidsShortcode::TAG, [HistoryUuidsShortcode::class, 'render']);
        add_shortcode(HistoryUuidsShortcode::DEPRECATED_TAG, [HistoryUuidsShortcode::class, 'render']);
        add_shortcode(CookiePolicyShortcode::TAG, [CookiePolicyShortcode::class, 'render']);

        $this->adInitiator = new AdInitiator();
        $this->adInitiator->start();

        $this->rpmInitiator = new RpmInitiator();
        $this->rpmInitiator->start();

        $this->anonymousAssetBuilder = new AnonymousAssetBuilder(
            $this->getTableName(AnonymousAssetBuilder::TABLE_NAME),
            RCB_OPT_PREFIX,
            trailingslashit(RCB_PATH) . $this->getAssets()->getPublicFolder()
        );

        if ($this->isPro()) {
            $this->tcfVendorListNormalizer = new TcfVendorListNormalizer(
                RCB_DB_PREFIX,
                Service::getExternalContainerUrl('rcb') . '1.0.0/tcf/gvl/',
                $this->getCompLanguage()
            );
        }

        $this->getScanner()->probablyForceSitemaps();

        $this->overrideConstructFreemium();
        $this->overrideConstruct();
    }

    /**
     * Define constants which relies on i18n localization loaded.
     */
    public function i18n() {
        parent::i18n();

        // Internal hook; see CU-jbayae
        $args = apply_filters('RCB/Misc/ProUrlArgs', [
            'partner' => null,
            'aid' => null,
        ]);

        // Check if affiliate Link is allowed (only when privacy policy got accepted)
        $isLicensed = !empty(
            $this->getRpmInitiator()->getPluginUpdater()->getCurrentBlogLicense()->getActivation()->getCode()
        );

        if (!$isLicensed) {
            $args['aid'] = null;
        }

        $translatedUrl = __('https://devowl.io/go/real-cookie-banner?source=rcb-lite', 'real-cookie-banner');
        $translatedUrl = add_query_arg($args, $translatedUrl);

        define('RCB_PRO_VERSION', $translatedUrl);
    }

    /**
     * Register settings.
     */
    public function registerSettings() {
        General::getInstance()->register();
        Consent::getInstance()->register();
        Multisite::getInstance()->register();
        CountryBypass::getInstance()->register();
        TCF::getInstance()->register();
        GoogleConsentMode::getInstance()->register();

        $this->overrideRegisterSettings();
    }

    /**
     * Register post types and custom taxonomies.
     */
    public function registerPostTypes() {
        Cookie::getInstance()->register();
        Blocker::getInstance()->register();
        CookieGroup::getInstance()->register();
        BannerLink::getInstance()->register();

        $this->overrideRegisterPostTypes();
    }

    /**
     * The init function is fired even the init hook of WordPress. If possible
     * it should register all hooks to have them in one place.
     */
    public function init() {
        $this->excludeAssets = new ExcludeAssets($this);

        $templatesService = Templates::instance();
        $configService = Config::instance();
        $viewScanner = ViewScanner::instance();
        $restScanner = RestScanner::instance();
        $navMenuLinks = NavMenuLinks::instance();
        $scanner = $this->getScanner();
        $scannerQuery = $scanner->getQuery();
        $scannerOnChangeDetection = $scanner->getOnChangeDetection();

        // Register all your hooks here
        add_action('rest_api_init', [$templatesService, 'rest_api_init']);
        add_action('rest_api_init', [$configService, 'rest_api_init']);
        add_action('rest_api_init', [RestConsent::instance(), 'rest_api_init']);
        add_action('rest_api_init', [RestStats::instance(), 'rest_api_init']);
        add_action('rest_api_init', [$restScanner, 'rest_api_init']);
        add_action('rest_api_init', [Import::instance(), 'rest_api_init']);
        add_action('wp', [$this->getAssets(), 'wp']);
        add_action('admin_enqueue_scripts', [$this->getAssets(), 'admin_enqueue_scripts']);
        add_action('wp_enqueue_scripts', [$this->getAssets(), 'wp_enqueue_scripts'], 0);
        add_action('login_enqueue_scripts', [$this->getAssets(), 'login_enqueue_scripts'], 0);
        add_action('DevOwl/RealQueue/Job/Label', [DbConsentV2::class, 'real_queue_job_label'], 10, 3);
        add_action('DevOwl/RealQueue/Job/Actions', [DbConsentV2::class, 'real_queue_job_actions'], 10, 2);
        add_action('DevOwl/RealQueue/Error/Description', [DbConsentV2::class, 'real_queue_error_description'], 10, 3);
        add_action('DevOwl/RealQueue/Rest/Status/AdditionalData/' . DbConsentV2::REAL_QUEUE_TYPE, [
            DbConsentV2::class,
            'real_queue_additional_data_migration_progress',
        ]);
        add_action('fluent_community/portal_head', [$this->getAssets(), 'fluent_community_portal_head']);
        add_action('DevOwl/RealQueue/Job/Label', [$scanner, 'real_queue_job_label'], 10, 3);
        add_action('DevOwl/RealQueue/Job/Actions', [$scanner, 'real_queue_job_actions'], 10, 2);
        add_action('DevOwl/RealQueue/Error/Description', [$scanner, 'real_queue_error_description'], 10, 3);
        add_action('DevOwl/RealQueue/EnqueueScripts', [$this->getAssets(), 'real_queue_enqueue_scripts']);
        add_action('DevOwl/RealQueue/Rest/Status/AdditionalData/rcb-scan-list', [
            $restScanner,
            'real_queue_additional_data_list',
        ]);
        add_action('DevOwl/RealQueue/Rest/Status/AdditionalData/rcb-scan-notice', [
            $restScanner,
            'real_queue_additional_data_notice',
        ]);
        add_action('admin_menu', [$this->getConfigPage(), 'admin_menu']);
        add_filter(
            'plugin_action_links_' . plugin_basename(RCB_FILE),
            [$this->getConfigPage(), 'plugin_action_links'],
            10,
            2
        );
        add_action('customize_register', [$this->getBanner()->getCustomize(), 'customize_register']);
        add_action('wp_body_open', [$this->getBanner(), 'wp_body_open']);
        add_action('wp_footer', [$this->getBanner(), 'wp_footer']);
        add_action('login_footer', [$this->getBanner(), 'wp_footer']);
        add_action('admin_bar_menu', [$this->getBanner(), 'admin_bar_menu'], 100);
        add_action('admin_bar_menu', [$viewScanner, 'admin_bar_menu'], 100);
        add_action('save_post_' . Cookie::CPT_NAME, [AddCookie::class, 'save_post'], 10, 3);
        add_action('save_post_' . Blocker::CPT_NAME, [Blocker::getInstance(), 'save_post'], 10, 3);
        add_action('save_post_' . Blocker::CPT_NAME, [Cache::getInstance(), 'invalidate']);
        add_action('save_post_page', [BannerLink::getInstance(), 'save_post_page']);
        add_action('delete_' . CookieGroup::TAXONOMY_NAME, [CookieGroup::getInstance(), 'deleted'], 10, 4);
        add_action('edited_' . CookieGroup::TAXONOMY_NAME, [Stats::getInstance(), 'edited_group']);
        add_action('deleted_post', [Blocker::getInstance(), 'deleted_post']);
        add_action('admin_notices', [$this->getConfigPage(), 'admin_notices_preinstalled_environment']);
        add_action('admin_notices', [$this->getConfigPage(), 'admin_notices_ad_blocker']);
        add_action('posts_where', [Utils::class, 'posts_where_find_in_set'], 10, 2);
        add_action('post_updated', [$scannerOnChangeDetection, 'post_updated'], 10, 3);
        add_action('save_post', [$scannerOnChangeDetection, 'save_post'], 10, 2);
        add_action('delete_post', [$scannerOnChangeDetection, 'delete_post'], 10);
        add_action('wp_trash_post', [$scannerOnChangeDetection, 'wp_trash_post']);
        add_action('untrash_post', [$scannerOnChangeDetection, 'wp_trash_post']);
        add_action('wp_nav_menu_item_custom_fields', [$navMenuLinks, 'wp_nav_menu_item_custom_fields'], 10, 2);
        add_action('wp_update_nav_menu_item', [$navMenuLinks, 'wp_update_nav_menu_item'], 10, 3);
        add_action('admin_head-nav-menus.php', [$navMenuLinks, 'admin_head']);
        add_action('customize_controls_head', [$navMenuLinks, 'customize_controls_head']);
        add_action('before_delete_post', [General::getInstance(), 'before_delete_post'], 0);
        add_action('wp_trash_post', [General::getInstance(), 'before_delete_post']);
        add_action('delete_post', [BannerLink::getInstance(), 'delete_post']);
        add_action('wp_trash_post', [BannerLink::getInstance(), 'delete_post']);
        add_action(
            'update_option_wp_page_for_privacy_policy',
            [BannerLink::getInstance(), 'update_option_wp_page_for_privacy_policy'],
            10,
            2
        );
        add_action('updated_post_meta', [BannerLink::getInstance(), 'added_or_updated_post_meta'], 10, 4);
        add_action('added_post_meta', [BannerLink::getInstance(), 'added_or_updated_post_meta'], 10, 4);
        add_action('RCB/Migration/RegisterActions', [new DashboardTileMigrationMajor2(), 'actions']);
        add_action('RCB/Migration/RegisterActions', [new DashboardTileMigrationMajor3(), 'actions']);
        add_action('RCB/Migration/RegisterActions', [new DashboardTileMigrationMajor4(), 'actions']);
        add_action('RCB/Migration/RegisterActions', [new DashboardTileMigrationMajor5(), 'actions']);
        add_action('RCB/Migration/RegisterActions', [new DashboardTileTcfV2IllegalUsage(), 'actions']);
        add_action(
            'added_post_meta',
            [$this->getNotices(), 'added_post_meta_data_processing_in_unsafe_countries'],
            10,
            4
        );

        add_filter('request', [$configService, 'modify_wp_post_types_temporarily']);
        add_filter(
            'update_post_metadata',
            [$this->getNotices(), 'update_post_meta_data_processing_in_unsafe_countries'],
            10,
            4
        );
        add_filter('nav_menu_link_attributes', [$navMenuLinks, 'nav_menu_link_attributes'], 10, 2);
        add_filter('wp_setup_nav_menu_item', [$navMenuLinks, 'wp_setup_nav_menu_item']);
        add_filter('walker_nav_menu_start_el', [$navMenuLinks, 'walker_nav_menu_start_el'], 10, 2);
        add_filter('customize_nav_menu_available_item_types', [
            $navMenuLinks,
            'register_customize_nav_menu_item_types',
        ]);
        add_filter('customize_nav_menu_available_items', [$navMenuLinks, 'register_customize_nav_menu_items'], 10, 4);
        add_filter('RCB/Revision/Current', [$navMenuLinks, 'revisionCurrent']);
        add_filter('RCB/Revision/Current', [TemplateConsumers::getInstance(), 'revisionCurrent']);

        add_filter('oembed_result', [$this->getBlocker(), 'modifyOEmbedHtmlToKeepOriginalUrl'], 10, 2);
        add_filter('embed_oembed_html', [$this->getBlocker(), 'modifyOEmbedHtmlToKeepOriginalUrl'], 10, 2);
        add_filter('RCB/Hints', [$this->getAssets(), 'hints_dashboard_tile_predefined_links'], 100);
        add_filter('rest_post_dispatch', [$configService, 'rest_post_dispatch'], 10, 3);
        add_filter('rest_prepare_' . Cookie::CPT_NAME, [$templatesService, 'rest_prepare_templates'], 10, 2);
        add_filter('rest_' . Cookie::CPT_NAME . '_item_schema', [Cookie::getInstance(), 'rest_item_schema']);
        add_filter('rest_prepare_' . Blocker::CPT_NAME, [$templatesService, 'rest_prepare_templates'], 10, 2);
        add_filter('rest_prepare_' . Cookie::CPT_NAME, [$this->getCompLanguage(), 'rest_prepare_post'], 10, 3);
        add_filter('rest_prepare_' . Blocker::CPT_NAME, [$this->getCompLanguage(), 'rest_prepare_post'], 10, 3);
        add_filter('rest_prepare_' . BannerLink::CPT_NAME, [$this->getCompLanguage(), 'rest_prepare_post'], 10, 3);
        if ($this->isPro()) {
            add_filter(
                'rest_prepare_' . TcfVendorConfiguration::CPT_NAME,
                [$this->getCompLanguage(), 'rest_prepare_post'],
                10,
                3
            );
        }
        add_filter(
            'rest_prepare_' . CookieGroup::TAXONOMY_NAME,
            [$this->getCompLanguage(), 'rest_prepare_taxonomy'],
            10,
            3
        );
        add_filter('RCB/Revision/Current', [$scannerQuery, 'revisionCurrent']);
        add_filter('display_post_states', [CookiePolicy::getInstance(), 'display_post_states'], 10, 2);
        add_filter('page_row_actions', [CookiePolicy::getInstance(), 'page_row_actions'], 10, 2);

        // Multilingual
        add_filter('rest_' . Cookie::CPT_NAME . '_query', [Hooks::getInstance(), 'rest_query']);
        add_filter('rest_' . Blocker::CPT_NAME . '_query', [Hooks::getInstance(), 'rest_query']);
        add_filter('rest_' . BannerLink::CPT_NAME . '_query', [Hooks::getInstance(), 'rest_query']);
        add_action('rest_api_init', [Hooks::getInstance(), 'rest_api_init'], 1);
        add_filter('RCB/Hints', [Hooks::getInstance(), 'hints']);
        add_filter('RCB/Query/Arguments', [Hooks::getInstance(), 'queryArguments']);
        add_filter(
            'DevOwl/Multilingual/Copy/Meta/post/' . Blocker::META_NAME_SERVICES,
            [Hooks::getInstance(), 'copy_blocker_connected_services_meta'],
            10,
            5
        );
        add_filter(
            'DevOwl/Multilingual/Copy/Meta/post/' . Blocker::META_NAME_TCF_VENDORS,
            [Hooks::getInstance(), 'copy_blocker_connected_services_meta'],
            10,
            5
        );
        add_filter('RCB/Revision/Context', [Hooks::getInstance(), 'context']);
        add_filter('RCB/Revision/Context/Translate', [Hooks::getInstance(), 'contextTranslate']);
        add_filter('RCB/Revision/Hash', [Hooks::getInstance(), 'revisionHash']);
        add_filter('RCB/Revision/NeedsRetrigger', [Hooks::getInstance(), 'revisionNeedsRetrigger']);

        $this->getBanner()->getCustomize()->enableOptionsAutoload();
        General::getInstance()->enableOptionsAutoload();
        Consent::getInstance()->enableOptionsAutoload();
        Multisite::getInstance()->enableOptionsAutoload();
        TCF::getInstance()->enableOptionsAutoload();
        GoogleConsentMode::getInstance()->enableOptionsAutoload();
        CountryBypass::getInstance()->enableOptionsAutoload();

        if ($scanner->isActive()) {
            ComingSoonPlugins::getInstance()->bypass();
            add_action('shutdown', [$scanner, 'teardown']);
            add_filter('show_admin_bar', '__return_false');
            add_filter('RCB/Blocker/ResolveBlockables', [$scanner, 'resolve_blockables'], 50, 2);
        }

        // If country bypass is active, add the filter so the frontend fetches the WP REST API and create consent
        if (CountryBypass::getInstance()->isActive()) {
            add_filter('RCB/Consent/DynamicPreDecision', [CountryBypass::getInstance(), 'dynamicPredecision'], 10, 2);
        }

        // If bannerless consent is active, add the filter so the frontend fetches the WP REST API and create consent
        if (Consent::getInstance()->isBannerLessConsent()) {
            add_filter('RCB/Consent/DynamicPreDecision', [Consent::getInstance(), 'dynamicPredecision'], 12, 2);
        }

        $this->overrideInitCustomize();
        $this->overrideInit();

        // Allow to reset all available data and recreated
        if (isset($_GET['rcb-reset-all']) && current_user_can(self::MANAGE_MIN_CAPABILITY)) {
            check_admin_referer('rcb-reset-all');
            Reset::getInstance()->all(isset($_GET['reset-consents']));
            wp_safe_redirect($this->getConfigPage()->getUrl());
            exit();
        }

        // Allow to reset all available data and recreated
        if (isset($_GET['rcb-reset-texts']) && current_user_can(self::MANAGE_MIN_CAPABILITY)) {
            check_admin_referer('rcb-reset-texts');
            // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- nonce-verified admin reset; languages sanitized before persist.
            $resetLangRaw = isset($_GET['reset-lang']) ? wp_unslash($_GET['reset-lang']) : [];
            $resetLang = is_array($resetLangRaw) ? array_map('sanitize_text_field', $resetLangRaw) : null;
            Reset::getInstance()->texts($resetLang);
            wp_safe_redirect($this->getConfigPage()->getUrl());
            exit();
        }

        // Create default content
        if (
            $this->getConfigPage()->isVisible() &&
            (get_site_option(Activator::OPTION_NAME_NEEDS_DEFAULT_CONTENT) ||
                // Multisite added sites does not trigger activation hook
                CookieGroup::getInstance()->getEssentialGroup() === null ||
                // Compatibility with PolyLang and WPML
                count(Hooks::getInstance()->getLanguagesWithoutEssentialGroup()) > 0)
        ) {
            $this->getActivator()->addInitialContent();
        }

        // If we reached next thursday, update the GVL automatically
        TCF::getInstance()->probablyUpdateGvl();

        // If we reached next sunday, update the country database automatically
        CountryBypass::getInstance()->probablyUpdateDatabase();
    }

    /**
     * Check if any plugin specific setting got changed in customize.
     *
     * @param array $response
     */
    public function customize_save_response($response) {
        if (AbstractCustomizePanel::gotUpdated($response, RCB_OPT_PREFIX)) {
            /**
             * Real Cookie Banner (Content Blocker, banner) got updated in the Customize.
             *
             * @hook RCB/Customize/Updated
             * @param {array} $response
             * @return {array}
             */
            $response = apply_filters('RCB/Customize/Updated', $response);
        }
        return $response;
    }

    /**
     * See filter RCB/Query/Arguments.
     *
     * @param array $arguments
     * @param string $context
     */
    public function queryArguments($arguments, $context) {
        /**
         * Modify arguments to `get_posts` and `get_terms`. This can be especially useful for WPML / PolyLang.
         *
         * @hook RCB/Query/Arguments
         * @param {array} $arguments
         * @param {string} $context
         * @return {array}
         */
        return apply_filters('RCB/Query/Arguments', array_merge(['suppress_filters' => false], $arguments), $context);
    }

    /**
     * Return the base URL to assets specially for Real Cookie Banner.
     *
     * @param string $path
     */
    public function getBaseAssetsUrl($path) {
        return RealUtilsCore::getInstance()->getBaseAssetsUrl('wp-real-cookie-banner/' . $path);
    }

    /**
     * Get config page.
     *
     * @codeCoverageIgnore
     */
    public function getConfigPage() {
        return $this->configPage;
    }

    /**
     * Get banner.
     *
     * @codeCoverageIgnore
     */
    public function getBanner() {
        return $this->banner;
    }

    /**
     * Get blocker.
     *
     * @codeCoverageIgnore
     */
    public function getBlocker() {
        return $this->blocker;
    }

    /**
     * Get request uuid 4.
     *
     * @codeCoverageIgnore
     */
    public function getPageRequestUuid4() {
        return $this->pageRequestUuid4;
    }

    /**
     * Get compatibility language class.
     *
     * @codeCoverageIgnore
     */
    public function getCompLanguage() {
        return $this->compLanguage;
    }

    /**
     * Get ad initiator from `real-utils`.
     *
     * @codeCoverageIgnore
     */
    public function getAdInitiator() {
        return $this->adInitiator;
    }

    /**
     * Get ad initiator from `real-product-manager-wp-client`.
     *
     * @codeCoverageIgnore
     */
    public function getRpmInitiator() {
        return $this->rpmInitiator;
    }

    /**
     * Getter.
     *
     * @codeCoverageIgnore
     */
    public function getAnonymousAssetBuilder() {
        return $this->anonymousAssetBuilder;
    }

    /**
     * Getter.
     *
     * @codeCoverageIgnore
     */
    public function getTcfVendorListNormalizer() {
        return $this->tcfVendorListNormalizer;
    }

    /**
     * Getter.
     *
     * @codeCoverageIgnore
     */
    public function getExcludeAssets() {
        return $this->excludeAssets;
    }

    /**
     * Getter.
     *
     * @codeCoverageIgnore
     */
    public function getNotices() {
        return $this->notices;
    }

    /**
     * Getter.
     *
     * @codeCoverageIgnore
     */
    public function getScanner() {
        return $this->scanner;
    }

    /**
     * Getter.
     *
     * @codeCoverageIgnore
     */
    public function getRealQueue() {
        return $this->realQueue;
    }

    /**
     * Getter.
     *
     * @codeCoverageIgnore
     */
    public function getCookieConsentManagement() {
        if ($this->cookieConsentManagement === null) {
            $settings = new Settings(
                General::getInstance(),
                Consent::getInstance(),
                CookiePolicy::getInstance(),
                CountryBypass::getInstance(),
                TCF::getInstance(),
                Multisite::getInstance(),
                GoogleConsentMode::getInstance()
            );
            $this->cookieConsentManagement = new CookieConsentManagement($settings, Revision::getInstance());
        }

        return $this->cookieConsentManagement;
    }

    /**
     * Check if a license is active.
     */
    public function isLicenseActive() {
        return !empty(
            Core::getInstance()
                ->getRpmInitiator()
                ->getPluginUpdater()
                ->getCurrentBlogLicense()
                ->getActivation()
                ->getCode()
        );
    }

    /**
     * Get singleton core class.
     *
     * @return Core
     */
    public static function getInstance() {
        return !isset(self::$me) ? (self::$me = new Core()) : self::$me;
    }
}

/**
 * See API docs.
 *
 * @api {get} /real-cookie-banner/v1/plugin Get plugin information
 * @apiHeader {string} X-WP-Nonce
 * @apiName GetPlugin
 * @apiGroup Plugin
 *
 * @apiSuccessExample {json} Success-Response:
 * {
 *     Name: "My plugin",
 *     PluginURI: "https://example.com/my-plugin",
 *     Version: "0.1.0",
 *     Description: "This plugin is doing something.",
 *     Author: "<a href="https://example.com">John Smith</a>",
 *     AuthorURI: "https://example.com",
 *     TextDomain: "my-plugin",
 *     DomainPath: "/languages",
 *     Network: false,
 *     Title: "<a href="https://example.com">My plugin</a>",
 *     AuthorName: "John Smith"
 * }
 * @apiVersion 1.0.0
 */
