Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[4.4] Upmerge #2998

Closed
jgerman-bot opened this issue Sep 14, 2023 · 0 comments · Fixed by #3005
Closed

[4.4] Upmerge #2998

jgerman-bot opened this issue Sep 14, 2023 · 0 comments · Fixed by #3005

Comments

@jgerman-bot
Copy link

New language relevant PR in upstream repo: joomla/joomla-cms#41733 Here are the upstream changes:

Click to expand the diff!
diff --git a/administrator/components/com_admin/script.php b/administrator/components/com_admin/script.php
index 61b836e58a745..7c784b7a374dd 100644
--- a/administrator/components/com_admin/script.php
+++ b/administrator/components/com_admin/script.php
@@ -41,6 +41,50 @@ class JoomlaInstallerScript
      */
     protected $fromVersion = null;
 
+    /**
+     * Callback for collecting errors. Like function(string $context, \Throwable $error){};
+     *
+     * @var callable
+     *
+     * @since  __DEPLOY_VERSION__
+     */
+    protected $errorCollector;
+
+    /**
+     * Set the callback for collecting errors.
+     *
+     * @param   callable  $callback  The callback Like function(string $context, \Throwable $error){};
+     *
+     * @return  void
+     *
+     * @since  __DEPLOY_VERSION__
+     */
+    public function setErrorCollector(callable $callback)
+    {
+        $this->errorCollector = $callback;
+    }
+
+    /**
+     * Collect errors.
+     *
+     * @param  string      $context  A context/place where error happened
+     * @param  \Throwable  $error    The error that occurred
+     *
+     * @return  void
+     *
+     * @since  __DEPLOY_VERSION__
+     */
+    protected function collectError(string $context, \Throwable $error)
+    {
+        // The errorCollector are required
+        // However when someone already running the script manually the code may fail.
+        if ($this->errorCollector) {
+            call_user_func($this->errorCollector, $context, $error);
+        } else {
+            Log::add($error->getMessage(), Log::ERROR, 'Update');
+        }
+    }
+
     /**
      * Function to act prior to installation process begins
      *
@@ -83,30 +127,45 @@ public function preflight($action, $installer)
      */
     public function update($installer)
     {
-        $options['format']    = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
-        $options['text_file'] = 'joomla_update.php';
+        // Uninstall plugins before removing their files and folders
+        try {
+            $this->uninstallRepeatableFieldsPlugin();
+        } catch (\Throwable $e) {
+            $this->collectError('uninstallRepeatableFieldsPlugin', $e);
+        }
 
-        Log::addLogger($options, Log::INFO, ['Update', 'databasequery', 'jerror']);
+        try {
+            $this->uninstallEosPlugin();
+        } catch (\Throwable $e) {
+            $this->collectError('uninstallEosPlugin', $e);
+        }
 
+        // Remove old files
         try {
             Log::add(Text::_('COM_JOOMLAUPDATE_UPDATE_LOG_DELETE_FILES'), Log::INFO, 'Update');
-        } catch (RuntimeException $exception) {
-            // Informational log only
+            $this->deleteUnexistingFiles();
+        } catch (\Throwable $e) {
+            $this->collectError('deleteUnexistingFiles', $e);
         }
 
-        // Uninstall plugins before removing their files and folders
-        $this->uninstallRepeatableFieldsPlugin();
-        $this->uninstallEosPlugin();
-
-        // This needs to stay for 2.5 update compatibility
-        $this->deleteUnexistingFiles();
-        $this->updateManifestCaches();
-        $this->updateDatabase();
-        $this->updateAssets($installer);
-        $this->clearStatsCache();
-        $this->convertTablesToUtf8mb4(true);
-        $this->addUserAuthProviderColumn();
-        $this->cleanJoomlaCache();
+        // Further update
+        try {
+            $this->updateManifestCaches();
+            $this->updateDatabase();
+            $this->updateAssets($installer);
+            $this->clearStatsCache();
+            $this->convertTablesToUtf8mb4(true);
+            $this->addUserAuthProviderColumn();
+        } catch (\Throwable $e) {
+            $this->collectError('Further update', $e);
+        }
+
+        // Clean cache
+        try {
+            $this->cleanJoomlaCache();
+        } catch (\Throwable $e) {
+            $this->collectError('cleanJoomlaCache', $e);
+        }
     }
 
     /**
@@ -131,7 +190,7 @@ protected function clearStatsCache()
                     ->where($db->quoteName('element') . ' = ' . $db->quote('stats'))
             )->loadResult();
         } catch (Exception $e) {
-            echo Text::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED', $e->getCode(), $e->getMessage()) . '<br>';
+            $this->collectError(__METHOD__, $e);
 
             return;
         }
@@ -155,7 +214,7 @@ protected function clearStatsCache()
         try {
             $db->setQuery($query)->execute();
         } catch (Exception $e) {
-            echo Text::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED', $e->getCode(), $e->getMessage()) . '<br>';
+            $this->collectError(__METHOD__, $e);
 
             return;
         }
@@ -187,7 +246,7 @@ protected function updateDatabaseMysql()
         try {
             $results = $db->loadObjectList();
         } catch (Exception $e) {
-            echo Text::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED', $e->getCode(), $e->getMessage()) . '<br>';
+            $this->collectError(__METHOD__, $e);
 
             return;
         }
@@ -202,7 +261,7 @@ protected function updateDatabaseMysql()
             try {
                 $db->execute();
             } catch (Exception $e) {
-                echo Text::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED', $e->getCode(), $e->getMessage()) . '<br>';
+                $this->collectError(__METHOD__, $e);
 
                 return;
             }
@@ -564,7 +623,7 @@ protected function updateManifestCaches()
         try {
             $extensions = $db->loadObjectList();
         } catch (Exception $e) {
-            echo Text::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED', $e->getCode(), $e->getMessage()) . '<br>';
+            $this->collectError(__METHOD__, $e);
 
             return;
         }
@@ -574,7 +633,10 @@ protected function updateManifestCaches()
 
         foreach ($extensions as $extension) {
             if (!$installer->refreshManifestCache($extension->extension_id)) {
-                echo Text::sprintf('FILES_JOOMLA_ERROR_MANIFEST', $extension->type, $extension->element, $extension->name, $extension->client_id) . '<br>';
+                $this->collectError(
+                    __METHOD__,
+                    new \Exception(Text::sprintf('FILES_JOOMLA_ERROR_MANIFEST', $extension->type, $extension->element, $extension->name, $extension->client_id))
+                );
             }
         }
     }
@@ -8100,6 +8162,8 @@ public function updateAssets($installer)
             $asset->setLocation(1, 'last-child');
 
             if (!$asset->store()) {
+                $this->collectError(__METHOD__, new \Exception($asset->getError(true)));
+
                 // Install failed, roll back changes
                 $installer->abort(Text::sprintf('JLIB_INSTALLER_ABORT_COMP_INSTALL_ROLLBACK', $asset->getError(true)));
 
@@ -8133,8 +8197,7 @@ public function convertTablesToUtf8mb4($doDbFixMsg = false)
         try {
             $rows = $db->loadRowList(0);
         } catch (Exception $e) {
-            // Render the error message from the Exception object
-            Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+            $this->collectError(__METHOD__, $e);
 
             if ($doDbFixMsg) {
                 // Show an error message telling to check database problems
@@ -8161,8 +8224,7 @@ public function convertTablesToUtf8mb4($doDbFixMsg = false)
         try {
             $convertedDB = $db->loadResult();
         } catch (Exception $e) {
-            // Render the error message from the Exception object
-            Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+            $this->collectError(__METHOD__, $e);
 
             if ($doDbFixMsg) {
                 // Show an error message telling to check database problems
@@ -8194,8 +8256,7 @@ public function convertTablesToUtf8mb4($doDbFixMsg = false)
                         } catch (Exception $e) {
                             $converted = $convertedDB;
 
-                            // Still render the error message from the Exception object
-                            Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+                            $this->collectError(__METHOD__, $e);
                         }
                     }
                 }
@@ -8229,8 +8290,7 @@ public function convertTablesToUtf8mb4($doDbFixMsg = false)
                             } catch (Exception $e) {
                                 $converted = 99;
 
-                                // Still render the error message from the Exception object
-                                Factory::getApplication()->enqueueMessage($e->getMessage(), 'error');
+                                $this->collectError(__METHOD__, $e);
                             }
                         }
                     }
@@ -8369,6 +8429,9 @@ public function postflight($action, $installer)
             }
         }
 
+        // Refresh versionable assets cache.
+        Factory::getApplication()->flushAssets();
+
         return true;
     }
 
@@ -8838,25 +8901,20 @@ protected function fixTemplateMode(): void
     {
         $db = Factory::getContainer()->get(DatabaseInterface::class);
 
-        array_map(
-            function ($template) use ($db) {
-                $clientId = $template === 'atum' ? 1 : 0;
-                $query = $db->getQuery(true)
-                    ->update($db->quoteName('#__template_styles'))
-                    ->set($db->quoteName('inheritable') . ' = 1')
-                    ->where($db->quoteName('template') . ' = ' . $db->quote($template))
-                    ->where($db->quoteName('client_id') . ' = ' . $clientId);
-
-                try {
-                    $db->setQuery($query)->execute();
-                } catch (Exception $e) {
-                    echo Text::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED', $e->getCode(), $e->getMessage()) . '<br>';
-
-                    return;
-                }
-            },
-            ['atum', 'cassiopeia']
-        );
+        foreach (['atum', 'cassiopeia'] as $template) {
+            $clientId = $template === 'atum' ? 1 : 0;
+            $query    = $db->getQuery(true)
+                ->update($db->quoteName('#__template_styles'))
+                ->set($db->quoteName('inheritable') . ' = 1')
+                ->where($db->quoteName('template') . ' = ' . $db->quote($template))
+                ->where($db->quoteName('client_id') . ' = ' . $clientId);
+
+            try {
+                $db->setQuery($query)->execute();
+            } catch (Exception $e) {
+                $this->collectError(__METHOD__, $e);
+            }
+        }
     }
 
     /**
@@ -8885,7 +8943,7 @@ protected function addUserAuthProviderColumn(): void
         try {
             $db->setQuery($query)->execute();
         } catch (Exception $e) {
-            echo Text::sprintf('JLIB_DATABASE_ERROR_FUNCTION_FAILED', $e->getCode(), $e->getMessage()) . '<br>';
+            $this->collectError(__METHOD__, $e);
 
             return;
         }
diff --git a/administrator/components/com_joomlaupdate/src/Controller/DisplayController.php b/administrator/components/com_joomlaupdate/src/Controller/DisplayController.php
index c70e53384fb80..453782289b834 100644
--- a/administrator/components/com_joomlaupdate/src/Controller/DisplayController.php
+++ b/administrator/components/com_joomlaupdate/src/Controller/DisplayController.php
@@ -65,6 +65,15 @@ public function display($cachable = false, $urlparams = false)
                 $view->setModel($warningsModel, false);
             }
 
+            // Check for update result
+            if ($lName === 'complete') {
+                $state = $model->getState();
+                $state->set('update_finished_with_error', $this->app->getUserState('com_joomlaupdate.update_finished_with_error'));
+                $state->set('update_errors', (array) $this->app->getUserState('com_joomlaupdate.update_errors', []));
+                $state->set('installer_message', $this->app->getUserState('com_joomlaupdate.installer_message'));
+                $state->set('log_file', $this->app->get('log_path') . '/joomla_update.php');
+            }
+
             // Perform update source preference check and refresh update information.
             $model->applyUpdateSite();
             $model->refreshUpdates();
diff --git a/administrator/components/com_joomlaupdate/src/Controller/UpdateController.php b/administrator/components/com_joomlaupdate/src/Controller/UpdateController.php
index 796ee9d86fb84..4ab584cee8d3a 100644
--- a/administrator/components/com_joomlaupdate/src/Controller/UpdateController.php
+++ b/administrator/components/com_joomlaupdate/src/Controller/UpdateController.php
@@ -11,6 +11,8 @@
 namespace Joomla\Component\Joomlaupdate\Administrator\Controller;
 
 use Joomla\CMS\Factory;
+use Joomla\CMS\Filesystem\File;
+use Joomla\CMS\Installer\Installer;
 use Joomla\CMS\Language\Text;
 use Joomla\CMS\Log\Log;
 use Joomla\CMS\MVC\Controller\BaseController;
@@ -40,19 +42,21 @@ public function download()
     {
         $this->checkToken();
 
-        $options['format']    = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
-        $options['text_file'] = 'joomla_update.php';
-        Log::addLogger($options, Log::INFO, ['Update', 'databasequery', 'jerror']);
-        $user = $this->app->getIdentity();
+        /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */
+        $model = $this->getModel('Update');
+        $user  = $this->app->getIdentity();
 
