From da86e5495c9b47f063beaaa912d26f7ae36e2e07 Mon Sep 17 00:00:00 2001
From: Sebastian Fischer <typo3@evoweb.de>
Date: Sun, 20 Aug 2017 18:18:15 +0200
Subject: [PATCH] [FEATURE] Improve status output of refindex update

With adding progressbar support while updating the reference index,
it is now possible to see which table is updated and how much progress
has already been achieved.

To be able to implement the progressbar in the frontend in a different
way, a progress listener interface is introduced which can be added separately.

At the same time, the FlashMessage logic is now put in the TYPO3 Backend
controller and the $cli_echo is now used to inject the progress listener interface

Resolves: #82062
Releases: master
Change-Id: Id11ba8a9f3e033eaea9f3aff5a7f5eb0602cfa02
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/53748
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Georg Ringer <georg.ringer@gmail.com>
Tested-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
Reviewed-by: Sebastian Fischer <typo3@evoweb.de>
Reviewed-by: Georg Ringer <georg.ringer@gmail.com>
Reviewed-by: Anja Leichsenring <aleichsenring@ab-softlab.de>
---
 .../ReferenceIndexProgressListener.php        | 125 ++++++++++++++++++
 .../Command/ReferenceIndexUpdateCommand.php   |  14 +-
 .../View/ProgressListenerInterface.php        |  60 +++++++++
 .../core/Classes/Database/ReferenceIndex.php  | 113 ++++++++++------
 ...2-ProgressForReferenceIndexUpdateOnCLI.rst |  22 +++
 .../ReferenceIndexUpdatedPrerequisite.php     |  18 +--
 .../FilesWithMultipleReferencesCommand.php    |   7 +-
 .../Classes/Command/LostFilesCommand.php      |   6 +-
 .../Classes/Command/MissingFilesCommand.php   |   5 +-
 .../Command/MissingRelationsCommand.php       |   5 +-
 .../DatabaseIntegrityController.php           |  17 ++-
 11 files changed, 336 insertions(+), 56 deletions(-)
 create mode 100644 typo3/sysext/backend/Classes/Command/ProgressListener/ReferenceIndexProgressListener.php
 create mode 100644 typo3/sysext/backend/Classes/View/ProgressListenerInterface.php
 create mode 100644 typo3/sysext/core/Documentation/Changelog/master/Feature-82062-ProgressForReferenceIndexUpdateOnCLI.rst

