2014-11-07 08:26:12 -05:00
< ? php
2025-06-30 09:04:05 -04:00
2014-11-07 08:26:12 -05:00
/**
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 ;
2025-05-14 14:10:34 -04:00
use OC\Config\ConfigManager ;
2025-07-10 10:01:09 -04:00
use OC\DB\MigrationService ;
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 ;
2025-07-10 10:01:09 -04:00
use OCP\App\Events\AppUpdateEvent ;
2014-11-07 08:26:12 -05:00
use OCP\App\IAppManager ;
2016-02-08 20:51:12 -05:00
use OCP\App\ManagerEvent ;
2025-07-10 10:54:10 -04:00
use OCP\BackgroundJob\IJobList ;
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 ;
2025-06-02 11:45:35 -04:00
use OCP\IAppConfig ;
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 ;
2025-06-04 09:33:25 -04:00
use OCP\Server ;
2025-01-24 06:52:33 -05:00
use OCP\ServerVersion ;
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 */
2024-12-04 10:04:19 -05:00
private array $enabledAppsCache = [];
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 ,
2025-01-24 06:52:33 -05:00
private ServerVersion $serverVersion ,
2025-06-04 15:38:43 -04:00
private ConfigManager $configManager ,
2025-07-31 11:44:44 -04:00
private DependencyAnalyzer $dependencyAnalyzer ,
2023-07-11 03:18:17 -04:00
) {
2014-11-07 08:26:12 -05:00
}
2024-08-27 06:14:09 -04:00
private function getNavigationManager () : INavigationManager {
if ( $this -> navigationManager === null ) {
2025-06-04 09:33:25 -04:00
$this -> navigationManager = Server :: get ( INavigationManager :: class );
2024-08-27 06:14:09 -04:00
}
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' );
}
2025-06-04 09:33:25 -04:00
$this -> appConfig = Server :: get ( AppConfig :: class );
2024-03-06 08:17:14 -05:00
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' );
}
2025-06-04 09:33:25 -04:00
$this -> urlGenerator = Server :: get ( IURLGenerator :: class );
2024-04-22 09:50:06 -04:00
return $this -> urlGenerator ;
}
2014-11-07 08:26:12 -05:00
/**
2024-12-04 10:04:19 -05:00
* For all enabled apps , return the value of their 'enabled' config key .
*
* @ return array < string , string > appId => enabled ( may be 'yes' , or a json encoded list of group ids )
2014-11-07 08:26:12 -05:00
*/
2024-12-04 10:04:19 -05:00
private function getEnabledAppsValues () : array {
if ( ! $this -> enabledAppsCache ) {
2025-06-02 12:05:40 -04:00
/** @var array<string,string> */
2025-06-02 11:45:35 -04:00
$values = $this -> getAppConfig () -> searchValues ( 'enabled' , false , IAppConfig :: VALUE_STRING );
2015-11-19 03:11:14 -05:00
$alwaysEnabledApps = $this -> getAlwaysEnabledApps ();
foreach ( $alwaysEnabledApps as $appId ) {
$values [ $appId ] = 'yes' ;
}
2024-12-04 10:04:19 -05:00
$this -> enabledAppsCache = array_filter ( $values , function ( $value ) {
2014-11-07 08:26:12 -05:00
return $value !== 'no' ;
});
2024-12-04 10:04:19 -05:00
ksort ( $this -> enabledAppsCache );
2014-11-07 08:26:12 -05:00
}
2024-12-04 10:04:19 -05:00
return $this -> enabledAppsCache ;
2014-11-07 08:26:12 -05:00
}
2015-02-02 08:47:29 -05:00
/**
2024-12-04 10:00:20 -05:00
* Deprecated alias
2015-02-02 08:47:29 -05:00
*
* @ return string []
*/
public function getInstalledApps () {
2024-12-04 10:00:20 -05:00
return $this -> getEnabledApps ();
}
/**
* List all enabled apps , either for everyone or for some groups
*
* @ return list < string >
*/
public function getEnabledApps () : array {
2024-12-04 10:04:19 -05:00
return array_keys ( $this -> getEnabledAppsValues ());
2015-02-02 08:47:29 -05:00
}
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
2025-06-18 10:03:20 -04:00
* @ return list < string >
2015-02-02 08:47:29 -05:00
*/
2015-02-05 09:11:07 -05:00
public function getEnabledAppsForUser ( IUser $user ) {
2024-12-04 10:04:19 -05:00
$apps = $this -> getEnabledAppsValues ();
2015-02-02 08:47:29 -05:00
$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
public function getEnabledAppsForGroup ( IGroup $group ) : array {
2024-12-04 10:04:19 -05:00
$apps = $this -> getEnabledAppsValues ();
2019-06-25 09:20:06 -04:00
$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 )) {
2025-07-31 09:14:48 -04:00
try {
$path = $this -> getAppPath ( $app );
2023-02-08 06:35:57 -05:00
\OC_App :: registerAutoloading ( $app , $path );
2025-07-31 09:14:48 -04:00
} catch ( AppPathNotFoundException $e ) {
$this -> logger -> info ( 'Error during app loading: ' . $e -> getMessage (), [
'exception' => $e ,
'app' => $app ,
]);
2023-02-08 06:35:57 -05:00
}
}
}
2025-04-14 08:30:00 -04:00
// prevent app loading from printing output
2023-02-08 06:35:57 -05:00
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
public function getAppRestriction ( string $appId ) : array {
2024-12-04 10:04:19 -05:00
$values = $this -> getEnabledAppsValues ();
2019-06-25 09:20:06 -04:00
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 ();
}
2024-12-04 10:04:19 -05:00
$enabledAppsValues = $this -> getEnabledAppsValues ();
if ( isset ( $enabledAppsValues [ $appId ])) {
return $this -> checkAppForUser ( $enabledAppsValues [ $appId ], $user );
2015-02-02 08:47:29 -05:00
} 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
*/
2024-12-04 09:41:17 -05:00
public function isInstalled ( $appId ) : bool {
return $this -> isEnabledForAnyone ( $appId );
}
public function isEnabledForAnyone ( string $appId ) : bool {
2024-12-04 10:04:19 -05:00
$enabledAppsValues = $this -> getEnabledAppsValues ();
return isset ( $enabledAppsValues [ $appId ]);
2014-11-07 08:26:12 -05:00
}
2024-10-23 06:55:28 -04:00
/**
* Overwrite the `max-version` requirement for this app .
*/
public function overwriteNextcloudRequirement ( string $appId ) : void {
2019-09-05 06:55:24 -04:00
$ignoreMaxApps = $this -> config -> getSystemValue ( 'app_install_overwrite' , []);
if ( ! in_array ( $appId , $ignoreMaxApps , true )) {
$ignoreMaxApps [] = $appId ;
}
2024-10-23 06:55:28 -04:00
$this -> config -> setSystemValue ( 'app_install_overwrite' , $ignoreMaxApps );
}
/**
* Remove the `max-version` overwrite for this app .
* This means this app now again can not be enabled if the `max-version` is smaller than the current Nextcloud version .
*/
public function removeOverwriteNextcloudRequirement ( string $appId ) : void {
$ignoreMaxApps = $this -> config -> getSystemValue ( 'app_install_overwrite' , []);
$ignoreMaxApps = array_filter ( $ignoreMaxApps , fn ( string $id ) => $id !== $appId );
$this -> config -> setSystemValue ( 'app_install_overwrite' , $ignoreMaxApps );
2019-09-05 06:55:24 -04:00
}
2023-02-07 10:56:04 -05:00
public function loadApp ( string $app ) : void {
if ( isset ( $this -> loadedApps [ $app ])) {
return ;
}
$this -> loadedApps [ $app ] = true ;
2025-07-31 09:14:48 -04:00
try {
$appPath = $this -> getAppPath ( $app );
} catch ( AppPathNotFoundException $e ) {
$this -> logger -> info ( 'Error during app loading: ' . $e -> getMessage (), [
'exception' => $e ,
'app' => $app ,
]);
2023-02-07 10:56:04 -05:00
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 );
2025-04-14 08:30:00 -04:00
if ( is_file ( $appPath . '/appinfo/app.php' )) {
$this -> logger -> error ( '/appinfo/app.php is not supported anymore, use \OCP\AppFramework\Bootstrap\IBootstrap on the application class instead.' , [
2023-02-07 10:56:04 -05:00
'app' => $app ,
]);
}
2025-06-04 09:33:25 -04:00
$coordinator = Server :: get ( Coordinator :: class );
2023-02-07 10:56:04 -05:00
$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' ])) {
2025-09-23 05:16:33 -04:00
$settingsManager = \OCP\Server :: get ( ISettingsManager :: class );
2023-02-08 03:22:22 -05:00
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
}
2025-09-25 11:46:02 -04:00
if ( ! empty ( $info [ 'settings' ][ 'admin-delegation' ])) {
foreach ( $info [ 'settings' ][ 'admin-delegation' ] as $setting ) {
2025-09-23 05:16:33 -04:00
$settingsManager -> registerSetting ( ISettingsManager :: SETTINGS_DELEGATION , $setting );
}
}
2025-09-25 11:46:02 -04:00
if ( ! empty ( $info [ 'settings' ][ 'admin-delegation-section' ])) {
foreach ( $info [ 'settings' ][ 'admin-delegation-section' ] as $section ) {
$settingsManager -> registerSection ( ISettingsManager :: SETTINGS_DELEGATION , $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 " );
}
2025-04-14 08:30:00 -04:00
2023-02-07 10:56:04 -05:00
/**
* 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 ]);
}
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
2025-06-05 09:31:14 -04:00
* @ throws \InvalidArgumentException if the application is not installed yet
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 );
2025-06-05 09:31:14 -04:00
if ( $this -> config -> getAppValue ( $appId , 'installed_version' , '' ) === '' ) {
throw new \InvalidArgumentException ( " $appId is not installed, cannot be enabled. " );
}
2019-09-05 06:55:24 -04:00
if ( $forceEnable ) {
2024-10-23 06:55:28 -04:00
$this -> overwriteNextcloudRequirement ( $appId );
2019-09-05 06:55:24 -04:00
}
2024-12-04 10:04:19 -05:00
$this -> enabledAppsCache [ $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 ();
2025-05-14 14:10:34 -04:00
2025-06-04 15:38:43 -04:00
$this -> configManager -> migrateConfigLexiconKeys ( $appId );
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
}
2025-06-05 09:31:14 -04:00
if ( $this -> config -> getAppValue ( $appId , 'installed_version' , '' ) === '' ) {
throw new \InvalidArgumentException ( " $appId is not installed, cannot be enabled. " );
}
2019-09-05 06:55:24 -04:00
if ( $forceEnable ) {
2024-10-23 06:55:28 -04:00
$this -> overwriteNextcloudRequirement ( $appId );
2019-09-05 06:55:24 -04:00
}
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
2024-12-04 10:04:19 -05:00
$this -> enabledAppsCache [ $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 ();
2025-05-14 14:10:34 -04:00
2025-06-04 15:38:43 -04:00
$this -> configManager -> migrateConfigLexiconKeys ( $appId );
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
*/
2024-10-23 06:55:28 -04:00
public function disableApp ( $appId , $automaticDisabled = false ) : void {
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
}
2024-12-04 10:04:19 -05:00
unset ( $this -> enabledAppsCache [ $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 .
*
2025-07-29 11:47:40 -04:00
* @ psalm - taint - specialize
*
2016-11-17 06:30:52 -05:00
* @ throws AppPathNotFoundException if app folder can ' t be found
*/
2025-07-10 10:01:09 -04:00
public function getAppPath ( string $appId , bool $ignoreCache = false ) : string {
2025-07-29 11:47:40 -04:00
$appId = $this -> cleanAppId ( $appId );
if ( $appId === '' ) {
throw new AppPathNotFoundException ( 'App id is empty' );
} elseif ( $appId === 'core' ) {
return __DIR__ . '/../../../core' ;
}
if (( $dir = $this -> findAppInDirectories ( $appId , $ignoreCache )) != false ) {
return $dir [ 'path' ] . '/' . $appId ;
2016-11-17 06:30:52 -05:00
}
2025-07-29 11:47:40 -04:00
throw new AppPathNotFoundException ( 'Could not find path for ' . $appId );
2016-11-17 06:30:52 -05:00
}
2019-08-25 09:27:04 -04:00
/**
* Get the web path for the given app .
*
* @ throws AppPathNotFoundException if app path can ' t be found
*/
2019-09-05 12:35:40 -04:00
public function getAppWebPath ( string $appId ) : string {
2025-07-29 11:47:40 -04:00
if (( $dir = $this -> findAppInDirectories ( $appId )) != false ) {
return \OC :: $WEBROOT . $dir [ 'url' ] . '/' . $appId ;
}
throw new AppPathNotFoundException ( 'Could not find web path for ' . $appId );
}
/**
* Find the apps root for an app id .
*
* If multiple copies are found , the apps root the latest version is returned .
*
* @ param bool $ignoreCache ignore cache and rebuild it
* @ return false | array { path : string , url : string } the apps root shape
*/
public function findAppInDirectories ( string $appId , bool $ignoreCache = false ) {
$sanitizedAppId = $this -> cleanAppId ( $appId );
if ( $sanitizedAppId !== $appId ) {
return false ;
}
// FIXME replace by a property or a cache
static $app_dir = [];
if ( isset ( $app_dir [ $appId ]) && ! $ignoreCache ) {
return $app_dir [ $appId ];
}
$possibleApps = [];
foreach ( \OC :: $APPSROOTS as $dir ) {
if ( file_exists ( $dir [ 'path' ] . '/' . $appId )) {
$possibleApps [] = $dir ;
}
}
if ( empty ( $possibleApps )) {
return false ;
} elseif ( count ( $possibleApps ) === 1 ) {
$dir = array_shift ( $possibleApps );
$app_dir [ $appId ] = $dir ;
return $dir ;
} else {
$versionToLoad = [];
foreach ( $possibleApps as $possibleApp ) {
$appData = $this -> getAppInfoByPath ( $possibleApp [ 'path' ] . '/' . $appId . '/appinfo/info.xml' );
$version = $appData [ 'version' ] ? ? '' ;
if ( empty ( $versionToLoad ) || version_compare ( $version , $versionToLoad [ 'version' ], '>' )) {
$versionToLoad = [
'dir' => $possibleApp ,
'version' => $version ,
];
}
}
if ( ! isset ( $versionToLoad [ 'dir' ])) {
return false ;
}
$app_dir [ $appId ] = $versionToLoad [ 'dir' ];
return $versionToLoad [ 'dir' ];
2019-08-25 09:27:04 -04:00
}
}
2015-04-01 09:37:22 -04:00
/**
* Clear the cached list of apps when enabling / disabling an app
*/
2024-10-23 06:55:28 -04:00
public function clearAppsCache () : void {
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 = [];
2024-12-04 10:04:19 -05:00
$apps = $this -> getEnabledApps ();
2015-07-07 06:12:54 -04:00
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 , '>' )
2025-07-29 10:50:00 -04:00
&& $this -> 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 ) {
2024-10-07 16:10:09 -04:00
throw new \InvalidArgumentException ( 'Calling IAppManager::getAppInfo() with a path is no longer supported. Please call IAppManager::getAppInfoByPath() instead and verify that the path is good before calling.' );
}
if ( $lang === null && isset ( $this -> appInfos [ $appId ])) {
return $this -> appInfos [ $appId ];
}
try {
$appPath = $this -> getAppPath ( $appId );
} catch ( AppPathNotFoundException ) {
return null ;
}
$file = $appPath . '/appinfo/info.xml' ;
$data = $this -> getAppInfoByPath ( $file , $lang );
if ( $lang === null ) {
$this -> appInfos [ $appId ] = $data ;
}
return $data ;
}
public function getAppInfoByPath ( string $path , ? string $lang = null ) : ? array {
if ( ! str_ends_with ( $path , '/appinfo/info.xml' )) {
return null ;
2018-01-29 07:09:32 -05:00
}
$parser = new InfoParser ( $this -> memCacheFactory -> createLocal ( 'core.appinfo' ));
2024-10-07 16:10:09 -04:00
$data = $parser -> parse ( $path );
2018-01-29 07:09:32 -05:00
if ( is_array ( $data )) {
2025-06-17 05:28:04 -04:00
$data = $parser -> applyL10N ( $data , $lang );
2018-01-29 07:09:32 -05:00
}
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 ])) {
2025-01-24 06:52:33 -05:00
if ( $appId === 'core' ) {
$this -> appVersions [ $appId ] = $this -> serverVersion -> getVersionString ();
} else {
$appInfo = $this -> getAppInfo ( $appId );
$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
}
2025-03-24 12:02:50 -04:00
/**
* Returns the installed versions of all apps
*
* @ return array < string , string >
*/
2025-06-02 11:45:35 -04:00
public function getAppInstalledVersions ( bool $onlyEnabled = false ) : array {
return $this -> getAppConfig () -> getAppInstalledVersions ( $onlyEnabled );
2025-03-24 12:02:50 -04:00
}
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 {
2024-12-04 10:04:19 -05:00
$apps = $this -> getEnabledApps ();
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 ];
2025-07-29 10:50:00 -04:00
} elseif ( ! $this -> 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 {
2025-05-28 07:26:07 -04:00
if ( $appId === 'core' ) {
return true ;
}
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 ) {
2025-08-20 11:08:04 -04:00
if (
isset ( $appInfo [ 'dependencies' ][ 'backend' ])
&& is_array ( $appInfo [ 'dependencies' ][ 'backend' ])
&& in_array ( $backend , $appInfo [ 'dependencies' ][ 'backend' ], true )
) {
return true ;
2024-07-13 10:51:16 -04:00
}
}
return false ;
}
2024-09-12 10:17:19 -04:00
2025-02-13 08:21:36 -05:00
/**
* Clean the appId from forbidden characters
*
* @ psalm - taint - escape callable
* @ psalm - taint - escape cookie
* @ psalm - taint - escape file
* @ psalm - taint - escape has_quotes
* @ psalm - taint - escape header
* @ psalm - taint - escape html
* @ psalm - taint - escape include
* @ psalm - taint - escape ldap
* @ psalm - taint - escape shell
* @ psalm - taint - escape sql
* @ psalm - taint - escape unserialize
*/
2024-09-12 10:17:19 -04:00
public function cleanAppId ( string $app ) : string {
2025-02-13 08:21:36 -05:00
/* Only lowercase alphanumeric is allowed */
2025-09-16 08:53:57 -04:00
return preg_replace ( '/(^[0-9_-]+|[^a-z0-9_-]+|[_-]+$)/' , '' , $app );
2024-09-12 10:17:19 -04:00
}
2025-07-10 10:01:09 -04:00
/**
* Run upgrade tasks for an app after the code has already been updated
*
* @ throws AppPathNotFoundException if app folder can ' t be found
*/
public function upgradeApp ( string $appId ) : bool {
// for apps distributed with core, we refresh app path in case the downloaded version
// have been installed in custom apps and not in the default path
$appPath = $this -> getAppPath ( $appId , true );
$this -> clearAppsCache ();
$l = \OC :: $server -> getL10N ( 'core' );
$appData = $this -> getAppInfo ( $appId , false , $l -> getLanguageCode ());
if ( $appData === null ) {
throw new AppPathNotFoundException ( 'Could not find ' . $appId );
}
$ignoreMaxApps = $this -> config -> getSystemValue ( 'app_install_overwrite' , []);
$ignoreMax = in_array ( $appId , $ignoreMaxApps , true );
\OC_App :: checkAppDependencies (
$this -> config ,
$l ,
$appData ,
$ignoreMax
);
\OC_App :: registerAutoloading ( $appId , $appPath , true );
\OC_App :: executeRepairSteps ( $appId , $appData [ 'repair-steps' ][ 'pre-migration' ]);
2025-07-10 10:54:10 -04:00
$ms = new MigrationService ( $appId , Server :: get ( \OC\DB\Connection :: class ));
2025-07-10 10:01:09 -04:00
$ms -> migrate ();
\OC_App :: executeRepairSteps ( $appId , $appData [ 'repair-steps' ][ 'post-migration' ]);
2025-07-10 10:54:10 -04:00
$queue = Server :: get ( IJobList :: class );
foreach ( $appData [ 'repair-steps' ][ 'live-migration' ] as $step ) {
$queue -> add ( \OC\Migration\BackgroundRepair :: class , [
'app' => $appId ,
'step' => $step ]);
}
2025-07-10 10:01:09 -04:00
// update appversion in app manager
$this -> clearAppsCache ();
$this -> getAppVersion ( $appId , false );
2025-07-29 11:53:16 -04:00
// Setup background jobs
foreach ( $appData [ 'background-jobs' ] as $job ) {
$queue -> add ( $job );
}
2025-07-10 10:01:09 -04:00
//set remote/public handlers
foreach ( $appData [ 'remote' ] as $name => $path ) {
$this -> config -> setAppValue ( 'core' , 'remote_' . $name , $appId . '/' . $path );
}
foreach ( $appData [ 'public' ] as $name => $path ) {
$this -> config -> setAppValue ( 'core' , 'public_' . $name , $appId . '/' . $path );
}
\OC_App :: setAppTypes ( $appId );
$version = $this -> getAppVersion ( $appId );
$this -> config -> setAppValue ( $appId , 'installed_version' , $version );
// migrate eventual new config keys in the process
/** @psalm-suppress InternalMethod */
$this -> configManager -> migrateConfigLexiconKeys ( $appId );
2025-09-16 07:46:45 -04:00
$this -> configManager -> updateLexiconEntries ( $appId );
2025-07-10 10:01:09 -04:00
$this -> dispatcher -> dispatchTyped ( new AppUpdateEvent ( $appId ));
$this -> dispatcher -> dispatch ( ManagerEvent :: EVENT_APP_UPDATE , new ManagerEvent (
ManagerEvent :: EVENT_APP_UPDATE , $appId
));
return true ;
}
2025-07-29 10:50:00 -04:00
public function isUpgradeRequired ( string $appId ) : bool {
$versions = $this -> getAppInstalledVersions ();
$currentVersion = $this -> getAppVersion ( $appId );
if ( $currentVersion && isset ( $versions [ $appId ])) {
$installedVersion = $versions [ $appId ];
if ( ! version_compare ( $currentVersion , $installedVersion , '=' )) {
return true ;
}
}
return false ;
}
public function isAppCompatible ( string $serverVersion , array $appInfo , bool $ignoreMax = false ) : bool {
2025-07-31 11:44:44 -04:00
return count ( $this -> dependencyAnalyzer -> analyzeServerVersion ( $serverVersion , $appInfo , $ignoreMax )) === 0 ;
2025-07-29 10:50:00 -04:00
}
2014-11-07 08:26:12 -05:00
}