<?php
|
namespace JVBase\rest\routes;
|
use JVBase\JVB;
|
use JVBase\rest\RestRouteManager;
|
use WP_REST_Request;
|
use WP_REST_Response;
|
use WP_Error;
|
use Exception;
|
|
if (!defined('ABSPATH')) {
|
exit; // Exit if accessed directly
|
}
|
class VoteRoutes extends RestRouteManager
|
{
|
public function __construct()
|
{
|
$this->cache_name = 'karma';
|
$this->cache_ttl = 86400;
|
parent::__construct();
|
add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
|
}
|
|
/**
|
* Registers vote routes
|
* @return void
|
*/
|
public function registerRoutes():void
|
{
|
register_rest_route($this->namespace, 'vote', [
|
[
|
'methods' => 'POST',
|
'callback' => [$this, 'handleVote'],
|
'permission_callback' => [$this, 'checkPermission']
|
],
|
[
|
'methods' => 'GET',
|
'callback' => [$this, 'getVotes'],
|
'permission_callback' => [$this, 'checkPermission']
|
]
|
]);
|
}
|
|
/**
|
* @param WP_REST_Request $request
|
*
|
* @return WP_REST_Response
|
*/
|
public function handleVote(WP_REST_Request $request):WP_REST_Response
|
{
|
global $karma;
|
if (!array_key_exists($request->get_param('content'), $karma)) {
|
return new WP_REST_Response([
|
'success' => false,
|
'message' => __('Invalid content', 'jvb'),
|
]);
|
}
|
$vote = $request->get_param('vote');
|
if (!$request->get_param('item_id') || !in_array($vote, ['up', 'down'])) {
|
return new WP_REST_Response([
|
'success' => false,
|
'message' => __('Invalid item or vote attempt', 'jvb'),
|
]);
|
}
|
|
//cursory sanitization
|
$user = (int) $request->get_param('user');
|
if (!$this->userCheck($user)) {
|
return new WP_REST_Response([
|
'success' => false,
|
'message' => __('User doesn\'t match. Bot?', 'jvb'),
|
]);
|
}
|
$operation = sanitize_text_field($request->get_param('id'));
|
|
$data = [
|
'user' => $user,
|
'item_id' => (int) $request->get_param('item_id'),
|
'content' => sanitize_text_field($request->get_param('content')),
|
'vote' => sanitize_text_field($vote),
|
];
|
|
error_log('Final Vote Data: '.print_r($data, true));
|
error_log('Operation: '.print_r($operation, true));
|
$queue = JVB()->queue();
|
$queue->queueOperation(
|
'karmic',
|
$user,
|
$data,
|
[
|
'priority' => 'high',
|
'operation_id' => $operation,
|
]
|
);
|
|
return new WP_REST_Response([
|
'success' => true,
|
'message' => __('Operation queued', 'jvb'),
|
'operation_id' => $operation
|
]);
|
}
|
|
/**
|
* @param WP_Error|array $result
|
* @param object $operation
|
* @param array $data
|
*
|
* @return WP_Error|array
|
*/
|
public function processOperation(WP_Error|array $result, object $operation, array $data):WP_Error|array
|
{
|
if ($operation->type !== 'karmic') {
|
return $result;
|
}
|
// Get parameters from request
|
global $karma;
|
|
//Check if item exists
|
$item = ($karma[$data['content']] === 'term') ?
|
get_term($data['item_id'], BASE.$data['content']) :
|
get_post($data['item_id']);
|
if (!$item || is_wp_error($item)) {
|
return [
|
'success' => false,
|
'result' => __('Invalid item', 'jvb')
|
];
|
}
|
|
global $wpdb;
|
$table_name = $wpdb->prefix . BASE . 'karma_' . $data['content'];
|
$key = $data['user'];
|
error_log('Processing: '.print_r($data, true));
|
// Check if user has already voted on this post
|
$existing_vote = $wpdb->get_var(
|
$wpdb->prepare(
|
"SELECT vote FROM {$table_name} WHERE item_id = %d AND user_id = %d",
|
$data['item_id'],
|
$data['user']
|
)
|
);
|
|
// Begin transaction for data integrity
|
$wpdb->query('START TRANSACTION');
|
|
try {
|
// Initialize response data
|
$response_data = [
|
'item_id' => $data['item_id'],
|
'previous_vote' => $existing_vote,
|
'new_vote' => $data['vote'],
|
'updated' => false
|
];
|
|
error_log('Existing: '.print_r($existing_vote, true));
|
error_log('New: '.print_r($data['vote'], true));
|
|
// If user hasn't voted before
|
if ($existing_vote === null) {
|
// Insert new vote
|
$inserted = $wpdb->insert(
|
$table_name,
|
[
|
'item_id' => $data['item_id'],
|
'user_id' => $data['user'],
|
'vote' => $data['vote'],
|
'date' => current_time('mysql')
|
],
|
['%d', '%d', '%s', '%s']
|
);
|
|
if (!$inserted) {
|
throw new Exception('Failed to record vote');
|
}
|
|
// Increment the appropriate vote counter
|
$this->updateVoteCount($data['content'], $data['item_id'], $data['vote'], 1);
|
|
$response_data['updated'] = true;
|
$this->cache->invalidate($key);
|
} elseif ($existing_vote !== $data['vote']) {
|
// If user is changing their vote
|
// Update existing vote
|
$updated = $wpdb->update(
|
$table_name,
|
[
|
'vote' => $data['vote'],
|
],
|
[
|
|
'item_id' => $data['item_id'],
|
'user_id' => $data['user'],
|
],
|
['%s'],
|
['%d', '%d']
|
);
|
|
if (!$updated) {
|
throw new Exception('Failed to update vote');
|
}
|
|
$this->updateVoteCount($data['content'], $data['item_id'], $existing_vote, -1);
|
|
// Increment new vote type
|
$this->updateVoteCount($data['content'], $data['item_id'], $data['vote'], 1);
|
|
$response_data['updated'] = true;
|
$this->cache->invalidate($key);
|
} else {
|
// If user is clicking the same vote again (toggle off)
|
// Remove the vote
|
$deleted = $wpdb->delete(
|
$table_name,
|
[
|
'item_id' => $data['item_id'],
|
'user_id' => $data['user']
|
],
|
['%d', '%d']
|
);
|
|
if (!$deleted) {
|
throw new Exception('Failed to remove vote');
|
}
|
|
// Decrement the vote counter
|
$this->updateVoteCount($data['content'], $data['item_id'], $data['vote'], -1);
|
|
$response_data['new_vote'] = null;
|
$response_data['updated'] = true;
|
$this->cache->invalidate($key);
|
}
|
|
$wpdb->query('COMMIT');
|
|
return [
|
'success' => true,
|
'result' => __('Vote handled', 'jvb'),
|
];
|
} catch (Exception $e) {
|
$wpdb->query('ROLLBACK');
|
return [
|
'success' => false,
|
'result' => $e->getMessage()
|
];
|
}
|
}
|
|
/**
|
* @param string $content
|
* @param int $ID
|
* @param string $vote
|
* @param int $value
|
*
|
* @return void
|
*/
|
protected function updateVoteCount(string $content, int $ID, string $vote, int $value):void
|
{
|
global $karma;
|
|
$key = ($vote==='down') ? BASE.'downvotes' : BASE.'upvotes';
|
|
switch ($karma[$content]) {
|
case 'post':
|
$old = (int) get_post_meta($ID, $key, true);
|
$new = max(0, $old + $value);
|
update_post_meta($ID, $key, $new);
|
$up = (int) get_post_meta($ID, BASE.'upvotes', true);
|
$down = (int) get_post_meta($ID, BASE.'downvotes', true);
|
update_post_meta($ID, BASE.'karma', $up - $down);
|
break;
|
case 'term':
|
$old = (int) get_term_meta($ID, $key, true);
|
$new = max(0, $old + $value);
|
update_term_meta($ID, $key, $new);
|
$up = (int) get_term_meta($ID, BASE.'upvotes', true);
|
$down = (int) get_term_meta($ID, BASE.'downvotes', true);
|
update_term_meta($ID, BASE.'karma', $up - $down);
|
break;
|
case 'user':
|
$old = (int) get_user_meta($ID, $key, true);
|
$new = max(0, $old + $value);
|
update_user_meta($ID, $key, $new);
|
$up = (int) get_user_meta($ID, BASE.'upvotes', true);
|
$down = (int) get_user_meta($ID, BASE.'downvotes', true);
|
update_user_meta($ID, BASE.'karma', $up - $down);
|
break;
|
case 'response':
|
// Direct table update for responses
|
global $wpdb;
|
$table = $wpdb->prefix . BASE . 'responses';
|
|
// Update vote count
|
$field = str_replace(BASE, '', $key);
|
$wpdb->query($wpdb->prepare(
|
"UPDATE $table SET $field = GREATEST(0, $field + %d), karma = (upvotes - downvotes) WHERE id = %d",
|
$value,
|
$ID
|
));
|
break;
|
}
|
}
|
|
/**
|
* @param WP_REST_Request $request
|
*
|
* @return WP_REST_Response
|
*/
|
public function getVotes(WP_REST_Request $request):WP_REST_Response
|
{
|
$user = $request->get_param('user')??get_current_user_id();
|
$cache = $this->cache->get($user);
|
if ($cache) {
|
return new WP_REST_Response($cache);
|
}
|
|
|
global $wpdb;
|
$votes = [];
|
|
foreach (jvbGlobalKarma() as $type => $content_types) {
|
foreach ($content_types as $content_type) {
|
$table_name = $wpdb->prefix . BASE . 'karma_'. $content_type;
|
|
// Skip if table doesn't exist
|
if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
|
continue;
|
}
|
|
$results = $wpdb->get_results(
|
$wpdb->prepare(
|
"SELECT item_id, vote, date
|
FROM {$table_name}
|
WHERE user_id = %d",
|
$user
|
)
|
);
|
|
if ($results && !is_wp_error($results)) {
|
foreach ($results as $vote) {
|
$votes[$content_type][$vote->item_id] = $vote->vote;
|
}
|
}
|
}
|
}
|
|
// Store in cache
|
$this->cache->set($user, $votes);
|
|
return new WP_REST_Response($votes);
|
}
|
}
|