       1 <?php
       2 /**
       3  * Plugin Name: Easy Digital Downloads - Reviews
       4  * Plugin URI: http://easydigitaldownloads.com/extension/reviews/
       5  * Description: A fully featured reviewing system for Easy Digital Downloads.
       6  * Author: Sunny Ratilal
       7  * Version: 1.1
       8  * Requires at least: 3.6
       9  * Tested up to: 3.6
      10  *
      11  * Text Domain: edd-reviews
      12  * Domain Path: languages
      13  *
      14  * Copyright 2013 Sunny Ratilal
      15  *
      16  * @package     EDD_Reviews
      17  * @category    Core
      18  * @author      Sunny Ratilal
      19  * @version     1.1
      20  */
      21
      22 // Exit if accessed directly
      23 if ( ! defined( 'ABSPATH' ) ) exit;
      24
      25 if ( ! class_exists( 'EDD_Reviews' ) ) :
      26
      27 /**
      28  * EDD_Reviews Class
      29  *
      30  * @package EDD_Reviews
      31  * @since   1.0
      32  * @version 1.1
      33  * @author  Sunny Ratilal
      34  */
      35 final class EDD_Reviews {
      36     /**
      37      * EDD Reviews uses many variables, several of which can be filtered to
      38      * customize the way it operates. Most of these variables are stored in a
      39      * private array that gets updated with the help of PHP magic methods.
      40      *
      41      * @var array
      42      * @see EDD_Reviews::setup_globals()
      43      * @since 1.0
      44      */
      45     private $data;
      46
      47     /**
      48      * Holds the instance
      49      *
      50      * Ensures that only one instance of EDD Reviews exists in memory at any one
      51      * time and it also prevents needing to define globals all over the place.
      52      *
      53      * TL;DR This is a static property property that holds the singleton instance.
      54      *
      55      * @var object
      56      * @static
      57      * @since 1.0
      58      */
      59     private static $instance;
      60
      61     /**
      62      * Boolean whether or not to use the singleton, comes in handy
      63      * when doing testing
      64      *
      65      * @var bool
      66      * @static
      67      * @since 1.0
      68      */
      69     public static $testing = false;
      70
      71     /**
      72      * Holds the version number
      73      *
      74      * @var string
      75      * @since 1.0
      76      */
      77     public $version = '1.1';
      78
      79     /**
      80      * Get the instance and store the class inside it. This plugin utilises
      81      * the PHP singleton design pattern.
      82      *
      83      * @since 1.0
      84      * @static
      85      * @staticvar array $instance
      86      * @access public
      87      * @see edd_reviews();
      88      * @uses EDD_Reviews::setup_globals() Setup the globals needed
      89      * @uses EDD_Reviews::load_classes() Loads all the classes
      90      * @uses EDD_Reviews::hooks() Setup hooks and actions
      91      * @return object self::$instance Instance
      92      */
      93     public static function get_instance() {
      94         if ( ! isset( self::$instance ) && ! ( self::$instance instanceof EDD_Reviews ) || self::$testing ) {
      95             self::$instance = new EDD_Reviews;
      96             self::$instance->setup_globals();
      97             self::$instance->load_classes();
      98             self::$instance->hooks();
      99             self::$instance->updater();
     100         }
     101
     102         return self::$instance;
     103     }
     104
     105     /**
     106      * Constructor Function
     107      *
     108      * @since 1.0
     109      * @access protected
     110      * @see EDD_Reviews::init()
     111      * @see EDD_Reviews::activation()
     112      */
     113     public function __construct() {
     114         self::$instance = $this;
     115
     116         add_action( 'init', array( $this, 'init' ) );
     117         register_activation_hook( __FILE__, array( $this, 'activation' ) );
     118     }
     119
     120     /**
     121      * Sets up the constants/globals used
     122      *
     123      * @since 1.0
     124      * @static
     125      * @access public
     126      */
     127     private function setup_globals() {
     128         // File Path and URL Information
     129         $this->file          = __FILE__;
     130         $this->basename      = apply_filters( 'edd_reviews_plugin_basenname', plugin_basename( $this->file ) );
     131         $this->plugin_url    = plugin_dir_url( __FILE__ );
     132         $this->plugin_path   = plugin_dir_path( __FILE__ );
     133         $this->lang_dir      = apply_filters( 'edd_reviews_lang_dir',         trailingslashit( $this->plugin_path . 'languages' ) );
     134
     135         // Assets
     136         $this->assets_dir    = apply_filters( 'edd_reviews_assets_dir',       trailingslashit( $this->plugin_path . 'assets'    ) );
     137         $this->assets_url    = apply_filters( 'edd_reviews_assets_url',       trailingslashit( $this->plugin_url  . 'assets'    ) );
     138
     139         // Classes
     140         $this->classes_dir   = apply_filters( 'edd_reviews_classes_dir',      trailingslashit( $this->plugin_path . 'classes'   ) );
     141         $this->classes_url   = apply_filters( 'edd_reviews_classes_url',      trailingslashit( $this->plugin_url  . 'classes'   ) );
     142
     143         // Templating
     144         $this->templates_dir = apply_filters( 'edd_reviews_templates_dir',    trailingslashit( $this->plugin_path . 'templates' ) );
     145         $this->templates_url = apply_filters( 'edd_reviews_templates_url',    trailingslashit( $this->plugin_url  . 'templates' ) );
     146     }
     147
     148     /**
     149      * Throw error on object clone
     150      *
     151      * The whole idea of the singleton design pattern is that there is a single
     152      * object therefore, we don't want the object to be cloned.
     153      *
     154      * @since 1.0
     155      * @access protected
     156      * @return void
     157      */
     158     public function __clone() {
     159         // Cloning instances of the class is forbidden
     160         _doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'edd-reviews' ), '1.0' );
     161     }
     162
     163     /**
     164      * Disable unserializing of the class
     165      *
     166      * @since 1.0
     167      * @access protected
     168      * @return void
     169      */
     170     public function __wakeup() {
     171         // Unserializing instances of the class is forbidden
     172         _doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; huh?', 'edd-reviews' ), '1.0' );
     173     }
     174
     175     /**
     176      * Magic method for checking if custom variables have been set
     177      *
     178      * @since 1.0
     179      * @access protected
     180      * @return void
     181      */
     182     public function __isset( $key ) {
     183         return isset( $this->data[ $key ] );
     184     }
     185
     186     /**
     187      * Magic method for getting variables
     188      *
     189      * @since 1.0
     190      * @access protected
     191      * @return void
     192      */
     193     public function __get( $key ) {
     194         return isset( $this->data[ $key ] ) ? $this->data[ $key ] : null;
     195     }
     196
     197     /**
     198      * Magic method for setting variables
     199      *
     200      * @since 1.0
     201      * @access protected
     202      * @return void
     203      */
     204     public function __set( $key, $value ) {
     205         $this->data[ $key ] = $value;
     206     }
     207
     208     /**
     209      * Magic method for unsetting variables
     210      *
     211      * @since 1.0
     212      * @access protected
     213      * @return void
     214      */
     215     public function __unset( $key ) {
     216         if ( isset( $this->data[ $key ] ) )
     217             unset( $this->data[ $key ] );
     218     }
     219
     220     /**
     221      * Magic method to prevent notices and errors from invalid method calls
     222      *
     223      * @since 1.0
     224      * @access public
     225      *
     226      * @param string $name
     227      * @param array $args
     228      *
     229      * @return void
     230      */
     231     public function __call( $name = '', $args = array() ) {
     232         unset( $name, $args );
     233         return null;
     234     }
     235
     236     /**
     237      * Reset the instance of the class
     238      *
     239      * @since 1.0
     240      * @access public
     241      * @static
     242      */
     243     public static function reset() {
     244         self::$instance = null;
     245     }
     246
     247     /**
     248      * Function fired on init
     249      *
     250      * This function is called on WordPress 'init'. It's triggered from the
     251      * constructor function.
     252      *
     253      * @since 1.0
     254      * @access public
     255      *
     256      * @uses EDD_Reviews::load_plugin_textdomain()
     257      * @uses EDD_Reviews::add_shortcodes()
     258      *
     259      * @return void
     260      */
     261     public function init() {
     262         do_action( 'edd_reviews_before_init' );
     263
     264         $this->load_plugin_textdomain();
     265
     266         $this->add_shortcodes();
     267
     268         do_action( 'edd_reviews_after_init' );
     269     }
     270
     271     /**
     272      * Loads Classes
     273      *
     274      * @since 1.0
     275      * @access private
     276      * @return void
     277      */
     278     private function load_classes() {
     279         if ( ! class_exists( 'EDD_License' ) ) require $this->classes_dir . 'class-edd-license-handler.php';
     280         require $this->classes_dir . 'shortcodes/class-edd-reviews-shortcode-review.php';
     281         require $this->classes_dir . 'widgets/class-reviews-widget.php';
     282         require $this->classes_dir . 'widgets/class-featured-review-widget.php';
     283     }
     284
     285     /**
     286      * Load Plugin Text Domain
     287      *
     288      * Looks for the plugin translation files in certain directories and loads
     289      * them to allow the plugin to be localised
     290      *
     291      * @since 1.0
     292      * @access public
     293      * @return bool True on success, false on failure
     294      */
     295     public function load_plugin_textdomain() {
     296         // Traditional WordPress plugin locale filter
     297         $locale = apply_filters( 'plugin_locale',  get_locale(), 'edd-reviews' );
     298         $mofile = sprintf( '%1$s-%2$s.mo', 'edd-reviews', $locale );
     299
     300         // Setup paths to current locale file
     301         $mofile_local  = $this->lang_dir . $mofile;
     302
     303         if ( file_exists( $mofile_local ) ) {
     304             // Look in the /wp-content/plugins/edd-reviews/languages/ folder
     305             load_textdomain( 'edd-reviews', $mofile_local );
     306         } else {
     307             // Load the default language files
     308             load_plugin_textdomain( 'edd-reviews', false, $this->lang_dir );
     309         }
     310
     311         return false;
     312     }
     313
     314     /**
     315      * Activation function fires when the plugin is activated.
     316      *
     317      * This function is fired when the activation hook is called by WordPress,
     318      * it flushes the rewrite rules and disables the plugin if EDD isn't active
     319      * and throws an error.
     320      *
     321      * @since 1.0
     322      * @access public
     323      *
     324      * @return void
     325      */
     326     public function activation() {
     327         flush_rewrite_rules();
     328
     329         if ( ! class_exists( 'Easy_Digital_Downloads' ) ) {
     330             if ( is_plugin_active( $this->basename ) ) {
     331                 deactivate_plugins( $this->basename );
     332                 unset( $_GET[ 'activate' ] );
     333                 add_action( 'admin_notices', array( $this, 'admin_notices' ) );
     334             }
     335         }
     336     }
     337
     338     /**
     339      * Adds all the shortcodes
     340      *
     341      * @since 1.0
     342      * @access public
     343      * @return void
     344      */
     345     public function add_shortcodes() {
     346         add_shortcode( 'review', array( 'EDD_Reviews_Shortcode_Review', 'render' ) );
     347     }
     348
     349     /**
     350      * Adds all the hooks/filters
     351      *
     352      * The plugin relies heavily on the use of hooks and filters and modifies
     353      * default WordPress behaviour by the use of actions and filters which are
     354      * provided by WordPress.
     355      *
     356      * Actions are provided to hook on this function, before the hooks and filters
     357      * are added and after they are added. The class object is passed via the action.
     358      *
     359      * @since 1.0
     360      * @access public
     361      * @return void
     362      */
     363     public function hooks() {
     364         do_action_ref_array( 'edd_reviews_before_setup_actions', array( &$this ) );
     365
     366         /** Actions */
     367         add_action( 'comment_post',                            array( $this, 'save_review_meta'   ) );
     368         add_action( 'add_meta_boxes',                          array( $this, 'disable_trackbacks' ) );
     369         add_action( 'add_meta_boxes',                          array( $this, 'change_meta_boxes'  ) );
     370         add_action( 'wp_enqueue_scripts',                      array( $this, 'load_styles'        ) );
     371         add_action( 'wp_enqueue_scripts',                      array( $this, 'load_scripts'       ) );
     372         add_action( 'admin_enqueue_scripts',                   array( $this, 'admin_scripts'      ) );
     373         add_action( 'the_content',                             array( $this, 'microdata'          ) );
     374         add_action( 'admin_init',                              array( $this, 'activate_license'   ) );
     375         add_action( 'wp_before_admin_bar_render',              array( $this, 'admin_bar_menu'     ) );
     376         add_action( 'edd_reviews_review_display',              array( $this, 'render_review'      ), 10, 3 );
     377         add_action( 'widgets_init',                            array( $this, 'register_widgets'   ) );
     378         add_action( 'init',                                    array( $this, 'process_vote'       ) );
     379         add_action( 'wp_ajax_edd_reviews_process_vote',        array( $this, 'process_ajax_vote'  ) );
     380         add_action( 'wp_ajax_nopriv_edd_reviews_process_vote', array( $this, 'process_ajax_vote'  ) );
     381         add_action( 'wp_dashboard_setup',                      array( $this, 'dashboard_widgets'  ) );
     382         add_action( 'add_meta_boxes',                          array( $this, 'add_meta_boxes'     ) );
     383         add_action( 'edit_comment',                            array( $this, 'update_review_meta' ) );
     384         add_action( 'init',                                    array( $this, 'tinymce_button'     ) );
     385         add_action( 'init',                                    array( $this, 'process_mce_dialog' ) );
     386
     387         /** Filters */
     388         add_filter( 'preprocess_comment',                      array( $this, 'check_author'       ) );
     389         add_filter( 'edd_download_supports',                   array( $this, 'enable_comments'    ) );
     390         add_filter( 'preprocess_comment',                      array( $this, 'check_review_title' ) );
     391         add_filter( 'preprocess_comment',                      array( $this, 'check_rating'       ) );
     392         add_filter( 'comment_form_default_fields',             array( $this, 'remove_url'         ) );
     393         add_filter( 'edd_settings_styles',                     array( $this, 'styles_settings'    ) );
     394         add_filter( 'edd_settings_misc',                       array( $this, 'misc_settings'      ) );
     395         add_filter( 'manage_edit-comments_columns',            array( $this, 'custom_columns'     ) );
     396         add_filter( 'manage_comments_custom_column',           array( $this, 'custom_column_data' ), 10, 2 );
     397         add_filter( 'comments_open',                           array( $this, 'open_all_comments'  ), 10, 2 );
     398         add_filter( 'comments_template',                       array( $this, 'comments_template'  ) );
     399         add_filter( 'edd_api_valid_query_modes',               array( $this, 'register_api_mode'  ) );
     400         add_filter( 'edd_api_output_data',                     array( $this, 'api_output'         ), 10, 3 );
     401         add_filter( 'query_vars',                              array( $this, 'query_vars'         ) );
     402         add_filter( 'plugin_row_meta',                         array( $this, 'plugin_links'       ), 10, 2 );
     403
     404         do_action_ref_array( 'edd_reviews_after_setup_actions', array( &$this ) );
     405     }
     406
     407     /**
     408      * Register Widgets
     409      *
     410      * @since 1.0
     411      * @access public
     412      * @return void
     413      */
     414     public function register_widgets() {
     415         register_widget( 'EDD_Reviews_Widget_Reviews' );
     416         register_widget( 'EDD_Reviews_Widget_Featured_Review' );
     417     }
     418
     419     /**
     420      * Comments Template
     421      *
     422      * Override the default comments template for the download post type
     423      *
     424      * @since 1.0
     425      * @access public
     426      * @return string Path to the review template
     427      */
     428     public function comments_template( $template ) {
     429         if ( 'download' !== get_post_type() )
     430             return $template;
     431
     432         // Looks in the stylesheet directory (i.e. if a child theme is used)
     433         if ( file_exists( trailingslashit( get_stylesheet_directory() ) . 'edd_templates/reviews.php' ) ) {
     434             return trailingslashit( get_stylesheet_directory() ) . 'edd_templates/reviews.php';
     435         // Looks in the theme directory (i.e. the parent theme directory)
     436         } elseif ( file_exists( trailingslashit( get_template_directory() ) . 'edd_templates/reviews.php' ) ) {
     437             return trailingslashit( get_template_directory() ) . 'edd_templates/reviews.php';
     438         // Otherwise returns the default template provided by the plugin
     439         } else {
     440             return apply_filters( 'edd_reviews_comments_template_path', $this->plugin_path . '/templates/reviews.php' );
     441         }
     442     }
     443
     444     /**
     445      * Reviews form
     446      *
     447      * This function is called by the reviews template and overrides the default
     448      * comments form by replacing the fields in order for reviews to be placed.
     449      *
     450      * @since 1.0
     451      * @access public
     452      * @return void
     453      */
     454     public function reviews_form() {
     455         echo '<div id="reviews_form" class="edd_reviews_form">';
     456
     457         $commenter = wp_get_current_commenter();
     458
     459         $form = array(
     460             'title_reply' => apply_filters( 'edd_reviews_leave_a_review_text',  __( 'Leave a Review', 'edd-reviews' ) ),
     461             'comment_notes_before' => '',
     462             'comment_notes_after' => '',
     463             'fields' => array(
     464                 'author' => '<p class="comment-form-author">' . '<label for="author">' . __( 'Name', 'edd-reviews' ) . '<span class="required">*</span></label>' .
     465                             '<input id="author" name="author" type="text" value="' . esc_attr( $commenter['comment_author'] ) . '" size="30" aria-required="true" /></p>',
     466                 'email'  => '<p class="comment-form-email"><label for="email">' . __( 'Email', 'edd-reviews' ) . '<span class="required">*</span></label>' .
     467                             '<input id="email" name="email" type="text" value="' . esc_attr(  $commenter['comment_author_email'] ) . '" size="30" aria-required="true" /></p>',
     468             ),
     469             'label_submit' => __( 'Submit Review', 'edd-reviews' ),
     470             'logged_in_as' => '',
     471             'comment_field' => ''
     472         );
     473
     474         $form['comment_field'] = apply_filters( 'edd_reviews_review_form_template', '
     475             <p class="comment_form_review_title">
     476                 <label for="edd_review_title">' . __( 'Review Title', 'edd-reviews' ) . '<span class="required">*</span></label>
     477                 <input type="text" name="edd_review_title" id="edd_review_title" value="" size="30" aria-required="true" />
     478             </p>
     479
     480             <p class="comment_form_rating">
     481                 <label for="edd_rating">' . __( 'Rating', 'edd-reviews' ) . '<span class="required">*</span></label>
     482                 <span class="edd_reviews_rating_box">
     483                     <span class="edd_star_rating"></span>
     484                     <span class="edd_ratings">
     485                         <a class="edd_rating" href="" data-rating="5"><span></span></a>
     486                         <span class="edd_show_if_no_js"><input type="radio" name="edd_rating" id="edd_rating" value="5"/>5&nbsp;</span>
     487
     488                         <a class="edd_rating" href="" data-rating="4"><span></span></a>
     489                         <span class="edd_show_if_no_js"><input type="radio" name="edd_rating" id="edd_rating" value="4"/>4&nbsp;</span>
     490
     491                         <a class="edd_rating" href="" data-rating="3"><span></span></a>
     492                         <span class="edd_show_if_no_js"><input type="radio" name="edd_rating" id="edd_rating" value="3"/>3&nbsp;</span>
     493
     494                         <a class="edd_rating" href="" data-rating="2"><span></span></a>
     495                         <span class="edd_show_if_no_js"><input type="radio" name="edd_rating" id="edd_rating" value="2"/>2&nbsp;</span>
     496
     497                         <a class="edd_rating" href="" data-rating="1"><span></span></a>
     498                         <span class="edd_show_if_no_js"><input type="radio" name="edd_rating" id="edd_rating" value="1"/>1&nbsp;</span>
     499                     </span>
     500                 </span>
     501             </p>
     502
     503             <p class="comment-form-comment">
     504                 <label for="comment">' . __( 'Review', 'edd-reviews' ) . '<span class="required">*</span></label>
     505                 <textarea id="comment" name="comment" cols="45" rows="8" aria-required="true"></textarea>
     506             </p>
     507
     508             <input type="hidden" id="edd_rating" name="edd_rating" />
     509             <input type="hidden" name="edd_review" value="true" />
     510         ' );
     511
     512         do_action( 'edd_reviews_review_form_before' );
     513
     514         comment_form( apply_filters( 'edd_reviews_review_form_args', $form ) );
     515
     516         do_action( 'edd_reviews_review_form_after' );
     517
     518         echo '</div>';
     519     }
     520
     521     /**
     522      * Checks if multiple reviews have been disabled and then verifies
     523      * if the author has already posted a review for this download (product).
     524      * This function queries the database for any reviews by taking the
     525      * comment_post_ID and comment_author_email and if anything is returned, execution
     526      * of the comment addition will fail with wp_die().
     527      *
     528      * @since 1.2
     529      * @access public
     530      * @param  array $commentdata All the comment data sent via $_POST
     531      * @global array $edd_options Used to access the EDD Options
     532      * @return object|bool Returns an instance of wp_die() or the comment data
     533      */
     534     public function check_author( $commentdata ) {
     535         global $edd_options;
     536
     537         if ( isset( $edd_options['edd_reviews_disable_multiple_reviews'] ) ) {
     538             $args = array(
     539                 'author_email' => $commentdata['comment_author_email'],
     540                 'post_id'      => $commentdata['comment_post_ID']
     541             );
     542
     543             $comments = get_comments( $args );
     544
     545             if ( $comments ) {
     546                 wp_die(
     547                     __( 'You are only allowed to post one review for this product. Multiple reviews have been disabled.', 'edd-reviews' ),
     548                     __( 'Multiple Reviews Not Allowed', 'edd-reviews' ),
     549                     array( 'back_link' => true )
     550                 );
     551             } else {
     552                 return $commentdata;
     553             }
     554         } else {
     555             return $commentdata;
     556         }
     557     }
     558
     559     /**
     560      * Checks if a review title has been entered otherwise dies with an error
     561      *
     562      * @since 1.0
     563      * @access public
     564      * @param  array $commentdata All the comment data sent via $_POST
     565      * @return array $commentdata All the comment data sent via $_POST
     566      */
     567     public function check_review_title( $commentdata ) {
     568         if ( isset( $_POST['edd_review'] ) && ! isset( $_POST['edd_review_title'] ) ) {
     569             wp_die( sprintf( __( '%sERROR:%s You did not add a review title.', 'edd-reviews' ), '<strong>', '</strong>' ), __( 'Error', 'edd-reviews' ), array( 'back_link' => true ) );
     570         }
     571
     572         return $commentdata;
     573     }
     574
     575     /**
     576      * Checks if a rating has been made otherwise dies with an error
     577      *
     578      * @since 1.0
     579      * @access public
     580      * @param  array $commentdata All the comment data sent via $_POST
     581      * @return array $commentdata All the comment data sent via $_POST
     582      */
     583     public function check_rating( $commentdata ) {
     584         if ( isset( $_POST['edd_review'] ) && ! isset( $_POST['edd_rating'] ) && is_int( $_POST['edd_rating'] )  && ( ! $_POST['edd_rating'] > 5 || $_POST['edd_rating'] < 0 ) ) {
     585             wp_die( sprintf( __( '%sERROR:%s You did not add a rating or the rating you supplied was not validated.', 'edd-reviews' ), '<strong>', '</strong>' ), __( 'Error', 'edd-reviews' ), array( 'back_link' => true ) );
     586         }
     587
     588         return $commentdata;
     589     }
     590
     591     /**
     592      * Remove URL field from Comments (Review) Form only on the Download page
     593      *
     594      * @since 1.0
     595      * @access public
     596      *
     597      * @param  array $fields All the comment fields
     598      * @return array $fields Updated list of comment fields without the URL
     599      */
     600     public function remove_url( $fields ) {
     601         if ( is_singular( 'download' ) && comments_open() ) {
     602             if ( isset( $fields['url'] ) ) {
     603                 unset( $fields['url'] );
     604             }
     605         }
     606
     607         return $fields;
     608     }
     609
     610     /**
     611      * Save the Review Meta Data
     612      *
     613      * @since 1.0
     614      * @access public
     615      * @param int $comment_id Comment ID
     616      * @return void
     617      */
     618     public function save_review_meta( $comment_id ) {
     619         /** Check if a rating has been submitted */
     620         if ( isset( $_POST['edd_review'] ) && isset( $_POST['edd_rating'] ) && ! empty( $_POST['edd_review_title'] ) ) {
     621             $rating = wp_filter_nohtml_kses( $_POST['edd_rating'] );
     622             add_comment_meta( $comment_id, 'edd_rating', $rating );
     623         }
     624
     625         /** Check if a review title has been submitted */
     626         if ( isset( $_POST['edd_review'] ) && isset( $_POST['edd_review_title'] ) && ! empty( $_POST['edd_review_title'] ) ) {
     627             $review_title = sanitize_text_field( wp_filter_nohtml_kses( esc_html( $_POST['edd_review_title'] ) ) );
     628             add_comment_meta( $comment_id, 'edd_review_title', $review_title );
     629         }
     630     }
     631
     632     /**
     633      * Microdata
     634      *
     635      * @since 1.0
     636      * @access public
     637      * @global object $post Used to access the post data
     638      *
     639      * @uses EDD_Reviews::average_rating()
     640      *
     641      * @param  string $content Content of the post
     642      * @return string $content Content of the post with the microdata
     643      */
     644     public function microdata( $content ) {
     645         global $post;
     646
     647         // Bail if we're not on a download page
     648         if ( ! is_singular( 'download' ) )
     649             return $content;
     650
     651         do_action( 'edd_reviews_microdata_before' );
     652         ?>
     653         <div style="display:none" class="edd-review-microdata" itemprop="aggregateRating" itemscope itemtype="http://schema.org/AggregateRating">
     654             <span itemprop="ratingValue"><?php echo $this->average_rating(); ?></span>
     655             <span itemprop="reviewCount"><?php echo wp_count_comments( $post->ID )->total_comments; ?></span>
     656         </div>
     657         <?php
     658         do_action( 'edd_reviews_microdata_after' );
     659         return $content;
     660     }
     661
     662     /**
     663      * Get Average Rating
     664      *
     665      * @since 1.0
     666      * @access public
     667      * @global object Used to access the post data
     668      *
     669      * @param  bool $echo Whether to echo the result or return it
     670      * @return string $average Returns the average rating
     671      */
     672     public function average_rating( $echo = true ) {
     673         global $post;
     674
     675         $reviews = get_comments( apply_filters( 'edd_reviews_average_rating_query_args', array(
     676             'post_id' => $post->ID
     677         ) ) );
     678
     679         $total_ratings = 0;
     680
     681         foreach ( $reviews as $review ) {
     682             $rating = get_comment_meta( $review->comment_ID, 'edd_rating', true );
     683             $total_ratings += $rating;
     684         }
     685
     686         $total = wp_count_comments( $post->ID )->total_comments;
     687
     688         if ( 0 == $total )
     689             $total = 1;
     690
     691         $average = round( $total_ratings / $total, 1 );
     692
     693         if ( $echo ) {
     694             echo $average;
     695         } else {
     696             return $average;
     697         }
     698     }
     699
     700     /**
     701      * Enable Reviews on all Downloads
     702      *
     703      * @since 1.0
     704      * @access public
     705      *
     706      * @param array $support What the Downloads post type supports (e.g. title)
     707      * @return array Merged array with comments support enabled for downloads
     708      */
     709     public function enable_comments( $supports ) {
     710         return array_merge( $supports, array( 'comments' ) );
     711     }
     712
     713     /**
     714      * Disable Trackbacks
     715      *
     716      * This function removes the Trackbacks meta box from the Add/Edit Download
     717      * screen as it's not the sole purpose of this plugin and if any were to be
     718      * made, they wouldn't render correctly as the plugin doesn't provide
     719      * support for trackbacks.
     720      *
     721      * @since 1.0
     722      * @access public
     723      * @return void
     724      */
     725     public function disable_trackbacks() {
     726         remove_meta_box( 'trackbacksdiv', 'download', 'normal' );
     727     }
     728
     729     /**
     730      * Edit Meta Boxes
     731      *
     732      * The Comments meta box on the Add/Edit Download screen is renamed here and
     733      * the callback function for the comments meta box is also changed.
     734      *
     735      * @since 1.0
     736      * @access public
     737      * @global array Used to edit the admin meta boxes
     738      * @return void
     739      */
     740     public function change_meta_boxes() {
     741         global $wp_meta_boxes;
     742
     743         /**
     744          * The comment status box (whether comments are open or not) does not
     745          * need to be displayed because a filter is used to force open all
     746          * comments for the downloads post type
     747          */
     748         unset( $wp_meta_boxes['download']['normal']['core']['commentstatusdiv'] );
     749
     750         /** Titles */
     751         $wp_meta_boxes['download']['normal']['core']['commentsdiv']['title'] = __( 'Reviews', 'edd-reviews' );
     752
     753         /** Callbacks */
     754         $wp_meta_boxes['download']['normal']['core']['commentsdiv']['callback'] = array( $this, 'post_comment_meta_box' );
     755     }
     756
     757     /**
     758      * Override the default comment status meta box
     759      *
     760      * @since 1.0
     761      * @access public
     762      *
     763      * @param object $post Post Object
     764      * @return void
     765      */
     766     public function post_comment_status_meta_box( $post ) {
     767         ?>
     768         <input name="advanced_view" type="hidden" value="1" />
     769         <p class="meta-options">
     770             <label for="comment_status" class="selectit">
     771                 <input name="comment_status" type="checkbox" id="comment_status" value="open" <?php checked( $post->comment_status, 'open' ); ?> /> <?php _e( 'Allow reviews.', 'edd-reviews' ) ?>
     772             </label>
     773         </p>
     774         <?php
     775     }
     776
     777     /**
     778      * Override the default comments meta box on Add/Edit screen
     779      *
     780      * @since 1.0
     781      * @access public
     782      * @global object Used to query the database using the WordPress Database API
     783      *
     784      * @param object $post Current Post
     785      * @return void
     786      */
     787     public function post_comment_meta_box( $post ) {
     788         global $wpdb;
     789
     790         wp_nonce_field( 'get-comments', 'add_comment_nonce', false );
     791
     792         $total = get_comments( array( 'post_id' => $post->ID, 'number' => 1, 'count' => true ) );
     793         $wp_list_table = _get_list_table( 'WP_Post_Comments_List_Table' );
     794         $wp_list_table->display( true );
     795
     796         if ( 1 > $total ) {
     797             echo '<p id="no-comments">' . apply_filters( 'edd_reviews_admin_no_reviews_text', __( 'No reviews yet.', 'edd-reviews' ) ) . '</p>';
     798         } else {
     799             $hidden = get_hidden_meta_boxes( get_current_screen() );
     800             if ( ! in_array( 'commentsdiv', $hidden ) ) {
     801                 ?>
     802                 <script type="text/javascript">jQuery(document).ready(function(){commentsBox.get(<?php echo $total; ?>, 10);});</script>
     803                 <?php
     804             }
     805
     806             ?>
     807             <p class="hide-if-no-js" id="show-comments"><a href="#commentstatusdiv" onclick="commentsBox.get(<?php echo $total; ?>);return false;"><?php _e('Show comments'); ?></a> <span class="spinner"></span></p>
     808             <?php
     809         }
     810
     811         wp_comment_trashnotice();
     812     }
     813
     814     /**
     815      * Load Styles
     816      *
     817      * @since 1.0
     818      * @access public
     819      * @global array $edd_options Used to access the EDD Options
     820      * @return void
     821      */
     822     public function load_styles() {
     823         global $edd_options;
     824
     825         wp_register_style( 'edd-reviews-admin', $this->assets_url . 'css/edd-reviews-admin.css', array( ), $this->version );
     826         wp_enqueue_style( 'edd-reviews-admin' );
     827
     828         if ( isset( $edd_options['edd_reviews_disable_css'] ) )
     829             return;
     830
     831         wp_register_style( 'edd-reviews', $this->assets_url . 'css/edd-reviews.css', array( ), $this->version );
     832         wp_enqueue_style( 'edd-reviews' );
     833     }
     834
     835     /**
     836      * Load Scripts
     837      *
     838      * @since 1.0
     839      * @access public
     840      * @return void
     841      */
     842     public function load_scripts() {
     843         wp_register_script( 'edd-reviews-js', $this->assets_url . 'js/edd-reviews.js', array( 'jquery' ), $this->version );
     844
     845         if ( is_singular( 'download' ) ) {
     846             wp_enqueue_script( 'edd-reviews-js' );
     847         }
     848
     849         $edd_reviews_params = array(
     850             'ajax_url'         => admin_url( 'admin-ajax.php' ),
     851             'edd_voting_nonce' => wp_create_nonce( 'edd_reviews_voting_nonce' ),
     852             'thank_you_msg'    => apply_filters( 'edd_reviews_thank_you_for_voting_message', __( 'Thank you for your feedback.', 'edd-reviews' ) )
     853         );
     854
     855         wp_localize_script( 'edd-reviews-js', 'edd_reviews_params', apply_filters( 'edd_reviews_js_params', $edd_reviews_params ) );
     856     }
     857
     858     /**
     859      * Load Admin Scripts/Styles
     860      *
     861      * @since 1.0
     862      * @access public
     863      * @global object Used to access the current 'screen' that the user is
     864      *  browsing
     865      * @global object Used to access the post data
     866      * @return void
     867      */
     868     public function admin_scripts() {
     869         global $current_screen, $post;
     870
     871         wp_register_style( 'edd-reviews-admin', $this->assets_url . 'css/edd-reviews-admin.css', array( ), $this->version );
     872
     873         wp_enqueue_style( 'edd-reviews-admin' );
     874         wp_enqueue_style( 'wp-color-picker' );
     875     }
     876
     877     /**
     878      * Shows Review Meta on Comments List Table
     879      *
     880      * @since 1.0
     881      * @access public
     882      *
     883      * @param array $columns All the columns on the list table
     884      * @return array $columns New columns with Review Title and Rating added
     885      */
     886     function custom_columns( $columns ) {
     887         $columns['title']  = __( 'Review Title', 'edd-reviews' );
     888         $columns['rating'] = __( 'Rating', 'edd-reviews' );
     889
     890         return $columns;
     891     }
     892
     893     /**
     894      * Display Custom Column Data
     895      *
     896      * @since 1.0
     897      * @access public
     898      *
     899      * @param string $column Current column
     900      * @param int $comment_ID Comment ID
     901      * @return void
     902      */
     903     public function custom_column_data( $column, $comment_ID ) {
     904         if ( 'title' == $column ) {
     905             if ( get_comment_meta( $comment_ID, 'edd_review_title', true ) )
     906                 echo get_comment_meta( $comment_ID, 'edd_review_title', true );
     907             else
     908                 echo '-';
     909         }
     910
     911         if ( 'rating' == $column ) {
     912             if ( get_comment_meta( $comment_ID, 'edd_rating', true ) )
     913                 echo get_comment_meta( $comment_ID, 'edd_rating', true ) . ' / 5';
     914             else
     915                 echo '-';
     916         }
     917     }
     918
     919     /**
     920      * Open Comments for all Downloads
     921      *
     922      * @since 1.0
     923      * @access public
     924      *
     925      * @param bool $open Whether the comments are open or not
     926      * @param string $post_id Post ID
     927      * @return bool $open Whether the comments are open or not
     928      */
     929     public function open_all_comments( $open, $post_id ) {
     930         $post_type = get_post_type( $post_id );
     931
     932         if ( 'download' == $post_type )
     933             $open = true;
     934
     935         return $open;
     936     }
     937
     938     /**
     939      * Register Misc Settings
     940      *
     941      * @since 1.0
     942      * @access public
     943      *
     944      * @param array $settings Existing registered settings
     945      * @param array New settings
     946      * @return array Merged array with new settings added
     947      */
     948     public function misc_settings( $settings ) {
     949         $new = array(
     950             array(
     951                 'id'   => 'edd_review_settings',
     952                 'name' => '<strong>' . __( 'Reviews', 'edd-reviews' ) . '</strong>',
     953                 'desc' => '',
     954                 'type' => 'header'
     955             ),
     956             array(
     957                 'id'   => 'edd_reviews_enable_breakdown',
     958                 'name' => __( 'Enable review breakdown', 'edd-reviews' ),
     959                 'desc' => __( 'This will show how many people have rated the download for each star rating.', 'edd-reviews' ),
     960                 'type' => 'checkbox',
     961                 'size' => 'regular'
     962             ),
     963             array(
     964                 'id'   => 'edd_reviews_disable_multiple_reviews',
     965                 'name' => __( 'Disable multiple reviews by same author', 'edd-reviews' ),
     966                 'desc' => __( 'This will disallow authors to post multiple reviews on the same download', 'edd-reviews' ),
     967                 'type' => 'checkbox',
     968                 'size' => 'regular'
     969             ),
     970         );
     971
     972         return array_merge( $settings, $new );
     973     }
     974
     975     /**
     976      * Register Misc Settings
     977      *
     978      * @since 1.0
     979      * @access public
     980      *
     981      * @param array $settings Existing registered settings
     982      * @param array New settings
     983      * @return array Merged array with new settings added
     984      */
     985     public function styles_settings( $settings ) {
     986         $new = array(
     987             array(
     988                 'id'   => 'edd_reviews_styling_options',
     989                 'name' => '<strong>' . __( 'Reviews', 'edd-reviews' ) . '</strong>',
     990                 'desc' => '',
     991                 'type' => 'header'
     992             ),
     993             array(
     994                 'id'   => 'edd_reviews_disable_css',
     995                 'name' => __( 'Disable EDD Reviews CSS', 'edd-reviews' ),
     996                 'desc' => __( 'Check this to disable styling for the reviews provided by the EDD Reviews plugin', 'edd-reviews' ),
     997                 'type' => 'checkbox',
     998                 'size' => 'regular'
     999             )
    1000         );
    1001
    1002         return array_merge( $settings, $new );
    1003     }
    1004
    1005     /**
    1006      * Adds "View Reviews" Link to Admin Bar
    1007      *
    1008      * @since 1.0
    1009      * @access public
    1010      * @global object $wp_admin_bar Used to add nodes to the WordPress Admin Bar
    1011      * @global object $post Used to access the post data
    1012      * @return void
    1013      */
    1014     public function admin_bar_menu() {
    1015         global $wp_admin_bar, $post;
    1016
    1017         if ( is_admin() && current_user_can( 'moderate_comments' ) ) {
    1018             $current_screen = get_current_screen();
    1019
    1020             if ( 'post' == $current_screen->base && 'add' != $current_screen->action && ( $post_type_object = get_post_type_object( $post->post_type ) ) && 'download' == $post->post_type && current_user_can( $post_type_object->cap->read_post, $post->ID ) && ( $post_type_object->public ) && ( $post_type_object->show_in_admin_bar ) && current_user_can( 'moderate_comments' ) ) {
    1021                 if ( wp_count_comments( $post->ID )->total_comments > 0 ) {
    1022                     $wp_admin_bar->add_node( array(
    1023                         'id' => 'edd-view-reviews',
    1024                         'title' => __( 'View Reviews', 'edd-reviews' ) . '<span class="edd-review-count-wrap"><span class="edd-review-count">' . wp_count_comments( $post->ID )->total_comments . '</span></span>',
    1025                         'href' => admin_url( 'edit-comments.php?p=' . $post->ID )
    1026                     ) );
    1027                 }
    1028             }
    1029         } elseif ( is_singular( 'download' ) && current_user_can( 'moderate_comments' ) ) {
    1030             if ( wp_count_comments( $post->ID )->total_comments > 0 ) {
    1031                 $wp_admin_bar->add_node( array(
    1032                     'id' => 'edd-view-reviews',
    1033                     'title' => __( 'View Reviews', 'edd-reviews' ) . '<span class="edd-review-count-wrap"><span class="edd-review-count">' . wp_count_comments( $post->ID )->total_comments . '</span></span>',
    1034                     'href' => admin_url( 'edit-comments.php?p=' . $post->ID )
    1035                 ) );
    1036             }
    1037         }
    1038     }
    1039
    1040     /**
    1041      * Handles the displaying of any notices in the admin area
    1042      *
    1043      * @since 1.0
    1044      * @access public
    1045      * @return void
    1046      */
    1047     public function admin_notices() {
    1048         echo '<div class="error"><p>' . sprintf( __( 'You must install %sEasy Digital Downloads%s for the Reviews Add-On to work.', 'edd-reviews' ), '<a href="http://easydigitaldownloads.com" title="Easy Digital Downloads">', '</a>' ) . '</p></div>';
    1049     }
    1050
    1051     /**
    1052      * Count the number of reviews from the database
    1053      *
    1054      * @since 1.0
    1055      * @access public
    1056      * @global object $wpdb Used to query the database using the WordPress
    1057      *   Database API
    1058      * @global object $post Used to access the post data
    1059      * @return string $count Number of reviews
    1060      */
    1061     public function count_reviews() {
    1062         global $wpdb, $post;
    1063
    1064         $count = $wpdb->get_var(
    1065             $wpdb->prepare(
    1066                 "
    1067                 SELECT COUNT(meta_value)
    1068                 FROM {$wpdb->commentmeta}
    1069                 LEFT JOIN {$wpdb->comments} ON {$wpdb->commentmeta}.comment_id = {$wpdb->comments}.comment_ID
    1070                 WHERE meta_key = 'edd_rating'
    1071                 AND comment_post_ID = %d
    1072                 AND comment_approved = '1'
    1073                 AND meta_value > 0
    1074                 ",
    1075                 $post->ID
    1076             )
    1077         );
    1078
    1079         return $count;
    1080     }
    1081
    1082     /**
    1083      * Count the number of ratings from the database
    1084      *
    1085      * @since 1.0
    1086      * @access public
    1087      * @global object $wpdb Used to query the database using the WordPress
    1088      *   Database API
    1089      * @global object $post Used to access the post data
    1090      * @return string $count Number of reviews
    1091      */
    1092     public function count_ratings() {
    1093         global $wpdb, $post;
    1094
    1095         $count = $wpdb->get_var(
    1096             $wpdb->prepare(
    1097                 "
    1098                 SELECT SUM(meta_value)
    1099                 FROM {$wpdb->commentmeta}
    1100                 LEFT JOIN {$wpdb->comments} ON {$wpdb->commentmeta}.comment_id = {$wpdb->comments}.comment_ID
    1101                 WHERE meta_key = 'edd_rating'
    1102                 AND comment_post_ID = %d
    1103                 AND comment_approved = '1'
    1104                 ",
    1105                 $post->ID
    1106             )
    1107         );
    1108
    1109         return $count;
    1110     }
    1111
    1112     /**
    1113      * Gets the number of the reviews by a rating
    1114      *
    1115      * @since 1.0
    1116      * @access public
    1117      * @global object $wpdb Used to query the database using the WordPress
    1118      *   Database API
    1119      * @param int $rating Rating (1 - 5)
    1120      * @return int $number Number of reviews
    1121      */
    1122     public function get_review_count_by_rating( $rating ) {
    1123         global $wpdb, $post;
    1124
    1125         $rating = (int) $rating;
    1126
    1127         if ( $rating < 1 && $rating > 5 )
    1128             return;
    1129
    1130         $count = $wpdb->get_var(
    1131             $wpdb->prepare(
    1132                 "
    1133                 SELECT COUNT(meta_value)
    1134                 FROM {$wpdb->commentmeta}
    1135                 LEFT JOIN {$wpdb->comments} ON {$wpdb->commentmeta}.comment_id = {$wpdb->comments}.comment_ID
    1136                 WHERE meta_key = 'edd_rating'
    1137                 AND meta_value = {$rating}
    1138                 AND comment_approved = '1'
    1139                 AND meta_value > 0
    1140                 AND {$wpdb->comments}.comment_post_ID = %s
    1141                 ",
    1142                 $post->ID
    1143             )
    1144         );
    1145
    1146         return $count;
    1147     }
    1148
    1149     /**
    1150      * Build Reviews (comments) title
    1151      *
    1152      * @since 1.0
    1153      * @access public
    1154      * @global object $post Used to access the post data
    1155      *
    1156      * @uses EDD_Reviews::count_reviews()
    1157      *
    1158      * @param int $average Average ratings for reviews
    1159      * @return void
    1160      */
    1161     public function reviews_title( $average = null ) {
    1162         global $post;
    1163
    1164         if ( $average ) :
    1165
    1166         do_action( 'edd_reviews_title_before' );
    1167         ?>
    1168         <div itemprop="aggregateRating" itemscope itemtype="http://schema.org/AggregateRating">
    1169             <span itemprop="ratingValue" style="display:none"><?php echo $average; ?></span>
    1170             <h2 class="comments-title" id="comments-title"><?php echo sprintf( apply_filters( 'edd_reviews_reviews_title', __( '%s Reviews for %s', 'edd-reviews' ) ), '<span itemprop="reviewCount" class="edd-review-count">' . $this->count_reviews() . '</span>', get_the_title( $post->ID ) ); ?></h2>
    1171         </div>
    1172         <?php
    1173         else :
    1174         ?>
    1175         <h2 class="comments-title" id="comments-title"><?php apply_filters( 'edd_reviews_review_title_default',  _e( 'Reviews', 'edd-reviews' ) ); ?></h2>
    1176         <?php
    1177         do_action( 'edd_reviews_title_after' );
    1178         endif;
    1179     }
    1180
    1181     /**
    1182      * Checks if the reviewer has purchased the download
    1183      *
    1184      * @since 1.0
    1185      * @access public
    1186      * @global object $post Used to access the post data
    1187      * @return bool Whether reviews has purchased download or not
    1188      */
    1189     public function reviewer_has_purchased_download() {
    1190         global $post;
    1191
    1192         $user_email = wp_get_current_commenter();
    1193         $user_email = $user_email['comment_author_email'];
    1194
    1195         if ( edd_has_user_purchased( $user_email, $post->ID ) )
    1196             return true;
    1197
    1198         return false;
    1199     }
    1200
    1201     /**
    1202      * Add Classes to the Reviews
    1203      *
    1204      * @since 1.0
    1205      * @access public
    1206      * @param array $classes Comment classes
    1207      * @return array $classes Comment (reviews) classes with 'review' added
    1208      */
    1209     public function review_classes( $classes ) {
    1210         $classes[] = 'review';
    1211
    1212         return $classes;
    1213     }
    1214
    1215     /**
    1216      * Template for displaying reviews
    1217      *
    1218      * This function just executes the hooks to display the review.
    1219      *
    1220      * @since 1.0
    1221      * @access public
    1222      *
    1223      * @param object $comment
    1224      * @param array  $args
    1225      * @param string $depth
    1226      * @return void
    1227      */
    1228     public function review( $comment, $args, $depth ) {
    1229         $GLOBALS['comment'] = $comment;
    1230         extract( $args, EXTR_SKIP );
    1231
    1232         do_action( 'edd_reviews_review_before' );
    1233         do_action( 'edd_reviews_review_display', $comment, $args, $depth );
    1234         do_action( 'edd_reviews_review_after' );
    1235     }
    1236
    1237     /**
    1238      * Renders each review
    1239      *
    1240      * NOTE: Closing tags have been left out deliberately because WordPress will
    1241      * supply the closing tag automatically. See here:
    1242      * http://codex.wordpress.org/Function_Reference/wp_list_comments
    1243      *
    1244      * @since 1.0
    1245      * @access public
    1246      *
    1247      * @uses EDD_Reviews::voting_info()
    1248      *
    1249      * @param object $comment
    1250      * @param array $args
    1251      * @param string $depth
    1252      * @return void
    1253      */
    1254     public function render_review( $comment, $args, $depth ) {
    1255         ob_start();
    1256         $rating = get_comment_meta( get_comment_ID(), 'edd_rating', true );
    1257         ?>
    1258
    1259         <?php if ( $depth == 1 ) : ?>
    1260         <li itemprop="review" itemscope itemtype="http://schema.org/Review" <?php comment_class(); ?> id="li-comment-<?php comment_ID() ?>">
    1261             <article id="comment-<?php comment_ID(); ?>" class="comment comment_container edd_reviews_container">
    1262                 <?php if ( get_option( 'show_avatars' ) ) echo get_avatar( $GLOBALS['comment'], 60 ); ?>
    1263
    1264                 <div class="edd-review-meta">
    1265                     <span itemprop="name" class="review-title-text"><?php echo get_comment_meta( get_comment_ID(), 'edd_review_title', true ); ?> <?php if ( current_user_can( 'edit_others_posts' ) ) : ?><span class="edit-link"><a class="comment-edit-link" href="<?php echo admin_url( 'comment.php?action=editcomment&c=' . get_comment_ID() ); ?>"><?php _e( 'Edit Review', 'edd-reviews' ); ?></a></span><?php endif; ?> </span>
    1266
    1267                     <div itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating" class="star-rating">
    1268                         <div class="edd_reviews_rating_box" role="img" aria-label="<?php echo $rating . ' ' . __( 'stars', 'edd-reviews' ); ?>">
    1269                             <div class="edd_star_rating" style="width: <?php echo ( 19 * $rating ); ?>px"></div>
    1270                         </div>
    1271                         <div style="display:none" itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating">
    1272                             <meta itemprop="worstRating" content="1" />
    1273                             <span itemprop="ratingValue"><?php echo $rating; ?></span>
    1274                             <span itemprop="bestRating">5</span>
    1275                         </div>
    1276                     </div>
    1277
    1278                     <?php if ( 0 == $GLOBALS['comment']->comment_approved ) : ?>
    1279                         <p class="moderation"><em><?php apply_filters( 'edd_reviews_moderation_message', _e( 'Your review is awaiting moderation.', 'edd-reviews' ) ); ?></em></p>
    1280                     <?php else : ?>
    1281                         <p class="edd-reviews-author-meta"><?php _e( 'by', 'edd-reviews' ); ?> <span itemprop="author"><?php comment_author(); ?></span>, <time itemprop="datePublished" time datetime="<?php echo get_comment_date( 'c' ); ?>"><?php echo get_comment_date( get_option( 'date_format' ) ); ?></time>:</p>
    1282                     <?php endif; ?>
    1283                 </div>
    1284                 <div class="clear"></div>
    1285
    1286                 <div class="comment-text review-text">
    1287                     <div itemprop="description" class="description edd-review-content comment-content">
    1288                         <?php comment_text(); ?>
    1289                     </div>
    1290                 </div>
    1291
    1292                 <?php if ( ( isset( $_GET['edd_reviews_vote'] ) && $_GET['edd_reviews_vote'] == 'success' && isset( $_GET['edd_c'] ) && is_numeric( $_GET['edd_c'] ) && $_GET['edd_c'] == get_comment_ID() ) || EDD()->session->get( 'wordpress_edd_reviews_voted_' . get_comment_ID() ) ) : ?>
    1293                 <div class="edd-review-vote edd-yellowfade">
    1294                     <p style="margin:0;padding:0;"><?php echo apply_filters( 'edd_reviews_thank_you_for_voting_message', __( 'Thank you for your feedback.', 'edd-reviews' ) ); ?></p>
    1295                     <?php $this->voting_info(); ?>
    1296                 </div>
    1297                 <?php else: ?>
    1298                 <div class="edd-review-vote">
    1299                     <?php do_action( 'edd_reviews_voting_box_before' ); ?>
    1300                     <?php $this->voting_info(); ?>
    1301                     <p><?php echo apply_filters( 'edd_reviews_voting_intro_text', _e( 'Help other customers find the most helpful reviews', 'edd-reviews' ) ); ?></p>
    1302                     <p>
    1303                         <?php echo apply_filters( 'edd_reviews_review_helpful_text', __( 'Did you find this review helpful?', 'edd-reviews' ) ); ?>
    1304                         <span class="edd-reviews-voting-buttons">
    1305                             <a class="vote-yes" data-edd-reviews-comment-id="<?php echo get_comment_ID(); ?>" data-edd-reviews-vote="yes" rel="nofollow" href="<?php echo add_query_arg( array( 'edd_review_vote' => 'yes', 'edd_c' => get_comment_ID() ) ); ?>"><?php _e( 'Yes', 'edd-reviews' ); ?></a>
    1306                             <a class="vote-no" data-edd-reviews-comment-id="<?php echo get_comment_ID(); ?>" data-edd-reviews-vote="no" rel="nofollow" href="<?php echo add_query_arg( array( 'edd_review_vote' => 'no', 'edd_c' => get_comment_ID() ) ); ?>"><?php _e( 'No', 'edd-reviews' ); ?></a>
    1307                         </span>
    1308                     </p>
    1309                     <?php do_action( 'edd_reviews_voting_box_after' ); ?>
    1310                 </div>
    1311                 <?php endif; ?>
    1312             </article>
    1313             <div class="clear"></div>
    1314         <?php elseif ( $depth > 1 ) : ?>
    1315         <li itemprop="review" itemscope itemtype="http://schema.org/Review" <?php comment_class(); ?> id="li-comment-<?php comment_ID() ?>">
    1316             <article id="comment-<?php comment_ID(); ?>" class="comment comment_container edd_reviews_container">
    1317                 <?php if ( get_option( 'show_avatars' ) ) echo get_avatar( $GLOBALS['comment'], 44 ); ?>
    1318
    1319                 <?php if ( 0 == $GLOBALS['comment']->comment_approved ) : ?>
    1320                 <p class="moderation"><em><?php apply_filters( 'edd_reviews_moderation_message', _e( 'Your review is awaiting moderation.', 'edd-reviews' ) ); ?></em></p>
    1321                 <?php else : ?>
    1322                 <p class="edd-reviews-comment-author"><strong><?php comment_author(); ?></strong></p>
    1323                 <p class="edd-reviews-date-published"><time itemprop="datePublished" time datetime="<?php echo get_comment_date( 'c' ); ?>"><?php echo get_comment_date( get_option( 'date_format' ) ); ?></time></p>
    1324                 <?php endif; ?>
    1325                 <div class="clear"></div>
    1326
    1327                 <div class="comment-text review-text">
    1328                     <div itemprop="description" class="description edd-review-content comment-content">
    1329                         <?php comment_text(); ?>
    1330                     </div>
    1331                 </div>
    1332             </article>
    1333             <div class="clear"></div>
    1334         <?php
    1335         endif;
    1336         echo ob_get_clean();
    1337     }
    1338
    1339     /**
    1340      * Display Voting Info
    1341      *
    1342      * Example output: 2 of 8 people found this review helpful
    1343      *
    1344      * @since 1.0
    1345      * @access public
    1346      * @global object $GLOBALS['comment'] Current comment
    1347      * @return void
    1348      */
    1349     public function voting_info() {
    1350         $comment = $GLOBALS['comment'];
    1351
    1352         $votes = array(
    1353             'yes' => get_comment_meta( $comment->comment_ID, 'edd_review_vote_yes', true ),
    1354             'no'  => get_comment_meta( $comment->comment_ID, 'edd_review_vote_no',  true ),
    1355         );
    1356
    1357         if ( ! empty( $votes['yes'] ) && $votes['yes'] > 1 ) {
    1358             $total = $votes['yes'] + $votes['no'];
    1359
    1360             echo '<p class="edd-review-voting-feedback">' . sprintf( __( '%s of %s people found this review helpful.', 'edd-reviews' ), $votes['yes'], $total ) . '</p>';
    1361         }
    1362     }
    1363
    1364     /**
    1365      * Conditional whether or not to display review breakdown
    1366      *
    1367      * @since 1.0
    1368      * @access public
    1369      * @global array $edd_options Used to access the EDD Options
    1370      *
    1371      * @uses EDD_Reviews::review_breakdown()
    1372      *
    1373      * @return void
    1374      */
    1375     public function maybe_show_review_breakdown() {
    1376         global $edd_options;
    1377
    1378         if ( isset( $edd_options['edd_reviews_enable_breakdown'] ) && 1 == $edd_options['edd_reviews_enable_breakdown'] ) {
    1379             $this->review_breakdown();
    1380         }
    1381     }
    1382
    1383     /**
    1384      * Reviews Breakdown
    1385      *
    1386      * Shows a breakdown of all the reviews and the number of people that given
    1387      * each rating for each download
    1388      *
    1389      * Example: 8 people gave a 5 star rating; 10 people have a 2 star rating
    1390      *
    1391      * @since 1.0
    1392      * @access public
    1393      *
    1394      * @uses EDD_Reviews::display_total_reviews_count()
    1395      * @uses EDD_Reviews::display_review_counts()
    1396      *
    1397      * @return void
    1398      */
    1399     public function review_breakdown() {
    1400         echo '<div class="edd_reviews_breakdown">';
    1401         $this->display_total_reviews_count();
    1402         $this->display_review_counts();
    1403         echo '</div>';
    1404     }
    1405
    1406     /**
    1407      * Displays the total reviews count
    1408      *
    1409      * @since 1.0
    1410      * @access public
    1411      *
    1412      * @uses EDD_Reviews::count_reviews()
    1413      *
    1414      * @return void
    1415      */
    1416     public function display_total_reviews_count() {
    1417         echo '<div class="edd-reviews-total-count">' . $this->count_reviews() . ' ' . _n( 'review', 'reviews', $this->count_reviews(), 'edd-reviews' ) . '</div>';
    1418     }
    1419
    1420     /**
    1421      * Displays reviews count for each rating by looping through 1 - 5
    1422      *
    1423      * @since 1.0
    1424      * @access public
    1425      *
    1426      * @uses EDD_Reviews::get_review_count_by_rating()
    1427      * @uses EDD_Reviews::count_reviews()
    1428      *
    1429      * @return void
    1430      */
    1431     public function display_review_counts() {
    1432         $output = '';
    1433
    1434         for ( $i = 5; $i >= 1; $i-- ) {
    1435             $number = $this->get_review_count_by_rating( $i );
    1436
    1437             $all = $this->count_reviews();
    1438
    1439             ( $all == 0 ) ? $all = 1 : $all;
    1440
    1441             $output .= '<div class="edd-counter-container edd-counter-container-'. $i .'">';
    1442             $output .= '<div class="edd-counter-label">'. $i . ' ' . _n( 'star', 'stars', $i, 'edd-reviews' ) . '</div>';
    1443             $output .= '<div class="edd-counter-back"><span class="edd-counter-front" style="width: '. number_format( ( $number / $all ) * 100, 1 ) .'px"></span></div>';
    1444             $output .= '<div class="edd-review-count">'. $number .'</div>';
    1445             $output .= '</div>';
    1446         }
    1447
    1448         echo $output;
    1449     }
    1450
    1451     /**
    1452      * Process Vote from Review
    1453      *
    1454      * This function is called if a JavaScript isn't enabled
    1455      *
    1456      * @since 1.0
    1457      * @access public
    1458      * @return void
    1459      */
    1460     public function process_vote() {
    1461         if ( isset( $_GET['edd_review_vote'] ) && isset( $_GET['edd_c'] ) && is_numeric( $_GET['edd_c'] ) ) {
    1462             $this->add_comment_vote_meta( $_GET['edd_c'], $_GET['edd_review_vote'] );
    1463
    1464             // Remove the query arguments to prevent multiple votes
    1465             $url = remove_query_arg( array( 'edd_c', 'edd_review_vote' ) );
    1466             wp_redirect( $url . '?edd_reviews_vote=success&edd_c=' . $_GET['edd_c'] .'#comment-' . intval( $_GET['edd_c'] ) );
    1467             die();
    1468         }
    1469     }
    1470
    1471     /**
    1472      * Add Comment Vote
    1473      *
    1474      * @since 1.0
    1475      * @access public
    1476      *
    1477      * @param int $comment_id Comment ID
    1478      * @param string $vote Whether the vote was yes or no
    1479      * @return void
    1480      */
    1481     public function add_comment_vote_meta( $comment_id, $vote ) {
    1482         if ( 'yes' == $vote ) {
    1483             $value = get_comment_meta( $comment_id, 'edd_review_vote_yes', true );
    1484
    1485             if ( ! empty( $value ) && ! $value > 0 ) {
    1486                 $number = 1;
    1487                 add_comment_meta( $comment_id, 'edd_review_vote_yes', $number );
    1488             } else {
    1489                 $number = $value + 1;
    1490                 update_comment_meta( $comment_id, 'edd_review_vote_yes', $number );
    1491             }
    1492
    1493         } elseif ( 'no' == $vote ) {
    1494             $value = get_comment_meta( $comment_id, 'edd_review_vote_no', true );
    1495
    1496             if ( ! empty( $value ) && ! $value > 0 ) {
    1497                 $number = 1;
    1498                 add_comment_meta( $comment_id, 'edd_review_vote_yes', $number );
    1499             } else {
    1500                 $number = $value + 1;
    1501                 update_comment_meta( $comment_id, 'edd_review_vote_no', $number );
    1502             }
    1503
    1504             add_comment_meta( $comment_id, 'edd_review_vote_no', $number );
    1505         }
    1506     }
    1507
    1508     /**
    1509      * Checks whether an AJAX request has been sent
    1510      *
    1511      * @since 1.0
    1512      * @access public
    1513      * @return bool Whether or not AJAX $_GET header has been passed
    1514      */
    1515     public function is_ajax_request() {
    1516         return (bool) ( isset( $_POST['edd_reviews_ajax'] ) && ! empty( $_REQUEST['action'] ) );
    1517     }
    1518
    1519     /**
    1520      * Process Voting for the Reviews via AJAX
    1521      *
    1522      * Processes the voting button appended to the bottom of each review by adding
    1523      * or updating the comment meta via AJAX.
    1524      *
    1525      * @since 1.0
    1526      * @access public
    1527      *
    1528      * @uses EDD_Reviews::is_ajax_request()
    1529      *
    1530      * @return mixed returns if AJAX check fails
    1531      */
    1532     public function process_ajax_vote() {
    1533         // Bail if an AJAX request isn't sent
    1534         if ( ! $this->is_ajax_request() )
    1535             return;
    1536
    1537         check_ajax_referer( 'edd_reviews_voting_nonce', 'security', true );
    1538
    1539         if ( ! isset( $_POST['review_vote'] ) )
    1540             return;
    1541
    1542         if ( isset( $_POST['review_vote'] ) && isset( $_POST['comment_id'] ) && is_numeric( $_POST['comment_id'] ) ) {
    1543             $this->add_comment_vote_meta( $_POST['comment_id'], $_POST['review_vote'] );
    1544
    1545             EDD()->session->set( 'wordpress_edd_reviews_voted_' . $_POST['comment_id'], 'yes' );
    1546
    1547             echo 'success';
    1548         } else {
    1549             echo 'fail';
    1550         }
    1551
    1552         die();
    1553     }
    1554
    1555     /**
    1556      * Register Dashboard Widgets
    1557      *
    1558      * @since 1.0
    1559      * @access public
    1560      * @return void
    1561      */
    1562     public function dashboard_widgets() {
    1563         if ( is_blog_admin() && current_user_can( 'moderate_comments' ) ) {
    1564             $recent_reviews_title = apply_filters( 'edd_reviews_recent_reviews_dashboard_widget_title', __( 'Easy Digital Downloads Recent Reviews', 'edd-reviews' ) );
    1565             wp_add_dashboard_widget(
    1566                 'edd_reviews_dashboard_recent_reviews',
    1567                 $recent_reviews_title,
    1568                 array( 'EDD_Reviews', 'render_dashboard_widget' )
    1569             );
    1570         }
    1571     }
    1572
    1573     /**
    1574      * Render the Dashboard Widget
    1575      *
    1576      * @since 1.0
    1577      * @access public
    1578      * @global object $wpdb Used to query the database using the WordPress Database API
    1579      * @return void
    1580      */
    1581     public function render_dashboard_widget() {
    1582         global $wpdb;
    1583
    1584         $reviews = $wpdb->get_results(
    1585             "
    1586             SELECT *, SUBSTRING( comment_content, 1, 100 ) AS excerpt
    1587             FROM {$wpdb->comments}
    1588             LEFT JOIN $wpdb->posts ON {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID
    1589             WHERE comment_approved = '1'
    1590             AND post_password = ''
    1591             AND post_type = 'download'
    1592             AND comment_type = ''
    1593             ORDER BY comment_date_gmt DESC
    1594             LIMIT 5
    1595             "
    1596         );
    1597
    1598         if ( $reviews ) {
    1599             echo '<div id="edd-reviews-list">';
    1600
    1601             foreach ( $reviews as $review ) {
    1602                 $output = '<div id="review-'. $review->ID .'">';
    1603
    1604                 $output .= ( get_option( 'show_avatars' ) ) ? get_avatar( $review->comment_author_email, 50 ) : '';
    1605
    1606                 $output .= '<div class="edd-dashboard-review-wrap">';
    1607
    1608                 $rating = get_comment_meta( $review->comment_ID, 'edd_rating', true );
    1609
    1610                 $output .= '<h4 class="meta"><a href="' . get_permalink( $review->ID ) . '#comment-' . absint( $review->comment_ID ) .'">' . esc_html__( get_comment_meta( $review->comment_ID, 'edd_review_title', true ) ) . '</a></h4>';
    1611                 $output .= '<div class="edd_reviews_rating_box"><div class="edd_star_rating" style="width: ' . 19 * $rating  . 'px"></div></div>';
    1612                 $output .= '<p>' . __( 'By', 'edd-reviews' ) . ' ' . esc_html( $review->comment_author ) . ', ' . get_comment_date( get_option( 'date_format()' ), $review->comment_ID ) . '</p>';
    1613                 $output .= '<blockquote>' . wp_kses_data( $review->excerpt ) . ' ...</blockquote></div>';
    1614                 $output .= '</div>';
    1615
    1616                 echo $output;
    1617             }
    1618
    1619             echo '</div>';
    1620         } else {
    1621             echo '<p>' . __( 'There are no reviews yet.', 'edd-reviews' ) . '</p>';
    1622         }
    1623     }
    1624
    1625     /**
    1626      * Add the Meta Boxes
    1627      *
    1628      * @since 1.0
    1629      * @access public
    1630      * @return void
    1631      */
    1632     public function add_meta_boxes() {
    1633         add_meta_box( 'edd_reviews_review_meta_box', __( 'Review Information', 'edd-reviews' ), array( $this, 'review_meta_box' ), 'comment', 'normal', 'high' );
    1634     }
    1635
    1636     /**
    1637      * Render the Review Meta Box
    1638      *
    1639      * Outputs the Review Information meta box on the Edit Comment screen. This
    1640      * meta box displays the review title and the star rating.  It also allows
    1641      * for it to be edited.
    1642      *
    1643      * @since 1.0
    1644      * @access public
    1645      * @param object $comment Comment information
    1646      * @return void
    1647      */
    1648     public function review_meta_box( $comment ) {
    1649         ?>
    1650         <table class="form-table editcomment">
    1651             <tbody>
    1652                 <tr valign="top">
    1653                     <td class="first"><?php _e( 'Review Title:', 'edd-reviews' ); ?></td>
    1654                     <td><input type="text" class="widefat" id="edd_reviews_review_title" name="edd_reviews_review_title" value="<?php echo get_comment_meta( $comment->comment_ID, 'edd_review_title', true ); ?>" /></td>
    1655                 </tr>
    1656                 <tr valign="top">
    1657                     <td class="first"><?php _e( 'Rating:', 'edd-reviews' ); ?></td>
    1658                     <td><input type="text" class="widefat" id="edd_reviews_rating" name="edd_reviews_rating" value="<?php echo get_comment_meta( $comment->comment_ID, 'edd_rating', true ); ?>" /></td>
    1659                 </tr>
    1660             </tbody>
    1661         </table>
    1662         <?php
    1663     }
    1664
    1665     /**
    1666      * Save the Meta Data from the Meta Box on the Edit Comment Screen
    1667      *
    1668      * @since 1.0
    1669      * @access public
    1670      * @param int $comment_id Comment ID
    1671      * @return void
    1672      */
    1673     public function update_review_meta( $comment_id ) {
    1674         $review_title = sanitize_text_field( $_POST['edd_reviews_review_title'] );
    1675         $rating       = intval( $_POST['edd_reviews_rating'] );
    1676
    1677         if ( empty ( $review_title ) ) {
    1678             wp_die( sprintf( __( '%sError%s: Please add a review title.', 'edd-reviews' ), '<strong>', '</strong>' ), __( 'Error', 'edd-reviews' ), array( 'back_link' => true ) );
    1679         }
    1680
    1681         if ( ! ( $rating > 0 && $rating <= 5 ) ) {
    1682             wp_die( sprintf( __( '%sError%s: Please add a valid rating between 1 and 5.', 'edd-reviews' ), '<strong>', '</strong>' ), __( 'Error', 'edd-reviews' ), array( 'back_link' => true ) );
    1683         }
    1684
    1685         update_comment_meta( $comment_id, 'edd_review_title', $review_title );
    1686         update_comment_meta( $comment_id, 'edd_rating',       $rating       );
    1687     }
    1688
    1689     /**
    1690      * Register API Query Mode
    1691      *
    1692      * @since 1.0
    1693      * @access public
    1694      * @param array $modes Whitelisted query modes
    1695      * @return array $modes Updated list of query modes
    1696      */
    1697     public function register_api_mode( $modes ) {
    1698         $modes[] = 'reviews';
    1699         return $modes;
    1700     }
    1701
    1702     /**
    1703      * Add 'review_id' Query Var into WordPress Whitelisted Query Vars
    1704      *
    1705      * @since 1.0
    1706      * @access public
    1707      * @param array $vars Array of WordPress allowed query vars
    1708      * @return array $vars Updated array of WordPress query vars to allow
    1709      *  Reviews to integrate with the EDD API
    1710      */
    1711     public function query_vars( $vars ) {
    1712         $vars[] = 'review_id';
    1713         return $vars;
    1714     }
    1715
    1716     /**
    1717      * Processes the Data Outputted when an API Call for Reviews is Triggered
    1718      *
    1719      * @since 1.0
    1720      * @access public
    1721      * @global object $wpdb Used to query the database using the WordPress
    1722      *   Database API
    1723      * @global object $wp_query Used to access the query vars
    1724      *
    1725      * @param array $data Array to hold the output
    1726      * @param array $query_mode Query mode (i.e. reviews)
    1727      * @param object $api_object EDD_API Object
    1728      *
    1729      * @return array $data All the data for when the API call for reviews is fired
    1730      */
    1731     public function api_output( $data, $query_mode, $api_object ) {
    1732         global $wpdb, $wp_query;
    1733
    1734         // Bail if the query mode isn't reviews
    1735         if ( 'reviews' !== $query_mode )
    1736             return $data;
    1737
    1738         // Get the review_id query var
    1739         $review_id = isset( $wp_query->query_vars['review_id'] ) ? $wp_query->query_vars['review_id'] : null;
    1740
    1741         if ( $review_id ) {
    1742             // Get the review from the database
    1743             $review = $wpdb->get_results(
    1744                 $wpdb->prepare(
    1745                     "
    1746                     SELECT *
    1747                     FROM {$wpdb->comments}
    1748                     INNER JOIN {$wpdb->posts} ON {$wpdb->comments}.comment_post_ID = {$wpdb->posts}.ID
    1749                     WHERE comment_ID = '%d'
    1750                     LIMIT 1
    1751                     ",
    1752                     $review_id
    1753                 )
    1754             );
    1755
    1756             if ( $review ) :
    1757                 $data['reviews']['id']             = $review[0]->comment_ID;
    1758                 $data['reviews']['title']          = get_comment_meta( $review[0]->comment_ID, 'edd_review_title', true );
    1759                 $data['reviews']['download_id']    = $review[0]->comment_post_ID;
    1760                 $data['reviews']['download_title'] = $review[0]->post_title;
    1761                 $data['reviews']['rating']         = get_comment_meta( $review[0]->comment_ID, 'edd_rating', true );
    1762                 $data['reviews']['author']         = $review[0]->comment_author;
    1763                 $data['reviews']['email']          = $review[0]->comment_author_email;
    1764                 $data['reviews']['IP']             = $review[0]->comment_author_IP;
    1765                 $data['reviews']['date']           = $review[0]->comment_date;
    1766                 $data['reviews']['date_gmt']       = $review[0]->comment_date_gmt;
    1767                 $data['reviews']['content']        = $review[0]->comment_content;
    1768                 $data['reviews']['status']         = $review[0]->comment_approved;
    1769                 $data['reviews']['user_id']        = $review[0]->user_id;
    1770             else :
    1771                 $error['error'] = sprintf( __( 'Review %s not found!', 'edd-reviews' ), $review_id );
    1772                 return $error;
    1773             endif;
    1774         } else {
    1775             // Get total reviews count from the database
    1776             $total_reviews = $wpdb->get_var(
    1777                 $wpdb->prepare(
    1778                     "
    1779                     SELECT COUNT(meta_value)
    1780                     FROM {$wpdb->commentmeta}
    1781                     LEFT JOIN {$wpdb->comments} ON {$wpdb->commentmeta}.comment_id = {$wpdb->comments}.comment_ID
    1782                     WHERE meta_key = 'edd_rating'
    1783                     AND comment_approved = '1'
    1784                     AND meta_value > 0
    1785                     "
    1786                 )
    1787             );
    1788
    1789             /** Total Reviews */
    1790             $data['reviews']['total'] = $total_reviews;
    1791
    1792             /** Most Recent Review */
    1793             $most_recent_review = get_comments( array( 'post_type' => 'download', 'number' => 1 ) );
    1794
    1795             $data['reviews']['most_recent']['id']             = $most_recent_review[0]->comment_ID;
    1796             $data['reviews']['most_recent']['title']          = get_comment_meta( $most_recent_review[0]->comment_ID, 'edd_review_title', true );
    1797             $data['reviews']['most_recent']['download_id']    = $most_recent_review[0]->comment_post_ID;
    1798             $data['reviews']['most_recent']['download_title'] = get_the_title( $most_recent_review[0]->comment_post_ID );
    1799             $data['reviews']['most_recent']['rating']         = get_comment_meta( $most_recent_review[0]->comment_ID, 'edd_rating', true );
    1800             $data['reviews']['most_recent']['author']         = $most_recent_review[0]->comment_author;
    1801             $data['reviews']['most_recent']['email']          = $most_recent_review[0]->comment_author_email;
    1802             $data['reviews']['most_recent']['IP']             = $most_recent_review[0]->comment_author_IP;
    1803             $data['reviews']['most_recent']['date']           = $most_recent_review[0]->comment_date;
    1804             $data['reviews']['most_recent']['date_gmt']       = $most_recent_review[0]->comment_date_gmt;
    1805             $data['reviews']['most_recent']['content']        = $most_recent_review[0]->comment_content;
    1806             $data['reviews']['most_recent']['status']         = $most_recent_review[0]->comment_approved;
    1807             $data['reviews']['most_recent']['user_id']        = $most_recent_review[0]->user_id;
    1808         }
    1809
    1810         // Allow extensions to add to the data outpt
    1811         $data = apply_filters( 'edd_reviews_api_output_data', $data );
    1812
    1813         return $data;
    1814     }
    1815
    1816     /**
    1817      * Is the User Allowed to See TinyMCE?
    1818      *
    1819      * @since 1.0
    1820      * @access public
    1821      * @return bool Whether the user can see TinyMCE or not
    1822      */
    1823     public function user_can_see_tinymce() {
    1824         return ( current_user_can( 'edit_posts' ) && current_user_can( 'edit_pages' ) );
    1825     }
    1826
    1827     /**
    1828      * Add TinyMCE Button
    1829      *
    1830      * Adds a button to the TinyMCE editor to easily embed reviews into posts
    1831      * and pages
    1832      *
    1833      * @since 1.0
    1834      * @access public
    1835      *
    1836      * @uses EDD_Reviews::user_can_see_tinymce()
    1837      *
    1838      * @return void
    1839      */
    1840     public function tinymce_button() {
    1841         if ( $this->user_can_see_tinymce() && 'true' == get_user_option( 'rich_editing' ) ) {
    1842             add_filter( 'mce_external_plugins', array( $this, 'add_plugin'      ) );
    1843             add_filter( 'mce_buttons',          array( $this, 'register_button' ) );
    1844         }
    1845     }
    1846
    1847     /**
    1848      * Register TinyMCE Plugin
    1849      *
    1850      * @since 1.0
    1851      * @access public
    1852      * @param array $plugin_array Array of TinyMCE Plugins
    1853      * @return array $plugin_array Array of TinyMCE Plugins
    1854      */
    1855     public function add_plugin( $plugin_array ) {
    1856         $plugin_array['edd_reviews'] = $this->assets_url . 'js/edd-reviews-admin.js';
    1857         return $plugin_array;
    1858     }
    1859
    1860     /**
    1861      * Register TinyMCE Button
    1862      *
    1863      * @since 1.0
    1864      * @access public
    1865      * @param array $buttons Array of TinyMCE Button
    1866      * @return array $buttons Array of TinyMCE Button
    1867      */
    1868     public function register_button( $buttons ) {
    1869         array_push( $buttons, '|', 'edd_reviews' );
    1870         return $buttons;
    1871     }
    1872
    1873     /**
    1874      * Process the TinyMCE Modal Dialog
    1875      *
    1876      * @since 1.0
    1877      * @access public
    1878      * @global object $wpdb Used to query the database using the WordPress
    1879      *   Database API
    1880      * @return void
    1881      */
    1882     public function process_mce_dialog() {
    1883         if ( is_user_logged_in() && isset( $_GET['edd_reviews_mce_dialog'] ) && 'true' == $_GET['edd_reviews_mce_dialog'] ) {
    1884             global $wpdb;
    1885
    1886             $reviews = $wpdb->get_results( $wpdb->prepare( "SELECT meta_value, comment_id FROM {$wpdb->commentmeta} WHERE meta_key = %s", 'edd_review_title' ) );
    1887             ?>
    1888             <!DOCTYPE html>
    1889             <html <?php language_attributes(); ?>>
    1890             <head>
    1891                 <meta charset="utf-8" />
    1892                 <title><?php _e( 'Embed Review', 'edd-reviews' ); ?></title>
    1893                 <script type="text/javascript" src="<?php echo includes_url(); ?>/js/tinymce/tiny_mce_popup.js"></script>
    1894                 <script type="text/javascript">
    1895                 var edd_reviews_dialog = {
    1896                     local_ed: 'ed',
    1897
    1898                     init: function (ed) {
    1899                         edd_reviews_dialog.local_ed = ed;
    1900                         tinyMCEPopup.resizeToInnerSize();
    1901                     },
    1902
    1903                     insert: function insertButton(ed) {
    1904                         tinyMCEPopup.execCommand('mceRemoveNode', false, null);
    1905
    1906                         var elem     = document.getElementById('edd_reviews_shortcode_dialog'),
    1907                             selected = elem.options[elem.selectedIndex].value;
    1908                             output   = '';
    1909
    1910                         output = '[review id="' + selected + '"]';
    1911                         tinyMCEPopup.execCommand('mceReplaceContent', false, output);
    1912                         tinyMCEPopup.close();
    1913                     }
    1914                 }
    1915
    1916                 tinyMCEPopup.onInit.add(edd_reviews_dialog.init, edd_reviews_dialog);
    1917                 </script>
    1918             </head>
    1919             <body>
    1920             <?php
    1921             if ( $reviews ) {
    1922                 echo '<h2>' . __( 'Select a Review to Embed' ) . '</h2>';
    1923                 echo '<p><select id="edd_reviews_shortcode_dialog" name="edd_reviews_shortcode_dialog">';
    1924
    1925                 foreach ( $reviews as $review ) :
    1926                     echo '<option value="' . $review->comment_id . '">' . esc_html( $review->meta_value ) . '</option>';
    1927                 endforeach;
    1928
    1929                 echo '</select></p>';
    1930
    1931                 echo '<p><a href="javascript:edd_reviews_dialog.insert(edd_reviews_dialog.local_ed)" id="insert" style="display: block; line-height: 24px;">' . __( 'Embed Review', 'edd-reviews' ) . '</a></p>';
    1932             } else {
    1933                 echo '<h2>' . __( 'No Reviews Have Been Created Yet', 'edd-reviews' ) . '</h2>';
    1934             }
    1935             ?>
    1936             </body>
    1937             </html>
    1938             <?php
    1939             die();
    1940         }
    1941     }
    1942
    1943     /**
    1944      * Plugin Action Links
    1945      *
    1946      * This function adds a link to the plugin action links bar on the Plugins
    1947      * Administrati on page to for the API documentation and to Easy Digital Downloads
    1948      * Support Forum.
    1949      *
    1950      * @since 1.0
    1951      * @access public
    1952      * @param array $links Plugin Action Links
    1953      * @return array $links Plugin Action Links
    1954      */
    1955     public function plugin_links( $links, $file ) {
    1956         static $this_plugin;
    1957
    1958         if ( ! $this_plugin ) {
    1959             $this_plugin = $this->basename;
    1960         }
    1961
    1962         if ( $file == $this_plugin ) {
    1963             $api_link = '<a href="' . $this->plugin_url . 'api-docs/index.html' . '">' . __( 'API Documentation', 'edd-reviews' ) . '</a>';
    1964             array_unshift( $links , $api_link );
    1965         }
    1966
    1967         return $links;
    1968     }
    1969
    1970     /**
    1971      * Loads the Updater
    1972      *
    1973      * Instantiates the Software Licensing Plugin Updater and passes the plugin
    1974      * data to the class.
    1975      *
    1976      * @since 1.0
    1977      * @access public
    1978      * @return void
    1979      */
    1980     public function updater() {
    1981         $license = new EDD_License( $this->file, 'Product Reviews', $this->version, 'Sunny Ratilal', 'edd_reviews_licensing_license_key' );
    1982     }
    1983 }
    1984
    1985 /**
    1986  * The callback for the reviews called by wp_list_comments().
    1987  *
    1988  * This must be placed outside of the class because wp_list_comments()
    1989  * doesn't allow a function to be passed as a callback within a class,
    1990  * it must be a separate function.
    1991  *
    1992  * @since 1.0
    1993  *
    1994  * @param object $comment
    1995  * @param array  $args
    1996  * @param string $depth
    1997  * @return void
    1998  */
    1999 function edd_reviews_callback( $comment, $args, $depth ) {
    2000     edd_reviews()->review( $comment, $args, $depth );
    2001 }
    2002 /**
    2003  * Loads a single instance of EDD Reviews
    2004  *
    2005  * This follows the PHP singleton design pattern.
    2006  *
    2007  * Use this function like you would a global variable, except without needing
    2008  * to declare the global.
    2009  *
    2010  * @example <?php $edd_reviews = edd_reviews(); ?>
    2011  *
    2012  * @since 1.0
    2013  *
    2014  * @see EDD_Reviews::get_instance()
    2015  *
    2016  * @return object Returns an instance of the EDD_Reviews class
    2017  */
    2018 function edd_reviews() {
    2019     return EDD_Reviews::get_instance();
    2020 }
    2021
    2022 /**
    2023  * Loads plugin after all the others have loaded and have registered their
    2024  * hooks and filters
    2025  */
    2026 add_action( 'plugins_loaded', 'edd_reviews', apply_filters( 'edd_reviews_action_priority', 10 ) );
    2027
    2028 endif;
    2029