chore: drop legacy WhatsNew

This feature was not used in 8 years and from frontend did not even
properly work anymore and was implemented using deprecated API.
So get rid of it.

The last version that was using a changelog from the changelog server
was Nextcloud 20.

We use the firstrunwizard nowadays for informing about Nextcloud
changes in new releases.

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
Ferdinand Thiessen 2026-01-15 23:07:17 +01:00
parent a9f3534bc4
commit 428e76214e
No known key found for this signature in database
GPG key ID: 7E849AE05218500F
18 changed files with 25 additions and 1707 deletions

View file

@ -9,7 +9,6 @@ declare(strict_types=1);
*/
namespace OCA\UpdateNotification;
use OC\Updater\ChangesCheck;
use OC\Updater\VersionCheck;
use OCP\AppFramework\Services\IInitialState;
@ -17,7 +16,6 @@ class UpdateChecker {
public function __construct(
private VersionCheck $updater,
private ChangesCheck $changesCheck,
private IInitialState $initialState,
) {
}
@ -41,13 +39,6 @@ class UpdateChecker {
if (strpos($data['url'], 'https://') === 0) {
$result['downloadLink'] = $data['url'];
}
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;
}

View file

@ -8,7 +8,6 @@ declare(strict_types=1);
*/
namespace OCA\UpdateNotification\Tests;
use OC\Updater\ChangesCheck;
use OC\Updater\VersionCheck;
use OCA\UpdateNotification\UpdateChecker;
use OCP\AppFramework\Services\IInitialState;
@ -17,7 +16,6 @@ use Test\TestCase;
class UpdateCheckerTest extends TestCase {
private ChangesCheck&MockObject $changesChecker;
private VersionCheck&MockObject $updater;
private IInitialState&MockObject $initialState;
private UpdateChecker $updateChecker;
@ -26,11 +24,9 @@ class UpdateCheckerTest extends TestCase {
parent::setUp();
$this->updater = $this->createMock(VersionCheck::class);
$this->changesChecker = $this->createMock(ChangesCheck::class);
$this->initialState = $this->createMock(IInitialState::class);
$this->updateChecker = new UpdateChecker(
$this->updater,
$this->changesChecker,
$this->initialState,
);
}
@ -85,15 +81,10 @@ class UpdateCheckerTest extends TestCase {
'versionstring' => 'Nextcloud 1.2.3',
'web' => 'https://docs.nextcloud.com/myUrl',
'url' => 'https://downloads.nextcloud.org/server',
'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' => '1.2.3',
@ -102,7 +93,6 @@ class UpdateCheckerTest extends TestCase {
'versionIsEol' => false,
'updateLink' => 'https://docs.nextcloud.com/myUrl',
'downloadLink' => 'https://downloads.nextcloud.org/server',
'changes' => $changes,
];
$this->assertSame($expected, $this->updateChecker->getUpdateState());
}
@ -126,7 +116,6 @@ class UpdateCheckerTest extends TestCase {
'versionstring' => 'Nextcloud 1.2.3',
'web' => 'https://docs.nextcloud.com/myUrl',
'url' => 'https://downloads.nextcloud.org/server',
'changes' => 'https://updates.nextcloud.com/changelog_server/?version=123.0.0',
'autoupdater' => '1',
'eol' => '0',
]);

View file

@ -3215,12 +3215,6 @@
)]]></code>
</DeprecatedMethod>
</file>
<file src="core/Controller/WhatsNewController.php">
<DeprecatedMethod>
<code><![CDATA[getUserValue]]></code>
<code><![CDATA[setUserValue]]></code>
</DeprecatedMethod>
</file>
<file src="core/Middleware/TwoFactorMiddleware.php">
<DeprecatedInterface>
<code><![CDATA[private]]></code>

View file

