PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.8.1
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.8.1
5.8.0 5.8.1 5.7.0 5.6.2 5.6.3 5.6.1 5.6.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 All 41 releases
double-opt-in / src / Frontend / ConfirmationShortcodes.php

ConfirmationShortcodes.php in Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification 5.8.1, at src/Frontend/ConfirmationShortcodes.php

234 lines 7.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Shortcodes for the confirmation and error pages.
4 *
5 * - [doi_confirmation_status] says what happened to the link the visitor
6 * just clicked: confirmed, already confirmed, expired or invalid.
7 * - [doi_error_message] explains the `?doi_error=<code>` that a refused
8 * submission is redirected with. Up to 5.7 nothing read that parameter.
9 * - [doi_field name="…"] greets with a value from the form, only in the
10 * request that confirmed it.
11 *
12 * Without a shortcode on the page, a failed link still gets its message in
13 * front of the content — the new integration system set the status but
14 * showed it nowhere.
15 *
16 * @package Forge12\DoubleOptIn\Frontend
17 * @since 5.8.0
18 */
19
20 declare( strict_types=1 );
21
22 namespace Forge12\DoubleOptIn\Frontend;
23
24 use Forge12\DoubleOptIn\Integration\AbstractFormIntegration;
25 use Forge12\DoubleOptIn\Integration\OptInError;
26
27 if ( ! defined( 'ABSPATH' ) ) {
28 exit;
29 }
30
31 class ConfirmationShortcodes {
32
33 /** @var callable(): string */
34 private $status;
35
36 /**
37 * Form values of the opt-in confirmed in this request.
38 *
39 * @var array<string, mixed>|null
40 */
41 private $confirmedFields = null;
42
43 /** @var bool */
44 private $statusShown = false;
45
46 /**
47 * @param callable|null $status Current validation status; defaults to the integration system's.
48 */
49 public function __construct( ?callable $status = null ) {
50 $this->status = $status ?? array( AbstractFormIntegration::class, 'getValidationStatus' );
51 }
52
53 public function register(): void {
54 add_shortcode( 'doi_confirmation_status', array( $this, 'renderStatus' ) );
55 add_shortcode( 'doi_error_message', array( $this, 'renderError' ) );
56 add_shortcode( 'doi_field', array( $this, 'renderField' ) );
57
58 add_action( 'f12_cf7_doubleoptin_after_confirm', array( $this, 'rememberConfirmed' ), 1, 2 );
59 // After do_shortcode (11): by then a [doi_confirmation_status] on the page has run.
60 add_filter( 'the_content', array( $this, 'prependFallbackNotice' ), 12 );
61 }
62
63 /**
64 * @param mixed $hash Confirmed hash (unused).
65 * @param mixed $optIn The confirmed opt-in (legacy wrapper or entity).
66 */
67 public function rememberConfirmed( $hash, $optIn ): void {
68 if ( is_object( $optIn ) && method_exists( $optIn, 'getEntity' ) ) {
69 $optIn = $optIn->getEntity();
70 }
71 if ( is_object( $optIn ) && method_exists( $optIn, 'getContentArray' ) ) {
72 $this->confirmedFields = (array) $optIn->getContentArray();
73 }
74 }
75
76 /**
77 * Default texts per validation status.
78 *
79 * @return array<string, string>
80 */
81 public static function statusMessages(): array {
82 return array(
83 'confirmed' => __( 'Thank you! Your email address has been confirmed.', 'double-opt-in' ),
84 'already_confirmed' => __( 'Your opt-in has already been confirmed.', 'double-opt-in' ),
85 'expired' => __( 'This confirmation link has expired. Please submit the form again.', 'double-opt-in' ),
86 'not_found' => __( 'This confirmation link is invalid.', 'double-opt-in' ),
87 );
88 }
89
90 /**
91 * [doi_confirmation_status confirmed="…" already_confirmed="…" expired="…" not_found="…" none="…"]
92 *
93 * @param mixed $atts Shortcode attributes.
94 */
95 public function renderStatus( $atts = array() ): string {
96 $status = (string) call_user_func( $this->status );
97 $atts = shortcode_atts( array_merge( self::statusMessages(), array( 'none' => '' ) ), is_array( $atts ) ? $atts : array(), 'doi_confirmation_status' );
98
99 $this->statusShown = true;
100
101 $key = $status !== '' && isset( $atts[ $status ] ) ? $status : 'none';
102 $message = (string) $atts[ $key ];
103 if ( $message === '' ) {
104 return '';
105 }
106
107 return self::notice( $key === 'none' ? 'none' : $status, $message );
108 }
109
110 /**
111 * Visitor-facing texts per OptInError code.
112 *
113 * @return array<string, string>
114 */
115 public static function errorMessages(): array {
116 $messages = array(
117 OptInError::RATE_LIMIT_IP => __( 'Too many sign-ups from your connection in a short time. Please try again later.', 'double-opt-in' ),
118 OptInError::RATE_LIMIT_EMAIL => __( 'This email address was signed up several times in a short time. Please check your inbox for the confirmation mail or try again later.', 'double-opt-in' ),
119 OptInError::RECIPIENT_INVALID => __( 'This email address cannot receive mail. Please check it and try again.', 'double-opt-in' ),
120 OptInError::NO_RECIPIENT => __( 'No valid email address was entered. Please try again.', 'double-opt-in' ),
121 OptInError::UNIQUE_EMAIL_DUPLICATE => __( 'This email address is already signed up.', 'double-opt-in' ),
122 OptInError::CONSENT_NOT_GIVEN => __( 'Please agree to the consent text to sign up.', 'double-opt-in' ),
123 OptInError::SAVE_FAILED => __( 'Your sign-up could not be saved. Please try again later.', 'double-opt-in' ),
124 OptInError::SUBMISSION_CANCELLED => __( 'Your sign-up could not be completed. Please try again later.', 'double-opt-in' ),
125 );
126
127 /**
128 * Texts shown by [doi_error_message] per error code.
129 *
130 * @since 5.8.0
131 *
132 * @param array<string, string> $messages code => text.
133 */
134 $filtered = apply_filters( 'f12_doi_error_messages', $messages );
135
136 return is_array( $filtered ) ? $filtered : $messages;
137 }
138
139 /**
140 * [doi_error_message default="…"]
141 *
142 * @param mixed $atts Shortcode attributes.
143 */
144 public function renderError( $atts = array() ): string {
145 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only display of a redirect parameter.
146 $code = isset( $_GET['doi_error'] ) ? sanitize_key( wp_unslash( (string) $_GET['doi_error'] ) ) : '';
147 if ( $code === '' ) {
148 return '';
149 }
150
151 $atts = shortcode_atts(
152 array( 'default' => __( 'Your sign-up could not be completed. Please try again later.', 'double-opt-in' ) ),
153 is_array( $atts ) ? $atts : array(),
154 'doi_error_message'
155 );
156 $messages = self::errorMessages();
157 $message = isset( $messages[ $code ] ) ? (string) $messages[ $code ] : (string) $atts['default'];
158
159 return $message === '' ? '' : self::notice( 'error-' . $code, $message );
160 }
161
162 /**
163 * [doi_field name="first-name" default="…"]
164 *
165 * @param mixed $atts Shortcode attributes.
166 */
167 public function renderField( $atts = array() ): string {
168 $atts = shortcode_atts(
169 array(
170 'name' => '',
171 'default' => '',
172 ),
173 is_array( $atts ) ? $atts : array(),
174 'doi_field'
175 );
176
177 $default = esc_html( (string) $atts['default'] );
178 if ( $this->confirmedFields === null || (string) call_user_func( $this->status ) !== 'confirmed' ) {
179 return $default;
180 }
181
182 $value = $this->confirmedFields[ (string) $atts['name'] ] ?? null;
183 if ( is_array( $value ) ) {
184 $value = implode( ', ', array_map( 'strval', array_filter( $value, 'is_scalar' ) ) );
185 }
186 if ( ! is_scalar( $value ) || trim( (string) $value ) === '' ) {
187 return $default;
188 }
189
190 return esc_html( (string) $value );
191 }
192
193 /**
194 * A failed link on a page without [doi_confirmation_status]: say so first.
195 *
196 * @param mixed $content Post content.
197 *
198 * @return mixed
199 */
200 public function prependFallbackNotice( $content ) {
201 if ( $this->statusShown || ! is_string( $content ) || ! is_main_query() || ! in_the_loop() ) {
202 return $content;
203 }
204
205 $status = (string) call_user_func( $this->status );
206 if ( ! in_array( $status, array( 'already_confirmed', 'expired', 'not_found' ), true ) ) {
207 return $content;
208 }
209
210 /**
211 * Whether a failed confirmation link gets its message in front of the
212 * page content when the page has no [doi_confirmation_status].
213 *
214 * @since 5.8.0
215 *
216 * @param bool $show Default true.
217 * @param string $status Validation status.
218 */
219 if ( ! apply_filters( 'f12_doi_validation_notice_auto', true, $status ) ) {
220 return $content;
221 }
222
223 $this->statusShown = true;
224
225 return self::notice( $status, self::statusMessages()[ $status ] ) . $content;
226 }
227
228 private static function notice( string $status, string $message ): string {
229 return '<div class="doi-validation-notice doi-notice-' . esc_attr( $status ) . '" role="status"><p>'
230 . esc_html( $message )
231 . '</p></div>';
232 }
233 }
234