| @@ -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,51 @@ | ||
| 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 | + /** | |
| 68 | + * Widgets contents. | |
| 69 | + * | |
| 70 | + * @since 4.7.1 | |
| 71 | + * | |
| 72 | + * @var array | |
| 73 | + */ | |
| 74 | + private static $widgets_contents = array(); | |
| 75 | + | |
| 76 | + /** | |
| 32 | 77 | * Adds shortcodes. |
| 33 | 78 | */ |
| 34 | 79 | public static function init() { |
| 35 | 80 | // login |
| @@ -40,18 +85,27 @@ | ||
| 40 | 85 | add_shortcode( 'groups_group_info', array( __CLASS__, 'groups_group_info' ) ); |
| 41 | 86 | // user groups |
| 42 | 87 | add_shortcode( 'groups_user_groups', array( __CLASS__, 'groups_user_groups' ) ); |
| 43 | 88 | // groups |
| 44 | - add_shortcode( 'groups_groups', array( __CLASS__, 'groups_groups' ) ); | |
| 89 | + add_shortcode( 'groups_groups', array( __CLASS__, 'groups_groups' ) ); | |
| 45 | 90 | // join a group |
| 46 | - add_shortcode( 'groups_join', array( __CLASS__, 'groups_join' ) ); | |
| 91 | + add_shortcode( 'groups_join', array( __CLASS__, 'groups_join' ) ); | |
| 47 | 92 | // leave a group |
| 48 | - add_shortcode( 'groups_leave', array( __CLASS__, 'groups_leave' ) ); | |
| 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 ); | |
| 49 | 103 | } |
| 50 | 104 | |
| 51 | 105 | /** |
| 52 | 106 | * Renders the Groups login form. |
| 53 | - * | |
| 107 | + * | |
| 54 | 108 | * The user is redirected to the current page after login by default. |
| 55 | 109 | * The user can be redirected to a specific URL after login by |
| 56 | 110 | * indicating the <code>redirect</code> attribute. |
| 57 | 111 | * |
| @@ -56,23 +110,27 @@ | ||
| 56 | 110 | * indicating the <code>redirect</code> attribute. |
| 57 | 111 | * |
| 58 | 112 | * @param array $atts |
| 59 | 113 | * @param string $content |
| 114 | + * | |
| 60 | 115 | * @return string the rendered form or empty |
| 61 | 116 | */ |
| 62 | 117 | 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 | - ) | |
| 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 | |
| 72 | 130 | ); |
| 73 | - $redirect = trim( $redirect ); | |
| 74 | - $show_logout = trim( strtolower( $show_logout ) ); | |
| 131 | + $redirect = isset( $atts['redirect'] ) ? trim( $atts['redirect'] ) : $current_url; | |
| 132 | + $show_logout = isset( $atts['show_logout'] ) ? trim( strtolower( $atts['show_logout'] ) ) : 'no'; | |
| 75 | 133 | $output = ''; |
| 76 | 134 | if ( !is_user_logged_in() ) { |
| 77 | 135 | $output .= wp_login_form( |
| 78 | 136 | array( |
| @@ -88,14 +146,14 @@ | ||
| 88 | 146 | ) |
| 89 | 147 | ); |
| 90 | 148 | } |
| 91 | 149 | } |
| 92 | - return $output; | |
| 150 | + return $output; // nosemgrep audit.php.wp.security.sqli.shortcode-attr, audit.php.wp.security.xss.shortcode-attr | |
| 93 | 151 | } |
| 94 | 152 | |
| 95 | 153 | /** |
| 96 | 154 | * Renders the Groups logout link. |
| 97 | - * | |
| 155 | + * | |
| 98 | 156 | * The link is rendered if the user is logged in. |
| 99 | 157 | * The user is redirected to the current page after logout by default. |
| 100 | 158 | * The user can be redirected to a specific URL after logout by |
| 101 | 159 | * indicating the <code>redirect</code> attribute. |
| @@ -101,25 +159,29 @@ | ||
| 101 | 159 | * indicating the <code>redirect</code> attribute. |
| 102 | 160 | * |
| 103 | 161 | * @param array $atts |
| 104 | 162 | * @param string $content not used |
| 163 | + * | |
| 105 | 164 | * @return string logout link, is empty if not logged in |
| 106 | 165 | */ |
| 107 | 166 | 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 | - ) | |
| 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 | |
| 116 | 178 | ); |
| 117 | - $redirect = trim( $redirect ); | |
| 179 | + $redirect = isset( $atts['redirect'] ) ? trim( $atts['redirect'] ) : $current_url; | |
| 118 | 180 | $output = ''; |
| 119 | 181 | if ( is_user_logged_in() ) { |
| 120 | 182 | $output .= sprintf( '<a href="%s">', esc_url( wp_logout_url( $redirect ) ) ); |
| 121 | - $output .= __( 'Log out', GROUPS_PLUGIN_DOMAIN ); | |
| 183 | + $output .= esc_html__( 'Log out', 'groups' ); | |
| 122 | 184 | $output .= '</a>'; |
| 123 | 185 | } |
| 124 | 186 | return $output; |
| 125 | 187 | } |
| @@ -125,8 +187,9 @@ | ||
| 125 | 187 | } |
| 126 | 188 | |
| 127 | 189 | /** |
| 128 | 190 | * Renders information about a group. |
| 191 | + * | |
| 129 | 192 | * Attributes: |
| 130 | 193 | * - "group" : group name or id |
| 131 | 194 | * - "show" : what to show, can be "name", "description", "count" |
| 132 | 195 | * - "format" : |
| @@ -131,21 +194,29 @@ | ||
| 131 | 194 | * - "show" : what to show, can be "name", "description", "count" |
| 132 | 195 | * - "format" : |
| 133 | 196 | * - "single" : used with show="count", single form, defaults to '1' |
| 134 | 197 | * - "plural" : used with show="count", plural form, defaults to '%d', must contain %d to show number |
| 135 | - * | |
| 198 | + * | |
| 136 | 199 | * @param array $atts attributes |
| 137 | 200 | * @param string $content content to render |
| 138 | - * @return rendered information | |
| 201 | + * | |
| 202 | + * @return string rendered information | |
| 139 | 203 | */ |
| 140 | 204 | public static function groups_group_info( $atts, $content = null ) { |
| 205 | + | |
| 141 | 206 | global $wpdb; |
| 142 | - $output = ""; | |
| 207 | + | |
| 208 | + if ( !self::validate( 'groups_group_info', $atts, $content ) ) { | |
| 209 | + return ''; | |
| 210 | + } | |
| 211 | + | |
| 212 | + $output = ''; | |
| 143 | 213 | $options = shortcode_atts( |
| 144 | 214 | array( |
| 145 | - 'group' => '', | |
| 146 | - 'show' => '', | |
| 215 | + 'group' => '', | |
| 216 | + 'show' => '', | |
| 147 | 217 | 'format' => '', |
| 218 | + 'none' => '0', | |
| 148 | 219 | 'single' => '1', |
| 149 | 220 | 'plural' => '%d' |
| 150 | 221 | ), |
| 151 | 222 | $atts |
| @@ -155,9 +226,9 @@ | ||
| 155 | 226 | if ( !$current_group ) { |
| 156 | 227 | $current_group = Groups_Group::read_by_name( $group ); |
| 157 | 228 | } |
| 158 | 229 | if ( $current_group ) { |
| 159 | - switch( $options['show'] ) { | |
| 230 | + switch ( $options['show'] ) { | |
| 160 | 231 | case 'name' : |
| 161 | 232 | $output .= wp_filter_nohtml_kses( $current_group->name ); |
| 162 | 233 | break; |
| 163 | 234 | case 'description' : |
| @@ -163,11 +234,11 @@ | ||
| 163 | 234 | case 'description' : |
| 164 | 235 | $output .= wp_filter_nohtml_kses( $current_group->description ); |
| 165 | 236 | break; |
| 166 | 237 | case 'count' : |
| 167 | - $user_group_table = _groups_get_tablename( "user_group" ); | |
| 238 | + $user_group_table = _groups_get_tablename( 'user_group' ); | |
| 168 | 239 | $count = $wpdb->get_var( $wpdb->prepare( |
| 169 | - "SELECT COUNT(*) FROM $user_group_table WHERE group_id = %d", | |
| 240 | + "SELECT COUNT(*) FROM $user_group_table WHERE group_id = %d", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared | |
| 170 | 241 | Groups_Utility::id( $current_group->group_id ) |
| 171 | 242 | ) ); |
| 172 | 243 | if ( $count === null ) { |
| 173 | 244 | $count = 0; |
| @@ -173,33 +244,44 @@ | ||
| 173 | 244 | $count = 0; |
| 174 | 245 | } else { |
| 175 | 246 | $count = intval( $count ); |
| 176 | 247 | } |
| 177 | - $output .= _n( $options['single'], sprintf( $options['plural'], $count ), $count, GROUPS_PLUGIN_DOMAIN ); | |
| 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 | + } | |
| 178 | 258 | break; |
| 179 | - // @todo experimental - could use pagination, sorting, link to profile, ... | |
| 180 | 259 | case 'users' : |
| 181 | - $user_group_table = _groups_get_tablename( "user_group" ); | |
| 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' ); | |
| 182 | 263 | $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", | |
| 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 | |
| 184 | 265 | Groups_Utility::id( $current_group->group_id ) |
| 185 | 266 | ) ); |
| 186 | 267 | if ( $users ) { |
| 187 | 268 | $output .= '<ul>'; |
| 188 | - foreach( $users as $user ) { | |
| 189 | - $output .= '<li>' . wp_filter_nohtml_kses( $user->user_login ) . '</li>'; | |
| 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>'; | |
| 190 | 272 | } |
| 191 | 273 | $output .= '</ul>'; |
| 192 | 274 | } |
| 193 | - | |
| 194 | 275 | break; |
| 195 | 276 | } |
| 196 | 277 | } |
| 197 | - return $output; | |
| 278 | + return $output; // nosemgrep audit.php.wp.security.sqli.shortcode-attr, audit.php.wp.security.xss.shortcode-attr | |
| 198 | 279 | } |
| 199 | 280 | |
| 200 | 281 | /** |
| 201 | 282 | * Renders the current or a specific user's groups. |
| 283 | + * | |
| 202 | 284 | * Attributes: |
| 203 | 285 | * - "user_id" OR "user_login" OR "user_email" to identify the user, if none given assumes the current user |
| 204 | 286 | * - "format" : one of "list" "div" "ul" or "ol" - "list" and "ul" are equivalent |
| 205 | 287 | * - "list_class" : defaults to "groups" |
| @@ -205,15 +287,21 @@ | ||
| 205 | 287 | * - "list_class" : defaults to "groups" |
| 206 | 288 | * - "item_class" : defaults to "name" |
| 207 | 289 | * - "order_by" : defaults to "name", also accepts "group_id" |
| 208 | 290 | * - "order" : default to "ASC", also accepts "asc", "desc" and "DESC" |
| 209 | - * | |
| 291 | + * | |
| 210 | 292 | * @param array $atts attributes |
| 211 | 293 | * @param string $content not used |
| 212 | - * @return rendered groups for current user | |
| 294 | + * | |
| 295 | + * @return string rendered groups for current user | |
| 213 | 296 | */ |
| 214 | 297 | public static function groups_user_groups( $atts, $content = null ) { |
| 215 | - $output = ""; | |
| 298 | + | |
| 299 | + if ( !self::validate( 'groups_user_groups', $atts, $content ) ) { | |
| 300 | + return ''; | |
| 301 | + } | |
| 302 | + | |
| 303 | + $output = ''; | |
| 216 | 304 | $options = shortcode_atts( |
| 217 | 305 | array( |
| 218 | 306 | 'user_id' => null, |
| 219 | 307 | 'user_login' => null, |
| @@ -246,15 +334,15 @@ | ||
| 246 | 334 | $user_id = get_current_user_id(); |
| 247 | 335 | } |
| 248 | 336 | if ( $user_id !== null ) { |
| 249 | 337 | $user = new Groups_User( $user_id ); |
| 250 | - $groups = $user->groups; | |
| 338 | + $groups = $user->get_groups(); | |
| 251 | 339 | |
| 252 | 340 | if ( !empty( $groups ) ) { |
| 253 | - // group attr | |
| 341 | + // group attr | |
| 254 | 342 | if ( $options['group'] !== null ) { |
| 255 | 343 | $groups = array(); |
| 256 | - $groups_incl = explode( ",", $options['group'] ); | |
| 344 | + $groups_incl = explode( ',', $options['group'] ); | |
| 257 | 345 | foreach ( $groups_incl as $group_incl ) { |
| 258 | 346 | $group = trim( $group_incl ); |
| 259 | 347 | $current_group = Groups_Group::read( $group ); |
| 260 | 348 | if ( !$current_group ) { |
| @@ -260,9 +348,9 @@ | ||
| 260 | 348 | if ( !$current_group ) { |
| 261 | 349 | $current_group = Groups_Group::read_by_name( $group ); |
| 262 | 350 | } |
| 263 | 351 | if ( $current_group ) { |
| 264 | - if ( Groups_User_Group::read( $user_id, $current_group->group_id ) ) { | |
| 352 | + if ( Groups_User::user_is_member( $user_id, $current_group->group_id ) ) { | |
| 265 | 353 | $groups[] = $current_group; |
| 266 | 354 | } |
| 267 | 355 | } |
| 268 | 356 | } |
| @@ -268,9 +356,9 @@ | ||
| 268 | 356 | } |
| 269 | 357 | } |
| 270 | 358 | // exclude_group attr |
| 271 | 359 | if ( $options['exclude_group'] !== null ) { |
| 272 | - $groups_excl = explode( ",", $options['exclude_group'] ); | |
| 360 | + $groups_excl = explode( ',', $options['exclude_group'] ); | |
| 273 | 361 | foreach ( $groups_excl as $key => $group_excl ) { |
| 274 | 362 | $group = trim( $group_excl ); |
| 275 | 363 | $current_group = Groups_Group::read( $group ); |
| 276 | 364 | if ( !$current_group ) { |
| @@ -287,9 +375,9 @@ | ||
| 287 | 375 | unset( $groups[$key] ); |
| 288 | 376 | } |
| 289 | 377 | } |
| 290 | 378 | } |
| 291 | - switch( $options['order_by'] ) { | |
| 379 | + switch ( $options['order_by'] ) { | |
| 292 | 380 | case 'group_id' : |
| 293 | 381 | usort( $groups, array( __CLASS__, 'sort_id' ) ); |
| 294 | 382 | break; |
| 295 | 383 | default : |
| @@ -294,9 +382,9 @@ | ||
| 294 | 382 | break; |
| 295 | 383 | default : |
| 296 | 384 | usort( $groups, array( __CLASS__, 'sort_name' ) ); |
| 297 | 385 | } |
| 298 | - switch( $options['order'] ) { | |
| 386 | + switch ( $options['order'] ) { | |
| 299 | 387 | case 'desc' : |
| 300 | 388 | case 'DESC' : |
| 301 | 389 | $groups = array_reverse( $groups ); |
| 302 | 390 | break; |
| @@ -301,9 +389,9 @@ | ||
| 301 | 389 | $groups = array_reverse( $groups ); |
| 302 | 390 | break; |
| 303 | 391 | } |
| 304 | 392 | |
| 305 | - switch( $options['format'] ) { | |
| 393 | + switch ( $options['format'] ) { | |
| 306 | 394 | case 'list' : |
| 307 | 395 | case 'ul' : |
| 308 | 396 | $output .= '<ul class="' . esc_attr( $options['list_class'] ) . '">'; |
| 309 | 397 | break; |
| @@ -312,20 +400,24 @@ | ||
| 312 | 400 | break; |
| 313 | 401 | default : |
| 314 | 402 | $output .= '<div class="' . esc_attr( $options['list_class'] ) . '">'; |
| 315 | 403 | } |
| 316 | - foreach( $groups as $group ) { | |
| 317 | - switch( $options['format'] ) { | |
| 404 | + foreach ( $groups as $group ) { | |
| 405 | + switch ( $options['format'] ) { | |
| 318 | 406 | case 'list' : |
| 319 | 407 | case 'ul' : |
| 320 | 408 | case 'ol' : |
| 321 | - $output .= '<li class="' . esc_attr( $options['item_class'] ) . '">' . $group->name . '</li>'; | |
| 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>'; | |
| 322 | 412 | break; |
| 323 | 413 | default : |
| 324 | - $output .= '<div class="' . esc_attr( $options['item_class'] ) . '">' . $group->name . '</div>'; | |
| 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>'; | |
| 325 | 417 | } |
| 326 | 418 | } |
| 327 | - switch( $options['format'] ) { | |
| 419 | + switch ( $options['format'] ) { | |
| 328 | 420 | case 'list' : |
| 329 | 421 | case 'ul' : |
| 330 | 422 | $output .= '</ul>'; |
| 331 | 423 | break; |
| @@ -344,27 +436,30 @@ | ||
| 344 | 436 | * Group comparison by group_id. |
| 345 | 437 | * |
| 346 | 438 | * @param Groups_Group $a |
| 347 | 439 | * @param Groups_Group $b |
| 440 | + * | |
| 348 | 441 | * @return int |
| 349 | 442 | */ |
| 350 | 443 | public static function sort_id( $a, $b ) { |
| 351 | - return $a->group_id - $b->group_id; | |
| 444 | + return $a->get_id() - $b->get_id(); | |
| 352 | 445 | } |
| 353 | 446 | |
| 354 | 447 | /** |
| 355 | 448 | * Group comparison by name. |
| 356 | - * | |
| 449 | + * | |
| 357 | 450 | * @param Groups_Group $a |
| 358 | 451 | * @param Groups_Group $b |
| 452 | + * | |
| 359 | 453 | * @return int |
| 360 | 454 | */ |
| 361 | 455 | public static function sort_name( $a, $b ) { |
| 362 | - return strcmp( $a->name, $b->name ); | |
| 456 | + return strcmp( $a->get_name(), $b->get_name() ); | |
| 363 | 457 | } |
| 364 | 458 | |
| 365 | 459 | /** |
| 366 | 460 | * Renders a list of the site's groups. |
| 461 | + * | |
| 367 | 462 | * Attributes: |
| 368 | 463 | * - "format" : one of "list" "div" "ul" or "ol" - "list" and "ul" are equivalent |
| 369 | 464 | * - "list_class" : defaults to "groups" |
| 370 | 465 | * - "item_class" : defaults to "name" |
| @@ -372,13 +467,20 @@ | ||
| 372 | 467 | * - "order" : default to "ASC", also accepts "asc", "desc" and "DESC" |
| 373 | 468 | * |
| 374 | 469 | * @param array $atts attributes |
| 375 | 470 | * @param string $content not used |
| 376 | - * @return rendered groups | |
| 471 | + * | |
| 472 | + * @return string rendered groups | |
| 377 | 473 | */ |
| 378 | 474 | public static function groups_groups( $atts, $content = null ) { |
| 475 | + | |
| 379 | 476 | global $wpdb; |
| 380 | - $output = ""; | |
| 477 | + | |
| 478 | + if ( !self::validate( 'groups_groups', $atts, $content ) ) { | |
| 479 | + return ''; | |
| 480 | + } | |
| 481 | + | |
| 482 | + $output = ''; | |
| 381 | 483 | $options = shortcode_atts( |
| 382 | 484 | array( |
| 383 | 485 | 'format' => 'list', |
| 384 | 486 | 'list_class' => 'groups', |
| @@ -387,9 +489,9 @@ | ||
| 387 | 489 | 'order' => 'ASC' |
| 388 | 490 | ), |
| 389 | 491 | $atts |
| 390 | 492 | ); |
| 391 | - switch( $options['order_by'] ) { | |
| 493 | + switch ( $options['order_by'] ) { | |
| 392 | 494 | case 'group_id' : |
| 393 | 495 | case 'name' : |
| 394 | 496 | $order_by = $options['order_by']; |
| 395 | 497 | break; |
| @@ -395,9 +497,9 @@ | ||
| 395 | 497 | break; |
| 396 | 498 | default : |
| 397 | 499 | $order_by = 'name'; |
| 398 | 500 | } |
| 399 | - switch( $options['order'] ) { | |
| 501 | + switch ( $options['order'] ) { | |
| 400 | 502 | case 'asc' : |
| 401 | 503 | case 'ASC' : |
| 402 | 504 | case 'desc' : |
| 403 | 505 | case 'DESC' : |
| @@ -405,13 +507,13 @@ | ||
| 405 | 507 | break; |
| 406 | 508 | default : |
| 407 | 509 | $order = 'ASC'; |
| 408 | 510 | } |
| 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'] ) { | |
| 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'] ) { | |
| 414 | 516 | case 'list' : |
| 415 | 517 | case 'ul' : |
| 416 | 518 | $output .= '<ul class="' . esc_attr( $options['list_class'] ) . '">'; |
| 417 | 519 | break; |
| @@ -420,21 +522,21 @@ | ||
| 420 | 522 | break; |
| 421 | 523 | default : |
| 422 | 524 | $output .= '<div class="' . esc_attr( $options['list_class'] ) . '">'; |
| 423 | 525 | } |
| 424 | - foreach( $groups as $group ) { | |
| 526 | + foreach ( $groups as $group ) { | |
| 425 | 527 | $group = new Groups_Group( $group->group_id ); |
| 426 | - switch( $options['format'] ) { | |
| 528 | + switch ( $options['format'] ) { | |
| 427 | 529 | case 'list' : |
| 428 | 530 | case 'ul' : |
| 429 | 531 | case 'ol' : |
| 430 | - $output .= '<li class="' . esc_attr( $options['item_class'] ) . '">' . $group->name . '</li>'; | |
| 532 | + $output .= '<li class="' . esc_attr( $options['item_class'] ) . '">' . stripslashes( esc_html( $group->get_name() ) ) . '</li>'; | |
| 431 | 533 | break; |
| 432 | 534 | default : |
| 433 | - $output .= '<div class="' . esc_attr( $options['item_class'] ) . '">' . $group->name . '</div>'; | |
| 535 | + $output .= '<div class="' . esc_attr( $options['item_class'] ) . '">' . stripslashes( esc_html( $group->get_name() ) ) . '</div>'; | |
| 434 | 536 | } |
| 435 | 537 | } |
| 436 | - switch( $options['format'] ) { | |
| 538 | + switch ( $options['format'] ) { | |
| 437 | 539 | case 'list' : |
| 438 | 540 | case 'ul' : |
| 439 | 541 | $output .= '</ul>'; |
| 440 | 542 | break; |
| @@ -449,159 +551,851 @@ | ||
| 449 | 551 | } |
| 450 | 552 | |
| 451 | 553 | /** |
| 452 | 554 | * Renders a form that lets a user join a group. |
| 453 | - * * Attributes: | |
| 555 | + * | |
| 556 | + * Attributes: | |
| 557 | + * | |
| 454 | 558 | * - "group" : (required) group name or id |
| 455 | - * | |
| 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 | + * | |
| 456 | 566 | * @param array $atts attributes |
| 457 | 567 | * @param string $content not used |
| 568 | + * | |
| 569 | + * @return string | |
| 458 | 570 | */ |
| 459 | 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 | + | |
| 460 | 579 | $nonce_action = 'groups_action'; |
| 461 | 580 | $nonce = 'nonce_join'; |
| 462 | - $output = ""; | |
| 581 | + $output = ''; | |
| 463 | 582 | |
| 464 | 583 | $options = shortcode_atts( |
| 465 | 584 | array( |
| 585 | + 'class' => '', | |
| 466 | 586 | 'group' => '', |
| 467 | 587 | 'display_message' => true, |
| 468 | 588 | 'display_is_member' => false, |
| 469 | - 'submit_text' => __( 'Join the %s group', GROUPS_PLUGIN_DOMAIN ) | |
| 589 | + 'redirect' => true, | |
| 590 | + 'submit_class' => '', | |
| 591 | + /* translators: group name */ | |
| 592 | + 'submit_text' => esc_html__( 'Join the %s group', 'groups' ) | |
| 470 | 593 | ), |
| 471 | 594 | $atts |
| 472 | 595 | ); |
| 473 | - extract( $options ); | |
| 474 | 596 | |
| 475 | - if ( $display_message === 'false' ) { | |
| 476 | - $display_message = false; | |
| 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 | + } | |
| 477 | 624 | } |
| 478 | - if ( $display_is_member === 'true' ) { | |
| 479 | - $display_is_member = true; | |
| 480 | - } | |
| 481 | 625 | |
| 626 | + $class = trim( $options['class'] ); | |
| 627 | + $submit_class = trim( $options['submit_class'] ); | |
| 482 | 628 | $group = trim( $options['group'] ); |
| 483 | 629 | $current_group = Groups_Group::read( $group ); |
| 484 | 630 | if ( !$current_group ) { |
| 485 | 631 | $current_group = Groups_Group::read_by_name( $group ); |
| 486 | 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 | + | |
| 487 | 656 | if ( $current_group ) { |
| 488 | 657 | if ( $user_id = get_current_user_id() ) { |
| 658 | + $joined = false; | |
| 489 | 659 | $submitted = false; |
| 490 | 660 | $invalid_nonce = false; |
| 491 | - if ( !empty( $_POST['groups_action'] ) && $_POST['groups_action'] == 'join' ) { | |
| 661 | + if ( groups_sanitize_post( 'groups_action' ) === 'join' ) { | |
| 492 | 662 | $submitted = true; |
| 493 | - if ( !wp_verify_nonce( $_POST[$nonce], $nonce_action ) ) { | |
| 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 | |
| 494 | 665 | $invalid_nonce = true; |
| 495 | 666 | } |
| 496 | 667 | } |
| 497 | 668 | if ( $submitted && !$invalid_nonce ) { |
| 498 | 669 | // 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 | - ); | |
| 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 | + } | |
| 507 | 703 | } |
| 508 | 704 | } |
| 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">'; | |
| 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 | + ); | |
| 512 | 729 | $output .= '<form action="#" method="post">'; |
| 513 | 730 | $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 ); | |
| 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 ); | |
| 517 | 738 | $output .= '</form>'; |
| 518 | 739 | $output .= '</div>'; |
| 519 | 740 | } else if ( $display_message ) { |
| 520 | - if ( $submitted && !$invalid_nonce && isset( $join_group ) && $join_group->group_id === $current_group->group_id ) { | |
| 741 | + if ( $joined ) { | |
| 521 | 742 | $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 ) ); | |
| 743 | + /* translators: group name */ | |
| 744 | + $output .= sprintf( esc_html__( 'You have joined the %s group.', 'groups' ), stripslashes( wp_filter_nohtml_kses( $current_group->name ) ) ); | |
| 523 | 745 | $output .= '</div>'; |
| 524 | - } | |
| 525 | - else if ( $display_is_member && isset( $current_group ) && $current_group !== false ) { | |
| 746 | + } else if ( $display_is_member && $current_group !== false ) { | |
| 526 | 747 | $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 ) ); | |
| 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 ) ) ); | |
| 528 | 750 | $output .= '</div>'; |
| 529 | 751 | } |
| 530 | 752 | } |
| 531 | 753 | } |
| 532 | 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 | + | |
| 533 | 776 | return $output; |
| 534 | 777 | } |
| 535 | 778 | |
| 536 | 779 | /** |
| 537 | 780 | * Renders a form that lets a user leave a group. |
| 538 | - * * Attributes: | |
| 781 | + * | |
| 782 | + * Attributes: | |
| 783 | + * | |
| 539 | 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 | |
| 540 | 790 | * |
| 541 | 791 | * @param array $atts attributes |
| 542 | 792 | * @param string $content not used |
| 793 | + * | |
| 794 | + * @return string | |
| 543 | 795 | */ |
| 544 | 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 | + | |
| 545 | 804 | $nonce_action = 'groups_action'; |
| 546 | 805 | $nonce = 'nonce_leave'; |
| 547 | - $output = ""; | |
| 806 | + $output = ''; | |
| 548 | 807 | |
| 549 | 808 | $options = shortcode_atts( |
| 550 | 809 | array( |
| 810 | + 'class' => '', | |
| 551 | 811 | 'group' => '', |
| 552 | 812 | 'display_message' => true, |
| 553 | - 'submit_text' => __( 'Leave the %s group', GROUPS_PLUGIN_DOMAIN ), | |
| 813 | + 'redirect' => true, | |
| 814 | + 'submit_class' => '', | |
| 815 | + /* translators: group name */ | |
| 816 | + 'submit_text' => esc_html__( 'Leave the %s group', 'groups' ), | |
| 554 | 817 | ), |
| 555 | 818 | $atts |
| 556 | 819 | ); |
| 557 | - extract( $options ); | |
| 558 | 820 | |
| 559 | - if ( $display_message === 'false' ) { | |
| 560 | - $display_message = false; | |
| 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 | + } | |
| 561 | 847 | } |
| 562 | 848 | |
| 849 | + $class = trim( $options['class'] ); | |
| 850 | + $submit_class = trim( $options['submit_class'] ); | |
| 563 | 851 | $group = trim( $options['group'] ); |
| 564 | 852 | $current_group = Groups_Group::read( $group ); |
| 565 | 853 | if ( !$current_group ) { |
| 566 | 854 | $current_group = Groups_Group::read_by_name( $group ); |
| 567 | 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 | + | |
| 568 | 879 | if ( $current_group ) { |
| 569 | 880 | if ( $user_id = get_current_user_id() ) { |
| 881 | + $left = false; | |
| 570 | 882 | $submitted = false; |
| 571 | 883 | $invalid_nonce = false; |
| 572 | - if ( !empty( $_POST['groups_action'] ) && $_POST['groups_action'] == 'leave' ) { | |
| 884 | + if ( groups_sanitize_post( 'groups_action' ) === 'leave' ) { | |
| 573 | 885 | $submitted = true; |
| 574 | - if ( !wp_verify_nonce( $_POST[$nonce], $nonce_action ) ) { | |
| 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 | |
| 575 | 888 | $invalid_nonce = true; |
| 576 | 889 | } |
| 577 | 890 | } |
| 578 | 891 | if ( $submitted && !$invalid_nonce ) { |
| 579 | 892 | // 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 ); | |
| 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 | + } | |
| 583 | 921 | } |
| 584 | 922 | } |
| 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">'; | |
| 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 | + ); | |
| 588 | 947 | $output .= '<form action="#" method="post">'; |
| 589 | 948 | $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 ); | |
| 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 ); | |
| 593 | 956 | $output .= '</form>'; |
| 594 | 957 | $output .= '</div>'; |
| 595 | 958 | } 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 ) ); | |
| 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 ) ) ); | |
| 599 | 963 | $output .= '</div>'; |
| 600 | 964 | } |
| 601 | 965 | } |
| 602 | 966 | } |
| 603 | 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 | + | |
| 604 | 989 | return $output; |
| 605 | 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 | + } | |
| 606 | 1399 | } |
| 1400 | + | |
| 607 | 1401 | Groups_Shortcodes::init(); |