PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.5.0
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.5.0
5.5.0 5.4.0 5.3.2 5.3.1 5.1.6 5.1.5 trunk 2.1.5 2.11 2.12 2.13 2.15 3.0.0 3.0.1 3.0.2 3.0.3 3.0.5 3.0.51 3.0.60 3.0.61 3.0.62 3.0.70 3.0.71 3.0.72 3.1.0 All 34 releases
double-opt-in / src / Admin / AdminNoticeIncompleteForms.php

AdminNoticeIncompleteForms.php in Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification 5.5.0, at src/Admin/AdminNoticeIncompleteForms.php

160 lines 5.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Admin Notice: Incomplete Forms Auto-Disabled
4 *
5 * @package Forge12\DoubleOptIn\Admin
6 * @since 4.5.0
7 */
8
9 namespace Forge12\DoubleOptIn\Admin;
10
11 use Forge12\DoubleOptIn\Migration\MigrationFormCompletenessSweep;
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 /**
18 * Renders an admin notice listing the forms that the
19 * {@see MigrationFormCompletenessSweep} migration auto-disabled at the
20 * last upgrade. Each entry links to the form-settings edit page so the
21 * admin can complete the configuration and re-enable it manually.
22 *
23 * Listens on the option key {@see MigrationFormCompletenessSweep::AFFECTED_FORMS_OPTION}.
24 * The migration writes the list once at upgrade; the notice stays
25 * around across page loads until the admin explicitly dismisses it
26 * (via a nonced query-arg link) — we deliberately do NOT auto-clear
27 * the option after one render, because the migration is rare and the
28 * admin may not catch the notice on the first admin page-load.
29 *
30 * Plan/doi-completeness-gate.md §2.4 (Admin-Notice mit Liste betroffener
31 * Forms + Links zur Edit-Seite).
32 */
33 class AdminNoticeIncompleteForms {
34
35 private const DISMISS_QUERY_ARG = 'f12_doi_dismiss_incomplete_notice';
36 private const DISMISS_NONCE = 'f12_doi_dismiss_incomplete';
37
38 /**
39 * Hook the notice into the WordPress admin lifecycle.
40 *
41 * Idempotent — calling it twice does not double-register thanks to
42 * WordPress's add_action de-duplication for identical callbacks.
43 */
44 public function register(): void {
45 add_action( 'admin_init', array( $this, 'maybeDismiss' ) );
46 add_action( 'admin_notices', array( $this, 'render' ) );
47 }
48
49 /**
50 * If the admin clicked the dismiss link, clear the option and
51 * redirect to drop the query args from the URL bar.
52 */
53 public function maybeDismiss(): void {
54 if ( ! isset( $_GET[ self::DISMISS_QUERY_ARG ] ) ) {
55 return;
56 }
57 if ( ! current_user_can( 'manage_options' ) ) {
58 return;
59 }
60 $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
61 if ( ! wp_verify_nonce( $nonce, self::DISMISS_NONCE ) ) {
62 return;
63 }
64
65 delete_option( MigrationFormCompletenessSweep::AFFECTED_FORMS_OPTION );
66
67 $redirect = remove_query_arg( array( self::DISMISS_QUERY_ARG, '_wpnonce' ) );
68 wp_safe_redirect( $redirect );
69 exit;
70 }
71
72 /**
73 * Render the notice if there is anything to show.
74 *
75 * Capability-gated to `manage_options` because non-admin users have
76 * no business seeing form-settings paths.
77 */
78 public function render(): void {
79 if ( ! current_user_can( 'manage_options' ) ) {
80 return;
81 }
82
83 $affected = get_option( MigrationFormCompletenessSweep::AFFECTED_FORMS_OPTION, array() );
84 if ( ! is_array( $affected ) || empty( $affected ) ) {
85 return;
86 }
87
88 $items = array();
89 foreach ( $affected as $row ) {
90 if ( ! is_array( $row ) ) {
91 continue;
92 }
93 $formId = (int) ( $row['form_id'] ?? 0 );
94 $missing = (array) ( $row['missing'] ?? array() );
95 if ( $formId <= 0 ) {
96 continue;
97 }
98
99 $title = get_the_title( $formId );
100 if ( empty( $title ) ) {
101 /* translators: %d: form/post ID */
102 $title = sprintf( __( 'Form #%d', 'double-opt-in' ), $formId );
103 }
104
105 // Deep-link into the React SPA edit page. The
106 // admin-page slug `doi-forms` is registered by
107 // AdminPageController.
108 $editUrl = admin_url( 'admin.php?page=doi-forms#/forms/' . $formId . '/edit' );
109
110 $missingLabels = array_map( array( $this, 'labelForMissingField' ), $missing );
111
112 $items[] = sprintf(
113 '<li><a href="%s">%s</a> &mdash; %s: <code>%s</code></li>',
114 esc_url( $editUrl ),
115 esc_html( $title ),
116 esc_html__( 'missing', 'double-opt-in' ),
117 esc_html( implode( ', ', $missingLabels ) )
118 );
119 }
120
121 if ( empty( $items ) ) {
122 return;
123 }
124
125 $dismissUrl = wp_nonce_url(
126 add_query_arg( self::DISMISS_QUERY_ARG, '1' ),
127 self::DISMISS_NONCE
128 );
129
130 printf(
131 '<div class="notice notice-warning"><p><strong>%s</strong></p><p>%s</p><ul>%s</ul><p><a href="%s">%s</a></p></div>',
132 esc_html__( 'Double Opt-In: forms auto-disabled due to incomplete configuration', 'double-opt-in' ),
133 esc_html__( 'The following forms had their Double Opt-In disabled during the upgrade because required fields were missing. Open each form to complete its configuration and re-enable it.', 'double-opt-in' ),
134 implode( '', $items ), // each item is already escaped above
135 esc_url( $dismissUrl ),
136 esc_html__( 'Dismiss this notice', 'double-opt-in' )
137 );
138 }
139
140 /**
141 * Map a stable missing-field ID to a translatable user-facing label.
142 *
143 * Mirrors the message map in FormSettingsValidator::messageForMissingField()
144 * but at the field-name level (the notice already says "missing"
145 * surrounding it).
146 */
147 private function labelForMissingField( string $field ): string {
148 $labels = apply_filters(
149 'f12_doi_form_missing_field_labels',
150 array(
151 'recipient' => __( 'Recipient field', 'double-opt-in' ),
152 'subject' => __( 'Subject', 'double-opt-in' ),
153 'body_or_template' => __( 'Email body or template', 'double-opt-in' ),
154 )
155 );
156
157 return $labels[ $field ] ?? $field;
158 }
159 }
160