Files
calvana/screenshots/functions.php.bak
Omair Saleh c18dc50657 persona section overhaul — editorial gap-px grid + fresh photography
SECTION REDESIGN:
- Killed standalone dashboard image (fake AI laptop, added nothing)
- New gap-px grid (signature pattern 2) with border-l-2 accents (pattern 1)
- Numbered anchors (01-04) as visual rhythm per brand guide
- Wider container: max-w-7xl matches hero width

PERSONA CHANGES:
- Renamed 'Organisation' -> 'Programme Manager'
- Reorder: Charity Manager, Programme Manager, Personal Fundraiser, Volunteer
- Updated /for/organisations page content to match

PHOTOGRAPHY (4 new images via gemini-3-pro-image-preview):
- persona-charity-manager.jpg — hijabi woman at mosque office desk
- persona-programme-manager.jpg — man at desk with campaign calendar
- persona-fundraiser.jpg — woman on London park bench with phone
- persona-volunteer.jpg — young man handing card at charity gala
- All optimized: 2.7MB -> 342KB (87% reduction via sharp)
- Consistent documentary candid style, 3:2 landscape, warm tones

FOOTER:
- 'Organisations' -> 'Programme Managers' in nav links
2026-03-03 22:01:53 +08:00

728 lines
26 KiB
PHP

<?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
}