refactor(core): migrate web updater to Vue

Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
This commit is contained in:
Ferdinand Thiessen 2026-01-15 00:28:07 +01:00
parent 2a385fd5b8
commit f7dad729e4
No known key found for this signature in database
GPG key ID: 7E849AE05218500F
10 changed files with 452 additions and 277 deletions

View file

@ -29,6 +29,7 @@ module.exports = {
public: path.join(__dirname, 'core/src', 'public.ts'),
public_share_auth: path.join(__dirname, 'core/src', 'public-share-auth.ts'),
'twofactor-request-token': path.join(__dirname, 'core/src', 'twofactor-request-token.ts'),
update: path.join(__dirname, 'core/src', 'update.ts'),
},
dashboard: {
main: path.join(__dirname, 'apps/dashboard/src', 'main.js'),

View file

@ -106,7 +106,6 @@ if (Util::needUpgrade()) {
});
$updater->listen('\OC\Updater', 'failure', function ($message) use ($eventSource, $config): void {
$eventSource->send('failure', $message);
$eventSource->close();
$config->setSystemValue('maintenance', false);
});
$updater->listen('\OC\Updater', 'setDebugLogLevel', function ($logLevel, $logLevelName) use ($eventSource, $l): void {

View file

@ -1,160 +0,0 @@
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2014 ownCloud Inc.
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
(function() {
OC.Update = {
_started: false,
options: {},
/**
* Start the update process.
*
* @param $el progress list element
* @param options
*/
start: function($el, options) {
if (this._started) {
return
}
this.options = options
let hasWarnings = false
this.$el = $el
this._started = true
const self = this
$(window).on('beforeunload.inprogress', function() {
return t('core', 'The update is in progress, leaving this page might interrupt the process in some environments.')
})
$('#update-progress-title').html(t(
'core',
'Update to {version}',
{
version: options.version,
},
))
const updateEventSource = new OC.EventSource(OC.getRootPath() + '/core/ajax/update.php')
updateEventSource.listen('success', function(message) {
self.setMessage(message)
})
updateEventSource.listen('notice', function(message) {
self.setPermanentMessage(message)
hasWarnings = true
})
updateEventSource.listen('error', function(message) {
$('#update-progress-message').hide()
$('#update-progress-icon')
.addClass('icon-error-white')
.removeClass('icon-loading-dark')
message = message || t('core', 'An error occurred.')
$(window).off('beforeunload.inprogress')
self.setErrorMessage(message)
message = t('core', 'Please reload the page.')
$('<p>').append('<a href=".">' + message + '</a>').appendTo($el)
updateEventSource.close()
})
updateEventSource.listen('failure', function(message) {
$(window).off('beforeunload.inprogress')
$('#update-progress-message').hide()
$('#update-progress-icon')
.addClass('icon-error-white')
.removeClass('icon-loading-dark')
self.setErrorMessage(message)
const updateUnsuccessful = $('<p>')
if (message === 'Exception: Updates between multiple major versions and downgrades are unsupported.') {
updateUnsuccessful.append(t('core', 'The update was unsuccessful. For more information <a href="{url}">check our forum post</a> covering this issue.', { url: 'https://help.nextcloud.com/t/updates-between-multiple-major-versions-are-unsupported/7094' }))
} else if (OC.Update.options.productName === 'Nextcloud') {
updateUnsuccessful.append(t('core', 'The update was unsuccessful. '
+ 'Please report this issue to the '
+ '<a href="https://github.com/nextcloud/server/issues" target="_blank">Nextcloud community</a>.'))
}
updateUnsuccessful.appendTo($el)
})
updateEventSource.listen('done', function() {
$(window).off('beforeunload.inprogress')
$('#update-progress-message').hide()
$('#update-progress-icon')
.addClass('icon-checkmark-white')
.removeClass('icon-loading-dark')
if (hasWarnings) {
$el.find('.update-show-detailed').before($('<input type="button" class="primary" value="' + t('core', 'Continue to {productName}', OC.Update.options) + '">').on('click', function() {
window.location.reload()
}))
} else {
$el.find('.update-show-detailed').before($('<p id="redirect-countdown"></p>'))
for (let i = 0; i <= 4; i++) {
self.updateCountdown(i, 4)
}
setTimeout(function() {
window.location = window.location.href
window.location.reload()
}, 3000)
}
})
},
updateCountdown: function(i, total) {
setTimeout(function() {
$('#redirect-countdown').text(n('core', 'The update was successful. Redirecting you to {productName} in %n second.', 'The update was successful. Redirecting you to {productName} in %n seconds.', i, OC.Update.options))
}, (total - i) * 1000)
},
setMessage: function(message) {
$('#update-progress-message').html(message)
$('#update-progress-detailed')
.append('<p>' + message + '</p>')
},
setPermanentMessage: function(message) {
$('#update-progress-message').html(message)
$('#update-progress-message-warnings')
.show()
.append($('<ul>').append(message))
$('#update-progress-detailed')
.append('<p>' + message + '</p>')
},
setErrorMessage: function(message) {
$('#update-progress-message-error')
.show()
.html(message)
$('#update-progress-detailed')
.append('<p>' + message + '</p>')
},
}
})()
window.addEventListener('DOMContentLoaded', function() {
$('.updateButton').on('click', function() {
const $updateEl = $('.update')
const $progressEl = $('.update-progress')
$progressEl.removeClass('hidden')
$('.updateOverview').addClass('hidden')
$('#update-progress-message-error').hide()
$('#update-progress-message-warnings').hide()
OC.Update.start($progressEl, {
productName: $updateEl.attr('data-productname'),
version: $updateEl.attr('data-version'),
})
return false
})
$('.update-show-detailed').on('click', function() {
$('#update-progress-detailed').toggleClass('hidden')
return false
})
})

20
core/src/update.ts Normal file
View file

@ -0,0 +1,20 @@
/*!
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import { getCSPNonce } from '@nextcloud/auth'
import { loadState } from '@nextcloud/initial-state'
import Vue, { defineAsyncComponent } from 'vue'
__webpack_nonce__ = getCSPNonce()
const UpdaterAdmin = defineAsyncComponent(() => import('./views/UpdaterAdmin.vue'))
const UpdaterAdminCli = defineAsyncComponent(() => import('./views/UpdaterAdminCli.vue'))
const view = loadState('core', 'updaterView')
const app = new Vue({
name: 'NextcloudUpdater',
render: (h) => view === 'adminCli' ? h(UpdaterAdminCli) : h(UpdaterAdmin),
})
app.$mount('#core-updater')

View file

@ -0,0 +1,317 @@
<!--
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<script setup lang="ts">
import {
mdiAlertCircleOutline,
mdiCheckCircleOutline,
mdiChevronDown,
mdiChevronUp,
mdiCloseCircleOutline,
mdiInformationOutline,
} from '@mdi/js'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import { generateFilePath } from '@nextcloud/router'
import { NcButton, NcIconSvgWrapper, NcLoadingIcon } from '@nextcloud/vue'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import NcGuestContent from '@nextcloud/vue/components/NcGuestContent'
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
import OCEventSource from '../OC/eventsource.js'
const updateInfo = loadState<{
appsToUpgrade: { id: string, name: string, version: string, oldVersion: string }[]
incompatibleAppsList: { id: string, name: string }[]
isAppsOnlyUpgrade: boolean
oldTheme: string | null
productName: string
version: string
}>('core', 'updateInfo')
const isShowingDetails = ref(false)
const isUpdateRunning = ref(false)
const isUpdateDone = ref(false)
const messages = ref<{ message: string, type: string }[]>([])
const wasSuccessfull = computed(() => messages.value.every((msg) => msg.type === 'success' || msg.type === 'notice'))
const hasErrors = computed(() => messages.value.some((msg) => msg.type === 'error' || msg.type === 'failure'))
const resultIcon = computed(() => wasSuccessfull.value ? mdiCheckCircleOutline : (hasErrors.value ? mdiCloseCircleOutline : mdiAlertCircleOutline))
const statusMessage = computed(() => {
if (isUpdateDone.value) {
if (!wasSuccessfull.value) {
return t('core', 'The update completed with warnings. Please check the details for more information.')
} else {
return t('core', 'The update completed successfully.')
}
}
return messages.value.at(-1)?.message || t('core', 'Preparing update…')
})
const redirectCountdown = ref(6)
const redirectMessage = computed(() => {
if (!isUpdateDone.value || !wasSuccessfull.value) {
return ''
}
return t('core', 'You will be redirected to {productName} in {count} seconds.', { productName: updateInfo.productName, count: redirectCountdown.value })
})
onMounted(() => window.addEventListener('beforeunload', onUnload))
onUnmounted(() => window.removeEventListener('beforeunload', onUnload))
/**
* Get the status icon for a given severity
*
* @param type - The severity
*/
function getSeverityIcon(type: string) {
switch (type) {
case 'success':
return mdiCheckCircleOutline
case 'notice':
return mdiInformationOutline
case 'warning':
return mdiAlertCircleOutline
case 'error':
case 'failure':
return mdiCloseCircleOutline
default:
return mdiInformationOutline
}
}
/**
* Start the update process
*/
async function onStartUpdate() {
if (isUpdateRunning.value || isUpdateDone.value) {
return
}
isUpdateRunning.value = true
const eventSource = new OCEventSource(generateFilePath('core', '', 'ajax/update.php'))
eventSource.listen('success', (message) => {
messages.value.push({ message, type: 'success' })
})
eventSource.listen('notice', (message) => {
messages.value.push({ message, type: 'notice' })
})
eventSource.listen('error', (message) => {
messages.value.push({ message, type: 'error' })
isUpdateRunning.value = false
isUpdateDone.value = true
eventSource.close()
})
eventSource.listen('failure', (message) => {
messages.value.push({ message, type: 'failure' })
})
eventSource.listen('done', () => {
isUpdateRunning.value = false
isUpdateDone.value = true
eventSource.close()
updateCountdown()
})
}
/**
* Update the countdown for the redirect
*/
function updateCountdown() {
if (hasErrors.value || !wasSuccessfull.value) {
return
}
if (--redirectCountdown.value > 0) {
window.setTimeout(updateCountdown, 1000)
} else {
reloadPage()
}
}
/**
* Handle the beforeunload event to warn the user if an update is running.
*
* @param event - The beforeunload event object.
*/
function onUnload(event: BeforeUnloadEvent) {
if (isUpdateRunning.value) {
event.preventDefault()
event.returnValue = t('core', 'The update is in progress, leaving this page might interrupt the process in some environments.')
}
}
/**
* Reload the page
*/
function reloadPage() {
window.location.reload()
}
</script>
<template>
<NcGuestContent>
<h2>
{{ updateInfo.isAppsOnlyUpgrade
? t('core', 'App update required')
: t('core', '{productName} will be updated to version {version}', { productName: updateInfo.productName, version: updateInfo.version }) }}
</h2>
<NcNoteCard v-if="!!updateInfo.oldTheme" type="info">
{{ t('core', 'The theme {oldTheme} has been disabled.', { oldTheme: updateInfo.oldTheme }) }}
</NcNoteCard>
<NcNoteCard v-if="updateInfo.incompatibleAppsList.length" type="warning">
{{ t('core', 'These incompatible apps will be disabled:') }}
<ul :aria-label="t('core', 'Incompatible apps')" :class="$style.updater__appsList">
<li v-for="app of updateInfo.incompatibleAppsList" :key="'app-disable-' + app.id">
{{ app.name }} ({{ app.id }})
</li>
</ul>
</NcNoteCard>
<NcNoteCard v-if="updateInfo.incompatibleAppsList.length" type="info">
{{ t('core', 'These apps will be updated:') }}
<ul :aria-label="t('core', 'Apps to update')" :class="$style.updater__appsList">
<li v-for="app of updateInfo.appsToUpgrade" :key="'app-update-' + app.id">
{{ t('core', '{app} from {oldVersion} to {version}', { app: `${app.name} (${app.id})`, oldVersion: app.oldVersion, version: app.version }) }}
</li>
</ul>
</NcNoteCard>
<p>
<strong>{{ t('core', 'Please make sure that the database, the config folder and the data folder have been backed up before proceeding.') }}</strong>
<br>
{{ t('core', 'To avoid timeouts with larger installations, you can instead run the following command from your installation directory:') }}
<pre>./occ upgrade</pre>
</p>
<NcButton
v-if="!isUpdateRunning && !isUpdateDone"
:class="$style.updater__updateButton"
variant="primary"
@click="onStartUpdate">
{{ t('core', 'Start update') }}
</NcButton>
<NcButton
v-else
:class="$style.updater__updateButton"
:disabled="isUpdateRunning"
variant="primary"
@click="reloadPage">
{{ t('core', 'Continue to {productName}', { productName: updateInfo.productName }) }}
</NcButton>
<div v-if="isUpdateRunning || isUpdateDone">
<h2>{{ t('core', 'Update to {version}', { version: updateInfo.version }) }}</h2>
<NcLoadingIcon v-if="isUpdateRunning" />
<NcIconSvgWrapper
v-else
:path="resultIcon"
:class="{
[$style.updater__messageIcon_success]: wasSuccessfull,
[$style.updater__messageIcon_error]: hasErrors && !wasSuccessfull,
[$style.updater__messageIcon_warning]: !hasErrors && !wasSuccessfull,
}" />
<div aria-live="polite">
<em>{{ statusMessage }}</em><br>
<span v-if="redirectMessage">{{ redirectMessage }}</span>
</div>
<NcButton
aria-controlls="core-update-details"
:aria-expanded="isShowingDetails"
variant="tertiary"
@click="isShowingDetails = !isShowingDetails">
<template #icon>
<NcIconSvgWrapper
:path="isShowingDetails ? mdiChevronUp : mdiChevronDown" />
</template>
{{ isShowingDetails ? t('core', 'Hide details') : t('core', 'Show details') }}
</NcButton>
<Transition
:enter-active-class="$style.updater__transition_active"
:leave-active-class="$style.updater__transition_active"
:leave-to-class="$style.updater__transition_collapsed"
:enter-class="$style.updater__transition_collapsed">
<ul
v-show="isShowingDetails"
id="core-update-details"
:aria-label="t('core', 'Update details')"
:class="$style.updater__messageList">
<li
v-for="{ message, type } of messages"
:key="message"
:class="$style.updater__message">
<NcIconSvgWrapper
:class="{
[$style.updater__messageIcon_error]: type === 'error' || type === 'failure',
[$style.updater__messageIcon_info]: type === 'notice',
[$style.updater__messageIcon_success]: type === 'success',
[$style.updater__messageIcon_warning]: type === 'warning',
}"
:path="getSeverityIcon(type)" />
<span :class="$style.updater__messageText">{{ message }}</span>
</li>
</ul>
</Transition>
</div>
</NcGuestContent>
</template>
<style module>
.updater__appsList {
list-style-type: disc;
margin-inline-start: var(--default-clickable-area);
}
.updater__updateButton {
margin-inline: auto;
margin-block: 1rem;
}
.updater__messageList {
max-height: 50vh;
overflow: visible scroll;
padding-inline-start: var(--default-grid-baseline);
}
.updater__message {
display: flex;
align-items: center;
justify-content: start;
gap: var(--default-grid-baseline);
}
.updater__messageText {
text-align: start;
}
.updater__messageIcon_success {
color: var(--color-element-success);
}
.updater__messageIcon_info {
color: var(--color-element-info);
}
.updater__messageIcon_error {
color: var(--color-element-error);
}
.updater__messageIcon_warning {
color: var(--color-element-warning);
}
.updater__transition_active {
transition: all var(--animation-slow);
}
.updater__transition_collapsed {
opacity: 0;
max-height: 0px;
}
</style>

