HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/ai.naniguide.com/app/Services/Form/FormGdprChecklist.php
<?php

namespace App\Services\Form;

/**
 * F4.4 — Static catalog of GDPR rules audited by `form.check_gdpr`.
 *
 * Runtime SoT for the auditor; mirrors `form.json` →
 * `entities.gdpr_checklist[]`. F38 Pest reflection asserts dual-source
 * parity (D80).
 */
class FormGdprChecklist
{
    /**
     * @return list<array{id:string,label:string,rationale:string,fix_hint:string}>
     */
    public static function rules(): array
    {
        return [
            [
                'id' => 'consent_checkbox',
                'label' => 'Explicit consent checkbox present',
                'rationale' => 'GDPR Article 7 requires affirmative action; a checkbox is the clearest evidence. Must be UNTICKED by default — pre-ticked is invalid.',
                'fix_hint' => 'Add a checkbox before the submit button: \'I agree to receive {topic} updates from {Brand}. I can unsubscribe anytime.\' Untick by default.',
            ],
            [
                'id' => 'privacy_policy_link',
                'label' => 'Privacy policy link visible',
                'rationale' => 'GDPR Article 13 requires the data subject is told where to find the privacy notice. Link must be visible at submit time, not buried in a footer two pages away.',
                'fix_hint' => 'Add a small-text link below the submit button: \'See our privacy policy.\' Point to your real privacy page.',
            ],
            [
                'id' => 'double_opt_in_enabled',
                'label' => 'Double opt-in enabled on parent list',
                'rationale' => 'Two-step confirmation produces a verifiable consent record (timestamp + IP + click). Single opt-in is legal but weaker — under DPA scrutiny, double opt-in materially raises your defence.',
                'fix_hint' => 'On the parent mail list\'s settings, enable \'Subscribe confirmation\' (`mail_lists.subscribe_confirmation=1`). The form requires no change.',
            ],
            [
                'id' => 'consent_language_explicit',
                'label' => 'Consent text says what you\'ll send',
                'rationale' => 'GDPR consent must be specific. \'I agree to receive emails\' is too vague — name the kind of email (newsletter / product updates / promotional offers).',
                'fix_hint' => 'Update the consent checkbox label to name the email type: \'I agree to receive {newsletter|product updates|offers} from {Brand}.\'',
            ],
            [
                'id' => 'data_minimization',
                'label' => 'Required-field count <=3',
                'rationale' => 'GDPR Article 5(1)(c) — data minimization. Collecting required fields you don\'t actually use is a violation. 3 required fields is a soft ceiling for marketing forms; B2B lead-gen can justify up to 5 if every field has a documented use.',
                'fix_hint' => 'Mark fields you don\'t strictly need as `required=0`. If you can\'t articulate why a field is required, downgrade it to optional or remove it.',
            ],
            [
                'id' => 'unsubscribe_info',
                'label' => 'Success message mentions unsubscribe',
                'rationale' => 'Reinforces consent reversibility. Optional under GDPR but materially raises trust + reduces complaint rate. Most ESPs require unsubscribe in every email anyway — say it on the form too.',
                'fix_hint' => 'Add to your success message: \'You can unsubscribe anytime via the link in any email.\' One sentence is enough.',
            ],
        ];
    }

    /**
     * @return list<string>
     */
    public static function ruleIds(): array
    {
        return array_map(fn (array $r) => $r['id'], self::rules());
    }

    /**
     * @return array{id:string,label:string,rationale:string,fix_hint:string}|null
     */
    public static function rule(string $id): ?array
    {
        foreach (self::rules() as $rule) {
            if ($rule['id'] === $id) {
                return $rule;
            }
        }

        return null;
    }
}