Yii: Добавляем рейтинг статей CStarRating с возможностью оценивать статью зарегистрированным пользователям
Добавляем звёзды рейтинга для статей с помощью класса CStarRating, чтобы пользователи могли оценить понравившуюся статью. Подробнее про класс CStarRating читаем здесь в документации Yii.
В базе данных MySQL делаем:
1) В таблице статей добавляем поле 'rating' (int(11) с NULL по-умолчанию). Здесь будет общий рейтинг, суммарный от всех пользователь, но "нормированный к сотне" (то есть 5 звёзд соответствуют максимальному значению 500)
2) Создадим дополнительную таблицу 'rating', здесь каждая запись, это оценка конкретного пользователя для конкретной статьи (оценка по звёздам от 1 до 5):
-- -- Структура таблицы `rating` -- CREATE TABLE IF NOT EXISTS `rating` ( `id` int(10) NOT NULL AUTO_INCREMENT, `article_id` int(10) NOT NULL, `user_id` int(10) NOT NULL, `value` tinyint(3) NOT NULL, PRIMARY KEY (`id`), KEY `user_id` (`user_id`), KEY `article_id` (`article_id`,`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; -- -- Ограничения внешнего ключа сохраненных таблиц -- -- -- Ограничения внешнего ключа таблицы `rating` -- ALTER TABLE `rating` ADD CONSTRAINT `rating_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `tbl_users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, ADD CONSTRAINT `rating_ibfk_1` FOREIGN KEY (`article_id`) REFERENCES `tbl_article` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
3) В файл вида подробной статьи добавляем оценку + возможность оценить для зарегистрированного пользователя
<div class="b-rating__with_text">
<?php
$data->ShowRating();
echo '<span> '.$data->marksCount.' '
.CLocoHelper::GetFormatWord('оценка', $data->marksCount).'</span>'
?>
</div>
<div class="b-rating__with_text">
<?php
if (!Yii::app()->user->isGuest){
$this->widget('CStarRating', array(
'name' => 'user-input-rating',
'minRating' => '1',
'maxRating' => '5',
'value' => $data->GetUserRating(), // mark 1...5
'starWidth'=>'15',
'ratingStepSize' => '1',
//'titles'=> CHtml::listData(RatingDesc::model()->cache(3600)->findAll(),'id','desc'),
'allowEmpty'=>false,
'callback' => 'function(value) {SetRating(value, ' . $data->id . ');}',
'cssFile'=>'/css/rating/jquery.rating.css',
));
echo '<span> оцените статью</span>';}
?>
</div>
<?php
$cs = Yii::app()->getClientScript();
$cs->registerScriptFile('/js/rating.js');
?>
/**
* Return rating in specific format
* @return string rating in specific format
*/
public function GetFormattedRating()
{
$rating = $this->rating / 100;
$rating = number_format(round($rating * 4) / 4, 2);
$rating = sprintf("%1.2f", $rating);
$rating = trim(rtrim($rating, "0"), '.');
return $rating;
}
/**
* Return user mark for the good
* @return string user mark for the good
*/
public function GetUserRating()
{
$value = Rating::model()->findByAttributes(array(
'article_id' => $this->id,
'user_id' => Yii::app()->user->id,
));
//echo $value;
if (isset($value))
return sprintf("%d", $value->value);
else
return '0';
}
/**
* Show good rating with css styles
*/
public function ShowRating()
{
$good_rating = $this->GetFormattedRating();
echo '<div class="x-rating x-rate-' . round($good_rating * 4) . '"></div>';
}
/**
* Set mark for the good by user
* @param $article_id
* @param $rate mark (1-5)
*/
public function actionSetMark($article_id, $rate)
{
if (Yii::app()->user->isGuest) {
echo 'you are not registered user';
return;
}
$user = Yii::app()->user->id;
$rating = Rating::model()->with(array('article', 'article.marksCount'))
->findByAttributes(array(
'article_id' => $article_id,
'user_id' => $user,
));
if (empty($rating)) { // если никто не оценивал статью ещё
if ($rate == 'undefined') {
return;
}
$rating = new Rating;
$rating->article_id = $article_id;
$rating->user_id = $user;
$rating->value = $rate;
if ($rating->save()) {
$good = $rating->article;
$good->rating = round($good->rating + ($rate * 100 - $good->rating) / ($good->marksCount + 1));
$good->save();
echo 'success';
}
else
echo 'fail saving';
} else {
if ($rating->value != $rate) {
if ($rate == 'undefined') {
echo 'rate deleted';
$rating->delete();
return;
}
$old_mark = $rating->value;
$rating->value = $rate;
if ($rating->save()) {
$good = $rating->article;
$good->rating = round($good->rating + ($rate * 100 - $old_mark * 100) / $good->marksCount);
$good->save();
echo 'success';
}
else
echo 'fail saving';
} else
echo 'no need to change';
}
}
6) В /js/rating.js
/*
* Send new user choice, then recieve object where define what need to change
* and change selects
*/
function SetRating(value, good) {
$.ajax({
url:"/review/setmark",
type:'GET',
data: "good_id="+good+"&rate=" + value,
success: RatingSet
});
}
function RatingSet(data){
}
7) В css/main.css
.x-rating {
float:left;
height:16px;
margin-bottom:0;
margin-left:20px;
margin-right:0;
margin-top:1px;
width:75px;
}
.x-rate-0 {background:url(../css/star0.gif) -75px 0 no-repeat;}
.x-rate-1 {background:url(../css/star1.gif) -75px 0 no-repeat;}
.x-rate-2 {background:url(../css/star2.gif) -75px 0 no-repeat;}
.x-rate-3 {background:url(../css/star3.gif) -75px 0 no-repeat;}
.x-rate-4 {background:url(../css/star0.gif) -60px 0 no-repeat;}
.x-rate-5 {background:url(../css/star1.gif) -60px 0 no-repeat;}
.x-rate-6 {background:url(../css/star2.gif) -60px 0 no-repeat;}
.x-rate-7 {background:url(../css/star3.gif) -60px 0 no-repeat;}
.x-rate-8 {background:url(../css/star0.gif) -45px 0 no-repeat;}
.x-rate-9 {background:url(../css/star1.gif) -45px 0 no-repeat;}
.x-rate-10 {background:url(../css/star2.gif) -45px 0 no-repeat;}
.x-rate-11 {background:url(../css/star3.gif) -45px 0 no-repeat;}
.x-rate-12 {background:url(../css/star0.gif) -30px 0 no-repeat;}
.x-rate-13 {background:url(../css/star1.gif) -30px 0 no-repeat;}
.x-rate-14 {background:url(../css/star2.gif) -30px 0 no-repeat;}
.x-rate-15 {background:url(../css/star3.gif) -30px 0 no-repeat;}
.x-rate-16 {background:url(../css/star0.gif) -15px 0 no-repeat;}
.x-rate-17 {background:url(../css/star1.gif) -15px 0 no-repeat;}
.x-rate-18 {background:url(../css/star2.gif) -15px 0 no-repeat;}
.x-rate-19 {background:url(../css/star3.gif) -15px 0 no-repeat;}
.x-rate-20 {background:url(../css/star0.gif) 0 0 no-repeat;}
7a) Забыл. Положите в папку css папку rating (для оценивания зарегистрированными пользователями, там стили и картинки): rating.zip
Ред. 09.08.2012: В виде вызов CStarRating (просмотр статьи полностью):
...
<div class="b-rating__with_text">
<?php
if (!Yii::app()->user->isGuest){
$this->widget('CStarRating', array(
'name' => 'user-input-rating',
'minRating' => '1',
'maxRating' => '5',
'value' => $data->GetUserRating(), // mark 1...5
'starWidth'=>'15',
'ratingStepSize' => '1',
//'titles'=> CHtml::listData(RatingDesc::model()->cache(3600)->findAll(),'id','desc'),
'allowEmpty'=>false,
'callback' => 'function(value) {SetRating(value, ' . $data->id . ');}',
'cssFile'=>'/css/rating/jquery.rating.css',
));
echo '<span> оцените статью</span>';}
?>
</div>
<?php
$cs = Yii::app()->getClientScript();
$cs->registerScriptFile('/js/rating.js');
?>
<?php
/**
* This is the model class for table "rating".
*
* The followings are the available columns in table 'rating':
* @property integer $id
* @property integer $article_id
* @property integer $user_id
* @property integer $value
*
* The followings are the available model relations:
* @property Users $user
* @property Article $article
*/
class Rating extends CActiveRecord
{
/**
* Returns the static model of the specified AR class.
* @param string $className active record class name.
* @return Rating the static model class
*/
public static function model($className=__CLASS__)
{
return parent::model($className);
}
/**
* @return string the associated database table name
*/
public function tableName()
{
return 'rating';
}
/**
* @return array validation rules for model attributes.
*/
public function rules()
{
// NOTE: you should only define rules for those attributes that
// will receive user inputs.
return array(
array('article_id, user_id, value', 'required'),
array('article_id, user_id, value', 'numerical', 'integerOnly'=>true),
// The following rule is used by search().
// Please remove those attributes that should not be searched.
array('id, article_id, user_id, value', 'safe', 'on'=>'search'),
);
}
/**
* @return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'user' => array(self::BELONGS_TO, 'Users', 'user_id'),
'article' => array(self::BELONGS_TO, 'Article', 'article_id'),
);
}
/**
* @return array customized attribute labels (name=>label)
*/
public function attributeLabels()
{
return array(
'id' => 'ID',
'article_id' => 'Article',
'user_id' => 'User',
'value' => 'Value',
);
}
/**
* Retrieves a list of models based on the current search/filter conditions.
* @return CActiveDataProvider the data provider that can return the models based on the search/filter conditions.
*/
public function search()
{
// Warning: Please modify the following code to remove attributes that
// should not be searched.
$criteria=new CDbCriteria;
$criteria->compare('id',$this->id);
$criteria->compare('article_id',$this->article_id);
$criteria->compare('user_id',$this->user_id);
$criteria->compare('value',$this->value);
return new CActiveDataProvider($this, array(
'criteria'=>$criteria,
));
}
}
Ред. 04.10.2012: Ещё я использую helper, который обозвал CLocoHelper.php для склонения слова "оценка" и поместил в protected/helpers (создал папку helpers)
<?php
class CLocoHelper
{
...
public static function GetFormatWord($word, $number)
{
$num = $number % 10;
if ($word == 'оценка') {
if ($num == 1)
return 'оценка';
elseif ($num > 1 && $num < 5)
return 'оценки';
else
return 'оценок';
}
}
...
}
almix
Разработчик Loco, автор статей по веб-разработке на Yii, CodeIgniter, MODx и прочих инструментах. Создатель Team Sense.
Вы можете почитать все статьи от almix'а.
- 14 Разработка приложения на Yii. Урок 14: Выводим список категорий как новый виджет на сайте (29.04.2015)
- 13 Разработка приложения на Yii. Урок 13: Внедряем категории для статей. (29.04.2015)
- 12 Разработка приложения на Yii. Урок 12: Профилирование приложения, включаем кеширование. (20.01.2015)
- 11 Yiico. Видеокурс по разработке сайта на Yii. Урок 11: Отладка приложения, включаем журналирование. (20.01.2015)
- 10 Yiico. Видеокурс по разработке сайта на Yii. Урок 10: Если ваше приложение находится не в корневой папке, а во вложенной. (19.10.2014)
- 9 Разработка сайта на Yii с нуля. Урок 9. Выборка статей определённого автора. (13.08.2014)
- 8 Разработка сайта на Yii с нуля. Урок 8. Вызов в моделях функции, общей для них. Как избегать дублирования кода? (25.07.2014)
- 7 Разработка сайта на Yii с нуля. Урок 7. Изменение количества выводимых записей на странице в CGridView. Включаем сессии Yii. (04.09.2013)
- 6 Разработка сайта на Yii с нуля. Урок 6. Автоматич. отправка оповещений об одобренных комментариях на email автора комментария (11.08.2013)
- 5 Разработка сайта на Yii с нуля. Урок 5. Переименовываем blog в yiico. Изменяем "Home" в breadcrumbs. Включаем Gzip-сжатие. (09.08.2013)
- 4 Курс по Yii с нуля. Урок 4. Дорабатываем простую работу с пользователями: хранение пароля при редактировании пользователя. (13.04.2013)
- 3 Курс по Yii с нуля. Урок 3. Создаём новых пользователей. Организуем простую систему авторизации. Аутентификация, пароли, соли. (20.07.2014)
- 2 Yii + Git (github) на Mac. (24.03.2013)
- 2 Курс по Yii с нуля. Урок 2. Переносим и настраиваем Yii и проект нашего сайта на рабочем сервере. Избавляемся от index.php в url (09.07.2014)
- 1 Курс по Yii с нуля. Урок 1. Устанавливаем Yii на локальном компьютере. Заводим проект будущего сайта. (10.03.2013)
- 0 Composer – пакетный менеджер PHP. Что и как? (22.07.2014)
- 0 MySQL: проверить содержится ли значение в поле столбца (в столбце хранится строка значений через запятую) (22.05.2014)
- 0 Yii: Статичные страницы (создание, редактирование, удаление) (28.04.2014)
- 0 Yii: Расширение ECKEditor = Связка ckeditor + kcfinder (визуальный редактор с бесплатным файловым менеджером) (28.04.2014)
- 0 Yii: Bootstrap tabs, делаем активной вкладку на которую выполняется переход по ссылке (20.03.2014)
- 0 Yii: Доступ к атрибуту модели из файла шаблона (Вызов в шаблоне какого-либо атрибута модели). (17.02.2014)
- 0 Yii: Множественный автокомплит с помощью CJuiAutoComplete (автокомплит нескольких значений в одно поле) (07.02.2014)
- 0 Yii: Фотогалерея через поведение (доработка расширения imagesgallerymanager) (08.03.2016)
- 0 Yii: Установка и настройка Yii-app заготовки приложения от Crisu83 (09.03.2014)
- 0 Twitter Bootstrap Carousel Crossfade (09.09.2013)
13 комментариев
А у меня, вот, ничего не работает. Выдаёт ошибку:
include(Rating.php) [<a href='function.include'>function.include</a>]: failed to open stream: No such file or directory
Ищет модель Rating, а её то нет.
Я так понял, эта статья является продолжением другой статьи. Если не тяжело, напишите пожалуйста какой.
Спасибо!
Нет, Виталий, эта статья сама по себе. Ошибка
include(Rating.php) [<a href='function.include'>function.include</a>]: failed to open stream: No such file or directory
У меня возникает постоянно, когда кодировка всего файла не UTF-8, редактор ставит свою для mac кодировку. Решается проблема перекодировкой и сохранением файла.Porselanosa, проверил, всё до конца (17 строк) в rating.js проверьте подключили ли вы его в <head>? Смотрите дописал в статью выше.
Сергей, правильный вопрос. marksCount - это введённая в модели Article связь - количество оценок для статьи с указанным id. То есть подсчитывает сколько оценок у этой статьи, через self::STAT из таблицы rating:
Меняю значение , но размер не меняется. в чем может быть проблема ?
Большое спасибо за рейтинг!
Очень пригодилось, не знаю сколько бы сам писал этот функционал и не думаю, что получилось бы так хорошо.
Хочу уточнить небольшую деталь, заметил, что не совсем корректно считает рейтинг, не сходились цифры. Например оцениваем в первый раз на 5 звезд, в таблицу статей вносится рейтинг 250, в 2 раза меньше, чем положено 500.
Как мне кажется в ArticleController нужно изменить строку
убрать выделенное красным.Плюсовать 1 не надо т.к. количество голосов marksCount берется после сохранения модели уже с учетом проголосовавшего.
Может быть это только у меня так, т.к. код кое где правил под себя, но после исправления арифметика стала считаться правильно.