<?php
|
namespace JVBase\managers;
|
|
use Exception;
|
use WP_Error;
|
use WP_Post;
|
use WP_Term;
|
use WP_User;
|
|
if (!defined('ABSPATH')) {
|
exit; // Exit if accessed directly
|
}
|
/**
|
* Response Manager
|
*
|
* Handles a responses
|
*
|
*/
|
class KarmaManager
|
{
|
protected static array $instances;
|
protected string $key;
|
protected string $references;
|
protected CustomTable $table;
|
protected static Cache $cache;
|
|
private function __construct(string $key, ?string $references = null)
|
{
|
$this->key = $key;
|
if (is_null($references)) {
|
$references = match($key){
|
'post' => 'post',
|
'term' => 'term',
|
'user' => 'user',
|
default => false
|
};
|
if (!$references) {
|
error_log('[KarmaManager]::__construct No references set, and no default found. Could not create instance');
|
unset(self::$instances[$key]);
|
return;
|
}
|
}
|
|
$this->references = $references;
|
$test = $this->defineTable();
|
if (!$test) {
|
unset(self::$instances[$key]);
|
}
|
|
// $this->cache = Cache::for($this->references.'_karma');
|
// switch ($this->references) {
|
// case 'post':
|
// $this->cache->connect('post');
|
// break;
|
// case 'term':
|
// $this->cache->connect('term');
|
// break;
|
// case 'user':
|
// $this->cache->connect('user');
|
// break;
|
// }
|
if (!isset(self::$cache)) {
|
self::$cache = Cache::for('user_karma')->connect('user');
|
}
|
|
add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
|
}
|
|
public static function getInstance(string $key):KarmaManager|false
|
{
|
if (!isset(self::$instances[$key])) {
|
return false;
|
}
|
return self::$instances[$key];
|
}
|
|
public static function for(string $key, ?string $references = null): KarmaManager
|
{
|
$key = sanitize_key($key);
|
if (!isset(self::$instances[$key])) {
|
self::$instances[$key] = new KarmaManager($key, $references);
|
}
|
|
return self::$instances[$key];
|
}
|
protected function getReferenceTable(CustomTable $table):array
|
{
|
switch ($this->references) {
|
case 'post':
|
case 'posts':
|
return [
|
$table->getPostIDType(),
|
$table->getPostTable(),
|
'ID'
|
];
|
case 'term':
|
case 'terms':
|
return [
|
$table->getTermIDType(),
|
$table->getTermTable(),
|
'term_id'
|
];
|
case 'user':
|
case 'users':
|
return [
|
$table->getUserIDType(),
|
$table->getUserTable(),
|
'ID'
|
];
|
}
|
//If we get here, it is a custom table.
|
$custom = CustomTable::getInstance($this->references);
|
if (!$custom) {
|
return [false,false,false];
|
}
|
return [
|
'bigint(20)',
|
$custom->getFullTableName(),
|
'id'
|
];
|
}
|
protected function defineTable():bool
|
{
|
$table = CustomTable::for('karma_'.$this->key);
|
[$type, $referenceTable, $column] = $this->getReferenceTable($table);
|
if (!$type) {
|
error_log('[KarmaManager]::defineTable Attempted to build reference for invalid table: '.$this->references);
|
CustomTable::destroyInstance('karma_'.$this->key);
|
return false;
|
}
|
|
$table->setColumns([
|
'id' => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
|
'item_id' => "{$type} NOT NULL",
|
'user_id' => "{$table->getUserIDType()} NOT NULL",
|
'content' => 'varchar(255) NOT NULL',
|
'vote' => "ENUM('up','down') NOT NULL",
|
'created_at'=> 'datetime NOT NULL DEFAULT CURRENT_TIMESTAMP',
|
'updated_at'=> 'datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP'
|
]);
|
$table->setKeys([
|
['key' => 'PRIMARY', 'value' => 'id'],
|
['key' => 'UNIQUE', 'value' => '`user_item` (`user_id`, `item_id`, `content`)'],
|
'`item_vote` (`item_id`,`content`,`vote`)',
|
'`item_id` (`item_id`)',
|
'`user_id` (`user_id`)'
|
]);
|
$base = BASE;
|
$table->setConstraints([
|
"CONSTRAINT `{$base}kt_{$this->key}_{$type}_item_id` FOREIGN KEY (`item_id`)
|
REFERENCES `{$referenceTable}` (`{$column}`) ON DELETE CASCADE",
|
"CONSTRAINT `{$base}kt_{$this->key}_{$type}_user_id` FOREIGN KEY (`user_id`)
|
REFERENCES `{$table->getUserTable()}` (`ID`) ON DELETE CASCADE"
|
]);
|
$table->defineTable();
|
$this->table = $table;
|
return true;
|
}
|
|
protected function response(?bool $success = null, string $message =''):array
|
{
|
$response = [
|
'success' => is_null($success) ? 'partial' : $success,
|
];
|
if (!empty($message)) {
|
$response['message'] = $message;
|
}
|
return $response;
|
}
|
|
protected function getItem(int $itemID, string $content):object|false
|
{
|
$based = jvbCheckBase($content);
|
return match($this->references) {
|
'post' => get_post($itemID),
|
'term' => get_term($itemID, $based),
|
'user' => get_userdata($itemID),
|
'response' => JVB()->responses()->response->get(['id' => $itemID])??false,
|
default => false //TODO if we do have custom tables, we'll have to set that up here
|
};
|
}
|
|
public function vote(int $userID, int $itemID, string $content, ?bool $vote = null):array
|
{
|
|
$content = jvbNoBase($content);
|
$item = $this->getItem($itemID, $content);
|
|
if (!$item || is_wp_error($item)) {
|
return $this->response(false, 'Could not find target');
|
}
|
|
$message = '';
|
//removing record
|
if (is_null($vote)){
|
$success = $this->table->delete([
|
'user_id' => $userID,
|
'item_id' => $itemID,
|
'content' => $content
|
]);
|
if ($success) {
|
$message = 'Vote removed successfully';
|
}
|
} else {
|
|
$success = $this->table->findOrCreate(
|
[
|
'user_id' => $userID,
|
'item_id' => $itemID,
|
'content' => $content
|
],
|
[
|
'vote' => $vote ? 'up' : 'down'
|
]
|
);
|
if (!$success) {
|
$message = 'Could not cast your vote';
|
} else {
|
$message = 'Vote cast successfully';
|
}
|
}
|
self::$cache->forget($userID);
|
|
JVB()->queue()->queueOperation(
|
'karmic',
|
0,
|
[
|
'items' => [
|
'itemID' => $itemID,
|
'content' => $content,
|
],
|
'type' => $this->references,
|
],
|
[
|
'chunk_key' => 'items',
|
]
|
);
|
return $this->response((bool) $success, $message);
|
}
|
|
public function getCount(int $itemID, string $content):array
|
{
|
|
$upvotes = count($this->table->pluck(
|
'id',
|
[
|
'item_id' => $itemID,
|
'content' => $content,
|
'vote' => 'up'
|
]));
|
$downvotes = count($this->table->pluck(
|
'id',
|
[
|
'item_id' => $itemID,
|
'content' => $content,
|
'vote' => 'down'
|
]
|
));
|
return [
|
'upvotes' => $upvotes,
|
'downvotes' => $downvotes,
|
'karmic_score' => $upvotes - $downvotes
|
];
|
}
|
|
public function processOperation(WP_Error|array $result, object $operation, array $data):WP_Error|array
|
{
|
if ($operation->type !== 'karmic') {
|
return $result;
|
}
|
if ($data['type'] !== $this->references) {
|
return $result;
|
}
|
$results = [];
|
foreach ($data['items'] as $item) {
|
[$upvotes, $downvotes, $karma] = $this->getCount($item['itemID'], $item['content']);
|
try {
|
switch ($this->references) {
|
case 'post':
|
update_post_meta($item['itemID'], BASE.'upvotes', $upvotes);
|
update_post_meta($item['itemID'], BASE.'downvotes', $downvotes);
|
update_post_meta($item['itemID'], BASE.'karma', $karma);
|
break;
|
case 'term':
|
update_term_meta($item['itemID'], BASE.'upvotes', $upvotes);
|
update_term_meta($item['itemID'], BASE.'downvotes', $downvotes);
|
update_term_meta($item['itemID'], BASE.'karma', $karma);
|
break;
|
case 'user':
|
update_user_meta($item['itemID'], BASE.'upvotes', $upvotes);
|
update_user_meta($item['itemID'], BASE.'downvotes', $downvotes);
|
update_user_meta($item['itemID'], BASE.'karma', $karma);
|
break;
|
case 'response':
|
JVB()->responses()->response->update(
|
[
|
'upvotes' => $upvotes,
|
'downvotes' => $downvotes,
|
'karma' => $karma
|
],
|
[
|
'id' => $item['itemID']
|
]
|
);
|
break;
|
}
|
$results[$item['itemID']] = true;
|
} catch (Exception $e) {
|
$results[$item['itemID']] = false;
|
}
|
|
}
|
return [
|
'success' => true,
|
'result' => $results
|
];
|
}
|
|
public static function getUserVotes(int $userID):array
|
{
|
return self::$cache->remember(
|
$userID,
|
function() use($userID) {
|
$votes = [];
|
foreach (self::$instances as $instance) {
|
$results = $instance->table->getMany([
|
'where' => [
|
'user_id' => $userID
|
]
|
]);
|
foreach ($results as $result) {
|
if (!array_key_exists($result['content'], $votes)) {
|
$votes[$result['content']] = [];
|
}
|
$votes[$result['content']][$result['item_id']] = $result['vote'];
|
}
|
}
|
return $votes;
|
}
|
);
|
}
|
}
|