Настройки в админке
Класс виджета
<?php
/**
* Виджет популярных новостей за неделю на основе оставленных за этот период комментариев.
*/
namespace RusDTP;
use DateTime;
use Exception;
use WP_Post;
use WP_Widget;
class Popular_News extends WP_Widget {
public $widget_args, $widget_instance;
private $default_per_posts = 5;
private $cron_hook = 'popular-news-event';
public function __construct() {
parent::__construct( 'popular-news', 'Популярные новости', [
'classname' => 'widget_popular_news',
'description' => 'Популярные новости портала за неделю.',
] );
$this->alt_option_name = 'widget_popular_news';
add_action( $this->cron_hook, [ $this, 'update_cache' ] );
add_action( 'wp_insert_comment', [ $this, 'cron_update_cache_before_insert_comment' ] );
add_action( 'transition_comment_status', [ $this, 'cron_update_cache_before_transition_comment' ], 11, 2 );
}
/**
* Обновляет настройки виджета.
*
* @param array $new_instance
* @param array $old_instance
*
* @return array
*/
public function update( $new_instance, $old_instance ): array {
$instance = $old_instance;
$instance['title'] = sanitize_text_field( $new_instance['title'] );
$instance['number'] = absint( $new_instance['number'] );
if ( abs( $new_instance['number'] ) !== abs( $old_instance['number'] ?? 0 ) ) {
$this->update_cache();
}
return $instance;
}
/**
* Выводит на экран настройки виджета.
*
* @param array $instance Текущие настройки.
*
* @return void
*/
public function form( $instance ): void {
$title = $instance['title'] ?? '';
$number = $instance['number'] ?? $this->default_per_posts;
?>
<p class="desc">
<?= $this->widget_options['description'] ?>
</p>
<p>
<label for="<?= $this->get_field_id( 'title' ) ?>">
<?php _e( 'Title:' ) ?>
</label>
<input class="widefat"
id="<?= $this->get_field_id( 'title' ) ?>"
name="<?= $this->get_field_name( 'title' ) ?>"
type="text"
value="<?= esc_attr( $title ) ?>"
/>
</p>
<p>
<label for="<?= $this->get_field_id( 'number' ) ?>">
Количество выводимых новостей:
</label>
<input class="tiny-text"
id="<?= $this->get_field_id( 'number' ) ?>"
name="<?= $this->get_field_name( 'number' ) ?>"
type="number"
step="1"
min="1"
value="<?= absint( $number ) ?>"
size="3"
/>
</p>
<?php
}
/**
* Выводит на экран контент виджета.
*
* @param array $args
* @param array $instance
*
* @return void
*/
public function widget( $args, $instance ): void {
$this->widget_args = $args;
$this->widget_instance = $instance;
get_template_part( '/templates/widgets/widget-popular-news', null, [ 'module' => $this ] );
}
/**
* Возвращает заголовок виджета.
*
* @return string
*/
public function get_widget_title(): string {
$title = empty( $this->widget_instance['title'] ) ? 'Популярные записи' : $this->widget_instance['title'];
$title = apply_filters( 'widget_title', $title, $this->widget_instance, $this->id_base );
return esc_html( $title );
}
/**
* Возвращает количество отображаемых в виджете новостей.
*
* @return int
*/
public function get_widget_per_posts(): int {
$per_posts = absint( $this->widget_instance['number'] ?? 0 );
if ( $per_posts ) {
return $per_posts;
}
return absint( $this->get_settings()[ $this->number ]['number'] ?? $this->default_per_posts );
}
/**
* Возвращает популярные новости на основе комментариев в количестве, указанном в настройках виджета.
*
* @param $per_posts
*
* @return WP_Post[]
*/
private function get_posts( $per_posts ): array {
global $wpdb;
$posts = [];
// Дата последнего комментария
$date_start = $wpdb->get_var( "
SELECT comment_date
FROM $wpdb->comments
WHERE comment_approved = '1' AND comment_type = 'comment'
ORDER BY comment_date DESC
LIMIT 0,1
" );
if ( $date_start ) {
try {
// Дата комментария на неделю ранее
$date_end = new DateTime( $date_start );
$date_end = $date_end->modify( '-1 weeks' )->format( 'Y-m-d H:i:s' );
} catch ( Exception $e ) {
return [];
}
// Запрос комментариев от последнего комметария до комментария неделю раннее
$result = $wpdb->get_results( "
SELECT comment_ID, comment_post_ID
FROM $wpdb->comments
WHERE comment_approved = '1'
AND comment_type = 'comment'
AND comment_date >= '$date_end'
AND comment_date <= '$date_start'
" );
if ( $result && is_array( $result ) ) {
$post_ids = [];
foreach ( $result as $item ) {
if ( isset( $post_ids[ $item->comment_post_ID ] ) ) {
++ $post_ids[ $item->comment_post_ID ];
} else {
$post_ids[ $item->comment_post_ID ] = 1;
}
}
arsort( $post_ids, SORT_NUMERIC );
$posts = get_posts( [
'post_type' => 'post',
'posts_per_page' => $per_posts,
'post__in' => array_keys( $post_ids ),
'orderby' => 'post__in',
] );
}
}
return $posts;
}
/**
* Возвращает новости из кеша.
*
* @param bool $flush Пересоздать кеш
*
* @return WP_Post[]
*/
public function get_cached_posts( bool $flush = false ): array {
$posts = get_transient( __CLASS__ );
if ( false === $posts || $flush ) {
$posts = $this->get_posts( $this->get_widget_per_posts() );
if ( $posts ) {
update_post_caches( $posts, 'post', false, false );
}
set_transient( __CLASS__, $posts );
}
return array_map( 'get_post', $posts );
}
/**
* Обновляет кеш.
*
* @return void
*/
public function update_cache(): void {
$this->get_cached_posts( true );
}
public function add_cron_update_cache(): void {
if ( ! wp_next_scheduled( $this->cron_hook ) ) {
wp_schedule_single_event( time() + 1, $this->cron_hook );
}
}
/**
* Добавляет крон задачу на обновлении кеша при добавлении нового комментария.
*
* @return void
*/
public function cron_update_cache_before_insert_comment(): void {
$this->add_cron_update_cache();
}
/**
* Добавляет крон задачу на обновлении кеша при смене статуса любого комментария.
*
* @param string $new_status
* @param string $old_status
*
* @return void
*/
public function cron_update_cache_before_transition_comment( string $new_status, string $old_status ): void {
in_array( 'approved', [ $new_status, $old_status ], true ) && $this->add_cron_update_cache();
}
}
Файл шаблона
Пример оформление во фронте
<?php
/**
* Шаблон виджета "Свежие комментарии".
*
* @var $module RusDTP\Popular_News
*/
$module = $args['module'] ?? null;
if ( ! $module ) {
return;
}
$posts = $module->get_cached_posts();
?>
<?= $module->widget_args['before_widget'] ?>
<?= $module->widget_args['before_title'] . $module->get_widget_title() . $module->widget_args['after_title'] ?>
<?php if ( $posts ): ?>
<div class="widget_popular_news__items">
<?php
global $post;
foreach ( $posts as $post ) {
setup_postdata( $post );
// Ссылка на миниатюру
preg_match( '/(<img[^>]+>)/i', get_the_excerpt(), $matches );
$img_url = $matches[0] ?? null;
?>
<div class="widget_popular_news__item">
<a class="widget_popular_news__item-thumb">
<?= $img_url ?>
</a>
<div>
<a class="widget_popular_news__item-title" href="<?php the_permalink( $post ) ?>">
<?= get_the_title( $post ) ?>
</a>
<a class="widget_popular_news__item-comments-count icon__comment-count"
href="<?php the_permalink( $post ) ?>#comments"
>
<?= get_comments_number( $post ); ?>
</a>
</div>
</div>
<?php
}
wp_reset_postdata();
?>
</div>
<?php else: ?>
<p class="widget_popular_news__empty">
К сожалению, популярных постов за указанный период не нашлось.
</p>
<?php endif; ?>
<?= $module->widget_args['after_widget'] ?>