String must contain '%id' and '%class' to inherit the category ID and
* the $class argument used for formatting in themes.
* Default '
'.
* @type string $category_after The HTML or text to append to $title_after if $categorize is true.
* Default ''.
* @type string $category_orderby How to order the bookmark category based on term scheme if $categorize
* is true. Default 'name'.
* @type string $category_order Whether to order categories in ascending or descending order if
* $categorize is true. Accepts 'ASC' (ascending) or 'DESC' (descending).
* Default 'ASC'.
* }
* @return void|string Void if 'echo' argument is true, HTML list of bookmarks if 'echo' is false.
*/
function wp_list_bookmarks( $args = '' ) {
$defaults = array(
'orderby' => 'name',
'order' => 'ASC',
'limit' => -1,
'category' => '',
'exclude_category' => '',
'category_name' => '',
'hide_invisible' => 1,
'show_updated' => 0,
'echo' => 1,
'categorize' => 1,
'title_li' => __( 'Bookmarks' ),
'title_before' => '',
'title_after' => '
',
'category_orderby' => 'name',
'category_order' => 'ASC',
'class' => 'linkcat',
'category_before' => '',
'category_after' => '',
);
$parsed_args = wp_parse_args( $args, $defaults );
$output = '';
if ( ! is_array( $parsed_args['class'] ) ) {
$parsed_args['class'] = explode( ' ', $parsed_args['class'] );
}
$parsed_args['class'] = array_map( 'sanitize_html_class', $parsed_args['class'] );
$parsed_args['class'] = trim( implode( ' ', $parsed_args['class'] ) );
if ( $parsed_args['categorize'] ) {
$cats = get_terms(
array(
'taxonomy' => 'link_category',
'name__like' => $parsed_args['category_name'],
'include' => $parsed_args['category'],
'exclude' => $parsed_args['exclude_category'],
'orderby' => $parsed_args['category_orderby'],
'order' => $parsed_args['category_order'],
'hierarchical' => 0,
)
);
if ( empty( $cats ) ) {
$parsed_args['categorize'] = false;
}
}
if ( $parsed_args['categorize'] ) {
// Split the bookmarks into ul's for each category.
foreach ( (array) $cats as $cat ) {
$params = array_merge( $parsed_args, array( 'category' => $cat->term_id ) );
$bookmarks = get_bookmarks( $params );
if ( empty( $bookmarks ) ) {
continue;
}
$output .= str_replace(
array( '%id', '%class' ),
array( "linkcat-$cat->term_id", $parsed_args['class'] ),
$parsed_args['category_before']
);
/**
* Filters the category name.
*
* @since 2.2.0
*
* @param string $cat_name The category name.
*/
$catname = apply_filters( 'link_category', $cat->name );
$output .= $parsed_args['title_before'];
$output .= $catname;
$output .= $parsed_args['title_after'];
$output .= "\n\t\n";
$output .= _walk_bookmarks( $bookmarks, $parsed_args );
$output .= "\n\t
\n";
$output .= $parsed_args['category_after'] . "\n";
}
} else {
// Output one single list using title_li for the title.
$bookmarks = get_bookmarks( $parsed_args );
if ( ! empty( $bookmarks ) ) {
if ( ! empty( $parsed_args['title_li'] ) ) {
$output .= str_replace(
array( '%id', '%class' ),
array( 'linkcat-' . $parsed_args['category'], $parsed_args['class'] ),
$parsed_args['category_before']
);
$output .= $parsed_args['title_before'];
$output .= $parsed_args['title_li'];
$output .= $parsed_args['title_after'];
$output .= "\n\t\n";
$output .= _walk_bookmarks( $bookmarks, $parsed_args );
$output .= "\n\t
\n";
$output .= $parsed_args['category_after'] . "\n";
} else {
$output .= _walk_bookmarks( $bookmarks, $parsed_args );
}
}
}
/**
* Filters the bookmarks list before it is echoed or returned.
*
* @since 2.5.0
*
* @param string $html The HTML list of bookmarks.
*/
$html = apply_filters( 'wp_list_bookmarks', $output );
if ( $parsed_args['echo'] ) {
echo $html;
} else {
return $html;
}
}
*
* @see map_deep()
*
* @param mixed $data The array, object, or scalar value to inspect.
* @return mixed The filtered content.
*/
function wp_kses_post_deep( $data ) {
return map_deep( $data, 'wp_kses_post' );
}
/**
* Strips all HTML from a text string.
*
* This function expects slashed data.
*
* @since 2.1.0
*
* @param string $data Content to strip all HTML from.
* @return string Filtered content without any HTML.
*/
function wp_filter_nohtml_kses( $data ) {
return addslashes( wp_kses( stripslashes( $data ), 'strip' ) );
}
/**
* Adds all KSES input form content filters.
*
* All hooks have default priority. The `wp_filter_kses()` function is added to
* the 'pre_comment_content' and 'title_save_pre' hooks.
*
* The `wp_filter_post_kses()` function is added to the 'content_save_pre',
* 'excerpt_save_pre', and 'content_filtered_save_pre' hooks.
*
* @since 2.0.0
*/
function kses_init_filters() {
// Normal filtering.
add_filter( 'title_save_pre', 'wp_filter_kses' );
// Comment filtering.
if ( current_user_can( 'unfiltered_html' ) ) {
add_filter( 'pre_comment_content', 'wp_filter_post_kses' );
} else {
add_filter( 'pre_comment_content', 'wp_filter_kses' );
}
// Post filtering.
add_filter( 'content_save_pre', 'wp_filter_post_kses' );
add_filter( 'excerpt_save_pre', 'wp_filter_post_kses' );
add_filter( 'content_filtered_save_pre', 'wp_filter_post_kses' );
}
/**
* Removes all KSES input form content filters.
*
* A quick procedural method to removing all of the filters that KSES uses for
* content in WordPress Loop.
*
* Does not remove the `kses_init()` function from {@see 'init'} hook (priority is
* default). Also does not remove `kses_init()` function from {@see 'set_current_user'}
* hook (priority is also default).
*
* @since 2.0.6
*/
function kses_remove_filters() {
// Normal filtering.
remove_filter( 'title_save_pre', 'wp_filter_kses' );
// Comment filtering.
remove_filter( 'pre_comment_content', 'wp_filter_post_kses' );
remove_filter( 'pre_comment_content', 'wp_filter_kses' );
// Post filtering.
remove_filter( 'content_save_pre', 'wp_filter_post_kses' );
remove_filter( 'excerpt_save_pre', 'wp_filter_post_kses' );
remove_filter( 'content_filtered_save_pre', 'wp_filter_post_kses' );
}
/**
* Sets up most of the KSES filters for input form content.
*
* First removes all of the KSES filters in case the current user does not need
* to have KSES filter the content. If the user does not have `unfiltered_html`
* capability, then KSES filters are added.
*
* @since 2.0.0
*/
function kses_init() {
kses_remove_filters();
if ( ! current_user_can( 'unfiltered_html' ) ) {
kses_init_filters();
}
}
/**
* Filters an inline style attribute and removes disallowed rules.
*
* @since 2.8.1
*
* @param string $css A string of CSS rules.
* @param string $deprecated Not used.
* @return string Filtered string of CSS rules.
*/
function safecss_filter_attr( $css, $deprecated = '' ) {
if ( ! empty( $deprecated ) ) {
_deprecated_argument( __FUNCTION__, '2.8.1' ); // Never implemented.
}
$css = wp_kses_no_null( $css );
$css = str_replace( array( "\n", "\r", "\t" ), '', $css );
$allowed_protocols = wp_allowed_protocols();
$css_array = explode( ';', trim( $css ) );
/**
* Filters list of allowed CSS attributes.
*
* @since 2.8.1
* @since 4.4.0 Added support for `min-height`, `max-height`, `min-width`, and `max-width`.
* @since 4.6.0 Added support for `list-style-type`.
* @since 5.0.0 Added support for `background-image`.
* @since 5.1.0 Added support for `text-transform`.
* @since 5.2.0 Added support for `background-position` and `grid-template-columns`.
* @since 5.3.0 Added support for `grid`, `flex` and `column` layout properties.
* Extend `background-*` support of individual properties.
* @since 5.3.1 Added support for gradient backgrounds.
* @since 5.7.1 Added support for `object-position`.
*
* @param string[] $attr Array of allowed CSS attributes.
*/
$allowed_attr = apply_filters(
'safe_style_css',
array(
'background',
'background-color',
'background-image',
'background-position',
'background-size',
'background-attachment',
'background-blend-mode',
'border',
'border-radius',
'border-width',
'border-color',
'border-style',
'border-right',
'border-right-color',
'border-right-style',
'border-right-width',
'border-bottom',
'border-bottom-color',
'border-bottom-style',
'border-bottom-width',
'border-left',
'border-left-color',
'border-left-style',
'border-left-width',
'border-top',
'border-top-color',
'border-top-style',
'border-top-width',
'border-spacing',
'border-collapse',
'caption-side',
'columns',
'column-count',
'column-fill',
'column-gap',
'column-rule',
'column-span',
'column-width',
'color',
'font',
'font-family',
'font-size',
'font-style',
'font-variant',
'font-weight',
'letter-spacing',
'line-height',
'text-align',
'text-decoration',
'text-indent',
'text-transform',
'height',
'min-height',
'max-height',
'width',
'min-width',
'max-width',
'margin',
'margin-right',
'margin-bottom',
'margin-left',
'margin-top',
'padding',
'padding-right',
'padding-bottom',
'padding-left',
'padding-top',
'flex',
'flex-basis',
'flex-direction',
'flex-flow',
'flex-grow',
'flex-shrink',
'grid-template-columns',
'grid-auto-columns',
'grid-column-start',
'grid-column-end',
'grid-column-gap',
'grid-template-rows',
'grid-auto-rows',
'grid-row-start',
'grid-row-end',
'grid-row-gap',
'grid-gap',
'justify-content',
'justify-items',
'justify-self',
'align-content',
'align-items',
'align-self',
'clear',
'cursor',
'direction',
'float',
'list-style-type',
'object-position',
'overflow',
'vertical-align',
)
);
/*
* CSS attributes that accept URL data types.
*
* This is in accordance to the CSS spec and unrelated to
* the sub-set of supported attributes above.
*
* See: https://developer.mozilla.org/en-US/docs/Web/CSS/url
*/
$css_url_data_types = array(
'background',
'background-image',
'cursor',
'list-style',
'list-style-image',
);
/*
* CSS attributes that accept gradient data types.
*
*/
$css_gradient_data_types = array(
'background',
'background-image',
);
if ( empty( $allowed_attr ) ) {
return $css;
}
$css = '';
foreach ( $css_array as $css_item ) {
if ( '' === $css_item ) {
continue;
}
$css_item = trim( $css_item );
$css_test_string = $css_item;
$found = false;
$url_attr = false;
$gradient_attr = false;
if ( strpos( $css_item, ':' ) === false ) {
$found = true;
} else {
$parts = explode( ':', $css_item, 2 );
$css_selector = trim( $parts[0] );
if ( in_array( $css_selector, $allowed_attr, true ) ) {
$found = true;
$url_attr = in_array( $css_selector, $css_url_data_types, true );
$gradient_attr = in_array( $css_selector, $css_gradient_data_types, true );
}
}
if ( $found && $url_attr ) {
// Simplified: matches the sequence `url(*)`.
preg_match_all( '/url\([^)]+\)/', $parts[1], $url_matches );
foreach ( $url_matches[0] as $url_match ) {
// Clean up the URL from each of the matches above.
preg_match( '/^url\(\s*([\'\"]?)(.*)(\g1)\s*\)$/', $url_match, $url_pieces );
if ( empty( $url_pieces[2] ) ) {
$found = false;
break;
}
$url = trim( $url_pieces[2] );
if ( empty( $url ) || wp_kses_bad_protocol( $url, $allowed_protocols ) !== $url ) {
$found = false;
break;
} else {
// Remove the whole `url(*)` bit that was matched above from the CSS.
$css_test_string = str_replace( $url_match, '', $css_test_string );
}
}
}
if ( $found && $gradient_attr ) {
$css_value = trim( $parts[1] );
if ( preg_match( '/^(repeating-)?(linear|radial|conic)-gradient\(([^()]|rgb[a]?\([^()]*\))*\)$/', $css_value ) ) {
// Remove the whole `gradient` bit that was matched above from the CSS.
$css_test_string = str_replace( $css_value, '', $css_test_string );
}
}
if ( $found ) {
// Check for any CSS containing \ ( & } = or comments, except for url() usage checked above.
$allow_css = ! preg_match( '%[\\\(&=}]|/\*%', $css_test_string );
/**
* Filters the check for unsafe CSS in `safecss_filter_attr`.
*
* Enables developers to determine whether a section of CSS should be allowed or discarded.
* By default, the value will be false if the part contains \ ( & } = or comments.
* Return true to allow the CSS part to be included in the output.
*
* @since 5.5.0
*
* @param bool $allow_css Whether the CSS in the test string is considered safe.
* @param string $css_test_string The CSS string to test.
*/
$allow_css = apply_filters( 'safecss_filter_attr_allow_css', $allow_css, $css_test_string );
// Only add the CSS part if it passes the regex check.
if ( $allow_css ) {
if ( '' !== $css ) {
$css .= ';';
}
$css .= $css_item;
}
}
}
return $css;
}
/**
* Helper function to add global attributes to a tag in the allowed HTML list.
*
* @since 3.5.0
* @since 5.0.0 Add support for `data-*` wildcard attributes.
* @access private
* @ignore
*
* @param array $value An array of attributes.
* @return array The array of attributes with global attributes added.
*/
function _wp_add_global_attributes( $value ) {
$global_attributes = array(
'aria-describedby' => true,
'aria-details' => true,
'aria-label' => true,
'aria-labelledby' => true,
'aria-hidden' => true,
'class' => true,
'id' => true,
'style' => true,
'title' => true,
'role' => true,
'data-*' => true,
);
if ( true === $value ) {
$value = array();
}
if ( is_array( $value ) ) {
return array_merge( $value, $global_attributes );
}
return $value;
}