PluginProbe
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification / 5.6.1
Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification v5.6.1
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 3.0.60 3.0.61 3.0.62 All 38 releases
double-opt-in / src / Migration / AvadaDeprecationNotice.php

AvadaDeprecationNotice.php in Double Opt-In for Contact Form 7 – Secure, GDPR-Compliant Email Verification 5.6.1, at src/Migration/AvadaDeprecationNotice.php

275 lines 9.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Avada Deprecation Notice
4 *
5 * In Core 4.99 this class shows an admin warning on every admin page for
6 * installations where Avada is detected AND a DOI setting exists for any
7 * Avada form. The notice lets the user claim a free grandfather license
8 * for the paid `double-opt-in-avada` addon that ships in Phase 2.
9 *
10 * In Core 5.0+ the same class stays active — at this point Avada is no
11 * longer bundled in Core, so the notice becomes an error-level prompt
12 * if the user has not yet claimed or installed the addon.
13 *
14 * Removal planned: Core 6.0 (one major version after the addon has been
15 * the only supported path).
16 *
17 * @package Forge12\DoubleOptIn\Migration
18 * @since 4.99.0
19 */
20
21 namespace Forge12\DoubleOptIn\Migration;
22
23 if ( ! defined( 'ABSPATH' ) ) {
24 exit;
25 }
26
27 final class AvadaDeprecationNotice {
28
29 /** Option name for storing the grandfather license key after a successful claim. */
30 public const OPTION_GRANDFATHER_LICENSE = 'f12_doi_avada_grandfather_license';
31
32 /** Option name recording that the user dismissed the pre-5.0 notice. */
33 private const OPTION_DISMISSED = 'f12_doi_avada_deprecation_dismissed';
34
35 /** Option name recording the last known Avada-usage detection result (cached for 24h). */
36 private const TRANSIENT_AVADA_USED = 'f12_doi_avada_in_use';
37
38 /** The REST endpoint Core exposes for client-side claim requests. */
39 private const REST_NAMESPACE = 'f12-doi/v1';
40 private const REST_ROUTE = '/avada-migration/claim';
41
42 public static function register(): void {
43 add_action( 'admin_notices', array( self::class, 'renderNotice' ) );
44 add_action( 'wp_ajax_f12_doi_avada_dismiss', array( self::class, 'handleDismiss' ) );
45 add_action( 'rest_api_init', array( self::class, 'registerRestRoute' ) );
46 }
47
48 /**
49 * Decide whether the notice should appear at all on this request.
50 */
51 public static function shouldRender(): bool {
52 // Already claimed? Silence the notice.
53 if ( get_option( self::OPTION_GRANDFATHER_LICENSE, '' ) !== '' ) {
54 return false;
55 }
56
57 // Dismissed pre-5.0 AND we are still pre-5.0? Silence.
58 if ( get_option( self::OPTION_DISMISSED, false ) && ! self::isCore5OrLater() ) {
59 return false;
60 }
61
62 // Avada not installed or not actively used with DOI? Silence.
63 if ( ! self::isAvadaInUseWithDoi() ) {
64 return false;
65 }
66
67 return current_user_can( 'manage_options' );
68 }
69
70 public static function renderNotice(): void {
71 if ( ! self::shouldRender() ) {
72 return;
73 }
74
75 $isPostRemoval = self::isCore5OrLater();
76 $class = $isPostRemoval ? 'notice notice-error' : 'notice notice-warning';
77 $title = $isPostRemoval
78 ? __( 'Avada DOI support has moved to a paid addon', 'double-opt-in' )
79 : __( 'Avada DOI support is moving to a paid addon', 'double-opt-in' );
80
81 $body = $isPostRemoval
82 ? __( 'Your Avada forms with Double Opt-In configured are currently not sending verification emails. Install the free Double Opt-In Avada grandfather license to restore this functionality. This one-time offer is available for existing free-plugin users.', 'double-opt-in' )
83 : __( 'In the next major release, Avada integration will move out of the free plugin and into a separate paid addon. Existing free-plugin users with Avada forms qualify for a free, permanent grandfather license. Claim it now so your forms keep working after the update.', 'double-opt-in' );
84
85 $nonce = wp_create_nonce( 'f12-doi-avada-migration' );
86
87 ?>
88 <div class="<?php echo esc_attr( $class ); ?>" id="f12-doi-avada-notice">
89 <p><strong><?php echo esc_html( $title ); ?></strong></p>
90 <p><?php echo esc_html( $body ); ?></p>
91 <p>
92 <button type="button"
93 class="button button-primary"
94 id="f12-doi-avada-claim"
95 data-nonce="<?php echo esc_attr( $nonce ); ?>">
96 <?php esc_html_e( 'Claim free Avada grandfather license', 'double-opt-in' ); ?>
97 </button>
98 <?php if ( ! $isPostRemoval ) : ?>
99 <button type="button"
100 class="button"
101 id="f12-doi-avada-dismiss"
102 data-nonce="<?php echo esc_attr( wp_create_nonce( 'f12-doi-avada-dismiss' ) ); ?>">
103 <?php esc_html_e( 'Dismiss until update', 'double-opt-in' ); ?>
104 </button>
105 <?php endif; ?>
106 <a href="https://www.forge12.com/shop/contact-form-7-double-opt-in"
107 target="_blank"
108 rel="noopener">
109 <?php esc_html_e( 'Learn more', 'double-opt-in' ); ?>
110 </a>
111 </p>
112 <div id="f12-doi-avada-result" style="margin-top: 10px;"></div>
113 </div>
114 <script>
115 (function () {
116 const ajaxUrl = <?php echo wp_json_encode( admin_url( 'admin-ajax.php' ) ); ?>;
117 const restUrl = <?php echo wp_json_encode( rest_url( self::REST_NAMESPACE . self::REST_ROUTE ) ); ?>;
118 const restNonce = <?php echo wp_json_encode( wp_create_nonce( 'wp_rest' ) ); ?>;
119
120 const claim = document.getElementById('f12-doi-avada-claim');
121 const dismiss = document.getElementById('f12-doi-avada-dismiss');
122 const result = document.getElementById('f12-doi-avada-result');
123
124 if (claim) {
125 claim.addEventListener('click', async function () {
126 claim.disabled = true;
127 result.textContent = <?php echo wp_json_encode( __( 'Requesting grandfather license…', 'double-opt-in' ) ); ?>;
128 try {
129 const response = await fetch(restUrl, {
130 method: 'POST',
131 headers: {
132 'Content-Type': 'application/json',
133 'X-WP-Nonce': restNonce
134 }
135 });
136 const data = await response.json();
137 if (data.success) {
138 result.innerHTML = <?php echo wp_json_encode( __( '<strong>Success.</strong> Your Avada grandfather license has been activated. The Avada addon plugin will be installed automatically on your next update.', 'double-opt-in' ) ); ?>;
139 document.getElementById('f12-doi-avada-notice').className = 'notice notice-success';
140 } else {
141 result.textContent = data.message || <?php echo wp_json_encode( __( 'Could not claim the grandfather license. Please contact support.', 'double-opt-in' ) ); ?>;
142 claim.disabled = false;
143 }
144 } catch (e) {
145 result.textContent = <?php echo wp_json_encode( __( 'Network error while claiming license. Please try again.', 'double-opt-in' ) ); ?>;
146 claim.disabled = false;
147 }
148 });
149 }
150
151 if (dismiss) {
152 dismiss.addEventListener('click', async function () {
153 const body = new URLSearchParams();
154 body.set('action', 'f12_doi_avada_dismiss');
155 body.set('_wpnonce', dismiss.getAttribute('data-nonce'));
156 await fetch(ajaxUrl, { method: 'POST', body: body });
157 document.getElementById('f12-doi-avada-notice').remove();
158 });
159 }
160 })();
161 </script>
162 <?php
163 }
164
165 public static function handleDismiss(): void {
166 check_ajax_referer( 'f12-doi-avada-dismiss' );
167 if ( ! current_user_can( 'manage_options' ) ) {
168 wp_die( '', 403 );
169 }
170 update_option( self::OPTION_DISMISSED, true );
171 wp_send_json_success();
172 }
173
174 public static function registerRestRoute(): void {
175 register_rest_route(
176 self::REST_NAMESPACE,
177 self::REST_ROUTE,
178 array(
179 'methods' => 'POST',
180 'callback' => array( self::class, 'handleClaimRequest' ),
181 'permission_callback' => static function () {
182 return current_user_can( 'manage_options' );
183 },
184 )
185 );
186 }
187
188 /**
189 * REST callback — server-side wrapper around the Forge12 grandfather endpoint.
190 *
191 * @param \WP_REST_Request $request
192 * @return \WP_REST_Response
193 */
194 public static function handleClaimRequest( $request ) {
195 $client = new GrandfatherLicenseClient();
196 $response = $client->claimAvada(
197 home_url(),
198 get_option( 'admin_email', '' ),
199 self::buildAttestation()
200 );
201
202 if ( $response['success'] ) {
203 update_option( self::OPTION_GRANDFATHER_LICENSE, $response['licenseKey'] );
204 }
205
206 return new \WP_REST_Response( $response );
207 }
208
209 /**
210 * Build the self-reported attestation payload that accompanies the claim.
211 *
212 * @return array<string,mixed>
213 */
214 private static function buildAttestation(): array {
215 return array(
216 'avadaDetected' => self::isAvadaActive(),
217 'doiConfiguredOnAvadaForm' => self::doiConfiguredOnAvadaForm(),
218 'lastActivity' => gmdate( 'Y-m-d' ),
219 );
220 }
221
222 /**
223 * Cache-backed check: does this site have at least one Avada form with
224 * DOI settings configured?
225 */
226 public static function isAvadaInUseWithDoi(): bool {
227 $cached = get_transient( self::TRANSIENT_AVADA_USED );
228 if ( $cached === 'yes' ) {
229 return true;
230 }
231 if ( $cached === 'no' ) {
232 return false;
233 }
234
235 $result = self::isAvadaActive() && self::doiConfiguredOnAvadaForm();
236 set_transient( self::TRANSIENT_AVADA_USED, $result ? 'yes' : 'no', DAY_IN_SECONDS );
237
238 return $result;
239 }
240
241 public static function isAvadaActive(): bool {
242 return class_exists( '\\Fusion_Forms' ) || function_exists( 'fusion_builder_init' );
243 }
244
245 /**
246 * Expensive query — only call via the cached isAvadaInUseWithDoi().
247 *
248 * Counts posts of post_type `fusion_form` that have a DOI settings
249 * meta entry. A single row is enough to qualify the site for the notice.
250 */
251 public static function doiConfiguredOnAvadaForm(): bool {
252 global $wpdb;
253
254 $sql = $wpdb->prepare(
255 "SELECT COUNT(*) FROM {$wpdb->postmeta} pm
256 INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
257 WHERE p.post_type = %s
258 AND pm.meta_key = %s
259 AND pm.meta_value != ''
260 LIMIT 1",
261 'fusion_form',
262 'f12-cf7-doubleoptin'
263 );
264
265 return (int) $wpdb->get_var( $sql ) > 0;
266 }
267
268 private static function isCore5OrLater(): bool {
269 if ( ! defined( 'FORGE12_OPTIN_VERSION' ) ) {
270 return false;
271 }
272 return version_compare( FORGE12_OPTIN_VERSION, '5.0.0', '>=' );
273 }
274 }
275