Current File : //var/www/vinorea/modules/autoupgrade/classes/Services/PhpVersionResolverService.php
<?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\Services;

use LogicException;
use PrestaShop\Module\AutoUpgrade\Exceptions\DistributionApiException;
use PrestaShop\Module\AutoUpgrade\Exceptions\UpgradeException;
use PrestaShop\Module\AutoUpgrade\Models\PrestashopRelease;
use PrestaShop\Module\AutoUpgrade\VersionUtils;
use PrestaShop\Module\AutoUpgrade\Xml\FileLoader;

class PhpVersionResolverService
{
    const COMPATIBILITY_INVALID = 0;
    const COMPATIBILITY_VALID = 1;
    const COMPATIBILITY_UNKNOWN = 2;

    /** @var DistributionApiService */
    private $distributionApiService;
    /** @var FileLoader */
    private $fileLoader;
    /** @var string */
    private $currentPsVersion;

    /**
     * @param DistributionApiService $distributionApiService
     */
    public function __construct(DistributionApiService $distributionApiService, FileLoader $fileLoader, string $currentPsVersion)
    {
        $this->distributionApiService = $distributionApiService;
        $this->fileLoader = $fileLoader;
        $this->currentPsVersion = $currentPsVersion;
    }

    /**
     * @return array{"php_min_version": string, "php_max_version": string, "php_current_version": string}|null
     */
    public function getPhpCompatibilityRange(string $targetVersion): ?array
    {
        if (version_compare($targetVersion, '8', '<')) {
            $targetMinorVersion = VersionUtils::splitPrestaShopVersion($targetVersion)['minor'];

            $requirements = $this->getPrestashop17Requirements();
            if (isset($requirements[$targetMinorVersion])) {
                $range = [
                    'php_min_version' => $requirements[$targetMinorVersion]->getPhpMinVersion(),
                    'php_max_version' => $requirements[$targetMinorVersion]->getPhpMaxVersion(),
                ];
            }

            if (!isset($range)) {
                return null;
            }
        } else {
            try {
                $range = $this->distributionApiService->getPhpVersionRequirements($targetVersion);
            } catch (DistributionApiException $apiException) {
                return null;
            }
        }
        $currentPhpVersion = VersionUtils::getHumanReadableVersionOf(PHP_VERSION_ID);
        $range['php_current_version'] = $currentPhpVersion;

        return $range;
    }

    /**
     * @return self::COMPATIBILITY_*
     */
    public function getPhpRequirementsState(int $currentPhpVersionId, ?string $currentPrestashopVersion): int
    {
        if (null == $currentPrestashopVersion) {
            return self::COMPATIBILITY_UNKNOWN;
        }

        $phpCompatibilityRange = $this->getPhpCompatibilityRange($currentPrestashopVersion);

        if (null == $phpCompatibilityRange) {
            return self::COMPATIBILITY_UNKNOWN;
        }

        $versionMin = VersionUtils::getPhpVersionId($phpCompatibilityRange['php_min_version']);
        $versionMax = VersionUtils::getPhpVersionId($phpCompatibilityRange['php_max_version']);

        $versionMinWithoutPatch = VersionUtils::getPhpMajorMinorVersionId($versionMin);
        $versionMaxWithoutPatch = VersionUtils::getPhpMajorMinorVersionId($versionMax);

        $currentVersion = VersionUtils::getPhpMajorMinorVersionId($currentPhpVersionId);

        if ($currentVersion >= $versionMinWithoutPatch && $currentVersion <= $versionMaxWithoutPatch) {
            return self::COMPATIBILITY_VALID;
        }

        return self::COMPATIBILITY_INVALID;
    }

