wp/wp-includes/shortcodes.php
changeset 9 177826044cd9
parent 7 cf61fcea0001
child 16 a86126ab1dd4
equal deleted inserted replaced
8:c7c34916027a 9:177826044cd9
    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
   262 		.         ')'
   266 		.         ')'
   263 		.         '\\[\\/\\2\\]'             // Closing shortcode tag
   267 		.         '\\[\\/\\2\\]'             // Closing shortcode tag
   264 		.     ')?'
   268 		.     ')?'
   265 		. ')'
   269 		. ')'
   266 		. '(\\]?)';                          // 6: Optional second closing brocket for escaping shortcodes: [[tag]]
   270 		. '(\\]?)';                          // 6: Optional second closing brocket for escaping shortcodes: [[tag]]
       
   271 	// phpcs:enable
   267 }
   272 }
   268 
   273 
   269 /**
   274 /**
   270  * Regular Expression callable for do_shortcode() for calling shortcode hook.
   275  * Regular Expression callable for do_shortcode() for calling shortcode hook.
       
   276  *
   271  * @see get_shortcode_regex for details of the match array contents.
   277  * @see get_shortcode_regex for details of the match array contents.
   272  *
   278  *
   273  * @since 2.5.0
   279  * @since 2.5.0
   274  * @access private
   280  * @access private
   275  *
   281  *
   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( '&#91;' => '&#091;', '&#93;' => '&#093;' );
   357 	$trans   = array(
       
   358 		'&#91;' => '&#091;',
       
   359 		'&#93;' => '&#093;',
       
   360 	);
   352 	$content = strtr( $content, $trans );
   361 	$content = strtr( $content, $trans );
   353 	$trans = array( '[' => '&#91;', ']' => '&#93;' );
   362 	$trans   = array(
       
   363 		'[' => '&#91;',
       
   364 		']' => '&#93;',
       
   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( '&#91;' => '[', '&#93;' => ']' );
   462 	$trans = array(
   451         $content = strtr( $content, $trans );
   463 		'&#91;' => '[',
   452 
   464 		'&#93;' => ']',
   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 }