Move plugins to manifest, pin Docker version, add Makefile

- Add plugins.txt listing all plugins for reproducible installs
- Add Makefile with setup/start/stop/install-plugins targets
- Remove user/plugins/ from git tracking
- Pin Docker image to 1.7.49.5-ls244

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-19 00:55:59 +02:00
parent 8f9ac9ca6e
commit 4f52d4d085
2738 changed files with 0 additions and 472444 deletions
@@ -1,128 +0,0 @@
(function() {
'use strict';
// Function to refresh a captcha image
const refreshCaptchaImage = function(container) {
const img = container.querySelector('img');
if (!img) {
console.warn('Cannot find captcha image in container');
return;
}
// Get the base URL and field ID
const baseUrl = img.dataset.baseUrl || img.src.split('?')[0];
const fieldId = img.dataset.fieldId || container.dataset.fieldId;
// Force reload by adding/updating timestamp and field ID
const timestamp = new Date().getTime();
let newUrl = baseUrl + '?t=' + timestamp;
if (fieldId) {
newUrl += '&field=' + fieldId;
}
img.src = newUrl;
// Also clear the input field if we can find it
const formField = container.closest('.form-field');
if (formField) {
const input = formField.querySelector('input[type="text"]');
if (input) {
input.value = '';
// Try to focus the input
try { input.focus(); } catch(e) {}
}
}
};
// Function to set up click handlers for refresh buttons
const setupRefreshButtons = function() {
// Find all captcha containers
const containers = document.querySelectorAll('[data-captcha-provider="basic-captcha"]');
containers.forEach(function(container) {
// Find the refresh button within this container
const button = container.querySelector('button');
if (!button) {
return;
}
// Remove any existing listeners (just in case)
button.removeEventListener('click', handleRefreshClick);
// Add the click handler
button.addEventListener('click', handleRefreshClick);
});
};
// Click handler function
const handleRefreshClick = function(event) {
// Prevent default behavior and stop propagation
event.preventDefault();
event.stopPropagation();
// Find the container
const container = this.closest('[data-captcha-provider="basic-captcha"]');
if (!container) {
return false;
}
// Refresh the image
refreshCaptchaImage(container);
return false;
};
// Set up a mutation observer to handle dynamically added captchas
const setupMutationObserver = function() {
// Check if MutationObserver is available
if (typeof MutationObserver === 'undefined') return;
// Create a mutation observer to watch for new captcha elements
const observer = new MutationObserver(function(mutations) {
let needsSetup = false;
mutations.forEach(function(mutation) {
if (mutation.type === 'childList' && mutation.addedNodes.length) {
// Check if any of the added nodes contain our captcha containers
for (let i = 0; i < mutation.addedNodes.length; i++) {
const node = mutation.addedNodes[i];
if (node.nodeType === Node.ELEMENT_NODE) {
// Check if this element has or contains captcha containers
if (node.querySelector && (
node.matches('[data-captcha-provider="basic-captcha"]') ||
node.querySelector('[data-captcha-provider="basic-captcha"]')
)) {
needsSetup = true;
break;
}
}
}
}
});
if (needsSetup) {
setupRefreshButtons();
}
});
// Start observing the document
observer.observe(document.body, {
childList: true,
subtree: true
});
};
// Initialize on DOM ready
document.addEventListener('DOMContentLoaded', function() {
setupRefreshButtons();
setupMutationObserver();
// Also connect to XHR system if available (for best of both worlds)
if (window.GravFormXHR && window.GravFormXHR.captcha) {
window.GravFormXHR.captcha.register('basic-captcha', {
reset: function(container, form) {
refreshCaptchaImage(container);
}
});
}
});
})();
@@ -1,166 +0,0 @@
(function() {
'use strict';
// Register the handler with the form system when it's ready
const registerRecaptchaHandler = function() {
if (window.GravFormXHR && window.GravFormXHR.captcha) {
window.GravFormXHR.captcha.register('recaptcha', {
reset: function(container, form) {
if (!form || !form.id) {
console.warn('Cannot reset reCAPTCHA: form is invalid or missing ID');
return;
}
const formId = form.id;
console.log(`Attempting to reset reCAPTCHA for form: ${formId}`);
// First try the expected ID pattern from the Twig template
const recaptchaId = `g-recaptcha-${formId}`;
// We need to look more flexibly for the container
let widgetContainer = document.getElementById(recaptchaId);
// If not found by ID, look for the div inside the captcha provider container
if (!widgetContainer) {
// Try to find it inside the captcha provider container
widgetContainer = container.querySelector('.g-recaptcha');
if (!widgetContainer) {
// If that fails, look more broadly in the form
widgetContainer = form.querySelector('.g-recaptcha');
if (!widgetContainer) {
// Last resort - create a new container if needed
console.warn(`reCAPTCHA container #${recaptchaId} not found. Creating a new one.`);
widgetContainer = document.createElement('div');
widgetContainer.id = recaptchaId;
widgetContainer.className = 'g-recaptcha';
container.appendChild(widgetContainer);
}
}
}
console.log(`Found reCAPTCHA container for form: ${formId}`);
// Get configuration from data attributes
const parentContainer = container.closest('[data-captcha-provider="recaptcha"]');
if (!parentContainer) {
console.warn('Cannot find reCAPTCHA parent container with data-captcha-provider attribute.');
return;
}
const sitekey = parentContainer.dataset.sitekey;
const version = parentContainer.dataset.version || '2-checkbox';
const isV3 = version.startsWith('3');
const isInvisible = version === '2-invisible';
if (!sitekey) {
console.warn('Cannot reinitialize reCAPTCHA - missing sitekey attribute');
return;
}
console.log(`Re-rendering reCAPTCHA widget for form: ${formId}, version: ${version}`);
// Handle V3 reCAPTCHA differently
if (isV3) {
try {
// For v3, we don't need to reset anything visible, just make sure we have the API
if (typeof grecaptcha !== 'undefined' && typeof grecaptcha.execute === 'function') {
// Create a new execution context for the form
const actionName = `form_${formId}`;
const tokenInput = form.querySelector('input[name="token"]') ||
form.querySelector('input[name="data[token]"]');
const actionInput = form.querySelector('input[name="action"]') ||
form.querySelector('input[name="data[action]"]');
if (tokenInput && actionInput) {
// Clear previous token
tokenInput.value = '';
// Set the action name
actionInput.value = actionName;
console.log(`reCAPTCHA v3 ready for execution on form: ${formId}`);
} else {
console.warn(`Cannot find token or action inputs for reCAPTCHA v3 in form: ${formId}`);
}
} else {
console.warn('reCAPTCHA v3 API not properly loaded.');
}
} catch (e) {
console.error(`Error setting up reCAPTCHA v3: ${e.message}`);
}
return;
}
// For v2, handle visible widget reset
// Clear the container to ensure fresh rendering
widgetContainer.innerHTML = '';
// Check if reCAPTCHA API is available
if (typeof grecaptcha !== 'undefined' && typeof grecaptcha.render === 'function') {
try {
// Render with a slight delay to ensure DOM is settled
setTimeout(() => {
grecaptcha.render(widgetContainer.id || widgetContainer, {
'sitekey': sitekey,
'theme': parentContainer.dataset.theme || 'light',
'size': isInvisible ? 'invisible' : 'normal',
'callback': function(token) {
console.log(`reCAPTCHA verification completed for form: ${formId}`);
// If it's invisible reCAPTCHA, submit the form automatically
if (isInvisible && window.GravFormXHR && typeof window.GravFormXHR.submit === 'function') {
window.GravFormXHR.submit(form);
}
}
});
console.log(`Successfully rendered reCAPTCHA for form: ${formId}`);
}, 100);
} catch (e) {
console.error(`Error rendering reCAPTCHA widget: ${e.message}`);
widgetContainer.innerHTML = '<p style="color:red;">Error initializing reCAPTCHA.</p>';
}
} else {
console.warn('reCAPTCHA API not available. Attempting to reload...');
// Remove existing script if any
const existingScript = document.querySelector('script[src*="google.com/recaptcha/api.js"]');
if (existingScript) {
existingScript.parentNode.removeChild(existingScript);
}
// Create new script element
const script = document.createElement('script');
script.src = `https://www.google.com/recaptcha/api.js${isV3 ? '?render=' + sitekey : ''}`;
script.async = true;
script.defer = true;
script.onload = function() {
console.log('reCAPTCHA API loaded, retrying widget render...');
setTimeout(() => {
const retryContainer = document.querySelector(`[data-captcha-provider="recaptcha"]`);
if (retryContainer && form) {
window.GravFormXHR.captcha.getProvider('recaptcha').reset(retryContainer, form);
}
}, 200);
};
document.head.appendChild(script);
}
}
});
console.log('reCAPTCHA XHR handler registered successfully');
} else {
console.error('GravFormXHR.captcha not found. Make sure the Form plugin is loaded correctly.');
}
};
// Try to register the handler immediately if GravFormXHR is already available
if (window.GravFormXHR && window.GravFormXHR.captcha) {
registerRecaptchaHandler();
} else {
// Otherwise, wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
// Give a small delay to ensure GravFormXHR is initialized
setTimeout(registerRecaptchaHandler, 100);
});
}
})();
@@ -1,121 +0,0 @@
(function() {
'use strict';
// Register the handler with the form system when it's ready
const registerTurnstileHandler = function() {
if (window.GravFormXHR && window.GravFormXHR.captcha) {
window.GravFormXHR.captcha.register('turnstile', {
reset: function(container, form) {
const formId = form.id;
const containerId = `cf-turnstile-${formId}`;
const widgetContainer = document.getElementById(containerId);
if (!widgetContainer) {
console.warn(`Turnstile container #${containerId} not found.`);
return;
}
// Get configuration from data attributes
const parentContainer = container.closest('[data-captcha-provider="turnstile"]');
const sitekey = parentContainer ? parentContainer.dataset.sitekey : null;
if (!sitekey) {
console.warn('Cannot reinitialize Turnstile - missing sitekey attribute');
return;
}
// Clear the container to ensure fresh rendering
widgetContainer.innerHTML = '';
console.log(`Re-rendering Turnstile widget for form: ${formId}`);
// Check if Turnstile API is available
if (typeof window.turnstile !== 'undefined') {
try {
// Reset any existing widgets
try {
window.turnstile.reset(containerId);
} catch (e) {
// Ignore reset errors, we'll re-render anyway
}
// Render with a slight delay to ensure DOM is settled
setTimeout(() => {
window.turnstile.render(`#${containerId}`, {
sitekey: sitekey,
theme: parentContainer ? (parentContainer.dataset.theme || 'light') : 'light',
callback: function(token) {
console.log(`Turnstile verification completed for form: ${formId} with token:`, token.substring(0, 10) + '...');
// Create or update hidden input for token
let tokenInput = form.querySelector('input[name="cf-turnstile-response"]');
if (!tokenInput) {
console.log('Creating new hidden input for Turnstile token');
tokenInput = document.createElement('input');
tokenInput.type = 'hidden';
tokenInput.name = 'cf-turnstile-response';
form.appendChild(tokenInput);
} else {
console.log('Updating existing hidden input for Turnstile token');
}
tokenInput.value = token;
// Also add a debug attribute
form.setAttribute('data-turnstile-verified', 'true');
},
'expired-callback': function() {
console.log(`Turnstile token expired for form: ${formId}`);
},
'error-callback': function(error) {
console.error(`Turnstile error for form ${formId}: ${error}`);
}
});
}, 100);
} catch (e) {
console.error(`Error rendering Turnstile widget: ${e.message}`);
widgetContainer.innerHTML = '<p style="color:red;">Error initializing Turnstile.</p>';
}
} else {
console.warn('Turnstile API not available. Attempting to reload...');
// Remove existing script if any
const existingScript = document.querySelector('script[src*="challenges.cloudflare.com/turnstile/v0/api.js"]');
if (existingScript) {
existingScript.parentNode.removeChild(existingScript);
}
// Create new script element
const script = document.createElement('script');
script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
script.async = true;
script.defer = true;
script.onload = function() {
console.log('Turnstile API loaded, retrying widget render...');
setTimeout(() => {
const retryContainer = document.querySelector('[data-captcha-provider="turnstile"]');
if (retryContainer && form) {
window.GravFormXHR.captcha.getProvider('turnstile').reset(retryContainer, form);
}
}, 200);
};
document.head.appendChild(script);
}
}
});
console.log('Turnstile XHR handler registered successfully');
} else {
console.error('GravFormXHR.captcha not found. Make sure the Form plugin is loaded correctly.');
}
};
// Try to register the handler immediately if GravFormXHR is already available
if (window.GravFormXHR && window.GravFormXHR.captcha) {
registerTurnstileHandler();
} else {
// Otherwise, wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
// Give a small delay to ensure GravFormXHR is initialized
setTimeout(registerTurnstileHandler, 100);
});
}
})();