Skip to content
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
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,18 @@ You can see all available parameters and options directly from the console by us

For more information on using commands, please refer to the [PrestaShop developer documentation](https://devdocs.prestashop-project.org/8/basics/keeping-up-to-date/upgrade-module/upgrade-cli/#rollback-cli)

## Check Modules Compatibility

A command is available to verify module compatibility and detect available updates before performing a store update.

Run the command from the root directory of the module:

```
php bin/console update:check-modules <your-admin-dir>
```

This command helps you review module compatibility before launching an update, giving you clearer visibility on potential issues or available updates.

## Channels

There are 3 channels available for an update:
Expand Down Expand Up @@ -200,6 +212,8 @@ impact.
| `backup:create` | `PS_AUTOUP_KEEP_IMAGES` | `--include-images` | `true` (default), `false`, `'true'`, `'false'`, `'1'`, `'0'`, `1`, `0`, `'on'`, `'off'` | If enabled, retains all images in the backup. This operation can take a long time depending on the storage of your images |
| `backup:restore` | no option available | `--backup` | Valid file name | Specify the backup name to restore. The allowed values can be found with backup:list command) |
| `backup:delete` | no option available | `--backup` | Valid file name | Specify the backup name to delete. The allowed values can be found with backup:list command) |
| `update:check-modules` | no option available | `--channel` | `online`, `online_recommended` (default), `local` | Selects the update channel for module compatibility checks. The `local` channel requires specific files to be placed in the download folder. |
| `update:check-modules` | no option available | `--zip` | Valid file name | Sets the ZIP archive in local mode for module checking. |
| | | | | |

### Back Office Environment Variables
Expand Down
2 changes: 2 additions & 0 deletions bin/console
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0)
*/

use PrestaShop\Module\AutoUpgrade\Commands\CheckModulesCommand;
use PrestaShop\Module\AutoUpgrade\Commands\CheckNewVersionCommand;
use PrestaShop\Module\AutoUpgrade\Commands\CheckRequirementsCommand;
use PrestaShop\Module\AutoUpgrade\Commands\CreateBackupCommand;
Expand Down Expand Up @@ -61,6 +62,7 @@ $application->add(new CheckNewVersionCommand());
$application->add(new CreateBackupCommand());
$application->add(new ListBackupCommand());
$application->add(new DeleteBackupCommand());
$application->add(new CheckModulesCommand());

$input = new ArgvInput();
$output = new ConsoleOutput();
Expand Down
207 changes: 207 additions & 0 deletions classes/Commands/CheckModulesCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
<?php

/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/

namespace PrestaShop\Module\AutoUpgrade\Commands;

use Exception;
use PrestaShop\Module\AutoUpgrade\Exceptions\MarketplaceApiException;
use PrestaShop\Module\AutoUpgrade\Models\Module\Marketplace\Module;
use PrestaShop\Module\AutoUpgrade\Models\Module\Marketplace\Release;
use PrestaShop\Module\AutoUpgrade\Parameters\UpgradeConfiguration;
use PrestaShop\Module\AutoUpgrade\Task\ExitCode;
use PrestaShop\Module\AutoUpgrade\UpgradeContainer;
use Symfony\Component\Console\Helper\ProgressIndicator;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;