diff --git a/typo3/sysext/backend/Classes/Command/ProgressListener/ReferenceIndexProgressListener.php b/typo3/sysext/backend/Classes/Command/ProgressListener/ReferenceIndexProgressListener.php
new file mode 100644
index 000000000000..9ad606827e90
--- /dev/null
+++ b/typo3/sysext/backend/Classes/Command/ProgressListener/ReferenceIndexProgressListener.php
@@ -0,0 +1,125 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Backend\Command\ProgressListener;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Psr\Log\LogLevel;
+use Symfony\Component\Console\Helper\ProgressBar;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use TYPO3\CMS\Backend\View\ProgressListenerInterface;
+
+/**
+ * Shows the update for the reference index progress on the command line.
+ * @internal not part of TYPO3 Public API as it is an implementation of a concrete feature.
+ */
+class ReferenceIndexProgressListener implements ProgressListenerInterface
+{
+    /**
+     * @var SymfonyStyle
+     */
+    protected $io;
+
+    /**
+     * @var ProgressBar
+     */
+    protected $progressBar;
+
+    /**
+     * @var bool
+     */
+    protected $isEnabled = false;
+
+    public function initialize(SymfonyStyle $io)
+    {
+        $this->io = $io;
+        $this->isEnabled = $io->isQuiet() === false;
+    }
+
+    public function start(int $maxSteps = 0, string $additionalMessage = null): void
+    {
+        if (!$this->isEnabled) {
+            return;
+        }
+        $tableName = $additionalMessage;
+        if ($maxSteps > 0) {
+            $this->io->section('Update index of table ' . $tableName);
+            $this->progressBar = $this->io->createProgressBar($maxSteps);
+            $this->progressBar->start($maxSteps);
+        } else {
+            $this->io->section('Nothing to update for table ' . $tableName);
+            $this->progressBar = null;
+        }
+    }
+
+    public function advance(int $step = 1, string $additionalMessage = null): void
+    {
+        if (!$this->isEnabled) {
+            return;
+        }
+        if ($additionalMessage) {
+            $this->showMessageWhileInProgress(function () use ($additionalMessage) {
+                $this->io->writeln($additionalMessage);
+            });
+        }
+        if ($this->progressBar !== null) {
+            $this->progressBar->advance();
+        }
+    }
+
+    public function finish(string $additionalMessage = null): void
+    {
+        if (!$this->isEnabled) {
+            return;
+        }
+        if ($this->progressBar !== null) {
+            $this->progressBar->finish();
+            $this->progressBar = null;
+        }
+        $this->io->writeln(PHP_EOL);
+        if ($additionalMessage) {
+            $this->io->writeln($additionalMessage);
+        }
+    }
+
+    public function log(string $message, string $logLevel = LogLevel::INFO): void
+    {
+        if (!$this->isEnabled) {
+            return;
+        }
+        $this->showMessageWhileInProgress(function () use ($message, $logLevel) {
+            switch ($logLevel) {
+                case LogLevel::ERROR:
+                    $this->io->error($message);
+                    break;
+                case LogLevel::WARNING:
+                    $this->io->warning($message);
+                    break;
+                default:
+                    $this->io->writeln($message);
+            }
+        });
+    }
+
+    protected function showMessageWhileInProgress(callable $messageFunction): void
+    {
+        if ($this->progressBar !== null) {
+            $this->progressBar->clear();
+            $messageFunction();
+            $this->progressBar->display();
+        } else {
+            $messageFunction();
+        }
+    }
+}
diff --git a/typo3/sysext/backend/Classes/Command/ReferenceIndexUpdateCommand.php b/typo3/sysext/backend/Classes/Command/ReferenceIndexUpdateCommand.php
index f9fc7fe591b1..2127f9d15152 100644
--- a/typo3/sysext/backend/Classes/Command/ReferenceIndexUpdateCommand.php
+++ b/typo3/sysext/backend/Classes/Command/ReferenceIndexUpdateCommand.php
@@ -18,6 +18,8 @@ use Symfony\Component\Console\Command\Command;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use TYPO3\CMS\Backend\Command\ProgressListener\ReferenceIndexProgressListener;
 use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Database\ReferenceIndex;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -50,13 +52,19 @@ class ReferenceIndexUpdateCommand extends Command
     protected function execute(InputInterface $input, OutputInterface $output)
     {
         Bootstrap::initializeBackendAuthentication();
+        $io = new SymfonyStyle($input, $output);
 
         $isTestOnly = $input->getOption('check');
-        $isSilent = $output->getVerbosity() !== OutputInterface::VERBOSITY_QUIET;
 
-        /** @var ReferenceIndex $referenceIndex */
+        $progressListener = GeneralUtility::makeInstance(ReferenceIndexProgressListener::class);
+        $progressListener->initialize($io);
         $referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
         $referenceIndex->enableRuntimeCache();
-        $referenceIndex->updateIndex($isTestOnly, $isSilent);
+        if ($isTestOnly) {
+            $io->section('Reference Index being TESTED (nothing written, remove the "--check" argument)');
+        } else {
+            $io->section('Reference Index is now being updated');
+        }
+        $referenceIndex->updateIndex($isTestOnly, $progressListener);
     }
 }
