1<?php 2/*********************************************************** 3 Copyright (C) 2008-2014 Hewlett-Packard Development Company, L.P. 4 Copyright (C) 2015-2018, Siemens AG 5 6 This program is free software; you can redistribute it and/or 7 modify it under the terms of the GNU General Public License 8 version 2 as published by the Free Software Foundation. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License along 16 with this program; if not, write to the Free Software Foundation, Inc., 17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18 ***********************************************************/ 19 20use Fossology\Lib\BusinessRules\LicenseMap; 21use Fossology\Lib\Db\DbManager; 22use Symfony\Component\HttpFoundation\Response; 23use Fossology\Lib\BusinessRules\ObligationMap; 24use Fossology\Lib\Util\StringOperation; 25 26define("TITLE_ADMIN_LICENSE_FILE", _("License Administration")); 27 28class admin_license_file extends FO_Plugin 29{ 30 /** @var DbManager */ 31 private $dbManager; 32 33 function __construct() 34 { 35 $this->Name = "admin_license"; 36 $this->Title = TITLE_ADMIN_LICENSE_FILE; 37 $this->MenuList = "Admin::License Admin"; 38 $this->DBaccess = PLUGIN_DB_ADMIN; 39 $this->vars = array(); 40 $this->obligationSelectorName = "assocObligations"; ///< Selector name for obligation list 41 $this->obligationSelectorId = "assocObligations"; ///< Selector id for obligation list 42 parent::__construct(); 43 44 $this->dbManager = $GLOBALS['container']->get('db.manager'); 45 } 46 47 /** 48 * \brief Customize submenus. 49 */ 50 function RegisterMenus() 51 { 52 if ($this->State != PLUGIN_STATE_READY) { 53 return(0); 54 } 55 56 $URL = $this->Name."&add=y"; 57 $text = _("Add new license"); 58 menu_insert("Main::".$this->MenuList."::Add License",0, $URL, $text); 59 $URL = $this->Name; 60 $text = _("Select license family"); 61 menu_insert("Main::".$this->MenuList."::Select License",0, $URL, $text); 62 } 63 64 public function Output() 65 { 66 $V = ""; 67 $errorstr = "License not added"; 68 69 // update the db 70 if (@$_POST["updateit"]) { 71 $resultstr = $this->Updatedb($_POST); 72 $this->vars['message'] = $resultstr; 73 if (strstr($resultstr, $errorstr)) { 74 return $this->Updatefm(0); 75 } else { 76 return $this->Inputfm(); 77 } 78 } 79 80 if (@$_REQUEST['add'] == 'y') { 81 return $this->Updatefm(0); 82 } 83 84 // Add new rec to db 85 if (@$_POST["addit"]) { 86 $resultstr = $this->Adddb(); 87 $this->vars['message'] = $resultstr; 88 if (strstr($resultstr, $errorstr)) { 89 return $this->Updatefm(0); 90 } else { 91 return $this->Inputfm(); 92 } 93 } 94 95 // bring up the update form 96 $rf_pk = @$_REQUEST['rf_pk']; 97 if ($rf_pk) { 98 return $this->Updatefm($rf_pk); 99 } 100 101 // return a license text 102 if (@$_GET["getLicenseText"] && @$_GET["licenseID"]) { 103 $licenseText = $this->getLicenseTextForID(@$_GET["licenseID"]); 104 if (! $licenseText) { 105 return new Response("Error in querying license text.", 106 Response::HTTP_BAD_REQUEST, array( 107 'Content-type' => 'text/plain' 108 )); 109 } 110 return new Response($licenseText, Response::HTTP_OK, 111 array( 112 'Content-type' => 'text/plain' 113 )); 114 } 115 116 if (@$_POST["req_shortname"]) { 117 $this->vars += $this->getLicenseListData($_POST["req_shortname"], $_POST["req_marydone"]); 118 } 119 $this->vars['Inputfm'] = $this->Inputfm(); 120 return $this->render('admin_license_file.html.twig'); 121 } 122 123 /** 124 * \brief Build the input form 125 * 126 * \return The input form as a string 127 */ 128 function Inputfm() 129 { 130 $V = "<FORM name='Inputfm' action='?mod=" . $this->Name . "' method='POST'>"; 131 $V.= _("What license family do you wish to view:<br>"); 132 133 // qualify by marydone, short name and long name 134 // all are optional 135 $V.= "<p>"; 136 $V.= _("Filter: "); 137 $V.= "<select name='req_marydone'>\n"; 138 $Selected = (@$_REQUEST['req_marydone'] == 'all') ? " SELECTED ": ""; 139 $text = _("All"); 140 $V.= "<option value='all' $Selected> $text </option>"; 141 $Selected = (@$_REQUEST['req_marydone'] == 'done') ? " SELECTED ": ""; 142 $text = _("Checked"); 143 $V.= "<option value='done' $Selected> $text </option>"; 144 $Selected = (@$_REQUEST['req_marydone'] == 'notdone') ? " SELECTED ": ""; 145 $text = _("Not Checked"); 146 $V.= "<option value='notdone' $Selected> $text </option>"; 147 $V.= "</select>"; 148 $V.= " "; 149 150 // by short name -ajax-> fullname 151 $V.= _("License family name: "); 152 $Shortnamearray = $this->FamilyNames(); 153 $Shortnamearray = array("All"=>"All") + $Shortnamearray; 154 $Selected = @$_REQUEST['req_shortname']; 155 $Pulldown = Array2SingleSelect($Shortnamearray, "req_shortname", $Selected); 156 $V.= $Pulldown; 157 $V.= " "; 158 $text = _("Find"); 159 $V.= "<INPUT type='submit' value='$text'>\n"; 160 $V .= "</FORM>\n"; 161 $V.= "<hr>"; 162 163 return $V; 164 } 165 166 167 private function getLicenseData($where) 168 { 169 $sql = "SELECT rf_pk, marydone, rf_shortname, rf_spdx_compatible, " . 170 "rf_shortname, rf_fullname, rf_url, rf_text, ". 171 "string_agg(ob_topic, ';') AS ob_topic " . 172 "FROM ONLY license_ref " . 173 "LEFT OUTER JOIN obligation_map ON rf_fk = rf_pk " . 174 "LEFT OUTER JOIN obligation_ref ON ob_fk = ob_pk " . 175 "$where GROUP BY rf_pk ORDER BY rf_shortname"; 176 177 return $this->dbManager->getRows($sql); 178 } 179 180 /** 181 * \brief Build the input form 182 * 183 * \param $namestr - license family name 184 * \param $filter - marydone value requested 185 * 186 * \return The input form as a string 187 */ 188 function getLicenseListData($namestr, $filter) 189 { 190 // look at all 191 if ($namestr == "All") { 192 $where = ""; 193 } else { 194 $where = "where rf_shortname like '" . pg_escape_string($namestr) . "%' "; 195 } 196 197 // $filter is one of these: "all", "done", "notdone" 198 if ($filter != "all") { 199 if (empty($where)) { 200 $where .= "where "; 201 } else { 202 $where .= " and "; 203 } 204 if ($filter == "done") { 205 $where .= " marydone=true"; 206 } 207 if ($filter == "notdone") { 208 $where .= " marydone=false"; 209 } 210 } 211 212 $data = $this->getLicenseData($where); 213 if (! $data) { 214 $dataMessage = _( 215 "No licenses matching the filter and name pattern were found"); 216 } else { 217 $dataSize = sizeof($data); 218 $plural = ""; 219 220 if ($dataSize > 1) { 221 $plural = "s"; 222 } 223 $dataMessage = $dataSize . _(" License$plural found"); 224 } 225 226 return array( 227 'data' => $data, 228 'dataMessage' => $dataMessage, 229 'message' => "", 230 'tracebackURI' => Traceback_uri()); 231 } 232 233 function Updatefm($rf_pk) 234 { 235 $this->vars += $this->getUpdatefmData($rf_pk); 236 return $this->render('admin_license-upload_form.html.twig', $this->vars); 237 } 238 239 /** 240 * @brief Update forms 241 * @param int $rf_pk - for the license to update, empty to add 242 * @return string The input form 243 */ 244 function getUpdatefmData($rf_pk) 245 { 246 $vars = array(); 247 248 $rf_pk_update = ""; 249 250 if (0 < count($_POST)) { 251 $rf_pk_update = $_POST['rf_pk']; 252 if (! empty($rf_pk)) { 253 $rf_pk_update = $rf_pk; 254 } else if (empty($rf_pk_update)) { 255 $rf_pk_update = $_GET['rf_pk']; 256 } 257 } 258 259 $vars['obligationSelectorName'] = $this->obligationSelectorName . "[]"; 260 $vars['obligationSelectorId'] = $this->obligationSelectorId; 261 262 $vars['actionUri'] = "?mod=" . $this->Name . "&rf_pk=$rf_pk_update"; 263 $vars['req_marydone'] = array_key_exists('req_marydone', $_POST) ? $_POST['req_marydone'] : ''; 264 $vars['req_shortname'] = array_key_exists('req_shortname', $_POST) ? $_POST['req_shortname'] : ''; 265 $vars['req_marydone'] = array_key_exists('req_marydone', $_POST) ? $_POST['req_marydone'] : ''; 266 $vars['rf_shortname'] = array_key_exists('rf_shortname', $_POST) ? $_POST['rf_shortname'] : ''; 267 $vars['rf_fullname'] = array_key_exists('rf_fullname', $_POST) ? $_POST['rf_fullname'] : ''; 268 $vars['rf_text'] = array_key_exists('rf_text', $_POST) ? $_POST['rf_text'] : ''; 269 $selectedObligations = array_key_exists($this->obligationSelectorName, 270 $_POST) ? $_POST[$this->obligationSelectorName] : []; 271 272 $parentMap = new LicenseMap($this->dbManager, 0, LicenseMap::CONCLUSION); 273 $parentLicenes = $parentMap->getTopLevelLicenseRefs(); 274 $vars['parentMap'] = array(0=>'[self]'); 275 foreach ($parentLicenes as $licRef) { 276 $vars['parentMap'][$licRef->getId()] = $licRef->getShortName(); 277 } 278 279 $reportMap = new LicenseMap($this->dbManager, 0, LicenseMap::REPORT); 280 $reportLicenes = $reportMap->getTopLevelLicenseRefs(); 281 $vars['reportMap'] = array(0=>'[self]'); 282 foreach ($reportLicenes as $licRef) { 283 $vars['reportMap'][$licRef->getId()] = $licRef->getShortName(); 284 } 285 286 $obligationMap = new ObligationMap($this->dbManager); 287 $obligations = $obligationMap->getObligations(); 288 foreach ($obligations as $obligation) { 289 $vars['obligationTopics'][$obligation['ob_pk']] = $obligation['ob_topic']; 290 } 291 foreach ($selectedObligations as $obligation) { 292 $row['obligationSelected'][$obligation] = $obligationMap->getTopicNameFromId( 293 $obligation); 294 } 295 296 if ($rf_pk > 0) { // true if this is an update 297 $row = $this->dbManager->getSingleRow( 298 "SELECT * FROM ONLY license_ref WHERE rf_pk=$1", array($rf_pk), 299 __METHOD__ . '.forUpdate'); 300 if ($row === false) { 301 $text = _("ERROR: No licenses matching this key"); 302 $text1 = _("was found"); 303 return ["error" => "$text ($rf_pk) $text1."]; 304 } 305 $row['rf_parent'] = $parentMap->getProjectedId($rf_pk); 306 $row['rf_report'] = $reportMap->getProjectedId($rf_pk); 307 308 $obligationsAssigned = $parentMap->getObligationsForLicenseRef($rf_pk); 309 foreach ($obligationsAssigned as $obligation) { 310 $row['obligationSelected'][$obligation] = $obligationMap->getTopicNameFromId( 311 $obligation); 312 } 313 } else { 314 $row = array( 315 'rf_active' => 't', 316 'marydone' => 'f', 317 'rf_text_updatable' => 't', 318 'rf_parent' => 0, 319 'rf_report' => 0, 320 'rf_risk' => 0, 321 'rf_spdx_compatible' => 'f', 322 'rf_url' => '', 323 'rf_detector_type' => 1, 324 'rf_notes' => '' 325 ); 326 } 327 328 foreach (array_keys($row) as $key) { 329 if (array_key_exists($key, $_POST)) { 330 $row[$key] = $_POST[$key]; 331 } 332 } 333 334 $vars['boolYesNoMap'] = array("true"=>"Yes", "false"=>"No"); 335 $row['rf_active'] = $this->isTrue($row['rf_active']) ? 'true' : 'false'; 336 $row['marydone'] = $this->isTrue($row['marydone']) ? 'true' : 'false'; 337 $row['rf_text_updatable'] = $this->isTrue($row['rf_text_updatable']) ? 'true' : 'false'; 338 $row['rf_spdx_compatible'] = $this->isTrue($row['rf_spdx_compatible']) ? 'true' : 'false'; 339 $vars['risk_level'] = array_key_exists('risk_level', $_POST) ? intval($_POST['risk_level']) : $row['rf_risk']; 340 $vars['isReadOnly'] = !(empty($rf_pk) || $row['rf_text_updatable']=='true'); 341 $vars['detectorTypes'] = array("1"=>"Reference License", "2"=>"Nomos", "3"=>"Unconcrete License"); 342 343 $vars['rfId'] = $rf_pk?:$rf_pk_update; 344 345 return array_merge($vars,$row); 346 } 347 348 /** @brief Check if a variable is true 349 * @param mixed $value 350 * @return boolean 351 */ 352 private function isTrue($value) 353 { 354 if (is_bool($value)) { 355 return $value; 356 } else { 357 $value = strtolower($value); 358 return ($value === 't' || $value === 'true'); 359 } 360 } 361 362 /** @brief check if shortname or license text of this license is existing */ 363 private function isShortnameBlocked($rfId, $shortname, $text) 364 { 365 $sql = "SELECT count(*) from license_ref where rf_pk <> $1 and (LOWER(rf_shortname) = LOWER($2) or (rf_text <> '' 366 and rf_text = $3 and LOWER(rf_text) NOT LIKE 'license by nomos.'))"; 367 $check_count = $this->dbManager->getSingleRow($sql, 368 array( 369 $rfId, 370 $shortname, 371 $text 372 ), __METHOD__ . '.countLicensesByNomos'); 373 return (0 < $check_count['count']); 374 } 375 376 /** @brief check if shortname is changed */ 377 private function isShortNameExists($rfId, $shortname) 378 { 379 $sql = "SELECT LOWER(rf_shortname) AS rf_shortname FROM license_ref WHERE rf_pk=$1"; 380 $row = $this->dbManager->getSingleRow($sql,array($rfId),__METHOD__.'.DoNotChnageShortName'); 381 if ($row['rf_shortname'] === strtolower($shortname)) { 382 return 1; 383 } else { 384 return 0; 385 } 386 } 387 388 /** 389 * \brief Update the database 390 * 391 * \return An update status string 392 */ 393 function Updatedb() 394 { 395 $rfId = intval($_POST['rf_pk']); 396 $shortname = StringOperation::replaceUnicodeControlChar(trim($_POST['rf_shortname'])); 397 $fullname = StringOperation::replaceUnicodeControlChar(trim($_POST['rf_fullname'])); 398 $url = $_POST['rf_url']; 399 $notes = $_POST['rf_notes']; 400 $text = StringOperation::replaceUnicodeControlChar(trim($_POST['rf_text'])); 401 $parent = $_POST['rf_parent']; 402 $report = $_POST['rf_report']; 403 $riskLvl = intval($_POST['risk_level']); 404 $selectedObligations = array_key_exists($this->obligationSelectorName, 405 $_POST) ? $_POST[$this->obligationSelectorName] : []; 406 407 if (empty($shortname)) { 408 $text = _("ERROR: The license shortname is empty. License not added."); 409 return "<b>$text</b><p>"; 410 } 411 412 if (!$this->isShortNameExists($rfId,$shortname)) { 413 $text = _("ERROR: The shortname can not be changed. License not added."); 414 return "<b>$text</b><p>"; 415 } 416 417 $md5term = (empty($text) || stristr($text, "License by Nomos")) ? 'null' : 'md5($10)'; 418 419 $sql = "UPDATE license_ref SET 420 rf_active=$2, marydone=$3, rf_shortname=$4, rf_fullname=$5, 421 rf_url=$6, rf_notes=$7, rf_text_updatable=$8, rf_detector_type=$9, rf_text=$10, 422 rf_md5=$md5term, rf_risk=$11, rf_spdx_compatible=$12, rf_flag=$13 423 WHERE rf_pk=$1"; 424 $params = array($rfId, 425 $_POST['rf_active'],$_POST['marydone'],$shortname,$fullname, 426 $url,$notes,$_POST['rf_text_updatable'],$_POST['rf_detector_type'],$text, 427 $riskLvl,$_POST['rf_spdx_compatible'],2); 428 $statement = __METHOD__ . ".updateLicense"; 429 if ($md5term == "null") { 430 $statement .= ".nullMD5"; 431 } 432 $this->dbManager->prepare($statement, $sql); 433 $this->dbManager->freeResult($this->dbManager->execute($statement, $params)); 434 435 $licenseMapDelStatement = __METHOD__ . '.deleteFromMap'; 436 $licenseMapDelSql = 'DELETE FROM license_map WHERE rf_fk=$1 AND usage=$2'; 437 $this->dbManager->prepare($licenseMapDelStatement, $licenseMapDelSql); 438 439 $parentMap = new LicenseMap($this->dbManager, 0, LicenseMap::CONCLUSION); 440 if ($parent == 0) { 441 // Update conclusion to self 442 $this->dbManager->execute($licenseMapDelStatement, 443 array($rfId, LicenseMap::CONCLUSION)); 444 } else { 445 $parentLicenses = $parentMap->getTopLevelLicenseRefs(); 446 if (array_key_exists($parent, $parentLicenses) && 447 $parent != $parentMap->getProjectedId($rfId)) { 448 $this->dbManager->execute($licenseMapDelStatement, 449 array($rfId, LicenseMap::CONCLUSION)); 450 $this->dbManager->insertTableRow('license_map', 451 array('rf_fk'=>$rfId, 'rf_parent'=>$parent, 'usage'=>LicenseMap::CONCLUSION)); 452 } 453 } 454 455 if ($report == 0) { 456 // Update report to self 457 $this->dbManager->execute($licenseMapDelStatement, 458 array($rfId, LicenseMap::REPORT)); 459 } else { 460 $reportMap = new LicenseMap($this->dbManager, 0, LicenseMap::REPORT); 461 $reportLicenses = $parentMap->getTopLevelLicenseRefs(); 462 if (array_key_exists($report, $reportLicenses) && 463 $report != $reportMap->getProjectedId($rfId)) { 464 $this->dbManager->execute($licenseMapDelStatement, 465 array($rfId, LicenseMap::REPORT)); 466 $this->dbManager->insertTableRow('license_map', 467 array('rf_fk'=>$rfId, 'rf_parent'=>$report, 'usage'=>LicenseMap::REPORT)); 468 } 469 } 470 471 $obligationMap = new ObligationMap($this->dbManager); 472 foreach ($selectedObligations as $obligation) { 473 $obligationMap->associateLicenseWithObligation($obligation, $rfId); 474 } 475 476 $allObligations = $parentMap->getObligationsForLicenseRef($rfId); 477 $removedObligations = array_diff($allObligations, $selectedObligations); 478 foreach ($removedObligations as $obligation) { 479 $obligationMap->unassociateLicenseFromObligation($obligation, $rfId); 480 } 481 482 $ob = "License $_POST[rf_shortname] updated.<p>"; 483 return $ob; 484 } 485 486 487 /** 488 * \brief Add a new license_ref to the database 489 * 490 * \return An add status string 491 */ 492 function Adddb() 493 { 494 $rf_shortname = StringOperation::replaceUnicodeControlChar(trim($_POST['rf_shortname'])); 495 $rf_fullname = StringOperation::replaceUnicodeControlChar(trim($_POST['rf_fullname'])); 496 $rf_url = $_POST['rf_url']; 497 $rf_notes = $_POST['rf_notes']; 498 $rf_text = StringOperation::replaceUnicodeControlChar(trim($_POST['rf_text'])); 499 $parent = $_POST['rf_parent']; 500 $report = $_POST['rf_report']; 501 $riskLvl = intval($_POST['risk_level']); 502 $selectedObligations = array_key_exists($this->obligationSelectorName, 503 $_POST) ? $_POST[$this->obligationSelectorName] : []; 504 505 if (empty($rf_shortname)) { 506 $text = _("ERROR: The license shortname is empty. License not added."); 507 return "<b>$text</b><p>"; 508 } 509 510 if ($this->isShortnameBlocked(0,$rf_shortname,$rf_text)) { 511 $text = _("ERROR: The shortname or license text already exist in the license list. License not added."); 512 return "<b>$text</b><p>"; 513 } 514 515 $md5term = (empty($rf_text) || stristr($rf_text, "License by Nomos")) ? 'null' : 'md5($7)'; 516 $stmt = __METHOD__.'.rf'; 517 $sql = "INSERT into license_ref ( 518 rf_active, marydone, rf_shortname, rf_fullname, 519 rf_url, rf_notes, rf_md5, rf_text, rf_text_updatable, 520 rf_detector_type, rf_risk, rf_spdx_compatible) 521 VALUES ( 522 $1, $2, $3, $4, $5, $6, $md5term, $7, $8, $9, $10, $11) RETURNING rf_pk"; 523 $this->dbManager->prepare($stmt,$sql); 524 $res = $this->dbManager->execute($stmt, 525 array( 526 $_POST['rf_active'], 527 $_POST['marydone'], 528 $rf_shortname, 529 $rf_fullname, 530 $rf_url, 531 $rf_notes, 532 $rf_text, 533 $_POST['rf_text_updatable'], 534 $_POST['rf_detector_type'], 535 $riskLvl, 536 $_POST['rf_spdx_compatible'] 537 )); 538 $row = $this->dbManager->fetchArray($res); 539 $rfId = $row['rf_pk']; 540 541 $parentMap = new LicenseMap($this->dbManager, 0, LicenseMap::CONCLUSION); 542 $parentLicenses = $parentMap->getTopLevelLicenseRefs(); 543 if (array_key_exists($parent, $parentLicenses)) { 544 $this->dbManager->insertTableRow('license_map', 545 array( 546 'rf_fk' => $rfId, 547 'rf_parent' => $parent, 548 'usage' => LicenseMap::CONCLUSION 549 )); 550 } 551 552 $reportMap = new LicenseMap($this->dbManager, 0, LicenseMap::REPORT); 553 $reportLicenses = $reportMap->getTopLevelLicenseRefs(); 554 if (array_key_exists($report, $reportLicenses)) { 555 $this->dbManager->insertTableRow('license_map', 556 array( 557 'rf_fk' => $rfId, 558 'rf_parent' => $report, 559 'usage' => LicenseMap::REPORT 560 )); 561 } 562 563 $obligationMap = new ObligationMap($this->dbManager); 564 foreach ($selectedObligations as $obligation) { 565 $obligationMap->associateLicenseWithObligation($obligation, $rfId); 566 } 567 568 return "License $_POST[rf_shortname] (id=$rfId) added.<p>"; 569 } 570 571 572 /** 573 * \brief get an array of family names based on the 574 * 575 * \return an array of family names based on the 576 * license_ref.shortname. 577 * A family name is the name before most punctuation. 578 * 579 * \example the family name of "GPL V2" is "GPL" 580 */ 581 function FamilyNames() 582 { 583 $familynamearray = array(); 584 $Shortnamearray = DB2KeyValArray("license_ref", "rf_pk", "rf_shortname", " order by rf_shortname"); 585 586 // truncate each name to the family name 587 foreach ($Shortnamearray as $shortname) { 588 // start with exceptions 589 if (($shortname == "No_license_found") || ($shortname == "Unknown license")) { 590 $familynamearray[$shortname] = $shortname; 591 } else { 592 $tok = strtok($shortname, " _-([/"); 593 $familynamearray[$tok] = $tok; 594 } 595 } 596 597 return ($familynamearray); 598 } 599 600 private function getLicenseTextForID($licenseID) 601 { 602 $sql = "select rf_text from license_ref where rf_pk=$1"; 603 $result = $this->dbManager->getSingleRow($sql, array($licenseID)); 604 605 if (! $result) { 606 return false; 607 } 608 return $result['rf_text']; 609 } 610} 611 612$NewPlugin = new admin_license_file(); 613