<?php
|
namespace JVBase\rest\routes;
|
|
use JVBase\rest\PermissionHandler;
|
use JVBase\rest\Rest;
|
use JVBase\meta\Meta;
|
use JVBase\managers\NewsRelationships;
|
use JVBase\rest\Route;
|
use WP_Query;
|
use WP_Error;
|
use WP_REST_Request;
|
use WP_REST_Response;
|
|
if (!defined('ABSPATH')) {
|
exit; // Exit if accessed directly
|
}
|
|
class NewsRoutes extends Rest
|
{
|
protected int $per_page;
|
protected bool|object $manager = false;
|
public function __construct()
|
{
|
$this->cacheName = 'news';
|
parent::__construct();
|
|
add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
|
}
|
|
/**
|
* Registers news routes
|
* @return void
|
*/
|
public function registerRoutes():void
|
{
|
Route::for('news')
|
->get([$this, 'getNews'])
|
->args([
|
'page' => 'integer|default:1|min:1',
|
'shop' => 'integer',
|
'type' => 'integer',
|
'artist' => 'array',
|
'orderby' => 'string|enum:date,title,name,popularity,karma,random|default:date',
|
'order' => 'string|enum:ASC,DESC|default:DESC',
|
'date-filter' => 'string|enum:today,week,month,year',
|
'per_page' => 'integer|default:20|min:1|max:100',
|
'dateFrom' => 'string',
|
'dateTo' => 'string',
|
'watched' => 'boolean',
|
])
|
->auth(PermissionHandler::combine(['user','nonce', ['actionNonce'=>'dash-']]))
|
->rateLimit(20)
|
->post([$this, 'handleNewsOperation'])
|
->args([
|
'user' => 'integer|required',
|
'id' => 'string|required',
|
'post_title' => 'string|required',
|
'post_excerpt' => 'string',
|
'post_content' => 'string|required',
|
'type' => 'integer',
|
])
|
->auth(PermissionHandler::combine(['user','nonce',['actionNonce'=>'dash-']]))
|
->rateLimit(30)
|
->register();
|
|
Route::for(Route::pattern('news/{id}'))
|
->get([$this, 'getNewsItem'])
|
->arg('id', 'integer|required')
|
->auth(PermissionHandler::combine(['user','nonce', ['actionNonce'=>'dash-']]))
|
->rateLimit(30)
|
->register();
|
}
|
|
/**
|
* @param WP_REST_Request $request
|
*
|
* @return WP_REST_Response
|
*/
|
public function handleNewsOperation(WP_REST_Request $request):WP_REST_Response
|
{
|
$queue = JVB()->queue();
|
$data = $request->get_params();
|
$user = absint($data['user']);
|
$operationID = sanitize_text_field($data['id']);
|
|
unset($data['user']);
|
unset($data['id']);
|
$queue->queueOperation(
|
'new_news',
|
$user,
|
$data,
|
[
|
'operation_id' => $operationID,
|
'priority' => 'high',
|
'notification' => true,
|
]
|
);
|
return $this->queued($operationID);
|
|
}
|
|
/**
|
* @param WP_REST_Request $request
|
*
|
* @return WP_REST_Response
|
*/
|
public function getNews(WP_REST_Request $request):WP_REST_Response
|
{
|
$args = $this->buildQueryArgs($request);
|
$key = $this->cache->generateKey($args);
|
$cache = $this->cache->get($key);
|
if ($cache) {
|
return $this->success($cache);
|
}
|
$args['post_type'] = BASE.'news';
|
|
$query = new WP_Query($args);
|
$items = array_map([$this, 'formatItem'], $query->posts);
|
|
$results = [
|
'items' => $items,
|
'has_more' => $query->max_num_pages > $args['paged'],
|
'total_items' => $query->found_posts,
|
'total_pages' => $query->max_num_pages
|
];
|
|
$this->cache->set($key, $results);
|
|
return $this->success($results);
|
}
|
|
/**
|
* @param WP_REST_Request $request
|
*
|
* @return array
|
*/
|
protected function buildQueryArgs(WP_REST_Request $request):array
|
{
|
$data = $request->get_params();
|
|
$page = intval($request->get_param('page') ?? 1);
|
$args = [
|
'paged' => $page,
|
'post_status' => 'publish'
|
];
|
|
$args = $this->applyTaxonomyFilters($args, $data);
|
$args = $this->applyOrderFilters($args, $data);
|
$args = $this->applyDateFilters($args, $data);
|
return $this->applyWatchedFilter($args, $data);
|
}
|
|
/**
|
* @return void
|
*/
|
protected function checkRelationshipManager():void
|
{
|
if (!$this->manager) {
|
$this->manager = new NewsRelationships();
|
}
|
}
|
|
/**
|
* @param array $args
|
* @param array $data
|
*
|
* @return array
|
*/
|
protected function applyTaxonomyFilters(array $args, array $data):array
|
{
|
if (array_key_exists('shop', $data)) {
|
$this->checkRelationshipManager();
|
$shop = (int) $data['shop'];
|
$artists = $this->manager->getShopArtistsWithNews($shop);
|
$args['author__in'] = $artists;
|
}
|
if (array_key_exists('type', $data)) {
|
$args['tax_query'] = [[
|
'taxonomy' => BASE.'ntype',
|
'terms' => (int) $data['type'],
|
]];
|
}
|
if (array_key_exists('artist', $data)) {
|
$artist_ids = array_map('intval', (array)$data['artist']);
|
$args['author__in'] = $artist_ids;
|
}
|
|
return $args;
|
}
|
|
/**
|
* @param array $args
|
* @param array $data
|
*
|
* @return array
|
*/
|
protected function applyOrderFilters(array $args, array $data):array
|
{
|
if (array_key_exists('orderby', $data) && $data['orderby'] === 'random') {
|
// Handle random ordering
|
$current_seed = jvbGetRandomSeed();
|
$args['orderby'] = 'RAND(' . $current_seed . ')';
|
unset($args['order']);
|
} else {
|
// Standard ordering
|
if (in_array($data['orderby'], ['date', 'title', 'name'])) {
|
$args['orderby'] = $data['orderby'];
|
} else {
|
switch ($data['orderby']) {
|
case 'popularity':
|
$args['meta_key'] = BASE.'upvotes';
|
$args['orderby'] = 'meta_value_num';
|
break;
|
case 'karma':
|
$args['meta_key'] = BASE.'karma';
|
$args['orderby'] = 'meta_value_num';
|
break;
|
default:
|
$args['orderby'] = 'date';
|
}
|
}
|
$args['order'] = (in_array($data['order'], ['ASC', 'DESC'])) ? $data['order'] : 'DESC';
|
}
|
|
return $args;
|
}
|
|
/**
|
* @param array $args
|
* @param array $data
|
*
|
* @return array
|
*/
|
protected function applyDateFilters(array $args, array $data):array
|
{
|
if (!array_key_exists('date-filter', $data) && !array_key_exists('dateFrom', $data)) {
|
return $args;
|
}
|
if (array_key_exists('dateFrom', $data)) {
|
$dateFrom = strtotime($data['dateFrom']);
|
$dateTo = strtotime($data['dateTo']);
|
if ($dateFrom !== false && $dateTo !== false) {
|
$args['date_query'] = [
|
[
|
'after' => date("c", $dateFrom),
|
'before' => date("c", $dateTo),
|
'inclusive' => true,
|
]
|
];
|
}
|
} else {
|
switch ($data['date-filter']) {
|
case 'today':
|
$args['date_query'] = [['after' => '1 day ago']];
|
break;
|
case 'week':
|
$args['date_query'] = [['after' => '1 week ago']];
|
break;
|
case 'month':
|
$args['date_query'] = [['after' => '1 month ago']];
|
break;
|
case 'year':
|
$args['date_query'] = [['after' => '1 year ago']];
|
break;
|
}
|
}
|
return $args;
|
}
|
|
/**
|
* @param array $args
|
* @param array $data
|
*
|
* @return array
|
*/
|
protected function applyWatchedFilter(array $args, array $data):array
|
{
|
if (!array_key_exists('watched', $data)) {
|
return $args;
|
}
|
|
global $wpdb;
|
|
$favourites_table = $wpdb->prefix . BASE . 'favourites';
|
$post_types = [BASE.'news'];
|
$placeholders = implode(',', array_fill(0, count($post_types), '%s'));
|
|
$favourited_ids = $wpdb->get_col($wpdb->prepare(
|
"SELECT target_id FROM {$favourites_table}
|
WHERE user_id = %d AND type IN ($placeholders)",
|
array_merge(
|
[get_current_user_id()],
|
$post_types
|
)
|
));
|
|
if (empty($favourited_ids)) {
|
$favourited_ids = [0];
|
}
|
|
$args['post__in'] = isset($args['post__in'])
|
? array_intersect($args['post__in'], $favourited_ids)
|
: $favourited_ids;
|
|
|
return $args;
|
}
|
|
|
/**
|
* @param int|object $post
|
*
|
* @return array
|
*/
|
public function formatItem(int|object $post):array
|
{
|
if (is_int($post)) {
|
$post = get_post($post);
|
if (!$post || is_wp_error($post)) {
|
return [];
|
}
|
}
|
|
$artist = jvbContentFromUser($post->post_author);
|
$upvotes = (int) get_post_meta($post->ID, BASE.'upvotes', true);
|
$downvotes = (int) get_post_meta($post->ID, BASE.'downvotes', true);
|
$comments = JVB()->routes('comments')->getItemResponse($post->ID, BASE.'news');
|
|
return [
|
'id' => $post->ID,
|
'title' => $post->post_title,
|
'tldr' => $post->post_excerpt,
|
'post_content' => $post->post_content,
|
'content' => 'news',
|
'date' => $post->post_date,
|
'artist' => [
|
'id' => $artist['id'],
|
'name' => $artist['name'],
|
'url' => $artist['url']
|
],
|
'shop' => $artist['shop'],
|
'upvotes' => $upvotes,
|
'downvotes' => $downvotes,
|
'comments' => $comments,
|
'type' => $artist['type'],
|
];
|
}
|
|
/**
|
* @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):array|WP_Error
|
{
|
if ($operation->type != 'new_news') {
|
return $result;
|
}
|
|
$user_id = $operation->user_id;
|
|
$title = sanitize_text_field($data['post_title']);
|
$tldr = sanitize_textarea_field($data['post_excerpt']);
|
|
$content = wp_kses_post($data['post_content']);
|
unset($data['post_title']);
|
unset($data['post_excerpt']);
|
unset($data['post_content']);
|
$result = [];
|
|
$ID = wp_insert_post([
|
'post_type' => BASE.'news',
|
'post_author' => $user_id,
|
'post_title' => $title,
|
'post_excerpt' => $tldr,
|
'post_content' => $content,
|
'post_status' => 'publish'
|
]);
|
$result['post_id'] = ($ID && !is_wp_error($ID));
|
|
$type = get_term((int) $data['type'], BASE.'ntype');
|
if ($type && !is_wp_error($type)) {
|
$terms = wp_set_post_terms($ID, $type, BASE.'ntype');
|
$result['term_update'] = ($terms && !is_wp_error($terms));
|
}
|
unset($data['type']);
|
|
if ($ID) {
|
$meta = Meta::forPost($ID);
|
foreach ($data as $key => $value) {
|
$m = $meta->set($key, $value);
|
$result[$key] = $m;
|
}
|
}
|
|
$this->cache->flush();
|
|
return [
|
'success' => true,
|
'result' => $result
|
];
|
}
|
|
/**
|
* Get a single news item by ID
|
*
|
* @param WP_REST_Request $request
|
* @return WP_REST_Response
|
*/
|
public function getNewsItem(WP_REST_Request $request): WP_REST_Response
|
{
|
$id = absint($request->get_param('id'));
|
|
$cache = $this->cache->get($id);
|
if ($cache) {
|
return $this->success($cache);
|
}
|
|
$post = get_post($id);
|
|
if (!$post || $post->post_type !== BASE.'news' || $post->post_status !== 'publish') {
|
return $this->error('News item not found', 'not_found', 404);
|
}
|
|
$item = $this->formatItem($post);
|
|
$this->cache->set($id, $item);
|
|
return $this->success($item);
|
}
|
}
|