summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--apps/updatenotification/lib/Settings/Admin.php73
-rw-r--r--apps/updatenotification/lib/UpdateChecker.php17
-rw-r--r--apps/updatenotification/src/components/root.vue15
-rw-r--r--apps/updatenotification/tests/Settings/AdminTest.php6
-rw-r--r--apps/updatenotification/tests/UpdateCheckerTest.php38
-rw-r--r--core/Migrations/Version14000Date20180626223656.php62
-rw-r--r--lib/composer/composer/autoload_classmap.php4
-rw-r--r--lib/composer/composer/autoload_static.php4
-rw-r--r--lib/private/L10N/Factory.php32
-rw-r--r--lib/private/Updater/ChangesCheck.php157
-rw-r--r--lib/private/Updater/ChangesMapper.php57
-rw-r--r--lib/private/Updater/ChangesResult.php61
-rw-r--r--lib/private/Updater/VersionCheck.php9
-rw-r--r--lib/public/L10N/IFactory.php10
-rw-r--r--tests/lib/Updater/ChangesCheckTest.php344
-rw-r--r--tests/lib/Updater/VersionCheckTest.php10
16 files changed, 853 insertions, 46 deletions
diff --git a/apps/updatenotification/lib/Settings/Admin.php b/apps/updatenotification/lib/Settings/Admin.php
index 1696e97d3ae..b859ca79f62 100644
--- a/apps/updatenotification/lib/Settings/Admin.php
+++ b/apps/updatenotification/lib/Settings/Admin.php
@@ -31,6 +31,8 @@ use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\IDateTimeFormatter;
use OCP\IGroupManager;
+use OCP\IUserSession;
+use OCP\L10N\IFactory;
use OCP\Settings\ISettings;
use OCP\Util;
@@ -43,21 +45,25 @@ class Admin implements ISettings {
private $groupManager;
/** @var IDateTimeFormatter */
private $dateTimeFormatter;
-
- /**
- * @param IConfig $config
- * @param UpdateChecker $updateChecker
- * @param IGroupManager $groupManager
- * @param IDateTimeFormatter $dateTimeFormatter
- */
- public function __construct(IConfig $config,
- UpdateChecker $updateChecker,
- IGroupManager $groupManager,
- IDateTimeFormatter $dateTimeFormatter) {
+ /** @var IUserSession */
+ private $session;
+ /** @var IFactory */
+ private $l10nFactory;
+
+ public function __construct(
+ IConfig $config,
+ UpdateChecker $updateChecker,
+ IGroupManager $groupManager,
+ IDateTimeFormatter $dateTimeFormatter,
+ IUserSession $session,
+ IFactory $l10nFactory
+ ) {
$this->config = $config;
$this->updateChecker = $updateChecker;
$this->groupManager = $groupManager;
$this->dateTimeFormatter = $dateTimeFormatter;
+ $this->session = $session;
+ $this->l10nFactory = $l10nFactory;
}
/**
@@ -93,8 +99,7 @@ class Admin implements ISettings {
'channels' => $channels,
'newVersionString' => empty($updateState['updateVersion']) ? '' : $updateState['updateVersion'],
'downloadLink' => empty($updateState['downloadLink']) ? '' : $updateState['downloadLink'],
- 'changelogURL' => empty($updateState['changelog']) ? false : $updateState['changelog'],
- 'whatsNew' => empty($updateState['whatsNew']) ? false : $updateState['whatsNew'],
+ 'changes' => $this->filterChanges($updateState['changes'] ?? []),
'updaterEnabled' => empty($updateState['updaterEnabled']) ? false : $updateState['updaterEnabled'],
'versionIsEol' => empty($updateState['versionIsEol']) ? false : $updateState['versionIsEol'],
'isDefaultUpdateServerURL' => $updateServerURL === $defaultUpdateServerURL,
@@ -109,6 +114,48 @@ class Admin implements ISettings {
return new TemplateResponse('updatenotification', 'admin', $params, '');
}
+ protected function filterChanges(array $changes) {
+ $filtered = [];
+ if(isset($changes['changelogURL'])) {
+ $filtered['changelogURL'] = $changes['changelogURL'];
+ }
+ if(!isset($changes['whatsNew'])) {
+ return $filtered;
+ }
+
+ $isFirstCall = true;
+ do {
+ $lang = $this->l10nFactory->iterateLanguage($isFirstCall);
+ if($this->findWhatsNewTranslation($lang, $filtered, $changes['whatsNew'])) {
+ return $filtered;
+ }
+ $isFirstCall = false;
+ } while($lang !== 'en');
+
+ 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/apps/updatenotification/lib/UpdateChecker.php b/apps/updatenotification/lib/UpdateChecker.php
index ec8e119049b..bd03cb442be 100644
--- a/apps/updatenotification/lib/UpdateChecker.php
+++ b/apps/updatenotification/lib/UpdateChecker.php
@@ -25,17 +25,21 @@ declare(strict_types=1);
namespace OCA\UpdateNotification;
+use OC\Updater\ChangesCheck;
use OC\Updater\VersionCheck;
class UpdateChecker {
/** @var VersionCheck */
private $updater;
+ /** @var ChangesCheck */
+ private $changesCheck;
/**
* @param VersionCheck $updater
*/
- public function __construct(VersionCheck $updater) {
+ public function __construct(VersionCheck $updater, ChangesCheck $changesCheck) {
$this->updater = $updater;
+ $this->changesCheck = $changesCheck;
}
/**
@@ -56,11 +60,12 @@ class UpdateChecker {
if (strpos($data['url'], 'https://') === 0) {
$result['downloadLink'] = $data['url'];
}
- if (strpos($data['changelog'], 'https://') === 0) {
- $result['changelog'] = $data['changelog'];
- }
- if (is_array($data['whatsNew']) && count($data['whatsNew']) <= 3) {
- $result['whatsNew'] = $data['whatsNew'];
+ if (strpos($data['changes'], 'https://') === 0) {
+ try {
+ $result['changes'] = $this->changesCheck->check($data['changes'], $data['version']);
+ } catch (\Exception $e) {
+ // no info, not a problem
+ }
}
return $result;
diff --git a/apps/updatenotification/src/components/root.vue b/apps/updatenotification/src/components/root.vue
index 96cce351cac..fcc5a9dd831 100644
--- a/apps/updatenotification/src/components/root.vue
+++ b/apps/updatenotification/src/components/root.vue
@@ -222,7 +222,9 @@
},
whatsNew: function () {
-
+ if(this.whatsNewData.length === 0) {
+ return null;
+ }
var whatsNew = [];
for (var i in this.whatsNewData) {
whatsNew[i] = { icon: 'icon-star-dark', longtext: this.whatsNewData[i] };
@@ -307,7 +309,6 @@
beforeMount: function() {
// Parse server data
var data = JSON.parse($('#updatenotification').attr('data-json'));
- console.warn(data);
this.newVersionString = data.newVersionString;
this.lastCheckedDate = data.lastChecked;
@@ -321,7 +322,15 @@
this.notifyGroups = data.notifyGroups;
this.isDefaultUpdateServerURL = data.isDefaultUpdateServerURL;
this.versionIsEol = data.versionIsEol;
- this.whatsNewData = data.whatsNew;
+ if(data.changes && data.changes.changelogURL) {
+ this.changelogURL = data.changes.changelogURL;
+ }
+ if(data.changes && data.changes.whatsNew) {
+ if(data.changes.whatsNew.admin) {
+ this.whatsNewData = this.whatsNewData.concat(data.changes.whatsNew.admin);
+ }
+ this.whatsNewData = this.whatsNewData.concat(data.changes.whatsNew.regular);
+ }
},
mounted: function () {
this._$el = $(this.$el);
diff --git a/apps/updatenotification/tests/Settings/AdminTest.php b/apps/updatenotification/tests/Settings/AdminTest.php
index 3e069907507..ed9614a3b6b 100644
--- a/apps/updatenotification/tests/Settings/AdminTest.php
+++ b/apps/updatenotification/tests/Settings/AdminTest.php
@@ -99,8 +99,7 @@ class AdminTest extends TestCase {
'updateAvailable' => true,
'updateVersion' => '8.1.2',
'downloadLink' => 'https://downloads.nextcloud.org/server',
- 'changelog' => 'https://nextcloud.com/changelog/#8.1.2',
- 'whatsNew' => ['Autoshare to mother-in-law', 'Faster backend', 'Sparkling frontend'],
+ 'changes' => 'https://updates.nextcloud.com/changelog_server/?version=8.1.2',
'updaterEnabled' => true,
'versionIsEol' => false,
]);
@@ -126,8 +125,7 @@ class AdminTest extends TestCase {
'channels' => $channels,
'newVersionString' => '8.1.2',
'downloadLink' => 'https://downloads.nextcloud.org/server',
- 'changelogURL' => 'https://nextcloud.com/changelog/#8.1.2',
- 'whatsNew' => ['Autoshare to mother-in-law', 'Faster backend', 'Sparkling frontend'],
+ 'changesURL' => 'https://updates.nextcloud.com/changelog_server/?version=8.1.2',
'updaterEnabled' => true,
'versionIsEol' => false,
'isDefaultUpdateServerURL' => true,
diff --git a/apps/updatenotification/tests/UpdateCheckerTest.php b/apps/updatenotification/tests/UpdateCheckerTest.php
index 0a82f950b9a..16861eea54e 100644
--- a/apps/updatenotification/tests/UpdateCheckerTest.php
+++ b/apps/updatenotification/tests/UpdateCheckerTest.php
@@ -25,11 +25,14 @@ declare(strict_types=1);
namespace OCA\UpdateNotification\Tests;
+use OC\Updater\ChangesCheck;
use OC\Updater\VersionCheck;
use OCA\UpdateNotification\UpdateChecker;
use Test\TestCase;
class UpdateCheckerTest extends TestCase {
+ /** @var ChangesCheck|\PHPUnit_Framework_MockObject_MockObject */
+ protected $changesChecker;
/** @var VersionCheck|\PHPUnit_Framework_MockObject_MockObject */
private $updater;
/** @var UpdateChecker */
@@ -39,7 +42,8 @@ class UpdateCheckerTest extends TestCase {
parent::setUp();
$this->updater = $this->createMock(VersionCheck::class);
- $this->updateChecker = new UpdateChecker($this->updater);
+ $this->changesChecker = $this->createMock(ChangesCheck::class);
+ $this->updateChecker = new UpdateChecker($this->updater, $this->changesChecker);
}
public function testGetUpdateStateWithUpdateAndInvalidLink() {
@@ -51,8 +55,7 @@ class UpdateCheckerTest extends TestCase {
'versionstring' => 'Nextcloud 123',
'web'=> 'javascript:alert(1)',
'url'=> 'javascript:alert(2)',
- 'changelog' => 'javascript:alert(3)',
- 'whatsNew' => 'javascript:alert(4)',
+ 'changes' => 'javascript:alert(3)',
'autoupdater'=> '0',
'eol'=> '1',
]);
@@ -67,20 +70,40 @@ class UpdateCheckerTest extends TestCase {
}
public function testGetUpdateStateWithUpdateAndValidLink() {
+ $changes = [
+ 'changelog' => 'https://nextcloud.com/changelog/#123-0-0',
+ 'whatsNew' => [
+ 'en' => [
+ 'regular' => [
+ 'Yardarm heave to brig spyglass smartly pillage',
+ 'Bounty gangway bilge skysail rope\'s end',
+ 'Maroon cutlass spirits nipperkin Plate Fleet',
+ ],
+ 'admin' => [
+ 'Scourge of the seven seas coffer doubloon',
+ 'Brig me splice the main brace',
+ ]
+ ]
+ ]
+ ];
+
$this->updater
->expects($this->once())
->method('check')
->willReturn([
- 'version' => 123,
+ 'version' => '123',
'versionstring' => 'Nextcloud 123',
'web'=> 'https://docs.nextcloud.com/myUrl',
'url'=> 'https://downloads.nextcloud.org/server',
- 'changelog' => 'https://nextcloud.com/changelog/#123.0.0',
- 'whatsNew' => ['Brews coffee', 'Makes appointments', 'Orchestrates Terminators'],
+ 'changes' => 'https://updates.nextcloud.com/changelog_server/?version=123.0.0',
'autoupdater'=> '1',
'eol'=> '0',
]);
+ $this->changesChecker->expects($this->once())
+ ->method('check')
+ ->willReturn($changes);
+
$expected = [
'updateAvailable' => true,
'updateVersion' => 'Nextcloud 123',
@@ -88,8 +111,7 @@ class UpdateCheckerTest extends TestCase {
'versionIsEol' => false,
'updateLink' => 'https://docs.nextcloud.com/myUrl',
'downloadLink' => 'https://downloads.nextcloud.org/server',
- 'changelog' => 'https://nextcloud.com/changelog/#123.0.0',
- 'whatsNew' => ['Brews coffee', 'Makes appointments', 'Orchestrates Terminators'],
+ 'changes' => $changes,
];
$this->assertSame($expected, $this->updateChecker->getUpdateState());
}
diff --git a/core/Migrations/Version14000Date20180626223656.php b/core/Migrations/Version14000Date20180626223656.php
new file mode 100644
index 00000000000..72348aca3a3
--- /dev/null
+++ b/core/Migrations/Version14000Date20180626223656.php
@@ -0,0 +1,62 @@
+<?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\Migrations;
+
+use OCP\DB\ISchemaWrapper;
+use OCP\Migration\SimpleMigrationStep;
+
+class Version14000Date20180626223656 extends SimpleMigrationStep {
+ public function changeSchema(\OCP\Migration\IOutput $output, \Closure $schemaClosure, array $options) {
+ /** @var ISchemaWrapper $schema */
+ $schema = $schemaClosure();
+ if(!$schema->hasTable('whats_new')) {
+ $table = $schema->createTable('whats_new');
+ $table->addColumn('version', 'string', [
+ 'notnull' => true,
+ 'length' => 64,
+ 'default' => '11',
+ ]);
+ $table->addColumn('etag', 'string', [
+ 'notnull' => true,
+ 'length' => 64,
+ 'default' => '',
+ ]);
+ $table->addColumn('last_check', 'integer', [
+ 'notnull' => true,
+ 'length' => 4,
+ 'unsigned' => true,
+ 'default' => 0,
+ ]);
+ $table->addColumn('data', 'text', [
+ 'notnull' => true,
+ 'default' => '',
+ ]);
+ $table->setPrimaryKey(['version']);
+ $table->addIndex(['version', 'etag'], 'version_etag_idx');
+ }
+
+ return $schema;
+ }
+}
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index b416d6e77bd..a3d4564e3f9 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -595,6 +595,7 @@ return array(
'OC\\Core\\Migrations\\Version14000Date20180516101403' => $baseDir . '/core/Migrations/Version14000Date20180516101403.php',
'OC\\Core\\Migrations\\Version14000Date20180518120534' => $baseDir . '/core/Migrations/Version14000Date20180518120534.php',
'OC\\Core\\Migrations\\Version14000Date20180522074438' => $baseDir . '/core/Migrations/Version14000Date20180522074438.php',
+ 'OC\\Core\\Migrations\\Version14000Date20180626223656' => $baseDir . '/core/Migrations/Version14000Date20180626223656.php',
'OC\\DB\\Adapter' => $baseDir . '/lib/private/DB/Adapter.php',
'OC\\DB\\AdapterMySQL' => $baseDir . '/lib/private/DB/AdapterMySQL.php',
'OC\\DB\\AdapterOCI8' => $baseDir . '/lib/private/DB/AdapterOCI8.php',
@@ -1003,6 +1004,9 @@ return array(
'OC\\Template\\TemplateFileLocator' => $baseDir . '/lib/private/Template/TemplateFileLocator.php',
'OC\\URLGenerator' => $baseDir . '/lib/private/URLGenerator.php',
'OC\\Updater' => $baseDir . '/lib/private/Updater.php',
+ 'OC\\Updater\\ChangesCheck' => $baseDir . '/lib/private/Updater/ChangesCheck.php',
+ 'OC\\Updater\\ChangesMapper' => $baseDir . '/lib/private/Updater/ChangesMapper.php',
+ 'OC\\Updater\\ChangesResult' => $baseDir . '/lib/private/Updater/ChangesResult.php',
'OC\\Updater\\VersionCheck' => $baseDir . '/lib/private/Updater/VersionCheck.php',
'OC\\User\\Backend' => $baseDir . '/lib/private/User/Backend.php',
'OC\\User\\Database' => $baseDir . '/lib/private/User/Database.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 2ada951e03e..e0b158f90f8 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -625,6 +625,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Core\\Migrations\\Version14000Date20180516101403' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180516101403.php',
'OC\\Core\\Migrations\\Version14000Date20180518120534' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180518120534.php',
'OC\\Core\\Migrations\\Version14000Date20180522074438' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180522074438.php',
+ 'OC\\Core\\Migrations\\Version14000Date20180626223656' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180626223656.php',
'OC\\DB\\Adapter' => __DIR__ . '/../../..' . '/lib/private/DB/Adapter.php',
'OC\\DB\\AdapterMySQL' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterMySQL.php',
'OC\\DB\\AdapterOCI8' => __DIR__ . '/../../..' . '/lib/private/DB/AdapterOCI8.php',
@@ -1033,6 +1034,9 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
'OC\\Template\\TemplateFileLocator' => __DIR__ . '/../../..' . '/lib/private/Template/TemplateFileLocator.php',
'OC\\URLGenerator' => __DIR__ . '/../../..' . '/lib/private/URLGenerator.php',
'OC\\Updater' => __DIR__ . '/../../..' . '/lib/private/Updater.php',
+ 'OC\\Updater\\ChangesCheck' => __DIR__ . '/../../..' . '/lib/private/Updater/ChangesCheck.php',
+ 'OC\\Updater\\ChangesMapper' => __DIR__ . '/../../..' . '/lib/private/Updater/ChangesMapper.php',
+ 'OC\\Updater\\ChangesResult' => __DIR__ . '/../../..' . '/lib/private/Updater/ChangesResult.php',
'OC\\Updater\\VersionCheck' => __DIR__ . '/../../..' . '/lib/private/Updater/VersionCheck.php',
'OC\\User\\Backend' => __DIR__ . '/../../..' . '/lib/private/User/Backend.php',
'OC\\User\\Database' => __DIR__ . '/../../..' . '/lib/private/User/Database.php',
diff --git a/lib/private/L10N/Factory.php b/lib/private/L10N/Factory.php
index 8c8735836bb..79495f5ff88 100644
--- a/lib/private/L10N/Factory.php
+++ b/lib/private/L10N/Factory.php
@@ -32,6 +32,7 @@ namespace OC\L10N;
use OCP\IConfig;
use OCP\IRequest;
+use OCP\IUser;
use OCP\IUserSession;
use OCP\L10N\IFactory;
@@ -321,6 +322,37 @@ 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';
+ }
+ }
+
/**
* @param string $locale
* @return bool
diff --git a/lib/private/Updater/ChangesCheck.php b/lib/private/Updater/ChangesCheck.php
new file mode 100644
index 00000000000..095f63db879
--- /dev/null
+++ b/lib/private/Updater/ChangesCheck.php
@@ -0,0 +1,157 @@
+<?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\Updater;
+
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\Http\Client\IClientService;
+use OCP\Http\Client\IResponse;
+use OCP\ILogger;
+
+class ChangesCheck {
+ /** @var IClientService */
+ protected $clientService;
+ /** @var ChangesMapper */
+ private $mapper;
+ /** @var ILogger */
+ private $logger;
+
+ const RESPONSE_NO_CONTENT = 0;
+ const RESPONSE_USE_CACHE = 1;
+ const RESPONSE_HAS_CONTENT = 2;
+
+ public function __construct(IClientService $clientService, ChangesMapper $mapper, ILogger $logger) {
+ $this->clientService = $clientService;
+ $this->mapper = $mapper;
+ $this->logger = $logger;
+ }
+
+ /**
+ * @throws \Exception
+ */
+ public function check(string $uri, string $version): array {
+ try {
+ $version = $this->normalizeVersion($version);
+ $changesInfo = $this->mapper->getChanges($version);
+ if($changesInfo->getLastCheck() + 1800 > time()) {
+ return json_decode($changesInfo->getData(), true);
+ }
+ } catch (DoesNotExistException $e) {
+ $changesInfo = new ChangesResult();
+ }
+
+ $response = $this->queryChangesServer($uri, $changesInfo);
+
+ switch($this->evaluateResponse($response)) {
+ case self::RESPONSE_NO_CONTENT:
+ return [];
+ case self::RESPONSE_USE_CACHE:
+ return json_decode($changesInfo->getData(), true);
+ case self::RESPONSE_HAS_CONTENT:
+ default:
+ $data = $this->extractData($response->getBody());
+ $changesInfo->setData(json_encode($data));
+ $changesInfo->setEtag($response->getHeader('Etag'));
+ $this->cacheResult($changesInfo, $version);
+
+ return $data;
+ }
+ }
+
+ protected function evaluateResponse(IResponse $response): int {
+ if($response->getStatusCode() === 304) {
+ return self::RESPONSE_USE_CACHE;
+ } else if($response->getStatusCode() === 404) {
+ return self::RESPONSE_NO_CONTENT;
+ } else if($response->getStatusCode() === 200) {
+ return self::RESPONSE_HAS_CONTENT;
+ }
+ $this->logger->debug('Unexpected return code {code} from changelog server', [
+ 'app' => 'core',
+ 'code' => $response->getStatusCode(),
+ ]);
+ return self::RESPONSE_NO_CONTENT;
+ }
+
+ protected function cacheResult(ChangesResult $entry, string $version) {
+ if($entry->getVersion() === $version) {
+ $this->mapper->update($entry);
+ } else {
+ $entry->setVersion($version);
+ $this->mapper->insert($entry);
+ }
+ }
+
+ /**
+ * @throws \Exception
+ */
+ protected function queryChangesServer(string $uri, ChangesResult $entry): IResponse {
+ $headers = [];
+ if($entry->getEtag() !== '') {
+ $headers['If-None-Match'] = [$entry->getEtag()];
+ }
+
+ $entry->setLastCheck(time());
+ $client = $this->clientService->newClient();
+ return $client->get($uri, [
+ 'headers' => $headers,
+ ]);
+ }
+
+ protected function extractData($body):array {
+ $data = [];
+ if ($body) {
+ $loadEntities = libxml_disable_entity_loader(true);
+ $xml = @simplexml_load_string($body);
+ libxml_disable_entity_loader($loadEntities);
+ if ($xml !== false) {
+ $data['changelogURL'] = (string)$xml->changelog['href'];
+ $data['whatsNew'] = [];
+ foreach($xml->whatsNew as $infoSet) {
+ $data['whatsNew'][(string)$infoSet['lang']] = [
+ 'regular' => (array)$infoSet->regular->item,
+ 'admin' => (array)$infoSet->admin->item,
+ ];
+ }
+ } else {
+ libxml_clear_errors();
+ }
+ }
+ return $data;
+ }
+
+ /**
+ * 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 {
+ $versionNumbers = array_slice(explode('.', $version), 0, 3);
+ $versionNumbers[0] = $versionNumbers[0] ?: '0'; // deal with empty input
+ while(count($versionNumbers) < 3) {
+ // changelog server expects x.y.z, pad 0 if it is too short
+ $versionNumbers[] = 0;
+ }
+ return implode('.', $versionNumbers);
+ }
+}
diff --git a/lib/private/Updater/ChangesMapper.php b/lib/private/Updater/ChangesMapper.php
new file mode 100644
index 00000000000..d1548c415ca
--- /dev/null
+++ b/lib/private/Updater/ChangesMapper.php
@@ -0,0 +1,57 @@
+<?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\Updater;
+
+use OCP\AppFramework\Db\DoesNotExistException;
+use OCP\AppFramework\Db\QBMapper;
+use OCP\DB\QueryBuilder\IQueryBuilder;
+use OCP\IDBConnection;
+
+class ChangesMapper extends QBMapper {
+ const TABLE_NAME = 'whats_new';
+
+ public function __construct(IDBConnection $db) {
+ parent::__construct($db, self::TABLE_NAME);
+ }
+
+ /**
+ * @throws DoesNotExistException
+ */
+ public function getChanges(string $version): ChangesResult {
+ /* @var $qb IQueryBuilder */
+ $qb = $this->db->getQueryBuilder();
+ $result = $qb->select('*')
+ ->from(self::TABLE_NAME)
+ ->where($qb->expr()->eq('version', $qb->createNamedParameter($version)))
+ ->execute();
+
+ $data = $result->fetch();
+ $result->closeCursor();
+ if ($data === false) {
+ throw new DoesNotExistException('Changes info is not present');
+ }
+ return ChangesResult::fromRow($data);
+ }
+}
diff --git a/lib/private/Updater/ChangesResult.php b/lib/private/Updater/ChangesResult.php
new file mode 100644
index 00000000000..95a1af3910c
--- /dev/null
+++ b/lib/private/Updater/ChangesResult.php
@@ -0,0 +1,61 @@
+<?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\Updater;
+
+use OCP\AppFramework\Db\Entity;
+
+/**
+ * Class ChangesResult
+ *
+ * @package OC\Updater
+ * @method string getVersion()=1
+ * @method void setVersion(string $version)
+ * @method string getEtag()
+ * @method void setEtag(string $etag)
+ * @method int getLastCheck()
+ * @method void setLastCheck(int $lastCheck)
+ * @method string getData()
+ * @method void setData(string $data)
+ */
+class ChangesResult extends Entity {
+ /** @var string */
+ protected $version = '';
+
+ /** @var string */
+ protected $etag = '';
+
+ /** @var int */
+ protected $lastCheck = 0;
+
+ /** @var string */
+ protected $data = '';
+
+ public function __construct() {
+ $this->addType('version', 'string');
+ $this->addType('etag', 'string');
+ $this->addType('lastCheck', 'int');
+ $this->addType('data', 'string');
+ }
+}
diff --git a/lib/private/Updater/VersionCheck.php b/lib/private/Updater/VersionCheck.php
index bc505211634..3cbd7061fe0 100644
--- a/lib/private/Updater/VersionCheck.php
+++ b/lib/private/Updater/VersionCheck.php
@@ -55,7 +55,7 @@ class VersionCheck {
*/
public function check() {
// Look up the cache - it is invalidated all 30 minutes
- if (false && ((int)$this->config->getAppValue('core', 'lastupdatedat') + 1800) > time()) {
+ if (((int)$this->config->getAppValue('core', 'lastupdatedat') + 1800) > time()) {
return json_decode($this->config->getAppValue('core', 'lastupdateResult'), true);
}
@@ -70,7 +70,7 @@ class VersionCheck {
$version = Util::getVersion();
$version['installed'] = $this->config->getAppValue('core', 'installedat');
$version['updated'] = $this->config->getAppValue('core', 'lastupdatedat');
- $version['updatechannel'] = 'stable'; //\OC_Util::getChannel();
+ $version['updatechannel'] = \OC_Util::getChannel();
$version['edition'] = '';
$version['build'] = \OC_Util::getBuild();
$version['php_major'] = PHP_MAJOR_VERSION;
@@ -97,10 +97,7 @@ class VersionCheck {
$tmp['versionstring'] = (string)$data->versionstring;
$tmp['url'] = (string)$data->url;
$tmp['web'] = (string)$data->web;
- $tmp['changelog'] = isset($data->changelog) ? (string)$data->changelog : '';
- // TODO: one's it is decided, use the proper field…
- $tmp['whatsNew'] = isset($data->whatsNew) ? ((array)$data->whatsNew)['item'] : null;
- $tmp['whatsNew'] = isset($data->whatsNew_admin) ? ((array)$data->whatsNew_admin)['item'] : (string)$data->whatsNew;
+ $tmp['changes'] = isset($data->changes) ? (string)$data->changes : '';
$tmp['autoupdater'] = (string)$data->autoupdater;
$tmp['eol'] = isset($data->eol) ? (string)$data->eol : '0';
} else {
diff --git a/lib/public/L10N/IFactory.php b/lib/public/L10N/IFactory.php
index 263ebe81d05..9c006073200 100644
--- a/lib/public/L10N/IFactory.php
+++ b/lib/public/L10N/IFactory.php
@@ -89,4 +89,14 @@ interface IFactory {
* @since 14.0.0
*/
public function createPluralFunction($string);
+
+ /**
+ * 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'
+ * @since 14.0.0
+ */
+ public function iterateLanguage(bool $reset = false): string;
}
diff --git a/tests/lib/Updater/ChangesCheckTest.php b/tests/lib/Updater/ChangesCheckTest.php
new file mode 100644
index 00000000000..5f7e8ad4f0a
--- /dev/null
+++ b/tests/lib/Updater/ChangesCheckTest.php
@@ -0,0 +1,344 @@
+<?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 Test\Updater;
+
+use OC\Updater\ChangesCheck;
+use OC\Updater\ChangesMapper;
+use OC\Updater\ChangesResult;
+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 {
+ /** @var IClientService|\PHPUnit_Framework_MockObject_MockObject */
+ protected $clientService;
+
+ /** @var ChangesCheck */
+ protected $checker;
+
+ /** @var ChangesMapper|\PHPUnit_Framework_MockObject_MockObject */
+ protected $mapper;
+
+ /** @var ILogger|\PHPUnit_Framework_MockObject_MockObject */
+ protected $logger;
+
+ public function setUp() {
+ parent::setUp();
+
+ $this->clientService = $this->createMock(IClientService::class);
+ $this->mapper = $this->createMock(ChangesMapper::class);
+ $this->logger = $this->createMock(ILogger::class);
+
+ $this->checker = new ChangesCheck($this->clientService, $this->mapper, $this->logger);
+ }
+
+ public function statusCodeProvider():array {
+ return [
+ [200, ChangesCheck::RESPONSE_HAS_CONTENT],
+ [304, ChangesCheck::RESPONSE_USE_CACHE],
+ [404, ChangesCheck::RESPONSE_NO_CONTENT],
+ [418, ChangesCheck::RESPONSE_NO_CONTENT],
+ ];
+ }
+
+ /**
+ * @dataProvider statusCodeProvider
+ */
+ public function testEvaluateResponse(int $statusCode, int $expected) {
+ $response = $this->createMock(IResponse::class);
+ $response->expects($this->atLeastOnce())
+ ->method('getStatusCode')
+ ->willReturn($statusCode);
+
+ if(!in_array($statusCode, [200, 304, 404])) {
+ $this->logger->expects($this->once())
+ ->method('debug');
+ }
+
+ $evaluation = $this->invokePrivate($this->checker, 'evaluateResponse', [$response]);
+ $this->assertSame($expected, $evaluation);
+ }
+
+ public function testCacheResultInsert() {
+ $version = '13.0.4';
+ $entry = $this->createMock(ChangesResult::class);
+ $entry->expects($this->exactly(2))
+ ->method('__call')
+ ->withConsecutive(['getVersion'], ['setVersion', [$version]])
+ ->willReturnOnConsecutiveCalls('', null);
+
+ $this->mapper->expects($this->once())
+ ->method('insert');
+ $this->mapper->expects($this->never())
+ ->method('update');
+
+ $this->invokePrivate($this->checker, 'cacheResult', [$entry, $version]);
+ }
+
+ public function testCacheResultUpdate() {
+ $version = '13.0.4';
+ $entry = $this->createMock(ChangesResult::class);
+ $entry->expects($this->once())
+ ->method('__call')
+ ->willReturn($version);
+
+ $this->mapper->expects($this->never())
+ ->method('insert');
+ $this->mapper->expects($this->once())
+ ->method('update');
+
+ $this->invokePrivate($this->checker, 'cacheResult', [$entry, $version]);
+ }
+
+ public function changesXMLProvider(): array {
+ return [
+ [ # 0 - full example
+ '<?xml version="1.0" encoding="utf-8" ?>
+<release xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="https://updates.nextcloud.com/changelog_server/schema.xsd"
+ version="13.0.0">
+ <changelog href="https://nextcloud.com/changelog/#13-0-0"/>
+ <whatsNew lang="en">
+ <regular>
+ <item>Refined user interface</item>
+ <item>End-to-end Encryption</item>
+ <item>Video and Text Chat</item>
+ </regular>
+ <admin>
+ <item>Changes to the Nginx configuration</item>
+ <item>Theming: CSS files were consolidated</item>
+ </admin>
+ </whatsNew>
+ <whatsNew lang="de">
+ <regular>
+ <item>Überarbeitete Benutzerschnittstelle</item>
+ <item>Ende-zu-Ende Verschlüsselung</item>
+ <item>Video- und Text-Chat</item>
+ </regular>
+ <admin>
+ <item>Änderungen an der Nginx Konfiguration</item>
+ <item>Theming: CSS Dateien wurden konsolidiert</item>
+ </admin>
+ </whatsNew>
+</release>',
+ [
+ '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' => [
+ 'Überarbeitete Benutzerschnittstelle',
+ 'Ende-zu-Ende Verschlüsselung',
+ 'Video- und Text-Chat'
+ ],
+ 'admin' => [
+ 'Änderungen an der Nginx Konfiguration',
+ 'Theming: CSS Dateien wurden konsolidiert'
+ ],
+ ],
+ ],
+ ]
+ ],
+ [ # 1- admin part not translated
+ '<?xml version="1.0" encoding="utf-8" ?>
+<release xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="https://updates.nextcloud.com/changelog_server/schema.xsd"
+ version="13.0.0">
+ <changelog href="https://nextcloud.com/changelog/#13-0-0"/>
+ <whatsNew lang="en">
+ <regular>
+ <item>Refined user interface</item>
+ <item>End-to-end Encryption</item>
+ <item>Video and Text Chat</item>
+ </regular>
+ <admin>
+ <item>Changes to the Nginx configuration</item>
+ <item>Theming: CSS files were consolidated</item>
+ </admin>
+ </whatsNew>
+ <whatsNew lang="de">
+ <regular>
+ <item>Überarbeitete Benutzerschnittstelle</item>
+ <item>Ende-zu-Ende Verschlüsselung</item>
+ <item>Video- und Text-Chat</item>
+ </regular>
+ </whatsNew>
+</release>',
+ [
+ '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' => [
+ 'Überarbeitete Benutzerschnittstelle',
+ 'Ende-zu-Ende Verschlüsselung',
+ 'Video- und Text-Chat'
+ ],
+ 'admin' => [
+ ],
+ ],
+ ],
+ ]
+ ],
+ [ # 2 - minimal set
+ '<?xml version="1.0" encoding="utf-8" ?>
+<release xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="https://updates.nextcloud.com/changelog_server/schema.xsd"
+ version="13.0.0">
+ <changelog href="https://nextcloud.com/changelog/#13-0-0"/>
+ <whatsNew lang="en">
+ <regular>
+ <item>Refined user interface</item>
+ <item>End-to-end Encryption</item>
+ <item>Video and Text Chat</item>
+ </regular>
+ </whatsNew>
+</release>',
+ [
+ 'changelogURL' => 'https://nextcloud.com/changelog/#13-0-0',
+ 'whatsNew' => [
+ 'en' => [
+ 'regular' => [
+ 'Refined user interface',
+ 'End-to-end Encryption',
+ 'Video and Text Chat'
+ ],
+ 'admin' => [],
+ ],
+ ],
+ ]
+ ],
+ [ # 3 - minimal set (procrastinator edition)
+ '<?xml version="1.0" encoding="utf-8" ?>
+<release xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
+ xsi:noNamespaceSchemaLocation="https://updates.nextcloud.com/changelog_server/schema.xsd"
+ version="13.0.0">
+ <changelog href="https://nextcloud.com/changelog/#13-0-0"/>
+ <whatsNew lang="en">
+ <regular>
+ <item>Write this tomorrow</item>
+ </regular>
+ </whatsNew>
+</release>',
+ [
+ 'changelogURL' => 'https://nextcloud.com/changelog/#13-0-0',
+ 'whatsNew' => [
+ 'en' => [
+ 'regular' => [
+ 'Write this tomorrow',
+ ],
+ 'admin' => [],
+ ],
+ ],
+ ]
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider changesXMLProvider
+ */
+ public function testExtractData(string $body, array $expected) {
+ $actual = $this->invokePrivate($this->checker, 'extractData', [$body]);
+ $this->assertSame($expected, $actual);
+ }
+
+ public function etagProvider() {
+ return [
+ [''],
+ ['a27aab83d8205d73978435076e53d143']
+ ];
+ }
+
+ /**
+ * @dataProvider etagProvider
+ */
+ public function testQueryChangesServer(string $etag) {
+ $uri = 'https://changes.nextcloud.server/?13.0.5';
+ $entry = $this->createMock(ChangesResult::class);
+ $entry->expects($this->any())
+ ->method('__call')
+ ->willReturn($etag);
+
+ $expectedHeaders = $etag === '' ? [] : ['If-None-Match: ' . $etag];
+
+ $client = $this->createMock(IClient::class);
+ $client->expects($this->once())
+ ->method('get')
+ ->with($uri, ['headers' => $expectedHeaders])
+ ->willReturn($this->createMock(IResponse::class));
+
+ $this->clientService->expects($this->once())
+ ->method('newClient')
+ ->willReturn($client);
+
+ $response = $this->invokePrivate($this->checker, 'queryChangesServer', [$uri, $entry]);
+ $this->assertInstanceOf(IResponse::class, $response);
+ }
+
+ public function versionProvider(): array {
+ return [
+ ['13.0.7', '13.0.7'],
+ ['13.0.7.3', '13.0.7'],
+ ['13.0.7.3.42', '13.0.7'],
+ ['13.0', '13.0.0'],
+ ['13', '13.0.0'],
+ ['', '0.0.0'],
+ ];
+ }
+
+ /**
+ * @dataProvider versionProvider
+ */
+ public function testNormalizeVersion(string $input, string $expected) {
+ $normalized = $this->invokePrivate($this->checker, 'normalizeVersion', [$input]);
+ $this->assertSame($expected, $normalized);
+ }
+}
diff --git a/tests/lib/Updater/VersionCheckTest.php b/tests/lib/Updater/VersionCheckTest.php
index c7165b34efe..6da4fd2c3b2 100644
--- a/tests/lib/Updater/VersionCheckTest.php
+++ b/tests/lib/Updater/VersionCheckTest.php
@@ -62,6 +62,7 @@ class VersionCheckTest extends \Test\TestCase {
'versionstring' => 'ownCloud 8.0.4',
'url' => 'https://download.example.org/community/owncloud-8.0.4.zip',
'web' => 'http://doc.example.org/server/8.0/admin_manual/maintenance/upgrade.html',
+ 'changes' => '',
];
$this->config
@@ -84,8 +85,7 @@ class VersionCheckTest extends \Test\TestCase {
'versionstring' => 'ownCloud 8.0.4',
'url' => 'https://download.example.org/community/owncloud-8.0.4.zip',
'web' => 'http://doc.example.org/server/8.0/admin_manual/maintenance/upgrade.html',
- 'changelog' => '',
- 'whatsNew' => '',
+ 'changes' => '',
'autoupdater' => '0',
'eol' => '1',
];
@@ -183,8 +183,7 @@ class VersionCheckTest extends \Test\TestCase {
'versionstring' => '',
'url' => '',
'web' => '',
- 'changelog' => '',
- 'whatsNew' => '',
+ 'changes' => '',
'autoupdater' => '',
'eol' => '0',
];
@@ -279,8 +278,7 @@ class VersionCheckTest extends \Test\TestCase {
'versionstring' => '',
'url' => '',
'web' => '',
- 'changelog' => '',
- 'whatsNew' => '',
+ 'changes' => '',
'autoupdater' => '',
'eol' => '0',
];