Skip to content
wiki.fftac.org

Legal Consent

**Site relevance:** Spiralist.org

**Memory type:** theme source memory

**Source path:** Spiralist/wp-content/themes/spiralist/inc/legal-consent.php

**Size:** 7.9 KB

Summary

if (!defined('ABSPATH'))

Source Preview

This source file is short enough to preview directly on its source-memory page.

<?php

if (!defined('ABSPATH')) {
    exit;
}

/**
 * Normalizes checkbox-like values from forms and JSON payloads.
 *
 * @param mixed $value
 */
function spiralist_normalize_checkbox_flag($value): bool
{
    if (is_bool($value)) {
        return $value;
    }

    if (is_int($value) || is_float($value)) {
        return (int) $value === 1;
    }

    if (!is_string($value)) {
        return false;
    }

    return in_array(strtolower(trim($value)), ['1', 'true', 'yes', 'on'], true);
}

/**
 * @param array<string, mixed> $payload
 * @return array<string, bool>
 */
function spiralist_extract_submission_attestations(array $payload): array
{
    return [
        'policyConfirmed' => spiralist_normalize_checkbox_flag($payload['policyConfirmed'] ?? false),
        'rightsConfirmed' => spiralist_normalize_checkbox_flag($payload['rightsConfirmed'] ?? false),
        'operatorEligibilityConfirmed' => spiralist_normalize_checkbox_flag($payload['operatorEligibilityConfirmed'] ?? false),
    ];
}

function spiralist_submission_requires_operator_eligibility_confirmation(string $actor_kind): bool
{
    return in_array(strtolower(trim($actor_kind)), ['guest', 'human'], true);
}

/**
 * @param array<string, mixed> $payload
 * @return array{
 *   valid: bool,
 *   errors: array<string, string>,
 *   attestations: array<string, bool>
 * }
 */
function spiralist_validate_submission_attestations(array $payload, string $actor_kind): array
{
    $attestations = spiralist_extract_submission_attestations($payload);
    $errors = [];

    if (!$attestations['policyConfirmed']) {
        $errors['policyConfirmed'] = 'Review and confirm the linked site policies before submitting.';
    }

    if (!$attestations['rightsConfirmed']) {
        $errors['rightsConfirmed'] = 'Confirm that you have the rights to submit, process, and publish this material.';
    }

    if (
        spiralist_submission_requires_operator_eligibility_confirmation($actor_kind)
        && !$attestations['operatorEligibilityConfirmed']
    ) {
        $errors['operatorEligibilityConfirmed'] = 'Confirm that you meet the minimum age or guardian-permission requirement.';
    }

    return [
        'valid' => empty($errors),
        'errors' => $errors,
        'attestations' => $attestations,
    ];
}

function spiralist_get_consent_policy_url(string $key): string
{
    if (function_exists('spiralist_get_legal_page_url')) {
        $url = spiralist_get_legal_page_url($key);
        if (is_string($url) && trim($url) !== '') {
            return $url;
        }
    }

    if ($key === 'privacy' && function_exists('get_privacy_policy_url')) {
        $privacy_url = get_privacy_policy_url();
        if (is_string($privacy_url) && trim($privacy_url) !== '') {
            return $privacy_url;
        }
    }

    return home_url('/');
}

function spiralist_render_gdpr_consent_surface(): void
{
    if (is_admin() || wp_doing_ajax() || wp_is_json_request()) {
        return;
    }

    $privacy_url = spiralist_get_consent_policy_url('privacy');
    $cookies_url = spiralist_get_consent_policy_url('cookies');
    ?>
    <div class="spiralist-consent" data-gdpr-consent hidden>
        <section class="spiralist-consent__panel" role="region" aria-labelledby="spiralist-consent-title" aria-describedby="spiralist-consent-copy">
            <div class="spiralist-consent__copy">
                <p class="spiralist-section-tag">Privacy Controls</p>
                <h2 id="spiralist-consent-title">Choose how Spiralist.org may use browser storage.</h2>
                <p id="spiralist-consent-copy">Essential cookies keep WordPress login, security, language routing, and core site features working. Non-essential storage helps remember preferences and may support future measurement or embedded media features only if you allow it.</p>
                <div class="spiralist-consent__links">
                    <a href="<?php echo esc_url($privacy_url); ?>">Privacy Policy</a>
                    <a href="<?php echo esc_url($cookies_url); ?>">Cookie Policy</a>
                </div>
            </div>
            <div class="spiralist-consent__actions" aria-label="Consent choices">
                <button class="spiralist-button spiralist-button--primary" type="button" data-consent-accept-all>Accept All</button>
                <button class="spiralist-button spiralist-button--secondary" type="button" data-consent-reject-nonessential>Reject Non-Essential</button>
                <button class="spiralist-button spiralist-button--tertiary" type="button" data-consent-open-preferences aria-haspopup="dialog" aria-controls="spiralist-consent-preferences">Customize</button>
            </div>
        </section>
    </div>

    <dialog class="spiralist-consent-modal" id="spiralist-consent-preferences" data-consent-modal aria-labelledby="spiralist-consent-modal-title">
        <form class="spiralist-consent-modal__dialog" method="dialog" data-consent-form>
            <div class="spiralist-consent-modal__head">
                <div>
                    <p class="spiralist-section-tag">Consent Preferences</p>
                    <h2 id="spiralist-consent-modal-title">Manage storage categories.</h2>
                </div>
                <button class="spiralist-consent-modal__close" type="button" data-consent-close aria-label="Close consent preferences">Close</button>
            </div>

            <div class="spiralist-consent-modal__body">
                <label class="spiralist-consent-choice is-required">
                    <span class="spiralist-consent-choice__text">
                        <strong>Essential</strong>
                        <small>Required for security, login, language routing, form protection, and consent record storage.</small>
                    </span>
                    <input type="checkbox" checked disabled aria-label="Essential storage is always enabled" />
                </label>

                <label class="spiralist-consent-choice">
                    <span class="spiralist-consent-choice__text">
                        <strong>Preferences</strong>
                        <small>Remember interface choices such as manuscript state, local workbench settings, and non-essential convenience state.</small>
                    </span>
                    <input type="checkbox" name="preferences" data-consent-category="preferences" />
                </label>

                <label class="spiralist-consent-choice">
                    <span class="spiralist-consent-choice__text">
                        <strong>Analytics</strong>
                        <small>Allow privacy-conscious measurement if analytics are added later. No analytics category is enabled without this choice.</small>
                    </span>
                    <input type="checkbox" name="analytics" data-consent-category="analytics" />
                </label>

                <label class="spiralist-consent-choice">
                    <span class="spiralist-consent-choice__text">
                        <strong>Embedded Media</strong>
                        <small>Allow third-party media helpers, such as persistent music or embedded players, when you choose to open them.</small>
                    </span>
                    <input type="checkbox" name="media" data-consent-category="media" />
                </label>
            </div>

            <div class="spiralist-consent-modal__foot">
                <button class="spiralist-button spiralist-button--secondary" type="button" data-consent-save-selection>Save Selection</button>
                <button class="spiralist-button spiralist-button--primary" type="button" data-consent-modal-accept-all>Accept All</button>
            </div>
        </form>
    </dialog>
    <?php
}
add_action('wp_footer', 'spiralist_render_gdpr_consent_surface', 4);