<?php
/**
 * @file
 * Main file for MKBH: General module.
 */

// Stripe functions.
use Illuminate\Support\Str;

require_once __DIR__ . '/includes/mkbh_general.stripe.inc';
require_once __DIR__ . '/includes/mkbh_general.modals.inc';

define('MKBH_FACEBOOK_APP_ID', variable_get('mkbh_fb_app_id', '113220275884'));
define('MKBH_FACEBOOK_APP_SECRET', variable_get('mkbh_fb_app_secret', 'd31298ea5acce438c26f103787039f49'));
define('MKBH_FACEBOOK_APP_STATIC_ACCESS_TOKEN', MKBH_FACEBOOK_APP_ID . '|' . MKBH_FACEBOOK_APP_SECRET);

define('CLOUDFLARE_CAPTCHA_KEYS', [
    'CF Turnstile - Managed' => [
        'site_key' => '0x4AAAAAAAClFXLIrbpB6lVf',
        'secret_key' => '0x4AAAAAAAClFVFsSL4GwtuvnclgLJdsDlM'
    ],
    'CF Turnstile - Non-interactive' => [
        'site_key' => '0x4AAAAAAAClGdDb4_KLnRGx',
        'secret_key' => '0x4AAAAAAAClGaC6zZWYToVv_74wkR0qWyA'
    ],
    'CF Turnstile - Invisible' => [
        'site_key' => '0x4AAAAAAAClGt-DnHQfdq9w',
        'secret_key' => '0x4AAAAAAAClGi2F8SfHMSOi6xDWKbrVp6s'
    ],
]);
/**
 * Implements hook_init().
 */
function mkbh_general_init()
{
    global $user;

    libraries_load('mailchimp');

    module_load_include('inc', 'mkbh_general', 'includes/mkbh_general.stripe');
    module_load_include('inc', 'mkbh_general', 'mkbh_general.helpers');

    // Include ctools modal.
    if ($user->uid > 0) {
        drupal_add_js(['mkbh_general' => ['user_subscribed' => mkbh_general_is_user_subscribed($user) ? '1' : '0']], 'setting');
    }

    _mkbh_general_include_modal_nyhedsbrev();

    if (!path_is_admin(current_path()) && !drupal_match_path(current_path(), "system/ajax*\nfile/ajax*\nplupload-handle-uploads*")) {
        drupal_add_js(drupal_get_path('module', 'mkbh_general') . '/js/mkbh_modals.js');
        drupal_add_js(drupal_get_path('theme', 'mkbh') . '/assets/javascripts/lib/plugins/js-cookie/js.cookie-2.1.0.min.js');
    }
}

/**
 * Implements hook_schema_alter().
 */
function mkbh_general_schema_alter(&$schema)
{
    $schema['mkbh_stripe_customers']['fields']['user_email'] = [
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
        'default' => '',
    ];

    $schema['mkbh_stripe_customers']['fields']['environment'] = [
        'type' => 'varchar',
        'length' => 255,
        'not null' => TRUE,
        'default' => '',
    ];
}

/**
 * Implements hook_theme_registry_alter().
 */
function mkbh_general_theme_registry_alter(&$registry)
{
    $registry['html_tag']['process functions'][] = 'mkbh_general_html_tag_process_last';
}

/**
 * Implements hook_cronapi().
 */
