summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/files/css/merged.scss1
-rw-r--r--apps/files/js/app.js2
-rw-r--r--apps/updatenotification/lib/Settings/Admin.php33
-rw-r--r--core/Controller/WhatsNewController.php126
-rw-r--r--core/css/whatsnew.scss31
-rw-r--r--core/js/core.json1
-rw-r--r--core/js/merged-template-prepend.json1
-rw-r--r--core/js/public/whatsnew.js134
-rw-r--r--core/routes.php2
-rw-r--r--lib/composer/composer/autoload_classmap.php3
-rw-r--r--lib/composer/composer/autoload_static.php3
-rw-r--r--lib/private/L10N/Factory.php34
-rw-r--r--lib/private/L10N/LanguageIterator.php137
-rw-r--r--lib/private/Updater/ChangesCheck.php11
-rw-r--r--lib/public/L10N/IFactory.php16
-rw-r--r--lib/public/L10N/ILanguageIterator.php74
-rw-r--r--tests/lib/L10N/FactoryTest.php30
-rw-r--r--tests/lib/L10N/LanguageIteratorTest.php98
-rw-r--r--tests/lib/Updater/ChangesCheckTest.php39
19 files changed, 714 insertions, 62 deletions
diff --git a/apps/files/css/merged.scss b/apps/files/css/merged.scss
index d65bac512f1..8a11e55c269 100644
--- a/apps/files/css/merged.scss
+++ b/apps/files/css/merged.scss
@@ -2,3 +2,4 @@
@import 'upload.scss';
@import 'mobile.scss';
@import 'detailsView.scss';
+@import '../../../core/css/whatsnew.scss';
diff --git a/apps/files/js/app.js b/apps/files/js/app.js
index 52c92645b2d..c2cef563fa0 100644
--- a/apps/files/js/app.js
+++ b/apps/files/js/app.js
@@ -133,6 +133,8 @@
});
this._debouncedPersistShowHiddenFilesState = _.debounce(this._persistShowHiddenFilesState, 1200);
+
+ OCP.WhatsNew.query(); // for Nextcloud server
},
/**
diff --git a/apps/updatenotification/lib/Settings/Admin.php b/apps/updatenotification/lib/Settings/Admin.php
index b859ca79f62..cae62ee0a9f 100644
--- a/apps/updatenotification/lib/Settings/Admin.php
+++ b/apps/updatenotification/lib/Settings/Admin.php
@@ -123,39 +123,18 @@ class Admin implements ISettings {
return $filtered;
}
- $isFirstCall = true;
+ $iterator = $this->l10nFactory->getLanguageIterator();
do {
- $lang = $this->l10nFactory->iterateLanguage($isFirstCall);
- if($this->findWhatsNewTranslation($lang, $filtered, $changes['whatsNew'])) {
- return $filtered;
+ $lang = $iterator->current();
+ if(isset($changes['whatsNew'][$lang])) {
+ return $filtered['whatsNew'][$lang];
}
- $isFirstCall = false;
- } while($lang !== 'en');
+ $iterator->next();
+ } while($lang !== 'en' && $iterator->valid());
return $filtered;
}
- protected function getLangTrunk(string $lang):string {
- $pos = strpos($lang, '_');
- if($pos !== false) {
- $lang = substr($lang, 0, $pos);
- }
- return $lang;
- }
-
- protected function findWhatsNewTranslation(string $lang, array &$result, array $whatsNew): bool {
- if(isset($whatsNew[$lang])) {
- $result['whatsNew'] = $whatsNew[$lang];
- return true;
- }
- $trunkedLang = $this->getLangTrunk($lang);
- if($trunkedLang !== $lang && isset($whatsNew[$trunkedLang])) {
- $result['whatsNew'] = $whatsNew[$trunkedLang];
- return true;
- }
- return false;
- }
-
/**
* @param array $groupIds
* @return array
diff --git a/core/Controller/WhatsNewController.php b/core/Controller/WhatsNewController.php
new file mode 100644
index 00000000000..c3a6d28cea2
--- /dev/null
+++ b/core/Controller/WhatsNewController.php
@@ -0,0 +1,126 @@
+<?php
+/**
+ * @copyright Copyright (c) 2018 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\Core\Controller;
+
+use OC\CapabilitiesManager;
+use OC\Security\IdentityProof\Manager;
+use OC\Updater\ChangesCheck;
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\Defaults;
+use OCP\IConfig;
+use OCP\IRequest;
+use OCP\IUserManager;
+use OCP\IUserSession;
+use OCP\L10N\IFactory;
+
+class WhatsNewController extends OCSController {
+
+ /** @var IConfig */
+ protected $config;
+ /** @var IUserSession */
+ private $userSession;
+ /** @var ChangesCheck */
+ private $whatsNewService;
+ /** @var IFactory */
+ private $langFactory;
+ /** @var Defaults */
+ private $defaults;
+
+ public function __construct(
+ string $appName,
+ IRequest $request,
+ CapabilitiesManager $capabilitiesManager,
+ IUserSession $userSession,
+ IUserManager $userManager,
+ Manager $keyManager,
+ IConfig $config,
+ ChangesCheck $whatsNewService,
+ IFactory $langFactory,
+ Defaults $defaults
+ ) {
+ parent::__construct($appName, $request, $capabilitiesManager, $userSession, $userManager, $keyManager);
+ $this->config = $config;
+ $this->userSession = $userSession;
+ $this->whatsNewService = $whatsNewService;
+ $this->langFactory = $langFactory;
+ $this->defaults = $defaults;
+ }
+
+ /**
+ * @NoAdminRequired
+ */
+ public function get():DataResponse {
+ $user = $this->userSession->getUser();
+ if($user === null) {
+ throw new \RuntimeException("Acting user cannot be resolved");
+ }
+ $lastRead = $this->config->getUserValue($user->getUID(), 'core', 'whatsNewLastRead', 0);
+ $currentVersion = $this->whatsNewService->normalizeVersion($this->config->getSystemValue('version'));
+
+ if(version_compare($lastRead, $currentVersion, '>=')) {
+ return new DataResponse([], Http::STATUS_NO_CONTENT);
+ }
+
+ try {
+ $iterator = $this->langFactory->getLanguageIterator();
+ $whatsNew = $this->whatsNewService->getChangesForVersion($currentVersion);
+ $resultData = [
+ 'changelogURL' => $whatsNew['changelogURL'],
+ 'product' => $this->defaults->getName(),
+ 'version' => $currentVersion,
+ ];
+ do {
+ $lang = $iterator->current();
+ if(isset($whatsNew['whatsNew'][$lang])) {
+ $resultData['whatsNew'] = $whatsNew['whatsNew'][$lang];
+ break;
+ }
+ $iterator->next();
+ } while ($lang !== 'en' && $iterator->valid());
+ return new DataResponse($resultData);
+ } catch (DoesNotExistException $e) {
+ return new DataResponse([], Http::STATUS_NO_CONTENT);
+ }
+ }
+
+ /**
+ * @NoAdminRequired
+ *
+ * @throws \OCP\PreConditionNotMetException
+ * @throws DoesNotExistException
+ */
+ public function dismiss(string $version):DataResponse {
+ $user = $this->userSession->getUser();
+ if($user === null) {
+ throw new \RuntimeException("Acting user cannot be resolved");
+ }
+ $version = $this->whatsNewService->normalizeVersion($version);
+ // checks whether it's a valid version, throws an Exception otherwise
+ $this->whatsNewService->getChangesForVersion($version);
+ $this->config->setUserValue($user->getUID(), 'core', 'whatsNewLastRead', $version);
+ return new DataResponse();
+ }
+}
diff --git a/core/css/whatsnew.scss b/core/css/whatsnew.scss
new file mode 100644
index 00000000000..1c2ab08333a
--- /dev/null
+++ b/core/css/whatsnew.scss
@@ -0,0 +1,31 @@
+/**
+ * @copyright Copyright (c) 2018, Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ */
+
+.whatsNewPopover {
+ bottom: 35px !important;
+ left: 15px !important;
+ width: 270px;
+ background-color: var(--color-background-dark);
+}
+
+.whatsNewPopover p {
+ width: auto !important;
+}
+
+.whatsNewPopover .caption {
+ font-weight: bolder;
+ cursor: auto !important;
+}
+
+.whatsNewPopover .icon-close {
+ position: absolute;
+ right: 0;
+}
+
+.whatsNewPopover::after {
+ content: none;
+}
diff --git a/core/js/core.json b/core/js/core.json
index 41b927147b6..502e3a57976 100644
--- a/core/js/core.json
+++ b/core/js/core.json
@@ -48,6 +48,7 @@
"public/appconfig.js",
"public/comments.js",
"public/publicpage.js",
+ "public/whatsnew.js",
"multiselect.js",
"oc-requesttoken.js",
"setupchecks.js",
diff --git a/core/js/merged-template-prepend.json b/core/js/merged-template-prepend.json
index f4ef511bc78..c274201d97e 100644
--- a/core/js/merged-template-prepend.json
+++ b/core/js/merged-template-prepend.json
@@ -7,6 +7,7 @@
"eventsource.js",
"public/appconfig.js",
"public/comments.js",
+ "public/whatsnew.js",
"config.js",
"oc-requesttoken.js",
"apps.js",
diff --git a/core/js/public/whatsnew.js b/core/js/public/whatsnew.js
new file mode 100644
index 00000000000..20a871ada27
--- /dev/null
+++ b/core/js/public/whatsnew.js
@@ -0,0 +1,134 @@
+/**
+ * @copyright (c) 2017 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ */
+
+(function(OCP) {
+ "use strict";
+
+ OCP.WhatsNew = {
+
+ query: function(options) {
+ options = options || {};
+ var dismissOptions = options.dismiss || {};
+ $.ajax({
+ type: 'GET',
+ url: options.url || OC.linkToOCS('core', 2) + 'whatsnew?format=json',
+ success: options.success || function(data, statusText, xhr) {
+ OCP.WhatsNew._onQuerySuccess(data, statusText, xhr, dismissOptions);
+ },
+ error: options.error || this._onQueryError
+ });
+ },
+
+ dismiss: function(version, options) {
+ options = options || {};
+ $.ajax({
+ type: 'POST',
+ url: options.url || OC.linkToOCS('core', 2) + 'whatsnew',
+ data: {version: encodeURIComponent(version)},
+ success: options.success || this._onDismissSuccess,
+ error: options.error || this._onDismissError
+ });
+ // remove element immediately
+ $('.whatsNewPopover').remove();
+ },
+
+ _onQuerySuccess: function(data, statusText, xhr, dismissOptions) {
+ console.debug('querying Whats New data was successful: ' + statusText);
+ console.debug(data);
+
+ if(xhr.status !== 200) {
+ return;
+ }
+
+ var item, menuItem, text, icon;
+
+ var div = document.createElement('div');
+ div.classList.add('popovermenu', 'open', 'whatsNewPopover', 'menu-left');
+
+ var list = document.createElement('ul');
+
+ // header
+ item = document.createElement('li');
+ menuItem = document.createElement('span');
+ menuItem.className = "menuitem";
+
+ text = document.createElement('span');
+ text.innerText = t('core', 'New in') + ' ' + data['ocs']['data']['product'];
+ text.className = 'caption';
+ menuItem.appendChild(text);
+
+ icon = document.createElement('span');
+ icon.className = 'icon-close';
+ icon.onclick = function () {
+ OCP.WhatsNew.dismiss(data['ocs']['data']['version'], dismissOptions);
+ };
+ menuItem.appendChild(icon);
+
+ item.appendChild(menuItem);
+ list.appendChild(item);
+
+ // Highlights
+ for (var i in data['ocs']['data']['whatsNew']['regular']) {
+ var whatsNewTextItem = data['ocs']['data']['whatsNew']['regular'][i];
+ item = document.createElement('li');
+
+ menuItem = document.createElement('span');
+ menuItem.className = "menuitem";
+
+ icon = document.createElement('span');
+ icon.className = 'icon-star-dark';
+ menuItem.appendChild(icon);
+
+ text = document.createElement('p');
+ text.innerHTML = _.escape(whatsNewTextItem);
+ menuItem.appendChild(text);
+
+ item.appendChild(menuItem);
+ list.appendChild(item);
+ }
+
+ // Changelog URL
+ if(!_.isUndefined(data['ocs']['data']['changelogURL'])) {
+ item = document.createElement('li');
+
+ menuItem = document.createElement('a');
+ menuItem.href = data['ocs']['data']['changelogURL'];
+ menuItem.rel = 'noreferrer noopener';
+ menuItem.target = '_blank';
+
+ icon = document.createElement('span');
+ icon.className = 'icon-link';
+ menuItem.appendChild(icon);
+
+ text = document.createElement('span');
+ text.innerText = t('core', 'View changelog');
+ menuItem.appendChild(text);
+
+ item.appendChild(menuItem);
+ list.appendChild(item);
+ }
+
+ div.appendChild(list);
+ document.body.appendChild(div);
+ },
+
+ _onQueryError: function (x, t, e) {
+ console.debug('querying Whats New Data resulted in an error: ' + t + e);
+ console.debug(x);
+ },
+
+ _onDismissSuccess: function(data) {
+ //noop
+ },
+
+ _onDismissError: function (data) {
+ console.debug('dismissing Whats New data resulted in an error: ' + data);
+ }
+ };
+})(OCP);
diff --git a/core/routes.php b/core/routes.php
index 90282c5ebf7..c5df3a362f5 100644
--- a/core/routes.php
+++ b/core/routes.php
@@ -76,6 +76,8 @@ $application->registerRoutes($this, [
['root' => '/core', 'name' => 'Navigation#getAppsNavigation', 'url' => '/navigation/apps', 'verb' => 'GET'],
['root' => '/core', 'name' => 'Navigation#getSettingsNavigation', 'url' => '/navigation/settings', 'verb' => 'GET'],
['root' => '/core', 'name' => 'AutoComplete#get', 'url' => '/autocomplete/get', 'verb' => 'GET'],
+ ['root' => '/core', 'name' => 'WhatsNew#get', 'url' => '/whatsnew', 'verb' => 'GET'],
+ ['root' => '/core', 'name' => 'WhatsNew#dismiss', 'url' => '/whatsnew', 'verb' => 'POST'],
],
]);
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 7113f60bc68..1c67fb9327f 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -259,6 +259,7 @@ return array(
'OCP\\IUserSession' => $baseDir . '/lib/public/IUserSession.php',
'OCP\\Image' => $baseDir . '/lib/public/Image.php',
'OCP\\L10N\\IFactory' => $baseDir . '/lib/public/L10N/IFactory.php',
+ 'OCP\\L10N\\ILanguageIterator' => $baseDir . '/lib/public/L10N/ILanguageIterator.php',
'OCP\\LDAP\\IDeletionFlagSupport' => $baseDir . '/lib/public/LDAP/IDeletionFlagSupport.php',
'OCP\\LDAP\\ILDAPProvider' => $baseDir . '/lib/public/LDAP/ILDAPProvider.php',
'OCP\\LDAP\\ILDAPProviderFactory' => $baseDir . '/lib/public/LDAP/ILDAPProviderFactory.php',
@@ -596,6 +597,7 @@ return array(
'OC\\Core\\Controller\\TwoFactorChallengeController' => $baseDir . '/core/Controller/TwoFactorChallengeController.php',
'OC\\Core\\Controller\\UserController' => $baseDir . '/core/Controller/UserController.php',
'OC\\Core\\Controller\\WalledGardenController' => $baseDir . '/core/Controller/WalledGardenController.php',
+ 'OC\\Core\\Controller\\WhatsNewController' => $baseDir . '/core/Controller/WhatsNewController.php',
'OC\\Core\\Middleware\\TwoFactorMiddleware' => $baseDir . '/core/Middleware/TwoFactorMiddleware.php',
'OC\\Core\\Migrations\\Version13000Date20170705121758' => $baseDir . '/core/Migrations/Version13000Date20170705121758.php',
'OC\\Core\\Migrations\\Version13000Date20170718121200' => $baseDir . '/core/Migrations/Version13000Date20170718121200.php',
@@ -793,6 +795,7 @@ return array(
'OC\\L10N\\Factory' => $baseDir . '/lib/private/L10N/Factory.php',
'OC\\L10N\\L10N' => $baseDir . '/lib/private/L10N/L10N.php',
'OC\\L10N\\L10NString' => $baseDir . '/lib/private/L10N/L10NString.php',
+ 'OC\\L10N\\LanguageIterator' => $baseDir . '/lib/private/L10N/LanguageIterator.php',
'OC\\L10N\\LanguageNotFoundException' => $baseDir . '/lib/private/L10N/LanguageNotFoundException.php',
'OC\\LargeFileHelper' => $baseDir . '/lib/private/LargeFileHelper.php',
'OC\\Lock\\AbstractLockingProvider' => $baseDir . '/lib/private/Lock/AbstractLockingProvider.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index ffb50df5b09..e40a5ea75db 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -289,6 +289,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OCP\\IUserSession' => __DIR__ . '/../../..' . '/lib/public/IUserSession.php',
'OCP\\Image' => __DIR__ . '/../../..' . '/lib/public/Image.php',
'OCP\\L10N\\IFactory' => __DIR__ . '/../../..' . '/lib/public/L10N/IFactory.php',
+ 'OCP\\L10N\\ILanguageIterator' => __DIR__ . '/../../..' . '/lib/public/L10N/ILanguageIterator.php',
'OCP\\LDAP\\IDeletionFlagSupport' => __DIR__ . '/../../..' . '/lib/public/LDAP/IDeletionFlagSupport.php',
'OCP\\LDAP\\ILDAPProvider' => __DIR__ . '/../../..' . '/lib/public/LDAP/ILDAPProvider.php',
'OCP\\LDAP\\ILDAPProviderFactory' => __DIR__ . '/../../..' . '/lib/public/LDAP/ILDAPProviderFactory.php',
@@ -626,6 +627,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Core\\Controller\\TwoFactorChallengeController' => __DIR__ . '/../../..' . '/core/Controller/TwoFactorChallengeController.php',
'OC\\Core\\Controller\\UserController' => __DIR__ . '/../../..' . '/core/Controller/UserController.php',
'OC\\Core\\Controller\\WalledGardenController' => __DIR__ . '/../../..' . '/core/Controller/WalledGardenController.php',
+ 'OC\\Core\\Controller\\WhatsNewController' => __DIR__ . '/../../..' . '/core/Controller/WhatsNewController.php',
'OC\\Core\\Middleware\\TwoFactorMiddleware' => __DIR__ . '/../../..' . '/core/Middleware/TwoFactorMiddleware.php',
'OC\\Core\\Migrations\\Version13000Date20170705121758' => __DIR__ . '/../../..' . '/core/Migrations/Version13000Date20170705121758.php',
'OC\\Core\\Migrations\\Version13000Date20170718121200' => __DIR__ . '/../../..' . '/core/Migrations/Version13000Date20170718121200.php',
@@ -823,6 +825,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\L10N\\Factory' => __DIR__ . '/../../..' . '/lib/private/L10N/Factory.php',
'OC\\L10N\\L10N' => __DIR__ . '/../../..' . '/lib/private/L10N/L10N.php',
'OC\\L10N\\L10NString' => __DIR__ . '/../../..' . '/lib/private/L10N/L10NString.php',
+ 'OC\\L10N\\LanguageIterator' => __DIR__ . '/../../..' . '/lib/private/L10N/LanguageIterator.php',
'OC\\L10N\\LanguageNotFoundException' => __DIR__ . '/../../..' . '/lib/private/L10N/LanguageNotFoundException.php',
'OC\\LargeFileHelper' => __DIR__ . '/../../..' . '/lib/private/LargeFileHelper.php',
'OC\\Lock\\AbstractLockingProvider' => __DIR__ . '/../../..' . '/lib/private/Lock/AbstractLockingProvider.php',
diff --git a/lib/private/L10N/Factory.php b/lib/private/L10N/Factory.php
index 79495f5ff88..cc2de174509 100644
--- a/lib/private/L10N/Factory.php
+++ b/lib/private/L10N/Factory.php
@@ -35,6 +35,7 @@ use OCP\IRequest;
use OCP\IUser;
use OCP\IUserSession;
use OCP\L10N\IFactory;
+use OCP\L10N\ILanguageIterator;
/**
* A factory that generates language instances
@@ -322,35 +323,12 @@ class Factory implements IFactory {
return array_search($lang, $languages) !== false;
}
- public function iterateLanguage(bool $reset = false): string {
- static $i = 0;
- if($reset) {
- $i = 0;
- }
- switch($i) {
- /** @noinspection PhpMissingBreakStatementInspection */
- case 0:
- $i++;
- $forcedLang = $this->config->getSystemValue('force_language', false);
- if(is_string($forcedLang)) {
- return $forcedLang;
- }
- /** @noinspection PhpMissingBreakStatementInspection */
- case 1:
- $i++;
- $user = $this->userSession->getUser();
- if($user instanceof IUser) {
- $userLang = $this->config->getUserValue($user->getUID(), 'core', 'lang', null);
- if(is_string($userLang)) {
- return $userLang;
- }
- }
- case 2:
- $i++;
- return $this->config->getSystemValue('default_language', 'en');
- default:
- return 'en';
+ public function getLanguageIterator(IUser $user = null): ILanguageIterator {
+ $user = $user ?? $this->userSession->getUser();
+ if($user === null) {
+ throw new \RuntimeException('Failed to get an IUser instance');
}
+ return new LanguageIterator($user, $this->config);
}
/**
diff --git a/lib/private/L10N/LanguageIterator.php b/lib/private/L10N/LanguageIterator.php
new file mode 100644
index 00000000000..ba8f942c912
--- /dev/null
+++ b/lib/private/L10N/LanguageIterator.php
@@ -0,0 +1,137 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2018 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OC\L10N;
+
+use OCP\IConfig;
+use OCP\IUser;
+use OCP\L10N\ILanguageIterator;
+
+class LanguageIterator implements ILanguageIterator {
+ private $i = 0;
+ /** @var IConfig */
+ private $config;
+ /** @var IUser */
+ private $user;
+
+ public function __construct(IUser $user, IConfig $config) {
+ $this->config = $config;
+ $this->user = $user;
+ }
+
+ /**
+ * Rewind the Iterator to the first element
+ */
+ public function rewind() {
+ $this->i = 0;
+ }
+
+ /**
+ * Return the current element
+ *
+ * @since 14.0.0
+ */
+ public function current(): string {
+ switch($this->i) {
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case 0:
+ $forcedLang = $this->config->getSystemValue('force_language', false);
+ if(is_string($forcedLang)) {
+ return $forcedLang;
+ }
+ $this->next();
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case 1:
+ $forcedLang = $this->config->getSystemValue('force_language', false);
+ if(is_string($forcedLang)
+ && ($truncated = $this->getTruncatedLanguage($forcedLang)) !== $forcedLang
+ ) {
+ return $truncated;
+ }
+ $this->next();
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case 2:
+ $userLang = $this->config->getUserValue($this->user->getUID(), 'core', 'lang', null);
+ if(is_string($userLang)) {
+ return $userLang;
+ }
+ $this->next();
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case 3:
+ $userLang = $this->config->getUserValue($this->user->getUID(), 'core', 'lang', null);
+ if(is_string($userLang)
+ && ($truncated = $this->getTruncatedLanguage($userLang)) !== $userLang
+ ) {
+ return $truncated;
+ }
+ $this->next();
+ case 4:
+ return $this->config->getSystemValue('default_language', 'en');
+ /** @noinspection PhpMissingBreakStatementInspection */
+ case 5:
+ $defaultLang = $this->config->getSystemValue('default_language', 'en');
+ if(($truncated = $this->getTruncatedLanguage($defaultLang)) !== $defaultLang) {
+ return $truncated;
+ }
+ $this->next();
+ default:
+ return 'en';
+ }
+ }
+
+ /**
+ * Move forward to next element
+ *
+ * @since 14.0.0
+ */
+ public function next() {
+ ++$this->i;
+ }
+
+ /**
+ * Return the key of the current element
+ *
+ * @since 14.0.0
+ */
+ public function key(): int {
+ return $this->i;
+ }
+
+ /**
+ * Checks if current position is valid
+ *
+ * @since 14.0.0
+ */
+ public function valid(): bool {
+ return $this->i <= 6;
+ }
+
+ protected function getTruncatedLanguage(string $lang):string {
+ $pos = strpos($lang, '_');
+ if($pos !== false) {
+ $lang = substr($lang, 0, $pos);
+ }
+ return $lang;
+ }
+}
diff --git a/lib/private/Updater/ChangesCheck.php b/lib/private/Updater/ChangesCheck.php
index 095f63db879..095cf396e89 100644
--- a/lib/private/Updater/ChangesCheck.php
+++ b/lib/private/Updater/ChangesCheck.php
@@ -48,6 +48,15 @@ class ChangesCheck {
}
/**
+ * @throws DoesNotExistException
+ */
+ public function getChangesForVersion(string $version): array {
+ $version = $this->normalizeVersion($version);
+ $changesInfo = $this->mapper->getChanges($version);
+ return json_decode($changesInfo->getData(), true);
+ }
+
+ /**
* @throws \Exception
*/
public function check(string $uri, string $version): array {
@@ -145,7 +154,7 @@ class ChangesCheck {
* returns a x.y.z form of the provided version. Extra numbers will be
* omitted, missing ones added as zeros.
*/
- protected function normalizeVersion(string $version): string {
+ public function normalizeVersion(string $version): string {
$versionNumbers = array_slice(explode('.', $version), 0, 3);
$versionNumbers[0] = $versionNumbers[0] ?: '0'; // deal with empty input
while(count($versionNumbers) < 3) {
diff --git a/lib/public/L10N/IFactory.php b/lib/public/L10N/IFactory.php
index 9c006073200..1bc231e4e2e 100644
--- a/lib/public/L10N/IFactory.php
+++ b/lib/public/L10N/IFactory.php
@@ -21,6 +21,8 @@
*/
namespace OCP\L10N;
+use OCP\IUser;
+
/**
* @since 8.2.0
*/
@@ -93,10 +95,16 @@ interface IFactory {
/**
* iterate through language settings (if provided) in this order:
* 1. returns the forced language or:
- * 2. returns the user language or:
- * 3. returns the system default language or:
- * 4+∞. returns 'en'
+ * 2. if applicable, the trunk of 1 (e.g. "fu" instead of "fu_BAR"
+ * 3. returns the user language or:
+ * 4. if applicable, the trunk of 3
+ * 5. returns the system default language or:
+ * 6. if applicable, the trunk of 5
+ * 7+∞. returns 'en'
+ *
+ * Hint: in most cases findLanguage() suits you fine
+ *
* @since 14.0.0
*/
- public function iterateLanguage(bool $reset = false): string;
+ public function getLanguageIterator(IUser $user = null): ILanguageIterator;
}
diff --git a/lib/public/L10N/ILanguageIterator.php b/lib/public/L10N/ILanguageIterator.php
new file mode 100644
index 00000000000..ef923dfd249
--- /dev/null
+++ b/lib/public/L10N/ILanguageIterator.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * @copyright Copyright (c) 2018 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\L10N;
+
+/**
+ * Interface ILanguageIterator
+ *
+ * iterator across language settings (if provided) in this order:
+ * 1. returns the forced language or:
+ * 2. if applicable, the trunk of 1 (e.g. "fu" instead of "fu_BAR"
+ * 3. returns the user language or:
+ * 4. if applicable, the trunk of 3
+ * 5. returns the system default language or:
+ * 6. if applicable, the trunk of 5
+ * 7+∞. returns 'en'
+ *
+ * if settings are not present or truncating is not applicable, the iterator
+ * skips to the next valid item itself
+ *
+ * @package OCP\L10N
+ *
+ * @since 14.0.0
+ */
+interface ILanguageIterator extends \Iterator {
+
+ /**
+ * Return the current element
+ *
+ * @since 14.0.0
+ */
+ public function current(): string;
+
+ /**
+ * Move forward to next element
+ *
+ * @since 14.0.0
+ */
+ public function next();
+
+ /**
+ * Return the key of the current element
+ *
+ * @since 14.0.0
+ */
+ public function key():int;
+
+ /**
+ * Checks if current position is valid
+ *
+ * @since 14.0.0
+ */
+ public function valid():bool;
+}
diff --git a/tests/lib/L10N/FactoryTest.php b/tests/lib/L10N/FactoryTest.php
index 3008e0a239c..be842cf12c7 100644
--- a/tests/lib/L10N/FactoryTest.php
+++ b/tests/lib/L10N/FactoryTest.php
@@ -14,6 +14,7 @@ use OCP\IConfig;
use OCP\IRequest;
use OCP\IUser;
use OCP\IUserSession;
+use OCP\L10N\ILanguageIterator;
use Test\TestCase;
/**
@@ -596,4 +597,33 @@ class FactoryTest extends TestCase {
$this->assertSame($expected, $result);
}
+ public function languageIteratorRequestProvider():array {
+ return [
+ [ true, $this->createMock(IUser::class)],
+ [ false, $this->createMock(IUser::class)],
+ [ false, null]
+ ];
+ }
+
+ /**
+ * @dataProvider languageIteratorRequestProvider
+ */
+ public function testGetLanguageIterator(bool $hasSession, IUser $iUserMock = null) {
+ $factory = $this->getFactory();
+
+ if($iUserMock === null) {
+ $matcher = $this->userSession->expects($this->once())
+ ->method('getUser');
+
+ if($hasSession) {
+ $matcher->willReturn($this->createMock(IUser::class));
+ } else {
+ $this->expectException(\RuntimeException::class);
+ }
+ }
+
+ $iterator = $factory->getLanguageIterator($iUserMock);
+ $this->assertInstanceOf(ILanguageIterator::class, $iterator);
+ }
+
}
diff --git a/tests/lib/L10N/LanguageIteratorTest.php b/tests/lib/L10N/LanguageIteratorTest.php
new file mode 100644
index 00000000000..c8b1b24685f
--- /dev/null
+++ b/tests/lib/L10N/LanguageIteratorTest.php
@@ -0,0 +1,98 @@
+<?php
+/**
+ * @copyright Copyright (c) 2018 Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
+ *
+ * @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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Test\L10N;
+
+use OC\L10N\LanguageIterator;
+use OCP\IConfig;
+use OCP\IUser;
+use Test\TestCase;
+
+class LanguageIteratorTest extends TestCase {
+ /** @var IUser|\PHPUnit_Framework_MockObject_MockObject */
+ protected $user;
+ /** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
+ protected $config;
+ /** @var LanguageIterator */
+ protected $iterator;
+
+ public function setUp() {
+ parent::setUp();
+
+ $this->user = $this->createMock(IUser::class);
+ $this->config = $this->createMock(IConfig::class);
+
+ $this->iterator = new LanguageIterator($this->user, $this->config);
+ }
+
+ public function languageSettingsProvider() {
+ return [
+ // all language settings set
+ [ 'de_DE', 'es_CU', 'zh_TW', ['de_DE', 'de', 'es_CU', 'es', 'zh_TW', 'zh', 'en']],
+ [ 'de', 'es', 'zh', ['de', 'es', 'zh', 'en']],
+ [ 'en', 'en', 'en', ['en', 'en', 'en', 'en']],
+ // one possible setting is missing each
+ [ false, 'es_CU', 'zh_TW', ['es_CU', 'es', 'zh_TW', 'zh', 'en']],
+ [ false, 'es', 'zh_TW', ['es', 'zh_TW', 'zh', 'en']],
+ [ false, 'es_CU', 'zh', ['es_CU', 'es', 'zh', 'en']],
+ [ 'de_DE', null, 'zh_TW', ['de_DE', 'de', 'zh_TW', 'zh', 'en']],
+ [ 'de_DE', null, 'zh', ['de_DE', 'de', 'zh', 'en']],
+ [ 'de', null, 'zh_TW', ['de', 'zh_TW', 'zh', 'en']],
+ [ 'de_DE', 'es_CU', 'en', ['de_DE', 'de', 'es_CU', 'es', 'en', 'en']],
+ [ 'de', 'es_CU', 'en', ['de', 'es_CU', 'es', 'en', 'en']],
+ [ 'de_DE', 'es', 'en', ['de_DE', 'de', 'es', 'en', 'en']],
+ // two possible settings are missing each
+ [ false, null, 'zh_TW', ['zh_TW', 'zh', 'en']],
+ [ false, null, 'zh', ['zh', 'en']],
+ [ false, 'es_CU', 'en', ['es_CU', 'es', 'en', 'en']],
+ [ false, 'es', 'en', ['es', 'en', 'en']],
+ [ 'de_DE', null, 'en', ['de_DE', 'de', 'en', 'en']],
+ [ 'de', null, 'en', ['de', 'en', 'en']],
+ // nothing is set
+ [ false, null, 'en', ['en', 'en']],
+
+ ];
+ }
+
+ /**
+ * @dataProvider languageSettingsProvider
+ */
+ public function testIterator($forcedLang, $userLang, $sysLang, $expectedValues) {
+ $this->config->expects($this->any())
+ ->method('getSystemValue')
+ ->willReturnMap([
+ ['force_language', false, $forcedLang],
+ ['default_language', 'en', $sysLang],
+ ]);
+ $this->config->expects($this->any())
+ ->method('getUserValue')
+ ->willReturn($userLang);
+
+ foreach ($expectedValues as $expected) {
+ $this->assertTrue($this->iterator->valid());
+ $this->assertSame($expected, $this->iterator->current());
+ $this->iterator->next();
+ }
+ $this->assertFalse($this->iterator->valid());
+ }
+}
diff --git a/tests/lib/Updater/ChangesCheckTest.php b/tests/lib/Updater/ChangesCheckTest.php
index cbb298647d0..fe25e8cebaf 100644
--- a/tests/lib/Updater/ChangesCheckTest.php
+++ b/tests/lib/Updater/ChangesCheckTest.php
@@ -29,11 +29,11 @@ namespace Test\Updater;
use OC\Updater\ChangesCheck;
use OC\Updater\ChangesMapper;
use OC\Updater\ChangesResult;
+use OCP\AppFramework\Db\DoesNotExistException;
use OCP\Http\Client\IClient;
use OCP\Http\Client\IClientService;
use OCP\Http\Client\IResponse;
use OCP\ILogger;
-use const Solarium\QueryType\Select\Query\Component\Facet\INCLUDE_LOWER;
use Test\TestCase;
class ChangesCheckTest extends TestCase {
@@ -338,7 +338,42 @@ class ChangesCheckTest extends TestCase {
* @dataProvider versionProvider
*/
public function testNormalizeVersion(string $input, string $expected) {
- $normalized = $this->invokePrivate($this->checker, 'normalizeVersion', [$input]);
+ $normalized = $this->checker->normalizeVersion($input);
$this->assertSame($expected, $normalized);
}
+
+ public function changeDataProvider():array {
+ $testDataFound = $testDataNotFound = $this->versionProvider();
+ array_walk($testDataFound, function(&$params) { $params[] = true; });
+ array_walk($testDataNotFound, function(&$params) { $params[] = false; });
+ return array_merge($testDataFound, $testDataNotFound);
+ }
+
+ /**
+ * @dataProvider changeDataProvider
+ *
+ */
+ public function testGetChangesForVersion(string $inputVersion, string $normalizedVersion, bool $isFound) {
+ $mocker = $this->mapper->expects($this->once())
+ ->method('getChanges')
+ ->with($normalizedVersion);
+
+ if(!$isFound) {
+ $this->expectException(DoesNotExistException::class);
+ $mocker->willThrowException(new DoesNotExistException('Changes info is not present'));
+ } else {
+ $entry = $this->createMock(ChangesResult::class);
+ $entry->expects($this->once())
+ ->method('__call')
+ ->with('getData')
+ ->willReturn('{"changelogURL":"https:\/\/nextcloud.com\/changelog\/#13-0-0","whatsNew":{"en":{"regular":["Refined user interface","End-to-end Encryption","Video and Text Chat"],"admin":["Changes to the Nginx configuration","Theming: CSS files were consolidated"]},"de":{"regular":["\u00dcberarbeitete Benutzerschnittstelle","Ende-zu-Ende Verschl\u00fcsselung","Video- und Text-Chat"],"admin":["\u00c4nderungen an der Nginx Konfiguration","Theming: CSS Dateien wurden konsolidiert"]}}}');
+
+ $mocker->willReturn($entry);
+ }
+
+ /** @noinspection PhpUnhandledExceptionInspection */
+ $data = $this->checker->getChangesForVersion($inputVersion);
+ $this->assertTrue(isset($data['whatsNew']['en']['regular']));
+ $this->assertTrue(isset($data['changelogURL']));
+ }
}