AJAX комментарии в WordPress

Итак, комментарии на AJAX. Перед написанием этого поста я потратил добрую половину дня на их разработку — хорошо, что я уже был с ними знаком. В самый первый раз, когда я ещё начинал изучать jQuery, на создание асинхронных комментариев с нуля у меня ушли первые две недели января.

Кстати, я долго думал, имеет ли смысл поставить комментарии со стороннего сервиса, например disqus — но в итоге решил что свои комментарии на AJAX будут покруче.

Требования к AJAX-комментам

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

  • самое главное – возможность работы со стандартными древовидными комментариями comment-reply.js,
  • вывод ошибок, в том числе вордпрессовских, особо с этим заморачиваться не буду, сделаю через alert() пока что,
  • проверка комментария на наличие запрещенных HTML-тегов. Читайте о том, как изменить список разрешенных тегов,
  • модерация комментария при необходимости,
  • запоминание в кукисах значений полей «Имя» и «Email»,
  • исключить возможность двойного нажатия на кнопку «Отправить»,
  • не используем никакие плагины WordPress;

Шаг 1. Подготовка. Структура HTML.

Будем отталкиваться от стандартных тем WordPress, это Twenty Ten и Twenty Eleven. Разумеется, то, что представлено в следующем куске кода — всего лишь HTML-шаблон, который должен сориентировать вас по атрибутам class и id, а также по расположению элементов, а вообще все эти штуки делаются через php!

<!-- можно <ul> или <ol> -->
<ol class="commentlist">
	<!-- класс depth-1 показывает уровень вложенности -->
	<li class="comment depth-1" id="li-comment-4">
		<div id="comment-4">
			<!-- тут содержимое комментария -->
			<div class="reply">
				<a class="comment-reply-link">Ответить</a>
			</div>
		</div>
		<!-- дальше идут ответы к предыдущему комментарию  -->
		<ul class="children">
			<!-- depth-2 - значит вложенность второго уровня -->
			<li class="comment depth-2" id="li-comment-5">
				<div id="comment-5">
					<!-- тут содержимое комментария -->
					<div class="reply">
						<a class="comment-reply-link">Ответить</a>
					</div>
				</div>
			</li>
		</ul>
	</li>
</ol>
<div id="respond">
	<!-- тут нужно обратить внимание также на атрибуты name -->
	<a id="cancel-comment-reply-link" style="display:none;">Отменить ответ</a>
	<form id="commentform">
		<input name="author" id="author" type="text" />
		<input name="email" id="email" type="text" /><!-- или type="email", как хотите -->
		<input name="url" id="url" type="text" />
		<textarea name="comment" id="comment"></textarea>
		<input name="submit" type="submit" id="submit" value="Отправить" />
		<!-- если у вас нет hidden-полей попробуйте вставить их через php-функцию comment_id_fields() -->
		<input type="hidden" name="comment_post_ID" />
		<input type="hidden" name="comment_parent" />
	</form>
</div>

Так что, если у вас что-то добавляется не туда, куда надо, сверяйтесь по этому листингу.

Примерно так это будет выглядеть:

список комментариев с указанием CSS-классов элементов

В код моего блога советую не залазить, у меня там структура немного другая, она может вас только запутать ещё больше. Если что-то не получается или есть вопрос — просто задайте его мне прямо в комментариях к этому посту.

Шаг 2. CSS

Если у вас со структурой всё окей, то стили по сути и не понадобятся, за исключением оформления ошибок, возникающих в результате валидации полей. То есть, если кто-то ввёл неверный адрес email, поле должно стать красного цвета (например).

input.error, textarea.error{
	background: #fe0000; /* или background:red или любой другой цвет, который больше вписывается в дизайн вашего блога */
}

Добавляем этот код в основной файл стилей вашей темы, обычно это — style.css.

Шаг 3. Подключение скриптов

Во-первых, давайте в папке с темой создадим какой-нибудь файл JavaScript, в который мы потом добавим весь наш код. У меня это будет файл comments.js.

Теперь наша задача – правильно подключить библиотеку jQuery и файл comments.js. Для этого воспользуемся функцией wp_enqueue_script().

Следующий код вставляем в файл functions.php вашей текущей темы:

function true_include_my_comment_script() {
	wp_enqueue_script( 'jquery' );
 	wp_enqueue_script( 'commentjs', get_stylesheet_directory_uri() . '/comments.js', array('jquery'), null );
}
 
add_action( 'wp_enqueue_scripts', 'true_include_my_comment_script' );

Зайдите в исходный код страницы (в Windows – Ctrl + U) и посмотрите, появился ли там comments.js, если да — переходим к следующему шагу, если нет — открываем файлы header.php и footer.php и убеждаемся, что там присутствуют функции wp_head() и wp_footer() соответственно.

Шаг 4. Скрипты jQuery

Открываем наш файл comments.js и вписываем туда:

