Powered by CodeIgniter

Уроки

(28)
14
16 голосов
Учиться, учиться и еще раз учиться — развитие личности идет таким путем.
Рейтинг для CMSДрузья, шестеренка рейтинга завершена, и теперь она входит в состав дистрибутива. По-умолчанию она выключена, поэтому не забудьте ее активировать. Шестеренка удалась на славу — количеством настроек и возможных вариаций «по теме» вы будете довольны.
Поскольку с момента публикации прошлого урока появилось множество нововведений, то некоторые моменты мы пройдем заново.
Файловая структура остается без изменений, а вот количество настроек в файле конфигурации существенно увеличилось.
Шестеренка воздействует на ноды, комменты, пользователей и сообщества. Причем, вы можете по желанию активировать те или иные элементы. Обратимся к конфигурационному файлу.

Файл конфигурации

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 все больше становится вам понятна.
Спасибо за проявленный интерес.

Давайте повторим, какие уроки вы бы хотели увидеть в ближайшем будущем?
12:14 ← 26 сентября 2009 Отправить в Твиттер adminadmin  RSS comments 22

Комментарии (22) ↓

DrON DrON time 13:33 ← 26 сентября 2009 #
Спасибо, давно жду этого урока
inetlover inetlover time 13:50 ← 26 сентября 2009 #
Просто гениально и оригинально, не как у всех.

По поводу уроков скажу, что я лично буду рад любому. Но, так как на сегодня Сogear не только сравнялась с конкурирующими CMS, но и по многим параметрам превосходит, то предлагаю сделать простые уроки, на широкую аудиторию, например про Виджеты о теме урока писал здесь.

И еще, скажите, пожалуйста, а уроки и пожелания (что где добавить или поменять) — это одно и тоже? Если нет, то куда можно высказывать пожелания?
Автор
admin admin time 14:09 ← 26 сентября 2009 #
Спасибо, но мне еще очень многому нужно научиться.
Чем больше я узнаю, тем больше понимаю, что не знаю ничего.
Как говорил Сократ, «я знаю, что ничего не знаю».

По-хорошему, я хочу реализовать свои идеи с нуля, чтобы не зависеть от CI, о чем писал ранее.
То есть создать вторую ветку движка (назвать ее, допустим, 2.0 или, вообще, выделить ей отдельное имя — cogears), которая будет построена на собственном ядре с использованием jQuery.

Смотрите, теперь любой пользователь может создавать топики на этом сайте.
В «Баг-трекер» можно публиковать баги, в сообщество «На заметку» — пожелания и идеи.
inetlover inetlover time 16:17 ← 26 сентября 2009 #
Как говорил Сократ, «я знаю, что ничего не знаю»
с этим не поспоришь и никуда от этого не деться.

По-хорошему, я хочу реализовать свои идеи с нуля, чтобы не зависеть от CI, о чем писал ранее.
То есть создать вторую ветку движка (назвать ее, допустим, 2.0 или, вообще, выделить ей отдельное имя — cogears), которая будет построена на собственном ядре с использованием jQuery.
Я об этом в курсе, а те шаги, которые вы делаете по увеличению функционала в версии 1.0 направлены в первую очередь на популяризацию движка в данный момент — я это понимаю и поддерживаю.
dqpb dqpb time 19:54 ← 27 сентября 2009 #
вновь вазвращаюсь к вопросы 2.0 и 1.0 будут совместимы в шестеренках и мы без особой сложности сможем адаптировать уже наработанные шестеренки с 1.0 на 2.0 этот момент ещё в планах? Дим, а в 1.0 c mootool на jQuery всетаки будет переход?

