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

Fix Dashboard Updates widget's display of the most recent modx version #16608

Merged
merged 2 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions core/src/Revolution/Processors/SoftwareUpdate/Base.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

/*
* This file is part of MODX Revolution.
*
* Copyright (c) MODX, LLC. All Rights Reserved.
*
* For complete copyright and license information, see the COPYRIGHT and LICENSE
* files found in the top-level directory of this distribution.
*/

namespace MODX\Revolution\Processors\SoftwareUpdate;

use MODX\Revolution\Processors\Processor;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
use MODX\Revolution\modX;

/**
* Provides base methods and shared properties for building status data used
* in the front end display of software updates (MODX and Extras)
*
* @package MODX\Revolution\Processors\SoftwareUpdate
*/
class Base extends Processor
{
public $apiClient = null;
public $apiFactory = null;
public $apiHost = 'https://sentinel.modx.com';
public $apiGetReleasesPath = '/releases/products/997329d1-6f68-48d5-8e5e-5251adbb1f38/upgrades/';
public $apiGetFilePath = '/releases/variants/[downloadId]/download/';

public function process()
{
return parent::process();
}

/**
* Initialize the client responsible for fetching upgrades-related data.
*
* @return
*/
public function initApiClient()
{
if (!$this->apiClient) {
$this->apiClient = $this->modx->services->get(ClientInterface::class);
$this->apiFactory = $this->modx->services->get(RequestFactoryInterface::class);
}
}

/**
* Builds the API link used to fetch file data
*
* @param array $requestParams Query parameters
* @param string $targetId An intermediate id used to fetch the actual download link
* @return string The full URI to pass into the upgrades API
*/
public function buildRequestUri(array $requestParams = [], string $targetId = ''): string
{
$uri = $this->apiHost;
/*
When a $targetId is passed in, we are making the final request whose response
reveals the real update file path. Otherwise the request gets a full list of
potential upgrades based on criteria passed in the $requestParams
*/
$uri .= !empty($targetId)
? str_replace('[downloadId]', $targetId, $this->apiGetFilePath)
: $this->apiGetReleasesPath
;
if (count($requestParams) > 0) {
$uri .= '?' . http_build_query($requestParams);
}
return $uri;
}
}
59 changes: 59 additions & 0 deletions core/src/Revolution/Processors/SoftwareUpdate/GetFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

/*
* This file is part of MODX Revolution.
*
* Copyright (c) MODX, LLC. All Rights Reserved.
*
* For complete copyright and license information, see the COPYRIGHT and LICENSE
* files found in the top-level directory of this distribution.
*/

namespace MODX\Revolution\Processors\SoftwareUpdate;

use MODX\Revolution\Processors\SoftwareUpdate\Base;
use Psr\Http\Client\ClientExceptionInterface;
use MODX\Revolution\modX;

/**
* Retrieves the downloadable file URL and other metadata for the specified MODX upgrade package
*
* @property string $downloadId An identifier used to retrieve the package's download URL
* @package MODX\Revolution\Processors\SoftwareUpdate
*/
class GetFile extends Base
{
public function process()
{
$downloadId = $this->getProperty('downloadId', null);
$responseData = [];

if ($downloadId) {
$this->initApiClient();

$uri = $this->buildRequestUri(['uuid' => $this->modx->uuid], $downloadId);
$request = $this->apiFactory->createRequest('GET', $uri)
->withHeader('Accept', 'application/json')
->withHeader('Content-Type', 'application/json');
try {
$response = $this->apiClient->sendRequest($request);
} catch (ClientExceptionInterface $e) {
$this->modx->log(modX::LOG_LEVEL_ERROR, $e->getMessage());
return $this->failure($e->getMessage());
}

$fileData = $response->getBody()->getContents();

if ($fileData) {
$fileData = json_decode($fileData, true);
if (!empty($fileData['zip_url']) && strpos($fileData['zip_url'], 'http') === 0) {
$name = basename($fileData['zip_url']);
$responseData['filename'] = $name;
$responseData['zip'] = $fileData['zip_url'];
$responseData['status'] = $response->getStatusCode();
}
}
return $this->success('', $responseData);
}
}
}
145 changes: 145 additions & 0 deletions core/src/Revolution/Processors/SoftwareUpdate/GetList.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php

/*
* This file is part of MODX Revolution.
*
* Copyright (c) MODX, LLC. All Rights Reserved.
*
* For complete copyright and license information, see the COPYRIGHT and LICENSE
* files found in the top-level directory of this distribution.
*/

