<?php

use Manza\CharityRight\Etc;
use Manza\CharityRight\Mappers\AppealMapper;
use Manza\CharityRight\Theme;

require_once get_stylesheet_directory() . "/vendor/autoload.php";

// List of files which will be included.
// You can append ! then a comma limited list of class names which must exist to that file.
$vlTailwind_includes = array(
	'/setup.php',					                    // Some theme setup functions
	'/template-tags.php',			                    // Custom template tags
	'/pagination.php',				                    // Pagination
	'/sidebars.php',				                    // Sidebars
	'/scripts-and-styles.php',		                    // enqueue scripts and styles
	'/page-titles.php', 			                    // page title function
	'/breadcrumbs.php', 			                    // breadcrumbs trail function
	'/acf.php', 					                    // ACF settings
	'/menus.php', 					                    // MENU inits
    '/ajax.php',                                        // Custom AJAX returning.
    '/blocks.php',                                      // The custom block registry.
	'/cpt.php',											// Custom post type declarations
    '/blog-import.php',                                 // Blog post importer. NOTE: Remove this once we're deployed live and happy.
    '/dashboard.php',                                   // Configure the WP dashboard.
    '/images.php',                                      // Configure media library images.
	"/ajax-campaigns.php",								// custom AJAX campaign loader
	"/ajax-blogs.php",									// custom AJAX blog loader
    '/ramadan-challenge.php',                           // receiving ramadan challenge appeals.
    '/team365.php',                                     // receiving team365 appeals.
);

foreach ( $vlTailwind_includes as $file ) {
	$filepath = locate_template( 'includes' . $file );
	if ( ! $filepath ) {
		trigger_error( "Error locating /includes{$file} for inclusion", E_USER_ERROR );
	}
	require_once $filepath;
}

function buildHeaderTree( array &$elements, $parentId = 0 )
{
	$branch = array();
	foreach ( $elements as &$element )
	{
		if ( $element->menu_item_parent == $parentId )
		{
			$children = buildHeaderTree( $elements, $element->ID );
			if ( $children )
				$element->wpse_children = $children;

			$branch[$element->ID] = $element;
			unset( $element );
		}
	}
	return $branch;
}

function searchSocialMedia($name, $array) {
	foreach ($array as $key => $val) {
		if ($val['name'] === $name) {
			return $key;
		}
	}
	return null;
}

add_image_size( 'cr_card_thumb', 600, 350 , true);
add_image_size( 'cr_large_blog_thumb', 600, 480 , true);

// custom excerpt length
function wpdocs_custom_excerpt_length( $length ) {
	return 30;
}
add_filter( 'excerpt_length', 'wpdocs_custom_excerpt_length', 999 );

function new_excerpt_more( $more ) {
    return '...';
}
add_filter('excerpt_more', 'new_excerpt_more');

(new Theme)->run();

// Force asset URLs to use www to avoid cross-origin script/font blocking.
add_filter( 'script_loader_src', function( $src ) {
    return str_replace( 'https://charityright.org.uk', 'https://www.charityright.org.uk', $src );
} );
add_filter( 'style_loader_src', function( $src ) {
    return str_replace( 'https://charityright.org.uk', 'https://www.charityright.org.uk', $src );
} );

// Redirect function from author pages
add_action('template_redirect', 'my_custom_disable_author_page');

function my_custom_disable_author_page() {
	 global $wp_query;

	 if ( is_author() ) {
		 // Redirect to homepage, set status to 301 permenant redirect.
		 // Function defaults to 302 temporary redirect.
		 wp_redirect(get_option('home'), 301);
		 exit;
	 }
}

function itsme_disable_feed() {
	wp_die( __( 'No feed available, please visit the <a href="'. esc_url( home_url( '/' ) ) .'">homepage</a>!' ) );
}

function disable_attachment_pages() {
	if (is_attachment()) {
		// Get the parent post ID
		$parent_post_id = wp_get_post_parent_id(get_the_ID());

		// Redirect to parent post if available, or homepage if not
		if ($parent_post_id) {
			wp_redirect(get_permalink($parent_post_id), 301);
		} else {
			wp_redirect(home_url(), 301);
		}
		exit;
	}
}
add_action('template_redirect', 'disable_attachment_pages');