@ -1,104 +0,0 @@
<?php
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Controller;
use OC\Updater\ChangesCheck;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\ApiRoute;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController;
use OCP\Defaults;
use OCP\IConfig;
use OCP\IRequest;
use OCP\IUserSession;
use OCP\L10N\IFactory;
use OCP\PreConditionNotMetException;
class WhatsNewController extends OCSController {
public function __construct(
string $appName,
IRequest $request,
private IUserSession $userSession,
private IConfig $config,
private ChangesCheck $whatsNewService,
private IFactory $langFactory,
private Defaults $defaults,
) {
parent::__construct($appName, $request);
}
/**
* Get the changes
*
* @return DataResponse<Http::STATUS_OK, array{changelogURL: string, product: string, version: string, whatsNew?: array{regular: list<string>, admin: list<string>}}, array{}>|DataResponse<Http::STATUS_NO_CONTENT, list<empty>, array{}>
*
* 200: Changes returned
* 204: No changes
*/
#[NoAdminRequired]
#[ApiRoute(verb: 'GET', url: '/whatsnew', root: '/core')]
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->getProductName(),
'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);
}
}
/**
* Dismiss the changes
*
* @param string $version Version to dismiss the changes for
*
* @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
* @throws PreConditionNotMetException
* @throws DoesNotExistException
*
* 200: Changes dismissed
*/
#[NoAdminRequired]
#[ApiRoute(verb: 'POST', url: '/whatsnew', root: '/core')]
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();
}
}

View file

@ -99,14 +99,6 @@ class AddMissingIndicesListener implements IEventListener {
true
);
$event->addMissingIndex(
'whats_new',
'version',
['version'],
[],
true
);
$event->addMissingIndex(
'cards',
'cards_abiduri',

View file

@ -1,52 +0,0 @@
<?php
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Migrations;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version14000Date20180626223656 extends SimpleMigrationStep {
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if (!$schema->hasTable('whats_new')) {
$table = $schema->createTable('whats_new');
$table->addColumn('id', 'integer', [
'autoincrement' => true,
'notnull' => true,
'length' => 4,
'unsigned' => true,
]);
$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(['id']);
$table->addUniqueIndex(['version'], 'version');
$table->addIndex(['version', 'etag'], 'version_etag_idx');
}
return $schema;
}
}

View file

@ -0,0 +1,23 @@
<?php
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Core\Migrations;
use OCP\DB\ISchemaWrapper;
use OCP\Migration\IOutput;
use OCP\Migration\SimpleMigrationStep;
class Version34000Date20260122120000 extends SimpleMigrationStep {
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if ($schema->hasTable('whats_new')) {
$schema->dropTable('whats_new');
}
return $schema;
}
}

View file

