2014-11-07 08:26:12 -05:00
< ? php
/**
2016-07-21 11:07:57 -04:00
* @ copyright Copyright ( c ) 2016 , ownCloud , Inc .
*
2016-05-26 13:56:05 -04:00
* @ author Arthur Schiwon < blizzz @ arthur - schiwon . de >
2016-07-21 11:07:57 -04:00
* @ author Bjoern Schiessle < bjoern @ schiessle . org >
2017-11-07 07:47:42 -05:00
* @ author Christoph Schaefer " christophł@wolkesicher.de "
2020-03-31 04:49:10 -04:00
* @ author Christoph Wurst < christoph @ winzerhof - wurst . at >
2019-12-03 13:57:53 -05:00
* @ author Daniel Kesselberg < mail @ danielkesselberg . de >
* @ author Daniel Rudolf < github . com @ daniel - rudolf . de >
* @ author Greta Doci < gretadoci @ gmail . com >
2016-07-21 11:07:57 -04:00
* @ author Joas Schilling < coding @ schilljs . com >
2017-11-06 09:56:42 -05:00
* @ author Julius Haertl < jus @ bitgrid . net >
2019-12-03 13:57:53 -05:00
* @ author Julius Härtl < jus @ bitgrid . net >
2016-05-26 13:56:05 -04:00
* @ author Lukas Reschke < lukas @ statuscode . ch >
2022-09-15 05:15:25 -04:00
* @ author Maxence Lange < maxence @ artificial - owl . com >
2015-03-26 06:44:34 -04:00
* @ author Morris Jobke < hey @ morrisjobke . de >
2016-07-21 12:13:36 -04:00
* @ author Robin Appelman < robin @ icewind . nl >
2019-12-03 13:57:53 -05:00
* @ author Roeland Jago Douma < roeland @ famdouma . nl >
2016-01-12 09:02:16 -05:00
* @ author Thomas Müller < thomas . mueller @ tmit . eu >
2019-12-03 13:57:53 -05:00
* @ author Tobia De Koninck < tobia @ ledfan . be >
2020-12-16 08:54:15 -05:00
* @ author Vincent Petry < vincent @ nextcloud . com >
2015-03-26 06:44:34 -04:00
*
* @ 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 ,
2019-12-03 13:57:53 -05:00
* along with this program . If not , see < http :// www . gnu . org / licenses />
2015-03-26 06:44:34 -04:00
*
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 ;
2022-12-08 04:55:19 -05:00
use OCP\EventDispatcher\IEventDispatcher ;
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 ;
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 ;
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 ;
2016-02-08 20:51:12 -05:00
use Symfony\Component\EventDispatcher\EventDispatcherInterface ;
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' ,
];
2023-02-08 02:59:35 -05:00
private IUserSession $userSession ;
private IConfig $config ;
private AppConfig $appConfig ;
private IGroupManager $groupManager ;
private ICacheFactory $memCacheFactory ;
private EventDispatcherInterface $legacyDispatcher ;
2022-12-08 04:55:19 -05:00
private IEventDispatcher $dispatcher ;
2023-02-08 02:59:35 -05:00
private LoggerInterface $logger ;
2019-07-15 16:19:11 -04:00
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 = [];
2015-04-02 02:28:42 -04:00
public function __construct ( IUserSession $userSession ,
2019-09-05 06:55:24 -04:00
IConfig $config ,
2018-01-17 15:10:40 -05:00
AppConfig $appConfig ,
2015-04-02 02:28:42 -04:00
IGroupManager $groupManager ,
2016-02-08 20:51:12 -05:00
ICacheFactory $memCacheFactory ,
2022-12-08 04:55:19 -05:00
EventDispatcherInterface $legacyDispatcher ,
IEventDispatcher $dispatcher ,
2021-04-16 08:26:43 -04:00
LoggerInterface $logger ) {
2014-11-07 08:26:12 -05:00
$this -> userSession = $userSession ;
2019-09-05 06:55:24 -04:00
$this -> config = $config ;
2014-11-07 08:26:12 -05:00
$this -> appConfig = $appConfig ;
$this -> groupManager = $groupManager ;
2015-04-01 09:37:22 -04:00
$this -> memCacheFactory = $memCacheFactory ;
2022-12-08 04:55:19 -05:00
$this -> legacyDispatcher = $legacyDispatcher ;
2016-02-08 20:51:12 -05:00
$this -> dispatcher = $dispatcher ;
2019-07-15 16:19:11 -04:00
$this -> logger = $logger ;
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 ) {
$values = $this -> appConfig -> getValues ( false , 'enabled' );
2015-11-19 03:11:14 -05:00
$alwaysEnabledApps = $this -> getAlwaysEnabledApps ();
2020-04-10 08:19:56 -04:00
foreach ( $alwaysEnabledApps as $appId ) {
2015-11-19 03:11:14 -05:00
$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 ());
}
/**
* 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 ) {
2023-02-08 07:29:57 -05:00
$this -> appTypes = $this -> appConfig -> 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
* @ param \OCP\IUser $user ( optional ) if not defined , the currently logged in user will be used
* @ 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 {
2020-04-10 08:19:56 -04:00
if ( empty ( $enabled )) {
2016-01-07 13:49:40 -05:00
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 ();
2021-04-16 08:26:43 -04:00
$this -> logger -> warning ( 'AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r ( $enabled , true ) . ' - json error code: ' . $jsonError );
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 ();
2021-04-16 08:26:43 -04:00
$this -> logger -> warning ( 'AppManger::checkAppForUser - can\'t decode group IDs: ' . print_r ( $enabled , true ) . ' - json error code: ' . $jsonError );
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 ;
}
$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' );
2023-02-08 03:22:22 -05:00
$eventLogger = \OC :: $server -> get ( IEventLogger :: class );
$eventLogger -> start ( 'bootstrap:load_app_' . $app , 'Load app: ' . $app );
2023-02-07 10:56:04 -05:00
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' ;
2014-11-07 08:26:12 -05:00
$this -> appConfig -> setValue ( $appId , 'enabled' , 'yes' );
2022-12-08 04:55:19 -05:00
$this -> dispatcher -> dispatchTyped ( new AppEnableEvent ( $appId ));
$this -> legacyDispatcher -> 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 );
2014-11-07 08:26:12 -05:00
$this -> appConfig -> setValue ( $appId , 'enabled' , json_encode ( $groupIds ));
2022-12-08 04:55:19 -05:00
$this -> dispatcher -> dispatchTyped ( new AppEnableEvent ( $appId , $groupIds ));
$this -> legacyDispatcher -> 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 ) {
2021-10-01 10:40:25 -04:00
$previousSetting = $this -> appConfig -> getValue ( $appId , 'enabled' , 'yes' );
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 ]);
2014-11-07 08:26:12 -05:00
$this -> appConfig -> 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 ));
$this -> legacyDispatcher -> 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 .
*
* @ param string $appId
* @ return string
* @ throws AppPathNotFoundException if app folder can ' t be found
*/
public function getAppPath ( $appId ) {
$appPath = \OC_App :: getAppPath ( $appId );
2020-04-10 08:19:56 -04:00
if ( $appPath === false ) {
2016-11-17 06:30:52 -05:00
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 );
2020-04-10 08:19:56 -04:00
if ( $appWebPath === false ) {
2019-08-25 09:27:04 -04:00
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 () {
2017-12-15 05:31:13 -05:00
$settingsMemCache = $this -> memCacheFactory -> createDistributed ( 'settings' );
2015-04-01 09:37:22 -04:00
$settingsMemCache -> clear ( 'listApps' );
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 );
$appDbVersion = $this -> appConfig -> getValue ( $appId , 'installed_version' );
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 " .
*
* @ param string $appId app id
*
2018-01-29 07:09:32 -05:00
* @ param bool $path
* @ param 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 {
2020-04-10 08:19:56 -04: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
*/
public function getDefaultEnabledApps () : array {
$this -> loadShippedJson ();
return $this -> defaultEnabled ;
}
2023-03-29 16:36:45 -04:00
public function getDefaultAppForUser ( ? IUser $user = null ) : string {
// Set fallback to always-enabled files app
$appId = 'files' ;
$defaultApps = explode ( ',' , $this -> config -> getSystemValueString ( 'defaultapp' , 'dashboard,files' ));
$user ? ? = $this -> userSession -> getUser ();
if ( $user !== null ) {
$userDefaultApps = explode ( ',' , $this -> config -> getUserValue ( $user -> getUID (), 'core' , 'defaultapp' ));
$defaultApps = array_filter ( array_merge ( $userDefaultApps , $defaultApps ));
}
// 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 ;
}
2014-11-07 08:26:12 -05:00
}