]> source.dussan.org Git - nextcloud-server.git/commitdiff
Add experimental applications switch
authorLukas Reschke <lukas@owncloud.com>
Mon, 30 Mar 2015 13:58:20 +0000 (15:58 +0200)
committerLukas Reschke <lukas@owncloud.com>
Fri, 3 Apr 2015 11:21:24 +0000 (13:21 +0200)
Allows administrators to disable or enabled experimental applications as well as show the trust level.

18 files changed:
config/config.sample.php
core/templates/layout.user.php
lib/private/app.php
lib/private/app/appmanager.php
lib/private/installer.php
lib/private/ocsclient.php
lib/private/server.php
lib/private/templatelayout.php
lib/public/app/iappmanager.php
settings/application.php
settings/apps.php [deleted file]
settings/controller/appsettingscontroller.php
settings/css/settings.css
settings/js/apps.js
settings/routes.php
settings/templates/apps.php
tests/lib/OCSClientTest.php [new file with mode: 0644]
tests/settings/controller/AppSettingsControllerTest.php [new file with mode: 0644]

index 60932ab7d9b77df499c73dcbb86161e63a62c073..f17473b7d8014b88a42abfb8c7e08759d9664b58 100644 (file)
@@ -576,6 +576,15 @@ $CONFIG = array(
  */
 'appstoreurl' => 'https://api.owncloud.com/v1',
 
+/**
+ * Whether to 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
index 880a276c7253fe1e9ba59dcc1d211133f6162942..87a6a9216d2492ab0f415c83d808f34988f71d39 100644 (file)
                                        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>
index 84bc23608fb0a2c56644938ffef014cf936b2887..ee92f8f5adc9e7baa8a6794b952e00a2dcad4e3d 100644 (file)
@@ -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']);
@@ -780,8 +786,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 +826,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 +851,7 @@ class OC_App {
                        }
                }
                if ($onlyLocal) {
-                       $remoteApps = array();
+                       $remoteApps = [];
                } else {
                        $remoteApps = OC_App::getAppstoreApps();
                }
@@ -865,34 +871,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 +891,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 +920,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 +1073,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)) {
index 2a147d4de6f4b99bf067eeae6743ce1998064188..c9d4a777c4aaf72b56dcb4b15496fa696cd718d2 100644 (file)
@@ -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');
        }
index e30344b1b10f5cec832607c408d29439c2864490..41f13f0f5f9acc8ce1aab15eddefbb7033144c16 100644 (file)
@@ -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, '>')) {
index f69426ddafeb8dff624c296d5f056a50480650a6..30747c0d5fb237fa2e231ec51d87c9be9e7b353e 100644 (file)
 
 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');
        }
 
        /**
@@ -71,36 +87,50 @@ 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) {
-                       return null;
-               }
-
-               if($response->getStatusCode() !== 200) {
+                       $this->logger->error(
+                               sprintf('Could not get categories: %s', $e->getMessage()),
+                               [
+                                       'app' => 'core',
+                               ]
+                       );
                        return null;
                }
 
                $loadEntities = libxml_disable_entity_loader(true);
-               $data = simplexml_load_string($response->getBody());
+               $data = @simplexml_load_string($response->getBody());
                libxml_disable_entity_loader($loadEntities);
 
+               if($data === false) {
+                       $this->logger->error(
+                               'Could not get categories, content was no valid XML',
+                               [
+                                       'app' => 'core',
+                               ]
+                       );
+                       return null;
+               }
+
                $tmp = $data->data;
                $cats = [];
 
                foreach ($tmp->category as $value) {
-
                        $id = (int)$value->id;
                        $name = (string)$value->name;
                        $cats[$id] = $name;
-
                }
 
                return $cats;
@@ -108,50 +138,63 @@ 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;
-               }
-
-               if($response->getStatusCode() !== 200) {
-                       return null;
+                       $this->logger->error(
+                               sprintf('Could not get applications: %s', $e->getMessage()),
+                               [
+                                       'app' => 'core',
+                               ]
+                       );
+                       return [];
                }
 
                $loadEntities = libxml_disable_entity_loader(true);
-               $data = simplexml_load_string($response->getBody());
+               $data = @simplexml_load_string($response->getBody());
                libxml_disable_entity_loader($loadEntities);
 
+               if($data === false) {
+                       $this->logger->error(
+                               'Could not get applications, content was no valid XML',
+                               [
+                                       'app' => 'core',
+                               ]
+                       );
+                       return [];
+               }
+
                $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;
@@ -167,9 +210,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 +227,111 @@ 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) {
-                       return null;
-               }
-
-               if($response->getStatusCode() !== 200) {
+                       $this->logger->error(
+                               sprintf('Could not get application: %s', $e->getMessage()),
+                               [
+                                       'app' => 'core',
+                               ]
+                       );
                        return null;
                }
 
                $loadEntities = libxml_disable_entity_loader(true);
-               $data = simplexml_load_string($response->getBody());
+               $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);
+               if($data === false) {
+                       $this->logger->error(
+                               'Could not get application, content was no valid XML',
+                               [
+                                       'app' => 'core',
+                               ]
+                       );
                        return null;
                }
+
+               $tmp = $data->data->content;
+
                $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['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) {
-                       return null;
-               }
-
-               if($response->getStatusCode() !== 200) {
+                       $this->logger->error(
+                               sprintf('Could not get application download URL: %s', $e->getMessage()),
+                               [
+                                       'app' => 'core',
+                               ]
+                       );
                        return null;
                }
 
                $loadEntities = libxml_disable_entity_loader(true);
-               $data = simplexml_load_string($response->getBody());
+               $data = @simplexml_load_string($response->getBody());
                libxml_disable_entity_loader($loadEntities);
 
+               if($data === false) {
+                       $this->logger->error(
+                               'Could not get application download URL, content was no valid XML',
+                               [
+                                       'app' => 'core',
+                               ]
+                       );
+                       return null;
+               }
+
                $tmp = $data->data->content;
-               $app = array();
+               $app = [];
                if (isset($tmp->downloadlink)) {
-                       $app['downloadlink'] = $tmp->downloadlink;
+                       $app['downloadlink'] = (string)$tmp->downloadlink;
                } else {
                        $app['downloadlink'] = '';
                }
index 8c5169f229e39b0fa3992891f3823cfe885a72aa..cfdbd800a77c6c21eb977f733136f99dd42edfad 100644 (file)
@@ -391,6 +391,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()
+                       );
+               });
        }
 
        /**
@@ -836,6 +843,13 @@ class Server extends SimpleContainer implements IServerContainer {
                return $this->webRoot;
        }
 
+       /**
+        * @return \OC\OCSClient
+        */
+       public function getOcsClient() {
+               return $this->query('OcsClient');
+       }
+
        /**
         * @return \OCP\IDateTimeZone
         */
index ee1412fba7437b736e4c9ec32c2b0931195e854a..448276ca7feb6ea4dce97e761a3182fd76855098 100644 (file)
@@ -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') {
index f50a7f64174ea65cb0a3a701ede81cf2c4fef9f9..69b8c335d6728ae97fe79a781bd219f5a28b2cdc 100644 (file)
@@ -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();
 }