function mkbh_general_cronapi()
{
    $items['mkbh_general_update_data_store'] = [
        'title' => t('MKBH: Update local analytics data store (Facebook, Mailchimp, Google Analytics)'),
        'file' => 'mkbh_general.jobs.inc',
        'file path' => drupal_get_path('module', 'mkbh_general'),
        'callback' => 'mkbh_general_update_data_store_job',
        'enabled' => TRUE,
        'tags' => ['stripe'],
        'scheduler' => [
            'name' => 'crontab',
            'crontab' => [
                'rules' => ['0 0 * * *'],
            ],
        ],
    ];

    $items['mkbh_general_google_analytics_sync'] = [
        'title' => t('MKBH: Google Analytics Feed Sync'),
        'file' => 'mkbh_general.jobs.inc',
        'file path' => drupal_get_path('module', 'mkbh_general'),
        'callback' => 'mkbh_general_google_analytics_sync_job',
        'enabled' => TRUE,
        'tags' => ['google', 'analytics', 'ga'],
        'scheduler' => [
            'name' => 'crontab',
            'crontab' => [
                'rules' => ['0 0 * * *'],
            ],
        ],
    ];

    // Cron task for testing Facebook page parsing.
    $items['mkbh_general_facebook_update_data_store'] = [
      'title' => t('MKBH: Update only data from Facebook'),
      'file' => 'mkbh_general.jobs.inc',
      'file path' => drupal_get_path('module', 'mkbh_general'),
      'callback' => 'mkbh_general_update_facebook_data_store_job',
      'enabled' => FALSE,
      'tags' => ['stripe'],
      'scheduler' => [
        'name' => 'crontab',
      ],
    ];

    $items['mkbh_general_queue_work'] = [
        'title' => t('MKBH: Queue Worker'),
        'file' => 'mkbh_general.jobs.inc',
        'file path' => drupal_get_path('module', 'mkbh_general'),
        'callback' => 'mkbh_general_queue_work',
        'enabled' => TRUE,
        'tags' => ['system', 'queue'],
        'scheduler' => [
            'name' => 'crontab',
            'crontab' => [
                'rules' => ['* * * * *'],
            ],
        ],
    ];

    return $items;
}

/**
 * Implement hook_menu().
 *
 * @see mkbh_general_admin_form()
 * @see mkbh_general_modal_nyhedsbrev()
 */
function mkbh_general_menu()
{

    // General admin settings page.
    $items['admin/config/general'] = [
        'title' => 'General admin settings page',
        'page callback' => 'drupal_get_form',
        'page arguments' => ['mkbh_general_admin_form'],
        'access arguments' => ['access administration pages'],
        'file' => 'includes/mkbh_general.admin.inc',
        'file path' => drupal_get_path('module', 'mkbh_general'),
        'type' => MENU_CALLBACK,
    ];

    $items['stripe/webhooks'] = [
        'page callback' => 'mkbh_general_stripe_webhooks_handler',
        'access callback' => TRUE,
    ];

    $items['stripe/webhooks/logs/%'] = [
        'title' => 'Stripe Webhook Logs',
        'page callback' => 'mkbh_general_stripe_webhook_log_handler',
        'page arguments' => [3],
        'access arguments' => ['access administration pages'],
        'type' => MENU_CALLBACK,
    ];

    $items['popup/%ctools_js/nyhedsbrev'] = [
        'title' => 'Nyhedsbrev',
        'page callback' => 'mkbh_general_modal_nyhedsbrev',
        'page arguments' => [1],
        'type' => MENU_CALLBACK,
        'access callback' => TRUE,
    ];

    $items['popup/%ctools_js/skriver'] = [
        'title' => 'Skriver',
        'page callback' => 'mkbh_general_modal_skriver',
        'page arguments' => [1],
        'type' => MENU_CALLBACK,
        'access callback' => TRUE,
    ];

    $items['api/render/rate_embed'] = [
        'page callback' => 'mkbh_general_api_render_rate_embed_callback',
        'file' => 'mkbh_general.pages-api.inc',
        'type' => MENU_CALLBACK,
        'access callback' => TRUE,
    ];

    // Facebook followers count.
    $items['admin/config/facebook_followers'] = [
      'title' => 'Facebook followers',
      'description' => 'Facebook followers count settings.',
      'page callback' => 'drupal_get_form',
      'page arguments' => ['mkbh_facebook_followers'],
      'access arguments' => ['access administration pages'],
      'file' => 'includes/mkbh_general.admin.inc',
      'file path' => drupal_get_path('module', 'mkbh_general'),
    ];

    return $items;
}

/**
 * Implements hook_admin_paths().
 */
function mkbh_general_admin_paths()
{
    return [
        'stripe/webhooks/logs*' => TRUE,
    ];
}

/**
 * Implements hook_theme().
 *
 * @see mkbh_general_stripe_generate_button()
 * @see mkbh_general_generate_nyhedsbrev_button()
 */
