diff options
author | Lukas Reschke <lukas@owncloud.com> | 2015-04-09 10:07:32 +0200 |
---|---|---|
committer | Lukas Reschke <lukas@owncloud.com> | 2015-04-09 10:07:32 +0200 |
commit | ba52f6f8fc0d88000332e9e64c48a56c526823d9 (patch) | |
tree | 3aa8f934e0d0183e1a842c4163f83dac887bed96 /settings | |
parent | 56f1ffe820383ac82c208552213848c6a8deec6d (diff) | |
parent | 98698e05e86a85e3f9aa813c314c3ed6739fbb43 (diff) | |
download | nextcloud-server-ba52f6f8fc0d88000332e9e64c48a56c526823d9.tar.gz nextcloud-server-ba52f6f8fc0d88000332e9e64c48a56c526823d9.zip |
Merge pull request #15314 from owncloud/app-categories-15274
Add different trust levels to AppStore interface
Diffstat (limited to 'settings')
-rw-r--r-- | settings/application.php | 14 | ||||
-rw-r--r-- | settings/apps.php | 42 | ||||
-rw-r--r-- | settings/controller/appsettingscontroller.php | 75 | ||||
-rw-r--r-- | settings/css/settings.css | 67 | ||||
-rw-r--r-- | settings/js/apps.js | 41 | ||||
-rw-r--r-- | settings/routes.php | 40 | ||||
-rw-r--r-- | settings/templates/apps.php | 62 | ||||
-rw-r--r-- | settings/tests/js/appsSpec.js | 54 |
8 files changed, 294 insertions, 101 deletions
diff --git a/settings/application.php b/settings/application.php index eb8f0f7a999..be127da31ac 100644 --- a/settings/application.php +++ b/settings/application.php @@ -71,7 +71,10 @@ class Application extends App { $c->query('Request'), $c->query('L10N'), $c->query('Config'), - $c->query('ICacheFactory') + $c->query('ICacheFactory'), + $c->query('INavigationManager'), + $c->query('IAppManager'), + $c->query('OcsClient') ); }); $container->registerService('SecuritySettingsController', function(IContainer $c) { @@ -192,6 +195,15 @@ class Application extends App { $container->registerService('ClientService', function(IContainer $c) { return $c->query('ServerContainer')->getHTTPClientService(); }); + $container->registerService('INavigationManager', function(IContainer $c) { + return $c->query('ServerContainer')->getNavigationManager(); + }); + $container->registerService('IAppManager', function(IContainer $c) { + return $c->query('ServerContainer')->getAppManager(); + }); + $container->registerService('OcsClient', function(IContainer $c) { + return $c->query('ServerContainer')->getOcsClient(); + }); $container->registerService('Util', function(IContainer $c) { return new \OC_Util(); }); diff --git a/settings/apps.php b/settings/apps.php deleted file mode 100644 index 7245b6610e0..00000000000 --- a/settings/apps.php +++ /dev/null @@ -1,42 +0,0 @@ -<?php -/** - * @author Bart Visscher <bartv@thisnet.nl> - * @author Frank Karlitschek <frank@owncloud.org> - * @author Jan-Christoph Borchardt <hey@jancborchardt.net> - * @author Lukas Reschke <lukas@owncloud.com> - * @author Morris Jobke <hey@morrisjobke.de> - * @author Robin Appelman <icewind@owncloud.com> - * @author Thomas Müller <thomas.mueller@tmit.eu> - * - * @copyright Copyright (c) 2015, ownCloud, Inc. - * @license AGPL-3.0 - * - * This code is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License, version 3, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License, version 3, - * along with this program. If not, see <http://www.gnu.org/licenses/> - * - */ - -OC_Util::checkAdminUser(); -\OC::$server->getSession()->close(); - -// Load the files we need -\OC_Util::addVendorScript('handlebars/handlebars'); -\OCP\Util::addScript("settings", "settings"); -\OCP\Util::addStyle("settings", "settings"); -\OC_Util::addVendorScript('select2/select2'); -\OC_Util::addVendorStyle('select2/select2'); -\OCP\Util::addScript("settings", "apps"); -\OC_App::setActiveNavigationEntry( "core_apps" ); - -$tmpl = new OC_Template( "settings", "apps", "user" ); -$tmpl->printPage(); - diff --git a/settings/controller/appsettingscontroller.php b/settings/controller/appsettingscontroller.php index 9a85f6d3b97..f1b62bb1d38 100644 --- a/settings/controller/appsettingscontroller.php +++ b/settings/controller/appsettingscontroller.php @@ -27,8 +27,13 @@ namespace OC\Settings\Controller; use OC\App\DependencyAnalyzer; use OC\App\Platform; use OC\OCSClient; +use OCP\App\IAppManager; use \OCP\AppFramework\Controller; +use OCP\AppFramework\Http\ContentSecurityPolicy; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Http\TemplateResponse; use OCP\ICacheFactory; +use OCP\INavigationManager; use OCP\IRequest; use OCP\IL10N; use OCP\IConfig; @@ -44,6 +49,12 @@ class AppSettingsController extends Controller { private $config; /** @var \OCP\ICache */ private $cache; + /** @var INavigationManager */ + private $navigationManager; + /** @var IAppManager */ + private $appManager; + /** @var OCSClient */ + private $ocsClient; /** * @param string $appName @@ -51,16 +62,53 @@ class AppSettingsController extends Controller { * @param IL10N $l10n * @param IConfig $config * @param ICacheFactory $cache + * @param INavigationManager $navigationManager + * @param IAppManager $appManager + * @param OCSClient $ocsClient */ public function __construct($appName, IRequest $request, IL10N $l10n, IConfig $config, - ICacheFactory $cache) { + ICacheFactory $cache, + INavigationManager $navigationManager, + IAppManager $appManager, + OCSClient $ocsClient) { parent::__construct($appName, $request); $this->l10n = $l10n; $this->config = $config; $this->cache = $cache->create($appName); + $this->navigationManager = $navigationManager; + $this->appManager = $appManager; + $this->ocsClient = $ocsClient; + } + + /** + * Enables or disables the display of experimental apps + * @param bool $state + * @return DataResponse + */ + public function changeExperimentalConfigState($state) { + $this->config->setSystemValue('appstore.experimental.enabled', $state); + $this->appManager->clearAppsCache(); + return new DataResponse(); + } + + /** + * @NoCSRFRequired + * @return TemplateResponse + */ + public function viewApps() { + $params = []; + $params['experimentalEnabled'] = $this->config->getSystemValue('appstore.experimental.enabled', false); + $this->navigationManager->setActiveEntry('core_apps'); + + $templateResponse = new TemplateResponse($this->appName, 'apps', $params, 'user'); + $policy = new ContentSecurityPolicy(); + $policy->addAllowedImageDomain('https://apps.owncloud.com'); + $templateResponse->setContentSecurityPolicy($policy); + + return $templateResponse; } /** @@ -77,16 +125,15 @@ class AppSettingsController extends Controller { ['id' => 1, 'displayName' => (string)$this->l10n->t('Not enabled')], ]; - if(OCSClient::isAppStoreEnabled()) { - $categories[] = ['id' => 2, 'displayName' => (string)$this->l10n->t('Recommended')]; + if($this->ocsClient->isAppStoreEnabled()) { // apps from external repo via OCS - $ocs = OCSClient::getCategories(); + $ocs = $this->ocsClient->getCategories(); if ($ocs) { foreach($ocs as $k => $v) { - $categories[] = array( + $categories[] = [ 'id' => $k, 'displayName' => str_replace('ownCloud ', '', $v) - ); + ]; } } } @@ -97,7 +144,8 @@ class AppSettingsController extends Controller { } /** - * Get all available categories + * Get all available apps in a category + * * @param int $category * @return array */ @@ -134,16 +182,9 @@ class AppSettingsController extends Controller { }); break; default: - if ($category === 2) { - $apps = \OC_App::getAppstoreApps('approved'); - if ($apps) { - $apps = array_filter($apps, function ($app) { - return isset($app['internalclass']) && $app['internalclass'] === 'recommendedapp'; - }); - } - } else { - $apps = \OC_App::getAppstoreApps('approved', $category); - } + $filter = $this->config->getSystemValue('appstore.experimental.enabled', false) ? 'all' : 'approved'; + + $apps = \OC_App::getAppstoreApps($filter, $category); if (!$apps) { $apps = array(); } else { diff --git a/settings/css/settings.css b/settings/css/settings.css index 3bc0a442f06..426fdbb8b94 100644 --- a/settings/css/settings.css +++ b/settings/css/settings.css @@ -203,22 +203,52 @@ input.userFilter {width: 200px;} background: #fbb; } -.recommendedapp { - font-size: 11px; - background-position: left center; - padding-left: 18px; - vertical-align: top; +span.version { + margin-left: 1em; + margin-right: 1em; + color: #555; } -span.version { margin-left:1em; margin-right:1em; color:#555; } #app-navigation .app-external, -.app-version, -.recommendedapp { +.app-version { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)"; filter: alpha(opacity=50); opacity: .5; } +.app-level { + margin-top: 8px; +} +.app-level span { + color: #555; + background-color: transparent; + border: 1px solid #555; + border-radius: 3px; + padding: 3px 6px; +} +.app-level .official { + border-color: #37ce02; + background-position: left center; + background-position: 5px center; + padding-left: 25px; +} +.app-level .approved { + border-color: #e8c805; +} +.app-level .experimental { + background-color: #ce3702; + border-color: #ce3702; + color: #fff; +} +.apps-experimental { + color: #ce3702; +} + +.app-score { + position: relative; + top: 4px; +} + #apps-list { position: relative; height: 100%; @@ -226,6 +256,9 @@ span.version { margin-left:1em; margin-right:1em; color:#555; } .section { position: relative; } +.section h2.app-name { + margin-bottom: 8px; +} .app-image { float: left; padding-right: 10px; @@ -245,7 +278,7 @@ span.version { margin-left:1em; margin-right:1em; color:#555; } .app-name, .app-version, .app-score, -.recommendedapp { +.app-level { display: inline-block; } @@ -270,13 +303,17 @@ span.version { margin-left:1em; margin-right:1em; color:#555; } white-space: pre-line; } -#app-category-2 { +#app-category-1 { border-bottom: 1px solid #e8e8e8; } +/* capitalize "Other" category */ +#app-category-925 { + text-transform: capitalize; +} .app-dependencies { margin-top: 10px; - color: #c33; + color: #ce3702; } .missing-dependencies { @@ -311,7 +348,7 @@ table.grid td.date{ #security-warning li { list-style: initial; margin: 10px 0; - color: #c33; + color: #ce3702; } #shareAPI p { padding-bottom: 0.8em; } #shareAPI input#shareapiExpireAfterNDays {width: 25px;} @@ -362,12 +399,12 @@ table.grid td.date{ } span.success { - background: #37ce02; - border-radius: 3px; + background: #37ce02; + border-radius: 3px; } span.error { - background: #ce3702; + background: #ce3702; } diff --git a/settings/js/apps.js b/settings/js/apps.js index 3db84e8acd5..1bd7ffdf790 100644 --- a/settings/js/apps.js +++ b/settings/js/apps.js @@ -9,6 +9,17 @@ Handlebars.registerHelper('score', function() { } return new Handlebars.SafeString(''); }); +Handlebars.registerHelper('level', function() { + if(typeof this.level !== 'undefined') { + if(this.level === 200) { + return new Handlebars.SafeString('<span class="official icon-checkmark">Official</span>'); + } else if(this.level === 100) { + return new Handlebars.SafeString('<span class="approved">Approved</span>'); + } else { + return new Handlebars.SafeString('<span class="experimental">Experimental</span>'); + } + } +}); OC.Settings = OC.Settings || {}; OC.Settings.Apps = OC.Settings.Apps || { @@ -73,7 +84,6 @@ OC.Settings.Apps = OC.Settings.Apps || { this._loadCategoryCall = $.ajax(OC.generateUrl('settings/apps/list?category={categoryId}', { categoryId: categoryId }), { - data:{}, type:'GET', success: function (apps) { OC.Settings.Apps.State.apps = _.indexBy(apps.apps, 'id'); @@ -81,13 +91,27 @@ OC.Settings.Apps = OC.Settings.Apps || { var template = Handlebars.compile(source); if (apps.apps.length) { + apps.apps.sort(function(a,b) { + return b.level - a.level; + }); + + var firstExperimental = false; _.each(apps.apps, function(app) { - OC.Settings.Apps.renderApp(app, template, null); + if(app.level === 0 && firstExperimental === false) { + firstExperimental = true; + OC.Settings.Apps.renderApp(app, template, null, true); + } else { + OC.Settings.Apps.renderApp(app, template, null, false); + } }); } else { $('#apps-list').addClass('hidden'); $('#apps-list-empty').removeClass('hidden'); } + + $('.app-level .official').tipsy({fallback: t('core', 'Official apps are developed by and within the ownCloud community. They offer functionality central to ownCloud and are ready for production use.')}); + $('.app-level .approved').tipsy({fallback: t('core', 'Approved apps are developed by trusted developers and have passed a cursory security check. They are actively maintained in an open code repository and their maintainers deem them to be stable for casual to normal use.')}); + $('.app-level .experimental').tipsy({fallback: t('core', 'This app is not checked for security issues and is new or known to be unstable. Install on your own risk.')}); }, complete: function() { $('#apps-list').removeClass('icon-loading'); @@ -95,7 +119,7 @@ OC.Settings.Apps = OC.Settings.Apps || { }); }, - renderApp: function(app, template, selector) { + renderApp: function(app, template, selector, firstExperimental) { if (!template) { var source = $("#app-template").html(); template = Handlebars.compile(source); @@ -103,6 +127,7 @@ OC.Settings.Apps = OC.Settings.Apps || { if (typeof app === 'string') { app = OC.Settings.Apps.State.apps[app]; } + app.firstExperimental = firstExperimental; var html = template(app); if (selector) { @@ -438,6 +463,16 @@ OC.Settings.Apps = OC.Settings.Apps || { $select.change(); }); + $(document).on('click', '#enable-experimental-apps', function () { + var state = $(this).prop('checked') + $.ajax(OC.generateUrl('settings/apps/experimental'), { + data: {state: state}, + type: 'POST', + success:function () { + location.reload(); + } + }); + }); } }; diff --git a/settings/routes.php b/settings/routes.php index af9ac1d8eea..1bb14812145 100644 --- a/settings/routes.php +++ b/settings/routes.php @@ -33,25 +33,27 @@ namespace OC\Settings; $application = new Application(); -$application->registerRoutes($this, array( - 'resources' => array( - 'groups' => array('url' => '/settings/users/groups'), - 'users' => array('url' => '/settings/users/users') - ), - 'routes' => array( - array('name' => 'MailSettings#setMailSettings', 'url' => '/settings/admin/mailsettings', 'verb' => 'POST'), - array('name' => 'MailSettings#storeCredentials', 'url' => '/settings/admin/mailsettings/credentials', 'verb' => 'POST'), - array('name' => 'MailSettings#sendTestMail', 'url' => '/settings/admin/mailtest', 'verb' => 'POST'), - array('name' => 'AppSettings#listCategories', 'url' => '/settings/apps/categories', 'verb' => 'GET'), - array('name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'), - array('name' => 'SecuritySettings#trustedDomains', 'url' => '/settings/admin/security/trustedDomains', 'verb' => 'POST'), - array('name' => 'Users#setMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'), - array('name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'), - array('name' => 'LogSettings#getEntries', 'url' => '/settings/admin/log/entries', 'verb' => 'GET'), - array('name' => 'LogSettings#download', 'url' => '/settings/admin/log/download', 'verb' => 'GET'), +$application->registerRoutes($this, [ + 'resources' => [ + 'groups' => ['url' => '/settings/users/groups'], + 'users' => ['url' => '/settings/users/users'] + ], + 'routes' => [ + ['name' => 'MailSettings#setMailSettings', 'url' => '/settings/admin/mailsettings', 'verb' => 'POST'], + ['name' => 'MailSettings#storeCredentials', 'url' => '/settings/admin/mailsettings/credentials', 'verb' => 'POST'], + ['name' => 'MailSettings#sendTestMail', 'url' => '/settings/admin/mailtest', 'verb' => 'POST'], + ['name' => 'AppSettings#listCategories', 'url' => '/settings/apps/categories', 'verb' => 'GET'], + ['name' => 'AppSettings#viewApps', 'url' => '/settings/apps', 'verb' => 'GET'], + ['name' => 'AppSettings#listApps', 'url' => '/settings/apps/list', 'verb' => 'GET'], + ['name' => 'AppSettings#changeExperimentalConfigState', 'url' => '/settings/apps/experimental', 'verb' => 'POST'], + ['name' => 'SecuritySettings#trustedDomains', 'url' => '/settings/admin/security/trustedDomains', 'verb' => 'POST'], + ['name' => 'Users#setMailAddress', 'url' => '/settings/users/{id}/mailAddress', 'verb' => 'PUT'], + ['name' => 'LogSettings#setLogLevel', 'url' => '/settings/admin/log/level', 'verb' => 'POST'], + ['name' => 'LogSettings#getEntries', 'url' => '/settings/admin/log/entries', 'verb' => 'GET'], + ['name' => 'LogSettings#download', 'url' => '/settings/admin/log/download', 'verb' => 'GET'], ['name' => 'CheckSetup#check', 'url' => '/settings/ajax/checksetup', 'verb' => 'GET'], - ) -)); + ] +]); /** @var $this \OCP\Route\IRouter */ @@ -62,8 +64,6 @@ $this->create('settings_personal', '/settings/personal') ->actionInclude('settings/personal.php'); $this->create('settings_users', '/settings/users') ->actionInclude('settings/users.php'); -$this->create('settings_apps', '/settings/apps') - ->actionInclude('settings/apps.php'); $this->create('settings_admin', '/settings/admin') ->actionInclude('settings/admin.php'); // Settings ajax actions diff --git a/settings/templates/apps.php b/settings/templates/apps.php index a2fe5d9b63a..31de7fa2318 100644 --- a/settings/templates/apps.php +++ b/settings/templates/apps.php @@ -1,3 +1,27 @@ +<?php +style('settings', 'settings'); +vendor_style( + 'core', + [ + 'select2/select2', + ] +); +vendor_script( + 'core', + [ + 'handlebars/handlebars', + 'select2/select2' + ] +); +script( + 'settings', + [ + 'settings', + 'apps', + ] +); +/** @var array $_ */ +?> <script id="categories-template" type="text/x-handlebars-template"> {{#each this}} <li id="app-category-{{id}}" data-category-id="{{id}}" tabindex="0"> @@ -16,6 +40,18 @@ </script> <script id="app-template" type="text/x-handlebars"> + {{#if firstExperimental}} + <div class="section apps-experimental"> + <h2><?php p($l->t('Experimental applications ahead')) ?></h2> + <p> + <?php p($l->t('Experimental apps are not checked for security ' . + 'issues, new or known to be unstable and under heavy ' . + 'development. Installing them can cause data loss or security ' . + 'breaches.')) ?> + </p> + </div> + {{/if}} + <div class="section" id="app-{{id}}"> {{#if preview}} <div class="app-image{{#if previewAsIcon}} app-image-icon{{/if}} hidden"> @@ -23,17 +59,19 @@ {{/if}} <h2 class="app-name"><a href="{{detailpage}}" target="_blank">{{name}}</a></h2> <div class="app-version"> {{version}}</div> + {{#if profilepage}}<a href="{{profilepage}}" target="_blank" rel="noreferrer">{{/if}} <div class="app-author"><?php p($l->t('by')); ?> {{author}} {{#if licence}} ({{licence}}-<?php p($l->t('licensed')); ?>) {{/if}} </div> + {{#if profilepage}}</a>{{/if}} + <div class="app-level"> + {{{level}}} + </div> {{#if score}} <div class="app-score">{{{score}}}</div> {{/if}} - {{#if internalclass}} - <div class="{{internalclass}} icon-checkmark">{{internallabel}}</div> - {{/if}} <div class="app-detailpage"></div> <div class="app-description-container hidden"> @@ -95,6 +133,24 @@ <ul id="apps-categories"> </ul> + <div id="app-settings"> + <div id="app-settings-header"> + <button class="settings-button" data-apps-slide-toggle="#app-settings-content"></button> + </div> + + <div id="app-settings-content" class="apps-experimental"> + <input type="checkbox" id="enable-experimental-apps" <?php if($_['experimentalEnabled']) { print_unescaped('checked="checked"'); }?>> + <label for="enable-experimental-apps"><?php p($l->t('Enable experimental apps')) ?></label> + <p> + <small> + <?php p($l->t('Experimental apps are not checked for security ' . + 'issues, new or known to be unstable and under heavy ' . + 'development. Installing them can cause data loss or security ' . + 'breaches.')) ?> + </small> + </p> + </div> + </div> </div> <div id="app-content"> <div id="apps-list" class="icon-loading"></div> diff --git a/settings/tests/js/appsSpec.js b/settings/tests/js/appsSpec.js index 39329c19246..311fade2c66 100644 --- a/settings/tests/js/appsSpec.js +++ b/settings/tests/js/appsSpec.js @@ -98,4 +98,58 @@ describe('OC.Settings.Apps tests', function() { expect(results[0]).toEqual('somestuff'); }); }); + + describe('loading categories', function() { + var suite = this; + + beforeEach( function(){ + suite.server = sinon.fakeServer.create(); + }); + + afterEach( function(){ + suite.server.restore(); + }); + + function getResultsFromDom() { + var results = []; + $('#apps-list .section:not(.hidden)').each(function() { + results.push($(this).attr('data-id')); + }); + return results; + } + + it('sorts all applications using the level', function() { + Apps.loadCategory('TestId'); + + suite.server.requests[0].respond( + 200, + { + 'Content-Type': 'application/json' + }, + JSON.stringify({ + apps: [ + { + id: 'foo', + level: 0 + }, + { + id: 'alpha', + level: 300 + }, + { + id: 'delta', + level: 200 + } + ] + }) + ); + + var results = getResultsFromDom(); + expect(results.length).toEqual(3); + expect(results[0]).toEqual('alpha'); + expect(results[1]).toEqual('delta'); + expect(results[2]).toEqual('foo'); + }); + }); + }); |