add_action('do_feed', 'itsme_disable_feed', 1);
add_action('do_feed_rdf', 'itsme_disable_feed', 1);
add_action('do_feed_rss', 'itsme_disable_feed', 1);
add_action('do_feed_rss2', 'itsme_disable_feed', 1);
add_action('do_feed_atom', 'itsme_disable_feed', 1);
add_action('do_feed_rss2_comments', 'itsme_disable_feed', 1);
add_action('do_feed_atom_comments', 'itsme_disable_feed', 1);

add_action('init', function() {
    // Add rewrite rule for campaign with query parameter
    add_rewrite_rule(
        'campaigns/([^/]+)/?$',
        'index.php?post_type=campaign&name=$matches[1]',
        'top'
    );

    // Register your query vars
    add_filter('query_vars', function($vars) {
        $vars[] = 'p'; // Using 'p' instead of 'page'
        return $vars;
    });
});

// Prevent canonical redirect
add_filter('redirect_canonical', function($redirect_url, $requested_url) {
    if (is_singular('campaign')) {
        return false;
    }
    return $redirect_url;
}, 10, 2);

// donations
add_action('rest_api_init', function () {
    register_rest_route('charityright/v1', '/donations', [
        'methods' => 'GET',
        'callback' => 'get_external_donations',
        'permission_callback' => '__return_true', // Adjust permissions if necessary
    ]);
});

if (!function_exists('wp_money_format')) {
    function wp_money_format($amount, $currency_symbol = '£', $decimal_places = 2, $thousands_separator = ',', $decimal_separator = '.') {
        // Ensure the amount is a float
        $amount = floatval($amount);

        // Format the amount
        $formatted_amount = number_format($amount, $decimal_places, $decimal_separator, $thousands_separator);

        // Return with the currency symbol
        return $currency_symbol . $formatted_amount;
    }
}

function get_external_donations($request) {
    // Define the appeal ID if necessary, otherwise, pass it from the request
    $appeal_id = $request->get_param('appeal_id');
    $page = $request->get_param('page') ?? 1;
    $per_page = 5; // Define how many donations you want per page

    // External API URL with the appeal_id
    $url = trim(str_replace("donate", "", get_field("generic_donate_url", "options")), "/") . "/api/v1/appeal-donations/{$appeal_id}?page={$page}&per_page={$per_page}";

    // Fetch donations from the external API
    $response = wp_remote_get($url);

    if (is_wp_error($response)) {
        return new WP_REST_Response([
            'message' => 'Failed to fetch donations from external API',
            'error' => $response->get_error_message()
        ], 500);
    }

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

    if (!$data || !isset($data['data']) || !is_array($data['data'])) {
        return new WP_REST_Response([
            'message' => 'No donations found or invalid data format'
        ], 404);
    }


    $formattedDonations = array_map(function($donation) {
        return [
            'id' => $donation['id'],
            'donor_name' => $donation['is_anonymous'] ? 'Anonymous' : $donation['donor_name'],
            'initials' => strtoupper(substr($donation['donor_name'], 0, 1)), // Extract initials
            'donated_at' => $donation['created_at'], // Pass the raw date, format on frontend
            'amount' => $donation['amount'],
            'donor_message' => $donation['donor_message'] ?? ''
        ];
    }, $data['data']);

    // Return formatted donations and pagination info
    return new WP_REST_Response([
        'donations' => $formattedDonations,
        'pagination' => [
            'current_page' => $data['current_page'],
            'last_page' => $data['last_page'],
            'total' => $data['total']
        ]
    ], 200);
}

add_action('rest_api_init', function () {
    register_rest_route('custom/v1', '/delete-post', array(
        'methods' => 'DELETE',
        'callback' => 'handle_delete_post',
        'permission_callback' => '__return_true',
        'args' => array(
            'slug' => array(
                'required' => true,
                'validate_callback' => function($param) {
                    return is_string($param); // Ensure slug is a string
                }
            ),
            'api_key' => array(
                'required' => true
            )
        )
    ));

    register_rest_route('custom/v1', '/update-campaign-preview', array(
        'methods' => 'POST',
        'callback' => 'handle_update_campaign_preview',
        'permission_callback' => '__return_true',
        'args' => array(
            'slug' => array(
                'required' => true,
                'validate_callback' => function($param) {
                    return is_string($param); // Ensure slug is a string
                }
            ),
            'api_key' => array(
                'required' => true
            )
        )
    ));

    register_rest_route('custom/v1', '/update-strava-leaderboard', array(
        'methods' => 'POST',
        'callback' => 'handle_strava_leaderboard',
        'permission_callback' => '__return_true',
        'args' => array(
            'api_key' => array(
                'required' => true
            ),
            'list' => array(
                'required' => false, // Set to true if `list` is mandatory
                'validate_callback' => function ($param, $request, $key) {
                    return is_array($param); // Ensure `list` is an array
                }
            )
        )
    ));
});

