PluginProbe ʕ •ᴥ•ʔ
Advanced Custom Fields (ACF®) / 6.8.9
Advanced Custom Fields (ACF®) v6.8.9
6.8.9 6.8.8 6.8.7 6.8.6 6.8.5 6.8.4 6.8.3 6.8.2 6.8.1 5.8.5 5.8.6 5.8.7 5.8.8 5.8.9 5.9.0 5.9.1 5.9.2 5.9.3 5.9.4 5.9.5 5.9.6 5.9.7 5.9.8 5.9.9 6.0.0 6.0.1 6.0.2 6.0.3 6.0.4 6.0.5 6.0.6 6.0.7 6.1.0 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.1.6 6.1.7 6.1.8 6.2.0 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.2.6.1 6.2.7 6.2.8 6.2.9 6.3.0 6.3.1 6.3.10.2 6.3.11 6.3.12 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.6.1 6.4.0 6.4.0.1 6.4.1 6.4.2 6.4.3 6.5.0 6.5.1 6.6.0 6.6.1 6.6.2 6.7.0 6.7.1 6.7.2 6.8.0 trunk 1.0.0 1.0.2 1.0.3 1.0.5 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.1.1 2.1.3 2.1.4 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.6 3.0.7 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 3.2.0 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.2.8 3.2.9 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.3.8 3.3.9 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.5.4 3.5.5 3.5.6 3.5.7 3.5.8 4.0.0 4.0.1 4.0.2 4.0.3 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.1.5 4.1.6 4.1.8 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.3.7 4.3.8 4.3.9 4.4.0 4.4.1 4.4.10 4.4.11 4.4.12 4.4.2 4.4.3 4.4.4 4.4.5 4.4.6 4.4.7 4.4.8 4.4.9 5.10 5.10.1 5.10.2 5.11 5.11.1 5.11.2 5.11.3 5.11.4 5.12 5.12.1 5.12.2 5.12.3 5.12.4 5.12.5 5.12.6 5.6.10 5.6.2 5.6.3 5.6.4 5.6.5 5.6.6 5.6.7 5.6.8 5.6.9 5.7.0 5.7.1 5.7.10 5.7.12 5.7.13 5.7.2 5.7.3 5.7.4 5.7.5 5.7.6 5.7.7 5.7.8 5.7.9 5.8.0 5.8.1 5.8.10 5.8.11 5.8.12 5.8.13 5.8.14 5.8.2 5.8.3 5.8.4
advanced-custom-fields / includes / admin / admin-email-opt-in-banner.php
advanced-custom-fields / includes / admin Last commit date
post-types 5 months ago tools 6 months ago views 1 month ago admin-email-opt-in-banner.php 1 month ago admin-internal-post-type-list.php 6 months ago admin-internal-post-type.php 6 months ago admin-notices.php 6 months ago admin-options-pages-preview.php 6 months ago admin-tools.php 6 months ago admin-upgrade.php 6 months ago admin.php 1 month ago index.php 2 years ago
admin-email-opt-in-banner.php
284 lines
1 <?php
2 /**
3 * @package ACF
4 * @author WP Engine
5 *
6 * © 2026 Advanced Custom Fields (ACF®). All rights reserved.
7 * "ACF" is a trademark of WP Engine.
8 * Licensed under the GNU General Public License v2 or later.
9 * https://www.gnu.org/licenses/gpl-2.0.html
10 */
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 if ( ! class_exists( 'ACF_Admin_Email_Opt_In_Banner' ) ) :
17
18 /**
19 * Renders the ACF Free email opt-in banner on ACF admin screens
20 * (Field Groups, Post Types, Taxonomies).
21 *
22 * Dismissed/submitted state is persisted per-site in the
23 * `acf_email_opt_in_banner_state` option.
24 */
25 class ACF_Admin_Email_Opt_In_Banner {
26
27 /**
28 * Option key used to persist the banner's dismissed/submitted state.
29 * Stored per-subsite on multisite (uses `get_option`/`update_option`,
30 * not `get_site_option`).
31 *
32 * @var string
33 */
34 const STATE_OPTION = 'acf_email_opt_in_banner_state';
35
36 /**
37 * Persisted state value set when the banner is dismissed.
38 *
39 * @var string
40 */
41 const STATE_DISMISSED = 'dismissed';
42
43 /**
44 * Persisted state value set after a successful submission.
45 *
46 * @var string
47 */
48 const STATE_SUBMITTED = 'submitted';
49
50 /**
51 * Constructor.
52 *
53 * @since 6.8.6
54 */
55 public function __construct() {
56 add_action( 'current_screen', array( $this, 'current_screen' ) );
57 add_action( 'wp_ajax_acf/email_opt_in_banner/state', array( $this, 'ajax_set_state' ) );
58 add_action( 'wp_ajax_acf/email_opt_in_banner/submit', array( $this, 'ajax_submit' ) );
59 }
60
61 /**
62 * Hooks the banner onto ACF admin screens only.
63 *
64 * @since 6.8.6
65 */
66 public function current_screen() {
67 if ( ! $this->should_show() ) {
68 return;
69 }
70
71 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_scripts' ) );
72 add_action( 'admin_footer', array( $this, 'render' ) );
73 }
74
75 /**
76 * Determines if the banner should be shown for the current request.
77 *
78 * @since 6.8.6
79 *
80 * @return boolean
81 */
82 public function should_show() {
83 if ( ! acf_get_setting( 'show_admin' ) ) {
84 return false;
85 }
86
87 if ( ! current_user_can( acf_get_setting( 'capability' ) ) ) {
88 return false;
89 }
90
91 if ( ! $this->is_supported_screen() ) {
92 return false;
93 }
94
95 $default = ! $this->is_state_persisted();
96
97 /**
98 * Filters whether the ACF Free email opt-in banner should be shown.
99 *
100 * @since 6.8.6
101 *
102 * @param boolean $show Whether to show the banner.
103 */
104 return (bool) apply_filters( 'acf/admin/show_email_opt_in_banner', $default );
105 }
106
107 /**
108 * Returns true on the ACF Free admin screens the banner supports:
109 * the Field Groups, Post Types and Taxonomies list screens, and the
110 * Options Pages preview.
111 *
112 * @since 6.8.6
113 *
114 * @return boolean
115 */
116 public function is_supported_screen() {
117 $screens = array(
118 'edit-acf-field-group',
119 'edit-acf-post-type',
120 'edit-acf-taxonomy',
121 );
122
123 foreach ( $screens as $screen ) {
124 if ( acf_is_screen( $screen ) ) {
125 return true;
126 }
127 }
128
129 // The Options Pages preview is a submenu page rather than a list screen.
130 if ( 'acf_options_preview' === acf_request_arg( 'page', '' ) ) {
131 return true;
132 }
133
134 return false;
135 }
136
137 /**
138 * Returns true if the banner's state has already been persisted by a
139 * previous dismiss or submit.
140 *
141 * @since 6.8.6
142 *
143 * @return boolean
144 */
145 public function is_state_persisted() {
146 $state = get_option( self::STATE_OPTION, '' );
147
148 return in_array( $state, array( self::STATE_DISMISSED, self::STATE_SUBMITTED ), true );
149 }
150
151 /**
152 * AJAX handler that persists the banner's dismissed/submitted state.
153 *
154 * @since 6.8.6
155 */
156 public function ajax_set_state() {
157 if ( ! acf_verify_ajax() || ! acf_current_user_can_admin() ) {
158 wp_send_json_error();
159 }
160
161 $state = acf_request_arg( 'state', '' );
162
163 if ( ! in_array( $state, array( self::STATE_DISMISSED, self::STATE_SUBMITTED ), true ) ) {
164 wp_send_json_error();
165 }
166
167 update_option( self::STATE_OPTION, $state, false );
168
169 wp_send_json_success( array( 'state' => $state ) );
170 }
171
172 /**
173 * AJAX handler that submits the opt-in and persists the `submitted`
174 * state on success.
175 *
176 * @since 6.8.6
177 */
178 public function ajax_submit() {
179 if ( ! acf_verify_ajax() || ! acf_current_user_can_admin() ) {
180 wp_send_json_error();
181 }
182
183 if ( self::STATE_SUBMITTED === get_option( self::STATE_OPTION, '' ) ) {
184 wp_send_json_success( array( 'state' => self::STATE_SUBMITTED ) );
185 }
186
187 $email = sanitize_email( (string) acf_request_arg( 'email', '' ) );
188 if ( ! is_email( $email ) ) {
189 wp_send_json_error();
190 }
191
192 $response = $this->send_opt_in( $this->build_opt_in_payload( $email ) );
193
194 if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
195 wp_send_json_error();
196 }
197
198 update_option( self::STATE_OPTION, self::STATE_SUBMITTED, false );
199
200 wp_send_json_success( array( 'state' => self::STATE_SUBMITTED ) );
201 }
202
203 /**
204 * Builds the opt-in submission payload in the API shape expected
205 * by the endpoint that subscribes the email address to ACF
206 * updates and news.
207 *
208 * @since 6.8.6
209 *
210 * @param string $email Sanitized email address.
211 * @return array
212 */
213 private function build_opt_in_payload( $email ) {
214 return array(
215 'fields' => array(
216 array(
217 'objectTypeId' => '0-1',
218 'name' => 'email',
219 'value' => $email,
220 ),
221 ),
222 );
223 }
224
225 /**
226 * POSTs a prepared opt-in payload to the endpoint that subscribes the
227 * email address to ACF updates and news.
228 *
229 * @since 6.8.6
230 *
231 * @param array $payload The payload from `build_opt_in_payload`.
232 * @return array|WP_Error The `wp_remote_post` response.
233 */
234 private function send_opt_in( $payload ) {
235 $url = 'https://api.hsforms.com/submissions/v3/integration/submit/46851451/e420c547-8255-4339-888c-32c58e36a80f';
236
237 return wp_remote_post(
238 $url,
239 array(
240 'timeout' => 10,
241 'headers' => array( 'Content-Type' => 'application/json' ),
242 'body' => wp_json_encode( $payload ),
243 )
244 );
245 }
246
247 /**
248 * Enqueues the banner script and localized strings.
249 *
250 * @since 6.8.6
251 */
252 public function admin_enqueue_scripts() {
253 $suffix = defined( 'ACF_DEVELOPMENT_MODE' ) && ACF_DEVELOPMENT_MODE ? '' : '.min';
254 $version = acf_get_setting( 'version' );
255
256 wp_register_script( 'acf-email-opt-in-banner', acf_get_url( 'assets/build/js/acf-email-opt-in-banner' . $suffix . '.js' ), array( 'jquery', 'acf' ), $version, true );
257 wp_enqueue_script( 'acf-email-opt-in-banner' );
258
259 wp_localize_script(
260 'acf-email-opt-in-banner',
261 'acf_email_opt_in_banner',
262 array(
263 'empty_email' => __( 'Email address is required.', 'acf' ),
264 'invalid_email' => __( 'Please enter a valid email address.', 'acf' ),
265 'generic_error' => __( 'Something went wrong on our end. Please try again.', 'acf' ),
266 )
267 );
268 }
269
270 /**
271 * Renders the banner markup in the admin footer for JS to relocate.
272 *
273 * The email field is intentionally not prefilled to avoid accidental opt-ins.
274 *
275 * @since 6.8.6
276 */
277 public function render() {
278 acf_get_view( 'email-opt-in-banner' );
279 }
280 }
281
282 new ACF_Admin_Email_Opt_In_Banner();
283 endif;
284