Skip to content

Commit a44f663

Browse files
committed
Fix version constrains, support multiple stable branches.
Related to drush-ops/drush#3748 (comment)
1 parent 31d024f commit a44f663

File tree

5 files changed

+147
-50
lines changed

5 files changed

+147
-50
lines changed

build/build-composer-json.php

Lines changed: 60 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
<?php
22

3+
use Doctrine\Common\Cache\FilesystemCache;
4+
use DrupalComposer\DrupalSecurityAdvisories\Projects;
5+
use DrupalComposer\DrupalSecurityAdvisories\UrlHelper;
6+
use DrupalComposer\DrupalSecurityAdvisories\VersionParser;
37
use GuzzleHttp\Client;
48
use GuzzleHttp\HandlerStack;
59
use Kevinrob\GuzzleCache\CacheMiddleware;
6-
use Doctrine\Common\Cache\FilesystemCache;
7-
use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy;
810
use Kevinrob\GuzzleCache\Storage\DoctrineCacheStorage;
11+
use Kevinrob\GuzzleCache\Strategy\GreedyCacheStrategy;
912

1013
require __DIR__ . '/vendor/autoload.php';
1114

@@ -16,62 +19,74 @@
1619
$stack = HandlerStack::create();
1720
$stack->push(
1821
new CacheMiddleware(
19-
new PrivateCacheStrategy(
22+
new GreedyCacheStrategy(
2023
new DoctrineCacheStorage(
2124
new FilesystemCache(__DIR__ . '/cache')
22-
)
25+
),
26+
3600
2327
)
2428
),
2529
'cache'
2630
);
2731
$client = new Client(['handler' => $stack]);
28-
29-
$data = json_decode($client->get('https://www.drupal.org/api-d7/node.json?type=project_release&taxonomy_vocabulary_7=100&field_release_build_type=static')->getBody());
30-
31-
$projects = [];
32+
$projects = new Projects($client);
3233
$conflict = [];
3334

