PluginProbe ʕ •ᴥ•ʔ
JetFormBuilder — Dynamic Blocks Form Builder / trunk
JetFormBuilder — Dynamic Blocks Form Builder vtrunk
3.6.3.1 3.6.3 3.6.2.2 3.6.2.1 3.6.2 3.6.1.1 3.6.1 3.6.0.1 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.1.0 2.1.1 2.1.10 2.1.11 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 3.0.0 3.0.0.1 3.0.0.2 3.0.0.3 3.0.1 3.0.1.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.0.1 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.1 3.2.2 3.2.3 3.3.0 3.3.1 3.3.2 3.3.3 3.3.3.1 3.3.4 3.3.4.1 3.3.4.2 3.4.0 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.5.1 3.4.5.2 3.4.6 3.4.7 3.4.7.1 3.5.0 3.5.1 3.5.1.1 3.5.1.2 3.5.2 3.5.2.1 3.5.3 3.5.4 3.5.5 3.5.6 3.5.6.1 3.5.6.2 3.5.6.3 3.6.0
jetformbuilder / compatibility / jet-engine / update-options-notice.php
jetformbuilder / compatibility / jet-engine Last commit date
actions 2 weeks ago assets 1 week ago blocks 1 month ago compatibility 1 year ago generators 2 months ago macros 2 months ago map-field 1 year ago methods 1 year ago option-query 1 year ago preset-sources 2 years ago jet-engine.php 2 weeks ago update-options-notice.php 2 weeks ago
update-options-notice.php
346 lines
1 <?php
2
3 namespace JFB_Compatibility\Jet_Engine;
4
5 use JFB_Modules\Post_Type\Module as Post_Type_Module;
6
7 if ( ! defined( 'WPINC' ) ) {
8 die;
9 }
10
11 class Update_Options_Notice {
12
13 const SCAN_VERSION_OPTION = 'jet_fb_jet_engine_update_options_scan_version';
14 const NOTICE_OPTION = 'jet_fb_jet_engine_update_options_notice';
15 const DISMISS_META_KEY = 'jet_fb_jet_engine_update_options_notice_dismissed';
16 const NOTICE_QUERY_ARG = 'jet_fb_dismiss_jet_engine_update_options_notice';
17 const SCAN_SCHEMA_VERSION = '1';
18 const SCAN_BATCH_SIZE = 30;
19
20 public function init_hooks() {
21 add_action( 'save_post_' . Post_Type_Module::SLUG, array( $this, 'invalidate_scan_cache' ) );
22
23 if ( ! is_admin() ) {
24 return;
25 }
26
27 add_action( 'admin_init', array( $this, 'maybe_scan_affected_forms' ) );
28 add_action( 'admin_init', array( $this, 'maybe_dismiss_notice' ) );
29 add_action( 'admin_notices', array( $this, 'render_affected_forms_notice' ) );
30 }
31
32 public function remove_hooks() {
33 remove_action( 'save_post_' . Post_Type_Module::SLUG, array( $this, 'invalidate_scan_cache' ) );
34 remove_action( 'admin_init', array( $this, 'maybe_scan_affected_forms' ) );
35 remove_action( 'admin_init', array( $this, 'maybe_dismiss_notice' ) );
36 remove_action( 'admin_notices', array( $this, 'render_affected_forms_notice' ) );
37 }
38
39 public function invalidate_scan_cache() {
40 delete_option( self::SCAN_VERSION_OPTION );
41 }
42
43 public function maybe_scan_affected_forms() {
44 if ( ! current_user_can( 'manage_options' ) ) {
45 return;
46 }
47
48 if (
49 wp_doing_ajax() ||
50 wp_doing_cron() ||
51 ( defined( 'REST_REQUEST' ) && REST_REQUEST )
52 ) {
53 return;
54 }
55
56 $version = $this->get_scan_version();
57 $notice = get_option( self::NOTICE_OPTION, array() );
58
59 if ( get_option( self::SCAN_VERSION_OPTION, '' ) === $version ) {
60 return;
61 }
62
63 $tracked_ids = array_values(
64 array_unique(
65 array_filter(
66 array_map( 'intval', $notice['tracked_ids'] ?? array() )
67 )
68 )
69 );
70
71 if ( array_key_exists( 'tracked_ids', $notice ) ) {
72 $forms = empty( $tracked_ids )
73 ? array()
74 : $this->scan_specific_forms( $tracked_ids );
75
76 update_option( self::SCAN_VERSION_OPTION, $version, false );
77 update_option(
78 self::NOTICE_OPTION,
79 array(
80 'version' => $version,
81 'notice_token' => wp_generate_uuid4(),
82 'tracked_ids' => $tracked_ids,
83 'forms' => $forms,
84 ),
85 false
86 );
87
88 return;
89 }
90
91 $forms = $this->scan_affected_forms();
92 $tracked_ids = array_values( array_unique( array_map( 'intval', wp_list_pluck( $forms, 'id' ) ) ) );
93
94 update_option( self::SCAN_VERSION_OPTION, $version, false );
95 update_option(
96 self::NOTICE_OPTION,
97 array(
98 'version' => $version,
99 'notice_token' => wp_generate_uuid4(),
100 'tracked_ids' => $tracked_ids,
101 'forms' => $forms,
102 ),
103 false
104 );
105 }
106
107 public function maybe_dismiss_notice() {
108 if ( ! current_user_can( 'manage_options' ) ) {
109 return;
110 }
111
112 if ( empty( $_GET[ self::NOTICE_QUERY_ARG ] ) ) {
113 return;
114 }
115
116 check_admin_referer( self::NOTICE_QUERY_ARG );
117
118 update_user_meta(
119 get_current_user_id(),
120 self::DISMISS_META_KEY,
121 $this->get_scan_version()
122 );
123
124 wp_safe_redirect(
125 remove_query_arg(
126 array(
127 self::NOTICE_QUERY_ARG,
128 '_wpnonce',
129 )
130 )
131 );
132 exit;
133 }
134
135 public function render_affected_forms_notice() {
136 if ( ! current_user_can( 'manage_options' ) ) {
137 return;
138 }
139
140 $notice = get_option( self::NOTICE_OPTION, array() );
141
142 if ( empty( $notice['forms'] ) || empty( $notice['version'] ) ) {
143 return;
144 }
145
146 if ( $this->get_scan_version() !== $notice['version'] ) {
147 return;
148 }
149
150 if ( $this->is_notice_dismissed( $notice ) ) {
151 return;
152 }
153
154 $forms = array_slice( $notice['forms'], 0, 5 );
155 $more_forms = count( $notice['forms'] ) - count( $forms );
156 $dismiss_url = wp_nonce_url(
157 add_query_arg( self::NOTICE_QUERY_ARG, '1' ),
158 self::NOTICE_QUERY_ARG
159 );
160
161 ?>
162 <div class="notice notice-warning">
163 <p>
164 <strong><?php esc_html_e( 'JetFormBuilder: review JetEngine Update Options actions after this update.', 'jet-form-builder' ); ?></strong>
165 </p>
166 <p>
167 <?php esc_html_e( 'JetEngine Update Options actions now require the manage_options capability before they can modify an Options Page. Review the forms below if you expected these updates to run for public or lower-privilege submissions.', 'jet-form-builder' ); ?>
168 </p>
169 <p>
170 <?php esc_html_e( 'If you intentionally need a non-admin flow, replace this action with Call Hook and update the option in custom code where you can enforce your own capability and request validation rules.', 'jet-form-builder' ); ?>
171 </p>
172 <ul style="list-style: disc; margin-left: 1.5em;">
173 <?php foreach ( $forms as $form ) : ?>
174 <li>
175 <a href="<?php echo esc_url( $form['edit_link'] ); ?>">
176 <?php echo esc_html( $form['title'] ); ?>
177 </a>
178 <?php echo esc_html( ' (#' . $form['id'] . ')' ); ?>:
179 <?php echo esc_html( implode( '; ', $form['issues'] ) ); ?>
180 </li>
181 <?php endforeach; ?>
182 </ul>
183 <?php if ( $more_forms > 0 ) : ?>
184 <p>
185 <?php
186 echo esc_html(
187 sprintf(
188 /* translators: %d: number of hidden forms */
189 __( 'Plus %d more form(s) that should be reviewed.', 'jet-form-builder' ),
190 $more_forms
191 )
192 );
193 ?>
194 </p>
195 <?php endif; ?>
196 <p>
197 <a href="<?php echo esc_url( $dismiss_url ); ?>" class="button button-secondary">
198 <?php esc_html_e( 'Dismiss', 'jet-form-builder' ); ?>
199 </a>
200 </p>
201 </div>
202 <?php
203 }
204
205 private function scan_affected_forms(): array {
206 $affected_forms = array();
207 $offset = 0;
208
209 while ( true ) {
210 $form_ids = get_posts(
211 array(
212 'post_type' => Post_Type_Module::SLUG,
213 'post_status' => array( 'publish', 'draft', 'pending', 'future', 'private' ),
214 'posts_per_page' => self::SCAN_BATCH_SIZE,
215 'offset' => $offset,
216 'fields' => 'ids',
217 'orderby' => 'ID',
218 'order' => 'ASC',
219 'no_found_rows' => true,
220 'update_post_meta_cache' => false,
221 'update_post_term_cache' => false,
222 )
223 );
224
225 if ( empty( $form_ids ) ) {
226 break;
227 }
228
229 foreach ( $form_ids as $form_id ) {
230 $issues = $this->get_form_issues( (int) $form_id );
231
232 if ( empty( $issues ) ) {
233 continue;
234 }
235
236 $affected_forms[] = array(
237 'id' => (int) $form_id,
238 'title' => get_the_title( $form_id ) ?: sprintf(
239 /* translators: %d: form ID */
240 __( 'Form #%d', 'jet-form-builder' ),
241 $form_id
242 ),
243 'edit_link' => get_edit_post_link( $form_id, 'raw' ) ?: admin_url( 'post.php?post=' . absint( $form_id ) . '&action=edit' ),
244 'issues' => array_values( array_unique( $issues ) ),
245 );
246 }
247
248 if ( count( $form_ids ) < self::SCAN_BATCH_SIZE ) {
249 break;
250 }
251
252 $offset += self::SCAN_BATCH_SIZE;
253 }
254
255 return $affected_forms;
256 }
257
258 private function scan_specific_forms( array $form_ids ): array {
259 $affected_forms = array();
260
261 foreach ( $form_ids as $form_id ) {
262 $form_id = (int) $form_id;
263
264 if ( $form_id <= 0 || Post_Type_Module::SLUG !== get_post_type( $form_id ) ) {
265 continue;
266 }
267
268 $issues = $this->get_form_issues( $form_id );
269
270 if ( empty( $issues ) ) {
271 continue;
272 }
273
274 $affected_forms[] = array(
275 'id' => $form_id,
276 'title' => get_the_title( $form_id ) ?: sprintf(
277 /* translators: %d: form ID */
278 __( 'Form #%d', 'jet-form-builder' ),
279 $form_id
280 ),
281 'edit_link' => get_edit_post_link( $form_id, 'raw' ) ?: admin_url( 'post.php?post=' . absint( $form_id ) . '&action=edit' ),
282 'issues' => array_values( array_unique( $issues ) ),
283 );
284 }
285
286 return $affected_forms;
287 }
288
289 private function get_form_issues( int $form_id ): array {
290 $actions = jet_form_builder()->post_type->get_actions( $form_id );
291 $issues = array();
292
293 foreach ( $actions as $action ) {
294 if ( 'update_options' !== ( $action['type'] ?? '' ) ) {
295 continue;
296 }
297
298 $issues = array_merge( $issues, $this->get_update_options_action_issues( $action ) );
299 }
300
301 return array_values( array_unique( $issues ) );
302 }
303
304 private function get_update_options_action_issues( array $action ): array {
305 $settings = $action['settings']['update_options'] ?? $action['settings'] ?? array();
306 $options_page = (string) ( $settings['options_page'] ?? '' );
307 $issues = array();
308
309 if ( '' !== $options_page ) {
310 $issues[] = __(
311 'updates the JetEngine Options Page, which now requires manage_options during form submission',
312 'jet-form-builder'
313 );
314 } else {
315 $issues[] = __(
316 'uses the JetEngine Update Options action, which now requires manage_options during form submission',
317 'jet-form-builder'
318 );
319 }
320
321 return $issues;
322 }
323
324 private function get_scan_version(): string {
325 return jet_form_builder()->get_version() . ':' . self::SCAN_SCHEMA_VERSION;
326 }
327
328 private function is_notice_dismissed( array $notice ): bool {
329 $dismissed = (string) get_user_meta( get_current_user_id(), self::DISMISS_META_KEY, true );
330
331 if ( '' === $dismissed ) {
332 return false;
333 }
334
335 if ( $dismissed === $this->get_scan_version() ) {
336 return true;
337 }
338
339 return $dismissed === $this->get_notice_dismiss_token( $notice );
340 }
341
342 private function get_notice_dismiss_token( array $notice ): string {
343 return (string) ( $notice['notice_token'] ?? $notice['version'] ?? '' );
344 }
345 }
346