mirror of
https://github.com/nextcloud/server.git
synced 2026-06-10 09:13:19 -04:00
refactor(appstore): split controllers and use proper root
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
parent
5b756ad8bc
commit
f6a37dc608
31 changed files with 3194 additions and 2334 deletions
16
apps/appstore/REUSE.toml
Normal file
16
apps/appstore/REUSE.toml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
version = 1
|
||||
SPDX-PackageName = "nextcloud"
|
||||
SPDX-PackageSupplier = "Nextcloud <info@nextcloud.com>"
|
||||
SPDX-PackageDownloadLocation = "https://github.com/nextcloud/server"
|
||||
|
||||
[[annotations]]
|
||||
path = ["tests/fixtures/categories.json", "tests/fixtures/categories-api-response.json"]
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2026 Nextcloud GmbH and Nextcloud contributors"
|
||||
SPDX-License-Identifier = "CC-BY-SA-4.0"
|
||||
|
||||
[[annotations]]
|
||||
path = ["img/app.svg"]
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2018-2024 Google LLC"
|
||||
SPDX-License-Identifier = "Apache-2.0"
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
return [
|
||||
'routes' => [
|
||||
['name' => 'AppSettings#getAppDiscoverJSON', 'url' => '/settings/api/apps/discover', 'verb' => 'GET', 'root' => ''],
|
||||
['name' => 'AppSettings#getAppDiscoverMedia', 'url' => '/settings/api/apps/media', 'verb' => 'GET', 'root' => ''],
|
||||
['name' => 'AppSettings#listCategories', 'url' => '/settings/apps/categories', 'verb' => 'GET' , 'root' => ''],
|
||||
['name' => 'AppSettings#viewApps', 'url' => '/settings/apps', 'verb' => 'GET' , 'root' => ''],
|
||||
['name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET' , 'root' => ''],
|
||||
['name' => 'AppSettings#enableApp', 'url' => '/settings/apps/enable/{appId}', 'verb' => 'GET' , 'root' => ''],
|
||||
['name' => 'AppSettings#enableApp', 'url' => '/settings/apps/enable/{appId}', 'verb' => 'POST' , 'root' => ''],
|
||||
['name' => 'AppSettings#enableApps', 'url' => '/settings/apps/enable', 'verb' => 'POST' , 'root' => ''],
|
||||
['name' => 'AppSettings#disableApp', 'url' => '/settings/apps/disable/{appId}', 'verb' => 'GET' , 'root' => ''],
|
||||
['name' => 'AppSettings#disableApps', 'url' => '/settings/apps/disable', 'verb' => 'POST' , 'root' => ''],
|
||||
['name' => 'AppSettings#updateApp', 'url' => '/settings/apps/update/{appId}', 'verb' => 'GET' , 'root' => ''],
|
||||
['name' => 'AppSettings#uninstallApp', 'url' => '/settings/apps/uninstall/{appId}', 'verb' => 'GET' , 'root' => ''],
|
||||
['name' => 'AppSettings#viewApps', 'url' => '/settings/apps/{category}', 'verb' => 'GET', 'defaults' => ['category' => ''] , 'root' => ''],
|
||||
['name' => 'AppSettings#viewApps', 'url' => '/settings/apps/{category}/{id}', 'verb' => 'GET', 'defaults' => ['category' => '', 'id' => ''] , 'root' => ''],
|
||||
['name' => 'AppSettings#force', 'url' => '/settings/apps/force', 'verb' => 'POST' , 'root' => ''],
|
||||
],
|
||||
];
|
||||
|
|
@ -8,7 +8,8 @@ $baseDir = $vendorDir;
|
|||
return array(
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
'OCA\\Appstore\\AppInfo\\Application' => $baseDir . '/../lib/AppInfo/Application.php',
|
||||
'OCA\\Appstore\\Controller\\AppSettingsController' => $baseDir . '/../lib/Controller/AppSettingsController.php',
|
||||
'OCA\\Appstore\\Controller\\ApiController' => $baseDir . '/../lib/Controller/ApiController.php',
|
||||
'OCA\\Appstore\\Controller\\DiscoverController' => $baseDir . '/../lib/Controller/DiscoverController.php',
|
||||
'OCA\\Appstore\\Controller\\PageController' => $baseDir . '/../lib/Controller/PageController.php',
|
||||
'OCA\\Appstore\\Search\\AppSearch' => $baseDir . '/../lib/Search/AppSearch.php',
|
||||
'OCA\\Settings\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,14 +7,14 @@ namespace Composer\Autoload;
|
|||
class ComposerStaticInitAppstore
|
||||
{
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
'O' =>
|
||||
'O' =>
|
||||
array (
|
||||
'OCA\\Appstore\\' => 13,
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'OCA\\Appstore\\' =>
|
||||
'OCA\\Appstore\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/../lib',
|
||||
),
|
||||
|
|
@ -23,9 +23,10 @@ class ComposerStaticInitAppstore
|
|||
public static $classMap = array (
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
'OCA\\Appstore\\AppInfo\\Application' => __DIR__ . '/..' . '/../lib/AppInfo/Application.php',
|
||||
'OCA\\Appstore\\Controller\\AppSettingsController' => __DIR__ . '/..' . '/../lib/Controller/AppSettingsController.php',
|
||||
'OCA\\Appstore\\Controller\\ApiController' => __DIR__ . '/..' . '/../lib/Controller/ApiController.php',
|
||||
'OCA\\Appstore\\Controller\\DiscoverController' => __DIR__ . '/..' . '/../lib/Controller/DiscoverController.php',
|
||||
'OCA\\Appstore\\Controller\\PageController' => __DIR__ . '/..' . '/../lib/Controller/PageController.php',
|
||||
'OCA\\Appstore\\Search\\AppSearch' => __DIR__ . '/..' . '/../lib/Search/AppSearch.php',
|
||||
'OCA\\Settings\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php',
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 321 B After Width: | Height: | Size: 321 B |
|
|
@ -23,10 +23,12 @@ class Application extends App implements IBootstrap {
|
|||
parent::__construct(self::APP_ID, $urlParams);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function register(IRegistrationContext $context): void {
|
||||
$context->registerSearchProvider(AppSearch::class);
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
public function boot(IBootContext $context): void {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
497
apps/appstore/lib/Controller/ApiController.php
Normal file
497
apps/appstore/lib/Controller/ApiController.php
Normal file
|
|
@ -0,0 +1,497 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Appstore\Controller;
|
||||
|
||||
use OC\App\AppManager;
|
||||
use OC\App\AppStore\Bundles\BundleFetcher;
|
||||
use OC\App\AppStore\Fetcher\AppFetcher;
|
||||
use OC\App\AppStore\Fetcher\CategoryFetcher;
|
||||
use OC\App\AppStore\Version\VersionParser;
|
||||
use OC\App\DependencyAnalyzer;
|
||||
use OC\Installer;
|
||||
use OCA\Appstore\AppInfo\Application;
|
||||
use OCP\App\AppPathNotFoundException;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\ApiRoute;
|
||||
use OCP\AppFramework\Http\Attribute\OpenAPI;
|
||||
use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\OCS\OCSException;
|
||||
use OCP\AppFramework\OCS\OCSNotFoundException;
|
||||
use OCP\AppFramework\OCSController;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IGroup;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IRequest;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\Server;
|
||||
use OCP\Support\Subscription\IRegistry;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
#[OpenAPI(scope: OpenAPI::SCOPE_ADMINISTRATION)]
|
||||
class ApiController extends OCSController {
|
||||
|
||||
/** @var array */
|
||||
private $allApps = [];
|
||||
|
||||
public function __construct(
|
||||
IRequest $request,
|
||||
private IConfig $config,
|
||||
private IAppConfig $appConfig,
|
||||
private AppManager $appManager,
|
||||
private DependencyAnalyzer $dependencyAnalyzer,
|
||||
private CategoryFetcher $categoryFetcher,
|
||||
private AppFetcher $appFetcher,
|
||||
private IFactory $l10nFactory,
|
||||
private BundleFetcher $bundleFetcher,
|
||||
private Installer $installer,
|
||||
private IRegistry $subscriptionRegistry,
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
parent::__construct(Application::APP_ID, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available categories
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, list<array{id: string, displayName: string}>, array{}>
|
||||
*
|
||||
* 200: The categories were found successfully
|
||||
*/
|
||||
#[ApiRoute(verb: 'GET', url: '/api/v1/apps/categories')]
|
||||
public function listCategories(): DataResponse {
|
||||
$currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
|
||||
|
||||
$categories = $this->categoryFetcher->get();
|
||||
$categories = array_map(fn ($category) => [
|
||||
'id' => $category['id'],
|
||||
'displayName' => $category['translations'][$currentLanguage]['name'] ?? $category['translations']['en']['name'],
|
||||
], $categories);
|
||||
|
||||
return new DataResponse($categories);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available apps
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, list<array{id: string, name: string, description: string, ...}>, array{}>
|
||||
*
|
||||
* 200: The apps were found successfully
|
||||
*/
|
||||
#[ApiRoute(verb: 'GET', url: '/api/v1/apps')]
|
||||
public function listApps(): DataResponse {
|
||||
$apps = $this->getAllApps();
|
||||
|
||||
$ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
|
||||
if (!is_array($ignoreMaxApps)) {
|
||||
$this->logger->warning('The value given for app_install_overwrite is not an array. Ignoring...');
|
||||
$ignoreMaxApps = [];
|
||||
}
|
||||
|
||||
// Extend existing app details
|
||||
$apps = array_map(function (array $appData) use ($ignoreMaxApps) {
|
||||
if (isset($appData['appstoreData'])) {
|
||||
$appstoreData = $appData['appstoreData'];
|
||||
$appData['screenshot'] = $this->createProxyPreviewUrl($appstoreData['screenshots'][0]['url'] ?? '');
|
||||
$appData['category'] = $appstoreData['categories'];
|
||||
$appData['releases'] = $appstoreData['releases'];
|
||||
}
|
||||
|
||||
$newVersion = $this->installer->isUpdateAvailable($appData['id']);
|
||||
if ($newVersion) {
|
||||
$appData['update'] = $newVersion;
|
||||
}
|
||||
|
||||
// fix groups to be an array
|
||||
$groups = [];
|
||||
if (is_string($appData['groups'])) {
|
||||
/** @var list<string>|string $groups */
|
||||
$groups = json_decode($appData['groups']);
|
||||
// ensure 'groups' is an array
|
||||
if (!is_array($groups)) {
|
||||
$groups = [$groups];
|
||||
}
|
||||
}
|
||||
$appData['groups'] = $groups;
|
||||
$appData['canUninstall'] = !$appData['active'] && $appData['removable'];
|
||||
|
||||
// analyze dependencies
|
||||
$ignoreMax = in_array($appData['id'], $ignoreMaxApps);
|
||||
$missing = $this->dependencyAnalyzer->analyze($appData, $ignoreMax);
|
||||
$appData['canInstall'] = empty($missing);
|
||||
$appData['missingDependencies'] = $missing;
|
||||
|
||||
$appData['missingMinNextcloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['min-version']);
|
||||
$appData['missingMaxNextcloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['max-version']);
|
||||
$appData['isCompatible'] = $this->dependencyAnalyzer->isMarkedCompatible($appData);
|
||||
|
||||
return $appData;
|
||||
}, $apps);
|
||||
|
||||
usort($apps, $this->sortApps(...));
|
||||
|
||||
/**
|
||||
* @var list<array{id: string, name: string, description: string, ...}> $apps
|
||||
*/
|
||||
return new DataResponse($apps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable one apps
|
||||
*
|
||||
* App will be enabled for specific groups only if $groups is defined
|
||||
*
|
||||
* @param string $appId - The app to enable
|
||||
* @param list<string> $groups - The groups to enable the app for
|
||||
* @param bool $force - Whether to force enable the app even if Nextcloud version requirements are not met
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, array{update_required: bool}, array{}>
|
||||
* @throws OCSException - if the app could not be enabled
|
||||
*
|
||||
* 200: App successfully enabled
|
||||
*/
|
||||
#[PasswordConfirmationRequired(strict: true)]
|
||||
#[ApiRoute(verb: 'POST', url: '/api/v1/apps/enable')]
|
||||
public function enableApp(string $appId, array $groups = [], bool $force = false): DataResponse {
|
||||
try {
|
||||
$appId = $this->appManager->cleanAppId($appId);
|
||||
if ($force) {
|
||||
$this->appManager->overwriteNextcloudRequirement($appId);
|
||||
}
|
||||
|
||||
// Check if app is already downloaded
|
||||
if (!$this->installer->isDownloaded($appId)) {
|
||||
$this->installer->downloadApp($appId);
|
||||
}
|
||||
|
||||
$this->installer->installApp($appId);
|
||||
|
||||
if (count($groups) > 0) {
|
||||
$this->appManager->enableAppForGroups($appId, $this->getGroupList($groups));
|
||||
} else {
|
||||
$this->appManager->enableApp($appId);
|
||||
}
|
||||
$updateRequired = $this->appManager->isUpgradeRequired($appId);
|
||||
return new DataResponse(['update_required' => $updateRequired]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('could not enable app', ['exception' => $e]);
|
||||
throw new OCSException('could not enable app', Http::STATUS_INTERNAL_SERVER_ERROR, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable an app
|
||||
*
|
||||
* @param string $appId - The app to disable
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, array{}, array{}>
|
||||
* @throws OCSException - if the app could not be disabled
|
||||
*
|
||||
* 200: App successfully disabled
|
||||
*/
|
||||
#[PasswordConfirmationRequired(strict: false)]
|
||||
#[ApiRoute(verb: 'POST', url: '/api/v1/apps/disable')]
|
||||
public function disableApp(string $appId): DataResponse {
|
||||
try {
|
||||
$appId = $this->appManager->cleanAppId($appId);
|
||||
$this->appManager->disableApp($appId);
|
||||
return new DataResponse([]);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('could not disable app', ['exception' => $e]);
|
||||
throw new OCSException('could not disable app', Http::STATUS_INTERNAL_SERVER_ERROR, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstall an app.
|
||||
* This will disable the app - if needed - and then remove the app from the system
|
||||
*
|
||||
* @param string $appId - The app to uninstall
|
||||
* @return DataResponse<Http::STATUS_OK, array{}, array{}>
|
||||
* @throws OCSException - if the app could not be uninstalled
|
||||
*
|
||||
* 200: App successfully uninstalled
|
||||
*/
|
||||
#[PasswordConfirmationRequired(strict: true)]
|
||||
#[ApiRoute(verb: 'POST', url: '/api/v1/apps/uninstall')]
|
||||
public function uninstallApp(string $appId): DataResponse {
|
||||
$appId = $this->appManager->cleanAppId($appId);
|
||||
$result = $this->installer->removeApp($appId);
|
||||
if ($result !== false) {
|
||||
// If this app was force enabled, remove the force-enabled-state
|
||||
$this->appManager->removeOverwriteNextcloudRequirement($appId);
|
||||
$this->appManager->clearAppsCache();
|
||||
return new DataResponse([]);
|
||||
}
|
||||
throw new OCSException('could not remove app', Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an app
|
||||
*
|
||||
* @param string $appId - The app to update
|
||||
* @return DataResponse<Http::STATUS_OK, array{}, array{}>
|
||||
* @throws OCSException - if the app could not be updated
|
||||
*
|
||||
* 200: App successfully updated
|
||||
*/
|
||||
#[PasswordConfirmationRequired(strict: true)]
|
||||
#[ApiRoute(verb: 'POST', url: '/api/v1/apps/update')]
|
||||
public function updateApp(string $appId): DataResponse {
|
||||
$appId = $this->appManager->cleanAppId($appId);
|
||||
|
||||
$this->config->setSystemValue('maintenance', true);
|
||||
try {
|
||||
$result = $this->installer->updateAppstoreApp($appId);
|
||||
$this->config->setSystemValue('maintenance', false);
|
||||
if ($result === false) {
|
||||
throw new \Exception('Update failed');
|
||||
}
|
||||
} catch (\Exception $ex) {
|
||||
$this->config->setSystemValue('maintenance', false);
|
||||
throw new OCSException('could not update app', Http::STATUS_INTERNAL_SERVER_ERROR, $ex);
|
||||
}
|
||||
|
||||
return new DataResponse([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable all apps of a bundle
|
||||
*
|
||||
* @param string $bundleId - The bundle to enable
|
||||
* @return DataResponse<Http::STATUS_OK, array{}, array{}>
|
||||
* @throws OCSException - if the bundle, or one app within, could not be enabled
|
||||
*
|
||||
* 200: Bundle successfully enabled
|
||||
*/
|
||||
#[PasswordConfirmationRequired(strict: true)]
|
||||
#[ApiRoute(verb: 'POST', url: '/api/v1/bundles/enable')]
|
||||
public function enableBundle(string $bundleId): DataResponse {
|
||||
try {
|
||||
$bundle = $this->bundleFetcher->getBundleByIdentifier($bundleId);
|
||||
$this->config->setSystemValue('maintenance', true);
|
||||
$this->installer->installAppBundle($bundle);
|
||||
} catch (\BadMethodCallException $e) {
|
||||
throw new OCSNotFoundException('Bundle not found', $e);
|
||||
} catch (\Exception $exception) {
|
||||
$this->logger->error('could not enable bundle', ['bundleId' => $bundleId, 'exception' => $exception]);
|
||||
throw new OCSException('could not enable bundle', Http::STATUS_INTERNAL_SERVER_ERROR, $exception);
|
||||
} finally {
|
||||
$this->config->setSystemValue('maintenance', false);
|
||||
}
|
||||
|
||||
return new DataResponse([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert URL to proxied URL so CSP is no problem
|
||||
*/
|
||||
private function createProxyPreviewUrl(string $url): string {
|
||||
if ($url === '') {
|
||||
return '';
|
||||
}
|
||||
return 'https://usercontent.apps.nextcloud.com/' . base64_encode($url);
|
||||
}
|
||||
|
||||
private function fetchApps() {
|
||||
$appClass = new \OC_App();
|
||||
$apps = $appClass->listAllApps();
|
||||
foreach ($apps as $app) {
|
||||
$app['installed'] = true;
|
||||
|
||||
if (isset($app['screenshot'][0])) {
|
||||
$appScreenshot = $app['screenshot'][0] ?? null;
|
||||
if (is_array($appScreenshot)) {
|
||||
// Screenshot with thumbnail
|
||||
$appScreenshot = $appScreenshot['@value'];
|
||||
}
|
||||
|
||||
$app['screenshot'] = $this->createProxyPreviewUrl($appScreenshot);
|
||||
}
|
||||
$this->allApps[$app['id']] = $app;
|
||||
}
|
||||
|
||||
$apps = $this->getAppsForCategory('');
|
||||
$supportedApps = $this->subscriptionRegistry->delegateGetSupportedApps();
|
||||
foreach ($apps as $app) {
|
||||
$app['appstore'] = true;
|
||||
if (!array_key_exists($app['id'], $this->allApps)) {
|
||||
$this->allApps[$app['id']] = $app;
|
||||
} else {
|
||||
$this->allApps[$app['id']] = array_merge($app, $this->allApps[$app['id']]);
|
||||
}
|
||||
|
||||
if (in_array($app['id'], $supportedApps)) {
|
||||
$this->allApps[$app['id']]['level'] = \OC_App::supportedApp;
|
||||
}
|
||||
}
|
||||
|
||||
// add bundle information
|
||||
$bundles = $this->bundleFetcher->getBundles();
|
||||
foreach ($bundles as $bundle) {
|
||||
foreach ($bundle->getAppIdentifiers() as $identifier) {
|
||||
foreach ($this->allApps as &$app) {
|
||||
if ($app['id'] === $identifier) {
|
||||
$app['bundleIds'][] = $bundle->getIdentifier();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getAllApps() {
|
||||
if (empty($this->allApps)) {
|
||||
$this->fetchApps();
|
||||
}
|
||||
return $this->allApps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all apps for a category from the app store
|
||||
*
|
||||
* @param string $requestedCategory
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function getAppsForCategory($requestedCategory = ''): array {
|
||||
$versionParser = new VersionParser();
|
||||
$formattedApps = [];
|
||||
$apps = $this->appFetcher->get();
|
||||
foreach ($apps as $app) {
|
||||
// Skip all apps not in the requested category
|
||||
if ($requestedCategory !== '') {
|
||||
$isInCategory = false;
|
||||
foreach ($app['categories'] as $category) {
|
||||
if ($category === $requestedCategory) {
|
||||
$isInCategory = true;
|
||||
}
|
||||
}
|
||||
if (!$isInCategory) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($app['releases'][0]['rawPlatformVersionSpec'])) {
|
||||
continue;
|
||||
}
|
||||
$nextcloudVersion = $versionParser->getVersion($app['releases'][0]['rawPlatformVersionSpec']);
|
||||
$nextcloudVersionDependencies = [];
|
||||
if ($nextcloudVersion->getMinimumVersion() !== '') {
|
||||
$nextcloudVersionDependencies['nextcloud']['@attributes']['min-version'] = $nextcloudVersion->getMinimumVersion();
|
||||
}
|
||||
if ($nextcloudVersion->getMaximumVersion() !== '') {
|
||||
$nextcloudVersionDependencies['nextcloud']['@attributes']['max-version'] = $nextcloudVersion->getMaximumVersion();
|
||||
}
|
||||
$phpVersion = $versionParser->getVersion($app['releases'][0]['rawPhpVersionSpec']);
|
||||
|
||||
try {
|
||||
$this->appManager->getAppPath($app['id']);
|
||||
$existsLocally = true;
|
||||
} catch (AppPathNotFoundException) {
|
||||
$existsLocally = false;
|
||||
}
|
||||
|
||||
$phpDependencies = [];
|
||||
if ($phpVersion->getMinimumVersion() !== '') {
|
||||
$phpDependencies['php']['@attributes']['min-version'] = $phpVersion->getMinimumVersion();
|
||||
}
|
||||
if ($phpVersion->getMaximumVersion() !== '') {
|
||||
$phpDependencies['php']['@attributes']['max-version'] = $phpVersion->getMaximumVersion();
|
||||
}
|
||||
if (isset($app['releases'][0]['minIntSize'])) {
|
||||
$phpDependencies['php']['@attributes']['min-int-size'] = $app['releases'][0]['minIntSize'];
|
||||
}
|
||||
$authors = '';
|
||||
foreach ($app['authors'] as $key => $author) {
|
||||
$authors .= $author['name'];
|
||||
if ($key !== count($app['authors']) - 1) {
|
||||
$authors .= ', ';
|
||||
}
|
||||
}
|
||||
|
||||
$currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
|
||||
$enabledValue = $this->appConfig->getValueString($app['id'], 'enabled', 'no');
|
||||
$groups = null;
|
||||
if ($enabledValue !== 'no' && $enabledValue !== 'yes') {
|
||||
$groups = $enabledValue;
|
||||
}
|
||||
|
||||
$currentVersion = '';
|
||||
if ($this->appManager->isEnabledForAnyone($app['id'])) {
|
||||
$currentVersion = $this->appManager->getAppVersion($app['id']);
|
||||
} else {
|
||||
$currentVersion = $app['releases'][0]['version'];
|
||||
}
|
||||
|
||||
$formattedApps[] = [
|
||||
'id' => $app['id'],
|
||||
'app_api' => false,
|
||||
'name' => $app['translations'][$currentLanguage]['name'] ?? $app['translations']['en']['name'],
|
||||
'description' => $app['translations'][$currentLanguage]['description'] ?? $app['translations']['en']['description'],
|
||||
'summary' => $app['translations'][$currentLanguage]['summary'] ?? $app['translations']['en']['summary'],
|
||||
'license' => $app['releases'][0]['licenses'],
|
||||
'author' => $authors,
|
||||
'shipped' => $this->appManager->isShipped($app['id']),
|
||||
'version' => $currentVersion,
|
||||
'types' => [],
|
||||
'documentation' => [
|
||||
'admin' => $app['adminDocs'],
|
||||
'user' => $app['userDocs'],
|
||||
'developer' => $app['developerDocs']
|
||||
],
|
||||
'website' => $app['website'],
|
||||
'bugs' => $app['issueTracker'],
|
||||
'dependencies' => array_merge(
|
||||
$nextcloudVersionDependencies,
|
||||
$phpDependencies
|
||||
),
|
||||
'level' => ($app['isFeatured'] === true) ? 200 : 100,
|
||||
'missingMaxNextcloudVersion' => false,
|
||||
'missingMinNextcloudVersion' => false,
|
||||
'canInstall' => true,
|
||||
'screenshot' => isset($app['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/' . base64_encode($app['screenshots'][0]['url']) : '',
|
||||
'score' => $app['ratingOverall'],
|
||||
'ratingNumOverall' => $app['ratingNumOverall'],
|
||||
'ratingNumThresholdReached' => $app['ratingNumOverall'] > 5,
|
||||
'removable' => $existsLocally,
|
||||
'active' => $this->appManager->isEnabledForUser($app['id']),
|
||||
'needsDownload' => !$existsLocally,
|
||||
'groups' => $groups,
|
||||
'fromAppStore' => true,
|
||||
'appstoreData' => $app,
|
||||
];
|
||||
}
|
||||
|
||||
return $formattedApps;
|
||||
}
|
||||
|
||||
private function getGroupList(array $groups) {
|
||||
$groupManager = Server::get(IGroupManager::class);
|
||||
$groupsList = [];
|
||||
foreach ($groups as $group) {
|
||||
$groupItem = $groupManager->get($group);
|
||||
if ($groupItem instanceof IGroup) {
|
||||
$groupsList[] = $groupManager->get($group);
|
||||
}
|
||||
}
|
||||
return $groupsList;
|
||||
}
|
||||
|
||||
private function sortApps($a, $b) {
|
||||
$a = (string)$a['name'];
|
||||
$b = (string)$b['name'];
|
||||
if ($a === $b) {
|
||||
return 0;
|
||||
}
|
||||
return ($a < $b) ? -1 : 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,689 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
namespace OCA\Appstore\Controller;
|
||||
|
||||
use OC\App\AppManager;
|
||||
use OC\App\AppStore\Bundles\BundleFetcher;
|
||||
use OC\App\AppStore\Fetcher\AppDiscoverFetcher;
|
||||
use OC\App\AppStore\Fetcher\AppFetcher;
|
||||
use OC\App\AppStore\Fetcher\CategoryFetcher;
|
||||
use OC\App\AppStore\Version\VersionParser;
|
||||
use OC\App\DependencyAnalyzer;
|
||||
use OC\Installer;
|
||||
use OCA\AppAPI\Service\ExAppsPageService;
|
||||
use OCP\App\AppPathNotFoundException;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
||||
use OCP\AppFramework\Http\Attribute\OpenAPI;
|
||||
use OCP\AppFramework\Http\Attribute\PasswordConfirmationRequired;
|
||||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||
use OCP\AppFramework\Http\FileDisplayResponse;
|
||||
use OCP\AppFramework\Http\JSONResponse;
|
||||
use OCP\AppFramework\Http\NotFoundResponse;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\Files\AppData\IAppDataFactory;
|
||||
use OCP\Files\IAppData;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\Files\SimpleFS\ISimpleFile;
|
||||
use OCP\Files\SimpleFS\ISimpleFolder;
|
||||
use OCP\Http\Client\IClientService;
|
||||
use OCP\IConfig;
|
||||
use OCP\IGroup;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IL10N;
|
||||
use OCP\INavigationManager;
|
||||
use OCP\IRequest;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserSession;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\Security\RateLimiting\ILimiter;
|
||||
use OCP\Server;
|
||||
use OCP\Util;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
|
||||
class AppSettingsController extends Controller {
|
||||
|
||||
/** @var array */
|
||||
private $allApps = [];
|
||||
|
||||
private IAppData $appData;
|
||||
|
||||
public function __construct(
|
||||
string $appName,
|
||||
IRequest $request,
|
||||
IAppDataFactory $appDataFactory,
|
||||
private IL10N $l10n,
|
||||
private IConfig $config,
|
||||
private INavigationManager $navigationManager,
|
||||
private AppManager $appManager,
|
||||
private CategoryFetcher $categoryFetcher,
|
||||
private AppFetcher $appFetcher,
|
||||
private IFactory $l10nFactory,
|
||||
private IGroupManager $groupManager,
|
||||
private BundleFetcher $bundleFetcher,
|
||||
private Installer $installer,
|
||||
private IURLGenerator $urlGenerator,
|
||||
private LoggerInterface $logger,
|
||||
private IInitialState $initialState,
|
||||
private AppDiscoverFetcher $discoverFetcher,
|
||||
private IClientService $clientService,
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->appData = $appDataFactory->get('appstore');
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress UndefinedClass AppAPI is shipped since 30.0.1
|
||||
*
|
||||
* @return TemplateResponse
|
||||
*/
|
||||
#[NoCSRFRequired]
|
||||
public function viewApps(): TemplateResponse {
|
||||
$this->navigationManager->setActiveEntry('core_apps');
|
||||
|
||||
$this->initialState->provideInitialState('appstoreEnabled', $this->config->getSystemValueBool('appstoreenabled', true));
|
||||
$this->initialState->provideInitialState('appstoreBundles', $this->getBundles());
|
||||
$this->initialState->provideInitialState('appstoreUpdateCount', count($this->getAppsWithUpdates()));
|
||||
$this->initialState->provideInitialState('isAllInOne', filter_var(getenv('THIS_IS_AIO'), FILTER_VALIDATE_BOOL));
|
||||
|
||||
$groups = array_map(static fn (IGroup $group): array => [
|
||||
'id' => $group->getGID(),
|
||||
'name' => $group->getDisplayName(),
|
||||
], $this->groupManager->search('', 5));
|
||||
|
||||
$this->initialState->provideInitialState('usersSettings', [ 'systemGroups' => $groups]);
|
||||
|
||||
if ($this->appManager->isEnabledForAnyone('app_api')) {
|
||||
try {
|
||||
Server::get(ExAppsPageService::class)->provideAppApiState($this->initialState);
|
||||
} catch (\Psr\Container\NotFoundExceptionInterface|\Psr\Container\ContainerExceptionInterface $e) {
|
||||
}
|
||||
}
|
||||
|
||||
$policy = new ContentSecurityPolicy();
|
||||
$policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
|
||||
|
||||
$templateResponse = new TemplateResponse('settings', 'settings/empty', ['pageTitle' => $this->l10n->t('Settings')]);
|
||||
$templateResponse->setContentSecurityPolicy($policy);
|
||||
|
||||
Util::addScript('appstore', 'main');
|
||||
|
||||
return $templateResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active entries for the app discover section
|
||||
*/
|
||||
#[NoCSRFRequired]
|
||||
public function getAppDiscoverJSON(): JSONResponse {
|
||||
$data = $this->discoverFetcher->get(true);
|
||||
return new JSONResponse($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a image for the app discover section - this is proxied for privacy and CSP reasons
|
||||
*
|
||||
* @param string $image
|
||||
* @throws \Exception
|
||||
*/
|
||||
#[NoCSRFRequired]
|
||||
public function getAppDiscoverMedia(string $fileName, ILimiter $limiter, IUserSession $session): Response {
|
||||
$getEtag = $this->discoverFetcher->getETag() ?? date('Y-m');
|
||||
$etag = trim($getEtag, '"');
|
||||
|
||||
$folder = null;
|
||||
try {
|
||||
$folder = $this->appData->getFolder('app-discover-cache');
|
||||
$this->cleanUpImageCache($folder, $etag);
|
||||
} catch (\Throwable $e) {
|
||||
$folder = $this->appData->newFolder('app-discover-cache');
|
||||
}
|
||||
|
||||
// Get the current cache folder
|
||||
try {
|
||||
$folder = $folder->getFolder($etag);
|
||||
} catch (NotFoundException $e) {
|
||||
$folder = $folder->newFolder($etag);
|
||||
}
|
||||
|
||||
$info = pathinfo($fileName);
|
||||
$hashName = md5($fileName);
|
||||
$allFiles = $folder->getDirectoryListing();
|
||||
// Try to find the file
|
||||
$file = array_filter($allFiles, function (ISimpleFile $file) use ($hashName) {
|
||||
return str_starts_with($file->getName(), $hashName);
|
||||
});
|
||||
// Get the first entry
|
||||
$file = reset($file);
|
||||
// If not found request from Web
|
||||
if ($file === false) {
|
||||
$user = $session->getUser();
|
||||
// this route is not public thus we can assume a user is logged-in
|
||||
assert($user !== null);
|
||||
// Register a user request to throttle fetching external data
|
||||
// this will prevent using the server for DoS of other systems.
|
||||
$limiter->registerUserRequest(
|
||||
'settings-discover-media',
|
||||
// allow up to 24 media requests per hour
|
||||
// this should be a sane default when a completely new section is loaded
|
||||
// keep in mind browsers request all files from a source-set
|
||||
24,
|
||||
60 * 60,
|
||||
$user,
|
||||
);
|
||||
|
||||
if (!$this->checkCanDownloadMedia($fileName)) {
|
||||
$this->logger->warning('Tried to load media files for app discover section from untrusted source');
|
||||
return new NotFoundResponse(Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
try {
|
||||
$client = $this->clientService->newClient();
|
||||
$fileResponse = $client->get($fileName);
|
||||
$contentType = $fileResponse->getHeader('Content-Type');
|
||||
$extension = $info['extension'] ?? '';
|
||||
$file = $folder->newFile($hashName . '.' . base64_encode($contentType) . '.' . $extension, $fileResponse->getBody());
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->warning('Could not load media file for app discover section', ['media_src' => $fileName, 'exception' => $e]);
|
||||
return new NotFoundResponse();
|
||||
}
|
||||
} else {
|
||||
// File was found so we can get the content type from the file name
|
||||
$contentType = base64_decode(explode('.', $file->getName())[1] ?? '');
|
||||
}
|
||||
|
||||
$response = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => $contentType]);
|
||||
// cache for 7 days
|
||||
$response->cacheFor(604800, false, true);
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function checkCanDownloadMedia(string $filename): bool {
|
||||
$urlInfo = parse_url($filename);
|
||||
if (!isset($urlInfo['host']) || !isset($urlInfo['path'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Always allowed hosts
|
||||
if ($urlInfo['host'] === 'nextcloud.com') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hosts that need further verification
|
||||
// Github is only allowed if from our organization
|
||||
$ALLOWED_HOSTS = ['github.com', 'raw.githubusercontent.com'];
|
||||
if (!in_array($urlInfo['host'], $ALLOWED_HOSTS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (str_starts_with($urlInfo['path'], '/nextcloud/') || str_starts_with($urlInfo['path'], '/nextcloud-gmbh/')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove orphaned folders from the image cache that do not match the current etag
|
||||
* @param ISimpleFolder $folder The folder to clear
|
||||
* @param string $etag The etag (directory name) to keep
|
||||
*/
|
||||
private function cleanUpImageCache(ISimpleFolder $folder, string $etag): void {
|
||||
// Cleanup old cache folders
|
||||
$allFiles = $folder->getDirectoryListing();
|
||||
foreach ($allFiles as $dir) {
|
||||
try {
|
||||
if ($dir->getName() !== $etag) {
|
||||
$dir->delete();
|
||||
}
|
||||
} catch (NotPermittedException $e) {
|
||||
// ignore folder for now
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getAppsWithUpdates() {
|
||||
$appClass = new \OC_App();
|
||||
$apps = $appClass->listAllApps();
|
||||
foreach ($apps as $key => $app) {
|
||||
$newVersion = $this->installer->isUpdateAvailable($app['id']);
|
||||
if ($newVersion === false) {
|
||||
unset($apps[$key]);
|
||||
}
|
||||
}
|
||||
return $apps;
|
||||
}
|
||||
|
||||
private function getBundles() {
|
||||
$result = [];
|
||||
$bundles = $this->bundleFetcher->getBundles();
|
||||
foreach ($bundles as $bundle) {
|
||||
$result[] = [
|
||||
'name' => $bundle->getName(),
|
||||
'id' => $bundle->getIdentifier(),
|
||||
'appIdentifiers' => $bundle->getAppIdentifiers()
|
||||
];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available categories
|
||||
*
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function listCategories(): JSONResponse {
|
||||
return new JSONResponse($this->getAllCategories());
|
||||
}
|
||||
|
||||
private function getAllCategories() {
|
||||
$currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
|
||||
|
||||
$categories = $this->categoryFetcher->get();
|
||||
return array_map(fn ($category) => [
|
||||
'id' => $category['id'],
|
||||
'displayName' => $category['translations'][$currentLanguage]['name'] ?? $category['translations']['en']['name'],
|
||||
], $categories);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert URL to proxied URL so CSP is no problem
|
||||
*/
|
||||
private function createProxyPreviewUrl(string $url): string {
|
||||
if ($url === '') {
|
||||
return '';
|
||||
}
|
||||
return 'https://usercontent.apps.nextcloud.com/' . base64_encode($url);
|
||||
}
|
||||
|
||||
private function fetchApps() {
|
||||
$appClass = new \OC_App();
|
||||
$apps = $appClass->listAllApps();
|
||||
foreach ($apps as $app) {
|
||||
$app['installed'] = true;
|
||||
|
||||
if (isset($app['screenshot'][0])) {
|
||||
$appScreenshot = $app['screenshot'][0] ?? null;
|
||||
if (is_array($appScreenshot)) {
|
||||
// Screenshot with thumbnail
|
||||
$appScreenshot = $appScreenshot['@value'];
|
||||
}
|
||||
|
||||
$app['screenshot'] = $this->createProxyPreviewUrl($appScreenshot);
|
||||
}
|
||||
$this->allApps[$app['id']] = $app;
|
||||
}
|
||||
|
||||
$apps = $this->getAppsForCategory('');
|
||||
$supportedApps = $appClass->getSupportedApps();
|
||||
foreach ($apps as $app) {
|
||||
$app['appstore'] = true;
|
||||
if (!array_key_exists($app['id'], $this->allApps)) {
|
||||
$this->allApps[$app['id']] = $app;
|
||||
} else {
|
||||
$this->allApps[$app['id']] = array_merge($app, $this->allApps[$app['id']]);
|
||||
}
|
||||
|
||||
if (in_array($app['id'], $supportedApps)) {
|
||||
$this->allApps[$app['id']]['level'] = \OC_App::supportedApp;
|
||||
}
|
||||
}
|
||||
|
||||
// add bundle information
|
||||
$bundles = $this->bundleFetcher->getBundles();
|
||||
foreach ($bundles as $bundle) {
|
||||
foreach ($bundle->getAppIdentifiers() as $identifier) {
|
||||
foreach ($this->allApps as &$app) {
|
||||
if ($app['id'] === $identifier) {
|
||||
$app['bundleIds'][] = $bundle->getIdentifier();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getAllApps() {
|
||||
return $this->allApps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all available apps in a category
|
||||
*
|
||||
* @return JSONResponse
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function listApps(): JSONResponse {
|
||||
$this->fetchApps();
|
||||
$apps = $this->getAllApps();
|
||||
|
||||
$dependencyAnalyzer = Server::get(DependencyAnalyzer::class);
|
||||
|
||||
$ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
|
||||
if (!is_array($ignoreMaxApps)) {
|
||||
$this->logger->warning('The value given for app_install_overwrite is not an array. Ignoring...');
|
||||
$ignoreMaxApps = [];
|
||||
}
|
||||
|
||||
// Extend existing app details
|
||||
$apps = array_map(function (array $appData) use ($dependencyAnalyzer, $ignoreMaxApps) {
|
||||
if (isset($appData['appstoreData'])) {
|
||||
$appstoreData = $appData['appstoreData'];
|
||||
$appData['screenshot'] = $this->createProxyPreviewUrl($appstoreData['screenshots'][0]['url'] ?? '');
|
||||
$appData['category'] = $appstoreData['categories'];
|
||||
$appData['releases'] = $appstoreData['releases'];
|
||||
}
|
||||
|
||||
$newVersion = $this->installer->isUpdateAvailable($appData['id']);
|
||||
if ($newVersion) {
|
||||
$appData['update'] = $newVersion;
|
||||
}
|
||||
|
||||
// fix groups to be an array
|
||||
$groups = [];
|
||||
if (is_string($appData['groups'])) {
|
||||
$groups = json_decode($appData['groups']);
|
||||
// ensure 'groups' is an array
|
||||
if (!is_array($groups)) {
|
||||
$groups = [$groups];
|
||||
}
|
||||
}
|
||||
$appData['groups'] = $groups;
|
||||
$appData['canUnInstall'] = !$appData['active'] && $appData['removable'];
|
||||
|
||||
// fix licence vs license
|
||||
if (isset($appData['license']) && !isset($appData['licence'])) {
|
||||
$appData['licence'] = $appData['license'];
|
||||
}
|
||||
|
||||
$ignoreMax = in_array($appData['id'], $ignoreMaxApps);
|
||||
|
||||
// analyse dependencies
|
||||
$missing = $dependencyAnalyzer->analyze($appData, $ignoreMax);
|
||||
$appData['canInstall'] = empty($missing);
|
||||
$appData['missingDependencies'] = $missing;
|
||||
|
||||
$appData['missingMinOwnCloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['min-version']);
|
||||
$appData['missingMaxOwnCloudVersion'] = !isset($appData['dependencies']['nextcloud']['@attributes']['max-version']);
|
||||
$appData['isCompatible'] = $dependencyAnalyzer->isMarkedCompatible($appData);
|
||||
|
||||
return $appData;
|
||||
}, $apps);
|
||||
|
||||
usort($apps, [$this, 'sortApps']);
|
||||
|
||||
return new JSONResponse(['apps' => $apps, 'status' => 'success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all apps for a category from the app store
|
||||
*
|
||||
* @param string $requestedCategory
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function getAppsForCategory($requestedCategory = ''): array {
|
||||
$versionParser = new VersionParser();
|
||||
$formattedApps = [];
|
||||
$apps = $this->appFetcher->get();
|
||||
foreach ($apps as $app) {
|
||||
// Skip all apps not in the requested category
|
||||
if ($requestedCategory !== '') {
|
||||
$isInCategory = false;
|
||||
foreach ($app['categories'] as $category) {
|
||||
if ($category === $requestedCategory) {
|
||||
$isInCategory = true;
|
||||
}
|
||||
}
|
||||
if (!$isInCategory) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($app['releases'][0]['rawPlatformVersionSpec'])) {
|
||||
continue;
|
||||
}
|
||||
$nextCloudVersion = $versionParser->getVersion($app['releases'][0]['rawPlatformVersionSpec']);
|
||||
$nextCloudVersionDependencies = [];
|
||||
if ($nextCloudVersion->getMinimumVersion() !== '') {
|
||||
$nextCloudVersionDependencies['nextcloud']['@attributes']['min-version'] = $nextCloudVersion->getMinimumVersion();
|
||||
}
|
||||
if ($nextCloudVersion->getMaximumVersion() !== '') {
|
||||
$nextCloudVersionDependencies['nextcloud']['@attributes']['max-version'] = $nextCloudVersion->getMaximumVersion();
|
||||
}
|
||||
$phpVersion = $versionParser->getVersion($app['releases'][0]['rawPhpVersionSpec']);
|
||||
|
||||
try {
|
||||
$this->appManager->getAppPath($app['id']);
|
||||
$existsLocally = true;
|
||||
} catch (AppPathNotFoundException) {
|
||||
$existsLocally = false;
|
||||
}
|
||||
|
||||
$phpDependencies = [];
|
||||
if ($phpVersion->getMinimumVersion() !== '') {
|
||||
$phpDependencies['php']['@attributes']['min-version'] = $phpVersion->getMinimumVersion();
|
||||
}
|
||||
if ($phpVersion->getMaximumVersion() !== '') {
|
||||
$phpDependencies['php']['@attributes']['max-version'] = $phpVersion->getMaximumVersion();
|
||||
}
|
||||
if (isset($app['releases'][0]['minIntSize'])) {
|
||||
$phpDependencies['php']['@attributes']['min-int-size'] = $app['releases'][0]['minIntSize'];
|
||||
}
|
||||
$authors = '';
|
||||
foreach ($app['authors'] as $key => $author) {
|
||||
$authors .= $author['name'];
|
||||
if ($key !== count($app['authors']) - 1) {
|
||||
$authors .= ', ';
|
||||
}
|
||||
}
|
||||
|
||||
$currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
|
||||
$enabledValue = $this->config->getAppValue($app['id'], 'enabled', 'no');
|
||||
$groups = null;
|
||||
if ($enabledValue !== 'no' && $enabledValue !== 'yes') {
|
||||
$groups = $enabledValue;
|
||||
}
|
||||
|
||||
$currentVersion = '';
|
||||
if ($this->appManager->isEnabledForAnyone($app['id'])) {
|
||||
$currentVersion = $this->appManager->getAppVersion($app['id']);
|
||||
} else {
|
||||
$currentVersion = $app['releases'][0]['version'];
|
||||
}
|
||||
|
||||
$formattedApps[] = [
|
||||
'id' => $app['id'],
|
||||
'app_api' => false,
|
||||
'name' => $app['translations'][$currentLanguage]['name'] ?? $app['translations']['en']['name'],
|
||||
'description' => $app['translations'][$currentLanguage]['description'] ?? $app['translations']['en']['description'],
|
||||
'summary' => $app['translations'][$currentLanguage]['summary'] ?? $app['translations']['en']['summary'],
|
||||
'license' => $app['releases'][0]['licenses'],
|
||||
'author' => $authors,
|
||||
'shipped' => $this->appManager->isShipped($app['id']),
|
||||
'version' => $currentVersion,
|
||||
'default_enable' => '',
|
||||
'types' => [],
|
||||
'documentation' => [
|
||||
'admin' => $app['adminDocs'],
|
||||
'user' => $app['userDocs'],
|
||||
'developer' => $app['developerDocs']
|
||||
],
|
||||
'website' => $app['website'],
|
||||
'bugs' => $app['issueTracker'],
|
||||
'detailpage' => $app['website'],
|
||||
'dependencies' => array_merge(
|
||||
$nextCloudVersionDependencies,
|
||||
$phpDependencies
|
||||
),
|
||||
'level' => ($app['isFeatured'] === true) ? 200 : 100,
|
||||
'missingMaxOwnCloudVersion' => false,
|
||||
'missingMinOwnCloudVersion' => false,
|
||||
'canInstall' => true,
|
||||
'screenshot' => isset($app['screenshots'][0]['url']) ? 'https://usercontent.apps.nextcloud.com/' . base64_encode($app['screenshots'][0]['url']) : '',
|
||||
'score' => $app['ratingOverall'],
|
||||
'ratingNumOverall' => $app['ratingNumOverall'],
|
||||
'ratingNumThresholdReached' => $app['ratingNumOverall'] > 5,
|
||||
'removable' => $existsLocally,
|
||||
'active' => $this->appManager->isEnabledForUser($app['id']),
|
||||
'needsDownload' => !$existsLocally,
|
||||
'groups' => $groups,
|
||||
'fromAppStore' => true,
|
||||
'appstoreData' => $app,
|
||||
];
|
||||
}
|
||||
|
||||
return $formattedApps;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $appId
|
||||
* @param array $groups
|
||||
* @return JSONResponse
|
||||
*/
|
||||
#[PasswordConfirmationRequired]
|
||||
public function enableApp(string $appId, array $groups = []): JSONResponse {
|
||||
return $this->enableApps([$appId], $groups);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable one or more apps
|
||||
*
|
||||
* apps will be enabled for specific groups only if $groups is defined
|
||||
*
|
||||
* @param array $appIds
|
||||
* @param array $groups
|
||||
* @return JSONResponse
|
||||
*/
|
||||
#[PasswordConfirmationRequired(strict: true)]
|
||||
public function enableApps(array $appIds, array $groups = []): JSONResponse {
|
||||
try {
|
||||
$updateRequired = false;
|
||||
|
||||
foreach ($appIds as $appId) {
|
||||
$appId = $this->appManager->cleanAppId($appId);
|
||||
|
||||
// Check if app is already downloaded
|
||||
if (!$this->installer->isDownloaded($appId)) {
|
||||
$this->installer->downloadApp($appId);
|
||||
}
|
||||
|
||||
$this->installer->installApp($appId);
|
||||
|
||||
if (count($groups) > 0) {
|
||||
$this->appManager->enableAppForGroups($appId, $this->getGroupList($groups));
|
||||
} else {
|
||||
$this->appManager->enableApp($appId);
|
||||
}
|
||||
$updateRequired = $updateRequired || $this->appManager->isUpgradeRequired($appId);
|
||||
}
|
||||
return new JSONResponse(['data' => ['update_required' => $updateRequired]]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('could not enable apps', ['exception' => $e]);
|
||||
return new JSONResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private function getGroupList(array $groups) {
|
||||
$groupManager = Server::get(IGroupManager::class);
|
||||
$groupsList = [];
|
||||
foreach ($groups as $group) {
|
||||
$groupItem = $groupManager->get($group);
|
||||
if ($groupItem instanceof IGroup) {
|
||||
$groupsList[] = $groupManager->get($group);
|
||||
}
|
||||
}
|
||||
return $groupsList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $appId
|
||||
* @return JSONResponse
|
||||
*/
|
||||
#[PasswordConfirmationRequired]
|
||||
public function disableApp(string $appId): JSONResponse {
|
||||
return $this->disableApps([$appId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $appIds
|
||||
* @return JSONResponse
|
||||
*/
|
||||
#[PasswordConfirmationRequired]
|
||||
public function disableApps(array $appIds): JSONResponse {
|
||||
try {
|
||||
foreach ($appIds as $appId) {
|
||||
$appId = $this->appManager->cleanAppId($appId);
|
||||
$this->appManager->disableApp($appId);
|
||||
}
|
||||
return new JSONResponse([]);
|
||||
} catch (\Exception $e) {
|
||||
$this->logger->error('could not disable app', ['exception' => $e]);
|
||||
return new JSONResponse(['data' => ['message' => $e->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $appId
|
||||
* @return JSONResponse
|
||||
*/
|
||||
#[PasswordConfirmationRequired]
|
||||
public function uninstallApp(string $appId): JSONResponse {
|
||||
$appId = $this->appManager->cleanAppId($appId);
|
||||
$result = $this->installer->removeApp($appId);
|
||||
if ($result !== false) {
|
||||
// If this app was force enabled, remove the force-enabled-state
|
||||
$this->appManager->removeOverwriteNextcloudRequirement($appId);
|
||||
$this->appManager->clearAppsCache();
|
||||
return new JSONResponse(['data' => ['appid' => $appId]]);
|
||||
}
|
||||
return new JSONResponse(['data' => ['message' => $this->l10n->t('Could not remove app.')]], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $appId
|
||||
* @return JSONResponse
|
||||
*/
|
||||
public function updateApp(string $appId): JSONResponse {
|
||||
$appId = $this->appManager->cleanAppId($appId);
|
||||
|
||||
$this->config->setSystemValue('maintenance', true);
|
||||
try {
|
||||
$result = $this->installer->updateAppstoreApp($appId);
|
||||
$this->config->setSystemValue('maintenance', false);
|
||||
} catch (\Exception $ex) {
|
||||
$this->config->setSystemValue('maintenance', false);
|
||||
return new JSONResponse(['data' => ['message' => $ex->getMessage()]], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
if ($result !== false) {
|
||||
return new JSONResponse(['data' => ['appid' => $appId]]);
|
||||
}
|
||||
return new JSONResponse(['data' => ['message' => $this->l10n->t('Could not update app.')]], Http::STATUS_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
private function sortApps($a, $b) {
|
||||
$a = (string)$a['name'];
|
||||
$b = (string)$b['name'];
|
||||
if ($a === $b) {
|
||||
return 0;
|
||||
}
|
||||
return ($a < $b) ? -1 : 1;
|
||||
}
|
||||
|
||||
public function force(string $appId): JSONResponse {
|
||||
$appId = $this->appManager->cleanAppId($appId);
|
||||
$this->appManager->overwriteNextcloudRequirement($appId);
|
||||
return new JSONResponse();
|
||||
}
|
||||
}
|
||||
197
apps/appstore/lib/Controller/DiscoverController.php
Normal file
197
apps/appstore/lib/Controller/DiscoverController.php
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Appstore\Controller;
|
||||
|
||||
use OC\App\AppStore\Fetcher\AppDiscoverFetcher;
|
||||
use OC\App\AppStore\Fetcher\ResponseDefinitions;
|
||||
use OCA\Appstore\AppInfo\Application;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\Attribute\ApiRoute;
|
||||
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
||||
use OCP\AppFramework\Http\Attribute\OpenAPI;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\Http\FileDisplayResponse;
|
||||
use OCP\AppFramework\OCS\OCSBadRequestException;
|
||||
use OCP\AppFramework\OCS\OCSNotFoundException;
|
||||
use OCP\AppFramework\OCSController;
|
||||
use OCP\Files\AppData\IAppDataFactory;
|
||||
use OCP\Files\IAppData;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\Files\SimpleFS\ISimpleFile;
|
||||
use OCP\Files\SimpleFS\ISimpleFolder;
|
||||
use OCP\Http\Client\IClientService;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserSession;
|
||||
use OCP\Security\RateLimiting\ILimiter;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* @psalm-import-type AppStoreFetcherDiscoverElement from ResponseDefinitions
|
||||
*/
|
||||
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
|
||||
class DiscoverController extends OCSController {
|
||||
|
||||
private IAppData $appData;
|
||||
|
||||
public function __construct(
|
||||
IRequest $request,
|
||||
IAppDataFactory $appDataFactory,
|
||||
private IClientService $clientService,
|
||||
private AppDiscoverFetcher $discoverFetcher,
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
parent::__construct(Application::APP_ID, $request);
|
||||
$this->appData = $appDataFactory->get(Application::APP_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active entries for the app discover section
|
||||
*
|
||||
* @return DataResponse<Http::STATUS_OK, list<AppStoreFetcherDiscoverElement>, array{}>
|
||||
*
|
||||
* 200: List of active entries for the app discover section
|
||||
*/
|
||||
#[NoCSRFRequired]
|
||||
#[ApiRoute(verb: 'GET', url:'/api/v1/discover')]
|
||||
public function getAppDiscoverJSON(): DataResponse {
|
||||
$data = $this->discoverFetcher->get(true);
|
||||
return new DataResponse($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a image for the app discover section - this is proxied for privacy and CSP reasons
|
||||
*
|
||||
* @param string $fileName - The image file name
|
||||
* @return FileDisplayResponse<Http::STATUS_OK, array{'Content-Type': string}>
|
||||
* @throws OCSBadRequestException - if the media source is not trusted
|
||||
* @throws OCSNotFoundException - if the media file could not be found
|
||||
*
|
||||
* 200: The media file was found and is returned
|
||||
* 400: The media source is not trusted
|
||||
* 404: The media file could not be found
|
||||
*/
|
||||
#[NoCSRFRequired]
|
||||
#[ApiRoute(verb: 'GET', url: '/api/v1/discover/media')]
|
||||
public function getAppDiscoverMedia(string $fileName, ILimiter $limiter, IUserSession $session): FileDisplayResponse {
|
||||
$getEtag = $this->discoverFetcher->getETag() ?? date('Y-m');
|
||||
$etag = trim($getEtag, '"');
|
||||
|
||||
$folder = null;
|
||||
try {
|
||||
$folder = $this->appData->getFolder('app-discover-cache');
|
||||
$this->cleanUpImageCache($folder, $etag);
|
||||
} catch (\Throwable $e) {
|
||||
$folder = $this->appData->newFolder('app-discover-cache');
|
||||
}
|
||||
|
||||
// Get the current cache folder
|
||||
try {
|
||||
$folder = $folder->getFolder($etag);
|
||||
} catch (NotFoundException $e) {
|
||||
$folder = $folder->newFolder($etag);
|
||||
}
|
||||
|
||||
$info = pathinfo($fileName);
|
||||
$hashName = md5($fileName);
|
||||
$allFiles = $folder->getDirectoryListing();
|
||||
// Try to find the file
|
||||
$file = array_filter($allFiles, function (ISimpleFile $file) use ($hashName) {
|
||||
return str_starts_with($file->getName(), $hashName);
|
||||
});
|
||||
// Get the first entry
|
||||
$file = reset($file);
|
||||
// If not found request from Web
|
||||
if ($file === false) {
|
||||
$user = $session->getUser();
|
||||
// this route is not public thus we can assume a user is logged-in
|
||||
assert($user !== null);
|
||||
// Register a user request to throttle fetching external data
|
||||
// this will prevent using the server for DoS of other systems.
|
||||
$limiter->registerUserRequest(
|
||||
'settings-discover-media',
|
||||
// allow up to 24 media requests per hour
|
||||
// this should be a sane default when a completely new section is loaded
|
||||
// keep in mind browsers request all files from a source-set
|
||||
24,
|
||||
60 * 60,
|
||||
$user,
|
||||
);
|
||||
|
||||
if (!$this->checkCanDownloadMedia($fileName)) {
|
||||
$this->logger->warning('Tried to load media files for app discover section from untrusted source');
|
||||
throw new OCSBadRequestException('Untrusted media source');
|
||||
}
|
||||
|
||||
try {
|
||||
$client = $this->clientService->newClient();
|
||||
$fileResponse = $client->get($fileName);
|
||||
$contentType = $fileResponse->getHeader('Content-Type');
|
||||
$extension = $info['extension'] ?? '';
|
||||
$file = $folder->newFile($hashName . '.' . base64_encode($contentType) . '.' . $extension, $fileResponse->getBody());
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->warning('Could not load media file for app discover section', ['media_src' => $fileName, 'exception' => $e]);
|
||||
throw new OCSNotFoundException('Media file not found');
|
||||
}
|
||||
} else {
|
||||
// File was found so we can get the content type from the file name
|
||||
$contentType = base64_decode(explode('.', $file->getName())[1] ?? '');
|
||||
}
|
||||
|
||||
$response = new FileDisplayResponse($file, Http::STATUS_OK, ['Content-Type' => $contentType]);
|
||||
// cache for 7 days
|
||||
$response->cacheFor(604800, false, true);
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function checkCanDownloadMedia(string $filename): bool {
|
||||
$urlInfo = parse_url($filename);
|
||||
if (!isset($urlInfo['host']) || !isset($urlInfo['path'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Always allowed hosts
|
||||
if ($urlInfo['host'] === 'nextcloud.com') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Hosts that need further verification
|
||||
// Github is only allowed if from our organization
|
||||
$ALLOWED_HOSTS = ['github.com', 'raw.githubusercontent.com'];
|
||||
if (!in_array($urlInfo['host'], $ALLOWED_HOSTS)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (str_starts_with($urlInfo['path'], '/nextcloud/') || str_starts_with($urlInfo['path'], '/nextcloud-gmbh/')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove orphaned folders from the image cache that do not match the current etag
|
||||
* @param ISimpleFolder $folder The folder to clear
|
||||
* @param string $etag The etag (directory name) to keep
|
||||
*/
|
||||
private function cleanUpImageCache(ISimpleFolder $folder, string $etag): void {
|
||||
// Cleanup old cache folders
|
||||
$allFiles = $folder->getDirectoryListing();
|
||||
foreach ($allFiles as $dir) {
|
||||
try {
|
||||
if ($dir->getName() !== $etag) {
|
||||
$dir->delete();
|
||||
}
|
||||
} catch (NotPermittedException $e) {
|
||||
// ignore folder for now
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
110
apps/appstore/lib/Controller/PageController.php
Normal file
110
apps/appstore/lib/Controller/PageController.php
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace OCA\Appstore\Controller;
|
||||
|
||||
use OC\App\AppManager;
|
||||
use OC\App\AppStore\Bundles\BundleFetcher;
|
||||
use OC\Installer;
|
||||
use OCA\AppAPI\Service\ExAppsPageService;
|
||||
use OCA\Appstore\AppInfo\Application;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Http\Attribute\FrontpageRoute;
|
||||
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
|
||||
use OCP\AppFramework\Http\Attribute\OpenAPI;
|
||||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\INavigationManager;
|
||||
use OCP\IRequest;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Server;
|
||||
use OCP\Util;
|
||||
|
||||
#[OpenAPI(scope: OpenAPI::SCOPE_IGNORE)]
|
||||
class PageController extends Controller {
|
||||
|
||||
public function __construct(
|
||||
IRequest $request,
|
||||
private IL10N $l10n,
|
||||
private IConfig $config,
|
||||
private Installer $installer,
|
||||
private AppManager $appManager,
|
||||
private IURLGenerator $urlGenerator,
|
||||
private IInitialState $initialState,
|
||||
private BundleFetcher $bundleFetcher,
|
||||
private INavigationManager $navigationManager,
|
||||
) {
|
||||
parent::__construct(Application::APP_ID, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @psalm-suppress UndefinedClass AppAPI is shipped since 30.0.1
|
||||
*
|
||||
* @return TemplateResponse
|
||||
*/
|
||||
#[NoCSRFRequired]
|
||||
#[FrontpageRoute(verb: 'GET', url: '/settings/apps', root: '')]
|
||||
#[FrontpageRoute(verb: 'GET', url: '/settings/apps/{category}', defaults: ['category' => ''], root: '')]
|
||||
#[FrontpageRoute(verb: 'GET', url: '/settings/apps/{category}/{id}', defaults: ['category' => '', 'id' => ''], root: '')]
|
||||
public function viewApps(): TemplateResponse {
|
||||
$this->navigationManager->setActiveEntry('core_apps');
|
||||
|
||||
$this->initialState->provideInitialState('appstoreEnabled', $this->config->getSystemValueBool('appstoreenabled', true));
|
||||
$this->initialState->provideInitialState('appstoreBundles', $this->getBundles());
|
||||
$this->initialState->provideInitialState('appstoreDeveloperDocs', $this->urlGenerator->linkToDocs('developer-manual'));
|
||||
$this->initialState->provideInitialState('appstoreUpdateCount', count($this->getAppsWithUpdates()));
|
||||
|
||||
if ($this->appManager->isEnabledForAnyone('app_api')) {
|
||||
try {
|
||||
Server::get(ExAppsPageService::class)->provideAppApiState($this->initialState);
|
||||
} catch (\Psr\Container\NotFoundExceptionInterface|\Psr\Container\ContainerExceptionInterface $e) {
|
||||
}
|
||||
}
|
||||
|
||||
$policy = new ContentSecurityPolicy();
|
||||
$policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
|
||||
|
||||
$templateResponse = new TemplateResponse(Application::APP_ID, 'empty', ['pageTitle' => $this->l10n->t('App store')]);
|
||||
$templateResponse->setContentSecurityPolicy($policy);
|
||||
|
||||
Util::addStyle(Application::APP_ID, 'main');
|
||||
Util::addScript(Application::APP_ID, 'main');
|
||||
|
||||
return $templateResponse;
|
||||
}
|
||||
|
||||
|
||||
private function getAppsWithUpdates() {
|
||||
$appClass = new \OC_App();
|
||||
$apps = $appClass->listAllApps();
|
||||
foreach ($apps as $key => $app) {
|
||||
$newVersion = $this->installer->isUpdateAvailable($app['id']);
|
||||
if ($newVersion === false) {
|
||||
unset($apps[$key]);
|
||||
}
|
||||
}
|
||||
return $apps;
|
||||
}
|
||||
|
||||
private function getBundles() {
|
||||
$result = [];
|
||||
$bundles = $this->bundleFetcher->getBundles();
|
||||
foreach ($bundles as $bundle) {
|
||||
$result[] = [
|
||||
'name' => $bundle->getName(),
|
||||
'id' => $bundle->getIdentifier(),
|
||||
'appIdentifiers' => $bundle->getAppIdentifiers()
|
||||
];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ declare(strict_types=1);
|
|||
*/
|
||||
namespace OCA\Appstore\Search;
|
||||
|
||||
use OCA\Appstore\AppInfo\Application;
|
||||
use OCP\IL10N;
|
||||
use OCP\INavigationManager;
|
||||
use OCP\IUser;
|
||||
|
|
@ -25,7 +26,7 @@ class AppSearch implements IProvider {
|
|||
|
||||
#[\Override]
|
||||
public function getId(): string {
|
||||
return 'settings_apps';
|
||||
return Application::APP_ID;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
|
|
@ -35,7 +36,7 @@ class AppSearch implements IProvider {
|
|||
|
||||
#[\Override]
|
||||
public function getOrder(string $route, array $routeParameters): int {
|
||||
return $route === 'settings.AppSettings.viewApps' ? -50 : 100;
|
||||
return $route === 'appstore.Page.viewApps' ? -50 : 100;
|
||||
}
|
||||
|
||||
#[\Override]
|
||||
|
|
|
|||
|
|
@ -1,104 +0,0 @@
|
|||
{
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "settings-administration",
|
||||
"version": "0.0.1",
|
||||
"description": "Nextcloud settings",
|
||||
"license": {
|
||||
"name": "agpl"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"securitySchemes": {
|
||||
"basic_auth": {
|
||||
"type": "http",
|
||||
"scheme": "basic"
|
||||
},
|
||||
"bearer_auth": {
|
||||
"type": "http",
|
||||
"scheme": "bearer"
|
||||
}
|
||||
},
|
||||
"schemas": {}
|
||||
},
|
||||
"paths": {
|
||||
"/index.php/settings/admin/log/download": {
|
||||
"get": {
|
||||
"operationId": "log_settings-download",
|
||||
"summary": "download logfile",
|
||||
"description": "This endpoint requires admin access",
|
||||
"tags": [
|
||||
"log_settings"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Logfile returned",
|
||||
"headers": {
|
||||
"Content-Disposition": {
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"attachment; filename=\"nextcloud.log\""
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"application/octet-stream": {
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "binary"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"message"
|
||||
],
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Logged in account must be an admin",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"message"
|
||||
],
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
|
|
@ -1,709 +0,0 @@
|
|||
{
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "settings-full",
|
||||
"version": "0.0.1",
|
||||
"description": "Nextcloud settings",
|
||||
"license": {
|
||||
"name": "agpl"
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"securitySchemes": {
|
||||
"basic_auth": {
|
||||
"type": "http",
|
||||
"scheme": "basic"
|
||||
},
|
||||
"bearer_auth": {
|
||||
"type": "http",
|
||||
"scheme": "bearer"
|
||||
}
|
||||
},
|
||||
"schemas": {
|
||||
"DeclarativeForm": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"priority",
|
||||
"section_type",
|
||||
"section_id",
|
||||
"storage_type",
|
||||
"title",
|
||||
"app",
|
||||
"fields"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"priority": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"section_type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"admin",
|
||||
"personal"
|
||||
]
|
||||
},
|
||||
"section_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"storage_type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"internal",
|
||||
"external"
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"doc_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"app": {
|
||||
"type": "string"
|
||||
},
|
||||
"fields": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/DeclarativeFormField"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"DeclarativeFormField": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"title",
|
||||
"type",
|
||||
"default",
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"text",
|
||||
"password",
|
||||
"email",
|
||||
"tel",
|
||||
"url",
|
||||
"number",
|
||||
"checkbox",
|
||||
"multi-checkbox",
|
||||
"radio",
|
||||
"select",
|
||||
"multi-select"
|
||||
]
|
||||
},
|
||||
"placeholder": {
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": {
|
||||
"type": "object"
|
||||
},
|
||||
"options": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"value": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"format": "double"
|
||||
},
|
||||
{
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"sensitive": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"OCSMeta": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"status",
|
||||
"statuscode"
|
||||
],
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"statuscode": {
|
||||
"type": "integer"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"totalitems": {
|
||||
"type": "string"
|
||||
},
|
||||
"itemsperpage": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"paths": {
|
||||
"/index.php/settings/admin/log/download": {
|
||||
"get": {
|
||||
"operationId": "log_settings-download",
|
||||
"summary": "download logfile",
|
||||
"description": "This endpoint requires admin access",
|
||||
"tags": [
|
||||
"log_settings"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Logfile returned",
|
||||
"headers": {
|
||||
"Content-Disposition": {
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"attachment; filename=\"nextcloud.log\""
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"content": {
|
||||
"application/octet-stream": {
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"format": "binary"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"message"
|
||||
],
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Logged in account must be an admin",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"message"
|
||||
],
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/settings/api/declarative/value": {
|
||||
"post": {
|
||||
"operationId": "declarative_settings-set-value",
|
||||
"summary": "Sets a declarative settings value",
|
||||
"tags": [
|
||||
"declarative_settings"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"app",
|
||||
"formId",
|
||||
"fieldId",
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"app": {
|
||||
"type": "string",
|
||||
"description": "ID of the app"
|
||||
},
|
||||
"formId": {
|
||||
"type": "string",
|
||||
"description": "ID of the form"
|
||||
},
|
||||
"fieldId": {
|
||||
"type": "string",
|
||||
"description": "ID of the field"
|
||||
},
|
||||
"value": {
|
||||
"type": "object",
|
||||
"description": "Value to be saved"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Value set successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Not logged in or not an admin user",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid arguments to save value",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/settings/api/declarative/value-sensitive": {
|
||||
"post": {
|
||||
"operationId": "declarative_settings-set-sensitive-value",
|
||||
"summary": "Sets a declarative settings value. Password confirmation is required for sensitive values.",
|
||||
"description": "This endpoint requires password confirmation",
|
||||
"tags": [
|
||||
"declarative_settings"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"app",
|
||||
"formId",
|
||||
"fieldId",
|
||||
"value"
|
||||
],
|
||||
"properties": {
|
||||
"app": {
|
||||
"type": "string",
|
||||
"description": "ID of the app"
|
||||
},
|
||||
"formId": {
|
||||
"type": "string",
|
||||
"description": "ID of the form"
|
||||
},
|
||||
"fieldId": {
|
||||
"type": "string",
|
||||
"description": "ID of the field"
|
||||
},
|
||||
"value": {
|
||||
"type": "object",
|
||||
"description": "Value to be saved"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Value set successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Not logged in or not an admin user",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid arguments to save value",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/settings/api/declarative/forms": {
|
||||
"get": {
|
||||
"operationId": "declarative_settings-get-forms",
|
||||
"summary": "Gets all declarative forms with the values prefilled.",
|
||||
"tags": [
|
||||
"declarative_settings"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Forms returned",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/DeclarativeForm"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -79,8 +79,8 @@
|
|||
<span class="hidden-visually">{{ t('appstore', 'Actions') }}</span>
|
||||
</th>
|
||||
</tr>
|
||||
<template v-for="bundle in bundles">
|
||||
<tr :key="bundle.id">
|
||||
<tbody v-for="bundle in bundles" :key="bundle.id">
|
||||
<tr>
|
||||
<th :id="`app-table-rowgroup-${bundle.id}`" colspan="5" scope="rowgroup">
|
||||
<div class="apps-list__bundle-heading">
|
||||
<span class="apps-list__bundle-header">
|
||||
|
|
@ -99,7 +99,7 @@
|
|||
:headers="`app-table-rowgroup-${bundle.id}`"
|
||||
:app="app"
|
||||
:category="category" />
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
<ul v-if="useAppStoreView" class="apps-list__store-container">
|
||||
<AppItem
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { OCSResponse } from '@nextcloud/typings/ocs'
|
||||
import type { IAppDiscoverElements } from '../../constants/AppDiscoverTypes.ts'
|
||||
|
||||
import { mdiEyeOffOutline } from '@mdi/js'
|
||||
|
|
@ -70,7 +71,8 @@ function shuffleArray<T>(array: T[]): T[] {
|
|||
*/
|
||||
onBeforeMount(async () => {
|
||||
try {
|
||||
const { data } = await axios.get<Record<string, unknown>[]>(generateUrl('/settings/api/apps/discover'))
|
||||
const response = await axios.get<OCSResponse<Record<string, unknown>[]>>(generateOcsUrl('/apps/appstore/api/v1/discover'))
|
||||
const { data } = response.data.ocs
|
||||
if (data.length === 0) {
|
||||
logger.info('No app discover elements available (empty response)')
|
||||
hasError.value = true
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
?>
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCA\Settings\Tests\AppInfo;
|
||||
|
||||
use OCA\Settings\AppInfo\Application;
|
||||
use OCA\Settings\Controller\AdminSettingsController;
|
||||
use OCA\Settings\Controller\AppSettingsController;
|
||||
use OCA\Settings\Controller\AuthSettingsController;
|
||||
use OCA\Settings\Controller\CheckSetupController;
|
||||
use OCA\Settings\Controller\LogSettingsController;
|
||||
use OCA\Settings\Controller\MailSettingsController;
|
||||
use OCA\Settings\Controller\UsersController;
|
||||
use OCA\Settings\Middleware\SubadminMiddleware;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\IAppContainer;
|
||||
use OCP\AppFramework\Middleware;
|
||||
use Test\TestCase;
|
||||
|
||||
/**
|
||||
* Class ApplicationTest
|
||||
*
|
||||
* @package Tests\Settings
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\Group('DB')]
|
||||
class ApplicationTest extends TestCase {
|
||||
protected Application $app;
|
||||
protected IAppContainer $container;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->app = new Application();
|
||||
$this->container = $this->app->getContainer();
|
||||
}
|
||||
|
||||
public function testContainerAppName(): void {
|
||||
$this->app = new Application();
|
||||
$this->assertEquals('settings', $this->container->get('appName'));
|
||||
}
|
||||
|
||||
public static function dataContainerQuery(): array {
|
||||
return [
|
||||
[AdminSettingsController::class, Controller::class],
|
||||
[AppSettingsController::class, Controller::class],
|
||||
[AuthSettingsController::class, Controller::class],
|
||||
[CheckSetupController::class, Controller::class],
|
||||
[LogSettingsController::class, Controller::class],
|
||||
[MailSettingsController::class, Controller::class],
|
||||
[UsersController::class, Controller::class],
|
||||
|
||||
[SubadminMiddleware::class, Middleware::class],
|
||||
];
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('dataContainerQuery')]
|
||||
public function testContainerQuery(string $service, string $expected): void {
|
||||
$this->assertTrue($this->container->query($service) instanceof $expected);
|
||||
}
|
||||
}
|
||||
90
apps/appstore/tests/Controller/ApiControllerTest.php
Normal file
90
apps/appstore/tests/Controller/ApiControllerTest.php
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCA\Appstore\Tests\Controller;
|
||||
|
||||
use OC\App\AppManager;
|
||||
use OC\App\AppStore\Bundles\BundleFetcher;
|
||||
use OC\App\AppStore\Fetcher\AppFetcher;
|
||||
use OC\App\AppStore\Fetcher\CategoryFetcher;
|
||||
use OC\App\DependencyAnalyzer;
|
||||
use OC\Installer;
|
||||
use OCA\Appstore\Controller\ApiController;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\IAppConfig;
|
||||
use OCP\IConfig;
|
||||
use OCP\IRequest;
|
||||
use OCP\L10N\IFactory;
|
||||
use OCP\Support\Subscription\IRegistry;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Test\TestCase;
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\Group('DB')]
|
||||
class ApiControllerTest extends TestCase {
|
||||
private IRequest&MockObject $request;
|
||||
private IConfig&MockObject $config;
|
||||
private IAppConfig&MockObject $appConfig;
|
||||
private AppManager&MockObject $appManager;
|
||||
private DependencyAnalyzer&MockObject $dependencyAnalyzer;
|
||||
private CategoryFetcher&MockObject $categoryFetcher;
|
||||
private AppFetcher&MockObject $appFetcher;
|
||||
private IFactory&MockObject $l10nFactory;
|
||||
private BundleFetcher&MockObject $bundleFetcher;
|
||||
private Installer&MockObject $installer;
|
||||
private IRegistry&MockObject $subscriptionRegistry;
|
||||
private LoggerInterface&MockObject $logger;
|
||||
|
||||
private ApiController $apiController;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->appConfig = $this->createMock(IAppConfig::class);
|
||||
$this->appManager = $this->createMock(AppManager::class);
|
||||
$this->dependencyAnalyzer = $this->createMock(DependencyAnalyzer::class);
|
||||
$this->categoryFetcher = $this->createMock(CategoryFetcher::class);
|
||||
$this->appFetcher = $this->createMock(AppFetcher::class);
|
||||
$this->l10nFactory = $this->createMock(IFactory::class);
|
||||
$this->bundleFetcher = $this->createMock(BundleFetcher::class);
|
||||
$this->installer = $this->createMock(Installer::class);
|
||||
$this->subscriptionRegistry = $this->createMock(IRegistry::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
|
||||
$this->apiController = new ApiController(
|
||||
$this->request,
|
||||
$this->config,
|
||||
$this->appConfig,
|
||||
$this->appManager,
|
||||
$this->dependencyAnalyzer,
|
||||
$this->categoryFetcher,
|
||||
$this->appFetcher,
|
||||
$this->l10nFactory,
|
||||
$this->bundleFetcher,
|
||||
$this->installer,
|
||||
$this->subscriptionRegistry,
|
||||
$this->logger,
|
||||
);
|
||||
}
|
||||
|
||||
public function testListCategories(): void {
|
||||
$json = file_get_contents(__DIR__ . '/../fixtures/categories.json');
|
||||
$this->categoryFetcher
|
||||
->expects($this->once())
|
||||
->method('get')
|
||||
->willReturn(json_decode($json, true)['data']);
|
||||
|
||||
$response = $this->apiController->listCategories();
|
||||
$this->assertInstanceOf(DataResponse::class, $response);
|
||||
$this->assertSame(200, $response->getStatus());
|
||||
|
||||
$jsonResponse = json_encode($response->getData());
|
||||
$this->assertJsonStringEqualsJsonFile(__DIR__ . '/../fixtures/categories-api-response.json', $jsonResponse);
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
132
apps/appstore/tests/Controller/PageControllerTest.php
Normal file
132
apps/appstore/tests/Controller/PageControllerTest.php
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*!
|
||||
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
*/
|
||||
namespace OCA\Appstore\Tests\Controller;
|
||||
|
||||
use OC\App\AppManager;
|
||||
use OC\App\AppStore\Bundles\BundleFetcher;
|
||||
use OC\Installer;
|
||||
use OCA\Appstore\Controller\PageController;
|
||||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Services\IInitialState;
|
||||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\INavigationManager;
|
||||
use OCP\IRequest;
|
||||
use OCP\IURLGenerator;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Test\TestCase;
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\Group('DB')]
|
||||
class PageControllerTest extends TestCase {
|
||||
private IRequest&MockObject $request;
|
||||
private IL10N&MockObject $l10n;
|
||||
private IConfig&MockObject $config;
|
||||
private INavigationManager&MockObject $navigationManager;
|
||||
private AppManager&MockObject $appManager;
|
||||
private BundleFetcher&MockObject $bundleFetcher;
|
||||
private Installer&MockObject $installer;
|
||||
private IURLGenerator&MockObject $urlGenerator;
|
||||
private IInitialState&MockObject $initialState;
|
||||
private PageController $pageController;
|
||||
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->l10n = $this->createMock(IL10N::class);
|
||||
$this->l10n->expects($this->any())
|
||||
->method('t')
|
||||
->willReturnArgument(0);
|
||||
$this->config = $this->createMock(IConfig::class);
|
||||
$this->navigationManager = $this->createMock(INavigationManager::class);
|
||||
$this->appManager = $this->createMock(AppManager::class);
|
||||
$this->bundleFetcher = $this->createMock(BundleFetcher::class);
|
||||
$this->installer = $this->createMock(Installer::class);
|
||||
$this->urlGenerator = $this->createMock(IURLGenerator::class);
|
||||
$this->initialState = $this->createMock(IInitialState::class);
|
||||
|
||||
$this->pageController = new PageController(
|
||||
$this->request,
|
||||
$this->l10n,
|
||||
$this->config,
|
||||
$this->installer,
|
||||
$this->appManager,
|
||||
$this->urlGenerator,
|
||||
$this->initialState,
|
||||
$this->bundleFetcher,
|
||||
$this->navigationManager,
|
||||
);
|
||||
}
|
||||
|
||||
public function testViewApps(): void {
|
||||
$this->bundleFetcher->expects($this->once())->method('getBundles')->willReturn([]);
|
||||
$this->installer->expects($this->any())
|
||||
->method('isUpdateAvailable')
|
||||
->willReturn(false);
|
||||
$this->config
|
||||
->expects($this->once())
|
||||
->method('getSystemValueBool')
|
||||
->with('appstoreenabled', true)
|
||||
->willReturn(true);
|
||||
$this->navigationManager
|
||||
->expects($this->once())
|
||||
->method('setActiveEntry')
|
||||
->with('core_apps');
|
||||
|
||||
$this->initialState
|
||||
->expects($this->exactly(4))
|
||||
->method('provideInitialState');
|
||||
|
||||
$policy = new ContentSecurityPolicy();
|
||||
$policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
|
||||
|
||||
$expected = new TemplateResponse('appstore',
|
||||
'empty',
|
||||
[
|
||||
'pageTitle' => 'App store'
|
||||
],
|
||||
'user');
|
||||
$expected->setContentSecurityPolicy($policy);
|
||||
|
||||
$this->assertEquals($expected, $this->pageController->viewApps());
|
||||
}
|
||||
|
||||
public function testViewAppsAppstoreNotEnabled(): void {
|
||||
$this->installer->expects($this->any())
|
||||
->method('isUpdateAvailable')
|
||||
->willReturn(false);
|
||||
$this->bundleFetcher->expects($this->once())->method('getBundles')->willReturn([]);
|
||||
$this->config
|
||||
->expects($this->once())
|
||||
->method('getSystemValueBool')
|
||||
->with('appstoreenabled', true)
|
||||
->willReturn(false);
|
||||
$this->navigationManager
|
||||
->expects($this->once())
|
||||
->method('setActiveEntry')
|
||||
->with('core_apps');
|
||||
|
||||
$this->initialState
|
||||
->expects($this->exactly(4))
|
||||
->method('provideInitialState');
|
||||
|
||||
$policy = new ContentSecurityPolicy();
|
||||
$policy->addAllowedImageDomain('https://usercontent.apps.nextcloud.com');
|
||||
|
||||
$expected = new TemplateResponse('appstore',
|
||||
'empty',
|
||||
[
|
||||
'pageTitle' => 'App store'
|
||||
],
|
||||
'user');
|
||||
$expected->setContentSecurityPolicy($policy);
|
||||
|
||||
$this->assertEquals($expected, $this->pageController->viewApps());
|
||||
}
|
||||
}
|
||||
62
apps/appstore/tests/fixtures/categories-api-response.json
vendored
Normal file
62
apps/appstore/tests/fixtures/categories-api-response.json
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
[
|
||||
{
|
||||
"displayName": "AI",
|
||||
"id": "ai"
|
||||
},
|
||||
{
|
||||
"displayName": "Customization",
|
||||
"id": "customization"
|
||||
},
|
||||
{
|
||||
"displayName": "Dashboard",
|
||||
"id": "dashboard"
|
||||
},
|
||||
{
|
||||
"displayName": "Files",
|
||||
"id": "files"
|
||||
},
|
||||
{
|
||||
"displayName": "Games",
|
||||
"id": "games"
|
||||
},
|
||||
{
|
||||
"displayName": "Integration",
|
||||
"id": "integration"
|
||||
},
|
||||
{
|
||||
"displayName": "Monitoring",
|
||||
"id": "monitoring"
|
||||
},
|
||||
{
|
||||
"displayName": "Multimedia",
|
||||
"id": "multimedia"
|
||||
},
|
||||
{
|
||||
"displayName": "Office & text",
|
||||
"id": "office"
|
||||
},
|
||||
{
|
||||
"displayName": "Organization",
|
||||
"id": "organization"
|
||||
},
|
||||
{
|
||||
"displayName": "Search",
|
||||
"id": "search"
|
||||
},
|
||||
{
|
||||
"displayName": "Security",
|
||||
"id": "security"
|
||||
},
|
||||
{
|
||||
"displayName": "Social & communication",
|
||||
"id": "social"
|
||||
},
|
||||
{
|
||||
"displayName": "Tools",
|
||||
"id": "tools"
|
||||
},
|
||||
{
|
||||
"displayName": "Flow",
|
||||
"id": "workflow"
|
||||
}
|
||||
]
|
||||
202
apps/appstore/tests/fixtures/categories.json
vendored
Normal file
202
apps/appstore/tests/fixtures/categories.json
vendored
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
{
|
||||
"data": [
|
||||
{
|
||||
"id": "ai",
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "AI",
|
||||
"description": "Artificial Intelligence apps"
|
||||
},
|
||||
"de": {
|
||||
"name": "AI",
|
||||
"description": "Apps für künstliche Intelligenz"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "customization",
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "Customization",
|
||||
"description": "Themes, layout and UX change apps"
|
||||
},
|
||||
"de": {
|
||||
"name": "Anpassung",
|
||||
"description": "Apps zur Änderung von Design, Layout und Benutzererfahrung"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "dashboard",
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "Dashboard",
|
||||
"description": "Apps including Nextcloud Dashboard widgets"
|
||||
},
|
||||
"de": {
|
||||
"name": "Dashboard",
|
||||
"description": "Apps einschließlich Nextcloud Dashboard-Widgets"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "files",
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "Files",
|
||||
"description": "File management and Files app extension apps"
|
||||
},
|
||||
"de": {
|
||||
"name": "Dateien",
|
||||
"description": "Dateimanagement sowie Erweiterungs-Apps für die Dateien-App"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "games",
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "Games",
|
||||
"description": "Games run in your Nextcloud"
|
||||
},
|
||||
"de": {
|
||||
"name": "Spiele",
|
||||
"description": "Spiele für deine Nextcloud"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "integration",
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "Integration",
|
||||
"description": "Apps that connect Nextcloud with other services and platforms"
|
||||
},
|
||||
"de": {
|
||||
"name": "Einbindung",
|
||||
"description": "Apps die Nextcloud mit anderen Diensten und Plattformen verbinden"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "monitoring",
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "Monitoring",
|
||||
"description": "Data statistics, system diagnostics and activity apps"
|
||||
},
|
||||
"de": {
|
||||
"name": "Überwachung",
|
||||
"description": "Datenstatistiken-, Systemdiagnose- und Aktivitäten-Apps"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "multimedia",
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "Multimedia",
|
||||
"description": "Audio, video and picture apps"
|
||||
},
|
||||
"de": {
|
||||
"name": "Multimedia",
|
||||
"description": "Audio-, Video- und Bilder-Apps"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "office",
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "Office & text",
|
||||
"description": "Office and text processing apps"
|
||||
},
|
||||
"de": {
|
||||
"name": "Büro & Text",
|
||||
"description": "Büro- und Textverarbeitungs-Apps"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "organization",
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "Organization",
|
||||
"description": "Time management, Todo list and calendar apps"
|
||||
},
|
||||
"de": {
|
||||
"name": "Organisation",
|
||||
"description": "Time management, Todo list and calendar apps"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "search",
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "Search",
|
||||
"description": "Search related apps"
|
||||
},
|
||||
"de": {
|
||||
"name": "Suche",
|
||||
"description": "Verwandte Apps durchsuchen"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "security",
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "Security",
|
||||
"description": "Apps that provide additional security mechanisms like authentication, authorization, encryption, etc."
|
||||
},
|
||||
"de": {
|
||||
"name": "Sicherheit",
|
||||
"description": "Apps die zusätzliche Sicherheitsmechanismen bereitstellen, wie z. B. Authentifizierung, Autorisierung, Verschlüsselung usw."
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "social",
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "Social & communication",
|
||||
"description": "Messaging, contact management and social media apps"
|
||||
},
|
||||
"de": {
|
||||
"name": "Kommunikation",
|
||||
"description": "Nachrichten-, Kontaktverwaltungs- und Social-Media-Apps"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tools",
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "Tools",
|
||||
"description": "Everything else"
|
||||
},
|
||||
"de": {
|
||||
"name": "Werkzeuge",
|
||||
"description": "Alles Andere"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "workflow",
|
||||
"translations": {
|
||||
"en": {
|
||||
"name": "Flow",
|
||||
"description": "Apps for Nextcloud Flow"
|
||||
},
|
||||
"de": {
|
||||
"name": "Flow",
|
||||
"description": "Apps für Nextcloud Flow"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"timestamp": 1770475258,
|
||||
"ncversion": "34.0.0.0",
|
||||
"ETag": "\"6986f131-6575\""
|
||||
}
|
||||
|
|
@ -22,7 +22,6 @@ return array(
|
|||
'OCA\\Settings\\ConfigLexicon' => $baseDir . '/../lib/ConfigLexicon.php',
|
||||
'OCA\\Settings\\Controller\\AISettingsController' => $baseDir . '/../lib/Controller/AISettingsController.php',
|
||||
'OCA\\Settings\\Controller\\AdminSettingsController' => $baseDir . '/../lib/Controller/AdminSettingsController.php',
|
||||
'OCA\\Settings\\Controller\\AppSettingsController' => $baseDir . '/../lib/Controller/AppSettingsController.php',
|
||||
'OCA\\Settings\\Controller\\AuthSettingsController' => $baseDir . '/../lib/Controller/AuthSettingsController.php',
|
||||
'OCA\\Settings\\Controller\\AuthorizedGroupController' => $baseDir . '/../lib/Controller/AuthorizedGroupController.php',
|
||||
'OCA\\Settings\\Controller\\ChangePasswordController' => $baseDir . '/../lib/Controller/ChangePasswordController.php',
|
||||
|
|
@ -48,7 +47,6 @@ return array(
|
|||
'OCA\\Settings\\Mailer\\NewUserMailHelper' => $baseDir . '/../lib/Mailer/NewUserMailHelper.php',
|
||||
'OCA\\Settings\\Middleware\\SubadminMiddleware' => $baseDir . '/../lib/Middleware/SubadminMiddleware.php',
|
||||
'OCA\\Settings\\ResponseDefinitions' => $baseDir . '/../lib/ResponseDefinitions.php',
|
||||
'OCA\\Settings\\Search\\AppSearch' => $baseDir . '/../lib/Search/AppSearch.php',
|
||||
'OCA\\Settings\\Search\\SectionSearch' => $baseDir . '/../lib/Search/SectionSearch.php',
|
||||
'OCA\\Settings\\Search\\UserSearch' => $baseDir . '/../lib/Search/UserSearch.php',
|
||||
'OCA\\Settings\\Sections\\Admin\\Additional' => $baseDir . '/../lib/Sections/Admin/Additional.php',
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ class ComposerStaticInitSettings
|
|||
'OCA\\Settings\\ConfigLexicon' => __DIR__ . '/..' . '/../lib/ConfigLexicon.php',
|
||||
'OCA\\Settings\\Controller\\AISettingsController' => __DIR__ . '/..' . '/../lib/Controller/AISettingsController.php',
|
||||
'OCA\\Settings\\Controller\\AdminSettingsController' => __DIR__ . '/..' . '/../lib/Controller/AdminSettingsController.php',
|
||||
'OCA\\Settings\\Controller\\AppSettingsController' => __DIR__ . '/..' . '/../lib/Controller/AppSettingsController.php',
|
||||
'OCA\\Settings\\Controller\\AuthSettingsController' => __DIR__ . '/..' . '/../lib/Controller/AuthSettingsController.php',
|
||||
'OCA\\Settings\\Controller\\AuthorizedGroupController' => __DIR__ . '/..' . '/../lib/Controller/AuthorizedGroupController.php',
|
||||
'OCA\\Settings\\Controller\\ChangePasswordController' => __DIR__ . '/..' . '/../lib/Controller/ChangePasswordController.php',
|
||||
|
|
@ -63,7 +62,6 @@ class ComposerStaticInitSettings
|
|||
'OCA\\Settings\\Mailer\\NewUserMailHelper' => __DIR__ . '/..' . '/../lib/Mailer/NewUserMailHelper.php',
|
||||
'OCA\\Settings\\Middleware\\SubadminMiddleware' => __DIR__ . '/..' . '/../lib/Middleware/SubadminMiddleware.php',
|
||||
'OCA\\Settings\\ResponseDefinitions' => __DIR__ . '/..' . '/../lib/ResponseDefinitions.php',
|
||||
'OCA\\Settings\\Search\\AppSearch' => __DIR__ . '/..' . '/../lib/Search/AppSearch.php',
|
||||
'OCA\\Settings\\Search\\SectionSearch' => __DIR__ . '/..' . '/../lib/Search/SectionSearch.php',
|
||||
'OCA\\Settings\\Search\\UserSearch' => __DIR__ . '/..' . '/../lib/Search/UserSearch.php',
|
||||
'OCA\\Settings\\Sections\\Admin\\Additional' => __DIR__ . '/..' . '/../lib/Sections/Admin/Additional.php',
|
||||
|
|
|
|||
|
|
@ -2306,12 +2306,6 @@
|
|||
<code><![CDATA[IAccountManager::PROPERTY_TWITTER]]></code>
|
||||
</DeprecatedConstant>
|
||||
</file>
|
||||
<file src="apps/settings/lib/Controller/AppSettingsController.php">
|
||||
<DeprecatedMethod>
|
||||
<code><![CDATA[getAppValue]]></code>
|
||||
<code><![CDATA[getSupportedApps]]></code>
|
||||
</DeprecatedMethod>
|
||||
</file>
|
||||
<file src="apps/settings/lib/Controller/MailSettingsController.php">
|
||||
<DeprecatedMethod>
|
||||
<code><![CDATA[getUserValue]]></code>
|
||||
|
|
|
|||
|
|
@ -245,8 +245,8 @@ class NavigationManager implements INavigationManager {
|
|||
'type' => 'settings',
|
||||
'id' => 'core_apps',
|
||||
'order' => 5,
|
||||
'href' => $this->urlGenerator->linkToRoute('appstore.AppSettings.viewApps'),
|
||||
'icon' => $this->urlGenerator->imagePath('appstore', 'apps.svg'),
|
||||
'href' => $this->urlGenerator->linkToRoute('appstore.Page.viewApps'),
|
||||
'icon' => $this->urlGenerator->imagePath('appstore', 'app.svg'),
|
||||
'name' => $l->t('Apps'),
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -449,13 +449,11 @@ class OC_App {
|
|||
if ($appPath !== false) {
|
||||
$appIcon = $appPath . '/img/' . $app . '.svg';
|
||||
if (file_exists($appIcon)) {
|
||||
$info['preview'] = $urlGenerator->imagePath($app, $app . '.svg');
|
||||
$info['previewAsIcon'] = true;
|
||||
$info['icon'] = $urlGenerator->imagePath($app, $app . '.svg');
|
||||
} else {
|
||||
$appIcon = $appPath . '/img/app.svg';
|
||||
if (file_exists($appIcon)) {
|
||||
$info['preview'] = $urlGenerator->imagePath($app, 'app.svg');
|
||||
$info['previewAsIcon'] = true;
|
||||
$info['icon'] = $urlGenerator->imagePath($app, 'app.svg');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
976
openapi.json
976
openapi.json
|
|
@ -17055,6 +17055,982 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/appstore/api/v1/apps/categories": {
|
||||
"get": {
|
||||
"operationId": "appstore-api-list-categories",
|
||||
"summary": "Get all available categories",
|
||||
"description": "This endpoint requires admin access",
|
||||
"tags": [
|
||||
"appstore/api"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The categories were found successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"displayName"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"displayName": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Logged in account must be an admin",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/appstore/api/v1/apps": {
|
||||
"get": {
|
||||
"operationId": "appstore-api-list-apps",
|
||||
"summary": "Get all available apps",
|
||||
"description": "This endpoint requires admin access",
|
||||
"tags": [
|
||||
"appstore/api"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The apps were found successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Logged in account must be an admin",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/appstore/api/v1/apps/enable": {
|
||||
"post": {
|
||||
"operationId": "appstore-api-enable-app",
|
||||
"summary": "Enable one apps",
|
||||
"description": "App will be enabled for specific groups only if $groups is defined\nThis endpoint requires admin access\nThis endpoint requires password confirmation",
|
||||
"tags": [
|
||||
"appstore/api"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"appId"
|
||||
],
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "string",
|
||||
"description": "- The app to enable"
|
||||
},
|
||||
"groups": {
|
||||
"type": "array",
|
||||
"default": [],
|
||||
"description": "- The groups to enable the app for",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"force": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "- Whether to force enable the app even if Nextcloud version requirements are not met"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "App successfully enabled",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"update_required"
|
||||
],
|
||||
"properties": {
|
||||
"update_required": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Logged in account must be an admin",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/appstore/api/v1/apps/disable": {
|
||||
"post": {
|
||||
"operationId": "appstore-api-disable-app",
|
||||
"summary": "Disable an app",
|
||||
"description": "This endpoint requires admin access\nThis endpoint requires password confirmation",
|
||||
"tags": [
|
||||
"appstore/api"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"appId"
|
||||
],
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "string",
|
||||
"description": "- The app to disable"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "App successfully disabled",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Logged in account must be an admin",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/appstore/api/v1/apps/uninstall": {
|
||||
"post": {
|
||||
"operationId": "appstore-api-uninstall-app",
|
||||
"summary": "Uninstall an app. This will disable the app - if needed - and then remove the app from the system",
|
||||
"description": "This endpoint requires admin access\nThis endpoint requires password confirmation",
|
||||
"tags": [
|
||||
"appstore/api"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"appId"
|
||||
],
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "string",
|
||||
"description": "- The app to uninstall"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "App successfully uninstalled",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Logged in account must be an admin",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/appstore/api/v1/apps/update": {
|
||||
"post": {
|
||||
"operationId": "appstore-api-update-app",
|
||||
"summary": "Update an app",
|
||||
"description": "This endpoint requires admin access\nThis endpoint requires password confirmation",
|
||||
"tags": [
|
||||
"appstore/api"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"appId"
|
||||
],
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "string",
|
||||
"description": "- The app to update"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "App successfully updated",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Logged in account must be an admin",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ocs/v2.php/apps/appstore/api/v1/bundles/enable": {
|
||||
"post": {
|
||||
"operationId": "appstore-api-enable-bundle",
|
||||
"summary": "Enable all apps of a bundle",
|
||||
"description": "This endpoint requires admin access\nThis endpoint requires password confirmation",
|
||||
"tags": [
|
||||
"appstore/api"
|
||||
],
|
||||
"security": [
|
||||
{
|
||||
"bearer_auth": []
|
||||
},
|
||||
{
|
||||
"basic_auth": []
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"bundleId"
|
||||
],
|
||||
"properties": {
|
||||
"bundleId": {
|
||||
"type": "string",
|
||||
"description": "- The bundle to enable"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "OCS-APIRequest",
|
||||
"in": "header",
|
||||
"description": "Required to be true for the API request to pass",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Bundle successfully enabled",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Current user is not logged in",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "Logged in account must be an admin",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"ocs"
|
||||
],
|
||||
"properties": {
|
||||
"ocs": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"meta",
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"meta": {
|
||||
"$ref": "#/components/schemas/OCSMeta"
|
||||
},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/index.php/ocm/shares": {
|
||||
"post": {
|
||||
"operationId": "cloud_federation_api-request_handler-add-share",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
</plugins>
|
||||
<projectFiles>
|
||||
<directory name="apps/admin_audit"/>
|
||||
<directory name="apps/appstore"/>
|
||||
<directory name="apps/cloud_federation_api"/>
|
||||
<directory name="apps/comments"/>
|
||||
<directory name="apps/contactsinteraction"/>
|
||||
|
|
|
|||
|
|
@ -279,7 +279,7 @@ class NavigationManagerTest extends TestCase {
|
|||
'id' => 'core_apps',
|
||||
'order' => 5,
|
||||
'href' => '/apps/test/',
|
||||
'icon' => '/apps/settings/img/apps.svg',
|
||||
'icon' => '/apps/appstore/img/app.svg',
|
||||
'name' => 'Apps',
|
||||
'active' => false,
|
||||
'type' => 'settings',
|
||||
|
|
|
|||
Loading…
Reference in a new issue