<?php



class PostRatings{







  const

    VERSION      = '2.4',                                                   // plugin version

    PROJECT_URI  = 'http://digitalnature.eu/forum/plugins/post-ratings/',   // url to support forums

    ID           = 'post-ratings',                                          // internally used for text domain, theme option group name etc.

    MIN_VOTES    = 1,                                                       // minimum vote count (MV)

    BR1          = '(v / (v + MV)) * r + (MV / (v + MV)) * R',              // bayesian rating formula: the IMDB version

    BR2          = '((AV * R) + (v * r)) / (AV + v)';                       // bayesian rating formula: thebroth.com version



  protected static $instance;



  protected

    $options     = null,



    // stores rated post IDs for the current session;

    // we're using this for to get the rated state in our ajax calls

    $rated_posts = array(),



    // default option values

    $defaults    = array(

                     'version'     => self::VERSION,

                     'anonymous_vote'   => true,

                     'max_rating'       => 5,

                     'bayesian_formula' => self::BR1,

                     'user_formula'     => '',

                     'custom_filter'    => '',

                     'before_post'      => false,

                     'after_post'       => true,

                     'post_types'       => array('post'),

                     'visibility'       => array('home', 'singular'),  // same as WP conditional "tags", but with "is_" omitted



                     // internal, global stats

                     'avg_rating'       => 0,

                     'num_votes'        => 0,

                     'num_rated_posts'  => 0,

                   );







 /*

  * This will instantiate the class if needed, and return the only class instance if not...

  *

  * @since 1.0

  */

  public static function app(){



    // first run?

    if(!(self::$instance instanceof self)){



      self::$instance = new self();



      // localize

      load_plugin_textdomain(self::ID, false, dirname(plugin_basename(__FILE__)).'/lang');



      if(is_admin()){

        add_action('admin_menu', array(self::$instance, 'CreateMenu'));

        add_action('admin_init', array(self::$instance, 'RegisterSettings'));



      }else{

        add_action('wp', array(self::$instance, 'Run'));

      }



      // register shortcode

      add_shortcode('rate', array(self::$instance, 'Shortcode'));



      // register widget

      add_action('widgets_init', array(self::$instance, 'Widget'));



      // empty our cache when a new post is written or when a post gets deleted

      add_action('save_post',    array(self::$instance, 'clearQueryCache'));

      add_action('deleted_post', array(self::$instance, 'clearQueryCache'));



      // run on plugin uninstall; not sure when does this run?!

      register_uninstall_hook(__FILE__, array(__CLASS__, 'Uninstall'));

    }



    return self::$instance;

  }











 /*

  * A single instance only

  *

  * @since 1.0

  */

  final protected function __construct(){



    // initialize early, to avoid endless loop within update_option() + settings validation callback.

    // stupid settings API...

    $this->getOptions();

  }







 /*

  * No cloning

  *

  * @since 1.0

  */

  final protected function __clone(){}









  /*

   * Returns one or all plugin options.

   *

   * @since   1.0

   * @param   string $key   Option to get; if not given all options are returned

   * @return  mixed         Option(s)

   */

  public function getOptions($key = false){



      // first call, initialize the options

    if(!isset($this->options)){



      $options = get_option(self::ID);



      // options exist

      if($options !== false){



        if(!isset($options['version']))

          $options['version'] = '1.0';



        $new_version = version_compare($options['version'], self::VERSION, '!=');

        $desync = array_diff_key($this->defaults, $options) !== array_diff_key($options, $this->defaults);



        // update options if version changed, or we have missing/extra (out of sync) option entries

        if($new_version || $desync){



          $new_options = array();



          // check for new options and set defaults if necessary

          foreach($this->defaults as $option => $value)

            $new_options[$option] = isset($options[$option]) ? $options[$option] : $value;



          // update version info

          $new_options['version'] = self::VERSION;



          update_option(self::ID, $new_options);

          $this->options = $new_options;



        // no update was required

        }else{

          $this->options = $options;

        }





      // new install (plugin was just activated)

      }else{

        update_option(self::ID, $this->defaults);

        $this->options = $this->defaults;

      }

    }



    return $key ? $this->options[$key] : $this->options;

  }







  /*

   * Loads a template file from the theme or child theme directory.

   *

   * @since   1.0

   * @param   string $_name   Template name, without the '.php' suffix

   * @param   array $_vars    Variables to expose in the template. Note that unlike WP, we're not exposing all the global variable mess inside it...

   */

  final public function loadTemplate($_name, $_vars = array()){



    // you cannot let locate_template to load your template

    // because WP devs made sure you can't pass

    // variables to your template :(

    $_located = locate_template("{$_name}.php", false, false);



    // use the default one if the (child) theme doesn't have it

    if(!$_located)

      $_located = dirname(__FILE__).'/templates/'.$_name.'.php';



    unset($_name);



    // create variables

    if($_vars)

      extract($_vars);



    ob_start();



    // load it

    require $_located;



    return ob_get_clean();

  }







 /*

  * Hook our plugin options menu / page

  *

  * @since 1.0

  */

  public function CreateMenu(){

    //add_options_page(__('Post Ratings', self::ID), __('Post Ratings', self::ID), 'manage_options', self::ID, array($this, 'SettingsPage'));

	add_menu_page(__('Post Ratings', self::ID), __('Post Ratings', self::ID), 'manage_options', self::ID, array($this, 'SettingsPage'));

	//add_submenu_page( 'Post Ratings', $themename, __( 'Post Ratings', self::ID ), 'manage_options', 'options-framework', 'optionsframework_page' );

  }







 /*

  * Register our setting with the new useless Settings API bloat...

  *

  * @since 1.0

  */

  public function RegisterSettings(){

    register_setting(self::ID, self::ID, array($this, 'ValidateSettings'));

    add_filter('plugin_action_links', array($this, 'PluginSettingsLink'), 10, 2);

  }







 /*

  * Settings link in the plugin list

  *

  * @since    1.0

  * @param    string $file

  * @param    array $links

  * @return   array

  */

  public function PluginSettingsLink($links, $file){



    if(plugin_basename(__FILE__) === $file){

      $settings_link = '<a href="'.add_query_arg(array('page' => self::ID), admin_url('options-general.php')).'">'.__('Settings', self::ID).'</a>';

      array_unshift($links, $settings_link);

    }



    return $links;

  }







 /*

  * Validate user input for our settings

  *

  * @since    1.0

  * @param    array $input

  * @return   array

  */

  public function ValidateSettings($input){



    extract($input);



    $options = array(

      'anonymous_vote'   => isset($anonymous_vote) ? true : false,

      'max_rating'       => min(max((int)$max_rating, 1), 10),

      'bayesian_formula' => wp_filter_nohtml_kses($bayesian_formula),



      // only allow super admins to change this, because it's a little sensitive (part of this string is used inside the top rated db query)

      'user_formula'     => current_user_can('edit_plugins') ? wp_filter_nohtml_kses($user_formula) : $this->getOptions('user_formula'),



      'before_post'      => isset($before_post) ? true : false,

      'after_post'       => isset($after_post) ? true : false,

      'custom_filter'    => wp_filter_nohtml_kses($custom_filter),

      'post_types'       => isset($post_types) ? array_filter($post_types, 'post_type_exists') : array(),

      'visibility'       => isset($visibility) ? array_map('wp_filter_nohtml_kses', $visibility) : array(),



      // internal, global stats

      'avg_rating'       => $this->getOptions('avg_rating'),

      'num_votes'        => $this->getOptions('num_votes'),

      'num_rated_posts'  => $this->getOptions('num_rated_posts'),



      'version'          => self::VERSION,

    );



    if(isset($remove_ratings) || ($options['max_rating'] !== $this->getOptions('max_rating'))){

      $options['avg_rating'] = $options['num_votes'] = $options['num_rated_posts'] = 0;

      $this->DeleteRatingRecords();

    }



    return $options;

  }







 /*

  * The options page (form)

  *

  * @since 1.0

  */

  public function SettingsPage(){



    $options = $this->getOptions();

    extract($options);



    $generic_pages = array(

      'home'     => __('Home', self::ID),

      'archive'  => __('Archives', self::ID),

      'singular' => __('Single pages', self::ID),

      'search'   => __('Search results', self::ID),

    );



    ?>

    <div class="wrap metabox-holder">

      <h2><?php _e('Post Ratings', self::ID); ?></h2>



      <form method="post" action="options.php" style="position:relative;">



        <div class="postbox" style="position:absolute;right:0;top:0;">

          <h3><span><?php _e('Global stats', self::ID); ?></span></h3>

          <div class="inside">

            <p><?php printf(__('%1$s votes (on %2$s posts)', self::ID), sprintf('<strong>%d</strong>', $num_votes), sprintf('<strong>%d</strong>', $num_rated_posts)); ?></p>

            <p><?php printf(__('Average vote count per post: %s', self::ID), sprintf('<strong>%.2F</strong>', @($options['num_votes'] / $options['num_rated_posts']))); ?></p>

            <p><?php printf(__('Average rating per post: %s', self::ID), sprintf('<strong>%.2F</strong>', $avg_rating)); ?></p>

          </div>

        </div>



        <?php settings_fields(self::ID); ?>



        <table class="form-table">



          <tr valign="top">

            <th scope="row"><?php _e('Access level', self::ID); ?></th>

            <td>

              <label for="anonymous_vote">

                <input type="checkbox" id="anonymous_vote" name="<?php echo self::ID; ?>[anonymous_vote]" value="cookie" <?php checked($anonymous_vote); ?> />

                <?php _e('Allow unregistered users to vote', self::ID); ?>

             </label>

            </td>

          </tr>

<!--

          <tr valign="top">

            <th scope="row"><?php _e('Maximum rating', self::ID); ?></th>

            <td>

              <input type="text" size="3" name="<?php echo self::ID; ?>[max_rating]" value="<?php echo $max_rating; ?>" />

              <p><span class="description"><?php _e('Changing this option will reset existing post rating records', self::ID); ?></span></p>

            </td>

          </tr>



          <tr valign="top">

            <th scope="row">

              <?php _e('Bayesian rating (score) formula', self::ID); ?>

            </th>

            <td>



              <fieldset>

               <p>

               <label for="bayesian_formula_1">

                 <input id="bayesian_formula_1" name="<?php echo self::ID; ?>[bayesian_formula]" type="radio" value="<?php echo self::BR1; ?>" <?php checked($bayesian_formula, self::BR1); ?> />



                  <code style="font-size: 14px;">(<em>v</em> / (<em>v</em> + <strong>MV</strong>)) * <em>r</em> + (<strong>MV</strong> / (<em>v</em> + <strong>MV</strong>)) * <strong>R</strong></code> (<?php printf(__('from %s', self::ID), '<a href="http://en.wikipedia.org/wiki/Internet_Movie_Database#User_ratings_of_films" target="_blank">IMDB</a>'); ?>)

               </label>

               </p>



               <p>

               <label for="bayesian_formula_2">

                 <input id="bayesian_formula_2" name="<?php echo self::ID; ?>[bayesian_formula]" type="radio" value="<?php echo self::BR2; ?>" <?php checked($bayesian_formula, self::BR2); ?> />

                 <code style="font-size: 14px;">((<strong>AV</strong> * <strong>R</strong>) + (<em>v</em> * <em>r</em>)) / (<strong>AV</strong> + <em>v</em>)</code> (<?php printf(__('from %s', self::ID), '<a href="https://gist.github.com/44522/" target="_blank">thebroth</a>'); ?>)

               </label>

               </p>



               <p>

               <label for="user_formula">

                 <input id="user_formula" name="<?php echo self::ID; ?>[bayesian_formula]" type="radio" value="0" <?php checked($bayesian_formula, 0); ?> />

                 <?php _e('I have my own formula:', self::ID); ?>

                 <input <?php if(!current_user_can('edit_plugins')): ?>disabled="disabled"<?php endif; ?> type=text name="<?php echo self::ID; ?>[user_formula]" size="46" class="code" value="<?php echo $user_formula; ?>" />

               </label> <a href="#" onclick="jQuery('#legend').toggle();">(<?php _e('Legend', self::ID); ?>)</a>

               </p>



               <div id="legend" style="display:none;">

                <p>

                 <code style="font-size: 14px;"><strong>AV</strong></code> = <?php _e('Global average number of votes per post', self::ID); ?>

                 <br />

                 <code style="font-size: 14px;"><strong>&nbsp;V</strong></code> = <?php _e('Global number of votes (from all posts)', self::ID); ?>

                 <br />

                 <code style="font-size: 14px;"><em>&nbsp;v</em></code> = <?php _e('Number of votes from the current post', self::ID); ?>

                 <br />

                 <code style="font-size: 14px;"><strong>&nbsp;R</strong></code> = <?php printf(__('Global average rating per post (from 1 to %d)', self::ID), $max_rating); ?>

                 <br />

                 <code style="font-size: 14px;"><em>&nbsp;r</em></code> = <?php printf(__('Average rating of the current post (from 1 to %d)', self::ID), $max_rating); ?>

                 <br />

                 <code style="font-size: 14px;"><strong>MV</strong></code> = <?php printf(__('Minimum vote count per post to consider (%d by default)', self::ID), self::MIN_VOTES); ?>

                 <br />

                 <code style="font-size: 14px;"><strong>MR</strong></code> = <?php printf(__('Maximum rating, see option above (currently %d)', self::ID), $max_rating); ?>

               </p>

              </div>



              </fieldset>

            </td>

          </tr>

-->



          <tr valign="top">

            <th scope="row"><?php _e('Allow ratings on', self::ID); ?></th>

            <td>

              <fieldset>

                 <?php foreach(get_post_types(array('public' => true)) as $type): ?>

                 <?php $object = get_post_type_object($type); ?>

                 <label for="post_type_<?php echo $type; ?>">

                   <input type="checkbox" value="<?php echo $type; ?>" id="post_type_<?php echo $type; ?>" name="<?php echo self::ID; ?>[post_types][]" <?php checked(in_array($type, $post_types)); ?> />

                   <?php echo $object->labels->name; ?>

                 </label>

                 <br />

                 <?php endforeach; ?>

              </fieldset>

            </td>

          </tr>



          <tr valign="top">

            <th scope="row"><?php _e('Locations of the rate links', self::ID); ?></th>

            <td>



              <fieldset>

               <label for="location_before_post">

                 <input id="location_before_post" name="<?php echo self::ID; ?>[before_post]" type="checkbox" <?php checked($before_post); ?> />

                 <?php _e('Before post content', self::ID); ?>

               </label>

               <br />

               <label for="location_after_post">

                 <input id="location_after_post" name="<?php echo self::ID; ?>[after_post]" type="checkbox" <?php checked($after_post); ?> />

                 <?php _e('After post content ', self::ID); ?>

               </label>

               <br />

               <br />

<!--

               <label for="custom_filter">

                  <?php _e('I have my own action hook:', self::ID); ?>

                  <input id="custom_filter" class="code" type="text" value="<?php echo $custom_filter; ?>" name="<?php echo self::ID; ?>[custom_filter]" size="40" />

               </label>

-->

              </fieldset>



<!--              <p>

                <span class="description"><?php printf(__('You can also add it manually anywhere by using the %s shortcode', self::ID), '<code>[rate]</code>'); ?></span>

              </p> -->



            </td>

          </tr>



<!--

          <tr valign="top">

            <th scope="row"><?php _e('Page visibility', self::ID); ?></th>

            <td>

              <fieldset>

                 <?php foreach($generic_pages as $page => $label): ?>

                 <label for="visibility_<?php echo $page; ?>">

                   <input type="checkbox" value="<?php echo $page; ?>" id="visibility_<?php echo $page; ?>" name="<?php echo self::ID; ?>[visibility][]" <?php checked(in_array($page, $visibility)); ?> />

                   <?php echo $label; ?>

                 </label>

                 <br />

                 <?php endforeach; ?>

              </fieldset>

            </td>

          </tr>

-->



          <tr><td colspan="2">&nbsp;</td></tr>



            <tr valign="top">



            <th scope="row" colspan="2">



              <input type="submit" class="button-primary" value="<?php _e('Save Changes', self::ID); ?>" />

              &nbsp;

              <label for="remove_all">

                <input id="remove_all" type="checkbox" value="1" name="<?php echo self::ID; ?>[remove_ratings]" />

                <?php _e('Delete rating records from all posts', self::ID); ?>

              </label>



            </th>



          </tr>



        </table>



      </form>



    </div>

    <?php

  }







 /*

  * Javascript and CSS used by the plugin

  *

  * @since 1.0

  */

  public function assets(){



    wp_enqueue_script('jquery');

    wp_enqueue_script(self::ID, get_template_directory_uri() .'/js/post-ratings.js', array('jquery'), self::VERSION, true);



    // note that Atom-based themes alread have this variable "localized"

    if(!class_exists('Atom') || (class_exists('Atom') && (!defined('Atom::VERSION'))))

      wp_localize_script(self::ID, 'post_ratings', array('blog_url' => home_url('/')));



    // allow themes to override css

    $style = is_readable(get_stylesheet_directory().'/css/post-ratings.css') ? get_stylesheet_directory_uri().'/css/post-ratings.css' : plugins_url('post-ratings.css', __FILE__);



    wp_enqueue_style(self::ID, $style);

  }







 /*

  * Remove plugin options and rating stats on uninstall

  *

  * @since 1.0

  */

  public static function Uninstall(){

    PostRatings()->DeleteRatingRecords();

    delete_option(self::ID);

  }







 /*

  * Get user rating for a post

  *

  * @since    1.0

  * @param    int $post_id     Post ID

  * @return   array            Rating, vote count and bayesian rating

  */

  public function getRating($post_id){



    $options = $this->getOptions();

    extract($options);



    $rating = (float)get_post_meta($post_id, 'rating', true);

    $votes = (int)get_post_meta($post_id, 'votes', true);



    $bayesian_rating = 0;



    if($votes != 0){

      $avg_num_votes = ($num_rated_posts != 0) ? ($num_votes / $num_rated_posts) : 0;



      $identifiers = array(

        'AV' => $avg_num_votes,

        'MV' => self::MIN_VOTES,

        'MR' => $max_rating,

        'V'  => $num_votes,

        'v'  => $votes,

        'R'  => $avg_rating,

        'r'  => $rating,

      );



      if(!$bayesian_formula)

        $bayesian_formula = $user_formula;



      if(!$bayesian_formula)

        $bayesian_formula = 'r';



      $bayesian_formula = strtr($bayesian_formula, $identifiers);



      // safe eval - only super admins can set their own formula

      $bayesian_rating = (float)@eval("return ({$bayesian_formula});");

      $bayesian_rating = 100 * ($bayesian_rating / $max_rating);

    }



    return compact('rating', 'votes', 'bayesian_rating');

  }







 /*

  * Adjust user meta key name, or cookie key name for multisite blogs (except primary blog)

  *

  * @since    2.0

  * @param    string

  * @return   string

  */

  private function getRecordsKey($key){

    if(is_multisite() && !is_main_site())

      $key .= '_'.get_current_blog_id();



    return $key;

  }







 /*

  * Attempt to get the visitor's IP address

  *

  * @since    2.3

  * @return   string

  */

  private function getIP(){



    if(isset($_SERVER['HTTP_CLIENT_IP']))

      return $_SERVER['HTTP_CLIENT_IP'];



    if(isset($_SERVER['HTTP_X_FORWARDED_FOR']))

      return $_SERVER['HTTP_X_FORWARDED_FOR'];



    if(isset($_SERVER['HTTP_X_FORWARDED']))

      return $_SERVER['HTTP_X_FORWARDED'];



    if(isset($_SERVER['HTTP_FORWARDED_FOR']))

      return $_SERVER['HTTP_FORWARDED_FOR'];



    if(isset($_SERVER['HTTP_FORWARDED']))

      return $_SERVER['HTTP_FORWARDED'];



    return $_SERVER['REMOTE_ADDR'];

  }







 /*

  * Process rating, or set up plugin hooks if this is not a rate request

  *

  * @since 1.0

  */