    /**
     * @throws DistributionApiException
     * @throws UpgradeException
     */
    public function getPrestashopDestinationRelease(int $currentPhpVersionId): ?PrestashopRelease
    {
        $currentPhpVersion = VersionUtils::getPhpMajorMinorVersionId($currentPhpVersionId);

        if ($currentPhpVersion < 70100) {
            throw new LogicException('The minimum version to use the module is PHP 7.1');
        }

        /** @var array<string, PrestashopRelease> $releasesFromChannelFile */
        $releasesFromChannelFile = $this->getReleasesFromChannelFile();
        if (empty($releasesFromChannelFile)) {
            throw new UpgradeException('Unable to retrieve latest 1.7 release of Prestashop.');
        }

        if ($currentPhpVersion < 70200) {
            $release = $releasesFromChannelFile['1.7'];

            // current version is superior or equal
            if (version_compare($this->currentPsVersion, $release->getVersion(), '>=')) {
                return null;
            }

            return $release;
        }

        $validReleases = [];

        foreach ($this->distributionApiService->getReleases() as $release) {
            if ($release->getStability() !== 'stable') {
                continue;
            }

            // current version is superior or equal
            if (version_compare($this->currentPsVersion, $release->getVersion(), '>=')) {
                continue;
            }

            $versionMin = VersionUtils::getPhpVersionId($release->getPhpMinVersion());
            $versionMax = VersionUtils::getPhpVersionId($release->getPhpMaxVersion());

            $versionMinWithoutPatch = VersionUtils::getPhpMajorMinorVersionId($versionMin);
            $versionMaxWithoutPatch = VersionUtils::getPhpMajorMinorVersionId($versionMax);

            // verify php compatibility
            if ($currentPhpVersion >= $versionMinWithoutPatch && $currentPhpVersion <= $versionMaxWithoutPatch) {
                // verify channel.xml matching
                $branch = VersionUtils::splitPrestaShopVersion($release->getVersion())['minor'];
                if (isset($releasesFromChannelFile[$branch]) && version_compare($releasesFromChannelFile[$branch]->getVersion(), $release->getVersion(), '>=')) {
                    $releaseNote = $releasesFromChannelFile[$branch]->getReleaseNoteUrl();
                    $release->setReleaseNoteUrl($releaseNote);
                    $validReleases[] = $release;
                }
            }
        }

        /** @var PrestashopRelease $release */
        $release = array_reduce($validReleases, function ($carry, $item) {
            if ($carry === null || version_compare($item->getVersion(), $carry->getVersion()) > 0) {
                return $item;
            }

            return $carry;
        });

        return $release;
    }

    /**
     * @return array<string, PrestashopRelease>
     */
    private function getPrestashop17Requirements(): array
    {
        return [
            '1.7.0' => new PrestashopRelease('1.7.0.0', 'stable', '7.1', '5.4'),
            '1.7.1' => new PrestashopRelease('1.7.1.0', 'stable', '7.1', '5.4'),
            '1.7.2' => new PrestashopRelease('1.7.2.0', 'stable', '7.1', '5.4'),
            '1.7.3' => new PrestashopRelease('1.7.3.0', 'stable', '7.1', '5.4'),
            '1.7.4' => new PrestashopRelease('1.7.4.0', 'stable', '7.1', '5.6'),
            '1.7.5' => new PrestashopRelease('1.7.5.0', 'stable', '7.2', '5.6'),
            '1.7.6' => new PrestashopRelease('1.7.6.0', 'stable', '7.2', '5.6'),
            '1.7.7' => new PrestashopRelease('1.7.7.0', 'stable', '7.3', '5.6'),
            '1.7.8' => new PrestashopRelease('1.7.8.0', 'stable', '7.4', '5.6'),
        ];
    }

    /**
     * @return array<string, PrestashopRelease>
     *
     * @throws UpgradeException
     */
    public function getReleasesFromChannelFile(): array
    {
        $releases = [];
        $channelFile = $this->fileLoader->getXmlChannel();

        if (!$channelFile) {
            throw new UpgradeException('Unable to retrieve channel.xml from API.');
        }

        foreach ($channelFile->channel as $channel) {
            if ((string) $channel['name'] !== 'stable') {
                continue;
            }

            foreach ($channel->branch as $branch) {
                $cleanedZipUrl = str_replace(["\n", "\r"], '', $branch->download->link);
                $cleanedZipUrl = trim($cleanedZipUrl);

                $cleanedChangelogUrl = str_replace(["\n", "\r"], '', $branch->changelog);
                $cleanedChangelogUrl = trim($cleanedChangelogUrl);

                $releases[(string) $branch['name']] = new PrestashopRelease(
                        (string) $branch->num,
                    'stable',
                    null,
                    null,
                        $cleanedZipUrl,
                        'https://api.prestashop.com/xml/md5/' . $branch->num,
                        (string) $branch->download->md5,
                        $cleanedChangelogUrl
                    );
            }
        }

        return $releases;
    }
}