diff --git a/README.md b/README.md
index 77f58ec9..745a9ea7 100644
--- a/README.md
+++ b/README.md
@@ -6,3 +6,51 @@ the name for the external site, an icon appears. When this icon is clicked by a
user, the external website appears in the Nextcloud frame. For the user, this
external site appears as if it is part of Nextcloud but, in fact, this can be
any external URL.
+
+## OCS API
+
+It is also possible to get the sites via an OCS endpoint. The request must be authenticated.
+Only sites for the user“s language are returned:
+```bash
+curl -H "OCS-APIRequest: true" \
+ https://admin:admin@localhost/ocs/v2.php/apps/external/api/v1
+```
+
+### Response
+```xml
+
+
+
+ ok
+ 200
+ OK
+
+
+
+ 23
+ Homepage
+ https://localhost/index.php
+ en
+ external.svg
+
+
+
+```
+
+### Capability
+
+The app registers a capability, so clients can check that before making the actual OCS request:
+```xml
+
+
+ ...
+
+
+ ...
+
+
+ sites
+
+
+ ...
+```
diff --git a/ajax/setsites.php b/ajax/setsites.php
deleted file mode 100644
index 7b7ee626..00000000
--- a/ajax/setsites.php
+++ /dev/null
@@ -1,43 +0,0 @@
-getL10N('external');
-
-foreach($sites as $site) {
- if (strpos($site[1], 'https://') === 0) {
- continue;
- }
- if (strpos($site[1], 'http://') === 0) {
- continue;
- }
- if (strncmp($site[1], '/', 1) === 0) {
- continue;
- }
- OC_JSON::error(array("data" => array( "message" => $l->t('Please enter valid urls - they have to start with either http://, https:// or /') )));
- return;
-}
-
-if (sizeof($sites) == 0) {
- $appConfig = \OC::$server->getAppConfig();
- $appConfig->deleteKey('external', 'sites');
-} else {
- OCP\Config::setAppValue('external', 'sites', json_encode($sites));
-}
-OC_JSON::success(array("data" => array( "message" => $l->t("External sites saved.") )));
diff --git a/appinfo/app.php b/appinfo/app.php
index b27fa4ef..fd013498 100644
--- a/appinfo/app.php
+++ b/appinfo/app.php
@@ -1,44 +1,23 @@
*
- * @author Frank Karlitschek
- * @copyright 2012 Frank Karlitschek frank@owncloud.org
+ * @license GNU AGPL version 3 or any later version
*
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or any later version.
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
*
- * This library is distributed in the hope that it will be useful,
+ * 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.
+ * GNU Affero General Public License for more details.
*
- * You should have received a copy of the GNU Affero General Public
- * License along with this library. If not, see .
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
*
*/
-use OCA\External\External;
-
-OCP\App::registerAdmin('external', 'settings');
-
-$sites = External::getSites();
-if (!empty($sites)) {
- $urlGenerator = \OC::$server->getURLGenerator();
- $navigationManager = \OC::$server->getNavigationManager();
- for ($i = 0; $i < sizeof($sites); $i++) {
- $navigationEntry = function () use ($i, $urlGenerator, $sites) {
- return [
- 'id' => 'external_index' . ($i + 1),
- 'order' => 80 + $i,
- 'href' => $urlGenerator->linkToRoute('external_index', ['id'=> $i + 1]),
- 'icon' => $urlGenerator->imagePath('external', !empty($sites[$i][2]) ? $sites[$i][2] : 'external.svg'),
- 'name' => $sites[$i][0],
- ];
- };
- $navigationManager->add($navigationEntry);
- }
-}
+$app = new \OCA\External\AppInfo\Application();
+$app->register();
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 77c92018..6cd37eee 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -24,9 +24,14 @@
https://github.com/nextcloud/external/issues
https://github.com/nextcloud/external.git
- 1.2
+ 2.0.0
+ External
+
+
+ OCA\External\Settings\Admin
+
diff --git a/appinfo/routes.php b/appinfo/routes.php
index d6cf34df..4b9c81b4 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -1,12 +1,33 @@
- * This file is licensed under the Affero General Public License version 3 or later.
- * See the COPYING-README file.
+ * @copyright Copyright (c) 2017 Joas Schilling
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
*/
-/** @var $this \OCP\Route\IRouter */
-$this->create('external_index', '/{id}')
- ->actionInclude('external/index.php');
-$this->create('external_ajax_setsites', 'ajax/setsites.php')
- ->actionInclude('external/ajax/setsites.php');
+return [
+ 'routes' => [
+ ['name' => 'page#showPage', 'url' => '/{id}', 'verb' => 'GET'],
+ ],
+ 'ocs' => [
+ ['name' => 'API#get', 'url' => '/api/{apiVersion}', 'verb' => 'GET', 'requirements' => ['apiVersion' => 'v1']],
+ ['name' => 'API#getAdmin', 'url' => '/api/{apiVersion}/sites', 'verb' => 'GET', 'requirements' => ['apiVersion' => 'v1']],
+ ['name' => 'API#add', 'url' => '/api/{apiVersion}/sites', 'verb' => 'POST', 'requirements' => ['apiVersion' => 'v1']],
+ ['name' => 'API#update', 'url' => '/api/{apiVersion}/sites/{id}', 'verb' => 'PUT', 'requirements' => ['apiVersion' => 'v1', 'id' => '\d+']],
+ ['name' => 'API#delete', 'url' => '/api/{apiVersion}/sites/{id}', 'verb' => 'DELETE', 'requirements' => ['apiVersion' => 'v1', 'id' => '\d+']],
+ ],
+];
diff --git a/css/style.css b/css/style.css
index bbf8b2ea..3f59d25c 100644
--- a/css/style.css
+++ b/css/style.css
@@ -1,14 +1,26 @@
-/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- /
-/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */
+.site-url {
+ width: 250px;
+}
+
+#loading_sites {
+ width: 512px;
+}
+
+.delete-button {
+ display: none;
+ cursor: pointer;
+}
-.site_url {
- width: 250px;
+.invalid-value {
+ border-color: red !important;
}
-.delete_button {
- display: none;
+li:hover .delete-button {
+ display: inline-block;
}
-.external_sites {
- width: 470px;
+#ifm {
+ display: block;
+ width: 100%;
+ height: 100%;
}
diff --git a/index.php b/index.php
deleted file mode 100644
index aee54d4d..00000000
--- a/index.php
+++ /dev/null
@@ -1,47 +0,0 @@
-.
- *
- */
-
-
-use OCA\External\External;
-
-OCP\JSON::checkAppEnabled('external');
-OCP\User::checkLoggedIn();
-OCP\Util::addStyle( 'external', 'style');
-
-$id = isset($_GET['id']) ? (int)$_GET['id'] : 1;
-
-$sites = External::getSites();
-if (sizeof($sites) >= $id) {
- $url = $sites[$id - 1][1];
- OCP\App::setActiveNavigationEntry('external_index' . $id);
-
- $tmpl = new OCP\Template('external', 'frame', 'user');
- //overwrite x-frame-options
- header('X-Frame-Options: ALLOW-FROM *');
-
- $tmpl->assign('url', $url);
- $tmpl->printPage();
-} else {
- \OC_Util::redirectToDefaultPage();
-}
-
diff --git a/js/admin.js b/js/admin.js
index 539017f3..91435b18 100644
--- a/js/admin.js
+++ b/js/admin.js
@@ -1,62 +1,203 @@
-$(document).ready(function(){
-
- // Handler functions
- function addSiteEventHandler(event) {
- event.preventDefault();
+/**
+ * @copyright Copyright (c) 2017 Joas Schilling
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
- saveSites();
+/* global OC, OCA, _, Backbone */
+(function(OC, OCA, _, Backbone) {
+ if (!OCA.External) {
+ /**
+ * @namespace
+ */
+ OCA.External = {};
}
- function deleteButtonEventHandler(event) {
- event.preventDefault();
+ Handlebars.registerHelper('isSelected', function(currentValue, itemValue) {
+ return currentValue === itemValue;
+ });
+
+ Handlebars.registerHelper('getIcons', function() {
+ return OCA.External.App.availableIcons;
+ });
+
+ Handlebars.registerHelper('getLanguages', function() {
+ return OCA.External.App.availableLanguages;
+ });
+
+ OCA.External.Models = OCA.External.Models || {};
- if($(this).parent().is(':only-child')) {
- $(this).parent().children('input').val('');
- $(this).parent().children('select').val('');
- } else {
- $(this).tipsy('hide');
- $(this).parent().remove();
+ OCA.External.Models.Site = Backbone.Model.extend({
+ defaults: {
+ name: '',
+ url: '',
+ icon: '',
+ lang: ''
+ },
+
+ parse: function(response) {
+ if (!_.isUndefined(response.ocs)) {
+ // Parse of single response from save/create
+ return response.ocs.data;
+ }
+
+ // Parse of entry from collection data
+ return response;
}
+ });
- saveSites();
- }
+ OCA.External.Models.SiteCollection = Backbone.Collection.extend({
+ model: OCA.External.Models.Site,
+ url: OC.linkToOCS('apps/external/api/v1', 2) + 'sites',
- function saveSites() {
- var post = $('#external').serialize();
- OC.msg.startSaving('#external .msg');
- $.post( OC.filePath('external','ajax','setsites.php') , post, function(data) {
- OC.msg.finishedSaving('#external .msg', data);
- });
- }
+ parse: function(response) {
+ return response.ocs.data.sites;
+ }
+ });
- function showDeleteButton() {
- $(this).find('img.delete_button').fadeIn(100);
- }
+ OCA.External.App = {
+ /** @property {OCA.External.Models.SiteCollection} _sites */
+ _sites: null,
- function hideDeleteButton() {
- $(this).find('img.delete_button').fadeOut(100);
- }
+ $list: null,
- // Initialize events
- $('input[name^=site_]').change(addSiteEventHandler);
- $('select[name^=site_]').change(addSiteEventHandler);
- $('img.delete_button').click(deleteButtonEventHandler);
- $('img.delete_button').tipsy();
+ _compiledTemplate: null,
- $('#external li').hover(showDeleteButton, hideDeleteButton);
+ init: function() {
+ var self = this;
+ this.$list = $('ul.external_sites');
- $('#add_external_site').click(function(event) {
- event.preventDefault();
+ this._sites = new OCA.External.Models.SiteCollection();
+ this._sites.fetch({
+ success: function(_, response) {
+ $('#loading_sites').removeClass('icon-loading-small');
+ self.availableIcons = response.ocs.data.icons;
+ self.availableLanguages = response.ocs.data.languages;
- $('#external ul li:last').clone().appendTo('#external ul');
- $('#external ul li:last input').val('');
- $('#external ul li:last select').val('');
+ if (response.ocs.data.sites.length === 0) {
+ var $el = $(self._compiledTemplate({
+ id: 'undefined'
+ }));
+ self._attachEvents($el);
+ self.$list.append($el);
+ } else {
+ self._render();
+ }
+ }
+ });
- $('input.site_url:last').prev('input.site_name').andSelf().change(addSiteEventHandler);
- $('img.delete_button').click(deleteButtonEventHandler);
- $('img.delete_button:last').tipsy();
- $('#external li:last').hover(showDeleteButton, hideDeleteButton);
+ $('#add_external_site').click(function(e) {
+ e.preventDefault();
- });
+ var $el = $(self._compiledTemplate({
+ id: 'undefined',
+ icon: 'external.svg'
+ }));
+ self._attachEvents($el);
+ self.$list.append($el);
+ });
+ this._compiledTemplate = Handlebars.compile($('#site-template').html());
+ },
+
+ _render: function() {
+ var self = this;
+
+ _.each(this._sites.models, function(site) {
+ var $el = $(self._compiledTemplate(site.attributes));
+ self._attachEvents($el);
+ self.$list.append($el);
+ });
+ },
+
+ _attachEvents: function($site) {
+ $site.find('.delete-button').click(_.bind(this._deleteSite, this));
+ $site.find('.trigger-save').change(_.bind(this._saveSite, this));
+ },
+
+ _deleteSite: function(e) {
+ e.preventDefault();
+
+ var $site = $(e.target).closest('li'),
+ site = this._sites.get($site.data('site-id'));
+
+ $site.find('.saving').removeClass('hidden');
+ if (!_.isUndefined(site)) {
+ site.destroy({
+ success: function() {
+ $site.remove();
+ }
+ });
+ } else {
+ $site.remove();
+ }
+ },
+
+ _saveSite: function(e) {
+ e.preventDefault();
+
+ var $target = $(e.target),
+ $site = $target.closest('li'),
+ site = this._sites.get($site.data('site-id')),
+ data = {
+ name: $site.find('.site-name').val(),
+ url: $site.find('.site-url').val(),
+ lang: $site.find('.site-lang').val(),
+ icon: $site.find('.site-icon').val()
+ };
+
+ $site.find('.failure').addClass('hidden');
+ $site.find('.saved').addClass('hidden');
+ $site.find('.saving').removeClass('hidden');
+
+ if (!_.isUndefined(site)) {
+ site.save(data, {
+ success: function() {
+ $site.find('.saving').addClass('hidden');
+ $site.find('.saved').removeClass('hidden');
+ setTimeout(function() {
+ $site.find('.saved').addClass('hidden');
+ }, 2500);
+ },
+ error: function() {
+ $site.find('.saving').addClass('hidden');
+ $site.find('.failure').removeClass('hidden');
+ }
+ });
+ } else {
+ this._sites.create(data, {
+ success: function(site) {
+ $site.data('site-id', site.get('id'));
+ $site.find('.saving').addClass('hidden');
+ $site.find('.saved').removeClass('hidden');
+ setTimeout(function() {
+ $site.find('.saved').addClass('hidden');
+ }, 2500);
+ },
+ error: function() {
+ $site.find('.saving').addClass('hidden');
+ $site.find('.failure').removeClass('hidden');
+ }
+ });
+ }
+ }
+ }
+})(OC, OCA, _, Backbone);
+
+$(document).ready(function(){
+ OCA.External.App.init();
});
diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php
new file mode 100644
index 00000000..87e69b4e
--- /dev/null
+++ b/lib/AppInfo/Application.php
@@ -0,0 +1,66 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\External\AppInfo;
+
+use OCA\External\Capabilities;
+use OCA\External\SitesManager;
+use OCP\AppFramework\App;
+
+class Application extends App {
+
+ public function __construct() {
+ parent::__construct('external');
+
+ $this->getContainer()->registerCapability(Capabilities::class);
+ }
+
+ public function register() {
+ $this->registerNavigationEntries();
+ }
+
+ public function registerNavigationEntries() {
+ $server = $this->getContainer()->getServer();
+ /** @var SitesManager $sitesManager */
+ $sitesManager = $this->getContainer()->query(SitesManager::class);
+
+ $sites = $sitesManager->getSitesByLanguage($server->getL10NFactory()->findLanguage());
+
+ foreach ($sites as $id => $site) {
+ $server->getNavigationManager()->add(function() use ($site, $server) {
+ $url = $server->getURLGenerator();
+
+ try {
+ $image = $url->imagePath('external', $site['icon']);
+ } catch (\RuntimeException $e) {
+ $image = $url->imagePath('external', 'external.svg');
+ }
+ return [
+ 'id' => 'external_index' . $site['id'],
+ 'order' => 80 + $site['id'],
+ 'href' => $url->linkToRoute('external.page.showPage', ['id'=> $site['id']]),
+ 'icon' => $image,
+ 'name' => $site['name'],
+ ];
+ });
+ }
+ }
+}
diff --git a/lib/Capabilities.php b/lib/Capabilities.php
new file mode 100644
index 00000000..3f8f65e9
--- /dev/null
+++ b/lib/Capabilities.php
@@ -0,0 +1,47 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\External;
+
+use OCP\Capabilities\ICapability;
+
+/**
+ * Class Capabilities
+ *
+ * @package OCA\External
+ */
+class Capabilities implements ICapability {
+
+ /**
+ * Return this classes capabilities
+ *
+ * @return array
+ */
+ public function getCapabilities() {
+ return [
+ 'external' => [
+ 'v1' => [
+ 'sites',
+ ],
+ ],
+ ];
+ }
+}
diff --git a/lib/Controller/APIController.php b/lib/Controller/APIController.php
new file mode 100644
index 00000000..3cebc754
--- /dev/null
+++ b/lib/Controller/APIController.php
@@ -0,0 +1,153 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\External\Controller;
+
+use OCA\External\Exceptions\IconNotFoundException;
+use OCA\External\Exceptions\InvalidNameException;
+use OCA\External\Exceptions\InvalidURLException;
+use OCA\External\Exceptions\LanguageNotFoundException;
+use OCA\External\Exceptions\SiteNotFoundException;
+use OCA\External\SitesManager;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\AppFramework\OCSController;
+use OCP\IL10N;
+use OCP\IRequest;
+use OCP\IURLGenerator;
+
+class APIController extends OCSController {
+ /** @var SitesManager */
+ private $sitesManager;
+
+ /** @var IURLGenerator */
+ private $url;
+
+ /** @var IL10N */
+ private $l;
+
+ /**
+ * @param string $appName
+ * @param IRequest $request
+ * @param SitesManager $sitesManager
+ * @param IURLGenerator $url
+ * @param IL10N $l
+ */
+ public function __construct($appName, IRequest $request, SitesManager $sitesManager, IURLGenerator $url, IL10N $l) {
+ parent::__construct($appName, $request);
+
+ $this->sitesManager = $sitesManager;
+ $this->url = $url;
+ $this->l = $l;
+ }
+
+ /**
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ *
+ * @return DataResponse
+ */
+ public function get() {
+ $data = $this->sitesManager->getSitesByLanguage($this->l->getLanguageCode());
+
+ $sites = [];
+ foreach ($data as $site) {
+ $site['icon'] = $this->url->getAbsoluteURL($this->url->imagePath('external', $site['icon']));
+ $sites[] = $site;
+ }
+ return new DataResponse($sites);
+ }
+
+ /**
+ * @NoCSRFRequired
+ *
+ * @return DataResponse
+ */
+ public function getAdmin() {
+ $icons = array_map(function($icon) {
+ return ['icon' => $icon, 'name' => $icon];
+ }, $this->sitesManager->getAvailableIcons());
+ array_unshift($icons, ['icon' => '', 'name' => $this->l->t('Select an icon')]);
+
+ $languages = $this->sitesManager->getAvailableLanguages();
+ array_unshift($languages, ['code' => '', 'name' => $this->l->t('All languages')]);
+
+ return new DataResponse([
+ 'sites' => array_values($this->sitesManager->getSites()),
+ 'icons' => $icons,
+ 'languages' => $languages,
+ ]);
+ }
+
+ /**
+ * @param string $name
+ * @param string $url
+ * @param string $lang
+ * @param string $icon
+ * @return DataResponse
+ */
+ public function add($name, $url, $lang, $icon) {
+ try {
+ return new DataResponse($this->sitesManager->addSite($name, $url, $lang, $icon));
+ } catch (InvalidNameException $e) {
+ return new DataResponse($this->l->t('The given name is invalid'), Http::STATUS_BAD_REQUEST);
+ } catch (InvalidURLException $e) {
+ return new DataResponse($this->l->t('The given url is invalid'), Http::STATUS_BAD_REQUEST);
+ } catch (LanguageNotFoundException $e) {
+ return new DataResponse($this->l->t('The given language does not exist'), Http::STATUS_BAD_REQUEST);
+ } catch (IconNotFoundException $e) {
+ return new DataResponse($this->l->t('The given icon does not exist'), Http::STATUS_BAD_REQUEST);
+ }
+ }
+
+ /**
+ * @param int $id
+ * @param string $name
+ * @param string $url
+ * @param string $lang
+ * @param string $icon
+ * @return DataResponse
+ */
+ public function update($id, $name, $url, $lang, $icon) {
+ try {
+ return new DataResponse($this->sitesManager->updateSite($id, $name, $url, $lang, $icon));
+ } catch (SiteNotFoundException $e) {
+ return new DataResponse($this->l->t('The site does not exist'), Http::STATUS_NOT_FOUND);
+ } catch (InvalidNameException $e) {
+ return new DataResponse($this->l->t('The given name is invalid'), Http::STATUS_BAD_REQUEST);
+ } catch (InvalidURLException $e) {
+ return new DataResponse($this->l->t('The given url is invalid'), Http::STATUS_BAD_REQUEST);
+ } catch (LanguageNotFoundException $e) {
+ return new DataResponse($this->l->t('The given language does not exist'), Http::STATUS_BAD_REQUEST);
+ } catch (IconNotFoundException $e) {
+ return new DataResponse($this->l->t('The given icon does not exist'), Http::STATUS_BAD_REQUEST);
+ }
+ }
+
+ /**
+ * @param int $id
+ * @return DataResponse
+ */
+ public function delete($id) {
+ $this->sitesManager->deleteSite($id);
+ return new DataResponse();
+ }
+}
diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php
new file mode 100644
index 00000000..31ccd28f
--- /dev/null
+++ b/lib/Controller/PageController.php
@@ -0,0 +1,72 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\External\Controller;
+
+use OCA\External\Exceptions\SiteNotFoundException;
+use OCA\External\SitesManager;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\ContentSecurityPolicy;
+use OCP\AppFramework\Http\RedirectResponse;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\INavigationManager;
+use OCP\IRequest;
+
+class PageController extends Controller {
+
+ /** @var SitesManager */
+ protected $sitesManager;
+
+ /** @var INavigationManager */
+ protected $navigationManager;
+
+ public function __construct($appName, IRequest $request, INavigationManager $navigationManager, SitesManager $sitesManager) {
+ parent::__construct($appName, $request);
+ $this->sitesManager = $sitesManager;
+ $this->navigationManager = $navigationManager;
+ }
+
+ /**
+ * @NoAdminRequired
+ * @NoCSRFRequired
+ *
+ * @param int $id
+ * @return TemplateResponse|RedirectResponse
+ */
+ public function showPage($id) {
+ try {
+ $site = $this->sitesManager->getSiteById($id);
+ $this->navigationManager->setActiveEntry('external_index' . $id);
+
+ $response = new TemplateResponse('external', 'frame', [
+ 'url' => $site['url'],
+ ], 'user');
+
+ $policy = new ContentSecurityPolicy();
+ $policy->addAllowedChildSrcDomain('*');
+ $response->setContentSecurityPolicy($policy);
+
+ return $response;
+ } catch (SiteNotFoundException $e) {
+ return new RedirectResponse(\OC_Util::getDefaultPageUrl());
+ }
+ }
+}
diff --git a/lib/Exceptions/IconNotFoundException.php b/lib/Exceptions/IconNotFoundException.php
new file mode 100644
index 00000000..6e698571
--- /dev/null
+++ b/lib/Exceptions/IconNotFoundException.php
@@ -0,0 +1,24 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\External\Exceptions;
+
+class IconNotFoundException extends \OutOfBoundsException {}
diff --git a/lib/Exceptions/InvalidNameException.php b/lib/Exceptions/InvalidNameException.php
new file mode 100644
index 00000000..44295d9f
--- /dev/null
+++ b/lib/Exceptions/InvalidNameException.php
@@ -0,0 +1,24 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\External\Exceptions;
+
+class InvalidNameException extends \UnexpectedValueException {}
diff --git a/lib/Exceptions/InvalidURLException.php b/lib/Exceptions/InvalidURLException.php
new file mode 100644
index 00000000..6f6aba5d
--- /dev/null
+++ b/lib/Exceptions/InvalidURLException.php
@@ -0,0 +1,24 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\External\Exceptions;
+
+class InvalidURLException extends \UnexpectedValueException {}
diff --git a/lib/Exceptions/LanguageNotFoundException.php b/lib/Exceptions/LanguageNotFoundException.php
new file mode 100644
index 00000000..ec9cdc67
--- /dev/null
+++ b/lib/Exceptions/LanguageNotFoundException.php
@@ -0,0 +1,24 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\External\Exceptions;
+
+class LanguageNotFoundException extends \OutOfBoundsException {}
diff --git a/lib/Exceptions/SiteNotFoundException.php b/lib/Exceptions/SiteNotFoundException.php
new file mode 100644
index 00000000..430933c9
--- /dev/null
+++ b/lib/Exceptions/SiteNotFoundException.php
@@ -0,0 +1,24 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\External\Exceptions;
+
+class SiteNotFoundException extends \OutOfBoundsException {}
diff --git a/lib/Settings/Admin.php b/lib/Settings/Admin.php
new file mode 100644
index 00000000..9f2f2987
--- /dev/null
+++ b/lib/Settings/Admin.php
@@ -0,0 +1,55 @@
+
+ *
+ * @author Joas Schilling
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\External\Settings;
+
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\Settings\ISettings;
+
+class Admin implements ISettings {
+
+ /**
+ * @return TemplateResponse
+ */
+ public function getForm() {
+ return new TemplateResponse('external', 'settings', [], 'blank');
+ }
+
+ /**
+ * @return string the section ID, e.g. 'sharing'
+ */
+ public function getSection() {
+ return 'additional';
+ }
+
+ /**
+ * @return int whether the form should be rather on the top or bottom of
+ * the admin section. The forms are arranged in ascending order of the
+ * priority values. It is required to return a value between 0 and 100.
+ *
+ * E.g.: 70
+ */
+ public function getPriority() {
+ return 55;
+ }
+}
diff --git a/lib/SitesManager.php b/lib/SitesManager.php
new file mode 100644
index 00000000..67021a67
--- /dev/null
+++ b/lib/SitesManager.php
@@ -0,0 +1,323 @@
+
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+namespace OCA\External;
+
+use OCA\External\Exceptions\IconNotFoundException;
+use OCA\External\Exceptions\InvalidNameException;
+use OCA\External\Exceptions\InvalidURLException;
+use OCA\External\Exceptions\LanguageNotFoundException;
+use OCA\External\Exceptions\SiteNotFoundException;
+use OCP\App\AppPathNotFoundException;
+use OCP\App\IAppManager;
+use OCP\IConfig;
+use OCP\L10N\IFactory;
+
+class SitesManager {
+
+ /** @var IConfig */
+ protected $config;
+
+ /** @var IFactory */
+ protected $languageFactory;
+
+ /** @var IAppManager */
+ protected $appManager;
+
+ public function __construct(IConfig $config, IAppManager $appManager, IFactory $languageFactory) {
+ $this->config = $config;
+ $this->appManager = $appManager;
+ $this->languageFactory = $languageFactory;
+ }
+
+ /**
+ * @param int $id
+ * @return array
+ * @throws SiteNotFoundException
+ */
+ public function getSiteById($id) {
+ $sites = $this->getSites();
+
+ if (isset($sites[$id])) {
+ return $sites[$id];
+ }
+
+ throw new SiteNotFoundException();
+ }
+
+ /**
+ * @param string $lang
+ * @return array[]
+ */
+ public function getSitesByLanguage($lang) {
+ $sites = $this->getSites();
+
+ $langSites = [];
+ foreach ($sites as $id => $site) {
+ if ($site['lang'] !== '' && $site['lang'] !== $lang) {
+ continue;
+ }
+ $langSites[$id] = $site;
+ }
+
+ return $langSites;
+ }
+
+ /**
+ * @return array[]
+ */
+ public function getSites() {
+ $jsonEncodedList = $this->config->getAppValue('external', 'sites', '');
+ $sites = json_decode($jsonEncodedList, true);
+
+ if (!is_array($sites) || empty($sites)) {
+ return [];
+ }
+
+ if (isset($sites[0][0])) {
+ return $this->getSitesFromOldConfig($sites);
+ }
+
+ return $sites;
+ }
+
+ /**
+ * @param string $name
+ * @param string $url
+ * @param string $lang
+ * @param string $icon
+ * @return array
+ * @throws InvalidNameException
+ * @throws InvalidURLException
+ * @throws LanguageNotFoundException
+ * @throws IconNotFoundException
+ */
+ public function addSite($name, $url, $lang, $icon) {
+ $id = 1 + (int) $this->config->getAppValue('external', 'max_site', 0);
+
+ if ($name === '') {
+ throw new InvalidNameException();
+ }
+
+ if (filter_var($url, FILTER_VALIDATE_URL) === false ||
+ strpos($url, 'http://') === strpos($url, 'https://')) {
+ throw new InvalidURLException();
+ }
+
+ if ($lang !== '') {
+ $valid = false;
+ foreach ($this->getAvailableLanguages() as $language) {
+ if ($language['code'] === $lang) {
+ $valid = true;
+ break;
+ }
+ }
+
+ if (!$valid) {
+ throw new LanguageNotFoundException();
+ }
+ }
+
+ $icons = $this->getAvailableIcons();
+ if ($icon === '') {
+ $icon = 'external.svg';
+ }
+ if (!in_array($icon, $icons, true)) {
+ throw new IconNotFoundException();
+ }
+
+ $sites = $this->getSites();
+ $sites[$id] = [
+ 'id' => $id,
+ 'name' => $name,
+ 'url' => $url,
+ 'lang' => $lang,
+ 'icon' => $icon,
+ ];
+ $this->config->setAppValue('external', 'sites', json_encode($sites));
+ $this->config->setAppValue('external', 'max_site', $id);
+
+ return $sites[$id];
+ }
+
+ /**
+ * @param int $id
+ * @param string $name
+ * @param string $url
+ * @param string $lang
+ * @param string $icon
+ * @return array
+ * @throws SiteNotFoundException
+ * @throws InvalidNameException
+ * @throws InvalidURLException
+ * @throws LanguageNotFoundException
+ * @throws IconNotFoundException
+ */
+ public function updateSite($id, $name, $url, $lang, $icon) {
+ $sites = $this->getSites();
+ if (!isset($sites[$id])) {
+ throw new SiteNotFoundException();
+ }
+
+ if ($name === '') {
+ throw new InvalidNameException();
+ }
+
+ if (filter_var($url, FILTER_VALIDATE_URL) === false ||
+ strpos($url, 'http://') === strpos($url, 'https://')) {
+ throw new InvalidURLException();
+ }
+
+ if ($lang !== '') {
+ $valid = false;
+ foreach ($this->getAvailableLanguages() as $language) {
+ if ($language['code'] === $lang) {
+ $valid = true;
+ break;
+ }
+ }
+
+ if (!$valid) {
+ throw new LanguageNotFoundException();
+ }
+ }
+
+ $icons = $this->getAvailableIcons();
+ if ($icon === '') {
+ $icon = 'external.svg';
+ }
+ if (!in_array($icon, $icons, true)) {
+ throw new IconNotFoundException();
+ }
+
+ $sites[$id] = [
+ 'id' => $id,
+ 'name' => $name,
+ 'url' => $url,
+ 'lang' => $lang,
+ 'icon' => $icon,
+ ];
+ $this->config->setAppValue('external', 'sites', json_encode($sites));
+
+ return $sites[$id];
+ }
+
+ /**
+ * @param int $id
+ */
+ public function deleteSite($id) {
+ $sites = $this->getSites();
+ if (!isset($sites[$id])) {
+ return;
+ }
+
+ unset($sites[$id]);
+ $this->config->setAppValue('external', 'sites', json_encode($sites));
+ }
+
+ /**
+ * @param array[] $sites
+ * @return array[]
+ */
+ protected function getSitesFromOldConfig($sites) {
+ $fixedSites = [];
+
+ /** @var array[] $sites */
+ foreach ($sites as $id => $site) {
+ $fixedSites[$id + 1] = [
+ 'id' => $id + 1,
+ 'name' => $site[0],
+ 'url' => $site[1],
+ // TODO when php7+ is supported: 'icon' => $site[2] ?? 'external.svg',
+ 'icon' => isset($site[2]) ? $site[2] : 'external.svg',
+ 'lang' => '',
+ ];
+ }
+
+ $this->config->setAppValue('external', 'sites', json_encode($fixedSites));
+ $this->config->setAppValue('external', 'max_site', max(array_keys($fixedSites)));
+ return $fixedSites;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getAvailableIcons() {
+ try {
+ return array_map('basename', glob($this->appManager->getAppPath('external') . '/img/*.*'));
+ } catch (AppPathNotFoundException $e) {
+ return ['external.svg'];
+ }
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getAvailableLanguages() {
+ $languageCodes = $this->languageFactory->findAvailableLanguages();
+
+ $languages = [];
+ foreach ($languageCodes as $lang) {
+ $l = $this->languageFactory->get('settings', $lang);
+ $potentialName = $l->t('__language_name__');
+
+ $ln = ['code' => $lang, 'name' => $lang];
+ if ($l->getLanguageCode() === $lang && strpos($potentialName, '_') !== 0) {
+ $ln = ['code' => $lang, 'name' => $potentialName];
+ } else if ($lang === 'en') {
+ $ln = ['code' => $lang, 'name' => 'English (US)'];
+ }
+
+ $languages[] = $ln;
+ }
+
+ $commonLangCodes = ['en', 'es', 'fr', 'de', 'de_DE', 'ja', 'ar', 'ru', 'nl', 'it', 'pt_BR', 'pt_PT', 'da', 'fi_FI', 'nb_NO', 'sv', 'tr', 'zh_CN', 'ko'];
+
+ usort($languages, function ($a, $b) use ($commonLangCodes) {
+ $aC = array_search($a['code'], $commonLangCodes, true);
+ $bC = array_search($b['code'], $commonLangCodes, true);
+
+ if ($aC === false && $bC !== false) {
+ // If a is common, but b is not, list a before b
+ return 1;
+ }
+ if ($aC !== false && $bC === false) {
+ // If a is common, but b is not, list a before b
+ return -1;
+ }
+ if ($aC !== false && $bC !== false) {
+ // If a is common, but b is not, list a before b
+ return $aC - $bC;
+ }
+ if ($a['code'] === $a['name'] && $b['code'] !== $b['name']) {
+ // If a doesn't have a name, but b does, list b before a
+ return 1;
+ }
+ if ($a['code'] !== $a['name'] && $b['code'] === $b['name']) {
+ // If a does have a name, but b doesn't, list a before b
+ return -1;
+ }
+ // Otherwise compare the names
+ return strcmp($a['name'], $b['name']);
+ });
+ return $languages;
+ }
+}
diff --git a/lib/external.php b/lib/external.php
deleted file mode 100644
index 6f2268a9..00000000
--- a/lib/external.php
+++ /dev/null
@@ -1,36 +0,0 @@
-.
- *
- */
-
-namespace OCA\External;
-
-class External {
-
- public static function getSites() {
- if (($sites = json_decode(\OCP\Config::getAppValue("external", "sites", ''))) != null) {
- return $sites;
- }
-
- return array();
- }
-
-}
diff --git a/settings.php b/settings.php
deleted file mode 100644
index 3b082333..00000000
--- a/settings.php
+++ /dev/null
@@ -1,12 +0,0 @@
-assign('images', glob(\OC_App::getAppPath('external') . '/img/*.*'));
-
-return $tmpl->fetchPage();
diff --git a/templates/frame.php b/templates/frame.php
index 212bd9a0..f5c54372 100644
--- a/templates/frame.php
+++ b/templates/frame.php
@@ -1,5 +1,29 @@
-
+
+ *
+ * @author Frank Karlitschek
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+script('external', 'external');
+style('external', 'style');
-
+
diff --git a/templates/settings.php b/templates/settings.php
index 512a4a67..54088e2b 100644
--- a/templates/settings.php
+++ b/templates/settings.php
@@ -1,3 +1,34 @@
+
+ * @copyright Copyright (c) 2017 Joas Schilling
+ *
+ * @author Frank Karlitschek
+ * @author Joas Schilling
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+style('external', 'style');
+script('external', 'admin');
+
+/** @var array $_ */
+/** @var \OCP\IL10N $l */
+?>