  public function Run(){



    $options = $this->getOptions();

    extract($options);



    if(!isset($_GET['rate'])){

      if($custom_filter)

        add_filter($custom_filter, array($this, 'ControlBlockHook'));



      if($before_post || $after_post){

        // post content

        add_filter('the_content', array($this, 'ControlBlockHook'), 20);



        // bbpress

        add_filter('bbp_get_topic_content', array($this, 'ControlBlockHook'));

        add_filter('bbp_get_reply_content', array($this, 'ControlBlockHook'));

      }



      add_action('wp_enqueue_scripts', array($this, 'assets'));



    // this is our $.ajax request

    }else{



      defined('DOING_AJAX') or define('DOING_AJAX', true);



      $post_id  = (int)$_GET['post_id'];

      $voted    =  min(max((int)$_GET['rate'], 1), $max_rating);

      $error    = '';

      $post     = &get_post($post_id);

      $rating   = 0;

      $votes    = 0;



      if(!$post){

        $error = __("Invalid vote! Cheatin' uh?", self::ID);



      }else{



        // get current post rating and vote count

        extract($this->getRating($post->ID));



        // vote seems valid, register it

        if($this->currentUserCanRate($post_id)){



          // increase global post rate count if this is the first vote

          if($votes < 1)

            $options['num_rated_posts']++;



          // global vote count

          $options['num_votes']++;



          // update post rating and vote count

          $votes++;

          $rating = (($rating * ($votes - 1)) + $voted) / $votes;



          update_post_meta($post->ID, 'rating', $rating);

          update_post_meta($post->ID, 'votes', $votes);



          // update global stats

          $options['avg_rating'] = ($options['num_votes'] > 0) ? ((($options['avg_rating'] * ($options['num_votes'] - 1))  + $voted) / $options['num_votes']) : 0;

          update_option(self::ID, $options);



          $ip_cache = get_transient('post_ratings_ip_cache');



          if(!$ip_cache)

            $ip_cache = array();



          $posts_rated = isset($_COOKIE[$this->getRecordsKey('posts_rated')]) ? explode('-', $_COOKIE[$this->getRecordsKey('posts_rated')]) : array();

          $posts_rated = array_map('intval', array_filter($posts_rated));



          // add user's IP to the cache

          $ip_cache[$post_id][] = $this->getIP();



          // keep it light, only 10 records per post and maximum 10 post records (=> max. 100 ip entries)

          // also, the data gets deleted after 2 weeks if there's no activity during this time...



          if(count($ip_cache[$post_id]) > 10)

            array_shift($ip_cache[$post_id]);



          if(count($ip_cache) > 10)

            array_shift($ip_cache);



          set_transient('post_ratings_ip_cache', $ip_cache, 60 * 60 * 24 * 14);



          // update user meta

          if(is_user_logged_in()){

            $user = wp_get_current_user();



            $current_user_ratings = get_user_meta($user->ID, $this->getRecordsKey('posts_rated'), true);



            if(!$current_user_ratings)

              $current_user_ratings = array();



            $posts_rated = array_unique(array_merge($posts_rated, array_filter($current_user_ratings)));



            update_user_meta($user->ID, $this->getRecordsKey('posts_rated'), $posts_rated);

          }



          // update cookie

          $posts_rated = array_slice($posts_rated, -20); // keep it under 20 entries

          $posts_rated[] = $post_id;

          setcookie($this->getRecordsKey('posts_rated'), implode('-', $posts_rated),  time() + 60 * 60 * 24 * 90, '/'); // expires in 90 days



          $this->rated_posts[] = $post_id;



          do_action('rated_post', $post_id);

          $this->clearQueryCache();



        }else{

          $error = __('You cannot rate this post!', self::ID);

        }



      }



      // send updated info

      echo json_encode(array(

        'error'      => $error,

        'rating'     => sprintf('%.2F', $rating),

        'max_rating' => $max_rating,

        'votes'      => $votes,

        'html'       => $this->getControl($post_id, true),

      ));



      exit;

    }



  }







 /*

  * Delete all ratings-related meta data from the database

  *

  * @since 1.0

  */

  public function DeleteRatingRecords(){



    // clear cache, just in case we have a persistent cache plugin active

    wp_cache_flush();



    delete_transient('post_ratings_ip_cache');



    // remove all our meta entries

    delete_metadata('post', 0, 'rating', '', $delete_all = true);

    delete_metadata('post', 0, 'votes', '', $delete_all = true);

    delete_metadata('user', 0, $this->getRecordsKey('posts_rated'), '', $delete_all = true);



    // delete the current user's cookie too; this is probably useless because it only handles the current user;

    // we should store a unique ID on both the server and client computer

    // and if this ID doesn't match with the one on the user's computer then expire his cookie

    if(isset($_COOKIE[$this->getRecordsKey('posts_rated')]))

      setcookie($this->getRecordsKey('posts_rated'), null, -1, '/');

  }







 /*

  * Hook for the content

  *

  * @since     1.8

  * @param     string $content

  * @return    string

  */

