diff --git a/source/packages/plg_webservices_perfectpublisher/perfectpublisher.xml b/source/packages/plg_webservices_perfectpublisher/perfectpublisher.xml deleted file mode 100644 index fee522db..00000000 --- a/source/packages/plg_webservices_perfectpublisher/perfectpublisher.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - Web Services - Perfect Publisher - Moko Consulting - 2026-05-28 - Copyright (C) 2026 Moko Consulting. All rights reserved. - GPL-3.0-or-later - hello@mokoconsulting.tech - https://mokoconsulting.tech - 02.34.30-dev - Joomla Web Services API routes for Perfect Publisher (com_autotweet) — channels, posts, requests, rules, and feeds. - Moko\Plugin\WebServices\PerfectPublisher - - services - src - - diff --git a/source/packages/plg_webservices_perfectpublisher/services/provider.php b/source/packages/plg_webservices_perfectpublisher/services/provider.php deleted file mode 100644 index d7f8ced6..00000000 --- a/source/packages/plg_webservices_perfectpublisher/services/provider.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: Joomla.Plugin - * INGROUP: MokoWaaS - * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS - * PATH: /source/packages/plg_webservices_perfectpublisher/services/provider.php - * VERSION: 02.34.30 - * BRIEF: DI service provider for Perfect Publisher Web Services plugin - */ - -defined('_JEXEC') or die; - -use Joomla\CMS\Extension\PluginInterface; -use Joomla\CMS\Factory; -use Joomla\CMS\Plugin\PluginHelper; -use Joomla\DI\Container; -use Joomla\DI\ServiceProviderInterface; -use Joomla\Event\DispatcherInterface; -use Moko\Plugin\WebServices\PerfectPublisher\Extension\PerfectPublisherApi; - -return new class implements ServiceProviderInterface -{ - public function register(Container $container): void - { - $container->set( - PluginInterface::class, - function (Container $container) { - $dispatcher = $container->get(DispatcherInterface::class); - $plugin = new PerfectPublisherApi( - $dispatcher, - (array) PluginHelper::getPlugin('webservices', 'perfectpublisher') - ); - $plugin->setApplication(Factory::getApplication()); - return $plugin; - } - ); - } -}; diff --git a/source/packages/plg_webservices_perfectpublisher/src/Extension/PerfectPublisherApi.php b/source/packages/plg_webservices_perfectpublisher/src/Extension/PerfectPublisherApi.php deleted file mode 100644 index 5bdd432e..00000000 --- a/source/packages/plg_webservices_perfectpublisher/src/Extension/PerfectPublisherApi.php +++ /dev/null @@ -1,539 +0,0 @@ - - * - * SPDX-License-Identifier: GPL-3.0-or-later - * - * FILE INFORMATION - * DEFGROUP: Joomla.Plugin - * INGROUP: MokoWaaS - * REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoWaaS - * PATH: /source/packages/plg_webservices_perfectpublisher/src/Extension/PerfectPublisherApi.php - * VERSION: 02.34.30 - * BRIEF: Web Services API plugin for Perfect Publisher (com_autotweet) - */ - -namespace Moko\Plugin\WebServices\PerfectPublisher\Extension; - -defined('_JEXEC') or die; - -use Joomla\CMS\Factory; -use Joomla\CMS\Plugin\CMSPlugin; -use Joomla\CMS\Event\Application\BeforeApiRouteEvent; -use Joomla\CMS\Router\ApiRouter; -use Joomla\Event\SubscriberInterface; - -/** - * Perfect Publisher Web Services API Plugin - * - * Registers REST API routes for Perfect Publisher (com_autotweet) data. - * Provides read access to channels, posts, requests, rules, and feeds. - * Provides write access to create publish requests. - * - * Routes: - * GET /v1/perfectpublisher/channels List social channels - * GET /v1/perfectpublisher/channels/:id Get channel detail - * GET /v1/perfectpublisher/posts List published posts - * GET /v1/perfectpublisher/posts/:id Get post detail - * GET /v1/perfectpublisher/requests List pending requests - * POST /v1/perfectpublisher/requests Create a publish request - * GET /v1/perfectpublisher/rules List publishing rules - * GET /v1/perfectpublisher/feeds List RSS feeds - * GET /v1/perfectpublisher/channeltypes List channel type definitions - * GET /v1/perfectpublisher/stats Dashboard statistics - * - * @since 02.13.01 - */ -final class PerfectPublisherApi extends CMSPlugin implements SubscriberInterface -{ - /** - * @return array - */ - public static function getSubscribedEvents(): array - { - return [ - 'onBeforeApiRoute' => 'onBeforeApiRoute', - ]; - } - - /** - * Register API routes. - * - * @param BeforeApiRouteEvent $event The API route event - * - * @return void - */ - public function onBeforeApiRoute(BeforeApiRouteEvent $event): void - { - $router = $event->getRouter(); - - // All routes are handled by this plugin directly via custom callbacks - // because com_autotweet uses FOF, not standard Joomla MVC - - $router->addRoute( - new \Joomla\Router\Route( - ['GET'], - 'v1/perfectpublisher/channels', - [$this, 'getChannels'] - ) - ); - - $router->addRoute( - new \Joomla\Router\Route( - ['GET'], - 'v1/perfectpublisher/channels/:id', - [$this, 'getChannel'] - ) - ); - - $router->addRoute( - new \Joomla\Router\Route( - ['GET'], - 'v1/perfectpublisher/posts', - [$this, 'getPosts'] - ) - ); - - $router->addRoute( - new \Joomla\Router\Route( - ['GET'], - 'v1/perfectpublisher/posts/:id', - [$this, 'getPost'] - ) - ); - - $router->addRoute( - new \Joomla\Router\Route( - ['GET'], - 'v1/perfectpublisher/requests', - [$this, 'getRequests'] - ) - ); - - $router->addRoute( - new \Joomla\Router\Route( - ['POST'], - 'v1/perfectpublisher/requests', - [$this, 'createRequest'] - ) - ); - - $router->addRoute( - new \Joomla\Router\Route( - ['GET'], - 'v1/perfectpublisher/rules', - [$this, 'getRules'] - ) - ); - - $router->addRoute( - new \Joomla\Router\Route( - ['GET'], - 'v1/perfectpublisher/feeds', - [$this, 'getFeeds'] - ) - ); - - $router->addRoute( - new \Joomla\Router\Route( - ['GET'], - 'v1/perfectpublisher/channeltypes', - [$this, 'getChannelTypes'] - ) - ); - - $router->addRoute( - new \Joomla\Router\Route( - ['GET'], - 'v1/perfectpublisher/stats', - [$this, 'getStats'] - ) - ); - } - - /** - * GET /v1/perfectpublisher/channels - * - * @return void - */ - public function getChannels(): void - { - $db = Factory::getDbo(); - $app = Factory::getApplication(); - $limit = (int) $app->input->get('limit', 20); - $offset = (int) $app->input->get('offset', 0); - - $query = $db->getQuery(true) - ->select('c.*, ct.name AS channeltype_name, ct.max_chars') - ->from($db->quoteName('#__autotweet_channels', 'c')) - ->leftJoin( - $db->quoteName('#__autotweet_channeltypes', 'ct') - . ' ON ' . $db->quoteName('c.channeltype_id') - . ' = ' . $db->quoteName('ct.id') - ) - ->order($db->quoteName('c.ordering') . ' ASC'); - - $published = $app->input->get('published', null); - if ($published !== null) { - $query->where($db->quoteName('c.published') . ' = ' . (int) $published); - } - - $db->setQuery($query, $offset, $limit); - - $this->sendJsonResponse($db->loadObjectList()); - } - - /** - * GET /v1/perfectpublisher/channels/:id - * - * @return void - */ - public function getChannel(): void - { - $id = (int) Factory::getApplication()->input->get('id', 0); - $db = Factory::getDbo(); - - $query = $db->getQuery(true) - ->select('c.*, ct.name AS channeltype_name, ct.max_chars, ct.description AS channeltype_desc') - ->from($db->quoteName('#__autotweet_channels', 'c')) - ->leftJoin( - $db->quoteName('#__autotweet_channeltypes', 'ct') - . ' ON ' . $db->quoteName('c.channeltype_id') - . ' = ' . $db->quoteName('ct.id') - ) - ->where($db->quoteName('c.id') . ' = ' . $id); - - $db->setQuery($query); - $result = $db->loadObject(); - - if (!$result) { - $this->sendJsonError('Channel not found', 404); - return; - } - - // Strip sensitive OAuth params - if (isset($result->params)) { - $params = json_decode($result->params, true); - if (is_array($params)) { - foreach (['access_token', 'access_secret', 'client_secret', 'api_secret', 'password'] as $key) { - if (isset($params[$key])) { - $params[$key] = '***'; - } - } - $result->params = json_encode($params); - } - } - - $this->sendJsonResponse($result); - } - - /** - * GET /v1/perfectpublisher/posts - * - * @return void - */ - public function getPosts(): void - { - $db = Factory::getDbo(); - $app = Factory::getApplication(); - $limit = (int) $app->input->get('limit', 20); - $offset = (int) $app->input->get('offset', 0); - - $query = $db->getQuery(true) - ->select('p.*, c.name AS channel_name') - ->from($db->quoteName('#__autotweet_posts', 'p')) - ->leftJoin( - $db->quoteName('#__autotweet_channels', 'c') - . ' ON ' . $db->quoteName('p.channel_id') - . ' = ' . $db->quoteName('c.id') - ) - ->order($db->quoteName('p.postdate') . ' DESC'); - - $pubstate = $app->input->get('pubstate', ''); - if ($pubstate !== '') { - $query->where($db->quoteName('p.pubstate') . ' = ' . $db->quote($pubstate)); - } - - $channel = (int) $app->input->get('channel_id', 0); - if ($channel > 0) { - $query->where($db->quoteName('p.channel_id') . ' = ' . $channel); - } - - $db->setQuery($query, $offset, $limit); - - $this->sendJsonResponse($db->loadObjectList()); - } - - /** - * GET /v1/perfectpublisher/posts/:id - * - * @return void - */ - public function getPost(): void - { - $id = (int) Factory::getApplication()->input->get('id', 0); - $db = Factory::getDbo(); - - $query = $db->getQuery(true) - ->select('p.*, c.name AS channel_name, ct.name AS channeltype_name') - ->from($db->quoteName('#__autotweet_posts', 'p')) - ->leftJoin( - $db->quoteName('#__autotweet_channels', 'c') - . ' ON ' . $db->quoteName('p.channel_id') - . ' = ' . $db->quoteName('c.id') - ) - ->leftJoin( - $db->quoteName('#__autotweet_channeltypes', 'ct') - . ' ON ' . $db->quoteName('c.channeltype_id') - . ' = ' . $db->quoteName('ct.id') - ) - ->where($db->quoteName('p.id') . ' = ' . $id); - - $db->setQuery($query); - $result = $db->loadObject(); - - if (!$result) { - $this->sendJsonError('Post not found', 404); - return; - } - - $this->sendJsonResponse($result); - } - - /** - * GET /v1/perfectpublisher/requests - * - * @return void - */ - public function getRequests(): void - { - $db = Factory::getDbo(); - $app = Factory::getApplication(); - $limit = (int) $app->input->get('limit', 20); - $offset = (int) $app->input->get('offset', 0); - - $query = $db->getQuery(true) - ->select('*') - ->from($db->quoteName('#__autotweet_requests')) - ->order($db->quoteName('publish_up') . ' ASC'); - - $published = $app->input->get('published', null); - if ($published !== null) { - $query->where($db->quoteName('published') . ' = ' . (int) $published); - } - - $db->setQuery($query, $offset, $limit); - - $this->sendJsonResponse($db->loadObjectList()); - } - - /** - * POST /v1/perfectpublisher/requests - * - * Create a new publish request. Required fields: description. - * Optional: url, image_url, publish_up, plugin, priority. - * - * @return void - */ - public function createRequest(): void - { - $app = Factory::getApplication(); - $db = Factory::getDbo(); - $data = json_decode($app->input->json->getRaw(), true); - - if (empty($data['description'])) { - $this->sendJsonError('Field "description" is required', 400); - return; - } - - $now = Factory::getDate()->toSql(); - $user = Factory::getUser(); - - $row = (object) [ - 'ref_id' => $data['ref_id'] ?? null, - 'plugin' => $data['plugin'] ?? 'manual-api', - 'priority' => (int) ($data['priority'] ?? 5), - 'publish_up' => $data['publish_up'] ?? $now, - 'description' => $data['description'], - 'typeinfo' => (int) ($data['typeinfo'] ?? 0), - 'url' => $data['url'] ?? null, - 'image_url' => $data['image_url'] ?? null, - 'created' => $now, - 'created_by' => $user->id, - 'params' => json_encode($data['params'] ?? []), - 'published' => (int) ($data['published'] ?? 1), - ]; - - $db->insertObject('#__autotweet_requests', $row, 'id'); - - $this->sendJsonResponse( - ['id' => $row->id, 'status' => 'created'], - 201 - ); - } - - /** - * GET /v1/perfectpublisher/rules - * - * @return void - */ - public function getRules(): void - { - $db = Factory::getDbo(); - - $query = $db->getQuery(true) - ->select('r.*, rt.name AS ruletype_name, rt.description AS ruletype_desc, c.name AS channel_name') - ->from($db->quoteName('#__autotweet_rules', 'r')) - ->leftJoin( - $db->quoteName('#__autotweet_ruletypes', 'rt') - . ' ON ' . $db->quoteName('r.ruletype_id') - . ' = ' . $db->quoteName('rt.id') - ) - ->leftJoin( - $db->quoteName('#__autotweet_channels', 'c') - . ' ON ' . $db->quoteName('r.channel_id') - . ' = ' . $db->quoteName('c.id') - ) - ->order($db->quoteName('r.ordering') . ' ASC'); - - $db->setQuery($query); - - $this->sendJsonResponse($db->loadObjectList()); - } - - /** - * GET /v1/perfectpublisher/feeds - * - * @return void - */ - public function getFeeds(): void - { - $db = Factory::getDbo(); - - $query = $db->getQuery(true) - ->select('*') - ->from($db->quoteName('#__autotweet_feeds')) - ->order($db->quoteName('ordering') . ' ASC'); - - $db->setQuery($query); - - $this->sendJsonResponse($db->loadObjectList()); - } - - /** - * GET /v1/perfectpublisher/channeltypes - * - * @return void - */ - public function getChannelTypes(): void - { - $db = Factory::getDbo(); - - $query = $db->getQuery(true) - ->select('*') - ->from($db->quoteName('#__autotweet_channeltypes')) - ->order($db->quoteName('id') . ' ASC'); - - $db->setQuery($query); - - $this->sendJsonResponse($db->loadObjectList()); - } - - /** - * GET /v1/perfectpublisher/stats - * - * Dashboard statistics: post counts by status, channel counts, recent activity. - * - * @return void - */ - public function getStats(): void - { - $db = Factory::getDbo(); - - // Posts by status - $db->setQuery( - $db->getQuery(true) - ->select('pubstate, COUNT(*) AS total') - ->from($db->quoteName('#__autotweet_posts')) - ->group($db->quoteName('pubstate')) - ); - $postsByStatus = $db->loadObjectList('pubstate'); - - // Active channels - $db->setQuery( - $db->getQuery(true) - ->select('COUNT(*) AS total') - ->from($db->quoteName('#__autotweet_channels')) - ->where($db->quoteName('published') . ' = 1') - ); - $activeChannels = (int) $db->loadResult(); - - // Pending requests - $db->setQuery( - $db->getQuery(true) - ->select('COUNT(*) AS total') - ->from($db->quoteName('#__autotweet_requests')) - ->where($db->quoteName('published') . ' = 1') - ); - $pendingRequests = (int) $db->loadResult(); - - // Posts last 24h - $db->setQuery( - $db->getQuery(true) - ->select('COUNT(*) AS total') - ->from($db->quoteName('#__autotweet_posts')) - ->where($db->quoteName('postdate') . ' >= DATE_SUB(NOW(), INTERVAL 1 DAY)') - ); - $posts24h = (int) $db->loadResult(); - - // Posts last 7d - $db->setQuery( - $db->getQuery(true) - ->select('COUNT(*) AS total') - ->from($db->quoteName('#__autotweet_posts')) - ->where($db->quoteName('postdate') . ' >= DATE_SUB(NOW(), INTERVAL 7 DAY)') - ); - $posts7d = (int) $db->loadResult(); - - $this->sendJsonResponse([ - 'posts_by_status' => $postsByStatus, - 'active_channels' => $activeChannels, - 'pending_requests' => $pendingRequests, - 'posts_24h' => $posts24h, - 'posts_7d' => $posts7d, - ]); - } - - /** - * Send a JSON API response. - * - * @param mixed $data Response data - * @param int $status HTTP status code - * - * @return void - */ - private function sendJsonResponse($data, int $status = 200): void - { - $app = Factory::getApplication(); - $app->setHeader('Content-Type', 'application/json; charset=utf-8'); - $app->setHeader('Status', (string) $status); - echo json_encode(['data' => $data], JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); - $app->close(); - } - - /** - * Send a JSON error response. - * - * @param string $message Error message - * @param int $status HTTP status code - * - * @return void - */ - private function sendJsonError(string $message, int $status = 400): void - { - $app = Factory::getApplication(); - $app->setHeader('Content-Type', 'application/json; charset=utf-8'); - $app->setHeader('Status', (string) $status); - echo json_encode(['error' => $message], JSON_UNESCAPED_SLASHES); - $app->close(); - } -} diff --git a/source/pkg_mokowaas.xml b/source/pkg_mokowaas.xml index e898479c..07365e9e 100644 --- a/source/pkg_mokowaas.xml +++ b/source/pkg_mokowaas.xml @@ -26,7 +26,6 @@ mod_mokowaas_cache.zip mod_mokowaas_categories.zip plg_webservices_mokowaas.zip - plg_webservices_perfectpublisher.zip plg_task_mokowaasdemo.zip plg_task_mokowaassync.zip plg_task_mokowaas_tickets.zip diff --git a/source/script.php b/source/script.php index c0c63580..5bca126c 100644 --- a/source/script.php +++ b/source/script.php @@ -498,8 +498,7 @@ class Pkg_MokowaasInstallerScript $db->quote('mokowaasdemo'), $db->quote('mokowaassync'), $db->quote('mokowaas_tickets'), - $db->quote('perfectpublisher'), - $db->quote('mokoonyx'), + $db->quote('mokoonyx'), ]; $query = $db->getQuery(true)