View file

@ -0,0 +1,53 @@
<!--
- SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
- SPDX-License-Identifier: AGPL-3.0-or-later
-->
<script setup lang="ts">
import { mdiInformationOutline } from '@mdi/js'
import { loadState } from '@nextcloud/initial-state'
import { t } from '@nextcloud/l10n'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcGuestContent from '@nextcloud/vue/components/NcGuestContent'
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
const updateInfo = loadState<{
tooBig: boolean
cliUpgradeLink: string
}>('core', 'updateInfo')
</script>
<template>
<NcGuestContent>
<h2>{{ t('core', 'Update needed') }}</h2>
<p>
{{ updateInfo.tooBig
? t('core', 'Please use the command line updater because you have a big instance with more than 50 accounts.')
: t('core', 'Please use the command line updater because updating via browser is disabled in your config.php.') }}
<NcButton :class="$style.updaterAdminCli__button" :href="updateInfo.cliUpgradeLink">
<template #icon>
<NcIconSvgWrapper :path="mdiInformationOutline" />
</template>
{{ t('core', 'Documentation') }}
</NcButton>
</p>
<NcNoteCard type="warning">
{{ t('core', 'I know that if I continue doing the update via web UI has the risk, that the request runs into a timeout and could cause data loss, but I have a backup and know how to restore my instance in case of a failure.') }}
<NcButton
href="?IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup=IAmSuperSureToDoThis"
variant="tertiary">
{{ t('core', 'Upgrade via web on my own risk') }}
</NcButton>
</NcNoteCard>
</NcGuestContent>
</template>
<style module>
.updaterAdminCli__button {
margin-block: 1rem;
margin-inline: auto;
}
</style>