34-
class UrlHelper {
35-
36-
public static function prepareUrl($url) {
37-
return str_replace('https://www.drupal.org/api-d7/node', 'https://www.drupal.org/api-d7/node.json', $url);
35+
/**
36+
* @param $url
37+
* @param \GuzzleHttp\Client $client
38+
*
39+
* @return array
40+
*/
41+
function fetchAllData($url, Client $client) {
42+
$results = [];
43+
$data = json_decode($client->get($url)->getBody());
44+
while (isset($data) && isset($data->list)) {
45+
$results = array_merge($results, $data->list);
46+
47+
if (isset($data->next)) {
48+
$data = json_decode($client->get(UrlHelper::prepareUrl($data->next))->getBody());
49+
}
50+
else {
51+
$data = NULL;
52+
}
3853
}
39-
54+
return $results;
4055
}
4156

42-
class VersionParser {
43-
44-
public static function getSemVer($version, $isCore) {
45-
$version = $isCore ? static::handleCore($version) : static::handleContrib($version);
46-
return static::isValid($version) ? $version : FALSE;
47-
}
57+
// Security releases
58+
$results = fetchAllData('https://www.drupal.org/api-d7/node.json?type=project_release&taxonomy_vocabulary_7=100&field_release_build_type=static', $client);
59+
foreach ($results as $result) {
60+
$nid = $result->field_release_project->id;
61+
$core = (int) substr($result->field_release_version, 0, 1);
4862

49-
public static function handleCore($version) {
50-
return $version;
63+
// Skip D6 and older.
64+
if ($core < 7) {
65+
continue;
5166
}
5267

53-
public static function handleContrib($version) {
54-
list($core, $version) = explode('-', $version, 2);
55-
return $version;
56-
}
68+
$project = $projects->getFromNid($nid);
5769

58-
public static function isValid($version) {
59-
return (strpos($version, 'unstable') === FALSE);
70+
if (!$project) {
71+
// @todo: log error
72+
continue;
6073
}
6174

62-
}
63-
64-
while (isset($data) && isset($data->list)) {
65-
$results = array_merge($results, $data->list);
66-
67-
if (isset($data->next)) {
68-
$data = json_decode($client->get(UrlHelper::prepareUrl($data->next))->getBody());
69-
}
70-
else {
71-
$data = NULL;
75+
try {
76+
$is_core = ($project->field_project_machine_name == 'drupal') ? TRUE : FALSE;
77+
$constraint = VersionParser::generateRangeConstraint($result->field_release_version, $is_core);
78+
if (!$constraint) {
79+
throw new InvalidArgumentException('Invalid version number.');
80+
}
81+
$conflict[$core]['drupal/' . $project->field_project_machine_name][] = $constraint;
82+
} catch (\Exception $e) {
83+
// @todo: log exception
84+
continue;
7285
}
7386
}
7487

88+
// Insecure releases
89+
$results = fetchAllData('https://www.drupal.org/api-d7/node.json?type=project_release&taxonomy_vocabulary_7=188131&field_release_build_type=static', $client);
7590
foreach ($results as $result) {
7691
$nid = $result->field_release_project->id;
7792
$core = (int) substr($result->field_release_version, 0, 1);
@@ -81,24 +96,20 @@ public static function isValid($version) {
8196
continue;
8297
}
8398

84-
try {
85-
if (!isset($projects[$nid])) {
86-
$project = json_decode($client->get('https://www.drupal.org/api-d7/node.json?nid=' . $nid)->getBody());
87-
$projects[$nid] = $project->list[0];
88-
}
89-
} catch (\GuzzleHttp\Exception\ServerException $e) {
90-
// @todo: log exception
99+
$project = $projects->getFromNid($nid);
100+
101+
if (!$project) {
102+
// @todo: log error
91103
continue;
92104
}
93105

94106
try {
95-
$project = $projects[$nid];
96107
$is_core = ($project->field_project_machine_name == 'drupal') ? TRUE : FALSE;
97-
$version = VersionParser::getSemVer($result->field_release_version, $is_core);
98-
if (!$version) {
108+
$constraint = VersionParser::generateExplicitConstraint($result->field_release_version, $is_core);
109+
if (!$constraint) {
99110
throw new InvalidArgumentException('Invalid version number.');
100111
}
101-
$conflict[$core]['drupal/' . $project->field_project_machine_name][] = '<' . $version;
112+
$conflict[$core]['drupal/' . $project->field_project_machine_name][] = $constraint;
102113
} catch (\Exception $e) {
103114
// @todo: log exception
104115
continue;
@@ -121,7 +132,7 @@ public static function isValid($version) {
121132

122133
foreach ($packages as $package => $constraints) {
123134
natsort($constraints);
124-
$composer['conflict'][$package] = implode(',', $constraints);
135+
$composer['conflict'][$package] = implode('|', $constraints);
125136
}
126137

127138
// drupal/core is a subtree split for drupal/drupal and has no own SAs.

build/composer.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
"composer/composer": "^1.0",
1111
"kevinrob/guzzle-cache-middleware": "^3.2",
1212
"guzzlehttp/guzzle": "^6.3",
13-
"doctrine/cache": "^1.7"
13+
"doctrine/cache": "^1.7",
14+
"ext-json": "*"
15+
},
16+
"autoload": {
17+
"psr-4": {
18+
"DrupalComposer\\DrupalSecurityAdvisories\\": "src"
19+
}
1420
}
1521
}

build/src/Projects.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace DrupalComposer\DrupalSecurityAdvisories;
4+
5+
class Projects {
6+
7+
protected $storage = [];
8+
9+
protected $client;
10+
11+
public function __construct(\GuzzleHttp\Client $client) {
12+
$this->client = $client;
13+
}
14+
15+
public function getFromNid($nid) {
16+
try {
17+
if (!isset($this->storage[$nid])) {
18+
$project = json_decode($this->client->get('https://www.drupal.org/api-d7/node.json?nid=' . $nid)->getBody());
19+
$this->storage[$nid] = $project->list[0];
20+
}
21+
} catch (\GuzzleHttp\Exception\ServerException $e) {
22+
$this->storage[$nid] = [];
23+
}
24+
return $this->storage[$nid];
25+
}
26+
27+
}

build/src/UrlHelper.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace DrupalComposer\DrupalSecurityAdvisories;
4+
5+
class UrlHelper {
6+
7+
public static function prepareUrl($url) {
8+
return str_replace('https://www.drupal.org/api-d7/node', 'https://www.drupal.org/api-d7/node.json', $url);
9+
}
10+
11+
}

build/src/VersionParser.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
namespace DrupalComposer\DrupalSecurityAdvisories;
4+
5+
class VersionParser {
6+
7+
public static function generateRangeConstraint($version, $isCore) {
8+
if (!static::isValid($version)) {
9+
return FALSE;
10+
}
11+
return $isCore ? static::handleCore($version) : static::handleContrib($version);
12+
}
13+
14+
public static function generateExplicitConstraint($version, $isCore) {
15+
if (!static::isValid($version)) {
16+
return FALSE;
17+
}
18+
if ($isCore) {
19+
return $version;
20+
}
21+
else {
22+
list($core, $version) = explode('-', $version, 2);
23+
}
24+
return $version;
25+
}
26+
27+
public static function handleCore($version) {
28+
list($major, $minor) = explode('.', $version);
29+
return ">=$major.$minor,<$version";
30+
}
31+
32+
public static function handleContrib($version) {
33+
list($core, $version) = explode('-', $version, 2);
34+
list($major, $minor) = explode('.', $version);
35+
return ">=$major.$minor,<$version";
36+
}
37+
38+
public static function isValid($version) {
39+
return (strpos($version, 'unstable') === FALSE);
40+
}
41+
42+
}

0 commit comments

Comments
 (0)