1<?php 2/* 3Copyright (C) 2014-2018,2020, Siemens AG 4Author: Johannes Najjar 5 6This program is free software; you can redistribute it and/or 7modify it under the terms of the GNU General Public License 8version 2 as published by the Free Software Foundation. 9 10This program is distributed in the hope that it will be useful, 11but WITHOUT ANY WARRANTY; without even the implied warranty of 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13GNU General Public License for more details. 14 15You should have received a copy of the GNU General Public License along 16with this program; if not, write to the Free Software Foundation, Inc., 1751 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 18*/ 19 20namespace Fossology\Lib\Dao; 21 22use Fossology\Lib\BusinessRules\ClearingDecisionProcessor; 23use Fossology\Lib\Data\AgentRef; 24use Fossology\Lib\Data\Clearing\ClearingEvent; 25use Fossology\Lib\Data\Clearing\ClearingEventBuilder; 26use Fossology\Lib\Data\Clearing\ClearingEventTypes; 27use Fossology\Lib\Data\ClearingDecision; 28use Fossology\Lib\Data\ClearingDecisionBuilder; 29use Fossology\Lib\Data\DecisionScopes; 30use Fossology\Lib\Data\DecisionTypes; 31use Fossology\Lib\Data\LicenseRef; 32use Fossology\Lib\Data\Tree\ItemTreeBounds; 33use Fossology\Lib\Db\DbManager; 34use Fossology\Lib\Proxy\UploadTreeProxy; 35use Fossology\Lib\Proxy\ScanJobProxy; 36use Fossology\Lib\Util\StringOperation; 37use Monolog\Logger; 38 39class ClearingDao 40{ 41 /** @var DbManager */ 42 private $dbManager; 43 /** @var Logger */ 44 private $logger; 45 /** @var UploadDao */ 46 private $uploadDao; 47 /** @var CopyrightDao */ 48 private $copyrightDao; 49 /** @var LicenseRef[] */ 50 private $licenseRefCache; 51 52 /** 53 * @param DbManager $dbManager 54 * @param UploadDao $uploadDao 55 */ 56 function __construct(DbManager $dbManager, UploadDao $uploadDao) 57 { 58 $this->dbManager = $dbManager; 59 $this->logger = new Logger(self::class); 60 $this->uploadDao = $uploadDao; 61 $this->licenseRefCache = array(); 62 global $container; 63 $this->copyrightDao = $container->get('dao.copyright'); 64 } 65 66 private function getRelevantDecisionsCte(ItemTreeBounds $itemTreeBounds, $groupId, $onlyCurrent, &$statementName, &$params, $condition="") 67 { 68 $uploadTreeTable = $itemTreeBounds->getUploadTreeTableName(); 69 70 $params[] = DecisionTypes::WIP; $p1 = "$". count($params); 71 $params[] = $groupId; $p2 = "$". count($params); 72 73 $sql_upload = ""; 74 if ('uploadtree' === $uploadTreeTable || 'uploadtree_a' === $uploadTreeTable) { 75 $params[] = $itemTreeBounds->getUploadId(); $p = "$". count($params); 76 $sql_upload = " AND ut.upload_fk=$p"; 77 } 78 if (!empty($condition)) { 79 $statementName .= ".(".$condition.")"; 80 $condition = " AND $condition"; 81 } 82 83 $filterClause = $onlyCurrent ? "DISTINCT ON(itemid)" : ""; 84 $sortClause = $onlyCurrent ? "ORDER BY itemid, scope, id DESC" : ""; 85 86 $statementName .= "." . $uploadTreeTable . ($onlyCurrent ? ".current": ""); 87 88 $globalScope = DecisionScopes::REPO; 89 $localScope = DecisionScopes::ITEM; 90 91 return "WITH decision AS ( 92 SELECT 93 $filterClause 94 cd.clearing_decision_pk AS id, 95 cd.pfile_fk AS pfile_id, 96 ut.uploadtree_pk AS itemid, 97 cd.user_fk AS user_id, 98 cd.decision_type AS type_id, 99 cd.scope AS scope, 100 EXTRACT(EPOCH FROM cd.date_added) AS ts_added 101 FROM clearing_decision cd 102 INNER JOIN $uploadTreeTable ut 103 ON ( 104 (ut.pfile_fk = cd.pfile_fk AND cd.scope = $globalScope) 105 OR (ut.uploadtree_pk = cd.uploadtree_fk 106 AND cd.scope = $localScope AND cd.group_fk = $p2)) 107 $sql_upload $condition 108 WHERE cd.decision_type != $p1 109 $sortClause 110 )"; 111 } 112 113 /** 114 * @param ItemTreeBounds $itemTreeBounds 115 * @param int $groupId 116 * @return LicenseRef[] 117 */ 118 function getClearedLicenses(ItemTreeBounds $itemTreeBounds, $groupId) 119 { 120 $statementName = __METHOD__; 121 122 $params = array($itemTreeBounds->getLeft(), $itemTreeBounds->getRight()); 123 $condition = "ut.lft BETWEEN $1 AND $2"; 124 125 $decisionsCte = $this->getRelevantDecisionsCte($itemTreeBounds, $groupId, $onlyCurrent=true, $statementName, $params, $condition); 126 $params[] = DecisionTypes::IRRELEVANT; 127 $sql = "$decisionsCte 128 SELECT 129 lr.rf_pk AS license_id, 130 lr.rf_shortname AS shortname, 131 lr.rf_fullname AS fullname 132 FROM decision 133 INNER JOIN clearing_decision_event cde ON cde.clearing_decision_fk = decision.id 134 INNER JOIN clearing_event ce ON 135 (ce.clearing_event_pk = cde.clearing_event_fk AND NOT ce.removed) 136 INNER JOIN license_ref lr ON lr.rf_pk = ce.rf_fk 137 WHERE type_id != $".count($params)." 138 GROUP BY license_id,shortname,fullname"; 139 140 $this->dbManager->prepare($statementName, $sql); 141 142 $res = $this->dbManager->execute($statementName, $params); 143 144 $licenses = array(); 145 while ($row = $this->dbManager->fetchArray($res)) { 146 $licenses[] = new LicenseRef($row['license_id'], $row['shortname'], $row['fullname']); 147 } 148 $this->dbManager->freeResult($res); 149 150 return $licenses; 151 } 152 153 154 /** 155 * @param ItemTreeBounds $itemTreeBounds 156 * @param int $groupId 157 * @param bool $onlyCurrent 158 * @return ClearingDecision[] 159 */ 160 function getFileClearings(ItemTreeBounds $itemTreeBounds, $groupId, $onlyCurrent=true, $forClearingHistory=false) 161 { 162 $this->dbManager->begin(); 163 164 $statementName = __METHOD__; 165 166 $params = array($itemTreeBounds->getItemId()); 167 $condition = "ut.uploadtree_pk = $1"; 168 169 $decisionsCte = $this->getRelevantDecisionsCte($itemTreeBounds, $groupId, $onlyCurrent, $statementName, $params, $condition); 170 171 $clearingsWithLicensesArray = $this->getDecisionsFromCte($decisionsCte, $statementName, $params, $forClearingHistory); 172 173 $this->dbManager->commit(); 174 return $clearingsWithLicensesArray; 175 } 176 177 /** 178 * @param ItemTreeBounds $itemTreeBounds 179 * @param int $groupId 180 * @param bool $includeSubFolders 181 * @param bool $onlyCurrent 182 * @return ClearingDecision[] 183 */ 184 function getFileClearingsFolder(ItemTreeBounds $itemTreeBounds, $groupId, $includeSubFolders=true, $onlyCurrent=true) 185 { 186 $this->dbManager->begin(); 187 188 $statementName = __METHOD__; 189 190 if (!$includeSubFolders) { 191 $params = array($itemTreeBounds->getItemId()); 192 $condition = "ut.realparent = $1"; 193 } else { 194 $params = array($itemTreeBounds->getLeft(), $itemTreeBounds->getRight()); 195 $condition = "ut.lft BETWEEN $1 AND $2"; 196 } 197 198 $decisionsCte = $this->getRelevantDecisionsCte($itemTreeBounds, $groupId, $onlyCurrent, $statementName, $params, $condition); 199 200 $clearingsWithLicensesArray = $this->getDecisionsFromCte($decisionsCte, $statementName, $params); 201 202 $this->dbManager->commit(); 203 return $clearingsWithLicensesArray; 204 } 205 206 /** 207 * @param string $decisionsCte 208 * @param string $statementName 209 * @param array $params 210 * @return ClearingDecision[] 211 */ 212 private function getDecisionsFromCte($decisionsCte, $statementName, $params, $forClearingHistory=false) 213 { 214 $sql = "$decisionsCte 215 SELECT 216 decision.*, 217 users.user_name AS user_name, 218 ce.clearing_event_pk as event_id, 219 ce.user_fk as event_user_id, 220 ce.group_fk as event_group_id, 221 lr.rf_pk AS license_id, 222 lr.rf_shortname AS shortname, 223 lr.rf_fullname AS fullname, 224 ce.removed AS removed, 225 ce.type_fk AS event_type_id, 226 ce.reportinfo AS reportinfo, 227 ce.comment AS comment, 228 ce.acknowledgement AS acknowledgement 229 FROM decision 230 LEFT JOIN users ON decision.user_id = users.user_pk 231 LEFT JOIN clearing_decision_event cde ON cde.clearing_decision_fk = decision.id 232 LEFT JOIN clearing_event ce ON ce.clearing_event_pk = cde.clearing_event_fk 233 LEFT JOIN license_ref lr ON lr.rf_pk = ce.rf_fk 234 ORDER BY decision.id DESC, event_id ASC"; 235 236 $this->dbManager->prepare($statementName, $sql); 237 238 $result = $this->dbManager->execute($statementName, $params); 239 $clearingsWithLicensesArray = array(); 240 241 $previousClearingId = -1; 242 $previousItemId = -1; 243 $clearingEvents = array(); 244 $clearingEventCache = array(); 245 $clearingDecisionBuilder = ClearingDecisionBuilder::create(); 246 $firstMatch = true; 247 while ($row = $this->dbManager->fetchArray($result)) { 248 $clearingId = $row['id']; 249 $itemId = $row['itemid']; 250 $licenseId = $row['license_id']; 251 $eventId = $row['event_id']; 252 $licenseShortName = $row['shortname']; 253 $licenseName = $row['fullname']; 254 $licenseIsRemoved = $row['removed']; 255 256 $eventType = $row['event_type_id']; 257 $eventUserId = $row['event_user_id']; 258 $eventGroupId = $row['event_group_id']; 259 $comment = $row['comment']; 260 $reportInfo = $row['reportinfo']; 261 $acknowledgement = $row['acknowledgement']; 262 263 if ($clearingId !== $previousClearingId && $itemId !== $previousItemId) { 264 //store the old one 265 if (!$firstMatch) { 266 $clearingsWithLicensesArray[] = $clearingDecisionBuilder->setClearingEvents($clearingEvents)->build(); 267 } 268 269 $firstMatch = false; 270 //prepare the new one 271 if ($forClearingHistory) { 272 $previousClearingId = $clearingId; 273 } else { 274 $previousItemId = $itemId; 275 } 276 $clearingEvents = array(); 277 $clearingDecisionBuilder = ClearingDecisionBuilder::create() 278 ->setClearingId($row['id']) 279 ->setUploadTreeId($itemId) 280 ->setPfileId($row['pfile_id']) 281 ->setUserName($row['user_name']) 282 ->setUserId($row['user_id']) 283 ->setType(intval($row['type_id'])) 284 ->setScope(intval($row['scope'])) 285 ->setTimeStamp($row['ts_added']); 286 } 287 288 if ($licenseId !== null) { 289 if (!array_key_exists($eventId, $clearingEventCache)) { 290 if (!array_key_exists($licenseId, $this->licenseRefCache)) { 291 $this->licenseRefCache[$licenseId] = new LicenseRef($licenseId, $licenseShortName, $licenseName); 292 } 293 $licenseRef = $this->licenseRefCache[$licenseId]; 294 $clearingEventCache[$eventId] = $this->buildClearingEvent($eventId, $eventUserId, $eventGroupId, $licenseRef, $licenseIsRemoved, $eventType, $reportInfo, $comment, $acknowledgement); 295 } 296 $clearingEvents[] = $clearingEventCache[$eventId]; 297 } 298 } 299 300 //! Add the last match 301 if (!$firstMatch) { 302 $clearingsWithLicensesArray[] = $clearingDecisionBuilder->setClearingEvents($clearingEvents)->build(); 303 } 304 $this->dbManager->freeResult($result); 305 306 return $clearingsWithLicensesArray; 307 } 308 /** 309 * @param ItemTreeBounds $itemTreeBounds 310 * @param int $groupId 311 * @return ClearingDecision|null 312 */ 313 public function getRelevantClearingDecision(ItemTreeBounds $itemTreeBounds, $groupId) 314 { 315 $clearingDecisions = $this->getFileClearings($itemTreeBounds, $groupId); 316 if (count($clearingDecisions) > 0) { 317 return $clearingDecisions[0]; 318 } 319 return null; 320 } 321 322 /** 323 * @param int $uploadTreeId 324 * @param int $groupId 325 */ 326 public function removeWipClearingDecision($uploadTreeId, $groupId) 327 { 328 $sql = "DELETE FROM clearing_decision WHERE uploadtree_fk=$1 AND group_fk=$2 AND decision_type=$3"; 329 $this->dbManager->prepare($stmt = __METHOD__, $sql); 330 $this->dbManager->freeResult($this->dbManager->execute($stmt, array($uploadTreeId, $groupId, DecisionTypes::WIP))); 331 } 332 333 /** 334 * @param int $uploadTreeId 335 * @param int $userId 336 * @param int $groupId 337 * @param int $decType 338 * @param int $scope 339 */ 340 public function createDecisionFromEvents($uploadTreeId, $userId, $groupId, $decType, $scope, $eventIds) 341 { 342 if ( ($scope == DecisionScopes::REPO) && 343 !empty($this->getCandidateLicenseCountForCurrentDecisions($uploadTreeId))) { 344 throw new \Exception( _("Cannot add candidate license as global decision\n") ); 345 } 346 347 $itemTreeBounds = $this->uploadDao->getItemTreeBounds($uploadTreeId); 348 $uploadId = $itemTreeBounds->getUploadId(); 349 $uploadTreeTable = $this->uploadDao->getUploadtreeTableName($uploadId); 350 $itemTreeBounds = $this->uploadDao->getItemTreeBounds($uploadTreeId, $uploadTreeTable); 351 352 if ($this->isDecisionIrrelevant($uploadTreeId, $groupId)) { 353 $this->copyrightDao->updateTable($itemTreeBounds, '', '', $userId, 'copyright', 'rollback'); 354 } else if ($decType == DecisionTypes::IRRELEVANT) { 355 $this->copyrightDao->updateTable($itemTreeBounds, '', '', $userId, 'copyright', 'delete', '2'); 356 } 357 358 $this->dbManager->begin(); 359 360 $this->removeWipClearingDecision($uploadTreeId, $groupId); 361 362 $statementName = __METHOD__; 363 $this->dbManager->prepare($statementName, 364 " 365INSERT INTO clearing_decision ( 366 uploadtree_fk, 367 pfile_fk, 368 user_fk, 369 group_fk, 370 decision_type, 371 scope 372) VALUES ( 373 $1, 374 (SELECT pfile_fk FROM uploadtree WHERE uploadtree_pk=$1), 375 $2, 376 $3, 377 $4, 378 $5) RETURNING clearing_decision_pk 379 "); 380 $res = $this->dbManager->execute($statementName, 381 array($uploadTreeId, $userId, $groupId, $decType, $scope)); 382 $result = $this->dbManager->fetchArray($res); 383 $clearingDecisionId = $result['clearing_decision_pk']; 384 $this->dbManager->freeResult($res); 385 386 $statementNameClearingDecisionEventInsert = __METHOD__ . ".insertClearingDecisionEvent"; 387 $this->dbManager->prepare($statementNameClearingDecisionEventInsert, 388 "INSERT INTO clearing_decision_event (clearing_decision_fk, clearing_event_fk) VALUES($1, $2)" 389 ); 390 391 foreach ($eventIds as $eventId) { 392 $this->dbManager->freeResult($this->dbManager->execute($statementNameClearingDecisionEventInsert, array($clearingDecisionId, $eventId))); 393 } 394 395 $this->dbManager->commit(); 396 } 397 398 /** 399 * @param ItemTreeBounds $itemTreeBounds 400 * @param int $groupId 401 * @return ClearingEvent[] sorted by ts_added 402 */ 403 public function getRelevantClearingEvents($itemTreeBounds, $groupId, $includeSubFolders=true) 404 { 405 $decision = $this->getFileClearingsFolder($itemTreeBounds, $groupId, $includeSubFolders, $onlyCurrent=true); 406 $events = array(); 407 $date = 0; 408 409 if (count($decision)) { 410 foreach ($decision[0]->getClearingEvents() as $event) { 411 $events[$event->getLicenseId()] = $event; 412 } 413 $date = $decision[0]->getTimeStamp(); 414 } 415 416 $stmt = __METHOD__; 417 $sql = 'SELECT rf_fk,rf_shortname,rf_fullname,clearing_event_pk,comment,type_fk,removed,reportinfo,acknowledgement, EXTRACT(EPOCH FROM date_added) AS ts_added 418 FROM clearing_event LEFT JOIN license_ref ON rf_fk=rf_pk 419 WHERE uploadtree_fk=$1 AND group_fk=$2 AND date_added>to_timestamp($3) 420 ORDER BY clearing_event_pk ASC'; 421 $this->dbManager->prepare($stmt, $sql); 422 $res = $this->dbManager->execute($stmt,array($itemTreeBounds->getItemId(),$groupId,$date)); 423 424 while ($row = $this->dbManager->fetchArray($res)) { 425 $licenseRef = new LicenseRef($row['rf_fk'],$row['rf_shortname'],$row['rf_fullname']); 426 $events[$row['rf_fk']] = ClearingEventBuilder::create() 427 ->setEventId($row['clearing_event_pk']) 428 ->setComment($row['comment']) 429 ->setTimeStamp($row['ts_added']) 430 ->setEventType($row['type_fk']) 431 ->setLicenseRef($licenseRef) 432 ->setRemoved($this->dbManager->booleanFromDb($row['removed'])) 433 ->setReportinfo($row['reportinfo']) 434 ->setAcknowledgement($row['acknowledgement']) 435 ->setUploadTreeId($itemTreeBounds->getItemId()) 436 ->build(); 437 } 438 $this->dbManager->freeResult($res); 439 return $events; 440 } 441 442 /** 443 * @param int $uploadTreeId 444 * @param int $userId 445 * @param int $groupId 446 * @param int $licenseId 447 * @param string $what 448 * @param string $changeTo 449 */ 450 public function updateClearingEvent($uploadTreeId, $userId, $groupId, $licenseId, $what, $changeTo) 451 { 452 $this->dbManager->begin(); 453 454 $statementGetOldata = "SELECT * FROM clearing_event WHERE uploadtree_fk=$1 AND rf_fk=$2 AND group_fk=$3 ORDER BY clearing_event_pk DESC LIMIT 1"; 455 $statementName = __METHOD__ . 'getOld'; 456 $params = array($uploadTreeId, $licenseId, $groupId); 457 $row = $this->dbManager->getSingleRow($statementGetOldata, $params, $statementName); 458 459 if (!$row) { //The license was not added as user decision yet -> we promote it here 460 $type = ClearingEventTypes::USER; 461 $row['type_fk'] = $type; 462 $row['comment'] = ""; 463 $row['reportinfo'] = ""; 464 $row['acknowledgement'] = ""; 465 } 466 467 $changeTo = StringOperation::replaceUnicodeControlChar($changeTo, false); 468 if ($what == 'reportinfo') { 469 $reportInfo = $changeTo; 470 $comment = $row['comment']; 471 $acknowledgement = $row['acknowledgement']; 472 } elseif ($what == 'comment') { 473 $reportInfo = $row['reportinfo']; 474 $comment = $changeTo; 475 $acknowledgement = $row['acknowledgement']; 476 } else { 477 $reportInfo = $row['reportinfo']; 478 $comment = $row['comment']; 479 $acknowledgement = $changeTo; 480 } 481 $this->insertClearingEvent($uploadTreeId, $userId, $groupId, $licenseId, false, $row['type_fk'], $reportInfo, $comment, $acknowledgement); 482 483 $this->dbManager->commit(); 484 485 } 486 487 public function copyEventIdTo($eventId, $itemId, $userId, $groupId) 488 { 489 $stmt = __METHOD__; 490 $this->dbManager->prepare($stmt, 491 "INSERT INTO clearing_event(uploadtree_fk, user_fk, group_fk, type_fk, rf_fk, removed, reportinfo, comment, acknowledgement) 492 SELECT $2, $3, $4, type_fk, rf_fk, removed, reportinfo, comment, acknowledgement FROM clearing_event WHERE clearing_event_pk = $1" 493 ); 494 495 $this->dbManager->freeResult($this->dbManager->execute($stmt, array($eventId, $itemId, $userId, $groupId))); 496 } 497 498 /** 499 * @param int $uploadTreeId 500 * @param int $userId 501 * @param int $groupId 502 * @param int $licenseId 503 * @param bool $isRemoved 504 * @param int $type ClearingEventTypes 505 * @param string $reportInfo 506 * @param string $comment 507 * @param int $jobId 508 * @return int $clearing_event_pk 509 */ 510 public function insertClearingEvent($uploadTreeId, $userId, $groupId, $licenseId, $isRemoved, $type = ClearingEventTypes::USER, $reportInfo = '', $comment = '', $acknowledgement = '', $jobId=0) 511 { 512 $insertIsRemoved = $this->dbManager->booleanToDb($isRemoved); 513 514 $reportInfo = StringOperation::replaceUnicodeControlChar($reportInfo); 515 $comment = StringOperation::replaceUnicodeControlChar($comment); 516 $acknowledgement = StringOperation::replaceUnicodeControlChar($acknowledgement); 517 518 $stmt = __METHOD__; 519 $params = array($uploadTreeId, $userId, $groupId, $type, $licenseId, $insertIsRemoved, $reportInfo, $comment, $acknowledgement); 520 $columns = "uploadtree_fk, user_fk, group_fk, type_fk, rf_fk, removed, reportinfo, comment, acknowledgement"; 521 $values = "$1,$2,$3,$4,$5,$6,$7,$8,$9"; 522 523 if ($jobId > 0) { 524 $stmt.= ".jobId"; 525 $params[] = $jobId; 526 $columns .= ", job_fk"; 527 $values .= ",$".count($params); 528 } else { 529 $this->markDecisionAsWip($uploadTreeId, $userId, $groupId); 530 } 531 532 $this->dbManager->prepare($stmt, "INSERT INTO clearing_event ($columns) VALUES($values) RETURNING clearing_event_pk"); 533 $res = $this->dbManager->execute($stmt, $params); 534 535 $row = $this->dbManager->fetchArray($res); 536 $this->dbManager->freeResult($res); 537 538 return intval($row['clearing_event_pk']); 539 } 540 541 /** 542 * @param int $jobId 543 * @return int[][] eventIds indexed by itemId and licenseId 544 */ 545 public function getEventIdsOfJob($jobId) 546 { 547 $statementName = __METHOD__; 548 $this->dbManager->prepare( 549 $statementName, 550 "SELECT uploadtree_fk, clearing_event_pk, rf_fk FROM clearing_event WHERE job_fk = $1" 551 ); 552 553 $res = $this->dbManager->execute($statementName, array($jobId)); 554 555 $events = array(); 556 while ($row = $this->dbManager->fetchArray($res)) { 557 $itemId = intval($row['uploadtree_fk']); 558 $eventId = intval($row['clearing_event_pk']); 559 $licenseId = intval($row['rf_fk']); 560 561 $events[$itemId][$licenseId] = $eventId; 562 } 563 $this->dbManager->freeResult($res); 564 565 return $events; 566 } 567 568 /** 569 * @param int $eventId 570 * @param int $userId 571 * @param int $groupId 572 * @param int $licenseRef 573 * @param $licenseIsRemoved 574 * @param $type 575 * @param $reportInfo 576 * @param string $comment 577 * @return ClearingEvent 578 */ 579 protected function buildClearingEvent($eventId, $userId, $groupId, $licenseRef, $licenseIsRemoved, $type, $reportInfo, $comment, $acknowledgement) 580 { 581 $removed = $this->dbManager->booleanFromDb($licenseIsRemoved); 582 583 return ClearingEventBuilder::create() 584 ->setEventId($eventId) 585 ->setUserId($userId) 586 ->setGroupId($groupId) 587 ->setEventType($type) 588 ->setLicenseRef($licenseRef) 589 ->setRemoved($removed) 590 ->setReportInfo($reportInfo) 591 ->setAcknowledgement($acknowledgement) 592 ->setComment($comment) 593 ->build(); 594 } 595 596 /** 597 * @param int $uploadTreeId 598 * @param int $userId 599 * @param int $groupId 600 */ 601 public function markDecisionAsWip($uploadTreeId, $userId, $groupId) 602 { 603 $statementName = __METHOD__; 604 605 $this->dbManager->prepare($statementName, 606 "INSERT INTO clearing_decision (uploadtree_fk,pfile_fk,user_fk,group_fk,decision_type,scope) VALUES ( 607 $1, (SELECT pfile_fk FROM uploadtree WHERE uploadtree_pk=$1), $2, $3, $4, $5)"); 608 $res = $this->dbManager->execute($statementName, 609 array($uploadTreeId, $userId, $groupId, DecisionTypes::WIP, DecisionScopes::ITEM)); 610 $this->dbManager->freeResult($res); 611 } 612 613 public function isDecisionWip($uploadTreeId, $groupId) 614 { 615 $sql = "SELECT decision_type FROM clearing_decision WHERE uploadtree_fk=$1 AND group_fk = $2 ORDER BY date_added DESC LIMIT 1"; 616 $latestDec = $this->dbManager->getSingleRow($sql, 617 array($uploadTreeId, $groupId), $sqlLog = __METHOD__); 618 if ($latestDec === false) { 619 return false; 620 } 621 return ($latestDec['decision_type'] == DecisionTypes::WIP); 622 } 623 624 public function isDecisionTBD($uploadTreeId, $groupId) 625 { 626 $sql = "SELECT decision_type FROM clearing_decision WHERE uploadtree_fk=$1 AND group_fk = $2 ORDER BY date_added DESC LIMIT 1"; 627 $latestDec = $this->dbManager->getSingleRow($sql, 628 array($uploadTreeId, $groupId), $sqlLog = __METHOD__); 629 if ($latestDec === false) { 630 return false; 631 } 632 return ($latestDec['decision_type'] == DecisionTypes::TO_BE_DISCUSSED); 633 } 634 635 public function isDecisionDNU($uploadTreeId, $groupId) 636 { 637 $sql = "SELECT decision_type FROM clearing_decision 638 WHERE uploadtree_fk=$1 AND group_fk = $2 639 ORDER BY clearing_decision_pk DESC LIMIT 1"; 640 $latestDec = $this->dbManager->getSingleRow($sql, 641 array($uploadTreeId, $groupId), $sqlLog = __METHOD__); 642 if ($latestDec === false) { 643 return false; 644 } 645 return ($latestDec['decision_type'] == DecisionTypes::DO_NOT_USE); 646 } 647 648 public function getClearingType($uploadTreeId, $groupId, $type) 649 { 650 $sql = "SELECT decision_type, scope FROM clearing_decision 651 WHERE uploadtree_fk=$1 AND group_fk = $2 652 ORDER BY clearing_decision_pk DESC LIMIT 1"; 653 $latestDec = $this->dbManager->getSingleRow($sql, 654 array($uploadTreeId, $groupId), $sqlLog = __METHOD__); 655 return $latestDec; 656 } 657 658 public function isDecisionIrrelevant($uploadTreeId, $groupId) 659 { 660 $sql = "SELECT decision_type FROM clearing_decision 661 WHERE uploadtree_fk=$1 AND group_fk = $2 662 ORDER BY clearing_decision_pk DESC LIMIT 1"; 663 $latestDec = $this->dbManager->getSingleRow($sql, 664 array($uploadTreeId, $groupId), $sqlLog = __METHOD__); 665 if ($latestDec === false) { 666 return false; 667 } 668 return ($latestDec['decision_type'] == DecisionTypes::IRRELEVANT); 669 } 670 671 /** 672 * @param ItemTreeBounds $itemTreeBound 673 * @param int $groupId 674 * @param boolean $onlyTried 675 * @return array[] where array has keys ("bulkId","id","text","matched","tried","removedLicenses","addedLicenses") 676 */ 677 public function getBulkHistory(ItemTreeBounds $itemTreeBound, $groupId, $onlyTried = true) 678 { 679 $uploadTreeTableName = $itemTreeBound->getUploadTreeTableName(); 680 $itemId = $itemTreeBound->getItemId(); 681 $uploadId = $itemTreeBound->getUploadId(); 682 $left = $itemTreeBound->getLeft(); 683 684 $params = array($uploadId, $itemId, $left, $groupId); 685 $stmt = __METHOD__ . "." . $uploadTreeTableName; 686 687 $triedExpr = "$3 between ut2.lft and ut2.rgt"; 688 $triedFilter = ""; 689 if ($onlyTried) { 690 $triedFilter = "and " . $triedExpr; 691 $stmt .= ".tried"; 692 } 693 694 $sql = "WITH alltried AS ( 695 SELECT lr.lrb_pk, ce.clearing_event_pk ce_pk, lr.rf_text, ce.uploadtree_fk, 696 $triedExpr AS tried 697 FROM license_ref_bulk lr 698 LEFT JOIN highlight_bulk h ON lrb_fk = lrb_pk 699 LEFT JOIN clearing_event ce ON ce.clearing_event_pk = h.clearing_event_fk 700 LEFT JOIN $uploadTreeTableName ut ON ut.uploadtree_pk = ce.uploadtree_fk 701 INNER JOIN $uploadTreeTableName ut2 ON ut2.uploadtree_pk = lr.uploadtree_fk 702 WHERE ut2.upload_fk = $1 AND lr.group_fk = $4 703 $triedFilter 704 ORDER BY lr.lrb_pk 705 ), aggregated_tried AS ( 706 SELECT DISTINCT ON(lrb_pk) lrb_pk, ce_pk, rf_text AS text, tried, matched 707 FROM ( 708 SELECT DISTINCT ON(lrb_pk) lrb_pk, ce_pk, rf_text, tried, true AS matched FROM alltried WHERE uploadtree_fk = $2 709 UNION ALL 710 SELECT DISTINCT ON(lrb_pk) lrb_pk, ce_pk, rf_text, tried, false AS matched FROM alltried WHERE uploadtree_fk != $2 OR uploadtree_fk IS NULL 711 ) AS result ORDER BY lrb_pk, matched DESC) 712 SELECT lrb_pk, text, rf_shortname, removing, tried, ce_pk, matched 713 FROM aggregated_tried 714 INNER JOIN license_set_bulk lsb ON lsb.lrb_fk = lrb_pk 715 INNER JOIN license_ref lrf ON lsb.rf_fk = lrf.rf_pk 716 ORDER BY lrb_pk"; 717 718 $this->dbManager->prepare($stmt, $sql); 719 $res = $this->dbManager->execute($stmt, $params); 720 721 $bulks = array(); 722 while ($row = $this->dbManager->fetchArray($res)) { 723 $bulkRun = $row['lrb_pk']; 724 if (!array_key_exists($bulkRun, $bulks)) { 725 $bulks[$bulkRun] = array( 726 "bulkId" => $row['lrb_pk'], 727 "id" => $row['ce_pk'], 728 "text" => $row['text'], 729 "matched" => $this->dbManager->booleanFromDb($row['matched']), 730 "tried" => $this->dbManager->booleanFromDb($row['tried']), 731 "removedLicenses" => array(), 732 "addedLicenses" => array()); 733 } 734 $key = $this->dbManager->booleanFromDb($row['removing']) ? 'removedLicenses' : 'addedLicenses'; 735 $bulks[$bulkRun][$key][] = $row['rf_shortname']; 736 } 737 738 $this->dbManager->freeResult($res); 739 return $bulks; 740 } 741 742 743 public function getBulkMatches($bulkId, $groupId) 744 { 745 $stmt = __METHOD__; 746 $sql = "SELECT uploadtree_fk AS itemid 747 FROM clearing_event ce 748 INNER JOIN highlight_bulk h 749 ON ce.clearing_event_pk = h.clearing_event_fk 750 WHERE lrb_fk = $1 AND group_fk = $2"; 751 752 $this->dbManager->prepare($stmt, $sql); 753 $res = $this->dbManager->execute($stmt, array($bulkId, $groupId)); 754 755 $result = $this->dbManager->fetchAll($res); 756 $this->dbManager->freeResult($res); 757 return $result; 758 } 759 760 /** 761 * @param ItemTreeBounds $itemTreeBounds 762 * @param int $groupId 763 * @return array mapping 'shortname'=>'count' 764 */ 765 function getClearedLicenseIdAndMultiplicities(ItemTreeBounds $itemTreeBounds, $groupId) 766 { 767 $statementName = __METHOD__; 768 769 $params = array($itemTreeBounds->getLeft(), $itemTreeBounds->getRight()); 770 $condition = "ut.lft BETWEEN $1 AND $2"; 771 772 $decisionsCte = $this->getRelevantDecisionsCte($itemTreeBounds, $groupId, $onlyCurrent=true, $statementName, $params, $condition); 773 $params[] = DecisionTypes::IRRELEVANT; 774 $sql = "$decisionsCte 775 SELECT 776 COUNT(DISTINCT itemid) AS count, 777 lr.rf_shortname AS shortname, 778 rf_pk 779 FROM decision 780 LEFT JOIN clearing_decision_event cde ON cde.clearing_decision_fk = decision.id 781 LEFT JOIN clearing_event ce ON ce.clearing_event_pk = cde.clearing_event_fk 782 LEFT JOIN license_ref lr ON lr.rf_pk = ce.rf_fk 783 WHERE (NOT ce.removed OR clearing_event_pk IS NULL) AND type_id!=$".count($params)." 784 GROUP BY shortname,rf_pk"; 785 786 $this->dbManager->prepare($statementName, $sql); 787 $res = $this->dbManager->execute($statementName, $params); 788 $multiplicity = array(); 789 while ($row = $this->dbManager->fetchArray($res)) { 790 $shortname= empty($row['rf_pk']) ? LicenseDao::NO_LICENSE_FOUND : $row['shortname']; 791 $multiplicity[$shortname] = $row; 792 } 793 $this->dbManager->freeResult($res); 794 795 return $multiplicity; 796 } 797 798 /** 799 * @param ItemTreeBounds $itemTreeBounds 800 * @param int $groupId 801 * @param int $userId 802 */ 803 public function markDirectoryAsDecisionType(ItemTreeBounds $itemTreeBounds, $groupId, $userId, $decisionMark) 804 { 805 if ($decisionMark == "doNotUse") { 806 $decisionMark = DecisionTypes::DO_NOT_USE; 807 } else { 808 $decisionMark = DecisionTypes::IRRELEVANT; 809 } 810 $this->markDirectoryAsDecisionTypeRec($itemTreeBounds, $groupId, $userId, false, $decisionMark); 811 } 812 813 /** 814 * @param ItemTreeBounds $itemTreeBounds 815 * @param int $groupId 816 * @param int $userId 817 */ 818 public function deleteDecisionTypeFromDirectory(ItemTreeBounds $itemTreeBounds, $groupId, $userId, $decisionMark) 819 { 820 if ($decisionMark == "deleteDoNotUse") { 821 $decisionMark = DecisionTypes::DO_NOT_USE; 822 } else { 823 $decisionMark = DecisionTypes::IRRELEVANT; 824 } 825 $this->markDirectoryAsDecisionTypeRec($itemTreeBounds, $groupId, $userId, true, $decisionMark); 826 } 827 828 /** 829 * @param ItemTreeBounds $itemTreeBounds 830 * @param int $groupId 831 * @param int $userId 832 */ 833 protected function markDirectoryAsDecisionTypeRec(ItemTreeBounds $itemTreeBounds, $groupId, $userId, $removeDecision=false, $decisionMark=DecisionTypes::IRRELEVANT) 834 { 835 $params = array($itemTreeBounds->getLeft(), $itemTreeBounds->getRight()); 836 $params[] = $groupId; 837 $a = count($params); 838 $options = array(UploadTreeProxy::OPT_SKIP_THESE=>'noLicense', 839 UploadTreeProxy::OPT_ITEM_FILTER=>' AND (lft BETWEEN $1 AND $2)', 840 UploadTreeProxy::OPT_GROUP_ID=>'$'.$a.''); 841 $uploadTreeProxy = new UploadTreeProxy($itemTreeBounds->getUploadId(), $options, $itemTreeBounds->getUploadTreeTableName()); 842 if (!$removeDecision) { 843 $sql = $uploadTreeProxy->asCTE() . 844 ' SELECT uploadtree_pk FROM UploadTreeView;'; 845 $itemRows = $this->dbManager->getRows($sql, $params, 846 __METHOD__ . ".getRevelantItems"); 847 $uploadTreeTableName = $itemTreeBounds->getUploadTreeTableName(); 848 /** @var ClearingDecisionProcessor $clearingDecisionEventProcessor */ 849 $clearingDecisionEventProcessor = $GLOBALS['container']->get( 850 'businessrules.clearing_decision_processor'); 851 foreach ($itemRows as $itemRow) { 852 $itemBounds = $this->uploadDao->getItemTreeBounds( 853 $itemRow['uploadtree_pk'], $uploadTreeTableName); 854 $clearingDecisionEventProcessor->makeDecisionFromLastEvents( 855 $itemBounds, $userId, $groupId, $decisionMark, DecisionScopes::ITEM); 856 } 857 } else { 858 $this->dbManager->begin(); 859 $params[] = $decisionMark; 860 $sql = $uploadTreeProxy->asCTE() . 861 ' DELETE FROM clearing_decision WHERE clearing_decision_pk IN ( 862 SELECT clearing_decision_pk FROM clearing_decision cd 863 INNER JOIN ( 864 SELECT MAX(date_added) AS date_added, uploadtree_fk 865 FROM clearing_decision WHERE uploadtree_fk IN ( 866 SELECT uploadtree_pk FROM UploadTreeView) 867 GROUP BY uploadtree_fk) cd2 868 ON cd.uploadtree_fk = cd2.uploadtree_fk 869 AND cd.date_added = cd2.date_added 870 AND decision_type = $' . ($a + 1) . ') 871 RETURNING clearing_decision_pk;'; 872 $clearingDecisionRows = $this->dbManager->getRows($sql, $params, 873 __METHOD__ . ".getRelevantDecisions"); 874 $clearingDecisions = array_map(function($x) { 875 return $x['clearing_decision_pk']; 876 }, $clearingDecisionRows); 877 $clearingDecisions = "{" . join(",", $clearingDecisions) . "}"; 878 879 $delEventSql = "DELETE FROM clearing_event WHERE clearing_event_pk IN (" . 880 "SELECT clearing_event_fk FROM clearing_decision_event " . 881 "WHERE clearing_decision_fk = ANY($1::int[]));"; 882 $this->dbManager->getSingleRow($delEventSql, array($clearingDecisions), 883 __METHOD__ . ".deleteEvent"); 884 885 $delCdEventSql = "DELETE FROM clearing_decision_event WHERE " . 886 "clearing_decision_fk = ANY($1::int[]);"; 887 $this->dbManager->getSingleRow($delCdEventSql, array($clearingDecisions), 888 __METHOD__ . ".deleteCdEvent"); 889 $this->dbManager->commit(); 890 $this->copyrightDao->updateTable($itemTreeBounds, '', '', $userId, 891 'copyright', 'rollback'); 892 } 893 } 894 895 /** 896 * @param uploadId 897 * @param int $groupId 898 * @return array $ids 899 */ 900 public function getMainLicenseIds($uploadId, $groupId) 901 { 902 $stmt = __METHOD__; 903 $sql = "SELECT rf_fk FROM upload_clearing_license WHERE upload_fk=$1 AND group_fk=$2"; 904 $this->dbManager->prepare($stmt, $sql); 905 $res = $this->dbManager->execute($stmt,array($uploadId,$groupId)); 906 $ids = array(); 907 while ($row = $this->dbManager->fetchArray($res)) { 908 $ids[$row['rf_fk']] = $row['rf_fk']; 909 } 910 $this->dbManager->freeResult($res); 911 return $ids; 912 } 913 914 /** 915 * @param $uploadId 916 * @param int $groupId 917 * @param int $licenseId 918 */ 919 public function makeMainLicense($uploadId, $groupId, $licenseId) 920 { 921 $this->dbManager->insertTableRow('upload_clearing_license', 922 array('upload_fk'=>$uploadId,'group_fk'=>$groupId,'rf_fk'=>$licenseId)); 923 } 924 925 /** 926 * @param uploadId 927 * @param int $groupId 928 * @param int $licenseId 929 */ 930 public function removeMainLicense($uploadId, $groupId, $licenseId) 931 { 932 $this->dbManager->getSingleRow('DELETE FROM upload_clearing_license WHERE upload_fk=$1 AND group_fk=$2 AND rf_fk=$3', 933 array($uploadId,$groupId,$licenseId)); 934 } 935 936 /** 937 * @param ItemTreeBounds $itemTreeBounds 938 * @param int $groupId 939 * @param bool $onlyCurrent 940 * @return ClearingDecision[] 941 */ 942 function getFilesForDecisionTypeFolderLevel(ItemTreeBounds $itemTreeBounds, $groupId, $onlyCurrent=true, $decisionMark="") 943 { 944 if (!empty($decisionMark)) { 945 $decisionMark = DecisionTypes::DO_NOT_USE; 946 } else { 947 $decisionMark = DecisionTypes::IRRELEVANT; 948 } 949 $statementName = __METHOD__; 950 $params = array(); 951 $decisionsCte = $this->getRelevantDecisionsCte($itemTreeBounds, $groupId, $onlyCurrent, $statementName, $params); 952 $params[] = $decisionMark; 953 $sql = "$decisionsCte 954 SELECT 955 itemid as uploadtree_pk, 956 lr.rf_shortname AS shortname, 957 comment 958 FROM decision 959 LEFT JOIN clearing_decision_event cde ON cde.clearing_decision_fk = decision.id 960 LEFT JOIN clearing_event ce ON ce.clearing_event_pk = cde.clearing_event_fk 961 LEFT JOIN license_ref lr ON lr.rf_pk = ce.rf_fk 962 WHERE type_id=$".count($params); 963 $this->dbManager->prepare($statementName, $sql); 964 $res = $this->dbManager->execute($statementName, $params); 965 $irrelevantFiles = $this->dbManager->fetchAll($res); 966 $this->dbManager->freeResult($res); 967 return $irrelevantFiles; 968 } 969 970 /** 971 * @param int $uploadId 972 * @param int $groupId 973 * @param int $userId 974 */ 975 public function getPreviousBulkIds($uploadId, $groupId, $userId, $onlyCount=0) 976 { 977 $stmt = __METHOD__; 978 $bulkIds = array(); 979 $sql = "SELECT jq_args FROM upload_reuse, jobqueue, job 980 WHERE upload_fk=$1 AND group_fk=$2 981 AND EXISTS(SELECT * FROM group_user_member gum WHERE gum.group_fk=upload_reuse.group_fk AND gum.user_fk=$3) 982 AND jq_type=$4 AND jq_job_fk=job_pk 983 AND job_upload_fk=reused_upload_fk AND job_group_fk=reused_group_fk"; 984 $this->dbManager->prepare($stmt, $sql); 985 $res = $this->dbManager->execute($stmt,array($uploadId, $groupId, $userId,'monkbulk')); 986 while ($row= $this->dbManager->fetchArray($res)) { 987 $bulkIds = array_merge($bulkIds,explode("\n", $row['jq_args'])); 988 } 989 $this->dbManager->freeResult($res); 990 if (empty($onlyCount)) { 991 return array_unique($bulkIds); 992 } else { 993 return count(array_unique($bulkIds)); 994 } 995 } 996 997 /** 998 * @param uploadTreeId 999 * @return count 1000 */ 1001 public function getCandidateLicenseCountForCurrentDecisions($uploadTreeId, $uploadId=0) 1002 { 1003 $params = array(); 1004 if (!empty($uploadId)) { 1005 $itemTreeBounds = $this->uploadDao->getParentItemBounds($uploadId, $uploadTreeTableName); 1006 $uploadTreeTableName = $this->uploadDao->getUploadtreeTableName($uploadId); 1007 $params[] = $itemTreeBounds->getLeft(); 1008 $params[] = $itemTreeBounds->getRight(); 1009 $condition = "UT.lft BETWEEN $1 AND $2"; 1010 $uploadtreeStatement = " uploadtree_fk IN (SELECT uploadtree_pk FROM $uploadTreeTableName UT WHERE $condition)"; 1011 } else { 1012 $params = array($uploadTreeId); 1013 $uploadtreeStatement = " uploadtree_fk = $1"; 1014 } 1015 1016 $sql = "WITH latestEvents AS ( 1017 SELECT rf_fk, date_added, removed FROM ( 1018 SELECT rf_fk, date_added, removed, row_number() 1019 OVER (PARTITION BY rf_fk ORDER BY date_added DESC) AS ROWNUM 1020 FROM clearing_event WHERE $uploadtreeStatement) SORTABLE 1021 WHERE ROWNUM = 1 ORDER BY rf_fk) 1022 SELECT count(*) FROM license_candidate WHERE license_candidate.rf_pk IN 1023 (SELECT rf_fk FROM latestEvents WHERE removed=false);"; 1024 $countCandidate = $this->dbManager->getSingleRow($sql, 1025 $params, $sqlLog = __METHOD__); 1026 1027 return $countCandidate['count']; 1028 } 1029 1030 /** 1031 * @param uploadId 1032 * @return count 1033 */ 1034 public function marklocalDecisionsAsGlobal($uploadId) 1035 { 1036 $statementName = __METHOD__ . $uploadId; 1037 1038 $sql = "WITH latestDecisions AS ( 1039 SELECT clearing_decision_pk FROM ( 1040 SELECT clearing_decision_pk, uploadtree_fk, date_added, row_number() 1041 OVER (PARTITION BY uploadtree_fk ORDER BY date_added DESC) AS ROWNUM 1042 FROM clearing_decision WHERE uploadtree_fk IN 1043 (SELECT uploadtree_pk FROM uploadtree WHERE upload_fk = $1)) SORTABLE 1044 WHERE ROWNUM = $2 ORDER BY uploadtree_fk) 1045 UPDATE clearing_decision SET scope = $2 WHERE clearing_decision_pk IN ( 1046 SELECT clearing_decision_pk FROM latestDecisions) RETURNING clearing_decision_pk"; 1047 1048 $countUpdated = $this->dbManager->getSingleRow($sql, 1049 array($uploadId, DecisionScopes::REPO), $statementName); 1050 1051 return count($countUpdated); 1052 } 1053} 1054