Browse Source
Backend work to provide NC whats New info to users
Backend work to provide NC whats New info to users
Signed-off-by: Arthur Schiwon <blizzz@arthur-schiwon.de>pull/10110/head
No known key found for this signature in database
GPG Key ID: 7424F1874854DF23
17 changed files with 593 additions and 62 deletions
-
2apps/files/js/app.js
-
33apps/updatenotification/lib/Settings/Admin.php
-
117core/Controller/WhatsNewController.php
-
1core/js/core.json
-
1core/js/merged-template-prepend.json
-
54core/js/public/whatsnew.js
-
2core/routes.php
-
3lib/composer/composer/autoload_classmap.php
-
3lib/composer/composer/autoload_static.php
-
34lib/private/L10N/Factory.php
-
137lib/private/L10N/LanguageIterator.php
-
11lib/private/Updater/ChangesCheck.php
-
16lib/public/L10N/IFactory.php
-
74lib/public/L10N/ILanguageIterator.php
-
30tests/lib/L10N/FactoryTest.php
-
98tests/lib/L10N/LanguageIteratorTest.php
-
39tests/lib/Updater/ChangesCheckTest.php
@ -0,0 +1,117 @@ |
|||
<?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\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; |
|||
|
|||
public function __construct( |
|||
string $appName, |
|||
IRequest $request, |
|||
CapabilitiesManager $capabilitiesManager, |
|||
IUserSession $userSession, |
|||
IUserManager $userManager, |
|||
Manager $keyManager, |
|||
IConfig $config, |
|||
ChangesCheck $whatsNewService, |
|||
IFactory $langFactory |
|||
) { |
|||
parent::__construct($appName, $request, $capabilitiesManager, $userSession, $userManager, $keyManager); |
|||
$this->config = $config; |
|||
$this->userSession = $userSession; |
|||
$this->whatsNewService = $whatsNewService; |
|||
$this->langFactory = $langFactory; |
|||
} |
|||
|
|||
/** |
|||
* @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']]; |
|||
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(); |
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
/** |
|||
* @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 || {}; |
|||
$.ajax({ |
|||
type: 'GET', |
|||
url: options.url || OC.linkToOCS('core', 2) + 'whatsnew?format=json', |
|||
success: options.success || this._onQuerySuccess, |
|||
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 |
|||
}); |
|||
}, |
|||
|
|||
_onQuerySuccess: function(data, statusText) { |
|||
console.debug('querying Whats New data was successful: ' + data || statusText); |
|||
console.debug(data); |
|||
}, |
|||
|
|||
_onQueryError: function (o, t, e) { |
|||
console.debug(o); |
|||
console.debug('querying Whats New Data resulted in an error: ' + t +e); |
|||
}, |
|||
|
|||
_onDismissSuccess: function(data) { |
|||
console.debug('dismissing Whats New data was successful: ' + data); |
|||
}, |
|||
|
|||
_onDismissError: function (data) { |
|||
console.debug('dismissing Whats New data resulted in an error: ' + data); |
|||
} |
|||
}; |
|||
})(OCP); |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
@ -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()); |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue