Лайк и дизлайк в постах WordPress

Добавление возможности оценить ваш пост на WordPress без плагина для всех пользователей, включая незарегистрированных посетителей. Результат вы видите в правом нижнем углу этой инструкции.

Вначале создадим некоторые функции для back-end, а затем разместим код jQuery на front-end. Делать будем по технологии Ajax, которая уже хорошо реализована в движке системы. Классы CSS подходят для Twitter Bootstrap 5.

Разметка HTML

Поместите код в подходящий для вашей темы файл отображения поста. В моём случае это шаблон для single.php, который расположен по адресу: ваша_тема/template-parts/content-single.php.

<?php if ( 'publish' === get_post_status() ) : ?>

	<div id="post-votes" class="btn-group align-items-end h-100 invisible" role="group" aria-label="Post votes">
		<button type="button" class="btn btn-sm border-0 p-0"><span class="dashicons dashicons-thumbs-up"></span></button>
		<button type="button" class="btn btn-sm border-0 p-0 px-2 disabled">0</button>
		<button type="button" class="btn btn-sm border-0 p-0"><span class="dashicons dashicons-thumbs-down"></span></button>
	</div>

<?php endif; ?>

Настройка back-end

Отредактируйте файл functions.php в корневом каталоге активной темы WP.

Подключение файла main.js для работы с JavaScript, дополнительную защиту запросов к серверу можно добавить через специальный код 'ajax-nonce' в хук 'wp_enqueue_scripts':

/**
 * Enqueue scripts and styles.
 */
function os_scripts() {
	wp_enqueue_style( 'dashicons' );
	wp_enqueue_script( 'main-js', get_template_directory_uri() . '/assets/js/main.js', array( 'jquery' ), '1.0', true );

	wp_localize_script(
		'main-js',
		'os_ajax',
		array(
			'ajax_url'   => admin_url( 'admin-ajax.php' ),
			'ajax_nonce' => wp_create_nonce( 'ajax-nonce' ),
		)
	);
}
add_action( 'wp_enqueue_scripts', 'os_scripts' );
  • Активируем встроенный в CMS набор SVG иконок 'dashicons'.
  • Подключаемый скрипт располагается по адресу: ваша_тема/assets/js/main.js. Библиотека jQuery установлена в качестве зависимости, поэтому активируется автоматически.
  • Функция 'wp_localize_script' в первом параметре содержит название скрипта 'main-js', к которому мы добавляем защиту. Второй параметр 'os_ajax' – это уникальное название JavaScript-объекта для взаимодействия с front-end. А в третьем параметре находится массив с данными о пути к файлу 'admin-ajax.php' и сам код защиты 'ajax-nonce'.

Следующие функции установят файл куки, в который будет помещён сериализованный массив PHP, в нём содержатся ID постов для исключения повторного нажатия на кнопки Лайк и Дизлайк. Для каждой задействованной записи создаётся скрытое от пользователей мета-поле '_os_post_votes', там и хранится информация о количестве голосов.

Функцию 'os_votes_cookie' можно использовать и для комментариев, поэтому я сделал её отдельной для универсальности:

/**
 * Update meta and set cookie array.
 *
 * @param  string $tax Taxonomy type (only 'post' or 'comment').
 * @param  int    $id  Current 'post' or 'comment' ID.
 * @return int         Votes count.
 */
function os_votes_cookie( $tax, $id ) {
	if ( 'post' !== $tax && 'comment' !== $tax ) {
		return 0;
	}

	$cookie_name  = "{$tax}_votes_" . COOKIEHASH;
	$cookie_array = ( isset( $_COOKIE[ $cookie_name ] ) ) ? maybe_unserialize( $_COOKIE[ $cookie_name ] ) : array();

	if ( ! is_array( $cookie_array ) || in_array( $id, $cookie_array ) || ! isset( $_POST['status'] ) ) {
		wp_send_json_error();
	}

	$field_type  = "_os_{$tax}_votes";
	$votes_meta  = ( 'post' === $tax ) ? get_post_meta( $id, $field_type, true ) : get_comment_meta( $id, $field_type, true );
	$votes       = ( ! empty( $votes_meta ) ) ? intval( $votes_meta ) : 0;
	$count       = ( 'dislike' === $_POST['status'] ) ? -1 : 1;
	$output      = ( $votes + $count );
	$cookie_life = ( time() + ( 30 * DAY_IN_SECONDS ) );

	if ( 'post' === $tax ) {
		update_post_meta( $id, $field_type, $output );
	} elseif ( 'comment' === $tax ) {
		update_comment_meta( $id, $field_type, $output );
	}

	array_push( $cookie_array, $id );
	setcookie( $cookie_name, serialize( $cookie_array ), $cookie_life, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );

	return $output;
}

