true,
'classname' => 'cat-post-widget',
'description' => __( 'List single category posts', 'category-posts' ),
);
parent::__construct( WIDGET_BASE_ID, __( 'Category Posts', 'category-posts' ), $widget_ops );
}
/**
* Calculate the attachment size for showing the feature image
*
* The attachment size should for width and height greater than the widget image width and height settings.
*
* @param int $post_thumbnail_id The id of the featured image attachment.
* @return string The HTML for the thumb related to the post
*
* @since 4.9.22
*/
public function post_thumbnail_attachment_size( $post_thumbnail_id ) {
// $this->instance is only populated once the widget/shortcode/block render path has
// set it (e.g. via widget()); guard against it being unset to avoid PHP warnings.
$thumb_w = isset( $this->instance['thumb_w'] ) ? intval( $this->instance['thumb_w'] ) : 0;
$thumb_h = isset( $this->instance['thumb_h'] ) ? intval( $this->instance['thumb_h'] ) : 0;
// Get attachment image by size
$image_sizes = array('thumbnail', 'medium', 'large', 'full');
foreach ($image_sizes as $img_size) {
$attachment_img_size = wp_get_attachment_image_src($post_thumbnail_id, $img_size);
if ($attachment_img_size[1] >= $thumb_w && $attachment_img_size[2] >= $thumb_h && ! (0 === $thumb_w && 0 === $thumb_h) ||
'full' === $img_size) {
$html = wp_get_attachment_image(
$post_thumbnail_id,
$img_size,
false,
array
(
"data-cat-posts-width" => $thumb_w,
"data-cat-posts-height" => $thumb_h
)
);
break;
}
}
if ( ! $html ) {
return '';
}
return $html;
}
/**
* Calculate the HTML for showing the feature image of a post item.
*
* Used as a filter for the thumb wordpress API to add css based stretching and cropping
* when the image is not at the requested dimensions
*
* @param int $post_id the ID of the post of which the thumb is a featured image.
* @param int $post_thumbnail_id The id of the featured image attachment.
* @param string|array $size The requested size identified by name or (width, height) array.
* @param mixed $attr ignored in this context.
* @return string The HTML for the thumb related to the post
*
* @since 4.1
*/
public function post_thumbnail_html( $post_id, $post_thumbnail_id, $size, $attr ) {
$html = $this->post_thumbnail_attachment_size($post_thumbnail_id);
// normalize style
$html = preg_replace( '/style="([^"]*)"/i', '', $html );
// replace size.
$array = array();
preg_match( '/width="([^"]*)"/i', $html, $array );
$pattern = '/\s' . $array[1] . 'px/';
$html = preg_replace( $pattern, ' ' . $size[0] . 'px', $html );
// replace width.
$pattern = '/width="' . $array[1] . '"/';
if ( ! empty( $this->instance['thumb_w'] ) ) {
$html = preg_replace( $pattern, 'width="' . $size[0] . '"', $html );
} else {
$html = preg_replace( $pattern, '', $html );
}
// replace height
$array = array();
preg_match( '/height="([^"]*)"/i', $html, $array );
$pattern = '/height="' . $array[1] . '"/';
if ( ! empty( $this->instance['thumb_h'] ) ) {
$html = preg_replace( $pattern, 'height="' . $size[1] . '"', $html );
} else {
$html = preg_replace( $pattern, '', $html );
}
$show_post_format = isset( $this->instance['show_post_format'] ) && ( 'none' !== $this->instance['show_post_format'] );
$post_format_class = '';
if ( $show_post_format || ! empty( $this->instance['thumb_hover'] ) ) {
$format = get_post_format() ? : 'standard';
$post_format_class = 'cat-post-format cat-post-format-' . $format;
}
$html = '' . $html . '';
return $html;
}
/*
* wrapper to execute the the_post_thumbnail with filters.
*/
/**
* Calculate the HTML for showing the thumb of a post item.
*
* It is a wrapper to execute the the_post_thumbnail with filters
*
* @param string|array $size The requested size identified by name or (width, height) array.
*
* @return string The HTML for the thumb related to the post and empty string if it can not be calculated
*
* @since 4.1
*/
public function the_post_thumbnail( $size = 'post-thumbnail' ) {
if ( empty( $size ) ) { // if junk value, make it a normal thumb.
// post_thumbnail_html() below always expects a [width, height] pixel
// pair to rewrite the width/height/sizes attributes with, not a named
// size string - passing through the string 'post-thumbnail' made it
// index into the string itself (e.g. $size[0] === 'p'), corrupting the
// generated markup (a "ppx" sizes attribute, wrong width/height).
$size = array( get_option( 'thumbnail_size_w', 150 ), get_option( 'thumbnail_size_h', 150 ) );
} elseif ( is_array( $size ) && ( 2 === count( $size ) ) ) { // good format at least.
// normalize to ints first.
list( $width, $height ) = array_map('intval', $size);
if ( ( 0 === $width ) && ( 0 === $height ) ) { // Both values zero then revert to ratio from the orig with large.
$size = array( get_option( 'large_size_w', 150 ), get_option( 'large_size_h', 150 ) );
} elseif ( ( 0 === $width ) && ( 0 !== $height ) ) {
// if thumb width 0 set to max/full widths for wp rendering.
$post_thumb = get_the_post_thumbnail( get_the_ID(), 'full' );
preg_match( '/(?<=width=")[\d]*/', $post_thumb, $thumb_full_w );
$size[0] = $thumb_full_w[0];
} elseif ( ( 0 !== $width ) && ( 0 === $height ) ) {
// if thumb height 0 get full thumb for ratio and calc height with ratio.
$post_thumb = get_the_post_thumbnail( get_the_ID(), 'full' );
preg_match( '/(?<=width=")[\d]*/', $post_thumb, $thumb_full_w );
preg_match( '/(?<=height=")[\d]*/', $post_thumb, $thumb_full_h );
$ratio = $thumb_full_w[0] / $thumb_full_h[0];
$size[1] = intval( $width / $ratio );
}
} else {
$size = array( get_option( 'thumbnail_size_w', 150 ), get_option( 'thumbnail_size_h', 150 ) ); // yet another form of junk.
}
$post_thumbnail_id = get_post_thumbnail_id( get_the_ID() );
if ( ! $post_thumbnail_id && ! empty( $this->instance['default_thunmbnail'] ) ) {
$post_thumbnail_id = $this->instance['default_thunmbnail'];
}
do_action( 'begin_fetch_post_thumbnail_html', get_the_ID(), $post_thumbnail_id, $size );
$ret = $this->post_thumbnail_html( get_the_ID(), $post_thumbnail_id, $size, '' );
do_action( 'end_fetch_post_thumbnail_html', get_the_ID(), $post_thumbnail_id, $size );
return $ret;
}
/**
* Excerpt more link filter
*
* @param string $more The "more" text passed by the filter.
*
* @return string The link to the post with the "more" text configured in the widget.
*/
public function excerpt_more_filter( $more ) {
return ' ' . esc_html( $this->instance['excerpt_more_text'] ) . '';
}
/**
* Apply the_content filter for excerpt
* This should show sharing buttons which comes with other widgets in the widget output in the same way as on the main content
*
* @param string $text The HTML with other applied excerpt filters.
*
* @return string If option hide_social_buttons is unchecked applay the_content filter.
*
* @since 4.6
*/
public function apply_the_excerpt( $text ) {
$ret = apply_filters( 'the_content', $text );
return $ret;
}
/**
* Calculate the wp-query arguments matching the filter settings of the widget
*
* @param array $instance Array which contains the various settings.
* @return array The array that can be fed to wp_Query to get the relevant posts
*
* @since 4.6
*/
public function queryArgs( $instance ) {
$valid_sort_orders = array(
'date', 'title', 'comment_count', 'rand'
);
if ( isset( $instance['sort_by'] ) && in_array( $instance['sort_by'], $valid_sort_orders, true ) ) {
$sort_by = $instance['sort_by'];
} else {
$sort_by = 'date';
}
$sort_order = ( isset( $instance['asc_sort_order'] ) && $instance['asc_sort_order'] ) ? 'ASC' : 'DESC';
// start sticky posts
$ignore_sticky = isset( $instance['sticky'] ) && $instance['sticky'] ? false : true;
// Get array of post info.
$args = array(
'orderby' => $sort_by,
'order' => $sort_order,
'ignore_sticky_posts' => $ignore_sticky, // Make sure we do not get stickies out of order.
'no_found_rows' => true, // Do not count the total numbers of rows by default.
);
$non_default_valid_status = array(
'publish',
'future',
'publish,future',
'private',
'private,publish',
'private,publish,future',
);
if ( isset( $instance['status'] ) && in_array( $instance['status'], $non_default_valid_status, true ) ) {
$args['post_status'] = $instance['status'];
}
if ( isset( $instance['num'] ) ) {
$args['showposts'] = (int) $instance['num'];
}
if ( isset( $instance['offset'] ) && ( (int) $instance['offset'] > 1 ) ) {
$args['offset'] = (int) $instance['offset'] - 1;
}
if ( isset( $instance['cat'] ) ) {
if ( isset( $instance['no_cat_childs'] ) && $instance['no_cat_childs'] ) {
$args['category__in'] = (int) $instance['cat'];
} else {
$args['cat'] = (int) $instance['cat'];
}
}
if ( is_singular() && isset( $instance['exclude_current_post'] ) && $instance['exclude_current_post'] ) {
$args['post__not_in'] = array( get_the_ID() );
}
if ( isset( $instance['hideNoThumb'] ) && $instance['hideNoThumb'] ) {
$args = array_merge(
$args,
array(
'meta_query' => array(
array(
'key' => '_thumbnail_id',
'compare' => 'EXISTS',
),
),
)
);
}
switch ( isset( $instance['date_range'] ) ? $instance['date_range'] : 'off' ) {
case 'days_ago':
$ago = (int) $instance['days_ago'];
// If there is no valid integer value given, bail.
if ( 0 === $ago ) {
break;
}
$date = date( 'Y-m-d', strtotime( '-' . $ago . ' days' ) );
$args['date_query'] = array(
'after' => $date,
'inclusive' => true,
);
break;
case 'between_dates':
// Validation note - not doing any, assuming the query will
// fail gracefully enough for now as it is not clear what Should
// the validation be right now.
$start_date = $instance['start_date'];
$end_date = $instance['end_date'];
$args['date_query'] = array(
'after' => $start_date,
'before' => $end_date,
'inclusive' => true,
);
break;
}
return $args;
}
/**
* Calculate the HTML of the title based on the widget settings
*
* @param string $before_title The sidebar configured HTML that should come
* before the title itself.
* @param string $after_title The sidebar configured HTML that should come
* after the title itself.
* @param array $instance Array which contains the various settings.
* @return string The HTML for the title area
*
* @since 4.6
*/
public function titleHTML( $before_title, $after_title, $instance ) {
$ret = '';
if ( isset( $instance['title_level'] ) && in_array( $instance['title_level'], array( 'H1','H2', 'H3', 'H4', 'H5', 'H6') ) ) {
$before_title = '';
$after_title = '';
}
// If no title, use the name of the category.
if ( ! isset( $instance['title'] ) || ! $instance['title'] ) {
$instance['title'] = '';
if ( isset( $instance['cat'] ) && 0 !== (int) $instance['cat'] ) {
$category_info = get_category( $instance['cat'] );
if ( $category_info && ! is_wp_error( $category_info ) ) {
$instance['title'] = $category_info->name;
} else {
$instance['cat'] = 0; // For further processing treat it like "all categories".
$instance['title'] = __( 'Category Posts', 'category-posts' );
}
} else {
$instance['title'] = __( 'Category Posts', 'category-posts' );
}
}
if ( ! ( isset( $instance['hide_title'] ) && $instance['hide_title'] ) ) {
$ret = $before_title;
if ( isset( $instance['is_shortcode'] ) ) {
$title = esc_html( $instance['title'] );
} else {
$title = apply_filters( 'widget_title', $instance['title'], $instance, WIDGET_BASE_ID );
}
if ( isset( $instance['title_link'] ) && $instance['title_link'] ) {
$new_tab_attr = '';
if ( ! empty( $instance['title_link_target'] ) ) {
$new_tab_attr = ' target="_blank" rel="noopener noreferrer"';
}
if ( isset( $instance['cat'] ) && 0 !== (int) $instance['cat'] ) {
$ret .= '' . $title . '';
} elseif ( isset( $instance['title_link_url'] ) && $instance['title_link_url'] ) {
$ret .= '' . $title . '';
} else {
$ret .= '' . $title . '';
}
} else {
$ret .= $title;
}
$ret .= $after_title;
}
if ( ! ( isset( $instance['hide_title'] ) && $instance['hide_title'] ) ) {
$ret = $this->add_heading_level( $instance, $ret, 'title_level' );
}
return $ret;
}
/**
* Add a heading level
*
* @return string The title string
*
* @since 5.0
*/
public function add_heading_level( $instance, $ret, $key ) {
$class = ( isset( $instance[ 'disable_theme_styles' ] ) && $instance[ 'disable_theme_styles' ] ) ? '' : ' class="widget-title"';
switch ( isset( $instance[ $key ] ) ? $instance[ $key ] : '' ) {
case 'H1':
$ret = '
' . $ret . '
';
break;
case 'H2':
$ret = '
' . $ret . '
';
break;
case 'H3':
$ret = '
' . $ret . '
';
break;
case 'H4':
$ret = '
' . $ret . '
';
break;
case 'H5':
$ret = '
' . $ret . '
';
break;
case 'H6':
$ret = '
' . $ret . '
';
break;
}
return $ret;
}
/**
* Get the URL of the blog page or home page if no explicit blog page is defined.
*
* @return string The URL of the blog page
*
* @since 4.8
*/
private function blog_page_url() {
$blog_page = get_option( 'page_for_posts' );
if ( $blog_page ) {
$url = get_permalink( $blog_page );
} else {
$url = home_url();
}
return $url;
}
/**
* Calculate the HTML of the load more button based on the widget settings
*
* @param array $instance Array which contains the various settings.
*
* @return string The HTML for the load more area
*
* @since 4.9
*/
public function loadMoreHTML( $instance ) {
global $post_count;
if ( ! $instance['enable_loadmore'] || $post_count <= $instance['num'] ) {
return '';
}
$ret = '
';
$context = 0;
if ( is_singular() ) {
$context = get_the_ID();
}
wp_enqueue_script( 'jquery' );
add_action( 'wp_footer', __NAMESPACE__ . '\embed_loadmore_scripts', 100 );
// We rely on the widget number to be properly set.
// but need a slight different handling for proper widgets.
if ( is_int( $this->number ) ) {
// it is a proper widget, add the prefix.
$id = 'widget-' . $this->number;
} else {
$id = str_replace( WIDGET_BASE_ID . '-', '', $this->number );
}
if ( 0 === strpos( $this->number, 'block-' ) ) {
$id = 'block-' . substr( $this->number, 6 );
}
// Placeholder
$placeholder_text = $instance['loadmore_text'] !== '' ? $instance['loadmore_text'] : sprintf( esc_attr__( 'Load More (%s/%s)', 'category-posts' ), '%step%', '%all%');
$pattern = '/%step%/';
$loadmore_text = preg_replace( $pattern, $instance['num'], $placeholder_text );
$pattern = '/%all%/';
$loadmore_text = preg_replace( $pattern, $post_count, $loadmore_text );
// Load more
$number = $instance['num'];
$start = $instance['offset'] + $number;
$loading = $instance['loading_text'] !== '' ? $instance['loading_text'] : esc_attr__( 'Loading...', 'category-posts' );
$scrollTo = isset( $instance['loadmore_scrollTo'] ) && $instance['loadmore_scrollTo'];
$ret .= '';
$ret .= '
';
return $ret;
}
/**
* Calculate the HTML of the footer based on the widget settings
*
* @param array $instance Array which contains the various settings.
* @return string The HTML for the footer area
*
* @since 4.6
*/
public function footerHTML( $instance ) {
$ret = '';
$url = '';
$text = '';
if ( isset( $instance['footer_link'] ) ) {
$url = $instance['footer_link'];
}
if ( isset( $instance['footer_link_text'] ) ) {
$text = $instance['footer_link_text'];
}
// if url is set, but no text, just use the url as text.
if ( empty( $text ) && ! empty( $url ) ) {
$text = $url;
}
// if no url is set but just text, assume the url should be to the relevant archive page
// category archive for categories filter and home page or blog page when "all categories"
// is used.
if ( ! empty( $text ) && empty( $url ) ) {
if ( isset( $instance['cat'] ) && ( 0 !== (int) $instance['cat'] ) && ( null !== get_category( $instance['cat'] ) ) ) {
$url = get_category_link( $instance['cat'] );
} else {
$url = $this->blog_page_url();
}
}
if ( ! empty( $url ) ) {
$new_tab_attr = '';
if ( ! empty( $instance['footer_link_target'] ) ) {
$new_tab_attr = ' target="_blank" rel="noopener noreferrer"';
}
$ret .= '' . esc_html( $text ) . '';
}
return $ret;
}
/**
* Current post item date string based on the format requested in the settings
*
* @param array $instance Array which contains the various settings.
* @param bool $everything_is_link Indicates whether the return string should avoid links.
*
* @since 4.8
*/
public function itemDate( $instance, $everything_is_link ) {
global $post;
$ret = '';
if ( ! isset( $instance['preset_date_format'] ) ) {
$preset_date_format = 'other';
} else {
$preset_date_format = $instance['preset_date_format'];
}
$attr = '';
switch ( $preset_date_format ) {
case 'sitedateandtime':
$date = get_the_time( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) );
break;
case 'localsitedateandtime':
$date = get_the_time( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ) ) . ' GMT';
$time = get_post_time( 'U', true );
$attr = ' data-publishtime="' . $time . '" data-format="time"';
add_action( 'wp_footer', __NAMESPACE__ . '\embed_date_scripts' );
break;
case 'sitedate':
$date = get_the_time( get_option( 'date_format' ) );
break;
case 'localsitedate':
$date = get_the_time( get_option( 'date_format' ) ) . ' GMT';
$time = get_post_time( 'U', true );
$attr = ' data-publishtime="' . $time . '" data-format="date"';
add_action( 'wp_footer', __NAMESPACE__ . '\embed_date_scripts' );
break;
default:
if ( isset( $instance['date_format'] ) && strlen( trim( $instance['date_format'] ) ) > 0 ) {
$date_format = $instance['date_format'];
} else {
$date_format = 'j M Y';
}
$date = get_the_time( $date_format );
break;
}
if ( isset( $instance['date_past_time'] ) && 0 < $instance['date_past_time'] && $post ) {
$post_date = get_the_time( "Y-m-d H:i:s", $post->ID );
$current_date = current_time( "Y-m-d H:i:s" );
$past_days = date_diff(
date_create( $post_date ),
date_create( $current_date )
)->days;
if ( $past_days <= $instance['date_past_time'] ) {
$date = human_time_diff( get_the_time( 'U' ), current_time( 'timestamp' ) );
$attr = ' data-publishtime="" data-format="sincepublished"';
}
}
$post_date_class = ( isset( $instance[ 'disable_theme_styles' ] ) && $instance[ 'disable_theme_styles' ] ) ? "" : " post-date";
$ret .= '' . $date . '';
return $ret;
}
/**
* Calculate the HTML for showing the thumb of a post item.
* Expected to be called from a loop with globals properly set.
*
* @param array $instance Array which contains the various settings.
* @param bool $no_link indicates whether the thumb should be wrapped in a link or a span.
* @return string The HTML for the thumb related to the post
*
* @since 4.6
*/
public function itemThumb( $instance, $no_link ) {
$ret = '';
if ( ( isset( $instance['default_thunmbnail'] ) && ( $instance['default_thunmbnail'] ) ) || has_post_thumbnail() ) {
$class = '';
$disable_css = isset( $instance['disable_css'] ) && $instance['disable_css'];
if ( isset( $this->instance['thumb_hover'] ) && ! $disable_css ) {
$class = 'class="cat-post-thumbnail cat-post-' . $instance['thumb_hover'] . '"';
} else {
$class = 'class="cat-post-thumbnail"';
}
$title_args = array( 'echo' => false );
if ( $no_link ) {
$ret .= '';
} else {
$ret .= '';
}
$ret .= $this->the_post_thumbnail( array( intval($this->instance['thumb_w']), intval($this->instance['thumb_h']) ) );
if ( $no_link ) {
$ret .= '';
} else {
$ret .= '';
}
}
return $ret;
}
/**
* Current post item categories string
*
* @param array $instance Array which contains the various settings.
* @param bool $everything_is_link Indicates whether the return string should avoid links.
*
* @since 4.8
*/
public function itemCategories( $instance, $everything_is_link ) {
$post_category_class = ( isset( $instance[ 'disable_theme_styles' ] ) && $instance[ 'disable_theme_styles' ] ) ? "" : " entry-categories post-categories";
$ret = '';
$cat_ids = wp_get_post_categories( get_the_ID(), array( 'number' => 0 ) );
foreach ( $cat_ids as $cat_id ) {
if ( $everything_is_link ) {
$ret .= ' ' . get_cat_name( $cat_id );
} else {
$ret .= " " . get_cat_name( $cat_id ) . '';
}
}
$ret .= '';
return $ret;
}
/**
* Current post item tags string
*
* @param array $instance Array which contains the various settings.
* @param bool $everything_is_link Indicates whether the return string should avoid links.
*
* @since 4.8
*/
public function itemTags( $instance, $everything_is_link ) {
$post_tag_class = ( isset( $instance[ 'disable_theme_styles' ] ) && $instance[ 'disable_theme_styles' ] ) ? "" : " widget_tag_cloud tagcloud post-tags";
$ret = '';
$tag_ids = wp_get_post_tags( get_the_ID(), array( 'number' => 0 ) );
foreach ( $tag_ids as $tag_id ) {
if ( $everything_is_link ) {
$ret .= ' ' . $tag_id->name;
} else {
$ret .= " " . $tag_id->name . '';
}
}
$ret .= '';
return $ret;
}
/**
* Current post item comment number string
*
* @param array $instance Array which contains the various settings.
* @param bool $everything_is_link Indicates whether the return string should avoid links.
*
* @since 4.8
*/
public function itemCommentNum( $instance, $everything_is_link ) {
global $post;
$ret = '';
if ( $everything_is_link ) {
$ret .= '(' . \get_comments_number() . ')';
} else {
$link = sprintf(
'(%3$d)',
esc_url( get_comments_link( $post->ID ) ),
esc_attr( sprintf( __( '(%d) comments to this post' ), get_comments_number() ) ),
get_comments_number()
);
$ret .= $link;
}
$ret .= '';
return $ret;
}
/**
* Current post item author string
*
* @param array $instance Array which contains the various settings.
* @param bool $everything_is_link Indicates whether the return string should avoid links.
*
* @since 4.8
*/
public function itemAuthor( $instance, $everything_is_link ) {
$post_author_class = ( isset( $instance[ 'disable_theme_styles' ] ) && $instance[ 'disable_theme_styles' ] ) ? "" : " post-author";
$ret = '';
if ( $everything_is_link ) {
$ret .= get_the_author();
} else {
$link = get_the_author_posts_link();
$ret .= $link;
}
$ret .= '';
return $ret;
}
/**
* Current post item excerpt string
*
* @param array $instance Array which contains the various settings.
* @param bool $everything_is_link Indicates whether the return string should avoid links.
*
* @since 4.8
*/
public function itemExcerpt( $instance, $everything_is_link ) {
global $post;
$context = isset( $instance['context'] ) ? $instance['context'] : CONTEXT_WIDGET;
$excerpt = '';
if ( CONTEXT_BLOCK === $context ) {
if ( isset( $instance['excerpt_radio'] ) && 'excerpt' === $instance['excerpt_radio'] ) {
$cpw_excerpt_length = function() { return 999; };
add_filter( 'excerpt_length', $cpw_excerpt_length, 20 );
$excerpt = wpautop(get_the_excerpt());
remove_filter( 'excerpt_length', $cpw_excerpt_length, 20 );
}
if ( isset( $instance['excerpt_radio'] ) && 'full_post' === $instance['excerpt_radio'] ) {
$excerpt = get_the_content();
}
}
if ( CONTEXT_WIDGET === $context || CONTEXT_SHORTCODE === $context ) {
// use the_excerpt filter to get the "normal" excerpt of the post
// then apply our filter to let users customize excerpts in their own way.
if ( isset( $instance['excerpt_length'] ) && ( $instance['excerpt_length'] > 0 ) ) {
$length = (int) $instance['excerpt_length'];
} else {
$length = 999; // Use the wordpress default.
}
if ( ! isset( $instance['excerpt_filters'] ) || $instance['excerpt_filters'] ) { // pre 4.7 widgets has filters on.
$excerpt = apply_filters( 'the_excerpt', \get_the_excerpt() );
} else { // if filters off replicate functionality of core generating excerpt.
$more_text = '[…]';
if ( isset( $instance['excerpt_more_text'] ) && $instance['excerpt_more_text'] ) {
$more_text = ltrim( $instance['excerpt_more_text'] );
}
if ( $everything_is_link ) {
$excerpt_more_text = ' ' . $more_text . '';
} else {
$excerpt_more_text = ' ' . $more_text . '';
}
if ( '' === $post->post_excerpt ) {
$text = get_the_content( '' );
$text = strip_shortcodes( $text );
$excerpt = \wp_trim_words( $text, $length, $excerpt_more_text );
// adjust html output same way as for the normal excerpt,
// just force all functions depending on the_excerpt hook.
$excerpt = shortcode_unautop( wpautop( convert_chars( convert_smilies( wptexturize( $excerpt ) ) ) ) );
} else {
$text = $post->post_excerpt;
$excerpt = \wp_trim_words( $text, $length, $excerpt_more_text );
$excerpt = shortcode_unautop( wpautop( convert_chars( convert_smilies( wptexturize( $excerpt ) ) ) ) );
}
}
}
// 'cpwp-wrap-text' is what the excerpt-lines CSS hooks on, but where it belongs
// depends on the "do not wrap thumbnail with text" setting, which is only known
// in itemHTML() where the stage wrapper is built. So only mark the paragraph
// here and let itemHTML() place 'cpwp-wrap-text'.
$excerpt = str_replace( '
', '
', $excerpt );
$ret = apply_filters( 'cpw_excerpt', $excerpt, $this );
return $ret;
}
/**
* Current post item More Link string
*
* @param array $instance Array which contains the various settings.
* @param bool $everything_is_link Indicates whether the return string should avoid links.
*
* @since 5.0
*/
public function itemMoreLink( $instance, $everything_is_link ) {
global $post;
$more_text = '[…]';
if ( isset( $instance['excerpt_more_text'] ) && '' !== $instance['excerpt_more_text'] ) {
$more_text = sanitize_text_field( $instance['excerpt_more_text'] );
}
$more_link_class = ( isset( $instance[ 'disable_theme_styles' ] ) && $instance[ 'disable_theme_styles' ] ) ? "" : " more-link";
if ( $everything_is_link ) {
$ret = ' ' . $more_text . '';
} else {
$ret = ' ' . $more_text . '';
}
if ( isset( $instance['excerpt_more_text'] ) && '' === $instance['excerpt_more_text'] ) {
$ret = '' !== apply_filters( 'excerpt_more', '' ) ? apply_filters( 'excerpt_more', '' ) : $ret;
}
return $ret;
}
/**
* Current post item title string
*
* @param array $instance Array which contains the various settings.
* @param bool $everything_is_link Indicates whether the return string should avoid links.
*
* @since 4.8
*/
public function itemTitle( $instance, $everything_is_link ) {
$ret = '';
if ( $everything_is_link ) {
$ret .= '' . get_the_title() . '';
} else {
$ret .= '' . get_the_title();
$ret .= '';
}
$ret = $this->add_heading_level( $instance, $ret, 'item_title_level' );
return $ret;
}
/**
* Calculate the HTML for a post item based on the widget settings and post.
* Expected to be called in an active loop with all the globals set.
*
* @param array $instance Array which contains the various settings.
* @param null|integer $current_post_id If on singular page specifies the id of
* the post, otherwise null.
* @return string The HTML for item related to the post
*
* @since 4.6
*/
public function itemHTML( $instance, $current_post_id ) {
global $post;
$everything_is_link = isset( $instance['everything_is_link'] ) && $instance['everything_is_link'];
$no_wrap = isset( $instance['text_do_not_wrap_thumb'] ) && $instance['text_do_not_wrap_thumb'];
$template = '';
if ( isset( $instance['template'] ) ) {
$template = $instance['template'];
} else {
$template = convert_settings_to_template( $instance );
}
$ret = '
';
return wp_kses_post($ret);
}
/**
* Filter to set the number of words in an excerpt
*
* @param int $length The number of words as configured by wordpress core or set by previous filters.
* @return int The number of words configured for the widget,
* or the $length parameter if it is not configured or garbage value.
*
* @since 4.6
*/
public function excerpt_length_filter( $length ) {
if ( isset( $this->instance['excerpt_length'] ) && $this->instance['excerpt_length'] > 0 ) {
$length = $this->instance['excerpt_length'];
}
return $length;
}
/**
* Set the proper excerpt filters based on the settings
*
* @param array $instance widget settings.
* @return void
*
* @since 4.6
*/
public function setExcerpFilters( $instance ) {
if ( isset( $instance['excerpt'] ) && $instance['excerpt'] ) {
// Excerpt length filter.
if ( isset( $instance['excerpt_length'] ) && ( (int) $instance['excerpt_length'] ) > 0 ) {
add_filter( 'excerpt_length', array( $this, 'excerpt_length_filter' ) );
}
if ( isset( $instance['excerpt_more_text'] ) && ( '' !== ltrim( $instance['excerpt_more_text'] ) ) ) {
add_filter( 'excerpt_more', array( $this, 'excerpt_more_filter' ) );
}
add_filter( 'the_excerpt', array( $this, 'apply_the_excerpt' ) );
}
}
/**
* Remove the excerpt filter
*
* @param array $instance widget settings.
* @return void
*
* @since 4.6
*/
public function removeExcerpFilters( $instance ) {
remove_filter( 'excerpt_length', array( $this, 'excerpt_length_filter' ) );
remove_filter( 'excerpt_more', array( $this, 'excerpt_more_filter' ) );
add_filter( 'get_the_excerpt', 'wp_trim_excerpt' );
remove_filter( 'the_excerpt', array( $this, 'apply_the_excerpt' ) );
}
/**
* The main widget display controller
*
* Called by the sidebar processing core logic to display the widget.
*
* @param array $args An array containing the "environment" setting for the widget,
* namely, the enclosing tags for the widget and its title.
* @param array $instance The settings associate with the widget.
*
* @since 4.1
*/
public function widget( $args, $instance ) {
global $before_title, $after_title;
$instance = upgrade_settings( $instance );
// A shortcode is rendered through this method as well, in that case the context
// was already set by Virtual_Widget::getHTML(), so do not overwrite it.
if ( ! isset( $instance['context'] ) ) {
$instance['context'] = CONTEXT_WIDGET;
}
extract( $args );
$this->instance = $instance;
$current_post_id = '';
if ( is_singular() ) {
$current_post_id = get_the_ID();
}
$items = $this->get_elements_HTML( $instance, $current_post_id, 0, 0 );
if ( ( 'nothing' === $instance['no_match_handling'] ) || ! empty( $items ) ) {
echo $before_widget; // Xss ok. This is how widget actually expected to behave.
echo $this->titleHTML( $before_title, $after_title, $instance );
$thumb = isset( $this->instance['template'] ) && preg_match( '/%thumb%/', $this->instance['template'] );
if ( ! ( isset( $instance['is_shortcode'] ) && $instance['is_shortcode'] ) ) { // the internal id is needed only for widgets.
echo '
\n";
} else {
echo '
';
}
// image crop browser fallback and workaround, no polyfill.
if ( $thumb ) {
if ( apply_filters( 'cpw_enqueue_resources', false ) ) {
frontend_script();
} else {
wp_enqueue_script( 'jquery' );
add_action( 'wp_footer', __NAMESPACE__ . '\embed_front_end_scripts', 100 );
}
}
// set widget filters.
if ( ! isset( $instance['excerpt_filters'] ) || $instance['excerpt_filters'] ) { // pre 4.7 widgets has filters on.
$this->setExcerpFilters( $instance );
}
foreach ( $items as $item ) {
echo $item;
}
echo "
\n";
// Load more only if we think we have more items.
if ( count( $items ) === (int) $instance['num'] ) {
echo $this->loadMoreHTML( $instance );
}
echo $this->footerHTML( $instance );
echo $after_widget; // Xss ok. This is how widget actually expected to behave.
// remove widget filters.
if ( ! isset( $instance['excerpt_filters'] ) || $instance['excerpt_filters'] ) { // pre 4.7 widgets has filters on.
$this->removeExcerpFilters( $instance );
}
wp_reset_postdata();
$number = $this->number;
// a temporary hack to handle difference in the number in a true widget
// and the number format expected at the rest of the places.
if ( is_numeric( $number ) ) {
$number = WIDGET_BASE_ID . '-' . $number;
}
if ( ! ( isset( $instance['is_shortcode'] ) && $instance['is_shortcode'] ) ) { // the internal id is needed only for widgets.
$number .= '-internal';
}
// enque relevant scripts and parameters to ensure correct image dimentions.
if ( isset( $instance['template'] ) && preg_match( '/%thumb%|%excerpt%/', $instance['template'] ) ) {
wp_enqueue_script( 'jquery' ); // just in case the theme or other plugins didn't enqueue it.
add_action(
'wp_footer',
function () use ( $number, $instance ) {
__NAMESPACE__ . '\\' . equal_cover_content_height( $number, $instance );
},
100
);
}
} elseif ( 'text' === $instance['no_match_handling'] ) {
echo $before_widget; // Xss ok. This is how widget actually expected to behave.
echo $this->titleHTML( $before_title, $after_title, $instance );
echo wp_kses_post( $instance['no_match_text'] );
echo $this->footerHTML( $instance );
echo $after_widget; // Xss ok. This is how widget actually expected to behave.
}
}
/**
* Get an array of HTML pre item, for item starting from a specific position.
*
* @since 4.9
*
* @param array $instance An array containing the settings of the widget.
* @param string $singular_id The ID of the post in which the widget is rendered,
* an empty string indicates the rendering context
* is not singular.
* @param int $start The start element (0 based).
* @param int $number The maximal number of elements to return. A value of 0
* Indicates to use the widget settings for that.
*
* @return string[] Array of HTML per element with the $start element first
* $start+1 next etc. An empty array is returned if there
* are no applicable items.
*/
public function get_elements_HTML( $instance, $singular_id, $start, $number ) {
global $post_count;
$ret = array();
if ( 0 === count( $instance ) ) {
$instance = default_settings();
}
$this->instance = $instance;
if ( $start > 0 ) {
$instance['offset'] = $start;
}
$number = (int) $number; // sanitize number with the side effect of non
// numbers are converted to zero.
if ( 0 < $number ) {
$instance['num'] = $number;
}
$args = $this->queryArgs( $instance );
$cat_posts = new \WP_Query( $args );
if( isset( $instance['enable_loadmore'] ) && $instance['enable_loadmore'] ) {
$args['showposts'] = 0;
$args['nopaging'] = true;
$post_count = ( new \WP_Query( $args ) )->post_count; // May there is a better workflow to count the loadable items
}
$current_post_id = null;
if ( '' !== $singular_id ) {
$current_post_id = (int) $singular_id;
}
$postCount = 0;
while ( $cat_posts->have_posts() ) {
$postCount++;
// If sticky posts are added, break anyway after the set number of posts.
if ( isset( $instance['sticky'] ) && $instance['sticky'] && $postCount > $instance['num'] ) {
break;
}
$cat_posts->the_post();
$ret[] = $this->itemHTML( $instance, $current_post_id );
}
wp_reset_postdata();
return $ret;
}
/**
* Update the options.
*
* @param array $new_instance The new settings of the widget.
* @param array $old_instance The current settings of the widget.
* @return array
*/
public function update( $new_instance, $old_instance ) {
$new_instance['title'] = sanitize_text_field( $new_instance['title'] ); // sanitize the title like core widgets do.
if ( ! isset( $new_instance['excerpt_filters'] ) ) {
$new_instance['excerpt_filters'] = '';
}
if ( current_user_can( 'unfiltered_html' ) ) {
$instance['text'] = $new_instance['template'];
} else {
$instance['text'] = wp_kses_post( $new_instance['template'] );
}
// Set the version of the DB structure.
$new_instance['ver'] = VERSION;
return $new_instance;
}
/**
* Output the title panel of the widget configuration form.
*
* @param array $instance The widget's settings.
* @return void
*
* @since 4.6
*/
public function formTitlePanel( $instance ) {
$cat = isset( $instance['cat'] ) ? (int) $instance['cat'] : 0;
$hide_title = false;
if ( isset( $instance['hide_title'] ) && $instance['hide_title'] ) {
$hide_title = true;
}
?>
\n" . $html . "\n";
return $ret;
}
/**
* Generate a form P element containing a select element
*
* @since 4.8
* @param array $instance The instance.
* @param string $key The key in the instance array.
* @param string $label The label to display and associate with the input.
* @param array $list An array of pairs value (index) => label to be used for the options.
* The labels are expected to be html escaped.
* @param int $default The value to use if the key is not set in the instance.
* @param bool $visible Indicates if the element should be visible when rendered.
*
* @return string HTML a P element contaning the select, its label, class based on the key
* and style set to display:none if visibility is off.
*/
private function get_select_block_html( $instance, $key, $label, $list, $default, $visible ) {
$value = $instance[ $key ];
if ( ! array_key_exists( $value, $list ) ) {
$value = $default;
}
if ( '' !== $label ) {
$label .= ':';
}
$ret = '\n" .
'\n";
return $this->get_wrap_block_html( $ret, $key, $visible );
}
/**
* Generate a form P element containing a textarea input
*
* @since 4.8
* @param array $instance The instance.
* @param string $key The key in the instance array.
* @param string $label The label to display and associate with the input (should be html escaped).
* @param string $placeholder The placeholder to use in the input (should be attribute escaped).
* @param bool $visible Indicates if the element should be visible when rendered.
* @param int $num_rows Number of rows.
*
* @return string HTML a P element containing the input, its label, class based on the key
* and style set to display:none if visibility is off.
*/
private function get_textarea_html( $instance, $key, $label, $placeholder, $visible, $num_rows ) {
$value = $instance[ $key ];
$ret = '';
return $this->get_wrap_block_html( $ret, $key, $visible );
}
/**
* Generate a form P element containing a text input
*
* @since 4.8
* @param array $instance The instance.
* @param string $key The key in the instance array.
* @param string $label The label to display and associate with the input.
* Should be html escaped.
* @param string $placeholder The placeholder to use in the input. should be attribute escaped.
* @param bool $visible Indicates if the element should be visible when rendered.
*
* @return string HTML a P element contaning the input, its label, class based on the key
* and style set to display:none if visibility is off.
*/
private function get_text_input_block_html( $instance, $key, $label, $placeholder, $visible ) {
$value = isset( $instance[ $key ] ) ? $instance[ $key ] : '';
$ret = '\n";
return $this->get_wrap_block_html( $ret, $key, $visible );
}
/**
* Generate a form P element containing a range input
*
* @since 4.8
* @param array $instance The instance.
* @param string $key The key in the instance array.
* @param string $label The label to display and associate with the input.
* expected to be escaped.
* @param int $min The minimum value allowed to be input.
* @param int $max The maximum value allowed to be input.
* @param string $value The start value.
* @param string $step The range of each step.
* @param bool $visible Indicates if the element should be visible when rendered.
*
* @return string HTML a P element contaning the input, its label, class based on the key
* and style set to display:none if visibility is off.
*/
private function get_range_input_block_html( $instance, $key, $label, $min, $max, $value, $step, $visible ) {
$value = $instance[ $key ];
$minMaxStep = '';
if ( '' !== $min ) {
$minMaxStep .= ' min="' . $min . '"';
}
if ( '' !== $max ) {
$minMaxStep .= ' max="' . $max . '"';
}
if ( '' !== $step ) {
$minMaxStep .= ' step="' . $step . '"';
}
$ret = '\n";
return $this->get_wrap_block_html( $ret, $key, $visible );
}
/**
* Generate a form P element containing a number input
*
* @since 4.8
* @param array $instance The instance.
* @param string $key The key in the instance array.
* @param string $label The label to display and associate with the input.
* expected to be escaped.
* @param int $min The minimum value allowed to be input.
* @param int $max The maximum value allowed to be input.
* @param string $placeholder The placeholder string to be used. expected to be escaped.
* @param bool $visible Indicates if the element should be visible when rendered.
*
* @return string HTML a P element contaning the input, its label, class based on the key
* and style set to display:none if visibility is off.
*/
private function get_number_input_block_html( $instance, $key, $label, $min, $max, $placeholder, $visible ) {
$value = $instance[ $key ];
$minmax = '';
if ( '' !== $min ) {
$minmax .= ' min="' . $min . '"';
}
if ( '' !== $max ) {
$minmax .= ' max="' . $max . '"';
}
$ret = '\n";
return $this->get_wrap_block_html( $ret, $key, $visible );
}
/**
* Generate a form P element containing a date input
*
* @since 4.9
* @param array $instance The instance.
* @param string $key The key in the instance array.
* @param string $label The label to display and associate with the input.
* expected to be escaped.
* @param bool $visible Indicates if the element should be visible when rendered.
*
* @return string HTML a P element containing the input, its label, class based on the key
* and style set to display:none if visibility is off.
*/
private function get_date_input_block_html( $instance, $key, $label, $visible ) {
$value = $instance[ $key ];
$ret = '\n";
return $this->get_wrap_block_html( $ret, $key, $visible );
}
/**
* Generate a form P element containing a checkbox input
*
* @since 4.8
* @param array $instance The instance.
* @param string $key The key in the instance array.
* @param string $label The label to display and associate with the checkbox.
* should be escaped string.
* @param bool $visible Indicates if the element should be visible when rendered.
*
* @return string HTML a P element contaning the checkbox, its label, class based on the key
* and style set to display:none if visibility is off.
*/
private function get_checkbox_block_html( $instance, $key, $label, $visible ) {
$value = false;
if ( array_key_exists( $key, $instance ) && $instance[ $key ] ) {
$value = true;
}
$ret = '\n";
return $this->get_wrap_block_html( $ret, $key, $visible );
}
/**
* Generate a form button element containing
*
* @since 4.9
* @param array $instance The instance.
* @param string $key The key in the instance array.
* @param string $label The label to display and associate with the button.
* should be escaped string.
*
* @return string HTML a button element and class based on the key.
*/
private function get_button_thumb_size_html( $instance, $key, $label ) {
$datas = '';
switch ( $key ) {
case 'thumb':
$datas = 'data-thumb-w="' . get_option( 'thumbnail_size_w' ) . '" data-thumb-h="' . get_option( 'thumbnail_size_h' ) . '"';
break;
case 'medium':
$datas = 'data-thumb-w="' . get_option( 'medium_size_w' ) . '" data-thumb-h="' . get_option( 'medium_size_h' ) . '"';
break;
case 'large':
$datas = 'data-thumb-w="' . get_option( 'large_size_w' ) . '" data-thumb-h="' . get_option( 'large_size_h' ) . '"';
break;
}
$ret = '\n";
return $ret;
}
/**
* Generate a form element containing native styled radio buttons
*
* @since 5.0
* @param array $instance The instance.
* @param string $key The key in the instance array.
* @param string $label The label to display and associate with the checkbox.
* should be escaped string.
* @param bool $visible Indicates if the element should be visible when rendered.
*
* @return string HTML a element contaning the radio buttons, it's label, class based on the key
* and style set to display:none if visibility is off.
*/
private function get_radio_buttons_block_html( $instance, $key, $values, $label, $visible ) {
$ret = '\n";
$ret .= '';
array_map ( function( $value ) use ( &$ret, $instance, $key ) {
if ( isset( $instance[ $key ] ) && $instance[ $key ] == $value ) {
$checked = true;
} else {
$checked = false;
}
$ret .= '' . "\n";
}, $values );
$ret .= "\n";
return $this->get_wrap_block_html( $ret, $key, $visible );
}
/**
* The widget configuration form back end.
*
* @param array $instance The parameters associated with the widget.
* @return void
*/
public function form( $instance ) {
if ( 0 === count( $instance ) ) { // new widget, use defaults.
$instance = default_settings();
} else { // updated widgets come from =< 4.6 excerpt filter is on.
if ( ! isset( $instance['excerpt_filters'] ) ) {
$instance['excerpt_filters'] = 'on';
}
}
$instance = upgrade_settings( $instance );
$item_title_level = $instance['item_title_level'];
$hide_post_titles = $instance['hide_post_titles'];
$excerpt_more_text = $instance['excerpt_more_text'];
$excerpt_filters = $instance['excerpt_filters'];
$date_format = $instance['date_format'];
$disable_css = $instance['disable_css'];
$disable_font_styles = $instance['disable_font_styles'];
$preset_date_format = $instance['preset_date_format'];
$thumb = ! empty( $instance['thumb'] );
$thumb_w = $instance['thumb_w'];
$thumb_fluid_width = $instance['thumb_fluid_width'];
$thumb_h = $instance['thumb_h'];
$default_thunmbnail = $instance['default_thunmbnail'];
$text_do_not_wrap_thumb = $instance['text_do_not_wrap_thumb'];
?>