  public function ControlBlockHook($content = ''){

    global $post, $wp_current_filter;



    $control = $this->getControl();



    if($control){



      $options = $this->getOptions();



      extract($options);



      // no post ID?

      // this is most likely the user's action tag, fired in the wrong place...

      if(empty($post->ID)){

        printf(__("Your '%s' action is must run in a post's context!", self::ID), $custom_filter);

        return $content;

      }



      // check if this is the right post type

      if(!in_array(get_post_type($post->ID), $post_types))

        return $content;



      // we don't want to insert our html in excerpts...

      // see here why: http://digitalnature.eu/blog/2011/09/12/how-to-correctly-hook-your-filter-to-the-post-content

      if(array_intersect(array('get_the_excerpt', 'the_excerpt'), $wp_current_filter))

        return $content;



      $continue = false;



      // this is the user's custom action, so directly output the HTML

      if(in_array($custom_filter, $wp_current_filter)){

        echo $control;



      // the_content

      }elseif(array_intersect(array('the_content', 'bbp_get_reply_content'), $wp_current_filter)){



        // we don't want to mess with custom loops

        if(in_the_loop()){

          if($before_post)

            $content = $control.$content;



          if($after_post)

            $content = $content.$control;

        }



      }

    }



    return $content;

  }







 /*

  * The rate links

  *

  * @since     1.0

  * @param     int $post_id

  * @param     bool $ignore_visibility_setting

  * @return    string

  */

  public function getControl($post_id = '', $ignore_visibility_setting = false){

    global $post;



    $control = array();

    $options = $this->getOptions();

    $post_id = $post_id ? $post_id : $post->ID;



    extract($options);



    // check if this is the right post type

    if(!in_array(get_post_type($post_id), $post_types))

      return false;



    if(empty($post_id))

      throw new Exception('Need a post ID...');



    $continue = false;



    if(!$ignore_visibility_setting){



      // page visibility check

      foreach($visibility as $page)

        if(call_user_func("is_{$page}"))

          $continue = true;



      // cpt archive check

      if(in_array('archive', $visibility) && is_post_type_archive($post_types))

        $continue = true;



      $continue = apply_filters('post_ratings_visibility', $continue);



    }



    if($continue || $ignore_visibility_setting){



      // get current post rating

      extract($this->getRating($post_id));



      $post = get_post($post_id);

      setup_postdata($post);



      $loaded = $this->loadTemplate('post-ratings-control', compact('rating', 'votes', 'bayesian_rating', 'max_rating'));



      wp_reset_postdata();



      return $loaded;

    }



    return false;

  }







 /*

  * Checks if the current user can rate a post.

  *

  * @since    1.0

  * @param    int $post_id     Optional, post ID to check (if not given, the global $post is used)

  * @return   bool

  */

  public function currentUserCanRate($post_id = false){



    global $post;



    $post_id = $post_id ? $post_id : $post->ID;



    $can_rate = false;



    if(in_array($post_id, $this->rated_posts))

      return false;



    // check if ratings are enabled for this post type

    if(in_array(get_post_type($post_id), $this->getOptions('post_types')))



      // check if the user is logged in; if not, only continue if anonymouse voting is allowed

      if($this->getOptions('anonymous_vote') || is_user_logged_in()){



        // last 100 IPs

        $ip_cache = get_transient('post_ratings_ip_cache');



        // client cookie

        $posts_rated = isset($_COOKIE[$this->getRecordsKey('posts_rated')]) ? explode('-', $_COOKIE[$this->getRecordsKey('posts_rated')]) : array();



        // also get user meta rating records if user is logged in

        if(is_user_logged_in()){

          $user = wp_get_current_user();

          $posts_rated = array_merge($posts_rated, (array)get_user_meta($user->ID, $this->getRecordsKey('posts_rated'), true));

        }



        $can_rate = !((isset($ip_cache[$post_id]) && in_array($this->getIP(), $ip_cache[$post_id])) || in_array($post_id, $posts_rated));



      }



    return apply_filters('post_ratings_access_check', $can_rate, $post_id);

  }







 /*

  * Get a list of most rated posts.

  * The results are returned as an array of objects

  *

  * @since     1.0

  * @param     array $args    Arguments, see below

  * @return    array

  */