Зы: «очко рейтинга» — может просто рейтинг :) и кто оставил голоса тоже было бы неплохо показывать.
mobman mobman time 20:06 ← 27 сентября 2009 #
и кто оставил голоса тоже было бы неплохо показывать.
А мне кажется не стоит. Иначе пойдет эффект взаимности ;)
dqpb dqpb time 20:15 ← 27 сентября 2009 #
По вашей логике вообще шестеренка виджет как наркотик! А что мне даст рейтинг? и тот голос который вы сняли? Ничего, динамичная информация, а раз это информация следует представлять её в полном объеме!
Автор
admin admin time 20:17 ← 27 сентября 2009 #
Можно это опционально сделать.
mobman mobman time 21:53 ← 27 сентября 2009 #
Да я впринципе и не спорю, а говорю лишь факт, что так и будет :)
Отрицать это бессмысленно.
Просто для одних это будет действительно нужной информацией, а для других поводом к разборкам и прочей ненужной перепалки. Все зависит от контенгента людей на сайте.
Ну это мое личное имхо, если опционально будет, то явно лишним данная вещь не будет
Автор
admin admin time 20:16 ← 27 сентября 2009 #
В новой версии хочу оставить свои основные наработки, но также и привнести то, что невозможно сделать на CI. Думаю, что перенести шестеренки на новый лад будет просто.
В 1.0 скорее всего перехода на jQuery не будет.
Вижу это как две ветки — не хочется отказываться от того, что уже сделано на текущий момент, но для реализации всего задуманного следует начать с чистого листа.
dqpb dqpb time 20:19 ← 27 сентября 2009 #
Дим, с чистого листа мы в крусе, просто подстравиваюсь под твои планы, дабы непопасть в просак :)
dqpb dqpb time 20:21 ← 27 сентября 2009 #
Можно это опционально сделать.
Согласен.
mobman mobman time 15:32 ← 26 сентября 2009 #
Отличный урок! Теперь постараюсь сам что-нибудь сделать своими руками!
Очень нравится движок! Считаю его лучшим блоговым и о-о-о-чень перспективным!
inetlover inetlover time 12:59 ← 29 сентября 2009 #
Информация для размышления.

Вчера на habrahabr.ru наблюдал разборки с товарищем rumkin.habrahabr.ru/, а дело в том, что товарищ rumkin пообещал кому-то нарисовать иконки, а обещание не выполнил в итоге карма rumkinа упала с 149 до 14, правда, админы вмешались и заблокировали страницу rumkinа на 29, а сегодня удалили пост m.habrahabr.ru/post/70889/ который и послужил агиткой для действия. Я к чему виду, а может сделать, что бы можно устанавливать максимальное число падение рейтинга за сутки, а то так можно и пост с дезой делать и кого-то сливать неугодного.
Автор
admin admin time 13:44 ← 29 сентября 2009 #
Это самая важная задача? Таких интересных моментов можно придумать множество.
Давайте я буду заниматься основной линией разработки, а вы — дополнительной :-)
Пора уже сообщество наше развивать.
inetlover inetlover time 14:35 ← 29 сентября 2009 #
Двумя руками за!!! Дима с твоей стороны не плохо запостить список работ, которые ты относишь к основной линии разработки, что бы хоть как то можно ориентироваться и понимать, что ты сделаешь, а что придется делать своими силами. Это актуально потому, что твой уровень намного выше в данном деле, чем наш вместе взятый.
Автор
admin admin time 14:42 ← 29 сентября 2009 #
Хорошо, ждите указаний в ближайшее время.
mobman mobman time 17:43 ← 29 сентября 2009 #
Я тоже согласен! Я бы попробывал что-нибудь сделать такое.
inetlover inetlover time 10:13 ← 30 сентября 2009 #
26 сентября, здесь была запущена шестеренка Рейтинг, я получил 10 голосов, израсходовав их я получил на следующий день снова 10, которые так же потратил и вот уже два дня я не получил не одного голоса, хотя, честно признаюсь на них рассчитывал :). Я был уверен, что каждые сутки пользователю в зависимости от его рейтинга выдается определенное количество голосов, но почему-то это оказалась не так :(.
Автор
admin admin time 10:27 ← 30 сентября 2009 #
Да, действительно, тем у кого рейтинг больше определенного числа (в данном случае больше трех), каждый день выдаются дополнительные голоса.
Сейчас изменю эту цифру на единицу.
inetlover inetlover time 00:20 ← 04 октября 2009 #
Наблюдаю за рейтингом с того момента как он появился, но до сих пор не понял, а куда уходят голоса за комментарии, посты и за сообщества?
Автор
admin admin time 02:13 ← 04 октября 2009 #
Что значит «куда»? В посты и комменты в ваш рейтинг и в новые голоса для вас, а для сообществ — просто для их рейтинга. Всмотритесь в код, голубчик!