diff options
-rw-r--r-- | config/config.sample.php | 9 | ||||
-rw-r--r-- | core/css/styles.css | 2 | ||||
-rw-r--r-- | core/templates/layout.user.php | 2 | ||||
-rw-r--r-- | lib/private/app.php | 110 | ||||
-rw-r--r-- | lib/private/app/appmanager.php | 2 | ||||
-rw-r--r-- | lib/private/installer.php | 17 | ||||
-rw-r--r-- | lib/private/ocsclient.php | 263 | ||||
-rw-r--r-- | lib/private/server.php | 14 | ||||
-rw-r--r-- | lib/private/templatelayout.php | 2 | ||||
-rw-r--r-- | lib/public/app/iappmanager.php | 5 | ||||
-rw-r--r-- | settings/application.php | 14 | ||||
-rw-r--r-- | settings/apps.php | 42 | ||||
-rw-r--r-- | settings/controller/appsettingscontroller.php | 75 | ||||
-rw-r--r-- | settings/css/settings.css | 67 | ||||
-rw-r--r-- | settings/js/apps.js | 41 | ||||
-rw-r--r-- | settings/routes.php | 40 | ||||
-rw-r--r-- | settings/templates/apps.php | 62 | ||||
-rw-r--r-- | settings/tests/js/appsSpec.js | 54 | ||||
-rw-r--r-- | tests/lib/OCSClientTest.php | 934 | ||||
-rw-r--r-- | tests/settings/controller/AppSettingsControllerTest.php | 231 |
20 files changed, 1719 insertions, 267 deletions
diff --git a/config/config.sample.php b/config/config.sample.php index 29f7c2ad55a..e3b81f69f6b 100644 --- a/config/config.sample.php +++ b/config/config.sample.php @@ -577,6 +577,15 @@ $CONFIG = array( 'appstoreurl' => 'https://api.owncloud.com/v1', /** + * Whether to show experimental apps in the appstore interface + * + * Experimental apps are not checked for security issues and are new or known + * to be unstable and under heavy development. Installing these can cause data + * loss or security breaches. + */ +'appstore.experimental.enabled' => false, + +/** * Use the ``apps_paths`` parameter to set the location of the Apps directory, * which should be scanned for available apps, and where user-specific apps * should be installed from the Apps store. The ``path`` defines the absolute diff --git a/core/css/styles.css b/core/css/styles.css index cbce78c525b..b1df8d1a8a2 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -670,7 +670,7 @@ label.infield { } .warning-input { - border-color: #cc3333 !important; + border-color: #ce3702 !important; } /* Fixes for log in page, TODO should be removed some time */ diff --git a/core/templates/layout.user.php b/core/templates/layout.user.php index 880a276c725..87a6a9216d2 100644 --- a/core/templates/layout.user.php +++ b/core/templates/layout.user.php @@ -123,7 +123,7 @@ if(OC_User::isAdminUser(OC_User::getUser())): ?> <li id="apps-management"> - <a href="<?php print_unescaped(OC_Helper::linkToRoute('settings_apps')); ?>" tabindex="4" + <a href="<?php print_unescaped(\OC::$server->getURLGenerator()->linkToRoute('settings.AppSettings.viewApps')); ?>" tabindex="4" <?php if( $_['appsmanagement_active'] ): ?> class="active"<?php endif; ?>> <img class="app-icon svg" alt="" src="<?php print_unescaped(OC_Helper::imagePath('settings', 'apps.svg')); ?>"> <div class="icon-loading-dark" style="display:none;"></div> diff --git a/lib/private/app.php b/lib/private/app.php index 4b3d4b82b82..aec67e6efd6 100644 --- a/lib/private/app.php +++ b/lib/private/app.php @@ -61,6 +61,7 @@ class OC_App { static private $loadedApps = array(); static private $altLogin = array(); private static $shippedApps = null; + const officialApp = 200; /** * clean the appId @@ -306,8 +307,13 @@ class OC_App { * @return int */ public static function downloadApp($app) { - $appData= OCSClient::getApplication($app); - $download= OCSClient::getApplicationDownload($app, 1); + $ocsClient = new OCSClient( + \OC::$server->getHTTPClientService(), + \OC::$server->getConfig(), + \OC::$server->getLogger() + ); + $appData = $ocsClient->getApplication($app); + $download= $ocsClient->getApplicationDownload($app); if(isset($download['downloadlink']) and $download['downloadlink']!='') { // Replace spaces in download link without encoding entire URL $download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']); @@ -507,7 +513,7 @@ class OC_App { /** * search for an app in all app-directories * - * @param $appId + * @param string $appId * @return mixed (bool|string) */ protected static function findAppInDirectories($appId) { @@ -726,6 +732,8 @@ class OC_App { /** * register a personal form to be shown + * @param string $app + * @param string $page */ public static function registerPersonal($app, $page) { self::$personalForms[] = $app . '/' . $page . '.php'; @@ -780,8 +788,9 @@ class OC_App { } /** - * Lists all apps, this is used in apps.php + * List all apps, this is used in apps.php * + * @param bool $onlyLocal * @return array */ public static function listAllApps($onlyLocal = false) { @@ -819,8 +828,7 @@ class OC_App { if (isset($info['shipped']) and ($info['shipped'] == 'true')) { $info['internal'] = true; - $info['internallabel'] = (string)$l->t('Recommended'); - $info['internalclass'] = 'recommendedapp'; + $info['level'] = self::officialApp; $info['removable'] = false; } else { $info['internal'] = false; @@ -845,7 +853,7 @@ class OC_App { } } if ($onlyLocal) { - $remoteApps = array(); + $remoteApps = []; } else { $remoteApps = OC_App::getAppstoreApps(); } @@ -865,34 +873,6 @@ class OC_App { } else { $combinedApps = $appList; } - // bring the apps into the right order with a custom sort function - usort($combinedApps, function ($a, $b) { - - // priority 1: active - if ($a['active'] != $b['active']) { - return $b['active'] - $a['active']; - } - - // priority 2: shipped - $aShipped = (array_key_exists('shipped', $a) && $a['shipped'] === 'true') ? 1 : 0; - $bShipped = (array_key_exists('shipped', $b) && $b['shipped'] === 'true') ? 1 : 0; - if ($aShipped !== $bShipped) { - return ($bShipped - $aShipped); - } - - // priority 3: recommended - $internalClassA = isset($a['internalclass']) ? $a['internalclass'] : ''; - $internalClassB = isset($b['internalclass']) ? $b['internalclass'] : ''; - if ($internalClassA != $internalClassB) { - $aTemp = ($internalClassA == 'recommendedapp' ? 1 : 0); - $bTemp = ($internalClassB == 'recommendedapp' ? 1 : 0); - return ($bTemp - $aTemp); - } - - // priority 4: alphabetical - return strcasecmp($a['name'], $b['name']); - - }); return $combinedApps; } @@ -913,15 +893,24 @@ class OC_App { } /** - * get a list of all apps on apps.owncloud.com - * - * @return array|false multi-dimensional array of apps. - * Keys: id, name, type, typename, personid, license, detailpage, preview, changed, description + * Get a list of all apps on the appstore + * @param string $filter + * @param string $category + * @return array|bool multi-dimensional array of apps. + * Keys: id, name, type, typename, personid, license, detailpage, preview, changed, description */ public static function getAppstoreApps($filter = 'approved', $category = null) { - $categories = array($category); + $categories = [$category]; + + $ocsClient = new OCSClient( + \OC::$server->getHTTPClientService(), + \OC::$server->getConfig(), + \OC::$server->getLogger() + ); + + if (is_null($category)) { - $categoryNames = OCSClient::getCategories(); + $categoryNames = $ocsClient->getCategories(); if (is_array($categoryNames)) { // Check that categories of apps were retrieved correctly if (!$categories = array_keys($categoryNames)) { @@ -933,34 +922,36 @@ class OC_App { } $page = 0; - $remoteApps = OCSClient::getApplications($categories, $page, $filter); - $app1 = array(); + $remoteApps = $ocsClient->getApplications($categories, $page, $filter); + $apps = []; $i = 0; $l = \OC::$server->getL10N('core'); foreach ($remoteApps as $app) { $potentialCleanId = self::getInternalAppIdByOcs($app['id']); // enhance app info (for example the description) - $app1[$i] = OC_App::parseAppInfo($app); - $app1[$i]['author'] = $app['personid']; - $app1[$i]['ocs_id'] = $app['id']; - $app1[$i]['internal'] = 0; - $app1[$i]['active'] = ($potentialCleanId !== false) ? self::isEnabled($potentialCleanId) : false; - $app1[$i]['update'] = false; - $app1[$i]['groups'] = false; - $app1[$i]['score'] = $app['score']; - $app1[$i]['removable'] = false; + $apps[$i] = OC_App::parseAppInfo($app); + $apps[$i]['author'] = $app['personid']; + $apps[$i]['ocs_id'] = $app['id']; + $apps[$i]['internal'] = 0; + $apps[$i]['active'] = ($potentialCleanId !== false) ? self::isEnabled($potentialCleanId) : false; + $apps[$i]['update'] = false; + $apps[$i]['groups'] = false; + $apps[$i]['score'] = $app['score']; + $apps[$i]['removable'] = false; if ($app['label'] == 'recommended') { - $app1[$i]['internallabel'] = (string)$l->t('Recommended'); - $app1[$i]['internalclass'] = 'recommendedapp'; + $apps[$i]['internallabel'] = (string)$l->t('Recommended'); + $apps[$i]['internalclass'] = 'recommendedapp'; } $i++; } - if (empty($app1)) { + + + if (empty($apps)) { return false; } else { - return $app1; + return $apps; } } @@ -1084,7 +1075,12 @@ class OC_App { public static function installApp($app) { $l = \OC::$server->getL10N('core'); $config = \OC::$server->getConfig(); - $appData=OCSClient::getApplication($app); + $ocsClient = new OCSClient( + \OC::$server->getHTTPClientService(), + $config, + \OC::$server->getLogger() + ); + $appData = $ocsClient->getApplication($app); // check if app is a shipped app or not. OCS apps have an integer as id, shipped apps use a string if (!is_numeric($app)) { diff --git a/lib/private/app/appmanager.php b/lib/private/app/appmanager.php index 2a147d4de6f..c9d4a777c4a 100644 --- a/lib/private/app/appmanager.php +++ b/lib/private/app/appmanager.php @@ -203,7 +203,7 @@ class AppManager implements IAppManager { /** * Clear the cached list of apps when enabling/disabling an app */ - protected function clearAppsCache() { + public function clearAppsCache() { $settingsMemCache = $this->memCacheFactory->create('settings'); $settingsMemCache->clear('listApps'); } diff --git a/lib/private/installer.php b/lib/private/installer.php index e30344b1b10..41f13f0f5f9 100644 --- a/lib/private/installer.php +++ b/lib/private/installer.php @@ -222,8 +222,13 @@ class OC_Installer{ * @throws Exception */ public static function updateAppByOCSId($ocsId) { - $appData = OCSClient::getApplication($ocsId); - $download = OCSClient::getApplicationDownload($ocsId, 1); + $ocsClient = new OCSClient( + \OC::$server->getHTTPClientService(), + \OC::$server->getConfig(), + \OC::$server->getLogger() + ); + $appData = $ocsClient->getApplication($ocsId); + $download = $ocsClient->getApplicationDownload($ocsId); if (isset($download['downloadlink']) && trim($download['downloadlink']) !== '') { $download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']); @@ -385,8 +390,12 @@ class OC_Installer{ $ocsid=OC_Appconfig::getValue( $app, 'ocsid', ''); if($ocsid<>'') { - - $ocsdata=OCSClient::getApplication($ocsid); + $ocsClient = new OCSClient( + \OC::$server->getHTTPClientService(), + \OC::$server->getConfig(), + \OC::$server->getLogger() + ); + $ocsdata = $ocsClient->getApplication($ocsid); $ocsversion= (string) $ocsdata['version']; $currentversion=OC_App::getAppVersion($app); if (version_compare($ocsversion, $currentversion, '>')) { diff --git a/lib/private/ocsclient.php b/lib/private/ocsclient.php index f69426ddafe..84469cb5e0d 100644 --- a/lib/private/ocsclient.php +++ b/lib/private/ocsclient.php @@ -32,36 +32,75 @@ namespace OC; +use OCP\Http\Client\IClientService; +use OCP\IConfig; +use OCP\ILogger; + /** - * This class provides an easy way for apps to store config values in the - * database. + * Class OCSClient is a class for communication with the ownCloud appstore + * + * @package OC */ - class OCSClient { + /** @var IClientService */ + private $httpClientService; + /** @var IConfig */ + private $config; + /** @var ILogger */ + private $logger; + + /** + * @param IClientService $httpClientService + * @param IConfig $config + * @param ILogger $logger + */ + public function __construct(IClientService $httpClientService, + IConfig $config, + ILogger $logger) { + $this->httpClientService = $httpClientService; + $this->config = $config; + $this->logger = $logger; + } /** * Returns whether the AppStore is enabled (i.e. because the AppStore is disabled for EE) * * @return bool */ - public static function isAppStoreEnabled() { - if (\OC::$server->getConfig()->getSystemValue('appstoreenabled', true) === false ) { - return false; - } - - return true; + public function isAppStoreEnabled() { + return $this->config->getSystemValue('appstoreenabled', true) === true; } /** * Get the url of the OCS AppStore server. * * @return string of the AppStore server - * - * This function returns the url of the OCS AppStore server. It´s possible - * to set it in the config file or it will fallback to the default */ - private static function getAppStoreURL() { - return \OC::$server->getConfig()->getSystemValue('appstoreurl', 'https://api.owncloud.com/v1'); + private function getAppStoreUrl() { + return $this->config->getSystemValue('appstoreurl', 'https://api.owncloud.com/v1'); + } + + /** + * @param string $body + * @param string $action + * @return null|\SimpleXMLElement + */ + private function loadData($body, $action) { + $loadEntities = libxml_disable_entity_loader(true); + $data = @simplexml_load_string($body); + libxml_disable_entity_loader($loadEntities); + + if($data === false) { + $this->logger->error( + sprintf('Could not get %s, content was no valid XML', $action), + [ + 'app' => 'core', + ] + ); + return null; + } + + return $data; } /** @@ -71,36 +110,41 @@ class OCSClient { * @note returns NULL if config value appstoreenabled is set to false * This function returns a list of all the application categories on the OCS server */ - public static function getCategories() { - if (!self::isAppStoreEnabled()) { + public function getCategories() { + if (!$this->isAppStoreEnabled()) { return null; } - $url = self::getAppStoreURL() . '/content/categories'; - $client = \OC::$server->getHTTPClientService()->newClient(); + $client = $this->httpClientService->newClient(); try { - $response = $client->get($url, ['timeout' => 5]); + $response = $client->get( + $this->getAppStoreUrl() . '/content/categories', + [ + 'timeout' => 5, + ] + ); } catch(\Exception $e) { + $this->logger->error( + sprintf('Could not get categories: %s', $e->getMessage()), + [ + 'app' => 'core', + ] + ); return null; } - if($response->getStatusCode() !== 200) { + $data = $this->loadData($response->getBody(), 'categories'); + if($data === null) { return null; } - $loadEntities = libxml_disable_entity_loader(true); - $data = simplexml_load_string($response->getBody()); - libxml_disable_entity_loader($loadEntities); - $tmp = $data->data; $cats = []; foreach ($tmp->category as $value) { - $id = (int)$value->id; $name = (string)$value->name; $cats[$id] = $name; - } return $cats; @@ -108,50 +152,54 @@ class OCSClient { /** * Get all the applications from the OCS server - * - * @return array|null an array of application data or null - * - * This function returns a list of all the applications on the OCS server - * @param array|string $categories + * @param array $categories * @param int $page * @param string $filter + * @return array An array of application data */ - public static function getApplications($categories, $page, $filter) { - if (!self::isAppStoreEnabled()) { - return (array()); + public function getApplications(array $categories, $page, $filter) { + if (!$this->isAppStoreEnabled()) { + return []; } - if (is_array($categories)) { - $categoriesString = implode('x', $categories); - } else { - $categoriesString = $categories; - } - - $version = '&version=' . implode('x', \OC_Util::getVersion()); - $filterUrl = '&filter=' . urlencode($filter); - $url = self::getAppStoreURL() . '/content/data?categories=' . urlencode($categoriesString) - . '&sortmode=new&page=' . urlencode($page) . '&pagesize=100' . $filterUrl . $version; - $apps = []; - - $client = \OC::$server->getHTTPClientService()->newClient(); + $client = $this->httpClientService->newClient(); try { - $response = $client->get($url, ['timeout' => 5]); + $response = $client->get( + $this->getAppStoreUrl() . '/content/data', + [ + 'timeout' => 5, + 'query' => [ + 'version' => implode('x', \OC_Util::getVersion()), + 'filter' => $filter, + 'categories' => implode('x', $categories), + 'sortmode' => 'new', + 'page' => $page, + 'pagesize' => 100, + 'approved' => $filter + ], + ] + ); } catch(\Exception $e) { - return null; + $this->logger->error( + sprintf('Could not get applications: %s', $e->getMessage()), + [ + 'app' => 'core', + ] + ); + return []; } - if($response->getStatusCode() !== 200) { - return null; + $data = $this->loadData($response->getBody(), 'applications'); + if($data === null) { + return []; } - $loadEntities = libxml_disable_entity_loader(true); - $data = simplexml_load_string($response->getBody()); - libxml_disable_entity_loader($loadEntities); - $tmp = $data->data->content; $tmpCount = count($tmp); + + $apps = []; for ($i = 0; $i < $tmpCount; $i++) { - $app = array(); + $app = []; $app['id'] = (string)$tmp[$i]->id; $app['name'] = (string)$tmp[$i]->name; $app['label'] = (string)$tmp[$i]->label; @@ -159,6 +207,7 @@ class OCSClient { $app['type'] = (string)$tmp[$i]->typeid; $app['typename'] = (string)$tmp[$i]->typename; $app['personid'] = (string)$tmp[$i]->personid; + $app['profilepage'] = (string)$tmp[$i]->profilepage; $app['license'] = (string)$tmp[$i]->license; $app['detailpage'] = (string)$tmp[$i]->detailpage; $app['preview'] = (string)$tmp[$i]->smallpreviewpic1; @@ -167,9 +216,11 @@ class OCSClient { $app['description'] = (string)$tmp[$i]->description; $app['score'] = (string)$tmp[$i]->score; $app['downloads'] = (int)$tmp[$i]->downloads; + $app['level'] = (int)$tmp[$i]->approved; $apps[] = $app; } + return $apps; } @@ -182,84 +233,94 @@ class OCSClient { * * This function returns an applications from the OCS server */ - public static function getApplication($id) { - if (!self::isAppStoreEnabled()) { + public function getApplication($id) { + if (!$this->isAppStoreEnabled()) { return null; } - $url = self::getAppStoreURL() . '/content/data/' . urlencode($id); - $client = \OC::$server->getHTTPClientService()->newClient(); + + $client = $this->httpClientService->newClient(); try { - $response = $client->get($url, ['timeout' => 5]); + $response = $client->get( + $this->getAppStoreUrl() . '/content/data/' . urlencode($id), + [ + 'timeout' => 5, + ] + ); } catch(\Exception $e) { + $this->logger->error( + sprintf('Could not get application: %s', $e->getMessage()), + [ + 'app' => 'core', + ] + ); return null; } - if($response->getStatusCode() !== 200) { + $data = $this->loadData($response->getBody(), 'application'); + if($data === null) { return null; } - $loadEntities = libxml_disable_entity_loader(true); - $data = simplexml_load_string($response->getBody()); - libxml_disable_entity_loader($loadEntities); - $tmp = $data->data->content; - if (is_null($tmp)) { - \OC_Log::write('core', 'Invalid OCS content returned for app ' . $id, \OC_Log::FATAL); - return null; - } + $app = []; - $app['id'] = $tmp->id; - $app['name'] = $tmp->name; - $app['version'] = $tmp->version; - $app['type'] = $tmp->typeid; - $app['label'] = $tmp->label; - $app['typename'] = $tmp->typename; - $app['personid'] = $tmp->personid; - $app['detailpage'] = $tmp->detailpage; - $app['preview1'] = $tmp->smallpreviewpic1; - $app['preview2'] = $tmp->smallpreviewpic2; - $app['preview3'] = $tmp->smallpreviewpic3; + $app['id'] = (int)$tmp->id; + $app['name'] = (string)$tmp->name; + $app['version'] = (string)$tmp->version; + $app['type'] = (string)$tmp->typeid; + $app['label'] = (string)$tmp->label; + $app['typename'] = (string)$tmp->typename; + $app['personid'] = (string)$tmp->personid; + $app['profilepage'] = (string)$tmp->profilepage; + $app['detailpage'] = (string)$tmp->detailpage; + $app['preview1'] = (string)$tmp->smallpreviewpic1; + $app['preview2'] = (string)$tmp->smallpreviewpic2; + $app['preview3'] = (string)$tmp->smallpreviewpic3; $app['changed'] = strtotime($tmp->changed); - $app['description'] = $tmp->description; - $app['detailpage'] = $tmp->detailpage; - $app['score'] = $tmp->score; + $app['description'] = (string)$tmp->description; + $app['detailpage'] = (string)$tmp->detailpage; + $app['score'] = (int)$tmp->score; return $app; } /** * Get the download url for an application from the OCS server - * + * @param $id * @return array|null an array of application data or null - * - * This function returns an download url for an applications from the OCS server - * @param string $id - * @param integer $item */ - public static function getApplicationDownload($id, $item) { - if (!self::isAppStoreEnabled()) { + public function getApplicationDownload($id) { + if (!$this->isAppStoreEnabled()) { return null; } - $url = self::getAppStoreURL() . '/content/download/' . urlencode($id) . '/' . urlencode($item); - $client = \OC::$server->getHTTPClientService()->newClient(); + $url = $this->getAppStoreUrl() . '/content/download/' . urlencode($id) . '/1'; + $client = $this->httpClientService->newClient(); try { - $response = $client->get($url, ['timeout' => 5]); + $response = $client->get( + $url, + [ + 'timeout' => 5, + ] + ); } catch(\Exception $e) { + $this->logger->error( + sprintf('Could not get application download URL: %s', $e->getMessage()), + [ + 'app' => 'core', + ] + ); return null; } - if($response->getStatusCode() !== 200) { + $data = $this->loadData($response->getBody(), 'application download URL'); + if($data === null) { return null; } - $loadEntities = libxml_disable_entity_loader(true); - $data = simplexml_load_string($response->getBody()); - libxml_disable_entity_loader($loadEntities); - $tmp = $data->data->content; - $app = array(); + $app = []; if (isset($tmp->downloadlink)) { - $app['downloadlink'] = $tmp->downloadlink; + $app['downloadlink'] = (string)$tmp->downloadlink; } else { $app['downloadlink'] = ''; } diff --git a/lib/private/server.php b/lib/private/server.php index 6b35998afa8..6df7722973e 100644 --- a/lib/private/server.php +++ b/lib/private/server.php @@ -400,6 +400,13 @@ class Server extends SimpleContainer implements IServerContainer { new \OC_Defaults() ); }); + $this->registerService('OcsClient', function(Server $c) { + return new OCSClient( + $this->getHTTPClientService(), + $this->getConfig(), + $this->getLogger() + ); + }); } /** @@ -841,6 +848,13 @@ class Server extends SimpleContainer implements IServerContainer { } /** + * @return \OC\OCSClient + */ + public function getOcsClient() { + return $this->query('OcsClient'); + } + + /** * @return \OCP\IDateTimeZone */ public function getDateTimeZone() { diff --git a/lib/private/templatelayout.php b/lib/private/templatelayout.php index ee1412fba74..448276ca7fe 100644 --- a/lib/private/templatelayout.php +++ b/lib/private/templatelayout.php @@ -107,7 +107,7 @@ class OC_TemplateLayout extends OC_Template { $userDisplayName = OC_User::getDisplayName(); $this->assign('user_displayname', $userDisplayName); $this->assign('user_uid', OC_User::getUser()); - $this->assign('appsmanagement_active', strpos(\OC::$server->getRequest()->getRequestUri(), OC_Helper::linkToRoute('settings_apps')) === 0 ); + $this->assign('appsmanagement_active', strpos(\OC::$server->getRequest()->getRequestUri(), \OC::$server->getURLGenerator()->linkToRoute('settings.AppSettings.viewApps')) === 0 ); $this->assign('enableAvatars', $this->config->getSystemValue('enable_avatars', true)); $this->assign('userAvatarSet', \OC_Helper::userAvatarSet(OC_User::getUser())); } else if ($renderAs == 'error') { diff --git a/lib/public/app/iappmanager.php b/lib/public/app/iappmanager.php index f50a7f64174..69b8c335d67 100644 --- a/lib/public/app/iappmanager.php +++ b/lib/public/app/iappmanager.php @@ -78,4 +78,9 @@ interface IAppManager { * @return string[] */ public function getInstalledApps(); + + /** + * Clear the cached list of apps when enabling/disabling an app + */ + public function clearAppsCache(); } diff --git a/settings/application.php b/settings/application.php index eb8f0f7a999..be127da31ac 100644 --- a/settings/application.php +++ b/settings/application.php @@ -71,7 +71,10 @@ class Application extends App { $c->query('Request'), $c->query('L10N'), $c->query('Config'), - $c->query('ICacheFactory') + $c->query('ICacheFactory'), + $c->query('INavigationManager'), + $c->query('IAppManager'), + $c->query('OcsClient') ); }); $container->registerService('SecuritySettingsController', function(IContainer $c) { @@ -192,6 +195,15 @@ class Application extends App { $container->registerService('ClientService', function(IContainer $c) { return $c->query('ServerContainer')->getHTTPClientService(); }); + $container->registerService('INavigationManager', function(IContainer $c) { + return $c->query('ServerContainer')->getNavigationManager(); + }); + $container->registerService('IAppManager', function(IContainer $c) { + return $c->query('ServerContainer')->getAppManager(); + }); + $container->registerService('OcsClient', function(IContainer $c) { + return $c->query('ServerContainer')->getOcsClient(); + }); $container->registerService('Util', function(IContainer $c) { return new \OC_Util(); }); diff --git a/settings/apps.php b/settings/apps.php deleted file mode 100644 index 7245b6610e0..00000000000 --- a/settings/apps.php +++ /dev/null @@ -1,42 +0,0 @@ -<?php -/** - * @author Bart Visscher <bartv@thisnet.nl> - * @author Frank Karlitschek <frank@owncloud.org> - * @author Jan-Christoph Borchardt <hey@jancborchardt.net> - * @author Lukas Reschke <lukas@owncloud.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <icewind@owncloud.com> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ - -OC_Util::checkAdminUser(); -\OC::$server->getSession()->close(); - -// Load the files we need -\OC_Util::addVendorScript('handlebars/handlebars'); -\OCP\Util::addScript("settings", "settings"); -\OCP\Util::addStyle("settings", "settings"); -\OC_Util::addVendorScript('select2/select2'); -\OC_Util::addVendorStyle('select2/select2'); -\OCP\Util::addScript("settings", "apps"); -\OC_App::setActiveNavigationEntry( "core_apps" ); - -$tmpl = new OC_Template( "settings", "apps", "user" ); -$tmpl->printPage(); - diff --git a/settings/controller/appsettingscontroller.php b/settings/controller/appsettingscontroller.php index 9a85f6d3b97..f1b62bb1d38 100644 --- a/settings/controller/appsettingscontroller.php +++ b/settings/controller/appsettingscontroller.php @@ -27,8 +27,13 @@ namespace OC\Settings\Controller; use OC\App\DependencyAnalyzer; use OC\App\Platform; use OC\OCSClient; +use OCP\App\IAppManager; use \OCP\AppFramework\Controller; +use OCP\AppFramework\Http\ContentSecurityPolicy; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http\TemplateResponse; use OCP\ICacheFactory; +use OCP\INavigationManager; use OCP\IRequest; use OCP\IL10N; use OCP\IConfig; @@ -44,6 +49,12 @@ class AppSettingsController extends Controller { private $config; /** @var \OCP\ICache */ private $cache; + /** @var INavigationManager */ + private $navigationManager; + /** @var IAppManager */ + private $appManager; + /** @var OCSClient */ + private $ocsClient; /** * @param string $appName @@ -51,16 +62,53 @@ class AppSettingsController extends Controller { * @param IL10N $l10n * @param IConfig $config * @param ICacheFactory $cache + * @param INavigationManager $navigationManager + * @param IAppManager $appManager + * @param OCSClient $ocsClient */ public function __construct($appName, IRequest $request, IL10N $l10n, IConfig $config, - ICacheFactory $cache) { + ICacheFactory $cache, + INavigationManager $navigationManager, + IAppManager $appManager, + OCSClient $ocsClient) { parent::__construct($appName, $request); $this->l10n = $l10n; $this->config = $config; $this->cache = $cache->create($appName); + $this->navigationManager = $navigationManager; + $this->appManager = $appManager; + $this->ocsClient = $ocsClient; + } + + /** + * Enables or disables the display of experimental apps + * @param bool $state + * @return DataResponse + */ + public function changeExperimentalConfigState($state) { + $this->config->setSystemValue('appstore.experimental.enabled', $state); + $this->appManager->clearAppsCache(); + return new DataResponse(); + } + + /** + * @NoCSRFRequired + * @return TemplateResponse + */ + public function viewApps() { + $params = []; + $params['experimentalEnabled'] = $this->config->getSystemValue('appstore.experimental.enabled', false); + $this->navigationManager->setActiveEntry('core_apps'); + + $templateResponse = new TemplateResponse($this->appName, 'apps', $params, 'user'); + $policy = new ContentSecurityPolicy(); + $policy->addAllowedImageDomain('https://apps.owncloud.com'); + $templateResponse->setContentSecurityPolicy($policy); + + return $templateResponse; } /** @@ -77,16 +125,15 @@ class AppSettingsController extends Controller { ['id' => 1, 'displayName' => (string)$this->l10n->t('Not enabled')], ]; - if(OCSClient::isAppStoreEnabled()) { - $categories[] = ['id' => 2, 'displayName' => (string)$this->l10n->t('Recommended')]; + if($this->ocsClient->isAppStoreEnabled()) { // apps from external repo via OCS - $ocs = OCSClient::getCategories(); + $ocs = $this->ocsClient->getCategories(); if ($ocs) { foreach($ocs as $k => $v) { - $categories[] = array( + $categories[] = [ 'id' => $k, 'displayName' => str_replace('ownCloud ', '', $v) - ); + ]; } } } @@ -97,7 +144,8 @@ class AppSettingsController extends Controller { } /** - * Get all available categories + * Get all available apps in a category + * * @param int $category * @return array */ @@ -134,16 +182,9 @@ class AppSettingsController extends Controller { }); break; default: - if ($category === 2) { - $apps = \OC_App::getAppstoreApps('approved'); - if ($apps) { - $apps = array_filter($apps, function ($app) { - return isset($app['internalclass']) && $app['internalclass'] === 'recommendedapp'; - }); - } - } else { - $apps = \OC_App::getAppstoreApps('approved', $category); - } + $filter = $this->config->getSystemValue('appstore.experimental.enabled', false) ? 'all' : 'approved'; + + $apps = \OC_App::getAppstoreApps($filter, $category); if (!$apps) { $apps = array(); } else { diff --git a/settings/css/settings.css b/settings/css/settings.css index 3bc0a442f06..426fdbb8b94 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -203,22 +203,52 @@ input.userFilter {width: 200px;} background: #fbb; } -.recommendedapp { - font-size: 11px; - background-position: left center; - padding-left: 18px; - vertical-align: top; +span.version { + margin-left: 1em; + margin-right: 1em; + color: #555; } -span.version { margin-left:1em; margin-right:1em; color:#555; } #app-navigation .app-external, -.app-version, -.recommendedapp { +.app-version { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; filter: alpha(opacity=50); opacity: .5; } +.app-level { + margin-top: 8px; +} +.app-level span { + color: #555; + background-color: transparent; + border: 1px solid #555; + border-radius: 3px; + padding: 3px 6px; +} +.app-level .official { + border-color: #37ce02; + background-position: left center; + background-position: 5px center; + padding-left: 25px; +} +.app-level .approved { + border-color: #e8c805; +} +.app-level .experimental { + background-color: #ce3702; + border-color: #ce3702; + color: #fff; +} +.apps-experimental { + color: #ce3702; +} + +.app-score { + position: relative; + top: 4px; +} + #apps-list { position: relative; height: 100%; @@ -226,6 +256,9 @@ span.version { margin-left:1em; margin-right:1em; color:#555; } .section { position: relative; } +.section h2.app-name { + margin-bottom: 8px; +} .app-image { float: left; padding-right: 10px; @@ -245,7 +278,7 @@ span.version { margin-left:1em; margin-right:1em; color:#555; } .app-name, .app-version, .app-score, -.recommendedapp { +.app-level { display: inline-block; } @@ -270,13 +303,17 @@ span.version { margin-left:1em; margin-right:1em; color:#555; } white-space: pre-line; } -#app-category-2 { +#app-category-1 { border-bottom: 1px solid #e8e8e8; } +/* capitalize "Other" category */ +#app-category-925 { + text-transform: capitalize; +} .app-dependencies { margin-top: 10px; - color: #c33; + color: #ce3702; } .missing-dependencies { @@ -311,7 +348,7 @@ table.grid td.date{ #security-warning li { list-style: initial; margin: 10px 0; - color: #c33; + color: #ce3702; } #shareAPI p { padding-bottom: 0.8em; } #shareAPI input#shareapiExpireAfterNDays {width: 25px;} @@ -362,12 +399,12 @@ table.grid td.date{ } span.success { - background: #37ce02; - border-radius: 3px; + background: #37ce02; + border-radius: 3px; } span.error { - background: #ce3702; + background: #ce3702; } diff --git a/settings/js/apps.js b/settings/js/apps.js index 3db84e8acd5..1bd7ffdf790 100644 --- a/settings/js/apps.js +++ b/settings/js/apps.js @@ -9,6 +9,17 @@ Handlebars.registerHelper('score', function() { } return new Handlebars.SafeString(''); }); +Handlebars.registerHelper('level', function() { + if(typeof this.level !== 'undefined') { + if(this.level === 200) { + return new Handlebars.SafeString('<span class="official icon-checkmark">Official</span>'); + } else if(this.level === 100) { + return new Handlebars.SafeString('<span class="approved">Approved</span>'); + } else { + return new Handlebars.SafeString('<span class="experimental">Experimental</span>'); + } + } +}); OC.Settings = OC.Settings || {}; OC.Settings.Apps = OC.Settings.Apps || { @@ -73,7 +84,6 @@ OC.Settings.Apps = OC.Settings.Apps || { this._loadCategoryCall = $.ajax(OC.generateUrl('settings/apps/list?category={categoryId}', { categoryId: categoryId }), { - data:{}, type:'GET', success: function (apps) { OC.Settings.Apps.State.apps = _.indexBy(apps.apps, 'id'); @@ -81,13 +91,27 @@ OC.Settings.Apps = OC.Settings.Apps || { var template = Handlebars.compile(source); if (apps.apps.length) { + apps.apps.sort(function(a,b) { + return b.level - a.level; + }); + + var firstExperimental = false; _.each(apps.apps, function(app) { - OC.Settings.Apps.renderApp(app, template, null); + if(app.level === 0 && firstExperimental === false) { + firstExperimental = true; + OC.Settings.Apps.renderApp(app, template, null, true); + } else { + OC.Settings.Apps.renderApp(app, template, null, false); + } }); } else { $('#apps-list').addClass('hidden'); $('#apps-list-empty').removeClass('hidden'); } + + $('.app-level .official').tipsy({fallback: t('core', 'Official apps are developed by and within the ownCloud community. They offer functionality central to ownCloud and are ready for production use.')}); + $('.app-level .approved').tipsy({fallback: t('core', 'Approved apps are developed by trusted developers and have passed a cursory security check. They are actively maintained in an open code repository and their maintainers deem them to be stable for casual to normal use.')}); + $('.app-level .experimental').tipsy({fallback: t('core', 'This app is not checked for security issues and is new or known to be unstable. Install on your own risk.')}); }, complete: function() { $('#apps-list').removeClass('icon-loading'); @@ -95,7 +119,7 @@ OC.Settings.Apps = OC.Settings.Apps || { }); }, - renderApp: function(app, template, selector) { + renderApp: function(app, template, selector, firstExperimental) { if (!template) { var source = $("#app-template").html(); template = Handlebars.compile(source); @@ -103,6 +127,7 @@ OC.Settings.Apps = OC.Settings.Apps || { if (typeof app === 'string') { app = OC.Settings.Apps.State.apps[app]; } + app.firstExperimental = firstExperimental; var html = template(app); if (selector) { @@ -438,6 +463,16 @@ OC.Settings.Apps = OC.Settings.Apps || { $select.change(); }); + $(document).on('click', '#enable-experimental-apps', function () { + var state = $(this).prop('checked') + $.ajax(OC.generateUrl('settings/apps/experimental'), { + data: {state: state}, + type: 'POST', + success:function () { + location.reload(); + } + }); + }); } }; diff --git a/settings/routes.php b/settings/routes.php index af9ac1d8eea..1bb14812145 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -33,25 +33,27 @@ namespace OC\Settings; $application = new Application(); -$application->registerRoutes($this, array( - 'resources' => array( - 'groups' => array('url' => '/settings/users/groups'), - 'users' => array('url' => '/settings/users/users') - ), - 'routes' => array( - array('name' => 'MailSettings#setMailSettings', 'url' => '/settings/admin/mailsettings', 'verb' => 'POST'), - array('name' => 'MailSettings#storeCredentials', 'url' => '/settings/admin/mailsettings/credentials', 'verb' => 'POST'), - array('name' => 'MailSettings#sendTestMail', 'url' => '/settings/admin/mailtest', 'verb' => 'POST'), - array('name' => 'AppSettings#listCategories', 'url' => '/settings/apps/categories', 'verb' => 'GET'), - array('name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'), - array('name' => 'SecuritySettings#trustedDomains', 'url' => '/settings/admin/security/trustedDomains', 'verb' => 'POST'), - array('name' => 'Users#setMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'), - array('name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'), - array('name' => 'LogSettings#getEntries', 'url' => '/settings/admin/log/entries', 'verb' => 'GET'), - array('name' => 'LogSettings#download', 'url' => '/settings/admin/log/download', 'verb' => 'GET'), +$application->registerRoutes($this, [ + 'resources' => [ + 'groups' => ['url' => '/settings/users/groups'], + 'users' => ['url' => '/settings/users/users'] + ], + 'routes' => [ + ['name' => 'MailSettings#setMailSettings', 'url' => '/settings/admin/mailsettings', 'verb' => 'POST'], + ['name' => 'MailSettings#storeCredentials', 'url' => '/settings/admin/mailsettings/credentials', 'verb' => 'POST'], + ['name' => 'MailSettings#sendTestMail', 'url' => '/settings/admin/mailtest', 'verb' => 'POST'], + ['name' => 'AppSettings#listCategories', 'url' => '/settings/apps/categories', 'verb' => 'GET'], + ['name' => 'AppSettings#viewApps', 'url' => '/settings/apps', 'verb' => 'GET'], + ['name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'], + ['name' => 'AppSettings#changeExperimentalConfigState', 'url' => '/settings/apps/experimental', 'verb' => 'POST'], + ['name' => 'SecuritySettings#trustedDomains', 'url' => '/settings/admin/security/trustedDomains', 'verb' => 'POST'], + ['name' => 'Users#setMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'], + ['name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'], + ['name' => 'LogSettings#getEntries', 'url' => '/settings/admin/log/entries', 'verb' => 'GET'], + ['name' => 'LogSettings#download', 'url' => '/settings/admin/log/download', 'verb' => 'GET'], ['name' => 'CheckSetup#check', 'url' => '/settings/ajax/checksetup', 'verb' => 'GET'], - ) -)); + ] +]); /** @var $this \OCP\Route\IRouter */ @@ -62,8 +64,6 @@ $this->create('settings_personal', '/settings/personal') ->actionInclude('settings/personal.php'); $this->create('settings_users', '/settings/users') ->actionInclude('settings/users.php'); -$this->create('settings_apps', '/settings/apps') - ->actionInclude('settings/apps.php'); $this->create('settings_admin', '/settings/admin') ->actionInclude('settings/admin.php'); // Settings ajax actions diff --git a/settings/templates/apps.php b/settings/templates/apps.php index a2fe5d9b63a..31de7fa2318 100644 --- a/settings/templates/apps.php +++ b/settings/templates/apps.php @@ -1,3 +1,27 @@ +<?php +style('settings', 'settings'); +vendor_style( + 'core', + [ + 'select2/select2', + ] +); +vendor_script( + 'core', + [ + 'handlebars/handlebars', + 'select2/select2' + ] +); +script( + 'settings', + [ + 'settings', + 'apps', + ] +); +/** @var array $_ */ +?> <script id="categories-template" type="text/x-handlebars-template"> {{#each this}} <li id="app-category-{{id}}" data-category-id="{{id}}" tabindex="0"> @@ -16,6 +40,18 @@ </script> <script id="app-template" type="text/x-handlebars"> + {{#if firstExperimental}} + <div class="section apps-experimental"> + <h2><?php p($l->t('Experimental applications ahead')) ?></h2> + <p> + <?php p($l->t('Experimental apps are not checked for security ' . + 'issues, new or known to be unstable and under heavy ' . + 'development. Installing them can cause data loss or security ' . + 'breaches.')) ?> + </p> + </div> + {{/if}} + <div class="section" id="app-{{id}}"> {{#if preview}} <div class="app-image{{#if previewAsIcon}} app-image-icon{{/if}} hidden"> @@ -23,17 +59,19 @@ {{/if}} <h2 class="app-name"><a href="{{detailpage}}" target="_blank">{{name}}</a></h2> <div class="app-version"> {{version}}</div> + {{#if profilepage}}<a href="{{profilepage}}" target="_blank" rel="noreferrer">{{/if}} <div class="app-author"><?php p($l->t('by')); ?> {{author}} {{#if licence}} ({{licence}}-<?php p($l->t('licensed')); ?>) {{/if}} </div> + {{#if profilepage}}</a>{{/if}} + <div class="app-level"> + {{{level}}} + </div> {{#if score}} <div class="app-score">{{{score}}}</div> {{/if}} - {{#if internalclass}} - <div class="{{internalclass}} icon-checkmark">{{internallabel}}</div> - {{/if}} <div class="app-detailpage"></div> <div class="app-description-container hidden"> @@ -95,6 +133,24 @@ <ul id="apps-categories"> </ul> + <div id="app-settings"> + <div id="app-settings-header"> + <button class="settings-button" data-apps-slide-toggle="#app-settings-content"></button> + </div> + + <div id="app-settings-content" class="apps-experimental"> + <input type="checkbox" id="enable-experimental-apps" <?php if($_['experimentalEnabled']) { print_unescaped('checked="checked"'); }?>> + <label for="enable-experimental-apps"><?php p($l->t('Enable experimental apps')) ?></label> + <p> + <small> + <?php p($l->t('Experimental apps are not checked for security ' . + 'issues, new or known to be unstable and under heavy ' . + 'development. Installing them can cause data loss or security ' . + 'breaches.')) ?> + </small> + </p> + </div> + </div> </div> <div id="app-content"> <div id="apps-list" class="icon-loading"></div> diff --git a/settings/tests/js/appsSpec.js b/settings/tests/js/appsSpec.js index 39329c19246..311fade2c66 100644 --- a/settings/tests/js/appsSpec.js +++ b/settings/tests/js/appsSpec.js @@ -98,4 +98,58 @@ describe('OC.Settings.Apps tests', function() { expect(results[0]).toEqual('somestuff'); }); }); + + describe('loading categories', function() { + var suite = this; + + beforeEach( function(){ + suite.server = sinon.fakeServer.create(); + }); + + afterEach( function(){ + suite.server.restore(); + }); + + function getResultsFromDom() { + var results = []; + $('#apps-list .section:not(.hidden)').each(function() { + results.push($(this).attr('data-id')); + }); + return results; + } + + it('sorts all applications using the level', function() { + Apps.loadCategory('TestId'); + + suite.server.requests[0].respond( + 200, + { + 'Content-Type': 'application/json' + }, + JSON.stringify({ + apps: [ + { + id: 'foo', + level: 0 + }, + { + id: 'alpha', + level: 300 + }, + { + id: 'delta', + level: 200 + } + ] + }) + ); + + var results = getResultsFromDom(); + expect(results.length).toEqual(3); + expect(results[0]).toEqual('alpha'); + expect(results[1]).toEqual('delta'); + expect(results[2]).toEqual('foo'); + }); + }); + }); diff --git a/tests/lib/OCSClientTest.php b/tests/lib/OCSClientTest.php new file mode 100644 index 00000000000..fa3f1fe7848 --- /dev/null +++ b/tests/lib/OCSClientTest.php @@ -0,0 +1,934 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +use OC\OCSClient; +use OCP\Http\Client\IClientService; +use OCP\IConfig; +use OCP\ILogger; + +/** + * Class OCSClientTest + */ +class OCSClientTest extends \Test\TestCase { + /** @var OCSClient */ + private $ocsClient; + /** @var IConfig */ + private $config; + /** @var IClientService */ + private $clientService; + /** @var ILogger */ + private $logger; + + public function setUp() { + parent::setUp(); + + $this->config = $this->getMockBuilder('\OCP\IConfig') + ->disableOriginalConstructor()->getMock(); + $this->clientService = $this->getMock('\OCP\Http\Client\IClientService'); + $this->logger = $this->getMock('\OCP\ILogger'); + + $this->ocsClient = new OCSClient( + $this->clientService, + $this->config, + $this->logger + ); + } + + public function testIsAppStoreEnabledSuccess() { + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->with('appstoreenabled', true) + ->will($this->returnValue(true)); + $this->assertTrue($this->ocsClient->isAppStoreEnabled()); + } + + public function testIsAppStoreEnabledFail() { + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->with('appstoreenabled', true) + ->will($this->returnValue(false)); + $this->assertFalse($this->ocsClient->isAppStoreEnabled()); + } + + public function testGetAppStoreUrl() { + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->with('appstoreurl', 'https://api.owncloud.com/v1') + ->will($this->returnValue('https://api.owncloud.com/v1')); + $this->assertSame('https://api.owncloud.com/v1', Test_Helper::invokePrivate($this->ocsClient, 'getAppStoreUrl')); + } + + public function testGetCategoriesDisabledAppStore() { + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->with('appstoreenabled', true) + ->will($this->returnValue(false)); + $this->assertNull($this->ocsClient->getCategories()); + } + + public function testGetCategoriesExceptionClient() { + $this->config + ->expects($this->at(0)) + ->method('getSystemValue') + ->with('appstoreenabled', true) + ->will($this->returnValue(true)); + $this->config + ->expects($this->at(1)) + ->method('getSystemValue') + ->with('appstoreurl', 'https://api.owncloud.com/v1') + ->will($this->returnValue('https://api.owncloud.com/v1')); + + $client = $this->getMock('\OCP\Http\Client\IClient'); + $client + ->expects($this->once()) + ->method('get') + ->with( + 'https://api.owncloud.com/v1/content/categories', + [ + 'timeout' => 5, + ] + ) + ->will($this->throwException(new \Exception('TheErrorMessage'))); + + $this->clientService + ->expects($this->once()) + ->method('newClient') + ->will($this->returnValue($client)); + + $this->logger + ->expects($this->once()) + ->method('error') + ->with( + 'Could not get categories: TheErrorMessage', + [ + 'app' => 'core', + ] + ); + + $this->assertNull($this->ocsClient->getCategories()); + } + + public function testGetCategoriesParseError() { + $this->config + ->expects($this->at(0)) + ->method('getSystemValue') + ->with('appstoreenabled', true) + ->will($this->returnValue(true)); + $this->config + ->expects($this->at(1)) + ->method('getSystemValue') + ->with('appstoreurl', 'https://api.owncloud.com/v1') + ->will($this->returnValue('https://api.owncloud.com/v1')); + + $response = $this->getMock('\OCP\Http\Client\IResponse'); + $response + ->expects($this->once()) + ->method('getBody') + ->will($this->returnValue('MyInvalidXml')); + + $client = $this->getMock('\OCP\Http\Client\IClient'); + $client + ->expects($this->once()) + ->method('get') + ->with( + 'https://api.owncloud.com/v1/content/categories', + [ + 'timeout' => 5, + ] + ) + ->will($this->returnValue($response)); + + $this->clientService + ->expects($this->once()) + ->method('newClient') + ->will($this->returnValue($client)); + + $this->logger + ->expects($this->once()) + ->method('error') + ->with( + 'Could not get categories, content was no valid XML', + [ + 'app' => 'core', + ] + ); + + $this->assertNull($this->ocsClient->getCategories()); + } + + public function testGetCategoriesSuccessful() { + $this->config + ->expects($this->at(0)) + ->method('getSystemValue') + ->with('appstoreenabled', true) + ->will($this->returnValue(true)); + $this->config + ->expects($this->at(1)) + ->method('getSystemValue') + ->with('appstoreurl', 'https://api.owncloud.com/v1') + ->will($this->returnValue('https://api.owncloud.com/v1')); + + $response = $this->getMock('\OCP\Http\Client\IResponse'); + $response + ->expects($this->once()) + ->method('getBody') + ->will($this->returnValue('<?xml version="1.0"?> + <ocs> + <meta> + <status>ok</status> + <statuscode>100</statuscode> + <message></message> + <totalitems>6</totalitems> + </meta> + <data> + <category> + <id>920</id> + <name>ownCloud Multimedia</name> + </category> + <category> + <id>921</id> + <name>ownCloud PIM</name> + </category> + <category> + <id>922</id> + <name>ownCloud Productivity</name> + </category> + <category> + <id>923</id> + <name>ownCloud Game</name> + </category> + <category> + <id>924</id> + <name>ownCloud Tool</name> + </category> + <category> + <id>925</id> + <name>ownCloud other</name> + </category> + </data> + </ocs> + ')); + + $client = $this->getMock('\OCP\Http\Client\IClient'); + $client + ->expects($this->once()) + ->method('get') + ->with( + 'https://api.owncloud.com/v1/content/categories', + [ + 'timeout' => 5, + ] + ) + ->will($this->returnValue($response)); + + $this->clientService + ->expects($this->once()) + ->method('newClient') + ->will($this->returnValue($client)); + + $expected = [ + 920 => 'ownCloud Multimedia', + 921 => 'ownCloud PIM', + 922 => 'ownCloud Productivity', + 923 => 'ownCloud Game', + 924 => 'ownCloud Tool', + 925 => 'ownCloud other', + ]; + $this->assertSame($expected, $this->ocsClient->getCategories()); + } + + public function testGetApplicationsDisabledAppStore() { + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->with('appstoreenabled', true) + ->will($this->returnValue(false)); + $this->assertSame([], $this->ocsClient->getApplications([], 1, 'approved')); + } + + public function testGetApplicationsExceptionClient() { + $this->config + ->expects($this->at(0)) + ->method('getSystemValue') + ->with('appstoreenabled', true) + ->will($this->returnValue(true)); + $this->config + ->expects($this->at(1)) + ->method('getSystemValue') + ->with('appstoreurl', 'https://api.owncloud.com/v1') + ->will($this->returnValue('https://api.owncloud.com/v1')); + + $client = $this->getMock('\OCP\Http\Client\IClient'); + $client + ->expects($this->once()) + ->method('get') + ->with( + 'https://api.owncloud.com/v1/content/data', + [ + 'timeout' => 5, + 'query' => [ + 'version' => implode('x', \OC_Util::getVersion()), + 'filter' => 'approved', + 'categories' => '815x1337', + 'sortmode' => 'new', + 'page' => 1, + 'pagesize' => 100, + 'approved' => 'approved', + ], + ] + ) + ->will($this->throwException(new \Exception('TheErrorMessage'))); + + $this->clientService + ->expects($this->once()) + ->method('newClient') + ->will($this->returnValue($client)); + + $this->logger + ->expects($this->once()) + ->method('error') + ->with( + 'Could not get applications: TheErrorMessage', + [ + 'app' => 'core', + ] + ); + + $this->assertSame([], $this->ocsClient->getApplications([815, 1337], 1, 'approved')); + } + + public function testGetApplicationsParseError() { + $this->config + ->expects($this->at(0)) + ->method('getSystemValue') + ->with('appstoreenabled', true) + ->will($this->returnValue(true)); + $this->config + ->expects($this->at(1)) + ->method('getSystemValue') + ->with('appstoreurl', 'https://api.owncloud.com/v1') + ->will($this->returnValue('https://api.owncloud.com/v1')); + + $response = $this->getMock('\OCP\Http\Client\IResponse'); + $response + ->expects($this->once()) + ->method('getBody') + ->will($this->returnValue('MyInvalidXml')); + + $client = $this->getMock('\OCP\Http\Client\IClient'); + $client + ->expects($this->once()) + ->method('get') + ->with( + 'https://api.owncloud.com/v1/content/data', + [ + 'timeout' => 5, + 'query' => [ + 'version' => implode('x', \OC_Util::getVersion()), + 'filter' => 'approved', + 'categories' => '815x1337', + 'sortmode' => 'new', + 'page' => 1, + 'pagesize' => 100, + 'approved' => 'approved', + ], + ] + ) + ->will($this->returnValue($response)); + + $this->clientService + ->expects($this->once()) + ->method('newClient') + ->will($this->returnValue($client)); + + $this->logger + ->expects($this->once()) + ->method('error') + ->with( + 'Could not get applications, content was no valid XML', + [ + 'app' => 'core', + ] + ); + + $this->assertSame([], $this->ocsClient->getApplications([815, 1337], 1, 'approved')); + } + + public function testGetApplicationsSuccessful() { + $this->config + ->expects($this->at(0)) + ->method('getSystemValue') + ->with('appstoreenabled', true) + ->will($this->returnValue(true)); + $this->config + ->expects($this->at(1)) + ->method('getSystemValue') + ->with('appstoreurl', 'https://api.owncloud.com/v1') + ->will($this->returnValue('https://api.owncloud.com/v1')); + + $response = $this->getMock('\OCP\Http\Client\IResponse'); + $response + ->expects($this->once()) + ->method('getBody') + ->will($this->returnValue('<?xml version="1.0"?> + <ocs> + <meta> + <status>ok</status> + <statuscode>100</statuscode> + <message></message> + <totalitems>2</totalitems> + <itemsperpage>100</itemsperpage> + </meta> + <data> + <content details="summary"> + <id>168707</id> + <name>Calendar 8.0</name> + <version>0.6.4</version> + <label>recommended</label> + <changed>2015-02-09T15:23:56+01:00</changed> + <created>2015-01-26T04:35:19+01:00</created> + <typeid>921</typeid> + <typename>ownCloud PIM</typename> + <language></language> + <personid>owncloud</personid> + <profilepage>http://opendesktop.org/usermanager/search.php?username=owncloud</profilepage> + <downloads>5393</downloads> + <score>60</score> + <description>Calendar App for ownCloud</description> + <comments>7</comments> + <fans>10</fans> + <licensetype>16</licensetype> + <approved>0</approved> + <category>1</category> + <license>AGPL</license> + <preview1></preview1> + <detailpage>https://apps.owncloud.com/content/show.php?content=168707</detailpage> + <downloadtype1></downloadtype1> + <downloadway1>0</downloadway1> + <downloadprice1>0</downloadprice1> + <downloadlink1>http://apps.owncloud.com/content/download.php?content=168707&id=1</downloadlink1> + <downloadgpgsignature1></downloadgpgsignature1> + <downloadgpgfingerprint1></downloadgpgfingerprint1> + <downloadpackagename1></downloadpackagename1> + <downloadrepository1></downloadrepository1> + <downloadname1></downloadname1> + <downloadsize1>885</downloadsize1> + </content> + <content details="summary"> + <id>168708</id> + <name>Contacts 8.0</name> + <version>0.3.0.18</version> + <label>recommended</label> + <changed>2015-02-09T15:18:58+01:00</changed> + <created>2015-01-26T04:45:17+01:00</created> + <typeid>921</typeid> + <typename>ownCloud PIM</typename> + <language></language> + <personid>owncloud</personid> + <profilepage>http://opendesktop.org/usermanager/search.php?username=owncloud</profilepage> + <downloads>4237</downloads> + <score>58</score> + <description></description> + <comments>3</comments> + <fans>6</fans> + <licensetype>16</licensetype> + <approved>200</approved> + <category>1</category> + <license>AGPL</license> + <preview1></preview1> + <detailpage>https://apps.owncloud.com/content/show.php?content=168708</detailpage> + <downloadtype1></downloadtype1> + <downloadway1>0</downloadway1> + <downloadprice1>0</downloadprice1> + <downloadlink1>http://apps.owncloud.com/content/download.php?content=168708&id=1</downloadlink1> + <downloadgpgsignature1></downloadgpgsignature1> + <downloadgpgfingerprint1></downloadgpgfingerprint1> + <downloadpackagename1></downloadpackagename1> + <downloadrepository1></downloadrepository1> + <downloadname1></downloadname1> + <downloadsize1>1409</downloadsize1> + </content> + </data> + </ocs> ')); + + $client = $this->getMock('\OCP\Http\Client\IClient'); + $client + ->expects($this->once()) + ->method('get') + ->with( + 'https://api.owncloud.com/v1/content/data', + [ + 'timeout' => 5, + 'query' => [ + 'version' => implode('x', \OC_Util::getVersion()), + 'filter' => 'approved', + 'categories' => '815x1337', + 'sortmode' => 'new', + 'page' => 1, + 'pagesize' => 100, + 'approved' => 'approved', + ], + ] + ) + ->will($this->returnValue($response)); + + $this->clientService + ->expects($this->once()) + ->method('newClient') + ->will($this->returnValue($client)); + + $expected = [ + [ + 'id' => '168707', + 'name' => 'Calendar 8.0', + 'label' => 'recommended', + 'version' => '0.6.4', + 'type' => '921', + 'typename' => 'ownCloud PIM', + 'personid' => 'owncloud', + 'license' => 'AGPL', + 'detailpage' => 'https://apps.owncloud.com/content/show.php?content=168707', + 'preview' => '', + 'preview-full' => '', + 'changed' => 1423491836, + 'description' => 'Calendar App for ownCloud', + 'score' => '60', + 'downloads' => 5393, + 'level' => 0, + 'profilepage' => 'http://opendesktop.org/usermanager/search.php?username=owncloud', + ], + [ + 'id' => '168708', + 'name' => 'Contacts 8.0', + 'label' => 'recommended', + 'version' => '0.3.0.18', + 'type' => '921', + 'typename' => 'ownCloud PIM', + 'personid' => 'owncloud', + 'license' => 'AGPL', + 'detailpage' => 'https://apps.owncloud.com/content/show.php?content=168708', + 'preview' => '', + 'preview-full' => '', + 'changed' => 1423491538, + 'description' => '', + 'score' => '58', + 'downloads' => 4237, + 'level' => 200, + 'profilepage' => 'http://opendesktop.org/usermanager/search.php?username=owncloud', + ], + ]; + $this->assertEquals($expected, $this->ocsClient->getApplications([815, 1337], 1, 'approved')); + } + + public function tesGetApplicationDisabledAppStore() { + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->with('appstoreenabled', true) + ->will($this->returnValue(false)); + $this->assertNull($this->ocsClient->getApplication('MyId')); + } + + public function testGetApplicationExceptionClient() { + $this->config + ->expects($this->at(0)) + ->method('getSystemValue') + ->with('appstoreenabled', true) + ->will($this->returnValue(true)); + $this->config + ->expects($this->at(1)) + ->method('getSystemValue') + ->with('appstoreurl', 'https://api.owncloud.com/v1') + ->will($this->returnValue('https://api.owncloud.com/v1')); + + $client = $this->getMock('\OCP\Http\Client\IClient'); + $client + ->expects($this->once()) + ->method('get') + ->with( + 'https://api.owncloud.com/v1/content/data/MyId', + [ + 'timeout' => 5, + ] + ) + ->will($this->throwException(new \Exception('TheErrorMessage'))); + + $this->clientService + ->expects($this->once()) + ->method('newClient') + ->will($this->returnValue($client)); + + $this->logger + ->expects($this->once()) + ->method('error') + ->with( + 'Could not get application: TheErrorMessage', + [ + 'app' => 'core', + ] + ); + + $this->assertNull($this->ocsClient->getApplication('MyId')); + } + + public function testGetApplicationParseError() { + $this->config + ->expects($this->at(0)) + ->method('getSystemValue') + ->with('appstoreenabled', true) + ->will($this->returnValue(true)); + $this->config + ->expects($this->at(1)) + ->method('getSystemValue') + ->with('appstoreurl', 'https://api.owncloud.com/v1') + ->will($this->returnValue('https://api.owncloud.com/v1')); + + $response = $this->getMock('\OCP\Http\Client\IResponse'); + $response + ->expects($this->once()) + ->method('getBody') + ->will($this->returnValue('MyInvalidXml')); + + $client = $this->getMock('\OCP\Http\Client\IClient'); + $client + ->expects($this->once()) + ->method('get') + ->with( + 'https://api.owncloud.com/v1/content/data/MyId', + [ + 'timeout' => 5, + ] + ) + ->will($this->returnValue($response)); + + $this->clientService + ->expects($this->once()) + ->method('newClient') + ->will($this->returnValue($client)); + + $this->logger + ->expects($this->once()) + ->method('error') + ->with( + 'Could not get application, content was no valid XML', + [ + 'app' => 'core', + ] + ); + + $this->assertNull($this->ocsClient->getApplication('MyId')); + } + + public function testGetApplicationSuccessful() { + $this->config + ->expects($this->at(0)) + ->method('getSystemValue') + ->with('appstoreenabled', true) + ->will($this->returnValue(true)); + $this->config + ->expects($this->at(1)) + ->method('getSystemValue') + ->with('appstoreurl', 'https://api.owncloud.com/v1') + ->will($this->returnValue('https://api.owncloud.com/v1')); + + $response = $this->getMock('\OCP\Http\Client\IResponse'); + $response + ->expects($this->once()) + ->method('getBody') + ->will($this->returnValue('<?xml version="1.0"?> + <ocs> + <meta> + <status>ok</status> + <statuscode>100</statuscode> + <message></message> + </meta> + <data> + <content details="full"> + <id>166053</id> + <name>Versioning</name> + <version>0.0.1</version> + <label>recommended</label> + <typeid>925</typeid> + <typename>ownCloud other</typename> + <language></language> + <personid>owncloud</personid> + <profilepage>http://opendesktop.org/usermanager/search.php?username=owncloud</profilepage> + <created>2014-07-07T16:34:40+02:00</created> + <changed>2014-07-07T16:34:40+02:00</changed> + <downloads>140</downloads> + <score>50</score> + <description>Placeholder for future updates</description> + <summary></summary> + <feedbackurl></feedbackurl> + <changelog></changelog> + <homepage></homepage> + <homepagetype></homepagetype> + <homepage2></homepage2> + <homepagetype2></homepagetype2> + <homepage3></homepage3> + <homepagetype3></homepagetype3> + <homepage4></homepage4> + <homepagetype4></homepagetype4> + <homepage5></homepage5> + <homepagetype5></homepagetype5> + <homepage6></homepage6> + <homepagetype6></homepagetype6> + <homepage7></homepage7> + <homepagetype7></homepagetype7> + <homepage8></homepage8> + <homepagetype8></homepagetype8> + <homepage9></homepage9> + <homepagetype9></homepagetype9> + <homepage10></homepage10> + <homepagetype10></homepagetype10> + <licensetype>16</licensetype> + <license>AGPL</license> + <donationpage></donationpage> + <comments>0</comments> + <commentspage>http://apps.owncloud.com/content/show.php?content=166053</commentspage> + <fans>0</fans> + <fanspage>http://apps.owncloud.com/content/show.php?action=fan&content=166053</fanspage> + <knowledgebaseentries>0</knowledgebaseentries> + <knowledgebasepage>http://apps.owncloud.com/content/show.php?action=knowledgebase&content=166053</knowledgebasepage> + <depend>ownCloud 7</depend> + <preview1></preview1> + <preview2></preview2> + <preview3></preview3> + <previewpic1></previewpic1> + <previewpic2></previewpic2> + <previewpic3></previewpic3> + <picsmall1></picsmall1> + <picsmall2></picsmall2> + <picsmall3></picsmall3> + <detailpage>https://apps.owncloud.com/content/show.php?content=166053</detailpage> + <downloadtype1></downloadtype1> + <downloadprice1>0</downloadprice1> + <downloadlink1>http://apps.owncloud.com/content/download.php?content=166053&id=1</downloadlink1> + <downloadname1></downloadname1> + <downloadgpgfingerprint1></downloadgpgfingerprint1> + <downloadgpgsignature1></downloadgpgsignature1> + <downloadpackagename1></downloadpackagename1> + <downloadrepository1></downloadrepository1> + <downloadsize1>1</downloadsize1> + </content> + </data> + </ocs> + ')); + + $client = $this->getMock('\OCP\Http\Client\IClient'); + $client + ->expects($this->once()) + ->method('get') + ->with( + 'https://api.owncloud.com/v1/content/data/MyId', + [ + 'timeout' => 5, + ] + ) + ->will($this->returnValue($response)); + + $this->clientService + ->expects($this->once()) + ->method('newClient') + ->will($this->returnValue($client)); + + $expected = [ + 'id' => 166053, + 'name' => 'Versioning', + 'version' => '0.0.1', + 'type' => '925', + 'label' => 'recommended', + 'typename' => 'ownCloud other', + 'personid' => 'owncloud', + 'profilepage' => 'http://opendesktop.org/usermanager/search.php?username=owncloud', + 'detailpage' => 'https://apps.owncloud.com/content/show.php?content=166053', + 'preview1' => '', + 'preview2' => '', + 'preview3' => '', + 'changed' => 1404743680, + 'description' => 'Placeholder for future updates', + 'score' => 50, + ]; + $this->assertSame($expected, $this->ocsClient->getApplication('MyId')); + } + + public function testGetApplicationDownloadDisabledAppStore() { + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->with('appstoreenabled', true) + ->will($this->returnValue(false)); + $this->assertNull($this->ocsClient->getApplicationDownload('MyId')); + } + + public function testGetApplicationDownloadExceptionClient() { + $this->config + ->expects($this->at(0)) + ->method('getSystemValue') + ->with('appstoreenabled', true) + ->will($this->returnValue(true)); + $this->config + ->expects($this->at(1)) + ->method('getSystemValue') + ->with('appstoreurl', 'https://api.owncloud.com/v1') + ->will($this->returnValue('https://api.owncloud.com/v1')); + + $client = $this->getMock('\OCP\Http\Client\IClient'); + $client + ->expects($this->once()) + ->method('get') + ->with( + 'https://api.owncloud.com/v1/content/download/MyId/1', + [ + 'timeout' => 5, + ] + ) + ->will($this->throwException(new \Exception('TheErrorMessage'))); + + $this->clientService + ->expects($this->once()) + ->method('newClient') + ->will($this->returnValue($client)); + + $this->logger + ->expects($this->once()) + ->method('error') + ->with( + 'Could not get application download URL: TheErrorMessage', + [ + 'app' => 'core', + ] + ); + + $this->assertNull($this->ocsClient->getApplicationDownload('MyId')); + } + + public function testGetApplicationDownloadParseError() { + $this->config + ->expects($this->at(0)) + ->method('getSystemValue') + ->with('appstoreenabled', true) + ->will($this->returnValue(true)); + $this->config + ->expects($this->at(1)) + ->method('getSystemValue') + ->with('appstoreurl', 'https://api.owncloud.com/v1') + ->will($this->returnValue('https://api.owncloud.com/v1')); + + $response = $this->getMock('\OCP\Http\Client\IResponse'); + $response + ->expects($this->once()) + ->method('getBody') + ->will($this->returnValue('MyInvalidXml')); + + $client = $this->getMock('\OCP\Http\Client\IClient'); + $client + ->expects($this->once()) + ->method('get') + ->with( + 'https://api.owncloud.com/v1/content/download/MyId/1', + [ + 'timeout' => 5, + ] + ) + ->will($this->returnValue($response)); + + $this->clientService + ->expects($this->once()) + ->method('newClient') + ->will($this->returnValue($client)); + + $this->logger + ->expects($this->once()) + ->method('error') + ->with( + 'Could not get application download URL, content was no valid XML', + [ + 'app' => 'core', + ] + ); + + $this->assertNull($this->ocsClient->getApplicationDownload('MyId')); + } + + public function testGetApplicationDownloadUrlSuccessful() { + $this->config + ->expects($this->at(0)) + ->method('getSystemValue') + ->with('appstoreenabled', true) + ->will($this->returnValue(true)); + $this->config + ->expects($this->at(1)) + ->method('getSystemValue') + ->with('appstoreurl', 'https://api.owncloud.com/v1') + ->will($this->returnValue('https://api.owncloud.com/v1')); + + $response = $this->getMock('\OCP\Http\Client\IResponse'); + $response + ->expects($this->once()) + ->method('getBody') + ->will($this->returnValue('<?xml version="1.0"?> + <ocs> + <meta> + <status>ok</status> + <statuscode>100</statuscode> + <message></message> + </meta> + <data> + <content details="download"> + <downloadlink>https://apps.owncloud.com/CONTENT/content-files/166052-files_trashbin.zip</downloadlink> + <mimetype>application/zip</mimetype> + <gpgfingerprint></gpgfingerprint> + <gpgsignature></gpgsignature> + <packagename></packagename> + <repository></repository> + </content> + </data> + </ocs> + ')); + + $client = $this->getMock('\OCP\Http\Client\IClient'); + $client + ->expects($this->once()) + ->method('get') + ->with( + 'https://api.owncloud.com/v1/content/download/MyId/1', + [ + 'timeout' => 5, + ] + ) + ->will($this->returnValue($response)); + + $this->clientService + ->expects($this->once()) + ->method('newClient') + ->will($this->returnValue($client)); + + $expected = [ + 'downloadlink' => 'https://apps.owncloud.com/CONTENT/content-files/166052-files_trashbin.zip', + ]; + $this->assertSame($expected, $this->ocsClient->getApplicationDownload('MyId')); + } +} diff --git a/tests/settings/controller/AppSettingsControllerTest.php b/tests/settings/controller/AppSettingsControllerTest.php new file mode 100644 index 00000000000..d6379deb9c8 --- /dev/null +++ b/tests/settings/controller/AppSettingsControllerTest.php @@ -0,0 +1,231 @@ +<?php +/** + * @author Lukas Reschke <lukas@owncloud.com> + * + * @copyright Copyright (c) 2015, ownCloud, Inc. + * @license AGPL-3.0 + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License, version 3, + * along with this program. If not, see <http://www.gnu.org/licenses/> + * + */ + +namespace OC\Settings\Controller; + +use OCP\AppFramework\Http\ContentSecurityPolicy; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http\TemplateResponse; +use Test\TestCase; +use OCP\IRequest; +use OCP\IL10N; +use OCP\IConfig; +use OCP\ICache; +use OCP\INavigationManager; +use OCP\App\IAppManager; +use OC\OCSClient; + +/** + * Class AppSettingsControllerTest + * + * @package OC\Settings\Controller + */ +class AppSettingsControllerTest extends TestCase { + /** @var AppSettingsController */ + private $appSettingsController; + /** @var IRequest */ + private $request; + /** @var IL10N */ + private $l10n; + /** @var IConfig */ + private $config; + /** @var ICache */ + private $cache; + /** @var INavigationManager */ + private $navigationManager; + /** @var IAppManager */ + private $appManager; + /** @var OCSClient */ + private $ocsClient; + + public function setUp() { + parent::setUp(); + + $this->request = $this->getMockBuilder('\OCP\IRequest') + ->disableOriginalConstructor()->getMock(); + $this->l10n = $this->getMockBuilder('\OCP\IL10N') + ->disableOriginalConstructor()->getMock(); + $this->l10n->expects($this->any()) + ->method('t') + ->will($this->returnArgument(0)); + $this->config = $this->getMockBuilder('\OCP\IConfig') + ->disableOriginalConstructor()->getMock(); + $cacheFactory = $this->getMockBuilder('\OCP\ICacheFactory') + ->disableOriginalConstructor()->getMock(); + $this->cache = $this->getMockBuilder('\OCP\ICache') + ->disableOriginalConstructor()->getMock(); + $cacheFactory + ->expects($this->once()) + ->method('create') + ->with('settings') + ->will($this->returnValue($this->cache)); + + $this->navigationManager = $this->getMockBuilder('\OCP\INavigationManager') + ->disableOriginalConstructor()->getMock(); + $this->appManager = $this->getMockBuilder('\OCP\App\IAppManager') + ->disableOriginalConstructor()->getMock(); + $this->ocsClient = $this->getMockBuilder('\OC\OCSClient') + ->disableOriginalConstructor()->getMock(); + + $this->appSettingsController = new AppSettingsController( + 'settings', + $this->request, + $this->l10n, + $this->config, + $cacheFactory, + $this->navigationManager, + $this->appManager, + $this->ocsClient + ); + } + + public function testChangeExperimentalConfigStateTrue() { + $this->config + ->expects($this->once()) + ->method('setSystemValue') + ->with('appstore.experimental.enabled', true); + $this->appManager + ->expects($this->once()) + ->method('clearAppsCache'); + $this->assertEquals(new DataResponse(), $this->appSettingsController->changeExperimentalConfigState(true)); + } + + public function testChangeExperimentalConfigStateFalse() { + $this->config + ->expects($this->once()) + ->method('setSystemValue') + ->with('appstore.experimental.enabled', false); + $this->appManager + ->expects($this->once()) + ->method('clearAppsCache'); + $this->assertEquals(new DataResponse(), $this->appSettingsController->changeExperimentalConfigState(false)); + } + + public function testListCategoriesCached() { + $this->cache + ->expects($this->exactly(2)) + ->method('get') + ->with('listCategories') + ->will($this->returnValue(['CachedArray'])); + $this->assertSame(['CachedArray'], $this->appSettingsController->listCategories()); + } + + public function testListCategoriesNotCachedWithoutAppStore() { + $expected = [ + [ + 'id' => 0, + 'displayName' => 'Enabled', + ], + [ + 'id' => 1, + 'displayName' => 'Not enabled', + ], + ]; + $this->cache + ->expects($this->once()) + ->method('get') + ->with('listCategories') + ->will($this->returnValue(null)); + $this->cache + ->expects($this->once()) + ->method('set') + ->with('listCategories', $expected, 3600); + + + $this->assertSame($expected, $this->appSettingsController->listCategories()); + } + + public function testListCategoriesNotCachedWithAppStore() { + $expected = [ + [ + 'id' => 0, + 'displayName' => 'Enabled', + ], + [ + 'id' => 1, + 'displayName' => 'Not enabled', + ], + [ + 'id' => 0, + 'displayName' => 'Tools', + ], + [ + 'id' => 1, + 'displayName' => 'Awesome Games', + ], + [ + 'id' => 2, + 'displayName' => 'PIM', + ], + [ + 'id' => 3, + 'displayName' => 'Papershop', + ], + ]; + + $this->cache + ->expects($this->once()) + ->method('get') + ->with('listCategories') + ->will($this->returnValue(null)); + $this->cache + ->expects($this->once()) + ->method('set') + ->with('listCategories', $expected, 3600); + + $this->ocsClient + ->expects($this->once()) + ->method('isAppStoreEnabled') + ->will($this->returnValue(true)); + $this->ocsClient + ->expects($this->once()) + ->method('getCategories') + ->will($this->returnValue( + [ + 'ownCloud Tools', + 'Awesome Games', + 'ownCloud PIM', + 'Papershop', + ] + )); + + $this->assertSame($expected, $this->appSettingsController->listCategories()); + } + + public function testViewApps() { + $this->config + ->expects($this->once()) + ->method('getSystemValue') + ->with('appstore.experimental.enabled', false); + $this->navigationManager + ->expects($this->once()) + ->method('setActiveEntry') + ->with('core_apps'); + + $policy = new ContentSecurityPolicy(); + $policy->addAllowedImageDomain('https://apps.owncloud.com'); + + $expected = new TemplateResponse('settings', 'apps', ['experimentalEnabled' => false], 'user'); + $expected->setContentSecurityPolicy($policy); + + $this->assertEquals($expected, $this->appSettingsController->viewApps()); + } +} |