2014-11-07 08:26:12 -05:00
< ? php
/**
2024-05-23 03:26:56 -04:00
* SPDX - FileCopyrightText : 2016 - 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX - FileCopyrightText : 2016 ownCloud , Inc .
* SPDX - License - Identifier : AGPL - 3.0 - only
2014-11-07 08:26:12 -05:00
*/
namespace OC\App ;
2018-06-06 05:43:47 -04:00
use OC\AppConfig ;
2023-02-07 10:56:04 -05:00
use OC\AppFramework\Bootstrap\Coordinator ;
use OC\ServerNotAvailableException ;
2023-02-08 03:22:22 -05:00
use OCP\Activity\IManager as IActivityManager ;
2016-11-17 06:30:52 -05:00
use OCP\App\AppPathNotFoundException ;
2022-12-08 04:55:19 -05:00
use OCP\App\Events\AppDisableEvent ;
use OCP\App\Events\AppEnableEvent ;
2014-11-07 08:26:12 -05:00
use OCP\App\IAppManager ;
2016-02-08 20:51:12 -05:00
use OCP\App\ManagerEvent ;
2023-02-08 03:22:22 -05:00
use OCP\Collaboration\AutoComplete\IManager as IAutoCompleteManager ;
use OCP\Collaboration\Collaborators\ISearch as ICollaboratorSearch ;
use OCP\Diagnostics\IEventLogger ;
2022-12-08 04:55:19 -05:00
use OCP\EventDispatcher\IEventDispatcher ;
2015-04-01 09:37:22 -04:00
use OCP\ICacheFactory ;
2019-09-05 06:55:24 -04:00
use OCP\IConfig ;
2019-06-25 09:20:06 -04:00
use OCP\IGroup ;
2014-11-07 08:26:12 -05:00
use OCP\IGroupManager ;
2024-08-27 06:14:09 -04:00
use OCP\INavigationManager ;
2024-03-06 05:13:29 -05:00
use OCP\IURLGenerator ;
2015-02-02 08:47:29 -05:00
use OCP\IUser ;
2014-11-07 08:26:12 -05:00
use OCP\IUserSession ;
2023-02-07 10:56:04 -05:00
use OCP\Settings\IManager as ISettingsManager ;
2021-04-16 08:26:43 -04:00
use Psr\Log\LoggerInterface ;
2014-11-07 08:26:12 -05:00
class AppManager implements IAppManager {
2016-01-14 09:23:07 -05:00
/**
* Apps with these types can not be enabled for certain groups only
* @ var string []
*/
protected $protectedAppTypes = [
'filesystem' ,
'prelogin' ,
'authentication' ,
'logging' ,
'prevent_group_restriction' ,
];
2015-10-16 10:30:29 -04:00
/** @var string[] $appId => $enabled */
2023-02-08 02:59:35 -05:00
private array $installedAppsCache = [];
2014-11-07 08:26:12 -05:00
2023-02-08 02:59:35 -05:00
/** @var string[]|null */
private ? array $shippedApps = null ;
2015-10-16 10:30:29 -04:00
2022-09-15 05:15:25 -04:00
private array $alwaysEnabled = [];
private array $defaultEnabled = [];
2015-10-16 10:30:29 -04:00
2018-01-29 07:09:32 -05:00
/** @var array */
2023-02-08 02:59:35 -05:00
private array $appInfos = [];
2018-01-29 07:09:32 -05:00
/** @var array */
2023-02-08 02:59:35 -05:00
private array $appVersions = [];
2018-01-29 07:09:32 -05:00
2019-07-23 04:28:47 -04:00
/** @var array */
2023-02-08 02:59:35 -05:00
private array $autoDisabledApps = [];
2023-02-08 05:46:07 -05:00
private array $appTypes = [];
2019-07-23 04:28:47 -04:00
2023-02-07 10:56:04 -05:00
/** @var array<string, true> */
private array $loadedApps = [];
2024-03-06 08:17:14 -05:00
private ? AppConfig $appConfig = null ;
2024-04-22 09:50:06 -04:00
private ? IURLGenerator $urlGenerator = null ;
2024-08-27 06:14:09 -04:00
private ? INavigationManager $navigationManager = null ;
2024-03-06 08:17:14 -05:00
2024-04-22 09:50:06 -04:00
/**
* Be extremely careful when injecting classes here . The AppManager is used by the installer ,
* so it needs to work before installation . See how AppConfig and IURLGenerator are injected for reference
*/
2023-07-11 03:18:17 -04:00
public function __construct (
private IUserSession $userSession ,
private IConfig $config ,
private IGroupManager $groupManager ,
private ICacheFactory $memCacheFactory ,
private IEventDispatcher $dispatcher ,
private LoggerInterface $logger ,
) {
2014-11-07 08:26:12 -05:00
}
2024-08-27 06:14:09 -04:00
private function getNavigationManager () : INavigationManager {
if ( $this -> navigationManager === null ) {
$this -> navigationManager = \OCP\Server :: get ( INavigationManager :: class );
}
return $this -> navigationManager ;
}
2024-03-07 19:05:24 -05:00
public function getAppIcon ( string $appId , bool $dark = false ) : ? string {
$possibleIcons = $dark ? [ $appId . '-dark.svg' , 'app-dark.svg' ] : [ $appId . '.svg' , 'app.svg' ];
2024-03-06 05:13:29 -05:00
$icon = null ;
foreach ( $possibleIcons as $iconName ) {
try {
2024-04-22 09:50:06 -04:00
$icon = $this -> getUrlGenerator () -> imagePath ( $appId , $iconName );
2024-03-06 05:13:29 -05:00
break ;
} catch ( \RuntimeException $e ) {
// ignore
}
}
return $icon ;
}
2024-03-06 08:17:14 -05:00
private function getAppConfig () : AppConfig {
if ( $this -> appConfig !== null ) {
return $this -> appConfig ;
}
if ( ! $this -> config -> getSystemValueBool ( 'installed' , false )) {
throw new \Exception ( 'Nextcloud is not installed yet, AppConfig is not available' );
}
$this -> appConfig = \OCP\Server :: get ( AppConfig :: class );
return $this -> appConfig ;
}
2024-04-22 09:50:06 -04:00
private function getUrlGenerator () : IURLGenerator {
if ( $this -> urlGenerator !== null ) {
return $this -> urlGenerator ;
}
if ( ! $this -> config -> getSystemValueBool ( 'installed' , false )) {
throw new \Exception ( 'Nextcloud is not installed yet, AppConfig is not available' );
}
$this -> urlGenerator = \OCP\Server :: get ( IURLGenerator :: class );
return $this -> urlGenerator ;
}
2014-11-07 08:26:12 -05:00
/**
* @ return string [] $appId => $enabled
*/
2023-02-08 02:59:35 -05:00
private function getInstalledAppsValues () : array {
2014-11-07 08:26:12 -05:00
if ( ! $this -> installedAppsCache ) {
2024-03-06 08:17:14 -05:00
$values = $this -> getAppConfig () -> getValues ( false , 'enabled' );
2015-11-19 03:11:14 -05:00
$alwaysEnabledApps = $this -> getAlwaysEnabledApps ();
foreach ( $alwaysEnabledApps as $appId ) {
$values [ $appId ] = 'yes' ;
}
2014-11-07 08:26:12 -05:00
$this -> installedAppsCache = array_filter ( $values , function ( $value ) {
return $value !== 'no' ;
});
ksort ( $this -> installedAppsCache );
}
return $this -> installedAppsCache ;
}
2015-02-02 08:47:29 -05:00
/**
* List all installed apps
*
* @ return string []
*/
public function getInstalledApps () {
return array_keys ( $this -> getInstalledAppsValues ());
}
2024-09-12 10:38:35 -04:00
/**
* Get a list of all apps in the apps folder
*
* @ return list < string > an array of app names ( string IDs )
*/
public function getAllAppsInAppsFolders () : array {
$apps = [];
foreach ( \OC :: $APPSROOTS as $apps_dir ) {
if ( ! is_readable ( $apps_dir [ 'path' ])) {
$this -> logger -> warning ( 'unable to read app folder : ' . $apps_dir [ 'path' ], [ 'app' => 'core' ]);
continue ;
}
$dh = opendir ( $apps_dir [ 'path' ]);
if ( is_resource ( $dh )) {
while (( $file = readdir ( $dh )) !== false ) {
if (
$file [ 0 ] != '.' &&
is_dir ( $apps_dir [ 'path' ] . '/' . $file ) &&
is_file ( $apps_dir [ 'path' ] . '/' . $file . '/appinfo/info.xml' )
) {
$apps [] = $file ;
}
}
}
}
return array_values ( array_unique ( $apps ));
}
2015-02-02 08:47:29 -05:00
/**
* List all apps enabled for a user
*
* @ param \OCP\IUser $user
* @ return string []
*/
2015-02-05 09:11:07 -05:00
public function getEnabledAppsForUser ( IUser $user ) {
2015-02-02 08:47:29 -05:00
$apps = $this -> getInstalledAppsValues ();
$appsForUser = array_filter ( $apps , function ( $enabled ) use ( $user ) {
return $this -> checkAppForUser ( $enabled , $user );
});
return array_keys ( $appsForUser );
}
2019-06-25 09:20:06 -04:00
/**
2022-12-08 04:55:19 -05:00
* @ param IGroup $group
2019-06-25 09:20:06 -04:00
* @ 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 );
}
2023-02-08 06:35:57 -05:00
/**
* 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 ;
}
2023-02-08 05:46:07 -05:00
/**
* 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 ) {
2023-02-08 07:29:57 -05:00
if ( in_array ( $type , $appTypes , true )) {
2023-02-08 05:46:07 -05:00
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 ) {
2024-03-06 08:17:14 -05:00
$this -> appTypes = $this -> getAppConfig () -> getValues ( false , 'types' ) ? : [];
2023-02-08 05:46:07 -05:00
}
if ( isset ( $this -> appTypes [ $app ])) {
return explode ( ',' , $this -> appTypes [ $app ]);
}
return [];
}
2019-07-23 04:28:47 -04:00
/**
* @ return array
*/
public function getAutoDisabledApps () : array {
return $this -> autoDisabledApps ;
}
2019-06-25 09:20:06 -04:00
/**
* @ 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 [];
}
2021-11-11 07:56:42 -05:00
return json_decode ( $values [ $appId ], true );
2019-06-25 09:20:06 -04:00
}
2014-11-07 08:26:12 -05:00
/**
* Check if an app is enabled for user
*
* @ param string $appId
2024-01-23 03:55:06 -05:00
* @ param \OCP\IUser | null $user ( optional ) if not defined , the currently logged in user will be used
2014-11-07 08:26:12 -05:00
* @ return bool
*/
public function isEnabledForUser ( $appId , $user = null ) {
2015-10-16 10:30:29 -04:00
if ( $this -> isAlwaysEnabled ( $appId )) {
return true ;
}
2017-03-20 05:11:49 -04:00
if ( $user === null ) {
2014-11-07 08:26:12 -05:00
$user = $this -> userSession -> getUser ();
}
2015-02-02 08:47:29 -05:00
$installedApps = $this -> getInstalledAppsValues ();
2014-11-07 08:26:12 -05:00
if ( isset ( $installedApps [ $appId ])) {
2015-02-02 08:47:29 -05:00
return $this -> checkAppForUser ( $installedApps [ $appId ], $user );
} else {
return false ;
}
}
2023-02-08 02:59:35 -05:00
private function checkAppForUser ( string $enabled , ? IUser $user ) : bool {
2015-02-02 08:47:29 -05:00
if ( $enabled === 'yes' ) {
return true ;
2017-03-20 05:11:49 -04:00
} elseif ( $user === null ) {
2015-02-02 08:47:29 -05:00
return false ;
} else {
2016-01-07 13:49:40 -05:00
if ( empty ( $enabled )) {
return false ;
}
2015-02-02 08:47:29 -05:00
$groupIds = json_decode ( $enabled );
2015-12-10 08:53:34 -05:00
if ( ! is_array ( $groupIds )) {
$jsonError = json_last_error ();
2024-06-09 09:36:32 -04:00
$jsonErrorMsg = json_last_error_msg ();
// this really should never happen (if it does, the admin should check the `enabled` key value via `occ config:list` because it's bogus for some reason)
$this -> logger -> warning ( 'AppManager::checkAppForUser - can\'t decode group IDs listed in app\'s enabled config key: ' . print_r ( $enabled , true ) . ' - JSON error (' . $jsonError . ') ' . $jsonErrorMsg );
2015-12-10 08:53:34 -05:00
return false ;
}
2015-02-02 08:47:29 -05:00
$userGroups = $this -> groupManager -> getUserGroupIds ( $user );
foreach ( $userGroups as $groupId ) {
2017-03-20 05:11:49 -04:00
if ( in_array ( $groupId , $groupIds , true )) {
2015-02-02 08:47:29 -05:00
return true ;
2014-11-07 08:26:12 -05:00
}
}
return false ;
}
}
2019-06-25 09:20:06 -04:00
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 ();
2024-06-09 09:36:32 -04:00
$jsonErrorMsg = json_last_error_msg ();
// this really should never happen (if it does, the admin should check the `enabled` key value via `occ config:list` because it's bogus for some reason)
$this -> logger -> warning ( 'AppManager::checkAppForGroups - can\'t decode group IDs listed in app\'s enabled config key: ' . print_r ( $enabled , true ) . ' - JSON error (' . $jsonError . ') ' . $jsonErrorMsg );
2019-06-25 09:20:06 -04:00
return false ;
}
return in_array ( $group -> getGID (), $groupIds );
}
}
2014-11-07 08:26:12 -05:00
/**
2018-02-22 10:00:26 -05:00
* 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 .
2014-11-07 08:26:12 -05:00
*
* @ param string $appId
2022-12-08 04:55:19 -05:00
* @ param IGroup [] | String [] $groups
2014-11-07 08:26:12 -05:00
* @ return bool
*/
public function isInstalled ( $appId ) {
2015-02-02 08:47:29 -05:00
$installedApps = $this -> getInstalledAppsValues ();
2014-11-07 08:26:12 -05:00
return isset ( $installedApps [ $appId ]);
}
2019-09-05 06:55:24 -04:00
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 );
}
}
2023-02-07 10:56:04 -05:00
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 ;
}
2024-06-27 06:25:20 -04:00
$eventLogger = \OC :: $server -> get ( IEventLogger :: class );
$eventLogger -> start ( " bootstrap:load_app: $app " , " Load app: $app " );
2023-02-07 10:56:04 -05:00
// 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' );
if ( $isBootable && $hasAppPhpFile ) {
2023-02-07 11:01:13 -05:00
$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.' , [
2023-02-07 10:56:04 -05:00
'app' => $app ,
]);
} elseif ( $hasAppPhpFile ) {
$eventLogger -> start ( " bootstrap:load_app: $app :app.php " , " Load legacy app.php app $app " );
2023-02-07 11:01:13 -05:00
$this -> logger -> debug ( '/appinfo/app.php is deprecated, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.' , [
2023-02-07 10:56:04 -05:00
'app' => $app ,
]);
try {
self :: requireAppFile ( $appPath );
} catch ( \Throwable $ex ) {
if ( $ex instanceof ServerNotAvailableException ) {
throw $ex ;
}
2023-02-08 05:46:07 -05:00
if ( ! $this -> isShipped ( $app ) && ! $this -> isType ( $app , [ 'authentication' ])) {
2023-02-07 11:01:13 -05:00
$this -> logger -> error ( " App $app threw an error during app.php load and will be disabled: " . $ex -> getMessage (), [
'exception' => $ex ,
2023-02-07 10:56:04 -05:00
]);
// Only disable apps which are not shipped and that are not authentication apps
2023-02-07 11:01:13 -05:00
$this -> disableApp ( $app , true );
2023-02-07 10:56:04 -05:00
} else {
2023-02-07 11:01:13 -05:00
$this -> logger -> error ( " App $app threw an error during app.php load: " . $ex -> getMessage (), [
'exception' => $ex ,
2023-02-07 10:56:04 -05:00
]);
}
}
$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 " );
2023-02-08 03:22:22 -05:00
$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 );
}
2023-02-07 10:56:04 -05:00
}
2023-02-08 03:22:22 -05:00
if ( ! empty ( $info [ 'activity' ][ 'settings' ])) {
foreach ( $info [ 'activity' ][ 'settings' ] as $setting ) {
$activityManager -> registerSetting ( $setting );
}
2023-02-07 10:56:04 -05:00
}
2023-02-08 03:22:22 -05:00
if ( ! empty ( $info [ 'activity' ][ 'providers' ])) {
foreach ( $info [ 'activity' ][ 'providers' ] as $provider ) {
$activityManager -> registerProvider ( $provider );
}
2023-02-07 10:56:04 -05:00
}
}
2023-02-08 03:22:22 -05:00
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 );
}
2023-02-07 10:56:04 -05:00
}
2023-02-08 03:22:22 -05:00
if ( ! empty ( $info [ 'settings' ][ 'admin-section' ])) {
foreach ( $info [ 'settings' ][ 'admin-section' ] as $section ) {
$settingsManager -> registerSection ( 'admin' , $section );
}
2023-02-07 10:56:04 -05:00
}
2023-02-08 03:22:22 -05:00
if ( ! empty ( $info [ 'settings' ][ 'personal' ])) {
foreach ( $info [ 'settings' ][ 'personal' ] as $setting ) {
$settingsManager -> registerSetting ( 'personal' , $setting );
}
2023-02-07 10:56:04 -05:00
}
2023-02-08 03:22:22 -05:00
if ( ! empty ( $info [ 'settings' ][ 'personal-section' ])) {
foreach ( $info [ 'settings' ][ 'personal-section' ] as $section ) {
$settingsManager -> registerSection ( 'personal' , $section );
}
2023-02-07 10:56:04 -05:00
}
}
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' ];
2023-02-08 03:22:22 -05:00
$collaboratorSearch = null ;
$autoCompleteManager = null ;
2023-02-07 10:56:04 -05:00
foreach ( $plugins as $plugin ) {
if ( $plugin [ '@attributes' ][ 'type' ] === 'collaborator-search' ) {
$pluginInfo = [
'shareType' => $plugin [ '@attributes' ][ 'share-type' ],
'class' => $plugin [ '@value' ],
];
2023-02-08 03:22:22 -05:00
$collaboratorSearch ? ? = \OC :: $server -> get ( ICollaboratorSearch :: class );
$collaboratorSearch -> registerPlugin ( $pluginInfo );
2023-02-07 10:56:04 -05:00
} elseif ( $plugin [ '@attributes' ][ 'type' ] === 'autocomplete-sort' ) {
2023-02-08 03:22:22 -05:00
$autoCompleteManager ? ? = \OC :: $server -> get ( IAutoCompleteManager :: class );
$autoCompleteManager -> registerSorter ( $plugin [ '@value' ]);
2023-02-07 10:56:04 -05:00
}
}
}
$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' ;
}
2014-11-07 08:26:12 -05:00
/**
* Enable an app for every user
*
* @ param string $appId
2019-09-05 06:55:24 -04:00
* @ param bool $forceEnable
2017-03-20 05:02:05 -04:00
* @ throws AppPathNotFoundException
2014-11-07 08:26:12 -05:00
*/
2019-09-05 06:55:24 -04:00
public function enableApp ( string $appId , bool $forceEnable = false ) : void {
2017-03-20 05:02:05 -04:00
// Check if app exists
$this -> getAppPath ( $appId );
2019-09-05 06:55:24 -04:00
if ( $forceEnable ) {
$this -> ignoreNextcloudRequirementForApp ( $appId );
}
2015-02-16 10:44:35 -05:00
$this -> installedAppsCache [ $appId ] = 'yes' ;
2024-03-06 08:17:14 -05:00
$this -> getAppConfig () -> setValue ( $appId , 'enabled' , 'yes' );
2022-12-08 04:55:19 -05:00
$this -> dispatcher -> dispatchTyped ( new AppEnableEvent ( $appId ));
2023-07-25 05:40:42 -04:00
$this -> dispatcher -> dispatch ( ManagerEvent :: EVENT_APP_ENABLE , new ManagerEvent (
2016-02-08 20:51:12 -05:00
ManagerEvent :: EVENT_APP_ENABLE , $appId
));
2015-04-01 09:37:22 -04:00
$this -> clearAppsCache ();
2014-11-07 08:26:12 -05:00
}
2017-01-04 04:40:14 -05:00
/**
* 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 );
}
2014-11-07 08:26:12 -05:00
/**
* Enable an app only for specific groups
*
* @ param string $appId
2022-12-08 04:55:19 -05:00
* @ param IGroup [] $groups
2019-09-05 06:55:24 -04:00
* @ param bool $forceEnable
2019-01-26 16:31:45 -05:00
* @ throws \InvalidArgumentException if app can ' t be enabled for groups
* @ throws AppPathNotFoundException
2014-11-07 08:26:12 -05:00
*/
2019-09-05 06:55:24 -04:00
public function enableAppForGroups ( string $appId , array $groups , bool $forceEnable = false ) : void {
2019-01-26 16:31:45 -05:00
// Check if app exists
$this -> getAppPath ( $appId );
2016-01-14 09:23:07 -05:00
$info = $this -> getAppInfo ( $appId );
2019-01-26 16:31:45 -05:00
if ( ! empty ( $info [ 'types' ]) && $this -> hasProtectedAppType ( $info [ 'types' ])) {
throw new \InvalidArgumentException ( " $appId can't be enabled for groups. " );
2016-01-14 09:23:07 -05:00
}
2019-09-05 06:55:24 -04:00
if ( $forceEnable ) {
$this -> ignoreNextcloudRequirementForApp ( $appId );
}
2022-12-08 04:55:19 -05:00
/** @var string[] $groupIds */
2014-11-07 08:26:12 -05:00
$groupIds = array_map ( function ( $group ) {
2022-12-08 04:55:19 -05:00
/** @var IGroup $group */
2019-06-25 09:20:06 -04:00
return ( $group instanceof IGroup )
? $group -> getGID ()
: $group ;
2014-11-07 08:26:12 -05:00
}, $groups );
2019-06-25 09:20:06 -04:00
2015-02-16 10:44:35 -05:00
$this -> installedAppsCache [ $appId ] = json_encode ( $groupIds );
2024-03-06 08:17:14 -05:00
$this -> getAppConfig () -> setValue ( $appId , 'enabled' , json_encode ( $groupIds ));
2022-12-08 04:55:19 -05:00
$this -> dispatcher -> dispatchTyped ( new AppEnableEvent ( $appId , $groupIds ));
2023-07-25 05:40:42 -04:00
$this -> dispatcher -> dispatch ( ManagerEvent :: EVENT_APP_ENABLE_FOR_GROUPS , new ManagerEvent (
2016-02-08 20:51:12 -05:00
ManagerEvent :: EVENT_APP_ENABLE_FOR_GROUPS , $appId , $groups
));
2015-04-01 09:37:22 -04:00
$this -> clearAppsCache ();
2014-11-07 08:26:12 -05:00
}
/**
* Disable an app for every user
*
* @ param string $appId
2019-07-23 04:28:47 -04:00
* @ param bool $automaticDisabled
2015-02-02 18:39:01 -05:00
* @ throws \Exception if app can ' t be disabled
2014-11-07 08:26:12 -05:00
*/
2019-07-23 04:28:47 -04:00
public function disableApp ( $appId , $automaticDisabled = false ) {
2015-10-16 10:30:29 -04:00
if ( $this -> isAlwaysEnabled ( $appId )) {
throw new \Exception ( " $appId can't be disabled. " );
2015-02-02 18:39:01 -05:00
}
2019-07-23 04:28:47 -04:00
if ( $automaticDisabled ) {
2024-03-06 08:17:14 -05:00
$previousSetting = $this -> getAppConfig () -> getValue ( $appId , 'enabled' , 'yes' );
2021-10-01 10:40:25 -04:00
if ( $previousSetting !== 'yes' && $previousSetting !== 'no' ) {
$previousSetting = json_decode ( $previousSetting , true );
}
$this -> autoDisabledApps [ $appId ] = $previousSetting ;
2019-07-23 04:28:47 -04:00
}
2015-02-16 10:44:35 -05:00
unset ( $this -> installedAppsCache [ $appId ]);
2024-03-06 08:17:14 -05:00
$this -> getAppConfig () -> setValue ( $appId , 'enabled' , 'no' );
2017-07-22 07:32:56 -04:00
// run uninstall steps
$appData = $this -> getAppInfo ( $appId );
if ( ! is_null ( $appData )) {
\OC_App :: executeRepairSteps ( $appId , $appData [ 'repair-steps' ][ 'uninstall' ]);
}
2022-12-08 04:55:19 -05:00
$this -> dispatcher -> dispatchTyped ( new AppDisableEvent ( $appId ));
2023-07-25 05:40:42 -04:00
$this -> dispatcher -> dispatch ( ManagerEvent :: EVENT_APP_DISABLE , new ManagerEvent (
2016-02-08 20:51:12 -05:00
ManagerEvent :: EVENT_APP_DISABLE , $appId
));
2015-04-01 09:37:22 -04:00
$this -> clearAppsCache ();
}
2016-11-17 06:30:52 -05:00
/**
* Get the directory for the given app .
*
* @ throws AppPathNotFoundException if app folder can ' t be found
*/
2024-09-12 10:17:19 -04:00
public function getAppPath ( string $appId ) : string {
2016-11-17 06:30:52 -05:00
$appPath = \OC_App :: getAppPath ( $appId );
if ( $appPath === false ) {
throw new AppPathNotFoundException ( 'Could not find path for ' . $appId );
}
return $appPath ;
}
2019-08-25 09:27:04 -04:00
/**
* Get the web path for the given app .
*
* @ param string $appId
* @ return string
* @ throws AppPathNotFoundException if app path can ' t be found
*/
2019-09-05 12:35:40 -04:00
public function getAppWebPath ( string $appId ) : string {
2019-08-25 09:27:04 -04:00
$appWebPath = \OC_App :: getAppWebPath ( $appId );
if ( $appWebPath === false ) {
throw new AppPathNotFoundException ( 'Could not find web path for ' . $appId );
}
return $appWebPath ;
}
2015-04-01 09:37:22 -04:00
/**
* Clear the cached list of apps when enabling / disabling an app
*/
2015-03-30 09:58:20 -04:00
public function clearAppsCache () {
2018-05-30 10:06:18 -04:00
$this -> appInfos = [];
2014-11-07 08:26:12 -05:00
}
2015-07-07 06:12:54 -04:00
/**
* Returns a list of apps that need upgrade
*
2017-03-20 05:11:49 -04:00
* @ param string $version Nextcloud version as array of version components
2015-07-07 06:12:54 -04:00
* @ return array list of app info from apps that need an upgrade
*
* @ internal
*/
2017-03-20 05:11:49 -04:00
public function getAppsNeedingUpgrade ( $version ) {
2015-07-07 06:12:54 -04:00
$appsToUpgrade = [];
$apps = $this -> getInstalledApps ();
foreach ( $apps as $appId ) {
$appInfo = $this -> getAppInfo ( $appId );
2024-03-06 08:17:14 -05:00
$appDbVersion = $this -> getAppConfig () -> getValue ( $appId , 'installed_version' );
2015-07-07 06:12:54 -04:00
if ( $appDbVersion
&& isset ( $appInfo [ 'version' ])
&& version_compare ( $appInfo [ 'version' ], $appDbVersion , '>' )
2017-03-20 05:11:49 -04:00
&& \OC_App :: isAppCompatible ( $version , $appInfo )
2015-07-07 06:12:54 -04:00
) {
$appsToUpgrade [] = $appInfo ;
}
}
return $appsToUpgrade ;
}
/**
* Returns the app information from " appinfo/info.xml " .
*
2024-01-23 03:55:06 -05:00
* @ param string | null $lang
2018-03-28 05:12:56 -04:00
* @ return array | null app info
2015-07-07 06:12:54 -04:00
*/
2018-01-29 07:09:32 -05:00
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 ;
}
2018-02-17 09:25:24 -05:00
public function getAppVersion ( string $appId , bool $useCache = true ) : string {
2018-01-29 07:09:32 -05:00
if ( ! $useCache || ! isset ( $this -> appVersions [ $appId ])) {
2019-07-18 05:33:58 -04:00
$appInfo = $this -> getAppInfo ( $appId );
2018-02-17 09:25:24 -05:00
$this -> appVersions [ $appId ] = ( $appInfo !== null && isset ( $appInfo [ 'version' ])) ? $appInfo [ 'version' ] : '0' ;
2015-07-07 06:12:54 -04:00
}
2018-01-29 07:09:32 -05:00
return $this -> appVersions [ $appId ];
2015-07-07 06:12:54 -04:00
}
/**
* Returns a list of apps incompatible with the given version
*
2017-03-20 05:11:49 -04:00
* @ param string $version Nextcloud version as array of version components
2015-07-07 06:12:54 -04:00
*
* @ return array list of app info from incompatible apps
*
* @ internal
*/
2018-02-21 07:00:41 -05:00
public function getIncompatibleApps ( string $version ) : array {
2015-07-07 06:12:54 -04:00
$apps = $this -> getInstalledApps ();
2020-03-26 04:30:18 -04:00
$incompatibleApps = [];
2015-07-07 06:12:54 -04:00
foreach ( $apps as $appId ) {
$info = $this -> getAppInfo ( $appId );
2018-03-28 05:12:56 -04:00
if ( $info === null ) {
2021-05-25 06:33:20 -04:00
$incompatibleApps [] = [ 'id' => $appId , 'name' => $appId ];
2020-04-10 04:35:09 -04:00
} elseif ( ! \OC_App :: isAppCompatible ( $version , $info )) {
2015-07-07 06:12:54 -04:00
$incompatibleApps [] = $info ;
}
}
return $incompatibleApps ;
}
2015-10-16 10:38:43 -04:00
/**
* @ inheritdoc
2018-02-07 10:03:21 -05:00
* In case you change this method , also change \OC\App\CodeChecker\InfoChecker :: isShipped ()
2015-10-16 10:38:43 -04:00
*/
2015-10-16 10:30:29 -04:00
public function isShipped ( $appId ) {
$this -> loadShippedJson ();
2017-03-20 05:11:49 -04:00
return in_array ( $appId , $this -> shippedApps , true );
2015-10-16 10:30:29 -04:00
}
2023-02-08 02:59:35 -05:00
private function isAlwaysEnabled ( string $appId ) : bool {
2015-10-16 10:38:43 -04:00
$alwaysEnabled = $this -> getAlwaysEnabledApps ();
2017-03-20 05:11:49 -04:00
return in_array ( $appId , $alwaysEnabled , true );
2015-10-16 10:30:29 -04:00
}
2018-02-07 10:03:21 -05:00
/**
* In case you change this method , also change \OC\App\CodeChecker\InfoChecker :: loadShippedJson ()
* @ throws \Exception
*/
2023-02-08 02:59:35 -05:00
private function loadShippedJson () : void {
2017-03-20 05:11:49 -04:00
if ( $this -> shippedApps === null ) {
2015-10-16 10:30:29 -04:00
$shippedJson = \OC :: $SERVERROOT . '/core/shipped.json' ;
2015-10-26 04:52:47 -04:00
if ( ! file_exists ( $shippedJson )) {
throw new \Exception ( " File not found: $shippedJson " );
2015-10-16 10:30:29 -04:00
}
2015-10-26 04:52:47 -04:00
$content = json_decode ( file_get_contents ( $shippedJson ), true );
$this -> shippedApps = $content [ 'shippedApps' ];
$this -> alwaysEnabled = $content [ 'alwaysEnabled' ];
2022-09-15 05:15:25 -04:00
$this -> defaultEnabled = $content [ 'defaultEnabled' ];
2015-10-16 10:30:29 -04:00
}
}
2015-10-16 10:38:43 -04:00
/**
* @ inheritdoc
*/
public function getAlwaysEnabledApps () {
$this -> loadShippedJson ();
return $this -> alwaysEnabled ;
}
2022-09-15 05:15:25 -04:00
/**
* @ inheritdoc
*/
public function isDefaultEnabled ( string $appId ) : bool {
return ( in_array ( $appId , $this -> getDefaultEnabledApps ()));
}
/**
* @ inheritdoc
*/
2024-01-23 03:55:06 -05:00
public function getDefaultEnabledApps () : array {
2022-09-15 05:15:25 -04:00
$this -> loadShippedJson ();
return $this -> defaultEnabled ;
}
2023-03-29 16:36:45 -04:00
2024-08-27 06:14:09 -04:00
/**
* @ inheritdoc
*/
2023-10-10 08:24:34 -04:00
public function getDefaultAppForUser ( ? IUser $user = null , bool $withFallbacks = true ) : string {
2024-08-27 06:14:09 -04:00
$id = $this -> getNavigationManager () -> getDefaultEntryIdForUser ( $user , $withFallbacks );
$entry = $this -> getNavigationManager () -> get ( $id );
return ( string ) $entry [ 'app' ];
2023-03-29 16:36:45 -04:00
}
2023-09-25 08:08:34 -04:00
2024-08-27 06:14:09 -04:00
/**
* @ inheritdoc
*/
2023-09-25 08:08:34 -04:00
public function getDefaultApps () : array {
2024-08-27 06:14:09 -04:00
$ids = $this -> getNavigationManager () -> getDefaultEntryIds ();
return array_values ( array_unique ( array_map ( function ( string $id ) {
$entry = $this -> getNavigationManager () -> get ( $id );
return ( string ) $entry [ 'app' ];
}, $ids )));
2023-09-25 08:08:34 -04:00
}
2024-08-27 06:14:09 -04:00
/**
* @ inheritdoc
*/
2023-09-25 08:08:34 -04:00
public function setDefaultApps ( array $defaultApps ) : void {
2024-08-27 06:14:09 -04:00
$entries = $this -> getNavigationManager () -> getAll ();
$ids = [];
foreach ( $defaultApps as $defaultApp ) {
foreach ( $entries as $entry ) {
if (( string ) $entry [ 'app' ] === $defaultApp ) {
$ids [] = ( string ) $entry [ 'id' ];
break ;
}
2023-09-25 08:08:34 -04:00
}
}
2024-08-27 06:14:09 -04:00
$this -> getNavigationManager () -> setDefaultEntryIds ( $ids );
2023-09-25 08:08:34 -04:00
}
2024-07-13 10:51:16 -04:00
public function isBackendRequired ( string $backend ) : bool {
foreach ( $this -> appInfos as $appInfo ) {
foreach ( $appInfo [ 'dependencies' ][ 'backend' ] as $appBackend ) {
if ( $backend === $appBackend ) {
return true ;
}
}
}
return false ;
}
2024-09-12 10:17:19 -04:00
public function cleanAppId ( string $app ) : string {
// FIXME should list allowed characters instead
return str_replace ([ '<' , '>' , '"' , " ' " , '\0' , '/' , '\\' , '..' ], '' , $app );
}
2014-11-07 08:26:12 -05:00
}