2020-09-02 14:48:08 +08:00
|
|
|
<?php namespace System\Classes;
|
|
|
|
|
2021-08-10 11:27:24 +08:00
|
|
|
use Http;
|
2020-09-02 14:48:08 +08:00
|
|
|
use Config;
|
2021-08-10 11:27:24 +08:00
|
|
|
use ApplicationException;
|
2021-04-01 23:05:15 +08:00
|
|
|
use Winter\Storm\Argon\Argon;
|
2020-09-02 14:48:08 +08:00
|
|
|
|
|
|
|
/**
|
2021-03-10 15:25:57 -06:00
|
|
|
* Reads and stores the Winter CMS source manifest information.
|
2020-09-02 14:48:08 +08:00
|
|
|
*
|
|
|
|
* The source manifest is a meta JSON file, stored on GitHub, that contains the hashsums of all module files across all
|
2021-03-10 15:25:57 -06:00
|
|
|
* buils of Winter CMS. This allows us to compare the Winter CMS installation against the expected file checksums and
|
2020-09-02 14:48:08 +08:00
|
|
|
* determine the installed build and whether it has been modified.
|
|
|
|
*
|
2021-04-01 23:05:15 +08:00
|
|
|
* Since Winter CMS v1.1.1, a forks manifest is also used to determine at which point we forked a branch off to a new
|
|
|
|
* major release. This allows us to track concurrent histories - ie. the 1.0.x history vs. the 1.1.x history.
|
|
|
|
*
|
2021-03-10 15:25:57 -06:00
|
|
|
* @package winter\wn-system-module
|
2020-09-02 14:48:08 +08:00
|
|
|
* @author Ben Thomson
|
|
|
|
*/
|
|
|
|
class SourceManifest
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var string The URL to the source manifest
|
|
|
|
*/
|
|
|
|
protected $source;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array Array of builds, keyed by build number, with files for keys and hashes for values.
|
|
|
|
*/
|
|
|
|
protected $builds = [];
|
|
|
|
|
2021-04-01 23:05:15 +08:00
|
|
|
/**
|
|
|
|
* @var array The version map where forks occurred.
|
|
|
|
*/
|
|
|
|
protected $forks;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string The URL to the forked version manifest
|
|
|
|
*/
|
|
|
|
protected $forksUrl;
|
|
|
|
|
2020-09-02 14:48:08 +08:00
|
|
|
/**
|
|
|
|
* Constructor
|
|
|
|
*
|
|
|
|
* @param string $manifest Manifest file to load
|
2021-04-01 23:05:15 +08:00
|
|
|
* @param string $branches Branches manifest file to load
|
2020-09-02 14:48:08 +08:00
|
|
|
* @param bool $autoload Loads the manifest on construct
|
|
|
|
*/
|
2021-04-01 23:05:15 +08:00
|
|
|
public function __construct($source = null, $forks = null, $autoload = true)
|
2020-09-02 14:48:08 +08:00
|
|
|
{
|
|
|
|
if (isset($source)) {
|
|
|
|
$this->setSource($source);
|
|
|
|
} else {
|
|
|
|
$this->setSource(
|
|
|
|
Config::get(
|
|
|
|
'cms.sourceManifestUrl',
|
2021-03-06 03:26:27 -06:00
|
|
|
'https://raw.githubusercontent.com/wintercms/meta/master/manifest/builds.json'
|
2020-09-02 14:48:08 +08:00
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-04-01 23:05:15 +08:00
|
|
|
if (isset($forks)) {
|
|
|
|
$this->setForksSource($forks);
|
|
|
|
} else {
|
|
|
|
$this->setForksSource(
|
|
|
|
Config::get(
|
|
|
|
'cms.forkManifestUrl',
|
|
|
|
'https://raw.githubusercontent.com/wintercms/meta/master/manifest/forks.json'
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-09-02 14:48:08 +08:00
|
|
|
if ($autoload) {
|
2021-04-01 23:05:15 +08:00
|
|
|
$this->loadSource();
|
|
|
|
$this->loadForks();
|
2020-09-02 14:48:08 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Sets the source manifest URL.
|
|
|
|
*
|
|
|
|
* @param string $source
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function setSource($source)
|
|
|
|
{
|
|
|
|
if (is_string($source)) {
|
|
|
|
$this->source = $source;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-01 23:05:15 +08:00
|
|
|
/**
|
|
|
|
* Sets the forked version manifest URL.
|
|
|
|
*
|
|
|
|
* @param string $forks
|
|
|
|
* @return void
|
|
|
|
*/
|
|
|
|
public function setForksSource($forks)
|
|
|
|
{
|
|
|
|
if (is_string($forks)) {
|
|
|
|
$this->forksUrl = $forks;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-02 14:48:08 +08:00
|
|
|
/**
|
|
|
|
* Loads the manifest file.
|
|
|
|
*
|
|
|
|
* @throws ApplicationException If the manifest is invalid, or cannot be parsed.
|
|
|
|
*/
|
2021-04-01 23:05:15 +08:00
|
|
|
public function loadSource()
|
2020-09-02 14:48:08 +08:00
|
|
|
{
|
2021-08-10 11:35:44 +08:00
|
|
|
if (file_exists($this->source)) {
|
|
|
|
$source = file_get_contents($this->source);
|
|
|
|
} else {
|
|
|
|
$source = Http::get($this->source)->body;
|
|
|
|
}
|
2021-08-10 11:27:24 +08:00
|
|
|
|
2020-09-02 14:48:08 +08:00
|
|
|
if (empty($source)) {
|
|
|
|
throw new ApplicationException(
|
|
|
|
'Source manifest not found'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$data = json_decode($source, true);
|
|
|
|
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
|
|
throw new ApplicationException(
|
|
|
|
'Unable to decode source manifest JSON data. JSON Error: ' . json_last_error_msg()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (!isset($data['manifest']) || !is_array($data['manifest'])) {
|
|
|
|
throw new ApplicationException(
|
|
|
|
'The source manifest at "' . $this->source . '" does not appear to be a valid source manifest file.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($data['manifest'] as $build) {
|
2021-04-01 23:05:15 +08:00
|
|
|
$this->builds[$this->getVersionInt($build['build'])] = [
|
|
|
|
'version' => $build['build'],
|
|
|
|
'parent' => $build['parent'],
|
2020-09-02 14:48:08 +08:00
|
|
|
'modules' => $build['modules'],
|
|
|
|
'files' => $build['files'],
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2021-04-01 23:05:15 +08:00
|
|
|
/**
|
|
|
|
* Loads the forked version manifest file.
|
|
|
|
*
|
|
|
|
* @throws ApplicationException If the manifest is invalid, or cannot be parsed.
|
|
|
|
*/
|
|
|
|
public function loadForks()
|
|
|
|
{
|
2021-08-10 11:35:44 +08:00
|
|
|
if (file_exists($this->forksUrl)) {
|
|
|
|
$forks = file_get_contents($this->forksUrl);
|
|
|
|
} else {
|
|
|
|
$forks = Http::get($this->forksUrl)->body;
|
|
|
|
}
|
2021-08-10 11:27:24 +08:00
|
|
|
|
2021-04-01 23:05:15 +08:00
|
|
|
if (empty($forks)) {
|
|
|
|
throw new ApplicationException(
|
|
|
|
'Forked version manifest not found'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$data = json_decode($forks, true);
|
|
|
|
|
|
|
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
|
|
|
throw new ApplicationException(
|
|
|
|
'Unable to decode forked version manifest JSON data. JSON Error: ' . json_last_error_msg()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (!isset($data['forks']) || !is_array($data['forks'])) {
|
|
|
|
throw new ApplicationException(
|
|
|
|
'The forked version manifest at "' . $this->forksUrl . '" does not appear to be a valid forked version
|
|
|
|
manifest file.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Map forks to int values
|
|
|
|
foreach ($data['forks'] as $child => $parent) {
|
|
|
|
$this->forks[$this->getVersionInt($child)] = $this->getVersionInt($parent);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2020-09-02 14:48:08 +08:00
|
|
|
/**
|
|
|
|
* Adds a FileManifest instance as a build to this source manifest.
|
|
|
|
*
|
2021-04-01 23:05:15 +08:00
|
|
|
* Changes between builds are calculated and stored with the build. Builds are stored in order of semantic
|
|
|
|
* versioning: ie. 1.1.1 > 1.1.0 > 1.0.468
|
2020-09-02 14:48:08 +08:00
|
|
|
*
|
|
|
|
* @param integer $build Build number.
|
|
|
|
* @param FileManifest $manifest The file manifest to add as a build.
|
|
|
|
* @return void
|
|
|
|
*/
|
2021-04-01 23:05:15 +08:00
|
|
|
public function addBuild($build, FileManifest $manifest)
|
2020-09-02 14:48:08 +08:00
|
|
|
{
|
2021-04-01 23:05:15 +08:00
|
|
|
$parent = $this->determineParent($build);
|
|
|
|
|
|
|
|
if (!is_null($parent)) {
|
|
|
|
$parent = $parent['version'];
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->builds[$this->getVersionInt($build)] = [
|
|
|
|
'version' => $build,
|
2020-09-02 14:48:08 +08:00
|
|
|
'modules' => $manifest->getModuleChecksums(),
|
2021-04-01 23:05:15 +08:00
|
|
|
'parent' => $parent,
|
|
|
|
'files' => $this->processChanges($manifest, $parent),
|
2020-09-02 14:48:08 +08:00
|
|
|
];
|
|
|
|
|
|
|
|
// Sort builds numerically in ascending order.
|
2021-04-01 23:05:15 +08:00
|
|
|
ksort($this->builds, SORT_NUMERIC);
|
2020-09-02 14:48:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets all builds.
|
|
|
|
*
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getBuilds()
|
|
|
|
{
|
2021-04-01 23:05:15 +08:00
|
|
|
return array_values(array_map(function ($build) {
|
|
|
|
return $build['version'];
|
|
|
|
}, $this->builds));
|
2020-09-02 14:48:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generates the JSON data to be stored with the source manifest.
|
|
|
|
*
|
|
|
|
* @throws ApplicationException If no builds have been added to this source manifest.
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function generate()
|
|
|
|
{
|
|
|
|
if (!count($this->builds)) {
|
|
|
|
throw new ApplicationException(
|
|
|
|
'No builds have been added to the manifest.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
$json = [
|
2021-04-01 23:05:15 +08:00
|
|
|
'_description' => 'This is the source manifest of changes to Winter CMS for each version. This is used to'
|
|
|
|
. ' determine which version of Winter CMS is in use, via the "winter:version" Artisan command.',
|
|
|
|
'_created' => Argon::now()->toIso8601String(),
|
2020-09-02 14:48:08 +08:00
|
|
|
'manifest' => [],
|
|
|
|
];
|
|
|
|
|
2021-04-01 23:05:15 +08:00
|
|
|
foreach (array_values($this->builds) as $details) {
|
2020-09-02 14:48:08 +08:00
|
|
|
$json['manifest'][] = [
|
2021-04-01 23:05:15 +08:00
|
|
|
'build' => $details['version'],
|
|
|
|
'parent' => $details['parent'] ?? null,
|
2020-09-02 14:48:08 +08:00
|
|
|
'modules' => $details['modules'],
|
|
|
|
'files' => $details['files'],
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
return json_encode($json, JSON_PRETTY_PRINT);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the filelist state at a selected build.
|
|
|
|
*
|
2021-04-01 23:05:15 +08:00
|
|
|
* This method will list all expected files and hashsums at the specified build number. It does this by following
|
|
|
|
* the history, switching branches as necessary.
|
2020-09-02 14:48:08 +08:00
|
|
|
*
|
2021-04-01 23:05:15 +08:00
|
|
|
* @param string|integer $build Build version to get the filelist state for.
|
2020-09-02 14:48:08 +08:00
|
|
|
* @throws ApplicationException If the specified build has not been added to the source manifest.
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getState($build)
|
|
|
|
{
|
2021-04-01 23:05:15 +08:00
|
|
|
if (is_string($build)) {
|
|
|
|
$build = $this->getVersionInt($build);
|
|
|
|
}
|
|
|
|
|
2020-09-02 14:48:08 +08:00
|
|
|
if (!isset($this->builds[$build])) {
|
|
|
|
throw new \Exception('The specified build has not been added.');
|
|
|
|
}
|
|
|
|
|
|
|
|
$state = [];
|
|
|
|
|
|
|
|
foreach ($this->builds as $number => $details) {
|
2021-04-01 23:05:15 +08:00
|
|
|
// Follow fork if necessary
|
|
|
|
if (isset($this->forks) && array_key_exists($build, $this->forks)) {
|
|
|
|
$state = $this->getState($this->forks[$build]);
|
|
|
|
}
|
|
|
|
|
2020-09-02 14:48:08 +08:00
|
|
|
if (isset($details['files']['added'])) {
|
|
|
|
foreach ($details['files']['added'] as $filename => $sum) {
|
|
|
|
$state[$filename] = $sum;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (isset($details['files']['modified'])) {
|
|
|
|
foreach ($details['files']['modified'] as $filename => $sum) {
|
|
|
|
$state[$filename] = $sum;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (isset($details['files']['removed'])) {
|
|
|
|
foreach ($details['files']['removed'] as $filename) {
|
|
|
|
unset($state[$filename]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($number === $build) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $state;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Compares a file manifest with the source manifest.
|
|
|
|
*
|
2021-03-10 15:25:57 -06:00
|
|
|
* This will determine the build of the Winter CMS installation.
|
2020-09-02 14:48:08 +08:00
|
|
|
*
|
2020-09-03 11:48:35 +08:00
|
|
|
* This will return an array with the following information:
|
|
|
|
* - `build`: The build number we determined was most likely the build installed.
|
|
|
|
* - `modified`: Whether we detected any modifications between the installed build and the manifest.
|
|
|
|
* - `confident`: Whether we are at least 60% sure that this is the installed build. More modifications to
|
|
|
|
* to the code = less confidence.
|
|
|
|
* - `changes`: If $detailed is true, this will include the list of files modified, created and deleted.
|
|
|
|
*
|
2020-09-02 14:48:08 +08:00
|
|
|
* @param FileManifest $manifest The file manifest to compare against the source.
|
2020-09-03 11:48:35 +08:00
|
|
|
* @param bool $detailed If true, the list of files modified, added and deleted will be included in the result.
|
|
|
|
* @return array
|
2020-09-02 14:48:08 +08:00
|
|
|
*/
|
2020-09-03 11:48:35 +08:00
|
|
|
public function compare(FileManifest $manifest, $detailed = false)
|
2020-09-02 14:48:08 +08:00
|
|
|
{
|
|
|
|
$modules = $manifest->getModuleChecksums();
|
|
|
|
|
|
|
|
// Look for an unmodified version
|
2021-04-01 23:05:15 +08:00
|
|
|
foreach ($this->getBuilds() as $buildString) {
|
|
|
|
$build = $this->builds[$this->getVersionInt($buildString)];
|
2020-09-02 14:48:08 +08:00
|
|
|
|
2021-04-01 23:05:15 +08:00
|
|
|
$matched = array_intersect_assoc($build['modules'], $modules);
|
|
|
|
|
|
|
|
if (count($matched) === count($build['modules'])) {
|
2020-09-03 11:48:35 +08:00
|
|
|
$details = [
|
2021-04-01 23:05:15 +08:00
|
|
|
'build' => $buildString,
|
2020-09-02 14:48:08 +08:00
|
|
|
'modified' => false,
|
2020-09-03 11:48:35 +08:00
|
|
|
'confident' => true,
|
2020-09-02 14:48:08 +08:00
|
|
|
];
|
2020-09-03 11:48:35 +08:00
|
|
|
|
|
|
|
if ($detailed) {
|
|
|
|
$details['changes'] = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
return $details;
|
2020-09-02 14:48:08 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we could not find an unmodified version, try to find the closest version and assume this is a modified
|
|
|
|
// install.
|
|
|
|
$buildMatch = [];
|
|
|
|
|
2021-04-01 23:05:15 +08:00
|
|
|
foreach ($this->getBuilds() as $buildString) {
|
|
|
|
$build = $this->builds[$this->getVersionInt($buildString)];
|
|
|
|
|
|
|
|
$state = $this->getState($buildString);
|
2020-09-02 14:48:08 +08:00
|
|
|
|
|
|
|
// Include only the files that match the modules being loaded in this file manifest
|
|
|
|
$availableModules = array_keys($modules);
|
|
|
|
|
|
|
|
foreach ($state as $file => $sum) {
|
|
|
|
// Determine module
|
|
|
|
$module = explode('/', $file)[2];
|
|
|
|
|
|
|
|
if (!in_array($module, $availableModules)) {
|
|
|
|
unset($state[$file]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$filesExpected = count($state);
|
|
|
|
$filesFound = [];
|
|
|
|
$filesChanged = [];
|
|
|
|
|
|
|
|
foreach ($manifest->getFiles() as $file => $sum) {
|
|
|
|
// Unknown new file
|
|
|
|
if (!isset($state[$file])) {
|
|
|
|
$filesChanged[] = $file;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Modified file
|
|
|
|
if ($state[$file] !== $sum) {
|
|
|
|
$filesFound[] = $file;
|
|
|
|
$filesChanged[] = $file;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pristine file
|
|
|
|
$filesFound[] = $file;
|
|
|
|
}
|
|
|
|
|
|
|
|
$foundPercent = count($filesFound) / $filesExpected;
|
|
|
|
$changedPercent = count($filesChanged) / $filesExpected;
|
|
|
|
|
|
|
|
$score = ((1 * $foundPercent) - $changedPercent);
|
2021-04-01 23:05:15 +08:00
|
|
|
$buildMatch[$buildString] = round($score * 100, 2);
|
2020-09-02 14:48:08 +08:00
|
|
|
}
|
|
|
|
|
2020-09-03 11:48:35 +08:00
|
|
|
// Find likely version
|
2020-09-02 14:48:08 +08:00
|
|
|
$likelyBuild = array_search(max($buildMatch), $buildMatch);
|
|
|
|
|
2020-09-03 11:48:35 +08:00
|
|
|
$details = [
|
2020-09-02 14:48:08 +08:00
|
|
|
'build' => $likelyBuild,
|
|
|
|
'modified' => true,
|
2020-09-03 11:48:35 +08:00
|
|
|
'confident' => ($buildMatch[$likelyBuild] >= 60)
|
2020-09-02 14:48:08 +08:00
|
|
|
];
|
2020-09-03 11:48:35 +08:00
|
|
|
|
|
|
|
if ($detailed) {
|
|
|
|
$details['changes'] = $this->processChanges($manifest, $likelyBuild);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $details;
|
2020-09-02 14:48:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determines file changes between the specified build and the previous build.
|
|
|
|
*
|
|
|
|
* Will return an array of added, modified and removed files.
|
|
|
|
*
|
|
|
|
* @param FileManifest $manifest The current build's file manifest.
|
2021-04-01 23:05:15 +08:00
|
|
|
* @param FileManifest|string|integer $previous Either a previous manifest, or the previous build number as an int
|
|
|
|
* or string, used to determine changes with this build.
|
2020-09-02 14:48:08 +08:00
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
protected function processChanges(FileManifest $manifest, $previous = null)
|
|
|
|
{
|
|
|
|
// If no previous build has been provided, all files are added
|
|
|
|
if (is_null($previous)) {
|
|
|
|
return [
|
|
|
|
'added' => $manifest->getFiles(),
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only save files if they are changing the "state" of the manifest (ie. the file is modified, added or removed)
|
2021-04-01 23:05:15 +08:00
|
|
|
if (is_int($previous) || is_string($previous)) {
|
2020-09-03 11:48:35 +08:00
|
|
|
$state = $this->getState($previous);
|
|
|
|
} else {
|
|
|
|
$state = $previous->getFiles();
|
|
|
|
}
|
2020-09-02 14:48:08 +08:00
|
|
|
$added = [];
|
|
|
|
$modified = [];
|
|
|
|
|
|
|
|
foreach ($manifest->getFiles() as $file => $sum) {
|
|
|
|
if (!isset($state[$file])) {
|
|
|
|
$added[$file] = $sum;
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
if ($state[$file] !== $sum) {
|
|
|
|
$modified[$file] = $sum;
|
|
|
|
}
|
|
|
|
unset($state[$file]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Any files still left in state have been removed
|
|
|
|
$removed = array_keys($state);
|
|
|
|
|
|
|
|
$changes = [];
|
|
|
|
if (count($added)) {
|
|
|
|
$changes['added'] = $added;
|
|
|
|
}
|
|
|
|
if (count($modified)) {
|
|
|
|
$changes['modified'] = $modified;
|
|
|
|
}
|
|
|
|
if (count($removed)) {
|
|
|
|
$changes['removed'] = $removed;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $changes;
|
|
|
|
}
|
2021-04-01 23:05:15 +08:00
|
|
|
|
|
|
|
protected function determineParent(string $build)
|
|
|
|
{
|
|
|
|
$buildInt = $this->getVersionInt($build);
|
|
|
|
|
|
|
|
// First, we'll check for a fork - if so, the source version for the fork is a parent
|
|
|
|
if (isset($this->forks) && array_key_exists($buildInt, $this->forks)) {
|
|
|
|
return $this->builds[$this->forks[$buildInt]];
|
|
|
|
}
|
|
|
|
|
|
|
|
// If not a fork, then determine the parent by finding the nearest minor version to the build
|
|
|
|
$parent = null;
|
|
|
|
|
|
|
|
for ($i = 1; $i <= 999; ++$i) {
|
|
|
|
if (array_key_exists($buildInt - $i, $this->builds)) {
|
|
|
|
$parent = $this->builds[$buildInt - $i];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $parent;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Converts a version string into an integer for comparison.
|
|
|
|
*
|
|
|
|
* @param string $version
|
|
|
|
* @throws ApplicationException if a version string does not match the format "major.minor.path"
|
|
|
|
* @return int
|
|
|
|
*/
|
|
|
|
protected function getVersionInt(string $version)
|
|
|
|
{
|
|
|
|
// Get major.minor.patch versions
|
|
|
|
if (!preg_match('/^([0-9]+)\.([0-9]+)\.([0-9]+)/', $version, $versionParts)) {
|
|
|
|
throw new ApplicationException('Invalid version string - must be of the format "major.minor.path"');
|
|
|
|
}
|
|
|
|
|
|
|
|
$int = $versionParts[1] * 1000000;
|
|
|
|
$int += $versionParts[2] * 1000;
|
|
|
|
$int += $versionParts[3];
|
|
|
|
|
|
|
|
return $int;
|
|
|
|
}
|
2020-09-02 14:48:08 +08:00
|
|
|
}
|