1.1.0 #1

Merged
RHPZAdmin merged 9 commits from Dev into master 2026-06-28 12:28:51 +00:00
37 changed files with 654 additions and 4 deletions
Showing only changes of commit 73471162bf - Show all commits

View File

@@ -1,6 +1,7 @@
<?php
namespace RomhackPlaza\Master\Api\Controller;
use RomhackPlaza\Master\Entity\EntryFeaturedRequest;
use XF\Api\Controller\AbstractController;
use XF\Mvc\Reply\AbstractReply;
@@ -22,4 +23,23 @@ class EntryController extends AbstractController
return $this->apiSuccess(['success' => true,'user_id' => $user->user_id,'count' => $count]);
}
public function actionPostFeatured(): AbstractReply
{
$entryId = $this->filter('entry_id', 'uint');
$userId = $this->filter('user_id', 'uint');
$entryTitle = $this->filter('entry_title', 'string');
/** @var EntryFeaturedRequest $entryFeaturedRequest */
$entryFeaturedRequest = $this->em()->create('RomhackPlaza\Master:EntryFeaturedRequest');
$entryFeaturedRequest->entry_id = $entryId;
$entryFeaturedRequest->user_id = $userId;
$entryFeaturedRequest->entry_title = $entryTitle;
$entryFeaturedRequest->featured_request_state = 'moderated';
$entryFeaturedRequest->save();
return $this->apiSuccess(['success' => true]);
}
}

View File

