From 15eb86514b5c249602cb0a3facc463b3f0d823d5 Mon Sep 17 00:00:00 2001 From: Ferdinand Thiessen Date: Thu, 15 Jan 2026 23:07:17 +0100 Subject: [PATCH] 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. We use the firstrunwizard nowadays for informing about Nextcloud changes in new releases. Signed-off-by: Ferdinand Thiessen --- core/Controller/WhatsNewController.php | 104 ------------ core/Listener/AddMissingIndicesListener.php | 8 - .../Version14000Date20180626223656.php | 52 ------ .../Version34000Date20260122120000.php | 23 +++ core/src/OCP/index.js | 2 - core/src/OCP/whatsnew.js | 150 ----------------- lib/composer/composer/autoload_classmap.php | 6 +- lib/composer/composer/autoload_static.php | 6 +- lib/private/Updater/Changes.php | 46 ----- lib/private/Updater/ChangesCheck.php | 158 ------------------ lib/private/Updater/ChangesMapper.php | 44 ----- 11 files changed, 25 insertions(+), 574 deletions(-) delete mode 100644 core/Controller/WhatsNewController.php delete mode 100644 core/Migrations/Version14000Date20180626223656.php create mode 100644 core/Migrations/Version34000Date20260122120000.php delete mode 100644 core/src/OCP/whatsnew.js delete mode 100644 lib/private/Updater/Changes.php delete mode 100644 lib/private/Updater/ChangesCheck.php delete mode 100644 lib/private/Updater/ChangesMapper.php diff --git a/core/Controller/WhatsNewController.php b/core/Controller/WhatsNewController.php deleted file mode 100644 index e54fb13b8922a..0000000000000 --- a/core/Controller/WhatsNewController.php +++ /dev/null @@ -1,104 +0,0 @@ -, admin: list}}, array{}>|DataResponse, 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, 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(); - } -} diff --git a/core/Listener/AddMissingIndicesListener.php b/core/Listener/AddMissingIndicesListener.php index 1130165d870d7..6e7571f4c0de8 100644 --- a/core/Listener/AddMissingIndicesListener.php +++ b/core/Listener/AddMissingIndicesListener.php @@ -99,14 +99,6 @@ public function handle(Event $event): void { true ); - $event->addMissingIndex( - 'whats_new', - 'version', - ['version'], - [], - true - ); - $event->addMissingIndex( 'cards', 'cards_abiduri', diff --git a/core/Migrations/Version14000Date20180626223656.php b/core/Migrations/Version14000Date20180626223656.php deleted file mode 100644 index 7d4dea585f626..0000000000000 --- a/core/Migrations/Version14000Date20180626223656.php +++ /dev/null @@ -1,52 +0,0 @@ -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; - } -} diff --git a/core/Migrations/Version34000Date20260122120000.php b/core/Migrations/Version34000Date20260122120000.php new file mode 100644 index 0000000000000..0fcf9b06980ed --- /dev/null +++ b/core/Migrations/Version34000Date20260122120000.php @@ -0,0 +1,23 @@ +hasTable('whats_new')) { + $schema->dropTable('whats_new'); + } + return $schema; + } +} diff --git a/core/src/OCP/index.js b/core/src/OCP/index.js index 4ad3577ee6be4..156351d8d9a65 100644 --- a/core/src/OCP/index.js +++ b/core/src/OCP/index.js @@ -10,7 +10,6 @@ import Collaboration from './collaboration.js' import * as Comments from './comments.js' import Loader from './loader.js' import Toast from './toast.js' -import * as WhatsNew from './whatsnew.js' /** @namespace OCP */ export default { @@ -29,5 +28,4 @@ export default { * @deprecated 19.0.0 use the `@nextcloud/dialogs` package instead */ Toast, - WhatsNew, } diff --git a/core/src/OCP/whatsnew.js b/core/src/OCP/whatsnew.js deleted file mode 100644 index 10966561272c5..0000000000000 --- a/core/src/OCP/whatsnew.js +++ /dev/null @@ -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) -} diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php index 973cb0235738b..ea981fe16150e 100644 --- a/lib/composer/composer/autoload_classmap.php +++ b/lib/composer/composer/autoload_classmap.php @@ -1476,7 +1476,6 @@ '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 @@ '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 @@ '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 @@ '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', diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php index f589072eb394f..cd0ee28a3fb37 100644 --- a/lib/composer/composer/autoload_static.php +++ b/lib/composer/composer/autoload_static.php @@ -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', diff --git a/lib/private/Updater/Changes.php b/lib/private/Updater/Changes.php deleted file mode 100644 index c941dfb3fa568..0000000000000 --- a/lib/private/Updater/Changes.php +++ /dev/null @@ -1,46 +0,0 @@ -addType('version', 'string'); - $this->addType('etag', 'string'); - $this->addType('lastCheck', Types::INTEGER); - $this->addType('data', 'string'); - } -} diff --git a/lib/private/Updater/ChangesCheck.php b/lib/private/Updater/ChangesCheck.php deleted file mode 100644 index e88969f62a84a..0000000000000 --- a/lib/private/Updater/ChangesCheck.php +++ /dev/null @@ -1,158 +0,0 @@ -clientService = $clientService; - $this->mapper = $mapper; - $this->logger = $logger; - } - - /** - * @throws DoesNotExistException - * @return array{changelogURL: string, whatsNew: array, regular: list}>} - */ - 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); - } -} diff --git a/lib/private/Updater/ChangesMapper.php b/lib/private/Updater/ChangesMapper.php deleted file mode 100644 index c399948ff10fe..0000000000000 --- a/lib/private/Updater/ChangesMapper.php +++ /dev/null @@ -1,44 +0,0 @@ - - */ -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); - } -}