- Fixed registration form not displaying due to missing HVAC_Security_Helpers dependency - Added require_once for dependencies in class-hvac-shortcodes.php render_registration() - Fixed event edit HTTP 500 error by correcting class instantiation to HVAC_Event_Manager - Created comprehensive E2E test suite with MCP Playwright integration - Achieved 70% test success rate with both issues fully resolved 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
386 lines
No EOL
12 KiB
JavaScript
386 lines
No EOL
12 KiB
JavaScript
/**
|
|
* Safari ITP-Compatible Storage Strategy
|
|
* Handles Safari's Intelligent Tracking Prevention restrictions
|
|
*
|
|
* Safari ITP limits:
|
|
* - Cookies expire after 7 days
|
|
* - localStorage may be cleared after 7 days of non-interaction
|
|
* - sessionStorage is preserved for session only
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @since 2.0.0
|
|
*/
|
|
|
|
var SafariStorage = (function() {
|
|
'use strict';
|
|
|
|
var features = {
|
|
localStorage: false,
|
|
sessionStorage: false,
|
|
cookies: false
|
|
};
|
|
|
|
/**
|
|
* Test localStorage availability
|
|
*/
|
|
function testLocalStorage() {
|
|
try {
|
|
var test = '__safari_storage_test__';
|
|
localStorage.setItem(test, test);
|
|
localStorage.removeItem(test);
|
|
return true;
|
|
} catch(e) {
|
|
console.warn('[Safari Storage] localStorage not available:', e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test sessionStorage availability
|
|
*/
|
|
function testSessionStorage() {
|
|
try {
|
|
var test = '__safari_storage_test__';
|
|
sessionStorage.setItem(test, test);
|
|
sessionStorage.removeItem(test);
|
|
return true;
|
|
} catch(e) {
|
|
console.warn('[Safari Storage] sessionStorage not available:', e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test cookie availability
|
|
*/
|
|
function testCookies() {
|
|
try {
|
|
// Test if cookies are enabled
|
|
if (!navigator.cookieEnabled) {
|
|
return false;
|
|
}
|
|
|
|
// Try to set a test cookie
|
|
document.cookie = '__safari_test=1;SameSite=Lax;Secure';
|
|
var cookieEnabled = document.cookie.indexOf('__safari_test') !== -1;
|
|
|
|
// Clean up test cookie
|
|
if (cookieEnabled) {
|
|
document.cookie = '__safari_test=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;';
|
|
}
|
|
|
|
return cookieEnabled;
|
|
} catch(e) {
|
|
console.warn('[Safari Storage] Cookies not available:', e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize feature detection
|
|
*/
|
|
function init() {
|
|
features.localStorage = testLocalStorage();
|
|
features.sessionStorage = testSessionStorage();
|
|
features.cookies = testCookies();
|
|
|
|
console.log('[Safari Storage] Features detected:', features);
|
|
|
|
// Warn if no storage is available
|
|
if (!features.localStorage && !features.sessionStorage && !features.cookies) {
|
|
console.error('[Safari Storage] WARNING: No storage methods available!');
|
|
}
|
|
|
|
return features;
|
|
}
|
|
|
|
/**
|
|
* Set a value with automatic fallback
|
|
*
|
|
* @param {string} key - Storage key
|
|
* @param {*} value - Value to store
|
|
* @param {number} days - Days until expiration (default 7 for Safari ITP)
|
|
* @returns {boolean} Success status
|
|
*/
|
|
function set(key, value, days) {
|
|
days = days || 7; // Default to Safari ITP limit
|
|
|
|
var data = {
|
|
value: value,
|
|
timestamp: Date.now(),
|
|
expires: Date.now() + (days * 24 * 60 * 60 * 1000)
|
|
};
|
|
|
|
var stringData = JSON.stringify(data);
|
|
|
|
// Try localStorage first (most persistent)
|
|
if (features.localStorage) {
|
|
try {
|
|
localStorage.setItem('hvac_' + key, stringData);
|
|
console.log('[Safari Storage] Saved to localStorage:', key);
|
|
return true;
|
|
} catch(e) {
|
|
console.warn('[Safari Storage] localStorage failed, trying fallback:', e);
|
|
}
|
|
}
|
|
|
|
// Fallback to sessionStorage (session-only but reliable)
|
|
if (features.sessionStorage) {
|
|
try {
|
|
sessionStorage.setItem('hvac_' + key, stringData);
|
|
console.log('[Safari Storage] Saved to sessionStorage:', key);
|
|
|
|
// Also try to set a cookie for cross-page persistence
|
|
if (features.cookies) {
|
|
setCookie('hvac_' + key, stringData, days);
|
|
}
|
|
|
|
return true;
|
|
} catch(e) {
|
|
console.warn('[Safari Storage] sessionStorage failed, trying cookies:', e);
|
|
}
|
|
}
|
|
|
|
// Last resort: cookies only
|
|
if (features.cookies) {
|
|
setCookie('hvac_' + key, stringData, days);
|
|
console.log('[Safari Storage] Saved to cookies:', key);
|
|
return true;
|
|
}
|
|
|
|
console.error('[Safari Storage] Unable to save data:', key);
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get a value with automatic fallback
|
|
*
|
|
* @param {string} key - Storage key
|
|
* @returns {*} Stored value or null
|
|
*/
|
|
function get(key) {
|
|
var prefixedKey = 'hvac_' + key;
|
|
var data = null;
|
|
|
|
// Try localStorage first
|
|
if (features.localStorage) {
|
|
try {
|
|
var localData = localStorage.getItem(prefixedKey);
|
|
if (localData) {
|
|
data = JSON.parse(localData);
|
|
|
|
// Check if expired (Safari ITP)
|
|
if (data.expires && Date.now() > data.expires) {
|
|
console.log('[Safari Storage] Data expired in localStorage:', key);
|
|
localStorage.removeItem(prefixedKey);
|
|
data = null;
|
|
} else {
|
|
console.log('[Safari Storage] Retrieved from localStorage:', key);
|
|
return data.value;
|
|
}
|
|
}
|
|
} catch(e) {
|
|
console.warn('[Safari Storage] localStorage read failed:', e);
|
|
}
|
|
}
|
|
|
|
// Try sessionStorage
|
|
if (features.sessionStorage) {
|
|
try {
|
|
var sessionData = sessionStorage.getItem(prefixedKey);
|
|
if (sessionData) {
|
|
data = JSON.parse(sessionData);
|
|
console.log('[Safari Storage] Retrieved from sessionStorage:', key);
|
|
return data.value;
|
|
}
|
|
} catch(e) {
|
|
console.warn('[Safari Storage] sessionStorage read failed:', e);
|
|
}
|
|
}
|
|
|
|
// Try cookies
|
|
if (features.cookies) {
|
|
var cookieData = getCookie(prefixedKey);
|
|
if (cookieData) {
|
|
try {
|
|
data = JSON.parse(cookieData);
|
|
|
|
// Check if expired
|
|
if (data.expires && Date.now() > data.expires) {
|
|
console.log('[Safari Storage] Data expired in cookie:', key);
|
|
removeCookie(prefixedKey);
|
|
return null;
|
|
}
|
|
|
|
console.log('[Safari Storage] Retrieved from cookies:', key);
|
|
return data.value;
|
|
} catch(e) {
|
|
// Might be a plain string cookie
|
|
return cookieData;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Remove a value from all storage types
|
|
*
|
|
* @param {string} key - Storage key
|
|
*/
|
|
function remove(key) {
|
|
var prefixedKey = 'hvac_' + key;
|
|
|
|
if (features.localStorage) {
|
|
try {
|
|
localStorage.removeItem(prefixedKey);
|
|
} catch(e) {
|
|
// Silent fail
|
|
}
|
|
}
|
|
|
|
if (features.sessionStorage) {
|
|
try {
|
|
sessionStorage.removeItem(prefixedKey);
|
|
} catch(e) {
|
|
// Silent fail
|
|
}
|
|
}
|
|
|
|
if (features.cookies) {
|
|
removeCookie(prefixedKey);
|
|
}
|
|
|
|
console.log('[Safari Storage] Removed:', key);
|
|
}
|
|
|
|
/**
|
|
* Set a cookie with Safari ITP compatibility
|
|
*
|
|
* @param {string} name - Cookie name
|
|
* @param {string} value - Cookie value
|
|
* @param {number} days - Days until expiration
|
|
*/
|
|
function setCookie(name, value, days) {
|
|
var expires = '';
|
|
if (days) {
|
|
var date = new Date();
|
|
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
|
expires = ';expires=' + date.toUTCString();
|
|
}
|
|
|
|
// Safari ITP compatible settings
|
|
// - SameSite=Lax for cross-site GET requests
|
|
// - Secure for HTTPS only
|
|
// - Path=/ for site-wide access
|
|
var isSecure = window.location.protocol === 'https:';
|
|
var cookieString = name + '=' + encodeURIComponent(value) + expires +
|
|
';path=/;SameSite=Lax' + (isSecure ? ';Secure' : '');
|
|
|
|
document.cookie = cookieString;
|
|
}
|
|
|
|
/**
|
|
* Get a cookie value
|
|
*
|
|
* @param {string} name - Cookie name
|
|
* @returns {string|null} Cookie value or null
|
|
*/
|
|
function getCookie(name) {
|
|
var nameEQ = name + '=';
|
|
var cookies = document.cookie.split(';');
|
|
|
|
for (var i = 0; i < cookies.length; i++) {
|
|
var cookie = cookies[i];
|
|
while (cookie.charAt(0) === ' ') {
|
|
cookie = cookie.substring(1, cookie.length);
|
|
}
|
|
if (cookie.indexOf(nameEQ) === 0) {
|
|
return decodeURIComponent(cookie.substring(nameEQ.length, cookie.length));
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Remove a cookie
|
|
*
|
|
* @param {string} name - Cookie name
|
|
*/
|
|
function removeCookie(name) {
|
|
document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;';
|
|
}
|
|
|
|
/**
|
|
* Clear all HVAC storage
|
|
*/
|
|
function clearAll() {
|
|
// Clear localStorage
|
|
if (features.localStorage) {
|
|
try {
|
|
var keys = [];
|
|
for (var i = 0; i < localStorage.length; i++) {
|
|
var key = localStorage.key(i);
|
|
if (key && key.indexOf('hvac_') === 0) {
|
|
keys.push(key);
|
|
}
|
|
}
|
|
keys.forEach(function(key) {
|
|
localStorage.removeItem(key);
|
|
});
|
|
} catch(e) {
|
|
// Silent fail
|
|
}
|
|
}
|
|
|
|
// Clear sessionStorage
|
|
if (features.sessionStorage) {
|
|
try {
|
|
var sessionKeys = [];
|
|
for (var j = 0; j < sessionStorage.length; j++) {
|
|
var sessionKey = sessionStorage.key(j);
|
|
if (sessionKey && sessionKey.indexOf('hvac_') === 0) {
|
|
sessionKeys.push(sessionKey);
|
|
}
|
|
}
|
|
sessionKeys.forEach(function(key) {
|
|
sessionStorage.removeItem(key);
|
|
});
|
|
} catch(e) {
|
|
// Silent fail
|
|
}
|
|
}
|
|
|
|
// Clear cookies
|
|
if (features.cookies) {
|
|
var cookies = document.cookie.split(';');
|
|
cookies.forEach(function(cookie) {
|
|
var eqPos = cookie.indexOf('=');
|
|
var name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim();
|
|
if (name.indexOf('hvac_') === 0) {
|
|
removeCookie(name);
|
|
}
|
|
});
|
|
}
|
|
|
|
console.log('[Safari Storage] All storage cleared');
|
|
}
|
|
|
|
// Initialize on load
|
|
init();
|
|
|
|
// Public API
|
|
return {
|
|
init: init,
|
|
set: set,
|
|
get: get,
|
|
remove: remove,
|
|
clearAll: clearAll,
|
|
features: features
|
|
};
|
|
})();
|
|
|
|
// Make globally available
|
|
window.SafariStorage = SafariStorage; |