function handle_delete_post($request) {
    // Validate API key (you should store this securely)
    $valid_api_key = 'AKDJSHF8932YRJDFKSAFH120';

    if ($request['api_key'] !== $valid_api_key) {
        return new WP_Error(
            'invalid_api_key',
            'Invalid API key provided.',
            array('status' => 403)
        );
    }

    // Get the slug from the request
    $slug = $request['slug'];

    // Retrieve the post by slug
    $post = get_page_by_path($slug, OBJECT, 'campaign');

    if (!$post) {
        return new WP_Error(
            'post_not_found',
            'Post not found',
            array('status' => 404)
        );
    }

     // Attempt to delete the post
    $deleted = wp_delete_post($post->ID, true);

    if (!$deleted) {
        return new WP_Error(
            'delete_failed',
            'Failed to delete post',
            array('status' => 500)
        );
    }

    return array(
        'status' => 'success',
        'message' => 'Post deleted successfully',
        'deleted_slug' => $slug
    );
}

if (!function_exists("number_shorten")) {
	/**
	 * Shorten long numbers and attaches K, M, B, etc. accordingly
	 * @param int $n
	 * @param int $precision
	 * 
	 * @return string
	 */
	function number_shorten( $n, $precision = 1 ) {
		if ($n < 900) {
			// 0 - 900
			$n_format = number_format($n, $precision);
			$suffix = '';
		} else if ($n < 900000) {
			// 0.9k-850k
			$n_format = number_format($n / 1000, $precision);
			$suffix = 'K';
		} else if ($n < 900000000) {
			// 0.9m-850m
			$n_format = number_format($n / 1000000, $precision);
			$suffix = 'M';
		} else if ($n < 900000000000) {
			// 0.9b-850b
			$n_format = number_format($n / 1000000000, $precision);
			$suffix = 'B';
		} else {
			// 0.9t+
			$n_format = number_format($n / 1000000000000, $precision);
			$suffix = 'T';
		}
	  // Remove unecessary zeroes after decimal. "1.0" -> "1"; "1.00" -> "1"
	  // Intentionally does not affect partials, eg "1.50" -> "1.50"
		if ( $precision > 0 ) {
			$dotzero = '.' . str_repeat( '0', $precision );
			$n_format = str_replace( $dotzero, '', $n_format );
		}
		return $n_format . $suffix;
	}
}	

function handle_update_campaign_preview ($request){
    $existingPost = Etc::findPostByMeta('campaign-preview', 'appeal_id', $request['id']);
    if ($existingPost) {

        // Update the existing post
        $postId = wp_update_post([
            'ID'            => $existingPost->ID,
            'post_title'    => $request['name'],
            'post_name'     => $request['slug'],
            'post_content'  => $request['story'],
            'post_type'     => 'campaign-preview'
        ]);

        // Return the updated post object
        $campaign_preview = get_post($postId);
    } else {
        // Create a new post
        $postId = wp_insert_post([
            'post_title'    => $request['name'],
            'post_name'     => $request['slug'],
            'post_content'  => $request['story'],
            'post_status'   => 'publish',
            'post_type'     => 'campaign-preview'
        ]);
        error_log($postId);


        // Add the custom meta field for appeal_id
        add_post_meta($postId, 'appeal_id', $request['id']);

        // Return the newly created post object
        $campaign_preview = get_post($postId);
    }
    $body = $request->get_body();
    error_log(print_r(json_decode($body, true), true));

    foreach (AppealMapper::toACF(json_decode($body, true)) as $fieldKey => $fieldValue) {
        update_field($fieldKey, $fieldValue, $campaign_preview);
    }

    return $campaign_preview;
}