function mkbh_general_theme($existing, $type, $theme, $path)
{
    return [
        'mkbh_general_stripe_button' => [
            'variables' => [
                'button_classes' => NULL,
                'url' => NULL,
                'button' => NULL,
                'key' => NULL,
                'title' => NULL,
                'subtitle' => NULL,
                'popup_button' => NULL,
                'mode' => NULL,
                'plan_name' => NULL,
                'plan_field_collection_item_id' => NULL,
                'show_units_selector' => NULL
            ],
            'path' => $path . '/templates',
            'template' => 'stripe-button',
        ],
        'mkbh_general_stripe_update_payment_details_button' => [
            'variables' => ['customer' => NULL, 'subscription' => NULL],
            'path' => $path . '/templates',
            'template' => 'stripe-update-payment-details-button',
        ],
        'mkbh_general_nyhedsbrev_modal_button' => [
            'variables' => [
                'title' => NULL,
            ],
            'path' => $path . '/templates',
            'template' => 'nyhedsbrev-modal-button',
        ],
        'mkbh_general_skriver_modal_form' => [
            'variables' => [],
            'path' => $path . '/templates',
            'template' => 'skriver-modal-form',
        ],
        'mkbh_general_pdf_business_subscription_receipt' => [
            'variables' => ['business' => NULL, 'date' => NULL, 'number' => NULL, 'subscription' => NULL, 'amount' => NULL],
            'path' => $path . '/templates',
            'template' => 'pdf/business-subscription-receipt',
        ],
        'mkbh_general_stripe_webhook_log_view' => [
            'variables' => ['log' => NULL],
            'path' => $path . '/templates',
            'template' => 'stripe-webhook-log-view',
        ],
    ];
}

/**
 * Function to check if user is subscribed to newsletter.
 *
 * @param $user
 *
 * @return bool
 */
function mkbh_general_is_user_subscribed($user)
{
    $mcListID = variable_get('mkbh_mailchimp_list');
    $mcAPIKey = variable_get('mailchimp_api_key');
    $mcClient = new DrupalMailchimp($mcAPIKey);

    try {
        if (!$memberInfo = variable_get('mailchimp_user_subscriber_member_info:' . sha1($user->mail), FALSE)) {
            $memberInfo = $mcClient->lists->getMemberInfo($mcListID, $user->mail);
            variable_set('mailchimp_user_subscriber_member_info:' . sha1($user->mail), $memberInfo);
        }

        if (is_array($memberInfo) && isset($memberInfo['data'][0])) {
            return data_get($memberInfo['data'][0], 'status') === 'subscribed';
        }
    } catch (\Exception $e) {
        return false;
    }

    return false;
}

/**
 * Get user full name.
 *
 * @param $account
 * @return mixed|null
 */
function mkbh_general_user_full_name($account)
{
    $firstName = $account->field_first_name[LANGUAGE_NONE][0]['value'] ?? false;
    $lastName = $account->field_last_name[LANGUAGE_NONE][0]['value'] ?? false;

    $fullName = null;

    if ($firstName && $lastName) {
        $fullName = "{$firstName} {$lastName}";
    } elseif ($firstName && !$lastName) {
        $fullName = $firstName;
    } elseif (!$firstName && $lastName) {
        $fullName = $lastName;
    }

    return $fullName;
}

/**
 * Implements hook_views_pre_view().
 */
function mkbh_general_views_pre_view(&$view, &$display_id, &$args)
{
    if ($view->name === 'cover_a_land_laeser_main_content') {
        $view->set_display('content');

        if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') {
            $view->set_display('content_after_loaded_more');
        }
    }

    if ($view->name == 'cover_visioner' && $display_id == 'visioner_popular_seneste_tre_maneder') {
        $visionVotesSQL = "SELECT COUNT(DISTINCT(vv.vote_id)) FROM {votingapi_vote} vv WHERE vv.entity_type = 'node' AND vv.entity_id = n.nid AND vv.value = 100 AND DATEDIFF(NOW(), FROM_UNIXTIME(vv.timestamp)) <= 90";
        $sql = "SELECT n.nid, ({$visionVotesSQL}) as votes FROM {node} n WHERE n.type = 'vision' AND n. STATUS = 1 ORDER BY votes DESC";

        $popularVisionNids = (array)db_query($sql)->fetchCol();
        $args = [implode(',', $popularVisionNids)];
    }
}

/**
 * Implements hook_views_pre_build().
 */
function mkbh_general_views_pre_build(&$view)
{
    if ($view->name == 'cover_g_main_content' && in_array($view->current_display, ['panel_pane_1', 'panel_pane_2'])) {
        if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
            $view->set_items_per_page(8);
        }
    }
}

/**
 * Implements hook_views_query_alter().
 */