@@ -63,7 +63,7 @@ class Club extends AbstractHandler
}
}
public function actionReject(\RomhackPlaza\Master\Entity\Club $club)
public function actionDelete(\RomhackPlaza\Master\Entity\Club $club)
{
$club->club_state = 'rejected';
$club->save();

View File

@@ -0,0 +1,61 @@
<?php
namespace RomhackPlaza\Master\ApprovalQueue;
use RomhackPlaza\Master\Helper\Laravel;
use XF\ApprovalQueue\AbstractHandler;
use XF\Mvc\Entity\Entity;
class EntryFeaturedRequest extends AbstractHandler
{
protected function canViewContent(Entity $content, &$error = null)
{
return $this->canActionContent($content, $error );
}
protected function canActionContent(Entity $content, &$error = null)
{
return \XF::visitor()->hasPermission('romhackplaza', 'canModerateEntries');
}
public function getEntityWith()
{
return ['User'];
}
public function actionApprove(\RomhackPlaza\Master\Entity\EntryFeaturedRequest $entryFeaturedRequest)
{
try {
$db = Laravel::db();
$db->update('entries', [
'featured' => 1,
'featured_at' => date('Y-m-d H:i:s', \XF::$time),
], 'id = ?',
[$entryFeaturedRequest->entry_id]
);
$entryFeaturedRequest->featured_request_state = 'visible';
$entryFeaturedRequest->save();
} catch ( \Exception $e ) {
\XF::logException($e, messagePrefix: "EntryFeaturedRequest error: " );
}
}
public function actionDelete(\RomhackPlaza\Master\Entity\EntryFeaturedRequest $entryFeaturedRequest)
{
$entryFeaturedRequest->featured_request_state = 'rejected';
$entryFeaturedRequest->save();
}
public function getTemplateName()
{
return 'public:approval_item_entry_featured_request';
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace RomhackPlaza\Master\Entity;
use XF\Entity\ApprovalQueue;
use XF\Entity\User;
use XF\Mvc\Entity\Entity;
use XF\Mvc\Entity\Structure;
/**
* @property-read int featured_request_id
* @property int $entry_id
* @property int $user_id
* @property string $entry_title
* @property string featured_request_state
* @property int featured_request_date
* @property-read ?User User
*/
class EntryFeaturedRequest extends Entity
{
#[\Override]
public static function getStructure(Structure $structure)
{
$structure->shortName = 'RomhackPlaza\Master:EntryFeaturedRequest';
$structure->table = 'xf_romhackplaza_entry_featured_request';
$structure->primaryKey = 'featured_request_id';
$structure->columns = [
'featured_request_id' => ['type' => self::UINT, 'autoIncrement' => true],
'entry_id' => ['type' => self::UINT, 'required' => true],
'user_id' => ['type' => self::UINT],
'entry_title' => ['type' => self::STR, 'maxLength' => 255],
'featured_request_state' => ['type' => self::STR, 'default' => 'moderated',
'allowedValues' => ['visible', 'moderated', 'rejected']
],
'featured_request_date' => ['type' => self::UINT, 'default' => \XF::$time]
];
$structure->relations = [
'User' => [
'entity' => 'XF:User',
'type' => self::TO_ONE,
'conditions' => 'user_id',
'primary' => true
],
];
return $structure;
}
protected function _postSave()
{
if( $this->isInsert() && $this->featured_request_state === 'moderated' ) {
$this->inQueue();
} else if( $this->isUpdate() && $this->isChanged('featured_request_state') ) {
if( $this->featured_request_state === 'moderated' ) {
$this->inQueue();
} else {
$this->removeQueue();
}
}
}
protected function _postDelete()
{
if( $this->featured_request_state === 'moderated' ) {
$this->removeQueue();
}
}
protected function inQueue()
{
$queue = $this->em()->find('XF:ApprovalQueue', ['rhpz_entry_featurerequest', $this->featured_request_id ] );
if( !$queue )
{
/** @var ApprovalQueue $newQueue */
$newQueue = $this->em()->create('XF:ApprovalQueue');
$newQueue->content_type = 'rhpz_entry_featurerequest';
$newQueue->content_id = $this->featured_request_id;
$newQueue->save();
}
}
protected function removeQueue()
{
$queue = $this->em()->find('XF:ApprovalQueue', ['rhpz_entry_featurerequest', $this->featured_request_id ] );
if( $queue )
$queue->delete();
}
}

26
Entity/News.php Normal file
View File

@@ -0,0 +1,26 @@
<?php
namespace RomhackPlaza\Master\Entity;
use XF\Mvc\Entity\Entity;
use XF\Mvc\Entity\Structure;
class News extends Entity
{
#[Override]
public static function getStructure(Structure $structure)
{
$structure->shortName = 'RomhackPlaza\Master:News';
$structure->table = 'xf_romhackplaza_news'; // Unused.
$structure->primaryKey = 'id';
$structure->columns = [
'id' => ['type' => self::UINT],
'user_id' => ['type' => self::UINT, 'required' => true],
'title' => ['type' => self::STR, 'required' => true],
'slug' => ['type' => self::STR, 'required' => true],
];
return $structure;
}
}

25
Helper/Helpers.php Normal file
View File

@@ -0,0 +1,25 @@
<?php
namespace RomhackPlaza\Master\Helper;
use XF\Mvc\Entity\Entity;
class Helpers
{
public static function getContentTitle( Entity $content, string $contentType )
{
return match ($contentType) {
'thread' => $content->title ?? '',
'post' => $content->Thread?->title ?? '',
'user' => $content->username ?? '',
'profile_post' => \XF\Util\Str::substr($content->content ?? '', 0, 50),
'profile_post_comment' => \XF\Util\Str::substr($content->content ?? '', 0, 50),
// Custom
'club' => $content->title ?? '',
'rhpz_entry_featurerequest' => $content->entry_title ?? '',
default => $content->title ?? $content->username ?? $content->content ?? "#{$content->getEntityId()}",
};
}
}

50
Pub/Controller/News.php Normal file
View File

@@ -0,0 +1,50 @@
<?php
namespace RomhackPlaza\Master\Pub\Controller;
use XF\Mvc\ParameterBag;
use XF\Mvc\Reply\AbstractReply;
use XF\Pub\Controller\AbstractController;
class News extends AbstractController {
public function actionIndex(ParameterBag $params): AbstractReply
{
$entryId = $params->id;
$laravelDb = \RomhackPlaza\Master\Helper\Laravel::db();
if( !$laravelDb )
return $this->notFound();
$entry = $laravelDb->fetchRow("SELECT id, slugFROM news WHERE id = ?", $entryId);
if( !$entry )
return $this->notFound();
return $this->redirect(\XF::options()->homePageUrl . '/news/' . $entry['slug']);
}
public function actionReport(ParameterBag $params): AbstractReply
{
$this->assertRegistrationRequired();
$entryId = $params->id;
$laravelDb = \RomhackPlaza\Master\Helper\Laravel::db();
if( !$laravelDb )
return $this->notFound();
$entry = $laravelDb->fetchRow("SELECT id, title, slug, user_id, description FROM news WHERE id = ?", $entryId);
if( !$entry )
return $this->notFound();
$entity = $this->em()->instantiateEntity('RomhackPlaza\Master:News', $entry);
$entity->setReadOnly(true);
$reportPlugin = $this->plugin('XF:Report');
return $reportPlugin->actionReport(
'romhackplaza_news', $entity,
$this->buildLink('romhackplaza_news/report', $entity),
\XF::options()->homePageUrl . '/news/report_redirect?id=' . $entity->id
);
}
}

View File

@@ -65,7 +65,7 @@ class Entry extends AbstractHandler
#[Override]
public function getContentMessage(Report $report)
{
return $report->content_info['message'];
return $report->content_info['description'];
}
#[Override]

75
Report/News.php Normal file
View File

@@ -0,0 +1,75 @@
<?php
namespace RomhackPlaza\Master\Report;
use XF\Entity\Report;
use Override;
use XF\Mvc\Entity\Entity;
use XF\Report\AbstractHandler;
class News extends AbstractHandler
{
#[Override]
protected function canViewContent(Report $report)
{
return \XF::visitor()->hasPermission('romhackplaza', 'view');
}
#[Override]
protected function canActionContent(Report $report)
{
return \XF::visitor()->hasPermission('romhackplaza', 'canEditOthersEntries');
}
#[Override]
public function setupReportEntityContent(Report $report, Entity $content)
{
$report->content_user_id = $content['user_id'];
$report->content_info = [
'id' => $content['id'],
'title' => $content['title'],
'slug' => $content['slug'],
'user_id' => $content['user_id'],
'description' => $content['description'] ?? "",
];
}
public function getContent($id)
{
$laravelDb = \RomhackPlaza\Master\Helper\Laravel::db();
if( $laravelDb ){
$entry = $laravelDb->fetchRow("SELECT id, title, slug, user_id, description FROM news WHERE id = ?", $id);
$entity = \XF::em()->instantiateEntity('RomhackPlaza\Master:News', $entry);
$entity->setReadOnly(true);
return $entity;
}
return null;
}
#[Override]
public function getContentTitle(Report $report)
{
return $report->content_info['title'] ?? 'Unknown';
}
#[Override]
public function getContentLink(Report $report)
{
return \XF::options()->homePageUrl . '/news/' . $report->content_info['slug'];
}
#[Override]
public function getContentMessage(Report $report)
{
return $report->content_info['description'];
}
#[Override]
public function getTemplateName()
{
return "public:report_content_romhackplaza_news";
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace RomhackPlaza\Master\Service\Discord;
use XF\Service\AbstractService;
class BotService extends AbstractService
{
protected string $dbPath;
protected ?\PDO $pdo = null;
protected function setup()
{
$this->dbPath = \XF::config('discord_db_path');
}
protected function getDb(): \PDO
{
if (!$this->pdo) {
$this->pdo = new \PDO("sqlite:" . $this->dbPath);
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$this->pdo->exec('PRAGMA journal_mode=WAL;');
$this->pdo->exec('PRAGMA busy_timeout=5000;');
}
return $this->pdo;
}
public function createAction( string $action, array $data ): bool
{
try {
$db = $this->getDb();
$stmt = $db->prepare("INSERT INTO actions (action, data) VALUES (:action, :data)");
return $stmt->execute([
':action' => $action,
':data' => json_encode($data)
]);
} catch (\PDOException $e) {
\XF::logException($e, messagePrefix: "BotService error: ");
return false;
}
}
}

View File

@@ -44,6 +44,18 @@ class Setup extends AbstractSetup
});
}
public function installStep3(): void
{
$this->schemaManager()->createTable('xf_romhackplaza_entry_featured_request', function(Create $table){
$table->addColumn('featured_request_id', 'int')->autoIncrement();
$table->addColumn('entry_id', 'int');
$table->addColumn('user_id', 'int');
$table->addColumn('entry_title', 'varchar', 255);
$table->addColumn('featured_request_state', 'enum')->values(['visible','moderated','rejected'])->setDefault('moderated');
$table->addColumn('featured_request_date', 'int');
});
}
public function uninstallStep1(): void
{
$this->schemaManager()->alterTable('xf_user', function($table) {
@@ -55,4 +67,9 @@ class Setup extends AbstractSetup
{
$this->schemaManager()->dropTable('xf_club');
}
public function uninstallStep3(): void
{
$this->schemaManager()->dropTable('xf_romhackplaza_entry_featured_request');
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace RomhackPlaza\Master\XF\Entity;
use RomhackPlaza\Master\Helper\Helpers;
class ApprovalQueue extends XFCP_ApprovalQueue
{
protected function _postSave()
{
parent::_postSave();
if( $this->isInsert() ){
$contentId = $this->content_id;
$contentType = $this->content_type;
\XF::runLater(function () use ($contentId, $contentType) {
$entity = \XF::app()->getContentTypeFieldValue($contentType, 'entity');
if( !$entity ){
return;
}
$content = \XF::em()->find($entity, $contentId);
$user = $contentType === 'user' ? $content : $content->User;
$service = \XF::service('RomhackPlaza\Master:Discord\BotService');
$service->createAction( 'approval_queue', [
'content_title' => Helpers::getContentTitle( $content, $contentType ),
'approval_item_url' => \XF::app()->router('public')->buildLink('canonical:approval-queue' ),
'approval_username' => $user?->username ?? '',
'approval_user_url' => $user?->user_id ? \XF::app()->router('public')->buildLink('canonical:members', $user ) : '',
'content_type' => $contentType,
]);
});
}
}
}

View File

@@ -15,4 +15,9 @@ class User extends XFCP_User
return $structure;
}
public function canCreateEntry()
{
return \XF::visitor()->hasPermission('romhackplaza', 'canSubmitEntry');
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace RomhackPlaza\Master\XF\Pub\Controller;
use XF\Mvc\ParameterBag;
class MiscController extends XFCP_MiscController
{
public function actionPreferencesPopup()
{
return $this->view('RomhackPlaza\Master:Misc\PreferencesPopup', 'misc_preferences_popup');
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace RomhackPlaza\Master\XF\Service\Report;
use XF\Entity\Report;
use XF\Repository\ReportRepository;
class CreatorService extends XFCP_CreatorService
{
protected function _save()
{
/** @var Report $report */
$report = parent::_save();
if( $report ){
\XF::runLater(function () use ($report){
$reportRepo = $this->repository(ReportRepository::class);
$handler = $reportRepo->getReportHandler($report->content_type, true);
$service = \XF::service('RomhackPlaza\Master:Discord\BotService');
$service->createAction('report_message', [
'content_title' => $handler->getContentTitle( $report ),
'report_message' => $report->Comments->last()->message,
'report_manage_url' => \XF::app()->router('public')->buildLink('canonical:reports', $report ),
'report_username' => $report->User?->username ?? "Unknown user",
'report_user_url' => \XF::app()->router('public')->buildLink('canonical:members', $report->User ),
'content_type' => $report->content_type,
'content_link' => $handler->getContentLink( $report )
]);
});
}
return $report;
}
}

View File

@@ -0,0 +1,6 @@
{
"from_class": "XF\\Entity\\ApprovalQueue",
"to_class": "RomhackPlaza\\Master\\XF\\Entity\\ApprovalQueue",
"execute_order": 10,
"active": true
}

View File

@@ -0,0 +1,6 @@
{
"from_class": "XF\\Pub\\Controller\\MiscController",
"to_class": "RomhackPlaza\\Master\\XF\\Pub\\Controller\\MiscController",
"execute_order": 10,
"active": true
}

View File

@@ -0,0 +1,6 @@
{
"from_class": "XF\\Service\\Report\\CreatorService",
"to_class": "RomhackPlaza\\Master\\XF\\Service\\Report\\CreatorService",
"execute_order": 10,
"active": true
}

View File

@@ -1,8 +1,17 @@
{
"XF-Entity-ApprovalQueue_RomhackPlaza-Master-XF-Entity-ApprovalQueue.json": {
"hash": "f4364e1ec74dfa80ff24368486e66ffb"
},
"XF-Entity-Forum_RomhackPlaza-Master-XF-Entity-Forum.json": {
"hash": "c7747b6ea6bd924d2d02e82917ff0df3"
},
"XF-Entity-User_RomhackPlaza-Master-XF-Entity-User.json": {
"hash": "811593d6f012a53b9fa2ced871de96a9"
},
"XF-Pub-Controller-MiscController_RomhackPlaza-Master-XF-Pub-Controller-MiscController.json": {
"hash": "1071fd498b2958031153de55eb4ca66a"
},
"XF-Service-Report-CreatorService_RomhackPlaza-Master-XF-Service-Report-CreatorService.json": {
"hash": "2def6639766241a5f2ff3c10fc168731"
}
}

View File

@@ -5,10 +5,22 @@
"club-entity.json": {
"hash": "ed572323131987e295051165e4ec3c5e"
},
"rhpz_entry_featurerequest-approval_queue_handler_class.json": {
"hash": "0452fab1abce77fa92858ee46c615d8a"
},
"rhpz_entry_featurerequest-entity.json": {
"hash": "62ab52f94cdc49ced9c18dd31eaf7f8c"
},
"romhackplaza_entry-entity.json": {
"hash": "8686ffd261eb8c1698a3ec6e31118f02"
},
"romhackplaza_entry-report_handler_class.json": {
"hash": "3e3d009b1b4671a506accc2e920307b2"
},
"romhackplaza_news-entity.json": {
"hash": "97f9641ceb338c2fdd489e2eed7fb7d1"
},
"romhackplaza_news-report_handler_class.json": {
"hash": "8af48d739b42b8794fd802eaf72e2025"
}
}

View File

@@ -0,0 +1,5 @@
{
"content_type": "rhpz_entry_featurerequest",
"field_name": "approval_queue_handler_class",
"field_value": "RomhackPlaza\\Master\\ApprovalQueue\\EntryFeaturedRequest"
}

View File

@@ -0,0 +1,5 @@
{
"content_type": "rhpz_entry_featurerequest",
"field_name": "entity",
"field_value": "RomhackPlaza\\Master\\Entity\\EntryFeaturedRequest"
}

View File

@@ -0,0 +1,5 @@
{
"content_type": "romhackplaza_news",
"field_name": "entity",
"field_value": "RomhackPlaza\\Master\\Entity\\News"
}

View File

@@ -0,0 +1,5 @@
{
"content_type": "romhackplaza_news",
"field_name": "report_handler_class",
"field_value": "RomhackPlaza\\Master\\Report\\News"
}

View File

@@ -10,6 +10,17 @@
namespace RomhackPlaza\Master\XF\Entity
{
class XFCP_ApprovalQueue extends \XF\Entity\ApprovalQueue {}
class XFCP_Forum extends \XF\Entity\Forum {}
class XFCP_User extends \XF\Entity\User {}
}
namespace RomhackPlaza\Master\XF\Pub\Controller
{
class XFCP_MiscController extends \XF\Pub\Controller\MiscController {}
}
namespace RomhackPlaza\Master\XF\Service\Report
{
class XFCP_CreatorService extends \XF\Service\Report\CreatorService {}
}

View File

@@ -35,6 +35,9 @@
"members.json": {
"hash": "cad1568d161bea1a7dd1f286b8788e2d"
},
"news.json": {
"hash": "c42353e9a510b365d09f2b64af401e55"
},
"pages.json": {
"hash": "8d75bf99b2fb6528f84433f4519d4da3"
},
@@ -45,7 +48,7 @@
"hash": "e14613a1bd0f1e1fadc084a009801570"
},
"rom_patcher.json": {
"hash": "2a2af404aa6395b02c711867a7287a6f"
"hash": "dff75392eea1fdffdaec193913d5d8a0"
},
"submissions_queue.json": {
"hash": "071c745afd8f28cf9505702496f72a18"

View File

@@ -0,0 +1,13 @@
{
"parent_navigation_id": "website",
"display_order": 250,
"navigation_type_id": "basic",
"type_config": {
"link": "{$xf.options.homePageUrl}/news",
"display_condition": "",
"extra_attributes": {
"icon": "newspaper"
}
},
"enabled": true
}

View File

@@ -3,7 +3,7 @@
"display_order": 100,
"navigation_type_id": "basic",
"type_config": {
"link": "{$xf.options.homePageUrl}/tools/rom-patcher",
"link": "{$xf.options.homePageUrl}/patch",
"display_condition": "",
"extra_attributes": {
"icon": "stamp"

View File

@@ -71,6 +71,12 @@
"version_string": "1.0.0",
"hash": "ef53538ae41a651c7f72ab6cb1135d8c"
},
"nav.news.txt": {
"global_cache": false,
"version_id": 1000000,
"version_string": "1.0.0",
"hash": "dd1ba1872df91985ed1ca4cde2dfe669"
},
"nav.pages.txt": {
"global_cache": false,
"version_id": 1000000,

View File

@@ -0,0 +1 @@
News

View File

@@ -10,5 +10,8 @@
},
"public_romhackplaza_entry_.json": {
"hash": "2c40d7266fcc22a5727830a69af2360c"
},
"public_romhackplaza_news_.json": {
"hash": "a012e23a91029aead56b061747d0334a"
}
}

View File

@@ -0,0 +1,11 @@
{
"route_type": "public",
"route_prefix": "romhackplaza_news",
"sub_name": "",
"format": ":int<id>",
"build_class": "",
"build_method": "",
"controller": "RomhackPlaza\\Master:News",
"context": "",
"action_prefix": ""
}

View File

@@ -7,5 +7,8 @@
},
"public/rhpz_category_view_clubs_submit_button.json": {
"hash": "a24a5a4b130b6198de7ef37610aa1a7d"
},
"public/rhpz_forum_overview_wrapper_search_button.json": {
"hash": "af109f16aca8a12c4f8496fc4431f4b8"
}
}

View File

@@ -0,0 +1,9 @@
{
"template": "forum_overview_wrapper",
"description": "Add Search button",
"execution_order": 10,
"enabled": true,
"action": "str_replace",
"find": "\t\t<xf:button href=\"{{ $xf.options.forumsDefaultPage == 'new_posts' ? link('forums/new-posts') : link('whats-new/posts') }}\" icon=\"bolt\">\n\t\t\t{{ phrase('new_posts') }}\n\t\t</xf:button>",
"replace": "\t\t$0\n\t\t<xf:button href=\"{{ link('search') }}\" icon=\"magnifying-glass\">\n\t\t\t{{ phrase('search') }}\n\t\t</xf:button>"
}

View File

@@ -9,6 +9,11 @@
"version_string": "1.0.0",
"hash": "da1f9c78c4e33597062258c0bba3cb4d"
},
"public/approval_item_entry_featured_request.html": {
"version_id": 1000000,
"version_string": "1.0.0",
"hash": "1383f7fa1680c2da22cfc08aa1ad55f5"
},
"public/club_delete_confirm.html": {
"version_id": 1000000,
"version_string": "1.0.0",
@@ -28,5 +33,10 @@
"version_id": 1000000,
"version_string": "1.0.0",
"hash": "d6c5c25a9af81a7da2bd8e2b9f74229e"
},
"public/report_content_romhackplaza_news.html": {
"version_id": 1000000,
"version_string": "1.0.0",
"hash": "d6c5c25a9af81a7da2bd8e2b9f74229e"
}
}

View File

@@ -0,0 +1,11 @@
<xf:macro id="approval_queue_macros::item_message_type"
arg-content="{$content}"
arg-user="{$content.User}"
arg-messageHtml="{$content.entry_title}"
arg-contentDate="{$content.featured_request_date}"
arg-typePhraseHtml="Featured Request"
arg-headerPhrase="{$content.entry_title}"
arg-spamDetails="{$spamDetails}"
arg-unapprovedItem="{$unapprovedItem}"
arg-handler="{$handler}"
/>

View File

@@ -0,0 +1,4 @@
<div class="block-row block-row--separated">
{{ phrase('content:') }}
{$report.content_info.description}
</div>