/**
 * AJAX post votes.
 */
function os_ajax_post_votes() {
	check_ajax_referer( 'ajax-nonce', 'nonce_code' );

	if ( ! isset( $_POST['onload'] ) ) {
		wp_send_json_error();
	}

	$output  = 0;
	$referer = wp_get_referer();
	$post_id = url_to_postid( $referer );

	if ( 'false' === $_POST['onload'] ) {
		$output = os_votes_cookie( 'post', $post_id );
	} else {
		$output = intval( get_post_meta( $post_id, '_os_post_votes', true ) );
	}

	wp_send_json_success( $output );
}
add_action( 'wp_ajax_post_votes', 'os_ajax_post_votes' );
add_action( 'wp_ajax_nopriv_post_votes', 'os_ajax_post_votes' );

Дополнение для смартфонов

Далее в JS вы можете видеть вспомогательный класс 'os-mobile', так я определяю, что посетитель зашёл на страницу с мобильного устройства и предпочитаю не отображать там всплывающие подсказки Tooltip. Класс добавляется следующим образом:

/**
 * Filters the list of CSS body class names for the current post or page.
 *
 * @param  string[] $classes An array of body class names.
 * @return string[]          An array with new body classes.
 */
function os_mobile_body_class( $classes ) {
	if ( wp_is_mobile() ) {
		$classes[] = 'os-mobile';
	}

	return $classes;
}
add_filter( 'body_class', 'os_mobile_body_class' );

Настройка front-end

Отредактируйте файл ваша_тема/assets/js/main.js, который был создан в предыдущем разделе и добавьте следующий код:

/**
 * File main.js.
 */

( function ( $ ) {
	$( document ).ready( function () {

		// Votes Tooltip helper.
		function osVotedTip( a, b ) {
			$( a ).tooltip( {
				title: 'Оценка уже сделана!',
			} ).tooltip( 'show' );

			$( b ).on( 'mouseleave', function () {
				$( a ).tooltip( 'dispose' );
			} );
		}

		// Create an AJAX request.
		function osAjaxRequest( action, onload = 'true', status = 'like' ) {
			return $.ajax( {
				url: os_ajax.ajax_url,
				type: 'POST',
				dataType: 'json',
				data: {
					nonce_code: os_ajax.ajax_nonce,
					action: action,
					onload: onload,
					status: status,
				},
				success: function ( response ) {},
			} );
		}

		// AJAX post votes.
		$( function () {
			let post  = '#post-votes';
			let votes = '.disabled';

			// AJAX by onload.
			osAjaxRequest( 'post_votes' ).done( function ( response ) {
				if ( 'undefined' !== typeof( response.data ) ) {
					$( post ).find( votes ).text( response.data );
				}

				$( post ).removeClass( 'invisible' );
			} );

			$( post ).find( 'button.btn' ).not( votes ).one( 'click', function () {
				let btn    = this;
				let target = $( this ).siblings( votes );
				let status = 'like';

				if ( $( this ).find( '.dashicons' ).hasClass( 'dashicons-thumbs-down' ) ) {
					status = 'dislike';
				}

				// AJAX by click.
				osAjaxRequest( 'post_votes', false, status ).done( function ( response ) {
					if ( 'undefined' !== typeof( response.data ) ) {
						target.text( response.data );
					} else {
						if ( ! $( 'body' ).hasClass( 'os-mobile' ) ) {
							osVotedTip( target, btn );
						}
					}
				} );

				if ( ! $( 'body' ).hasClass( 'os-mobile' ) ) {
					$( this ).on( 'click', function () {
						osVotedTip( target, btn );
					} );
				}
			} );
		} );
	} );
} )( jQuery );

На этом форуме используется именно такой код, как в примерах. Обсуждение темы открыто, отвечу здесь в топике на вопросы по данной инструкции.

Я планирую вести дневник разработки форума, если вы интересуетесь WordPress и вас устроит качество кода, то будет весьма полезно почитать. Стараюсь придерживаться WP Codex, пока только в JS используется Кириллица, скоро доберусь до её оптимизации.

С уважением, админ.

Добавить ответ