+        // Make sure logging is working before continue
         try {
-            Log::add(Text::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_START', $user->id, $user->name, \JVERSION), Log::INFO, 'Update');
-        } catch (\RuntimeException $exception) {
-            // Informational log only
+            Log::add('Test logging', Log::INFO, 'Update');
+        } catch (\Throwable $e) {
+            $message = Text::sprintf('COM_JOOMLAUPDATE_UPDATE_LOGGING_TEST_FAIL', $e->getMessage());
+            $this->setRedirect('index.php?option=com_joomlaupdate', $message, 'error');
+            return;
         }
 
-        /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */
-        $model  = $this->getModel('Update');
+        Log::add(Text::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_START', $user->id, $user->name, \JVERSION), Log::INFO, 'Update');
+
         $result = $model->download();
         $file   = $result['basename'];
 
@@ -81,11 +85,7 @@ public function download()
             $this->app->setUserState('com_joomlaupdate.file', $file);
             $url = 'index.php?option=com_joomlaupdate&task=update.install&' . $this->app->getSession()->getFormToken() . '=1';
 
-            try {
-                Log::add(Text::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_FILE', $file), Log::INFO, 'Update');
-            } catch (\RuntimeException $exception) {
-                // Informational log only
-            }
+            Log::add(Text::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_FILE', $file), Log::INFO, 'Update');
         } else {
             $this->app->setUserState('com_joomlaupdate.file', null);
             $url         = 'index.php?option=com_joomlaupdate';
@@ -108,19 +108,11 @@ public function install()
         $this->checkToken('get');
         $this->app->setUserState('com_joomlaupdate.oldversion', JVERSION);
 
-        $options['format']    = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
-        $options['text_file'] = 'joomla_update.php';
-        Log::addLogger($options, Log::INFO, ['Update', 'databasequery', 'jerror']);
-
-        try {
-            Log::add(Text::_('COM_JOOMLAUPDATE_UPDATE_LOG_INSTALL'), Log::INFO, 'Update');
-        } catch (\RuntimeException $exception) {
-            // Informational log only
-        }
-
         /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */
         $model = $this->getModel('Update');
 
+        Log::add(Text::_('COM_JOOMLAUPDATE_UPDATE_LOG_INSTALL'), Log::INFO, 'Update');
+
         $file = $this->app->getUserState('com_joomlaupdate.file', null);
         $model->createRestorationFile($file);
 
@@ -146,20 +138,33 @@ public function finalise()
             return;
         }
 
-        $options['format']    = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
-        $options['text_file'] = 'joomla_update.php';
-        Log::addLogger($options, Log::INFO, ['Update', 'databasequery', 'jerror']);
+        /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */
+        $model = $this->getModel('Update');
 
         try {
-            Log::add(Text::_('COM_JOOMLAUPDATE_UPDATE_LOG_FINALISE'), Log::INFO, 'Update');
-        } catch (\RuntimeException $exception) {
-            // Informational log only
+            $model->finaliseUpgrade();
+        } catch (\Throwable $e) {
+            $model->collectError('finaliseUpgrade', $e);
         }
 
-        /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */
-        $model = $this->getModel('Update');
+        // Check for update errors
+        if ($model->getErrors()) {
+            // The errors already should be logged at this point
+            // Collect a messages to show them later in the complete page
+            $errors = [];
+            foreach ($model->getErrors() as $error) {
+                $errors[] = $error->getMessage();
+            }
+
+            $this->app->setUserState('com_joomlaupdate.update_finished_with_error', true);
+            $this->app->setUserState('com_joomlaupdate.update_errors', $errors);
+        }
 
-        $model->finaliseUpgrade();
+        // Check for captured output messages in the installer
+        $msg = Installer::getInstance()->get('extension_message');
+        if ($msg) {
+            $this->app->setUserState('com_joomlaupdate.installer_message', $msg);
+        }
 
         $url = 'index.php?option=com_joomlaupdate&task=update.cleanup&' . Session::getFormToken() . '=1';
         $this->setRedirect($url);
@@ -184,29 +189,36 @@ public function cleanup()
             return;
         }
 
-        $options['format']    = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
-        $options['text_file'] = 'joomla_update.php';
-        Log::addLogger($options, Log::INFO, ['Update', 'databasequery', 'jerror']);
+        /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */
+        $model = $this->getModel('Update');
 
         try {
-            Log::add(Text::_('COM_JOOMLAUPDATE_UPDATE_LOG_CLEANUP'), Log::INFO, 'Update');
-        } catch (\RuntimeException $exception) {
-            // Informational log only
+            $model->cleanUp();
+        } catch (\Throwable $e) {
+            $model->collectError('cleanUp', $e);
         }
 
-        /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */
-        $model = $this->getModel('Update');
+        // Check for update errors
+        if ($model->getErrors()) {
+            // The errors already should be logged at this point
+            // Collect a messages to show them later in the complete page
+            $errors = $this->app->getUserState('com_joomlaupdate.update_errors', []);
+            foreach ($model->getErrors() as $error) {
+                $errors[] = $error->getMessage();
+            }
 
-        $model->cleanUp();
+            $this->app->setUserState('com_joomlaupdate.update_finished_with_error', true);
+            $this->app->setUserState('com_joomlaupdate.update_errors', $errors);
+        }
 
         $url = 'index.php?option=com_joomlaupdate&view=joomlaupdate&layout=complete';
-        $this->setRedirect($url);
 
-        try {
-            Log::add(Text::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_COMPLETE', \JVERSION), Log::INFO, 'Update');
-        } catch (\RuntimeException $exception) {
-            // Informational log only
+        // In case for errored update, redirect to component view
+        if ($this->app->getUserState('com_joomlaupdate.update_finished_with_error')) {
+            $url .= '&tmpl=component';
         }
+
+        $this->setRedirect($url);
     }
 
     /**
@@ -248,6 +260,17 @@ public function upload()
         /** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $model */
         $model = $this->getModel('Update');
 
+        // Make sure logging is working before continue
+        try {
+            Log::add('Test logging', Log::INFO, 'Update');
+        } catch (\Throwable $e) {
+            $message = Text::sprintf('COM_JOOMLAUPDATE_UPDATE_LOGGING_TEST_FAIL', $e->getMessage());
+            $this->setRedirect('index.php?option=com_joomlaupdate', $message, 'error');
+            return;
+        }
+
+        Log::add(Text::_('COM_JOOMLAUPDATE_UPDATE_LOG_UPLOAD'), Log::INFO, 'Update');
+
         try {
             $model->upload();
         } catch (\RuntimeException $e) {
diff --git a/administrator/components/com_joomlaupdate/src/Model/UpdateModel.php b/administrator/components/com_joomlaupdate/src/Model/UpdateModel.php
index b5ddcf7687b4f..bcbbd4e077d6d 100644
--- a/administrator/components/com_joomlaupdate/src/Model/UpdateModel.php
+++ b/administrator/components/com_joomlaupdate/src/Model/UpdateModel.php
@@ -21,6 +21,7 @@
 use Joomla\CMS\Installer\Installer;
 use Joomla\CMS\Language\Text;
 use Joomla\CMS\Log\Log;
+use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
 use Joomla\CMS\MVC\Model\BaseDatabaseModel;
 use Joomla\CMS\Plugin\PluginHelper;
 use Joomla\CMS\Updater\Update;
@@ -51,6 +52,28 @@ class UpdateModel extends BaseDatabaseModel
      */
     private $updateInformation = null;
 
+    /**
+     * Constructor
+     *
+     * @param   array                 $config   An array of configuration options.
+     * @param   ?MVCFactoryInterface  $factory  The factory.
+     *
+     * @since   __DEPLOY_VERSION__
+     * @throws  \Exception
+     */
+    public function __construct($config = [], MVCFactoryInterface $factory = null)
+    {
+        parent::__construct($config, $factory);
+
+        // Register a logger for update process
+        $options = [
+            'format'    => '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}',
+            'text_file' => 'joomla_update.php',
+        ];
+
+        Log::addLogger($options, Log::ALL, ['Update', 'databasequery', 'jerror']);
+    }
+
     /**
      * Detects if the Joomla! update site currently in use matches the one
      * configured in this component. If they don't match, it changes it.
@@ -630,6 +653,8 @@ public function createUpdateFile($basename = null): bool
      */
     public function finaliseUpgrade()
     {
+        Log::add(Text::_('COM_JOOMLAUPDATE_UPDATE_LOG_FINALISE'), Log::INFO, 'Update');
+
         $installer = Installer::getInstance();
 
         $manifest = $installer->isManifest(JPATH_MANIFESTS . '/files/joomla.xml');
@@ -658,27 +683,34 @@ public function finaliseUpgrade()
         // Run the script file.
         \JLoader::register('JoomlaInstallerScript', JPATH_ADMINISTRATOR . '/components/com_admin/script.php');
 
+        $msg           = '';
         $manifestClass = new \JoomlaInstallerScript();
+        $manifestClass->setErrorCollector(function (string $context, \Throwable $error) {
+            $this->collectError($context, $error);
+        });
 
-        ob_start();
-        ob_implicit_flush(false);
+        // Run Installer preflight
+        try {
+            ob_start();
 
-        if ($manifestClass && method_exists($manifestClass, 'preflight')) {
             if ($manifestClass->preflight('update', $installer) === false) {
+                $this->collectError('JoomlaInstallerScript::preflight', new \Exception('Script::preflight finished with "false" result.'));
                 $installer->abort(
                     Text::sprintf(
                         'JLIB_INSTALLER_ABORT_INSTALL_CUSTOM_INSTALL_FAILURE',
                         Text::_('JLIB_INSTALLER_INSTALL')
                     )
                 );
-
                 return false;
             }
-        }
 
-        // Create msg object; first use here.
-        $msg = ob_get_contents();
-        ob_end_clean();
+            // Append messages.
+            $msg .= ob_get_contents();
+            ob_end_clean();
+        } catch (\Throwable $e) {
+            $this->collectError('JoomlaInstallerScript::preflight', $e);
+            return false;
+        }
 
         // Get a database connector object.
         $db = version_compare(JVERSION, '4.2.0', 'lt') ? $this->getDbo() : $this->getDatabase();
@@ -699,6 +731,7 @@ public function finaliseUpgrade()
         try {
             $db->execute();
         } catch (\RuntimeException $e) {
+            $this->collectError('Extension check', $e);
             // Install failed, roll back changes.
             $installer->abort(
                 Text::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK', Text::_('JLIB_INSTALLER_UPDATE'), $e->getMessage())
@@ -721,6 +754,7 @@ public function finaliseUpgrade()
             $row->manifest_cache = $installer->generateManifestCache();
 
             if (!$row->store()) {
+                $this->collectError('Update the manifest_cache', new \Exception('Update the manifest_cache finished with "false" result.'));
                 // Install failed, roll back changes.
                 $installer->abort(
                     Text::sprintf('JLIB_INSTALLER_ABORT_FILE_ROLLBACK', Text::_('JLIB_INSTALLER_UPDATE'), $row->getError())
@@ -744,6 +778,7 @@ public function finaliseUpgrade()
             $row->set('manifest_cache', $installer->generateManifestCache());
 
             if (!$row->store()) {
+                $this->collectError('Write the manifest_cache', new \Exception('Writing the manifest_cache finished with "false" result.'));
                 // Install failed, roll back changes.
                 $installer->abort(Text::sprintf('JLIB_INSTALLER_ABORT_FILE_INSTALL_ROLLBACK', $row->getError()));
 
@@ -761,6 +796,7 @@ public function finaliseUpgrade()
         $result = $installer->parseSchemaUpdates($manifest->update->schemas, $row->extension_id);
 
         if ($result === false) {
+            $this->collectError('installer::parseSchemaUpdates', new \Exception('installer::parseSchemaUpdates finished with "false" result.'));
             // Install failed, rollback changes (message already logged by the installer).
             $installer->abort();
 
@@ -770,12 +806,12 @@ public function finaliseUpgrade()
         // Reinitialise the installer's extensions table's properties.
         $installer->extension->getFields(true);
 
-        // Start Joomla! 1.6.
-        ob_start();
-        ob_implicit_flush(false);
+        try {
+            ob_start();
 
-        if ($manifestClass && method_exists($manifestClass, 'update')) {
             if ($manifestClass->update($installer) === false) {
+                $this->collectError('JoomlaInstallerScript::update', new \Exception('Script::update finished with "false" result.'));
+
                 // Install failed, rollback changes.
                 $installer->abort(
                     Text::sprintf(
@@ -786,11 +822,14 @@ public function finaliseUpgrade()
 
                 return false;
             }
-        }
 
-        // Append messages.
-        $msg .= ob_get_contents();
-        ob_end_clean();
+            // Append messages.
+            $msg .= ob_get_contents();
+            ob_end_clean();
+        } catch (\Throwable $e) {
+            $this->collectError('JoomlaInstallerScript::update', $e);
+            return false;
+        }
 
         // Clobber any possible pending updates.
         $update = new \Joomla\CMS\Table\Update($db);
@@ -803,24 +842,22 @@ public function finaliseUpgrade()
         }
 
         // And now we run the postflight.
-        ob_start();
-        ob_implicit_flush(false);
-
-        if ($manifestClass && method_exists($manifestClass, 'postflight')) {
+        try {
+            ob_start();
             $manifestClass->postflight('update', $installer);
-        }
 
-        // Append messages.
-        $msg .= ob_get_contents();
-        ob_end_clean();
+            // Append messages.
+            $msg .= ob_get_contents();
+            ob_end_clean();
+        } catch (\Throwable $e) {
+            $this->collectError('JoomlaInstallerScript::postflight', $e);
+            return false;
+        }
 
-        if ($msg != '') {
+        if ($msg) {
             $installer->set('extension_message', $msg);
         }
 
-        // Refresh versionable assets cache.
-        Factory::getApplication()->flushAssets();
-
         return true;
     }
 
@@ -838,6 +875,12 @@ public function finaliseUpgrade()
      */
     public function cleanUp()
     {
+        try {
+            Log::add(Text::_('COM_JOOMLAUPDATE_UPDATE_LOG_CLEANUP'), Log::INFO, 'Update');
+        } catch (\RuntimeException $exception) {
+            // Informational log only
+        }
+
         // Load overrides plugin.
         PluginHelper::importPlugin('installer');
 
@@ -880,6 +923,12 @@ public function cleanUp()
         // Trigger event after joomla update.
         $app->triggerEvent('onJoomlaAfterUpdate', [$oldVersion]);
         $app->setUserState('com_joomlaupdate.oldversion', null);
+
+        try {
+            Log::add(Text::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_COMPLETE', \JVERSION), Log::INFO, 'Update');
+        } catch (\RuntimeException $exception) {
+            // Informational log only
+        }
     }
 
     /**
@@ -1721,4 +1770,37 @@ function ($value) {
 
         return $home || $menu;
     }
+
+    /**
+     * Collect errors that happened during update.
+     *
+     * @param  string      $context  A context/place where error happened
+     * @param  \Throwable  $error    The error that occurred
+     *
+     * @return  void
+     *
+     * @since  __DEPLOY_VERSION__
+     */
+    public function collectError(string $context, \Throwable $error)
+    {
+        // Store error for further processing by controller
+        $this->setError($error);
+
+        // Log it
+        Log::add(
+            sprintf(
+                'An error has occurred while running "%s". Code: %s. Message: %s.',
+                $context,
+                $error->getCode(),
+                $error->getMessage()
+            ),
+            Log::ERROR,
+            'Update'
+        );
+
+        if (JDEBUG) {
+            $trace = $error->getFile() . ':' . $error->getLine() . PHP_EOL . $error->getTraceAsString();
+            Log::add(sprintf('An error trace: %s.', $trace), Log::DEBUG, 'Update');
+        }
+    }
 }
diff --git a/administrator/components/com_joomlaupdate/tmpl/joomlaupdate/complete.php b/administrator/components/com_joomlaupdate/tmpl/joomlaupdate/complete.php
index e168c2697e00c..cf2d9268d73d7 100644
--- a/administrator/components/com_joomlaupdate/tmpl/joomlaupdate/complete.php
+++ b/administrator/components/com_joomlaupdate/tmpl/joomlaupdate/complete.php
@@ -13,15 +13,45 @@
 use Joomla\CMS\HTML\HTMLHelper;
 use Joomla\CMS\Language\Text;
 use Joomla\CMS\Router\Route;
+use Joomla\CMS\Uri\Uri;
+
+$hadErrors    = $this->state->get('update_finished_with_error');
+$errors       = $this->state->get('update_errors');
+$logFile      = $this->state->get('log_file');
+$installerMsg = $this->state->get('installer_message');
+$forumLink    = '<a href="https://forum.joomla.org/" target="_blank" rel="noopener noreferrer">https://forum.joomla.org/</a>';
 
 ?>
 <div class="card">
     <h2 class="card-header"><?php echo Text::_('COM_JOOMLAUPDATE_VIEW_COMPLETE_HEADING'); ?></h2>
     <div class="card-body">
-        <div class="alert alert-success">
-            <span class="icon-check-circle" aria-hidden="true"></span><span class="visually-hidden"><?php echo Text::_('NOTICE'); ?></span>
-            <?php echo Text::sprintf('COM_JOOMLAUPDATE_VIEW_COMPLETE_MESSAGE', '&#x200E;' . JVERSION); ?>
+        <?php if (!$hadErrors) : ?>
+            <div class="alert alert-success">
+                <span class="icon-check-circle" aria-hidden="true"></span><span class="visually-hidden"><?php echo Text::_('NOTICE'); ?></span>
+                <?php echo Text::sprintf('COM_JOOMLAUPDATE_VIEW_COMPLETE_MESSAGE', '&#x200E;' . JVERSION); ?>
+            </div>
+        <?php else : ?>
+            <div class="alert alert-error">
+                <span class="icon-check-circle" aria-hidden="true"></span><span class="visually-hidden"><?php echo Text::_('NOTICE'); ?></span>
+                <?php echo Text::sprintf('COM_JOOMLAUPDATE_VIEW_COMPLETE_WITH_ERROR_MESSAGE', $logFile, $forumLink); ?>
+            </div>
+            <p>
+                <a href="<?php echo Uri::base(true); ?>/" class="btn btn-primary"><?php echo Text::_('JGLOBAL_TPL_CPANEL_LINK_TEXT') ?></a>
+            </p>
+            <?php if ($errors) : ?>
+                <h3><?php echo Text::_('COM_JOOMLAUPDATE_VIEW_COMPLETE_UPDATE_ERRORS'); ?></h3>
+                <?php foreach ($errors as $error) : ?>
+                    <div class="alert alert-error"><?php echo $error; ?></div>
+                <?php endforeach; ?>
+            <?php endif; ?>
+        <?php endif; ?>
+
+        <?php if ($installerMsg) : ?>
+        <div>
+            <h3><?php echo Text::_('COM_JOOMLAUPDATE_VIEW_COMPLETE_INSTALLER_MESSAGE'); ?></h3>
+            <div class="alert alert-warning"><?php echo $installerMsg ?></div>
         </div>
+        <?php endif; ?>
     </div>
 </div>
 
diff --git a/administrator/language/en-GB/com_joomlaupdate.ini b/administrator/language/en-GB/com_joomlaupdate.ini
index 5fb332d3b8313..815c2f42b26d1 100644
--- a/administrator/language/en-GB/com_joomlaupdate.ini
+++ b/administrator/language/en-GB/com_joomlaupdate.ini
@@ -75,13 +75,18 @@ COM_JOOMLAUPDATE_UPDATE_LOG_FILE="File %s downloaded."
 COM_JOOMLAUPDATE_UPDATE_LOG_FINALISE="Finalising installation."
 COM_JOOMLAUPDATE_UPDATE_LOG_INSTALL="Starting installation of new version."
 COM_JOOMLAUPDATE_UPDATE_LOG_START="Update started by user %2$s (%1$s). Old version is %3$s."
+COM_JOOMLAUPDATE_UPDATE_LOG_UPLOAD="Uploading update file"
 COM_JOOMLAUPDATE_UPDATE_LOG_URL="Downloading update file from %s."
+COM_JOOMLAUPDATE_UPDATE_LOGGING_TEST_FAIL="Logging does not work. Make sure the log folder is writable, and try again. The logging test failed with message: %s"
 COM_JOOMLAUPDATE_UPDATING_HEAD="Update in progress"
 COM_JOOMLAUPDATE_UPDATING_INPROGRESS="Please wait; Joomla is being updated. This may take a while."
 COM_JOOMLAUPDATE_UPDATING_FAIL="The update has failed."
 COM_JOOMLAUPDATE_UPDATING_COMPLETE="The update is finalising."
 COM_JOOMLAUPDATE_VIEW_COMPLETE_HEADING="Joomla Version Update Status"
+COM_JOOMLAUPDATE_VIEW_COMPLETE_INSTALLER_MESSAGE="Messages captured by installer"
 COM_JOOMLAUPDATE_VIEW_COMPLETE_MESSAGE="Your site has been updated. Your Joomla version is now %s."
+COM_JOOMLAUPDATE_VIEW_COMPLETE_UPDATE_ERRORS="Brief list of captured errors"
+COM_JOOMLAUPDATE_VIEW_COMPLETE_WITH_ERROR_MESSAGE="The update completed with errors. Please examine the update logs %s for more details.<br>It is recommended to restore the site from backup, fix the issues that caused the update failure and try to update again.<br>You always can ask for help on Joomla! forum %s"
 COM_JOOMLAUPDATE_VIEW_DEFAULT_ACTUAL="Actual"
 COM_JOOMLAUPDATE_VIEW_DEFAULT_COMPATIBILITY_CHECK="Joomla! %s Compatibility Check"
 COM_JOOMLAUPDATE_VIEW_DEFAULT_COMPATIBLE_UPDATE_WARNING="Extensions marked with <span class='label label-warning'>X.X.X</span> have an extension update available for the current version of Joomla which is not marked as compatible with the updated version of Joomla. You should contact the extension developer for more information."
diff --git a/installation/src/Console/InstallCommand.php b/installation/src/Console/InstallCommand.php
index 7f5d3553e9eb6..1662512de4a3d 100644
--- a/installation/src/Console/InstallCommand.php
+++ b/installation/src/Console/InstallCommand.php
@@ -152,7 +152,7 @@ protected function doExecute(InputInterface $input, OutputInterface $output): in
         foreach ($files as $step => $schema) {
             $serverType = $db->getServerType();
 
-            if (\in_array($step, ['custom1', 'custom2']) && !is_file('sql/' . $serverType . '/' . $schema . '.sql')) {
+            if (\in_array($step, ['custom1', 'custom2']) && !is_file(JPATH_INSTALLATION . '/sql/' . $serverType . '/' . $schema . '.sql')) {
                 continue;
             }
 
diff --git a/installation/src/Controller/InstallationController.php b/installation/src/Controller/InstallationController.php
index 260dcb29853bd..beefe02532776 100644
--- a/installation/src/Controller/InstallationController.php
+++ b/installation/src/Controller/InstallationController.php
@@ -144,7 +144,7 @@ public function populate()
         $schema     = $files[$step];
         $serverType = $db->getServerType();
 
-        if (in_array($step, ['custom1', 'custom2']) && !is_file('sql/' . $serverType . '/' . $schema . '.sql')) {
+        if (in_array($step, ['custom1', 'custom2']) && !is_file(JPATH_INSTALLATION . '/sql/' . $serverType . '/' . $schema . '.sql')) {
             $this->sendJsonResponse($r);
 
             return;
diff --git a/libraries/src/Console/UpdateCoreCommand.php b/libraries/src/Console/UpdateCoreCommand.php
index 83af0e3c415a6..5468abd4635b6 100644
--- a/libraries/src/Console/UpdateCoreCommand.php
+++ b/libraries/src/Console/UpdateCoreCommand.php
@@ -11,9 +11,12 @@
 
 use Joomla\Application\Cli\CliInput;
 use Joomla\CMS\Extension\ExtensionHelper;
+use Joomla\CMS\Factory;
 use Joomla\CMS\Filesystem\File;
 use Joomla\CMS\Filesystem\Folder;
 use Joomla\CMS\Installer\InstallerHelper;
+use Joomla\CMS\Language\Text;
+use Joomla\CMS\Log\Log;
 use Joomla\Console\Command\AbstractCommand;
 use Joomla\Database\DatabaseInterface;
 use Symfony\Component\Console\Helper\ProgressBar;
@@ -131,6 +134,11 @@ private function configureIO(InputInterface $input, OutputInterface $output)
 
         $this->cliInput = $input;
         $this->ioStyle  = new SymfonyStyle($input, $output);
+
+        $language = Factory::getLanguage();
+        $language->load('lib_joomla', JPATH_ADMINISTRATOR);
+        $language->load('', JPATH_ADMINISTRATOR);
+        $language->load('com_joomlaupdate', JPATH_ADMINISTRATOR);
     }
 
     /**
@@ -154,6 +162,17 @@ public function doExecute(InputInterface $input, OutputInterface $output): int
 
         $model = $this->getUpdateModel();
 
+        // Make sure logging is working before continue
+        try {
+            Log::add('Test logging', Log::INFO, 'Update');
+        } catch (\Throwable $e) {
+            $message = Text::sprintf('COM_JOOMLAUPDATE_UPDATE_LOGGING_TEST_FAIL', $e->getMessage());
+            $this->ioStyle->error($message);
+            return self::ERR_UPDATE_FAILED;
+        }
+
+        Log::add(Text::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_START', 0, 'CLI', \JVERSION), Log::INFO, 'Update');
+
         $this->setUpdateInfo($model->getUpdateInformation());
 
         $this->progressBar->advance();
@@ -184,6 +203,12 @@ public function doExecute(InputInterface $input, OutputInterface $output): int
 
         if ($this->updateJoomlaCore($model)) {
             $this->progressBar->finish();
+
+            if ($model->getErrors()) {
+                $this->ioStyle->error('Update finished with errors. Please check logs for details.');
+                return self::ERR_UPDATE_FAILED;
+            }
+
             $this->ioStyle->success('Joomla core updated successfully!');
 
             return self::UPDATE_SUCCESSFUL;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging a pull request may close this issue.

4 participants