86 * |
86 * |
87 * @global array $shortcode_tags |
87 * @global array $shortcode_tags |
88 * |
88 * |
89 * @param string $tag Shortcode tag to remove hook for. |
89 * @param string $tag Shortcode tag to remove hook for. |
90 */ |
90 */ |
91 function remove_shortcode($tag) { |
91 function remove_shortcode( $tag ) { |
92 global $shortcode_tags; |
92 global $shortcode_tags; |
93 |
93 |
94 unset($shortcode_tags[$tag]); |
94 unset( $shortcode_tags[ $tag ] ); |
95 } |
95 } |
96 |
96 |
97 /** |
97 /** |
98 * Clear all shortcodes. |
98 * Clear all shortcodes. |
99 * |
99 * |
142 return false; |
142 return false; |
143 } |
143 } |
144 |
144 |
145 if ( shortcode_exists( $tag ) ) { |
145 if ( shortcode_exists( $tag ) ) { |
146 preg_match_all( '/' . get_shortcode_regex() . '/', $content, $matches, PREG_SET_ORDER ); |
146 preg_match_all( '/' . get_shortcode_regex() . '/', $content, $matches, PREG_SET_ORDER ); |
147 if ( empty( $matches ) ) |
147 if ( empty( $matches ) ) { |
148 return false; |
148 return false; |
|
149 } |
149 |
150 |
150 foreach ( $matches as $shortcode ) { |
151 foreach ( $matches as $shortcode ) { |
151 if ( $tag === $shortcode[2] ) { |
152 if ( $tag === $shortcode[2] ) { |
152 return true; |
153 return true; |
153 } elseif ( ! empty( $shortcode[5] ) && has_shortcode( $shortcode[5], $tag ) ) { |
154 } elseif ( ! empty( $shortcode[5] ) && has_shortcode( $shortcode[5], $tag ) ) { |
178 |
179 |
179 if ( false === strpos( $content, '[' ) ) { |
180 if ( false === strpos( $content, '[' ) ) { |
180 return $content; |
181 return $content; |
181 } |
182 } |
182 |
183 |
183 if (empty($shortcode_tags) || !is_array($shortcode_tags)) |
184 if ( empty( $shortcode_tags ) || ! is_array( $shortcode_tags ) ) { |
184 return $content; |
185 return $content; |
|
186 } |
185 |
187 |
186 // Find all registered tag names in $content. |
188 // Find all registered tag names in $content. |
187 preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $content, $matches ); |
189 preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $content, $matches ); |
188 $tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] ); |
190 $tagnames = array_intersect( array_keys( $shortcode_tags ), $matches[1] ); |
189 |
191 |
229 global $shortcode_tags; |
231 global $shortcode_tags; |
230 |
232 |
231 if ( empty( $tagnames ) ) { |
233 if ( empty( $tagnames ) ) { |
232 $tagnames = array_keys( $shortcode_tags ); |
234 $tagnames = array_keys( $shortcode_tags ); |
233 } |
235 } |
234 $tagregexp = join( '|', array_map('preg_quote', $tagnames) ); |
236 $tagregexp = join( '|', array_map( 'preg_quote', $tagnames ) ); |
235 |
237 |
236 // WARNING! Do not change this regex without changing do_shortcode_tag() and strip_shortcode_tag() |
238 // WARNING! Do not change this regex without changing do_shortcode_tag() and strip_shortcode_tag() |
237 // Also, see shortcode_unautop() and shortcode.js. |
239 // Also, see shortcode_unautop() and shortcode.js. |
|
240 |
|
241 // phpcs:disable Squiz.Strings.ConcatenationSpacing.PaddingFound -- don't remove regex indentation |
238 return |
242 return |
239 '\\[' // Opening bracket |
243 '\\[' // Opening bracket |
240 . '(\\[?)' // 1: Optional second opening bracket for escaping shortcodes: [[tag]] |
244 . '(\\[?)' // 1: Optional second opening bracket for escaping shortcodes: [[tag]] |
241 . "($tagregexp)" // 2: Shortcode name |
245 . "($tagregexp)" // 2: Shortcode name |
242 . '(?![\\w-])' // Not followed by word character or hyphen |
246 . '(?![\\w-])' // Not followed by word character or hyphen |
243 . '(' // 3: Unroll the loop: Inside the opening shortcode tag |
247 . '(' // 3: Unroll the loop: Inside the opening shortcode tag |
244 . '[^\\]\\/]*' // Not a closing bracket or forward slash |
248 . '[^\\]\\/]*' // Not a closing bracket or forward slash |
281 function do_shortcode_tag( $m ) { |
287 function do_shortcode_tag( $m ) { |
282 global $shortcode_tags; |
288 global $shortcode_tags; |
283 |
289 |
284 // allow [[foo]] syntax for escaping a tag |
290 // allow [[foo]] syntax for escaping a tag |
285 if ( $m[1] == '[' && $m[6] == ']' ) { |
291 if ( $m[1] == '[' && $m[6] == ']' ) { |
286 return substr($m[0], 1, -1); |
292 return substr( $m[0], 1, -1 ); |
287 } |
293 } |
288 |
294 |
289 $tag = $m[2]; |
295 $tag = $m[2]; |
290 $attr = shortcode_parse_atts( $m[3] ); |
296 $attr = shortcode_parse_atts( $m[3] ); |
291 |
297 |
292 if ( ! is_callable( $shortcode_tags[ $tag ] ) ) { |
298 if ( ! is_callable( $shortcode_tags[ $tag ] ) ) { |
293 /* translators: %s: shortcode tag */ |
299 /* translators: %s: shortcode tag */ |
294 $message = sprintf( __( 'Attempting to parse a shortcode without a valid callback: %s' ), $tag ); |
300 $message = sprintf( __( 'Attempting to parse a shortcode without a valid callback: %s' ), $tag ); |
346 * @param array $tagnames List of shortcodes to find. |
352 * @param array $tagnames List of shortcodes to find. |
347 * @return string Content with shortcodes filtered out. |
353 * @return string Content with shortcodes filtered out. |
348 */ |
354 */ |
349 function do_shortcodes_in_html_tags( $content, $ignore_html, $tagnames ) { |
355 function do_shortcodes_in_html_tags( $content, $ignore_html, $tagnames ) { |
350 // Normalize entities in unfiltered HTML before adding placeholders. |
356 // Normalize entities in unfiltered HTML before adding placeholders. |
351 $trans = array( '[' => '[', ']' => ']' ); |
357 $trans = array( |
|
358 '[' => '[', |
|
359 ']' => ']', |
|
360 ); |
352 $content = strtr( $content, $trans ); |
361 $content = strtr( $content, $trans ); |
353 $trans = array( '[' => '[', ']' => ']' ); |
362 $trans = array( |
|
363 '[' => '[', |
|
364 ']' => ']', |
|
365 ); |
354 |
366 |
355 $pattern = get_shortcode_regex( $tagnames ); |
367 $pattern = get_shortcode_regex( $tagnames ); |
356 $textarr = wp_html_split( $content ); |
368 $textarr = wp_html_split( $content ); |
357 |
369 |
358 foreach ( $textarr as &$element ) { |
370 foreach ( $textarr as &$element ) { |
359 if ( '' == $element || '<' !== $element[0] ) { |
371 if ( '' == $element || '<' !== $element[0] ) { |
360 continue; |
372 continue; |
361 } |
373 } |
362 |
374 |
363 $noopen = false === strpos( $element, '[' ); |
375 $noopen = false === strpos( $element, '[' ); |
364 $noclose = false === strpos( $element, ']' ); |
376 $noclose = false === strpos( $element, ']' ); |
365 if ( $noopen || $noclose ) { |
377 if ( $noopen || $noclose ) { |
366 // This element does not contain shortcodes. |
378 // This element does not contain shortcodes. |
367 if ( $noopen xor $noclose ) { |
379 if ( $noopen xor $noclose ) { |
368 // Need to encode stray [ or ] chars. |
380 // Need to encode stray [ or ] chars. |
388 $element = strtr( $element, $trans ); |
400 $element = strtr( $element, $trans ); |
389 continue; |
401 continue; |
390 } |
402 } |
391 |
403 |
392 // Get element name |
404 // Get element name |
393 $front = array_shift( $attributes ); |
405 $front = array_shift( $attributes ); |
394 $back = array_pop( $attributes ); |
406 $back = array_pop( $attributes ); |
395 $matches = array(); |
407 $matches = array(); |
396 preg_match('%[a-zA-Z0-9]+%', $front, $matches); |
408 preg_match( '%[a-zA-Z0-9]+%', $front, $matches ); |
397 $elname = $matches[0]; |
409 $elname = $matches[0]; |
398 |
410 |
399 // Look for shortcodes in each attribute separately. |
411 // Look for shortcodes in each attribute separately. |
400 foreach ( $attributes as &$attr ) { |
412 foreach ( $attributes as &$attr ) { |
401 $open = strpos( $attr, '[' ); |
413 $open = strpos( $attr, '[' ); |
402 $close = strpos( $attr, ']' ); |
414 $close = strpos( $attr, ']' ); |
403 if ( false === $open || false === $close ) { |
415 if ( false === $open || false === $close ) { |
404 continue; // Go to next attribute. Square braces will be escaped at end of loop. |
416 continue; // Go to next attribute. Square braces will be escaped at end of loop. |
405 } |
417 } |
406 $double = strpos( $attr, '"' ); |
418 $double = strpos( $attr, '"' ); |
412 // and we do not need to run KSES here. |
424 // and we do not need to run KSES here. |
413 $attr = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $attr ); |
425 $attr = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $attr ); |
414 } else { |
426 } else { |
415 // $attr like 'name = "[shortcode]"' or "name = '[shortcode]'" |
427 // $attr like 'name = "[shortcode]"' or "name = '[shortcode]'" |
416 // We do not know if $content was unfiltered. Assume KSES ran before shortcodes. |
428 // We do not know if $content was unfiltered. Assume KSES ran before shortcodes. |
417 $count = 0; |
429 $count = 0; |
418 $new_attr = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $attr, -1, $count ); |
430 $new_attr = preg_replace_callback( "/$pattern/", 'do_shortcode_tag', $attr, -1, $count ); |
419 if ( $count > 0 ) { |
431 if ( $count > 0 ) { |
420 // Sanitize the shortcode output using KSES. |
432 // Sanitize the shortcode output using KSES. |
421 $new_attr = wp_kses_one_attr( $new_attr, $elname ); |
433 $new_attr = wp_kses_one_attr( $new_attr, $elname ); |
422 if ( '' !== trim( $new_attr ) ) { |
434 if ( '' !== trim( $new_attr ) ) { |
444 * |
456 * |
445 * @param string $content Content to search for placeholders. |
457 * @param string $content Content to search for placeholders. |
446 * @return string Content with placeholders removed. |
458 * @return string Content with placeholders removed. |
447 */ |
459 */ |
448 function unescape_invalid_shortcodes( $content ) { |
460 function unescape_invalid_shortcodes( $content ) { |
449 // Clean up entire string, avoids re-parsing HTML. |
461 // Clean up entire string, avoids re-parsing HTML. |
450 $trans = array( '[' => '[', ']' => ']' ); |
462 $trans = array( |
451 $content = strtr( $content, $trans ); |
463 '[' => '[', |
452 |
464 ']' => ']', |
453 return $content; |
465 ); |
|
466 |
|
467 $content = strtr( $content, $trans ); |
|
468 |
|
469 return $content; |
454 } |
470 } |
455 |
471 |
456 /** |
472 /** |
457 * Retrieve the shortcode attributes regex. |
473 * Retrieve the shortcode attributes regex. |
458 * |
474 * |
477 * @return array|string List of attribute values. |
493 * @return array|string List of attribute values. |
478 * Returns empty array if trim( $text ) == '""'. |
494 * Returns empty array if trim( $text ) == '""'. |
479 * Returns empty string if trim( $text ) == ''. |
495 * Returns empty string if trim( $text ) == ''. |
480 * All other matches are checked for not empty(). |
496 * All other matches are checked for not empty(). |
481 */ |
497 */ |
482 function shortcode_parse_atts($text) { |
498 function shortcode_parse_atts( $text ) { |
483 $atts = array(); |
499 $atts = array(); |
484 $pattern = get_shortcode_atts_regex(); |
500 $pattern = get_shortcode_atts_regex(); |
485 $text = preg_replace("/[\x{00a0}\x{200b}]+/u", " ", $text); |
501 $text = preg_replace( "/[\x{00a0}\x{200b}]+/u", ' ', $text ); |
486 if ( preg_match_all($pattern, $text, $match, PREG_SET_ORDER) ) { |
502 if ( preg_match_all( $pattern, $text, $match, PREG_SET_ORDER ) ) { |
487 foreach ($match as $m) { |
503 foreach ( $match as $m ) { |
488 if (!empty($m[1])) |
504 if ( ! empty( $m[1] ) ) { |
489 $atts[strtolower($m[1])] = stripcslashes($m[2]); |
505 $atts[ strtolower( $m[1] ) ] = stripcslashes( $m[2] ); |
490 elseif (!empty($m[3])) |
506 } elseif ( ! empty( $m[3] ) ) { |
491 $atts[strtolower($m[3])] = stripcslashes($m[4]); |
507 $atts[ strtolower( $m[3] ) ] = stripcslashes( $m[4] ); |
492 elseif (!empty($m[5])) |
508 } elseif ( ! empty( $m[5] ) ) { |
493 $atts[strtolower($m[5])] = stripcslashes($m[6]); |
509 $atts[ strtolower( $m[5] ) ] = stripcslashes( $m[6] ); |
494 elseif (isset($m[7]) && strlen($m[7])) |
510 } elseif ( isset( $m[7] ) && strlen( $m[7] ) ) { |
495 $atts[] = stripcslashes($m[7]); |
511 $atts[] = stripcslashes( $m[7] ); |
496 elseif (isset($m[8]) && strlen($m[8])) |
512 } elseif ( isset( $m[8] ) && strlen( $m[8] ) ) { |
497 $atts[] = stripcslashes($m[8]); |
513 $atts[] = stripcslashes( $m[8] ); |
498 elseif (isset($m[9])) |
514 } elseif ( isset( $m[9] ) ) { |
499 $atts[] = stripcslashes($m[9]); |
515 $atts[] = stripcslashes( $m[9] ); |
|
516 } |
500 } |
517 } |
501 |
518 |
502 // Reject any unclosed HTML elements |
519 // Reject any unclosed HTML elements |
503 foreach( $atts as &$value ) { |
520 foreach ( $atts as &$value ) { |
504 if ( false !== strpos( $value, '<' ) ) { |
521 if ( false !== strpos( $value, '<' ) ) { |
505 if ( 1 !== preg_match( '/^[^<]*+(?:<[^>]*+>[^<]*+)*+$/', $value ) ) { |
522 if ( 1 !== preg_match( '/^[^<]*+(?:<[^>]*+>[^<]*+)*+$/', $value ) ) { |
506 $value = ''; |
523 $value = ''; |
507 } |
524 } |
508 } |
525 } |
509 } |
526 } |
510 } else { |
527 } else { |
511 $atts = ltrim($text); |
528 $atts = ltrim( $text ); |
512 } |
529 } |
513 return $atts; |
530 return $atts; |
514 } |
531 } |
515 |
532 |
516 /** |
533 /** |
529 * @param array $atts User defined attributes in shortcode tag. |
546 * @param array $atts User defined attributes in shortcode tag. |
530 * @param string $shortcode Optional. The name of the shortcode, provided for context to enable filtering |
547 * @param string $shortcode Optional. The name of the shortcode, provided for context to enable filtering |
531 * @return array Combined and filtered attribute list. |
548 * @return array Combined and filtered attribute list. |
532 */ |
549 */ |
533 function shortcode_atts( $pairs, $atts, $shortcode = '' ) { |
550 function shortcode_atts( $pairs, $atts, $shortcode = '' ) { |
534 $atts = (array)$atts; |
551 $atts = (array) $atts; |
535 $out = array(); |
552 $out = array(); |
536 foreach ($pairs as $name => $default) { |
553 foreach ( $pairs as $name => $default ) { |
537 if ( array_key_exists($name, $atts) ) |
554 if ( array_key_exists( $name, $atts ) ) { |
538 $out[$name] = $atts[$name]; |
555 $out[ $name ] = $atts[ $name ]; |
539 else |
556 } else { |
540 $out[$name] = $default; |
557 $out[ $name ] = $default; |
|
558 } |
541 } |
559 } |
542 /** |
560 /** |
543 * Filters a shortcode's default attributes. |
561 * Filters a shortcode's default attributes. |
544 * |
562 * |
545 * If the third parameter of the shortcode_atts() function is present then this filter is available. |
563 * If the third parameter of the shortcode_atts() function is present then this filter is available. |
575 |
593 |
576 if ( false === strpos( $content, '[' ) ) { |
594 if ( false === strpos( $content, '[' ) ) { |
577 return $content; |
595 return $content; |
578 } |
596 } |
579 |
597 |
580 if (empty($shortcode_tags) || !is_array($shortcode_tags)) |
598 if ( empty( $shortcode_tags ) || ! is_array( $shortcode_tags ) ) { |
581 return $content; |
599 return $content; |
|
600 } |
582 |
601 |
583 // Find all registered tag names in $content. |
602 // Find all registered tag names in $content. |
584 preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $content, $matches ); |
603 preg_match_all( '@\[([^<>&/\[\]\x00-\x20=]++)@', $content, $matches ); |
585 |
604 |
586 $tags_to_remove = array_keys( $shortcode_tags ); |
605 $tags_to_remove = array_keys( $shortcode_tags ); |
588 /** |
607 /** |
589 * Filters the list of shortcode tags to remove from the content. |
608 * Filters the list of shortcode tags to remove from the content. |
590 * |
609 * |
591 * @since 4.7.0 |
610 * @since 4.7.0 |
592 * |
611 * |
593 * @param array $tag_array Array of shortcode tags to remove. |
612 * @param array $tags_to_remove Array of shortcode tags to remove. |
594 * @param string $content Content shortcodes are being removed from. |
613 * @param string $content Content shortcodes are being removed from. |
595 */ |
614 */ |
596 $tags_to_remove = apply_filters( 'strip_shortcodes_tagnames', $tags_to_remove, $content ); |
615 $tags_to_remove = apply_filters( 'strip_shortcodes_tagnames', $tags_to_remove, $content ); |
597 |
616 |
598 $tagnames = array_intersect( $tags_to_remove, $matches[1] ); |
617 $tagnames = array_intersect( $tags_to_remove, $matches[1] ); |
599 |
618 |
621 * @return string|false The content stripped of the tag, otherwise false. |
640 * @return string|false The content stripped of the tag, otherwise false. |
622 */ |
641 */ |
623 function strip_shortcode_tag( $m ) { |
642 function strip_shortcode_tag( $m ) { |
624 // allow [[foo]] syntax for escaping a tag |
643 // allow [[foo]] syntax for escaping a tag |
625 if ( $m[1] == '[' && $m[6] == ']' ) { |
644 if ( $m[1] == '[' && $m[6] == ']' ) { |
626 return substr($m[0], 1, -1); |
645 return substr( $m[0], 1, -1 ); |
627 } |
646 } |
628 |
647 |
629 return $m[1] . $m[6]; |
648 return $m[1] . $m[6]; |
630 } |
649 } |