Merge pull request #10110 from nextcloud/feature/100500/whats-new-info-users

Display What's New info to users
This commit is contained in:
Morris Jobke 2018-07-13 17:25:51 +02:00 committed by GitHub
commit 14314584ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 714 additions and 62 deletions

View File

@ -2,3 +2,4 @@
@import 'upload.scss';
@import 'mobile.scss';
@import 'detailsView.scss';
@import '../../../core/css/whatsnew.scss';

View File

@ -133,6 +133,8 @@
});
this._debouncedPersistShowHiddenFilesState = _.debounce(this._persistShowHiddenFilesState, 1200);
OCP.WhatsNew.query(); // for Nextcloud server
},
/**

View File

@ -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

View File

@ -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();
}
}

31
core/css/whatsnew.scss Normal file
View File

@ -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;
}

View File

@ -48,6 +48,7 @@
"public/appconfig.js",
"public/comments.js",
"public/publicpage.js",
"public/whatsnew.js",
"multiselect.js",
"oc-requesttoken.js",
"setupchecks.js",

View File

@ -7,6 +7,7 @@
"eventsource.js",
"public/appconfig.js",
"public/comments.js",
"public/whatsnew.js",
"config.js",
"oc-requesttoken.js",
"apps.js",

134
core/js/public/whatsnew.js Normal file
View File

@ -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);

View File

@ -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'],
],
]);

View File

@ -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',

View File

@ -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',

View File

@ -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);
}
/**

View File

@ -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;
}
}

View File

@ -47,6 +47,15 @@ class ChangesCheck {
$this->logger = $logger;
}
/**
* @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
*/
@ -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) {

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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']));
}
}