index b4596037964be76b14fa036e2ee94d3d5d5e80a5..07a458d249f5d52e6423ae4b929e87ba1f0d04cc 100644 (file)
@@ -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) {
@@ -191,6 +194,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 (file)
index 7245b66..0000000
+++ /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();
-
index 9a85f6d3b97eec0eb473138aae8d1a71561066f5..f1b62bb1d3839e01c3a3e9af4283e105adaf71f8 100644 (file)
@@ -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 {
index c619bd7b9b30a04f2aa97a3c92aaedbb741be514..eb6b0f54053a269ba0e4cb6f0c5c998f833d7332 100644 (file)
@@ -210,6 +210,24 @@ span.version { margin-left:1em; margin-right:1em; color:#555; }
        opacity: .5;
 }
 
+.app-level {
+       color: white;
+}
+
+.app-level .official, .app-level .approved {
+       background-color: #E8C805;
+       border-radius: 2px;
+       margin-left: 5px;
+       padding: 3px;
+}
+
+.app-level .experimental {
+       background-color: #F02405;
+       border-radius: 2px;
+       margin-left: 5px;
+       padding: 3px;
+}
+
 #apps-list {
        position: relative;
        height: 100%;
@@ -236,6 +254,7 @@ span.version { margin-left:1em; margin-right:1em; color:#555; }
 .app-name,
 .app-version,
 .app-score,
+.app-level,
 .recommendedapp {
        display: inline-block;
 }
@@ -261,7 +280,7 @@ 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;
 }
 
index 3db84e8acd57a07dcd63a2fc7fec92cc1557e1de..f54611369b3b6b6eb1414592ac5cd8c1c73cefdc 100644 (file)
@@ -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">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 and its GitHub repository and offer functionality central to ownCloud. They are ready for serious 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 = $('#enable-experimental-apps').prop('checked');
+                       $.ajax(OC.generateUrl('settings/apps/experimental'), {
+                               data: {state: state},
+                               type: 'POST',
+                               success:function () {
+                                       location.reload();
+                               }
+                       });
+               });
        }
 };
 
index 5a069e5a1c69f1f25b7845de76b56669e6b8ac83..86b7fa2375c52eae740b67244a800cc97e1274b3 100644 (file)
 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
index a2fe5d9b63a8b4cc39566e8bea8fe49484ed46cd..f930ce6d444f046035243baee3a3f6df23f8e706 100644 (file)
@@ -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">
 </script>
 
 <script id="app-template" type="text/x-handlebars">
+       {{#if firstExperimental}}
+               <div style="background-color: lightyellow; border-top:1px solid black; border-bottom:  1px solid black;">
+                       <h2><?php p($l->t('Experimental applications ahead')) ?></h2>
+                       <p>
+                               <?php p($l->t('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.')) ?>
+                       </p>
+               </div>
+       {{/if}}
+
        <div class="section" id="app-{{id}}">
        {{#if preview}}
        <div class="app-image{{#if previewAsIcon}} app-image-icon{{/if}} hidden">
@@ -23,6 +59,9 @@
        {{/if}}
        <h2 class="app-name"><a href="{{detailpage}}" target="_blank">{{name}}</a></h2>
        <div class="app-version"> {{version}}</div>
+       <div class="app-level">
+               {{{level}}}
+       </div>
        <div class="app-author"><?php p($l->t('by')); ?> {{author}}
                {{#if licence}}
                ({{licence}}-<?php p($l->t('licensed')); ?>)
        <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" style="color: #c33">
+                       <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 and are new or known to be unstable and under heavy ' .
+                                               'development. Installing these 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/tests/lib/OCSClientTest.php b/tests/lib/OCSClientTest.php
new file mode 100644 (file)
index 0000000..6c83103
--- /dev/null
@@ -0,0 +1,931 @@
+<?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&amp;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&amp;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,
+                       ],
+                       [
+                               '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,
+                       ],
+               ];
+               $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&amp;content=166053</fanspage>
+                          <knowledgebaseentries>0</knowledgebaseentries>
+                          <knowledgebasepage>http://apps.owncloud.com/content/show.php?action=knowledgebase&amp;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&amp;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',
+                       '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 (file)
index 0000000..d6379de
--- /dev/null
@@ -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());
+       }
+}