View file

@ -1,60 +0,0 @@
<?php
/**
* SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2013-2015 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
?>
<div class="guest-box update" data-productname="<?php p($_['productName']) ?>" data-version="<?php p($_['version']) ?>">
<div class="updateOverview">
<?php if ($_['isAppsOnlyUpgrade']) { ?>
<h2 class="title"><?php p($l->t('App update required')); ?></h2>
<?php } else { ?>
<h2 class="title"><?php p($l->t('%1$s will be updated to version %2$s',
[$_['productName'], $_['version']])); ?></h2>
<?php } ?>
<?php if (!empty($_['appsToUpgrade'])) { ?>
<div class="text-left">
<span><?php p($l->t('The following apps will be updated:')); ?></span>
<ul class="content appList">
<?php foreach ($_['appsToUpgrade'] as $appInfo) { ?>
<li><?php p($appInfo['name']) ?> (<?php p($appInfo['id']) ?>)</li>
<?php } ?>
</ul>
</div>
<?php } ?>
<?php if (!empty($_['incompatibleAppsList'])) { ?>
<div class="text-left">
<span><?php p($l->t('These incompatible apps will be disabled:')) ?></span>
<ul class="content appList">
<?php foreach ($_['incompatibleAppsList'] as $appInfo) { ?>
<li><?php p($appInfo['name']) ?> (<?php p($appInfo['id']) ?>)</li>
<?php } ?>
</ul>
</div>
<?php } ?>
<?php if (!empty($_['oldTheme'])) { ?>
<div class="infogroup">
<?php p($l->t('The theme %s has been disabled.', [$_['oldTheme']])) ?>
</div>
<?php } ?>
<div class="text-left margin-top bold">
<?php p($l->t('Please make sure that the database, the config folder and the data folder have been backed up before proceeding.')) ?>
</div>
<input class="updateButton primary margin-top" type="button" value="<?php p($l->t('Start update')) ?>">
<div class="notecard warning">
<?php p($l->t('To avoid timeouts with larger installations, you can instead run the following command from your installation directory:')) ?>
<pre>./occ upgrade</pre>
</div>
</div>
<div class="update-progress hidden">
<h2 id="update-progress-title"></h2>
<div id="update-progress-icon" class="icon-loading-dark"></div>
<p id="update-progress-message-error" class="hidden"></p>
<ul id="update-progress-message-warnings" class="hidden"></ul>
<p id="update-progress-message"></p>
<a class="update-show-detailed"><?php p($l->t('Detailed logs')); ?> <span class="icon-caret-white"></span></a>
<div id="update-progress-detailed" class="hidden"></div>
</div>
</div>

View file

@ -0,0 +1,8 @@
<?php
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
?>
<div id="core-updater"></div>

View file

@ -1,32 +0,0 @@
<?php
/**
* SPDX-FileCopyrightText: 2017-2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-FileCopyrightText: 2016 ownCloud, Inc.
* SPDX-License-Identifier: AGPL-3.0-only
*/
?>
<div class="guest-box update" data-productname="<?php p($_['productName']) ?>" data-version="<?php p($_['version']) ?>">
<div class="updateOverview">
<h2 class="title"><?php p($l->t('Update needed')) ?></h2>
<div class="text-left">
<?php if ($_['tooBig']) {
p($l->t('Please use the command line updater because you have a big instance with more than 50 accounts.'));
} else {
p($l->t('Please use the command line updater because updating via browser is disabled in your config.php.'));
} ?><br><br>
<?php if (is_string($_['cliUpgradeLink']) && $_['cliUpgradeLink'] !== '') {
$cliUpgradeLink = $_['cliUpgradeLink'];
} else {
$cliUpgradeLink = link_to_docs('admin-cli-upgrade');
}
print_unescaped($l->t('For help, see the <a target="_blank" rel="noreferrer noopener" href="%s">documentation</a>.', [$cliUpgradeLink])); ?>
</div>
</div>
<?php if ($_['tooBig']) { ?>
<div class="notecard warning">
<p><?php p($l->t('I know that if I continue doing the update via web UI has the risk, that the request runs into a timeout and could cause data loss, but I have a backup and know how to restore my instance in case of a failure.')); ?></p>
<a class="button error margin-top" href="?IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup=IAmSuperSureToDoThis"><?php p($l->t('Upgrade via web on my own risk')); ?></a>
</div>
<?php } ?>
</div>