function handle_strava_leaderboard (WP_REST_Request $request){
    delete_field('strava_leaderboard', 'option');
    $list = $request->get_param('list');

    if ($list) {
        foreach ($list as $item) {
            $row = array(
                'athlete_name' => $item['name'],
                'athlete_image'  => $item['image'],
            'distance' => $item['distance']
            );

            add_row('strava_leaderboard', $row, 'option');
        }
    } else {
        error_log('No list received.');
    }
}


function ajax_words_of_hope_action_submit() : void{
    $email      = $_REQUEST[ "email" ];
    $name  = $_REQUEST[ "name" ];
    $school =  isset($_REQUEST[ "school" ]) ? $_REQUEST[ "school" ] : null;
    $form   =  $_REQUEST[ "form" ];
    $address   =  $_REQUEST[ "address" ];
    $message    =  $_REQUEST[ "message" ];
    $us_marketing   =  isset($_REQUEST[ "us_marketing" ]) ? $_REQUEST[ "us_marketing" ] : null;
    $uk_marketing   =  isset($_REQUEST[ "uk_marketing" ]) ? $_REQUEST[ "uk_marketing" ] : null;
    $is_in_us = $_REQUEST[ "is_in_us" ];


    $data = array(
        'email'        => $email,
        'name'         => $name,
        'school'       => $school,
        'form'         => $form,
        'address'      => $address,
        'message'      => $message,
        'us_marketing' => $us_marketing !== "on" && $is_in_us,
        'uk_marketing' => $uk_marketing == "on" && !$is_in_us,
    );

    // Set up the arguments for the HTTP request
    $args = array(
        'method'    => 'POST',
        'body'      => json_encode($data),
        'headers'   => array(
            'Content-Type'  => 'application/json',
        ),
    );

    $base_url = trim(get_field('generic_donate_url', 'options') ?? 'http://crukv2.test/donate/', '/');
    $wohURL = str_replace("/donate", "/api/word-of-hope/store", $base_url);

    $response = wp_remote_post($wohURL, $args);


    // Check for errors in the request
    if (is_wp_error($response)) {
        $error_message = $response->get_error_message();
        error_log("Error making HTTP request: $error_message");
        wp_send_json_error(array('message' => 'Error making HTTP request.'));
        return;
    }

    // Check the response from the external API
    $response_body = wp_remote_retrieve_body($response);
    $response_code = wp_remote_retrieve_response_code($response);

    error_log(print_r($response_body, true));
    if ($response_code === 201) {
        // Successfully submitted, return success response
        wp_send_json_success(array('message' => 'Your message has been successfully submitted.'));
    } else {
        // API returned an error, return failure response
        wp_send_json_error(array('message' => 'There was an error submitting your message.'));
    }

}

add_action( "wp_ajax_words_of_hope_action", "ajax_words_of_hope_action_submit" );
add_action( "wp_ajax_nopriv_words_of_hope_action", "ajax_words_of_hope_action_submit" );

// === CR Live Data: Real-time donation updates ===
add_action('init', function() {
    \Manza\CharityRight\Endpoints\CacheBust::register();
});

add_action('wp_enqueue_scripts', function() {
    if (is_singular('campaign')) {
        wp_enqueue_script('cr-live', get_template_directory_uri() . '/assets/js/cr-live.js', [], '1.0.0', true);
    }
});
// === End CR Live Data ===

// menu-fix-bust: force cache bust for gute.js
add_filter('script_loader_src', function($src, $handle) {
    if ($handle === 'vl_new_acf_blocks') {
        $src = add_query_arg('bust', '20260218d', $src);
    }
    return $src;
}, 10, 2);