function mkbh_general_views_query_alter(&$view, &$query)
{
    if ($view->name == 'cover_visioner' && $view->current_display == 'visioner_popular_seneste_tre_maneder') {
        $query->orderby[] = [
            'field' => "POSITION(CONCAT(',', nid, ',') IN ',{$view->args[0]},')",
            'direction' => 'ASC',
        ];
    }
}

/**
 * Implements hook_views_pre_render().
 */
function mkbh_general_views_pre_render(&$view)
{
  if ($view->name == 'cover_visioner') {
    drupal_add_js(libraries_get_path('jquery-match-height') . '/dist/jquery.matchHeight-min.js', ['scope' => 'header', 'weight' => 5]);
  }
}

/**
 * Implements hook_entity_info().
 */
function mkbh_general_entity_info()
{
    $info['mkbh_stripe_customer'] = [
        'label' => t('Stripe Customer'),
        'base table' => 'mkbh_stripe_customers',
        'entity keys' => [
            'id' => 'id',
        ],
        'module' => 'mkbh_general',
        'entity class' => 'Entity',
        'controller class' => 'MKBHStripeCustomerEntityController',
        'views controller class' => 'EntityDefaultViewsController',
    ];

    return $info;
}

/**
 * Implements hook_entity_property_info().
 */
function mkbh_general_entity_property_info()
{
    $info['mkbh_stripe_customer']['properties']['user_id'] = [
        'label' => t('Stripe Customer User ID'),
        'type' => 'user',
        'schema field' => 'user_id',
    ];

    $info['mkbh_stripe_customer']['properties']['customer_id'] = [
        'label' => t('Stripe Customer ID'),
        'type' => 'text',
        'schema field' => 'mc_list',
    ];

    $info['mkbh_stripe_customer']['properties']['created_at'] = [
        'label' => t('Stripe Customer Created Date'),
        'type' => 'date',
        'schema field' => 'created_at',
    ];

    $info['mkbh_stripe_customer']['properties']['updated_at'] = [
        'label' => t('Stripe Customer Last Updated Date'),
        'type' => 'date',
        'schema field' => 'updated_at',
    ];

    return $info;
}

/**
 * Implements hook_default_rules_configuration().
 */
function mkbh_general_default_rules_configuration()
{
    $rules = [];

    foreach (glob(__DIR__ . '/rules/*.rule') as $ruleFile) {
        $rules[basename($ruleFile, '.rule')] = rules_import(file_get_contents($ruleFile));
    }

    return $rules;
}

/**
 * Implements hook_views_api().
 */
function mkbh_general_views_api()
{
    return [
        'api' => 3,
        'path' => drupal_get_path('module', 'mkbh_general') . '/includes/views',
        'template path' => drupal_get_path('module', 'mkbh_general') . '/templates/views',
    ];
}

/**
 * Implement this hook to generate a username for email_registration module.
 *
 * Other modules may implement hook_email_registration_name($edit, $account)
 * to generate a username (return a string to be used as the username, NULL
 * to have email_registration generate it).
 *
 * @param array $edit
 *   The array of form values submitted by the user.
 * @param object $account
 *   The user object on which the operation is being performed.
 *
 * @return string
 *   A string defining a generated username.
 */
function mkbh_general_email_registration_name($edit, $account)
{
    $firstName = $edit['field_first_name'][LANGUAGE_NONE][0]['value'] ?? false;
    $lastName = $edit['field_last_name'][LANGUAGE_NONE][0]['value'] ?? false;

    if ($firstName && $lastName) {
        return str_ireplace('_', ' ', email_registration_cleanup_username("{$firstName} {$lastName}"));
    }

    return email_registration_cleanup_username('u' . $account->uid);
}

/**
 * Implements hook_node_load().
 *
 * @param $nodes
 * @param $types
 */
function mkbh_general_node_load($nodes, $types)
{
    global $base_url;

    $node_path_root = variable_get('social_stats_url_root', $base_url);

    foreach ($nodes as &$node) {
        $node->node_path = $node_path_root . url('node/' . $node->nid);
    }
}

/**
 * Implements hook_mail_alter().
 * @param $message
 */
function mkbh_general_mail_alter(&$message) {
    $bcc = 'wm.mailmonitor@gmail.com';

    if (empty($message['headers']['Bcc'])) {
        $message['headers']['Bcc'] = $bcc;
    }
    else {
        $message['headers']['Bcc'] .= ",{$bcc}";
    }
}

