2026-05-24 11:47:20 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
|
2026-05-27 21:24:38 +02:00
|
|
|
use App\Models\Entry;
|
2026-06-30 16:12:17 +02:00
|
|
|
use App\Models\News;
|
2026-05-24 11:47:20 +02:00
|
|
|
use Illuminate\Http\Client\ConnectionException;
|
|
|
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
|
|
|
|
|
|
class XenforoApiService {
|
|
|
|
|
|
|
|
|
|
private string $apiKey;
|
|
|
|
|
private int $superUserId;
|
|
|
|
|
private string $apiUrl;
|
|
|
|
|
|
|
|
|
|
public function __construct(){
|
|
|
|
|
$this->apiKey = config('services.xf_api.key');
|
|
|
|
|
$this->superUserId = config('services.xf_api.user');
|
|
|
|
|
$this->apiUrl = config('services.xf_api.url');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @throws ConnectionException
|
|
|
|
|
*/
|
|
|
|
|
private function get(string $endpoint, ?int $customUserId = null ): mixed
|
|
|
|
|
{
|
2026-06-25 13:40:34 +02:00
|
|
|
$response = Http::timeout(30)->withOptions(['verify' => false])->withHeaders([
|
2026-05-24 11:47:20 +02:00
|
|
|
'XF-Api-Key' => $this->apiKey,
|
|
|
|
|
'XF-Api-User' => $customUserId ?? $this->superUserId,
|
|
|
|
|
])->get("{$this->apiUrl}/{$endpoint}");
|
|
|
|
|
|
2026-06-25 13:06:14 +02:00
|
|
|
if( !$response->ok() ) {
|
|
|
|
|
\Illuminate\Support\Facades\Log::error("XF API error [{$response->status()}] on {$endpoint}: " . $response->body());
|
2026-05-24 11:47:20 +02:00
|
|
|
return null;
|
2026-06-25 13:06:14 +02:00
|
|
|
}
|
2026-05-24 11:47:20 +02:00
|
|
|
|
|
|
|
|
return $response->json();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function post(string $endpoint, ?int $customUserId = null, array $data = [] ): mixed
|
|
|
|
|
{
|
2026-06-25 13:40:34 +02:00
|
|
|
$response = Http::timeout(30)->withOptions(['verify' => false])->withHeaders([
|
2026-05-24 11:47:20 +02:00
|
|
|
'XF-Api-Key' => $this->apiKey,
|
|
|
|
|
'XF-Api-User' => $customUserId ?? $this->superUserId,
|
|
|
|
|
])->post("{$this->apiUrl}/{$endpoint}", $data);
|
|
|
|
|
|
2026-06-25 13:06:14 +02:00
|
|
|
if( !$response->ok() ) {
|
|
|
|
|
\Illuminate\Support\Facades\Log::error("XF API error [{$response->status()}] on {$endpoint}: " . $response->body());
|
2026-05-24 11:47:20 +02:00
|
|
|
return null;
|
2026-06-25 13:06:14 +02:00
|
|
|
}
|
2026-05-24 11:47:20 +02:00
|
|
|
|
|
|
|
|
return $response->json();
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-08 16:25:52 +02:00
|
|
|
private function delete(string $endpoint, ?int $customUserId = null, array $data = [] ): mixed
|
|
|
|
|
{
|
2026-06-25 13:40:34 +02:00
|
|
|
$response = Http::timeout(30)->withOptions(['verify' => false])->withHeaders([
|
2026-06-08 16:25:52 +02:00
|
|
|
'XF-Api-Key' => $this->apiKey,
|
|
|
|
|
'XF-Api-User' => $customUserId ?? $this->superUserId,
|
|
|
|
|
])->delete("{$this->apiUrl}/{$endpoint}", $data);
|
|
|
|
|
|
2026-06-25 13:06:14 +02:00
|
|
|
if( !$response->ok() ) {
|
|
|
|
|
\Illuminate\Support\Facades\Log::error("XF API error [{$response->status()}] on {$endpoint}: " . $response->body());
|
2026-06-08 16:25:52 +02:00
|
|
|
return null;
|
2026-06-25 13:06:14 +02:00
|
|
|
}
|
2026-06-08 16:25:52 +02:00
|
|
|
|
|
|
|
|
return $response->json();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-24 11:47:20 +02:00
|
|
|
public function getUserAlerts(int $userId): mixed
|
|
|
|
|
{
|
2026-05-25 09:55:47 +02:00
|
|
|
if( app(XenforoService::class)->getXfUser($userId)?->alerts_unviewed > 0 ) {
|
|
|
|
|
Cache::forget("xf_alerts_{$userId}");
|
|
|
|
|
}
|
2026-05-24 11:47:20 +02:00
|
|
|
|
|
|
|
|
return Cache::remember("xf_alerts_{$userId}", 60, function() use($userId) {
|
|
|
|
|
return $this->get("alerts?page=1&cutoff=7days", $userId);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function markAllNotificationsRead(int $userId): void
|
|
|
|
|
{
|
|
|
|
|
Cache::forget("xf_alerts_{$userId}");
|
2026-06-23 19:24:38 +02:00
|
|
|
$this->post("alerts/mark-all", $userId );
|
2026-05-24 11:47:20 +02:00
|
|
|
}
|
|
|
|
|
|
2026-05-25 09:55:47 +02:00
|
|
|
public function getConversations(int $userId): mixed
|
|
|
|
|
{
|
|
|
|
|
return Cache::remember("xf_conversations_{$userId}", 60, function() use($userId) {
|
|
|
|
|
return $this->get("conversations?page=1&receiver_id={$userId}", $userId);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 20:54:10 +02:00
|
|
|
public function createConversation( array $userIdList, string $title, string $message, bool $conversationOpen, bool $openInvite ): bool
|
|
|
|
|
{
|
2026-06-23 19:24:38 +02:00
|
|
|
$response = $this->post("conversations",
|
|
|
|
|
data: ['recipient_ids' => $userIdList, 'title' => $title, 'message' => $message, 'open_invite' => $openInvite, 'conversation_open' => $conversationOpen]
|
|
|
|
|
);
|
2026-06-02 20:54:10 +02:00
|
|
|
return $response['success'] ?? false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-16 16:21:43 +02:00
|
|
|
public function createCommentsThread( Entry|News $entry ): bool
|
2026-05-27 21:24:38 +02:00
|
|
|
{
|
|
|
|
|
if( !$entry->comments_thread_id || $entry->comments_thread_id <= 0 ){
|
|
|
|
|
$data = [
|
|
|
|
|
'node_id' => config('xenforo.comments_node_id'),
|
2026-06-30 16:12:17 +02:00
|
|
|
'title' => $entry->complete_title ?? $entry->title,
|
2026-05-27 21:24:38 +02:00
|
|
|
'message' => $entry->description,
|
2026-06-30 16:12:17 +02:00
|
|
|
'prefix_id' => config('xenforo.comments_prefixes')[$entry->type ?? "news"] ?? 1,
|
|
|
|
|
'custom_fields' => $entry->type ? [ 'entry_id' => $entry->id ] : [ 'news_id' => $entry->id ],
|
2026-05-27 21:24:38 +02:00
|
|
|
'discussion_open' => true,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// TODO: Flag must be removed.
|
|
|
|
|
$response = $this->post("threads?api_bypass_permissions=true", config('xenforo.bot_user_id'), $data );
|
|
|
|
|
if( $response['success'] === true ){
|
|
|
|
|
$commentsThreadId = $response['thread']['thread_id'];
|
|
|
|
|
$entry->update(['comments_thread_id' => $commentsThreadId]);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @throws ConnectionException
|
|
|
|
|
*/
|
|
|
|
|
public function getThreadPosts(int $threadId, int $page = 1 ): array
|
|
|
|
|
{
|
|
|
|
|
$response = $this->get("threads/{$threadId}/posts?page=$page");
|
|
|
|
|
if( !isset( $response['posts'] ) || $response['posts'] === [] )
|
|
|
|
|
return [ 'posts' => [], 'pagination' => null ];
|
|
|
|
|
|
|
|
|
|
return $response;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 20:54:10 +02:00
|
|
|
public function updateEntriesCount(int $entryCount, int $userId): bool
|
|
|
|
|
{
|
|
|
|
|
$response = $this->post("romhackplaza_entry/update_entry_count", data: ['count' => $entryCount, 'user_id' => $userId] );
|
|
|
|
|
return $response['success'] ?? false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-16 16:21:43 +02:00
|
|
|
public function featuredRequest( Entry $entry ): bool
|
|
|
|
|
{
|
|
|
|
|
$response = $this->post("romhackplaza_entry/featured", data: [
|
|
|
|
|
'entry_id' => $entry->id, 'user_id' => $entry->user_id, 'entry_title' => $entry->complete_title ?? $entry->title,
|
|
|
|
|
]);
|
|
|
|
|
return $response['success'] ?? false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-08 16:25:52 +02:00
|
|
|
public function deleteThreadWithEntry(int $threadId): bool
|
|
|
|
|
{
|
|
|
|
|
return (bool) $this->delete( "threads/{$threadId}", data: ['reason' => "Deletion with entry." ] );
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function restoreThreadWithEntry(int $threadId): bool
|
|
|
|
|
{
|
|
|
|
|
return (bool) $this->post("threads/{$threadId}/undelete" );
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-23 19:24:38 +02:00
|
|
|
public function _migrateUser(array $data): array
|
|
|
|
|
{
|
|
|
|
|
$response = $this->post("migrate/user", data: $data );
|
|
|
|
|
if( !$response || !$response['success'] )
|
|
|
|
|
return [false,false];
|
|
|
|
|
|
|
|
|
|
return [ $response['user_id'] ?? false, $response['password_set'] ?? false ];
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-24 11:47:20 +02:00
|
|
|
}
|