@ -9002,249 +9002,6 @@
}
}
},
"/ocs/v2.php/core/whatsnew": {
"get": {
"operationId": "whats_new-get",
"summary": "Get the changes",
"tags": [
"whats_new"
],
"security": [
{
"bearer_auth": []
},
{
"basic_auth": []
}
],
"parameters": [
{
"name": "OCS-APIRequest",
"in": "header",
"description": "Required to be true for the API request to pass",
"required": true,
"schema": {
"type": "boolean",
"default": true
}
}
],
"responses": {
"200": {
"description": "Changes returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {
"type": "object",
"required": [
"changelogURL",
"product",
"version"
],
"properties": {
"changelogURL": {
"type": "string"
},
"product": {
"type": "string"
},
"version": {
"type": "string"
},
"whatsNew": {
"type": "object",
"required": [
"regular",
"admin"
],
"properties": {
"regular": {
"type": "array",
"items": {
"type": "string"
}
},
"admin": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
}
}
}
}
}
},
"204": {
"description": "No changes"
},
"401": {
"description": "Current user is not logged in",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {}
}
}
}
}
}
}
}
}
},
"post": {
"operationId": "whats_new-dismiss",
"summary": "Dismiss the changes",
"tags": [
"whats_new"
],
"security": [
{
"bearer_auth": []
},
{
"basic_auth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"version"
],
"properties": {
"version": {
"type": "string",
"description": "Version to dismiss the changes for"
}
}
}
}
}
},
"parameters": [
{
"name": "OCS-APIRequest",
"in": "header",
"description": "Required to be true for the API request to pass",
"required": true,
"schema": {
"type": "boolean",
"default": true
}
}
],
"responses": {
"200": {
"description": "Changes dismissed",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {}
}
}
}
}
}
}
},
"500": {
"description": "",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
},
"401": {
"description": "Current user is not logged in",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {}
}
}
}
}
}
}
}
}
}
},
"/index.php/avatar/{userId}/{size}/dark": {
"get": {
"operationId": "avatar-get-avatar-dark",

View file

@ -9002,249 +9002,6 @@
}
}
},
"/ocs/v2.php/core/whatsnew": {
"get": {
"operationId": "whats_new-get",
"summary": "Get the changes",
"tags": [
"whats_new"
],
"security": [
{
"bearer_auth": []
},
{
"basic_auth": []
}
],
"parameters": [
{
"name": "OCS-APIRequest",
"in": "header",
"description": "Required to be true for the API request to pass",
"required": true,
"schema": {
"type": "boolean",
"default": true
}
}
],
"responses": {
"200": {
"description": "Changes returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {
"type": "object",
"required": [
"changelogURL",
"product",
"version"
],
"properties": {
"changelogURL": {
"type": "string"
},
"product": {
"type": "string"
},
"version": {
"type": "string"
},
"whatsNew": {
"type": "object",
"required": [
"regular",
"admin"
],
"properties": {
"regular": {
"type": "array",
"items": {
"type": "string"
}
},
"admin": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
}
}
}
}
}
},
"204": {
"description": "No changes"
},
"401": {
"description": "Current user is not logged in",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {}
}
}
}
}
}
}
}
}
},
"post": {
"operationId": "whats_new-dismiss",
"summary": "Dismiss the changes",
"tags": [
"whats_new"
],
"security": [
{
"bearer_auth": []
},
{
"basic_auth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"version"
],
"properties": {
"version": {
"type": "string",
"description": "Version to dismiss the changes for"
}
}
}
}
}
},
"parameters": [
{
"name": "OCS-APIRequest",
"in": "header",
"description": "Required to be true for the API request to pass",
"required": true,
"schema": {
"type": "boolean",
"default": true
}
}
],
"responses": {
"200": {
"description": "Changes dismissed",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {}
}
}
}
}
}
}
},
"500": {
"description": "",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
},
"401": {
"description": "Current user is not logged in",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {}
}
}
}
}
}
}
}
}
}
},
"/index.php/avatar/{userId}/{size}/dark": {
"get": {
"operationId": "avatar-get-avatar-dark",

View file

@ -10,7 +10,6 @@ import Collaboration from './collaboration.js'
import * as Comments from './comments.ts'
import Loader from './loader.js'
import Toast from './toast.js'
import * as WhatsNew from './whatsnew.js'
/** @namespace OCP */
export default {
@ -32,5 +31,4 @@ export default {
* @deprecated 19.0.0 use the `@nextcloud/dialogs` package instead
*/
Toast,
WhatsNew,
}

View file

@ -1,150 +0,0 @@
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { generateOcsUrl } from '@nextcloud/router'
import $ from 'jquery'
import _ from 'underscore'
import logger from '../logger.js'
/**
* @param {any} options -
*/
export function query(options) {
options = options || {}
const dismissOptions = options.dismiss || {}
$.ajax({
type: 'GET',
url: options.url || generateOcsUrl('core/whatsnew?format=json'),
success: options.success || function(data, statusText, xhr) {
onQuerySuccess(data, statusText, xhr, dismissOptions)
},
error: options.error || onQueryError,
})
}
/**
* @param {any} version -
* @param {any} options -
*/
export function dismiss(version, options) {
options = options || {}
$.ajax({
type: 'POST',
url: options.url || generateOcsUrl('core/whatsnew'),
data: { version: encodeURIComponent(version) },
success: options.success || onDismissSuccess,
error: options.error || onDismissError,
})
// remove element immediately
$('.whatsNewPopover').remove()
}
/**
* @param {any} data -
* @param {any} statusText -
* @param {any} xhr -
* @param {any} dismissOptions -
*/
function onQuerySuccess(data, statusText, xhr, dismissOptions) {
logger.debug('querying Whats New data was successful: ' + statusText, { data })
if (xhr.status !== 200) {
return
}
let item, menuItem, text, icon
const div = document.createElement('div')
div.classList.add('popovermenu', 'open', 'whatsNewPopover', 'menu-left')
const 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() {
dismiss(data.ocs.data.version, dismissOptions)
}
menuItem.appendChild(icon)
item.appendChild(menuItem)
list.appendChild(item)
// Highlights
for (const i in data.ocs.data.whatsNew.regular) {
const 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-checkmark'
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)
}
/**
* @param {any} x -
* @param {any} t -
* @param {any} e -
*/
function onQueryError(x, t, e) {
logger.debug('querying Whats New Data resulted in an error: ' + t + e)
logger.debug(x)
}
/**
*/
function onDismissSuccess() {
// noop
}
/**
* @param {any} data -
*/
function onDismissError(data) {
logger.debug('dismissing Whats New data resulted in an error: ' + data)
}

View file

@ -1476,7 +1476,6 @@ return array(
'OC\\Core\\Controller\\WalledGardenController' => $baseDir . '/core/Controller/WalledGardenController.php',
'OC\\Core\\Controller\\WebAuthnController' => $baseDir . '/core/Controller/WebAuthnController.php',
'OC\\Core\\Controller\\WellKnownController' => $baseDir . '/core/Controller/WellKnownController.php',
'OC\\Core\\Controller\\WhatsNewController' => $baseDir . '/core/Controller/WhatsNewController.php',
'OC\\Core\\Controller\\WipeController' => $baseDir . '/core/Controller/WipeController.php',
'OC\\Core\\Data\\LoginFlowV2Credentials' => $baseDir . '/core/Data/LoginFlowV2Credentials.php',
'OC\\Core\\Data\\LoginFlowV2Tokens' => $baseDir . '/core/Data/LoginFlowV2Tokens.php',
@ -1506,7 +1505,6 @@ 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\\Core\\Migrations\\Version14000Date20180710092004' => $baseDir . '/core/Migrations/Version14000Date20180710092004.php',
'OC\\Core\\Migrations\\Version14000Date20180712153140' => $baseDir . '/core/Migrations/Version14000Date20180712153140.php',
'OC\\Core\\Migrations\\Version15000Date20180926101451' => $baseDir . '/core/Migrations/Version15000Date20180926101451.php',
@ -1585,6 +1583,7 @@ return array(
'OC\\Core\\Migrations\\Version33000Date20251124110529' => $baseDir . '/core/Migrations/Version33000Date20251124110529.php',
'OC\\Core\\Migrations\\Version33000Date20251126152410' => $baseDir . '/core/Migrations/Version33000Date20251126152410.php',
'OC\\Core\\Migrations\\Version33000Date20251209123503' => $baseDir . '/core/Migrations/Version33000Date20251209123503.php',
'OC\\Core\\Migrations\\Version34000Date20260122120000' => $baseDir . '/core/Migrations/Version34000Date20260122120000.php',
'OC\\Core\\Notification\\CoreNotifier' => $baseDir . '/core/Notification/CoreNotifier.php',
'OC\\Core\\ResponseDefinitions' => $baseDir . '/core/ResponseDefinitions.php',
'OC\\Core\\Service\\CronService' => $baseDir . '/core/Service/CronService.php',
@ -2230,9 +2229,6 @@ return array(
'OC\\Translation\\TranslationManager' => $baseDir . '/lib/private/Translation/TranslationManager.php',
'OC\\URLGenerator' => $baseDir . '/lib/private/URLGenerator.php',
'OC\\Updater' => $baseDir . '/lib/private/Updater.php',
'OC\\Updater\\Changes' => $baseDir . '/lib/private/Updater/Changes.php',
'OC\\Updater\\ChangesCheck' => $baseDir . '/lib/private/Updater/ChangesCheck.php',
'OC\\Updater\\ChangesMapper' => $baseDir . '/lib/private/Updater/ChangesMapper.php',
'OC\\Updater\\Exceptions\\ReleaseMetadataException' => $baseDir . '/lib/private/Updater/Exceptions/ReleaseMetadataException.php',
'OC\\Updater\\ReleaseMetadata' => $baseDir . '/lib/private/Updater/ReleaseMetadata.php',
'OC\\Updater\\VersionCheck' => $baseDir . '/lib/private/Updater/VersionCheck.php',

View file

@ -1517,7 +1517,6 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Core\\Controller\\WalledGardenController' => __DIR__ . '/../../..' . '/core/Controller/WalledGardenController.php',
'OC\\Core\\Controller\\WebAuthnController' => __DIR__ . '/../../..' . '/core/Controller/WebAuthnController.php',
'OC\\Core\\Controller\\WellKnownController' => __DIR__ . '/../../..' . '/core/Controller/WellKnownController.php',
'OC\\Core\\Controller\\WhatsNewController' => __DIR__ . '/../../..' . '/core/Controller/WhatsNewController.php',
'OC\\Core\\Controller\\WipeController' => __DIR__ . '/../../..' . '/core/Controller/WipeController.php',
'OC\\Core\\Data\\LoginFlowV2Credentials' => __DIR__ . '/../../..' . '/core/Data/LoginFlowV2Credentials.php',
'OC\\Core\\Data\\LoginFlowV2Tokens' => __DIR__ . '/../../..' . '/core/Data/LoginFlowV2Tokens.php',
@ -1547,7 +1546,6 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'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\\Core\\Migrations\\Version14000Date20180710092004' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180710092004.php',
'OC\\Core\\Migrations\\Version14000Date20180712153140' => __DIR__ . '/../../..' . '/core/Migrations/Version14000Date20180712153140.php',
'OC\\Core\\Migrations\\Version15000Date20180926101451' => __DIR__ . '/../../..' . '/core/Migrations/Version15000Date20180926101451.php',
@ -1626,6 +1624,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Core\\Migrations\\Version33000Date20251124110529' => __DIR__ . '/../../..' . '/core/Migrations/Version33000Date20251124110529.php',
'OC\\Core\\Migrations\\Version33000Date20251126152410' => __DIR__ . '/../../..' . '/core/Migrations/Version33000Date20251126152410.php',
'OC\\Core\\Migrations\\Version33000Date20251209123503' => __DIR__ . '/../../..' . '/core/Migrations/Version33000Date20251209123503.php',
'OC\\Core\\Migrations\\Version34000Date20260122120000' => __DIR__ . '/../../..' . '/core/Migrations/Version34000Date20260122120000.php',
'OC\\Core\\Notification\\CoreNotifier' => __DIR__ . '/../../..' . '/core/Notification/CoreNotifier.php',
'OC\\Core\\ResponseDefinitions' => __DIR__ . '/../../..' . '/core/ResponseDefinitions.php',
'OC\\Core\\Service\\CronService' => __DIR__ . '/../../..' . '/core/Service/CronService.php',
@ -2271,9 +2270,6 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OC\\Translation\\TranslationManager' => __DIR__ . '/../../..' . '/lib/private/Translation/TranslationManager.php',
'OC\\URLGenerator' => __DIR__ . '/../../..' . '/lib/private/URLGenerator.php',
'OC\\Updater' => __DIR__ . '/../../..' . '/lib/private/Updater.php',
'OC\\Updater\\Changes' => __DIR__ . '/../../..' . '/lib/private/Updater/Changes.php',
'OC\\Updater\\ChangesCheck' => __DIR__ . '/../../..' . '/lib/private/Updater/ChangesCheck.php',
'OC\\Updater\\ChangesMapper' => __DIR__ . '/../../..' . '/lib/private/Updater/ChangesMapper.php',
'OC\\Updater\\Exceptions\\ReleaseMetadataException' => __DIR__ . '/../../..' . '/lib/private/Updater/Exceptions/ReleaseMetadataException.php',
'OC\\Updater\\ReleaseMetadata' => __DIR__ . '/../../..' . '/lib/private/Updater/ReleaseMetadata.php',
'OC\\Updater\\VersionCheck' => __DIR__ . '/../../..' . '/lib/private/Updater/VersionCheck.php',

View file

@ -1,46 +0,0 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Updater;
use OCP\AppFramework\Db\Entity;
use OCP\DB\Types;
/**
* Class Changes
*
* @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 Changes 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', Types::INTEGER);
$this->addType('data', 'string');
}
}

View file

@ -1,158 +0,0 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Updater;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\Http\Client\IClientService;
use OCP\Http\Client\IResponse;
use Psr\Log\LoggerInterface;
class ChangesCheck {
/** @var IClientService */
protected $clientService;
/** @var ChangesMapper */
private $mapper;
private LoggerInterface $logger;
public const RESPONSE_NO_CONTENT = 0;
public const RESPONSE_USE_CACHE = 1;
public const RESPONSE_HAS_CONTENT = 2;
public function __construct(IClientService $clientService, ChangesMapper $mapper, LoggerInterface $logger) {
$this->clientService = $clientService;
$this->mapper = $mapper;
$this->logger = $logger;
}
/**
* @throws DoesNotExistException
* @return array{changelogURL: string, whatsNew: array<string, array{admin: list<string>, regular: list<string>}>}
*/
public function getChangesForVersion(string $version): array {
$version = $this->normalizeVersion($version);
$changesInfo = $this->mapper->getChanges($version);
$changesData = json_decode($changesInfo->getData(), true);
if (empty($changesData)) {
throw new DoesNotExistException('Unable to decode changes info');
}
return $changesData;
}
/**
* @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 Changes();
}
$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;
} elseif ($response->getStatusCode() === 404) {
return self::RESPONSE_NO_CONTENT;
} elseif ($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(Changes $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, Changes $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) {
if (\LIBXML_VERSION < 20900) {
$loadEntities = libxml_disable_entity_loader(true);
$xml = @simplexml_load_string($body);
libxml_disable_entity_loader($loadEntities);
} else {
$xml = @simplexml_load_string($body);
}
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.
*/
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) {
// changelog server expects x.y.z, pad 0 if it is too short
$versionNumbers[] = 0;
}
return implode('.', $versionNumbers);
}
}

View file

@ -1,44 +0,0 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OC\Updater;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\QBMapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
/**
* @template-extends QBMapper<Changes>
*/
class ChangesMapper extends QBMapper {
public const TABLE_NAME = 'whats_new';
public function __construct(IDBConnection $db) {
parent::__construct($db, self::TABLE_NAME);
}
/**
* @throws DoesNotExistException
*/
public function getChanges(string $version): Changes {
/* @var $qb IQueryBuilder */
$qb = $this->db->getQueryBuilder();
$result = $qb->select('*')
->from(self::TABLE_NAME)
->where($qb->expr()->eq('version', $qb->createNamedParameter($version)))
->executeQuery();
$data = $result->fetch();
$result->closeCursor();
if ($data === false) {
throw new DoesNotExistException('Changes info is not present');
}
return Changes::fromRow($data);
}
}

View file

@ -12549,249 +12549,6 @@
}
}
},
"/ocs/v2.php/core/whatsnew": {
"get": {
"operationId": "core-whats_new-get",
"summary": "Get the changes",
"tags": [
"core/whats_new"
],
"security": [
{
"bearer_auth": []
},
{
"basic_auth": []
}
],
"parameters": [
{
"name": "OCS-APIRequest",
"in": "header",
"description": "Required to be true for the API request to pass",
"required": true,
"schema": {
"type": "boolean",
"default": true
}
}
],
"responses": {
"200": {
"description": "Changes returned",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {
"type": "object",
"required": [
"changelogURL",
"product",
"version"
],
"properties": {
"changelogURL": {
"type": "string"
},
"product": {
"type": "string"
},
"version": {
"type": "string"
},
"whatsNew": {
"type": "object",
"required": [
"regular",
"admin"
],
"properties": {
"regular": {
"type": "array",
"items": {
"type": "string"
}
},
"admin": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
}
}
}
}
}
}
},
"204": {
"description": "No changes"
},
"401": {
"description": "Current user is not logged in",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {}
}
}
}
}
}
}
}
}
},
"post": {
"operationId": "core-whats_new-dismiss",
"summary": "Dismiss the changes",
"tags": [
"core/whats_new"
],
"security": [
{
"bearer_auth": []
},
{
"basic_auth": []
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"version"
],
"properties": {
"version": {
"type": "string",
"description": "Version to dismiss the changes for"
}
}
}
}
}
},
"parameters": [
{
"name": "OCS-APIRequest",
"in": "header",
"description": "Required to be true for the API request to pass",
"required": true,
"schema": {
"type": "boolean",
"default": true
}
}
],
"responses": {
"200": {
"description": "Changes dismissed",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {}
}
}
}
}
}
}
},
"500": {
"description": "",
"content": {
"text/plain": {
"schema": {
"type": "string"
}
}
}
},
"401": {
"description": "Current user is not logged in",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"ocs"
],
"properties": {
"ocs": {
"type": "object",
"required": [
"meta",
"data"
],
"properties": {
"meta": {
"$ref": "#/components/schemas/OCSMeta"
},
"data": {}
}
}
}
}
}
}
}
}
}
},
"/index.php/avatar/{userId}/{size}/dark": {
"get": {
"operationId": "core-avatar-get-avatar-dark",

View file

@ -1,378 +0,0 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace Test\Updater;
use OC\Updater\Changes;
use OC\Updater\ChangesCheck;
use OC\Updater\ChangesMapper;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\Http\Client\IClient;
use OCP\Http\Client\IClientService;
use OCP\Http\Client\IResponse;
use Psr\Log\LoggerInterface;
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 LoggerInterface|\PHPUnit\Framework\MockObject\MockObject */
protected $logger;
protected function setUp(): void {
parent::setUp();
$this->clientService = $this->createMock(IClientService::class);
$this->mapper = $this->createMock(ChangesMapper::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->checker = new ChangesCheck($this->clientService, $this->mapper, $this->logger);
}
public static function statusCodeProvider(): array {
return [
[200, ChangesCheck::RESPONSE_HAS_CONTENT],
[304, ChangesCheck::RESPONSE_USE_CACHE],
[404, ChangesCheck::RESPONSE_NO_CONTENT],
[418, ChangesCheck::RESPONSE_NO_CONTENT],
];
}
#[\PHPUnit\Framework\Attributes\DataProvider('statusCodeProvider')]
public function testEvaluateResponse(int $statusCode, int $expected): void {
$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(): void {
$version = '13.0.4';
$entry = $this->createMock(Changes::class);
$entry->expects($this->exactly(2))
->method('__call')
->willReturnMap([
['getVersion', [], ''],
['setVersion', [$version], 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(): void {
$version = '13.0.4';
$entry = $this->createMock(Changes::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 static 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' => [],
],
],
]
],
[ # 4 - empty
'',
[]
],
];
}
#[\PHPUnit\Framework\Attributes\DataProvider('changesXMLProvider')]
public function testExtractData(string $body, array $expected): void {
$actual = $this->invokePrivate($this->checker, 'extractData', [$body]);
$this->assertSame($expected, $actual);
}
public static function etagProvider() {
return [
[''],
['a27aab83d8205d73978435076e53d143']
];
}
#[\PHPUnit\Framework\Attributes\DataProvider('etagProvider')]
public function testQueryChangesServer(string $etag): void {
$uri = 'https://changes.nextcloud.server/?13.0.5';
$entry = $this->createMock(Changes::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 static 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'],
];
}
#[\PHPUnit\Framework\Attributes\DataProvider('versionProvider')]
public function testNormalizeVersion(string $input, string $expected): void {
$normalized = $this->checker->normalizeVersion($input);
$this->assertSame($expected, $normalized);
}
public static function changeDataProvider():array {
$testDataFound = $testDataNotFound = self::versionProvider();
array_walk($testDataFound, static function (&$params): void {
$params[] = true;
});
array_walk($testDataNotFound, static function (&$params): void {
$params[] = false;
});
return array_merge($testDataFound, $testDataNotFound);
}
#[\PHPUnit\Framework\Attributes\DataProvider('changeDataProvider')]
public function testGetChangesForVersion(string $inputVersion, string $normalizedVersion, bool $isFound): void {
$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(Changes::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']));
}
public function testGetChangesForVersionEmptyData(): void {
$entry = $this->createMock(Changes::class);
$entry->expects($this->once())
->method('__call')
->with('getData')
->willReturn('');
$this->mapper->expects($this->once())
->method('getChanges')
->with('13.0.7')
->willReturn($entry);
$this->expectException(DoesNotExistException::class);
/** @noinspection PhpUnhandledExceptionInspection */
$this->checker->getChangesForVersion('13.0.7');
}
}