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