AJAX star rating plugin

This article is also available in the following languages:
By schneimi
In the days of social networks, you often want to give users the possibility to rate things and to show the average rating. The most common user interface implementation is a star rating system. This plugin offers you an easy, customizable way to enable your users to star rate any CakePHP model you want.

Features

  • Multi user, multi model rating
  • Guest rating
  • Just one element in your views
  • Seamless integration with AJAX
  • Prototype and jQuery support
  • Cross browser compatibility
  • Fallback for disabled javascript
  • Various configurations

Requirements

  • CakePHP 1.2 or 1.3
  • Prototype or jQuery framework
  • User id stored in session for secure rating

Demonstration

A demo can be tested and downloaded at
http://ratingdemo.schneimi.spacequadrat.de/


Download

Latest version for CakePHP 1.2: http://www.wuala.com/mystic11/public/rating2.3.zip

Latest version for CakePHP 1.3: http://www.wuala.com/mystic11/public/rating2.4.zip

    Installation and Use

    1) Make sure you meet the requirements above. For the download and integration of a javascript framework, please visit http://www.prototypejs.org/ for Prototype and http://jquery.com/ for jQuery.


    2) Extract the plugin, including the subfolder rating, to your app plugins folder app/plugins.


    3) Copy the rating/config/plugin_rating.php to your app configs folder app/config and change the settings to your desire. It is recommended to let Rating.showHelp set to true until everything works.


    4) Apply the install.sql to your database to create the ratings table.


    CREATE TABLE `ratings` (
      `id` int(11) unsigned NOT NULL auto_increment,
      `user_id` char(36) NOT NULL default '',
      `model_id` char(36) NOT NULL default '',  
      `model` varchar(100) NOT NULL default '',
      `rating` tinyint(2) unsigned NOT NULL default '0',
      `name` varchar(100) default '',
      `created` datetime default NULL,
      `modified` datetime default NULL,
      PRIMARY KEY (`id`),
      KEY `rating` (`model_id`,`model`,`rating`,`name`)
    );

    5) Load the plugin javascript and css files into your layout. Replace [your_framework] with prototype_min or jquery_min depending on the framework you use.

    View Template:


    <?php
      
    echo $javascript->link('/rating/js/rating_[your_framework]');
      echo 
    $html->css('/rating/css/rating');
    ?>


    6) For full model integration in your app, apply the following relation to your models. (replace [name_of_your_model])

    Model Class:

    <?php 
    var $hasMany = array('Rating' => 
                         array(
    'className'   => 'Rating',
                               
    'foreignKey'  => 'model_id',
                               
    'conditions' => array('Rating.model' => '[name_of_your_model]'),
                               
    'dependent'   => true,
                               
    'exclusive'   => true
                   
    )
    );
    ?>


    7) If you set Rating.saveToModel to true, then add the defined Rating.modelAverageField and Rating.modelVotesField to all models you want to rate. To do that you can use the following SQL statements (replace [your_table], [Rating.modelAverageField], [Rating.modelVotesField]).

    ALTER TABLE [your_table] ADD (`[Rating.modelAverageField]` decimal(3,1) unsigned default '0.0');
    ALTER TABLE [your_table] ADD (`[Rating.modelVotesField]` int(11) unsigned default '0');

    If the plugin shows the fields are still missing, try to clear the model cache of your app at app/tmp/cache/models.


    8) You can change the styles of the rating element in the css file rating/vendors/css/rating.css.


    9) Finally you can place the rating element in your views as follows. (replace [name_of_your_model] and [id_of_your_model])


    Default rating element for one model id

    View Template:


    <?php
    echo $this->element('rating', array('plugin' => 'rating',
                                        
    'model' => '[name_of_your_model]',
                                        
    'id' => [id_of_your_model]));
    ?>

    Default rating element for one model id
    If you want to have different ratings for one model id like sound and picture of a movie, you can use the additional name parameter.

    View Template:


    <?php
      
    echo $this->element('rating', array('plugin' => 'rating',
                                          
    'model' => '[name_of_your_model]',
                                          
    'id' => [id_of_your_model],
                                          
    'name' => 'sound'));

      echo 
    $this->element('rating', array('plugin' => 'rating',
                                          
    'model' => '[name_of_your_model]',
                                          
    'id' => [id_of_your_model],
                                          
    'name' => 'picture'));
    ?>

    Individual configuration of a rating element
    Sometimes you want to use more than one style of rating elements in your app. That can be reached with the 'config' parameter and different config files in 'app/config'. Just clone the default 'plugin_rating.php' and give it a different name, which you then pass to the element.

    View Template:


      // uses 'plugin_rating.php' in 'app/config'
      echo $this->element('rating', array('plugin' => 'rating', 
                                          'model' => '[name_of_your_model]',
                                          'id' => [id_of_your_model]));
      
      // uses 'plugin_rating_style1.php' in 'app/config'
      echo $this->element('rating', array('plugin' => 'rating',
                                          'model' => '[name_of_your_model]',
                                          'id' => [id_of_your_model],
                                          'config' => 'plugin_rating_style1'));


    rating/config/plugin_rating.php
    <?php
    /**
     * Config file for the AJAX star rating plugin.
     *
     * @author Michael Schneidt <michael.schneidt@arcor.de>
     * @copyright Copyright 2009, Michael Schneidt
     * @license http://www.opensource.org/licenses/mit-license.php
     * @link http://bakery.cakephp.org/articles/view/ajax-star-rating-plugin-1
     * @version 2.4
     */

    /**
     * Disable the user rating.
     */
    $config['Rating.disable'] = false;

    /**
     * Show errors and warnings that should help to setup the plugin.
     */
    $config['Rating.showHelp'] = true;

    /**
     * CakePHP app root.
     * 
     * If you access your app like http://yourdomain/mycake then /mycake/ is your app root.
     */
    $config['Rating.appRoot'] = '';

    /**
     * Show a flash message after rating.
     * 
     * (displays 'Rating.flashMessage')
     */
    $config['Rating.flash'] = false;

    /**
     * Message shown on flash.
     * 
     * (depends on 'Rating.flash')
     */
    $config['Rating.flashMessage'] = __('Your rating has been saved.'true);

    /**
     * Enable fallback for disabled javascript.
     * 
     * (this inserts additional html code)
     */
    $config['Rating.fallback'] = true;

    /**
     * Show flash message on fallback save redirect.
     * 
     * (displays 'Rating.flashMessage')
     */
    $config['Rating.fallbackFlash'] = false;

    /**
     * User id location in the session data.
     */
    $config['Rating.sessionUserId'] = 'User.id';

    /**
     * Enable Guest rating. (ignores 'Rating.sessionUserId')
     * 
     * Guest access is stored in cookie to prevent multiple ratings (not secure!)
     */
    $config['Rating.guest'] = false;

    /**
     * Guest cookie duration time. (interpreted with strtotime())
     */
    $config['Rating.guestDuration'] = '1 week';

    /**
     * Maximum rating.
     */
    $config['Rating.maxRating'] = 5;

    /**
     * Location of the full star image.
     */
    $config['Rating.starFullImageLocation'] = 'rating/img/star-full.png';

    /**
     * Location of the empty star image.
     */
    $config['Rating.starEmptyImageLocation'] = 'rating/img/star-empty.png';

    /**
     * Location of the half star image.
     */
    $config['Rating.starHalfImageLocation'] = 'rating/img/star-half.png';

    /**
     * Save the average rating and vote count to the rated model.
     * 
     * This may speed up loading, because the values must not be
     * calculated from the ratings on every access. This is also 
     * helpful if you want to sort the model by rating data, e.g. 
     * using pagination sort.
     * 
     * This config only works, if you use no more than one rating 
     * element (name parameter) for each model id and no different 
     * config files (config parameter) with same field names set.
     * 
     * If set to true, you have to add the 'Rating.modelAverageField' 
     * and 'Rating.modelVotesField' to your rated models.
     */
    $config['Rating.saveToModel'] = false;

    /**
     * Field name in models for the average rating.
     * 
     * SQL: ALTER TABLE <model_table> ADD <Rating.modelAverageField> decimal(3,1) unsigned default '0.0';
     * 
     * (depends on 'Rating.saveToModel')
     */
    $config['Rating.modelAverageField'] = 'rating';

    /**
     * Field name in models for the rating votes.
     * 
     * SQL: ALTER TABLE <model_table> ADD <Rating.modelVotesField> int(11) unsigned default '0';
     * 
     * (depends on 'Rating.saveToModel')
     */
    $config['Rating.modelVotesField'] = 'votes';

    /**
     * Allow users to change their ratings.
     */
    $config['Rating.allowChange'] = true;

    /**
     * Allow users to delete their ratings by 
     * deselecting the current rating.
     * 
     * (depends on 'Rating.allowChange')
     */
    $config['Rating.allowDelete'] = true;

    /**
     * Display the user rating in stars instead of the average rating.
     */
    $config['Rating.showUserRatingStars'] = false;

    /**
     * Show a mark to indicate the user rating.
     *  
     * (change mark in /vendors/css/rating.css .rating-user)
     */
    $config['Rating.showUserRatingMark'] = true;

    /**
     * Define the text beside the stars.
     * 
     * %AVG% Average rating
     * %MAX% Maximum rating
     * %VOTES% Number of votes
     * %RATING% User rating
     */
    $config['Rating.statusText'] = '%AVG% / %MAX%  (%VOTES%)';

    /**
     * Show 'Rating.mouseOverMessages' on mouseover.
     */
    $config['Rating.showMouseOverMessages'] = true;

    /**
     * Messages that are showing on mouseover.
     *  
     * If you want to put links into the messages like for login, you have
     * to do that manually, because the CakePHP helpers don't work here yet.
     * 
     * 'login' this message appears if the user is not signed in.
     * 'rated' this message appears if the user rated already.
     * 'delete' this message appears if the user mouseovers his rating and 'Rating.allowDelete' is set true.
     * '1' to 'Rating.maxRating' represent the different rating values.
     * 
     * (depends on 'Rating.showMouseOverMessages')
     */
    $config['Rating.mouseOverMessages'] = array('login' => __('Please login to rate'true),
                                                
    'rated' => __('Thanks for your rating'true),
                                                
    'delete' => __('Click to remove your rating'true),
                                                
    '1' => __('Really bad'true),
                                                
    '2' => __('Bad'true),
                                                
    '3' => __('Average'true),
                                                
    '4' => __('Good'true),
                                                
    '5' => __('Really good'true));
    ?>


    rating/models/ratings.php

    Model Class:

    <?php 
    /**
     * Model for the AJAX star rating plugin.
     *
     * @author Michael Schneidt <michael.schneidt@arcor.de>
     * @copyright Copyright 2009, Michael Schneidt
     * @license http://www.opensource.org/licenses/mit-license.php
     * @link http://bakery.cakephp.org/articles/view/ajax-star-rating-plugin-1
     * @version 2.4
     */
    class Rating extends Model {
      var 
    $name 'Rating';
      
      var 
    $validate = array('user_id' => array('rule' => array('maxLength'36),
                                               
    'required' => true),
                            
    'model_id' => array('rule' => array('maxLength'36),
                                                
    'required' => true),
                            
    'model' => array('rule' => 'alphaNumeric',
                                             
    'required' => true));
    }
    ?>


    rating/views/ratings/view.ctp

    View Template:


    <?php
    /**
     * View for the AJAX star rating plugin.
     *
     * @author Michael Schneidt <michael.schneidt@arcor.de>
     * @copyright Copyright 2009, Michael Schneidt
     * @license http://www.opensource.org/licenses/mit-license.php
     * @link http://bakery.cakephp.org/articles/view/ajax-star-rating-plugin-1
     * @version 2.4
     */
    ?>
     
    <?php
      
    // decision to enable or disable the rating
      
    $enable = ($session->check(Configure::read('Rating.sessionUserId')) // logged in user or guest
                   
    || (Configure::read('Rating.guest') && $session->check('Rating.guest_id')))
                 && !
    Configure::read('Rating.disable'// plugin is enabled
                 
    && (Configure::read('Rating.allowChange'// changing is allowed or it's the first rating
                     
    || (!Configure::read('Rating.allowChange') && $data['%RATING%'] == 0));

      
    // the images are displayed here before js initialization to avoid flickering.
      
    echo $rating->stars($model$id$data$options$enable);
      
      
    // format the statusText and write it back
      
    $text $rating->format(Configure::read('Rating.statusText'), $data);
      
    Configure::write('Rating.statusText'$text);
    ?>

    <div id="<?php echo $model.'_rating_'.$options['name'].'_'.$id.'_text'?>" class="<?php echo !empty($text) ? 'rating-text' 'rating-notext'?>">
      <?php
        
    echo $text;
      
    ?>
    </div>

    <?php
      
    // initialize the rating element
      
    if (!Configure::read('Rating.disable')) {
        echo 
    $javascript->codeBlock("ratingInit('".$model.'_rating_'.$options['name'].'_'.$id."', "
                                               
    ."'".addslashes(json_encode($data))."'," 
                                               
    ."'".addslashes(json_encode(Configure::read('Rating')))."',"
                                               
    ."'".addslashes(json_encode($options))."',"
                                               
    .intval($enable).");");
      }
    ?>

    <?php if (Configure::read('Rating.fallback')): ?>
    <noscript>
      <div class="fallback">
        <?php
          
    if ($enable) {
            
    // show fallback form
            
    echo $form->create('Rating'
                               array(
    'type' => 'get',
                                     
    'url' => array('action' => 'save')));
            echo 
    $form->radio('value',
                              
    $rating->options(), 
                              array(
    'legend' => false,
                                    
    'id' => $model.'_rating_'.$options['name'].'_'.$id,
                                    
    'value' => $data['%RATING%']));
            echo 
    $form->hidden('model', array('value' => $model));
            echo 
    $form->hidden('rating', array('value' => $id));
            echo 
    $form->hidden('name', array('value' => $options['name']));
            echo 
    $form->hidden('config', array('value' => $options['config']));
            echo 
    $form->hidden('fallback', array('value' => true));
            echo 
    $form->submit(__('Vote'true),
                               array(
    'div' => false,
                                     
    'title' => __('Vote'true)));
            
            echo 
    $form->end();
          }
        
    ?>
      </div>
      
      <?php
        
    // get mouseover messages for showing
        
    $mouseOverMessages Configure::read('Rating.mouseOverMessages');
      
    ?>
      
      <?php // show login message
            
    if (!$enable && Configure::read('Rating.showMouseOverMessages')
                && !empty(
    $mouseOverMessages['login'])
                && !
    Configure::read('Rating.disable')
                && 
    $data['%RATING%'] == 0): ?>
        <div id="<?php echo $model.'_rating_'.$options['name'].'_'.$id.'_text'?>" class="<?php echo !empty($text) ? 'rating-text' 'rating-notext'?>">
          <?php
            
    echo $mouseOverMessages['login'];
          
    ?>
        </div>
      <?php endif; ?>
      
      <?php // show rated message
            
    if (!$enable && Configure::read('Rating.showMouseOverMessages')
                && !empty(
    $mouseOverMessages['rated'])
                && 
    $data['%RATING%'] > 0): ?>
        <div id="<?php echo $model.'_rating_'.$options['name'].'_'.$id.'_text'?>" class="<?php echo !empty($text) ? 'rating-text' 'rating-notext'?>">
          <?php
            
    echo $mouseOverMessages['rated'];
          
    ?>
        </div>
      <?php endif; ?>
    </noscript>
    <?php endif; ?>

    <?php
      
    // show flash message
      
    if (Configure::read('Rating.flash')) {
        
    $session->flash('rating');
      }
      
      
    // debug sql dump
      
    echo $this->element('sql_dump');
    ?>

    rating/views/elements/rating.ctp

    View Template:


    <?php
    /**
     * Element for the AJAX star rating plugin.
     *
     * @author Michael Schneidt <michael.schneidt@arcor.de>
     * @copyright Copyright 2009, Michael Schneidt
     * @license http://www.opensource.org/licenses/mit-license.php
     * @link http://bakery.cakephp.org/articles/view/ajax-star-rating-plugin-1
     * @version 2.4
     */
    ?>

    <?php
      
    // default name
      
    if (empty($name)) {
        
    $name 'default';
      }
      
      
    // default config
      
    if (empty($config)) {
        
    $config 'plugin_rating';
      }
    ?>

    <div id="<?php echo $model.'_rating_'.$name.'_'.$id?>" class="rating">
      <?php
        
    echo $this->requestAction('rating/ratings/view/'.$model.'/'.$id.'/'.base64_encode(json_encode(array('name' => $name'config' => $config))), array('return'));
      
    ?>
    </div>

    rating/views/helpers/rating.php
    <?php
    /**
     * Helper for the AJAX star rating plugin.
     *
     * @author Michael Schneidt <michael.schneidt@arcor.de>
     * @copyright Copyright 2009, Michael Schneidt
     * @license http://www.opensource.org/licenses/mit-license.php
     * @link http://bakery.cakephp.org/articles/view/ajax-star-rating-plugin-1
     * @version 2.4
     */
    class RatingHelper extends AppHelper {
      var 
    $helpers = array('Html''Form''Session');

      
    /**
       * Creates the stars for a rating.
       *
       * @param string $model Model name
       * @param integer $id Model id
       * @param array $data Rating data
       * @param array $options Options
       * @param boolean $enable Enable element
       * @return Stars as HTML images
       */
      
    function stars($model$id$data$options$enable) {
        
    $output '';
        
    $starImage Configure::read('Rating.starEmptyImageLocation');
        
        if (
    Configure::read('Rating.showUserRatingStars')) {
          
    $stars $data['%RATING%'];
        } else {
          
    $stars $data['%AVG%'];
        }
        
        for (
    $i 1$i <= $data['%MAX%']; $i++) {
          if (
    $i <= floor($stars)) {
            
    $starImage Configure::read('Rating.starFullImageLocation');
          } else if (
    $i == floor($stars) + && preg_match('/[0-9]\.[5-9]/'$stars)) {
            
    $starImage Configure::read('Rating.starHalfImageLocation');
          } else {
            
    $starImage Configure::read('Rating.starEmptyImageLocation');
          }
          
          if (
    Configure::read('Rating.showUserRatingMark') && $i <= $data['%RATING%']) {
            
    $class 'rating-user';
          } else {
            
    $class 'rating';
          }
          
          if (!
    $enable) {
            
    $class .= '-disabled';
          }
          
          
    $htmlImage $this->Html->image('/'.$starImage
                                          array(
    'class' => $class,
                                                
    'id' => $model.'_rating_'.$options['name'].'_'.$id.'_'.$i,
                                                
    'alt' => __('Rate it with 'true).$i));

          if (
    Configure::read('Rating.fallback')) {
            
    $output .= $this->Form->label($model.'.rating'
                                          
    $htmlImage
                                          array(
    'for' => $model.'Rating'.ucfirst($options['name']).$id.$i,
                                                
    'class' => 'fallback'));
          } else {
            
    $output .= $htmlImage;
          }
        }

        return 
    $output;
      }
      
      
    /**
       * Formats a text in replacing data wildcards.
       *
       * @param string $text
       * @param array $data
       * @return Formatted text
       */
      
    function format($text$data) {
        foreach (
    $data as $wildcard => $value) {
          
    $text str_replace($wildcard$value$text);
        }
        
        
    // fix lost blanks in js (excluding blanks between html tags)
        
    $text preg_replace('/(?!(?:[^<]+>|[^>]+<\/(.*)>))( )/''&nbsp;'$text);
        
        return 
    $text;
      }
      
      
    /**
       * Creates options for fallback radio buttons.
       * 
       * @return Radio options
       */
      
    function options() {
        
    $options = array();
        
        if (
    Configure::read('Rating.showMouseOverMessages')) {
          
    $options Configure::read('Rating.mouseOverMessages');
          unset(
    $options['login'], $options['rated'], $options['delete']);
        } else {
          
    $options range(0Configure::read('Rating.maxRating'));
          unset(
    $options[0]);
        }
        
        return 
    $options;
      }
    }
    ?>


    rating/controllers/ratings_controller.php

    Controller Class:

    <?php 
    /**
     * Controller for the AJAX star rating plugin.
     *
     * @author Michael Schneidt <michael.schneidt@arcor.de>
     * @copyright Copyright 2009, Michael Schneidt
     * @license http://www.opensource.org/licenses/mit-license.php
     * @link http://bakery.cakephp.org/articles/view/ajax-star-rating-plugin-1
     * @version 2.4
     */
    class RatingsController extends RatingAppController {
      
    /**
       * Renders the content for the rating element.
       *
       * @param string $model Name of the model
       * @param integer $id Id of the model
       * @param string $options JSON/BASE64 encoded options
       */
      
    function view($model ''$id 0$options '') {
        
    $this->layout null;
        
        
    $userRating null;
        
    $avgRating null;
        
    $votes null;
        
    $modelInstance ClassRegistry::init($model);
        
    $optionsData json_decode(base64_decode($options), true);
        
        
    $name $optionsData['name'];
        
    $config $optionsData['config'];    
        
        
    // load the config file
        
    $this->__loadConfig($config);
        
        
    // setup guest access
        
    if (Configure::read('Rating.guest'
            && !
    $this->Session->check(Configure::read('Rating.sessionUserId'))) {
          
    $this->__setupGuest();
        }
        
        
    // check if user id exists in session
        
    if (Configure::read('Rating.showHelp'
            && !
    Configure::read('Rating.guest'
            && (!
    $this->Session->check(Configure::read('Rating.sessionUserId')) 
                || !
    $this->Session->read(Configure::read('Rating.sessionUserId')) > 0)) {
          echo 
    '<p>Warning: No valid user id was found at "'.Configure::read('Rating.sessionUserId').'" in the session.</p>';
        }
        
        
    // check if model id exists
        
    $modelInstance->id $id;
        
        if (
    Configure::read('Rating.showHelp') && !$modelInstance->exists(true)) {
          echo 
    '<p>Error: The model_id "'.$id.'" of "'.$model.'" does not exist.</p>';
        }

        
    // choose between user id and guest id
        
    if (!$this->Session->read(Configure::read('Rating.sessionUserId')) 
            && (
    Configure::read('Rating.guest') && $this->Session->read('Rating.guest_id'))) {
          
    $userId $this->Session->read('Rating.guest_id');
        } else {
          
    $userId $this->Session->read(Configure::read('Rating.sessionUserId'));
        }

        if (!empty(
    $userId)) {
          
    $userRating $this->Rating->field('rating',
                                             array(
    'model' => $model
                                                   
    'model_id' => $id
                                                   
    'user_id' => $userId,
                                                   
    'name' => $name));
        }

        if (empty(
    $userRating)) {
          
    $userRating 0;
        }
        
        
    // retrieve rating values from model or calculate them
        
    if (Configure::read('Rating.saveToModel')) {
          if (
    Configure::read('Rating.showHelp'
              && !
    $modelInstance->hasField(Configure::read('Rating.modelAverageField'))) {
            echo 
    '<p>Error: The average field "'.Configure::read('Rating.modelAverageField').'" in the model "'.$model.'" does not exist.</p>';
          }
          
          if (
    Configure::read('Rating.showHelp'
              && !
    $modelInstance->hasField(Configure::read('Rating.modelVotesField'))) {
            echo 
    '<p>Error: The votes field "'.Configure::read('Rating.modelVotesField').'" in the model "'.$model.'" does not exist.</p>';
          }
          
          
    $values $modelInstance->find(array($modelInstance->name.".".$modelInstance->primaryKey => $id),
                                         array(
    Configure::read('Rating.modelAverageField'), 
                                               
    Configure::read('Rating.modelVotesField')),
                                         
    null,
                                         -
    1);
          
          
    $avgRating $values[$modelInstance->name][Configure::read('Rating.modelAverageField')];
          
    $votes $values[$modelInstance->name][Configure::read('Rating.modelVotesField')];
        } else {
          
    $values $this->Rating->find(array('model' => $model,
                                              
    'model_id' => $id,
                                              
    'name' => $name),
                                        array(
    'AVG(Rating.rating)''COUNT(*)'));
          
          
    $avgRating round($values[0]['AVG(`Rating`.`rating`)'], 1);
          
    $votes $values[0]['COUNT(*)'];
        }
        
        if (empty(
    $votes)) {
          
    $votes 0;
        }
        
        if (
    $avgRating && !strpos($avgRating'.')) {
          
    $avgRating $avgRating.'.0';
        } else if (!
    $avgRating) {
          
    $avgRating '0.0';
        }

        
    $this->set('id'$id);
        
    $this->set('model'$model);
        
    $this->set('config'$config);
        
    $this->set('options'$optionsData);
        
    $this->set('data', array('%VOTES%' => $votes.' '.__n('vote''votes'$votestrue), 
                                 
    '%RATING%' => $userRating
                                 
    '%AVG%' => $avgRating,
                                 
    '%MAX%' => Configure::read('Rating.maxRating')));
        
    $this->render('view');
      }
      
      
    /**
       * Saves the user selected rating value. Depending on the plugin 
       * configuration, it also updates or deletes the rating.
       *
       * @param string $model Name of the model
       * @param integer $id Id of the model
       * @param integer $value User rating value
       */
      
    function save($model ''$id 0$value 0) {
        
    $this->layout null;
        
    $saved false;
        
    $fallback false;
        
    $referer Controller::referer();
        
        
    $name $this->params['url']['name'];
        
    $config $this->params['url']['config'];
        
        
    // load the config file
        
    $this->__loadConfig($config);
        
        
    // data from fallback form
        
    if (isset($this->params['url']['fallback']) 
            && 
    $this->params['url']['fallback']) {
          
    $fallback true;
          
          
    $model $this->params['url']['model'];
          
    $id $this->params['url']['rating'];
          
    $value $this->params['url']['value'];
        }

        
    // check if model id exists
        
    $modelInstance ClassRegistry::init($model);
        
    $modelInstance->id $id;
        
        if (!
    $modelInstance->exists(true)) {
          if (!
    $fallback) {
            
    $this->view($model$idbase64_encode(json_encode(array('name' => $name'config' => $config))));
          } else {
            
    $this->redirect($referer);
          }
          
          return;
        }
        
        
    // choose between user and guest id
        
    if (Configure::read('Rating.guest') && $this->Session->read('Rating.guest_id')) {
          
    $userId $this->Session->read('Rating.guest_id');
        } else {
          
    $userId $this->Session->read(Configure::read('Rating.sessionUserId'));
        }
        
        
    // check if a rating already exists 
        
    $userRating $this->Rating->find(array('model' => $model
                                                
    'model_id' => $id
                                                
    'user_id' => $userId,
                                                
    'name' => $name));
        
        
    // save, update or delete rating
        
    if (!empty($userRating) && Configure::read('Rating.allowChange')) {
          
    $this->Rating->id $userRating['Rating']['id'];
          
          if (
    $userRating['Rating']['rating'] == $value && Configure::read('Rating.allowDelete')) {
            
    $this->Rating->delete($userRating['Rating']['id']);
            
    $saved true;
          } else {
            
    $saved $this->Rating->saveField('rating'$value);
          }
        } else if (empty(
    $userRating) && $userId) {
          
    $this->data['Rating']['rating'] = $value;
          
    $this->data['Rating']['model'] = $model;
          
    $this->data['Rating']['model_id'] = $id;
          
    $this->data['Rating']['user_id'] = $userId;
          
    $this->data['Rating']['name'] = $name;
          
          
    $this->Rating->create();
          
    $saved $this->Rating->save($this->data);
        }
        
           
        
    // set flash message
        
    if ($saved && Configure::read('Rating.flash')) {
          
    $this->Session->setFlash(Configure::read('Rating.flashMessage'), 
                                   
    'default'
                                   array(
    'class' => 'rating-flash'),
                                   
    'rating');
        }    
        
        
    // save rating values to model
        
    if ($saved && Configure::read('Rating.saveToModel')) {
          
    // check if fields exist in model
          
    if (!$modelInstance->hasField(Configure::read('Rating.modelAverageField')) 
              && !
    $modelInstance->hasField(Configure::read('Rating.modelVotesField'))) {
            if (!
    $fallback) {
              
    $this->view($model$idbase64_encode(json_encode(array('name' => $name'config' => $config))));
            } else {
              
    $this->redirect($referer);
            }
            
            return;
          }
          
          
    // retrieve actual rating values 
          
    $values $this->Rating->find(array('model' => $model,
                                              
    'model_id' => $id,
                                              
    'name' => $name),
                                        array(
    'AVG(Rating.rating)''COUNT(*)'));

          
    $avgRating round($values[0]['AVG(`Rating`.`rating`)'], 1);
          
    $votes $values[0]['COUNT(*)'];
          
          if (
    $avgRating && !strpos($avgRating'.')) {
            
    $avgRating $avgRating.'.0';
          } else if (!
    $avgRating) {
            
    $avgRating '0.0';
          }

          if (empty(
    $votes)) {
            
    $votes '0';
          }
          
          
    $modelInstance->id $id;
          
          
    // save rating values
          
    if ($modelInstance->exists()) {
            
    $modelInstance->saveField(Configure::read('Rating.modelAverageField'), $avgRating);
            
    $modelInstance->saveField(Configure::read('Rating.modelVotesField'), $votes);
          }
        }
        
        
    // show view again
        
    if (!$fallback) {
          
    $this->view($model$idbase64_encode(json_encode(array('name' => $name'config' => $config))));
        } else {
          if (
    $saved && Configure::read('Rating.fallbackFlash')) {
            
    $this->flash(Configure::read('Rating.flashMessage'), Controller::referer());
            
    $this->Session->setFlash(null);
          } else {
            
    $this->redirect($referer);
          }
        }
        
        
    $this->autoRender false;
      }
      
      
    /**
       * Loads a config file.
       * 
       * @param $file Name of the config file
       */
      
    function __loadConfig($file) {
        
    // still support config values of v2.3 elements
        
    if (count(explode('.'$file)) > 0) {
          
    $file str_replace('.''_'$file);
        }
        
        
    // load config from app config folder
        
    if (Configure::load($file) === false) {
          
    // load config from plugin config folder
          
    if (Configure::load('rating.'.$file) === false) {
            echo 
    '<p>Error: The '.$file.'.php could not be found in your app/config or app/plugins/rating/config folder. Please create it from the default rating/config/plugin_rating.php.</p>';
          }
        }
      }
      
      
    /**
       * Setup the guest id in session and cookie.
       */
      
    function __setupGuest() {
        if (!
    $this->Session->check('Rating.guest_id'
            && !
    $this->Cookie->read('Rating.guest_id')) {
          
    App::import('Core''String');
          
    $uuid String::uuid();

          
    $this->Session->write('Rating.guest_id'$uuid);
          
    $this->Cookie->write('Rating.guest_id'$uuidfalseConfigure::read('Rating.guestDuration'));
        } else if (
    Configure::read('Rating.guest'
                   && 
    $this->Cookie->read('Rating.guest_id')) {
          
    $this->Session->write('Rating.guest_id'$this->Cookie->read('Rating.guest_id'));
        }
      }
    }
    ?>

    rating/rating_app_controller.php

    Controller Class:

    <?php 
    /**
     * AppController for the AJAX star rating plugin.
     *
     * @author Michael Schneidt <michael.schneidt@arcor.de>
     * @copyright Copyright 2009, Michael Schneidt
     * @license http://www.opensource.org/licenses/mit-license.php
     * @link http://bakery.cakephp.org/articles/view/ajax-star-rating-plugin-1
     * @version 2.4
     */ 
    class RatingAppController extends AppController {
      var 
    $uses = array('rating.Rating');
      var 
    $helpers = array('Javascript''rating.Rating');
      var 
    $components = array('Cookie''Session');
    }
    ?>


    rating/vendors/js/rating_jquery.js
    /**
     * jQuery javascript for the CakePHP AJAX star rating plugin.
     *
     * @author Michael Schneidt <michael.schneidt@arcor.de>
     * @copyright Copyright 2009, Michael Schneidt
     * @license http://www.opensource.org/licenses/mit-license.php
     * @link http://bakery.cakephp.org/articles/view/ajax-star-rating-plugin-1
     * @version 2.4
     */

    /**
     * Holds the settings for all rating elements.
     */
    var ratingSettings = new Array();

    /**
     * Initializes the rating element.
     *
     * @param element Id of the rating element
     * @param data JSON encoded rating data
     * @param config JSON encoded plugin configurations
     * @param options JSON encoded rating options
     * @param enabled Enable the rating for a user
     */
    function ratingInit(element, data, config, options, enabled) {
      ratingSettings[element] = new Array();  
      
      ratingSettings[element]['data'] = eval('(' + data + ')');
      ratingSettings[element]['options'] = eval('(' + options + ')');
        ratingSettings[element]['config'] =  eval('(' + config + ')');
      ratingSettings[element]['enabled'] = enabled;
      
      for (var i = 1; i <= ratingSettings[element]['data']['%MAX%']; i++) {
        $('#' + element + '_' + i).bind('mouseenter', {i: i}, function(e) {
          // workaround against event after reload
          var target = e.relatedTarget || e.toElement;
          
          if (target && target.id != element + '_' + e.data.i) {
            if (ratingSettings[element]['enabled']) {
              ratingSet(element, e.data.i);
            }
            
            if (ratingSettings[element]['config']['showMouseOverMessages']) {
              ratingMessages(element, e.data.i);
            }
          }
        });
        
        if (ratingSettings[element]['enabled']) {
          $('#' + element + '_' + i).bind('click', {i: i}, function(e) {
            ratingSave(element, e.data.i);
          });
        }
      }

      $('#' + element).bind('mouseleave', function(e) {
        ratingReset(element)
      });
      
      $('#' + element + '_text').bind('mouseenter', function(e) {
        ratingReset(element);
        
        if (ratingSettings[element]['config']['showMouseOverMessages']) {
          ratingMessages(element);
        }
      });
      
      ratingReset(element);
    }

    /**
     * Sets the rating element to a rating value.
     *
     * @param element Name of element
     * @param value Rating value 
     */
    function ratingSet(element, value) {
      var starImg = ratingSettings[element]['config']['starEmptyImageLocation'];
      
      for (i = 1; i <= ratingSettings[element]['data']['%MAX%']; i++) {
        if (i <= Math.floor(value)) {
          starImg = ratingSettings[element]['config']['starFullImageLocation'];
        } else if (i == Math.floor(value) + 1 && value.toString().match(/[0-9]\.[5-9]/)) {
          starImg = ratingSettings[element]['config']['starHalfImageLocation'];
        } else {
          starImg = ratingSettings[element]['config']['starEmptyImageLocation'];
        }
        
        $('#' + element + '_' + i).attr({'src': ratingSettings[element]['config']['appRoot'] + starImg});
        
        // set user mark
        if (ratingSettings[element]['config']['showUserRatingMark'] && i <= ratingSettings[element]['data']['%RATING%']) {
          $('#' + element + '_' + i).attr({'class': 'rating-user'});
        } else {
          $('#' + element + '_' + i).attr({'class': 'rating'});
        }
        
        // disable stars
        if (!ratingSettings[element]['enabled'] && !$('#' + element + '_' + i).attr('class').match(/-disabled/)) {
          $('#' + element + '_' + i).attr({'class': $('#' + element + '_' + i).attr('class') + '-disabled'});
        }    
      }
    }

    /**
     * Resets the rating element.
     *
     * @param element Element id
     */
    function ratingReset(element) {
      if (ratingSettings[element]['config']['showUserRatingStars']) {
        ratingSet(element, ratingSettings[element]['data']['%RATING%']);
      } else {
        ratingSet(element, ratingSettings[element]['data']['%AVG%']);
      }
      
      $('#' + element + '_text').html(ratingSettings[element]['config']['statusText']);
    }

    /**
     * Does the AJAX call to save the rating and 
     * updates the rating element.
     *
     * @param element Name of element
     * @param value Rating value
     */
    function ratingSave(element, value) {
      data = element.split('_');
      
      if (ratingSettings[element]['enabled']) {
        $.ajax({
          url: ratingSettings[element]['config']['appRoot'] + 'rating/ratings/save/' + data[0] + '/' + data[3] + '/' + value + '?' + Math.floor(Math.random() * 999999),
          async: true,
          data: ratingSettings[element]['options'],
          error: function() {
            //alert('AJAX error');
          },
          beforeSend: function() {
            //$('#loader').show();
          },
          complete: function(XMLHttpRequest) {
            //$('#loader').hide();
            $('#' + element).html(XMLHttpRequest.responseText);
          }
        });
      }
    }

    /**
     * Displays the mouseOverMessages.
     *
     * @param element Name of element
     * @param value Rating value
     */
    function ratingMessages(element, value) {
      if (ratingSettings[element]['enabled'] && value > 0) {
        if (value == ratingSettings[element]['data']['%RATING%'] 
            && ratingSettings[element]['config']['allowDelete']
            && ratingSettings[element]['config']['mouseOverMessages']['delete']) {
          $('#' + element + '_text').html(ratingSettings[element]['config']['mouseOverMessages']['delete']);
        } else if (ratingSettings[element]['config']['mouseOverMessages'][value]) {
          $('#' + element + '_text').html(ratingSettings[element]['config']['mouseOverMessages'][value]);
        }
      } else if (!ratingSettings[element]['enabled']
          && !ratingSettings[element]['config']['disable']
          && ratingSettings[element]['data']['%RATING%'] == 0
          && ratingSettings[element]['config']['mouseOverMessages']['login']){
        $('#' + element + '_text').html(ratingSettings[element]['config']['mouseOverMessages']['login']);
      } else if (!ratingSettings[element]['enabled'] 
          && ratingSettings[element]['data']['%RATING%'] > 0
          && ratingSettings[element]['config']['mouseOverMessages']['rated']){
        $('#' + element + '_text').html(ratingSettings[element]['config']['mouseOverMessages']['rated']);
      } else {
        $('#' + element + '_text').html(ratingSettings[element]['config']['statusText']);
      }
    }

    rating/vendors/js/rating_prototype.js
    /**
     * Prototype javascript for the CakePHP AJAX star rating plugin.
     *
     * @author Michael Schneidt <michael.schneidt@arcor.de>
     * @copyright Copyright 2009, Michael Schneidt
     * @license http://www.opensource.org/licenses/mit-license.php
     * @link http://bakery.cakephp.org/articles/view/ajax-star-rating-plugin-1
     * @version 2.4
     */

    /**
     * Holds the settings for all rating elements.
     */
    var ratingSettings = new Array();

    /**
     * Initializes the rating element.
     *
     * @param element Id of the rating element
     * @param data JSON encoded rating data
     * @param options JSON encoded rating options
     * @param config JSON encoded plugin configurations
     * @param enabled Enable the rating for a user
     */
    function ratingInit(element, data, config, options, enabled) {
        ratingSettings[element] = new Array();
      
      ratingSettings[element]['data'] = eval('(' + data + ')');
      ratingSettings[element]['options'] = eval('(' + options + ')');
      ratingSettings[element]['config'] =  eval('(' + config + ')');
        ratingSettings[element]['enabled'] = enabled;

      for (var i = 1; i <= ratingSettings[element]['data']['%MAX%']; i++) {
        $(element + '_' + i).observe('mouseover', function(e) {
          var value = this.id.match(/[0-9]*$/);
          
          // workaround against event after reload
          var target = e.relatedTarget || e.toElement;
          
          if (target && target.id != element + '_' + value) {
            if (ratingSettings[element]['enabled']) {
              ratingSet(element, value);
            }
            
            if (ratingSettings[element]['config']['showMouseOverMessages']) {
              ratingMessages(element, value);
            }
          }      
        });
        
        $(element + '_' + i).observe('click', function(e) {
          var value = this.id.match(/[0-9]*$/);
          
          ratingSave(element, value);
        });
      }
      
      $(element).observe('mouseout', function(e) {
        // workaround against mouseout event on child elements like links
        var target = e.relatedTarget || e.toElement;

        if (target && target.parentNode && target.parentNode.id != null && !target.parentNode.id.match(element)) {
          ratingReset(element);
        }
      });
      
      $(element + '_text').observe('mouseover', function(e) {
        // workaround against mouseover event on child elements like links
        var target = e.relatedTarget || e.toElement;
        
        if (target && target.parentNode && target.parentNode.id != null && !target.parentNode.id.match(element)) {
          ratingReset(element);
          
          if (ratingSettings[element]['config']['showMouseOverMessages']) {
            ratingMessages(element);
          }
        }
      });
      
      ratingReset(element);
    }

    /**
     * Sets the rating element to a rating value.
     *
     * @param element Name of element
     * @param value Rating value 
     */
    function ratingSet(element, value) {
      var starImg = ratingSettings[element]['config']['starEmptyImageLocation'];
      
      for (i = 1; i <= ratingSettings[element]['data']['%MAX%']; i++) {
        if (i <= Math.floor(value)) {
          starImg = ratingSettings[element]['config']['starFullImageLocation'];
        } else if (i == Math.floor(value) + 1 && value.toString().match(/[0-9]\.[5-9]/)) {
          starImg = ratingSettings[element]['config']['starHalfImageLocation'];
        } else {
          starImg = ratingSettings[element]['config']['starEmptyImageLocation'];
        }
        
        $(element + '_' + i).src = ratingSettings[element]['config']['appRoot'] + starImg;
        
        // set user mark
        if (ratingSettings[element]['config']['showUserRatingMark'] && i <= ratingSettings[element]['data']['%RATING%']) {
          $(element + '_' + i).className = 'rating-user';
        } else {
          $(element + '_' + i).className = 'rating';
        }
        
        // disable stars
        if (!ratingSettings[element]['enabled'] && !$(element + '_' + i).className.match(/-disabled/)) {
          $(element + '_' + i).className += '-disabled';
        }    
      }
    }

    /**
     * Resets the rating element.
     *
     * @param element Element id
     */
    function ratingReset(element) {
      if (ratingSettings[element]['config']['showUserRatingStars']) {
        ratingSet(element, ratingSettings[element]['data']['%RATING%']);
      } else {
        ratingSet(element, ratingSettings[element]['data']['%AVG%']);
      }
      
      $(element + '_text').update(ratingSettings[element]['config']['statusText']);
    }

    /**
     * Does the AJAX call to save the rating and 
     * updates the rating element.
     *
     * @param element Name of element
     * @param value Rating value
     */
    function ratingSave(element, value) {
      data = element.split('_');
      
      if (ratingSettings[element]['enabled']) {
        new Ajax.Updater(element, ratingSettings[element]['config']['appRoot'] + 'rating/ratings/save/' + data[0] + '/' + data[3] + '/' + value + '?' + Math.floor(Math.random() * 999999), {               
          asynchronous: true,
          evalScripts: true,
          method: 'get',
          parameters: ratingSettings[element]['options'],
          onFailure: function error() {
            //alert('AJAX error');
          },
          onLoading: function (request) {
            //Element.show('loader');
          },
          onComplete: function (request, json) {
            //Element.hide('loader');
          }
        });
      }
    }

    /**
     * Displays the mouseOverMessages.
     *
     * @param element Name of element
     * @param value Rating value
     */
    function ratingMessages(element, value) {
      if (ratingSettings[element]['enabled'] && value > 0) {
        if (value == ratingSettings[element]['data']['%RATING%'] 
            && ratingSettings[element]['config']['allowDelete'] 
            && ratingSettings[element]['config']['mouseOverMessages']['delete']) {
          $(element + '_text').update(ratingSettings[element]['config']['mouseOverMessages']['delete']);
        } else if (ratingSettings[element]['config']['mouseOverMessages'][value]) {
          $(element + '_text').update(ratingSettings[element]['config']['mouseOverMessages'][value]);
        }
      } else if (!ratingSettings[element]['enabled']
            && !ratingSettings[element]['config']['disable']
          && ratingSettings[element]['data']['%RATING%'] == 0
          && ratingSettings[element]['config']['mouseOverMessages']['login']){
        $(element + '_text').update(ratingSettings[element]['config']['mouseOverMessages']['login']);
      } else if (!ratingSettings[element]['enabled'] 
          && ratingSettings[element]['data']['%RATING%'] > 0
          && ratingSettings[element]['config']['mouseOverMessages']['rated']){
        $(element + '_text').update(ratingSettings[element]['config']['mouseOverMessages']['rated']);
      } else {
        $(element + '_text').update(ratingSettings[element]['config']['statusText']);
      }
    }


    rating/vendors/css/rating.css
    /**
     * CSS for the AJAX star rating plugin.
     *
     * @author Michael Schneidt <michael.schneidt@arcor.de>
     * @copyright Copyright 2009, Michael Schneidt
     * @license http://www.opensource.org/licenses/mit-license.php
     * @link http://bakery.cakephp.org/articles/view/ajax-star-rating-plugin-1
     * @version 2.4
     */
     
    div.rating {
      font-size: 8pt;
      white-space: nowrap;
    }

    div.rating-text {
      display: inline;
      position: relative;
      top: -4px;
      padding-left: 5px;
      white-space: nowrap;
    }

    div.rating-notext {
      display: none;
    }

    img.rating {
      cursor: pointer;
      border-bottom: 2px solid transparent;
    }

    img.rating-disabled {
      cursor: default;
      border-bottom: 2px solid transparent;
    }

    img.rating-user {
      cursor: pointer;
      border-bottom: 2px solid #dddddd;
    }

    img.rating-user-disabled {
      cursor: default;  
      border-bottom: 2px solid #dddddd;
    }

    div.fallback {
      display: inline;
    }

    div.fallback form {
      display: inline;
    }

    div.fallback label {
      display: inline;
      position: relative;
      top: -4px;
    }

    div.rating label.fallback {
      display: inline;
    }

    div.fallback input[type="radio"] {

    }

    div.fallback input[type="submit"] {
      position: relative;
      top: -4px;
      left: 5px;
    }

    div.rating-flash {
      display: inline;
      position: relative;
      top: -4px;
      padding-left: 5px;
      white-space: nowrap;
    }


    content


    asdfasdf

    Page: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

    Comments

    • Posted 04/03/11 10:06:57 PM
      First - thanks for a great plugin - I have this 99.9% working....

      My issue is the one where the stars disappear on loading. I think this is a problem with the path to the App Root set in the config file.

      I have my app in a subfolder on my webroot called "cake". I can set the App Root to "/cake/" in the config file and this works fine if I go to "www.domainname.com/cake/".

      However, I want to use a different specific domain name to point at the cakephp app itself. This other domain name points to www.domainname.com/cake/ as its root folder. To access it this way, from reading the config file, I would think I should leave the App Root field in the config file as ''. This however leads to the "disappearing stars" issue. I have tried many variations of what to put in the App Root field without resolving it...

      I would be grateful for any assistance.

      Thanks again for the great plugin.

      Pete
      • Posted 04/08/11 04:49:13 PM
        [quote] I have tried many variations of what to put in the App Root field without resolving it...
        [end quote]
        I've just tried all sorts of changes, including to the filenames of the star images, but none of it makes any difference - there must be some sort of caching going on somewhere but it's got me beaten. I resolved it by manually inserting my app root into:

        $('#' + element + '_' + i).attr({'src': ratingSettings[element]['config']['appRoot'] + 'myAppRoot/' + starImg});

        ..it'll be interesting to see what happens when any caching expires...
        • Posted 04/11/11 01:14:53 PM
          Just to update this, I've just realised that I was changing plugin_rating in /plugins/rating/config NOT /config! Please ignore my earlier post.
          [quote] [quote] I have tried many variations of what to put in the App Root field without resolving it...
          [end quote]
          I've just tried all sorts of changes, including to the filenames of the star images, but none of it makes any difference - there must be some sort of caching going on somewhere but it's got me beaten. I resolved it by manually inserting my app root into:

          $('#' + element + '_' + i).attr({'src': ratingSettings[element]['config']['appRoot'] + 'myAppRoot/' + starImg});

          ..it'll be interesting to see what happens when any caching expires...
          [end quote]
    • Posted 04/03/11 03:13:25 PM
      Great plugin - thank you. Just in case anyone else is struggling to work out why you keep on getting redirected to "/rating/pages/home" when you are trying to display stars to guests but only allowing logged-in voting, don't forget to Auth->allow('view') in beforeFilter() in ratings_controller.

      Thanks again
    • Posted 02/18/11 10:34:04 PM
      First off this plugin is awesome.
      However, I'm using PostgreSQL and not MySQL like most other people.
      Almost everything has worked out of the box for me, just two little changes to get Postgres to work (maybe it's a Cake thing? I'm using 1.3.7), they are:
      In "ratings_controller.php"

      line 234 reads "$avgRating = round($values[0]['AVG(`Rating`.`rating`)'], 1);"
      line 235 reads "$votes = $values[0]['COUNT(*)'];"

      Change to read:

      line 234 "$avgRating = round($values[0]['avg'], 1);"
      line 235 "$votes = $values[0]['count'];"

      and you're done
    • Posted 11/17/10 01:43:14 AM
      Has anybody successfully gotten this to work with DarkAuth component?
      I can not seem to get it to read the user id from anywhere.

      Thanks
    • Posted 10/15/10 08:53:13 PM
      Hello! Thank you for the code.

      I have an question:

      Have an way to sum the ratings like on a Host Review: Price, Service, Space, Trafic.. Then sum and divide by 4 in this case to have a rating overall bar?

      Thank you
    • Posted 09/18/10 11:45:09 AM
      Brilliant plugin! thanks a lot! The only problem I'm having is that the plugin is displaying a SQL dump even when help is set to false. Anyone else had that problem?
      • Posted 09/18/10 05:44:38 PM
        The help setting is independent from the cake debug level. I am not sure if this is good practice, but you can easily change it in the view (/views/ratings/view.ctp):


          // debug sql dump
          if (Configure::read('Rating.showHelp')) {
            echo $this->element('sql_dump');
          }
    • Posted 08/28/10 10:34:35 AM
      A really amazing plugin! Just what I needed now :).
    • Posted 04/29/10 11:23:59 AM
      Hi Micheal, great plugin!
      Do you plan to migrate this to cake1.3?
      • Posted 04/29/10 03:34:11 PM
        Hi Micheal, great plugin!
        Do you plan to migrate this to cake1.3?

        I am on it, looks like it just needs some minor changes. :-)
    • Posted 02/24/10 09:42:40 AM
      I had this working for about a minute but now when i go to rate something I get the following error
      Error: The .php was not found in your app/config directory. Please copy it from rating/config/plugin.rating.php

      and the containing div of my page content replicates itself in the ratings box, the same type error that comment 30 was having.

      So I looked in my app/config directory and its in there. When i turn off javascript, the radio buttons show and I can rate something. I also investigated the fix used in comment 29 but that did not work and I got the same result.

      Still trying to get this to work.
      • Posted 02/24/10 10:51:38 AM
        Seems like the config parameter is not reaching the save method. I think it has something to do with the point in "plugin.rating", maybe you parse such extensions in your app.

        I should change this, so long you could try to change the filename to maybe plugin_rating.php and change the default config in the rating element as well.
        • Posted 09/10/11 01:23:34 AM
          Hello I also have the same problem. But it is not properly registered is not the way to the images. Asterisks are displayed normally, but the vote button only appears with a disabled JavaScript. Tell me how to solve this problem!
    • Posted 02/05/10 12:19:18 PM
      I am using jquery 1.1.4 minified and have set this plugin up according to the directions. However, when my page loads I can see the stars for a split second then they disappear and the rating status text is all that is left.

      When I disable javascript I see the star ratings, status text, radio buttons, vote button, and mouseover messages. Also, when javascript is disabled I can vote and it works fine, adding my vote to the database. So my guess is something is happening in the rating_query.js that fouls up the display.

      Any thoughts on how to fix this would be greatly appreciated.
      • Posted 02/07/10 08:09:12 AM
        Check the appRoot setting in your config file, it is used in js along with the image locations to change the source of the images. If the source is wrong, the images set in the view disappear on js initialization.
        • Posted 09/10/11 01:41:38 AM
          Hello I also have the same problem. But it is not properly registered is not the way to the images. Asterisks are displayed normally, but the vote button only appears with a disabled JavaScript. Tell me how to solve this problem!
    • Posted 01/04/10 02:36:42 PM
      It works for me using Auth.User.id. If you aren't logged in you'll get that warning if you have it set to not allow guests, and you have the help text turned on in your settings.

      If that warning goes away once you log in, then just turn off the help text in the plugin's config file.

    • Posted 12/06/09 08:24:34 AM
      how i can integrate with cake auth component

      $config['Rating.sessionUserId'] = 'Auth.User.id';

      does not work for me

      Every time a see: Warning: No valid user id was found at "Auth.User.id" in the session
    • Posted 11/26/09 12:12:39 AM
      Hi i am trying to integrate this plugin in my project, i dont have any problem in the view , working fine. When i click on rating the ajax request is made to rating/ratings/save/. , having all the parameters in the url. but not updated in the database. after the request , the whole page is loading in that particular div. i dont know what is the page.

      help me to crack

      Thanks in Advances..
      • Posted 11/27/09 01:52:28 AM
        Looks like another routing problem. Maybe what Sequoia wrote in comment 29 could help you.
    • Posted 11/25/09 01:05:05 PM

      Controller Class:

      <?php 
      //      $values = $modelInstance->find(array($modelInstance->primaryKey => $id), //causes "field 'id' is ambiguous" warning 
      //
      //fix:
      //
            
      $values $modelInstance->find(array($modelInstance->name.".".$modelInstance->primaryKey => $id), 
      array(
      Configure::read('Rating.modelAverageField'), 
                                                 
      Configure::read('Rating.modelVotesField')),
                                           
      null,
                                           -
      1);
      ?>
    • Posted 11/25/09 11:29:03 AM
      If you are overriding the default catchall route (/:controller/:action), here is a route to get you to the controller:

      Router::connect('/rating/ratings/:action/*', array('controller' => 'ratings','plugin'=>'rating'));
    • Posted 11/20/09 04:19:01 PM
      Hello Michael, hello Cakebakers,

      first of all i wish to thank Michael for this awesome piece of code.

      Got it, had to use the element like this:

      <?php
      echo $this->element('rating', array('plugin' => 'rating',
                                          
      'model' => 'Provider',
                                          
      'id' => $provider['Provider']['id']));
      ?>

      Just can repeat myself: Great work! Thanks a lot.
    • Posted 11/11/09 05:56:57 PM
      Hi Michael,

      there should be an error in the redirect with custom routes (named controllers) after saving the rate.

      Example:
      Router::connect(
          '/government',
          array('controller' => 'products', 'action' => 'display', 5)
      );

      There return an error view by calling the XMLHttpRequest.responseText…
      • Posted 11/11/09 06:39:30 PM
        it must be an absolute path in rating_jquery.js (line:132)… did you have a better solution? By exampe when the "appRoot" path is define by "/my_path/"…


        url: ratingSettings[element]['config']['appRoot'] + '/rating/ratings/save/' + data[0] + '/' + data[3] + '/' + value + '?' + Math.floor(Math.random() * 999999),
    • Posted 11/09/09 09:45:41 PM
      Thanks for the plugin! I've got it working but just wondering why its layed out vertically? maybe this is related to my other css? I couldn't see a setting about this anywhere.
      • Posted 11/10/09 02:25:33 AM
        Someone else had this problem with using the cake.generic.css, where labels are declared as block elements. This is already fixed for the next version, so long you can just change/add the following parts in the rating\vendors\css\rating.css:

        div.fallback label {
          display: inline;
          .
          .
        }
        div.rating label {
          display: inline;
        }
    • Posted 10/28/09 03:52:54 PM
      This was the first plugin I've ever installed, so I expected some hiccups.

      My biggest (only) problem was that I didn't copy the plugin.rating.php config file into /app/config/plugin.rating.php

      None of the Rating config values were set. All were NULL. I wasn't expecting to have to copy config files around. I thought I was diligent about reading the docs, but I must have missed something.

      If for no other reason that to make me look foolish, could someone post a link to the documentation that says to copy the config file?

      In addition, I think there is a 'bug' in the RatingsController where it loads the config file.

      Check this out from line 45 in the RatingsController:

      // load config    
          if (Configure::load($config) === false && Configure::read('Rating.showHelp')) {
            echo 'Error: The '.$config.'.php was not found in your app/config directory. Please copy it from rating/config/plugin.rating.php';
            exit;
          }

      Is the logic really saying if I can't load the config file AND the config value to display help is true, then display an error message?

      It would seem correct to remove the AND showHelp part of the logic. Yes?
      • Posted 10/28/09 06:34:05 PM
        Hi Janice

        somehow you missed step 3 in the installation instructions in this article as well as in the install.nfo file. Anyway, I aggree that the plugin could load some default configurations when there is no config file found.

        There should be an error message, but as you correctly found out, the condition is crap, to read the showHelp config doesn't make any sense when there are no configs loaded. ;-)

        Thx for that!
    • Posted 10/23/09 05:02:40 AM
      very nice script ...
      very useful...
    • Posted 10/19/09 03:12:00 PM
      Hi,

      ich bastel derzeit für ein Wahlpflichtfach an der FH mit cake herum und habe dein Beispiel erst lauffähig bekommen, als ich mal die Verzeichnisse verglichen habe und festgestellt habe, dass du in app\vendors\js\ eine Datei names protoaculous1.6.packed.js drin hast. Nach dem ich diese auch in mein app/webroot/js/ geschoben habe, läuft das Beispiel einwandfrei.

      Keine ahnung ob das für dich relevant ist ;)

      gruß

      Seven
      • Posted 10/28/09 06:20:09 PM
        Hi Seven,

        find ich toll, dass du im Rahmen der FH die Möglichkeit hast CakePHP zu verwenden.

        Das Plugin hat als Grundvoraussetzung dass entweder Prototype oder jQuery geladen ist, steht auch oben unter Requirements. Ist aber zugegeben etwas kurz und natürlich nicht klar was man machen muss, wenn man die beiden Frameworks gar nicht kennt. Werde ich mal überarbeiten, danke auf jeden Fall für das Feedback.

        Gruß,
        Michael
    • Posted 10/16/09 12:31:26 AM
      Wonderful plugin. Works perfectly.
      The flash messages work very well too, although as I already have flash messages in my website, I end up with some general flash messages appearing in the ratings boxes instead of in the initial place where I put my $session->flash().

      Would you have any suggestions on how to separate my own flash messages (and decide of the place in the view where it gets displayed), from the ratings flash?
      • Posted 10/16/09 06:45:19 PM
        Thx for your comment, I really forgot about this scenario.

        To solve the flash collisions you simply have to use a flash key, as shown in the following code.

        rating\controllers\ratings_controller.php line 258

        $this->Session->setFlash(Configure::read('Rating.flashMessage'), 
                                 'default', 
                                 array('class' => 'rating-flash'),
                                 'rating');

        rating\views\ratings\view.ctp line 95

        $session->flash('rating');

        Because of ajax reloading, the flash message element has to be within the plugin's view and cannot be placed elsewhere in your view. However it may help you to relocate the div.rating-flash via css in the rating\vendors\css\rating.css
        • Posted 10/17/09 05:29:44 PM
          To solve the flash collisions you simply have to use a flash key, as shown in the following code.
          Works like a charm. Thanks!
    • Posted 10/03/09 04:14:58 PM
      I'm having a problem where my Images go missing and instead of pointing at

      user/rating/image/star-full.png

      it now points at:-

      user/rating/img/star-full.png

      And when I click on a one of the image place holders I get this error:-

      Error: The action rating is not defined in controller UserController

      I assume I've missed something simple here since everyone else seems to get it working so easily. I'm using cake 1.2.5, and rating 2.0 with JQuery.

      Anyone have any ideas what the problem could be ?
      • Posted 10/04/09 06:03:45 AM
        The plugin uses the paths to the images that are set in the app/config/plugin.rating.php, default is "rating/img/star-full.png".

        Where does the "user/" come from in your case? I thought it is your appRoot, but in this case there shouldn't be the controller error.

        Maybe it would help you to download the working demo and setup a similar configuration of your situation.
    • Posted 08/12/09 04:32:54 PM
      Thanks for this great plug in!!!

      I wanted to let you know that I found a little bug.

      If you are using a custom primary key field in your model (not "id"), and you set Rating.saveAverageToModel to true, it won't work.

      To make it work I just modified line 105 in ratings_controller.php from this:

      $avgRating = $modelInstance->field(Configure::read('Rating.modelAverageField'), array($model.'.id' => $id));

      to this:

      $avgRating = $modelInstance->field(Configure::read('Rating.modelAverageField'), array($model.'.'.$modelInstance->primaryKey => $id));

      Thanks again for sharing it! It saved me a lot of work :)

      Nahuel
    • Posted 08/05/09 01:40:23 PM
      At a glance, seems to be a very nice plugin. Worked like a charm into my cake project. But there is some details that I would like to discuss.

      1) Javascript need to be declared before the element instaciation: not a big deal, but i follow the Steve Souders´ performance tips, so, i prefer to declare my javascript on the end of document. Actually, it shows an error if I do this. A solution is simple - instead of call ratingInit directly, do this:


      $(window).load(function() {
          renderInit( // arguments, etc..
      }

      So, the code will be executed after document load. If you have a public svn or git repo, i would like to help you with this.

      Thx,

      Yuri Teixeira.
      • Posted 08/06/09 11:26:40 AM
        Thanks for your hint and solution, that makes sense and is no problem to change because it should work in each case. Only the code has to be moved inside the js-files because Prototype must be supported as well.

        Maybe something like this:

        rating_prototype.js

        function ratingInit(element, data, options, config, enabled) {
          Event.observe(window, 'load', function() {
            ...
          });
        }

        rating_jquery.js

        function ratingInit(element, data, options, config, enabled) {
          $(window).load(function () {
            ...
          });
        }

        Regards
        • Posted 08/10/09 02:36:50 AM
          Sorry, forget about my last post, unfortunately that's not a solution because the window is only loaded once, meaning that there won't be further initialisations on the ajax updates that are important for an updated browser interface.

          I currently see no way how to handle that, if you find something working let me know.
    • Posted 07/22/09 12:45:06 PM
      Hey, I am new to cake but managed to complete both tutorials (Blog & ACL). I also did install the rating plugin, which is displayed fine. But as soon as I want to rate the following error occurs:


      Warning (512): DbAcl::check() - Failed ARO/ACO node lookup in permissions check.  Node references:
      Aro: Array
      (
          [User] => Array
              (
                  [id] => 3
                  [username] => test
                  [group_id] => 2
                  [created] => 2009-07-22 17:35:44
                  [modified] => 2009-07-22 17:35:44
              )

      )

      Aco: controllers/Ratings/save [CORE/cake/libs/controller/components/acl.php, line 239]

      Its seems to have something to do with the DBACl http://book.cakephp.org/view/644/Initialize-the-Db-Acl-tables but cannot really figure it our. Thanks for your help!
      • Posted 07/22/09 01:02:33 PM
        Hey, I am new to cake but managed to complete both tutorials (Blog & ACL). I also did install the rating plugin, which is displayed fine. But as soon as I want to rate the following error occurs:
        ....

        I discovered the mistake myself. I did not have the permission to rate, so had to allow the ratings_controller.php to do this by adding:

                function beforeFilter() {
                    parent::beforeFilter(); 
                    $this->Auth->allowedActions = array('*');
                }
    • Posted 07/22/09 12:45:05 PM
      Hey, I am new to cake but managed to complete both tutorials (Blog & ACL). I also did install the rating plugin, which is displayed fine. But as soon as I want to rate the following error occurs:


      Warning (512): DbAcl::check() - Failed ARO/ACO node lookup in permissions check.  Node references:
      Aro: Array
      (
          [User] => Array
              (
                  [id] => 3
                  [username] => test
                  [group_id] => 2
                  [created] => 2009-07-22 17:35:44
                  [modified] => 2009-07-22 17:35:44
              )

      )

      Aco: controllers/Ratings/save [CORE/cake/libs/controller/components/acl.php, line 239]

      Its seems to have something to do with the DBACl http://book.cakephp.org/view/644/Initialize-the-Db-Acl-tables but cannot really figure it our. Thanks for your help!
    • Posted 07/09/09 06:28:07 AM
      Hi, I have a little problem with rating plugin model content.

      I keep getting model_id does not exist when I use a plugin model. I enter the model as 'Plugin.Model' eg. 'News.Article'

      I've confirmed the model_id exists in d database, I've also tested the same code used in rating_controller->view()

      $model = 'News.Article';
      $modelInstance = ClassRegistry::init($model);
      $modelInstance->id = '3ad66b8f-55ce-11de-bb54-9f1eb639fa3a';
      var_dump($modelInstance->exists(true));

      In my test, it returns true, but in the the rating_controller it returns false.

      I'd really appreciate some help. Thanks in advance.
      • Posted 07/09/09 09:20:54 AM
        Hi,

        thx for your comment! I tried to reproduce your problem and created a dummy "news" plugin with a model "article" and also put an article with your uuid into the db "articles", but I didn't experience the problem. Your code snippet shows true on top of the ratings_controller->view().

        Do you use the latest CakePHP release? Did you test it with the demo? Atm I have no other idea, but I will let you know if I come up with something.

        Regards
        • Posted 07/10/09 06:42:04 AM
          Hi,

          I just tested it with the demo and I still get the same error. With models in app/models it works well.

          I'm using cake 1.2.3.8166.

          Here is how I called the rating plugin.

          echo $this->element('rating', array('plugin' => 'rating',
          'model' => 'News.Article',
          'id' => $article['Article']['id'])
          );
          ?>
          Thanks for your help.
          • Posted 07/10/09 08:09:34 AM
            Hi,

            ok now I got it too and could figure it out. The problem is, that the sanitization encodes the incoming uuid, that's why the model_id is not found. Further more a rating cannot be saved in the ratings table, because the model_id is an integer field.

            I will add the support in the next version, so long you can change the model_id from int to varchar and disable the encoding on Sanitize::clean (in view() and save()) as follows:


            Sanitize::clean($id, array('encode' => false));

            I really didn't think of uuids, so tanks for your comment.
    • Posted 07/08/09 06:45:46 AM
      Many thanks for this super cool plugin! Great job well done!

      Thanks

    Comments are closed for articles over a year old