View file

@ -17,7 +17,9 @@ use OCP\EventDispatcher\IEventDispatcher;
use OCP\Files\Events\BeforeFileSystemSetupEvent;
use OCP\Group\Events\GroupDeletedEvent;
use OCP\Group\Events\UserRemovedEvent;
use OCP\IAppConfig;
use OCP\IConfig;
use OCP\IInitialStateService;
use OCP\ILogger;
use OCP\IRequest;
use OCP\IURLGenerator;
@ -290,47 +292,49 @@ class OC {
$ignoreTooBigWarning = isset($_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup'])
&& $_GET['IKnowThatThisIsABigInstanceAndTheUpdateRequestCouldRunIntoATimeoutAndHowToRestoreABackup'] === 'IAmSuperSureToDoThis';
Util::addTranslations('core');
Util::addScript('core', 'common');
Util::addScript('core', 'main');
Util::addScript('core', 'update');
$initialState = Server::get(IInitialStateService::class);
$serverVersion = \OCP\Server::get(\OCP\ServerVersion::class);
if ($disableWebUpdater || ($tooBig && !$ignoreTooBigWarning)) {
// send http status 503
http_response_code(503);
header('Retry-After: 120');
$serverVersion = \OCP\Server::get(\OCP\ServerVersion::class);
$urlGenerator = Server::get(IURLGenerator::class);
$initialState->provideInitialState('core', 'updaterView', 'adminCli');
$initialState->provideInitialState('core', 'updateInfo', [
'cliUpgradeLink' => $cliUpgradeLink ?: $urlGenerator->linkToDocs('admin-cli-upgrade'),
'productName' => self::getProductName(),
'version' => $serverVersion->getVersionString(),
'tooBig' => $tooBig,
]);
// render error page
$template = Server::get(ITemplateManager::class)->getTemplate('', 'update.use-cli', 'guest');
$template->assign('productName', 'nextcloud'); // for now
$template->assign('version', $serverVersion->getVersionString());
$template->assign('tooBig', $tooBig);
$template->assign('cliUpgradeLink', $cliUpgradeLink);
$template->printPage();
Server::get(ITemplateManager::class)
->getTemplate('', 'update', 'guest')
->printPage();
die();
}
// check whether this is a core update or apps update
$installedVersion = $systemConfig->getValue('version', '0.0.0');
$currentVersion = implode('.', \OCP\Util::getVersion());
$currentVersion = implode('.', $serverVersion->getVersion());
// if not a core upgrade, then it's apps upgrade
$isAppsOnlyUpgrade = version_compare($currentVersion, $installedVersion, '=');
$oldTheme = $systemConfig->getValue('theme');
$systemConfig->setValue('theme', '');
\OCP\Util::addScript('core', 'common');
\OCP\Util::addScript('core', 'main');
\OCP\Util::addTranslations('core');
\OCP\Util::addScript('core', 'update');
/** @var \OC\App\AppManager $appManager */
$appManager = Server::get(\OCP\App\IAppManager::class);
$tmpl = Server::get(ITemplateManager::class)->getTemplate('', 'update.admin', 'guest');
$tmpl->assign('version', \OCP\Server::get(\OCP\ServerVersion::class)->getVersionString());
$tmpl->assign('isAppsOnlyUpgrade', $isAppsOnlyUpgrade);
// get third party apps
$ocVersion = \OCP\Util::getVersion();
$ocVersion = $serverVersion->getVersion();
$ocVersion = implode('.', $ocVersion);
$incompatibleApps = $appManager->getIncompatibleApps($ocVersion);
$incompatibleOverwrites = $systemConfig->getValue('app_install_overwrite', []);
@ -351,16 +355,41 @@ class OC {
throw new \OCP\HintException('Application ' . implode(', ', $incompatibleShippedApps) . ' is not present or has a non-compatible version with this server. Please check the apps directory.', $hint);
}
$tmpl->assign('appsToUpgrade', $appManager->getAppsNeedingUpgrade($ocVersion));
$tmpl->assign('incompatibleAppsList', $incompatibleDisabledApps);
$appConfig = Server::get(IAppConfig::class);
$appsToUpgrade = array_map(function ($app) use (&$appConfig) {
return [
'id' => $app['id'],
'name' => $app['name'],
'version' => $app['version'],
'oldVersion' => $appConfig->getValueString($app['id'], 'installed_version'),
];
}, $appManager->getAppsNeedingUpgrade($ocVersion));
$params = [
'appsToUpgrade' => $appsToUpgrade,
'incompatibleAppsList' => $incompatibleDisabledApps,
'isAppsOnlyUpgrade' => $isAppsOnlyUpgrade,
'oldTheme' => $oldTheme,
'productName' => self::getProductName(),
'version' => $serverVersion->getVersionString(),
];
$initialState->provideInitialState('core', 'updaterView', 'admin');
$initialState->provideInitialState('core', 'updateInfo', $params);
Server::get(ITemplateManager::class)
->getTemplate('', 'update', 'guest')
->printPage();
}
private static function getProductName(): string {
$productName = 'Nextcloud';
try {
$defaults = new \OC_Defaults();
$tmpl->assign('productName', $defaults->getName());
$productName = $defaults->getName();
} catch (Throwable $error) {
$tmpl->assign('productName', 'Nextcloud');
// ignore
}
$tmpl->assign('oldTheme', $oldTheme);
$tmpl->printPage();
return $productName;
}
public static function initSession(): void {