<?php

namespace Drupal\miniorange_saml;

use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Render\Markup;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Form\FormStateInterface;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;

/**
 * This file is part of miniOrange SAML plugin.
 *
 * MiniOrange SAML plugin is free software:
 * you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * miniOrange SAML plugin is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with miniOrange SAML plugin.
 * If not, see <http://www.gnu.org/licenses/>.
 */
class Utilities {

  /**
   * Generates the setup guide table for various identity providers.
   *
   * This function creates a table with links to setup guides for
   * different Identity Providers (e.g., Azure AD, ADFS, Okta, etc.)
   * and adds them to the form. It also includes links to
   * additional resources like FAQs and forums.
   *
   * @param array $form
   *   The form array to which the guide links will be added.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object to manage form submissions.
   */
  public static function spConfigGuide(array &$form, FormStateInterface $form_state) {
    $form['miniorange_idp_guide_link'] = ['#markup' => '<div class="mo_saml_sp_container_2" id="mo_guide_vt">'];
    $mo_Azure_AD                       = Markup::create('<strong><a href="' . MiniorangeSamlConstant::SETUP_GUIDE['azure_ad'] . '" class="mo_guide_text-color" target="_blank">Azure AD</a></strong>');
    $mo_ADFS                           = Markup::create('<strong><a href="' . MiniorangeSamlConstant::SETUP_GUIDE['adfs'] . '" class="mo_guide_text-color" target="_blank">ADFS</a></strong>');
    $mo_Okta                           = Markup::create('<strong><a class="mo_guide_text-color" href="' . MiniorangeSamlConstant::SETUP_GUIDE['okta'] . '" target="_blank">Okta</a></strong>');
    $mo_Google_Apps                    = Markup::create('<strong><a href="' . MiniorangeSamlConstant::SETUP_GUIDE['google_apps'] . '" class="mo_guide_text-color" target="_blank">Google Apps</a></strong>');
    $mo_Salesforce                     = Markup::create('<strong><a href="' . MiniorangeSamlConstant::SETUP_GUIDE['salesforce'] . '" class="mo_guide_text-color" target="_blank">Salesforce</a></strong>');
    $mo_miniOrange                     = Markup::create('<strong><a class="mo_guide_text-color" href="' . MiniorangeSamlConstant::SETUP_GUIDE['miniorange'] . '" target="_blank">miniOrange</a></strong>');
    $mo_PingOne                        = Markup::create('<strong><a class="mo_guide_text-color" href="' . MiniorangeSamlConstant::SETUP_GUIDE['pingone'] . '" target="_blank">PingOne</a></strong>');
    $mo_OneLogin                       = Markup::create('<strong><a href="' . MiniorangeSamlConstant::SETUP_GUIDE['onelogin'] . '" class="mo_guide_text-color" target="_blank">Onelogin</a></strong>');
    $mo_Bitium                         = Markup::create('<strong><a href="' . MiniorangeSamlConstant::SETUP_GUIDE['bitium'] . '" class="mo_guide_text-color" target="_blank">Bitium</a></strong>');
    $mo_centrify                       = Markup::create('<strong><a href="' . MiniorangeSamlConstant::SETUP_GUIDE['centrify'] . '" class="mo_guide_text-color" target="_blank">Centrify</a></strong>');
    $mo_Oracle                         = Markup::create('<strong><a href="' . MiniorangeSamlConstant::SETUP_GUIDE['oracle'] . '" class="mo_guide_text-color" target="_blank">Oracle</a></strong>');
    $mo_Shibboleth                     = Markup::create('<strong><a href="' . MiniorangeSamlConstant::SETUP_GUIDE['shibboleth2'] . '" class="mo_guide_text-color" target="_blank">Shibboleth</a></strong>');
    $mo_ping                           = Markup::create('<strong><a href="' . MiniorangeSamlConstant::SETUP_GUIDE['pingfederate'] . '" class="mo_guide_text-color" target="_blank">Ping Federate</a></strong>');
    $mo_openam                         = Markup::create('<strong><a href="' . MiniorangeSamlConstant::SETUP_GUIDE['openam'] . '" class="mo_guide_text-color" target="_blank">OpenAM</a></strong>');
    // $mo_authnevil             = Markup::create('<strong><a href="https://plugins.miniorange.com/drupal-single-sign-on-sso-using-authanvil-as-idp" class="mo_guide_text-color" target="_blank">AuthAnvil</a></strong>');
    $mo_auth0 = Markup::create('<strong><a href="' . MiniorangeSamlConstant::SETUP_GUIDE['auth0'] . '" class="mo_guide_text-color" target="_blank">auth0</a></strong>');
    // $mo_rsa                   = Markup::create('<strong><a href="https://plugins.miniorange.com/guide-for-drupal-single-sign-on-sso-using-rsa-securid-as-idp" class="mo_guide_text-color" target="_blank">RSA SecurID</a></strong>');
    $mo_Document_landing_page = Markup::create('<strong><a href="https://plugins.miniorange.com/configure-drupal-saml-single-sign-on" style="color:#fe7e00;" target="_blank">Other IDP</a></strong>');

    $mo_table_content = [
          [$mo_Azure_AD, $mo_ADFS],
          [$mo_Okta, $mo_Google_Apps],
          [$mo_Salesforce, $mo_OneLogin],
          [$mo_Oracle, $mo_Shibboleth],
          [$mo_centrify, $mo_PingOne],
          [$mo_ping, $mo_openam],
          [$mo_miniOrange, $mo_auth0],
    // array( $mo_authenvil, $mo_rsa ),.
    ];
    $header = [[
      'data' => t('Identity Provider Setup Guides'),
      'colspan' => 2,
      'style' => 'text-align:center;',
    ],
    ];

    $form['modules'] = [
      '#type' => 'table',
      '#header' => $header,
      '#rows' => $mo_table_content,
      '#attributes' => ['class' => ['mo_guide_table']],
    ];
    $form['modules'][8][1] = [
      '#markup' => $mo_Document_landing_page,
      '#wrapper_attributes' => ['colspan' => 2, 'style' => 'text-align:center;'],
    ];

    self::faq($form, $form_state);
    $form['miniorange_sp_guide_link_end'] = [
      '#markup' => '</div>',
    ];

  }