diff --git a/typo3/sysext/backend/Classes/View/ProgressListenerInterface.php b/typo3/sysext/backend/Classes/View/ProgressListenerInterface.php
new file mode 100644
index 000000000000..60668a16b4e3
--- /dev/null
+++ b/typo3/sysext/backend/Classes/View/ProgressListenerInterface.php
@@ -0,0 +1,60 @@
+<?php
+declare(strict_types = 1);
+namespace TYPO3\CMS\Backend\View;
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+use Psr\Log\LogLevel;
+
+/**
+ * An interface that tracks the progress of a longer-running progress.
+ * This acts as a wrapper for Symfony ProgressBar on CLI, so see the info here.
+ *
+ * The listener can be used multiple times when calling start() again a new progressbar starts.
+ *
+ * @internal this interface is still experimental, and not considered part of TYPO3 Public API.
+ */
+interface ProgressListenerInterface
+{
+
+    /**
+     * Start a progress by using the maximum items, and an additional header message.
+     *
+     * @param int $maxSteps set the maximum amount of items to be processed
+     * @param string|null $additionalMessage a separate text message
+     */
+    public function start(int $maxSteps = 0, string $additionalMessage = null): void;
+
+    /**
+     * Move the progress one step further
+     * @param int $step by default, this is "1" but can be used to skip further.
+     * @param string|null $additionalMessage a separate text message
+     */
+    public function advance(int $step = 1, string $additionalMessage = null): void;
+
+    /**
+     * Stop the progress, automatically setting it to 100%.
+     *
+     * @param string|null $additionalMessage a separate text message
+     */
+    public function finish(string $additionalMessage = null): void;
+
+    /**
+     * Can be used to render custom messages during the progress.
+     *
+     * @param string $message the message to render
+     * @param string $logLevel used as severity
+     */
+    public function log(string $message, string $logLevel = LogLevel::INFO): void;
+}
diff --git a/typo3/sysext/core/Classes/Database/ReferenceIndex.php b/typo3/sysext/core/Classes/Database/ReferenceIndex.php
index 4c0dcf25617e..86a168dcb9d2 100644
--- a/typo3/sysext/core/Classes/Database/ReferenceIndex.php
+++ b/typo3/sysext/core/Classes/Database/ReferenceIndex.php
@@ -18,14 +18,14 @@ use Doctrine\DBAL\DBALException;
 use Psr\EventDispatcher\EventDispatcherInterface;
 use Psr\Log\LoggerAwareInterface;
 use Psr\Log\LoggerAwareTrait;
+use Psr\Log\LogLevel;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Backend\View\ProgressListenerInterface;
 use TYPO3\CMS\Core\Cache\CacheManager;
 use TYPO3\CMS\Core\Configuration\FlexForm\FlexFormTools;
 use TYPO3\CMS\Core\Database\Platform\PlatformInformation;
 use TYPO3\CMS\Core\DataHandling\DataHandler;
 use TYPO3\CMS\Core\DataHandling\Event\IsTableExcludedFromReferenceIndexEvent;
-use TYPO3\CMS\Core\Messaging\FlashMessage;
-use TYPO3\CMS\Core\Messaging\FlashMessageRendererResolver;
 use TYPO3\CMS\Core\Registry;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -1061,15 +1061,25 @@ class ReferenceIndex implements LoggerAwareInterface
      * Updating Index (External API)
      *
      * @param bool $testOnly If set, only a test
-     * @param bool $cli_echo If set, output CLI status
+     * @param bool $cli_echo If set, output CLI status - but can now be of type ProgressListenerInterface, which should be used instead.
      * @return array Header and body status content
      */
