24 'licenses' => array(
'licenseId',
'licenseText',
'name'),
25 'exceptions' => array(
'licenseExceptionId',
'licenseExceptionText',
'name')
31 const BOA_LIST_URL =
'https://blueoakcouncil.org/list.json';
34 function startProcessingLicenseData()
40 $deleteDeprecated =
false;
41 $newLicenseRefData = array();
44 'licenses' =>
'https://spdx.org/licenses/licenses.json',
45 'exceptions' =>
'https://spdx.org/licenses/exceptions.json'
47 $usage =
"Usage: " . basename($argv[0]) .
" [options]
49 Create new licenseref.json file. Options are:
50 -E Update all existing licenses and also add new licenses.
51 (NOTE: there may be failure of test cases)
53 -e Only update existing licenses.
54 (NOTE: there may be failure of test cases)
56 -n Only add new licenses.
58 -d Delete deprecated licenses.
60 --type Usually licenses/exceptions (optional)
61 (ex: --type 'licenses')
63 --url From where you want to download (optional)
64 (ex: --url 'https://spdx.org/licenses/licenses.json')
67 (if --type and --url is empty then the script will automatically download the from below)
68 For type 'licenses' URL is : $scanList[licenses]
70 For type 'exceptions' URL is : $scanList[exceptions]";
72 $options = getopt(
"hcEend", array(
"type:",
"url:"));
74 $type = array_key_exists(
"type", $options) ? $options[
"type"] :
'';
75 $URL = array_key_exists(
"url", $options) ? $options[
"url"] :
'';
77 foreach ($options as $option => $optVal) {
82 $updateWithNew = $option;
85 $updateExisting = $option;
88 $addNewLicense = $option;
91 $deleteDeprecated =
true;
104 if (!empty($updateWithNew) || !empty($updateExisting) || !empty($addNewLicense)) {
105 if (!empty($type) && !empty($URL)) {
106 $newLicenseRefData = $this->
getListSPDX($type, $URL, $updateWithNew, $updateExisting, $addNewLicense,
107 $newLicenseRefData, $deleteDeprecated);
108 }
else if (!empty($type) && empty($URL)) {
109 echo
"Notice: --url cannot be empty if --type is provided \n";
110 }
else if (empty($type) && !empty($URL)) {
111 echo
"Notice: --type cannot be empty if --url is provided \n";
113 foreach ($scanList as $type => $URL) {
114 $newLicenseRefData = $this->
getListSPDX($type, $URL, $updateWithNew, $updateExisting, $addNewLicense,
115 $newLicenseRefData, $deleteDeprecated);
118 if (empty($newLicenseRefData)) {
119 echo
"\nERROR: No license data collected. Verify \$LIBEXECDIR ";
122 $newFileName =
"licenseRefNew.json";
123 if (file_exists($newFileName)) {
124 unlink($newFileName);
127 file_put_contents($newFileName, json_encode($newLicenseRefData, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES));
128 echo
"\n\n INFO: new $newFileName file created \n\n";
130 echo
"\nINVALID OPTION PROVIDED\n\n";
146 function getListSPDX($type, $URL, $updateWithNew, $updateExisting, $addNewLicense, $existingLicenseRefData,
151 if (!is_dir($LIBEXECDIR)) {
152 print
"FATAL: Directory '$LIBEXECDIR' does not exist.\n";
156 if (!is_readable($LIBEXECDIR)) {
157 print
"FATAL: Unable to access '$LIBEXECDIR'.\n";
161 $fileName =
"$LIBEXECDIR/licenseRef.json";
162 if (!file_exists($fileName)) {
163 print
"FATAL: File '$fileName' does not exist.\n";
167 if (empty($existingLicenseRefData)) {
168 echo
"INFO: get existing licenseRef.json from $LIBEXECDIR\n";
169 $getExistingLicenseRefData = file_get_contents(
"$fileName");
171 $existingLicenseRefData = (array) json_decode($getExistingLicenseRefData,
true);
174 if ($type ===
'licenses') {
176 if ($boaRaw !==
false) {
177 $boaJson = json_decode($boaRaw,
true);
178 foreach (($boaJson[
'ratings'] ?? []) as $rating) {
179 foreach (($rating[
'licenses'] ?? []) as $lic) {
180 if (!empty($lic[
'id'])) {
181 $boaIds[$lic[
'id']] =
true;
186 $boaCount = count($boaIds);
187 if ($boaCount === 0) {
188 echo
"WARNING: Blue Oak Council fetch failed or returned empty — Permissive classification degraded.\n";
190 echo
"INFO: loaded $boaCount permissive licenses from Blue Oak Council\n";
195 $rawList = file_get_contents($URL,
false, $httpCtx);
196 if ($rawList ===
false) {
197 print
"FATAL: Unable to fetch data from '$URL'.\n";
200 $getList = json_decode($rawList);
201 if ($getList ===
null || !isset($getList->$type)) {
202 print
"FATAL: Invalid data received from '$URL'.\n";
205 foreach ($getList->$type as $listValue) {
207 if (strstr($URL,
"spdx.org") !==
false) {
209 if (substr_compare($listValue->detailsUrl,
".html", -5) === 0) {
210 if (!isset($listValue->reference)) {
211 echo
"WARNING: Missing 'reference' field for '" . ($listValue->licenseExceptionId ??
'?') .
"', skipping.\n";
214 $baseUrl = str_replace(
"exceptions.json",
"", $URL);
215 $listValue->detailsUrl = $baseUrl . str_replace(
"./",
"", $listValue->reference);
218 $rawCurrentData = file_get_contents($listValue->detailsUrl,
false, $httpCtx);
219 if ($rawCurrentData ===
false) {
220 echo
"WARNING: Unable to fetch license details from '" . $listValue->detailsUrl .
"', skipping.\n";
223 $getCurrentData = json_decode($rawCurrentData,
true);
224 if (!is_array($getCurrentData) || empty($getCurrentData[$this->mapArrayData[$type][0]])) {
225 echo
"WARNING: Invalid or missing data from '" . $listValue->detailsUrl .
"', skipping.\n";
228 $getCurrentData = (array) $getCurrentData;
229 echo
"INFO: search for license " . $getCurrentData[$this->mapArrayData[$type][0]] .
"\n";
231 $licenseIdCheck = array_search($getCurrentData[$this->mapArrayData[$type][0]],
232 array_column($existingLicenseRefData,
'rf_shortname'));
233 $currentText = $this->
replaceUnicode($getCurrentData[$this->mapArrayData[$type][1]]);
234 $textCheck = array_search($currentText, array_column($existingLicenseRefData,
'rf_text'));
235 if ($deleteDeprecated && $listValue->isDeprecatedLicenseId && (
236 is_numeric($licenseIdCheck) &&
237 (!empty($updateWithNew) || !empty($updateExisting)))) {
239 echo
"INFO: removing deprecated license " .
240 $getCurrentData[$this->mapArrayData[$type][0]] .
"\n";
241 unset($existingLicenseRefData[$licenseIdCheck]);
242 $existingLicenseRefData = array_values($existingLicenseRefData);
244 } elseif ($listValue->isDeprecatedLicenseId) {
247 if (is_numeric($licenseIdCheck) &&
248 (!empty($updateWithNew) || !empty($updateExisting))) {
250 $existingLicenseRefData[$licenseIdCheck][
'rf_spdx_compatible'] =
251 $listValue->isDeprecatedLicenseId ?
"f" :
"t";
252 $existingLicenseRefData[$licenseIdCheck][
'rf_licensetype'] =
256 is_numeric($licenseIdCheck) &&
257 !is_numeric($textCheck) &&
258 (!empty($updateWithNew) ||
259 !empty($updateExisting)
262 $existingLicenseRefData[$licenseIdCheck][
'rf_fullname'] = $getCurrentData[$this->mapArrayData[$type][2]];
263 $existingLicenseRefData[$licenseIdCheck][
'rf_text'] = $currentText;
264 $existingLicenseRefData[$licenseIdCheck][
'rf_url'] = isset($getCurrentData[
'seeAlso'][0]) ? $getCurrentData[
'seeAlso'][0] : $existingLicenseRefData[$licenseIdCheck][
'rf_url'];
265 $existingLicenseRefData[$licenseIdCheck][
'rf_notes'] = (array_key_exists(
"licenseComments", $getCurrentData) ? $getCurrentData[
'licenseComments'] : $existingLicenseRefData[$licenseIdCheck][
'rf_notes']);
266 echo
"INFO: license " . $getCurrentData[$this->mapArrayData[$type][0]] .
" updated\n\n";
269 !is_numeric($licenseIdCheck) &&
270 !is_numeric($textCheck) &&
271 (!empty($updateWithNew) ||
272 !empty($addNewLicense)
275 $existingLicenseRefData[] = array(
276 'rf_shortname' => $getCurrentData[$this->mapArrayData[$type][0]],
277 'rf_text' => $currentText,
278 'rf_url' => isset($getCurrentData[
'seeAlso'][0]) ? $getCurrentData[
'seeAlso'][0] :
null,
279 'rf_add_date' =>
null,
280 'rf_copyleft' =>
null,
281 'rf_OSIapproved' =>
null,
282 'rf_fullname' => $getCurrentData[$this->mapArrayData[$type][2]],
283 'rf_FSFfree' =>
null,
284 'rf_GPLv2compatible' =>
null,
285 'rf_GPLv3compatible' =>
null,
286 'rf_notes' => (array_key_exists(
"licenseComments", $getCurrentData) ? $getCurrentData[
'licenseComments'] :
null),
290 'rf_text_updatable' =>
"f",
291 'rf_detector_type' => 1,
294 'rf_spdx_compatible' => $listValue->isDeprecatedLicenseId ?
"f" :
"t",
296 'rf_licensetype' => $this->getLicenseType($type, $getCurrentData, $boaIds),
298 echo
"INFO: new license " . $getCurrentData[$this->mapArrayData[$type][0]] .
" added\n\n";
301 return array_values($existingLicenseRefData);
311 if ($type ===
'exceptions') {
315 $licenseId = $licenseData[$this->mapArrayData[$type][0]] ??
'';
316 $name = strtolower($licenseData[$this->mapArrayData[$type][2]] ??
'');
317 $comments = strtolower($licenseData[
'licenseComments'] ??
'');
320 if (strpos($name,
'public domain') !==
false ||
321 strpos($name,
'unlicense') !==
false ||
322 strpos($comments,
'public domain') !==
false) {
323 return 'Public Domain';
327 if (strpos($name,
'creative commons') !==
false) {
328 if (strpos($licenseId,
'CC0') === 0 || strpos($licenseId,
'CC-PDM') === 0 ||
329 strpos($licenseId,
'CC-PDDC') === 0) {
330 return 'Public Domain';
332 if (strpos($licenseId,
'-NC-') !==
false ||
333 strpos($name,
'noncommercial') !==
false ||
334 strpos($name,
'non-commercial') !==
false) {
335 return 'Non-commercial';
338 if (strpos($licenseId,
'-ND') !==
false ||
339 strpos($name,
'noderivatives') !==
false ||
340 strpos($name,
'no derivatives') !==
false) {
341 return 'Source Available';
343 if (strpos($licenseId,
'-SA') !==
false || strpos($name,
'sharealike') !==
false) {
344 return 'Weak Copyleft';
349 if (strpos($licenseId,
'GFDL') === 0 ||
350 strpos($name,
'free documentation license') !==
false) {
351 return 'Weak Copyleft';
355 if (strpos($licenseId,
'No-Nuclear') !==
false ||
356 strpos($licenseId,
'No-Military') !==
false ||
357 strpos($name,
'no nuclear') !==
false ||
358 strpos($name,
'no military') !==
false) {
362 if (preg_match(
'/\bfont\b/i', $name)) {
366 if (strpos($name,
'database') !==
false ||
367 (strpos($name,
'data') !==
false && strpos($name,
'license') !==
false)) {
371 if (isset($boaIds[$licenseId])) {
375 $isCopyleft = strpos($name,
'copyleft') !==
false ||
376 strpos($comments,
'copyleft') !==
false ||
377 strpos($name,
'general public license') !==
false;
379 $isNetwork = strpos($name,
'affero') !==
false ||
380 strpos($name,
'network copyleft') !==
false ||
381 strpos($comments,
'affero') !==
false;
382 $isWeak = strpos($name,
'lesser') !==
false ||
383 strpos($name,
'limited') !==
false ||
384 strpos($name,
'weak copyleft') !==
false ||
385 strpos($comments,
'weak copyleft') !==
false ||
386 strpos($comments,
'lesser general public') !==
false;
388 return 'Network Copyleft';
391 return 'Weak Copyleft';
393 return 'Strong Copyleft';
396 if (strpos($name,
'non-commercial') !==
false ||
397 strpos($name,
'noncommercial') !==
false) {
398 return 'Non-commercial';
412 if ($text ===
null) {
435 return StringOperation::replaceUnicodeControlChar(str_replace($search,
446 for ($i = 0; $i < count($newLicenseRefData); $i++) {
447 $newLicenseRefData[$i][
"rf_fullname"] = $this->
replaceUnicode($newLicenseRefData[$i][
"rf_fullname"]);
448 $newLicenseRefData[$i][
"rf_text"] = $this->
replaceUnicode($newLicenseRefData[$i][
"rf_text"]);
449 $newLicenseRefData[$i][
"rf_notes"] = $this->
replaceUnicode($newLicenseRefData[$i][
"rf_notes"]);
462 $boaRaw = file_get_contents($url,
false, stream_context_create([
465 'user_agent' =>
'FOSSology/SPDX'
469 if ($boaRaw ===
false) {
470 throw new Exception(
"Failed to fetch BOA list");
474 }
catch (Exception $e) {
475 error_log(
"BOA List fetch error: " . $e->getMessage());
481 $obj->startProcessingLicenseData();
sanitizeRefData(&$newLicenseRefData)
getLicenseType($type, $licenseData, $boaIds)
Classify a license into rf_licensetype using name, comments, and BOA membership.
getListSPDX($type, $URL, $updateWithNew, $updateExisting, $addNewLicense, $existingLicenseRefData, $deleteDeprecated)
get SPDX license or exception list and update licenseref.json