PluginProbe
Groups – Memberships and Access Control / 4.7.1
Groups – Memberships and Access Control v4.7.1
4.7.1 4.7.0 4.6.0 4.5.0 4.4.0 4.3.0 trunk 1.0.0-beta-1 1.0.0-beta-2 1.0.0-beta-3 1.0.0-beta-3b 1.0.0-beta-3c 1.0.0-beta-3d 1.1.4 1.1.5 1.10.0 1.10.1 1.10.2 1.10.3 1.11.0 1.11.1 1.11.2 1.11.3 1.12.0 1.13.0 All 131 releases
groups / lib / views / class-groups-shortcodes.php

class-groups-shortcodes.php in Groups – Memberships and Access Control 4.7.1, at lib/views/class-groups-shortcodes.php

1,402 lines 42.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * class-groups-shortcodes.php
4 *
5 * Copyright (c) "kento" Karim Rahimpur www.itthinx.com
6 *
7 * This code is released under the GNU General Public License.
8 * See COPYRIGHT.txt and LICENSE.txt.
9 *
10 * This code is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * This header and all notices must be kept intact.
16 *
17 * @author Karim Rahimpur
18 * @package groups
19 * @since groups 1.0.0
20 */
21
22 if ( !defined( 'ABSPATH' ) ) {
23 exit;
24 }
25
26 // phpcs:disable PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.WP.AlternativeFunctions.rand_rand, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
27
28 /**
29 * Shortcode handlers
30 */
31 class Groups_Shortcodes {
32
33 /**
34 * Maximum amount of time to accept hashes for join and leave requests.
35 *
36 * @var int
37 */
38 const MAX_TIME_DELTA = 3600;
39
40 /**
41 * Hashed content map.
42 *
43 * @since 3.11.0
44 *
45 * @var array
46 */
47 private static $map = array();
48
49 /**
50 * During preprocessing.
51 *
52 * @since 3.11.0
53 *
54 * @var boolean
55 */
56 private static $preprocessing = false;
57
58 /**
59 * Shortcode queue.
60 *
61 * @since 4.7.0
62 *
63 * @var array
64 */
65 private static $shortcode_queue = array();
66
67 /**
68 * Widgets contents.
69 *
70 * @since 4.7.1
71 *
72 * @var array
73 */
74 private static $widgets_contents = array();
75
76 /**
77 * Adds shortcodes.
78 */
79 public static function init() {
80 // login
81 add_shortcode( 'groups_login', array( __CLASS__, 'groups_login' ) );
82 // logout
83 add_shortcode( 'groups_logout', array( __CLASS__, 'groups_logout' ) );
84 // group info
85 add_shortcode( 'groups_group_info', array( __CLASS__, 'groups_group_info' ) );
86 // user groups
87 add_shortcode( 'groups_user_groups', array( __CLASS__, 'groups_user_groups' ) );
88 // groups
89 add_shortcode( 'groups_groups', array( __CLASS__, 'groups_groups' ) );
90 // join a group
91 add_shortcode( 'groups_join', array( __CLASS__, 'groups_join' ) );
92 // leave a group
93 add_shortcode( 'groups_leave', array( __CLASS__, 'groups_leave' ) );
94 // @since 3.11.0 content preprocessing
95 add_filter( 'pre_render_block', array( __CLASS__, 'pre_render_block' ), 0, 3 );
96 // @since 3.11.0 map processing
97 add_filter( 'render_block', array( __CLASS__, 'render_block' ), 0, 3 );
98 // @since 4.7.0 shortcode queue ops
99 add_filter( 'pre_do_shortcode_tag', array( __CLASS__, 'pre_do_shortcode_tag' ), PHP_INT_MAX, 4 );
100 add_filter( 'do_shortcode_tag', array( __CLASS__, 'do_shortcode_tag' ), PHP_INT_MAX, 4 );
101 // @since 4.7.1 shortcodes in widgets
102 add_filter( 'widget_display_callback', array( __CLASS__, 'widget_display_callback' ), PHP_INT_MAX, 3 );
103 }
104
105 /**
106 * Renders the Groups login form.
107 *
108 * The user is redirected to the current page after login by default.
109 * The user can be redirected to a specific URL after login by
110 * indicating the <code>redirect</code> attribute.
111 *
112 * @param array $atts
113 * @param string $content
114 *
115 * @return string the rendered form or empty
116 */
117 public static function groups_login( $atts, $content = null ) {
118
119 if ( !self::validate( 'groups_login', $atts, $content ) ) {
120 return '';
121 }
122
123 $current_url = groups_get_current_url();
124 $atts = shortcode_atts(
125 array(
126 'redirect' => $current_url,
127 'show_logout' => 'no'
128 ),
129 $atts
130 );
131 $redirect = isset( $atts['redirect'] ) ? trim( $atts['redirect'] ) : $current_url;
132 $show_logout = isset( $atts['show_logout'] ) ? trim( strtolower( $atts['show_logout'] ) ) : 'no';
133 $output = '';
134 if ( !is_user_logged_in() ) {
135 $output .= wp_login_form(
136 array(
137 'echo' => false,
138 'redirect' => $redirect
139 )
140 );
141 } else {
142 if ( $show_logout == 'yes' ) {
143 $output .= self::groups_logout(
144 array(
145 'redirect' => $redirect
146 )
147 );
148 }
149 }
150 return $output; // nosemgrep audit.php.wp.security.sqli.shortcode-attr, audit.php.wp.security.xss.shortcode-attr
151 }
152
153 /**
154 * Renders the Groups logout link.
155 *
156 * The link is rendered if the user is logged in.
157 * The user is redirected to the current page after logout by default.
158 * The user can be redirected to a specific URL after logout by
159 * indicating the <code>redirect</code> attribute.
160 *
161 * @param array $atts
162 * @param string $content not used
163 *
164 * @return string logout link, is empty if not logged in
165 */
166 public static function groups_logout( $atts, $content = null ) {
167
168 if ( !self::validate( 'groups_logout', $atts, $content ) ) {
169 return '';
170 }
171
172 $current_url = groups_get_current_url();
173 $atts = shortcode_atts(
174 array(
175 'redirect' => $current_url
176 ),
177 $atts
178 );
179 $redirect = isset( $atts['redirect'] ) ? trim( $atts['redirect'] ) : $current_url;
180 $output = '';
181 if ( is_user_logged_in() ) {
182 $output .= sprintf( '<a href="%s">', esc_url( wp_logout_url( $redirect ) ) );
183 $output .= esc_html__( 'Log out', 'groups' );
184 $output .= '</a>';
185 }
186 return $output;
187 }
188
189 /**
190 * Renders information about a group.
191 *
192 * Attributes:
193 * - "group" : group name or id
194 * - "show" : what to show, can be "name", "description", "count"
195 * - "format" :
196 * - "single" : used with show="count", single form, defaults to '1'
197 * - "plural" : used with show="count", plural form, defaults to '%d', must contain %d to show number
198 *
199 * @param array $atts attributes
200 * @param string $content content to render
201 *
202 * @return string rendered information
203 */
204 public static function groups_group_info( $atts, $content = null ) {
205
206 global $wpdb;
207
208 if ( !self::validate( 'groups_group_info', $atts, $content ) ) {
209 return '';
210 }
211
212 $output = '';
213 $options = shortcode_atts(
214 array(
215 'group' => '',
216 'show' => '',
217 'format' => '',
218 'none' => '0',
219 'single' => '1',
220 'plural' => '%d'
221 ),
222 $atts
223 );
224 $group = trim( $options['group'] );
225 $current_group = Groups_Group::read( $group );
226 if ( !$current_group ) {
227 $current_group = Groups_Group::read_by_name( $group );
228 }
229 if ( $current_group ) {
230 switch ( $options['show'] ) {
231 case 'name' :
232 $output .= wp_filter_nohtml_kses( $current_group->name );
233 break;
234 case 'description' :
235 $output .= wp_filter_nohtml_kses( $current_group->description );
236 break;
237 case 'count' :
238 $user_group_table = _groups_get_tablename( 'user_group' );
239 $count = $wpdb->get_var( $wpdb->prepare(
240 "SELECT COUNT(*) FROM $user_group_table WHERE group_id = %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
241 Groups_Utility::id( $current_group->group_id )
242 ) );
243 if ( $count === null ) {
244 $count = 0;
245 } else {
246 $count = intval( $count );
247 }
248 switch ( $count ) {
249 case 0:
250 $output .= wp_kses_post( $options['none'] );
251 break;
252 case 1:
253 $output .= wp_kses_post( $options['single'] );
254 break;
255 default:
256 $output .= wp_kses_post( sprintf( $options['plural'], $count ) );
257 }
258 break;
259 case 'users' :
260 // Renders a basic user list, do not extend. For more detailed information,
261 // create a separate shortcode that could use pagination, sorting, link to profile, ...
262 $user_group_table = _groups_get_tablename( 'user_group' );
263 $users = $wpdb->get_results( $wpdb->prepare(
264 "SELECT * FROM $wpdb->users LEFT JOIN $user_group_table ON $wpdb->users.ID = $user_group_table.user_id WHERE $user_group_table.group_id = %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
265 Groups_Utility::id( $current_group->group_id )
266 ) );
267 if ( $users ) {
268 $output .= '<ul>';
269 foreach ( $users as $user ) {
270 $display_name = !empty( $user->display_name ) ? $user->display_name : $user->user_login;
271 $output .= '<li>' . wp_filter_nohtml_kses( $display_name ) . '</li>';
272 }
273 $output .= '</ul>';
274 }
275 break;
276 }
277 }
278 return $output; // nosemgrep audit.php.wp.security.sqli.shortcode-attr, audit.php.wp.security.xss.shortcode-attr
279 }
280
281 /**
282 * Renders the current or a specific user's groups.
283 *
284 * Attributes:
285 * - "user_id" OR "user_login" OR "user_email" to identify the user, if none given assumes the current user
286 * - "format" : one of "list" "div" "ul" or "ol" - "list" and "ul" are equivalent
287 * - "list_class" : defaults to "groups"
288 * - "item_class" : defaults to "name"
289 * - "order_by" : defaults to "name", also accepts "group_id"
290 * - "order" : default to "ASC", also accepts "asc", "desc" and "DESC"
291 *
292 * @param array $atts attributes
293 * @param string $content not used
294 *
295 * @return string rendered groups for current user
296 */
297 public static function groups_user_groups( $atts, $content = null ) {
298
299 if ( !self::validate( 'groups_user_groups', $atts, $content ) ) {
300 return '';
301 }
302
303 $output = '';
304 $options = shortcode_atts(
305 array(
306 'user_id' => null,
307 'user_login' => null,
308 'user_email' => null,
309 'format' => 'list',
310 'list_class' => 'groups',
311 'item_class' => 'name',
312 'order_by' => 'name',
313 'order' => 'ASC',
314 'group' => null,
315 'exclude_group' => null
316 ),
317 $atts
318 );
319 $user_id = null;
320 if ( $options['user_id'] !== null ) {
321 if ( $user = get_user_by( 'id', $options['user_id'] ) ) {
322 $user_id = $user->ID;
323 }
324 } else if ( $options['user_id'] !== null ) {
325 if ( $user = get_user_by( 'login', $options['user_login'] ) ) {
326 $user_id = $user->ID;
327 }
328 } else if ( $options['user_email'] !== null ) {
329 if ( $user = get_user_by( 'email', $options['user_login'] ) ) {
330 $user_id = $user->ID;
331 }
332 }
333 if ( $user_id === null ) {
334 $user_id = get_current_user_id();
335 }
336 if ( $user_id !== null ) {
337 $user = new Groups_User( $user_id );
338 $groups = $user->get_groups();
339
340 if ( !empty( $groups ) ) {
341 // group attr
342 if ( $options['group'] !== null ) {
343 $groups = array();
344 $groups_incl = explode( ',', $options['group'] );
345 foreach ( $groups_incl as $group_incl ) {
346 $group = trim( $group_incl );
347 $current_group = Groups_Group::read( $group );
348 if ( !$current_group ) {
349 $current_group = Groups_Group::read_by_name( $group );
350 }
351 if ( $current_group ) {
352 if ( Groups_User::user_is_member( $user_id, $current_group->group_id ) ) {
353 $groups[] = $current_group;
354 }
355 }
356 }
357 }
358 // exclude_group attr
359 if ( $options['exclude_group'] !== null ) {
360 $groups_excl = explode( ',', $options['exclude_group'] );
361 foreach ( $groups_excl as $key => $group_excl ) {
362 $group = trim( $group_excl );
363 $current_group = Groups_Group::read( $group );
364 if ( !$current_group ) {
365 $current_group = Groups_Group::read_by_name( $group );
366 }
367 if ( $current_group ) {
368 $groups_excl[$key] = $current_group->group_id;
369 } else {
370 unset( $groups_excl[$key] );
371 }
372 }
373 foreach ( $groups as $key => $group ) {
374 if ( in_array( $group->group_id, $groups_excl ) ) {
375 unset( $groups[$key] );
376 }
377 }
378 }
379 switch ( $options['order_by'] ) {
380 case 'group_id' :
381 usort( $groups, array( __CLASS__, 'sort_id' ) );
382 break;
383 default :
384 usort( $groups, array( __CLASS__, 'sort_name' ) );
385 }
386 switch ( $options['order'] ) {
387 case 'desc' :
388 case 'DESC' :
389 $groups = array_reverse( $groups );
390 break;
391 }
392
393 switch ( $options['format'] ) {
394 case 'list' :
395 case 'ul' :
396 $output .= '<ul class="' . esc_attr( $options['list_class'] ) . '">';
397 break;
398 case 'ol' :
399 $output .= '<ol class="' . esc_attr( $options['list_class'] ) . '">';
400 break;
401 default :
402 $output .= '<div class="' . esc_attr( $options['list_class'] ) . '">';
403 }
404 foreach ( $groups as $group ) {
405 switch ( $options['format'] ) {
406 case 'list' :
407 case 'ul' :
408 case 'ol' :
409 // @todo mixed assignments done above, unify to Groups_Group objects only
410 $name = $group instanceof Groups_Group ? $group->get_name() : $group->name;
411 $output .= '<li class="' . esc_attr( $options['item_class'] ) . '">' . stripslashes( esc_html( $name ) ) . '</li>';
412 break;
413 default :
414 // @todo mixed assignments done above, unify to Groups_Group objects only
415 $name = $group instanceof Groups_Group ? $group->get_name() : $group->name;
416 $output .= '<div class="' . esc_attr( $options['item_class'] ) . '">' . stripslashes( esc_html( $name ) ) . '</div>';
417 }
418 }
419 switch ( $options['format'] ) {
420 case 'list' :
421 case 'ul' :
422 $output .= '</ul>';
423 break;
424 case 'ol' :
425 $output .= '</ol>';
426 break;
427 default :
428 $output .= '</div>';
429 }
430 }
431 }
432 return $output;
433 }
434
435 /**
436 * Group comparison by group_id.
437 *
438 * @param Groups_Group $a
439 * @param Groups_Group $b
440 *
441 * @return int
442 */
443 public static function sort_id( $a, $b ) {
444 return $a->get_id() - $b->get_id();
445 }
446
447 /**
448 * Group comparison by name.
449 *
450 * @param Groups_Group $a
451 * @param Groups_Group $b
452 *
453 * @return int
454 */
455 public static function sort_name( $a, $b ) {
456 return strcmp( $a->get_name(), $b->get_name() );
457 }
458
459 /**
460 * Renders a list of the site's groups.
461 *
462 * Attributes:
463 * - "format" : one of "list" "div" "ul" or "ol" - "list" and "ul" are equivalent
464 * - "list_class" : defaults to "groups"
465 * - "item_class" : defaults to "name"
466 * - "order_by" : defaults to "name", also accepts "group_id"
467 * - "order" : default to "ASC", also accepts "asc", "desc" and "DESC"
468 *
469 * @param array $atts attributes
470 * @param string $content not used
471 *
472 * @return string rendered groups
473 */
474 public static function groups_groups( $atts, $content = null ) {
475
476 global $wpdb;
477
478 if ( !self::validate( 'groups_groups', $atts, $content ) ) {
479 return '';
480 }
481
482 $output = '';
483 $options = shortcode_atts(
484 array(
485 'format' => 'list',
486 'list_class' => 'groups',
487 'item_class' => 'name',
488 'order_by' => 'name',
489 'order' => 'ASC'
490 ),
491 $atts
492 );
493 switch ( $options['order_by'] ) {
494 case 'group_id' :
495 case 'name' :
496 $order_by = $options['order_by'];
497 break;
498 default :
499 $order_by = 'name';
500 }
501 switch ( $options['order'] ) {
502 case 'asc' :
503 case 'ASC' :
504 case 'desc' :
505 case 'DESC' :
506 $order = strtoupper( $options['order'] );
507 break;
508 default :
509 $order = 'ASC';
510 }
511 $group_table = _groups_get_tablename( 'group' );
512 // nosemgrep: audit.php.wp.security.sqli.shortcode-attr
513 $groups = $wpdb->get_results( "SELECT group_id FROM $group_table ORDER BY $order_by $order" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
514 if ( is_array( $groups ) && count( $groups ) > 0 ) {
515 switch ( $options['format'] ) {
516 case 'list' :
517 case 'ul' :
518 $output .= '<ul class="' . esc_attr( $options['list_class'] ) . '">';
519 break;
520 case 'ol' :
521 $output .= '<ol class="' . esc_attr( $options['list_class'] ) . '">';
522 break;
523 default :
524 $output .= '<div class="' . esc_attr( $options['list_class'] ) . '">';
525 }
526 foreach ( $groups as $group ) {
527 $group = new Groups_Group( $group->group_id );
528 switch ( $options['format'] ) {
529 case 'list' :
530 case 'ul' :
531 case 'ol' :
532 $output .= '<li class="' . esc_attr( $options['item_class'] ) . '">' . stripslashes( esc_html( $group->get_name() ) ) . '</li>';
533 break;
534 default :
535 $output .= '<div class="' . esc_attr( $options['item_class'] ) . '">' . stripslashes( esc_html( $group->get_name() ) ) . '</div>';
536 }
537 }
538 switch ( $options['format'] ) {
539 case 'list' :
540 case 'ul' :
541 $output .= '</ul>';
542 break;
543 case 'ol' :
544 $output .= '</ol>';
545 break;
546 default :
547 $output .= '</div>';
548 }
549 }
550 return $output;
551 }
552
553 /**
554 * Renders a form that lets a user join a group.
555 *
556 * Attributes:
557 *
558 * - "group" : (required) group name or id
559 * - "class" : (optional) container class to add
560 * - "display_message" : (optional) whether to display the message
561 * - "display_is_member" : (optional) whether to display the message that a user is a member
562 * - "redirect" : (optional) whether to redirect after accepted submission
563 * - "submit_class" : (optional) submit HTML element class to add
564 * - "submit_text" : (optional) submit HTML element text to use
565 *
566 * @param array $atts attributes
567 * @param string $content not used
568 *
569 * @return string
570 */
571 public static function groups_join( $atts, $content = null ) {
572
573 global $groups_join_data_init, $post;
574
575 if ( !self::validate( 'groups_join', $atts, $content ) ) {
576 return '';
577 }
578
579 $nonce_action = 'groups_action';
580 $nonce = 'nonce_join';
581 $output = '';
582
583 $options = shortcode_atts(
584 array(
585 'class' => '',
586 'group' => '',
587 'display_message' => true,
588 'display_is_member' => false,
589 'redirect' => true,
590 'submit_class' => '',
591 /* translators: group name */
592 'submit_text' => esc_html__( 'Join the %s group', 'groups' )
593 ),
594 $atts
595 );
596
597 $display_message = is_string( $options['display_message'] ) ? strtolower( $options['display_message'] ) : $options['display_message'];
598 $display_is_member = is_string( $options['display_is_member'] ) ? strtolower( $options['display_is_member'] ) : $options['display_is_member'];
599 $redirect = is_string( $options['redirect'] ) ? strtolower( $options['redirect'] ) : $options['redirect'];
600 $submit_text = $options['submit_text'];
601 $display_message = in_array( $display_message, array( 'true', 'yes', true ) );
602 $display_is_member = in_array( $display_is_member, array( 'true', 'yes', true ) );
603
604 if ( !is_bool( $redirect ) ) {
605 switch ( $redirect ) {
606 case 'true':
607 case 'yes':
608 $redirect = true;
609 break;
610 case 'false':
611 case 'no':
612 $redirect = false;
613 break;
614 default:
615 if ( is_string( $redirect ) ) {
616 $redirect = trim( $redirect );
617 if ( strlen( $redirect ) === 0 ) {
618 $redirect = true;
619 }
620 } else {
621 $redirect = true;
622 }
623 }
624 }
625
626 $class = trim( $options['class'] );
627 $submit_class = trim( $options['submit_class'] );
628 $group = trim( $options['group'] );
629 $current_group = Groups_Group::read( $group );
630 if ( !$current_group ) {
631 $current_group = Groups_Group::read_by_name( $group );
632 }
633 // bail out if no valid group
634 if ( !$current_group ) {
635 return '';
636 }
637
638 // @since 3.11.0 Restrict the functionality to authors with appropriate permission
639 $author_can_restrict_group_ids = array();
640 $author_id = isset( $post ) && !empty( $post->post_author ) ? $post->post_author : get_the_author_meta( 'ID' );
641 $author_id = is_numeric( $author_id ) ? intval( $author_id ) : null;
642 if ( $author_id !== null ) {
643 $author = new Groups_User( $author_id );
644 if ( $author->can( GROUPS_RESTRICT_ACCESS ) ) {
645 if ( $author->can( GROUPS_ADMINISTER_GROUPS ) ) {
646 $author_can_restrict_group_ids = Groups_Group::get_group_ids();
647 } else {
648 $author_can_restrict_group_ids = $author->get_group_ids_deep();
649 }
650 }
651 }
652 if ( !in_array( $current_group->group_id, $author_can_restrict_group_ids ) ) {
653 return '';
654 }
655
656 if ( $current_group ) {
657 if ( $user_id = get_current_user_id() ) {
658 $joined = false;
659 $submitted = false;
660 $invalid_nonce = false;
661 if ( groups_sanitize_post( 'groups_action' ) === 'join' ) {
662 $submitted = true;
663 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
664 if ( !groups_verify_post_nonce( $nonce, $nonce_action ) ) { // nosemgrep: scanner.php.wp.security.csrf.nonce-check-not-dying
665 $invalid_nonce = true;
666 }
667 }
668 if ( $submitted && !$invalid_nonce ) {
669 // add user to group
670 if ( isset( $_POST['groups-join-data'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
671 $hash = trim( groups_sanitize_post( 'groups-join-data' ) );
672 $groups_join_data = get_user_meta( $user_id, 'groups-join-data', true );
673 if ( is_array( $groups_join_data ) && isset( $groups_join_data[$hash] ) ) {
674 if ( isset( $groups_join_data[$hash]['group_id'] ) && isset( $groups_join_data[$hash]['time'] ) ) {
675 $group_id = $groups_join_data[$hash]['group_id'];
676 $dt = time() - $groups_join_data[$hash]['time'];
677 if ( $dt < apply_filters( 'groups_join_submit_max_time_delta', self::MAX_TIME_DELTA ) ) {
678 $joined = Groups_User_Group::create(
679 array(
680 'group_id' => $group_id,
681 'user_id' => $user_id
682 )
683 );
684 if ( $joined ) {
685 /**
686 * Whether to redirect after submit and successful addition to group.
687 *
688 * @since 3.6.0
689 *
690 * @param boolean|string $redirect true to redirect to current ULR, string to redirect to specific URL, false not to redirect
691 * @param array $atts shortcode attributes
692 * @param array $options evaluated shortcode options
693 *
694 * @return boolean|string
695 */
696 if ( apply_filters( 'groups_join_submit_redirect', $redirect, $atts, $options ) !== false ) {
697 self::maybe_redirect( $redirect );
698 }
699 }
700 }
701 }
702 }
703 }
704 }
705 if ( !Groups_User::user_is_member( $user_id, $current_group->group_id ) ) {
706 if ( !isset( $groups_join_data_init ) ) {
707 $groups_join_data_init = true;
708 delete_user_meta( $user_id, 'groups-join-data' );
709 }
710 $data = array(
711 'user_id' => $user_id,
712 'group_id' => $current_group->group_id,
713 'time' => time(),
714 'salt' => rand( 0, PHP_INT_MAX )
715 );
716 $hash = hash( 'sha256', json_encode( $data ) );
717 $groups_join_data = get_user_meta( $user_id, 'groups-join-data', true );
718 if ( !is_array( $groups_join_data ) ) {
719 $groups_join_data = array();
720 }
721 $groups_join_data[$hash] = $data;
722 update_user_meta( $user_id, 'groups-join-data', $groups_join_data );
723
724 $submit_text = sprintf( $options['submit_text'], stripslashes( wp_filter_nohtml_kses( $current_group->name ) ) );
725 $output .= sprintf(
726 '<div class="groups-join%s">',
727 strlen( $class ) > 0 ? ' ' . esc_attr( $class ) : '',
728 );
729 $output .= '<form action="#" method="post">';
730 $output .= '<input type="hidden" name="groups_action" value="join" />';
731 $output .= '<input type="hidden" name="groups-join-data" value="' . esc_attr( $hash ) . '" />';
732 $output .= sprintf(
733 '<input class="groups-join-submit%s" type="submit" value="%s" />',
734 strlen( $submit_class ) > 0 ? ' ' . esc_attr( $submit_class ) : '',
735 esc_attr( $submit_text )
736 );
737 $output .= wp_nonce_field( $nonce_action, $nonce, true, false );
738 $output .= '</form>';
739 $output .= '</div>';
740 } else if ( $display_message ) {
741 if ( $joined ) {
742 $output .= '<div class="groups-join joined">';
743 /* translators: group name */
744 $output .= sprintf( esc_html__( 'You have joined the %s group.', 'groups' ), stripslashes( wp_filter_nohtml_kses( $current_group->name ) ) );
745 $output .= '</div>';
746 } else if ( $display_is_member && $current_group !== false ) {
747 $output .= '<div class="groups-join member">';
748 /* translators: group name */
749 $output .= sprintf( esc_html__( 'You are a member of the %s group.', 'groups' ), stripslashes( wp_filter_nohtml_kses( $current_group->name ) ) );
750 $output .= '</div>';
751 }
752 }
753 }
754 }
755
756 if ( self::$preprocessing ) {
757 // surround content with hashmarks
758 // <!-- groups:{hash} -->{content}<!-- /groups:{hash} -->
759 $hash = md5( $output );
760 $prefix = sprintf( '<!-- groups:%s -->', $hash );
761 $suffix = sprintf( '<!-- /groups:%s -->', $hash );
762 self::$map[$hash] = array(
763 'prefix' => $prefix,
764 'suffix' => $suffix,
765 'content' => $output
766 );
767
768 $output = sprintf(
769 '%s%s%s',
770 $prefix,
771 $output,
772 $suffix
773 );
774 }
775
776 return $output;
777 }
778
779 /**
780 * Renders a form that lets a user leave a group.
781 *
782 * Attributes:
783 *
784 * - "group" : (required) group name or id
785 * - "class" : (optional) container class to add
786 * - "display_message" : (optional) whether to display the message
787 * - "redirect" : (optional) whether to redirect after accepted submission
788 * - "submit_class" : (optional) submit HTML element class to add
789 * - "submit_text" : (optional) submit HTML element text to use
790 *
791 * @param array $atts attributes
792 * @param string $content not used
793 *
794 * @return string
795 */
796 public static function groups_leave( $atts, $content = null ) {
797
798 global $groups_leave_data_init, $post;
799
800 if ( !self::validate( 'groups_leave', $atts, $content ) ) {
801 return '';
802 }
803
804 $nonce_action = 'groups_action';
805 $nonce = 'nonce_leave';
806 $output = '';
807
808 $options = shortcode_atts(
809 array(
810 'class' => '',
811 'group' => '',
812 'display_message' => true,
813 'redirect' => true,
814 'submit_class' => '',
815 /* translators: group name */
816 'submit_text' => esc_html__( 'Leave the %s group', 'groups' ),
817 ),
818 $atts
819 );
820
821 $display_message = is_string( $options['display_message'] ) ? strtolower( $options['display_message'] ) : $options['display_message'];
822 $redirect = is_string( $options['redirect'] ) ? strtolower( $options['redirect'] ) : $options['redirect'];
823 $submit_text = $options['submit_text'];
824
825 $display_message = in_array( $display_message, array( 'true', 'yes', true ) );
826
827 if ( !is_bool( $redirect ) ) {
828 switch ( $redirect ) {
829 case 'true':
830 case 'yes':
831 $redirect = true;
832 break;
833 case 'false':
834 case 'no':
835 $redirect = false;
836 break;
837 default:
838 if ( is_string( $redirect ) ) {
839 $redirect = trim( $redirect );
840 if ( strlen( $redirect ) === 0 ) {
841 $redirect = true;
842 }
843 } else {
844 $redirect = true;
845 }
846 }
847 }
848
849 $class = trim( $options['class'] );
850 $submit_class = trim( $options['submit_class'] );
851 $group = trim( $options['group'] );
852 $current_group = Groups_Group::read( $group );
853 if ( !$current_group ) {
854 $current_group = Groups_Group::read_by_name( $group );
855 }
856 // bail out if no valid group
857 if ( !$current_group ) {
858 return '';
859 }
860
861 // @since 3.11.0 Restrict the functionality to authors with appropriate permission
862 $author_can_restrict_group_ids = array();
863 $author_id = isset( $post ) && !empty( $post->post_author ) ? $post->post_author : get_the_author_meta( 'ID' );
864 $author_id = is_numeric( $author_id ) ? intval( $author_id ) : null;
865 if ( $author_id !== null ) {
866 $author = new Groups_User( $author_id );
867 if ( $author->can( GROUPS_RESTRICT_ACCESS ) ) {
868 if ( $author->can( GROUPS_ADMINISTER_GROUPS ) ) {
869 $author_can_restrict_group_ids = Groups_Group::get_group_ids();
870 } else {
871 $author_can_restrict_group_ids = $author->get_group_ids_deep();
872 }
873 }
874 }
875 if ( !in_array( $current_group->group_id, $author_can_restrict_group_ids ) ) {
876 return '';
877 }
878
879 if ( $current_group ) {
880 if ( $user_id = get_current_user_id() ) {
881 $left = false;
882 $submitted = false;
883 $invalid_nonce = false;
884 if ( groups_sanitize_post( 'groups_action' ) === 'leave' ) {
885 $submitted = true;
886 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
887 if ( !groups_verify_post_nonce( $nonce, $nonce_action ) ) { // nosemgrep: scanner.php.wp.security.csrf.nonce-check-not-dying
888 $invalid_nonce = true;
889 }
890 }
891 if ( $submitted && !$invalid_nonce ) {
892 // remove user from group
893 if ( isset( $_POST['groups-leave-data'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
894 $hash = trim( groups_sanitize_post( 'groups-leave-data' ) );
895 $groups_leave_data = get_user_meta( $user_id, 'groups-leave-data', true );
896 if ( is_array( $groups_leave_data ) && isset( $groups_leave_data[$hash] ) ) {
897 if ( isset( $groups_leave_data[$hash]['group_id'] ) && isset( $groups_leave_data[$hash]['time'] ) ) {
898 $group_id = $groups_leave_data[$hash]['group_id'];
899 $dt = time() - $groups_leave_data[$hash]['time'];
900 if ( $dt < apply_filters( 'groups_leave_submit_max_time_delta', self::MAX_TIME_DELTA ) ) {
901 $left = Groups_User_Group::delete( $user_id, $group_id );
902 if ( $left ) {
903 /**
904 * Whether to redirect after acceptedsubmit and successful removal from group.
905 *
906 * @since 3.6.0
907 *
908 * @param boolean|string $redirect true to redirect to current ULR, string to redirect to specific URL, false not to redirect
909 * @param array $atts shortcode attributes
910 * @param array $options evaluated shortcode options
911 *
912 * @return boolean|string
913 */
914 if ( apply_filters( 'groups_leave_submit_redirect', $redirect, $atts, $options ) !== false ) {
915 self::maybe_redirect( $redirect );
916 }
917 }
918 }
919 }
920 }
921 }
922 }
923 if ( Groups_User::user_is_member( $user_id, $current_group->group_id ) ) {
924 if ( !isset( $groups_leave_data_init ) ) {
925 $groups_leave_data_init = true;
926 delete_user_meta( $user_id, 'groups-leave-data' );
927 }
928 $data = array(
929 'user_id' => $user_id,
930 'group_id' => $current_group->group_id,
931 'time' => time(),
932 'salt' => rand( 0, PHP_INT_MAX )
933 );
934 $hash = hash( 'sha256', json_encode( $data ) );
935 $groups_leave_data = get_user_meta( $user_id, 'groups-leave-data', true );
936 if ( !is_array( $groups_leave_data ) ) {
937 $groups_leave_data = array();
938 }
939 $groups_leave_data[$hash] = $data;
940 update_user_meta( $user_id, 'groups-leave-data', $groups_leave_data );
941
942 $submit_text = sprintf( $options['submit_text'], stripslashes( wp_filter_nohtml_kses( $current_group->name ) ) );
943 $output .= sprintf(
944 '<div class="groups-leave%s">',
945 strlen( $class ) > 0 ? ' ' . esc_attr( $class ) : '',
946 );
947 $output .= '<form action="#" method="post">';
948 $output .= '<input type="hidden" name="groups_action" value="leave" />';
949 $output .= '<input type="hidden" name="groups-leave-data" value="' . esc_attr( $hash ) . '" />';
950 $output .= sprintf(
951 '<input class="groups-leave-submit%s" type="submit" value="%s" />',
952 strlen( $submit_class ) > 0 ? ' ' . esc_attr( $submit_class ) : '',
953 esc_attr( $submit_text )
954 );
955 $output .= wp_nonce_field( $nonce_action, $nonce, true, false );
956 $output .= '</form>';
957 $output .= '</div>';
958 } else if ( $display_message ) {
959 if ( $left ) {
960 $output .= '<div class="groups-leave left">';
961 /* translators: group name */
962 $output .= sprintf( esc_html__( 'You have left the %s group.', 'groups' ), stripslashes( wp_filter_nohtml_kses( $current_group->name ) ) );
963 $output .= '</div>';
964 }
965 }
966 }
967 }
968
969 if ( self::$preprocessing ) {
970 // surround content with hashmarks
971 // <!-- groups:{hash} -->{content}<!-- /groups:{hash} -->
972 $hash = md5( $output );
973 $prefix = sprintf( '<!-- groups:%s -->', $hash );
974 $suffix = sprintf( '<!-- /groups:%s -->', $hash );
975 self::$map[$hash] = array(
976 'prefix' => $prefix,
977 'suffix' => $suffix,
978 'content' => $output
979 );
980
981 $output = sprintf(
982 '%s%s%s',
983 $prefix,
984 $output,
985 $suffix
986 );
987 }
988
989 return $output;
990 }
991
992 /**
993 * Try to redirect.
994 *
995 * No redirect will happen if $redirect is false.
996 *
997 * A redirect to the current URL is attempted if $redirect is an empty string.
998 *
999 * Relative paths will try to redirect to the path off the home URL and other URL components present.
1000 *
1001 * @since 3.6.0
1002 *
1003 * @param boolean|string $redirect
1004 */
1005 private static function maybe_redirect( $redirect ) {
1006
1007 // Don't redirect
1008 if ( is_bool( $redirect ) && !$redirect ) {
1009 return;
1010 }
1011
1012 // Use the current URL if no specific URL is provided
1013 if ( is_string( $redirect ) && trim( $redirect ) !== '' ) {
1014 $redirect_url = trim( $redirect );
1015 } else {
1016 $redirect_url = groups_get_current_url();
1017 }
1018
1019 // Try to handle a relative URL, determine missing parts
1020 $parts = wp_parse_url( $redirect_url );
1021 if ( !isset( $parts['scheme'] ) ) {
1022 $parts['scheme'] = is_ssl() ? 'https' : 'http';
1023 }
1024 if ( !isset( $parts['host'] ) ) {
1025 $parts['host'] = wp_parse_url( home_url(), PHP_URL_HOST );
1026 }
1027 if ( !isset( $parts['path'] ) ) {
1028 $parts['path'] = wp_parse_url( home_url(), PHP_URL_PATH );
1029 } else {
1030 $home_path = wp_parse_url( home_url(), PHP_URL_PATH );
1031 if ( strpos( $parts['path'], $home_path ) !== 0 ) {
1032 $parts['path'] = trailingslashit( $home_path ) . ltrim( $parts['path'], '/\\' );
1033 }
1034 }
1035 // Put the absolute URL together
1036 $url = $parts['scheme'] . ':';
1037 if ( !empty( $parts['user'] ) && !empty( $parts['password'] ) ) {
1038 $url .= $parts['user'] . ':' . $parts['password'] . '@';
1039 }
1040 $url .= '//' . $parts['host'];
1041 if ( !empty( $parts['path'] ) ) {
1042 $url .= $parts['path'];
1043 }
1044 if ( !empty( $parts['query'] ) ) {
1045 $url .= '?' . $parts['query'];
1046 }
1047 if ( !empty( $parts['fragment'] ) ) {
1048 $url .= '#' . $parts['fragment'];
1049 }
1050 $redirect_url = $url;
1051
1052 // validate the URL and restrict to allowed hosts, uses the allowed_redirect_hosts filter restricting to the domain of the current site
1053 $redirect_url = wp_validate_redirect( $redirect_url ); // default fallback is ''
1054 if ( is_string( $redirect_url ) ) {
1055 $redirect_url = trim( $redirect_url );
1056 }
1057 if ( $redirect_url !== null && $redirect_url !== false && $redirect_url !== '' ) {
1058 if ( wp_redirect( $redirect_url ) ) { // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect
1059 exit;
1060 }
1061 }
1062 }
1063
1064 /**
1065 * Determine which blocks to preprocess.
1066 *
1067 * @since 3.11.0
1068 *
1069 * @return array
1070 */
1071 public static function get_preprocess_blocks() {
1072 $blocks = apply_filters(
1073 'groups_shortcodes_preprocess_blocks',
1074 array(
1075 'core/latest-posts'
1076 )
1077 );
1078 if ( !is_array( $blocks ) ) {
1079 $blocks = array();
1080 }
1081 return $blocks;
1082 }
1083
1084 /**
1085 * Content preprocessing.
1086 *
1087 * @since 3.11.0
1088 *
1089 * @param string|null $pre_render
1090 * @param array $parsed_block
1091 * @param WP_Block|null $parent_block
1092 *
1093 * @return string|null
1094 */
1095 public static function pre_render_block( $pre_render, $parsed_block, $parent_block ) {
1096 if ( in_array( $parsed_block['blockName'], self::get_preprocess_blocks() ) ) {
1097 // start preprocessing
1098 self::$preprocessing = true;
1099 add_filter( 'the_posts', array( __CLASS__, 'preprocess_the_posts' ), 10, 2 );
1100 }
1101 return $pre_render;
1102 }
1103
1104 /**
1105 * Map processing.
1106 *
1107 * @since 3.11.0
1108 *
1109 * @param string $block_content
1110 * @param array $parsed_block
1111 * @param WP_Block $block
1112 *
1113 * @return string
1114 */
1115 public static function render_block( $block_content, $parsed_block, $block ) {
1116 // Remove hashmarks leaving the content within.
1117 if ( in_array( $parsed_block['blockName'], self::get_preprocess_blocks() ) ) {
1118 // stop preprocessing
1119 remove_filter( 'the_posts', array( __CLASS__, 'preprocess_the_posts' ), 10 );
1120 self::$preprocessing = false;
1121 foreach ( self::$map as $hash => $data ) {
1122 $prefix = $data['prefix'] ?? '';
1123 $suffix = $data['suffix'] ?? '';
1124 $content = $data['content'] ?? '';
1125 $start = $prefix !== '' ? strpos( $block_content, $prefix ) : false;
1126 $end = $suffix !== '' ? strpos( $block_content, $suffix ) : false;
1127 if ( $start !== false && $end !== false ) {
1128 $block_content = substr( $block_content, 0, $start ) . $content . substr( $block_content, $end + strlen( $suffix ) );
1129 }
1130 }
1131 }
1132 return $block_content;
1133 }
1134
1135 /**
1136 * Preprocess posts.
1137 *
1138 * @since 3.11.0
1139 *
1140 * @param WP_Post[] $posts
1141 * @param WP_Query $query
1142 *
1143 * @return WP_Post[]
1144 */
1145 public static function preprocess_the_posts( $posts, $query ) {
1146 global $shortcode_tags, $post;
1147 if ( !empty( $shortcode_tags ) ) {
1148 // remember the global post object
1149 $original_post = $post;
1150 // remember the global registered shortcodes
1151 $original_shortcode_tags = $shortcode_tags;
1152 // limit processing to these shortcodes
1153 $do_shortcode_tags = array();
1154 if ( isset( $shortcode_tags['groups_join'] ) ) {
1155 $do_shortcode_tags['groups_join'] = $shortcode_tags['groups_join'];
1156 }
1157 if ( isset( $shortcode_tags['groups_leave'] ) ) {
1158 $do_shortcode_tags['groups_leave'] = $shortcode_tags['groups_leave'];
1159 }
1160 $shortcode_tags = $do_shortcode_tags;
1161 // preprocess content for each post
1162 $processed_posts = array();
1163 while ( !empty( $posts ) ) {
1164 // set the global $post to process within do_shortcode()
1165 $post = array_shift( $posts );
1166 $post->post_excerpt = do_shortcode( $post->post_excerpt );
1167 $post->post_content = do_shortcode( $post->post_content );
1168 array_push( $processed_posts, $post );
1169 }
1170 // modified posts to return
1171 $posts = $processed_posts;
1172 // restore the global registered shortcodes
1173 $shortcode_tags = $original_shortcode_tags;
1174 // restore the global post
1175 $post = $original_post;
1176 }
1177 return $posts;
1178 }
1179
1180 /**
1181 * Validate shortcode.
1182 *
1183 * @since 4.7.0
1184 *
1185 * @see do_shortcode()
1186 * @see do_shortcode_tag()
1187 *
1188 * @param string $tag shortcode tag
1189 * @param array $atts shortcode attributes
1190 * @param string $content shortcode content
1191 *
1192 * @return boolean
1193 */
1194 public static function validate( $tag, $atts, $content ) {
1195
1196 global $post;
1197
1198 // allow direct calls to shortcode processing functions
1199 if ( !self::is_processing( $tag, $atts ) ) {
1200 return true;
1201 }
1202
1203 $valid =
1204 isset( $post ) && !empty( $post->ID ) && ( !empty( $post->post_excerpt ) || !empty( $post->post_content ) ) ||
1205 !empty( self::$widgets_contents );
1206
1207 if ( $valid ) {
1208 $valid = ! (
1209 defined( 'REST_API_REQUEST' ) && REST_API_REQUEST ||
1210 defined( 'REST_REQUEST' ) && REST_REQUEST ||
1211 defined( 'WP_CLI' ) && WP_CLI ||
1212 defined( 'WPCOM_CLI_SCRIPT' ) && WPCOM_CLI_SCRIPT ||
1213 is_admin() ||
1214 is_feed() ||
1215 wp_doing_ajax() || // supersedes DOING_AJAX
1216 wp_is_json_request() ||
1217 wp_is_jsonp_request()
1218 );
1219 }
1220
1221 if ( $valid ) {
1222 $valid = false;
1223
1224 // @since 4.7.1 also consider the excerpt and widgets
1225 $contents = ( $post->post_excerpt ?? '' ) . ( $post->post_content ?? '' );
1226 if ( !empty( self::$widgets_contents ) ) {
1227 $contents .= implode( ' ', self::$widgets_contents );
1228 }
1229 /**
1230 * Allow to filter the contents considered for shortcode validation.
1231 *
1232 * @since 4.7.1
1233 *
1234 * @param string $contents contents considered
1235 * @param string $tag shortcode tag
1236 * @param array $atts shortcode attributes
1237 * @param string $content shortcode content
1238 *
1239 * @return string
1240 */
1241 $contents = apply_filters( 'groups_shortcodes_validate_contents', $contents, $tag, $atts, $content );
1242
1243 if ( !empty( $contents ) && has_shortcode( $contents, $tag ) ) {
1244 $matches = null;
1245 preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $contents, $matches );
1246 if ( !empty( $matches[1] ) && is_array( $matches[1] ) && count( $matches[1] ) > 0 ) {
1247 $tags = array_intersect( array( $tag ), $matches[1] );
1248 if ( !empty( $tags ) ) {
1249 // - does not support nested shortcodes
1250 // - Groups shortcodes do not support nesting
1251 $pattern = '@' . get_shortcode_regex( $tags ) . '@';
1252 $m = null;
1253 preg_match_all( $pattern, $contents, $m );
1254 // shortcode arguments list
1255 if ( isset( $m[3] ) && is_array( $m[3] ) && count( $m[3] ) > 0 ) {
1256 $found = false;
1257 foreach ( $m[3] as $args ) {
1258 if ( is_string( $args ) ) {
1259 $args = trim( $args );
1260 // decode in case unicode escape sequences used for quotes surrounding attributes need to be decoded for comparison
1261 $atts_decoded = json_decode( '"' . $args . '"' );
1262 $attributes = shortcode_parse_atts( $atts_decoded ?? $args );
1263 $match = true;
1264 foreach ( $attributes as $key => $value ) {
1265 if ( !array_key_exists( $key, $atts ) || $atts[$key] !== $value ) {
1266 $match = false;
1267 break;
1268 }
1269 }
1270 if ( $match ) {
1271 $found = true;
1272 break;
1273 }
1274 }
1275 }
1276 if ( $found ) {
1277 $valid = true;
1278 }
1279 }
1280 }
1281 }
1282 }
1283 }
1284
1285 return $valid;
1286 }
1287
1288 /**
1289 * Use return value short-circuit to signal shortcode instance processing.
1290 *
1291 * @since 4.7.0
1292 *
1293 * @param boolean|string $output short-circuit value
1294 * @param string $tag shortcode tag/name
1295 * @param array $attr shortcode attributes
1296 * @param array $m regex match
1297 *
1298 * @return boolean|string
1299 */
1300 public static function pre_do_shortcode_tag( $output, $tag, $attr, $m ) {
1301 $hash = md5( json_encode( $attr ) );
1302 self::$shortcode_queue[] = array( 'tag' => $tag, 'hash' => $hash );
1303 return $output;
1304 }
1305
1306 /**
1307 * Use output filter to signal shortcode processed.
1308 *
1309 * @since 4.7.0
1310 *
1311 * @param string $output shortcode output
1312 * @param string $tag shortcode tag/name
1313 * @param array $attr shortcode attributes
1314 * @param array $m regex match
1315 *
1316 * @return string
1317 */
1318 public static function do_shortcode_tag( $output, $tag, $attr, $m ) {
1319 $queue = array();
1320 for ( $i = count( self::$shortcode_queue ) - 1; $i >= 0; $i-- ) {
1321 if ( $tag === self::$shortcode_queue[$i]['tag'] ) {
1322 $hash = md5( json_encode( $attr ) );
1323 if ( $hash !== self::$shortcode_queue[$i]['hash'] ) {
1324 array_unshift( $queue, self::$shortcode_queue[$i] );
1325 }
1326 }
1327 }
1328 self::$shortcode_queue = $queue;
1329 return $output;
1330 }
1331
1332 /**
1333 * Use callback filter to gather shortcodes in widgets.
1334 *
1335 * @since 4.7.1
1336 *
1337 * @param array $instance widget instance settings
1338 * @param WP_Widget $widget widget object
1339 * @param array $args widget arguments
1340 *
1341 * @return array
1342 */
1343 public static function widget_display_callback( $instance, $widget, $args ) {
1344 if ( !empty( $instance ) && is_array( $instance ) ) {
1345 if ( is_object( $widget ) ) {
1346 if ( $widget instanceof WP_Widget_Text ) {
1347 if ( isset( $instance['text'] ) && is_string( $instance['text'] ) ) {
1348 self::$widgets_contents[] = $instance['text'];
1349 }
1350 } else if ( $widget instanceof WP_Widget_Block ) {
1351 if ( isset( $instance['content'] ) && is_string( $instance['content'] ) ) {
1352 self::$widgets_contents[] = $instance['content'];
1353 }
1354 } else {
1355 /**
1356 * Allow widgets to provide content that should be checked during shortcode validation.
1357 *
1358 * @since 4.7.1
1359 *
1360 * @param string|null $content widget content to check for shortcodes
1361 * @param array $instance widget instance settings
1362 * @param WP_Widget $widget widget object
1363 * @param array $args widget arguments
1364 *
1365 * @return string|null string content of the widget or null if widget's content should not be considered
1366 */
1367 $content = apply_filters( 'groups_shortcodes_widget_display_callback_widget_content', null, $instance, $widget, $args );
1368 if ( is_string( $content ) ) {
1369 self::$widgets_contents[] = $content;
1370 }
1371 }
1372 }
1373 }
1374 return $instance;
1375 }
1376
1377 /**
1378 * Is processing shortcode tag with given attributes.
1379 *
1380 * @since 4.7.0
1381 *
1382 * @param string $tag
1383 * @param array $atts
1384 *
1385 * @return boolean
1386 */
1387 private static function is_processing( $tag, $atts ) {
1388 $result = false;
1389 for ( $i = 0; $i < count( self::$shortcode_queue ); $i++ ) {
1390 if ( $tag === self::$shortcode_queue[$i]['tag'] ) {
1391 $hash = md5( json_encode( $atts ) );
1392 if ( $hash === self::$shortcode_queue[$i]['hash'] ) {
1393 $result = true;
1394 }
1395 }
1396 }
1397 return $result;
1398 }
1399 }
1400
1401 Groups_Shortcodes::init();
1402