Jake Vanderwerf
2026-05-31 d7e7d248cbe41cd7a9ef9c2fb022b6c4831f99a3
inc/rest/routes/VoteRoutes.php
@@ -2,10 +2,11 @@
namespace JVBase\rest\routes;
use JVBase\managers\CustomTable;
use JVBase\managers\KarmaManager;
use JVBase\registrar\Registrar;
use JVBase\rest\Response;
use JVBase\rest\Rest;
use JVBase\rest\Route;
use JVBase\utility\Features;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
@@ -46,7 +47,8 @@
            'user' => 'integer',
         ])
         ->auth('user')
         ->rateLimit(30);
         ->rateLimit(30)
         ->register();
    }
    /**
@@ -57,13 +59,14 @@
    public function handleVote(WP_REST_Request $request):WP_REST_Response
    {
        $content = sanitize_text_field($request->get_param('content')??'');
      if ((!Features::forContent($content)->has('karma') && !Features::forTaxonomy($content)->has('karma') && !Features::forUser($content)->has('karma'))) {
      $registrar = Registrar::getInstance($content);
      if (!$registrar || !$registrar->hasFeature('karma')) {
         return Response::validationError(['message' => __('Invalid content', 'jvb')]);
      }
        $vote = sanitize_text_field($request->get_param('vote')??'');
      $itemID = absint($request->get_param('item_id')??0);
        if ($itemID === 0 || !in_array($vote, ['up', 'down'])) {
        if ($itemID === 0 || !in_array($vote, [true, false, null])) {
         return Response::validationError(['message' => __('Invalid item or vote attempt', 'jvb')]);
        }
@@ -72,199 +75,27 @@
         return Response::validationError(['message' => __('User doesn\'t match. Bot?', 'jvb')]);
      }
        $operation = sanitize_text_field($request->get_param('id'));
      $type = match(true) {
         array_key_exists($content, JVB_CONTENT) => 'post',
         array_key_exists($content, JVB_TAXONOMY) => 'term',
         array_key_exists($content, JVB_USER) => 'user',
         default => false
      };
      $type = $registrar->getType()??false;
      if (!$type) {
         return Response::validationError(['message' => __('Invalid content type', 'jvb')]);
      }
        $data = [
            'user'      => $user,
            'item_id'   => $itemID,
            'content'   => $content,
         'type'      => $type,
            'vote'      => $vote,
        ];
        error_log('Final Vote Data: '.print_r($data, true));
        error_log('Operation: '.print_r($operation, true));
        $operationID = JVB()->queue()->add(
            'karmic',
            $user,
            $data,
            [
                'priority'      => 'high',
                'operation_id'  => $operation,
            ]
        );
      return $this->queued($operationID['operation_id']);
    }
   /**
    * @param WP_Error|array $result
    * @param object $operation
    * @param array $data
    *
    * @return WP_Error|array
    * @throws Exception
    */
    public function processOperation(WP_Error|array $result, object $operation, array $data):WP_Error|array
    {
        if ($operation->type !== 'karmic') {
            return $result;
        }
        //Check if item exists
      $item = match ($data['type']) {
         'post'   => get_post($data['item_id']),
         'term'   => get_term($data['item_id'], jvbCheckBase($data['content'])),
         'user'   => get_userdata($data['item_id']),
         default => false
      };
        if (!$item || is_wp_error($item)) {
            return [
                'success' => false,
                'result'   => __('Invalid item', 'jvb')
            ];
        }
      $table = CustomTable::for('karma_' . $data['content']);
      return $table->transaction(function($table) use ($data) {
         // Check existing vote
         $existing = $table->where([
            'item_id' => $data['item_id'],
            'user_id' => $data['user']
         ])->first();
         $existing_vote = $existing->vote ?? null;
         $new_vote = $data['vote'];
         // No previous vote - insert new
         if ($existing_vote === null) {
            $inserted = $table->create([
               'item_id' => $data['item_id'],
               'user_id' => $data['user'],
               'vote' => $new_vote,
            ]);
            if (!$inserted) {
               throw new Exception('Failed to record vote');
            }
            $this->updateVoteCount($data['content'], $data['type'], $data['item_id'], $new_vote, 1);
            $this->cache->invalidate($data['user']);
            return [
               'success' => true,
               'result' => __('Vote recorded', 'jvb'),
            ];
         }
         // Changing vote
         if ($existing_vote !== $new_vote) {
            $updated = $table->where([
               'item_id' => $data['item_id'],
               'user_id' => $data['user']
            ])->updateResults(['vote' => $new_vote]);
            if (!$updated) {
               throw new Exception('Failed to update vote');
            }
            // Decrement old, increment new
            $this->updateVoteCount($data['content'], $data['type'], $data['item_id'], $existing_vote, -1);
            $this->updateVoteCount($data['content'], $data['type'], $data['item_id'], $new_vote, 1);
            $this->cache->invalidate($data['user']);
            return [
               'success' => true,
               'result' => __('Vote updated', 'jvb'),
            ];
         }
         // Toggle off - remove vote
         $deleted = $table->where([
            'item_id' => $data['item_id'],
            'user_id' => $data['user']
         ])->deleteResults();
         if (!$deleted) {
            throw new Exception('Failed to remove vote');
         }
         $this->updateVoteCount($data['content'], $data['type'], $data['item_id'], $existing_vote, -1);
         $this->cache->delete($data['user']);
         return [
            'success' => true,
            'result' => __('Vote removed', 'jvb'),
         ];
      });
    }
    /**
     * @param string $content
     * @param int $ID
     * @param string $vote
     * @param int $value
     *
     * @return void
     */
    protected function updateVoteCount(string $content, string $type, int $ID, string $vote, int $value):void
    {
      $key = ($vote === 'down') ? BASE.'downvotes' : BASE.'upvotes';
      switch ($type) {
         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':
            $field = str_replace(BASE, '', $key);
            CustomTable::for('responses')->query(
               "UPDATE {table}
                SET $field = GREATEST(0, $field + %d),
                    karma = (upvotes - downvotes)
                WHERE id = %d",
               [$value, $ID]
            );
            break;
      $man = KarmaManager::getInstance($type);
      if (!$man) {
         return Response::validationError(['message' => __('Karma not set up', 'jvb')]);
      }
      [$success, $message] = $man->vote($user, $itemID, $content, $vote);
      return match ($success) {
         true, 'partial' => Response::success(['message' => $message]),
         default => Response::error($message),
      };
    }
    /**
     * @param WP_REST_Request $request
     *
@@ -279,27 +110,7 @@
         return Response::success($cache);
      }
      $votes = [];
      foreach (jvbGlobalKarma() as $type => $content_types) {
         foreach ($content_types as $content_type) {
            $table = CustomTable::for('karma_' . $content_type);
            // Skip if table doesn't exist
            global $wpdb;
            if ($wpdb->get_var("SHOW TABLES LIKE '{$table->getFullTableName()}'") != $table->getFullTableName()) {
               continue;
            }
            $results = $table->where(['user_id' => $user])->getResults();
            if (!empty($results)) {
               foreach ($results as $vote) {
                  $votes[$content_type][$vote->item_id] = $vote->vote;
               }
            }
         }
      }
      $votes = KarmaManager::getUserVotes($user);
      $this->cache->set($user, $votes);