From 0afb2c0046b55c123eafb4ab9ee77efa68d12463 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sat, 06 Jun 2026 17:15:31 +0000
Subject: [PATCH] =Starting the Favourites.js setup, converting previous Northeh stuff to new Registrar, fixing up Square.php integration to match
---
inc/rest/routes/ResponseRoutes.php | 1052 +++++++++++++++++++++++----------------------------------
1 files changed, 424 insertions(+), 628 deletions(-)
diff --git a/inc/rest/routes/ResponseRoutes.php b/inc/rest/routes/ResponseRoutes.php
index 31cbd9d..00c15a3 100644
--- a/inc/rest/routes/ResponseRoutes.php
+++ b/inc/rest/routes/ResponseRoutes.php
@@ -2,714 +2,510 @@
namespace JVBase\rest\routes;
use JVBase\JVB;
-use JVBase\rest\RestRouteManager;
-use JVBase\managers\Cache;
+use JVBase\rest\Rest;
+use JVBase\managers\CustomTable;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
if (!defined('ABSPATH')) {
- exit; // Exit if accessed directly
+ exit;
}
-class ResponseRoutes extends RestRouteManager
+
+/**
+ * Response Routes
+ *
+ * Handles threaded responses/comments for news items
+ */
+class ResponseRoutes extends Rest
{
- protected int $per_page;
- protected false|object $manager = false;
+ protected int $perPage = 20;
+ protected CustomTable $table;
+ protected CustomTable $karmaTable;
- public function __construct()
- {
- $this->cache_name = 'responses';
- parent::__construct();
- $this->action = 'dash-';
- $this->per_page = 20;
+ public function __construct()
+ {
+ $this->cacheName = 'responses';
+ $this->cacheTtl = 1800; // 30 minutes
+ parent::__construct();
- add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
- add_action('deleted_user', [$this, 'handleUserDeletion'], 10, 1);
- }
+ $this->table = CustomTable::for('responses');
+ $this->karmaTable = CustomTable::for('karma_response');
- /**
- * Registers response routes
- * @return void
- */
- public function registerRoutes():void
- {
- register_rest_route($this->namespace, '/response', [
- [
- 'methods' => 'GET',
- 'callback' => [$this, 'getResponses'],
- 'permission_callback' => [$this, 'checkPermission']
- ],
- [
- 'methods' => 'POST',
- 'callback' => [$this, 'handleResponseActions'],
- 'permission_callback' => [$this, 'checkPermission']
- ]
- ]);
- }
+ add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
+ add_action('deleted_user', [$this, 'handleUserDeletion'], 10, 1);
+ }
- /**
- * Get responses for a news post
- * @param WP_REST_Request $request
- * @return WP_REST_Response
- */
- public function getResponses(WP_REST_Request $request):WP_REST_Response
- {
- $item_id = (int) $request->get_param('item_id');
- error_log('Item ID: '.print_r($item_id, true));
- if (!$item_id) {
- return new WP_REST_Response([
- 'success'=> false,
- 'message' => 'Missing item ID'
- ]);
- }
+ public function registerRoutes(): void
+ {
+ register_rest_route($this->namespace, '/response', [
+ [
+ 'methods' => 'GET',
+ 'callback' => [$this, 'getResponses'],
+ 'permission_callback' => 'is_user_logged_in'
+ ],
+ [
+ 'methods' => 'POST',
+ 'callback' => [$this, 'handleResponseActions'],
+ 'permission_callback' => 'is_user_logged_in'
+ ]
+ ]);
+ }
- // Build query args
- $args = $this->buildQueryArgs($request);
- error_log('Args: '.print_r($args, true));
- $responses = $this->getItemResponse($item_id, $args);
- error_log('Responses: '.print_r($responses, true));
+ /**
+ * Get responses for an item
+ */
+ public function getResponses(WP_REST_Request $request): WP_REST_Response
+ {
+ $item_id = (int) $request->get_param('item_id');
- // Return formatted response
- return new WP_REST_Response($responses);
- }
+ if (!$item_id) {
+ return $this->error('Missing item ID', 'missing_item_id');
+ }
- /**
- * @param int $ID
- * @param string $postType
- *
- * @return void
- */
- protected function clearItemCache(int $ID, string $postType):void
- {
- $args = [
+ // Build query args
+ $args = $this->buildQueryArgs($request);
+ $cacheKey = $this->cache->generateKey(array_merge(['item_id' => $item_id], $args));
- BASE.'news' => $ID,
- 'post_type' => BASE.$postType,
- 'page' => 1,
- 'per_page' => 20,
- 'orderby' => 'created_at',
- 'order' => 'DESC'
- ];
- $key = $this->cache->generateKey($args);
- $this->cache->invalidate($key);
- }
+ // Check headers for 304 Not Modified
+ $headerCheck = $this->checkHeaders($request, $cacheKey);
+ if ($headerCheck) {
+ return $headerCheck;
+ }
- /**
- * @param int $ID
- * @param array $args
- *
- * @return array|WP_Error
- */
- public function getItemResponse(int $ID, array $args = []):array|WP_Error
- {
+ // Check cache
+ $cached = $this->cache->get($cacheKey);
+ if ($cached) {
+ return $this->success($cached);
+ }
- $default = [
- 'post_type' => BASE.'news',
- 'page' => 1,
- 'per_page' => 20,
- 'orderby' => 'created_at',
- 'order' => 'DESC'
- ];
+ // Get responses
+ $responses = $this->getItemResponse($item_id, $args);
- $args = wp_parse_args($default, $args);
+ if (is_wp_error($responses)) {
+ return $this->notFound($responses->get_error_message());
+ }
- $key = $this->cache->generateKey(array_merge([$args['post_type'] => $ID], $args));
- $check = $this->cache->get($key);
+ // Cache and return
+ $this->cache->set($cacheKey, $responses);
+ return $this->success($responses);
+ }
- if ($check) {
- return $check;
- }
+ /**
+ * Handle response actions (create/update/delete)
+ */
+ public function handleResponseActions(WP_REST_Request $request): WP_REST_Response
+ {
+ $data = $request->get_params();
+ $user_id = (int) ($data['user'] ?? 0);
- // Verify post exists and is of correct type
- $post = get_post($ID);
- if (!$post || $post->post_type !== $args['post_type']) {
- return new WP_Error(
- self::ERROR_NOT_FOUND,
- 'Item not found',
- ['status' => 404]
- );
- }
+ if (!$this->userCheck($user_id)) {
+ return $this->unauthorized('User verification failed');
+ }
- // Execute the query
- global $wpdb;
- $table = $wpdb->prefix . BASE . 'responses';
+ $operation_id = $data['id'] ?? uniqid('response_');
+ $action = sanitize_text_field($data['action'] ?? '');
- // Get total count
- $total_query = "SELECT COUNT(*) FROM $table WHERE item_id = %d";
- $total_args = [$ID];
+ if (!in_array($action, ['create', 'delete', 'update'])) {
+ return $this->error('Invalid action', 'invalid_action');
+ }
- // Add parent filter if specified
- if (isset($args['parent_id'])) {
- $total_query .= " AND parent_id " . ($args['parent_id'] === null ? "IS NULL" : "= %d");
- if ($args['parent_id'] !== null) {
- $total_args[] = $args['parent_id'];
- }
- }
+ // Validate required fields for create
+ if ($action === 'create') {
+ if (!isset($data['item_id'], $data['response'], $data['content'])) {
+ return $this->error('Missing required fields', 'missing_fields');
+ }
+ }
- $total_items = $wpdb->get_var($wpdb->prepare($total_query, $total_args));
+ // Prepare data for queue
+ $queue_data = match($action) {
+ 'create' => [
+ 'item_id' => (int) $data['item_id'],
+ 'parent_id' => isset($data['parent_id']) ? (int) $data['parent_id'] : null,
+ 'response' => wp_kses_post($data['response']),
+ 'content' => sanitize_text_field($data['content'])
+ ],
+ 'update' => [
+ 'response_id' => (int) $data['response_id'],
+ 'response' => isset($data['response']) ? wp_kses_post($data['response']) : null,
+ 'status' => isset($data['status']) && in_array($data['status'], ['published', 'hidden', 'flagged'])
+ ? $data['status']
+ : null
+ ],
+ 'delete' => [
+ 'response_id' => (int) $data['response_id']
+ ]
+ };
- // Build main query
- $query = "SELECT r.*,
- COALESCE((SELECT COUNT(*) FROM $table WHERE parent_id = r.id), 0) as reply_count,
- COALESCE((SELECT SUM(CASE WHEN vote = 'up' THEN 1 ELSE 0 END) FROM {$wpdb->prefix}" . BASE . "karma_response WHERE item_id = r.id), 0) as upvotes,
- COALESCE((SELECT SUM(CASE WHEN vote = 'down' THEN 1 ELSE 0 END) FROM {$wpdb->prefix}" . BASE . "karma_response WHERE item_id = r.id), 0) as downvotes
- FROM $table r
- WHERE r.item_id = %d";
+ // Queue the operation
+ JVB()->queue()->queueOperation(
+ $action . '_response',
+ $user_id,
+ $queue_data,
+ [
+ 'operation_id' => 'u' . $user_id . '_' . $operation_id,
+ 'priority' => 'high'
+ ]
+ );
- $query_args = [$ID];
+ return $this->queued($operation_id);
+ }
- // Apply parent filter
- if (isset($args['parent_id'])) {
- $query .= " AND r.parent_id " . ($args['parent_id'] === null ? "IS NULL" : "= %d");
- if ($args['parent_id'] !== null) {
- $query_args[] = $args['parent_id'];
- }
- }
+ /**
+ * Get responses with karma calculations
+ */
+ protected function getItemResponse(int $item_id, array $args = []): array|WP_Error
+ {
+ $defaults = [
+ 'page' => 1,
+ 'per_page' => $this->perPage,
+ 'orderby' => 'created_at',
+ 'order' => 'DESC',
+ 'parent_id' => null
+ ];
- // Apply order
- $order_column = in_array($args['orderby'], ['created_at', 'updated_at'])
- ? "r." . $args['orderby']
- : $args['orderby'];
+ $args = wp_parse_args($args, $defaults);
- $query .= " ORDER BY $order_column " . $args['order'];
+ // Verify post exists
+ $post = get_post($item_id);
+ if (!$post) {
+ return new WP_Error('not_found', 'Item not found');
+ }
- // Apply pagination
- $query .= " LIMIT %d OFFSET %d";
- $query_args[] = $args['per_page'];
- $query_args[] = ($args['page'] - 1) * $args['per_page'];
+ // Build query with karma calculations
+ $query = "
+ SELECT r.*,
+ COALESCE((SELECT COUNT(*) FROM {$this->table->getFullTableName()} WHERE parent_id = r.id), 0) as reply_count,
+ COALESCE((SELECT COUNT(*) FROM {$this->karmaTable->getFullTableName()} WHERE item_id = r.id AND vote = 'up'), 0) as upvotes,
+ COALESCE((SELECT COUNT(*) FROM {$this->karmaTable->getFullTableName()} WHERE item_id = r.id AND vote = 'down'), 0) as downvotes
+ FROM {$this->table->getFullTableName()} r
+ WHERE r.item_id = %d
+ ";
- // Get responses
- $responses = $wpdb->get_results($wpdb->prepare($query, $query_args));
+ $query_args = [$item_id];
- // Format responses
- $items = array_map([$this, 'formatItem'], $responses);
+ // Filter by parent_id
+ if (isset($args['parent_id'])) {
+ $query .= " AND r.parent_id " . ($args['parent_id'] === null ? "IS NULL" : "= %d");
+ if ($args['parent_id'] !== null) {
+ $query_args[] = $args['parent_id'];
+ }
+ }
- // Calculate pagination
- $total_pages = ceil($total_items / $args['per_page']);
+ // Get total count for pagination
+ $count_query = str_replace(
+ ['SELECT r.*,', 'COALESCE((SELECT COUNT(*) FROM', 'as reply_count,', 'as upvotes,', 'as downvotes'],
+ ['SELECT COUNT(*)', '', '', '', ''],
+ $query
+ );
+ $count_query = preg_replace('/COALESCE\([^)]+\)[^,]*,?/', '', $count_query);
+ $total_items = (int) $this->table->queryVar($count_query, $query_args);
- $return = [
- 'items' => $items,
- 'has_more' => $args['page'] < $total_pages,
- 'total_items' => (int) $total_items,
- 'total_pages' => $total_pages
- ];
+ // Add ordering
+ $order_column = in_array($args['orderby'], ['created_at', 'updated_at'])
+ ? "r.{$args['orderby']}"
+ : $args['orderby'];
- $this->cache->set($key, $return);
- return $return;
- }
+ $query .= " ORDER BY {$order_column} {$args['order']}";
- /**
- * Create a new response
- * @param WP_REST_Request $request
- * @return WP_REST_Response
- */
- public function handleResponseActions(WP_REST_Request $request):WP_REST_Response
- {
- $data = $request->get_params();
- $user_id = (int)$data['user'];
- if (!$this->userCheck($data['user'])) {
- return new WP_REST_Response([
- 'success' => false,
- 'message' => 'User doesn\'t match. Bot?'
- ]);
- }
- $operation_id = $data['id'] ?? uniqid('response_');
+ // Add pagination
+ $query .= " LIMIT %d OFFSET %d";
+ $query_args[] = $args['per_page'];
+ $query_args[] = ($args['page'] - 1) * $args['per_page'];
+ // Get responses
+ $responses = $this->table->queryResults($query, $query_args);
- // Validate required fields
- if (!array_key_exists('item_id', $data) || !array_key_exists('response', $data) || !array_key_exists('content', $data)) {
- error_log('Not enough data');
- return new WP_REST_Response([
- 'success' => false,
- 'message' => 'Missing required information.'
- ]);
- }
+ // Format responses
+ $items = array_map([$this, 'formatItem'], $responses);
- // Prepare data for queue
- $queue_data = [
- 'item_id' => (int) $data['item_id'],
- 'parent_id' => array_key_exists('parent_id', $data) ? (int) $data['parent_id'] : null,
- 'response' => wp_kses_post($data['response']),
- 'content'=> sanitize_text_field($data['content'])
- ];
+ return [
+ 'items' => $items,
+ 'has_more' => $args['page'] < ceil($total_items / $args['per_page']),
+ 'total_items' => $total_items,
+ 'total_pages' => (int) ceil($total_items / $args['per_page'])
+ ];
+ }
- error_log('Queue Data: '.print_r($queue_data, true));
+ /**
+ * Process queue operations
+ */
+ public function processOperation(WP_Error|array $result, object $operation, array $data): WP_Error|array
+ {
+ if (!in_array($operation->type, ['create_response', 'update_response', 'delete_response'])) {
+ return $result;
+ }
- $action = sanitize_text_field($data['action']);
- error_log('Action: '.print_r($action, true));
- if (!in_array($action, ['create', 'delete', 'update'])) {
- return new WP_REST_Response([
- 'success' => false,
- 'message' => 'Invalid action'
- ]);
- }
- error_log('Sanitized action. Here we go!');
+ return match($operation->type) {
+ 'create_response' => $this->createResponse($operation, $data),
+ 'update_response' => $this->updateResponse($data),
+ 'delete_response' => $this->deleteResponse($data),
+ default => $result
+ };
+ }
- // Add to queue
- $operation = JVB()->queue()->queueOperation(
- $action.'_response',
- $user_id,
- $queue_data,
- [
- 'operation_id' => 'u' . $user_id . '_' . $operation_id,
- 'priority' => 'high'
- ]
- );
- error_log('Queued for processing');
+ /**
+ * Create a new response
+ */
+ protected function createResponse(object $operation, array $data): array
+ {
+ $response_id = $this->table->insert([
+ 'item_id' => $data['item_id'],
+ 'content' => $data['content'],
+ 'user_id' => $operation->user_id,
+ 'parent_id' => $data['parent_id'],
+ 'response' => $data['response'],
+ 'status' => 'published'
+ ]);
- return new WP_REST_Response([
- 'success' => true,
- 'message' => 'Item queued for processing'
- ]);
- }
+ if (!$response_id) {
+ $this->logError('Failed to insert response', [
+ 'data' => $data,
+ 'error' => $this->table->getLastError()
+ ]);
- /**
- * Update a response
- * @param WP_REST_Request $request
- * @return WP_REST_Response
- */
- public function updateResponse(WP_REST_Request $request):WP_REST_Response
- {
- $id = (int) $request->get_param('id');
- $data = $request->get_params();
- $user_id = (int) $data['user'] ?? get_current_user_id();
- $operation_id = $data['id'] ?? uniqid('response_update_');
+ return ['success' => false, 'result' => 'Failed to create response'];
+ }
+ // Send notifications
+ $this->sendNotifications($data['item_id'], $data['parent_id'], $response_id, $operation->user_id);
- // Verify response exists
- global $wpdb;
- $table = $wpdb->prefix . BASE . 'responses';
- $response = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE id = %d", $id));
+ // Clear cache
+ $this->cache->flush();
- if (!$response) {
- return new WP_REST_Response([
- 'success' => false,
- 'message' => 'Item not found.'
- ]);
- }
+ return ['success' => true, 'result' => $response_id];
+ }
- // Check ownership or admin rights
- if ($response->user_id != $user_id) {
- return new WP_REST_Response([
- 'success' => false,
- 'message' => 'You do not have permission to delete this response'
- ]);
- }
+ /**
+ * Update a response
+ */
+ protected function updateResponse(array $data): array
+ {
+ $update_data = [];
- // Prepare data for queue
- $queue_data = [
- 'response_id' => $id,
- 'response' => !empty($data['response']) ? wp_kses_post($data['response']) : null,
- 'status' => !empty($data['status']) && in_array($data['status'], ['published', 'hidden', 'flagged'])
- ? $data['status']
- : null
- ];
+ if (isset($data['response'])) {
+ $update_data['response'] = $data['response'];
+ $update_data['updated_at'] = current_time('mysql');
+ }
- // Add to queue
- $operation = JVB()->queue()->queueOperation(
- 'update_response',
- $user_id,
- $queue_data,
- [
- 'operation_id' => 'u' . $user_id . '_' . $operation_id,
- 'priority' => 'high',
- 'notification' => false,
- ]
- );
+ if (isset($data['status'])) {
+ $update_data['status'] = $data['status'];
+ }
- return new WP_REST_Response($operation);
- }
+ if (empty($update_data)) {
+ return ['success' => false, 'result' => 'No fields to update'];
+ }
- /**
- * Delete a response
- * @param WP_REST_Request $request
- * @return WP_REST_Response
- */
- public function deleteResponse(WP_REST_Request $request):WP_REST_Response
- {
- $id = (int) $request->get_param('id');
- $user_id = get_current_user_id();
- $operation_id = $request->get_param('id') ?? uniqid('response_delete_');
+ $updated = $this->table->update(
+ $update_data,
+ ['id' => $data['response_id']]
+ );
- // Verify response exists
- global $wpdb;
- $table = $wpdb->prefix . BASE . 'responses';
- $response = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE id = %d", $id));
+ if ($updated === false) {
+ $this->logError('Failed to update response', [
+ 'data' => $data,
+ 'error' => $this->table->getLastError()
+ ]);
- if (!$response) {
- return new WP_REST_Response([
- 'success' => false,
- 'message' => 'Response not found',
- ]);
- }
-
- // Check ownership or admin rights
- if ($response->user_id != $user_id) {
- return new WP_REST_Response([
- 'success' => false,
- 'msesage' => 'You do not have permission to delete this resposne',
- ]);
- }
-
- // Add to queue
- $operation = JVB()->queue()->queueOperation(
- 'delete_response',
- $user_id,
- ['response_id' => $id],
- [
- 'operation_id' => 'u' . $user_id . '_' . $operation_id,
- 'priority' => 'high',
- 'notification' => false,
- ]
- );
-
- return new WP_REST_Response([
- 'success' => true,
- 'message' => 'Queued for processing'
- ]);
- }
-
- /**
- * Process operations from the queue
- * @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 (!in_array($operation->type, ['create_response', 'update_response', 'delete_response'])) {
- return $result;
- }
-
- global $wpdb;
- $table = $wpdb->prefix . BASE . 'responses';
-
- switch ($operation->type) {
- case 'create_response':
- // Create new response
- $inserted = $wpdb->insert(
- $table,
- [
- 'item_id' => $data['item_id'],
- 'content' => $data['content'],
- 'user_id' => $operation->user_id,
- 'parent_id' => $data['parent_id'],
- 'response' => $data['response'],
- 'status' => 'published',
- 'created_at' => current_time('mysql'),
- 'updated_at' => current_time('mysql')
- ],
- ['%d', '%d', '%d', '%s', '%s', '%s', '%s']
- );
-
- if (!$inserted) {
- error_log('Did not insert'.print_r($wpdb->last_error, true));
- JVB()->error()->log(
- '[ResponseRoutes]:processOperation',
- 'Failed to insert response',
- ['data' => $data, 'error' => $wpdb->last_error],
- 'error'
- );
- return [
- 'success' => false,
- 'result' => 'Failed to create response'
- ];
- }
-
- $response_id = $wpdb->insert_id;
- error_log('Response ID: '.print_r($response_id, true));
-
- // Send notification to post author
- $post = get_post($data['item_id']);
- if ($post && $post->post_author != $operation->user_id) {
- JVB()->notification()->addNotification(
- $post->post_author,
- 'new_response',
- [
- 'message' => 'Someone responded to your post',
- 'item_id' => $data['item_id'],
- 'response_id' => $response_id
- ]
- );
- }
-
- // Send notification to parent response author if this is a reply
- if ($data['parent_id']) {
- $parent = $wpdb->get_row($wpdb->prepare(
- "SELECT user_id FROM $table WHERE id = %d",
- $data['parent_id']
- ));
-
- if ($parent && $parent->user_id != $operation->user_id) {
- JVB()->notification()->addNotification(
- $parent->user_id,
- 'response_reply',
- [
- 'message' => 'Someone replied to your response',
- 'item_id' => $data['item_id'],
- 'response_id' => $response_id
- ]
- );
- }
- }
-
- $this->cache->forget($data['item_id']);
- return ['success' => true, 'result' => $response_id];
-
- case 'update_response':
- $update_data = [];
- $update_format = [];
-
- if (array_key_exists('response', $data)) {
- $update_data['response'] = $data['response'];
- $update_data['updated_at'] = current_time('mysql');
- $update_format[] = '%s';
- $update_format[] = '%s';
- }
-
- if (array_key_exists('status', $data)) {
- $update_data['status'] = $data['status'];
- $update_format[] = '%s';
- }
-
- if (empty($update_data)) {
- return ['success' => false, 'result' => 'No fields to update'];
- }
+ return ['success' => false, 'result' => 'Failed to update response'];
+ }
- $updated = $wpdb->update(
- $table,
- $update_data,
- ['id' => $data['response_id']],
- $update_format,
- ['%d']
- );
+ $this->cache->flush();
+ return ['success' => true, 'result' => $updated];
+ }
- if ($updated === false) {
- JVB()->error()->log(
- '[ResponseRoutes]:processOperation',
- 'Failed to update response',
- ['data' => $data, 'error' => $wpdb->last_error],
- 'error'
- );
- return [
- 'success' => false,
- 'result' => 'Failed to update response'
- ];
- }
+ /**
+ * Delete a response (or mark as deleted if it has replies)
+ */
+ protected function deleteResponse(array $data): array
+ {
+ $response = $this->table->get(['id' => $data['response_id']]);
- $this->cache->forget($data['item_id']);
- $this->cache->flush();
- return ['success' => true, 'result' => $updated];
+ if (!$response) {
+ return ['success' => false, 'result' => 'Response not found'];
+ }
- case 'delete_response':
- // Get response info before deleting
- $response = $wpdb->get_row($wpdb->prepare(
- "SELECT * FROM $table WHERE id = %d",
- $data['response_id']
- ));
+ // Check if it has replies
+ $has_replies = $this->table->where(['parent_id' => $data['response_id']])->countResults() > 0;
- if (!$response) {
- return ['success' => false, 'result' => 'Response not found'];
- }
+ if ($has_replies) {
+ // Don't delete, just mark as deleted
+ $updated = $this->table->update(
+ [
+ 'response' => '[ deleted ]',
+ 'status' => 'deleted',
+ 'updated_at' => current_time('mysql')
+ ],
+ ['id' => $data['response_id']]
+ );
- // Check if this response has replies
- $has_replies = $wpdb->get_var($wpdb->prepare(
- "SELECT COUNT(*) FROM $table WHERE parent_id = %d",
- $data['response_id']
- )) > 0;
+ $this->cache->flush();
+ return ['success' => true, 'result' => $updated];
+ }
- if ($has_replies) {
- // Don't delete, just mark as deleted and replace content
- $updated = $wpdb->update(
- $table,
- [
- 'response' => '[ deleted ]',
- 'status' => 'deleted',
- 'updated_at' => current_time('mysql')
- ],
- ['id' => $data['response_id']],
- ['%s', '%s', '%s'],
- ['%d']
- );
- $this->cache->flush();
- return ['success' => true, 'result' => $updated ];
- } else {
- // No replies, safe to actually delete
- $deleted = $wpdb->delete(
- $table,
- ['id' => $data['response_id']],
- ['%d']
- );
+ // No replies, safe to delete
+ $deleted = $this->table->delete(['id' => $data['response_id']]);
- if ($deleted === false) {
- JVB()->error()->log(
- '[ResponseRoutes]:processOperation',
- 'Failed to delete response',
- ['data' => $data, 'error' => $wpdb->last_error],
- 'error'
- );
- return [
- 'success' => false,
- 'result' => 'Failed to delete response'
- ];
- }
+ if ($deleted === false) {
+ $this->logError('Failed to delete response', [
+ 'data' => $data,
+ 'error' => $this->table->getLastError()
+ ]);
- $this->cache->forget($data['item_id']);
- $this->cache->flush();
- return ['success' => true, 'result' => $deleted];
- }
- }
+ return ['success' => false, 'result' => 'Failed to delete response'];
+ }
+ $this->cache->flush();
+ return ['success' => true, 'result' => $deleted];
+ }
- return $result;
- }
+ /**
+ * Send notifications for new responses
+ */
+ protected function sendNotifications(int $item_id, ?int $parent_id, int $response_id, int $user_id): void
+ {
+ // Notify post author
+ $post = get_post($item_id);
+ if ($post && $post->post_author != $user_id) {
+ JVB()->notification()->addNotification(
+ $post->post_author,
+ 'new_response',
+ [
+ 'message' => 'Someone responded to your post',
+ 'item_id' => $item_id,
+ 'response_id' => $response_id
+ ]
+ );
+ }
- /**
- * Build query arguments from request parameters
- * @param WP_REST_Request $request
- * @return array
- */
- protected function buildQueryArgs(WP_REST_Request $request):array
- {
- $page = max(1, (int) $request->get_param('page') ?? 1);
- $per_page = min(100, max(1, (int) $request->get_param('per_page') ?? $this->per_page));
+ // Notify parent response author if this is a reply
+ if ($parent_id) {
+ $parent = $this->table->get(['id' => $parent_id]);
- $args = [
- 'page' => $page,
- 'per_page' => $per_page,
- 'orderby' => 'created_at',
- 'order' => 'DESC'
- ];
+ if ($parent && $parent->user_id != $user_id) {
+ JVB()->notification()->addNotification(
+ $parent->user_id,
+ 'response_reply',
+ [
+ 'message' => 'Someone replied to your response',
+ 'item_id' => $item_id,
+ 'response_id' => $response_id
+ ]
+ );
+ }
+ }
+ }
- // Apply parent filter (null means top-level responses)
- if ($request->has_param('parent_id')) {
- $parent_id = $request->get_param('parent_id');
- $args['parent_id'] = $parent_id === '0' ? null : (int) $parent_id;
- }
+ /**
+ * Build query arguments from request
+ */
+ protected function buildQueryArgs(WP_REST_Request $request): array
+ {
+ $page = max(1, (int) ($request->get_param('page') ?? 1));
+ $per_page = min(100, max(1, (int) ($request->get_param('per_page') ?? $this->perPage)));
- // Apply ordering
- if ($request->has_param('orderby')) {
- $orderby = $request->get_param('orderby');
- $valid_orderby = ['created_at', 'updated_at', 'upvotes', 'downvotes'];
+ $args = [
+ 'page' => $page,
+ 'per_page' => $per_page,
+ 'orderby' => 'created_at',
+ 'order' => 'DESC'
+ ];
- if (in_array($orderby, $valid_orderby)) {
- $args['orderby'] = $orderby;
- }
- }
+ // Parent filter (null = top-level only)
+ if ($request->has_param('parent_id')) {
+ $parent_id = $request->get_param('parent_id');
+ $args['parent_id'] = $parent_id === '0' ? null : (int) $parent_id;
+ }
- if ($request->has_param('order')) {
- $order = strtoupper($request->get_param('order'));
- if (in_array($order, ['ASC', 'DESC'])) {
- $args['order'] = $order;
- }
- }
+ // Ordering
+ if ($request->has_param('orderby')) {
+ $orderby = $request->get_param('orderby');
+ $valid = ['created_at', 'updated_at', 'upvotes', 'downvotes'];
- return $args;
- }
+ if (in_array($orderby, $valid)) {
+ $args['orderby'] = $orderby;
+ }
+ }
- /**
- * @param int $itemID
- * @param int $ID
- *
- * @return array
- */
- protected function getChildren(int $itemID, int $ID):array
- {
- return $this->getItemResponse($itemID, ['parent_id' => $ID]);
- }
- /**
- * Format a response object for API output
- * @param object $response
- * @return array
- */
- protected function formatItem(object $response):array
- {
- if ($response->is_user_deleted) {
- // For deleted users, show anonymous info
- $formatted = [
- 'id' => (int) $response->id,
- 'item_id' => (int) $response->item_id,
- 'parent_id' => $response->parent_id ? (int) $response->parent_id : null,
- 'response' => $response->response,
- 'status' => $response->status,
- 'created_at' => $response->created_at,
- 'updated_at' => $response->updated_at,
- 'children' => $this->getChildren($response->item_id, $response->id),
- 'user' => [
- 'id' => null,
- 'name' => '[deleted user]',
- 'is_deleted' => true
- ],
- 'upvotes' => (int) ($response->upvotes ?? 0),
- 'downvotes' => (int) ($response->downvotes ?? 0),
- 'karma' => (int) ($response->upvotes ?? 0) - (int) ($response->downvotes ?? 0)
- ];
- } else {
- // Normal user processing as before
- error_log('Response: '.print_r($response, true));
- $artist = jvbContentFromUser($response->user_id);
+ if ($request->has_param('order')) {
+ $order = strtoupper($request->get_param('order'));
+ if (in_array($order, ['ASC', 'DESC'])) {
+ $args['order'] = $order;
+ }
+ }
- $formatted = [
- 'id' => (int) $response->id,
- 'item_id' => (int) $response->item_id,
- 'parent_id' => $response->parent_id ? (int) $response->parent_id : null,
- 'response' => $response->response,
- 'status' => $response->status,
- 'created_at' => $response->created_at,
- 'updated_at' => $response->updated_at,
- 'children' => $this->getChildren($response->item_id, $response->id),
- 'upvotes' => (int) ($response->upvotes ?? 0),
- 'downvotes' => (int) ($response->downvotes ?? 0),
- 'karma' => (int) ($response->upvotes ?? 0) - (int) ($response->downvotes ?? 0)
+ return $args;
+ }
- ];
+ /**
+ * Get child responses recursively
+ */
+ protected function getChildren(int $item_id, int $parent_id): array
+ {
+ return $this->getItemResponse($item_id, ['parent_id' => $parent_id]);
+ }
- // Add artist info if available
- if (!empty($artist)) {
- $formatted['artist'] = [
- 'name' => $artist['name'],
- 'url' => $artist['url'],
- 'shop' => $artist['shop'] ?? null
- ];
- }
- }
+ /**
+ * Format response for API output
+ */
+ protected function formatItem(object $response): array
+ {
+ $formatted = [
+ 'id' => (int) $response->id,
+ 'item_id' => (int) $response->item_id,
+ 'parent_id' => $response->parent_id ? (int) $response->parent_id : null,
+ 'response' => $response->response,
+ 'status' => $response->status,
+ 'created_at' => $response->created_at,
+ 'updated_at' => $response->updated_at,
+ 'children' => $this->getChildren($response->item_id, $response->id),
+ 'upvotes' => (int) ($response->upvotes ?? 0),
+ 'downvotes' => (int) ($response->downvotes ?? 0),
+ 'karma' => (int) ($response->upvotes ?? 0) - (int) ($response->downvotes ?? 0),
+ 'reply_count' => (int) ($response->reply_count ?? 0)
+ ];
- // Add reply count if available (for both deleted and non-deleted users)
- if (isset($response->reply_count)) {
- $formatted['reply_count'] = (int) $response->reply_count;
- }
+ // Handle deleted users
+ if ($response->is_user_deleted) {
+ $formatted['user'] = [
+ 'id' => null,
+ 'name' => '[deleted user]',
+ 'is_deleted' => true
+ ];
+ } else {
+ // Add artist info
+ $artist = jvbContentFromUser($response->user_id);
+ if (!empty($artist)) {
+ $formatted['artist'] = [
+ 'name' => $artist['name'],
+ 'url' => $artist['url'],
+ 'shop' => $artist['shop'] ?? null
+ ];
+ }
+ }
- return $formatted;
- }
+ return $formatted;
+ }
- /**
- * Handle user deletion by anonymizing their responses
- * @param int $user_id The deleted user ID
- */
- public function handleUserDeletion(int $user_id):void
- {
- global $wpdb;
- $table = $wpdb->prefix . BASE . 'news_responses';
+ /**
+ * Handle user deletion by anonymizing responses
+ */
+ public function handleUserDeletion(int $user_id): void
+ {
+ $updated = $this->table->update(
+ [
+ 'is_user_deleted' => 1,
+ 'updated_at' => current_time('mysql')
+ ],
+ ['user_id' => $user_id]
+ );
- // Anonymize all responses by this user
- $wpdb->update(
- $table,
- [
- 'is_user_deleted' => 1,
- // Keep user_id intact for internal tracking, but mark as deleted
- 'updated_at' => current_time('mysql')
- ],
- ['user_id' => $user_id],
- ['%d', '%s'],
- ['%d']
- );
+ $this->logError(
+ 'Anonymized responses for deleted user',
+ ['user_id' => $user_id, 'count' => $updated],
+ 'info'
+ );
- JVB()->error()->log(
- 'news_responses',
- 'Anonymized responses for deleted user',
- ['user_id' => $user_id, 'count' => $wpdb->rows_affected],
- 'info'
- );
- }
+ $this->cache->flush();
+ }
}
--
Gitblit v1.10.0