<?php

/**
 *  This class fixes cookies that do not have Secret;SameSite=None;Partitioned set.
 *
 *  See
 *  https://www.advanced-iframe.com/advanced-iframe/advanced-iframe-pro-demo/how-to-use-the-samesite-cookie-fix
 *  how to use this class directly.
 *
 *  $filter = 'ALL' - all cookies are modified
 *  $filter = 'cookie1,cookie2' - cookie1 and cookie2 are modified
 *  $filter = 'cookie1,cookie2,parameter=aiFixSameSite' - cookie1 and cookie2 are only modified if the get parameter
 *            is set to true. e.g. ?aiFixSameSite=true This setting has to be at the end and is saved in a
 *            session cookie. This makes it possible to modify the cookies only if it is in an iframe
 *
 *  Debugging: You can enable the debug mode if you call you page with ?aiDebugCookies=true
 *  or add aiDebugCookies to your $filter or set the false in line 18 to true.
 */
class AdvancedIframeCookie {
  static function addCookieSameSite($filter) {
    // Enable this to debug the outcome on top of your page.
    $debug = (isset($_GET['aiDebugCookies'])) || AdvancedIframeCookie::aiContains($filter, "aiDebugCookies");
    $first = true;
    $enableSameSiteFix = !empty($filter);
    $cookieName = '';
    $filterArray = array();

    // Find the get parameter name and fill the filter array with the cookie names.
    foreach (explode(',', $filter) as $value) {
      if (AdvancedIframeCookie::aiContains($value, "=")) {
        $value_array = explode('=', $value);
        $cookieName = trim($value_array[1]);
      } else {
        $filterArray[] = trim($value);
      }
    }

    // GET parameter to enable the fix only when cookieName is set
    if ($cookieName !== '' && isset($_GET[$cookieName])) {
      if ($_GET[$cookieName] === 'true') {
        header("Set-Cookie: " . $cookieName . "=1; path=/; Secure; SameSite=None; Partitioned;");
        $enableSameSiteFix = true;
      } else {
        header("Set-Cookie: " . $cookieName . "=1; expires=0; path=/; Secure; SameSite=None; Partitioned");
      }
    }

    if (isset($_COOKIE['cookieName'])) {
      $enableSameSiteFix = true;
    }

    // This is a check if the page is in an iframe - not supported yet by all browsers!
    // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Dest ->
    // Therefore the get parameter defined above should be used until then.
    // if( isset($_SERVER['HTTP_SEC_FETCH_DEST']) && $_SERVER['HTTP_SEC_FETCH_DEST'] == 'iframe' ) {}

    if ($enableSameSiteFix && AdvancedIframeCookie::isSecure()) {
      foreach (headers_list() as $value) {
        if (AdvancedIframeCookie::aiStartsWith($value, "Set-Cookie")) {
          if (AdvancedIframeCookie::isSameSiteCookie($value, $filterArray)) {
            if (!AdvancedIframeCookie::aiContains($value, "Secure")) {
              $value .= '; Secure';
            }
            if (!AdvancedIframeCookie::aiContains($value, "SameSite")) {
              $value .= '; SameSite=None';
            }
            if (!AdvancedIframeCookie::aiContains($value, "Partitioned")) {
              $value .= '; Partitioned';
            }
          }
          // The first cookie replaces all existing ones
          // https://www.php.net/manual/de/function.header.php -> See replace
          // All the others are appended
          if ($first) {
            header($value);
            $first = false;
          } else {
            header($value, false);
          }
        }
      }
      if ($debug) {
        echo 'SameSite cookie fix applied...<br>';
      }
    } elseif ($debug) {
      echo 'SameSite cookie fix is not applied...<br>';
    }


    if ($debug) {
      echo 'headers list: <br>';
      echo '<pre style="white-space:pre-wrap;overflow:hidden;">';
      print_r(headers_list());
      echo '</pre>';
    }
  }

  static function isSameSiteCookie($cookie, $filterArray) {
    foreach ($filterArray as $value) {
      if ($value === 'ALL' || AdvancedIframeCookie::aiContains($cookie, trim($value) . '=')) {
        return true;
      }
    }
    return false;
  }

  static function aiStartsWith($haystack, $needle) {
    return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== false;
  }

  static function aiContains($str, $substr) {
    return strpos($str, $substr) !== false;
  }

  /**
   * Duplicate of AdvancedIframeHelper::isSecure to be able to use this class standalone.
   */
  static function isSecure() {
    return (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off')
      || $_SERVER['SERVER_PORT'] == 443;
  }
}