-    public function updateIndex($testOnly, $cli_echo = false)
+    public function updateIndex($testOnly, $cli_echo = null)
     {
+        $progressListener = null;
+        if ($cli_echo instanceof ProgressListenerInterface) {
+            $progressListener = $cli_echo;
+            $cli_echo = null;
+        }
+        if ($cli_echo !== null) {
+            trigger_error('The second argument of ReferenceIndex->updateIndex() will not work in TYPO3 v11 anymore. Use the ProgressListener to show detailled results', E_USER_DEPRECATED);
+        } else {
+            // default value for now
+            $cli_echo = false;
+        }
         $errors = [];
         $tableNames = [];
         $recCount = 0;
-        $tableCount = 0;
         $headerContent = $testOnly ? 'Reference Index being TESTED (nothing written, remove the "--check" argument)' : 'Reference Index being Updated';
         if ($cli_echo) {
             echo '*******************************************' . LF . $headerContent . LF . '*******************************************' . LF;
@@ -1109,9 +1119,14 @@ class ReferenceIndex implements LoggerAwareInterface
                 continue;
             }
 
+            if ($progressListener) {
+                $progressListener->start($queryResult->rowCount(), $tableName);
+            }
             $tableNames[] = $tableName;
-            $tableCount++;
             while ($record = $queryResult->fetch()) {
+                if ($progressListener) {
+                    $progressListener->advance();
+                }
                 $refIndexObj = GeneralUtility::makeInstance(self::class);
                 if (isset($record['t3ver_wsid'])) {
                     $refIndexObj->setWorkspaceId($record['t3ver_wsid']);
@@ -1121,11 +1136,17 @@ class ReferenceIndex implements LoggerAwareInterface
                 if ($result['addedNodes'] || $result['deletedNodes']) {
                     $error = 'Record ' . $tableName . ':' . $record['uid'] . ' had ' . $result['addedNodes'] . ' added indexes and ' . $result['deletedNodes'] . ' deleted indexes';
                     $errors[] = $error;
+                    if ($progressListener) {
+                        $progressListener->log($error, LogLevel::WARNING);
+                    }
                     if ($cli_echo) {
                         echo $error . LF;
                     }
                 }
             }
+            if ($progressListener) {
+                $progressListener->finish();
+            }
 
             // Subselect based queries only work on the same connection
             if ($refIndexConnectionName !== $tableConnectionName) {
@@ -1166,6 +1187,9 @@ class ReferenceIndex implements LoggerAwareInterface
             if ($lostIndexes > 0) {
                 $error = 'Table ' . $tableName . ' has ' . $lostIndexes . ' lost indexes which are now deleted';
                 $errors[] = $error;
+                if ($progressListener) {
+                    $progressListener->log($error, LogLevel::WARNING);
+                }
                 if ($cli_echo) {
                     echo $error . LF;
                 }
@@ -1185,47 +1209,29 @@ class ReferenceIndex implements LoggerAwareInterface
         }
 
         // Searching lost indexes for non-existing tables
-        $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_refindex');
-        $queryBuilder->getRestrictions()->removeAll();
-        $lostTables = $queryBuilder
-            ->count('hash')
-            ->from('sys_refindex')
-            ->where(
-                $queryBuilder->expr()->notIn(
-                    'tablename',
-                    $queryBuilder->createNamedParameter($tableNames, Connection::PARAM_STR_ARRAY)
-                )
-            )->execute()
-            ->fetchColumn(0);
-
+        $lostTables = $this->getAmountOfUnusedTablesInReferenceIndex($tableNames);
         if ($lostTables > 0) {
             $error = 'Index table hosted ' . $lostTables . ' indexes for non-existing tables, now removed';
             $errors[] = $error;
+            if ($progressListener) {
+                $progressListener->log($error, LogLevel::WARNING);
+            }
             if ($cli_echo) {
                 echo $error . LF;
             }
             if (!$testOnly) {
-                $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_refindex');
-                $queryBuilder->delete('sys_refindex')
-                    ->where(
-                        $queryBuilder->expr()->notIn(
-                            'tablename',
-                            $queryBuilder->createNamedParameter($tableNames, Connection::PARAM_STR_ARRAY)
-                        )
-                    )->execute();
+                $this->removeReferenceIndexDataFromUnusedDatabaseTables($tableNames);
             }
         }
         $errorCount = count($errors);
-        $recordsCheckedString = $recCount . ' records from ' . $tableCount . ' tables were checked/updated.' . LF;
-        $flashMessage = GeneralUtility::makeInstance(
-            FlashMessage::class,
-            $errorCount ? implode('##LF##', $errors) : 'Index Integrity was perfect!',
-            $recordsCheckedString,
-            $errorCount ? FlashMessage::ERROR : FlashMessage::OK
-        );
-
-        $flashMessageRenderer = GeneralUtility::makeInstance(FlashMessageRendererResolver::class)->resolve();
-        $bodyContent = $flashMessageRenderer->render([$flashMessage]);
+        $recordsCheckedString = $recCount . ' records from ' . count($tableNames) . ' tables were checked/updated.' . LF;
+        if ($progressListener) {
+            if ($errorCount) {
+                $progressListener->log($recordsCheckedString . 'Updates: ' . $errorCount, LogLevel::WARNING);
+            } else {
+                $progressListener->log($recordsCheckedString . 'Index Integrity was perfect!', LogLevel::INFO);
+            }
+        }
         if ($cli_echo) {
             echo $recordsCheckedString . ($errorCount ? 'Updates: ' . $errorCount : 'Index Integrity was perfect!') . LF;
         }
@@ -1233,7 +1239,38 @@ class ReferenceIndex implements LoggerAwareInterface
             $registry = GeneralUtility::makeInstance(Registry::class);
             $registry->set('core', 'sys_refindex_lastUpdate', $GLOBALS['EXEC_TIME']);
         }
-        return [$headerContent, $bodyContent, $errorCount];
+        return [$headerContent, trim($recordsCheckedString), $errorCount, $errors];
+    }
+
+    protected function getAmountOfUnusedTablesInReferenceIndex(array $tableNames): int
+    {
+        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
+        $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_refindex');
+        $queryBuilder->getRestrictions()->removeAll();
+        $lostTables = $queryBuilder
+            ->count('hash')
+            ->from('sys_refindex')
+            ->where(
+                $queryBuilder->expr()->notIn(
+                    'tablename',
+                    $queryBuilder->createNamedParameter($tableNames, Connection::PARAM_STR_ARRAY)
+                )
+            )->execute()
+            ->fetchColumn(0);
+        return (int)$lostTables;
+    }
+
+    protected function removeReferenceIndexDataFromUnusedDatabaseTables(array $tableNames): void
+    {
+        $connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
+        $queryBuilder = $connectionPool->getQueryBuilderForTable('sys_refindex');
+        $queryBuilder->delete('sys_refindex')
+            ->where(
+                $queryBuilder->expr()->notIn(
+                    'tablename',
+                    $queryBuilder->createNamedParameter($tableNames, Connection::PARAM_STR_ARRAY)
+                )
+            )->execute();
     }
 
     /**
diff --git a/typo3/sysext/core/Documentation/Changelog/master/Feature-82062-ProgressForReferenceIndexUpdateOnCLI.rst b/typo3/sysext/core/Documentation/Changelog/master/Feature-82062-ProgressForReferenceIndexUpdateOnCLI.rst
new file mode 100644
index 000000000000..7f4b573b522c
--- /dev/null
+++ b/typo3/sysext/core/Documentation/Changelog/master/Feature-82062-ProgressForReferenceIndexUpdateOnCLI.rst
@@ -0,0 +1,22 @@
+.. include:: ../../Includes.txt
+
+============================================================
+Feature: #82062 - Progress for Reference Index update on CLI
+============================================================
+
+See :issue:`82062`
+
+Description
+===========
+
+The Reference Index updating process now shows the current status
+when looping over each database table, to have a more visualized
+status.
+
+
+Impact
+======
+
+Calling :php:`./typo3/sysext/core/bin/typo3 referenceindex:update -c` shows the new output when running the reference update (`-c` is for checking only).
+
+.. index:: CLI, ext:lowlevel
\ No newline at end of file
diff --git a/typo3/sysext/install/Classes/Updates/ReferenceIndexUpdatedPrerequisite.php b/typo3/sysext/install/Classes/Updates/ReferenceIndexUpdatedPrerequisite.php
index 9fb8ce947f8a..ef0b4fdf2c84 100644
--- a/typo3/sysext/install/Classes/Updates/ReferenceIndexUpdatedPrerequisite.php
+++ b/typo3/sysext/install/Classes/Updates/ReferenceIndexUpdatedPrerequisite.php
@@ -16,7 +16,10 @@ namespace TYPO3\CMS\Install\Updates;
  * The TYPO3 project - inspiring people to share!
  */
 
+use Symfony\Component\Console\Input\ArrayInput;
 use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use TYPO3\CMS\Backend\Command\ProgressListener\ReferenceIndexProgressListener;
 use TYPO3\CMS\Core\Database\ReferenceIndex;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 
@@ -61,13 +64,12 @@ class ReferenceIndexUpdatedPrerequisite implements PrerequisiteInterface, Chatty
      */
     public function ensure(): bool
     {
+        $this->output->writeln('Reference Index is being updated');
+        $progressListener = GeneralUtility::makeInstance(ReferenceIndexProgressListener::class);
+        $progressListener->initialize(new SymfonyStyle(new ArrayInput([]), $this->output));
         $this->referenceIndex->enableRuntimeCache();
-        ob_clean();
-        ob_start();
-        $result = $this->referenceIndex->updateIndex(false, true);
-        $output = ob_get_clean();
-        $this->output->write($output);
-        return $result[2] === 0;
+        $result = $this->referenceIndex->updateIndex(false, $progressListener);
+        return count($result[3]) === 0;
     }
 
     /**
@@ -78,8 +80,8 @@ class ReferenceIndexUpdatedPrerequisite implements PrerequisiteInterface, Chatty
     public function isFulfilled(): bool
     {
         $this->referenceIndex->enableRuntimeCache();
-        $result = $this->referenceIndex->updateIndex(true, false);
-        return $result[2] === 0;
+        $result = $this->referenceIndex->updateIndex(true);
+        return count($result[3]) === 0;
     }
 
     /**
diff --git a/typo3/sysext/lowlevel/Classes/Command/FilesWithMultipleReferencesCommand.php b/typo3/sysext/lowlevel/Classes/Command/FilesWithMultipleReferencesCommand.php
index ff208b22d5b0..fb275540412d 100644
--- a/typo3/sysext/lowlevel/Classes/Command/FilesWithMultipleReferencesCommand.php
+++ b/typo3/sysext/lowlevel/Classes/Command/FilesWithMultipleReferencesCommand.php
@@ -20,6 +20,7 @@ use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
 use Symfony\Component\Console\Style\SymfonyStyle;
+use TYPO3\CMS\Backend\Command\ProgressListener\ReferenceIndexProgressListener;
 use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Database\ConnectionPool;
 use TYPO3\CMS\Core\Database\ReferenceIndex;
@@ -131,8 +132,12 @@ If you want to get more detailed information, use the --verbose option.')
 
         // Update the reference index
         if ($updateReferenceIndex) {
+            $progressListener = GeneralUtility::makeInstance(ReferenceIndexProgressListener::class);
+            $progressListener->initialize($io);
+
             $referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
-            $referenceIndex->updateIndex(false, !$io->isQuiet());
+            $io->section('Reference Index is now being updated');
+            $referenceIndex->updateIndex(false, $progressListener);
         } else {
             $io->writeln('Reference index is assumed to be up to date, continuing.');
         }
diff --git a/typo3/sysext/lowlevel/Classes/Command/LostFilesCommand.php b/typo3/sysext/lowlevel/Classes/Command/LostFilesCommand.php
index 5bcf093592ff..7c8f00a681d4 100644
--- a/typo3/sysext/lowlevel/Classes/Command/LostFilesCommand.php
+++ b/typo3/sysext/lowlevel/Classes/Command/LostFilesCommand.php
@@ -20,6 +20,7 @@ use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
 use Symfony\Component\Console\Style\SymfonyStyle;
+use TYPO3\CMS\Backend\Command\ProgressListener\ReferenceIndexProgressListener;
 use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Database\ConnectionPool;
@@ -157,8 +158,11 @@ If you want to get more detailed information, use the --verbose option.')
 
         // Update the reference index
         if ($updateReferenceIndex) {
+            $progressListener = GeneralUtility::makeInstance(ReferenceIndexProgressListener::class);
+            $progressListener->initialize($io);
             $referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
-            $referenceIndex->updateIndex(false, !$io->isQuiet());
+            $io->section('Reference Index is now being updated');
+            $referenceIndex->updateIndex(false, $progressListener);
         } else {
             $io->writeln('Reference index is assumed to be up to date, continuing.');
         }
diff --git a/typo3/sysext/lowlevel/Classes/Command/MissingFilesCommand.php b/typo3/sysext/lowlevel/Classes/Command/MissingFilesCommand.php
index e0e1803c08f4..79ab4297af61 100644
--- a/typo3/sysext/lowlevel/Classes/Command/MissingFilesCommand.php
+++ b/typo3/sysext/lowlevel/Classes/Command/MissingFilesCommand.php
@@ -20,6 +20,7 @@ use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
 use Symfony\Component\Console\Style\SymfonyStyle;
+use TYPO3\CMS\Backend\Command\ProgressListener\ReferenceIndexProgressListener;
 use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Core\Environment;
 use TYPO3\CMS\Core\Database\ConnectionPool;
@@ -137,8 +138,10 @@ If you want to get more detailed information, use the --verbose option.')
 
         // Update the reference index
         if ($updateReferenceIndex) {
+            $progressListener = GeneralUtility::makeInstance(ReferenceIndexProgressListener::class);
+            $progressListener->initialize($io);
             $referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
-            $referenceIndex->updateIndex(false, !$io->isQuiet());
+            $referenceIndex->updateIndex(false, $progressListener);
         } else {
             $io->writeln('Reference index is assumed to be up to date, continuing.');
         }
diff --git a/typo3/sysext/lowlevel/Classes/Command/MissingRelationsCommand.php b/typo3/sysext/lowlevel/Classes/Command/MissingRelationsCommand.php
index e8f9d02c4e38..b257c0861782 100644
--- a/typo3/sysext/lowlevel/Classes/Command/MissingRelationsCommand.php
+++ b/typo3/sysext/lowlevel/Classes/Command/MissingRelationsCommand.php
@@ -20,6 +20,7 @@ use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
 use Symfony\Component\Console\Style\SymfonyStyle;
+use TYPO3\CMS\Backend\Command\ProgressListener\ReferenceIndexProgressListener;
 use TYPO3\CMS\Backend\Utility\BackendUtility;
 use TYPO3\CMS\Core\Core\Bootstrap;
 use TYPO3\CMS\Core\Database\ConnectionPool;
@@ -197,8 +198,10 @@ If you want to get more detailed information, use the --verbose option.')
 
         // Update the reference index
         if ($updateReferenceIndex) {
+            $progressListener = GeneralUtility::makeInstance(ReferenceIndexProgressListener::class);
+            $progressListener->initialize($io);
             $referenceIndex = GeneralUtility::makeInstance(ReferenceIndex::class);
-            $referenceIndex->updateIndex(false, !$io->isQuiet());
+            $referenceIndex->updateIndex(false, $progressListener);
         } else {
             $io->writeln('Reference index is assumed to be up to date, continuing.');
         }
diff --git a/typo3/sysext/lowlevel/Classes/Controller/DatabaseIntegrityController.php b/typo3/sysext/lowlevel/Classes/Controller/DatabaseIntegrityController.php
index 269b1c64282c..458a30409e82 100644
--- a/typo3/sysext/lowlevel/Classes/Controller/DatabaseIntegrityController.php
+++ b/typo3/sysext/lowlevel/Classes/Controller/DatabaseIntegrityController.php
@@ -25,6 +25,8 @@ use TYPO3\CMS\Core\Http\HtmlResponse;
 use TYPO3\CMS\Core\Imaging\Icon;
 use TYPO3\CMS\Core\Imaging\IconFactory;
 use TYPO3\CMS\Core\Localization\LanguageService;
+use TYPO3\CMS\Core\Messaging\FlashMessage;
+use TYPO3\CMS\Core\Messaging\FlashMessageRendererResolver;
 use TYPO3\CMS\Core\Page\PageRenderer;
 use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -309,11 +311,20 @@ class DatabaseIntegrityController
 
         if (GeneralUtility::_GP('_update') || GeneralUtility::_GP('_check')) {
             $testOnly = (bool)GeneralUtility::_GP('_check');
-            // Call the functionality
             $refIndexObj = GeneralUtility::makeInstance(ReferenceIndex::class);
             $refIndexObj->enableRuntimeCache();
-            [, $bodyContent] = $refIndexObj->updateIndex($testOnly);
-            $this->view->assign('content', str_replace('##LF##', '<br />', $bodyContent));
+            [, $recordsCheckedString, , $errors] = $refIndexObj->updateIndex($testOnly);
+            $flashMessage = GeneralUtility::makeInstance(
+                FlashMessage::class,
+                !empty($errors) ? implode("\n", $errors) : 'Index Integrity was perfect!',
+                $recordsCheckedString,
+                !empty($errors) ? FlashMessage::ERROR : FlashMessage::OK
+            );
+
+            $flashMessageRenderer = GeneralUtility::makeInstance(FlashMessageRendererResolver::class)->resolve();
+            $bodyContent = $flashMessageRenderer->render([$flashMessage]);
+
+            $this->view->assign('content', nl2br($bodyContent));
         }
     }
 
-- 
GitLab