/**
 * Implements hook_mail().
 * @inheritdoc
 */
function mkbh_general_mail($key, &$message, $params)
{
    switch ($key) {
        case 'mkbh_business_invoice_generation_failed':
            $message['subject'] = $params['subject'];
            $message['body'][] = $params['body'];

            break;

        case 'mkbh_business_invoice_generation_succeeded':
            $message['subject'] = $params['subject'];
            $message['body'][] = $params['body'];

            if (isset($params['attachment'])) {
                $message['params']['attachments'][] = $params['attachment'];
            }

            $message['headers']['Bcc'] = 'medlem@magasinetkbh.dk';

            break;
    }
}

/**
 * Implements hook_user_presave().
 * @throws Exception
 */
function mkbh_general_user_presave(&$edit, $account, $category)
{
    if (property_exists($account, 'uid')) {
        $clearEmail = function ($email) {
            return drupal_strtolower(
                trim(
                    $email
                )
            );
        };

        // fix email stored in database
        $email['mail'] = $clearEmail(
            $edit['mail']
        );

        $originalMail = $clearEmail(
            user_load($account->uid)->mail
        );

        $updatedMail = $clearEmail(
            $account->mail !== $edit['mail'] ? $edit['mail'] : $account->mail
        );

        if (trim($updatedMail) !== '' && $originalMail !== $updatedMail) {
            mkbh_general_stripe_initialization();

            if ($stripeCustomerId = mkbh_general_stripe_lookup_customer_id($originalMail)) {
                if ($stripeCustomer = \Stripe\Customer::retrieve($stripeCustomerId)) {
                    $stripeCustomer->email = $updatedMail;
                    $stripeCustomer->save();

                    database()->table('mkbh_stripe_customers')
                        ->where([
                            ['customer_id', '=', $stripeCustomerId],
                            ['environment', '=', MKBH_STRIPE_ENVIRONMENT],
                        ])
                        ->update([
                            'user_id' => $account->uid,
                            'user_email' => $updatedMail
                        ]);
                }
            }
        }
    }
}

/**
 * Implements hook_preprocess_HOOK().
 */
function mkbh_general_preprocess_html(&$vars)
{
    if ($term = menu_get_object('taxonomy_term', 2)) {
        $vars['classes_array'][] = 'page-vocabulary-' . drupal_clean_css_identifier($term->vocabulary_machine_name);
        $GLOBALS['mkbh__suggestions_page'] = ['page--no-main-container'];
    }
}

/**
 * Implements hook_preprocess_HOOK().
 *
 * @param $vars
 */
function mkbh_general_preprocess_page(&$vars)
{
    // redirect old donate page to new
    if (drupal_match_path(current_path(), 'node/97')) {
        drupal_goto('node/' . MKBH_DONATE_PERSONAL_NID);
    }

    // redirect old business donate page to new
    if (drupal_match_path(current_path(), 'node/3808')) {
        drupal_goto('node/' . MKBH_DONATE_BUSINESS_NID);
    }

    //Hide title for the taxonomy view page.
    if ($term = menu_get_object('taxonomy_term', 2)) {
        $vars['title'] = '';
        $vars['theme_hook_suggestions'][] = 'page__no_main_container';
    }
}

/**
 * Implements hook_alter().
 */
function mkbh_general_page_alter(&$page)
{
    if ($term = menu_get_object('taxonomy_term', 2)) {
        $page['#post_render'][] = 'mkbh_general_add_mkbh_vocabulary_header_page_post_render';
    }
}

/**
 * Implements hook_raven_watchdog_filter_alter().
 */
function mkbh_general_raven_watchdog_filter_alter(array &$filter)
{
    if (!defined('MKBH_ENVIRONMENT') || MKBH_ENVIRONMENT !== 'live') {
        $filter['process'] = FALSE;
    }
}

/**
 * Implements hook_raven_breadcrumb_alter().
 */
function mkbh_general_raven_breadcrumb_alter(array &$breadcrumb)
{
    if (!defined('MKBH_ENVIRONMENT') || MKBH_ENVIRONMENT !== 'live') {
        $breadcrumb['process'] = FALSE;
    }

    if (Str::is('MKBHCronLogger::*', data_get($breadcrumb, 'breadcrumb.category'))) {
        return false;
    }
}

