Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vote from comments: fix a notice #106

Open
wants to merge 32 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
b1f80f1
Remove Translation link
lesterchan Sep 18, 2017
0afe165
code cleanup
Jul 5, 2017
3b6b651
use data-* element to pass textual data
Jul 6, 2017
d5495ca
split process_ratings() so that it could be reused from Ajax but also
Jul 6, 2017
526abab
bind voting to comment submission.
Jul 6, 2017
8f555c7
comment+vote: show the vote in a new column in the comment-manage table
Jul 6, 2017
f93d6b1
comment+vote: don't forcefully output the error (useful if call from …
Jul 8, 2017
eaa8493
comment+vote: use error messages that process_ratings() send by ref
Jul 8, 2017
5947bff
comment+vote: REST API: added a handler for comment+vote from the WP …
Jul 8, 2017
1c9b932
comment+vote: moved code into its own PHP file
Jul 8, 2017
68e502f
captcha: added recaptcha support inherited from contact-form-7
Jun 29, 2017
f9037a5
scoring: added a bayesian average calculation helper. Avoid little-ev…
Jun 29, 2017
7195124
bugfix: removed a spurious return $options
Jul 30, 2017
d92ba69
return NULL for empty scores
Jul 30, 2017
a4ecde5
bugfix: regression from 5225aaec: was showing half-stars instead of t…
Aug 9, 2017
4610207
bugfix: regression from 5225aaec: spurious quote introduced when swit…
Aug 9, 2017
f7fc777
5225aaec followup: moved more javascript stuff outside of templates u…
Aug 9, 2017
c50e1a1
According to
Aug 30, 2017
4deb2f9
vote from comments: fix a warning (insert_comment hooks may come with…
Sep 16, 2017
ee1405d
coding standards
Sep 18, 2017
5957b21
voting from REST: bugfix
Sep 18, 2017
7c12329
rest: do not setcookie() if voting was done through a REST request
Sep 18, 2017
468847f
process_ratings: move the HTML return + cookie away from the internal…
Sep 18, 2017
75cc65c
simple-quotes around strings
Sep 19, 2017
417c41d
rest: REST handler (here an update-callback), must either return a va…
Sep 19, 2017
495a8df
reduce further js and php templates complexities
Oct 25, 2017
1131cae
spurious quote
Oct 25, 2017
c0c1272
Much more flexible way to hook into the validation process (at the pr…
Nov 2, 2017
9a38a3f
Don't hardcode a comment "Rate @" suffixe.
Nov 10, 2017
70fe66d
make js-based voting code more reusable
Jan 25, 2018
91e418f
js-based widget: use native querySelector() function rather than jQuery
Jan 25, 2018
024fffd
don't force rating script inside the footer. Recompress new code
Feb 10, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ Adds an AJAX rating system for your WordPress site's content.
### Development
[https://github.com/lesterchan/wp-postratings](https://github.com/lesterchan/wp-postratings "https://github.com/lesterchan/wp-postratings")

### Translations
[http://dev.wp-plugins.org/browser/wp-postratings/i18n/](http://dev.wp-plugins.org/browser/wp-postratings/i18n/ "http://dev.wp-plugins.org/browser/wp-postratings/i18n/")

### Credits
* Plugin icon by [Freepik](http://www.freepik.com) from [Flaticon](http://www.flaticon.com)
* Icons courtesy of [FamFamFam](http://www.famfamfam.com/ "FamFamFam") and [Everaldo](http://www.everaldo.com "Everaldo")
Expand Down
30 changes: 16 additions & 14 deletions includes/postratings-activation.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,22 @@ function ratings_activate() {
$charset_collate = $wpdb->get_charset_collate();

// Create Post Ratings Table
$create_sql = "CREATE TABLE $wpdb->ratings (".
"rating_id INT(11) NOT NULL auto_increment,".
"rating_postid INT(11) NOT NULL ,".
"rating_posttitle TEXT NOT NULL,".
"rating_rating INT(2) NOT NULL ,".
"rating_timestamp VARCHAR(15) NOT NULL ,".
"rating_ip VARCHAR(40) NOT NULL ,".
"rating_host VARCHAR(200) NOT NULL,".
"rating_username VARCHAR(50) NOT NULL,".
"rating_userid int(10) NOT NULL default '0',".
"PRIMARY KEY (rating_id),".
"KEY rating_userid (rating_userid),".
"KEY rating_postid_ip (rating_postid, rating_ip)) ".
"$charset_collate;";
$create_sql = <<<EOF
CREATE TABLE {$wpdb->ratings} (
rating_id INT(11) NOT NULL auto_increment,
rating_postid INT(11) NOT NULL,
rating_posttitle TEXT NOT NULL,
rating_rating INT(2) NOT NULL,
rating_timestamp VARCHAR(15) NOT NULL,
rating_ip VARCHAR(40) NOT NULL,
rating_host VARCHAR(200) NOT NULL,
rating_username VARCHAR(50) NOT NULL,
rating_userid int(10) NOT NULL default '0',
PRIMARY KEY (rating_id),
KEY rating_userid (rating_userid),
KEY rating_postid_ip (rating_postid, rating_ip))
{$charset_collate};
EOF;

require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
dbDelta( $create_sql );
Expand Down
83 changes: 83 additions & 0 deletions includes/postratings-bayesian-score.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php

function weighted_score($ns) {
$x = 0;
$i = 0; // start at 0: 1 star does not count anything in the mean
foreach ($ns as $v) {
$x += $i * $v;
// increase the weight of the next star
$i++;
}
return $x / array_sum($ns);
}

/*
User votes impact (aka "confidence level")

95%: 11(1.960)/0.5-5.5 = 38 votes
90%: 11(1.645)/0.5-5.5 = 31 votes
80%: 11(1.282)/0.5-5.5 = 23 votes
70%: 11(1.036)/0.5-5.5 = 17 votes
60%: 11(0.842)/0.5-5.5 = 13 votes
50%: 11(0.674)/0.5-5.5 = 9 votes
*/
define('NUM_VOTES_IMPACT_60', 0.842);
define('NUM_VOTES_IMPACT_80', 1.282);
define('NUM_VOTES_IMPACT_90', 1.645);
define('NUM_VOTES_IMPACT_95', 1.960);

// https://stackoverflow.com/a/40958702
// http://www.evanmiller.org/ranking-items-with-star-ratings.html
function bayesian_score($ns, $confidence) {
if (empty(array_filter($ns))) return NULL;

$ns = array_reverse($ns);
$N = array_sum($ns);
$K = count($ns);
$s = range($K,1,-1);
$s2 = array_map(function($e) {return pow($e, 2);}, $s);
$z = $confidence;
if (! function_exists('f')) {
function f($s, $ns) {
$N = array_sum($ns);
$K = count($ns);
$asum = [];
foreach(array_combine($s, $ns) as $sk => $nk) {
$asum[] = $sk * ($nk+1);
}
return array_sum($asum) / ($N+$K);
}
}

$fsns = f($s, $ns);
return $fsns - $z * sqrt( ( f($s2, $ns) - pow($fsns, 2)) / ($N+$K+1) );
}

function get_post_score_data($post_id) {
global $wpdb;

$def = [];
$f = $wpdb->get_results( $wpdb->prepare( "SELECT rating_rating as r, count(1) as c FROM {$wpdb->ratings} WHERE rating_postid = %d GROUP BY rating_rating", $post_id ));
foreach($f as $data) $def[$data->r] = (int)$data->c;
$def += array_fill( 1, intval( get_option( 'postratings_max', 5 ) ), 0 );
ksort( $def );

return $def;
}

function get_bayesian_score($post_id, $confidence) {
return bayesian_score( get_post_score_data( $post_id ), $confidence );
}


/* ToDo: how to produce a dynamically generated field (with no stored data at all?)
If it's not possible, we may consider (optionnally) storing the alternative average
inside the DB too */
/*
function get_postrating_bayesian_score($metadata, $object_id, $meta_key, $single) {
if ($meta_key && $meta_key == 'bayesian_score') {
return get_bayesian_score($object_id, floatval(constant('NUM_VOTES_IMPACT_' . intval(get_option('bayesian_votes_impact', 90)))) ? : NUM_VOTES_IMPACT_90);
}
}
add_filter('get_post_metadata', 'get_postrating_bayesian_score', 10, 4);
*/
43 changes: 43 additions & 0 deletions includes/postratings-captcha.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

function recaptcha_is_op() {
if (! is_plugin_active('contact-form-7/wp-contact-form-7.php')) {
return false;
}

$recaptcha = WPCF7_RECAPTCHA::get_instance();
if ( ! $recaptcha->is_active() ) {
return false;
}

return $recaptcha->get_sitekey();
}

function recaptcha_is_enabled() {
if (! ($opt = get_option('postratings_options')) ) {
return false;
}
if (! isset($opt['recaptcha']) || ! $opt['recaptcha']) {
return false;
}

return true;
}

function is_human() {
$recaptcha = WPCF7_RECAPTCHA::get_instance();
$response_token = wpcf7_recaptcha_response();
// return true for mutants and humans
return $recaptcha->verify( $response_token );
}


add_action( 'wp_enqueue_scripts', 'google_recaptcha' );
function google_recaptcha() {
if ( ! recaptcha_is_enabled() ) return;
if ( ! recaptcha_is_op() ) return;
wp_register_script( 'google-recaptcha',
add_query_arg( [ 'onload' => 'recaptchaCallback', 'render' => 'explicit' ],
'https://www.google.com/recaptcha/api.js' ),
[], '2.0', true );
}
97 changes: 97 additions & 0 deletions includes/postratings-comment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

add_filter( 'wp_insert_comment', 'process_ratings_from_comment' );
function process_ratings_from_comment($comment_id) {
if ( !isset($_POST['comment_post_ID']) || !isset($_POST['wp_postrating_form_value_' . $post_id]) ) {
return;
}

$post_id = (int)$_POST['comment_post_ID'];
$rate = (int)$_POST['wp_postrating_form_value_' . $post_id];
if (! $post_id || ! $rate) {
// ignored (could be simply a second comment while missing a second vote)
return;
}

$allow_to_vote_with_comment = (int)get_option('postratings_onlyifcomment');
if (! $allow_to_vote_with_comment) {
return;
}

$rate_id = 0; $last_error = '';
process_ratings($post_id, $rate, $rate_id, $last_error);
if ($rate_id) {
update_comment_meta( $comment_id, 'postratings_id', $rate_id );
}
// if $last_error: ToDo
}

add_filter( 'comments_array', 'comments_load_rate' );
function comments_load_rate($comments) {
if( ! count( $comments ) ) return;

global $wpdb;
foreach( $comments as $comment ) {
$rate_id = get_comment_meta($comment->comment_ID, 'postratings_id', true);
if (intval($rate_id)) {
$rate = $wpdb->get_var( $wpdb->prepare( "SELECT rating_rating FROM {$wpdb->ratings} WHERE rating_id = %d", intval($rate_id)) );
if ($rate) {
$comment->postrating_rate = $rate;
}
}
}

return $comments;
}

add_filter( 'manage_edit-comments_columns', 'comment_has_vote' );
function comment_has_vote( $columns ) {
$columns['comment-vote'] = __( 'Vote', 'wp-postratings' );
return $columns;
}


add_filter( 'manage_comments_custom_column', 'recent_comment_has_vote', 20, 2 );
function recent_comment_has_vote( $column_name, $comment_id ) {
if( 'comment-vote' != strtolower( $column_name ) ) return;
if ( ( $rate_id = get_comment_meta( $comment_id, 'postratings_id', true ) ) ) {
if (intval($rate_id)) {
global $wpdb;
$rate = $wpdb->get_var( $wpdb->prepare( "SELECT rating_rating FROM {$wpdb->ratings} WHERE rating_id = %d", intval($rate_id)) );
if ($rate) {
printf(__('Rated at %d', 'wp-postratings'), $rate);
}
}
}
}


// REST API specific
// to be called from the "update_callback" of register_rest_field()
function process_ratings_from_rest_API( WP_Comment $comment, $rate ) {
// see update_additional_fields_for_object()
if (! $comment->comment_post_ID || ! $rate) {
return new WP_Error( 'rest_comment_vote_invalid', 'Voted content not found.', array( 'status' => 500 ) );
}

$allow_to_vote_with_comment = (int)get_option('postratings_onlyifcomment');
if (! $allow_to_vote_with_comment) {
return new WP_Error( 'rest_comment_vote_invalid', 'Vote bound to comment are not allowed.', array( 'status' => 400 ) );
}

$rate_id = 0; $last_error = '';
process_ratings($comment->comment_post_ID, $rate, $rate_id, $last_error);
if ( $rate_id ) {
$updated = update_comment_meta( $comment->comment_ID, 'postratings_id', $rate_id );
return $updated;
}

if ( $last_error ) {
return new WP_Error( 'rest_comment_vote_invalid', $last_error, array( 'status' => 403 ) );
}
return new WP_Error( 'rest_comment_vote_invalid', 'Unknown error.', array( 'status' => 500 ) );
}
28 changes: 10 additions & 18 deletions includes/postratings-scripts.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,31 +42,23 @@ function ratings_scripts() {
wp_enqueue_style( 'wp-postratings-rtl', plugins_url( 'wp-postratings/css/postratings-css-rtl.css' ), false, WP_POSTRATINGS_VERSION, 'all' );
}
}
$postratings_max = intval( get_option( 'postratings_max' ) );
$postratings_custom = intval( get_option( 'postratings_customrating' ) );

wp_enqueue_script('wp-postratings', plugins_url('wp-postratings/js/postratings-js.dev.js'), array('jquery'), WP_POSTRATINGS_VERSION);

// these are static JS parameters
$postratings_ajax_style = get_option( 'postratings_ajax_style' );
$postratings_image = get_option( 'postratings_image' );
$postratings_plugins_url = plugins_url( 'wp-postratings' );
$postratings_javascript = '';
if($postratings_custom) {
for($i = 1; $i <= $postratings_max; $i++) {
$postratings_javascript .= 'var ratings_' . $i . '_mouseover_image=new Image();ratings_' . $i . '_mouseover_image.src="' . $postratings_plugins_url . '/images/' . $postratings_image . '/rating_' . $i . '_over.' . RATINGS_IMG_EXT . '";';
}
} else {
$postratings_javascript = 'var ratings_mouseover_image=new Image();ratings_mouseover_image.src="' . $postratings_plugins_url . '/images/' . $postratings_image . '/rating_over.' . RATINGS_IMG_EXT . '";';
}
wp_enqueue_script('wp-postratings', plugins_url('wp-postratings/js/postratings-js.js'), array('jquery'), WP_POSTRATINGS_VERSION, true);
wp_localize_script('wp-postratings', 'ratingsL10n', array(
'plugin_url' => $postratings_plugins_url,
'plugin_url' => plugins_url( 'wp-postratings' ),
'ajax_url' => admin_url('admin-ajax.php'),
'text_wait' => __('Please rate only 1 item at a time.', 'wp-postratings'),
'image' => $postratings_image,
'image' => get_option( 'postratings_image' ),
'image_ext' => RATINGS_IMG_EXT,
'max' => $postratings_max,
'max' => intval( get_option( 'postratings_max' ) ),
'show_loading' => intval($postratings_ajax_style['loading']),
'show_fading' => intval($postratings_ajax_style['fading']),
'custom' => $postratings_custom,
'l10n_print_after' => $postratings_javascript
'custom' => boolval( get_option( 'postratings_customrating', false ) ),
'captcha_sitekey' => recaptcha_is_enabled() ? recaptcha_is_op() : false,
'rtl' => intval( is_rtl() )
));
}

Expand Down
44 changes: 44 additions & 0 deletions includes/postratings-tests.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

// wp eval "include('$PWD/includes/postratings-tests.php'); get_post_rating_detail();"
// output results comparing the above two rating functions
function postratings_score_tests($confidence = NUM_VOTES_IMPACT_90, $arr = null) {
$tests = array(
[0,0,0,20,0], // 20x 4 stars
[0,0,20,0,0],
[10,0,0,0,10],
[0,0,0,10,0],
[0,0,25,15,4],
[0,0,10,0,0], // 10x 3 start
[0,0,0,0,1], // 5 stars
[0,0,30,0,0],
[0,0,0,0,10],
);

printf("## Confidence = %.3f\n", $confidence);
printf("% 7sstars% 8s \t| weighted \t bayesian \t diff\n", "", "");
print(str_repeat("-", 61) . "\n");
foreach($arr ? : $tests as $ns) {
$w = weighted_score($ns);
$b = bayesian_score($ns, $confidence);
// diff, floored at quarter
$d = floor(($b - $w) * 4)/4;

printf("%s \t| %.3f \t %.3f \t %s%s\n",
implode(',', array_map(function($e){return sprintf("% 3d",$e);}, $ns)),
$w, $b, /*$fw,*/
($d > 0 ? "\t " : ($d < 0 ? "\t" : '')),
$d != 0 ? sprintf("%.2f", $d) : "");
}
}

function get_post_rating_detail($stats) {
global $wpdb;

$post_stats = [];
foreach($wpdb->get_results("SELECT distinct rating_postid as p FROM {$wpdb->ratings}") as $i) {
$post_stats[] = get_post_score_data($i->p);
}

postratings_score_tests(NUM_VOTES_IMPACT_90, $post_stats);
}
Loading