diff --git a/src/packages/com_mokowaas/admin/language/en-GB/com_mokowaas.ini b/src/packages/com_mokowaas/admin/language/en-GB/com_mokowaas.ini index d9264ac6..1a2eb70b 100644 --- a/src/packages/com_mokowaas/admin/language/en-GB/com_mokowaas.ini +++ b/src/packages/com_mokowaas/admin/language/en-GB/com_mokowaas.ini @@ -16,3 +16,6 @@ COM_MOKOWAAS_CONFIGURE="Configure" COM_MOKOWAAS_TOGGLE_SUCCESS="Plugin state updated." COM_MOKOWAAS_TOGGLE_FAIL="Failed to update plugin state." COM_MOKOWAAS_CACHE_CLEARED="Cache cleared successfully." +COM_MOKOWAAS_EXTENSIONS_TITLE="Moko Extensions" +COM_MOKOWAAS_EXTENSIONS_INFO="Install and manage Moko Consulting Joomla packages. Extensions are downloaded from the official Gitea release server." +COM_MOKOWAAS_EXTENSIONS_LINK="Moko Extensions" diff --git a/src/packages/com_mokowaas/admin/src/Controller/DisplayController.php b/src/packages/com_mokowaas/admin/src/Controller/DisplayController.php index f00ee4e2..b6bdac57 100644 --- a/src/packages/com_mokowaas/admin/src/Controller/DisplayController.php +++ b/src/packages/com_mokowaas/admin/src/Controller/DisplayController.php @@ -88,4 +88,38 @@ class DisplayController extends BaseController echo json_encode($result); $app->close(); } + + /** + * Install a Moko extension from a download URL. + */ + public function installExtension() + { + Session::checkToken() or die(Text::_('JINVALID_TOKEN')); + + $app = Factory::getApplication(); + $user = $app->getIdentity(); + + if (!$user->authorise('core.admin')) + { + $app->setHeader('Content-Type', 'application/json'); + echo json_encode(['success' => false, 'message' => Text::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN')]); + $app->close(); + } + + $downloadUrl = $app->getInput()->getString('download_url', ''); + + if (empty($downloadUrl)) + { + $app->setHeader('Content-Type', 'application/json'); + echo json_encode(['success' => false, 'message' => 'Missing download URL.']); + $app->close(); + } + + $model = $this->getModel('Extensions'); + $result = $model->installFromUrl($downloadUrl); + + $app->setHeader('Content-Type', 'application/json'); + echo json_encode($result); + $app->close(); + } } diff --git a/src/packages/com_mokowaas/admin/src/Model/ExtensionsModel.php b/src/packages/com_mokowaas/admin/src/Model/ExtensionsModel.php new file mode 100644 index 00000000..6fbbb641 --- /dev/null +++ b/src/packages/com_mokowaas/admin/src/Model/ExtensionsModel.php @@ -0,0 +1,292 @@ + [ + 'label' => 'MokoWaaS', + 'description' => 'Admin dashboard, security firewall, tenant restrictions, health monitoring, and REST API.', + 'element' => 'pkg_mokowaas', + 'type' => 'package', + 'icon' => 'icon-shield-alt', + 'category' => 'Platform', + ], + 'MokoOnyx' => [ + 'label' => 'MokoOnyx', + 'description' => 'Modern Joomla site template with dark mode, custom layouts, and MokoWaaS integration.', + 'element' => 'mokoonyx', + 'type' => 'template', + 'icon' => 'icon-paint-brush', + 'category' => 'Templates', + ], + 'MokoCassiopeia' => [ + 'label' => 'MokoCassiopeia', + 'description' => 'Enhancement layer for Joomla\'s Cassiopeia template.', + 'element' => 'mokocassiopeia', + 'type' => 'template', + 'icon' => 'icon-paint-brush', + 'category' => 'Templates', + ], + 'MokoJoomTOS' => [ + 'label' => 'MokoJoomTOS', + 'description' => 'Terms of Service and privacy policy component with consent tracking.', + 'element' => 'com_mokojoomtos', + 'type' => 'component', + 'icon' => 'icon-file-contract', + 'category' => 'Components', + ], + 'MokoJoomHero' => [ + 'label' => 'MokoJoomHero', + 'description' => 'Random hero image module from a configurable folder.', + 'element' => 'mod_mokojoomhero', + 'type' => 'module', + 'icon' => 'icon-image', + 'category' => 'Modules', + ], + 'MokoWaaSAnnounce' => [ + 'label' => 'MokoWaaS Announce', + 'description' => 'Centralized announcement system via admin module.', + 'element' => 'mod_mokowaas_announce', + 'type' => 'module', + 'icon' => 'icon-bullhorn', + 'category' => 'Modules', + ], + 'MokoDPCalendarAPI' => [ + 'label' => 'DPCalendar API', + 'description' => 'Web Services plugin exposing DPCalendar events and calendars via REST API.', + 'element' => 'mokodpcalendarapi', + 'type' => 'plugin', + 'icon' => 'icon-calendar', + 'category' => 'Plugins', + ], + 'MokoGalleryCalendar' => [ + 'label' => 'Gallery Calendar', + 'description' => 'JoomGallery and DPCalendar integration — link galleries to events.', + 'element' => 'mokogallerycalendar', + 'type' => 'plugin', + 'icon' => 'icon-images', + 'category' => 'Plugins', + ], + ]; + + private const GITEA_URL = 'https://git.mokoconsulting.tech'; + private const GITEA_ORG = 'MokoConsulting'; + + /** + * Get the full catalog with install status and release info. + * + * @return array + */ + public function getCatalog(): array + { + $installed = $this->getInstalledVersions(); + $packages = []; + + foreach (self::CATALOG as $repo => $meta) + { + $release = $this->fetchLatestRelease($repo); + + $localVersion = $installed[$meta['element']] ?? null; + $remoteVersion = $release['version'] ?? ''; + $downloadUrl = $release['download_url'] ?? ''; + + $status = 'not_installed'; + + if ($localVersion !== null) + { + $status = 'installed'; + + if ($remoteVersion && version_compare( + preg_replace('/[^0-9.]/', '', $remoteVersion), + preg_replace('/[^0-9.]/', '', $localVersion), + '>' + )) + { + $status = 'update_available'; + } + } + + $packages[] = (object) [ + 'repo' => $repo, + 'label' => $meta['label'], + 'description' => $meta['description'], + 'element' => $meta['element'], + 'type' => $meta['type'], + 'icon' => $meta['icon'], + 'category' => $meta['category'], + 'local_version' => $localVersion ?? '', + 'remote_version' => $remoteVersion, + 'download_url' => $downloadUrl, + 'status' => $status, + 'repo_url' => self::GITEA_URL . '/' . self::GITEA_ORG . '/' . $repo, + ]; + } + + return $packages; + } + + /** + * Install an extension from a remote ZIP URL. + * + * @param string $url The download URL. + * + * @return array Result with success, message, and extension info. + */ + public function installFromUrl(string $url): array + { + $tmpPath = Factory::getConfig()->get('tmp_path', JPATH_ROOT . '/tmp'); + $tmpFile = $tmpPath . '/mokowaas_install_' . md5($url) . '.zip'; + + try + { + // Download + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 120); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + $data = curl_exec($ch); + $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); + $error = curl_error($ch); + curl_close($ch); + + if ($error || $code !== 200 || empty($data)) + { + return ['success' => false, 'message' => 'Download failed: ' . ($error ?: "HTTP {$code}")]; + } + + file_put_contents($tmpFile, $data); + + // Install via Joomla Installer + $installer = new \Joomla\CMS\Installer\Installer(); + $result = $installer->install($tmpFile); + + @unlink($tmpFile); + + if (!$result) + { + return ['success' => false, 'message' => 'Installation failed.']; + } + + return [ + 'success' => true, + 'message' => 'Installed successfully.', + ]; + } + catch (\Throwable $e) + { + @unlink($tmpFile); + + return ['success' => false, 'message' => 'Error: ' . $e->getMessage()]; + } + } + + /** + * Get installed versions of all Moko extensions. + * + * @return array element => version + */ + private function getInstalledVersions(): array + { + $db = $this->getDatabase(); + $elements = []; + + foreach (self::CATALOG as $meta) + { + $elements[] = $db->quote($meta['element']); + } + + $query = $db->getQuery(true) + ->select([$db->quoteName('element'), $db->quoteName('manifest_cache')]) + ->from($db->quoteName('#__extensions')) + ->where($db->quoteName('element') . ' IN (' . implode(',', $elements) . ')'); + $db->setQuery($query); + $rows = $db->loadObjectList() ?: []; + + $versions = []; + + foreach ($rows as $row) + { + $mc = json_decode($row->manifest_cache ?? '{}'); + $versions[$row->element] = $mc->version ?? '0.0.0'; + } + + return $versions; + } + + /** + * Fetch the latest release from Gitea for a repo. + * + * @param string $repo Repository name. + * + * @return array [version, download_url] or empty. + */ + private function fetchLatestRelease(string $repo): array + { + $url = self::GITEA_URL . '/api/v1/repos/' . self::GITEA_ORG . '/' . $repo . '/releases?limit=1'; + + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Accept: application/json']); + $response = curl_exec($ch); + $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($code !== 200 || empty($response)) + { + return []; + } + + $releases = json_decode($response, true); + + if (empty($releases[0])) + { + return []; + } + + $release = $releases[0]; + $version = $release['tag_name'] ?? ''; + + // Find the first .zip asset + $downloadUrl = ''; + + foreach ($release['assets'] ?? [] as $asset) + { + if (str_ends_with(strtolower($asset['name'] ?? ''), '.zip')) + { + $downloadUrl = $asset['browser_download_url'] ?? ''; + break; + } + } + + return [ + 'version' => $version, + 'download_url' => $downloadUrl, + ]; + } +} diff --git a/src/packages/com_mokowaas/admin/src/View/Extensions/HtmlView.php b/src/packages/com_mokowaas/admin/src/View/Extensions/HtmlView.php new file mode 100644 index 00000000..f086a84c --- /dev/null +++ b/src/packages/com_mokowaas/admin/src/View/Extensions/HtmlView.php @@ -0,0 +1,41 @@ +getModel(); + + $this->packages = $model->getCatalog(); + + $this->addToolbar(); + + $wa = Factory::getApplication()->getDocument()->getWebAssetManager(); + $wa->registerAndUseStyle('com_mokowaas.dashboard', 'com_mokowaas/dashboard.css'); + + parent::display($tpl); + } + + protected function addToolbar(): void + { + ToolbarHelper::title(Text::_('COM_MOKOWAAS_EXTENSIONS_TITLE'), 'puzzle-piece'); + ToolbarHelper::back('JTOOLBAR_BACK', 'index.php?option=com_mokowaas'); + } +} diff --git a/src/packages/com_mokowaas/admin/tmpl/dashboard/default.php b/src/packages/com_mokowaas/admin/tmpl/dashboard/default.php index dc0f6d8a..54b2a3f1 100644 --- a/src/packages/com_mokowaas/admin/tmpl/dashboard/default.php +++ b/src/packages/com_mokowaas/admin/tmpl/dashboard/default.php @@ -79,6 +79,10 @@ $categoryOrder = ['core', 'security', 'monitoring', 'content', 'tools', 'api']; + + + + diff --git a/src/packages/com_mokowaas/admin/tmpl/extensions/default.php b/src/packages/com_mokowaas/admin/tmpl/extensions/default.php new file mode 100644 index 00000000..22e99239 --- /dev/null +++ b/src/packages/com_mokowaas/admin/tmpl/extensions/default.php @@ -0,0 +1,149 @@ +packages; +$token = Session::getFormToken(); + +// Group by category +$grouped = []; +foreach ($packages as $pkg) +{ + $grouped[$pkg->category][] = $pkg; +} + +$statusBadge = [ + 'installed' => ['bg-success', 'Installed'], + 'update_available' => ['bg-warning text-dark', 'Update Available'], + 'not_installed' => ['bg-secondary', 'Not Installed'], +]; +?> + +
description); ?>
+ +