В моем примере я использовал плагин ACF. Но тоже самое можно адаптировать под стандартные произвольные поля. Мои произвольные поля: rating — числовое поле, по умолчанию значение 5. (поле нужно для рейтинга). А так же поле город town.
Шаг 1.
Идем в functions.php и регистрируем новый post_type.
add_action( 'init', 'true_register_post_type_init' ); // Использовать отзыв только внутри хука init
function true_register_post_type_init() {
$labels = array(
'name' => 'Отзывы',
'singular_name' => 'Отзыв', // админ панель Добавить->Отзыв
'add_new' => 'Добавить отзыв',
'add_new_item' => 'Добавить новый отзыв', // заголовок тега <title>
'edit_item' => 'Редактировать отзыв',
'new_item' => 'Новый отзыв',
'all_items' => 'Все отзывы',
'view_item' => 'Просмотр отзыва на сайте',
'search_items' => 'Искать отзыв',
'not_found' => 'Отзывов не найдено.',
'not_found_in_trash' => 'В корзине нет отзывов.',
'menu_name' => 'Отзывы' // ссылка в меню в админке
);
$args = array(
'labels' => $labels,
'public' => true,
'publicly_queryable' => false,
'show_ui' => true, // показывать интерфейс в админке
'has_archive' => false,
'menu_position' => 20, // порядок в меню
'supports' => array( 'title', 'editor', 'thumbnail')
);
register_post_type('testimonials', $args);
}
Шаг 2.
Здесь же, в functions.php добавляем ниже:
// Вывод уведомления, если есть записи на утверждение
// только для админов
if( current_user_can('manage_options') ){
add_action( 'admin_notices', 'my_plugin_notice' );
function my_plugin_notice() {
$count_posts = wp_count_posts('testimonials');
$countpanding = $count_posts->pending;// определяем количество записей на утверждении
if ($countpanding>0) { //если что-то есть, выводим в адмнике уведомление
?>
<div class="updated">
<p style="color:red; font-weight:bold; font-size:14px">Есть новые отзывы на утверждение! Количество: <?php echo $countpanding;?> </p>
<a href="/wp-admin/edit.php?post_status=pending&post_type=testimonials">Посмотреть</a>
</div>
<?php
}
}
}
add_action( 'admin_menu', 'add_user_menu_bubble' );
function add_user_menu_bubble(){
global $menu;
// записи
$count = wp_count_posts('testimonials')->pending; // на подтверждении
if( $count ){
foreach( $menu as $key => $value ){
if( $menu[$key][2] == 'edit.php?post_type=testimonials' ){
$menu[$key][0] .= ' <span class="awaiting-mod"><span class="pending-count">' . $count . '</span></span>';
break;
}
}
}
}
Этот код отвечает за уведомления в админке.
Шаг 3.
Создаем форму.
<form class="rr_review_form" id="featured_upload" method="post" action="#" enctype="multipart/form-data">
<input type="hidden" name="rRating" id="rRating" value="0">
<div class="form-footer clearfix">
<div class="row">
<div class="col-md-12">
<div class="form-group animated-labels">
<div class="input">
<input id="Name" class="form-control required" type="text" name="name" value="" required="required" placeholder="Ваше имя">
</div>
<span class="form-err"></span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="form-group animated-labels">
<div class="input">
<input id="Town" class="form-control required" type="text" name="town" value="" required="required" placeholder="Ваш город">
</div>
<span class="form-err"></span>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="form-group animated-labels">
<div class="input">
<textarea id="ReviewContent" class="form-control required" name="text" rows="10" style="resize: none; overflow-y: hidden;" required="required" placeholder="Текст отзыва"></textarea>
</div>
<span class="form-err"></span>
</div>
</div>
</div>
<div class="row" data-sid="RATING" style=" margin-bottom: 20px;
margin-top: 9px;">
<div class="col-md-12">
<label for="POPUP_RATING">Рейтинг</label>
<div class="input">
<div class="rating_wrap clearfix">
<div class="rating">
<span class="star rr_star glyphicon glyphicon-star-empty" data-current_width="20" data-message="Очень плохо" data-rating_value="1" id="rr_star_1"></span>
<span class="star rr_star glyphicon glyphicon-star-empty" data-current_width="40" data-message="Плохо" data-rating_value="2" id="rr_star_2"></span>
<span class="star rr_star glyphicon glyphicon-star-empty" data-current_width="60" data-message="Нормально" data-rating_value="3" id="rr_star_3"></span>
<span class="star rr_star glyphicon glyphicon-star-empty" data-current_width="80" data-message="Хорошо" data-rating_value="4" id="rr_star_4"></span>
<span class="star rr_star glyphicon glyphicon-star-empty" data-current_width="100" data-message="Отлично" data-rating_value="5" id="rr_star_5"></span>
<span class="stars_current" data-rating="0" style="width: 0%;"></span>
</div>
<div class="rating_message" data-message="Без оценки"> - Без оценки</div>
</div>
</div>
<span class="form-err"></span>
</div>
</div>
<div class="row">
<div class="col-md-12">
<label for="POPUP_RATING">Ваше фото</label>
<div class="input">
<input type="file" name="my_image_upload" id="my_image_upload" multiple="false" />
<?php wp_nonce_field( 'my_image_upload', 'my_image_upload_nonce' ); ?>
</div>
<span class="form-err"></span>
</div>
</div>
<div>
<button class="btn-lg btn btn-default" id="submitReview" type="button">Отправить</button>
</div>
<div class="result"></div>
<span class="name-field">Вы не заполнили поле Имя</span><br>
<span class="name-field2">Вы не заполнили поле Текст отзыва</span>
</div>
</form>
Шаг 4. Добавляем скрипты.
$(document).on('click', '.rr_star', function() {
var rate = $(this).data('rating_value');
$('#rRating').val(rate)
});
$(document).on('mouseenter', '.rr_review_form .rating .star', function() {
var $this = $(this),
currentStarWidth = $this.data('current_width'),
ratingValue = $this.data('rating_value'),
ratingMessage = $this.data('message');
$this.closest('.rating').find('.stars_current').width(currentStarWidth + '%');
$this.closest('.rating_wrap').find('.rating_message').text(ratingMessage)
});
$(document).on('mouseleave', '.rr_review_form .rating', function() {
var $this = $(this),
dataRating = $this.find('.stars_current').data('rating'),
ratingMessage = $this.closest('.rating_wrap').find('.rating_message').data('message');
$this.find('.stars_current').width(dataRating + '%');
$this.closest('.rating_wrap').find('.rating_message').text(ratingMessage)
});
$(document).on('click', '.rr_review_form .rating .star', function() {
var $this = $(this),
currentStarWidth = $this.data('current_width'),
ratingValue = $this.data('rating_value'),
ratingMessage = $this.data('message');
$this.closest('.rating').find('.stars_current').data('rating', currentStarWidth);
if ($this.closest('.input').find('input[name=RATING]').length) {
$this.closest('.input').find('input[name=RATING]').val(ratingValue)
} else {
$this.closest('.input').find('input[data-sid=RATING]').val(ratingValue)
}
$this.closest('.rating_wrap').find('.rating_message').data('message', ratingMessage)
});
$(document).on('click', '#submitReview', function() {
var name = $('#Name').val();
var text = $('#ReviewContent').val();
var rate = $('#rRating').val();
var my_image_upload = $('#my_image_upload').val();
if (name == '') {
$('#Name').addClass('wpcf7-not-valid');
$('.name-field').addClass('d-block');
} else {
$('#Name').removeClass('wpcf7-not-valid');
$('.name-field').removeClass('d-block');
}
if (text == '') {
$('#ReviewContent').addClass('wpcf7-not-valid');
$('.name-field2').addClass('d-block');
} else {
$('#ReviewContent').removeClass('wpcf7-not-valid');
$('.name-field2').removeClass('d-block');
}
if (name !== '' && text !== '') {
$.ajax({
url: '/wp-content/themes/kids/ajaxupload.php',
type: 'POST',
data: new FormData(document.getElementById("featured_upload")),
contentType: false,
cache: false,
processData: false,
success: function(data) {
$('.result').text('Спасибо! Ваш отзыв отправлен на модерацию.');
$('#submitReview').attr('disabled', true);
}
})
}
});
Здесь сразу и события на звездочки рейтинга и отправка самой формы в обработчик ajaxopload.php
Шаг 5. Обработчик ajaxopload.php
<?php
$parse_uri = explode( 'wp-content', $_SERVER['SCRIPT_FILENAME'] );
require_once( $parse_uri[0] . 'wp-load.php' );
$post_title = $_POST['name'];
$post_content = $_POST['text'];
$rate = $_POST['rate'];
$town = $_POST['town'];
$new_post = array(
'post_type' => 'testimonials',
'post_content' => $post_content,
'post_title' => $post_title,
'post_status' => 'pending',
);
$post_id = wp_insert_post($new_post);
if (
isset( $_POST['my_image_upload_nonce'], $post_id )
&& wp_verify_nonce( $_POST['my_image_upload_nonce'], 'my_image_upload' )
) {
// все ок! Продолжаем.
// Эти файлы должны быть подключены в лицевой части (фронт-энде).
require_once( ABSPATH . 'wp-admin/includes/image.php' );
require_once( ABSPATH . 'wp-admin/includes/file.php' );
require_once( ABSPATH . 'wp-admin/includes/media.php' );
// Позволим WordPress перехватить загрузку.
// не забываем указать атрибут name поля input - 'my_image_upload'
$attachment_id = media_handle_upload( 'my_image_upload', $post_id );
if ( is_wp_error( $attachment_id ) ) {
echo "Ошибка загрузки медиафайла.";
} else {
echo "Медиафайл был успешно загружен!";
}
} else {
echo "Проверка не пройдена. Невозможно загрузить файл.";
}
if( set_post_thumbnail($post_id, $attachment_id) )
echo 'Миниатюра установлена.';
else
echo 'Миниатюра удалена.';
update_field('rating', $rate, $post_id );
update_field('town', $town, $post_id );
$to = "почтаадмина@mail.ru";
$subject = "Новый отзыв ожидает вашей проверки";
$message = ' <p>Имя '.$post_title.'</p> </br> Текст отзыва: </br> '.$post_content.' ';
$headers = "Content-type: text/html; charset=utf-8 \r\n";
$headers .= "From: <info@вашадоменнаяпочта.ru>\r\n";
mail($to, $subject, $message, $headers);
?>
Этот скрипт создает запись в нашем post_type и отправляет данные на почту.
Шаг 6. Причесываем CSS
.rr_review_name {
color: #f68315;
font-family: MontserratBold;
font-size: 15px;
font-weight: 700;
line-height: 21px;
}
.text p {
color: #717171;
font-size: 16px;
line-height: 24px;
margin-top: 25px;
font-family: 'MontserratItalic';
max-width: 80%;
}
.stars_current {
width: 80%;
font-size: 0;
margin-top: 5px;
display: block;
color: #e3c45a;
letter-spacing: 3px;
}
.add_review button {
width: 225px;
height: 50px;
background-color: #613085;
background-image: none;
font-size: 14px;
margin-bottom: 30px;
}
.add_review {
margin-top: 85px;
}
#exampleModalCenter3 .modal-dialog {
max-width: 400px;
}
.rr_review_form input {
color: #717171;
font-size: 14px;
font-weight: 400;
line-height: 24px;
padding-left: 26px;
overflow: visible;
width: 100%;
height: 50px;
border: 1px solid #c3c3c3;
border-radius: 30px;
background-color: transparent;
}
.rr_review_form textarea {
color: #717171;
font-size: 14px;
font-weight: 400;
line-height: 24px;
padding-left: 26px;
overflow-y: auto !important;
width: 100%;
height: 155px;
border: 1px solid #c3c3c3;
border-radius: 30px;
background-color: transparent;
margin-right: 0;
max-width: 100%;
}
.form-control:focus {
color: #495057;
background-color: #fff;
border-color: #613085;
outline: 0;
box-shadow: 0 0 0 0.2rem rgba(97, 48, 133, 0.25);
}
form .row[data-sid=RATING] .rating {
/* float: left; */
position: relative;
width: 150px;
height: 24px;
font-size: 0;
line-height: 0;
background: url(../img/bigstars.svg) 0 -26px no-repeat;
margin-right: 8px;
margin-top: -2px;
display: flex;
display: inline-block;
}
form .row[data-sid=RATING] .rating .star {
position: relative;
z-index: 1;
margin: 0;
padding: 0;
height: 24px;
display: inline-block;
width: 20%;
background: none;
cursor: pointer;
border-radius: 0;
}
.stars, .rr_star {
color: #ffaf00;
}
.rating .stars_current {
position: absolute;
left: 0;
top: 0;
bottom: 0;
display: block;
width: 0;
background: url(../img/bigstars.svg) 0 0 no-repeat;
margin: 0;
}
.rating_message {
display: inline-block;
position: relative;
top: -5px;
}
.item-views.reviews_items .items .item .rating_wrap {
margin: 0;
display: inline-block;
vertical-align: top;
margin-top: 15px;
}
.rating {
position: relative;
width: 121px;
height: 19px;
font-size: 0;
line-height: 0;
background: url(../img/stars.svg) 0 -21px no-repeat;
}
.stars .rating .stars_current {
background: url(../img/stars.svg) 0 0 no-repeat;
}
.rr_review_form .row {
color: #717171;
font-size: 14px;
}
Я в тупую скопировал стили с сайта примера. Поэтому редактируйте их сами. Самое важное что здесь нужно это stars.svg и bigstars.svg без них у вас тупо не будут отображаться звездочки.
Шаг 7. Вывод комментариев
<div class="add_review">
<div class="button add_review_icon">
<button class="btn btn-default btn-lg animate-load" data-toggle="modal" data-target="#exampleModalCenter3">Оставить свой отзыв</button>
</div>
</div>
<div class="item-views reviews_items">
<div class="group-content">
<div class="sid- items">
<?php
$args = array(
'post_type' => 'testimonials',
'publish' => true,
);
query_posts($args);?>
<div class="testimonial_group">
<?php if (have_posts()) : ?>
<?php while (have_posts()) : the_post(); ?>
<div class="col-md-12 item question1" itemscope="" itemtype="http://schema.org/Review">
<div class="d-flex">
<div class="photo" style="background-image:url('<?php the_post_thumbnail_url('medium'); ?>');"></div>
<div class="">
<span itemprop="itemReviewed" itemscope="" itemtype="http://schema.org/Product">
<div class="rr_review_post_id" itemprop="name" style="display:none;">
<a href="https://kidsprofiki.com/otzyvy/" tabindex="-1">
Отзывы
</a>
</div>
</span>
<span class="rr_date" style="display:none;"><meta itemprop="datePublished" content="2019-02-27 14:04:04">
<time datetime="">
</time>
</span>
<div class="rr_review_name" itemprop="author" itemscope="" itemtype="http://schema.org/Person">
<span itemprop="name"><?php the_title(); ?> </span>
</div>
<span class="townn">г. <?php the_field('town'); ?></span>
<div class="top-wrapper">
<div class="rating_wrap">
<div class="stars">
<div class="rating current_5">
<span class="stars_current" style="<?php
$rate = get_field('rating');
switch ($rate) {
case 0:
echo "width: 0%;";
break;
case 1:
echo "width: 20%;";
break;
case 2:
echo "width: 40%;";
break;
case 3:
echo "width: 60%;";
break;
case 4:
echo "width: 80%;";
break;
case 5:
echo "width: 100%;";
break;
}
?>">
<?php
for($i=1;$i<=$rate;$i++)
{
print '★';
}
?>
</span>
</div>
</div>
</div>
</div>
<div style="display:none;" itemprop="reviewRating" itemscope="" itemtype="http://schema.org/Rating">
<span itemprop="ratingValue">
5
</span>
<span itemprop="bestRating">
5
</span>
<span itemprop="worstRating">
1
</span>
</div>
<div class="text"><span itemprop="reviewBody"><?php the_content(); ?></span></div>
</div>
</div>
</div>
<?php endwhile; ?>
<?php endif; ?>
</div>
</div>
</div>
</div>
Опять же, просто скопировал свою верстку. Стилизуйте и смотрите сами как вам надо.
Рабочий пример на сайте клиента: https://kidsprofiki.com/otzyvy/
Забыл сказать, что отзыв отправляется на модерацию и его надо еще опубликовать, чтоб он появился.
P.S. Я не претендую на лавры крутого программиста. Да, можно по-другому. Да, можно проще. Но я делюсь с вами своим решением. Возможно когда-то я все это переделаю. А пока, можете сами под себя переделывать сколько угодно )