Друзья, шестеренка рейтинга завершена, и теперь она входит в состав дистрибутива. По-умолчанию она выключена, поэтому не забудьте ее активировать. Шестеренка удалась на славу — количеством настроек и возможных вариаций «по теме» вы будете довольны.Поскольку с момента публикации прошлого урока появилось множество нововведений, то некоторые моменты мы пройдем заново.
Шестеренка воздействует на ноды, комменты, пользователей и сообщества. Причем, вы можете по желанию активировать те или иные элементы. Обратимся к конфигурационному файлу.
Файл конфигурации
title = "Points"
description = "Rating system."
core = 1.x
version = 1.0
group = modules
enabled = TRUE
; Некий параметр силы, коэффициент, который рассчитывает прибавку к силе голоса в зависимости от рейтинга пользоватлея
strength = 1
; Минимальное количество рейтинга для голосования
min_to_vote = "-1"
; Рейтинг ноды для вывода на главную
index_points = "3"
; Голоса
[charge]
; Активировть систему голосов
enabled = TRUE
; Сколько бонусных голосов будут получать пользователи раз в сутки
bonus_amount = "5"
; Каким рейтингом нужно обладать для получения бонусных голосов
bonus_points_require = "5"
; Какое максимальное количество голосов допустим для пользователя
max = "100"
; Когда пользователь голосует за созданное другим пользователем — дарит ли он ему ujkjc
gift = FALSE
; Ноды
[nodes]
; Отображение голосов в нодах
enabled = TRUE
; Показывать рейтинг ноды до голосования
show_points = TRUE
; Отображать количество проголосовавших
show_votes = FALSE
; Показывать гостям
show_to_guests = FALSE
; Округлять значение рейтинга
round = TRUE
[comments]
enabled = TRUE
show_points = TRUE
show_votes = FALSE
show_to_guests = FALSE
round = TRUE
[users]
enabled = TRUE
show_points = TRUE
show_votes = TRUE
show_to_guests = TRUE
round = FALSE
[community]
enabled = TRUE
show_points = TRUE
show_votes = TRUE
show_to_guests = TRUE
round = TRUE
[top]
; Сколько пользователей на страницу выводить в топе
per_page = 100
Надеюсь, что все страждущие получили то, что просили.
Также делаю акцент на том, что для редактирования настроек был создан административный интерфейс.
Панель управления
Настройки
Бонусы
Вы можете одарить пользователей голосами согласно особым критериям.<?php
/**
* Points control panel.
*
* @author Dmitriy Belyaev <admin@cogear.ru>
* @copyright Copyright © 2009, Dmitriy Belyeav
* @license http://cogear.ru/license.html
* @link http://cogear.ru
* @package Points
* @version $Id$
*/
class _Admin extends Controller
{
/**
* Constructor
*/
public function __construct(){
parent::Controller();
$this->points_tabs = new Panel('points_tabs',FALSE,FALSE,'tabs');
$this->points_tabs->set_title = TRUE;
$this->points_tabs->links_base = '/admin/points/';
$this->points_tabs->add(array('name'=>'index','text'=>fc_t('%settings'),'index'=>TRUE));
$this->points_tabs->add(array('name'=>'bonuses','text'=>fc_t('points_admin bonuses')));
$this->points_tabs->set_active(isset($this->uri->segments[3]) ? $this->uri->segments[3] : 'index');
$this->points_tabs->compile(12);
}
/**
* Show options
*
* @return void
*/
public function index(){
d('points_admin');
$this->form->set('admin/points')->label_after(TRUE)
->input('min_to_vote',array('vadlidation'=>'integer','js_validation'=>'digit'))
->input('index_points',array('vadlidation'=>'integer','js_validation'=>'digit'))
// Charge
->fieldset('charge',t('charge'))
->checkbox('charge[enabled]')
->input('charge[bonus_amount]',array('vadlidation'=>'integer','js_validation'=>'digit'))
->input('charge[bonus_points_require]',array('vadlidation'=>'integer','js_validation'=>'digit'))
->input('charge[max]',array('vadlidation'=>'integer','js_validation'=>'digit'))
// Nodes
->fieldset('nodes',t('gears nodes'))
->checkbox('nodes[enabled]')
->checkbox('nodes[show_points]')
->checkbox('nodes[show_votes]')
->checkbox('nodes[show_to_guests]')
->checkbox('nodes[round]')
->fieldset()
// Comments
->fieldset('comments',t('gears comments'))
->checkbox('comments[enabled]')
->checkbox('comments[show_points]')
->checkbox('comments[show_votes]')
->checkbox('comments[show_to_guests]')
->checkbox('comments[round]')
->fieldset()
// Users
->fieldset('users',t('gears users'))
->checkbox('users[enabled]')
->checkbox('users[show_points]')
->checkbox('users[show_votes]')
->checkbox('users[show_to_guests]')
->checkbox('users[round]')
->fieldset()
// Community
->fieldset('community',t('gears community'))
->checkbox('community[enabled]')
->checkbox('community[show_points]')
->checkbox('community[show_votes]')
->checkbox('community[show_to_guests]')
->checkbox('community[round]')
->fieldset()
->buttons('save')
->set_values($this->gears->points);
if($result = $this->form->result(TRUE)){
foreach(array('charge','nodes','comments','users','community') as $type){
foreach($this->gears->points->$type as $param=>$value){
if(!isset($result[$type][$param])) $result[$type][$param] = FALSE;
}
}
$this->info->set('gears/points/points')->change($result)->compile();
msg(t('form updated'));
$this->form->set_values($result);
}
$this->form->compile();
}
/**
* Manage bonuses
*
* @return void
*/
public function bonuses(){
foreach($this->user_groups->get_list(0) as $id=>$group){
$options[$id] = $group['name'];
}
d('points_admin_bonuses');
$this->form->set('admin/points/bonuses')
->input('names',array('autocomplete'=>array('url'=>'/user/autocomplete/','multiple'=>TRUE)))
->select('user_groups',array('multiple'=>TRUE,'options'=>$options))
->input('points',array('vadlidation'=>'integer','js_validation'=>'digit'))
->select('points_logic',array('options'=>array('<' => t('smaller'),'<=' => t('smaller_equals'),'=' => t('equals'),'>' => t('bigger'),'>=' => t('bigger_equals'))))
->input('points_counter',array('vadlidation'=>'integer','js_validation'=>'digit'))
->select('points_counter_logic',array('options'=>array('<' => t('smaller'),'<=' => t('smaller_equals'),'=' => t('equals'),'>' => t('bigger'),'>=' => t('bigger_equals'))))
->input('charge',array('vadlidation'=>'integer','js_validation'=>'digit'))
->select('charge_logic',array('options'=>array('<' => t('smaller'),'<=' => t('smaller_equals'),'=' => t('equals'),'>' => t('bigger'),'>=' => t('bigger_equals'))))
->datetime('reg_date',array('value' => '2008-1-1 00:00:00','range'=>'2008-'.date('Y')))
->datetime('last_visit',array('value' => '2008-1-1 00:00:00','range'=>'2008-'.date('Y')))
->input('charge_gift',array('vadlidation'=>'integer','js_validation'=>'digit'))
->buttons('gift');
if($result = $this->form->result()){
extract($result);
if(!empty($names)){
$names = _explode(',',$names);
$names = array_unique($names);
$users = $this->db->where_in('name',$names)->get('users')->result();
}
else {
if(!empty($user_groups)){
$this->db->where_in('user_group',$user_groups);
}
if(!empty($points)){
$this->db->where('points '.$points_logic.' '.$points);
}
if(!empty($points_counter)){
$this->db->where('points_counter '.$points_counter_logic.' '.$points_counter);
}
if(!empty($charge)){
$this->db->where('charge '.$charge_logic.' '.$charge);
}
if(!empty($reg_date)){
$this->db->where('reg_date > "'.$reg_date.'"');
}
if(!empty($last_visit)){
$this->db->where('last_visit > "'.$last_visit.'"');
}
$users = $this->db->get('users')->result();
}
if(!empty($users) && !empty($charge_gift)){
foreach($users as $user){
$this->db->update('users',array('charge'=>$user->charge+$charge_gift),array('id'=>$user->id));
$this->user->refresh($user->id);
}
msg(t('charge_gifted'));
}
}
$this->form->compile();
}
}
Добавление голосов пользователю
- Хук, который разместит значок для добавления голосов в панели пользователя.
- Скрипт на MooTools, который через prompt-окно запросит количество добавляемых голосов и отправит ajax-запрос на бекенд.
- Контроллер, который примет запрос на добавление голосов.
- Модель, которая добавит голосов пользователю.
Все это мы будет делать в процессе вместе с другими целями.
Хуки
Задачи, под которые придется использовать хуки вполне понятны:- Вывод голосования в ноды, комментарии, профили пользователей и сообщества.
- Размещение значка для добавления голосов в панели пользователя.
- Вывод информации о количестве голосов и о рейтинге пользователя в пользовательскую панель.
<?php
/**
* Points hooks
*
* @author Dmitriy Belyaev <admin@cogear.ru>
* @copyright Copyright © 2009, Dmitriy Belyeav
* @license http://cogear.ru/license.html
* @link http://cogear.ru
* @package Points
* @version $Id$
*/
/**
* Add points counters.
*
* @param object Breadcrumb
* @return void
*/
function points_breadcrumb_compile_($Breadcrumb){
$CI =& get_instance();
if($Breadcrumb->name == 'node_title' && $CI->gears->points->nodes->enabled){
$Breadcrumb->add($CI->points->code('node',$Breadcrumb->data,$CI->gears->points->nodes->show_points,$CI->gears->points->nodes->show_votes),100);
}
// Add charge votes to user
else if($Breadcrumb->name == 'userinfo_panel'){
if(acl('points add_charge') && $CI->gears->points->charge->enabled){
$Breadcrumb->add($CI->_template('points add_charge',array('user'=>$Breadcrumb->data),TRUE),2);
}
if($CI->gears->points->users->enabled){
$Breadcrumb->add($CI->points->code('user',$Breadcrumb->data,$CI->gears->points->users->show_points,$CI->gears->points->users->show_votes),100);
}
}
else if($Breadcrumb->name == 'comment_header' && $CI->gears->points->comments->enabled){
$Breadcrumb->add($CI->points->code('comment',$Breadcrumb->data,$CI->gears->points->comments->show_points,$CI->gears->points->comments->show_votes),100);
}
else if($Breadcrumb->name == 'community_header' && $CI->gears->points->community->enabled){
$Breadcrumb->add($CI->points->code('community',$Breadcrumb->data,$CI->gears->points->community->show_points,$CI->gears->points->community->show_votes),7);
}
}
/**
* Add points data into cpanel
*
* @param object $CI
* @return void
*/
function points_header($CI){
if(!$CI->user->get('id')) return;
if($CI->gears->points->users->enabled){
$points = array(
'data' => $CI->gears->points->users->round ? round($CI->user->get('points')) : $CI->user->get('points'),
'type'=>'text',
'id'=>'cpanel-points',
'link'=>l('/points/'),
);
if($CI->user->get('points') > 0){
$points['class'] = 'good';
}
elseif($CI->user->get('points') < 0){
$points['class'] = 'bad';
}
else {
$points['class'] = '';
}
$points['text'] = '<nobr>'.t('points rating',$points['data']).'</nobr>';
$CI->cpanel->add($points,3);
}
if($CI->gears->points->charge->enabled){
$charge = array(
'data' => $CI->user->get('charge'),
'type'=>'text',
'id'=>'cpanel-charge',
);
$charge['text'] = '<nobr>'.t('points charge',$charge['data']).'</nobr>';
$CI->cpanel->add($charge,4);
}
}
Контроллер
Задача контроллера — принимать и обрабатывать запросы пользователя, а также выводить список пользователей, сортируя его по их рейтингу.<?php
/**
* Points controller
*
* @author Dmitriy Belyaev <admin@cogear.ru>
* @copyright Copyright © 2009, Dmitriy Belyeav
* @license http://cogear.ru/license.html
* @link http://cogear.ru
* @package Points
* @version $Id$
*/
class Index extends Controller
{
/**
* Constructor
*/
public function __construct(){
parent::Controller();
}
/**
* Users top by points
*
* @param int Page
* @return void
*/
function index($page = 0){
$title = t('!points top');
$this->builder->h1($title,TRUE);
title($title,TRUE,TRUE);
$config['per_page'] = $this->gear->top->per_page;
$page = $this->pager((int)$page, $this->db->count_all_results('users',FALSE),$config);
$this->db->limit($page['limit'],$page['start']);
$users = $this->db->order_by('points','desc')->get('users')->result_array();
foreach($users as &$user){
$user['avatar'] = reset(make_icons($user['avatar']));
}
$header = array(
'avatar'=>array('','image','5%','class'=>'avatar'),
'name'=>array(fc_t('!user name'),'link','30%','left','before'=>'<h1>','after'=>'</h1>'),
'points'=>array(fc_t('!points top_rating'),'text','20%','before'=>'<h1>','after'=>'</h1>'),
);
if($this->gear->users->show_voted){
$header['points_counter'] = array(fc_t('!points top_voted'),'text','20%','before'=>'<h1>','after'=>'</h1>');
}
$info = array(
'link'=>array('/user'),
'link_add'=>array('url_name'),
'noname'=>'true',
);
$this->form->grid('top',$header,$users,$info)->compile();
}
/**
* Process voting
*
* @return json
*/
public function vote(){
$action = $this->input->post('action');
$type = $this->input->post('type');
$id = $this->input->post('id');
if(!$action OR
!$type OR
!$id OR
!in_array($type,array('node','comment','user','community')))
{
return _403();
}
/*
* Set default values
*/
$success = FALSE;
$msg = t('points failure');
$points = 0;
$charge = 0;
$table = $type == 'community' ? $type : $type.'s'; // node —> nodes
/*
* Set object of interests
*/
if(!$object = $this->db->get_where($table,array('id'=>$id))->row()){
$msg = t('points vote_nothing');
}
/*
* Check if user if logged
*/
if(!$this->user->is_logged()){
$msg = t('points registred_only');
}
/*
* Check self voting
*/
elseif($type == 'user' && $object->id == $this->user->get('id')
OR isset($object->aid) && $object->aid == $this->user->get('id')){
$msg = t('points vote_self');
}
/*
* Check charge
*/
elseif(!$this->user->get('charge') && $this->gears->points->charge->enabled){
$msg = t('points empty_charge');
}
/*
* Check votes duplicate
*/
elseif($this->points->check($type,$id)){
$msg = t('points duplicate');
}
/*
* Check if user is able to vote
*/
elseif($this->points->user_can_vote()) {
$points = $action == 'zero' ? $object->points : $this->points->calculate_vote_points($action,$object->points,$table);
// Insert log data
if($this->db->insert('points',array(
'type' => $type,
'tid' => $id,
'uid' => $this->user->get('id'),
'points' => $points
)) &&
// Update target data
$this->db->update($table,array(
'points' => $points,
'points_counter' => $object->points_counter + 1
),array('id'=>$object->id))){
if($this->gears->points->charge->enabled){
// Update charge info
$charge = $this->user->get('charge')-1;
$this->db->update('users',array('charge'=>$charge),array('id'=>$this->user->get('id')));
$this->user->refresh();
// Move charge to target user
if($this->gears->points->charge->gift){
switch($type){
case 'node':
case 'comment':
$uid = $object->aid;
break;
case 'user':
$uid = $object->id;
break;
case 'community':
$uid = $this->db->get_where('community_users',array('cid'=>$object->id,'role'=>'admin'))->row()->uid;
break;
}
$this->points->add_charge($uid);
}
// Flush node cache by tag
$this->cache->tags($table.'/'.$object->id)->clear();
}
if($type == 'node' && $this->gears->points->index_points){
if($points >= $this->gears->points->index_points && empty($object->promoted)){
$this->indexer->promote($object->id,FALSE);
}
elseif($points < $this->gears->points->index_points && !empty($object->promoted)){
$this->indexer->depromote($object->id);
}
}
// Flush points
$this->session->set('votes',FALSE);
$success = TRUE;
$msg = t('points success');
}
}
echo json_encode(array(
'success' => $success,
'msg' => $msg,
'points' => $points,
'points_counter' => t('points points_counter',$object->points_counter + 1),
'charge' => $charge,
'charge_plural' => t('points charge',$charge)
));
exit();
}
/**
* Add charge to user
*
* @return json
*/
public function add_charge(){
$uid = $this->input->post('uid');
if($uid && acl('points add_charge') && $this->points->add_charge($uid,$this->input->post('charge'))){
ajax(TRUE);
}
ajax(FALSE);
}
}
Скрипты
Как обычно, скрипты исполняют роль связующего звена между пользовательскими действиями и движком.function vote(action,type,id){
new Request.JSON({
url: '/ajax/points/vote/',
data: 'action='+action+'&type='+type+'&id='+id,
onComplete: function(re){
if(re.success){
switch(action){
case 'up':
$('vote-down-'+type+'-'+id).getParent().addClass('passive');
break;
case 'down':
$('vote-up-'+type+'-'+id).getParent().addClass('passive');
break;
}
$('votes-'+type+'-'+id).set('text',re.points);
if(re.points >= 0){
$('votes-'+type+'-'+id).removeClass('bad').addClass('good');
}
else {
$('votes-'+type+'-'+id).removeClass('good').addClass('bad');
}
if($('cpanel-charge')){
var charge_info = $('cpanel-charge').getElements('a');
charge_info[0].getElement('span').set('text',re.charge);
charge_info[1].set('text',re.charge_plural);
}
if($('points-counter-'+type+'-'+id) && re.points_counter){
$('points-counter-'+type+'-'+id).set('text',re.points_counter);
}
}
msg(re.msg);
}
}).post();
}
function add_charge(uid){
var charge = prompt(lang.points.add_charge,10);
if(charge){
new Request.JSON({
url: '/ajax/points/add_charge/',
data: 'uid='+uid+'&charge='+charge,
onComplete: function(re){
if(re.success){
msg(lang.points.add_charge_success);
}
else msg(lang.points.add_charge_failure);
}
}).post();
}
}
Стили
Применим технику спрайтов для того, чтобы все необходимые для реализации голосования картинки загружались одним запросом..vote-up a, .vote-down a, .votes a{
padding: 0 16px 0 0px;
width: 16px;
height: 16px;
}
.votes{
padding: 0 7px;
font-size: 1.2em;
text-align: center;
}
.voting div{
float: left;
vertical-align: baseline;
}
.vote-up a{
background: url(/gears/points/img/vote.gif) no-repeat 0px 0px;
}
.vote-up.passive a{
background: url(/gears/points/img/vote.gif) no-repeat -16px 0px;
}
.vote-down a{
background: url(/gears/points/img/vote.gif) no-repeat -34px 0px;
}
.vote-down.passive a{
background: url(/gears/points/img/vote.gif) no-repeat -49px 0px;
}
.votes a{
background: url(/gears/points/img/vote.gif) no-repeat -68px 0px;
cursor: pointer;
}
#cpanel-points.good a span, .good {
color:#7CCA4E;
}
#cpanel-points.bad a span,.bad {
color:#D64248;
}
#cpanel-points span, #cpanel-charge span{
font-size: 1.8em;
}
#cpanel-points a span{
color: #E3E3E3;
}
#cpanel-charge span{
color:#9C56CD;
}
.comment .vote-comment{
float: right;
display: block;
}
#userinfo_panel{
position: relative;
}
#userinfo_panel .voting{
margin: 10px 0 -10px 20px;
clear: right;
}
#userinfo_panel .vote-up,#userinfo_panel .vote-down{
padding-top: 3px;
}
#userinfo_panel .votes{
font-size: 1.4em;
}
#community_header {
position: relative;
}
#community_header .voting{
font-size: 1.2em;
margin: 10px 0 0 25px;
}
#community_header .voting .votes{
font-size: 1.2em;
vertical-align: middle;
}
.node .title .voting{
float: right;
font-size: 0.65em;
margin-top: 10px;
}
.points_counter{
font-size: 0.7em;
float: left;
padding: 0 5px;
}
Языковые переменные
[gears]
points = "Рейтинг"
points_description = "Фильтрация контента, используя систему рейтингов."
[points]
duplicate = "Повторное голосование запрещено."
registred_only = "Правом голоса обладают только зарегистрированные пользователи."
success = "Ваш голос учтен."
failure = "Произошла ошибка при зачислении голоса."
empty_charge = "У вас не хватает голосов для данного действия."
vote_self = "Вы не можете голосовать за самого себя."
vote_nothing = "За что голосуем?"
charge = "%z (голос|голоса|голосов)"
rating = "%z (очко рейтинга|очка рейтинга|очков рейтинга)"
add_charge = "Сколько голосов вы хотите добавить данному пользователю?"
add_charge_success = "Голоса успешно добавлены!"
add_charge_failure = "Не удалось добавить голоса пользователю.";
points_counter = "%d (голос|голоса|голосов)"
top = "Рейтинг пользователей"
top_rating = "Рейтинг"
top_voted = "Количество проголосовавших"
show_down = "Узнать оценку"
[acl]
add_charge = "Добавлять голоса пользователям"
[points_admin]
bonuses = "Бонусы"
index_points ="Рейтинг для топиков на главной странице"
min_to_vote = "Минимальный рейтинг пользователя для голосования"
bonus_amount = "Количество бонусных зарядов в сутки"
bonus_points_require = "Необходимые баллы для получения ежедневных бонусных зарядов"
charge = "Заряд"
max = "Максимальное количество зарядов"
enabled = "Включен"
show_points = "Показывать рейтинг до голосования"
show_votes = "Показывать количество проголосовавших"
show_to_guests = "Показывать гостям"
round = "Округлять значение до целых"
[points_admin_bonuses]
names = "Имя пользователя"
name_description = "Вы можете уточнить имя пользователя или же ввести несколько имен через запятую."
user_groups = "Группа пользователя"
user_groups_description = "Выберите одну или несколько пользовательских групп."
points = "Рейтинг пользователя"
points_counter = "Количество проголосовавших за пользователя"
charge = "Количество голосов у пользователя"
reg_date = "Зарегистрирован после"
last_visit = "Посещал сайт последний раз не позднее"
charge_gift = "Сколько голосов хотите подарить?"
charge_gifted = "Голоса успешно подарены."
gift = "Подарить голоса"
smaller = "меньше"
smaller_equals = "меньше или равно"
equals = "равно"
bigger = "больше"
bigger_equals = "больше или равно"
Модель
<?php
/**
* Points model
*
* @author Dmitriy Belyaev <admin@cogear.ru>
* @copyright Copyright © 2009, Dmitriy Belyeav
* @license http://cogear.ru/license.html
* @link http://cogear.ru
* @package Points
* @version $Id$
*/
class Points extends Model
{
/**
* User points
*
* @array
*/
private $votes = array();
/**
* Constructor
*/
public function __construct(){
parent::Model();
if($this->user->is_logged() && !$this->votes = $this->session->get('votes',TRUE)){
$this->votes = $this->user_votes($this->user->get('id'));
$this->session->set('votes',$this->votes);
if($this->gears->points->charge->enabled){
$last_charge_bonus = $this->user->get('last_charge_bonus');
$day = time() - 24*60*60;
if(!$last_charge_bonus OR strtotime($last_charge_bonus) < $day &&
!empty($this->gears->points->charge->bonus_points_require) &&
$this->gears->points->charge->bonus_points_require <= $this->user->get('points'))
{
$this->add_charge($this->user->get('id'),$this->gears->points->charge->bonus_amount);
}
}
}
}
/**
* Generate html code for vote
*
* @param string Type
* @param object Target object
* @param boolean Show points before vote
* @param boolean Show votes count
* @param boolean Make numeric value round
* @return string Output code
*/
public function code($type = 'node',$object = FALSE, $show_points = TRUE, $show_votes = TRUE,$round = TRUE){
$case = $type == 'community' ? $type : $type.'s';
if(!$this->user->is_logged() && empty($this->gears->points->$case->show_to_guests)){
return '';
}
$tid = $object->id;
switch($type){
case 'node':
case 'comment':
$is_owner = $object->aid == $this->user->get('id') ? TRUE : FALSE;
break;
case 'community':
$is_owner = $this->community->check($object) == 'admin' ? TRUE : FALSE;
break;
case 'user':
$is_owner = $object->id == $this->user->get('id') ? TRUE : FALSE;
}
return $this->_template('points > points',array(
'type' => $type,
'id' => $tid,
'is_owner' => $is_owner,
'show_points' => $show_points,
'show_votes' => $show_votes,
'voted' => isset($this->votes[$type][$tid]) ? $this->votes[$type][$tid]['points'] : FALSE,
'votes' => $round ? round($object->points) : $object->points,
'points_counter' => $object->points_counter
),TRUE);
}
/**
* Get user votes data
*
* @param int User id
* @return array
*/
private function user_votes($id = FALSE){
if($result = $this->db->get_where('points',array('uid'=>$id))->result_array()){
foreach($result as $point){
$votes[$point['type']][$point['tid']] = array(
'points'=>$point['points'],
'created_date'=>$point['created_date']
);
}
return $votes;
}
return array();
}
/**
* Check if user has voted
*
* @param string Type
* @param int Target id
* @return mixed
*/
public function check($type = 'node', $tid = FALSE){
return isset($this->votes[$type][$tid]);
}
/**
* Calculate points based on user authority
*
* @param string Action
* @param numeric Current points
* @param string Type of point.
* @return numeric
*/
public function calculate_vote_points($action,$points,$table = 'nodes'){
if($this->gears->points->$table->round){
$add = 1+round($this->user->get('points')/100*$this->gears->points->strength);
}
else {
$add = 1+round($this->user->get('points')/100*$this->gears->points->strength,2);
}
return $action == 'up' ? $points + $add : $points - $add;
}
/**
* Check if user have vote ability
*
* @return boolean
*/
public function user_can_vote(){
return $this->user->get('points') > ($this->gears->points->min_to_vote ? $this->gears->points->min_to_vote : -1);
}
/**
* Add charge to user
*
* @param int User id
* @param int Votes
* @return void
*/
public function add_charge($uid,$votes = 1){
if($user = $this->user->info($uid)){
if($this->gears->points->charge->max && $user->charge >= $this->gears->points->charge->max) return TRUE;
$this->db->update('users',array('charge'=>$user->charge+$votes,'last_charge_bonus' => date('Y-m-d H:i:s')
),array('id'=>$user->id));
$this->user->refresh($user->id);
return TRUE;
}
return FALSE;
}
}
Шаблоны
<div class="voting vote-{$type}">
<div class="vote-up{if $voted !== FALSE && $voted <= 0 OR $is_owner} passive{/if}"><a id="vote-up-{$type}-{$id}" href="javascript:void(0)" onclick="return vote('up','{$type}','{$id}')"></a></div>
{if $voted !== FALSE}
<div class="votes{if $votes > 0} good{elseif $votes == 0} zero{else} bad{/if}" id="votes-{$type}-{$id}">{$votes}</div>
{else}
{if $show_points OR $is_owner}
<div class="votes{if $votes > 0} good{elseif $votes == 0} zero{else} bad{/if}" id="votes-{$type}-{$id}">{$votes}</div>
{else}
<div class="votes{if $votes > 0} good{elseif $votes == 0} zero{else} bad{/if}" id="votes-{$type}-{$id}"><a onclick="return vote('zero','{$type}','{$id}')" title="{? t('points show_down')}"></a></div>
{/if}
{/if}
<div class="vote-down{if $voted !== FALSE && $voted >= 0 OR $is_owner} passive{/if}"><a id="vote-down-{$type}-{$id}" href="javascript:void(0)" onclick="return vote('down','{$type}','{$id}')"></a></div>
{if $show_votes}
<div class="points_counter" id="points-counter-{$type}-{$id}">{? t('points points_counter',$points_counter)}</div>
{/if}
</div>
<a href="javascript:void(0);" onclick="add_charge('{$user->id}')"><img src="/gears/points/img/add_charge.gif" width="16" height="16" alt="+"></a>
Схема
Создадим схему для автоматического создания и расширения таблиц в БД.CREATE TABLE `points` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`type` ENUM( 'node', 'comment', 'user', 'community' ) NOT NULL ,
`tid` INT UNSIGNED NOT NULL ,
`uid` INT UNSIGNED NOT NULL ,
`points` FLOAT( 2 ) NOT NULL ,
`created_date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ,
INDEX ( `type` , `tid` , `points` , `created_date` )
) ENGINE = MYISAM ;
ALTER TABLE `points` ADD UNIQUE (
`type` ,
`tid` ,
`uid`
);
ALTER TABLE `nodes` ADD `points` FLOAT( 3 ) NOT NULL AFTER `comments`,
ADD `points_counter` INT( 4 ) NOT NULL AFTER `points` ,
ADD INDEX ( `points` , `points_counter` );
ALTER TABLE `comments` ADD `points` FLOAT( 3 ) NOT NULL AFTER `body` ,
ADD `points_counter` INT( 4 ) NOT NULL AFTER `points` ,
ADD INDEX ( `points` , `points_counter` );
ALTER TABLE `community` ADD `points` FLOAT( 3 ) NOT NULL AFTER `created_date` ,
ADD `points_counter` INT( 4 ) NOT NULL AFTER `points` ,
ADD INDEX ( `points` , `points_counter` );
ALTER TABLE `users` ADD `points` FLOAT( 3 ) NOT NULL AFTER `avatar` ,
ADD `points_counter` INT( 4 ) NOT NULL AFTER `points` ,
ADD `charge` INT( 3 ) NOT NULL DEFAULT '3' AFTER `points_counter` ,
ADD `last_charge_bonus` DATETIME NULL DEFAULT NULL AFTER `charge`,
ADD INDEX ( `points` , `points_counter` , `charge` );
Изображения
Для реализации данной шестеренки нам понадобится всего два изображения. Используем прекрасный набор иконок famfamfam.
Заключение
Как результат проделанной работы мы получили отличную шестернку со множеством настроек, которая при всех своих возможностях не нагружает систему.Надеюсь, с каждым уроком логика работы с cogear все больше становится вам понятна.
Спасибо за проявленный интерес.
Давайте повторим, какие уроки вы бы хотели увидеть в ближайшем будущем?




По поводу уроков скажу, что я лично буду рад любому. Но, так как на сегодня Сogear не только сравнялась с конкурирующими CMS, но и по многим параметрам превосходит, то предлагаю сделать простые уроки, на широкую аудиторию, например про Виджеты о теме урока писал здесь.
И еще, скажите, пожалуйста, а уроки и пожелания (что где добавить или поменять) — это одно и тоже? Если нет, то куда можно высказывать пожелания?
Чем больше я узнаю, тем больше понимаю, что не знаю ничего.
Как говорил Сократ, «я знаю, что ничего не знаю».
По-хорошему, я хочу реализовать свои идеи с нуля, чтобы не зависеть от CI, о чем писал ранее.
То есть создать вторую ветку движка (назвать ее, допустим, 2.0 или, вообще, выделить ей отдельное имя — cogears), которая будет построена на собственном ядре с использованием jQuery.
Смотрите, теперь любой пользователь может создавать топики на этом сайте.
В «Баг-трекер» можно публиковать баги, в сообщество «На заметку» — пожелания и идеи.
Я об этом в курсе, а те шаги, которые вы делаете по увеличению функционала в версии 1.0 направлены в первую очередь на популяризацию движка в данный момент — я это понимаю и поддерживаю.
Зы: «очко рейтинга» — может просто рейтинг :) и кто оставил голоса тоже было бы неплохо показывать.
Отрицать это бессмысленно.
Просто для одних это будет действительно нужной информацией, а для других поводом к разборкам и прочей ненужной перепалки. Все зависит от контенгента людей на сайте.
Ну это мое личное имхо, если опционально будет, то явно лишним данная вещь не будет
В 1.0 скорее всего перехода на jQuery не будет.
Вижу это как две ветки — не хочется отказываться от того, что уже сделано на текущий момент, но для реализации всего задуманного следует начать с чистого листа.
Очень нравится движок! Считаю его лучшим блоговым и о-о-о-чень перспективным!
Вчера на habrahabr.ru наблюдал разборки с товарищем rumkin.habrahabr.ru/, а дело в том, что товарищ rumkin пообещал кому-то нарисовать иконки, а обещание не выполнил в итоге карма rumkinа упала с 149 до 14, правда, админы вмешались и заблокировали страницу rumkinа на 29, а сегодня удалили пост m.habrahabr.ru/post/70889/ который и послужил агиткой для действия. Я к чему виду, а может сделать, что бы можно устанавливать максимальное число падение рейтинга за сутки, а то так можно и пост с дезой делать и кого-то сливать неугодного.
Давайте я буду заниматься основной линией разработки, а вы — дополнительной :-)
Пора уже сообщество наше развивать.
Сейчас изменю эту цифру на единицу.