class CheckModulesCommand extends AbstractCommand
{
/** @var string */
protected static $defaultName = 'update:check-modules';

protected function configure(): void
{
$this
->setDescription('Check module compatibility and updates.')
->addOption(
'channel',
null,
InputOption::VALUE_REQUIRED,
"Select which update channel to use ('" . UpgradeConfiguration::CHANNEL_LOCAL . "' / '" . UpgradeConfiguration::CHANNEL_ONLINE_RECOMMENDED . "' / '" . UpgradeConfiguration::CHANNEL_ONLINE . "')"
)
->addOption('zip', null, InputOption::VALUE_REQUIRED, 'Sets the archive zip file for a local channel.')
->setHelp('This command checks the installed modules for compatibility with the target PrestaShop version and lists available updates.')
->addArgument(
'admin-dir',
InputArgument::REQUIRED,
'Name of the admin directory.'
);
}

protected function execute(InputInterface $input, OutputInterface $output): ?int
{
try {
$this->setupEnvironment($input, $output);
$config[UpgradeConfiguration::CHANNEL] = $input->getOption('channel') ?? UpgradeConfiguration::CHANNEL_ONLINE_RECOMMENDED;

$this->upgradeContainer->getConfigurationValidator()->validate($config);
$this->upgradeContainer->initPrestaShopAutoloader();
$this->upgradeContainer->initPrestaShopCore();
$channel = $config[UpgradeConfiguration::CHANNEL];

if ($channel === UpgradeConfiguration::CHANNEL_ONLINE_RECOMMENDED || $channel === UpgradeConfiguration::CHANNEL_ONLINE) {
$targetPsVersion = $this->upgradeContainer->getUpgrader()->getOnlineDestinationVersionForChannel($channel);
} else {
$zip = $input->getOption('zip');

if (empty($zip)) {
$output->writeln('<error> ✗ Please specify the destination zip file using the zip option..</error>');

return ExitCode::FAIL;
}

$fullFilePath = $this->upgradeContainer->getProperty(UpgradeContainer::DOWNLOAD_PATH) . DIRECTORY_SEPARATOR . $zip;
try {
$targetPsVersion = $this->upgradeContainer->getPrestashopVersionService()->extractPrestashopVersionFromZip($fullFilePath);
} catch (Exception $exception) {
$output->writeln('<error> ✗ We couldn\'t find a PrestaShop version in the .zip file that was uploaded in your local archive. Please try again.</error>');

return ExitCode::FAIL;
}
}

if ($targetPsVersion === null || version_compare($this->upgradeContainer->getCurrentPrestaShopVersion(), $targetPsVersion, '>=')) {
$output->writeln('<error> ✗ You are already running a PrestaShop version equal to or higher than the latest available for update.</error>');

return ExitCode::FAIL;
}

$modulesInstalled = $this->upgradeContainer->getModuleAdapter()->listModulesPresentInFolderAndInstalled();
$marketplaceService = $this->upgradeContainer->getMarketplaceService();

if (!empty($modulesInstalled)) {
$progressIndicator = new ProgressIndicator($output);
$output->writeln(sprintf('Prestashop version: %s', $targetPsVersion));
$progressIndicator->start('Retrieving modules informations, please wait...');

$table = new Table($output);
$table->setHeaders([
'Module',
'Compatible',
'Update available',
'Local version',
'Update version available',
]);

foreach ($modulesInstalled as $localModule) {
$progressIndicator->advance();
$localModuleName = $localModule['name'];
$localVersion = $localModule['currentVersion'];

try {
$moduleDetails = $marketplaceService->getModuleDetail($localModuleName);
} catch (MarketplaceApiException $e) {
$table->addRow([
$localModuleName,
'<error> ✗ Unable to retrieve module informations</error>',
]);
continue;
}

$analysis = $this->analyzeModuleReleases(
$moduleDetails,
$targetPsVersion,
$localVersion
);

$table->addRow([
$localModuleName,
$analysis['compatible'] ? '✓ Yes' : '✗ No',
$analysis['update_available'] ? '✓ Yes' : '✗ No',
$localVersion,
$analysis['compatible'] ? $analysis['compatible_release']->productVersion : '-',
]);
}
$progressIndicator->finish('Result:');
$table->render();
}

return ExitCode::SUCCESS;
} catch (Exception $e) {
$this->logger->error("An error occurred during the check process:\n" . $e);
throw $e;
}
}

/**
* @return array{
* compatible: bool,
* update_available: ?bool,
* compatible_release: ?Release,
* latest_release: Release
* } $data
*/
private function analyzeModuleReleases(
Module $module,
string $targetVersion,
string $localVersion
): array {
$releases = $module->technicalInfo->releases;

$compatibleReleases = [];
$latestRelease = null;

foreach ($releases as $release) {
if (!$latestRelease || version_compare($release->productVersion, $latestRelease->productVersion, '>')) {
$latestRelease = $release;
}

if (version_compare($targetVersion, $release->compatibleFrom, '>=') &&
version_compare($targetVersion, $release->compatibleTo, '<=')) {
$compatibleReleases[] = $release;
}
}

if (empty($compatibleReleases)) {
return [
'compatible' => false,
'update_available' => false,
'compatible_release' => null,
'latest_release' => $latestRelease,
];
}

usort($compatibleReleases, function ($a, $b) {
return version_compare($b->productVersion, $a->productVersion);
});
$bestCompatible = $compatibleReleases[0];

return [
'compatible' => true,
'update_available' => version_compare($bestCompatible->productVersion, $localVersion, '>'),
'compatible_release' => $bestCompatible,
'latest_release' => $latestRelease,
];
}
}
31 changes: 31 additions & 0 deletions classes/Exceptions/MarketplaceApiException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/

namespace PrestaShop\Module\AutoUpgrade\Exceptions;

use Exception;

class MarketplaceApiException extends Exception
{
const API_NOT_CALLABLE_CODE = 0;
const VERSION_NOT_FOUND_CODE = 1;
const EMPTY_DATA_CODE = 2;
}
44 changes: 44 additions & 0 deletions classes/Models/Module/Marketplace/Attributes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/

namespace PrestaShop\Module\AutoUpgrade\Models\Module\Marketplace;

class Attributes
{
/** @var string[] */
public $groups = [];
/** @var string[] */
public $combinations = [];

/**
* @param array{
* groups?: array<int, string>,
* combinations?: array<int, string>
* } $data
*/
public static function fromArray(array $data): self
{
$obj = new self();
$obj->groups = $data['groups'] ?? [];
$obj->combinations = $data['combinations'] ?? [];

return $obj;
}
}
49 changes: 49 additions & 0 deletions classes/Models/Module/Marketplace/Compatibility.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php
/**
* Copyright since 2007 PrestaShop SA and Contributors
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
*
* NOTICE OF LICENSE
*
* This source file is subject to the Academic Free License version 3.0
* that is bundled with this package in the file LICENSE.md.
* It is also available through the world-wide-web at this URL:
* https://opensource.org/licenses/AFL-3.0
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@prestashop.com so we can send you a copy immediately.
*
* @author PrestaShop SA and Contributors <contact@prestashop.com>
* @copyright Since 2007 PrestaShop SA and Contributors
* @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0
*/

namespace PrestaShop\Module\AutoUpgrade\Models\Module\Marketplace;

class Compatibility
{
/** @var bool */
public $checked = false;
/** @var string[] */
public $versions = [];
/** @var string[] */
public $defaultVersions = [];

/**
* @param array{
* compliance_checked?: bool,
* versions?: array<int, string>,
* default_versions?: array<int, string>
* } $data
*/
public static function fromArray(array $data): self
{
$obj = new self();

$obj->checked = (bool) ($data['compliance_checked'] ?? false);
$obj->versions = $data['versions'] ?? [];
$obj->defaultVersions = $data['default_versions'] ?? [];

return $obj;
}
}
Loading