term_id;
}
} else {
$term = get_term_by( 'slug', $tag, 'post_tag' );
if ( ! empty( $term ) ) {
$include_tag_id[] = $term->term_id;
}
}
}
if ( ! empty( $include_tag_id ) ) {
$args['tag__in'] = $include_tag_id;
}
if ( ! empty( $exclude_tag_id ) ) {
$args['tag__not_in'] = $exclude_tag_id;
}
}
if ( isset( $instance['number'] ) ) {
$args['posts_per_page'] = intval( $instance['number'] );
}
if ( ! empty( $instance['skip'] ) ) {
$args['offset'] = absint( $instance['skip'] );
if ( 'rand' === $args['orderby'] ) {
// Make offset and order by random working together.
$args2 = $args;
unset( $args2['offset'] );
$args2['posts_per_page'] = $args['offset'];
$args2['orderby'] = 'date';
$args2['order'] = 'desc';
$args2['fields'] = 'ids';
$query2 = new WP_Query( $args2 );
if ( ! empty( $query2->posts ) ) {
// phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_post__not_in -- Exclusion set is bounded by the small "skip" count, not an unbounded list.
$args['post__not_in'] = $query2->posts;
unset( $args['offset'] );
}
}
}
if ( ! empty( $instance['exclude_current'] ) ) {
$args['post__not_in'][] = get_the_ID();
}
if ( empty( $instance['pagination'] ) ) {
$args['no_found_rows'] = true;
} else {
$args['paged'] = flex_posts_get_current_page();
if ( ! empty( $args['offset'] ) ) {
// Modify offset value if pagination is active.
$old_offset = $args['offset'];
$new_offset = $old_offset + ( ( $args['paged'] - 1 ) * $args['posts_per_page'] );
$args['offset'] = $new_offset;
$args['old_offset'] = $old_offset;
}
}
return $args;
}
/**
* Front-end display of widget.
*
* @param array $instance Attributes.
* @param string $type Widget type.
*/
function flex_posts_display( $instance, $type = 'list' ) {
/**
* Filter: flex_posts_{type}_args
*
* Dynamic filter applied to the WP_Query args before querying posts for
* a specific type (widget/block). Replace {type} with the actual type
* used when adding a callback (e.g. 'list').
*
* @param array $args WP_Query args.
* @param array $instance Widget/block instance or attributes.
* @return array Modified WP_Query args.
*
* Example:
* add_filter( 'flex_posts_list_args', function( $args, $instance ) {
* $args['post_status'] = 'private';
* return $args;
* }, 10, 2 );
*/
$args = apply_filters( 'flex_posts_' . $type . '_args', flex_posts_get_query_args( $instance ), $instance );
$query = new WP_Query( $args );
$layout = 1;
if ( ! empty( $instance['layout'] ) ) {
$layout = absint( $instance['layout'] );
}
/**
* Filter: flex_posts_thumbnail_size
*
* Filter the thumbnail image size used for small thumbnails.
*
* @param string $size Default image size slug.
* @param array $context Instance/attributes that requested the size.
* @return string Image size slug.
*
* Example:
* add_filter( 'flex_posts_thumbnail_size', function( $size, $context ) {
* return 'medium';
* }, 10, 2 );
*/
$thumbnail_size = apply_filters( 'flex_posts_thumbnail_size', 'thumbnail', $instance );
/**
* Filter: flex_posts_medium_size
*
* Filter the medium image size used for larger thumbnails.
*
* @param string $size Default image size slug.
* @param array $context Instance/attributes that requested the size.
* @return string Image size slug.
*/
$medium_size = apply_filters( 'flex_posts_medium_size', '400x250-crop', $instance );
if ( ! empty( $instance['image_size'] ) ) {
$thumbnail_size = $instance['image_size'];
}
if ( ! empty( $instance['image_size2'] ) ) {
$medium_size = $instance['image_size2'];
}
if ( ! isset( $instance['show_image'] ) ) {
$instance['show_image'] = 'all';
}
if ( ! isset( $instance['show_title'] ) ) {
$instance['show_title'] = true;
}
$excerpt_length = 15;
if ( ! empty( $instance['excerpt_length'] ) ) {
$excerpt_length = absint( $instance['excerpt_length'] );
}
$readmore_text = __( 'Read more', 'flex-posts' );
if ( ! empty( $instance['readmore_text'] ) ) {
$readmore_text = $instance['readmore_text'];
}
$file = "flex-posts-{$type}-{$layout}.php";
/**
* Filter: flex_posts_template_directory
*
* Allow a theme to provide a directory prefix to locate templates.
*
* @param string $directory Directory prefix (can be empty).
* @return string Directory prefix to prepend to template filename.
*
* Example:
* add_filter( 'flex_posts_template_directory', function() {
* return 'my-plugin-templates/';
* } );
*/
$directory = apply_filters( 'flex_posts_template_directory', '' );
$template = locate_template( $directory . $file );
if ( empty( $template ) ) {
/**
* Filter: flex_posts_template
*
* Filter the full template path that will be included if a theme
* does not provide an override. Callback signature: function( $path, $type, $layout ).
*
* @param string $path Default template path.
* @param string $type The render type (e.g. 'list').
* @param int $layout Layout number.
* @return string Modified template path.
*/
$template = apply_filters( 'flex_posts_template', FLEX_POSTS_DIR . 'public/' . $file, $type, $layout );
}
if ( ! file_exists( $template ) ) {
return;
}
if ( $query->have_posts() ) {
if ( ! empty( $instance['show_excerpt'] ) ) {
add_filter( 'excerpt_more', '__return_null' );
}
include $template;
/**
* Action: flex_posts_end
*
* Fired at the end of the widget/block render. Useful to append
* pagination or other trailing content. Callback signature:
* function( $instance, $args, $max_num_pages, $found_posts ).
*
* @param array $instance Widget/block instance settings.
* @param array $args WP_Query arguments used.
* @param int $max_num_pages Maximum number of pages from the query.
* @param int $found_posts Total posts found by the query.
*
* Example:
* add_action( 'flex_posts_end', function( $instance, $args, $max, $found ) {
* // custom pagination
* }, 10, 4 );
*/
do_action( 'flex_posts_end', $instance, $args, $query->max_num_pages, $query->found_posts );
wp_reset_postdata();
if ( ! empty( $instance['show_excerpt'] ) ) {
remove_filter( 'excerpt_more', '__return_null' );
}
}
}
/**
* Get allowed html tags
*
* Shared escaping whitelist used when rendering the block title and the
* pagination markup.
*
* @return array
*/
function flex_posts_get_allowed_html() {
$attr = array(
'class' => array(),
'title' => array(),
);
/**
* Filter: flex_posts_allowed_html
*
* Allow modification of allowed HTML tags used by the plugin when
* rendering pagination and other safe markup. Expected to return an
* array in the same shape as wp_kses_allowed_html.
*
* @param array $allowed_html Default allowed HTML tags/attributes.
* @return array Modified allowed HTML tags/attributes.
*
* Example:
* add_filter( 'flex_posts_allowed_html', function( $allowed ) {
* $allowed['img'] = array( 'src' => true, 'alt' => true );
* return $allowed;
* } );
*/
$allowed_html = apply_filters(
'flex_posts_allowed_html',
array(
'a' => $attr + array( 'href' => array() ),
'ul' => $attr,
'li' => $attr,
'span' => $attr + array( 'aria-current' => array() ),
)
);
return $allowed_html;
}
/**
* Render block
*
* @param array $attributes Attributes.
* @param string $type Block type.
* @return string
*/
function flex_posts_render( $attributes, $type = 'list' ) {
ob_start();
$class = 'widget widget_flex-posts-' . $type;
if ( ! empty( $attributes['align'] ) ) {
$class .= ' align' . $attributes['align'];
}
$extra_attr = array( 'class' => $class );
/**
* Filter: flex_posts_extra_attr
*
* Filter the block wrapper attributes. Signature:
* function( $extra_attr, $attributes ).
*
* @param array $extra_attr Extra html attributes.
* @param array $attributes Block attributes.
* @return array Modified extra html attributes.
*/
$extra_attr = apply_filters( 'flex_posts_extra_attr', $extra_attr, $attributes );
$attr = get_block_wrapper_attributes( $extra_attr );
echo ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
/**
* Action: flex_posts_block_title_before
*
* Triggered before rendering the block title. Receives block attributes.
*
* @param array $attributes Block attributes.
*/
do_action( 'flex_posts_block_title_before', $attributes );
if ( ! empty( $attributes['title'] ) || ! empty( $attributes['title_cat'] ) ) {
/**
* Filter: flex_posts_block_title
*
* Filter the block title before it is rendered. Signature:
* function( $title, $attributes, $id ).
*
* @param string $title Title text.
* @param array $attributes Block attributes.
* @param string $id Block identifier.
* @return string Modified title.
*/
$title = apply_filters( 'flex_posts_block_title', $attributes['title'], $attributes, 'flex-posts-' . $type );
$el = 'h2';
if ( ! empty( $attributes['block_title_el'] ) ) {
if ( in_array( $attributes['block_title_el'], flex_posts_get_title_elements(), true ) ) {
$el = $attributes['block_title_el'];
}
}
echo '<' . sanitize_key( $el ) . ' class="widget-title">';
echo wp_kses( $title, flex_posts_get_allowed_html() );
echo '' . sanitize_key( $el ) . '>';
}
/**
* Action: flex_posts_block_title_after
*
* Triggered after rendering the block title. Receives block attributes.
*
* @param array $attributes Block attributes.
*/
do_action( 'flex_posts_block_title_after', $attributes );
flex_posts_display( $attributes, $type );
echo '';
$display = ob_get_clean();
return $display;
}
/**
* Add link to widget title
*
* @param string $title Title.
* @param array $instance Instance.
* @param string $id_base ID Base.
* @return string
*/
function flex_posts_widget_title( $title, $instance = array(), $id_base = '' ) {
if ( is_string( $id_base ) && strpos( $id_base, 'flex-posts' ) === 0 ) {
if ( ! empty( $instance['title_cat'] ) ) {
$title = empty( $instance['cat'] ) ? '' : get_cat_name( $instance['cat'] );
}
if ( $title ) {
$title_url = '';
if ( ! empty( $instance['title_url_cat'] ) ) {
$category_id = (int) $instance['cat'];
$title_url = empty( $category_id ) ? '' : get_term_link( $category_id );
} elseif ( ! empty( $instance['title_url'] ) ) {
$title_url = $instance['title_url'];
}
if ( $title_url ) {
$title = '' . $title . '';
}
}
}
return $title;
}
add_filter( 'widget_title', 'flex_posts_widget_title', 10, 3 );
add_filter( 'flex_posts_block_title', 'flex_posts_widget_title', 10, 3 );