jQuery.extend(jQuery.fn, {
	/*
	 * функция проверки, что длина поля не меньше 3х символов 
	 */
	checka: function () {
		if (jQuery(this).val().length < 3) {jQuery(this).addClass('error');return false} else {jQuery(this).removeClass('error');return true}
	},
	/*
	 * функция проверки правильности введенного email
	 */
	checke: function () {
		var emailReg = /^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/;
		var emailaddressVal = jQuery(this).val();
		if (!emailReg.test(emailaddressVal) || emailaddressVal == "") {
			jQuery(this).addClass('error');return false
		} else {
			jQuery(this).removeClass('error');return true
		}
	},
});
 
jQuery(function($){
	$('#commentform').submit(function(){
		// может такое случиться, что пользователь залогинен - нужно это проверить, иначе валидация не пройдет
		if($("#author").length) var author = $("#author").checka();
		if($("#email").length) var email = $("#email").checke();
		var comment = $("#comment").checka();
		// небольшое условие для того, чтобы исключить двойные нажатия на кнопку отправки
		// в это условие также входит валидация полей
		if (!$('#submit').hasClass('loadingform') && !$("#author").hasClass('error') && !$("#email").hasClass('error') && !$("#comment").hasClass('error')){
			$.ajax({
				type : 'POST',
				url : 'http://' + location.host + '/wp-admin/admin-ajax.php',
				data: $(this).serialize() + '&action=ajaxcomments',
				beforeSend: function(xhr){
					// действие при отправке формы, сразу после нажатия на кнопку #submit 
					$('#submit').addClass('loadingform').val('Загрузка');
				},
				error: function (request, status, error) {
					if(status==500){
						alert('Ошибка при добавлении комментария');
					} else if(status=='timeout'){
						alert('Ошибка: Сервер не отвечает, попробуй ещё.');
					} else {
						// ворпдрессовские ошибочки, не уверен, что это самый оптимальный вариант
						// если знаете способ получше - поделитесь
						var errormsg = request.responseText;
						var string1 = errormsg.split("<p>");
						var string2 = string1[1].split("</p>");
						alert(string2[0]);
					}
				},
				success: function (newComment) {
					// Если уже есть какие-то комментарии
					if($('.commentlist').length > 0){
						// Если текущий комментарий является ответом
						if($('#respond').parent().hasClass('comment')){
							// Если уже есть какие-то ответы
							if($('#respond').parent().children('.children').length){	
								$('#respond').parent().children('.children').append(newComment);
							} else {
								// Если нет, то добавляем  <ul class="children">
								newComment = '<ul class="children">'+newComment+'</ul>';
								$('#respond').parent().append(newComment);
							}
							// закрываем форму ответа
							$("#cancel-comment-reply-link").trigger("click");
						} else {
							// обычный коммент
							$('.commentlist').append(newComment);
						}
					}else{
						// если комментов пока ещё нет, тогда
						newComment = '<ul class="commentlist">'+newComment+'</ol>';
						$('#respond').before($(newComment));
					}
					// очищаем поле textarea
					$('#comment').val('');
				},
				complete: function(){
					// действие, после того, как комментарий был добавлен
					$('#submit').removeClass('loadingform').val('Отправить');
				}
			});
		}
		return false;
	});
});

Последний шаг. PHP-обработчик

Читайте подробнее о том, как обрабатываются асинхронные запросы в WordPress. Код обработчика:

