getIdentity(); if (!$user->authorise('core.manage', 'com_installer')) { $this->sendJson(403, ['error' => 'Not authorized — requires core.manage on com_installer']); return; } try { $db = Factory::getDbo(); $query = $db->getQuery(true) ->select([ $db->quoteName('e.extension_id'), $db->quoteName('e.name'), $db->quoteName('e.type'), $db->quoteName('e.element'), $db->quoteName('e.folder'), $db->quoteName('e.client_id'), $db->quoteName('e.enabled'), $db->quoteName('e.protected'), $db->quoteName('e.locked'), $db->quoteName('e.manifest_cache'), ]) ->from($db->quoteName('#__extensions', 'e')) ->order($db->quoteName('e.type') . ' ASC, ' . $db->quoteName('e.name') . ' ASC'); // Filter by type $typeFilter = $app->input->get('type', '', 'CMD'); if ($typeFilter !== '') { $query->where($db->quoteName('e.type') . ' = ' . $db->quote($typeFilter)); } // Filter by enabled $enabledFilter = $app->input->get('enabled', '', 'CMD'); if ($enabledFilter !== '') { $query->where($db->quoteName('e.enabled') . ' = ' . (int) $enabledFilter); } // Search name or element $search = $app->input->get('search', '', 'STRING'); if ($search !== '') { $searchQuoted = $db->quote('%' . $db->escape($search, true) . '%'); $query->where( '(' . $db->quoteName('e.name') . ' LIKE ' . $searchQuoted . ' OR ' . $db->quoteName('e.element') . ' LIKE ' . $searchQuoted . ')' ); } $db->setQuery($query); $rows = $db->loadAssocList(); // Get update sites for cross-reference $usQuery = $db->getQuery(true) ->select([ $db->quoteName('us.update_site_id'), $db->quoteName('us.name', 'site_name'), $db->quoteName('us.location'), $db->quoteName('us.enabled', 'site_enabled'), $db->quoteName('usm.extension_id'), ]) ->from($db->quoteName('#__update_sites', 'us')) ->innerJoin( $db->quoteName('#__update_sites_extensions', 'usm') . ' ON ' . $db->quoteName('us.update_site_id') . ' = ' . $db->quoteName('usm.update_site_id') ); $db->setQuery($usQuery); $updateSites = []; foreach ($db->loadAssocList() ?: [] as $us) { $updateSites[(int) $us['extension_id']] = [ 'name' => $us['site_name'], 'location' => $us['location'], 'enabled' => (bool) $us['site_enabled'], ]; } // Build response $extensions = []; foreach ($rows as $row) { $manifest = json_decode($row['manifest_cache'] ?: '{}', true); $extId = (int) $row['extension_id']; $ext = [ 'extension_id' => $extId, 'name' => $row['name'], 'type' => $row['type'], 'element' => $row['element'], 'folder' => $row['folder'] ?: null, 'client_id' => (int) $row['client_id'], 'enabled' => (bool) $row['enabled'], 'protected' => (bool) $row['protected'], 'locked' => (bool) $row['locked'], 'version' => $manifest['version'] ?? null, 'author' => $manifest['author'] ?? null, 'description' => $manifest['description'] ?? null, ]; if (isset($updateSites[$extId])) { $ext['update_server'] = $updateSites[$extId]; } $extensions[] = $ext; } $this->sendJson(200, [ 'status' => 'ok', 'count' => count($extensions), 'extensions' => $extensions, ]); } catch (\Throwable $e) { $this->sendJson(500, [ 'error' => 'Failed to list extensions', 'message' => $e->getMessage(), ]); } } /** * @param int $code HTTP status code * @param array $payload Response data * @return void */ private function sendJson(int $code, array $payload): void { $app = Factory::getApplication(); $app->setHeader('Content-Type', 'application/json', true); $app->setHeader('Status', (string) $code, true); echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT); $app->close(); } }