<?php
|
namespace JVBase\managers;
|
|
use JVBase\Instance;
|
use JVBase\registrar\Registrar;
|
use JVBase\rest\routes\ResponseRoutes;
|
|
if (!defined('ABSPATH')) {
|
exit; // Exit if accessed directly
|
}
|
/**
|
* Response Manager
|
*
|
* Handles a responses
|
* TODO
|
*/
|
class Responses
|
{
|
use Instance;
|
protected CustomTable $responses;
|
protected KarmaManager $karma;
|
protected ResponseRoutes $route;
|
protected int $perPage = 20;
|
|
private function __construct()
|
{
|
$this->route = ResponseRoutes::getInstance();
|
|
$this->defineTables();
|
$this->karma = KarmaManager::for('responses', $this->responses->getFullTableName());
|
$this->responses->setKarma($this->karma->karmaTable());
|
add_action('deleted_user', [$this, 'handleUserDeletion'], 10, 1);
|
}
|
protected function defineTables():void
|
{
|
$table = CustomTable::for('responses');
|
|
$table->setColumns([
|
'id' => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
|
'item_id' => "{$table->getPostIDType()} NOT NULL",
|
'type' => 'varchar(50) NOT NULL',
|
'user_id' => "{$table->getUserIdType()} NOT NULL",
|
'parent_id' => 'bigint(20) unsigned DEFAULT 0',
|
'response' => 'text NOT NULL',
|
'upvotes' => 'int NOT NULL DEFAULT 0',
|
'downvotes' => 'int NOT NULL DEFAULT 0',
|
'karmic_score' => 'int NOT NULL DEFAULT 0',
|
'history' => 'json DEFAULT {}',
|
'status' => "ENUM('publish', 'hidden','flagged','deleted') DEFAULT 'publish'",
|
'is_user_deleted'=> 'tinyint(1) DEFAULT 0',
|
'created_at' => 'datetime DEFAULT CURRENT_TIMESTAMP',
|
'updated_at' => 'datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP',
|
]);
|
|
$table->setKeys([
|
['key' => 'PRIMARY', 'value' => '`id`'],
|
'`item_lookup` (`item_id`)',
|
'`user_lookup` (`user_id`)',
|
'`parent_lookup` (`parent_id`)',
|
'`karma` (`karmic_score`)',
|
'`upvote` (`upvotes`)',
|
'`downvote` (`downvotes`)',
|
]);
|
|
$base = BASE;
|
$table->setConstraints([
|
"CONSTRAINT `{$base}responses_item` FOREIGN KEY (`item_id`)
|
REFERENCES `{$table->getPostTable()}` (`ID`) ON DELETE CASCADE",
|
]);
|
$table->defineTable();
|
|
$this->responses = $table;
|
}
|
public function responseExists(int $id):bool
|
{
|
return (bool) $this->responses->get([
|
'id' => $id,
|
]);
|
}
|
|
public function updateKarma(int $id, int $upvotes, int $downvotes, int $score):bool
|
{
|
return $this->responses->update([
|
'upvotes' => $upvotes,
|
'downvotes' => $downvotes,
|
'karmic_score' => $score,
|
], [
|
'id' => $id
|
]);
|
}
|
|
protected function itemExists(int $item_id, string $type):bool
|
{
|
return (bool)$this->getOriginalItem($item_id, $type);
|
}
|
public function getOriginalItem(int $item_id, string $type):object|false
|
{
|
[$method, $type] = $this->determineType($type);
|
if (!$method) {
|
return false;
|
}
|
$item = match($method) {
|
'post' => get_post($item_id),
|
'term' => get_term($item_id, $type),
|
'user' => get_userdata($item_id),
|
'response' => $this->responses->get(['id' => $item_id]),
|
default => false
|
};
|
return (is_wp_error($item)) ? false : $item;
|
}
|
public function getOriginalItemAuthors(int $item_id, string $type):array
|
{
|
[$method, $type] = $this->determineType($type);
|
$item = $this->getOriginalItem($item_id, $type);
|
return match ($method) {
|
'post' => [$item->post_author],
|
'term' => jvbGetTermOwners($item_id),
|
'user' => [$item->ID],
|
'response' => [$item->user_id],
|
};
|
}
|
protected function determineType(string $type):array
|
{
|
$method = match ($type) {
|
'post', 'page', 'product' => 'post',
|
'post_tag', 'category', 'term', 'taxonomy' => 'term',
|
'user' => 'user',
|
'response' => 'response',
|
default => false
|
};
|
if (!$method) {
|
$registrar = Registrar::getInstance($type);
|
$type = jvbCheckBase($type);
|
if ($registrar) {
|
$method = $registrar->getType();
|
}
|
}
|
if (!$method) {
|
error_log('[Responses]itemExists Invalid type: '.$type);
|
return [false, false];
|
}
|
return [$method, $type];
|
}
|
public function respondTo(int $item_id, string $type, int $user_id, string $response, ?int $parent_id = null):array
|
{
|
if (!$this->itemExists($item_id, $type)) {
|
return [
|
'success' => false,
|
'message' => 'Item does not exist',
|
];
|
}
|
if (empty($response)) {
|
return [
|
'success' => false,
|
'message' => 'Response content is empty'
|
];
|
}
|
|
$registrar = Registrar::getInstance($type);
|
if ($registrar) {
|
$type = $registrar->getBased();
|
}
|
$created = $this->responses->insert([
|
'item_id' => $item_id,
|
'user_id' => $user_id,
|
'parent_id' => $parent_id,
|
'response' => $response,
|
'type' => $type
|
]);
|
|
|
$users = $this->getOriginalItemAuthors($item_id, $type);
|
if ($parent_id) {
|
$users = array_merge($users, $this->getNotifyableUsers($parent_id));
|
}
|
$users = array_unique($users);
|
NotificationManager::getInstance()->unnotify(
|
$users,
|
'new_response',
|
$user_id,
|
);
|
|
return [
|
'success' => (bool)$created,
|
'message' => $created ? 'Response posted successfully' : 'Response not posted'
|
];
|
}
|
|
public function editResponse(int $id, string $response):array
|
{
|
$original = $this->responses->get(['id' => $id]);
|
if (!$original) {
|
return [
|
'success' => false,
|
'message' => 'Original response not found'
|
];
|
}
|
if (get_current_user_id() !== $original->user_id) {
|
return [
|
'success' => false,
|
'message' => 'This is not your response to edit'
|
];
|
}
|
$history = $this->pushHistory($original);
|
|
$updated = $this->responses->update([
|
'history' => $history,
|
'response' => sanitize_text_field($response)
|
], [
|
'user_id' => $original->user_id,
|
'id' => $id
|
]);
|
return [
|
'success' => (bool)$updated,
|
'message' => $updated ? 'Response edited successfully' : 'Response not edited'
|
];
|
}
|
|
public function deleteResponse(int $id):array
|
{
|
$original = $this->responses->get(['id' => $id]);
|
if (!$original) {
|
return [
|
'success' => true,
|
'message' => 'Item does not exist',
|
];
|
}
|
if ($original->user_id !== get_current_user_id()) {
|
return [
|
'success' => false,
|
'message' => 'This is not your response to delete'
|
];
|
}
|
|
$deleted = $this->responses->update([
|
'status' => 'deleted',
|
'history' => $this->pushHistory($original),
|
'response' => '[ deleted response ]'
|
], [
|
'id' => $id,
|
]);
|
|
NotificationManager::getInstance()->unnotify(
|
$this->getNotifyableUsers($id),
|
'new_response',
|
$original->user_id,
|
);
|
return [
|
'success' => (bool)$deleted,
|
'message' => $deleted ? 'Response deleted successfully' : 'Response not deleted'
|
];
|
}
|
|
public function hideResponse(int $id):array
|
{
|
$original = $this->responses->get(['id' => $id]);
|
if (!$original) {
|
return [
|
'success' => false,
|
'message' => 'Item does not exist',
|
];
|
}
|
$currentUser = get_current_user_id();
|
$isCommentOwner = $original->user_id === $currentUser;
|
if ($isCommentOwner) {
|
return $this->deleteResponse($id);
|
}
|
$item = $this->getOriginalItem($original->item_id, $original->type);
|
$authorIDs = $this->getOriginalItemAuthors($original->item_id, $original->type);
|
|
$isAuthor = in_array($currentUser, $authorIDs);
|
if (!$isAuthor) {
|
return [
|
'success' => false,
|
'message' => 'You cannot hide this response.'
|
];
|
}
|
|
$updated = $this->responses->update([
|
'status' => 'hidden',
|
], [
|
'id' => $id,
|
]);
|
|
return [
|
'success' => (bool)$updated,
|
'message' => $updated ? 'Response hidden successfully' : 'Response not hidden'
|
];
|
}
|
|
public function getUserResponses(int $userID, $args = [], bool $includeChildren = true):array
|
{
|
$args = wp_parse_args($args, [
|
'orderby' => 'created_at',
|
'order' => 'DESC',
|
'page' => 1,
|
'per_page' => 20,
|
]);
|
|
$where = [
|
'user_id' => $userID,
|
'parent_id' => null
|
];
|
if (array_key_exists('item_id', $args)) {
|
$where['item_id'] = $args['item_id'];
|
}
|
$responses = $this->responses->getMany(array_merge([
|
'where' => $where,
|
], $args), false, ARRAY_A);
|
|
if ($includeChildren) {
|
$responses['items'] = array_map(function ($r) use ($args) {
|
$r['children'] = $this->getChildResponses($r['id'], $args);
|
return $r;
|
}, $responses['items']);
|
}
|
return ['items'=> $responses];
|
}
|
public function getChildResponses(int $parentID, array $args, bool $includeChildren = true):array
|
{
|
$args = wp_parse_args($args, [
|
'orderby' => 'created_at',
|
'order' => 'DESC',
|
'page' => 1,
|
'per_page' => 20,
|
]);
|
|
$responses = $this->responses->getMany(array_merge([
|
'where' => [
|
'id' => $parentID,
|
]
|
], $args), true, ARRAY_A);
|
|
if ($includeChildren) {
|
$responses = array_map(function ($r) use ($args) {
|
$r['children'] = $this->getChildResponses($r['id'], $args);
|
return $r;
|
}, $responses);
|
}
|
return $responses;
|
}
|
|
public function getAllItemResponses(int $itemID, array $oldArgs = [], bool $includeChildren = true):array
|
{
|
$args = wp_parse_args($oldArgs, [
|
'orderby' => 'created_at',
|
'order' => 'DESC',
|
'page' => 1,
|
'per_page' => 20,
|
]);
|
|
if (array_key_exists('type', $oldArgs) || array_key_exists('content', $oldArgs) || array_key_exists('post_type', $oldArgs)) {
|
$args['type'] = jvbCheckBase($oldArgs['type']??$oldArgs['content']??$oldArgs['post_type']);
|
}
|
|
$where = [
|
'item_id' => $itemID,
|
'parent_id' => null
|
];
|
|
$responses = $this->responses->getMany(array_merge([
|
'where' => $where,
|
], $args), false, ARRAY_A);
|
|
if ($includeChildren) {
|
$responses['items'] = array_map(function ($r) use ($args) {
|
$r['children'] = $this->getChildResponses($r['id'], $args);
|
return $r;
|
}, $responses['items']);
|
}
|
return ['items'=> $responses];
|
}
|
|
public function handleUserDeletion(int $user_id):void
|
{
|
$updated = $this->responses->update([
|
'is_user_deleted' => 1,
|
], [
|
'user_id' => $user_id,
|
]);
|
|
Errors::getInstance()->log(
|
'Responses',
|
'Anonymized responses for deleted user',
|
[
|
'user_id' => $user_id,
|
'count' => $updated,
|
],
|
'info'
|
);
|
}
|
|
protected function getNotifyableUsers(int $id):array
|
{
|
$original = $this->responses->get(['id' => $id]);
|
if (!$original) {
|
return [];
|
}
|
$users = [$original->user_id];
|
if ($original->parent_id) {
|
$users = array_merge($users, $this->getNotifyableUsers($original->parent_id));
|
}
|
$users = array_merge($users, $this->getOriginalItemAuthors($original->item_id, $original->type));
|
|
return array_unique($users);
|
|
}
|
|
protected function pushHistory(object $original):array
|
{
|
$history = json_decode($original->history, true);
|
$history[] = [
|
'edited_at' => $original->updated_at,
|
'response' => $original->response,
|
'upvotes' => $original->upvotes,
|
'downvotes' => $original->downvotes,
|
];
|
return $history;
|
}
|
}
|