<?php
// если вы вставляете код не в новый файл, то <?php нужно удалить
function true_add_ajax_comment(){
	global $wpdb;
	$comment_post_ID = isset($_POST['comment_post_ID']) ? (int) $_POST['comment_post_ID'] : 0;
 
	$post = get_post($comment_post_ID);
 
	if ( empty($post->comment_status) ) {
		do_action('comment_id_not_found', $comment_post_ID);
		exit;
	}
 
	$status = get_post_status($post);
 
	$status_obj = get_post_status_object($status);
 
	/*
	 * различные проверки комментария
	 */
	if ( !comments_open($comment_post_ID) ) {
		do_action('comment_closed', $comment_post_ID);
		wp_die( __('Sorry, comments are closed for this item.') );
	} elseif ( 'trash' == $status ) {
		do_action('comment_on_trash', $comment_post_ID);
		exit;
	} elseif ( !$status_obj->public && !$status_obj->private ) {
		do_action('comment_on_draft', $comment_post_ID);
		exit;
	} elseif ( post_password_required($comment_post_ID) ) {
		do_action('comment_on_password_protected', $comment_post_ID);
		exit;
	} else {
		do_action('pre_comment_on_post', $comment_post_ID);
	}
 
	$comment_author       = ( isset($_POST['author']) )  ? trim(strip_tags($_POST['author'])) : null;
	$comment_author_email = ( isset($_POST['email']) )   ? trim($_POST['email']) : null;
	$comment_author_url   = ( isset($_POST['url']) )     ? trim($_POST['url']) : null;
	$comment_content      = ( isset($_POST['comment']) ) ? trim($_POST['comment']) : null;
 
	/* 
	 * проверяем, залогинен ли пользователь
	 */
	$user = wp_get_current_user();
	if ( $user->exists() ) {
		if ( empty( $user->display_name ) )
			$user->display_name=$user->user_login;
		$comment_author       = $wpdb->escape($user->display_name);
		$comment_author_email = $wpdb->escape($user->user_email);
		$comment_author_url   = $wpdb->escape($user->user_url);
		$user_ID = get_current_user_id();
		if ( current_user_can('unfiltered_html') ) {
			if ( wp_create_nonce('unfiltered-html-comment_' . $comment_post_ID) != $_POST['_wp_unfiltered_html_comment'] ) {
				kses_remove_filters(); // start with a clean slate
				kses_init_filters(); // set up the filters
			}
		}
	} else {
		if ( get_option('comment_registration') || 'private' == $status )
			wp_die( 'Вы должны зарегистрироваться или войти, чтобы оставлять комментарии.' );
	}
 
	$comment_type = '';
 
	/* 
	 * проверяем, заполнил ли пользователь все необходимые поля
 	 */
	if ( get_option('require_name_email') && !$user->exists() ) {
		if ( 6 > strlen($comment_author_email) || '' == $comment_author )
			wp_die( 'Ошибка: заполните необходимые поля (Имя, Email).' );
		elseif ( !is_email($comment_author_email))
			wp_die( 'Ошибка: введенный вами email некорректный.' );
	}
 
	if ( '' == trim($comment_content) ||  '<p><br></p>' == $comment_content )
		wp_die( 'Вы забыли про комментарий.' );
 
	/* 
	 * добавляем новый коммент и сразу же обращаемся к нему
	 */
	$comment_parent = isset($_POST['comment_parent']) ? absint($_POST['comment_parent']) : 0;
	$commentdata = compact('comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_type', 'comment_parent', 'user_ID');
	$comment_id = wp_new_comment( $commentdata );
	$comment = get_comment($comment_id);
 
	/*
	 * выставляем кукисы
	 */
	do_action('set_comment_cookies', $comment, $user);
 
	/*
	 * вложенность комментариев
	 */
	$comment_depth = 1;
	while($comment_parent){
		$comment_depth++;
		$parent_comment = get_comment($comment_parent);
		$comment_parent = $parent_comment->comment_parent;
	}
 
	$GLOBALS['comment'] = $comment;
	$GLOBALS['comment_depth'] = $comment_depth;
	/*
	 * ниже идет шаблон нового комментария, вы можете настроить его для себя,
	 * а можете воспользоваться функцией(которая скорее всего уже есть в теме) для его вывода
	 */
	?>
	<li <?php comment_class(); ?> id="li-comment-<?php comment_ID(); ?>">
		<div id="comment-<?php comment_ID(); ?>">
			<div class="comment-author vcard">
				<?php echo get_avatar( $comment, 40 ); ?>
				<cite class="fn"><?php echo get_comment_author_link(); ?></cite>
			</div>
			<?php if ( $comment->comment_approved == '0' ) : ?>
				<em class="comment-awaiting-moderation">Комментарий отправлен на проверку</em>
				<br />
			<?php endif; ?>
			<div class="comment-meta commentmetadata"><a href="<?php echo esc_url( get_comment_link( $comment->comment_ID ) ); ?>">
				<?php printf('%1$s в %2$s', get_comment_date(),  get_comment_time() ); ?></a><?php edit_comment_link('ред.', ' ' );	?>
			</div>
			<div class="comment-body"><?php comment_text(); ?></div>
		</div>
	</li>
	<?php
	die();
}
 
add_action('wp_ajax_ajaxcomments', 'true_add_ajax_comment'); // wp_ajax_{значение параметра action}
add_action('wp_ajax_nopriv_ajaxcomments', 'true_add_ajax_comment'); // wp_ajax_nopriv_{значение параметра action}

Комменты можете протестировать прямо на моем блоге, там совсем незначительные изменения в коде, также я протестировал весь код из статьи ничего не меняя, как есть, на теме Twenty Ten.

Со временем пост будет обновляться по мере совершенствования кода в нем, после появления каких-либо доработок.

Также есть доработки, которые я не буду добавлять, дабы не усложнять пост, но вам стоит попробовать сделать их самим:

  • При добавлении нового комментария неплохо также обновлять цифру с количеством комментариев, которую обычно можно найти рядом с датой публикацией поста и в заголовке непосредственно перед самими комментами,
  • Вы можете оформить ошибки в виде красивых всплывающих окон или в виде подсказок,
  • Очень здорово, если при нажатии на кнопку отправки, на ней будет появляться анимация-прелоадер, как у меня на блоге.
Миша Рудрастых Разработчик WordPress WooCommerce

Миша Рудрастых

Впервые познакомился с WordPress в 2009 году, и после двух лет мучений с Joomla и самописными движками это был просто бальзам на душу. С 2014 года меня можно встретить на WordCamp — официальной конфе по WP в Москве, иногда там выступаю. Также в настоящее время веду курсы по WordPress в Epic Skills в Питере.

Смотрите также