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