| Current Path : /home/echanges/www/administrator/components/com_watchfulli/classes/ |
| Current File : /home/echanges/www/administrator/components/com_watchfulli/classes/coreupdater.php |
<?php
/**
* @version admin/classes/coreupdater.php 2024-03-27 zanardigit
* @package Watchful Client
* @author Watchful
* @authorUrl https://watchful.net
* @copyright Copyright (c) 2012-2025 Watchful
* @license GNU/GPL v3 or later
* @description adapted from com_joomlaupdate controller
*/
use Joomla\CMS\Installer\InstallerHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Version;
use Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel;
defined('_JEXEC') or die;
class WatchfulliCoreUpdater
{
/** @var stdClass $extParams */
private $extParams;
private $options;
/** @var JoomlaupdateModelDefault|UpdateModel */
private $joomlaUpdateModel;
private $helper;
/** @var Joomla\CMS\Application\CMSWebApplicationInterface|Joomla\CMS\Application\CMSApplication */
private $application;
/** @var string */
private $tmpPath;
/**
* Performs the download of the update package
* @throws Exception
*/
public function __construct(stdClass $extParams)
{
$this->options['format'] = '{DATE}\t{TIME}\t{LEVEL}\t{CODE}\t{MESSAGE}';
$this->options['text_file'] = 'watchfulli';
$this->helper = new WatchfulliHelper();
$this->application = WatchfulliFactory::getApplication();
$this->tmpPath = $this->application->get('tmp_path');
$this->extParams = $extParams;
Log::addLogger($this->options, Log::INFO, ['Update', 'databasequery', 'jerror']);
}
/**
* Downloads the update package to the site.
*
* @param string $packageURL
*/
public function download($packageURL)
{
watchfulli::debug("downloading $packageURL");
$basename = basename($packageURL);
$target = $this->tmpPath . '/' . $basename;
if (!WatchfulliFileHelper::exists($target)) {
$this->downloadPackage($packageURL, $target);
}
// Is it a 0-byte file? If so, re-download please.
$filesize = @filesize($target);
if (empty($filesize)) {
$file = $this->downloadPackage($packageURL, $target);
} else {
$file = $basename;
}
if (!$file) {
$this->helper->response(
[
'task' => 'download',
'status' => 'error',
'message' => Text::_('COM_JOOMLAUPDATE_VIEW_UPDATE_DOWNLOADFAILED'),
]
);
}
watchfulli::debug("downloaded $file");
try {
$this->validateHash($target);
} catch (Exception $e) {
$this->helper->response(
[
'task' => 'download',
'status' => 'error',
'message' => $e->getMessage(),
]
);
}
watchfulli::debug("validated $file");
$this->helper->response(
[
'task' => 'download',
'status' => 'success',
'message' => $file,
]
);
}
/**
* @throws Exception
*/
private function validateHash(string $packagePath)
{
if (!$this->application->input->get('validatehash', 0)) {
return;
}
$hashes = [ 'sha256', 'sha384', 'sha512' ];
foreach ($hashes as $hash) {
if (empty($this->extParams->$hash)) {
continue;
}
$hashValue = hash_file($hash, $packagePath);
if ($hashValue !== $this->extParams->$hash) {
throw new Exception('COM_JMONITORING_UPDATE_CORE_ERROR_CHECKSUM_FAILED');
}
}
}
/**
* Start the installation of the new Joomla! version
*
* @param string $file
*
* @throws Exception
* @throws Exception
*/
public function install($file)
{
Log::add(Text::_('COM_JOOMLAUPDATE_UPDATE_LOG_INSTALL'), Log::INFO, 'Update');
watchfulli::debug("installing $file");
$this->setupCoreUpdateModel();
/** GH #1133 */
$useNewExtractor = version_compare(WatchfulliJoomlaVersion::getVersion(), '4.0.4', '>=');
$settingsMethod = $useNewExtractor ? 'createUpdateFile' : 'createRestorationFile';
$settingsFile = $useNewExtractor ? 'update.php' : 'restoration.php';
$extractor = $useNewExtractor ? 'extract.php' : 'restore.php';
watchfulli::debug("using $settingsMethod, $settingsFile, $extractor");
$this->joomlaUpdateModel->$settingsMethod($file);
watchfulli::debug("Done $settingsMethod");
// cannot continue unless we copy update settings file to com_joomlaupdate
// but we don't want to bother encrypting the JSON
$src = JPATH_COMPONENT_ADMINISTRATOR . '/' . $settingsFile ;
$dst = JPATH_ADMINISTRATOR . '/components/com_joomlaupdate/' . $settingsFile;
$res = preg_replace("/'kickstart\.security\.password' => '.*?'/", "'kickstart.security.password' => null", file_get_contents($src));
watchfulli::debug("Copying $src to $dst");
WatchfulliFileHelper::write($dst, $res);
watchfulli::debug("Copied $src to $dst");
// extractor expects to be running in its own directory
$cwd = getcwd();
chdir(dirname($dst));
// build JSON data
$json = json_encode(['task' => 'ping']);
WatchfulliFactory::getInput()->set('json', $json);
// capture the output from extractor
ob_start();
require_once JPATH_ADMINISTRATOR . '/components/com_joomlaupdate/' . $extractor;
$output = ob_get_clean();
// go back to our own directory
chdir($cwd);
$this->helper->response(
[
'task' => 'install',
'status' => 'success',
'message' => 'install ok',
'output' => $this->parseRestoreResponse($output),
]
);
}
/**
* Extract the update ZIP
*
* For issue #1002, testing using JInstallerHelper::unpack() in one go, instead of the Akeeba
* stepped extraction, due to issue with executing mismatched code during stepping.
*
* @param string $file
*
* @return void
*/
public function step($file)
{
$target = $this->tmpPath . '/' . $file;
watchfulli::debug("step $target");
try {
$extractionPath = $this->unpackFile($target);
watchfulli::debug("step $extractionPath");
} catch (Exception $ex) {
$this->helper->response(
[
'task' => 'step',
'status' => 'error',
'message' => $ex->getMessage(),
]
);
}
WatchfulliFileHelper::delete($target);
$this->helper->response(
[
'task' => 'step',
'status' => !empty($extractionPath) ? 'success' : 'error',
'message' => !empty($extractionPath) ? basename($extractionPath) : 'error',
'output' => [
'status' => true,
'message' => null,
'files' => 0,
'bytesIn' => 0,
'bytesOut' => 0,
'done' => true,
],
]
);
}
/**
* Unpack a given file
*
* @param string $file the name of the file to unpack
*
* @throws Exception
* @return string
*/
private function unpackFile($file)
{
watchfulli::debug("unpackFile $file");
if (!$file) {
throw new Exception('COM_JMONITORING_CANT_UNPACK_UPDATE_EMPTY_FILE');
}
if (!file_exists($file)) {
throw new Exception('COM_JMONITORING_CANT_UNPACK_UPDATE_MISSING_FILE');
}
if (extension_loaded('zip')) {
return $this->quickUnpack($file);
}
watchfulli::debug("unpackFile using JInstallerHelper::unpack()");
$package = InstallerHelper::unpack($file, true);
watchfulli::debug("unpackFile done");
if (empty($package)) {
throw new Exception('COM_JMONITORING_CANT_UNPACK_UPDATE');
}
return $package['dir'];
}
/**
* @throws Exception
*/
private function quickUnpack($file): string
{
watchfulli::debug("quickUnpack $file");
$zip = new ZipArchive();
$extractionPath = $this->tmpPath . '/' . uniqid('install_');
if (
!$zip->open($file) || !$zip->extractTo($extractionPath)
) {
throw new Exception('COM_JMONITORING_CANT_UNPACK_UPDATE');
}
$zip->close();
watchfulli::debug("quickUnpack done");
return $extractionPath;
}
/**
* Finalise the upgrade by running the necessary scripts
*
* @param string $directory
*
* @return void
* @throws Exception
*/
public function finalise($directory)
{
watchfulli::debug("finalise $directory");
// https://github.com/joomla/joomla-cms/issues/38474
if (file_exists(JPATH_ADMINISTRATOR . '/cache/autoload_psr4.php')) {
@unlink(JPATH_ADMINISTRATOR . '/cache/autoload_psr4.php');
}
$tmpDir = $this->tmpPath . '/' . $directory;
$this->setupCoreUpdateModel();
WatchfulliFileHelper::copyFolder($tmpDir, JPATH_ROOT);
watchfulli::debug("finalise copyFolder $tmpDir to JPATH_ROOT");
if ($this->joomlaUpdateModel->finaliseUpgrade())
{
$this->helper->response(
[
'task' => 'finalise',
'status' => 'success',
'message' => 'install ok',
]
);
}
$this->helper->response(
[
'task' => 'finalise',
'status' => 'error',
'message' => Text::_('COM_JOOMLAUPDATE_VIEW_UPDATE_DOWNLOADFAILED'),
]
);
}
/**
* Removes the extracted package file.
*
* @param string $directory
*
* @return void
* @throws Exception
*/
public function cleanup($directory)
{
watchfulli::debug("cleanup $directory");
$directory = $this->tmpPath . '/' . $directory;
// Remove joomla.xml from the site's root.
$target = JPATH_ROOT . '/joomla.xml';
WatchfulliFileHelper::delete($target);
InstallerHelper::cleanupInstall('', $directory);
watchfulli::debug("cleanup delete $directory");
// Remove the restoration.php file.
$target = JPATH_ADMINISTRATOR . '/components/com_joomlaupdate/restoration.php';
WatchfulliFileHelper::delete($target);
$this->helper->response(
[
'task' => 'cleanup',
'status' => 'success',
'message' => '',
]
);
}
/**
* Purges updates.
*
* @return void
* @throws Exception
* @throws Exception
*/
public function purge()
{
watchfulli::debug("purge");
JLoader::import('models.default', JPATH_ADMINISTRATOR . '/components/com_joomlaupdate');
$this->setupCoreUpdateModel();
// Purge updates
// Check for request forgeries
$this->joomlaUpdateModel->purge();
}
protected function parseRestoreResponse($response)
{
$delim = '###';
if (false === strpos($response, $delim))
{
return $response;
}
list($junk, $str) = explode('###', $response, 2);
list($junk, $str) = explode('###', strrev($str), 2);
unset($junk);
return json_decode(strrev($str));
}
/**
* Downloads a package file to a specific directory
*
* @param string $url The URL to download from
* @param string $target The directory to store the file
*
* @return string|bool True on success
*/
protected function downloadPackage($url, $target)
{
watchfulli::debug("downloadPackage $url $target");
Log::add(Text::sprintf('COM_JOOMLAUPDATE_UPDATE_LOG_URL', $url), Log::INFO, 'Update');
// Get the handler to download the package
try {
$http = WatchfulliFactory::getHttp();
} catch (Exception $e) {
return false;
}
watchfulli::debug("downloadPackage delete $target");
WatchfulliFileHelper::delete($target);
// Download the package
$result = $http->get($url);
$statusCode = $result->code ?? (method_exists($result, 'getStatusCode') ? $result->getStatusCode() : null);
watchfulli::debug("downloadPackage $url $target $statusCode");
if (!$result || ($statusCode != 200 && $statusCode != 310)) {
return false;
}
$body = $result->body ?? (method_exists($result, 'getBody') ? $result->getBody() : null);
// Write the file to disk
WatchfulliFileHelper::write($target, $body);
watchfulli::debug("downloadPackage done");
return basename($target);
}
/**
* @throws Exception
*/
private function setupCoreUpdateModel()
{
$this->joomlaUpdateModel = WatchfulliFactory::getUpdateModel();
}
}