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); - } -}