  public function getTopRated($args){

    global $wpdb;



    $args = wp_parse_args($args, array(

      'post_type'        => 'post',

      'number'           => 10,

      'offset'           => 0,

      'sortby'           => 'bayesian_rating',    // bayesian_rating, rating or votes

      'order'            => 'DESC',               // ASC or DESC (no reason to use ASC...)

      'date_limit'       => 0,                    // date limit in days

      'where'            => '',

      'bayesian_formula' => $this->getOptions('bayesian_formula'),

    ));



    $options = $this->getOptions();

    extract($options);

    extract($args);



    // averge votes per post

    $avg_num_votes = ($num_rated_posts != 0) ? ($num_votes / $num_rated_posts) : 0;



    $where = $date_limit ? "AND post_date > '".date('Y-m-d', strtotime(sprintf('-%d days', $date_limit)))."'" : $where;



    if(empty($bayesian_formula))

      $bayesian_formula = $user_formula;



    if(!$bayesian_formula)

      $bayesian_formula = 'r';



    $identifiers = array(

      'AV' => $avg_num_votes,

      'MV' => self::MIN_VOTES,

      'MR' => $max_rating,

      'V'  => $num_votes,

      'v'  => 'votes',

      'R'  => $avg_rating,

      'r'  => 'rating',

    );



    $bayesian_formula = strtr($bayesian_formula, $identifiers);



    // many thanks for this SQL query to Utku Yıldırım :)

    // http://stackoverflow.com/questions/8214902/order-database-results-by-bayesian-rating/8215068#8215068

    $query = "

      SELECT *, {$bayesian_formula} AS bayesian_rating

      FROM {$wpdb->posts}

      LEFT JOIN(

       SELECT DISTINCT post_id,

        (SELECT CAST(meta_value AS DECIMAL(10)) FROM {$wpdb->postmeta} WHERE {$wpdb->postmeta}.post_id = meta.post_id AND meta_key ='votes') AS votes,

        (SELECT CAST(meta_value AS DECIMAL(10,2)) FROM {$wpdb->postmeta} WHERE {$wpdb->postmeta}.post_id = meta.post_id AND meta_key ='rating') AS rating

        FROM {$wpdb->postmeta} meta )

       AS newmeta ON {$wpdb->posts}.ID = newmeta.post_id

      WHERE post_status = 'publish' AND post_type = '{$post_type}' {$where}

      GROUP BY ID

      ORDER BY {$sortby} {$order}

      LIMIT {$offset}, {$number}

    ";



    // check cache first

    $key = md5($query);

    $cache = wp_cache_get('get_top_rated', self::ID);



    if(isset($cache[$key])){

      $results = $cache[$key];



    // no cache, do the db query...

    }else{

      $results = $wpdb->get_results($query);

      $cache[$key] = $results;

      wp_cache_set('get_top_rated', $cache, self::ID);



    }



    return $results;

  }







 /*

  * Flushes cached queries.

  *

  * @since     1.9

  */

  public function clearQueryCache(){

    wp_cache_delete('get_top_rated');

  }







 /*

  * The [rate] shortcode

  *

  * @since     1.0

  * @params    array $atts     Can accept the post ID as argument; if not given, control() will use the $post global

  * @return    string

  */

  public function Shortcode($atts){



    $post_id = '';



    // check if a post ID was given as first argument

    if(isset($atts[0]) && is_numeric($atts[0]))

      $post_id = (int)$atts[0];



    // no, maybe it's the 2nd argument

    elseif(isset($atts[1]) && is_numeric($atts[1]))

      $post_id = (int)$atts[1];



    // check if a "force" attribute is present

    $force = array_search('force', (array)$atts) !== false;



    return $this->getControl($post_id, $force);

  }







 /*

  * Register the "Top Rated" widget

  *

  * @since 1.0

  */

  public function Widget(){

    require dirname(__FILE__).'/widget.php';

    register_widget('PostRatingsWidget');

  }

}







// a shortcut to our application

function PostRatings(){

  static $app;



  // first call to app() initializes the plugin

  if(!($app instanceof PostRatings))

    $app = PostRatings::app();



  return $app;

}





PostRatings();

















// @todo

add_filter('user_has_cap', 'post_ratings_has_cap', 10, 3);

add_filter('map_meta_cap', 'post_ratings_map_cap_for_sa', 10, 4);



function post_ratings_map_cap_for_sa($caps, $req_cap, $user_id, $args){



  // $args[0] is the post ID

  if(($req_cap === 'rate') && is_multisite() && is_super_admin($user_id) && isset($args[0]) && !PostRatings()->currentUserCanRate($args[0]))

    $caps[] = 'do_not_allow';



  return $caps;



}



function post_ratings_has_cap($allcaps, $caps, $args){



  // $args[2] is the post ID

  if($args[0] !== 'rate' && !isset($args[2]) || !PostRatings()->currentUserCanRate($args[2]))

    return $allcaps;



  $allcaps['rate'] = 1;



  return $allcaps;

}







/**

 * Returns an array of system fonts

 * Feel free to edit this, update the font fallbacks, etc.

 */

 

 ?>