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; } $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 = '
', '
', $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 = '
get_checkbox_block_html( $instance, 'no_cat_childs', esc_html__( 'Exclude child categories', 'category-posts' ), ! empty( $instance['cat'] ) ); echo $this->get_select_block_html( $instance, 'status', esc_html__( 'Status', 'category-posts' ), array( 'default' => esc_html__( 'WordPress Default', 'category-posts' ), 'publish' => esc_html__( 'Published', 'category-posts' ), 'future' => esc_html__( 'Scheduled', 'category-posts' ), 'private' => esc_html__( 'Private', 'category-posts' ), 'publish,future' => esc_html__( 'Published or Scheduled', 'category-posts' ), 'private,publish' => esc_html__( 'Published or Private', 'category-posts' ), 'private,future' => esc_html__( 'Private or Scheduled', 'category-posts' ), 'private,publish,future' => esc_html__( 'Published, Private or Scheduled', 'category-posts' ), ), 'default', true ); echo $this->get_number_input_block_html( $instance, 'num', esc_html__( 'Number of posts to show', 'category-posts' ), 1, '', '', true ); echo $this->get_number_input_block_html( $instance, 'offset', esc_html__( 'Start with post', 'category-posts' ), 1, '', '', true ); echo $this->get_select_block_html( $instance, 'date_range', esc_html__( 'Date Range', 'category-posts' ), array( 'off' => esc_html__( 'Off', 'category-posts' ), 'days_ago' => esc_html__( 'Days ago', 'category-posts' ), 'between_dates' => esc_html__( 'Between dates', 'category-posts' ), ), 'off', true ); ?>