namespace MODX\Revolution\Processors\SoftwareUpdate;

use MODX\Revolution\Processors\SoftwareUpdate\Base;
use MODX\Revolution\Processors\Workspace\Packages\GetList as PackagesGetList;
use MODX\Revolution\Transport\modTransportPackage;
use Psr\Http\Client\ClientExceptionInterface;
use MODX\Revolution\modX;

/**
* Retrieves status data for use in the front end display of software updates (MODX and Extras)
*
* @property string $softwareType Identifies which type of software status data should be
* retrieved (currently only two options: 'modx' or 'extras')
* @package MODX\Revolution\Processors\SoftwareUpdate
*/
class GetList extends Base
{
public $installedVersionData;

public function initialize()
{
$this->installedVersionData = $this->modx->getVersionData();
return parent::initialize();
}

public function process()
{
$softwareType = $this->getProperty('softwareType', 'modx');
$categoryData = [
'updateable' => 0
];
if ($softwareType === 'modx') {
$modxData = $this->getModxUpdates();
if (is_array($modxData)) {
$categoryData = array_merge($categoryData, $modxData);
}
} else {
$extrasData = $this->getExtrasUpdates();
if (is_array($extrasData)) {
$categoryData = array_merge($categoryData, $extrasData);
}
}
return $this->success('', $categoryData);
}

/**
* Fetches a list of MODX update candidates
*
* @return array Data indicating whether the current installation is
* updatable and the available releases if so
*/
public function getModxUpdates(): array
{
$this->initApiClient();

$uri = $this->buildRequestUri([
'current' => $this->installedVersionData['full_version'],
'level' => 'major',
'variant' => 'Traditional',
'prereleases' => 0
]);

$request = $this->apiFactory->createRequest('GET', $uri)
->withHeader('Accept', 'application/json')
->withHeader('Content-Type', 'application/json');
try {
$response = $this->apiClient->sendRequest($request);
} catch (ClientExceptionInterface $e) {
$this->modx->log(modX::LOG_LEVEL_ERROR, 'ClientExceptionInterface Err: ' . $e->getMessage());
return $this->failure($e->getMessage());
}

$listData = $response->getBody()->getContents();
$categoryData = [];
if ($listData) {
$listData = json_decode($listData, true);
$upgrades = $listData['upgrades'];
$selectedUpgrade = null;
if (!empty($upgrades)) {
$i = 0;
$upgradesCount = count($upgrades);
if ($upgradesCount === 1) {
$categoryData['updateable'] = 1;
$selectedUpgrade = $upgrades;
} else {
foreach ($upgrades as $upgrade) {
$selectedUpgrade = $upgrade;
break;
}
$categoryData['updateable'] = (int)version_compare($this->installedVersionData['full_version'], $upgrade['version'], '<');

Check warning on line 101 in core/src/Revolution/Processors/SoftwareUpdate/GetList.php

View workflow job for this annotation

GitHub Actions / phpcs

Line exceeds 120 characters; contains 142 characters
}
if ($categoryData['updateable']) {
/*
NOTE: This is superfluous now, but is done in preparation
for iterating through multiple displayable versions
*/
$categoryData['versions'][$i]['version'] = $selectedUpgrade['version'];
$urlSegments = explode('/', trim($selectedUpgrade['url'], '/'));
$categoryData['versions'][$i]['downloadId'] = $urlSegments[count($urlSegments) - 2];

$categoryData['latest']['version'] = $categoryData['versions'][0]['version'];
$categoryData['latest']['downloadId'] = $categoryData['versions'][0]['downloadId'];
}
}
}
return $categoryData;
}

/**
* Fetches a list of Extras update candidates
*
* @return array Data indicating whether any installed Extras are updatable
* and, if so, providing the names of those that are update candidates
*/
public function getExtrasUpdates(): array
{
$categoryData = [];
$packages = $this->modx->call(modTransportPackage::class, 'listPackages', [$this->modx, 1]);
if ($packages && array_key_exists('collection', $packages)) {
$packagesProcessor = new PackagesGetList($this->modx);

/** @var modTransportPackage $package */
foreach ($packages['collection'] as $package) {
$tmp = [];
$tmp = $packagesProcessor->checkForUpdates($package, $tmp);
if (!empty($tmp['updateable'])) {
$categoryData['names'][] = $package->get('package_name');
$categoryData['updateable']++;
}
}
}
return $categoryData;
}
}
73 changes: 21 additions & 52 deletions manager/controllers/default/dashboard/widget.updates.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

