Yii: Добавляем рейтинг статей 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 берется после сохранения модели уже с учетом проголосовавшего.
Может быть это только у меня так, т.к. код кое где правил под себя, но после исправления арифметика стала считаться правильно.