FOSSology  4.7.1
Open Source License Compliance by Open Source Software
LicenseCompatibilityRuleController.php
Go to the documentation of this file.
1 <?php
2 /*
3  SPDX-FileCopyrightText: © 2026 Harshit Gandhi <gandhiharshit716@gmail.com>
4 
5  SPDX-License-Identifier: GPL-2.0-only
6 */
12 namespace Fossology\UI\Api\Controllers;
13 
25 use Psr\Container\ContainerInterface;
26 use Psr\Http\Message\ServerRequestInterface as Request;
27 use Slim\Psr7\Factory\StreamFactory;
28 
34 {
38  const PAGE_PARAM = "page";
42  const LIMIT_PARAM = "limit";
46  const SEARCH_PARAM = "search";
50  const RULE_FETCH_LIMIT = 100;
51 
57 
61  public function __construct($container)
62  {
63  parent::__construct($container);
64  $this->compatibilityDao = $this->container->get('dao.compatibility');
65  }
66 
76  public function getRules($request, $response, $args)
77  {
78  $this->throwNotAdminException();
79  $apiVersion = ApiVersion::getVersion($request);
80  $query = $request->getQueryParams();
81 
82  // Compare against "" instead of using empty(), which is true for "0".
83  $limit = $query[self::LIMIT_PARAM] ?? "";
84  if ($limit !== "") {
85  $limit = filter_var($limit, FILTER_VALIDATE_INT);
86  if ($limit === false || $limit < 1) {
87  throw new HttpBadRequestException(
88  "limit should be positive integer > 1");
89  }
90  } else {
91  $limit = self::RULE_FETCH_LIMIT;
92  }
93 
94  $searchTerm = $query[self::SEARCH_PARAM] ?? "";
95  if (! is_string($searchTerm)) {
96  throw new HttpBadRequestException("search should be a string");
97  }
98  $searchTerm = trim($searchTerm);
99  if (! empty($searchTerm)) {
100  $searchTerm = "%" . $searchTerm . "%";
101  }
102 
103  $totalPages = $this->compatibilityDao->getTotalRulesCount($searchTerm);
104  $totalPages = intval(ceil($totalPages / $limit));
105 
106  $page = $query[self::PAGE_PARAM] ?? "";
107  if ($page !== "") {
108  $page = filter_var($page, FILTER_VALIDATE_INT);
109  if ($page === false || $page < 1) {
110  throw new HttpBadRequestException(
111  "page should be positive integer > 0");
112  }
113  if ($totalPages != 0 && $page > $totalPages) {
114  throw (new HttpBadRequestException(
115  "Can not exceed total pages: $totalPages"))
116  ->setHeaders(["X-Total-Pages" => $totalPages]);
117  }
118  } else {
119  $page = 1;
120  }
121 
122  $rules = [];
123  foreach ($this->compatibilityDao->getAllRules($limit,
124  $limit * ($page - 1), $searchTerm) as $row) {
125  $rules[] = LicenseCompatibilityRule::fromArray($row)
126  ->getArray($apiVersion);
127  }
128  return $response->withHeader("X-Total-Pages", $totalPages)
129  ->withJson($rules, 200);
130  }
131 
141  public function createRule($request, $response, $args)
142  {
143  $this->throwNotAdminException();
144  $rule = $this->parseRule($this->getParsedBody($request), true);
145 
146  $ruleId = $this->compatibilityDao->insertRule($rule["firstLic"],
147  $rule["secondLic"], $rule["firstType"], $rule["secondType"],
148  $rule["comment"], $rule["result"]);
149  if ($ruleId < 0) {
151  "Unable to create the compatibility rule.");
152  }
153 
154  $info = new Info(201, "Rule $ruleId added successfully.", InfoType::INFO);
155  return $response->withJson($info->getArray(), $info->getCode());
156  }
157 
167  public function updateRule($request, $response, $args)
168  {
169  $this->throwNotAdminException();
170  $ruleId = intval($args['id']);
171  if ($this->compatibilityDao->getRuleById($ruleId) === null) {
172  throw new HttpNotFoundException("Compatibility rule does not exist.");
173  }
174 
175  $rule = $this->parseRule($this->getParsedBody($request), false);
176  if (empty($rule)) {
177  throw new HttpBadRequestException("No rule values provided to update.");
178  }
179 
180  try {
181  $updated = $this->compatibilityDao->updateRuleFromArray([$ruleId => $rule]);
182  } catch (\UnexpectedValueException $e) {
183  throw new HttpBadRequestException($e->getMessage(), $e);
184  }
185  if ($updated < 1) {
187  "Unable to update the compatibility rule.");
188  }
189 
190  $info = new Info(200, "Rule $ruleId updated successfully.", InfoType::INFO);
191  return $response->withJson($info->getArray(), $info->getCode());
192  }
193 
203  public function deleteRule($request, $response, $args)
204  {
205  $this->throwNotAdminException();
206  $ruleId = intval($args['id']);
207  if ($this->compatibilityDao->getRuleById($ruleId) === null) {
208  throw new HttpNotFoundException("Compatibility rule does not exist.");
209  }
210 
211  if (! $this->compatibilityDao->deleteRule($ruleId)) {
213  "Unable to delete the compatibility rule.");
214  }
215 
216  $info = new Info(200, "Rule $ruleId deleted successfully.", InfoType::INFO);
217  return $response->withJson($info->getArray(), $info->getCode());
218  }
219 
229  public function importRules($request, $response, $args)
230  {
231  $this->throwNotAdminException();
232  $apiVersion = ApiVersion::getVersion($request);
233 
234  $symReq = \Symfony\Component\HttpFoundation\Request::createFromGlobals();
236  $adminLicenseFromYaml = $this->restHelper->getPlugin('admin_license_from_yaml');
237 
238  $uploadedFile = $symReq->files->get(
239  $adminLicenseFromYaml->getFileInputName($apiVersion), null);
240 
241  $res = $adminLicenseFromYaml->handleFileUpload($uploadedFile, true);
242  if (! $res[0]) {
243  throw new HttpBadRequestException($res[1]);
244  }
245 
246  $newInfo = new Info($res[2], $res[1], InfoType::INFO);
247  return $response->withJson($newInfo->getArray(), $newInfo->getCode());
248  }
249 
259  public function exportRules($request, $response, $args)
260  {
261  $this->throwNotAdminException();
262  $query = $request->getQueryParams();
263  $ruleId = 0;
264  if (array_key_exists('id', $query)) {
265  $ruleId = intval($query['id']);
266  }
267  if ($ruleId != 0 &&
268  $this->compatibilityDao->getRuleById($ruleId) === null) {
269  throw new HttpNotFoundException("Compatibility rule does not exist.");
270  }
271 
272  $licenseYamlExport = new LicenseCompatibilityRulesYamlExport(
273  $this->dbHelper->getDbManager(), $this->compatibilityDao);
274  $content = $licenseYamlExport->createYaml($ruleId);
275  $fileName = "fossology-license-comp-rules-export-" . date("YMj-Gis");
276  $newResponse = $response
277  ->withHeader('Content-type', 'text/x-yaml; charset=UTF-8')
278  ->withHeader('Content-Disposition',
279  'attachment; filename=' . $fileName . '.yaml')
280  ->withHeader('Pragma', 'no-cache')
281  ->withHeader('Cache-Control',
282  'no-cache, must-revalidate, maxage=1, post-check=0, pre-check=0')
283  ->withHeader('Expires', 'Expires: Thu, 19 Nov 1981 08:52:00 GMT');
284  $sf = new StreamFactory();
285  return $newResponse->withBody(
286  $content ? $sf->createStream($content) : $sf->createStream('')
287  );
288  }
289 
303  private function parseRule($body, $isNewRule)
304  {
305  if (empty($body) || ! is_array($body)) {
306  throw new HttpBadRequestException("Invalid request body.");
307  }
308  $rule = [];
309 
310  list($exists, $value) = $this->getRuleField($body, "firstLicenseId",
311  "first_license_id");
312  if ($exists) {
313  $rule["firstLic"] = $this->validateLicenseId($value, "firstLicenseId");
314  }
315  list($exists, $value) = $this->getRuleField($body, "secondLicenseId",
316  "second_license_id");
317  if ($exists) {
318  $rule["secondLic"] = $this->validateLicenseId($value, "secondLicenseId");
319  }
320  list($exists, $value) = $this->getRuleField($body, "firstType",
321  "first_type");
322  if ($exists) {
323  $rule["firstType"] = $this->validateLicenseType($value, "firstType");
324  }
325  list($exists, $value) = $this->getRuleField($body, "secondType",
326  "second_type");
327  if ($exists) {
328  $rule["secondType"] = $this->validateLicenseType($value, "secondType");
329  }
330  if (array_key_exists("comment", $body)) {
331  if (! is_string($body["comment"]) || empty(trim($body["comment"]))) {
332  throw new HttpBadRequestException(
333  "comment should be a non-empty string.");
334  }
335  $rule["comment"] = trim($body["comment"]);
336  }
337  if (array_key_exists("compatibility", $body)) {
338  $compatibility = filter_var($body["compatibility"],
339  FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
340  if ($compatibility === null) {
341  throw new HttpBadRequestException("compatibility should be a boolean.");
342  }
343  $rule["result"] = $compatibility;
344  }
345 
346  if (! $isNewRule) {
347  return $rule;
348  }
349  foreach (["comment", "result"] as $mandatory) {
350  if (! array_key_exists($mandatory, $rule)) {
351  throw new HttpBadRequestException("comment and compatibility are " .
352  "required to create a rule.");
353  }
354  }
355  $rule += ["firstLic" => null, "secondLic" => null, "firstType" => null,
356  "secondType" => null];
357  if ($rule["firstLic"] === null && $rule["secondLic"] === null &&
358  $rule["firstType"] === null && $rule["secondType"] === null) {
359  throw new HttpBadRequestException("At least one license or license type " .
360  "is required to create a rule.");
361  }
362  return $rule;
363  }
364 
375  private function getRuleField($body, $nameV2, $nameV1)
376  {
377  if (array_key_exists($nameV2, $body)) {
378  return [true, $body[$nameV2]];
379  }
380  if (array_key_exists($nameV1, $body)) {
381  return [true, $body[$nameV1]];
382  }
383  return [false, null];
384  }
385 
393  private function validateLicenseId($licenseId, $field)
394  {
395  if ($licenseId === null || $licenseId === "") {
396  return null;
397  }
398  $licenseId = filter_var($licenseId, FILTER_VALIDATE_INT);
399  if ($licenseId === false || $licenseId < 1) {
400  throw new HttpBadRequestException("$field should be positive integer.");
401  }
402  if (! $this->dbHelper->doesIdExist("license_ref", "rf_pk", $licenseId)) {
403  throw new HttpBadRequestException(
404  "No license found with id '$licenseId'.");
405  }
406  return $licenseId;
407  }
408 
416  private function validateLicenseType($licenseType, $field)
417  {
418  if ($licenseType === null || $licenseType === "") {
419  return null;
420  }
421  if (! is_string($licenseType)) {
422  throw new HttpBadRequestException("$field should be a string.");
423  }
424  $licenseType = trim($licenseType);
425  $licenseTypes = $this->getLicenseTypes();
426  if (! in_array($licenseType, $licenseTypes)) {
427  throw new HttpBadRequestException("Invalid $field '$licenseType', " .
428  "allowed values are: " . implode(", ", $licenseTypes) . ".");
429  }
430  return $licenseType;
431  }
432 
437  private function getLicenseTypes()
438  {
439  global $SysConf;
440 
441  $licenseTypes = $SysConf['SYSCONFIG']['LicenseTypes'] ?? "";
442  return array_filter(array_map('trim', explode(',', $licenseTypes)));
443  }
444 }
Helper class to export license list as a YAML from the DB.
validateLicenseType($licenseType, $field)
Validate a license type sent in the request body.
getRuleField($body, $nameV2, $nameV1)
Read a rule field from the request body.
parseRule($body, $isNewRule)
Validate the rule values sent in the request body.
validateLicenseId($licenseId, $field)
Validate a license ID sent in the request body.
Base controller for REST calls.
getParsedBody(ServerRequestInterface $request)
Parse request body as JSON and return associative PHP array.
Override Slim response for withJson function.
static getVersion(ServerRequestInterface $request)
Definition: ApiVersion.php:29
Different type of infos provided by REST.
Definition: InfoType.php:16
Info model to contain general error and return values.
Definition: Info.php:19
Model to hold a single license compatibility rule.
char * trim(char *ptext)
Trimming whitespace.
Definition: fossconfig.c:690