use MODX\Revolution\modX;
use MODX\Revolution\modDashboardWidgetInterface;
use MODX\Revolution\Processors\Workspace\Packages\GetList;
use MODX\Revolution\Processors\SoftwareUpdate\GetList as SoftwareUpdateGetList;
use MODX\Revolution\Smarty\modSmarty;
use MODX\Revolution\Transport\modTransportPackage;
use xPDO\xPDO;

/**
Expand All @@ -13,73 +12,44 @@
*/
class modDashboardWidgetUpdates extends modDashboardWidgetInterface
{
/** @var modX $modx */
public $modx;
public $latest_url = 'https://raw.githubusercontent.com/modxcms/revolution/3.x/_build/build.xml';
public $download_url = 'https://modx.com/download/latest';
public $updatesCacheExpire = 3600;


/**
* @return string
* @throws Exception
*/
public function render()
{
$processor = new GetList($this->modx);

$updateCacheKey = 'mgr/providers/updates/modx-core';
$updateCacheOptions = [
xPDO::OPT_CACHE_KEY => $this->modx->cacheManager->getOption('cache_packages_key', null, 'packages'),
xPDO::OPT_CACHE_HANDLER => $this->modx->cacheManager->getOption('cache_packages_handler', null, $this->modx->cacheManager->getOption(xPDO::OPT_CACHE_HANDLER)),
xPDO::OPT_CACHE_KEY => $this->modx->cacheManager->getOption(
'cache_packages_key',
null,
'packages'
),
xPDO::OPT_CACHE_HANDLER => $this->modx->cacheManager->getOption(
'cache_packages_handler',
null,
$this->modx->cacheManager->getOption(xPDO::OPT_CACHE_HANDLER)
),
];

if (!$data = $this->modx->cacheManager->get($updateCacheKey, $updateCacheOptions)) {
$data = [
'modx' => [
'updateable' => 0,
],
'packages' => [
'names' => [],
'updateable' => 0,
],
'modx' => [],
'extras' => []
];

if (function_exists('curl_init')) {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $this->latest_url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_TIMEOUT, 1);
$content = curl_exec($curl);
curl_close($curl);
if ($content) {
$xml = new SimpleXMLElement($content);
foreach ($xml->property as $key => $value) {
$name = (string)$value->attributes()->name;
if ($name == 'modx.core.version') {
$data['modx']['version'] = (string)$value->attributes()->value;
} elseif ($name == 'modx.core.release') {
$data['modx']['release'] = (string)$value->attributes()->value;
}
}
}
if (!empty($data['modx']['version']) && !empty($data['modx']['release'])) {
if ($version = $this->modx->getVersionData()) {
$data['modx']['full_version'] = $data['modx']['version'] . '-' . $data['modx']['release'];
$data['modx']['updateable'] = (int)version_compare($version['full_version'], $data['modx']['full_version'], '<');
}
}
$modxUpdatesProcessor = new SoftwareUpdateGetList($this->modx);
$modxData = $modxUpdatesProcessor->run()->getObject();
if (is_array($modxData) && array_key_exists('updateable', $modxData)) {
$data['modx'] = $modxData;
}

$packages = $this->modx->call(modTransportPackage::class, 'listPackages', [$this->modx, 1, 11, 0]);
/** @var modTransportPackage $package */
foreach ($packages['collection'] as $package) {
$tmp = [];
$tmp = $processor->checkForUpdates($package, $tmp);
if (!empty($tmp['updateable'])) {
$data['packages']['names'][] = $package->get('package_name');
$data['packages']['updateable']++;
}
$extrasUpdatesProcessor = new SoftwareUpdateGetList($this->modx, ['softwareType' => 'extras']);
$extrasData = $extrasUpdatesProcessor->run()->getObject();
if (is_array($extrasData) && array_key_exists('updateable', $extrasData)) {
$data['extras'] = $extrasData;
}

$this->modx->cacheManager->set($updateCacheKey, $data, $this->updatesCacheExpire, $updateCacheOptions);
Expand All @@ -92,7 +62,6 @@ public function render()

return $this->modx->smarty->fetch('dashboard/updates.tpl');
}

}

return 'modDashboardWidgetUpdates';
Loading
Loading