/**
 *  Fix empty tag script
 *  https://www.drupal.org/project/schema_metatag/issues/2915542
 *  Implements process_html_tag
 *
 * @param array $vars
 */
function mkbh_general_html_tag_process_last(&$vars)
{
    #This is a perfomance sensitive function as result it is needed fast testing
    if (
        $vars['element']['#tag'] === 'script'
        && !empty(($vars['element']['#value'])) && ($ltrimmed_value = ltrim($vars['element']['#value']))
        && $ltrimmed_value[0] === '{'
    ) {
        $vars['element']['#attributes']['type'] = 'application/ld+json';
    }
}

/**
 * Implements hook_variable_info().
 */
function mkbh_general_variable_info($options) {
  $variables['test_proxy'] = [
    'token' => TRUE,
    'title' => 'Test proxy',
    'description' => 'Proxy for a test request to the facebook page.',
    'type' => 'string'
  ];

  return $variables;
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function mkbh_general_form_user_login_alter(&$form, &$form_state, $form_id) {
  unset($form['name']['#description']);
  unset($form['pass']['#description']);
}

function mkbh_general_captcha($op, $captcha_type = '') {
    global $language;

    switch ($op) {
        case 'list':
            return [
                'CF Turnstile - Managed',
                'CF Turnstile - Non-interactive',
                'CF Turnstile - Invisible',
            ];

        case 'generate':
            $captcha = [];

            if (stripos($captcha_type, 'CF Turnstile') !== FALSE) {
                // Build the reCAPTCHA captcha form if site_key and secret_key are
                // configured. Captcha requires TRUE to be returned in solution.
                $captcha['solution'] = TRUE;
                $captcha['captcha_validate'] = 'mkbh_general_cf_captcha_validation';

                // As the validate callback does not depend on sid or solution, this
                // captcha type can be displayed on cached pages.
                $captcha['cacheable'] = TRUE;

                $attributes = array(
                    'class' => 'cf-turnstile',
                    'data-sitekey' => CLOUDFLARE_CAPTCHA_KEYS[$captcha_type]['site_key'],
                    'data-theme' => 'light',
                    'data-size' => 'normal',
                );

                $captcha['form']['cf_captcha_widget'] = array(
                    '#markup' => '<div' . drupal_attributes($attributes) . '></div>',
                );

                $data = array(
                    '#tag' => 'script',
                    '#value' => '',
                    '#attributes' => array(
                        'src' => url('https://challenges.cloudflare.com/turnstile/v0/api.js', array('query' => array('hl' => $language->language), 'absolute' => TRUE)),
                        'async' => 'async',
                        'defer' => 'defer',
                    ),
                );

                drupal_add_html_head($data, 'cf_captcha_api');
            }

            return $captcha;
    }

    return [];
}

function mkbh_general_cf_captcha_validation($solution, $response, $element, $form_state) {
    $captchaType = str_replace('mkbh_general/', '', $element['#captcha_type']);
    $captchaSecretKey = CLOUDFLARE_CAPTCHA_KEYS[$captchaType]['secret_key'];
    $captchaResponse = $form_state['input']['cf-turnstile-response'];

    if (mb_strlen($captchaResponse) === 0 || trim($captchaResponse) === '') {
        return FALSE;
    }

    try {
        return retry(3, function () use ($captchaSecretKey, $captchaResponse) {
            $curl = curl_init();

            curl_setopt_array($curl, [
                CURLOPT_URL => 'https://challenges.cloudflare.com/turnstile/v0/siteverify',
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_TIMEOUT => 5,
                CURLOPT_POST => true,
                CURLOPT_POSTFIELDS => http_build_query([
                    'secret' => $captchaSecretKey,
                    'response' => $captchaResponse,
                    'remoteip' => $_SERVER['REMOTE_ADDR']
                ]),
            ]);

            $response = curl_exec($curl);
            $json = json_decode($response, TRUE);

            if (!$json) {
                return FALSE;
            }

            $isSuccess = $json['success'] === TRUE;
            $errorCodes = $json['error-codes'];

            if (!$isSuccess && in_array('timeout-or-duplicate', $errorCodes)) {
                return TRUE;
            }

            return $isSuccess;
        });
    } catch (\Throwable $e) {
        return FALSE;
    }
}
