Version: current

      1 <?php
      2
      3 /**
      4  * Расширение которе можно повесить на объект
      5  * DataObject::add_extension('Image','RatingExtension');
      6  *
      7  * @author inxo, menedem
      8  */
      9 class RatingExtension extends DataObjectDecorator {
     10
     11     public $RatingInt = null;
     12     public $RatingFloat = null;
     13     public $Votes = null;
     14
     15     static $allow_comments = false;
     16     static $need_auth = false;
     17
     18     static $commentsPerPage = 5;
     19     static $max_rating = 5;
     20     static $vote_cookie_lifetime = 60;
     21
     22     /**
     23      * Дополнительный поля для объекта
     24      * @return <type>
     25      */
     26     function extraStatics() {
     27         return array(
     28             'casting' => array(
     29                 'RatingInt' => 'Int',
     30                 'RatingFloat' => 'Decimal',
     31                 'Votes' => 'Int',
     32                 'AlreadyVote' =>'Boolean',
     33                 'ShowForm' =>'Boolean'
     34             ),
     35         );
     36     }
     37
     38     static function allow_comments($allow = true) {
     39         self::$allow_comments = $allow;
     40     }
     41
     42     static function set_comments_per_page($count) {
     43         self::$commentsPerPage = $count;
     44     }
     45
     46     static function set_max_rating($rating = 5) {
     47         self::$max_rating = $rating;
     48     }
     49
     50     static function set_vote_cookie_lifetime($time) {
     51         self::$vote_cookie_lifetime = $time;
     52     }
     53
     54     static function set_need_auth($need = true) {
     55         self::$need_auth = $need;
     56     }
     57
     58     private function run_calc_rate(){
     59         $this->RatingFloat = 0;
     60         $this->RatingInt = 0;
     61         $this->Votes = 0;
     62
     63         $clazz = $this->owner->ClassName;
     64         $id = $this->owner->ID;
     65
     66         $votes = DataObject::get('RatingDataObject', 'ObjectClass LIKE \''.$clazz.'\' AND ObjectID = '.$id.'');
     67
     68         if (!$votes) {
     69             return false;
     70         }
     71
     72         $summ = 0;
     73         $this->Votes = Int::create('Int', $votes->Count());
     74
     75         foreach ($votes as $vote) {
     76             $summ += $vote->Rate;
     77         }
     78
     79         $this->RatingFloat = Decimal::create('Decimal', $summ / $this->Votes->getValue());
     80         $this->RatingInt = Int::create('Int', round($summ / $this->Votes->getValue()));
     81
     82         return true;
     83     }
     84
     85     function getRatingInt(){
     86         if(is_null($this->RatingInt))
     87             $this->run_calc_rate();
     88         return $this->RatingInt;
     89     }
     90
     91     function getRatingFloat(){
     92         if(is_null($this->RatingFloat))
     93             $this->run_calc_rate();
     94         return $this->RatingFloat;
     95     }
     96
     97     function getVotes(){
     98         if(is_null($this->Votes))
     99             $this->run_calc_rate();
    100         return $this->Votes;
    101     }
    102
    103     // Итератор для вывода оценки
    104     public function VotesIteration() {
    105         $rs = new DataObjectSet();
    106         $rating = $this->owner->Votes;
    107         if ($rating) {
    108             for($i = 0; $i < $rating->getValue(); $i++) {
    109                 $rs->push(new DataObject(array($i)));
    110             }
    111         }
    112         return $rs;
    113     }
    114
    115     // Итератор для вывода максимальной оценки
    116     public function MaxRatingIteration() {
    117         $rs = new DataObjectSet();
    118         $rating = self::$max_rating;
    119         $currentRating = $this->owner->RatingInt;
    120         for($i = 0; $i < $rating; $i++) {
    121             $active = ($currentRating) ? ($i < $currentRating->getValue()) : 0;
    122             $rs->push(new DataObject(array('Index' => $i, 'Active' => $active)));
    123         }
    124         return $rs;
    125     }
    126
    127     function getAlreadyVote(){
    128         $ratingID = Cookie::get('ratingid'); // Кука для идентификации
    129         if ($ratingID) {
    130             return !RatingDataObject::is_unique($ratingID, $this->owner->ClassName, $this->owner->ID);
    131         }
    132         return false;
    133     }
    134
    135     static function VoteNeedAuth() {
    136         return (self::$need_auth && !Member::CurrentUserID());
    137     }
    138
    139     public function VoteForm() {
    140         if (self::$need_auth) {
    141             if (!Member::CurrentUserID()) {
    142                 return false;
    143             }
    144         }
    145
    146         $votes = array();
    147         for($i = 1; $i <= self::$max_rating; $i++) {
    148             $votes[$i] = $i;
    149         }
    150
    151         $required = array();
    152         $fields = new FieldSet(
    153             new HiddenField("objid", "objid", $this->owner->ID),
    154             new HiddenField("objcls", "objcls", $this->owner->ClassName),
    155             new DropdownField('vote', _t('Rating.Vote',"Vote"), $votes)
    156         );
    157
    158         if (self::$allow_comments) {
    159             $required[] = 'author';
    160             $required[] = 'comment';
    161             $author = '';
    162             if (Member::currentUserID()) {
    163                 $member = Member::currentUser();
    164                 $author = trim($member->FirstName . ' ' . $member->Surname);
    165             }
    166             $fields->push(new TextField('author', _t('Rating.Author', "Author"), $author));
    167             $fields->push(new TextAreaField('comment', _t('Rating.Сomment', "Сomment")));
    168         }
    169
    170         $form = new Form(
    171             new RatingExtension_Controller(),
    172             "vote",
    173             $fields,
    174             new FieldSet(new FormAction('rating', 'Проголосовать')),
    175             new RequiredFields($required)
    176         );
    177         $form->disableSecurityToken();
    178         return $form;
    179     }
    180
    181     /**
    182      * Список комментариев с паджинацией
    183      *
    184      * return DataObjectSet
    185      */
    186     public function VoteComments() {
    187         $start = 0;
    188         // !!! start уже может использоваться для самой страницы
    189         if (isset($_GET['voteStart'])) {
    190             $start = (int) $_GET['voteStart'];
    191         }
    192         $count = self::$commentsPerPage;
    193         $ID = $this->owner->ID;
    194         $ClassName = $this->owner->ClassName;
    195
    196         $rs = DataObject::get('RatingDataObject', "ObjectClass = '{$ClassName}' AND ObjectID = '{$ID}'", 'ID DESC', '', "$start,$count");
    197         if (!$rs)
    198             return false;
    199         $rs->setPaginationGetVar('voteStart');
    200         return $rs;
    201     }
    202
    203     /**
    204      * md5 от случайной последовательности для засовывания в куку
    205      *
    206      * return string
    207      */
    208     static function genRatingID() {
    209         $str = '';
    210         for($i = 0; $i < 10; $i++) {
    211             $str .= rand(0, 9);
    212         }
    213         return md5($str . time());
    214     }
    215
    216 }
    217
    218 class RatingExtension_Controller extends ContentController {
    219
    220     /**
    221      * Принимает голос. Может обрабатывать как POST или GET запросы так и запросы в виде url: /rating/vote/$objcls/$objid[/$vote]
    222      *
    223      * @param SS_HTTPRequest $data
    224      *
    225      * @return mixed
    226      */
    227     function vote($data) {
    228         $error = false;
    229
    230         if (RatingExtension::VoteNeedAuth()) {
    231             $error = true;
    232             $error_msg = _t('Rating.NeedAuth',"Необходима авторизация");
    233         }
    234         // Подготовка параметров
    235         // POST
    236         $comment = '';
    237         $author = '';
    238         if(!$error && isset($data) && isset($data['objid']) && isset($data['objcls']) && isset($data['vote'])){
    239             if(is_numeric($data['objid']) && is_numeric($data['vote'])){
    240                 $vote = $data['vote'];
    241                 $cls = Convert::raw2sql($data['objcls']);
    242                 $id = Convert::raw2sql($data['objid']);
    243                 if (RatingExtension::$allow_comments) {
    244                     $comment = (isset($data['comment'])) ? Convert::raw2sql(trim($data['comment'])) : '';
    245                     $author = (isset($data['author'])) ? Convert::raw2sql(trim($data['author'])) : '';
    246                 }
    247             }
    248             else{
    249                 $error = true;
    250                 $error_msg = _t('Rating.Error',"Недопустимое действие");
    251             }
    252         }
    253         // GET
    254         elseif (!$error){ // TODO: в этом варианте стратегия должна быть голосуем за одного от общего родителя?
    255             $cls = Convert::raw2sql(Director::urlParam('ID'));
    256             $id = (int) Director::urlParam('OtherID');
    257             $vote = (int) Director::urlParam('AnotherID');
    258             $vote = ($vote) ? $vote : 1;
    259             //$vote = (isset($_REQUEST['vote']) && is_int($_REQUEST['vote'])) ? (int) $_REQUEST['vote'] : 1; // Так как у нас просто вариант проголосовать.
    260             if (RatingExtension::$allow_comments) {
    261                 $comment = (isset($_REQUEST['comment'])) ? Convert::raw2sql(trim($_REQUEST['comment'])) : '';
    262                 $author = (isset($_REQUEST['author'])) ? Convert::raw2sql(trim($_REQUEST['author'])) : '';
    263             }
    264         }
    265
    266         if (!(($vote >= 0) && ($vote <= RatingExtension::$max_rating))) {
    267             $error = true;
    268             $error_msg = _t('Rating.BadVote',"Неверная оценка!");
    269         }
    270
    271         // Сохраняем результат голосования
    272         if(!$error && isset($vote) && isset($cls) && isset($id)){
    273             // Проверим существование класса и что на нем висит расширение Rating
    274             if(ClassInfo::exists($cls) && ClassInfo::is_subclass_of($cls, 'DataObject') && DataObject::has_extension($cls, 'RatingExtension')){
    275                 $ratingID = Cookie::get('ratingid'); // Кука для идентификации
    276                 if (!$ratingID) {
    277                     $ratingID = RatingExtension::genRatingID();
    278                     Cookie::set('ratingid', $ratingID, RatingExtension::$vote_cookie_lifetime);
    279                 }
    280                 if (RatingDataObject::is_unique($ratingID, $cls, $id)) {
    281                     $rateData = new RatingDataObject(
    282                         array(
    283                             'RatingID'=> Convert::raw2sql($ratingID),
    284                             'ObjectClass' => $cls,
    285                             'ObjectID' => $id,
    286                             'Rate' => $vote,
    287                             'Comment' => $comment,
    288                             'Author' => $author,
    289                             'VotedUserID' => Member::currentUserID()
    290                         )
    291                     );
    292                     $rateData->write();
    293                 }
    294                 else {
    295                     $error = true;
    296                     $error_msg = _t('Rating.Voted',"Вы уже голосовали");
    297                 }
    298             } else {
    299                 $error = true;
    300                 $error_msg = _t('Rating.Error',"Недопустимое действие");
    301             }
    302         }
    303         // Решаем, какой запрос был
    304         if($this->isAjax()){
    305             // Возращаем данные
    306             if ($error) {
    307                 return Convert::array2json(array('error'=>$error_msg));
    308             }
    309             $obj = DataObject::get_by_id($cls, $id);
    310
    311             return Convert::array2json(array(
    312                 'RatingInt' => $obj->getRatingInt()->getValue(),
    313                 'RatingFloat' => $obj->getRatingFloat()->Nice(),
    314                 'Votes' => $obj->getVotes()->getValue(),
    315                 'AlreadyVote' => $obj->getAlreadyVote()
    316             ));
    317         }
    318
    319         if($error && isset($error_msg)){
    320             return $this->renderWith(array('RatingErrorPage', 'ErrorPage', 'Page'),array('Title'=>_t('Rating.ErrorTitle', 'Ошибка'), 'Content' => $error_msg));
    321         }
    322         Director::redirectBack();
    323         return null;
    324     }
    325
    326     /**
    327      * Возвращает по ajax очередную порцию комментариев, может принимать как POST или GET запросы так url вида: /rating/comments/$objcls/$objid/$start
    328      * Использует шаблон RatingCommentsList.ss
    329      *
    330      * @param SS_HTTPRequest $data
    331      *
    332      * @return string
    333      */
    334     function comments($data) {
    335         $error = false;
    336         if(isset($data) && isset($data['objid']) && isset($data['objcls']) && isset($data['start'])){
    337             if(is_numeric($data['objid']) && is_numeric($data['start'])){
    338                 $start = (int)$data['start'];
    339                 $cls = Convert::raw2sql($data['objcls']);
    340                 $id = Convert::raw2sql($data['objid']);
    341             }
    342             else{
    343                 $error = true;
    344                 $error_msg = _t('Rating.Error',"Недопустимое действие");
    345             }
    346         } else {
    347             $cls = Convert::raw2sql(Director::urlParam('ID'));
    348             $id = (int) Director::urlParam('OtherID');
    349             $start = (int) Director::urlParam('AnotherID');
    350         }
    351
    352         $comments = new DataObjectSet();
    353         if (!$error) {
    354             $count = RatingExtension::$commentsPerPage;
    355             $comments = DataObject::get('RatingDataObject', "ObjectClass = '{$cls}' AND ObjectID = '{$id}'", 'ID DESC', '', "$start,$count");
    356         }
    357         return $this->renderWith('RatingCommentsList', array('VoteComments'=> $comments));
    358     }
    359 }
    360

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -