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.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 3.0.60 3.0.61 3.0.62 3.0.70 3.0.71 3.0.72 All 35 releases
double-opt-in / core / credit_nudge.php

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

194 lines 6.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * The one-time ask: would you show a credit on your confirmation page?
4 *
5 * Asked once, after the plugin has actually done something, and never again
6 * either way.
7 *
8 * The shape of this notice is deliberate, because the tempting version is the
9 * one that gets a plugin thrown out of the directory. Guideline 10 requires the
10 * choice to be made through "clearly stated and understandable choices, not
11 * buried in the terms of use or documentation" — so: both answers are ordinary
12 * buttons of the same weight, the wording says plainly what will appear and
13 * where, and the preview shows the exact markup rather than describing it. No
14 * pre-selection, no dark pattern, no second asking.
15 *
16 * What it does lean on is legitimate and true: the plugin has confirmed a real,
17 * countable number of opt-ins for this site before it asks for anything, and it
18 * says why the link helps. That is the whole of the persuasion, and it is the
19 * only part worth having — a site owner tricked into it removes it the moment
20 * they notice, and leaves a one-star review on the way out.
21 */
22
23 namespace forge12\contactform7\CF7DoubleOptIn;
24
25 if ( ! defined( 'ABSPATH' ) ) {
26 exit;
27 }
28
29 /**
30 * Confirmed opt-ins required before the question is asked at all.
31 *
32 * High enough that the plugin has demonstrably earned the ask, low enough that
33 * an active site reaches it in a sensible time. Lower than the captcha plugin's
34 * equivalent (100 blocked spam submissions) because a confirmed opt-in is a
35 * much rarer event than a blocked spam attempt.
36 */
37 const CREDIT_NUDGE_THRESHOLD = 50;
38
39 /**
40 * Option holding the answer. Absent = not asked yet, 'accepted' / 'declined' = done.
41 */
42 const CREDIT_NUDGE_ANSWER_OPTION = 'f12_doi_credit_nudge_answer';
43
44 /**
45 * How many opt-ins this install has confirmed.
46 *
47 * The same counter the review notice uses, so the two never tell the site owner
48 * different numbers for the same thing.
49 */
50 function get_confirmed_count(): int {
51 $counters = get_option( 'f12_cf7_doubleoptin_telemetry_counters', array() );
52
53 return ( is_array( $counters ) && isset( $counters['confirmed_optins'] ) )
54 ? (int) $counters['confirmed_optins']
55 : 0;
56 }
57
58 /**
59 * Whether to ask on this screen.
60 */
61 function should_show_credit_nudge(): bool {
62 if ( ! current_user_can( 'manage_options' ) ) {
63 return false;
64 }
65
66 // Answered once, in either direction: never ask again.
67 if ( get_option( CREDIT_NUDGE_ANSWER_OPTION, '' ) !== '' ) {
68 return false;
69 }
70
71 // Already on — nothing to ask for. Covers the site owner who found the
72 // setting themselves.
73 if ( is_credit_enabled() ) {
74 return false;
75 }
76
77 if ( get_confirmed_count() < CREDIT_NUDGE_THRESHOLD ) {
78 return false;
79 }
80
81 // Never alongside the review notice. Two requests on one screen is the
82 // nagging that earns one-star reviews, and the review is the more valuable
83 // of the two.
84 if ( ! empty( $GLOBALS['f12_doi_review_notice_shown'] ) ) {
85 return false;
86 }
87
88 return true;
89 }
90
91 /**
92 * Render the notice.
93 */
94 function render_credit_nudge(): void {
95 if ( ! should_show_credit_nudge() ) {
96 return;
97 }
98
99 $confirmed = get_confirmed_count();
100 $accept = wp_nonce_url( add_query_arg( 'f12_doi_credit', 'yes' ), 'f12_doi_credit' );
101 $decline = wp_nonce_url( add_query_arg( 'f12_doi_credit', 'no' ), 'f12_doi_credit' );
102 ?>
103 <div class="notice notice-info f12-doi-credit-nudge">
104 <p>
105 <?php
106 printf(
107 wp_kses(
108 /* translators: %s: number of confirmed opt-ins, already formatted. */
109 __( 'Double Opt-In has confirmed <strong>%s opt-ins</strong> on this site so far.', 'double-opt-in' ),
110 array( 'strong' => array() )
111 ),
112 esc_html( number_format_i18n( $confirmed ) )
113 );
114 ?>
115 <?php esc_html_e( 'Would you show a small credit on your confirmation page? It helps other site owners find the plugin, and it is what keeps it free.', 'double-opt-in' ); ?>
116 </p>
117
118 <p style="margin:0 0 4px;"><?php esc_html_e( 'This is exactly what would appear, on the page a subscriber lands on after confirming:', 'double-opt-in' ); ?></p>
119 <div style="padding:8px 12px;background:#fff;border:1px solid #dcdcde;border-radius:3px;display:inline-block;margin-bottom:8px;">
120 <?php
121 // Rendered, not described: the real objection is "will this clutter
122 // my page", and only the actual thing answers it.
123 echo wp_kses(
124 get_credit_markup( true ),
125 array(
126 'p' => array( 'class' => true ),
127 'a' => array(
128 'href' => true,
129 'target' => true,
130 'rel' => true,
131 ),
132 )
133 );
134 ?>
135 </div>
136
137 <p>
138 <a href="<?php echo esc_url( $accept ); ?>" class="button button-primary">
139 <?php esc_html_e( 'Yes, show the link', 'double-opt-in' ); ?>
140 </a>
141 <a href="<?php echo esc_url( $decline ); ?>" class="button">
142 <?php esc_html_e( 'No thanks', 'double-opt-in' ); ?>
143 </a>
144 <span style="margin-left:8px;color:#646970;">
145 <?php esc_html_e( 'Asked once. You can change it any time under Settings.', 'double-opt-in' ); ?>
146 </span>
147 </p>
148 </div>
149 <?php
150 }
151
152 /**
153 * Record the answer.
154 *
155 * Nonce-checked and capability-checked: unlike a dismiss flag, "yes" writes a
156 * setting that changes what every visitor of the confirmation page sees, so a
157 * stray link must not be able to trigger it.
158 */
159 function handle_credit_nudge_answer(): void {
160 if ( ! isset( $_GET['f12_doi_credit'] ) ) {
161 return;
162 }
163
164 if ( ! current_user_can( 'manage_options' ) ) {
165 return;
166 }
167
168 check_admin_referer( 'f12_doi_credit' );
169
170 $answer = sanitize_text_field( wp_unslash( $_GET['f12_doi_credit'] ) );
171
172 if ( $answer === 'yes' ) {
173 $settings = get_option( 'f12-doi-settings', array() );
174 if ( ! is_array( $settings ) ) {
175 $settings = array();
176 }
177 $settings[ CREDIT_SETTING_KEY ] = 1;
178 update_option( 'f12-doi-settings', $settings );
179
180 update_option( CREDIT_NUDGE_ANSWER_OPTION, 'accepted' );
181 } else {
182 update_option( CREDIT_NUDGE_ANSWER_OPTION, 'declined' );
183 }
184
185 // Drop the parameters so a refresh does not replay the action.
186 wp_safe_redirect( remove_query_arg( array( 'f12_doi_credit', '_wpnonce' ) ) );
187 exit;
188 }
189
190 // Priority 20: after the review notice, so the guard in
191 // should_show_credit_nudge() can see whether that one already claimed this screen.
192 add_action( 'admin_notices', __NAMESPACE__ . '\render_credit_nudge', 20 );
193 add_action( 'admin_init', __NAMESPACE__ . '\handle_credit_nudge_answer' );
194