PluginProbe ʕ •ᴥ•ʔ
JetFormBuilder — Dynamic Blocks Form Builder / trunk
JetFormBuilder — Dynamic Blocks Form Builder vtrunk
3.6.4 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 / modules / actions-v2 / update-user / update-user.php
jetformbuilder / modules / actions-v2 / update-user Last commit date
assets 1 month ago properties 1 month ago update-user-action.php 1 month ago update-user.php 1 month ago
update-user.php
410 lines
1 <?php
2
3 namespace JFB_Modules\Actions_V2\Update_User;
4
5 use Jet_Form_Builder\Actions\Manager;
6 use Jet_Form_Builder\Admin\Tabs_Handlers\Tab_Handler_Manager;
7 use Jet_Form_Builder\Classes\Tools;
8 use JFB_Modules\Actions_V2\Interfaces\Action_Integration_Interface;
9 use JFB_Modules\Actions_V2\Traits\Action_Integration_Trait;
10 use JFB_Modules\Post_Type\Module as Post_Type_Module;
11
12 class Update_User implements Action_Integration_Interface {
13
14 use Action_Integration_Trait;
15
16 const SCAN_VERSION_OPTION = 'jet_fb_update_user_role_scan_version';
17 const NOTICE_OPTION = 'jet_fb_update_user_role_notice';
18 const DISMISS_META_KEY = 'jet_fb_update_user_role_notice_dismissed';
19 const NOTICE_QUERY_ARG = 'jet_fb_dismiss_update_user_role_notice';
20 const SCAN_SCHEMA_VERSION = '1';
21 const SCAN_BATCH_SIZE = 30;
22
23 public function rep_item_id() {
24 return 'update-user';
25 }
26
27 public function init_hooks() {
28 add_action(
29 'jet-form-builder/editor-assets/after',
30 array( $this, 'editor_assets' )
31 );
32
33 add_action( 'jet-form-builder/update-user/invalidate-role-scan', array( $this, 'invalidate_scan_cache' ) );
34 add_action( 'save_post_' . Post_Type_Module::SLUG, array( $this, 'invalidate_scan_cache' ) );
35
36 if ( ! is_admin() ) {
37 return;
38 }
39
40 add_action( 'admin_init', array( $this, 'maybe_scan_affected_forms' ) );
41 add_action( 'admin_init', array( $this, 'maybe_dismiss_notice' ) );
42 add_action( 'admin_notices', array( $this, 'render_affected_forms_notice' ) );
43 }
44
45 public function on_install() {
46 // TODO: Implement on_install() method.
47 }
48
49 public function register_actions( Manager $manager ) {
50 $manager->register_action_type( new Update_User_Action() );
51 }
52
53 public function editor_assets() {
54 $script_asset = require_once $this->get_dir( 'assets/build/editor.asset.php' );
55
56 array_push(
57 $script_asset['dependencies'],
58 'jet-fb-components',
59 'jet-fb-data',
60 'jet-fb-actions-v2',
61 'jet-fb-blocks-v2-to-actions-v2'
62 );
63
64 wp_enqueue_script(
65 $this->get_handle(),
66 $this->get_url( 'assets/build/editor.js' ),
67 $script_asset['dependencies'],
68 $script_asset['version'],
69 true
70 );
71 }
72
73 public function maybe_scan_affected_forms() {
74 if ( ! current_user_can( 'manage_options' ) ) {
75 return;
76 }
77
78 if (
79 wp_doing_ajax() ||
80 wp_doing_cron() ||
81 ( defined( 'REST_REQUEST' ) && REST_REQUEST )
82 ) {
83 return;
84 }
85
86 $version = $this->get_scan_version();
87 $notice = get_option( self::NOTICE_OPTION, array() );
88
89 if ( get_option( self::SCAN_VERSION_OPTION, '' ) === $version ) {
90 return;
91 }
92
93 $tracked_ids = array_values(
94 array_unique(
95 array_filter(
96 array_map( 'intval', $notice['tracked_ids'] ?? array() )
97 )
98 )
99 );
100
101 if ( array_key_exists( 'tracked_ids', $notice ) ) {
102 $forms = empty( $tracked_ids )
103 ? array()
104 : $this->scan_specific_forms( $tracked_ids );
105
106 update_option( self::SCAN_VERSION_OPTION, $version, false );
107 update_option(
108 self::NOTICE_OPTION,
109 array(
110 'version' => $version,
111 'notice_token' => wp_generate_uuid4(),
112 'tracked_ids' => $tracked_ids,
113 'forms' => $forms,
114 ),
115 false
116 );
117
118 return;
119 }
120
121 $forms = $this->scan_affected_forms();
122 $tracked_ids = array_values( array_unique( array_map( 'intval', wp_list_pluck( $forms, 'id' ) ) ) );
123
124 update_option( self::SCAN_VERSION_OPTION, $version, false );
125
126 update_option(
127 self::NOTICE_OPTION,
128 array(
129 'version' => $version,
130 'notice_token' => wp_generate_uuid4(),
131 'tracked_ids' => array_values( array_unique( array_map( 'intval', wp_list_pluck( $forms, 'id' ) ) ) ),
132 'forms' => $forms,
133 ),
134 false
135 );
136 }
137
138 public function maybe_dismiss_notice() {
139 if ( ! current_user_can( 'manage_options' ) ) {
140 return;
141 }
142
143 if ( empty( $_GET[ self::NOTICE_QUERY_ARG ] ) ) {
144 return;
145 }
146
147 check_admin_referer( self::NOTICE_QUERY_ARG );
148
149 update_user_meta(
150 get_current_user_id(),
151 self::DISMISS_META_KEY,
152 $this->get_scan_version()
153 );
154
155 wp_safe_redirect(
156 remove_query_arg(
157 array(
158 self::NOTICE_QUERY_ARG,
159 '_wpnonce',
160 )
161 )
162 );
163 exit;
164 }
165
166 public function invalidate_scan_cache() {
167 delete_option( self::SCAN_VERSION_OPTION );
168 }
169
170 public function render_affected_forms_notice() {
171 if ( ! current_user_can( 'manage_options' ) ) {
172 return;
173 }
174
175 $notice = get_option( self::NOTICE_OPTION, array() );
176
177 if ( empty( $notice['forms'] ) || empty( $notice['version'] ) ) {
178 return;
179 }
180
181 if ( $this->get_scan_version() !== $notice['version'] ) {
182 return;
183 }
184
185 if ( $this->is_notice_dismissed( $notice ) ) {
186 return;
187 }
188
189 $forms = array_slice( $notice['forms'], 0, 5 );
190 $more_forms = count( $notice['forms'] ) - count( $forms );
191 $dismiss_url = wp_nonce_url(
192 add_query_arg( self::NOTICE_QUERY_ARG, '1' ),
193 self::NOTICE_QUERY_ARG
194 );
195
196 ?>
197 <div class="notice notice-warning">
198 <p>
199 <strong><?php esc_html_e( 'JetFormBuilder: review Update User role transitions after this update.', 'jet-form-builder' ); ?></strong>
200 </p>
201 <p>
202 <?php esc_html_e( 'Some forms use Update User role changes that may now be skipped for users without the promote_users capability. Review the forms below and update the global Self-Promotable Roles setting or the action flow if needed.', 'jet-form-builder' ); ?>
203 </p>
204 <ul style="list-style: disc; margin-left: 1.5em;">
205 <?php foreach ( $forms as $form ) : ?>
206 <li>
207 <a href="<?php echo esc_url( $form['edit_link'] ); ?>">
208 <?php echo esc_html( $form['title'] ); ?>
209 </a>
210 <?php echo esc_html( ' (#' . $form['id'] . ')' ); ?>:
211 <?php echo esc_html( implode( '; ', $form['issues'] ) ); ?>
212 </li>
213 <?php endforeach; ?>
214 </ul>
215 <?php if ( $more_forms > 0 ) : ?>
216 <p>
217 <?php
218 echo esc_html(
219 sprintf(
220 /* translators: %d: number of hidden forms */
221 __( 'Plus %d more form(s) that should be reviewed.', 'jet-form-builder' ),
222 $more_forms
223 )
224 );
225 ?>
226 </p>
227 <?php endif; ?>
228 <p>
229 <a href="<?php echo esc_url( admin_url( 'edit.php?post_type=' . Post_Type_Module::SLUG ) ); ?>" class="button button-primary">
230 <?php esc_html_e( 'Review Forms', 'jet-form-builder' ); ?>
231 </a>
232 <a href="<?php echo esc_url( $dismiss_url ); ?>" class="button button-secondary">
233 <?php esc_html_e( 'Dismiss', 'jet-form-builder' ); ?>
234 </a>
235 </p>
236 </div>
237 <?php
238 }
239
240 private function scan_affected_forms(): array {
241 $affected_forms = array();
242 $offset = 0;
243
244 while ( true ) {
245 $form_ids = get_posts(
246 array(
247 'post_type' => Post_Type_Module::SLUG,
248 'post_status' => array( 'publish', 'draft', 'pending', 'future', 'private' ),
249 'posts_per_page' => self::SCAN_BATCH_SIZE,
250 'offset' => $offset,
251 'fields' => 'ids',
252 'orderby' => 'ID',
253 'order' => 'ASC',
254 'no_found_rows' => true,
255 'update_post_meta_cache' => false,
256 'update_post_term_cache' => false,
257 )
258 );
259
260 if ( empty( $form_ids ) ) {
261 break;
262 }
263
264 foreach ( $form_ids as $form_id ) {
265 $issues = $this->get_form_issues( $form_id );
266
267 if ( empty( $issues ) ) {
268 continue;
269 }
270
271 $affected_forms[] = array(
272 'id' => (int) $form_id,
273 'title' => get_the_title( $form_id ) ?: sprintf(
274 /* translators: %d: form ID */
275 __( 'Form #%d', 'jet-form-builder' ),
276 $form_id
277 ),
278 'edit_link' => get_edit_post_link( $form_id, 'raw' ) ?: admin_url( 'post.php?post=' . absint( $form_id ) . '&action=edit' ),
279 'issues' => array_values( array_unique( $issues ) ),
280 );
281 }
282
283 if ( count( $form_ids ) < self::SCAN_BATCH_SIZE ) {
284 break;
285 }
286
287 $offset += self::SCAN_BATCH_SIZE;
288 }
289
290 return $affected_forms;
291 }
292
293 private function scan_specific_forms( array $form_ids ): array {
294 $affected_forms = array();
295
296 foreach ( $form_ids as $form_id ) {
297 $form_id = (int) $form_id;
298
299 if ( $form_id <= 0 || Post_Type_Module::SLUG !== get_post_type( $form_id ) ) {
300 continue;
301 }
302
303 $issues = $this->get_form_issues( $form_id );
304
305 if ( empty( $issues ) ) {
306 continue;
307 }
308
309 $affected_forms[] = array(
310 'id' => $form_id,
311 'title' => get_the_title( $form_id ) ?: sprintf(
312 /* translators: %d: form ID */
313 __( 'Form #%d', 'jet-form-builder' ),
314 $form_id
315 ),
316 'edit_link' => get_edit_post_link( $form_id, 'raw' ) ?: admin_url( 'post.php?post=' . absint( $form_id ) . '&action=edit' ),
317 'issues' => array_values( array_unique( $issues ) ),
318 );
319 }
320
321 return $affected_forms;
322 }
323
324 private function get_form_issues( int $form_id ): array {
325 $actions = jet_form_builder()->post_type->get_actions( $form_id );
326 $issues = array();
327
328 foreach ( $actions as $action ) {
329 if ( 'update_user' !== ( $action['type'] ?? '' ) ) {
330 continue;
331 }
332
333 $issues = array_merge( $issues, $this->get_update_user_action_issues( $action ) );
334 }
335
336 return array_values( array_unique( $issues ) );
337 }
338
339 private function get_update_user_action_issues( array $action ): array {
340 $settings = $action['settings']['update_user'] ?? $action['settings'] ?? array();
341 $fields_map = $settings['fields_map'] ?? array();
342 $mapped_values = array_map( 'strval', array_values( (array) $fields_map ) );
343 $static_roles = array_values(
344 array_filter(
345 array_map(
346 'strval',
347 Tools::get_array_of_user_roles( $settings['user_role'] ?? array() )
348 )
349 )
350 );
351 $allowed_roles = $this->get_self_promotable_roles();
352 $issues = array();
353
354 if ( in_array( 'role', $mapped_values, true ) ) {
355 $issues[] = __(
356 'maps a form field to the user role, so self-service role changes now depend on the global Self-Promotable Roles list',
357 'jet-form-builder'
358 );
359 }
360
361 if ( empty( $static_roles ) ) {
362 return $issues;
363 }
364
365 $outside_allowlist = array_values( array_diff( $static_roles, $allowed_roles ) );
366
367 if ( ! empty( $outside_allowlist ) ) {
368 $issues[] = __(
369 'assigns static User Role values outside the global Self-Promotable Roles list, so they may be skipped for users without the promote_users capability',
370 'jet-form-builder'
371 );
372 }
373
374 return $issues;
375 }
376
377 private function get_self_promotable_roles(): array {
378 $options = Tab_Handler_Manager::get_options( 'options-tab' );
379 $roles = Tools::get_array_of_user_roles( $options['self_promotable_roles'] ?? array() );
380
381 return array_values(
382 array_filter(
383 array_map( 'strval', $roles )
384 )
385 );
386 }
387
388 private function get_scan_version(): string {
389 return jet_form_builder()->get_version() . ':' . self::SCAN_SCHEMA_VERSION;
390 }
391
392 private function is_notice_dismissed( array $notice ): bool {
393 $dismissed = (string) get_user_meta( get_current_user_id(), self::DISMISS_META_KEY, true );
394
395 if ( '' === $dismissed ) {
396 return false;
397 }
398
399 if ( $dismissed === $this->get_scan_version() ) {
400 return true;
401 }
402
403 return $dismissed === $this->get_notice_dismiss_token( $notice );
404 }
405
406 private function get_notice_dismiss_token( array $notice ): string {
407 return (string) ( $notice['notice_token'] ?? $notice['version'] ?? '' );
408 }
409 }
410