  /**
   * Adds FAQ and forum links to the form.
   *
   * This function adds a section with links to FAQs
   * and forum for user assistance.
   *
   * @param array $form
   *   The form array to which the FAQ and forum links will be added.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state object to manage form submissions.
   */
  public static function faq(&$form, &$form_state) {

    $form['miniorange_faq'] = [
      '#markup' => t('<br><div class="mo_saml_text_center"><b></b>
                          <a class="button button--small button--primary" href="https://faq.miniorange.com/kb/drupal/saml-drupal/" target="_blank">FAQs</a>
                          <b></b><a class="button button--small button--primary" href="https://forum.miniorange.com/" target="_blank">Ask questions on forum</a></div>'),
    ];
  }

  /**
   * Adds a network security advertisement to the form.
   *
   * @param array $form
   *   The form array.
   * @param array $form_state
   *   The form state.
   * @param string $moduleType
   *   The module type ('Network Security' or 'SCIM').
   *   Default is 'Network Security'.
   *
   * @return array
   *   The modified form array.
   */
  public static function advertiseNetworkSecurity(&$form, &$form_state, $moduleType = 'Network Security') {
    $mo_image = 'security.jpg';
    $mo_module = 'Web Security module';
    $mo_discription = 'Building a website is a time-consuming process that requires tremendous efforts. For smooth
                    functioning and protection from any sort of web attack appropriate security is essential and we
                     ensure to provide the best website security solutions available in the market.
                    We provide you enterprise-level security, protecting your Drupal site from hackers and malware.';
    $mo_knorMoreButton = 'https://plugins.miniorange.com/drupal-web-security-pro';
    $mo_downloadModule = 'https://www.drupal.org/project/security_login_secure';
    if ($moduleType === 'SCIM') {
      $mo_image = 'user-sync.png';
      $mo_module = 'User Provisioning (SCIM)';
      $mo_discription = 'miniOrange provides a ready to use solution for Drupal User Provisioning using SCIM (System for Cross-domain Identity Management) standard.
            This solution ensures that you can sync add, update, delete, and deactivate user operations with Drupal user list using the SCIM User Provisioner module.';
      $mo_downloadModule = 'https://www.drupal.org/project/user_provisioning';
      $mo_knorMoreButton = 'https://plugins.miniorange.com/drupal-user-provisioning-and-sync';
    }

    $base_url = \Drupal::request()->getSchemeAndHttpHost() . \Drupal::request()->getBaseUrl();
    $form['miniorange_idp_guide_link3'] = [
      '#markup' => '<div class="mo_saml_sp_container_2">
                                  ',
    ];
    $form['mo_idp_net_adv'] = [
      '#markup' => t('<form name="f1">
        <table id="idp_support" class="idp-table" style="border: none;">
        <h4 class="mo_ns_image1">Looking for a Drupal ' . $mo_module . '?</h4>
            <tr class="mo_ns_row">
                <th class="mo_ns_image1"><img
                            src="' . $base_url . '/' . \Drupal::service('extension.list.module')->getPath("miniorange_saml") . '/includes/images/' . $mo_image . '"
                            alt="miniOrange icon" height=10% width=35%>
                <br>
                        <h4>Drupal ' . $mo_module . '</h4>
                </th>
            </tr>

            <tr class="mo_ns_row">
                <td class="mo_ns_align">' . $mo_discription . ' </td>
            </tr>
            <tr class="mo_ns_row">
                <td class="mo_ns_align"><br> <a href=" ' . $mo_downloadModule . ' " target="_blank" class="button button--primary" style="float: left;">Download Plugin</a>
                       <a href="' . $mo_knorMoreButton . ' " class="button" style="float: right;"  target="_blank">Know More</a>
                </td>
            </tr>
        </table>
    </form>'),
    ];
    return $form;
  }

  /**
   * SEND SUPPORT QUERY | NEW FEATURE REQUEST | DEMO REQUEST.
   *
   * @param string $email
   *   The email address of the user sending the query.
   * @param string $phone
   *   The phone number of the user sending the query.
   * @param string $query
   *   The query message submitted by the user.
   * @param string $queryType
   *   = Support | Demo Request | New Feature Request.
   *
   * @return bool|mixed
   *   Returns the response from sending the support query,
   *   or FALSE if the query could not be sent.
   */
  public static function sendSupportQuery($email, $phone, $query, $queryType) {
    $support = new MiniorangeSamlSupport($email, $phone, $query, $queryType);
    $support_response = $support->sendSupportQuery();
    if ($support_response) {
      return $support_response;
    }
    else {
      return FALSE;
    }
  }

  /**
   * Checks if the customer is registered.
   *
   * @return bool
   *   TRUE if the customer is registered (all required values are set),
   *   otherwise FALSE.
   */
  public static function isCustomerRegistered() {
    if (\Drupal::config('miniorange_saml.settings')->get('miniorange_saml_customer_admin_email') == NULL
          || \Drupal::config('miniorange_saml.settings')->get('miniorange_saml_customer_id') == NULL
          || \Drupal::config('miniorange_saml.settings')->get('miniorange_saml_customer_admin_token') == NULL
          || \Drupal::config('miniorange_saml.settings')->get('miniorange_saml_customer_api_key') == NULL) {
      return FALSE;
    }
    return TRUE;
  }

  /**
   * Displays an error message based on the provided error details.
   *
   * @param string $error
   *   The error description.
   * @param string|null $message
   *   The error message to display.
   * @param string|null $cause
   *   The possible cause of the error.
   */
  public static function showErrorMessage($error, $message, $cause) {
    echo '<div style="font-family:Calibri;padding:0 3%;">';
    echo '<div style="color: #a94442;background-color: #f2dede;padding: 15px;margin-bottom: 20px;text-align:center;border:1px solid #E6B3B2;font-size:18pt;"> ERROR</div>
                                <div style="color: #a94442;font-size:14pt; margin-bottom:20px;"><p><strong>Error: </strong>' . $error . '</p>
                                    <p>' . $message . '</p>
                                    <p><strong>Possible Cause: </strong>' . $cause . '</p>
                                </div>
                                <div style="margin:3%;display:block;text-align:center;"></div>
                                <div style="margin:3%;display:block;text-align:center;">
                                    <input style="padding:1%;width:100px;background: #0091CD none repeat scroll 0% 0%;cursor: pointer;font-size:15px;border-width: 1px;border-style: solid;border-radius: 3px;white-space: nowrap;box-sizing: border-box;border-color: #0073AA;box-shadow: 0px 1px 0px rgba(120, 200, 230, 0.6) inset;color: #FFF;"type="button" value="Done" onClick="self.close();">
                                </div>';
    exit;
  }

  /**
   * Retrieves an array of variables for a specified class name.
   *
   * @param string $class_name
   *   The name of the class to get variables for.
   *
   * @return array
   *   The array of variables associated with the given class name.
   */
  public static function getVariableArray($class_name) {
    if ($class_name == "mo_options_enum_identity_provider") {
      $class_object = [
        'SP_Base_Url'  => 'miniorange_saml_base',
        'SP_Entity_ID' => 'miniorange_saml_base',
      ];

    }
    elseif ($class_name == "mo_options_enum_service_provider") {
      $class_object = [
        'Identity_name'          => 'miniorange_saml_idp_name',
        'Login_URL'              => 'miniorange_saml_idp_login_url',
        'Issuer'                 => 'miniorange_saml_idp_issuer',
        'Name_ID_format'         => 'miniorange_saml_nameid_format',
        'X509_certificate'       => 'miniorange_saml_idp_x509_certificate',
        'Enable_login_with_SAML' => 'miniorange_saml_enable_login',
      ];
    }
    return $class_object;
  }

  /**
   * Checks if cURL is installed and available.
   *
   * @return int
   *   Returns 1 if cURL is installed, 0 otherwise.
   */
  public static function isCurlInstalled() {
    if (in_array('curl', get_loaded_extensions())) {
      return 1;
    }
    return 0;
  }

  /**
   * Generates a unique ID by converting random bytes into a hexadecimal string.
   *
   * @return string
   *   A unique ID generated from random bytes.
   */
  public static function generateId() {
    return '_' . self::stringToHex(self::generateRandomBytes(21));
  }

  /**
   * Converts a string of bytes into a hexadecimal string.
   *
   * @param string $bytes
   *   The byte string to convert.
   *
   * @return string
   *   The hexadecimal representation of the byte string.
   */
  public static function stringToHex($bytes) {
    $ret = '';
    for ($i = 0; $i < strlen($bytes); $i++) {
      $ret .= sprintf('%02x', ord($bytes[$i]));
    }
    return $ret;
  }

  /**
   * Generates random bytes of a specified length using OpenSSL.
   *
   * @param int $length
   *   The length of the random bytes to generate.
   * @param bool $fallback
   *   Optional flag to use a fallback method if OpenSSL is not available.
   *
   * @return string
   *   The generated random bytes.
   */
  public static function generateRandomBytes($length, $fallback = TRUE) {
    return openssl_random_pseudo_bytes($length);
  }

  /**
   * Creates a SAML Authentication Request (AuthnRequest) XML.
   *
   * @param string $acsUrl
   *   The Assertion Consumer Service (ACS) URL.
   * @param string $issuer
   *   The issuer of the SAML request.
   * @param string $nameid_format
   *   The NameID format.
   * @param string $force_authn
   *   Optional flag to force authentication.
   * @param bool $rawXml
   *   Whether to return the raw XML string
   *   or URL-encoded base64-encoded string.
   *
   * @return string
   *   The generated SAML AuthnRequest as an XML string or encoded URL.
   */
  public static function createAuthnRequest($acsUrl, $issuer, $nameid_format, $force_authn = 'false', $rawXml = FALSE) {

    $requestXmlStr = '<?xml version="1.0" encoding="UTF-8"?>' .
                        '<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="' . self::generateId() .
                        '" Version="2.0" IssueInstant="' . self::generateTimestamp() . '"';

    if ($force_authn == 'true') {
      $requestXmlStr .= ' ForceAuthn="true"';
    }
    $requestXmlStr .= ' ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="' . $acsUrl .
                        '" ><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">' . $issuer . '</saml:Issuer><samlp:NameIDPolicy AllowCreate="true" Format="' . $nameid_format . '"
                        /></samlp:AuthnRequest>';
    if ($rawXml) {
      return $requestXmlStr;
    }
    $deflatedStr = gzdeflate($requestXmlStr);
    $base64EncodedStr = base64_encode($deflatedStr);
    $urlEncoded = urlencode($base64EncodedStr);
    return $urlEncoded;
  }

  /**
   * Generates a timestamp.
   *
   * This function returns a timestamp in UTC, formatted as 'Y-m-d\TH:i:s\Z'.
   * If no timestamp is provided, the current time will be used.
   *
   * @param int|null $instant
   *   The timestamp to format. If NULL, the current time will be used.
   *
   * @return string
   *   The formatted timestamp in UTC.
   */
  public static function generateTimestamp($instant = NULL) {
    if ($instant === NULL) {
      $instant = time();
    }
    return gmdate('Y-m-d\TH:i:s\Z', $instant);
  }

  /**
   * Executes an XPath query on the given XML node.
   *
   * @param \DOMNode $node
   *   The XML node to query.
   * @param string $query
   *   The XPath query to execute.
   *
   * @return \DOMNode[]
   *   The matching DOM nodes.
   */
  public static function xpQuery(\DOMNode $node, $query) {
    static $xpCache = NULL;

    if ($node->ownerDocument == NULL) {
      $doc = $node;
    }
    else {
      $doc = $node->ownerDocument;
    }
    if ($xpCache === NULL || !$xpCache->document->isSameNode($doc)) {
      $xpCache = new \DOMXPath($doc);
      $xpCache->registerNamespace('soap-env', 'http://schemas.xmlsoap.org/soap/envelope/');
      $xpCache->registerNamespace('saml_protocol', 'urn:oasis:names:tc:SAML:2.0:protocol');
      $xpCache->registerNamespace('saml_assertion', 'urn:oasis:names:tc:SAML:2.0:assertion');
      $xpCache->registerNamespace('saml_metadata', 'urn:oasis:names:tc:SAML:2.0:metadata');
      $xpCache->registerNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#');
      $xpCache->registerNamespace('xenc', 'http://www.w3.org/2001/04/xmlenc#');
    }

    $results = $xpCache->query($query, $node);
    $ret = [];
    for ($i = 0; $i < $results->length; $i++) {
      $ret[$i] = $results->item($i);
    }

    return $ret;
  }

  /**
   * Parses the NameId element from the given XML.
   *
   * @param \DOMElement $xml
   *   The XML element containing the NameId information.
   *
   * @return array
   *   An associative array containing the parsed attributes.
   */
  public static function parseNameId(\DOMElement $xml) {
    $ret = ['Value' => trim($xml->textContent)];

    foreach (['NameQualifier', 'SPNameQualifier', 'Format'] as $attr) {
      if ($xml->hasAttribute($attr)) {
        $ret[$attr] = $xml->getAttribute($attr);
      }
    }

    return $ret;
  }

  /**
   * Converts an XS DateTime string to a Unix timestamp.
   *
   * @param string $time
   *   The XS DateTime string to convert.
   *
   * @return int
   *   The Unix timestamp in UTC.
   */
  public static function xsDateTimeToTimestamp($time) {
    $matches = [];

    // We use a very strict regex to parse the timestamp.
    $regex = '/^(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)T(\\d\\d):(\\d\\d):(\\d\\d)(?:\\.\\d+)?Z$/D';
    if (preg_match($regex, $time, $matches) == 0) {
      echo sprintf("invalid SAML2 timestamp passed to xsDateTimeToTimestamp " . Xss::filter($time));
      // exit;.
    }

    // Extract the different components of the time from the  matches in the regex.
    // intval will ignore leading zeroes in the string.
    $year   = intval($matches[1]);
    $month  = intval($matches[2]);
    $day    = intval($matches[3]);
    $hour   = intval($matches[4]);
    $minute = intval($matches[5]);
    $second = intval($matches[6]);

    // We use gmmktime because the timestamp will always be given
    // in UTC.
    $ts = gmmktime($hour, $minute, $second, $month, $day, $year);

    return $ts;
  }

  /**
   * Extracts strings.
   *
   * @param \DOMElement $parent
   *   The parent XML element to search within.
   * @param string $namespaceURI
   *   The namespace URI of the target elements.
   * @param string $localName
   *   The local name of the target elements.
   *
   * @return string[]
   *   An array of text content from the matching elements.
   */
  public static function extractStrings(\DOMElement $parent, $namespaceURI, $localName) {
    $ret = [];
    for ($node = $parent->firstChild; $node !== NULL; $node = $node->nextSibling) {
      if ($node->namespaceURI !== $namespaceURI || $node->localName !== $localName) {
        continue;
      }
      $ret[] = trim($node->textContent);
    }

    return $ret;
  }

  /**
   * Validates the XML signature and checks the root element's signature.
   *
   * @param \DOMElement $root
   *   The root element of the XML document to validate.
   *
   * @return array|false
   *   An array containing the XML security signature object
   *   and certificates if valid, otherwise false.
   */
  public static function validateElement(\DOMElement $root) {
    // $data = $root->ownerDocument->saveXML($root);
    /* Create an XML security object. */
    $objXMLSecDSig = new XMLSecurityDSig();

    /* Both SAML messages and SAML assertions use the 'ID' attribute. */
    $objXMLSecDSig->idKeys[] = 'ID';

    /* Locate the XMLDSig Signature element to be used. */
    $signatureElement = self::xpQuery($root, './ds:Signature');

    if (count($signatureElement) === 0) {
      /* We don't have a signature element to validate. */
      return FALSE;
    }
    elseif (count($signatureElement) > 1) {
      echo sprintf("XMLSec: more than one signature element in root.");
      // exit;.
    }

    $signatureElement = $signatureElement[0];
    $objXMLSecDSig->sigNode = $signatureElement;

    /* Canonicalize the XMLDSig SignedInfo element in the message. */
    $objXMLSecDSig->canonicalizeSignedInfo();
    /* Validate referenced xml nodes. */
    if (!$objXMLSecDSig->validateReference()) {
      echo sprintf("XMLsec: digest validation failed");
      exit;
    }

    /* Check that $root is one of the signed nodes. */
    $rootSigned = FALSE;
    /** @var \DOMNode $signedNode */
    foreach ($objXMLSecDSig->getValidatedNodes() as $signedNode) {
      if ($signedNode->isSameNode($root)) {
        $rootSigned = TRUE;
        break;
      }
      elseif ($root->parentNode instanceof \DOMElement && $signedNode->isSameNode($root->ownerDocument)) {
        /* $root is the root element of a signed document. */
        $rootSigned = TRUE;
        break;
      }
    }

    if (!$rootSigned) {
      echo sprintf("XMLSec: The root element is not signed.");
      exit;
    }

    /* Now we extract all available X509 certificates in the signature element. */
    $certificates = [];
    foreach (self::xpQuery($signatureElement, './ds:KeyInfo/ds:X509Data/ds:X509Certificate') as $certNode) {
      $certData = trim($certNode->textContent);
      $certData = str_replace(["\r", "\n", "\t", ' '], '', $certData);
      $certificates[] = $certData;
    }

    $ret = [
      'Signature' => $objXMLSecDSig,
      'Certificates' => $certificates,
    ];
    return $ret;
  }

  /**
   * Validates the signature of the provided XML data.
   *
   * @param array $info
   *   The information containing the Signature element.
   * @param XMLSecurityKey $key
   *   The key used for signature validation.
   */
  public static function validateSignature(array $info, XMLSecurityKey $key) {
    /** @var XMLSecurityDSig $objXMLSecDSig */
    $objXMLSecDSig = $info['Signature'];

    $sigMethod = self::xpQuery($objXMLSecDSig->sigNode, './ds:SignedInfo/ds:SignatureMethod');
    if (empty($sigMethod)) {
      echo sprintf('Missing SignatureMethod element');
      // exit();
    }
    $sigMethod = $sigMethod[0];
    if (!$sigMethod->hasAttribute('Algorithm')) {
      echo sprintf('Missing Algorithm-attribute on SignatureMethod element.');
      // exit;.
    }
    $algo = $sigMethod->getAttribute('Algorithm');

    if ($key->type === XMLSecurityKey::RSA_SHA1 && $algo !== $key->type) {
      $key = self::castKey($key, $algo);
    }

    /* Check the signature. */
    if (!$objXMLSecDSig->verify($key)) {
      echo sprintf('Unable to validate Signature');
      // exit;.
    }
  }

  /**
   * Casts the provided key to the specified algorithm and type.
   *
   * @param XMLSecurityKey $key
   *   The key to be casted.
   * @param string $algorithm
   *   The algorithm to cast the key to.
   * @param string $type
   *   The type of the key (default is 'public').
   *
   * @return XMLSecurityKey
   *   The casted key.
   */
  public static function castKey(XMLSecurityKey $key, $algorithm, $type = 'public') {
    // Do nothing if algorithm is already the type of the key.
    if ($key->type === $algorithm) {
      return $key;
    }

    $keyInfo = openssl_pkey_get_details($key->key);
    if ($keyInfo === FALSE) {
      echo sprintf('Unable to get key details from XMLSecurityKey.');
      // exit;.
    }
    if (!isset($keyInfo['key'])) {
      echo sprintf('Missing key in public key details.');
      // exit;.
    }

    $newKey = new XMLSecurityKey($algorithm, ['type' => $type]);
    $newKey->loadKey($keyInfo['key']);

    return $newKey;
  }

  /**
   * Processes and validates the SAML response.
   *
   * @param string $currentURL
   *   The current URL to compare the response destination.
   * @param string $certFingerprint
   *   The fingerprint of the certificate used for signing.
   * @param array $signatureData
   *   The signature data containing certificates.
   * @param Saml2Response $response
   *   The SAML2 response to be validated.
   *
   * @return bool
   *   Returns true if the response is valid, false otherwise.
   */
  public static function processResponse($currentURL, $certFingerprint, $signatureData, Saml2Response $response) {
    $resCert = $signatureData['Certificates'][0];
    /* Validate Response-element destination. */
    $msgDestination = $response->getDestination();
    if ($msgDestination !== NULL && $msgDestination !== $currentURL) {
      echo sprintf('Destination in response doesn\'t match the current URL. Destination is "' .
      XSS::filter($msgDestination) . '", current URL is "' . XSS::filter($currentURL) . '".');
      // exit;.
    }

    $responseSigned = self::checkSign($certFingerprint, $signatureData, $resCert);

    /* Returning boolean $responseSigned */
    return $responseSigned;
  }

  /**
   * Checks the validity of the SAML response signature.
   *
   * @param string $certFingerprint
   *   The fingerprint of the certificate used for signing.
   * @param array $signatureData
   *   The signature data containing certificates.
   * @param string $resCert
   *   The certificate used to validate the signature.
   *
   * @return bool
   *   Returns true if the signature is valid, false otherwise.
   */
  public static function checkSign($certFingerprint, $signatureData, $resCert) {
    $certificates = $signatureData['Certificates'];

    if (count($certificates) === 0) {
      return FALSE;
    }
    else {
      $fpArray = [];
      $fpArray[] = $certFingerprint;
      $pemCert = self::findCertificate($fpArray, $certificates, $resCert);
    }

    $lastException = NULL;

    $key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, ['type' => 'public']);
    $key->loadKey($pemCert);

    try {
      /* Make sure that we have a valid signature */
      self::validateSignature($signatureData, $key);
      return TRUE;
    }
    catch (Exception $e) {
      $lastException = $e;
    }

    /* We were unable to validate the signature with any of our keys. */
    if ($lastException !== NULL) {
      throw $lastException;
    }
    else {
      return FALSE;
    }
  }

  /**
   * Validates the issuer and audience of a SAML response.
   *
   * @param Saml2Response $samlResponse
   *   The SAML response to validate.
   * @param string $spEntityId
   *   The entity ID of the Service Provider.
   * @param string $issuerToValidateAgainst
   *   The expected issuer to validate against.
   *
   * @return bool
   *   Returns true if both the issuer and audience are valid, otherwise false.
   */
  public static function validateIssuerAndAudience($samlResponse, $spEntityId, $issuerToValidateAgainst) {
    $issuer = current($samlResponse->getAssertions())->getIssuer();
    $audience = current(current($samlResponse->getAssertions())->getValidAudiences());
    if (strcmp($issuerToValidateAgainst, $issuer) === 0) {
      if (strcmp($audience, self::getIssuer()) === 0) {
        return TRUE;
      }
      else {
        if (array_key_exists('RelayState', $_REQUEST) && ($_REQUEST['RelayState'] == 'testValidate')) {
          self::testConfigurationLogs('Invalid Audience URI');
          echo '<div style="font-family:Calibri;padding:0 3%;">';
          echo '<div style="color: #a94442;background-color: #f2dede;padding: 15px;margin-bottom: 20px;text-align:center;border:1px solid #E6B3B2;font-size:18pt;"> ERROR</div>
                    <div style="color: #a94442;font-size:14pt; margin-bottom:20px;"><p><strong>Error: </strong>Invalid Audience URI.</p>
                    <p>Please contact your administrator and report the following error:</p>
                    <p><strong>Possible Cause: </strong>The value of \'Audience URI\' field on Identity Provider\'s side is incorrect</p>
                    <p>Expected one of the Audiences to be: ' . $spEntityId . '<p>
                    </div>
                    <div style="margin:3%;display:block;text-align:center;">
                    <div style="margin:3%;display:block;text-align:center;"><input style="padding:1%;width:100px;background: #0091CD none repeat scroll 0% 0%;cursor: pointer;font-size:15px;border-width: 1px;border-style: solid;border-radius: 3px;white-space: nowrap;box-sizing: border-box;border-color: #0073AA;box-shadow: 0px 1px 0px rgba(120, 200, 230, 0.6) inset;color: #FFF;"type="button" value="Done" onClick="self.close();"></div>';
        }
        else {
          Utilities::ssoLogs('Invalid Audience URI');
          echo '<div style="color: #a94442;font-size:14pt; margin-bottom:20px;"><p><b>Error: </b>We could not sign you in. Please contact your Administrator.</p></div>';
        }
        exit;
      }
    }
    else {
      if (array_key_exists('RelayState', $_REQUEST) && ($_REQUEST['RelayState'] == 'testValidate')) {
        self::testConfigurationLogs('Issuer cannot be verified');
        echo '<div style="font-family:Calibri;padding:0 3%;">';
        echo '<div style="color: #a94442;background-color: #f2dede;padding: 15px;margin-bottom: 20px;text-align:center;border:1px solid #E6B3B2;font-size:18pt;"> ERROR</div>
					<div style="color: #a94442;font-size:14pt; margin-bottom:20px;"><p><strong>Error: </strong>Issuer cannot be verified.</p>
					<p>Please contact your administrator and report the following error:</p>
					<p><strong>Possible Cause: </strong>The value in \'IdP Entity ID or Issuer\' field in Service Provider Settings is incorrect</p>
					<p><strong>Expected Entity ID: </strong>' . $issuer . '<p>
					<p><strong>Entity ID Found: </strong>' . Xss::filter($issuerToValidateAgainst) . '</p>
					</div>
					<div style="margin:3%;display:block;text-align:center;">
					<div style="margin:3%;display:block;text-align:center;"><input style="padding:1%;width:100px;background: #0091CD none repeat scroll 0% 0%;cursor: pointer;font-size:15px;border-width: 1px;border-style: solid;border-radius: 3px;white-space: nowrap;box-sizing: border-box;border-color: #0073AA;box-shadow: 0px 1px 0px rgba(120, 200, 230, 0.6) inset;color: #FFF;"type="button" value="Done" onClick="self.close();"></div>';
      }
      else {
        Utilities::ssoLogs('Issuer cannot be verified');
        echo '<div style="color: #a94442;font-size:14pt; margin-bottom:20px;"><p><b>Error: </b>We could not sign you in. Please contact your Administrator.</p></div>';
      }
      exit;
    }
  }

  /**
   * Searches for a certificate.
   *
   * @param array $certFingerprints
   *   The list of certificate fingerprints to match against.
   * @param array $certificates
   *   The list of certificates to search through.
   * @param string $resCert
   *   The certificate whose fingerprint is being matched.
   *
   * @return string|null
   *   The PEM format certificate if a match is found,
   *   or null if no match is found.
   */
  private static function findCertificate(array $certFingerprints, array $certificates, $resCert) {

    $resCert = Utilities::sanitizeCertificate($resCert);
    $candidates = [];

    foreach ($certificates as $cert) {
      $fp = strtolower(sha1(base64_decode($cert)));
      if (!in_array($fp, $certFingerprints, TRUE)) {
        $candidates[] = $fp;
        continue;
      }

      /* We have found a matching fingerprint. */
      $pem = "-----BEGIN CERTIFICATE-----\n" .
      chunk_split($cert, 64) .
      "-----END CERTIFICATE-----\n";

      return $pem;
    }
    if (array_key_exists('RelayState', $_REQUEST) && ($_REQUEST['RelayState'] == 'testValidate')) {
      self::testConfigurationLogs('Unable to find a certificate matching the configured fingerprint');
      echo '<div style="font-family:Calibri;padding:0 3%;">';
      echo '<div style="color: #a94442;background-color: #f2dede;padding: 15px;margin-bottom: 20px;text-align:center;border:1px solid #E6B3B2;font-size:18pt;"> ERROR</div>
				<div style="color: #a94442;font-size:14pt; margin-bottom:20px;"><p><strong>Error: </strong>Unable to find a certificate matching the configured fingerprint.</p>
				<p><strong>Possible Cause: </strong>Content of \'X.509 Certificate\' field in Service Provider Settings is incorrect</p>
				<p><b>Expected value:</b>' . $resCert . '</p>';
      echo str_repeat('&nbsp;', 15);
      echo '</div>
				<div style="margin:3%;display:block;text-align:center;">
				<form action="index.php">
				<div style="margin:3%;display:block;text-align:center;"><input style="padding:1%;width:100px;background: #0091CD none repeat scroll 0% 0%;cursor: pointer;font-size:15px;border-width: 1px;border-style: solid;border-radius: 3px;white-space: nowrap;box-sizing: border-box;border-color: #0073AA;box-shadow: 0px 1px 0px rgba(120, 200, 230, 0.6) inset;color: #FFF;"type="button" value="Done" onClick="self.close();"></div>';
    }
    else {
      Utilities::ssoLogs('Unable to find a certificate matching the configured fingerprint');
      echo ' <div style="color: #a94442;font-size:14pt; margin-bottom:20px;"><p><b>Error: </b>We could not sign you in. Please contact your Administrator.</p></div>';
    }
    exit;
  }

  /**
   * Sanitizes and formats the provided certificate.
   *
   * @param string $certificate
   *   The certificate to sanitize.
   * @param bool $getRaw
   *   If true, returns the raw sanitized certificate.
   *   If false, returns the formatted certificate.
   *
   * @return string
   *   The sanitized (and optionally formatted) certificate.
   */
  public static function sanitizeCertificate($certificate, $getRaw = FALSE) {
    $certificate = preg_replace("/[\r\n]+/", "", $certificate);
    $certificate = str_replace("-", "", $certificate);
    $certificate = str_replace("BEGIN CERTIFICATE", "", $certificate);
    $certificate = str_replace("END CERTIFICATE", "", $certificate);
    $certificate = str_replace(" ", "", $certificate);
    if ($getRaw) {
      return $certificate;
    }
    $certificate = chunk_split($certificate, 64, "\r\n");
    $certificate = "-----BEGIN CERTIFICATE-----\r\n" . $certificate . "-----END CERTIFICATE-----";
    return $certificate;
  }

  /**
   * Displays a SAML request.
   *
   * @param string $samlRequestResponceXML
   *   The SAML request or response XML to be displayed.
   * @param string $type
   *   Specifies whether the content
   *   is a 'displaySAMLRequest' or 'displaySAMLResponse'.
   */
  public static function printSamlRequest($samlRequestResponceXML, $type) {
    header("Content-Type: text/html");
    $doc = new \DOMDocument();
    $doc->preserveWhiteSpace = FALSE;
    $doc->formatOutput = TRUE;
    $doc->loadXML($samlRequestResponceXML);
    if ($type == 'displaySAMLRequest') {
      $show_value = 'SAML Request';
    }
    else {
      $show_value = 'SAML Response';
    }
    $out = $doc->saveXML();

    $out1 = htmlentities($out);
    $out1 = rtrim($out1);

    $xml = simplexml_load_string($out);

    $json = json_encode($xml);

    $array = json_decode($json);

    $url = \Drupal::service('extension.list.module')->getPath('miniorange_saml') . '/css/miniorange_saml.module.css';
    $jsurl = \Drupal::service('extension.list.module')->getPath('miniorange_saml') . '/js/showSAMLResponse.js';

    echo '<link rel=\'stylesheet\' id=\'mo_saml_admin_settings_style-css\'  href=\'' . $url . '\' type=\'text/css\' media=\'all\' />
            <script src=\'' . $jsurl . '\'></script>
			<div class="mo-display-logs" ><p type="text"   id="SAML_type">' . $show_value . '</p></div>

			<div type="text" id="SAML_display" class="mo-display-block"><pre class=\'brush: xml;\'>' . $out1 . '</pre></div>
			<br>
			<div style="margin:3%;display:block;text-align:center;">

			<div style="margin:3%;display:block;text-align:center;" >

            </div>
			<button id="copy" onclick="copyDivToClipboard()"  style="padding:1%;width:100px;background: #0091CD none repeat scroll 0% 0%;cursor: pointer;font-size:15px;border-width: 1px;border-style: solid;border-radius: 3px;white-space: nowrap;box-sizing: border-box;border-color: #0073AA;box-shadow: 0px 1px 0px rgba(120, 200, 230, 0.6) inset;color: #FFF;" >Copy</button>
			&nbsp;
               <button id="dwn_btn" onclick="test_download()" style="padding:1%;width:100px;background: #0091CD none repeat scroll 0% 0%;cursor: pointer;font-size:15px;border-width: 1px;border-style: solid;border-radius: 3px;white-space: nowrap;box-sizing: border-box;border-color: #0073AA;box-shadow: 0px 1px 0px rgba(120, 200, 230, 0.6) inset;color: #FFF;">Download</button>
			</div>
			</div>

			';

    exit;
  }

  /**
   * Handles the customer setup process.
   *
   * @param string $username
   *   The customer's username.
   * @param string $phone
   *   The customer's phone number.
   * @param string $password
   *   The customer's password.
   * @param bool $login
   *   (Optional) Indicates if the operation is a login attempt.
   * @param bool $called_from_popup
   *   (Optional) Indicates if the request is coming from a popup.
   * @param string|null $payment_plan
   *   (Optional) The payment plan associated with the registration.
   */
  public static function customerSetupSubmit($username, $phone, $password, $login = FALSE, $called_from_popup = FALSE, $payment_plan = NULL) {

    $base_url = \Drupal::request()->getSchemeAndHttpHost() . \Drupal::request()->getBaseUrl();
    $customer_config = new MiniorangeSamlCustomer($username, $phone, $password, NULL);
    $check_customer_response = json_decode($customer_config->checkCustomer());
    $db_config = \Drupal::configFactory()->getEditable('miniorange_saml.settings');

    if ($check_customer_response->status == 'TRANSACTION_LIMIT_EXCEEDED') {
      if ($called_from_popup == TRUE) {
        MiniorangeSamlSpRegistration::registerData(TRUE);
      }
      else {
        \Drupal::messenger()->addMessage(t('An error has been occured. Please Try after some time or <a href="mailto:' . MiniorangeSamlConstant::SUPPORT_EMAIL . '"><i>contact us</i></a>.'), 'error');
        return;
      }
    }
    if ($check_customer_response->status == 'CUSTOMER_NOT_FOUND') {
      if ($login == TRUE && $called_from_popup == FALSE) {
        \Drupal::messenger()->addMessage(t('The account with username <i>' . $username . '</i> does not exist.'), 'error');
        return;
      }
      $db_config->set('miniorange_saml_customer_admin_email', $username)->save();
      $db_config->set('miniorange_saml_customer_admin_phone', $phone)->save();
      $db_config->set('miniorange_saml_customer_admin_password', $password)->save();
      $send_otp_response = json_decode($customer_config->sendOtp());

      if ($send_otp_response->status == 'SUCCESS') {
        $db_config->set('miniorange_saml_tx_id', $send_otp_response->txId)->save();
        $db_config->set('miniorange_saml_status', 'VALIDATE_OTP')->save();

        if ($called_from_popup == TRUE) {
          MiniorangeSamlSpRegistration::miniorangeOtp(FALSE, FALSE, FALSE);
        }
        else {
          \Drupal::messenger()->addMessage(t('Verify email address by entering the passcode sent to @username', [
            '@username' => $username,
          ]));
        }
      }
      else {
        if ($called_from_popup == TRUE) {
          MiniorangeSamlSpRegistration::registerData(TRUE);
        }
        else {
          \Drupal::messenger()->addMessage(t('An error has been occured. Please try after some time.'), 'error');
        }
      }
    }
    elseif ($check_customer_response->status == 'CURL_ERROR') {
      if ($called_from_popup == TRUE) {
        MiniorangeSamlSpRegistration::registerData(TRUE);
      }
      else {
        \Drupal::messenger()->addMessage(t('cURL is not enabled. Please enable cURL'), 'error');
        return;
      }
    }
    else {
      $customer_keys_response = json_decode($customer_config->getCustomerKeys());

      if (json_last_error() == JSON_ERROR_NONE) {
        $db_config->set('miniorange_saml_customer_id', $customer_keys_response->id)->save();
        $db_config->set('miniorange_saml_customer_admin_token', $customer_keys_response->token)->save();
        $db_config->set('miniorange_saml_customer_admin_email', $username)->save();
        $db_config->set('miniorange_saml_customer_admin_phone', $phone)->save();
        $db_config->set('miniorange_saml_customer_api_key', $customer_keys_response->apiKey)->save();
        $db_config->set('miniorange_saml_status', 'PLUGIN_CONFIGURATION')->save();

        if ($called_from_popup == TRUE) {
          $redirect_url = \Drupal::config('miniorange_saml.settings')->get('redirect_plan_after_registration_' . $payment_plan);
          $redirect_url = str_replace('none', $username, $redirect_url);
        }
        else {
          \Drupal::messenger()->addMessage('Successfully retrieved your account.');
        }
      }
      else {
        if ($called_from_popup == TRUE) {
          MiniorangeSamlSpRegistration::registerData(FALSE, TRUE);
        }
        else {
          \Drupal::messenger()->addMessage(t('Invalid credentials'), 'error');
          return;
        }
      }
    }
  }

  /**
   * Resets customer setup configuration.
   *
   * @param bool $called_from_popup
   *   (Optional) Indicates if the request is coming from a popup.
   */
  public static function samlBack($called_from_popup = FALSE) {
    $db_edit = \Drupal::configFactory()->getEditable('miniorange_saml.settings');
    $db_edit->set('miniorange_saml_status', 'CUSTOMER_SETUP')->save();
    $db_edit->clear('miniorange_saml_customer_admin_email')->save();
    $db_edit->clear('miniorange_saml_customer_admin_phone')->save();
    $db_edit->clear('miniorange_saml_tx_id')->save();

    if ($called_from_popup == TRUE) {
      self::redirectToLicensing();
    }
    else {
      \Drupal::messenger()->addMessage(t('Register/Login with your miniOrange Account'), 'status');
    }
  }

  /**
   * Redirects the user to the licensing page.
   */
  public static function redirectToLicensing() {
    $base_url = \Drupal::request()->getSchemeAndHttpHost() . \Drupal::request()->getBaseUrl();
    $redirect_url = $base_url . MiniorangeSamlConstant::LICENSING_TAB_URL;
    $response = new RedirectResponse($redirect_url);
    $response->send();
  }

  /**
   * Checks if the Service Provider (SP) is properly configured in the system.
   *
   * @param string $relay_state
   *   The relay state.
   */
  public static function isSpConfigured($relay_state) {
    $db_config = \Drupal::config('miniorange_saml.settings');
    if (empty($db_config->get('miniorange_saml_idp_name')) || empty($db_config->get('miniorange_saml_idp_issuer')) || empty($db_config->get('miniorange_saml_idp_login_url'))) {
      if ($relay_state == 'testValidate') {
        self::testConfigurationLogs('Service Provider is not configured');
      }
      else {
        Utilities::ssoLogs('Service Provider is not configured');
      }
      self::moShowErrorMessage('Service Provider is not configured.');
    }

  }

  /**
   * Retrieves the current version of Drupal core.
   *
   * @return string
   *   The version of Drupal core.
   */
  public static function miniorangeGetDrupalCoreVersion() {
    return \DRUPAL::VERSION;
  }

  /**
   * Displays an error message in a styled HTML format.
   *
   * @param string $moErrorMessage
   *   The error message to be displayed.
   */
  public static function moShowErrorMessage($moErrorMessage) {
    echo '<div style="font-family:Calibri;padding:0 3%;">';
    echo '<div style="color: #a94442;background-color: #f2dede;padding: 15px;margin-bottom: 20px;text-align:center;border:1px solid #E6B3B2;font-size:18pt;"> ERROR</div>
                    <div style="color: #a94442;font-size:14pt; margin-bottom:20px;"><p><strong>Error: </strong>' . $moErrorMessage . '</p></div>
                    <div style="margin:3%;display:block;text-align:center;">
                    <div style="margin:3%;display:block;text-align:center;"><input style="padding:1%;width:100px;background: #0091CD none repeat scroll 0% 0%;cursor: pointer;font-size:15px;border-width: 1px;border-style: solid;border-radius: 3px;white-space: nowrap;box-sizing: border-box;border-color: #0073AA;box-shadow: 0px 1px 0px rgba(120, 200, 230, 0.6) inset;color: #FFF;"type="button" value="Done" onClick="self.close();"></div>';
    exit();
  }

  /**
   * Checks if the current environment is running in CLI mode.
   *
   * @return bool
   *   TRUE if running in CLI mode, FALSE otherwise.
   */
  public static function drupalIsCli() {
    $server = \Drupal::request()->server;
    $server_software = $server->get('SERVER_SOFTWARE');
    $server_argc = $server->get('argc');

    if (!isset($server_software) && (php_sapi_name() == 'cli' || (is_numeric($server_argc) && $server_argc > 0))) {
      return TRUE;
    }
    else {
      return FALSE;
    }
  }

  /**
   * Retrieves the base URL.
   *
   * @return string
   *   The base URL.
   */
  public static function getBaseUrl() {
    $base_url = \Drupal::request()->getSchemeAndHttpHost() . \Drupal::request()->getBaseUrl();
    $saved_base_url = \Drupal::config('miniorange_saml.settings')->get('miniorange_saml_base_url');
    return isset($saved_base_url) && !empty($saved_base_url) ? $saved_base_url : $base_url;
  }

  /**
   * Retrieves the issuer URL.
   *
   * @return string
   *   The issuer URL.
   */
  public static function getIssuer() {
    $saved_issuer = \Drupal::config('miniorange_saml.settings')->get('miniorange_saml_entity_id');
    return isset($saved_issuer) && !empty($saved_issuer) ? $saved_issuer : self::getBaseUrl();
  }

  /**
   * Retrieves the Acs URL.
   *
   * @return string
   *   The Acs URL.
   */
  public static function getAcsUrl() {
    $b_url = self::getBaseUrl();
    return substr($b_url, -1) == '/' ? $b_url . 'samlassertion' : $b_url . '/samlassertion';
  }

  /**
   * Logs an error message when a configuration test fails.
   *
   * @param string $error
   *   The error message to log.
   */
  public static function testConfigurationLogs(string $error) {
    $config_edit = \Drupal::configFactory()->getEditable('miniorange_saml.settings');
    $config_edit->set('miniorange_saml_test_configuration', 'Failed')->save();
    $config_edit->set('miniorange_saml_test_configuration_error', $error)->save();
  }

  /**
   * Logs SSO errors to the configuration.
   *
   * This function updates the 'miniorange_saml.settings' configuration to
   * record the SSO failure status and the specific error message.
   *
   * @param string $error
   *   The error message to log.
   */
  public static function ssoLogs(string $error) {
    $config_edit = \Drupal::configFactory()->getEditable('miniorange_saml.settings');
    $config_edit->set('miniorange_saml_sso_tried', 'Failed')->save();
    $config_edit->set('miniorange_saml_sso_error', $error)->save();
  }

  /**
   * Retrieves a list of custom user fields.
   *
   * @return array
   *   An associative array of custom user fields.
   */
  public static function customUserFields() {
    $entity_field_manager = \Drupal::service('entity_field.manager');
    $entities = $entity_field_manager->getFieldDefinitions('user', 'user');
    $custom_fields = ['' => '- Select Attribute Value -'];
    foreach ($entities as $key => $value) {
      if (!in_array($key, ['uid', 'uuid', 'name', 'mail', 'pass', 'roles', 'user_picture'])) {
        $custom_fields[$key] = $key;
      }
    }
    return $custom_fields;
  }

  /**
   * Generates a modal dialog based on the support query result.
   *
   * @param string $supportResponse
   *   The response status of the support query.
   * @param string $email
   *   The email address submitted in the query.
   * @param bool $isTrial
   *   Indicates if the user is on a trial.
   *
   * @return \Drupal\Core\Ajax\OpenModalDialogCommand
   *   The command to open a modal with the appropriate message.
   */
  public static function getModalFormAfterQuery($supportResponse, $email, $isTrial) {
    $trialMessage = t('Thank you for reaching out to us! We are processing your request and you will soon receive details on %email.', ['%email' => $email]);
    $queryMessage = t('Thanks for getting in touch! We will get back to you shortly.');
    $successMessage = $isTrial ? $trialMessage : $queryMessage;

    if ($supportResponse === MiniorangeSamlConstant::QUERY_SUCCESS) {
      $message = [
        '#type' => 'item',
        '#markup' => $successMessage,
      ];
      $ajax_form = new OpenModalDialogCommand('Thank you!', $message, ['width' => '50%']);
    }
    elseif ($supportResponse === MiniorangeSamlConstant::QUERY_INVALID_EMAIL) {
      $message = [
        '#type' => 'item',
        '#markup' => t('The email address entered %email, possibly invalid.
                        We discourage the use of disposable emails, please try again with a valid email.
                        You can also reach out to us at <a href="mailto:drupalsupport@xecurify.com">drupalsupport@xecurify.com</a>.',
                         ['%email' => $email]),
      ];
      $ajax_form = new OpenModalDialogCommand('⚠️ Invalid Email', $message, ['width' => '50%']);
    }
    else {
      $error = [
        '#type' => 'item',
        '#markup' => t('Error submitting the support query. Please send us your query at
                             <a href="mailto:drupalsupport@xecurify.com">
                             drupalsupport@xecurify.com</a>.'),
      ];
      $ajax_form = new OpenModalDialogCommand('⚠️ Error', $error, ['width' => '50%']);
    }

    return $ajax_form;
  }

  /**
   * Retrieves the current timezone formatted with its UTC offset.
   *
   * @return string
   *   The formatted timezone string.
   */
  public static function getFormattedTimezone() {
    $defaultTimezone = date_default_timezone_get();
    $differenceToUtc = date('P', time());
    return "$defaultTimezone (UTC $differenceToUtc)";
  }

  /**
   * Retrieves the metadata validation expiration time.
   *
   * @return string
   *   The formatted expiration time.
   */
  public static function getMetadataValidationTime() {
    $interval = time() + MiniorangeSamlConstant::TIME_VALID;
    return gmdate('Y-m-d\TH:i:s\Z', $interval);
  }

  /**
   * Get the names of user roles.
   *
   * @param bool $anonymous
   *   (optional) Set this to TRUE to include the 'anonymous' role.
   *   Defaults to FALSE.
   * @param bool $authenticated
   *   (optional) Set this to TRUE to include the 'authenticated' role.
   *   Defaults to FALSE.
   * @param null $permission
   *   (optional) A string containing a permission.
   *
   * @return array
   *   An associative array with the role id as the key
   *   and the role name as value.
   */
  public static function getUserRoles($anonymous = FALSE, $authenticated = FALSE, $permission = NULL) {
    $allRoles = Role::loadMultiple();
    $roles = [];

    if (!$anonymous) {
      unset($allRoles[RoleInterface::ANONYMOUS_ID]);
    }

    if (!$authenticated) {
      unset($allRoles[RoleInterface::AUTHENTICATED_ID]);
    }

    if (!empty($permission)) {
      $allRoles = array_filter($allRoles, function ($role) use ($permission) {
        return $role->hasPermission($permission);
      });
    }

    foreach ($allRoles as $role) {
      $roles[$role->id()] = $role->label();
    }

    return $roles;
  }

}
