migrate therinaldos.com data
Build & Deploy to DigitalOcean Space / build (push) Failing after 2m38s

This commit is contained in:
2024-05-05 15:50:45 -04:00
commit ef1ff240d4
23182 changed files with 3801898 additions and 0 deletions
+878
View File
@@ -0,0 +1,878 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Christoph Schaefer "christophł@wolkesicher.de"
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Daniel Rudolf <github.com@daniel-rudolf.de>
* @author Greta Doci <gretadoci@gmail.com>
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Haertl <jus@bitgrid.net>
* @author Julius Härtl <jus@bitgrid.net>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Maxence Lange <maxence@artificial-owl.com>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Robin Appelman <robin@icewind.nl>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
* @author Tobia De Koninck <tobia@ledfan.be>
* @author Vincent Petry <vincent@nextcloud.com>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\App;
use InvalidArgumentException;
use OC\AppConfig;
use OC\AppFramework\Bootstrap\Coordinator;
use OC\ServerNotAvailableException;
use OCP\Activity\IManager as IActivityManager;
use OCP\App\AppPathNotFoundException;
use OCP\App\Events\AppDisableEvent;
use OCP\App\Events\AppEnableEvent;
use OCP\App\IAppManager;
use OCP\App\ManagerEvent;
use OCP\Collaboration\AutoComplete\IManager as IAutoCompleteManager;
use OCP\Collaboration\Collaborators\ISearch as ICollaboratorSearch;
use OCP\Diagnostics\IEventLogger;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IUser;
use OCP\IUserSession;
use OCP\Settings\IManager as ISettingsManager;
use Psr\Log\LoggerInterface;
class AppManager implements IAppManager {
/**
* Apps with these types can not be enabled for certain groups only
* @var string[]
*/
protected $protectedAppTypes = [
'filesystem',
'prelogin',
'authentication',
'logging',
'prevent_group_restriction',
];
private IUserSession $userSession;
private IConfig $config;
private AppConfig $appConfig;
private IGroupManager $groupManager;
private ICacheFactory $memCacheFactory;
private IEventDispatcher $dispatcher;
private LoggerInterface $logger;
/** @var string[] $appId => $enabled */
private array $installedAppsCache = [];
/** @var string[]|null */
private ?array $shippedApps = null;
private array $alwaysEnabled = [];
private array $defaultEnabled = [];
/** @var array */
private array $appInfos = [];
/** @var array */
private array $appVersions = [];
/** @var array */
private array $autoDisabledApps = [];
private array $appTypes = [];
/** @var array<string, true> */
private array $loadedApps = [];
public function __construct(IUserSession $userSession,
IConfig $config,
AppConfig $appConfig,
IGroupManager $groupManager,
ICacheFactory $memCacheFactory,
IEventDispatcher $dispatcher,
LoggerInterface $logger) {
$this->userSession = $userSession;
$this->config = $config;
$this->appConfig = $appConfig;
$this->groupManager = $groupManager;
$this->memCacheFactory = $memCacheFactory;
$this->dispatcher = $dispatcher;
$this->logger = $logger;
}
/**
* @return string[] $appId => $enabled
*/
private function getInstalledAppsValues(): array {
if (!$this->installedAppsCache) {
$values = $this->appConfig->getValues(false, 'enabled');
$alwaysEnabledApps = $this->getAlwaysEnabledApps();
foreach ($alwaysEnabledApps as $appId) {
$values[$appId] = 'yes';
}
$this->installedAppsCache = array_filter($values, function ($value) {
return $value !== 'no';
});
ksort($this->installedAppsCache);
}
return $this->installedAppsCache;
}
/**
* List all installed apps
*
* @return string[]
*/
public function getInstalledApps() {
return array_keys($this->getInstalledAppsValues());
}
/**
* List all apps enabled for a user
*
* @param \OCP\IUser $user
* @return string[]
*/
public function getEnabledAppsForUser(IUser $user) {
$apps = $this->getInstalledAppsValues();
$appsForUser = array_filter($apps, function ($enabled) use ($user) {
return $this->checkAppForUser($enabled, $user);
});
return array_keys($appsForUser);
}
/**
* @param IGroup $group
* @return array
*/
public function getEnabledAppsForGroup(IGroup $group): array {
$apps = $this->getInstalledAppsValues();
$appsForGroups = array_filter($apps, function ($enabled) use ($group) {
return $this->checkAppForGroups($enabled, $group);
});
return array_keys($appsForGroups);
}
/**
* Loads all apps
*
* @param string[] $types
* @return bool
*
* This function walks through the Nextcloud directory and loads all apps
* it can find. A directory contains an app if the file /appinfo/info.xml
* exists.
*
* if $types is set to non-empty array, only apps of those types will be loaded
*/
public function loadApps(array $types = []): bool {
if ($this->config->getSystemValueBool('maintenance', false)) {
return false;
}
// Load the enabled apps here
$apps = \OC_App::getEnabledApps();
// Add each apps' folder as allowed class path
foreach ($apps as $app) {
// If the app is already loaded then autoloading it makes no sense
if (!$this->isAppLoaded($app)) {
$path = \OC_App::getAppPath($app);
if ($path !== false) {
\OC_App::registerAutoloading($app, $path);
}
}
}
// prevent app.php from printing output
ob_start();
foreach ($apps as $app) {
if (!$this->isAppLoaded($app) && ($types === [] || $this->isType($app, $types))) {
try {
$this->loadApp($app);
} catch (\Throwable $e) {
$this->logger->emergency('Error during app loading: ' . $e->getMessage(), [
'exception' => $e,
'app' => $app,
]);
}
}
}
ob_end_clean();
return true;
}
/**
* check if an app is of a specific type
*
* @param string $app
* @param array $types
* @return bool
*/
public function isType(string $app, array $types): bool {
$appTypes = $this->getAppTypes($app);
foreach ($types as $type) {
if (in_array($type, $appTypes, true)) {
return true;
}
}
return false;
}
/**
* get the types of an app
*
* @param string $app
* @return string[]
*/
private function getAppTypes(string $app): array {
//load the cache
if (count($this->appTypes) === 0) {
$this->appTypes = $this->appConfig->getValues(false, 'types') ?: [];
}
if (isset($this->appTypes[$app])) {
return explode(',', $this->appTypes[$app]);
}
return [];
}
/**
* @return array
*/
public function getAutoDisabledApps(): array {
return $this->autoDisabledApps;
}
/**
* @param string $appId
* @return array
*/
public function getAppRestriction(string $appId): array {
$values = $this->getInstalledAppsValues();
if (!isset($values[$appId])) {
return [];
}
if ($values[$appId] === 'yes' || $values[$appId] === 'no') {
return [];
}
return json_decode($values[$appId], true);
}
/**
* Check if an app is enabled for user
*
* @param string $appId
* @param \OCP\IUser|null $user (optional) if not defined, the currently logged in user will be used
* @return bool
*/
public function isEnabledForUser($appId, $user = null) {
if ($this->isAlwaysEnabled($appId)) {
return true;
}
if ($user === null) {
$user = $this->userSession->getUser();
}
$installedApps = $this->getInstalledAppsValues();
if (isset($installedApps[$appId])) {
return $this->checkAppForUser($installedApps[$appId], $user);
} else {
return false;
}
}
private function checkAppForUser(string $enabled, ?IUser $user): bool {
if ($enabled === 'yes') {
return true;
} elseif ($user === null) {
return false;
} else {
if (empty($enabled)) {
return false;
}
$groupIds = json_decode($enabled);
if (!is_array($groupIds)) {
$jsonError = json_last_error();
$this->logger->warning('AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r($enabled, true) . ' - json error code: ' . $jsonError);
return false;
}
$userGroups = $this->groupManager->getUserGroupIds($user);
foreach ($userGroups as $groupId) {
if (in_array($groupId, $groupIds, true)) {
return true;
}
}
return false;
}
}
private function checkAppForGroups(string $enabled, IGroup $group): bool {
if ($enabled === 'yes') {
return true;
} else {
if (empty($enabled)) {
return false;
}
$groupIds = json_decode($enabled);
if (!is_array($groupIds)) {
$jsonError = json_last_error();
$this->logger->warning('AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r($enabled, true) . ' - json error code: ' . $jsonError);
return false;
}
return in_array($group->getGID(), $groupIds);
}
}
/**
* Check if an app is enabled in the instance
*
* Notice: This actually checks if the app is enabled and not only if it is installed.
*
* @param string $appId
* @param IGroup[]|String[] $groups
* @return bool
*/
public function isInstalled($appId) {
$installedApps = $this->getInstalledAppsValues();
return isset($installedApps[$appId]);
}
public function ignoreNextcloudRequirementForApp(string $appId): void {
$ignoreMaxApps = $this->config->getSystemValue('app_install_overwrite', []);
if (!in_array($appId, $ignoreMaxApps, true)) {
$ignoreMaxApps[] = $appId;
$this->config->setSystemValue('app_install_overwrite', $ignoreMaxApps);
}
}
public function loadApp(string $app): void {
if (isset($this->loadedApps[$app])) {
return;
}
$this->loadedApps[$app] = true;
$appPath = \OC_App::getAppPath($app);
if ($appPath === false) {
return;
}
$eventLogger = \OC::$server->get(\OCP\Diagnostics\IEventLogger::class);
$eventLogger->start("bootstrap:load_app:$app", "Load $app");
// in case someone calls loadApp() directly
\OC_App::registerAutoloading($app, $appPath);
/** @var Coordinator $coordinator */
$coordinator = \OC::$server->get(Coordinator::class);
$isBootable = $coordinator->isBootable($app);
$hasAppPhpFile = is_file($appPath . '/appinfo/app.php');
$eventLogger = \OC::$server->get(IEventLogger::class);
$eventLogger->start('bootstrap:load_app_' . $app, 'Load app: ' . $app);
if ($isBootable && $hasAppPhpFile) {
$this->logger->error('/appinfo/app.php is not loaded when \OCP\AppFramework\Bootstrap\IBootstrap on the application class is used. Migrate everything from app.php to the Application class.', [
'app' => $app,
]);
} elseif ($hasAppPhpFile) {
$eventLogger->start("bootstrap:load_app:$app:app.php", "Load legacy app.php app $app");
$this->logger->debug('/appinfo/app.php is deprecated, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.', [
'app' => $app,
]);
try {
self::requireAppFile($appPath);
} catch (\Throwable $ex) {
if ($ex instanceof ServerNotAvailableException) {
throw $ex;
}
if (!$this->isShipped($app) && !$this->isType($app, ['authentication'])) {
$this->logger->error("App $app threw an error during app.php load and will be disabled: " . $ex->getMessage(), [
'exception' => $ex,
]);
// Only disable apps which are not shipped and that are not authentication apps
$this->disableApp($app, true);
} else {
$this->logger->error("App $app threw an error during app.php load: " . $ex->getMessage(), [
'exception' => $ex,
]);
}
}
$eventLogger->end("bootstrap:load_app:$app:app.php");
}
$coordinator->bootApp($app);
$eventLogger->start("bootstrap:load_app:$app:info", "Load info.xml for $app and register any services defined in it");
$info = $this->getAppInfo($app);
if (!empty($info['activity'])) {
$activityManager = \OC::$server->get(IActivityManager::class);
if (!empty($info['activity']['filters'])) {
foreach ($info['activity']['filters'] as $filter) {
$activityManager->registerFilter($filter);
}
}
if (!empty($info['activity']['settings'])) {
foreach ($info['activity']['settings'] as $setting) {
$activityManager->registerSetting($setting);
}
}
if (!empty($info['activity']['providers'])) {
foreach ($info['activity']['providers'] as $provider) {
$activityManager->registerProvider($provider);
}
}
}
if (!empty($info['settings'])) {
$settingsManager = \OC::$server->get(ISettingsManager::class);
if (!empty($info['settings']['admin'])) {
foreach ($info['settings']['admin'] as $setting) {
$settingsManager->registerSetting('admin', $setting);
}
}
if (!empty($info['settings']['admin-section'])) {
foreach ($info['settings']['admin-section'] as $section) {
$settingsManager->registerSection('admin', $section);
}
}
if (!empty($info['settings']['personal'])) {
foreach ($info['settings']['personal'] as $setting) {
$settingsManager->registerSetting('personal', $setting);
}
}
if (!empty($info['settings']['personal-section'])) {
foreach ($info['settings']['personal-section'] as $section) {
$settingsManager->registerSection('personal', $section);
}
}
}
if (!empty($info['collaboration']['plugins'])) {
// deal with one or many plugin entries
$plugins = isset($info['collaboration']['plugins']['plugin']['@value']) ?
[$info['collaboration']['plugins']['plugin']] : $info['collaboration']['plugins']['plugin'];
$collaboratorSearch = null;
$autoCompleteManager = null;
foreach ($plugins as $plugin) {
if ($plugin['@attributes']['type'] === 'collaborator-search') {
$pluginInfo = [
'shareType' => $plugin['@attributes']['share-type'],
'class' => $plugin['@value'],
];
$collaboratorSearch ??= \OC::$server->get(ICollaboratorSearch::class);
$collaboratorSearch->registerPlugin($pluginInfo);
} elseif ($plugin['@attributes']['type'] === 'autocomplete-sort') {
$autoCompleteManager ??= \OC::$server->get(IAutoCompleteManager::class);
$autoCompleteManager->registerSorter($plugin['@value']);
}
}
}
$eventLogger->end("bootstrap:load_app:$app:info");
$eventLogger->end("bootstrap:load_app:$app");
}
/**
* Check if an app is loaded
* @param string $app app id
* @since 26.0.0
*/
public function isAppLoaded(string $app): bool {
return isset($this->loadedApps[$app]);
}
/**
* Load app.php from the given app
*
* @param string $app app name
* @throws \Error
*/
private static function requireAppFile(string $app): void {
// encapsulated here to avoid variable scope conflicts
require_once $app . '/appinfo/app.php';
}
/**
* Enable an app for every user
*
* @param string $appId
* @param bool $forceEnable
* @throws AppPathNotFoundException
*/
public function enableApp(string $appId, bool $forceEnable = false): void {
// Check if app exists
$this->getAppPath($appId);
if ($forceEnable) {
$this->ignoreNextcloudRequirementForApp($appId);
}
$this->installedAppsCache[$appId] = 'yes';
$this->appConfig->setValue($appId, 'enabled', 'yes');
$this->dispatcher->dispatchTyped(new AppEnableEvent($appId));
$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE, new ManagerEvent(
ManagerEvent::EVENT_APP_ENABLE, $appId
));
$this->clearAppsCache();
}
/**
* Whether a list of types contains a protected app type
*
* @param string[] $types
* @return bool
*/
public function hasProtectedAppType($types) {
if (empty($types)) {
return false;
}
$protectedTypes = array_intersect($this->protectedAppTypes, $types);
return !empty($protectedTypes);
}
/**
* Enable an app only for specific groups
*
* @param string $appId
* @param IGroup[] $groups
* @param bool $forceEnable
* @throws \InvalidArgumentException if app can't be enabled for groups
* @throws AppPathNotFoundException
*/
public function enableAppForGroups(string $appId, array $groups, bool $forceEnable = false): void {
// Check if app exists
$this->getAppPath($appId);
$info = $this->getAppInfo($appId);
if (!empty($info['types']) && $this->hasProtectedAppType($info['types'])) {
throw new \InvalidArgumentException("$appId can't be enabled for groups.");
}
if ($forceEnable) {
$this->ignoreNextcloudRequirementForApp($appId);
}
/** @var string[] $groupIds */
$groupIds = array_map(function ($group) {
/** @var IGroup $group */
return ($group instanceof IGroup)
? $group->getGID()
: $group;
}, $groups);
$this->installedAppsCache[$appId] = json_encode($groupIds);
$this->appConfig->setValue($appId, 'enabled', json_encode($groupIds));
$this->dispatcher->dispatchTyped(new AppEnableEvent($appId, $groupIds));
$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, new ManagerEvent(
ManagerEvent::EVENT_APP_ENABLE_FOR_GROUPS, $appId, $groups
));
$this->clearAppsCache();
}
/**
* Disable an app for every user
*
* @param string $appId
* @param bool $automaticDisabled
* @throws \Exception if app can't be disabled
*/
public function disableApp($appId, $automaticDisabled = false) {
if ($this->isAlwaysEnabled($appId)) {
throw new \Exception("$appId can't be disabled.");
}
if ($automaticDisabled) {
$previousSetting = $this->appConfig->getValue($appId, 'enabled', 'yes');
if ($previousSetting !== 'yes' && $previousSetting !== 'no') {
$previousSetting = json_decode($previousSetting, true);
}
$this->autoDisabledApps[$appId] = $previousSetting;
}
unset($this->installedAppsCache[$appId]);
$this->appConfig->setValue($appId, 'enabled', 'no');
// run uninstall steps
$appData = $this->getAppInfo($appId);
if (!is_null($appData)) {
\OC_App::executeRepairSteps($appId, $appData['repair-steps']['uninstall']);
}
$this->dispatcher->dispatchTyped(new AppDisableEvent($appId));
$this->dispatcher->dispatch(ManagerEvent::EVENT_APP_DISABLE, new ManagerEvent(
ManagerEvent::EVENT_APP_DISABLE, $appId
));
$this->clearAppsCache();
}
/**
* Get the directory for the given app.
*
* @param string $appId
* @return string
* @throws AppPathNotFoundException if app folder can't be found
*/
public function getAppPath($appId) {
$appPath = \OC_App::getAppPath($appId);
if ($appPath === false) {
throw new AppPathNotFoundException('Could not find path for ' . $appId);
}
return $appPath;
}
/**
* Get the web path for the given app.
*
* @param string $appId
* @return string
* @throws AppPathNotFoundException if app path can't be found
*/
public function getAppWebPath(string $appId): string {
$appWebPath = \OC_App::getAppWebPath($appId);
if ($appWebPath === false) {
throw new AppPathNotFoundException('Could not find web path for ' . $appId);
}
return $appWebPath;
}
/**
* Clear the cached list of apps when enabling/disabling an app
*/
public function clearAppsCache() {
$this->appInfos = [];
}
/**
* Returns a list of apps that need upgrade
*
* @param string $version Nextcloud version as array of version components
* @return array list of app info from apps that need an upgrade
*
* @internal
*/
public function getAppsNeedingUpgrade($version) {
$appsToUpgrade = [];
$apps = $this->getInstalledApps();
foreach ($apps as $appId) {
$appInfo = $this->getAppInfo($appId);
$appDbVersion = $this->appConfig->getValue($appId, 'installed_version');
if ($appDbVersion
&& isset($appInfo['version'])
&& version_compare($appInfo['version'], $appDbVersion, '>')
&& \OC_App::isAppCompatible($version, $appInfo)
) {
$appsToUpgrade[] = $appInfo;
}
}
return $appsToUpgrade;
}
/**
* Returns the app information from "appinfo/info.xml".
*
* @param string|null $lang
* @return array|null app info
*/
public function getAppInfo(string $appId, bool $path = false, $lang = null) {
if ($path) {
$file = $appId;
} else {
if ($lang === null && isset($this->appInfos[$appId])) {
return $this->appInfos[$appId];
}
try {
$appPath = $this->getAppPath($appId);
} catch (AppPathNotFoundException $e) {
return null;
}
$file = $appPath . '/appinfo/info.xml';
}
$parser = new InfoParser($this->memCacheFactory->createLocal('core.appinfo'));
$data = $parser->parse($file);
if (is_array($data)) {
$data = \OC_App::parseAppInfo($data, $lang);
}
if ($lang === null) {
$this->appInfos[$appId] = $data;
}
return $data;
}
public function getAppVersion(string $appId, bool $useCache = true): string {
if (!$useCache || !isset($this->appVersions[$appId])) {
$appInfo = $this->getAppInfo($appId);
$this->appVersions[$appId] = ($appInfo !== null && isset($appInfo['version'])) ? $appInfo['version'] : '0';
}
return $this->appVersions[$appId];
}
/**
* Returns a list of apps incompatible with the given version
*
* @param string $version Nextcloud version as array of version components
*
* @return array list of app info from incompatible apps
*
* @internal
*/
public function getIncompatibleApps(string $version): array {
$apps = $this->getInstalledApps();
$incompatibleApps = [];
foreach ($apps as $appId) {
$info = $this->getAppInfo($appId);
if ($info === null) {
$incompatibleApps[] = ['id' => $appId, 'name' => $appId];
} elseif (!\OC_App::isAppCompatible($version, $info)) {
$incompatibleApps[] = $info;
}
}
return $incompatibleApps;
}
/**
* @inheritdoc
* In case you change this method, also change \OC\App\CodeChecker\InfoChecker::isShipped()
*/
public function isShipped($appId) {
$this->loadShippedJson();
return in_array($appId, $this->shippedApps, true);
}
private function isAlwaysEnabled(string $appId): bool {
$alwaysEnabled = $this->getAlwaysEnabledApps();
return in_array($appId, $alwaysEnabled, true);
}
/**
* In case you change this method, also change \OC\App\CodeChecker\InfoChecker::loadShippedJson()
* @throws \Exception
*/
private function loadShippedJson(): void {
if ($this->shippedApps === null) {
$shippedJson = \OC::$SERVERROOT . '/core/shipped.json';
if (!file_exists($shippedJson)) {
throw new \Exception("File not found: $shippedJson");
}
$content = json_decode(file_get_contents($shippedJson), true);
$this->shippedApps = $content['shippedApps'];
$this->alwaysEnabled = $content['alwaysEnabled'];
$this->defaultEnabled = $content['defaultEnabled'];
}
}
/**
* @inheritdoc
*/
public function getAlwaysEnabledApps() {
$this->loadShippedJson();
return $this->alwaysEnabled;
}
/**
* @inheritdoc
*/
public function isDefaultEnabled(string $appId): bool {
return (in_array($appId, $this->getDefaultEnabledApps()));
}
/**
* @inheritdoc
*/
public function getDefaultEnabledApps(): array {
$this->loadShippedJson();
return $this->defaultEnabled;
}
public function getDefaultAppForUser(?IUser $user = null, bool $withFallbacks = true): string {
// Set fallback to always-enabled files app
$appId = $withFallbacks ? 'files' : '';
$defaultApps = explode(',', $this->config->getSystemValueString('defaultapp', ''));
$defaultApps = array_filter($defaultApps);
$user ??= $this->userSession->getUser();
if ($user !== null) {
$userDefaultApps = explode(',', $this->config->getUserValue($user->getUID(), 'core', 'defaultapp'));
$defaultApps = array_filter(array_merge($userDefaultApps, $defaultApps));
if (empty($defaultApps) && $withFallbacks) {
/* Fallback on user defined apporder */
$customOrders = json_decode($this->config->getUserValue($user->getUID(), 'core', 'apporder', '[]'), true, flags:JSON_THROW_ON_ERROR);
if (!empty($customOrders)) {
// filter only entries with app key (when added using closures or NavigationManager::add the app is not guranteed to be set)
$customOrders = array_filter($customOrders, fn ($entry) => isset($entry['app']));
// sort apps by order
usort($customOrders, fn ($a, $b) => $a['order'] - $b['order']);
// set default apps to sorted apps
$defaultApps = array_map(fn ($entry) => $entry['app'], $customOrders);
}
}
}
if (empty($defaultApps) && $withFallbacks) {
$defaultApps = ['dashboard','files'];
}
// Find the first app that is enabled for the current user
foreach ($defaultApps as $defaultApp) {
$defaultApp = \OC_App::cleanAppId(strip_tags($defaultApp));
if ($this->isEnabledForUser($defaultApp, $user)) {
$appId = $defaultApp;
break;
}
}
return $appId;
}
public function getDefaultApps(): array {
return explode(',', $this->config->getSystemValueString('defaultapp', 'dashboard,files'));
}
public function setDefaultApps(array $defaultApps): void {
foreach ($defaultApps as $app) {
if (!$this->isInstalled($app)) {
$this->logger->debug('Can not set not installed app as default app', ['missing_app' => $app]);
throw new InvalidArgumentException('App is not installed');
}
}
$this->config->setSystemValue('defaultapp', join(',', $defaultApps));
}
}
@@ -0,0 +1,61 @@
<?php
/**
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Lukas Reschke <lukas@statuscode.ch>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\App\AppStore\Bundles;
use OCP\IL10N;
abstract class Bundle {
/** @var IL10N */
protected $l10n;
/**
* @param IL10N $l10n
*/
public function __construct(IL10N $l10n) {
$this->l10n = $l10n;
}
/**
* Get the identifier of the bundle
*
* @return string
*/
final public function getIdentifier() {
return substr(strrchr(get_class($this), '\\'), 1);
}
/**
* Get the name of the bundle
*
* @return string
*/
abstract public function getName();
/**
* Get the list of app identifiers in the bundle
*
* @return array
*/
abstract public function getAppIdentifiers();
}
@@ -0,0 +1,65 @@
<?php
/**
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Lukas Reschke <lukas@statuscode.ch>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\App\AppStore\Bundles;
use OCP\IL10N;
class BundleFetcher {
private IL10N $l10n;
public function __construct(IL10N $l10n) {
$this->l10n = $l10n;
}
/**
* @return Bundle[]
*/
public function getBundles(): array {
return [
new EnterpriseBundle($this->l10n),
new HubBundle($this->l10n),
new GroupwareBundle($this->l10n),
new SocialSharingBundle($this->l10n),
new EducationBundle($this->l10n),
];
}
/**
* Get the bundle with the specified identifier
*
* @param string $identifier
* @return Bundle
* @throws \BadMethodCallException If the bundle does not exist
*/
public function getBundleByIdentifier(string $identifier): Bundle {
foreach ($this->getBundles() as $bundle) {
if ($bundle->getIdentifier() === $identifier) {
return $bundle;
}
}
throw new \BadMethodCallException('Bundle with specified identifier does not exist');
}
}
@@ -0,0 +1,47 @@
<?php
/**
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
*
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\App\AppStore\Bundles;
class EducationBundle extends Bundle {
/**
* {@inheritDoc}
*/
public function getName() {
return $this->l10n->t('Education Edition');
}
/**
* {@inheritDoc}
*/
public function getAppIdentifiers() {
return [
'dashboard',
'circles',
'groupfolders',
'announcementcenter',
'quota_warning',
'user_saml',
];
}
}
@@ -0,0 +1,48 @@
<?php
/**
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
*
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\App\AppStore\Bundles;
class EnterpriseBundle extends Bundle {
/**
* {@inheritDoc}
*/
public function getName(): string {
return $this->l10n->t('Enterprise bundle');
}
/**
* {@inheritDoc}
*/
public function getAppIdentifiers(): array {
return [
'admin_audit',
'user_ldap',
'files_retention',
'files_automatedtagging',
'user_saml',
'files_accesscontrol',
'terms_of_service',
];
}
}
@@ -0,0 +1,46 @@
<?php
/**
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
*
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\App\AppStore\Bundles;
class GroupwareBundle extends Bundle {
/**
* {@inheritDoc}
*/
public function getName() {
return $this->l10n->t('Groupware bundle');
}
/**
* {@inheritDoc}
*/
public function getAppIdentifiers() {
return [
'calendar',
'contacts',
'deck',
'mail'
];
}
}
@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2020 Arthur Schiwon <blizzz@arthur-schiwon.de>
*
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Julius Härtl <jus@bitgrid.net>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\App\AppStore\Bundles;
class HubBundle extends Bundle {
public function getName() {
return $this->l10n->t('Hub bundle');
}
public function getAppIdentifiers() {
$hubApps = [
'spreed',
'contacts',
'calendar',
'mail',
];
$architecture = function_exists('php_uname') ? php_uname('m') : null;
if (isset($architecture) && PHP_OS_FAMILY === 'Linux' && in_array($architecture, ['x86_64', 'aarch64'])) {
$hubApps[] = 'richdocuments';
$hubApps[] = 'richdocumentscode' . ($architecture === 'aarch64' ? '_arm64' : '');
}
return $hubApps;
}
}
@@ -0,0 +1,45 @@
<?php
/**
* @copyright Copyright (c) 2017 Lukas Reschke <lukas@statuscode.ch>
*
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\App\AppStore\Bundles;
class SocialSharingBundle extends Bundle {
/**
* {@inheritDoc}
*/
public function getName() {
return $this->l10n->t('Social sharing bundle');
}
/**
* {@inheritDoc}
*/
public function getAppIdentifiers() {
return [
'socialsharing_twitter',
'socialsharing_facebook',
'socialsharing_email',
'socialsharing_diaspora',
];
}
}
@@ -0,0 +1,199 @@
<?php
/**
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Jakub Onderka <ahoj@jakubonderka.cz>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\App\AppStore\Fetcher;
use OC\App\AppStore\Version\VersionParser;
use OC\App\CompareVersion;
use OC\Files\AppData\Factory;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\Support\Subscription\IRegistry;
use Psr\Log\LoggerInterface;
class AppFetcher extends Fetcher {
/** @var CompareVersion */
private $compareVersion;
/** @var IRegistry */
protected $registry;
/** @var bool */
private $ignoreMaxVersion;
public function __construct(Factory $appDataFactory,
IClientService $clientService,
ITimeFactory $timeFactory,
IConfig $config,
CompareVersion $compareVersion,
LoggerInterface $logger,
IRegistry $registry) {
parent::__construct(
$appDataFactory,
$clientService,
$timeFactory,
$config,
$logger,
$registry
);
$this->compareVersion = $compareVersion;
$this->registry = $registry;
$this->fileName = 'apps.json';
$this->endpointName = 'apps.json';
$this->ignoreMaxVersion = true;
}
/**
* Only returns the latest compatible app release in the releases array
*
* @param string $ETag
* @param string $content
* @param bool [$allowUnstable] Allow unstable releases
*
* @return array
*/
protected function fetch($ETag, $content, $allowUnstable = false) {
/** @var mixed[] $response */
$response = parent::fetch($ETag, $content);
if (empty($response)) {
return [];
}
$allowPreReleases = $allowUnstable || $this->getChannel() === 'beta' || $this->getChannel() === 'daily' || $this->getChannel() === 'git';
$allowNightly = $allowUnstable || $this->getChannel() === 'daily' || $this->getChannel() === 'git';
foreach ($response['data'] as $dataKey => $app) {
$releases = [];
// Filter all compatible releases
foreach ($app['releases'] as $release) {
// Exclude all nightly and pre-releases if required
if (($allowNightly || $release['isNightly'] === false)
&& ($allowPreReleases || !str_contains($release['version'], '-'))) {
// Exclude all versions not compatible with the current version
try {
$versionParser = new VersionParser();
$serverVersion = $versionParser->getVersion($release['rawPlatformVersionSpec']);
$ncVersion = $this->getVersion();
$minServerVersion = $serverVersion->getMinimumVersion();
$maxServerVersion = $serverVersion->getMaximumVersion();
$minFulfilled = $this->compareVersion->isCompatible($ncVersion, $minServerVersion, '>=');
$maxFulfilled = $maxServerVersion !== '' &&
$this->compareVersion->isCompatible($ncVersion, $maxServerVersion, '<=');
$isPhpCompatible = true;
if (($release['rawPhpVersionSpec'] ?? '*') !== '*') {
$phpVersion = $versionParser->getVersion($release['rawPhpVersionSpec']);
$minPhpVersion = $phpVersion->getMinimumVersion();
$maxPhpVersion = $phpVersion->getMaximumVersion();
$minPhpFulfilled = $minPhpVersion === '' || $this->compareVersion->isCompatible(
PHP_VERSION,
$minPhpVersion,
'>='
);
$maxPhpFulfilled = $maxPhpVersion === '' || $this->compareVersion->isCompatible(
PHP_VERSION,
$maxPhpVersion,
'<='
);
$isPhpCompatible = $minPhpFulfilled && $maxPhpFulfilled;
}
if ($minFulfilled && ($this->ignoreMaxVersion || $maxFulfilled) && $isPhpCompatible) {
$releases[] = $release;
}
} catch (\InvalidArgumentException $e) {
$this->logger->warning($e->getMessage(), [
'exception' => $e,
]);
}
}
}
if (empty($releases)) {
// Remove apps that don't have a matching release
$response['data'][$dataKey] = [];
continue;
}
// Get the highest version
$versions = [];
foreach ($releases as $release) {
$versions[] = $release['version'];
}
usort($versions, function ($version1, $version2) {
return version_compare($version1, $version2);
});
$versions = array_reverse($versions);
if (isset($versions[0])) {
$highestVersion = $versions[0];
foreach ($releases as $release) {
if ((string)$release['version'] === (string)$highestVersion) {
$response['data'][$dataKey]['releases'] = [$release];
break;
}
}
}
}
$response['data'] = array_values(array_filter($response['data']));
return $response;
}
/**
* @param string $version
* @param string $fileName
* @param bool $ignoreMaxVersion
*/
public function setVersion(string $version, string $fileName = 'apps.json', bool $ignoreMaxVersion = true) {
parent::setVersion($version);
$this->fileName = $fileName;
$this->ignoreMaxVersion = $ignoreMaxVersion;
}
public function get($allowUnstable = false) {
$allowPreReleases = $allowUnstable || $this->getChannel() === 'beta' || $this->getChannel() === 'daily' || $this->getChannel() === 'git';
$apps = parent::get($allowPreReleases);
$allowList = $this->config->getSystemValue('appsallowlist');
// If the admin specified a allow list, filter apps from the appstore
if (is_array($allowList) && $this->registry->delegateHasValidSubscription()) {
return array_filter($apps, function ($app) use ($allowList) {
return in_array($app['id'], $allowList);
});
}
return $apps;
}
}
@@ -0,0 +1,55 @@
<?php
/**
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
*
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\App\AppStore\Fetcher;
use OC\Files\AppData\Factory;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\Support\Subscription\IRegistry;
use Psr\Log\LoggerInterface;
class CategoryFetcher extends Fetcher {
public function __construct(Factory $appDataFactory,
IClientService $clientService,
ITimeFactory $timeFactory,
IConfig $config,
LoggerInterface $logger,
IRegistry $registry) {
parent::__construct(
$appDataFactory,
$clientService,
$timeFactory,
$config,
$logger,
$registry
);
$this->fileName = 'categories.json';
$this->endpointName = 'categories.json';
}
}
@@ -0,0 +1,259 @@
<?php
/**
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
*
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Georg Ehrke <oc.list@georgehrke.com>
* @author Joas Schilling <coding@schilljs.com>
* @author John Molakvoæ <skjnldsv@protonmail.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Steffen Lindner <mail@steffen-lindner.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\App\AppStore\Fetcher;
use GuzzleHttp\Exception\ConnectException;
use OC\Files\AppData\Factory;
use OCP\AppFramework\Http;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Files\IAppData;
use OCP\Files\NotFoundException;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\Support\Subscription\IRegistry;
use Psr\Log\LoggerInterface;
abstract class Fetcher {
public const INVALIDATE_AFTER_SECONDS = 3600;
public const RETRY_AFTER_FAILURE_SECONDS = 300;
/** @var IAppData */
protected $appData;
/** @var IClientService */
protected $clientService;
/** @var ITimeFactory */
protected $timeFactory;
/** @var IConfig */
protected $config;
/** @var LoggerInterface */
protected $logger;
/** @var IRegistry */
protected $registry;
/** @var string */
protected $fileName;
/** @var string */
protected $endpointName;
/** @var ?string */
protected $version = null;
/** @var ?string */
protected $channel = null;
public function __construct(Factory $appDataFactory,
IClientService $clientService,
ITimeFactory $timeFactory,
IConfig $config,
LoggerInterface $logger,
IRegistry $registry) {
$this->appData = $appDataFactory->get('appstore');
$this->clientService = $clientService;
$this->timeFactory = $timeFactory;
$this->config = $config;
$this->logger = $logger;
$this->registry = $registry;
}
/**
* Fetches the response from the server
*
* @param string $ETag
* @param string $content
*
* @return array
*/
protected function fetch($ETag, $content) {
$appstoreenabled = $this->config->getSystemValueBool('appstoreenabled', true);
if ((int)$this->config->getAppValue('settings', 'appstore-fetcher-lastFailure', '0') > time() - self::RETRY_AFTER_FAILURE_SECONDS) {
return [];
}
if (!$appstoreenabled) {
return [];
}
$options = [
'timeout' => 60,
];
if ($ETag !== '') {
$options['headers'] = [
'If-None-Match' => $ETag,
];
}
if ($this->config->getSystemValueString('appstoreurl', 'https://apps.nextcloud.com/api/v1') === 'https://apps.nextcloud.com/api/v1') {
// If we have a valid subscription key, send it to the appstore
$subscriptionKey = $this->config->getAppValue('support', 'subscription_key');
if ($this->registry->delegateHasValidSubscription() && $subscriptionKey) {
$options['headers'] ??= [];
$options['headers']['X-NC-Subscription-Key'] = $subscriptionKey;
}
}
$client = $this->clientService->newClient();
try {
$response = $client->get($this->getEndpoint(), $options);
} catch (ConnectException $e) {
$this->config->setAppValue('settings', 'appstore-fetcher-lastFailure', (string)time());
throw $e;
}
$responseJson = [];
if ($response->getStatusCode() === Http::STATUS_NOT_MODIFIED) {
$responseJson['data'] = json_decode($content, true);
} else {
$responseJson['data'] = json_decode($response->getBody(), true);
$ETag = $response->getHeader('ETag');
}
$this->config->deleteAppValue('settings', 'appstore-fetcher-lastFailure');
$responseJson['timestamp'] = $this->timeFactory->getTime();
$responseJson['ncversion'] = $this->getVersion();
if ($ETag !== '') {
$responseJson['ETag'] = $ETag;
}
return $responseJson;
}
/**
* Returns the array with the categories on the appstore server
*
* @param bool [$allowUnstable] Allow unstable releases
* @return array
*/
public function get($allowUnstable = false) {
$appstoreenabled = $this->config->getSystemValueBool('appstoreenabled', true);
$internetavailable = $this->config->getSystemValueBool('has_internet_connection', true);
if (!$appstoreenabled || !$internetavailable) {
return [];
}
$rootFolder = $this->appData->getFolder('/');
$ETag = '';
$content = '';
try {
// File does already exists
$file = $rootFolder->getFile($this->fileName);
$jsonBlob = json_decode($file->getContent(), true);
// Always get latests apps info if $allowUnstable
if (!$allowUnstable && is_array($jsonBlob)) {
// No caching when the version has been updated
if (isset($jsonBlob['ncversion']) && $jsonBlob['ncversion'] === $this->getVersion()) {
// If the timestamp is older than 3600 seconds request the files new
if ((int)$jsonBlob['timestamp'] > ($this->timeFactory->getTime() - self::INVALIDATE_AFTER_SECONDS)) {
return $jsonBlob['data'];
}
if (isset($jsonBlob['ETag'])) {
$ETag = $jsonBlob['ETag'];
$content = json_encode($jsonBlob['data']);
}
}
}
} catch (NotFoundException $e) {
// File does not already exists
$file = $rootFolder->newFile($this->fileName);
}
// Refresh the file content
try {
$responseJson = $this->fetch($ETag, $content, $allowUnstable);
if (empty($responseJson)) {
return [];
}
// Don't store the apps request file
if ($allowUnstable) {
return $responseJson['data'];
}
$file->putContent(json_encode($responseJson));
return json_decode($file->getContent(), true)['data'];
} catch (ConnectException $e) {
$this->logger->warning('Could not connect to appstore: ' . $e->getMessage(), ['app' => 'appstoreFetcher']);
return [];
} catch (\Exception $e) {
$this->logger->warning($e->getMessage(), [
'exception' => $e,
'app' => 'appstoreFetcher',
]);
return [];
}
}
/**
* Get the currently Nextcloud version
* @return string
*/
protected function getVersion() {
if ($this->version === null) {
$this->version = $this->config->getSystemValueString('version', '0.0.0');
}
return $this->version;
}
/**
* Set the current Nextcloud version
* @param string $version
*/
public function setVersion(string $version) {
$this->version = $version;
}
/**
* Get the currently Nextcloud update channel
* @return string
*/
protected function getChannel() {
if ($this->channel === null) {
$this->channel = \OC_Util::getChannel();
}
return $this->channel;
}
/**
* Set the current Nextcloud update channel
* @param string $channel
*/
public function setChannel(string $channel) {
$this->channel = $channel;
}
protected function getEndpoint(): string {
return $this->config->getSystemValueString('appstoreurl', 'https://apps.nextcloud.com/api/v1') . '/' . $this->endpointName;
}
}
@@ -0,0 +1,53 @@
<?php
/**
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
*
* @author Lukas Reschke <lukas@statuscode.ch>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\App\AppStore\Version;
class Version {
/** @var string */
private $minVersion;
/** @var string */
private $maxVersion;
/**
* @param string $minVersion
* @param string $maxVersion
*/
public function __construct($minVersion, $maxVersion) {
$this->minVersion = $minVersion;
$this->maxVersion = $maxVersion;
}
/**
* @return string
*/
public function getMinimumVersion() {
return $this->minVersion;
}
/**
* @return string
*/
public function getMaximumVersion() {
return $this->maxVersion;
}
}
@@ -0,0 +1,85 @@
<?php
/**
* @copyright Copyright (c) 2016 Lukas Reschke <lukas@statuscode.ch>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\App\AppStore\Version;
/**
* Class VersionParser parses the versions as sent by the Nextcloud app store
*
* @package OC\App\AppStore
*/
class VersionParser {
/**
* @param string $versionString
* @return bool
*/
private function isValidVersionString($versionString) {
return (bool)preg_match('/^[0-9.]+$/', $versionString);
}
/**
* Returns the version for a version string
*
* @param string $versionSpec
* @return Version
* @throws \Exception If the version cannot be parsed
*/
public function getVersion($versionSpec) {
// * indicates that the version is compatible with all versions
if ($versionSpec === '*') {
return new Version('', '');
}
// Count the amount of =, if it is one then it's either maximum or minimum
// version. If it is two then it is maximum and minimum.
$versionElements = explode(' ', $versionSpec);
$firstVersion = $versionElements[0] ?? '';
$firstVersionNumber = substr($firstVersion, 2);
$secondVersion = $versionElements[1] ?? '';
$secondVersionNumber = substr($secondVersion, 2);
switch (count($versionElements)) {
case 1:
if (!$this->isValidVersionString($firstVersionNumber)) {
break;
}
if (str_starts_with($firstVersion, '>')) {
return new Version($firstVersionNumber, '');
}
return new Version('', $firstVersionNumber);
case 2:
if (!$this->isValidVersionString($firstVersionNumber) || !$this->isValidVersionString($secondVersionNumber)) {
break;
}
return new Version($firstVersionNumber, $secondVersionNumber);
}
throw new \Exception(
sprintf(
'Version cannot be parsed: %s',
$versionSpec
)
);
}
}
@@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
/**
* @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OC\App;
use InvalidArgumentException;
use function explode;
class CompareVersion {
private const REGEX_MAJOR = '/^\d+$/';
private const REGEX_MAJOR_MINOR = '/^\d+\.\d+$/';
private const REGEX_MAJOR_MINOR_PATCH = '/^\d+\.\d+\.\d+(?!\.\d+)/';
private const REGEX_ACTUAL = '/^\d+(\.\d+){1,2}/';
/**
* Checks if the given server version fulfills the given (app) version requirements.
*
* Version requirements can be 'major.minor.patch', 'major.minor' or just 'major',
* so '13.0.1', '13.0' and '13' are valid.
*
* @param string $actual version as major.minor.patch notation
* @param string $required version where major is required and minor and patch are optional
* @param string $comparator passed to `version_compare`
* @return bool whether the requirement is fulfilled
* @throws InvalidArgumentException if versions specified in an invalid format
*/
public function isCompatible(string $actual, string $required,
string $comparator = '>='): bool {
if (!preg_match(self::REGEX_ACTUAL, $actual, $matches)) {
throw new InvalidArgumentException("version specification $actual is invalid");
}
$cleanActual = $matches[0];
if (preg_match(self::REGEX_MAJOR, $required) === 1) {
return $this->compareMajor($cleanActual, $required, $comparator);
} elseif (preg_match(self::REGEX_MAJOR_MINOR, $required) === 1) {
return $this->compareMajorMinor($cleanActual, $required, $comparator);
} elseif (preg_match(self::REGEX_MAJOR_MINOR_PATCH, $required) === 1) {
return $this->compareMajorMinorPatch($cleanActual, $required, $comparator);
} else {
throw new InvalidArgumentException("required version $required is invalid");
}
}
private function compareMajor(string $actual, string $required,
string $comparator) {
$actualMajor = explode('.', $actual)[0];
$requiredMajor = explode('.', $required)[0];
return version_compare($actualMajor, $requiredMajor, $comparator);
}
private function compareMajorMinor(string $actual, string $required,
string $comparator) {
$actualMajor = explode('.', $actual)[0];
$actualMinor = explode('.', $actual)[1];
$requiredMajor = explode('.', $required)[0];
$requiredMinor = explode('.', $required)[1];
return version_compare("$actualMajor.$actualMinor",
"$requiredMajor.$requiredMinor", $comparator);
}
private function compareMajorMinorPatch($actual, $required, $comparator) {
$actualMajor = explode('.', $actual)[0];
$actualMinor = explode('.', $actual)[1];
$actualPatch = explode('.', $actual)[2];
$requiredMajor = explode('.', $required)[0];
$requiredMinor = explode('.', $required)[1];
$requiredPatch = explode('.', $required)[2];
return version_compare("$actualMajor.$actualMinor.$actualPatch",
"$requiredMajor.$requiredMinor.$requiredPatch", $comparator);
}
}
@@ -0,0 +1,403 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
* @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch>
*
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Joas Schilling <coding@schilljs.com>
* @author Julius Härtl <jus@bitgrid.net>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Stefan Weil <sw@weilnetz.de>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\App;
use OCP\IL10N;
class DependencyAnalyzer {
/** @var Platform */
private $platform;
/** @var \OCP\IL10N */
private $l;
/** @var array */
private $appInfo;
/**
* @param Platform $platform
* @param \OCP\IL10N $l
*/
public function __construct(Platform $platform, IL10N $l) {
$this->platform = $platform;
$this->l = $l;
}
/**
* @param array $app
* @returns array of missing dependencies
*/
public function analyze(array $app, bool $ignoreMax = false) {
$this->appInfo = $app;
if (isset($app['dependencies'])) {
$dependencies = $app['dependencies'];
} else {
$dependencies = [];
}
return array_merge(
$this->analyzeArchitecture($dependencies),
$this->analyzePhpVersion($dependencies),
$this->analyzeDatabases($dependencies),
$this->analyzeCommands($dependencies),
$this->analyzeLibraries($dependencies),
$this->analyzeOS($dependencies),
$this->analyzeOC($dependencies, $app, $ignoreMax)
);
}
public function isMarkedCompatible(array $app): bool {
if (isset($app['dependencies'])) {
$dependencies = $app['dependencies'];
} else {
$dependencies = [];
}
$maxVersion = $this->getMaxVersion($dependencies, $app);
if ($maxVersion === null) {
return true;
}
return !$this->compareBigger($this->platform->getOcVersion(), $maxVersion);
}
/**
* Truncates both versions to the lowest common version, e.g.
* 5.1.2.3 and 5.1 will be turned into 5.1 and 5.1,
* 5.2.6.5 and 5.1 will be turned into 5.2 and 5.1
* @param string $first
* @param string $second
* @return string[] first element is the first version, second element is the
* second version
*/
private function normalizeVersions($first, $second) {
$first = explode('.', $first);
$second = explode('.', $second);
// get both arrays to the same minimum size
$length = min(count($second), count($first));
$first = array_slice($first, 0, $length);
$second = array_slice($second, 0, $length);
return [implode('.', $first), implode('.', $second)];
}
/**
* Parameters will be normalized and then passed into version_compare
* in the same order they are specified in the method header
* @param string $first
* @param string $second
* @param string $operator
* @return bool result similar to version_compare
*/
private function compare($first, $second, $operator) {
// we can't normalize versions if one of the given parameters is not a
// version string but null. In case one parameter is null normalization
// will therefore be skipped
if ($first !== null && $second !== null) {
[$first, $second] = $this->normalizeVersions($first, $second);
}
return version_compare($first, $second, $operator);
}
/**
* Checks if a version is bigger than another version
* @param string $first
* @param string $second
* @return bool true if the first version is bigger than the second
*/
private function compareBigger($first, $second) {
return $this->compare($first, $second, '>');
}
/**
* Checks if a version is smaller than another version
* @param string $first
* @param string $second
* @return bool true if the first version is smaller than the second
*/
private function compareSmaller($first, $second) {
return $this->compare($first, $second, '<');
}
/**
* @param array $dependencies
* @return array
*/
private function analyzePhpVersion(array $dependencies) {
$missing = [];
if (isset($dependencies['php']['@attributes']['min-version'])) {
$minVersion = $dependencies['php']['@attributes']['min-version'];
if ($this->compareSmaller($this->platform->getPhpVersion(), $minVersion)) {
$missing[] = $this->l->t('PHP %s or higher is required.', [$minVersion]);
}
}
if (isset($dependencies['php']['@attributes']['max-version'])) {
$maxVersion = $dependencies['php']['@attributes']['max-version'];
if ($this->compareBigger($this->platform->getPhpVersion(), $maxVersion)) {
$missing[] = $this->l->t('PHP with a version lower than %s is required.', [$maxVersion]);
}
}
if (isset($dependencies['php']['@attributes']['min-int-size'])) {
$intSize = $dependencies['php']['@attributes']['min-int-size'];
if ($intSize > $this->platform->getIntSize() * 8) {
$missing[] = $this->l->t('%sbit or higher PHP required.', [$intSize]);
}
}
return $missing;
}
private function analyzeArchitecture(array $dependencies) {
$missing = [];
if (!isset($dependencies['architecture'])) {
return $missing;
}
$supportedArchitectures = $dependencies['architecture'];
if (empty($supportedArchitectures)) {
return $missing;
}
if (!is_array($supportedArchitectures)) {
$supportedArchitectures = [$supportedArchitectures];
}
$supportedArchitectures = array_map(function ($architecture) {
return $this->getValue($architecture);
}, $supportedArchitectures);
$currentArchitecture = $this->platform->getArchitecture();
if (!in_array($currentArchitecture, $supportedArchitectures, true)) {
$missing[] = $this->l->t('The following architectures are supported: %s', [implode(', ', $supportedArchitectures)]);
}
return $missing;
}
/**
* @param array $dependencies
* @return array
*/
private function analyzeDatabases(array $dependencies) {
$missing = [];
if (!isset($dependencies['database'])) {
return $missing;
}
$supportedDatabases = $dependencies['database'];
if (empty($supportedDatabases)) {
return $missing;
}
if (!is_array($supportedDatabases)) {
$supportedDatabases = [$supportedDatabases];
}
$supportedDatabases = array_map(function ($db) {
return $this->getValue($db);
}, $supportedDatabases);
$currentDatabase = $this->platform->getDatabase();
if (!in_array($currentDatabase, $supportedDatabases)) {
$missing[] = $this->l->t('The following databases are supported: %s', [implode(', ', $supportedDatabases)]);
}
return $missing;
}
/**
* @param array $dependencies
* @return array
*/
private function analyzeCommands(array $dependencies) {
$missing = [];
if (!isset($dependencies['command'])) {
return $missing;
}
$commands = $dependencies['command'];
if (!is_array($commands)) {
$commands = [$commands];
}
if (isset($commands['@value'])) {
$commands = [$commands];
}
$os = $this->platform->getOS();
foreach ($commands as $command) {
if (isset($command['@attributes']['os']) && $command['@attributes']['os'] !== $os) {
continue;
}
$commandName = $this->getValue($command);
if (!$this->platform->isCommandKnown($commandName)) {
$missing[] = $this->l->t('The command line tool %s could not be found', [$commandName]);
}
}
return $missing;
}
/**
* @param array $dependencies
* @return array
*/
private function analyzeLibraries(array $dependencies) {
$missing = [];
if (!isset($dependencies['lib'])) {
return $missing;
}
$libs = $dependencies['lib'];
if (!is_array($libs)) {
$libs = [$libs];
}
if (isset($libs['@value'])) {
$libs = [$libs];
}
foreach ($libs as $lib) {
$libName = $this->getValue($lib);
$libVersion = $this->platform->getLibraryVersion($libName);
if (is_null($libVersion)) {
$missing[] = $this->l->t('The library %s is not available.', [$libName]);
continue;
}
if (is_array($lib)) {
if (isset($lib['@attributes']['min-version'])) {
$minVersion = $lib['@attributes']['min-version'];
if ($this->compareSmaller($libVersion, $minVersion)) {
$missing[] = $this->l->t('Library %1$s with a version higher than %2$s is required - available version %3$s.',
[$libName, $minVersion, $libVersion]);
}
}
if (isset($lib['@attributes']['max-version'])) {
$maxVersion = $lib['@attributes']['max-version'];
if ($this->compareBigger($libVersion, $maxVersion)) {
$missing[] = $this->l->t('Library %1$s with a version lower than %2$s is required - available version %3$s.',
[$libName, $maxVersion, $libVersion]);
}
}
}
}
return $missing;
}
/**
* @param array $dependencies
* @return array
*/
private function analyzeOS(array $dependencies) {
$missing = [];
if (!isset($dependencies['os'])) {
return $missing;
}
$oss = $dependencies['os'];
if (empty($oss)) {
return $missing;
}
if (is_array($oss)) {
$oss = array_map(function ($os) {
return $this->getValue($os);
}, $oss);
} else {
$oss = [$oss];
}
$currentOS = $this->platform->getOS();
if (!in_array($currentOS, $oss)) {
$missing[] = $this->l->t('The following platforms are supported: %s', [implode(', ', $oss)]);
}
return $missing;
}
/**
* @param array $dependencies
* @param array $appInfo
* @return array
*/
private function analyzeOC(array $dependencies, array $appInfo, bool $ignoreMax) {
$missing = [];
$minVersion = null;
if (isset($dependencies['nextcloud']['@attributes']['min-version'])) {
$minVersion = $dependencies['nextcloud']['@attributes']['min-version'];
} elseif (isset($dependencies['owncloud']['@attributes']['min-version'])) {
$minVersion = $dependencies['owncloud']['@attributes']['min-version'];
} elseif (isset($appInfo['requiremin'])) {
$minVersion = $appInfo['requiremin'];
} elseif (isset($appInfo['require'])) {
$minVersion = $appInfo['require'];
}
$maxVersion = $this->getMaxVersion($dependencies, $appInfo);
if (!is_null($minVersion)) {
if ($this->compareSmaller($this->platform->getOcVersion(), $minVersion)) {
$missing[] = $this->l->t('Server version %s or higher is required.', [$this->toVisibleVersion($minVersion)]);
}
}
if (!$ignoreMax && !is_null($maxVersion)) {
if ($this->compareBigger($this->platform->getOcVersion(), $maxVersion)) {
$missing[] = $this->l->t('Server version %s or lower is required.', [$this->toVisibleVersion($maxVersion)]);
}
}
return $missing;
}
private function getMaxVersion(array $dependencies, array $appInfo): ?string {
if (isset($dependencies['nextcloud']['@attributes']['max-version'])) {
return $dependencies['nextcloud']['@attributes']['max-version'];
}
if (isset($dependencies['owncloud']['@attributes']['max-version'])) {
return $dependencies['owncloud']['@attributes']['max-version'];
}
if (isset($appInfo['requiremax'])) {
return $appInfo['requiremax'];
}
return null;
}
/**
* Map the internal version number to the Nextcloud version
*
* @param string $version
* @return string
*/
protected function toVisibleVersion($version) {
switch ($version) {
case '9.1':
return '10';
default:
if (str_starts_with($version, '9.1.')) {
$version = '10.0.' . substr($version, 4);
}
return $version;
}
}
/**
* @param $element
* @return mixed
*/
private function getValue($element) {
if (isset($element['@value'])) {
return $element['@value'];
}
return (string)$element;
}
}
+287
View File
@@ -0,0 +1,287 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
* @copyright Copyright (c) 2016, Lukas Reschke <lukas@statuscode.ch>
*
* @author Andreas Fischer <bantu@owncloud.com>
* @author Arthur Schiwon <blizzz@arthur-schiwon.de>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Daniel Kesselberg <mail@danielkesselberg.de>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\App;
use OCP\ICache;
use function libxml_disable_entity_loader;
use function simplexml_load_string;
class InfoParser {
/** @var \OCP\ICache|null */
private $cache;
/**
* @param ICache|null $cache
*/
public function __construct(ICache $cache = null) {
$this->cache = $cache;
}
/**
* @param string $file the xml file to be loaded
* @return null|array where null is an indicator for an error
*/
public function parse($file) {
if (!file_exists($file)) {
return null;
}
if ($this->cache !== null) {
$fileCacheKey = $file . filemtime($file);
if ($cachedValue = $this->cache->get($fileCacheKey)) {
return json_decode($cachedValue, true);
}
}
libxml_use_internal_errors(true);
if ((PHP_VERSION_ID < 80000)) {
$loadEntities = libxml_disable_entity_loader(false);
$xml = simplexml_load_string(file_get_contents($file));
libxml_disable_entity_loader($loadEntities);
} else {
$xml = simplexml_load_string(file_get_contents($file));
}
if ($xml === false) {
libxml_clear_errors();
return null;
}
$array = $this->xmlToArray($xml);
if (is_null($array)) {
return null;
}
if (!array_key_exists('info', $array)) {
$array['info'] = [];
}
if (!array_key_exists('remote', $array)) {
$array['remote'] = [];
}
if (!array_key_exists('public', $array)) {
$array['public'] = [];
}
if (!array_key_exists('types', $array)) {
$array['types'] = [];
}
if (!array_key_exists('repair-steps', $array)) {
$array['repair-steps'] = [];
}
if (!array_key_exists('install', $array['repair-steps'])) {
$array['repair-steps']['install'] = [];
}
if (!array_key_exists('pre-migration', $array['repair-steps'])) {
$array['repair-steps']['pre-migration'] = [];
}
if (!array_key_exists('post-migration', $array['repair-steps'])) {
$array['repair-steps']['post-migration'] = [];
}
if (!array_key_exists('live-migration', $array['repair-steps'])) {
$array['repair-steps']['live-migration'] = [];
}
if (!array_key_exists('uninstall', $array['repair-steps'])) {
$array['repair-steps']['uninstall'] = [];
}
if (!array_key_exists('background-jobs', $array)) {
$array['background-jobs'] = [];
}
if (!array_key_exists('two-factor-providers', $array)) {
$array['two-factor-providers'] = [];
}
if (!array_key_exists('commands', $array)) {
$array['commands'] = [];
}
if (!array_key_exists('activity', $array)) {
$array['activity'] = [];
}
if (!array_key_exists('filters', $array['activity'])) {
$array['activity']['filters'] = [];
}
if (!array_key_exists('settings', $array['activity'])) {
$array['activity']['settings'] = [];
}
if (!array_key_exists('providers', $array['activity'])) {
$array['activity']['providers'] = [];
}
if (!array_key_exists('settings', $array)) {
$array['settings'] = [];
}
if (!array_key_exists('admin', $array['settings'])) {
$array['settings']['admin'] = [];
}
if (!array_key_exists('admin-section', $array['settings'])) {
$array['settings']['admin-section'] = [];
}
if (!array_key_exists('personal', $array['settings'])) {
$array['settings']['personal'] = [];
}
if (!array_key_exists('personal-section', $array['settings'])) {
$array['settings']['personal-section'] = [];
}
if (array_key_exists('types', $array)) {
if (is_array($array['types'])) {
foreach ($array['types'] as $type => $v) {
unset($array['types'][$type]);
if (is_string($type)) {
$array['types'][] = $type;
}
}
} else {
$array['types'] = [];
}
}
if (isset($array['repair-steps']['install']['step']) && is_array($array['repair-steps']['install']['step'])) {
$array['repair-steps']['install'] = $array['repair-steps']['install']['step'];
}
if (isset($array['repair-steps']['pre-migration']['step']) && is_array($array['repair-steps']['pre-migration']['step'])) {
$array['repair-steps']['pre-migration'] = $array['repair-steps']['pre-migration']['step'];
}
if (isset($array['repair-steps']['post-migration']['step']) && is_array($array['repair-steps']['post-migration']['step'])) {
$array['repair-steps']['post-migration'] = $array['repair-steps']['post-migration']['step'];
}
if (isset($array['repair-steps']['live-migration']['step']) && is_array($array['repair-steps']['live-migration']['step'])) {
$array['repair-steps']['live-migration'] = $array['repair-steps']['live-migration']['step'];
}
if (isset($array['repair-steps']['uninstall']['step']) && is_array($array['repair-steps']['uninstall']['step'])) {
$array['repair-steps']['uninstall'] = $array['repair-steps']['uninstall']['step'];
}
if (isset($array['background-jobs']['job']) && is_array($array['background-jobs']['job'])) {
$array['background-jobs'] = $array['background-jobs']['job'];
}
if (isset($array['commands']['command']) && is_array($array['commands']['command'])) {
$array['commands'] = $array['commands']['command'];
}
if (isset($array['two-factor-providers']['provider']) && is_array($array['two-factor-providers']['provider'])) {
$array['two-factor-providers'] = $array['two-factor-providers']['provider'];
}
if (isset($array['activity']['filters']['filter']) && is_array($array['activity']['filters']['filter'])) {
$array['activity']['filters'] = $array['activity']['filters']['filter'];
}
if (isset($array['activity']['settings']['setting']) && is_array($array['activity']['settings']['setting'])) {
$array['activity']['settings'] = $array['activity']['settings']['setting'];
}
if (isset($array['activity']['providers']['provider']) && is_array($array['activity']['providers']['provider'])) {
$array['activity']['providers'] = $array['activity']['providers']['provider'];
}
if (isset($array['collaboration']['collaborators']['searchPlugins']['searchPlugin'])
&& is_array($array['collaboration']['collaborators']['searchPlugins']['searchPlugin'])
&& !isset($array['collaboration']['collaborators']['searchPlugins']['searchPlugin']['class'])
) {
$array['collaboration']['collaborators']['searchPlugins'] = $array['collaboration']['collaborators']['searchPlugins']['searchPlugin'];
}
if (isset($array['settings']['admin']) && !is_array($array['settings']['admin'])) {
$array['settings']['admin'] = [$array['settings']['admin']];
}
if (isset($array['settings']['admin-section']) && !is_array($array['settings']['admin-section'])) {
$array['settings']['admin-section'] = [$array['settings']['admin-section']];
}
if (isset($array['settings']['personal']) && !is_array($array['settings']['personal'])) {
$array['settings']['personal'] = [$array['settings']['personal']];
}
if (isset($array['settings']['personal-section']) && !is_array($array['settings']['personal-section'])) {
$array['settings']['personal-section'] = [$array['settings']['personal-section']];
}
if (isset($array['navigations']['navigation']) && $this->isNavigationItem($array['navigations']['navigation'])) {
$array['navigations']['navigation'] = [$array['navigations']['navigation']];
}
if ($this->cache !== null) {
$this->cache->set($fileCacheKey, json_encode($array));
}
return $array;
}
/**
* @param $data
* @return bool
*/
private function isNavigationItem($data): bool {
// Allow settings navigation items with no route entry
$type = $data['type'] ?? 'link';
if ($type === 'settings') {
return isset($data['name']);
}
return isset($data['name'], $data['route']);
}
/**
* @param \SimpleXMLElement $xml
* @return array
*/
public function xmlToArray($xml) {
if (!$xml->children()) {
return (string)$xml;
}
$array = [];
foreach ($xml->children() as $element => $node) {
$totalElement = count($xml->{$element});
if (!isset($array[$element])) {
$array[$element] = $totalElement > 1 ? [] : "";
}
/** @var \SimpleXMLElement $node */
// Has attributes
if ($attributes = $node->attributes()) {
$data = [
'@attributes' => [],
];
if (!count($node->children())) {
$value = (string)$node;
if (!empty($value)) {
$data['@value'] = $value;
}
} else {
$data = array_merge($data, $this->xmlToArray($node));
}
foreach ($attributes as $attr => $value) {
$data['@attributes'][$attr] = (string)$value;
}
if ($totalElement > 1) {
$array[$element][] = $data;
} else {
$array[$element] = $data;
}
// Just a value
} else {
if ($totalElement > 1) {
$array[$element][] = $this->xmlToArray($node);
} else {
$array[$element] = $this->xmlToArray($node);
}
}
}
return $array;
}
}
+86
View File
@@ -0,0 +1,86 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Bernhard Posselt <dev@bernhard-posselt.com>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Julius Härtl <jus@bitgrid.net>
* @author Morris Jobke <hey@morrisjobke.de>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\App;
use OCP\IBinaryFinder;
use OCP\IConfig;
/**
* Class Platform
*
* This class basically abstracts any kind of information which can be retrieved from the underlying system.
*
* @package OC\App
*/
class Platform {
private IConfig $config;
public function __construct(IConfig $config) {
$this->config = $config;
}
public function getPhpVersion(): string {
return phpversion();
}
public function getIntSize(): int {
return PHP_INT_SIZE;
}
public function getOcVersion(): string {
$v = \OCP\Util::getVersion();
return implode('.', $v);
}
public function getDatabase(): string {
$dbType = $this->config->getSystemValueString('dbtype', 'sqlite');
if ($dbType === 'sqlite3') {
$dbType = 'sqlite';
}
return $dbType;
}
public function getOS(): string {
return php_uname('s');
}
/**
* @param $command
*/
public function isCommandKnown(string $command): bool {
return \OCP\Server::get(IBinaryFinder::class)->findBinaryPath($command) !== false;
}
public function getLibraryVersion(string $name): ?string {
$repo = new PlatformRepository();
return $repo->findLibrary($name);
}
public function getArchitecture(): string {
return php_uname('m');
}
}
@@ -0,0 +1,226 @@
<?php
/**
* @copyright Copyright (c) 2016, ownCloud, Inc.
*
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @license AGPL-3.0
*
* This code is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License, version 3,
* along with this program. If not, see <http://www.gnu.org/licenses/>
*
*/
namespace OC\App;
/**
* Class PlatformRepository
*
* Inspired by the composer project - licensed under MIT
* https://github.com/composer/composer/blob/master/src/Composer/Repository/PlatformRepository.php#L82
*
* @package OC\App
*/
class PlatformRepository {
private array $packages;
public function __construct() {
$this->packages = $this->initialize();
}
protected function initialize(): array {
$loadedExtensions = get_loaded_extensions();
$packages = [];
// Extensions scanning
foreach ($loadedExtensions as $name) {
if (in_array($name, ['standard', 'Core'])) {
continue;
}
$ext = new \ReflectionExtension($name);
try {
$prettyVersion = $ext->getVersion();
/** @psalm-suppress TypeDoesNotContainNull
* @psalm-suppress RedundantCondition
* TODO Remove these annotations once psalm fixes the method signature ( https://github.com/vimeo/psalm/pull/8655 )
*/
$prettyVersion = $this->normalizeVersion($prettyVersion ?? '0');
} catch (\UnexpectedValueException $e) {
$prettyVersion = '0';
$prettyVersion = $this->normalizeVersion($prettyVersion);
}
$packages[$this->buildPackageName($name)] = $prettyVersion;
}
foreach ($loadedExtensions as $name) {
$prettyVersion = null;
switch ($name) {
case 'curl':
$curlVersion = curl_version();
$prettyVersion = $curlVersion['version'];
break;
case 'intl':
$name = 'ICU';
if (defined('INTL_ICU_VERSION')) {
$prettyVersion = INTL_ICU_VERSION;
} else {
$reflector = new \ReflectionExtension('intl');
ob_start();
$reflector->info();
$output = ob_get_clean();
preg_match('/^ICU version => (.*)$/m', $output, $matches);
$prettyVersion = $matches[1];
}
break;
case 'libxml':
$prettyVersion = LIBXML_DOTTED_VERSION;
break;
case 'openssl':
$prettyVersion = preg_replace_callback('{^(?:OpenSSL\s*)?([0-9.]+)([a-z]?).*}', function ($match) {
return $match[1] . (empty($match[2]) ? '' : '.' . (ord($match[2]) - 96));
}, OPENSSL_VERSION_TEXT);
break;
case 'pcre':
$prettyVersion = preg_replace('{^(\S+).*}', '$1', PCRE_VERSION);
break;
case 'uuid':
$prettyVersion = phpversion('uuid');
break;
case 'xsl':
$prettyVersion = LIBXSLT_DOTTED_VERSION;
break;
default:
// None handled extensions have no special cases, skip
continue 2;
}
if ($prettyVersion === null) {
continue;
}
try {
$prettyVersion = $this->normalizeVersion($prettyVersion);
} catch (\UnexpectedValueException $e) {
continue;
}
$packages[$this->buildPackageName($name)] = $prettyVersion;
}
return $packages;
}
private function buildPackageName(string $name): string {
return str_replace(' ', '-', $name);
}
public function findLibrary(string $name): ?string {
$extName = $this->buildPackageName($name);
if (isset($this->packages[$extName])) {
return $this->packages[$extName];
}
return null;
}
private static string $modifierRegex = '[._-]?(?:(stable|beta|b|RC|alpha|a|patch|pl|p)(?:[.-]?(\d+))?)?([.-]?dev)?';
/**
* Normalizes a version string to be able to perform comparisons on it
*
* https://github.com/composer/composer/blob/master/src/Composer/Package/Version/VersionParser.php#L94
*
* @param string $fullVersion optional complete version string to give more context
* @throws \UnexpectedValueException
*/
public function normalizeVersion(string $version, ?string $fullVersion = null): string {
$version = trim($version);
if (null === $fullVersion) {
$fullVersion = $version;
}
// ignore aliases and just assume the alias is required instead of the source
if (preg_match('{^([^,\s]+) +as +([^,\s]+)$}', $version, $match)) {
$version = $match[1];
}
// match master-like branches
if (preg_match('{^(?:dev-)?(?:master|trunk|default)$}i', $version)) {
return '9999999-dev';
}
if ('dev-' === strtolower(substr($version, 0, 4))) {
return 'dev-' . substr($version, 4);
}
// match classical versioning
if (preg_match('{^v?(\d{1,3})(\.\d+)?(\.\d+)?(\.\d+)?' . self::$modifierRegex . '$}i', $version, $matches)) {
$version = $matches[1]
. (!empty($matches[2]) ? $matches[2] : '.0')
. (!empty($matches[3]) ? $matches[3] : '.0')
. (!empty($matches[4]) ? $matches[4] : '.0');
$index = 5;
} elseif (preg_match('{^v?(\d{4}(?:[.:-]?\d{2}){1,6}(?:[.:-]?\d{1,3})?)' . self::$modifierRegex . '$}i', $version, $matches)) { // match date-based versioning
$version = preg_replace('{\D}', '-', $matches[1]);
$index = 2;
} elseif (preg_match('{^v?(\d{4,})(\.\d+)?(\.\d+)?(\.\d+)?' . self::$modifierRegex . '$}i', $version, $matches)) {
$version = $matches[1]
. (!empty($matches[2]) ? $matches[2] : '.0')
. (!empty($matches[3]) ? $matches[3] : '.0')
. (!empty($matches[4]) ? $matches[4] : '.0');
$index = 5;
}
// add version modifiers if a version was matched
if (isset($index)) {
if (!empty($matches[$index])) {
if ('stable' === $matches[$index]) {
return $version;
}
$version .= '-' . $this->expandStability($matches[$index]) . (!empty($matches[$index + 1]) ? $matches[$index + 1] : '');
}
if (!empty($matches[$index + 2])) {
$version .= '-dev';
}
return $version;
}
$extraMessage = '';
if (preg_match('{ +as +' . preg_quote($version) . '$}', $fullVersion)) {
$extraMessage = ' in "' . $fullVersion . '", the alias must be an exact version';
} elseif (preg_match('{^' . preg_quote($version) . ' +as +}', $fullVersion)) {
$extraMessage = ' in "' . $fullVersion . '", the alias source must be an exact version, if it is a branch name you should prefix it with dev-';
}
throw new \UnexpectedValueException('Invalid version string "' . $version . '"' . $extraMessage);
}
private function expandStability(string $stability): string {
$stability = strtolower($stability);
switch ($stability) {
case 'a':
return 'alpha';
case 'b':
return 'beta';
case 'p':
case 'pl':
return 'patch';
case 'rc':
return 'RC';
default:
return $stability;
}
}
}