В данной статье приведен пример системы рейтинга для любого сайта, которая работает на PHP (7.2+- [можно и меньше, но смотрите JSON encode русские символы])+MySQL (5.6+)+AJAX (jQuery 3.3.1+-).
Система устроена таким образом, что позволяет оценивать один товар/услугу/материал по пяти или одному критерию. Кроме того, формула учитывает количество проголосовавших и, в зависимости от этого количества, голос следующего человека имеет зависимое от общего числа проголосовавших значение и влияние на общий рейтинг материала.
Каким именно способом и образом Вы расположите все необходимые файлы у себя - роли не играет. Главное понять и правильно применить их все.
В базе данных должны быть следующие поля:
comm, product, user
CREATE TABLE `comm` (
`id` int(11) NOT NULL,
`text` mediumtext NOT NULL,
`prid` int(11) NOT NULL,
`ptyp` int(11) NOT NULL,
`uid` text NOT NULL COMMENT 'имя комм.',
`rait` text NOT NULL,
`date` text NOT NULL COMMENT 'дата сообщ.',
`ip` text NOT NULL,
`pub` int(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `product` (
`id` int(10) UNSIGNED NOT NULL,
`category_id` mediumtext,
`brand_id` tinyint(3) UNSIGNED NOT NULL,
`title` varchar(255) NOT NULL,
`data` mediumtext NOT NULL,
`copy_id` int(11) NOT NULL,
`alias` varchar(255) NOT NULL,
`content` text,
`shrtdsc` text NOT NULL,
`price` text NOT NULL,
`old_price` float NOT NULL DEFAULT '0',
`status` int(1) NOT NULL,
`seo_ttl` text NOT NULL,
`keywords` varchar(255) DEFAULT NULL,
`description` varchar(255) DEFAULT NULL,
`img` varchar(255) NOT NULL DEFAULT '/img/tech/_no-img.jpg',
`imgs` text NOT NULL,
`hit` int(1) NOT NULL,
`rel` text,
`uniq` text NOT NULL,
`ava` int(11) NOT NULL COMMENT 'наличие',
`prdts` text COMMENT 'из таблицы proddata id-шники',
`tbl` int(11) NOT NULL,
`szs` int(11) NOT NULL,
`colr` int(11) NOT NULL,
`sizes` text NOT NULL,
`date_pub` datetime NOT NULL,
`rait` text NOT NULL,
`dateup` datetime NOT NULL,
`uid` int(11) NOT NULL,
`pid` int(11) NOT NULL,
`chek` int(11) NOT NULL DEFAULT '1',
`uslugi` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `user` (
`id` int(10) UNSIGNED NOT NULL,
`login` varchar(255) NOT NULL,
`password` varchar(255) NOT NULL,
`email` varchar(255) NOT NULL,
`name` varchar(255) NOT NULL,
`stra` text NOT NULL,
`gor` text NOT NULL,
`fam` text NOT NULL,
`address` varchar(255) NOT NULL,
`role` int(11) NOT NULL DEFAULT '4',
`contacts` text NOT NULL,
`discount` float NOT NULL,
`qp` int(11) NOT NULL COMMENT 'кол-во товара для постщка',
`dreg` datetime NOT NULL,
`comm` int(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Пример базы данных позволит Вам, возможно, расширить свою или просто создать в той таблице, где лежат товары/материалы/услуги (`product`) поле `rait`. В нем будет храниться JSON
{"rait":3.29375,"raitmark":{"a":3.5,"b":3.125,"c":2.75,"d":3.125,"e":3.5},"raitTtl":3,"raitEvery":{"a":{"f":2,"g":0,"h":0,"i":0,"j":1},"b":{"f":1,"g":1,"h":0,"i":0,"j":1},"c":{"f":1,"g":0,"h":1,"i":0,"j":1},"d":{"f":1,"g":1,"h":0,"i":0,"j":1},"e":{"f":2,"g":0,"h":0,"i":0,"j":1}}}
Где rait - общий рейтинг
raitMark - голоса за 1-5 критериев по среднему показателю
raitTtl - проголосовало всего людей
raitEvery - проголосовало за каждый из критериев раз за каждую оценку.
Формула расчета рейтинга с примером использования функции.
<?php
function calcTtlVoteService($arr5,$rAll,$rCountTotalPeople,$service=1){
$minPls=0;
//$r=array(1,1,1,1,1);
$r=$arr5;
//var_dump($service);exit;
if($service==1) {
$a=$r[0]*50*20;
$b = $r[1] * 10 * 20;
$c = $r[2] * 10 * 20;
$d = $r[3] * 25 * 20;
$e = $r[4] * 5 * 20;//-1 действие
$sum=($a+$b+$c+$d+$e)/100;//89%-2
}else{
$a=$r[0];
$sum=$a*100/5;//89%-2
}
var_dump($sum);
$sum=$sum*5/100;//3.5 -3
var_dump($sum);
if($rAll>0)$sum=($rAll+$sum)/2;//(3.8+3.5)/2=3.65
if($sum>$rAll){$minPls=1;}//для последнего действия
if($rAll>0)$sum=$rAll-$sum;//0.15
$sum=$sum/$rCountTotalPeople;//2
if($sum<0)$sum=0-$sum;
if($rAll>0){($minPls==0)?$sum=$rAll-$sum:$sum=$rAll+$sum;}
echo $sum;
}
#пример:
calcTtlVoteService(array(1,5,5,5,5),0,1);
?>
Далее HTML код формы для голосования:
<?
$ser=0;//не услуги, а товар
if($_POST['ps']=='service'){
$ser=1;//услуги, а не товар
}
$id=(int)$_POST['id'];
?>
<div class="br3 p4 aj_commHtml">
<h3><i class="fa-comment"></i> Добавить отзыв</h3>
<div>
<textarea type="text" class="br3 txt" cols="92" placeholder="Текст сообщения" maxlength="1000"></textarea>
<span class="gry txtInputbcomm">Осталось: 1000 символов</span>
</div>
<div>
<?
//если оцениваем услугу
if($ser==1){
echo '
<div class="p4 aj_commD">
<h4 class="fB">Предлагаем Вам оценить качество предоставляемых услуг для данного поставщика</h4>
<div class="mv320 tableDiv">
<div class="table">
<div class="tr">
<div class="tbc brdrb">
Качество:
</div>
<div class="tbc brdrb">
<div class="in rate1"></div>
</div>
</div>
<div class="tr">
<div class="tbc brdrb">
Вежливость:
</div>
<div class="tbc brdrb">
<div class="in rate2"></div>
</div>
</div>
<div class="tr">
<div class="tbc brdrb">
Пунктуальность:
</div>
<div class="tbc brdrb">
<div class="in rate3"></div>
</div>
</div>
<div class="tr">
<div class="tbc brdrb">
Время отклика на заказ:
</div>
<div class="tbc brdrb">
<div class="in rate4"></div>
</div>
</div>
<div class="tr">
<div class="tbc">
Цена:
</div>
<div class="tbc">
<div class="in rate5"></div>
</div>
</div>
</div>
</div>
</div>
';
}else{
//если оцениваем товар
echo '<div class="p4 aj_commD">
<h4 class="fB">Предлагаем Вам оценить данный товар</h4>
<div class="mv320 tableDiv">
<div class="table">
<div class="tr">
<div class="tbc brdrb">
Ваша оценка:
</div>
<div class="tbc brdrb">
<div class="in rate1"></div>
</div>
</div>
</div>
</div>
</div>';
}
?>
</div>
<div class="br3 p4 ajx_err" style="display:none"></div>
<div class="br3 p4 ajx_goo" style="display:none"></div>
<div class="fc fsa fw">
<div class="br3 cp p_commBtn p_commBS" data-service="<?=$ser?>" data-id="<?=$id?>"><i class="fa-check-circle"></i> Отправить</div>
<!--<div class="br3 cp p_commBtn p_commBR"><i class="fa-refresh"></i> Обновить отзывы</div>-->
</div>
<div class="mh10 fB gry"><i class="fa-info-circle"></i> <a href="/text/comments-rules" target="_blank">Прочитайте правила для отзывов</a></div>
</div>
JS для этой формы + система звездочного рейтинга на JS
<script>
$('.p_commBS').on('click',function () {
var a,b,c,d,e,f,g=1,h=$(this).data('service'),j=$(this).data('id');
if(h==0){//product
a=$('.rate1').data('rate-value')
if(a===undefined){
$('.warning').html('Вы не оценили данный товар. Оцените его').slideDown(200)
g = 0
return false
}
f=[a]
}else {//service
a = $('.rate1').data('rate-value')
b = $('.rate2').data('rate-value')
c = $('.rate3').data('rate-value')
d = $('.rate4').data('rate-value')
e = $('.rate5').data('rate-value')
f = [a, b, c, d, e]
$.each(f, function (k, v) {
if (v === undefined) {
$('.warning').html('Вы не оценили один из критериев или несколько, оцените его/их').slideDown(200)
g = 0
return false
} else {
$('.warning').slideUp(100)
}
})
};//\service
if($('.aj_commHtml .txt').val().length<21)$('.warning').html('Текст отзыва должен содержать не менее 20 символов. Заполните текстовое поле.').slideDown(100),g=0
if(g==1){
$.ajax({
url: '/ajx',
data: {location_:location.href,set:'setFrmComm',vote:f,txt:$('.aj_commHtml .txt').val(),service:h,id:j},
type: 'POST',
beforeSend:function () {
$('.loading').slideDown(0);
},
success: function(d){
console.log(d)
$('.ajx_goo,.ajx_err').slideUp(50)
var jsn=JSON.parse(d);
if(jsn.txt=='success'){
$('.ajx_goo').slideDown(100).html('<i class="fa-bullhorn"></i> Ваш отзыв успешно добавлен. После проверки нашими сотрудниками, он повится на сайте. Ожидайте...<br>Благодарим за взаимодействие с нами!')
$('.p_loadComFrm').first().after(jsn.comment)
$('.txt,.p_commBS,.p_btnLdFrmCom,.aj_commD').slideUp(50, function() {
$('.txt,.p_commBS,.p_btnLdFrmCom,.aj_commHtml h3,.txtInputbcomm,.aj_commD').remove()
});
$('html, body').animate({
scrollTop:$('.ajx_otz').offset().top-$(window).height()/2
}, 1000);
g=0
}
if(jsn.txt!='success'){
$('.ajx_err').slideDown(100).html('<i class="fa-ban"></i> '+jsn.txt)
g=1
}
$('.loading').slideUp(0);
return false
},
error: function(){
alert('Ошибка. Попробуйте позже...');
$('.loading').slideUp(0);
}
});
}
});
//Count how many characters left
$('.txt').on('keyup paste change input propertychange',function(){
countTxt($('.txt'),$('.txtInputbcomm'));
if($('.aj_commHtml .txt').val().length>20)$('.warning').slideUp(100)
});
function countTxt(id,span){
var txtl =id.val().length;
var tl=1000-txtl;
if(tl>0){
span.text('Осталось символов: '+tl)
}else{span.text('Введено максимальное количество символов: '+txtl)}
};
//\//Count how many characters left
//rait
/*
* A highly customizable rating widget that supports images, utf8 glyphs and other html elements!
* https://github.com/auxiliary/rater
*/
$.fn.textWidth = function()
{
var html_calc = $('<span>' + $(this).html() + '</span>');
html_calc.css('font-size',$(this).css('font-size')).hide();
html_calc.prependTo('body');
var width = html_calc.width();
html_calc.remove();
if (width == 0)
{
var total = 0;
$(this).eq(0).children().each(function(){
total += $(this).textWidth();
});
return total;
}
return width;
};
$.fn.textHeight = function()
{
var html_calc = $('<span>' + $(this).html() + '</span>');
html_calc.css('font-size',$(this).css('font-size')).hide();
html_calc.prependTo('body');
var height = html_calc.height();
html_calc.remove();
return height;
};
/*
* IE8 doesn't support isArray!
*/
Array.isArray = function (obj) {
return Object.prototype.toString.call(obj) === "[object Array]";
};
/*
* Utf-32 isn't supported by default, so we have to use Utf-8 surrogates
*/
String.prototype.getbcodePointLength = function() {
return this.length-this.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g).length+1;
};
String.fromCodePoint= function() {
var chars= Array.prototype.slice.call(arguments);
for (var i= chars.length; i-->0;) {
var n = chars[i]-0x10000;
if (n>=0)
chars.splice(i, 1, 0xD800+(n>>10), 0xDC00+(n&0x3FF));
}
return String.fromCharCode.apply(null, chars);
};
/*
* Starting the plugin itself
*/
$.fn.rate = function(options)
{
if (options === undefined || typeof options === 'object')
{
return this.each(function(){
if (!$.data(this, "rate"))
{
$.data(this, "rate", new Rate(this, options));
}
});
}
else if (typeof options === 'string')
{
var args = arguments;
var returns;
this.each(function(){
var instance = $.data(this, "rate");
if (instance instanceof Rate && typeof instance[options] === 'function')
{
returns = instance[options].apply(instance, Array.prototype.slice.call(args, 1));
}
if (options === 'destroy')
{
// Unbind all events and empty the plugin data from instance
$(instance.element).off();
$.data(this, 'rate', null);
}
});
return returns !== undefined ? returns : this;
}
};
function Rate(element, options)
{
this.element = element;
this.settings = $.extend({}, $.fn.rate.settings, options);
this.set_faces = {}; // value, symbol pairs
this.build();
}
Rate.prototype.build = function()
{
this.layers = {};
this.value = 0;
this.raise_select_layer = false;
if (this.settings.initial_value)
{
this.value = this.settings.initial_value;
}
if ($(this.element).attr("data-rate-value"))
{
this.value = $(this.element).attr("data-rate-value");
}
/*
* Calculate the selected width based on the initial value
*/
var selected_width = this.value / this.settings.max_value * 100;
/*
* Let's support single strings as symbols as well as objects
*/
if (typeof this.settings.symbols[this.settings.selected_symbol_type] === 'string')
{
var symbol = this.settings.symbols[this.settings.selected_symbol_type];
this.settings.symbols[this.settings.selected_symbol_type] = {};
this.settings.symbols[this.settings.selected_symbol_type]['base'] = symbol;
this.settings.symbols[this.settings.selected_symbol_type]['selected'] = symbol;
this.settings.symbols[this.settings.selected_symbol_type]['hover'] = symbol;
}
/*
* Making the three main layers (base, select, hover)
*/
var base_layer = this.addLayer("base-layer t", 100, this.settings.symbols[
this.settings.selected_symbol_type]["base"], true);
var select_layer = this.addLayer("select-layer t", selected_width,
this.settings.symbols[this.settings.selected_symbol_type]["selected"], true);
var hover_layer = this.addLayer("hover-layer t", 0, this.settings.symbols[
this.settings.selected_symbol_type]["hover"], false);
/* var face_layer = this.addLayer("face-layer", 1, this.settings
.symbols[this.settings.face_layer_symbol_type][0], true); */
this.layers["base_layer"] = base_layer;
this.layers["select_layer"] = select_layer;
this.layers["hover_layer"] = hover_layer;
/*
* Bind the container to some events
*/
$(this.element).on("mousemove", $.proxy(this.hover, this));
$(this.element).on("click", $.proxy(this.select, this));
$(this.element).on("mouseleave", $.proxy(this.mouseout, this));
/*
* Set the main element as unselectable
*/
$(this.element).css({
"-webkit-touch-callout": "none",
"-webkit-user-select": "none",
"-khtml-user-select": "none",
"-moz-user-select": "none",
"-ms-user-select": "none",
"user-select": "none",
});
/*
* Update custom input field if provided
*/
if (this.settings.hasOwnProperty("update_input_field_name"))
{
this.settings.update_input_field_name.val(this.value);
}
}
/*
* Function to add a layer
*/
Rate.prototype.addLayer = function(layer_name, visible_width, symbol, visible)
{
var layer_body = "<div>";
for (var i = 0; i < this.settings.max_value; i++)
{
if (Array.isArray(symbol))
{
if (this.settings.convert_to_utf8)
{
symbol[i] = String.fromCodePoint(symbol[i]);
}
layer_body += "<span>" + (symbol[i]) + "</span>";
}
else
{
if (this.settings.convert_to_utf8)
{
symbol = String.fromCodePoint(symbol);
}
layer_body += "<span>" + symbol + "</span>";
}
}
layer_body += "</div>";
var layer = $(layer_body).addClass("rate-" + layer_name).appendTo(this.element);
$(layer).css({
width: visible_width + "%",
height: $(layer).children().eq(0).textHeight(),
overflow: 'hidden',
position: 'absolute',
top: 0,
display: visible ? 'block' : 'none',
'white-space': 'nowrap'
});
$(this.element).css({
width: $(layer).textWidth() + "px",
height: $(layer).height(),
position: 'relative',
cursor: this.settings.cursor,
});
return layer;
}
Rate.prototype.updateServer = function()
{
if (this.settings.url != undefined)
{
$.ajax({
url: this.settings.url,
type: this.settings.ajax_method,
data: $.extend({}, { value: this.getValue() }, this.settings.additional_data),
success: $.proxy(function(data){
$(this.element).trigger("updateSuccess", [data]);
}, this),
error: $.proxy(function(jxhr, msg, err){
$(this.element).trigger("updateError", [jxhr, msg, err]);
}, this)
});
}
}
Rate.prototype.getValue = function()
{
return this.value;
}
Rate.prototype.hover = function(ev)
{
var pad = parseInt($(this.element).css("padding-left").replace("px", ""));
var x = ev.pageX - $(this.element).offset().left - pad;
var val = this.toValue(x, true);
if (val != this.value)
{
this.raise_select_layer = false;
}
if (!this.raise_select_layer && !this.settings.readonly)
{
var visible_width = this.toWidth(val);
this.layers.select_layer.css({display: 'none'});
if (!this.settings.only_select_one_symbol)
{
this.layers.hover_layer.css({
width: visible_width + "%",
display: 'block'
});
}
else
{
var index_value = Math.floor(val);
this.layers.hover_layer.css({
width: "100%",
display: 'block'
});
this.layers.hover_layer.children("span").css({
visibility: 'hidden',
});
this.layers.hover_layer.children("span").eq(index_value != 0 ? index_value - 1 : 0).css({
visibility: 'visible',
});
}
}
}
/*
* Event for when a rating has been selected (clicked)
*/
Rate.prototype.select = function(ev)
{
if (!this.settings.readonly)
{
var old_value = this.getValue();
var pad = parseInt($(this.element).css("padding-left").replace("px", ""));
var x = ev.pageX - $(this.element).offset().left - pad;
var selected_width = this.toWidth(this.toValue(x, true));
this.setValue(this.toValue(selected_width));
this.raise_select_layer = true;
}
}
Rate.prototype.mouseout = function()
{
this.layers.hover_layer.css({display: 'none'});
this.layers.select_layer.css({display: 'block'});
}
/*
* Takes a width (px) and returns the value it resembles
*/
Rate.prototype.toWidth = function(val)
{
return val / this.settings.max_value * 100;
}
/*
* Takes a value and calculates the width of the selected/hovered layer
*/
Rate.prototype.toValue = function(width, in_pixels)
{
var val;
if (in_pixels)
{
val = width / this.layers.base_layer.textWidth() * this.settings.max_value;
}
else
{
val = width / 100 * this.settings.max_value;
}
// Make sure the division doesn't cause some small numbers added by
// comparing to a small arbitrary number.
var temp = val / this.settings.step_size;
if (temp - Math.floor(temp) < 0.00005)
{
val = Math.round(val / this.settings.step_size) * this.settings.step_size;
}
val = (Math.ceil(val / this.settings.step_size)) * this.settings.step_size;
val = val > this.settings.max_value ? this.settings.max_value : val;
return val;
}
Rate.prototype.getElement = function(layer_name, index)
{
return $(this.element).find(".rate-" + layer_name + " span").eq(index - 1);
}
Rate.prototype.getLayers = function()
{
return this.layers;
}
Rate.prototype.setFace = function(value, face)
{
this.set_faces[value] = face;
}
Rate.prototype.setAdditionalData = function(data)
{
this.settings.additional_data = data;
}
Rate.prototype.getAdditionalData = function()
{
return this.settings.additional_data;
}
Rate.prototype.removeFace = function(value)
{
delete this.set_faces[value];
}
Rate.prototype.setValue = function(value)
{
if (!this.settings.readonly)
{
if (value < 0)
{
value = 0;
}
else if (value > this.settings.max_value)
{
value = this.settings.max_value;
}
var old_value = this.getValue();
this.value = value;
/*
* About to change event, should support prevention later
*/
var change_event = $(this.element).trigger("change", {
"from": old_value,
"to": this.value
});
/*
* Set/Reset faces
*/
$(this.element).find(".rate-face").remove();
$(this.element).find("span").css({
visibility: 'visible'
});
var index_value = Math.ceil(this.value);
if (this.set_faces.hasOwnProperty(index_value))
{
var face = "<div>" + this.set_faces[index_value] + "</div>";
var base_layer_element = this.getElement('base-layer', index_value);
var select_layer_element = this.getElement('select-layer', index_value);
var hover_layer_element = this.getElement('hover-layer', index_value);
var left_pos = base_layer_element.textWidth() * (index_value - 1)
+ (base_layer_element.textWidth() - $(face).textWidth()) / 2;
$(face).appendTo(this.element).css({
display: 'inline-block',
position: 'absolute',
left: left_pos,
}).addClass("rate-face");
base_layer_element.css({
visibility: 'hidden'
});
select_layer_element.css({
visibility: 'hidden'
});
hover_layer_element.css({
visibility: 'hidden'
});
}
/*
* Set styles based on width and value
*/
if (!this.settings.only_select_one_symbol)
{
var width = this.toWidth(this.value);
this.layers.select_layer.css({
display: 'block',
width: width + "%",
height: this.layers.base_layer.css("height")
});
this.layers.hover_layer.css({
display: 'none',
height: this.layers.base_layer.css("height")
});
}
else
{
var width = this.toWidth(this.settings.max_value);
this.layers.select_layer.css({
display: 'block',
width: width + "%",
height: this.layers.base_layer.css("height")
});
this.layers.hover_layer.css({
display: 'none',
height: this.layers.base_layer.css("height")
});
this.layers.select_layer.children("span").css({
visibility: 'hidden',
});
this.layers.select_layer.children("span").eq(index_value != 0 ? index_value - 1 : 0).css({
visibility: 'visible',
});
}
// Update the data-rate-value attribute
$(this.element).attr("data-rate-value", this.value);
if (this.settings.change_once)
{
this.settings.readonly = true;
}
this.updateServer();
var change_event = $(this.element).trigger("afterChange", {
"from": old_value,
"to": this.value
});
/*
* Update custom input field if provided
*/
if (this.settings.hasOwnProperty("update_input_field_name"))
{
this.settings.update_input_field_name.val(this.value);
}
}
}
Rate.prototype.increment = function()
{
this.setValue(this.getValue() + this.settings.step_size);
}
Rate.prototype.decrement = function()
{
this.setValue(this.getValue() - this.settings.step_size);
}
$.fn.rate.settings = {
max_value: 5,
step_size: 0.5,
initial_value: 0,
symbols: {
fontawesome_star: {
base: '<i class="fa fa-star-o"></i>',
hover: '<i class="fa fa-star"></i>',
selected: '<i class="fa fa-star"></i>',
}
},
selected_symbol_type: 'utf8_star', // Must be a key from symbols
convert_to_utf8: false,
cursor: 'default',
readonly: false,
change_once: false, // Determines if the rating can only be set once
only_select_one_symbol: false, // If set to true, only selects the hovered/selected symbol and nothing prior to it
ajax_method: 'POST',
additional_data: {}, // Additional data to send to the server
//update_input_field_name = some input field set by the user
};
//load rait
var options = {
max_value: 6,
step_size: 0.5,
selected_symbol_type: 'hearts',
url: 'http://localhost/test.php',
initial_value: 3,
update_input_field_name: $("#input2"),
}
<?
if($ser==1){//services
echo '$(".rate1,.rate2,.rate3,.rate4,.rate5").rate({
selected_symbol_type: 'fontawesome_star',
max_value: 5,
step_size: 1
});';
}else{//product
echo '$(".rate1").rate({
selected_symbol_type: 'fontawesome_star',
max_value: 5,
step_size: 1
});';
}
?>
//\rait
</script>
Как можете видеть, сами звездочки взяты с GitHub (данный архив будет в архиве с другими файлами ниже), и подключаются с помощью JS, который между //rait ... //\rait в коде выше.
Подключать звездочки необходимо так:
Скачиваете архив (ниже), достаете оттуда stars.zip, распаковываете, берете файл JS rater.js или его уменьшенную версию rater.min.js
Подключаете к странице jQuery (можно и другую версию > 3, если есть уже на странице, то подключите только rater.js) и файл rater.js
<script src="https://code.jquery.com/jquery-1.11.3.min.js" charset="utf-8"></script>
<script src="/rater.js"></script>
Далее, на странице, где необходимо отобразить эти звездочки добавим JS код:
$.fn.rate.settings = {
max_value: 5,
step_size: 0.5,
initial_value: 0,
symbols: {
fontawesome_star: {
base: '<i class="fa fa-star-o"></i>',
hover: '<i class="fa fa-star"></i>',
selected: '<i class="fa fa-star"></i>',
}
},
selected_symbol_type: 'utf8_star', // Must be a key from symbols
convert_to_utf8: false,
cursor: 'default',
readonly: false,
change_once: false, // Determines if the rating can only be set once
only_select_one_symbol: false, // If set to true, only selects the hovered/selected symbol and nothing prior to it
ajax_method: 'POST',
additional_data: {}, // Additional data to send to the server
//update_input_field_name = some input field set by the user
};
$(".rate1").rate({
selected_symbol_type: 'fontawesome_star',
max_value: 5,
step_size: 1
});
Обратите внимание! Подключен шрифт fontawesome
Вы можете создать свой класс для звездочек, нарисовать их или взять готовое изображение и фантазировать как угодно, но мне было удобно взять именно FA, т.к. он уже был на сайте.
Также необходимо подключить стили
<link rel="stylesheet" href="/css/styleRater.css">
Файлы HTML+JS+CSS лежат в папке DEMO
stars.zip — это https://github.com/auxiliary/rater
first.sql – это пример базы данных, с DEMO данными
Файлы PHP даны в пример выполнения сценария PHP.
А именно, добавлены файлы, которые так или иначе имеют отношение к системе отзывов на сайте, который показан в видео и речь о котором идет в этом материале.
Итак, как Вы подгрузите форму для комментариев – решать Вам. То ли это будет AJAX запрос, то ли – готовый вывод через PHP.
Важно просто соблюсти HTML, а также подключить JS, CSS, и постараться изменить CSS, если названия классов совпадают.
Логика выполнения скрипта у меня:
1. commHtml.php
<?
#здесь важно все. ниже - это 5 звезд или 1, услуга - 5, товар - одна
$ser=0;//не услуги, а товар
if($_POST['ps']=='service'){
$ser=1;//услуги, а не товар
}
$id=(int)$_POST['id'];
?>
<div class="br3 p4 aj_commHtml">
<h3><i class="fa-comment"></i> Добавить отзыв</h3>
<div>
<textarea type="text" class="br3 txt" cols="92" placeholder="Текст сообщения" maxlength="1000"></textarea>
<span class="gry txtInputbcomm">Осталось: 1000 символов</span>
</div>
<div>
<?
//если оцениваем услугу
if($ser==1){
echo '
<div class="p4 aj_commD">
<h4 class="fB">Предлагаем Вам оценить качество предоставляемых услуг для данного поставщика</h4>
<div class="mv320 tableDiv">
<div class="table">
<div class="tr">
<div class="tbc brdrb">
Качество:
</div>
<div class="tbc brdrb">
<div class="in rate1"></div>
</div>
</div>
<div class="tr">
<div class="tbc brdrb">
Вежливость:
</div>
<div class="tbc brdrb">
<div class="in rate2"></div>
</div>
</div>
<div class="tr">
<div class="tbc brdrb">
Пунктуальность:
</div>
<div class="tbc brdrb">
<div class="in rate3"></div>
</div>
</div>
<div class="tr">
<div class="tbc brdrb">
Время отклика на заказ:
</div>
<div class="tbc brdrb">
<div class="in rate4"></div>
</div>
</div>
<div class="tr">
<div class="tbc">
Цена:
</div>
<div class="tbc">
<div class="in rate5"></div>
</div>
</div>
</div>
</div>
</div>
';
}else{
//если оцениваем товар
echo '<div class="p4 aj_commD">
<h4 class="fB">Предлагаем Вам оценить данный товар</h4>
<div class="mv320 tableDiv">
<div class="table">
<div class="tr">
<div class="tbc brdrb">
Ваша оценка:
</div>
<div class="tbc brdrb">
<div class="in rate1"></div>
</div>
</div>
</div>
</div>
</div>';
}
?>
</div>
<div class="br3 p4 ajx_err" style="display:none"></div>
<div class="br3 p4 ajx_goo" style="display:none"></div>
<div class="fc fsa fw">
<div class="br3 cp p_commBtn p_commBS" data-service="<?=$ser?>" data-id="<?=$id?>"><i class="fa-check-circle"></i> Отправить</div>
<!--<div class="br3 cp p_commBtn p_commBR"><i class="fa-refresh"></i> Обновить отзывы</div>-->
</div>
<div class="mh10 fB gry"><i class="fa-info-circle"></i> <a href="/text/comments-rules" target="_blank">Прочитайте правила для отзывов</a></div>
</div>
<script>
$('.p_commBS').on('click',function () {
var a,b,c,d,e,f,g=1,h=$(this).data('service'),j=$(this).data('id');
if(h==0){//product
a=$('.rate1').data('rate-value')
if(a===undefined){
$('.warning').html('Вы не оценили данный товар. Оцените его').slideDown(200)
g = 0
return false
}
f=[a]
}else {//service
a = $('.rate1').data('rate-value')
b = $('.rate2').data('rate-value')
c = $('.rate3').data('rate-value')
d = $('.rate4').data('rate-value')
e = $('.rate5').data('rate-value')
f = [a, b, c, d, e]
$.each(f, function (k, v) {
if (v === undefined) {
$('.warning').html('Вы не оценили один из критериев или несколько, оцените его/их').slideDown(200)
g = 0
return false
} else {
$('.warning').slideUp(100)
}
})
};//\service
if($('.aj_commHtml .txt').val().length<21)$('.warning').html('Текст отзыва должен содержать не менее 20 символов. Заполните текстовое поле.').slideDown(100),g=0
if(g==1){
$.ajax({
url: '/ajx',
data: {location_:location.href,set:'setFrmComm',vote:f,txt:$('.aj_commHtml .txt').val(),service:h,id:j},
type: 'POST',
beforeSend:function () {
$('.loading').slideDown(0);
},
success: function(d){
console.log(d)
$('.ajx_goo,.ajx_err').slideUp(50)
var jsn=JSON.parse(d);
if(jsn.txt=='success'){
$('.ajx_goo').slideDown(100).html('<i class="fa-bullhorn"></i> Ваш отзыв успешно добавлен. После проверки нашими сотрудниками, он повится на сайте. Ожидайте...<br>Благодарим за взаимодействие с нами!')
$('.p_loadComFrm').first().after(jsn.comment)
$('.txt,.p_commBS,.p_btnLdFrmCom,.aj_commD').slideUp(50, function() {
$('.txt,.p_commBS,.p_btnLdFrmCom,.aj_commHtml h3,.txtInputbcomm,.aj_commD').remove()
});
$('html, body').animate({
scrollTop:$('.ajx_otz').offset().top-$(window).height()/2
}, 1000);
g=0
}
if(jsn.txt!='success'){
$('.ajx_err').slideDown(100).html('<i class="fa-ban"></i> '+jsn.txt)
g=1
}
$('.loading').slideUp(0);
return false
},
error: function(){
alert('Ошибка. Попробуйте позже...');
$('.loading').slideUp(0);
}
});
}
});
//Count how many characters left
$('.txt').on('keyup paste change input propertychange',function(){
countTxt($('.txt'),$('.txtInputbcomm'));
if($('.aj_commHtml .txt').val().length>20)$('.warning').slideUp(100)
});
function countTxt(id,span){
var txtl =id.val().length;
var tl=1000-txtl;
if(tl>0){
span.text('Осталось символов: '+tl)
}else{span.text('Введено максимальное количество символов: '+txtl)}
};
//\//Count how many characters left
//rait
/*
* A highly customizable rating widget that supports images, utf8 glyphs and other html elements!
* https://github.com/auxiliary/rater
*/
$.fn.textWidth = function()
{
var html_calc = $('<span>' + $(this).html() + '</span>');
html_calc.css('font-size',$(this).css('font-size')).hide();
html_calc.prependTo('body');
var width = html_calc.width();
html_calc.remove();
if (width == 0)
{
var total = 0;
$(this).eq(0).children().each(function(){
total += $(this).textWidth();
});
return total;
}
return width;
};
$.fn.textHeight = function()
{
var html_calc = $('<span>' + $(this).html() + '</span>');
html_calc.css('font-size',$(this).css('font-size')).hide();
html_calc.prependTo('body');
var height = html_calc.height();
html_calc.remove();
return height;
};
/*
* IE8 doesn't support isArray!
*/
Array.isArray = function (obj) {
return Object.prototype.toString.call(obj) === "[object Array]";
};
/*
* Utf-32 isn't supported by default, so we have to use Utf-8 surrogates
*/
String.prototype.getbcodePointLength = function() {
return this.length-this.split(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g).length+1;
};
String.fromCodePoint= function() {
var chars= Array.prototype.slice.call(arguments);
for (var i= chars.length; i-->0;) {
var n = chars[i]-0x10000;
if (n>=0)
chars.splice(i, 1, 0xD800+(n>>10), 0xDC00+(n&0x3FF));
}
return String.fromCharCode.apply(null, chars);
};
/*
* Starting the plugin itself
*/
$.fn.rate = function(options)
{
if (options === undefined || typeof options === 'object')
{
return this.each(function(){
if (!$.data(this, "rate"))
{
$.data(this, "rate", new Rate(this, options));
}
});
}
else if (typeof options === 'string')
{
var args = arguments;
var returns;
this.each(function(){
var instance = $.data(this, "rate");
if (instance instanceof Rate && typeof instance[options] === 'function')
{
returns = instance[options].apply(instance, Array.prototype.slice.call(args, 1));
}
if (options === 'destroy')
{
// Unbind all events and empty the plugin data from instance
$(instance.element).off();
$.data(this, 'rate', null);
}
});
return returns !== undefined ? returns : this;
}
};
function Rate(element, options)
{
this.element = element;
this.settings = $.extend({}, $.fn.rate.settings, options);
this.set_faces = {}; // value, symbol pairs
this.build();
}
Rate.prototype.build = function()
{
this.layers = {};
this.value = 0;
this.raise_select_layer = false;
if (this.settings.initial_value)
{
this.value = this.settings.initial_value;
}
if ($(this.element).attr("data-rate-value"))
{
this.value = $(this.element).attr("data-rate-value");
}
/*
* Calculate the selected width based on the initial value
*/
var selected_width = this.value / this.settings.max_value * 100;
/*
* Let's support single strings as symbols as well as objects
*/
if (typeof this.settings.symbols[this.settings.selected_symbol_type] === 'string')
{
var symbol = this.settings.symbols[this.settings.selected_symbol_type];
this.settings.symbols[this.settings.selected_symbol_type] = {};
this.settings.symbols[this.settings.selected_symbol_type]['base'] = symbol;
this.settings.symbols[this.settings.selected_symbol_type]['selected'] = symbol;
this.settings.symbols[this.settings.selected_symbol_type]['hover'] = symbol;
}
/*
* Making the three main layers (base, select, hover)
*/
var base_layer = this.addLayer("base-layer t", 100, this.settings.symbols[
this.settings.selected_symbol_type]["base"], true);
var select_layer = this.addLayer("select-layer t", selected_width,
this.settings.symbols[this.settings.selected_symbol_type]["selected"], true);
var hover_layer = this.addLayer("hover-layer t", 0, this.settings.symbols[
this.settings.selected_symbol_type]["hover"], false);
/* var face_layer = this.addLayer("face-layer", 1, this.settings
.symbols[this.settings.face_layer_symbol_type][0], true); */
this.layers["base_layer"] = base_layer;
this.layers["select_layer"] = select_layer;
this.layers["hover_layer"] = hover_layer;
/*
* Bind the container to some events
*/
$(this.element).on("mousemove", $.proxy(this.hover, this));
$(this.element).on("click", $.proxy(this.select, this));
$(this.element).on("mouseleave", $.proxy(this.mouseout, this));
/*
* Set the main element as unselectable
*/
$(this.element).css({
"-webkit-touch-callout": "none",
"-webkit-user-select": "none",
"-khtml-user-select": "none",
"-moz-user-select": "none",
"-ms-user-select": "none",
"user-select": "none",
});
/*
* Update custom input field if provided
*/
if (this.settings.hasOwnProperty("update_input_field_name"))
{
this.settings.update_input_field_name.val(this.value);
}
}
/*
* Function to add a layer
*/
Rate.prototype.addLayer = function(layer_name, visible_width, symbol, visible)
{
var layer_body = "<div>";
for (var i = 0; i < this.settings.max_value; i++)
{
if (Array.isArray(symbol))
{
if (this.settings.convert_to_utf8)
{
symbol[i] = String.fromCodePoint(symbol[i]);
}
layer_body += "<span>" + (symbol[i]) + "</span>";
}
else
{
if (this.settings.convert_to_utf8)
{
symbol = String.fromCodePoint(symbol);
}
layer_body += "<span>" + symbol + "</span>";
}
}
layer_body += "</div>";
var layer = $(layer_body).addClass("rate-" + layer_name).appendTo(this.element);
$(layer).css({
width: visible_width + "%",
height: $(layer).children().eq(0).textHeight(),
overflow: 'hidden',
position: 'absolute',
top: 0,
display: visible ? 'block' : 'none',
'white-space': 'nowrap'
});
$(this.element).css({
width: $(layer).textWidth() + "px",
height: $(layer).height(),
position: 'relative',
cursor: this.settings.cursor,
});
return layer;
}
Rate.prototype.updateServer = function()
{
if (this.settings.url != undefined)
{
$.ajax({
url: this.settings.url,
type: this.settings.ajax_method,
data: $.extend({}, { value: this.getValue() }, this.settings.additional_data),
success: $.proxy(function(data){
$(this.element).trigger("updateSuccess", [data]);
}, this),
error: $.proxy(function(jxhr, msg, err){
$(this.element).trigger("updateError", [jxhr, msg, err]);
}, this)
});
}
}
Rate.prototype.getValue = function()
{
return this.value;
}
Rate.prototype.hover = function(ev)
{
var pad = parseInt($(this.element).css("padding-left").replace("px", ""));
var x = ev.pageX - $(this.element).offset().left - pad;
var val = this.toValue(x, true);
if (val != this.value)
{
this.raise_select_layer = false;
}
if (!this.raise_select_layer && !this.settings.readonly)
{
var visible_width = this.toWidth(val);
this.layers.select_layer.css({display: 'none'});
if (!this.settings.only_select_one_symbol)
{
this.layers.hover_layer.css({
width: visible_width + "%",
display: 'block'
});
}
else
{
var index_value = Math.floor(val);
this.layers.hover_layer.css({
width: "100%",
display: 'block'
});
this.layers.hover_layer.children("span").css({
visibility: 'hidden',
});
this.layers.hover_layer.children("span").eq(index_value != 0 ? index_value - 1 : 0).css({
visibility: 'visible',
});
}
}
}
/*
* Event for when a rating has been selected (clicked)
*/
Rate.prototype.select = function(ev)
{
if (!this.settings.readonly)
{
var old_value = this.getValue();
var pad = parseInt($(this.element).css("padding-left").replace("px", ""));
var x = ev.pageX - $(this.element).offset().left - pad;
var selected_width = this.toWidth(this.toValue(x, true));
this.setValue(this.toValue(selected_width));
this.raise_select_layer = true;
}
}
Rate.prototype.mouseout = function()
{
this.layers.hover_layer.css({display: 'none'});
this.layers.select_layer.css({display: 'block'});
}
/*
* Takes a width (px) and returns the value it resembles
*/
Rate.prototype.toWidth = function(val)
{
return val / this.settings.max_value * 100;
}
/*
* Takes a value and calculates the width of the selected/hovered layer
*/
Rate.prototype.toValue = function(width, in_pixels)
{
var val;
if (in_pixels)
{
val = width / this.layers.base_layer.textWidth() * this.settings.max_value;
}
else
{
val = width / 100 * this.settings.max_value;
}
// Make sure the division doesn't cause some small numbers added by
// comparing to a small arbitrary number.
var temp = val / this.settings.step_size;
if (temp - Math.floor(temp) < 0.00005)
{
val = Math.round(val / this.settings.step_size) * this.settings.step_size;
}
val = (Math.ceil(val / this.settings.step_size)) * this.settings.step_size;
val = val > this.settings.max_value ? this.settings.max_value : val;
return val;
}
Rate.prototype.getElement = function(layer_name, index)
{
return $(this.element).find(".rate-" + layer_name + " span").eq(index - 1);
}
Rate.prototype.getLayers = function()
{
return this.layers;
}
Rate.prototype.setFace = function(value, face)
{
this.set_faces[value] = face;
}
Rate.prototype.setAdditionalData = function(data)
{
this.settings.additional_data = data;
}
Rate.prototype.getAdditionalData = function()
{
return this.settings.additional_data;
}
Rate.prototype.removeFace = function(value)
{
delete this.set_faces[value];
}
Rate.prototype.setValue = function(value)
{
if (!this.settings.readonly)
{
if (value < 0)
{
value = 0;
}
else if (value > this.settings.max_value)
{
value = this.settings.max_value;
}
var old_value = this.getValue();
this.value = value;
/*
* About to change event, should support prevention later
*/
var change_event = $(this.element).trigger("change", {
"from": old_value,
"to": this.value
});
/*
* Set/Reset faces
*/
$(this.element).find(".rate-face").remove();
$(this.element).find("span").css({
visibility: 'visible'
});
var index_value = Math.ceil(this.value);
if (this.set_faces.hasOwnProperty(index_value))
{
var face = "<div>" + this.set_faces[index_value] + "</div>";
var base_layer_element = this.getElement('base-layer', index_value);
var select_layer_element = this.getElement('select-layer', index_value);
var hover_layer_element = this.getElement('hover-layer', index_value);
var left_pos = base_layer_element.textWidth() * (index_value - 1)
+ (base_layer_element.textWidth() - $(face).textWidth()) / 2;
$(face).appendTo(this.element).css({
display: 'inline-block',
position: 'absolute',
left: left_pos,
}).addClass("rate-face");
base_layer_element.css({
visibility: 'hidden'
});
select_layer_element.css({
visibility: 'hidden'
});
hover_layer_element.css({
visibility: 'hidden'
});
}
/*
* Set styles based on width and value
*/
if (!this.settings.only_select_one_symbol)
{
var width = this.toWidth(this.value);
this.layers.select_layer.css({
display: 'block',
width: width + "%",
height: this.layers.base_layer.css("height")
});
this.layers.hover_layer.css({
display: 'none',
height: this.layers.base_layer.css("height")
});
}
else
{
var width = this.toWidth(this.settings.max_value);
this.layers.select_layer.css({
display: 'block',
width: width + "%",
height: this.layers.base_layer.css("height")
});
this.layers.hover_layer.css({
display: 'none',
height: this.layers.base_layer.css("height")
});
this.layers.select_layer.children("span").css({
visibility: 'hidden',
});
this.layers.select_layer.children("span").eq(index_value != 0 ? index_value - 1 : 0).css({
visibility: 'visible',
});
}
// Update the data-rate-value attribute
$(this.element).attr("data-rate-value", this.value);
if (this.settings.change_once)
{
this.settings.readonly = true;
}
this.updateServer();
var change_event = $(this.element).trigger("afterChange", {
"from": old_value,
"to": this.value
});
/*
* Update custom input field if provided
*/
if (this.settings.hasOwnProperty("update_input_field_name"))
{
this.settings.update_input_field_name.val(this.value);
}
}
}
Rate.prototype.increment = function()
{
this.setValue(this.getValue() + this.settings.step_size);
}
Rate.prototype.decrement = function()
{
this.setValue(this.getValue() - this.settings.step_size);
}
$.fn.rate.settings = {
max_value: 5,
step_size: 0.5,
initial_value: 0,
symbols: {
fontawesome_star: {
base: '<i class="fa fa-star-o"></i>',
hover: '<i class="fa fa-star"></i>',
selected: '<i class="fa fa-star"></i>',
}
},
selected_symbol_type: 'utf8_star', // Must be a key from symbols
convert_to_utf8: false,
cursor: 'default',
readonly: false,
change_once: false, // Determines if the rating can only be set once
only_select_one_symbol: false, // If set to true, only selects the hovered/selected symbol and nothing prior to it
ajax_method: 'POST',
additional_data: {}, // Additional data to send to the server
//update_input_field_name = some input field set by the user
};
//load rait
<?
if($ser==1){//services
echo '$(".rate1,.rate2,.rate3,.rate4,.rate5").rate({
selected_symbol_type: \'fontawesome_star\',
max_value: 5,
step_size: 1
});';
}else{//product
echo '$(".rate1").rate({
selected_symbol_type: \'fontawesome_star\',
max_value: 5,
step_size: 1
});';
}
?>
//\rait
</script>
<style>
.aj_commHtml{background:#efffce}
.aj_commHtml h3{font:27px/1 Bebas,Arial,sans}
.aj_commD{background: #f8ffc2}
</style>
2. AjxController.php – через него происходит обработка запроса
<?
if($_SERVER['REQUEST_METHOD']=='POST' && $_SERVER['HTTP_X_REQUESTED_WITH']=='XMLHttpRequest'){
//set comm
if(!empty($_POST['set'])){
if($_POST['set'] == 'setFrmComm'){
$serOrProd = (int)AppController::clearCodeAll($_POST['service']);//1-service,0-product
if ($serOrProd==1) {
if (is_array($_POST['vote'])) {
foreach ($_POST['vote'] as $k => $v) {
$v = (int)$v;
if (empty($v)) {
echo '{"txt":"Вы не оценили один или несколько пунктов"}';
exit;
}
if ($v > 5 OR $v < 1) {
echo '{"txt":"Вы не оценили один или несколько пунктов"}';
exit;
}
}
}
$vote = array(
'a' => AppController::clearCodeAll($_POST['vote'][0]),
'b' => AppController::clearCodeAll($_POST['vote'][1]),
'c' => AppController::clearCodeAll($_POST['vote'][2]),
'd' => AppController::clearCodeAll($_POST['vote'][3]),
'e' => AppController::clearCodeAll($_POST['vote'][4])
);
}else{
if(!empty(AppController::clearCodeAll($_POST['vote'][0]))) {
$vote = array(
'a' => AppController::clearCodeAll($_POST['vote'][0])
);
}
}
//load comm to DB
$comm = new Comments();
$data = $_POST;
$comm->load($data);
$data_json = json_encode($vote);
$uidLogged=(int)$_SESSION['user']['id'];
if(
empty(AppController::clearCodeHtml($_POST['txt'])) OR
AppController::clearCodeHtml(iconv_strlen($_POST['txt'],'UTF-8')) < 19
){
echo '{"txt":"Возможно, данные заполнены не полностью"}';exit;
}
if(!$comm->validate($data)){
$comm->getErrors();
$_SESSION['form_data'] = $data;
redirect();
}
if($idComm = $comm->save('comm')) {
$comm = \R::load('comm', $idComm);
$comm->pub = 2;
$comm->ip = getenv('REMOTE_ADDR');
$comm->date = date("Y-m-d H:i:s");
$comm->rait = $data_json;
$comm->uid = $uidLogged;
$comm->ptyp = $serOrProd;
$comm->prid = (int)AppController::clearCodeAll($_POST['id']);
$comm->text = trim(AppController::clearCodeHtml($_POST['txt']));
\R::store($comm);
//show comment to user
$uNm = '';
if (isset($_SESSION['user'])) $uNm = $_SESSION['user']['name'];
$response = [];
$response['comment'] = '
<div data-id="' . $comm->id . '" class="pr br3 ovh p_comm">
<div class="pa z1 br3 t fc p_comNVNotice"><i class="br3 p4 fa-bullhorn"></i> Это Ваш отзыв. После проверки он будет доступен всем пользователям.</div>
<span class="pr br03 us t o5"><i class="fa-user"></i><span class="autor_name">' . $uNm . '</span></span>
<div class="in br03 p_comD p_comRait t o5" title="Средняя оценка пользователя">';
$count=0;
$raitAr = [];
$raitJ = json_decode($data_json);
//если рейтинг услуг
foreach ($raitJ as $z) {
$raitAr[] = $z;
}
if ($serOrProd == 1) {//service
$raitInt = $raitAr[0] * 50 * 20;//kach
$raitInt += $raitAr[1] * 10 * 20;
$raitInt += $raitAr[2] * 10 * 20;
$raitInt += $raitAr[3] * 25 * 20;
$raitInt += $raitAr[4] * 5 * 20;
$raitInt = $raitInt * 100 / 10000;
$raitInt = round($raitInt * 5 / 100);
$count = 5 - $raitInt;
}else{//product
$count=5-$raitAr[0];
}
//вывод средней оценки, если оценка НЕ 5
if ($count > 0 AND $count < 5) {
$i = 1;
$i2 = 5;
while ($count < $i2) {
$response['comment'] .= '<i class="fa-star"></i>';
$i2--;
}
while ($count >= $i) {
$response['comment'] .= '<i class="gry fa-star-o"></i>';
$i++;
}
} else {
$i3 = 5;
//если оценка 5
while ($count <= $i3) {
$response['comment'] .= '<i class="fa-star"></i>';
$i3--;
}
}
$response['comment'].= '</div>
<div class="in br03 p_comD t o5">'.$comm->date.'</div>
<div class="ova comment_text t o5">'.$comm->text.'</div>
</div>
';
//\show comment to user
$response['txt']='success';
echo (json_encode($response, JSON_UNESCAPED_UNICODE));
//send admin email
/* $transport = (new Swift_SmtpTransport(App::$app->getProperty('smtp_host'), App::$app->getProperty('smtp_port'), App::$app->getProperty('smtp_protocol')))
->setUsername(App::$app->getProperty('smtp_login'))
->setPassword(App::$app->getProperty('smtp_password'))
;*/
// Create the Mailer using your created Transport
/* $mailer = new Swift_Mailer($transport);
$date=date("Y-m-d H:i:s");
$body = '<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
</head>
<body>
'.$date.' Пользователь оставил новый отзыв.
<br><a href="'. App::$app->getProperty('path') .'superintendence/comments/edit?id='.$comm->id.'">Перейти в административную панель и опубликовать его.</a>
</p>
</body>
</html>';
$message_admin = (new Swift_Message($date.' новый отзыв'))
->setFrom([App::$app->getProperty('smtp_login') => App::$app->getProperty('shop_name')])
->setTo(App::$app->getProperty('admin_email'))
->setBody($body, 'text/html')
;
$mailer->send($message_admin);*/
//\send admin email
exit;
}
exit;
}//setFrmComm
}
}else{#\xmlHttp
echo 'err';exit;
}
#END IF XML
3. AppController.php очищает код:
<? public static function clearCodeAll($data){
$array1=array(
'\'','"','*','?','%','+','=','0x','&','\0',' ',"\n","\r","\s",'\\',',','.','~','`','^','$',';',':','{','}','[',']','(',')','//','http','wss','blob','localhost','|'
);
$data=strip_tags($data);
$data=htmlspecialchars($data,ENT_QUOTES);
$data=str_ireplace($array1,'',$data);
return $data;
}
public static function clearCodeSearch($data){
$array1=array(
'+','=','0x','\0','\\','~','`','^','$',';',':','{','}','[',']','(',')','//','http','wss','blob','localhost','|'
);
$data=strip_tags($data);
$data=htmlspecialchars($data,ENT_QUOTES);
$data=str_ireplace($array1,'',$data);
return $data;
}
public static function clearCodeHtml($data){
$data=strip_tags($data);
$data=htmlspecialchars($data,ENT_QUOTES);
return $data;
}
4. CommentsController.php
Здесь идет обработка рейтинга конкретного товара/услуги и обновление в БД данных о рейтинге.
Также здесь формируется большой JSON, который хранит в себе рейтинг одного товара/услуги в товаре/услуге.
<?//get Rating
$rait=explode(',',$data['rait']);
$a1=0;$a2=0;$a3=0;$a4=0;$a5=0;
$b1=0;$b2=0;$b3=0;$b4=0;$b5=0;
$c1=0;$c2=0;$c3=0;$c4=0;$c5=0;
$d1=0;$d2=0;$d3=0;$d4=0;$d5=0;
$e1=0;$e2=0;$e3=0;$e4=0;$e5=0;
//если рейтинга еще нет
if(empty($pUP->rait)){
if($rait[0]==5)$a1=1;
if($rait[0]==4)$a2=1;
if($rait[0]==3)$a3=1;
if($rait[0]==2)$a4=1;
if($rait[0]==1)$a5=1;
if($pUP->uslugi==1) {//service если нет рейт
if ($rait[1] == 5) $b1 = 1;
if ($rait[1] == 4) $b2 = 1;
if ($rait[1] == 3) $b3 = 1;
if ($rait[1] == 2) $b4 = 1;
if ($rait[1] == 1) $b5 = 1;
if ($rait[2] == 5) $c1 = 1;
if ($rait[2] == 4) $c2 = 1;
if ($rait[2] == 3) $c3 = 1;
if ($rait[2] == 2) $c4 = 1;
if ($rait[2] == 1) $c5 = 1;
if ($rait[3] == 5) $d1 = 1;
if ($rait[3] == 4) $d2 = 1;
if ($rait[3] == 3) $d3 = 1;
if ($rait[3] == 2) $d4 = 1;
if ($rait[3] == 1) $d5 = 1;
if ($rait[4] == 5) $e1 = 1;
if ($rait[4] == 4) $e2 = 1;
if ($rait[4] == 3) $e3 = 1;
if ($rait[4] == 2) $e4 = 1;
if ($rait[4] == 1) $e5 = 1;
$rait = array(
'rait' => calcTtlVoteService($rait, 0, 1,1),
'raitmark' => array('a' => $rait[0], 'b' => $rait[1], 'c' => $rait[2], 'd' => $rait[3], 'e' => $rait[4]),
'raitTtl' => 1,
'raitEvery' => array(
'a' => array(
'f' => $a1,//THIS REVERSE (5)
'g' => $a2,//4
'h' => $a3,//3
'i' => $a4,//2
'j' => $a5//1
),
'b' => array(
'f' => $b1,
'g' => $b2,
'h' => $b3,
'i' => $b4,
'j' => $b5,
),
'c' => array(
'f' => $c1,
'g' => $c2,
'h' => $c3,
'i' => $c4,
'j' => $c5,
),
'd' => array(
'f' => $d1,
'g' => $d2,
'h' => $d3,
'i' => $d4,
'j' => $d5,
),
'e' => array(
'f' => $e1,
'g' => $e2,
'h' => $e3,
'i' => $e4,
'j' => $e5,
)
)
);
}else{//product если рейт нет
$rait = array(
'rait' => calcTtlVoteService($rait[0], 0, 1,0),
'raitmark' => array('a' => $rait[0]),
'raitTtl' => 1,
'raitEvery' => array(
'a' => array(
'f' => $a1,//THIS REVERSE (5)
'g' => $a2,//4
'h' => $a3,//3
'i' => $a4,//2
'j' => $a5//1
)
)
);
}
$pUP->rait = json_encode($rait);
\R::store($pUP);
//empty($pUP->rait)-если рейтинга еще нет
redirect();
}else{
//если рейт уже есть
//getThisProdAllRaitValuesFromBase
$raitJSON=json_decode($pUP['rait']);
function jsonToArr($json){
$jsonToAr=[];
foreach ($json as $z) {
$jsonToAr[]=$z;
}
return $jsonToAr;
}
/**/
if($rait[0]==5)$a1=1;
if($rait[0]==4)$a2=1;
if($rait[0]==3)$a3=1;
if($rait[0]==2)$a4=1;
if($rait[0]==1)$a5=1;
if($pUP->uslugi==1) {//service если нет рейт
if ($rait[1] == 5) $b1 = 1;
if ($rait[1] == 4) $b2 = 1;
if ($rait[1] == 3) $b3 = 1;
if ($rait[1] == 2) $b4 = 1;
if ($rait[1] == 1) $b5 = 1;
if ($rait[2] == 5) $c1 = 1;
if ($rait[2] == 4) $c2 = 1;
if ($rait[2] == 3) $c3 = 1;
if ($rait[2] == 2) $c4 = 1;
if ($rait[2] == 1) $c5 = 1;
if ($rait[3] == 5) $d1 = 1;
if ($rait[3] == 4) $d2 = 1;
if ($rait[3] == 3) $d3 = 1;
if ($rait[3] == 2) $d4 = 1;
if ($rait[3] == 1) $d5 = 1;
if ($rait[4] == 5) $e1 = 1;
if ($rait[4] == 4) $e2 = 1;
if ($rait[4] == 3) $e3 = 1;
if ($rait[4] == 2) $e4 = 1;
if ($rait[4] == 1) $e5 = 1;
$rait = array(
'rait' => calcTtlVoteService($rait, $raitJSON->rait, $raitJSON->raitTtl, 1),
'raitmark' => array('a' => calcTtlVoteServiceNA($rait[0], $raitJSON->raitmark->a, $raitJSON->raitTtl),
'b' => calcTtlVoteServiceNA($rait[1], $raitJSON->raitmark->b, $raitJSON->raitTtl),
'c' => calcTtlVoteServiceNA($rait[2], $raitJSON->raitmark->c, $raitJSON->raitTtl),
'd' => calcTtlVoteServiceNA($rait[3], $raitJSON->raitmark->d, $raitJSON->raitTtl),
'e' => calcTtlVoteServiceNA($rait[4], $raitJSON->raitmark->e, $raitJSON->raitTtl)),
'raitTtl' => $raitJSON->raitTtl + 1,
'raitEvery' => array(
'a' => array(
'f' => $a1 + $raitJSON->raitEvery->a->f,
'g' => $a2 + $raitJSON->raitEvery->a->g,
'h' => $a3 + $raitJSON->raitEvery->a->h,
'i' => $a4 + $raitJSON->raitEvery->a->i,
'j' => $a5 + $raitJSON->raitEvery->a->j
),
'b' => array(
'f' => $b1 + $raitJSON->raitEvery->b->f,
'g' => $b2 + $raitJSON->raitEvery->b->g,
'h' => $b3 + $raitJSON->raitEvery->b->h,
'i' => $b4 + $raitJSON->raitEvery->b->i,
'j' => $b5 + $raitJSON->raitEvery->b->j
),
'c' => array(
'f' => $c1 + $raitJSON->raitEvery->c->f,
'g' => $c2 + $raitJSON->raitEvery->c->g,
'h' => $c3 + $raitJSON->raitEvery->c->h,
'i' => $c4 + $raitJSON->raitEvery->c->i,
'j' => $c5 + $raitJSON->raitEvery->c->j
),
'd' => array(
'f' => $d1 + $raitJSON->raitEvery->d->f,
'g' => $d2 + $raitJSON->raitEvery->d->g,
'h' => $d3 + $raitJSON->raitEvery->d->h,
'i' => $d4 + $raitJSON->raitEvery->d->i,
'j' => $d5 + $raitJSON->raitEvery->d->j
),
'e' => array(
'f' => $e1 + $raitJSON->raitEvery->e->f,
'g' => $e2 + $raitJSON->raitEvery->e->g,
'h' => $e3 + $raitJSON->raitEvery->e->h,
'i' => $e4 + $raitJSON->raitEvery->e->i,
'j' => $e5 + $raitJSON->raitEvery->e->j
)
)
);
}else{//prod если рейт есть
$rait = array(
'rait' => calcTtlVoteService($rait, $raitJSON->rait, $raitJSON->raitTtl, 0),
'raitmark' => array('a' => calcTtlVoteServiceNA($rait[0], $raitJSON->raitmark->a, $raitJSON->raitTtl)),
'raitTtl' => $raitJSON->raitTtl + 1,
'raitEvery' => array(
'a' => array(
'f' => $a1 + $raitJSON->raitEvery->a->f,
'g' => $a2 + $raitJSON->raitEvery->a->g,
'h' => $a3 + $raitJSON->raitEvery->a->h,
'i' => $a4 + $raitJSON->raitEvery->a->i,
'j' => $a5 + $raitJSON->raitEvery->a->j
)
)
);
}
$pUP->rait = json_encode($rait);
\R::store($pUP);
redirect();
/*\*/
}
Пример JSON'а, который формируется выше:
{
"rait":3.303125,"raitmark":
{"a":3.5833333333333335,"b":3.4375,"c":2.7916666666666665,"d":2.9375,"e":3.0833333333333335}
,"raitTtl":4,"raitEvery":
{"a":
{"f":2,"g":1,"h":0,"i":0,"j":1}
,"b":
{"f":2,"g":1,"h":0,"i":0,"j":1}
,"c":
{"f":1,"g":0,"h":2,"i":0,"j":1}
,"d":
{"f":1,"g":1,"h":0,"i":1,"j":1}
,"e":
{"f":2,"g":0,"h":0,"i":0,"j":2}
}
}
Где rait - общий рейтинг
raitMark - голоса за 1-5 критериев по среднему показателю
raitTtl - проголосовало всего людей
raitEvery - проголосовало за каждый из критериев раз за каждую оценку.
Например, всего проголосовало 11 человек, за первый критерий проголосовали так:
Пятерку поставили 7 раз, четверку - 1 раз, тройку - 2 раза, двойку - 0 раз, единицу - 0 раз (всего 11). Выглядеть будет так:
{"f":7,"g":1,"h":2,"i":0,"j":0}
Еще пример: всего проголосовало 19 человек, за пятерку - 4 раза, за четверку - 7 раз, за тройку - 2 раза, за двойку - 2 раза, за единицу - 4 раза. Всего - 19. Выглядеть так будет:
{"f":4,"g":7,"h":2,"i":2,"j":4}
Сейчас я понимаю, что нужно было просто сделать так:
{5:7,4:1,3:2,2:0,1:0}
5. view.php – визуализация рейтинга товара/услуги
<?//rating
if(!empty($product->rait)){
$raitJSON=json_decode($product->rait);
$rOut='';
$countR=5-round($raitJSON->rait, 0, PHP_ROUND_HALF_UP);
$iR=1;$i2R=5;$i3R=4;
if($countR>0 AND $countR<5){
while($countR<$i2R){
$rOut.= '<i class="cg fa-star"></i>';$i2R--;
}
while($countR>=$iR){
$rOut.= '<i class="gry fa-star-o"></i>';$iR++;
}
}else{
//если оценка 5
while($countR==5){
$rOut.= '<i class="cg fa-star"></i>';$i3R--;
}
}
function setP($val){
return 100-($val*100/5);
}
if($usl==1){//service
echo '<div class="fc fxas mh10 br3 p_rait">
<div class="p4 bgLg">
<div class="fs52 tc fB">'.substr($raitJSON->rait, 0, 4).'</div>
<div class="tc">'.$rOut.'</div>
<div class="tc gry">общий рейтинг'.
'<div class="line"></div>Всего проголосовало: <span class="fB">'.$raitJSON->raitTtl.'</span></div>
</div>
<div class="mv320 bgDg p4 tableDiv">
<div class="table">
<div class="tr">
<div class="pr z1 tbc brdrb p_rtP">Качество:<div class="pa z- p_rtngGr" style="right:'.setP($raitJSON->raitmark->a).'%"></div></div>
<div class="tbc brdrb fB">'.substr($raitJSON->raitmark->a, 0, 4).'</div>
</div>
<div class="tr">
<div class="pr z1 tbc brdrb p_rtP">Вежливость:<div class="pa z- p_rtngGr" style="right:'.setP($raitJSON->raitmark->b).'%"></div></div>
<div class="tbc brdrb fB">'.substr($raitJSON->raitmark->b, 0, 4).'</div>
</div>
<div class="tr">
<div class="pr z1 tbc brdrb p_rtP">Пунктуальность:<div class="pa z- p_rtngGr" style="right:'.setP($raitJSON->raitmark->c).'%"></div></div>
<div class="tbc brdrb fB">'.substr($raitJSON->raitmark->c, 0, 4).'</div>
</div>
<div class="tr">
<div class="pr z1 tbc brdrb p_rtP">Время отклика на заказ:<div class="pa z- p_rtngGr" style="right:'.setP($raitJSON->raitmark->d).'%"></div></div>
<div class="tbc brdrb fB">'.substr($raitJSON->raitmark->d, 0, 4).'</div>
</div>
<div class="tr">
<div class="pr z1 tbc p_rtP">Цена:<div class="pa z- p_rtngGr" style="right:'.setP($raitJSON->raitmark->e).'%"></div></div>
<div class="tbc fB">'.substr($raitJSON->raitmark->e, 0, 4).'</div>
</div>
</div>
</div>
</div>';
}else{
echo '<div class="fc fxas mh10 br3 p_rait">
<div class="p4 bgLg">
<div class="fs52 tc fB">'.substr($raitJSON->rait, 0, 4).'</div>
<div class="tc">'.$rOut.'</div>
<div class="tc gry">общий рейтинг'.
'<div class="line"></div>Всего проголосовало: <span class="fB">'.$raitJSON->raitTtl.'</span></div>
</div>
</div>';
}
}
/*\rating*/?>
Визуализация комментария:
<?
//Отображаем комм
$serOrProd = $usl;//1-service,0-product
$raitInt=0;
if(!empty($getAllPComm)){
$clss='';
$clss2='';
foreach ($getAllPComm as $k=>$v){
if(isset($_SESSION['user'])&&$v['uid']==$_SESSION['user']['id']&&$v['pub']==2){
$clss=' t p_comNV';
$txtNV='<div class="pa z1 br3 t fc p_comNVNotice"><i class="br3 p4 fa-bullhorn"></i> Это Ваш отзыв. После проверки он будет доступен всем пользователям.</div>';
}else{$clss='';$txtNV='';}
$uNm='';
$count=0;
$raitAr = [];
$raitJ = json_decode($v['rait']);
//если рейтинг услуг
foreach ($raitJ as $z) {
$raitAr[] = $z;
}
if ($serOrProd == 1) {//service
$raitInt = $raitAr[0] * 50 * 20;
$raitInt += $raitAr[1] * 10 * 20;
$raitInt += $raitAr[2] * 10 * 20;
$raitInt += $raitAr[3] * 25 * 20;
$raitInt += $raitAr[4] * 5 * 20;
$raitInt = $raitInt * 100 / 10000;
$raitInt = round($raitInt * 5 / 100);
$count = 5 - $raitInt;
}else{//product
$count=5-$raitAr[0];
}
foreach ($getAllUsrNmsComm as $k=>$v_){
if($v_['id']==$v['uid']){
$uNm=$v_['name'];
}
}
//если польз. авторизирован и он оставил отзыв, то отображаем ему отзыв с пометкой на проверке
//comment=2(new) for logged User
if(isset($_SESSION['user'])&&$v['uid']==$_SESSION['user']['id']&&$v['pub']==2){
$out= '
<div data-id="'.$v['id'].'" class="pr br3 ovh p_comm">
'.$txtNV.'
<span class="pr br03 us'.$clss.'"><i class="fa-user"></i><span class="autor_name">'.$uNm.'</span></span>
<div class="in br03 p_comD p_comRait'.$clss.'" title="Средняя оценка пользователя">';
//вывод средней оценки, если оценка НЕ 5
$i=1;$i2=5;$i3=4;
if($count>0 AND $count<5){
while($count<$i2){
$out.= '<i class="fa-star"></i>';$i2--;
}
while($count>=$i){
$out.= '<i class="gry fa-star-o"></i>';$i++;
}
}else{
//если оценка 5
while($count==5){
$out.= '<i class="fa-star"></i>';$i3--;
}
}
$out.= '</div>
<div class="in br03 p_comD'.$clss.'">'.$v['date'].'</div>
<div class="p4 br3 ova comment_text'.$clss.'">'.nl2br($v['text']).'</div>
</div>
';
echo $out;
}//\comment=2 for logged User
//comment=1(одобренные) for All
if($v['pub']==1){
$out= '
<div data-id="'.$v['id'].'" class="pr br3 ovh p_comm">
'.$txtNV.'
<span class="pr br03 us'.$clss.'"><i class="fa-user"></i><span class="autor_name">'.$uNm.'</span></span>
<div class="in br03 p_comD p_comRait'.$clss.'" title="Средняя оценка пользователя">';
//вывод средней оценки, если оценка НЕ 5
$i=1;$i2=5;$i3=4;
if($count>0 AND $count<5){
while($count<$i2){
$out.= '<i class="fa-star"></i>';$i2--;
}
while($count>=$i){
$out.= '<i class="gry fa-star-o"></i>';$i++;
}
}else{
//если оценка 5
while($count<=$i3){
$out.= '<i class="fa-star"></i>';$i3--;
}
}
$out.= '</div>
<div class="in br03 p_comD'.$clss.'">'.$v['date'].'</div>
<div class="p4 br3 ova comment_text'.$clss.'">'.nl2br($v['text']).'</div>
</div>
';
echo $out;
}//\comment=2 for All
}
}
Стили [CSS]:
/*rait*/
.rate1,.rate2,.rate3,.rate4,.rate5{min-width: 74px}
.rate-base-layer{color:#aaa}
.rate-hover-layer{color:orange}
.mv320{max-width:320px}
.rate-select-layer,.p_comRait{color:#4caf50}
.t{transition:all .5s}
.p_rtngGr{background:#e5f9bd;box-shadow:inset -1px 0 #a7cc5f}
/*\rait*/
/*div table*/
.brdrb{border-bottom:1px dashed #a2d43e}
.tableDiv{overflow:auto}
.table{display:table;text-align:left}
.tr{display:table-row;transition:all .5s linear}
.tbc{display:table-cell;white-space:nowrap;padding:1px 4px}
@media(max-width:760px){
.tableDiv{display:block}
.tbc{white-space:normal}
}
/*\div table*/
/*View rate*/
.p_rait{border:1px solid #ccc}
.p_rait .bgLg{border-right:1px solid #ccc}
.p_rait .brdrb{border-bottom:1px solid #dadada}
.p_rait .p_rtP{background:#fdfffa;box-shadow:inset -1px 0 #4caf50}
.line{width:100%;height:1px;background:#ccc;margin-top:4px}
/*\View rate*/
/*DEMO PAGE*/
@font-face{font-family:'Bebas';src:url("../fonts/BebasNeue.ttf")}
body{font:16px/18px 'Exo 2',Arial,sans}
.aj_commHtml{background:#efffce}
.aj_commHtml h3{font:27px/1 Bebas,Arial,sans}
.aj_commD{background: #f8ffc2}
.in{display: inline-block}
.pa,.us:after{position:absolute;top:0;right:0;bottom:0;left:0}
.p{position:absolute}
.ova{overflow:auto}
.p4{padding:4px}
.br3{border-radius:3px}
.cp{cursor:pointer}
.fs52{font-size:52px}
.fwb{font-weight:bold}
.pr{position:relative}
.tc{text-align:center}
.tj{text-align:justify}
.ovh,.ov{overflow:hidden}
.z1{z-index:1}
.z-{z-index:-1}
.aj_commHtml{max-width:960px;margin: 0 auto}
.fB{font:18px/1 Bebas,Arial,sans}
textarea{border:2px solid #f3f3f5;box-sizing: border-box;height:240px;padding:5px 10px;width:100%;transition:all 0.4s ease;color: inherit;font: inherit;margin: 0}
textarea:focus{border:2px solid #A2D43E;box-shadow:0 0 4px 0 #A2D43E}
.fc,.basketClass:after{display:-webkit-flex;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center}
.fx{display:-webkit-flex;display:flex}
.fsb{justify-content:space-between}
.fsa{justify-content:space-around}
.fjs{justify-content:flex-start}
.fw{flex-wrap:wrap}
.fe{align-items:flex-end}
.fxas{align-items:stretch}
.fv{align-items:flex-start;justify-content:flex-end}
.faz{align-items:flex-start}
.fg1{flex-grow:1}
.fx6{flex:6}
.fx1{flex:1}
.cg{color:#4caf50}
.bgLg{background:#f7f7f7}
.bgDg{background:#efefef}
.p_commBtn{position:relative;width:auto;display:block;background:#fff;box-sizing:border-box;color:#000;clear:both;text-align:center;text-transform:uppercase;font-size:20px;padding:20px;margin:10px 0;border:1px solid #A2D43E}
.fs14,.sml,.gry,.gry a,.related a,.short-desc{font-size:14px}
.gry a{color:#6f9b19}
.gry,.gry a{color:#666}
.fs52{font-size:52px}
/*wrn*/
.warning,.success{position:fixed;font:16px/22px 'Exo 2',sans;z-index:999999;top:0;left:0;right:0;background:rgba(255,134,0,.95);text-align:center;color:#fff;padding:5px 3px;display:none}
.success{background:#588E00}
.warning:after{content:'\f057';font-family:'FontAwesome';position:absolute;right:3px;opacity:.5;animation:o .7s infinite ease-in-out;-webkit-animation:o .7s infinite ease-in-out;cursor:pointer}
.warning i{-webkit-animation:o 1.5s infinite ease-in-out;animation:o 1.5s infinite ease-in-out}
@keyframes o{50%{opacity:.5}60%{opacity:1}}
@-webkit-keyframes o{50%{opacity:.5}60%{opacity:1}}
.warning:before{content:'';position:absolute;bottom:-3px;left:0;right:0;height:3px;animation:warn 3s infinite ease-in-out;-webkit-animation:warn 3s infinite ease-in-out}
@keyframes warn{0%{box-shadow:inset 0 -3px #FF4B00}20%{box-shadow:inset 0 3px #00D6FF}40%{box-shadow:inset 0 -3px #FF4B00}60%{box-shadow:inset 0 3px #00D6FF}80%{box-shadow:inset 0 -3px #FF4B00}100%{box-shadow:inset 0 3px #00D6FF}}
/*\wrn*/
/*comm*/
.br03,.us:after{-webkit-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px}
.p_comm{min-width:44vw;padding:4px 8px;margin:0 0 10px 0;border:1px solid #efefef}
.comment_text{font-size:17px;margin:10px 0 0 0;line-height:23px}
.autor_name{font-size:13px;padding:4px;color:#fff;margin:0 0 15px 1px}
.p_comD{font-size:10px;padding:4px;margin:-1px 0 0 20px;float:right;background: #e3ffe4;box-shadow:0 -3px}
.us{background:#4caf50;padding:3px 1px 5px 5px;box-shadow:inset 0 2px 3px #063309}
.p_commBtn{position:relative;width:auto;display:block;background:#fff;box-sizing:border-box;color:#000;clear:both;text-align:center;text-transform:uppercase;font-size:20px;padding:20px;margin:10px 0;border:1px solid #A2D43E}
.ajx_err{border:1px solid #FF4B00;background:#ffebeb;border-left-width:3px}
.ajx_goo{border:1px solid #4caf50;background:#e5ffd8;border-left-width:3px;animation:aa 1s infinite .5s}
@keyframes aa{50%{border-left-color:#32fd3a}}
.o5,.p_comNV{opacity:.5}
.p_comNVNotice{background:rgba(255,255,255,.72);border:1px solid #e3ff48}
.p_comm:hover .p_comNVNotice{opacity:0;transform:matrix3d(1,0,0.00,0,0.00,0,1.00,0.001,0,-1,0,0,0,0,0,1)}
.p_comm:hover .p_comNV,.p_comm:hover .o5{opacity:1}
.p_comNVNotice i{background:#313131;margin:0 7px 0 0;color:#fff}
.comment_text{background:#f9f9f9}
.us:after{content:'';z-index:-1;background:#26792a;transform:matrix3d(1,0,0.00,0,0.00,1,0.00,0.005,0,0,1,0,0,0,0,1);left:-3px;bottom:-3px;right:-3px}
/*\comm*/