// === UTM Parameter Forwarding (2026-02-19) ===
add_action('wp_footer', function() {
?>
<script>
(function(){
  var utmParams = ['utm_source','utm_medium','utm_campaign','utm_term','utm_content','utm_id'];
  var stored = {};
  
  // 1. Check URL for UTM params
  var urlParams = new URLSearchParams(window.location.search);
  var hasNew = false;
  utmParams.forEach(function(p) {
    var v = urlParams.get(p);
    if (v) { stored[p] = v; hasNew = true; }
  });
  
  // 2. If new UTMs found, save to localStorage
  if (hasNew) {
    localStorage.setItem('cr_utm', JSON.stringify(stored));
    localStorage.setItem('cr_utm_ts', Date.now());
  } else {
    // 3. Load from localStorage (expire after 30 days)
    try {
      var s = localStorage.getItem('cr_utm');
      var ts = localStorage.getItem('cr_utm_ts');
      if (s && ts && (Date.now() - ts) < 30*86400000) {
        stored = JSON.parse(s);
      }
    } catch(e) {}
  }
  
  // 4. Nothing stored? Exit
  if (!Object.keys(stored).length) return;
  
  // 5. Append UTMs to all links pointing to app.charityright.org.uk
  function tagLinks() {
    var links = document.querySelectorAll('a[href*="app.charityright.org.uk"]');
    links.forEach(function(a) {
      try {
        var url = new URL(a.href);
        var changed = false;
        Object.keys(stored).forEach(function(k) {
          if (!url.searchParams.has(k)) {
            url.searchParams.set(k, stored[k]);
            changed = true;
          }
        });
        if (changed) a.href = url.toString();
      } catch(e) {}
    });
  }
  
  // Run on load and observe DOM changes
  tagLinks();
  var observer = new MutationObserver(function() { tagLinks(); });
  observer.observe(document.body, { childList: true, subtree: true });
})();
</script>
<?php
}, 99);
// === END UTM Parameter Forwarding ===

// Add padding offset class for fixed header on pages that need it
add_filter('body_class', function($classes) {
    // Pages with their own header offset handling
    $excluded_templates = ['page-ramadan.php', 'page-fidya-kaffarah.php'];
    $template = get_page_template_slug();
    $template_file = basename(get_page_template());
    if (!in_array($template, $excluded_templates) && !in_array($template_file, $excluded_templates)) {
        $classes[] = 'cr-header-offset';
    }
    return $classes;
});

// Add crossorigin="anonymous" to third-party scripts for better error reporting
add_filter('script_loader_tag', function($tag, $handle, $src) {
    if (strpos($src, home_url()) !== false || strpos($src, '/wp-') === 0) {
        return $tag; // Skip local scripts
    }
    if (strpos($tag, 'crossorigin') !== false) {
        return $tag; // Already has crossorigin
    }
    return str_replace(' src=', ' crossorigin="anonymous" src=', $tag);
}, 10, 3);


// ══════════════════════════════════════════════════════
// IN-APP BROWSER FIXES — SITE-WIDE
// Fixes conversion-killing issues in Instagram, Facebook,
// TikTok, WhatsApp, Snapchat, Twitter, Threads in-app browsers
// ══════════════════════════════════════════════════════
add_action("wp_head", "cr_inapp_browser_fixes", 1);
function cr_inapp_browser_fixes() {
    ?>
    <style id="cr-inapp-fixes">
    /* Prevent auto-zoom on input focus — iOS zooms on <16px inputs */
    @supports (-webkit-touch-callout: none) {
        input, select, textarea { font-size: max(16px, 1em) !important; }
    }
    /* Safe area insets for notched devices */
    body { padding-bottom: env(safe-area-inset-bottom, 0px); }
    /* Prevent text inflation in WebViews */
    html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
    /* Fix rubber-banding scroll issues */
    html { overscroll-behavior-y: none; }
    /* 100vh fix — add dvh fallback globally */
    .min-h-screen, [style*="100vh"] { min-height: 100vh; min-height: 100dvh; }
    /* Minimum touch targets (WCAG 2.5.8) */
    a, button, [role="button"], input[type="submit"], input[type="button"] { min-height: 44px; }
    /* Fix Livewire/Alpine flicker in WebViews */
    [x-cloak] { display: none !important; }
    /* In-app browser banner */
    #cr-inapp-banner {
        display: none; position: fixed; bottom: 0; left: 0; right: 0; z-index: 999999;
        padding: 14px 20px calc(14px + env(safe-area-inset-bottom, 0px));
        background: rgba(26,26,46,.97); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
        border-top: 1px solid rgba(228,34,129,.25);
        text-align: center; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    }
    #cr-inapp-banner p {
        color: #fff; font-size: 13px; font-weight: 600; margin: 0 0 10px; line-height: 1.3;
    }
    #cr-inapp-banner .cr-iab-open {
        display: inline-block; padding: 11px 28px;
        background: #E42281; color: #fff !important; font-size: 13px; font-weight: 700;
        border-radius: 8px; text-decoration: none !important; min-height: 44px; line-height: 22px;
    }
    #cr-inapp-banner .cr-iab-dismiss {
        display: block; margin: 10px auto 0; background: none; border: none;
        color: rgba(255,255,255,.35); font-size: 11px; cursor: pointer; padding: 6px;
        min-height: 30px;
    }
    </style>
    <?php
}

