diff --git a/lib/base.php b/lib/base.php index 1db6b84c5fb..3ca4775dbe2 100644 --- a/lib/base.php +++ b/lib/base.php @@ -935,14 +935,15 @@ class OC { // emergency app disabling if ($requestPath === '/disableapp' && $request->getMethod() === 'POST' - && ((string)$request->getParam('appid')) !== '' + && ((array)$request->getParam('appid')) !== '' ) { \OCP\JSON::callCheck(); \OCP\JSON::checkAdminUser(); - $appId = (string)$request->getParam('appid'); - $appId = \OC_App::cleanAppId($appId); - - \OC_App::disable($appId); + $appIds = (array)$request->getParam('appid'); + foreach($appIds as $appId) { + $appId = \OC_App::cleanAppId($appId); + \OC_App::disable($appId); + } \OC_JSON::success(); exit(); } diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 9dea4d10fb2..23aff9df870 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -318,6 +318,12 @@ return array( 'OC\\AppFramework\\Utility\\TimeFactory' => $baseDir . '/lib/private/AppFramework/Utility/TimeFactory.php', 'OC\\AppHelper' => $baseDir . '/lib/private/AppHelper.php', 'OC\\App\\AppManager' => $baseDir . '/lib/private/App/AppManager.php', + 'OC\\App\\AppStore\\Bundles\\Bundle' => $baseDir . '/lib/private/App/AppStore/Bundles/Bundle.php', + 'OC\\App\\AppStore\\Bundles\\BundleFetcher' => $baseDir . '/lib/private/App/AppStore/Bundles/BundleFetcher.php', + 'OC\\App\\AppStore\\Bundles\\CoreBundle' => $baseDir . '/lib/private/App/AppStore/Bundles/CoreBundle.php', + 'OC\\App\\AppStore\\Bundles\\EnterpriseBundle' => $baseDir . '/lib/private/App/AppStore/Bundles/EnterpriseBundle.php', + 'OC\\App\\AppStore\\Bundles\\GroupwareBundle' => $baseDir . '/lib/private/App/AppStore/Bundles/GroupwareBundle.php', + 'OC\\App\\AppStore\\Bundles\\SocialSharingBundle' => $baseDir . '/lib/private/App/AppStore/Bundles/SocialSharingBundle.php', 'OC\\App\\AppStore\\Fetcher\\AppFetcher' => $baseDir . '/lib/private/App/AppStore/Fetcher/AppFetcher.php', 'OC\\App\\AppStore\\Fetcher\\CategoryFetcher' => $baseDir . '/lib/private/App/AppStore/Fetcher/CategoryFetcher.php', 'OC\\App\\AppStore\\Fetcher\\Fetcher' => $baseDir . '/lib/private/App/AppStore/Fetcher/Fetcher.php', @@ -727,6 +733,7 @@ return array( 'OC\\Repair\\NC11\\FixMountStorages' => $baseDir . '/lib/private/Repair/NC11/FixMountStorages.php', 'OC\\Repair\\NC11\\MoveAvatars' => $baseDir . '/lib/private/Repair/NC11/MoveAvatars.php', 'OC\\Repair\\NC11\\MoveAvatarsBackgroundJob' => $baseDir . '/lib/private/Repair/NC11/MoveAvatarsBackgroundJob.php', + 'OC\\Repair\\NC12\\InstallCoreBundle' => $baseDir . '/lib/private/Repair/NC12/InstallCoreBundle.php', 'OC\\Repair\\NC12\\UpdateLanguageCodes' => $baseDir . '/lib/private/Repair/NC12/UpdateLanguageCodes.php', 'OC\\Repair\\OldGroupMembershipShares' => $baseDir . '/lib/private/Repair/OldGroupMembershipShares.php', 'OC\\Repair\\RemoveRootShares' => $baseDir . '/lib/private/Repair/RemoveRootShares.php', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index 11d949de34a..709d59ff3d0 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -348,6 +348,12 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\AppFramework\\Utility\\TimeFactory' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Utility/TimeFactory.php', 'OC\\AppHelper' => __DIR__ . '/../../..' . '/lib/private/AppHelper.php', 'OC\\App\\AppManager' => __DIR__ . '/../../..' . '/lib/private/App/AppManager.php', + 'OC\\App\\AppStore\\Bundles\\Bundle' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/Bundle.php', + 'OC\\App\\AppStore\\Bundles\\BundleFetcher' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/BundleFetcher.php', + 'OC\\App\\AppStore\\Bundles\\CoreBundle' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/CoreBundle.php', + 'OC\\App\\AppStore\\Bundles\\EnterpriseBundle' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/EnterpriseBundle.php', + 'OC\\App\\AppStore\\Bundles\\GroupwareBundle' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/GroupwareBundle.php', + 'OC\\App\\AppStore\\Bundles\\SocialSharingBundle' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Bundles/SocialSharingBundle.php', 'OC\\App\\AppStore\\Fetcher\\AppFetcher' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Fetcher/AppFetcher.php', 'OC\\App\\AppStore\\Fetcher\\CategoryFetcher' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Fetcher/CategoryFetcher.php', 'OC\\App\\AppStore\\Fetcher\\Fetcher' => __DIR__ . '/../../..' . '/lib/private/App/AppStore/Fetcher/Fetcher.php', @@ -757,6 +763,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c 'OC\\Repair\\NC11\\FixMountStorages' => __DIR__ . '/../../..' . '/lib/private/Repair/NC11/FixMountStorages.php', 'OC\\Repair\\NC11\\MoveAvatars' => __DIR__ . '/../../..' . '/lib/private/Repair/NC11/MoveAvatars.php', 'OC\\Repair\\NC11\\MoveAvatarsBackgroundJob' => __DIR__ . '/../../..' . '/lib/private/Repair/NC11/MoveAvatarsBackgroundJob.php', + 'OC\\Repair\\NC12\\InstallCoreBundle' => __DIR__ . '/../../..' . '/lib/private/Repair/NC12/InstallCoreBundle.php', 'OC\\Repair\\NC12\\UpdateLanguageCodes' => __DIR__ . '/../../..' . '/lib/private/Repair/NC12/UpdateLanguageCodes.php', 'OC\\Repair\\OldGroupMembershipShares' => __DIR__ . '/../../..' . '/lib/private/Repair/OldGroupMembershipShares.php', 'OC\\Repair\\RemoveRootShares' => __DIR__ . '/../../..' . '/lib/private/Repair/RemoveRootShares.php', diff --git a/lib/private/App/AppStore/Bundles/Bundle.php b/lib/private/App/AppStore/Bundles/Bundle.php new file mode 100644 index 00000000000..47efc4e0cce --- /dev/null +++ b/lib/private/App/AppStore/Bundles/Bundle.php @@ -0,0 +1,59 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Bundles; + +use OCP\IL10N; + +abstract class Bundle { + /** @var IL10N */ + protected $l10n; + + /** + * @param IL10N $l10n + */ + public function __construct(IL10N $l10n) { + $this->l10n = $l10n; + } + + /** + * Get the identifier of the bundle + * + * @return string + */ + public final function getIdentifier() { + return substr(strrchr(get_class($this), '\\'), 1); + } + + /** + * Get the name of the bundle + * + * @return string + */ + public abstract function getName(); + + /** + * Get the list of app identifiers in the bundle + * + * @return array + */ + public abstract function getAppIdentifiers(); +} diff --git a/lib/private/App/AppStore/Bundles/BundleFetcher.php b/lib/private/App/AppStore/Bundles/BundleFetcher.php new file mode 100644 index 00000000000..01cd4d6a518 --- /dev/null +++ b/lib/private/App/AppStore/Bundles/BundleFetcher.php @@ -0,0 +1,80 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Bundles; + +use OCP\IL10N; + +class BundleFetcher { + /** @var IL10N */ + private $l10n; + + /** + * @param IL10N $l10n + */ + public function __construct(IL10N $l10n) { + $this->l10n = $l10n; + } + + /** + * @return Bundle[] + */ + public function getBundles() { + return [ + new EnterpriseBundle($this->l10n), + new GroupwareBundle($this->l10n), + new SocialSharingBundle($this->l10n), + ]; + } + + /** + * Bundles that should be installed by default after installation + * + * @return Bundle[] + */ + public function getDefaultInstallationBundle() { + return [ + new CoreBundle($this->l10n), + ]; + } + + /** + * Get the bundle with the specified identifier + * + * @param string $identifier + * @return Bundle + * @throws \BadMethodCallException If the bundle does not exist + */ + public function getBundleByIdentifier($identifier) { + /** @var Bundle[] $bundles */ + $bundles = array_merge( + $this->getBundles(), + $this->getDefaultInstallationBundle() + ); + foreach($bundles as $bundle) { + if($bundle->getIdentifier() === $identifier) { + return $bundle; + } + } + + throw new \BadMethodCallException('Bundle with specified identifier does not exist'); + } +} diff --git a/lib/private/App/AppStore/Bundles/CoreBundle.php b/lib/private/App/AppStore/Bundles/CoreBundle.php new file mode 100644 index 00000000000..a87292b9ec9 --- /dev/null +++ b/lib/private/App/AppStore/Bundles/CoreBundle.php @@ -0,0 +1,42 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Bundles; + +class CoreBundle extends Bundle { + + /** + * {@inheritDoc} + */ + public function getName() { + return 'Core bundle'; + } + + /** + * {@inheritDoc} + */ + public function getAppIdentifiers() { + return [ + 'bruteforcesettings', + ]; + } + +} diff --git a/lib/private/App/AppStore/Bundles/EnterpriseBundle.php b/lib/private/App/AppStore/Bundles/EnterpriseBundle.php new file mode 100644 index 00000000000..6d43a6210fa --- /dev/null +++ b/lib/private/App/AppStore/Bundles/EnterpriseBundle.php @@ -0,0 +1,47 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Bundles; + +class EnterpriseBundle extends Bundle { + + /** + * {@inheritDoc} + */ + public function getName() { + return (string)$this->l10n->t('Enterprise bundle'); + } + + /** + * {@inheritDoc} + */ + public function getAppIdentifiers() { + return [ + 'admin_audit', + 'user_ldap', + 'files_retention', + 'files_automatedtagging', + 'user_saml', + 'files_accesscontrol', + ]; + } + +} diff --git a/lib/private/App/AppStore/Bundles/GroupwareBundle.php b/lib/private/App/AppStore/Bundles/GroupwareBundle.php new file mode 100644 index 00000000000..7e7414f69c7 --- /dev/null +++ b/lib/private/App/AppStore/Bundles/GroupwareBundle.php @@ -0,0 +1,44 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Bundles; + +class GroupwareBundle extends Bundle { + + /** + * {@inheritDoc} + */ + public function getName() { + return (string)$this->l10n->t('Groupware bundle'); + } + + /** + * {@inheritDoc} + */ + public function getAppIdentifiers() { + return [ + 'calendar', + 'contacts', + 'spreed', + ]; + } + +} diff --git a/lib/private/App/AppStore/Bundles/SocialSharingBundle.php b/lib/private/App/AppStore/Bundles/SocialSharingBundle.php new file mode 100644 index 00000000000..8da84e8d1ef --- /dev/null +++ b/lib/private/App/AppStore/Bundles/SocialSharingBundle.php @@ -0,0 +1,46 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\App\AppStore\Bundles; + +class SocialSharingBundle extends Bundle { + + /** + * {@inheritDoc} + */ + public function getName() { + return (string)$this->l10n->t('Social sharing bundle'); + } + + /** + * {@inheritDoc} + */ + public function getAppIdentifiers() { + return [ + 'socialsharing_twitter', + 'socialsharing_googleplus', + 'socialsharing_facebook', + 'socialsharing_email', + 'socialsharing_diaspora', + ]; + } + +} diff --git a/lib/private/Installer.php b/lib/private/Installer.php index 0d6030d5744..8702f264e54 100644 --- a/lib/private/Installer.php +++ b/lib/private/Installer.php @@ -42,6 +42,8 @@ namespace OC; use Doctrine\DBAL\Exception\TableExistsException; +use OC\App\AppManager; +use OC\App\AppStore\Bundles\Bundle; use OC\App\AppStore\Fetcher\AppFetcher; use OC\App\CodeChecker\CodeChecker; use OC\App\CodeChecker\EmptyCheck; @@ -50,7 +52,9 @@ use OC\Archive\TAR; use OC_App; use OC_DB; use OC_Helper; +use OCP\App\IAppManager; use OCP\Http\Client\IClientService; +use OCP\IConfig; use OCP\ILogger; use OCP\ITempManager; use phpseclib\File\X509; @@ -67,21 +71,26 @@ class Installer { private $tempManager; /** @var ILogger */ private $logger; + /** @var IConfig */ + private $config; /** * @param AppFetcher $appFetcher * @param IClientService $clientService * @param ITempManager $tempManager * @param ILogger $logger + * @param IConfig $config */ public function __construct(AppFetcher $appFetcher, IClientService $clientService, ITempManager $tempManager, - ILogger $logger) { + ILogger $logger, + IConfig $config) { $this->appFetcher = $appFetcher; $this->clientService = $clientService; $this->tempManager = $tempManager; $this->logger = $logger; + $this->config = $config; } /** @@ -109,6 +118,7 @@ class Installer { } } + \OC_App::registerAutoloading($appId, $basedir); \OC_App::setupBackgroundJobs($info['background-jobs']); //run appinfo/install.php @@ -419,6 +429,27 @@ class Installer { } + /** + * Installs the app within the bundle and marks the bundle as installed + * + * @param Bundle $bundle + * @throws \Exception If app could not get installed + */ + public function installAppBundle(Bundle $bundle) { + $appIds = $bundle->getAppIdentifiers(); + foreach($appIds as $appId) { + if(!$this->isDownloaded($appId)) { + $this->downloadApp($appId); + } + $this->installApp($appId); + $app = new OC_App(); + $app->enable($appId); + } + $bundles = json_decode($this->config->getAppValue('core', 'installed.bundles', json_encode([])), true); + $bundles[] = $bundle->getIdentifier(); + $this->config->setAppValue('core', 'installed.bundles', json_encode($bundles)); + } + /** * Installs shipped apps * diff --git a/lib/private/Repair.php b/lib/private/Repair.php index e808774ec93..65e0342905a 100644 --- a/lib/private/Repair.php +++ b/lib/private/Repair.php @@ -30,12 +30,14 @@ namespace OC; +use OC\App\AppStore\Bundles\BundleFetcher; use OC\Repair\CleanTags; use OC\Repair\Collation; use OC\Repair\MoveUpdaterStepFile; use OC\Repair\NC11\CleanPreviews; use OC\Repair\NC11\FixMountStorages; use OC\Repair\NC11\MoveAvatars; +use OC\Repair\NC12\InstallCoreBundle; use OC\Repair\NC12\UpdateLanguageCodes; use OC\Repair\OldGroupMembershipShares; use OC\Repair\RemoveRootShares; @@ -136,6 +138,11 @@ class Repair implements IOutput{ ), new FixMountStorages(\OC::$server->getDatabaseConnection()), new UpdateLanguageCodes(\OC::$server->getDatabaseConnection(), \OC::$server->getConfig()), + new InstallCoreBundle( + \OC::$server->query(BundleFetcher::class), + \OC::$server->getConfig(), + \OC::$server->query(Installer::class) + ) ]; } diff --git a/lib/private/Repair/NC12/InstallCoreBundle.php b/lib/private/Repair/NC12/InstallCoreBundle.php new file mode 100644 index 00000000000..38583b09a89 --- /dev/null +++ b/lib/private/Repair/NC12/InstallCoreBundle.php @@ -0,0 +1,78 @@ + + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + */ + +namespace OC\Repair\NC12; + +use OC\App\AppStore\Bundles\BundleFetcher; +use OC\Installer; +use OCP\IConfig; +use OCP\Migration\IOutput; +use OCP\Migration\IRepairStep; + +class InstallCoreBundle implements IRepairStep { + /** @var BundleFetcher */ + private $bundleFetcher; + /** @var IConfig */ + private $config; + /** @var Installer */ + private $installer; + + /** + * @param BundleFetcher $bundleFetcher + * @param IConfig $config + * @param Installer $installer + */ + public function __construct(BundleFetcher $bundleFetcher, + IConfig $config, + Installer $installer) { + $this->bundleFetcher = $bundleFetcher; + $this->config = $config; + $this->installer = $installer; + } + + /** + * {@inheritdoc} + */ + public function getName() { + return 'Install new core bundle components'; + } + + /** + * {@inheritdoc} + */ + public function run(IOutput $output) { + $versionFromBeforeUpdate = $this->config->getSystemValue('version', '0.0.0'); + + if (version_compare($versionFromBeforeUpdate, '12.0.0.14', '>')) { + return; + } + + $defaultBundle = $this->bundleFetcher->getDefaultInstallationBundle(); + foreach($defaultBundle as $bundle) { + try { + $this->installer->installAppBundle($bundle); + $output->info('Successfully installed core app bundle.'); + } catch (\Exception $e) { + $output->warning('Could not install core app bundle: ' . $e->getMessage()); + } + } + } +} diff --git a/lib/private/Server.php b/lib/private/Server.php index 7724feb551b..b05e05660b0 100644 --- a/lib/private/Server.php +++ b/lib/private/Server.php @@ -43,6 +43,7 @@ namespace OC; use bantu\IniGetWrapper\IniGetWrapper; use OC\App\AppManager; +use OC\App\AppStore\Bundles\BundleFetcher; use OC\App\AppStore\Fetcher\AppFetcher; use OC\App\AppStore\Fetcher\CategoryFetcher; use OC\AppFramework\Http\Request; @@ -816,7 +817,12 @@ class Server extends ServerContainer implements IServerContainer { ); }); $this->registerAlias('MimeTypeLoader', \OCP\Files\IMimeTypeLoader::class); - + $this->registerService(BundleFetcher::class, function () { + return new BundleFetcher($this->getL10N('lib')); + }); + $this->registerService(AppFetcher::class, function() { + return $this->getAppFetcher(); + }); $this->registerService(\OCP\Notification\IManager::class, function (Server $c) { return new Manager( $c->query(IValidator::class) diff --git a/lib/private/Setup.php b/lib/private/Setup.php index e2806efad48..b1cf289d9aa 100644 --- a/lib/private/Setup.php +++ b/lib/private/Setup.php @@ -41,6 +41,7 @@ namespace OC; use bantu\IniGetWrapper\IniGetWrapper; use Exception; +use OC\App\AppStore\Bundles\BundleFetcher; use OCP\Defaults; use OCP\IL10N; use OCP\ILogger; @@ -63,11 +64,12 @@ class Setup { /** * @param SystemConfig $config * @param IniGetWrapper $iniWrapper + * @param IL10N $l10n * @param Defaults $defaults * @param ILogger $logger * @param ISecureRandom $random */ - function __construct(SystemConfig $config, + public function __construct(SystemConfig $config, IniGetWrapper $iniWrapper, IL10N $l10n, Defaults $defaults, @@ -364,8 +366,22 @@ class Setup { $group =\OC::$server->getGroupManager()->createGroup('admin'); $group->addUser($user); - //guess what this does + // Install shipped apps and specified app bundles Installer::installShippedApps(); + $installer = new Installer( + \OC::$server->getAppFetcher(), + \OC::$server->getHTTPClientService(), + \OC::$server->getTempManager(), + \OC::$server->getLogger(), + \OC::$server->getConfig() + ); + $bundleFetcher = new BundleFetcher(\OC::$server->getL10N('lib')); + $defaultInstallationBundles = $bundleFetcher->getDefaultInstallationBundle(); + foreach($defaultInstallationBundles as $bundle) { + try { + $installer->installAppBundle($bundle); + } catch (Exception $e) {} + } // create empty file in data dir, so we can later find // out that this is indeed an ownCloud data directory diff --git a/lib/private/Updater.php b/lib/private/Updater.php index 4427e4c48dc..c080ee0eb43 100644 --- a/lib/private/Updater.php +++ b/lib/private/Updater.php @@ -243,11 +243,11 @@ class Updater extends BasicEmitter { } // update all shipped apps - $disabledApps = $this->checkAppsRequirements(); + $this->checkAppsRequirements(); $this->doAppUpgrade(); // upgrade appstore apps - $this->upgradeAppStoreApps($disabledApps); + $this->upgradeAppStoreApps(\OC::$server->getAppManager()->getInstalledApps()); // install new shipped apps on upgrade OC_App::loadApps('authentication'); @@ -441,7 +441,8 @@ class Updater extends BasicEmitter { \OC::$server->getAppFetcher(), \OC::$server->getHTTPClientService(), \OC::$server->getTempManager(), - $this->log + $this->log, + \OC::$server->getConfig() ); if (Installer::isUpdateAvailable($app, \OC::$server->getAppFetcher())) { $this->emit('\OC\Updater', 'upgradeAppStoreApp', [$app]); diff --git a/lib/private/legacy/app.php b/lib/private/legacy/app.php index 3800b8b770e..111da7d0d40 100644 --- a/lib/private/legacy/app.php +++ b/lib/private/legacy/app.php @@ -365,7 +365,8 @@ class OC_App { \OC::$server->getAppFetcher(), \OC::$server->getHTTPClientService(), \OC::$server->getTempManager(), - \OC::$server->getLogger() + \OC::$server->getLogger(), + \OC::$server->getConfig() ); $isDownloaded = $installer->isDownloaded($appId); @@ -427,7 +428,8 @@ class OC_App { \OC::$server->getAppFetcher(), \OC::$server->getHTTPClientService(), \OC::$server->getTempManager(), - \OC::$server->getLogger() + \OC::$server->getLogger(), + \OC::$server->getConfig() ); return $installer->removeApp($app); } diff --git a/settings/Controller/AppSettingsController.php b/settings/Controller/AppSettingsController.php index 7be6c2bf562..ac77b2e7dd6 100644 --- a/settings/Controller/AppSettingsController.php +++ b/settings/Controller/AppSettingsController.php @@ -27,6 +27,7 @@ namespace OC\Settings\Controller; +use OC\App\AppStore\Bundles\BundleFetcher; use OC\App\AppStore\Fetcher\AppFetcher; use OC\App\AppStore\Fetcher\CategoryFetcher; use OC\App\AppStore\Version\VersionParser; @@ -50,6 +51,7 @@ class AppSettingsController extends Controller { const CAT_ENABLED = 0; const CAT_DISABLED = 1; const CAT_ALL_INSTALLED = 2; + const CAT_APP_BUNDLES = 3; /** @var \OCP\IL10N */ private $l10n; @@ -65,6 +67,8 @@ class AppSettingsController extends Controller { private $appFetcher; /** @var IFactory */ private $l10nFactory; + /** @var BundleFetcher */ + private $bundleFetcher; /** * @param string $appName @@ -76,6 +80,7 @@ class AppSettingsController extends Controller { * @param CategoryFetcher $categoryFetcher * @param AppFetcher $appFetcher * @param IFactory $l10nFactory + * @param BundleFetcher $bundleFetcher */ public function __construct($appName, IRequest $request, @@ -85,7 +90,8 @@ class AppSettingsController extends Controller { IAppManager $appManager, CategoryFetcher $categoryFetcher, AppFetcher $appFetcher, - IFactory $l10nFactory) { + IFactory $l10nFactory, + BundleFetcher $bundleFetcher) { parent::__construct($appName, $request); $this->l10n = $l10n; $this->config = $config; @@ -94,6 +100,7 @@ class AppSettingsController extends Controller { $this->categoryFetcher = $categoryFetcher; $this->appFetcher = $appFetcher; $this->l10nFactory = $l10nFactory; + $this->bundleFetcher = $bundleFetcher; } /** @@ -120,18 +127,14 @@ class AppSettingsController extends Controller { return $templateResponse; } - /** - * Get all available categories - * - * @return JSONResponse - */ - public function listCategories() { + private function getAllCategories() { $currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2); $formattedCategories = [ ['id' => self::CAT_ALL_INSTALLED, 'ident' => 'installed', 'displayName' => (string)$this->l10n->t('Your apps')], ['id' => self::CAT_ENABLED, 'ident' => 'enabled', 'displayName' => (string)$this->l10n->t('Enabled apps')], ['id' => self::CAT_DISABLED, 'ident' => 'disabled', 'displayName' => (string)$this->l10n->t('Disabled apps')], + ['id' => self::CAT_APP_BUNDLES, 'ident' => 'app-bundles', 'displayName' => (string)$this->l10n->t('App bundles')], ]; $categories = $this->categoryFetcher->get(); foreach($categories as $category) { @@ -142,7 +145,16 @@ class AppSettingsController extends Controller { ]; } - return new JSONResponse($formattedCategories); + return $formattedCategories; + } + + /** + * Get all available categories + * + * @return JSONResponse + */ + public function listCategories() { + return new JSONResponse($this->getAllCategories()); } /** @@ -334,6 +346,41 @@ class AppSettingsController extends Controller { return ($a < $b) ? -1 : 1; }); break; + case 'app-bundles': + $bundles = $this->bundleFetcher->getBundles(); + $apps = []; + foreach($bundles as $bundle) { + $newCategory = true; + $allApps = $appClass->listAllApps(); + $categories = $this->getAllCategories(); + foreach($categories as $singleCategory) { + $newApps = $this->getAppsForCategory($singleCategory['id']); + foreach($allApps as $app) { + foreach($newApps as $key => $newApp) { + if($app['id'] === $newApp['id']) { + unset($newApps[$key]); + } + } + } + $allApps = array_merge($allApps, $newApps); + } + + foreach($bundle->getAppIdentifiers() as $identifier) { + foreach($allApps as $app) { + if($app['id'] === $identifier) { + if($newCategory) { + $app['newCategory'] = true; + $app['categoryName'] = $bundle->getName(); + } + $app['bundleId'] = $bundle->getIdentifier(); + $newCategory = false; + $apps[] = $app; + continue; + } + } + } + } + break; default: $apps = $this->getAppsForCategory($category); diff --git a/settings/ajax/disableapp.php b/settings/ajax/disableapp.php index 8edd1c1453e..9b76236a15b 100644 --- a/settings/ajax/disableapp.php +++ b/settings/ajax/disableapp.php @@ -36,8 +36,9 @@ if (!array_key_exists('appid', $_POST)) { exit; } -$appId = (string)$_POST['appid']; -$appId = OC_App::cleanAppId($appId); - -OC_App::disable($appId); +$appIds = (array)$_POST['appid']; +foreach($appIds as $appId) { + $appId = OC_App::cleanAppId($appId); + OC_App::disable($appId); +} OC_JSON::success(); diff --git a/settings/ajax/enableapp.php b/settings/ajax/enableapp.php index b6d62671a63..4c4fa0be666 100644 --- a/settings/ajax/enableapp.php +++ b/settings/ajax/enableapp.php @@ -36,13 +36,20 @@ if ($lastConfirm < (time() - 30 * 60 + 15)) { // allow 15 seconds delay } $groups = isset($_POST['groups']) ? (array)$_POST['groups'] : null; +$appIds = isset($_POST['appIds']) ? (array)$_POST['appIds'] : []; try { - $app = new OC_App(); - $appId = (string)$_POST['appid']; - $appId = OC_App::cleanAppId($appId); - $app->enable($appId, $groups); - OC_JSON::success(['data' => ['update_required' => \OC_App::shouldUpgrade($appId)]]); + $updateRequired = false; + foreach($appIds as $appId) { + $app = new OC_App(); + $appId = OC_App::cleanAppId($appId); + $app->enable($appId, $groups); + if(\OC_App::shouldUpgrade($appId)) { + $updateRequired = true; + } + } + + OC_JSON::success(['data' => ['update_required' => $updateRequired]]); } catch (Exception $e) { \OCP\Util::writeLog('core', $e->getMessage(), \OCP\Util::ERROR); OC_JSON::error(array("data" => array("message" => $e->getMessage()) )); diff --git a/settings/ajax/updateapp.php b/settings/ajax/updateapp.php index 3020f828577..bcf8e149140 100644 --- a/settings/ajax/updateapp.php +++ b/settings/ajax/updateapp.php @@ -44,7 +44,8 @@ try { \OC::$server->getAppFetcher(), \OC::$server->getHTTPClientService(), \OC::$server->getTempManager(), - \OC::$server->getLogger() + \OC::$server->getLogger(), + \OC::$server->getConfig() ); $result = $installer->updateAppstoreApp($appId); $config->setSystemValue('maintenance', false); diff --git a/settings/css/settings.css b/settings/css/settings.css index 7e91877773e..0a1d4e046fe 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -519,13 +519,37 @@ input.userFilter {width: 200px;} /* APPS */ +/* Bundle header */ +#apps-list .apps-header { + display: table-row; + position: relative; +} +#apps-list .apps-header div { + display: table-cell; + height: 70px; +} +#apps-list .apps-header h2 { + display: table-cell; + position: absolute; + padding-left: 6px; + padding-top: 15px; +} +#apps-list .apps-header h2 .enable { + position: relative; + top: -1px; + margin-left: 12px; +} +#apps-list .apps-header h2 + .section { + margin-top: 50px; +} + #app-content > svg.app-filter { float: left; height: 0; width: 0; } -#app-category-disabled { +#app-category-app-bundles { margin-bottom: 20px; } @@ -558,6 +582,10 @@ span.version { border-radius: 3px; padding: 3px 6px; } +.app-level a { + padding: 10px; + white-space: nowrap; +} .app-level .official { border-color: #37ce02; background-position: left center; @@ -737,6 +765,7 @@ form.section { display: table; width: 100%; height: auto; + margin-bottom: 100px; } #apps-list.installed .section { diff --git a/settings/js/apps.js b/settings/js/apps.js index 3326886951f..6bad2cc842c 100644 --- a/settings/js/apps.js +++ b/settings/js/apps.js @@ -29,6 +29,7 @@ OC.Settings.Apps = OC.Settings.Apps || { State: { currentCategory: null, + currentCategoryElements: null, apps: null, $updateNotification: null, availableUpdates: 0 @@ -90,14 +91,15 @@ OC.Settings.Apps = OC.Settings.Apps || { }), { type:'GET', success: function (apps) { + OC.Settings.Apps.State.currentCategoryElements = apps.apps; var appListWithIndex = _.indexBy(apps.apps, 'id'); OC.Settings.Apps.State.apps = appListWithIndex; var appList = _.map(appListWithIndex, function(app) { // default values for missing fields return _.extend({level: 0}, app); }); - var source - if (categoryId === 'enabled' || categoryId === 'disabled' || categoryId === 'installed') { + var source; + if (categoryId === 'enabled' || categoryId === 'disabled' || categoryId === 'installed' || categoryId === 'app-bundles') { source = $("#app-template-installed").html(); $('#apps-list').addClass('installed'); } else { @@ -107,17 +109,19 @@ OC.Settings.Apps = OC.Settings.Apps || { var template = Handlebars.compile(source); if (appList.length) { - appList.sort(function(a,b) { - if (a.active !== b.active) { - return (a.active ? -1 : 1) - } else { - var levelDiff = b.level - a.level; - if (levelDiff === 0) { - return OC.Util.naturalSortCompare(a.name, b.name); + if(categoryId !== 'app-bundles') { + appList.sort(function (a, b) { + if (a.active !== b.active) { + return (a.active ? -1 : 1) + } else { + var levelDiff = b.level - a.level; + if (levelDiff === 0) { + return OC.Util.naturalSortCompare(a.name, b.name); + } + return levelDiff; } - return levelDiff; - } - }); + }); + } var firstExperimental = false; _.each(appList, function(app) { @@ -303,56 +307,126 @@ OC.Settings.Apps = OC.Settings.Apps || { return $.get(OC.generateUrl('apps/files')); }, - enableApp:function(appId, active, element, groups) { + enableAppBundle:function(bundleId, active, element, groups) { + if (OC.PasswordConfirmation.requiresPasswordConfirmation()) { + OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.enableAppBundle, this, bundleId, active, element, groups)); + return; + } + + var apps = OC.Settings.Apps.State.currentCategoryElements; + var appsToEnable = []; + apps.forEach(function(app) { + if(app['bundleId'] === bundleId) { + if(app['active'] === false) { + appsToEnable.push(app['id']); + } + } + }); + + OC.Settings.Apps.enableApp(appsToEnable, false, groups); + }, + + /** + * @param {string[]} appId + * @param {boolean} active + * @param {array} groups + */ + enableApp:function(appId, active, groups) { if (OC.PasswordConfirmation.requiresPasswordConfirmation()) { - OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.enableApp, this, appId, active, element, groups)); + OC.PasswordConfirmation.requirePasswordConfirmation(_.bind(this.enableApp, this, appId, active, groups)); return; } + var elements = []; + appId.forEach(function(appId) { + elements.push($('#app-'+appId+' .enable')); + }); + var self = this; - OC.Settings.Apps.hideErrorMessage(appId); + appId.forEach(function(appId) { + OC.Settings.Apps.hideErrorMessage(appId); + }); groups = groups || []; - var appItem = $('div#app-'+appId+''); + var appItems = []; + appId.forEach(function(appId) { + appItems.push($('div#app-'+appId+'')); + }); + if(active && !groups.length) { - element.val(t('settings','Disabling app …')); + elements.forEach(function(element) { + element.val(t('settings','Disabling app …')); + }); $.post(OC.filePath('settings','ajax','disableapp.php'),{appid:appId},function(result) { if(!result || result.status !== 'success') { if (result.data && result.data.message) { OC.Settings.Apps.showErrorMessage(appId, result.data.message); - appItem.data('errormsg', result.data.message); + appItems.forEach(function(appItem) { + appItem.data('errormsg', result.data.message); + }) } else { OC.Settings.Apps.showErrorMessage(appId, t('settings', 'Error while disabling app')); - appItem.data('errormsg', t('settings', 'Error while disabling app')); + appItems.forEach(function(appItem) { + appItem.data('errormsg', t('settings', 'Error while disabling app')); + }); } - element.val(t('settings','Disable')); - appItem.addClass('appwarning'); + elements.forEach(function(element) { + element.val(t('settings','Disable')); + }); + appItems.forEach(function(appItem) { + appItem.addClass('appwarning'); + }); } else { OC.Settings.Apps.rebuildNavigation(); - appItem.data('active',false); - appItem.data('groups', ''); - element.data('active',false); - appItem.removeClass('active'); - element.val(t('settings','Enable')); - element.parent().find(".groups-enable").hide(); - element.parent().find('#group_select').hide().val(null); + appItems.forEach(function(appItem) { + appItem.data('active', false); + appItem.data('groups', ''); + }); + elements.forEach(function(element) { + element.data('active', false); + }); + appItems.forEach(function(appItem) { + appItem.removeClass('active'); + }); + elements.forEach(function(element) { + element.val(t('settings', 'Enable')); + element.parent().find(".groups-enable").hide(); + element.parent().find('#group_select').hide().val(null); + }); OC.Settings.Apps.State.apps[appId].active = false; } },'json'); } else { // TODO: display message to admin to not refresh the page! // TODO: lock UI to prevent further operations - element.val(t('settings','Enabling app …')); - $.post(OC.filePath('settings','ajax','enableapp.php'),{appid: appId, groups: groups},function(result) { + elements.forEach(function(element) { + element.val(t('settings', 'Enabling app …')); + }); + + var appIdArray = []; + if( typeof appId === 'string' ) { + appIdArray = [appId]; + } else { + appIdArray = appId; + } + $.post(OC.filePath('settings','ajax','enableapp.php'),{appIds: appIdArray, groups: groups},function(result) { if(!result || result.status !== 'success') { if (result.data && result.data.message) { OC.Settings.Apps.showErrorMessage(appId, result.data.message); - appItem.data('errormsg', result.data.message); + appItems.forEach(function(appItem) { + appItem.data('errormsg', result.data.message); + }); } else { OC.Settings.Apps.showErrorMessage(appId, t('settings', 'Error while enabling app')); - appItem.data('errormsg', t('settings', 'Error while disabling app')); + appItems.forEach(function(appItem) { + appItem.data('errormsg', t('settings', 'Error while disabling app')); + }); } - element.val(t('settings','Enable')); - appItem.addClass('appwarning'); + elements.forEach(function(element) { + element.val(t('settings', 'Enable')); + }); + appItems.forEach(function(appItem) { + appItem.addClass('appwarning'); + }); } else { self._checkServerHealth().done(function() { if (result.data.update_required) { @@ -364,24 +438,40 @@ OC.Settings.Apps = OC.Settings.Apps || { } OC.Settings.Apps.rebuildNavigation(); - appItem.data('active',true); - element.data('active',true); - appItem.addClass('active'); - element.val(t('settings','Disable')); + appItems.forEach(function(appItem) { + appItem.data('active', true); + }); + elements.forEach(function(element) { + element.data('active', true); + }); + appItems.forEach(function(appItem) { + appItem.addClass('active'); + }); + elements.forEach(function(element) { + element.val(t('settings', 'Disable')); + }); var app = OC.Settings.Apps.State.apps[appId]; app.active = true; if (OC.Settings.Apps.isType(app, 'filesystem') || OC.Settings.Apps.isType(app, 'prelogin') || OC.Settings.Apps.isType(app, 'authentication') || OC.Settings.Apps.isType(app, 'logging')) { - element.parent().find(".groups-enable").prop('checked', true); - element.parent().find(".groups-enable").hide(); - element.parent().find('#group_select').hide().val(null); + elements.forEach(function(element) { + element.parent().find(".groups-enable").prop('checked', true); + element.parent().find(".groups-enable").hide(); + element.parent().find('#group_select').hide().val(null); + }); } else { - element.parent().find("#groups-enable").show(); + elements.forEach(function(element) { + element.parent().find("#groups-enable").show(); + }); if (groups) { - appItem.data('groups', JSON.stringify(groups)); + appItems.forEach(function(appItem) { + appItem.data('groups', JSON.stringify(groups)); + }); } else { - appItem.data('groups', ''); + appItems.forEach(function(appItem) { + appItem.data('groups', ''); + }); } } }).fail(function() { @@ -391,26 +481,40 @@ OC.Settings.Apps = OC.Settings.Apps || { appId, t('settings', 'Error: this app cannot be enabled because it makes the server unstable') ); - appItem.data('errormsg', t('settings', 'Error while enabling app')); - element.val(t('settings','Enable')); - appItem.addClass('appwarning'); + appItems.forEach(function(appItem) { + appItem.data('errormsg', t('settings', 'Error while enabling app')); + }); + elements.forEach(function(element) { + element.val(t('settings', 'Enable')); + }); + appItems.forEach(function(appItem) { + appItem.addClass('appwarning'); + }); }).fail(function() { OC.Settings.Apps.showErrorMessage( appId, t('settings', 'Error: could not disable broken app') ); - appItem.data('errormsg', t('settings', 'Error while disabling broken app')); - element.val(t('settings','Enable')); + appItems.forEach(function(appItem) { + appItem.data('errormsg', t('settings', 'Error while disabling broken app')); + }); + elements.forEach(function(element) { + element.val(t('settings', 'Enable')); + }); }); }); } },'json') .fail(function() { OC.Settings.Apps.showErrorMessage(appId, t('settings', 'Error while enabling app')); - appItem.data('errormsg', t('settings', 'Error while enabling app')); - appItem.data('active',false); - appItem.addClass('appwarning'); - element.val(t('settings','Enable')); + appItems.forEach(function(appItem) { + appItem.data('errormsg', t('settings', 'Error while enabling app')); + appItem.data('active', false); + appItem.addClass('appwarning'); + }); + elements.forEach(function(element) { + element.val(t('settings', 'Enable')); + }); }); } }, @@ -774,10 +878,17 @@ OC.Settings.Apps = OC.Settings.Apps || { $(document).on('click', '#apps-list input.enable', function () { var appId = $(this).data('appid'); + var bundleId = $(this).data('bundleid'); var element = $(this); var active = $(this).data('active'); - OC.Settings.Apps.enableApp(appId, active, element); + var category = $('#app-navigation').attr('data-category'); + if(bundleId) { + OC.Settings.Apps.enableAppBundle(bundleId, active, element); + element.val(t('settings', 'Enable all')); + } else { + OC.Settings.Apps.enableApp([appId], active); + } }); $(document).on('click', '#apps-list input.uninstall', function () { @@ -805,7 +916,7 @@ OC.Settings.Apps = OC.Settings.Apps || { var appId = element.data('appid'); if (appId) { - OC.Settings.Apps.enableApp(appId, false, element, groups); + OC.Settings.Apps.enableApp([appId], false, groups); OC.Settings.Apps.State.apps[appId].groups = groups; } }); diff --git a/settings/templates/apps.php b/settings/templates/apps.php index 310513722cf..260b042c078 100644 --- a/settings/templates/apps.php +++ b/settings/templates/apps.php @@ -29,8 +29,17 @@ script( -