Current File : /home/pacjaorg/public_html/kmm/libraries/src/Console/UpdateCoreCommand.php |
<?php
/**
* Joomla! Content Management System
*
* @copyright (C) 2020 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\CMS\Console;
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;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
// phpcs:disable PSR1.Files.SideEffects
\defined('JPATH_PLATFORM') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Console command for updating Joomla! core
*
* @since 4.0.0
*/
class UpdateCoreCommand extends AbstractCommand
{
/**
* The default command name
*
* @var string
* @since 4.0.0
*/
protected static $defaultName = 'core:update';
/**
* Stores the Input Object
* @var CliInput
* @since 4.0.0
*/
private $cliInput;
/**
* SymfonyStyle Object
* @var SymfonyStyle
* @since 4.0.0
*/
private $ioStyle;
/**
* Update Information
* @var array
* @since 4.0.0
*/
public $updateInfo;
/**
* Update Model
* @var array
* @since 4.0.0
*/
public $updateModel;
/**
* Progress Bar object
* @var ProgressBar
* @since 4.0.0
*/
public $progressBar;
/**
* Return code for successful update
* @since 4.0.0
*/
public const UPDATE_SUCCESSFUL = 0;
/**
* Return code for failed update
* @since 4.0.0
*/
public const ERR_UPDATE_FAILED = 2;
/**
* Return code for failed checks
* @since 4.0.0
*/
public const ERR_CHECKS_FAILED = 1;
/**
* @var DatabaseInterface
* @since 4.0.0
*/
private $db;
/**
* UpdateCoreCommand constructor.
*
* @param DatabaseInterface $db Database Instance
*
* @since 4.0.0
*/
public function __construct(DatabaseInterface $db)
{
$this->db = $db;
parent::__construct();
}
/**
* Configures the IO
*
* @param InputInterface $input Console Input
* @param OutputInterface $output Console Output
*
* @return void
*
* @since 4.0.0
*
*/
private function configureIO(InputInterface $input, OutputInterface $output)
{
ProgressBar::setFormatDefinition('custom', ' %current%/%max% -- %message%');
$this->progressBar = new ProgressBar($output, 8);
$this->progressBar->setFormat('custom');
$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);
}
/**
* Internal function to execute the command.
*
* @param InputInterface $input The input to inject into the command.
* @param OutputInterface $output The output to inject into the command.
*
* @return integer The command exit code
*
* @since 4.0.0
* @throws \Exception
*/
public function doExecute(InputInterface $input, OutputInterface $output): int
{
$this->configureIO($input, $output);
$this->ioStyle->title('Updating Joomla');
$this->progressBar->setMessage("Starting up ...");
$this->progressBar->start();
$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();
$this->progressBar->setMessage('Running checks ...');
if (!$this->updateInfo['hasUpdate']) {
$this->progressBar->finish();
$this->ioStyle->note('You already have the latest Joomla! version. ' . $this->updateInfo['latest']);
return self::ERR_CHECKS_FAILED;
}
$this->progressBar->advance();
$this->progressBar->setMessage('Check Database Table Structure...');
$errors = $this->checkSchema();
if ($errors > 0) {
$this->ioStyle->error('Database Table Structure not Up to Date');
$this->progressBar->finish();
$this->ioStyle->info('There were ' . $errors . ' errors');
return self::ERR_CHECKS_FAILED;
}
$this->progressBar->advance();
$this->progressBar->setMessage('Starting Joomla! update ...');
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;
}
$this->progressBar->finish();
$this->ioStyle->error('Update cannot be performed.');
return self::ERR_UPDATE_FAILED;
}
/**
* Initialise the command.
*
* @return void
*
* @since 4.0.0
*/
protected function configure(): void
{
$help = "<info>%command.name%</info> is used to update Joomla
\nUsage: <info>php %command.full_name%</info>";
$this->setDescription('Update Joomla');
$this->setHelp($help);
}
/**
* Update Core Joomla
*
* @param mixed $updatemodel Update Model
*
* @return boolean success
*
* @since 4.0.0
*/
private function updateJoomlaCore($updatemodel): bool
{
$updateInformation = $this->updateInfo;
if (!empty($updateInformation['hasUpdate'])) {
$this->progressBar->advance();
$this->progressBar->setMessage("Processing update package ...");
$package = $this->processUpdatePackage($updateInformation);
$this->progressBar->advance();
$this->progressBar->setMessage("Finalizing update ...");
$result = $updatemodel->finaliseUpgrade();
if ($result) {
$this->progressBar->advance();
$this->progressBar->setMessage("Cleaning up ...");
// Remove the administrator/cache/autoload_psr4.php file
$autoloadFile = JPATH_CACHE . '/autoload_psr4.php';
if (file_exists($autoloadFile)) {
File::delete($autoloadFile);
}
// Remove the xml
if (file_exists(JPATH_BASE . '/joomla.xml')) {
File::delete(JPATH_BASE . '/joomla.xml');
}
InstallerHelper::cleanupInstall($package['file'], $package['extractdir']);
$updatemodel->purge();
return true;
}
}
return false;
}
/**
* Sets the update Information
*
* @param array $data Stores the update information
*
* @since 4.0.0
*
* @return void
*/
public function setUpdateInfo($data): void
{
$this->updateInfo = $data;
}
/**
* Retrieves the Update model from com_joomlaupdate
*
* @return mixed
*
* @since 4.0.0
*
* @throws \Exception
*/
public function getUpdateModel()
{
if (!isset($this->updateModel)) {
$this->setUpdateModel();
}
return $this->updateModel;
}
/**
* Sets the Update Model
*
* @return void
*
* @since 4.0.0
*/
public function setUpdateModel(): void
{
$app = $this->getApplication();
$updatemodel = $app->bootComponent('com_joomlaupdate')->getMVCFactory($app)->createModel('Update', 'Administrator');
if (is_bool($updatemodel)) {
$this->updateModel = $updatemodel;
return;
}
$updatemodel->purge();
$updatemodel->refreshUpdates(true);
$this->updateModel = $updatemodel;
}
/**
* Downloads and extracts the update Package
*
* @param array $updateInformation Stores the update information
*
* @return array | boolean
*
* @since 4.0.0
*/
public function processUpdatePackage($updateInformation)
{
if (!$updateInformation['object']) {
return false;
}
$this->progressBar->advance();
$this->progressBar->setMessage("Downloading update package ...");
$file = $this->downloadFile($updateInformation['object']->downloadurl->_data);
$tmpPath = $this->getApplication()->get('tmp_path');
$updatePackage = $tmpPath . '/' . $file;
$this->progressBar->advance();
$this->progressBar->setMessage("Extracting update package ...");
$package = $this->extractFile($updatePackage);
$this->progressBar->advance();
$this->progressBar->setMessage("Copying files ...");
$this->copyFileTo($package['extractdir'], JPATH_BASE);
return ['file' => $updatePackage, 'extractdir' => $package['extractdir']];
}
/**
* Downloads the Update file
*
* @param string $url URL to update file
*
* @return boolean | string
*
* @since 4.0.0
*/
public function downloadFile($url)
{
$file = InstallerHelper::downloadPackage($url);
if (!$file) {
return false;
}
return $file;
}
/**
* Extracts Update file
*
* @param string $file Full path to file location
*
* @return array | boolean
*
* @since 4.0.0
*/
public function extractFile($file)
{
$package = InstallerHelper::unpack($file, true);
return $package;
}
/**
* Copy a file to a destination directory
*
* @param string $file Full path to file
* @param string $dir Destination directory
*
* @return void
*
* @since 4.0.0
*/
public function copyFileTo($file, $dir): void
{
Folder::copy($file, $dir, '', true);
}
/**
* Check Database Table Structure
*
* @return integer the number of errors
*
* @since 4.4.0
*/
public function checkSchema(): int
{
$app = $this->getApplication();
$app->getLanguage()->load('com_installer', JPATH_ADMINISTRATOR);
$coreExtensionInfo = ExtensionHelper::getExtensionRecord('joomla', 'file');
$dbmodel = $app->bootComponent('com_installer')->getMVCFactory($app)->createModel('Database', 'Administrator');
// Ensure we only get information for core
$dbmodel->setState('filter.extension_id', $coreExtensionInfo->extension_id);
// We're filtering by a single extension which must always exist - so can safely access this through element 0 of the array
$changeInformation = $dbmodel->getItems()[0];
foreach ($changeInformation['errorsMessage'] as $msg) {
$this->ioStyle->info($msg);
}
return $changeInformation['errorsCount'];
}
}