add_action("wp_body_open", "cr_inapp_browser_banner", 1);
function cr_inapp_browser_banner() {
    ?>
    <div id="cr-inapp-banner" role="complementary" aria-label="Open in browser">
        <p>For the best experience, open in your browser</p>
        <a id="cr-iab-open" class="cr-iab-open" href="#">Open in Safari ↗</a>
        <button class="cr-iab-dismiss" id="cr-iab-dismiss" aria-label="Dismiss">Continue here</button>
    </div>
    <script>
    (function(){
        if(sessionStorage.getItem("cr_iab_off")) return;
        var ua=navigator.userAgent||"";
        var isInApp=false;
        // Detect all major in-app browsers
        if(/FBAN|FBAV/i.test(ua)) isInApp=true;         // Facebook
        if(/Instagram/i.test(ua)) isInApp=true;          // Instagram
        if(/TikTok|BytedanceWebview|musical_ly/i.test(ua)) isInApp=true; // TikTok
        if(/Snapchat/i.test(ua)) isInApp=true;           // Snapchat
        if(/Twitter|Threads/i.test(ua)) isInApp=true;    // Twitter/Threads
        if(/Line\//i.test(ua)) isInApp=true;             // LINE
        if(/\bwv\b|WebView/i.test(ua)) isInApp=true;    // Generic Android WebView
        if(/LinkedIn/i.test(ua)) isInApp=true;           // LinkedIn
        if(/Pinterest/i.test(ua)) isInApp=true;          // Pinterest
        if(/Telegram/i.test(ua)) isInApp=true;           // Telegram
        // iOS: has iPhone/iPad but NOT Safari = in-app WebView
        if(!isInApp&&/iPhone|iPad/.test(ua)&&!/Safari/i.test(ua)) isInApp=true;
        if(!isInApp) return;

        var b=document.getElementById("cr-inapp-banner");
        var o=document.getElementById("cr-iab-open");
        var d=document.getElementById("cr-iab-dismiss");
        if(!b||!o) return;

        var url=window.location.href;
        if(/iPhone|iPad|iPod/i.test(ua)){
            o.href=url; o.setAttribute("target","_blank"); o.textContent="Open in Safari ↗";
        } else {
            o.href="intent:"+url.replace(/^https?/,"")+"#Intent;scheme=https;end";
            o.textContent="Open in Chrome ↗";
        }
        b.style.display="block";

        if(d) d.addEventListener("click",function(){
            b.style.display="none";
            sessionStorage.setItem("cr_iab_off","1");
        });

        // CRITICAL: Intercept window.open calls and redirect to same-window
        // This fixes ALL donate buttons site-wide that use window.open
        var _origOpen = window.open;
        window.open = function(url, target) {
            if(url && typeof url === "string" && (url.indexOf("app.charityright.org.uk") > -1 || url.indexOf("/donate") > -1)) {
                // In-app browser: redirect same-window instead of popup
                window.location.href = url;
                return null;
            }
            return _origOpen.apply(window, arguments);
        };
    })();
    </script>
    <?php
}


// ══════════════════════════════════════════════════════
// PREFETCH + PRECONNECT — Speed up checkout transition
// In-app browsers have cold DNS/TCP so this is critical
// ══════════════════════════════════════════════════════
add_action("wp_head", "cr_inapp_prefetch_hints", 2);
function cr_inapp_prefetch_hints() {
    ?>
    <link rel="preconnect" href="https://app.charityright.org.uk" crossorigin>
    <link rel="dns-prefetch" href="https://app.charityright.org.uk">
    <link rel="preconnect" href="https://js.stripe.com" crossorigin>
    <link rel="dns-prefetch" href="https://js.stripe.com">
    <link rel="preconnect" href="https://fonts.bunny.net" crossorigin>
    <?php
}
