1<?php 2/* 3Copyright (C) 2014-2017, Siemens AG 4 5This program is free software; you can redistribute it and/or 6modify it under the terms of the GNU General Public License 7version 2 as published by the Free Software Foundation. 8 9This program is distributed in the hope that it will be useful, 10but WITHOUT ANY WARRANTY; without even the implied warranty of 11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12GNU General Public License for more details. 13 14You should have received a copy of the GNU General Public License along 15with this program; if not, write to the Free Software Foundation, Inc., 1651 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17*/ 18 19namespace Fossology\Lib\Application; 20 21use Fossology\Lib\Db\DbManager; 22use Fossology\Lib\Util\ArrayOperation; 23 24/** 25 * @file 26 * @brief Helper class for Obligation CSV Import 27 */ 28 29/** 30 * @class ObligationCsvImport 31 * @brief Helper class for Obligation CSV Import 32 */ 33class ObligationCsvImport 34{ 35 /** @var DbManager $dbManager 36 * DB manager to be used */ 37 protected $dbManager; 38 /** @var string $delimiter 39 * Delimiter used in the CSV */ 40 protected $delimiter = ','; 41 /** @var string $enclosure 42 * Ecnlosure used in the CSV */ 43 protected $enclosure = '"'; 44 /** @var null|array $headrow 45 * Header of CSV */ 46 protected $headrow = null; 47 /** @var array $alias 48 * Alias for headers */ 49 protected $alias = array( 50 'type'=>array('type','Type'), 51 'topic'=>array('topic','Obligation or Risk topic'), 52 'text'=>array('text','Full Text'), 53 'classification'=>array('classification','Classification'), 54 'modifications'=>array('modifications','Apply on modified source code'), 55 'comment'=>array('comment','Comment'), 56 'licnames'=>array('licnames','Associated Licenses'), 57 'candidatenames'=>array('candidatenames','Associated candidate Licenses') 58 ); 59 60 /** 61 * Constructor 62 * @param DbManager $dbManager DB manager to use 63 */ 64 public function __construct(DbManager $dbManager) 65 { 66 $this->dbManager = $dbManager; 67 $this->obligationMap = $GLOBALS['container']->get('businessrules.obligationmap'); 68 } 69 70 /** 71 * @brief Update the delimiter 72 * @param string $delimiter New delimiter to use. 73 */ 74 public function setDelimiter($delimiter=',') 75 { 76 $this->delimiter = substr($delimiter,0,1); 77 } 78 79 /** 80 * @brief Update the enclosure 81 * @param string $enclosure New enclosure to use. 82 */ 83 public function setEnclosure($enclosure='"') 84 { 85 $this->enclosure = substr($enclosure,0,1); 86 } 87 88 /** 89 * @brief Read the CSV line by line and import it. 90 * @param string $filename Location of the CSV file. 91 * @return string message Error message, if any. Otherwise 92 * `Read csv: <count> licenses` on success. 93 */ 94 public function handleFile($filename) 95 { 96 if (!is_file($filename) || ($handle = fopen($filename, 'r')) === false) { 97 return _('Internal error'); 98 } 99 $cnt = -1; 100 $msg = ''; 101 try { 102 while (($row = fgetcsv($handle,0,$this->delimiter,$this->enclosure)) !== false) { 103 $log = $this->handleCsv($row); 104 if (!empty($log)) { 105 $msg .= "$log\n"; 106 } 107 $cnt++; 108 } 109 $msg .= _('Read csv').(": $cnt ")._('obligations'); 110 } catch(\Exception $e) { 111 fclose($handle); 112 return $msg .= _('Error while parsing file').': '.$e->getMessage(); 113 } 114 fclose($handle); 115 return $msg; 116 } 117 118 /** 119 * Handle a single row read from the CSV. If headrow is not set, then handle 120 * current row as head row. 121 * @param array $row Single row from CSV 122 * @return string $log Log messages 123 */ 124 private function handleCsv($row) 125 { 126 if ($this->headrow===null) { 127 $this->headrow = $this->handleHeadCsv($row); 128 return 'head okay'; 129 } 130 131 $mRow = array(); 132 foreach (array('type','topic','text','classification','modifications','comment','licnames','candidatenames') as $needle) { 133 $mRow[$needle] = $row[$this->headrow[$needle]]; 134 } 135 136 return $this->handleCsvObligation($mRow); 137 } 138 139 /** 140 * @brief Handle a row as head row. 141 * @param array $row Head row to be handled. 142 * @throws \Exception 143 * @return boolean[]|mixed[] Parsed head row. 144 */ 145 private function handleHeadCsv($row) 146 { 147 $headrow = array(); 148 foreach (array('type','topic','text','classification','modifications','comment','licnames','candidatenames') as $needle) { 149 $col = ArrayOperation::multiSearch($this->alias[$needle], $row); 150 if (false === $col) { 151 throw new \Exception("Undetermined position of $needle"); 152 } 153 $headrow[$needle] = $col; 154 } 155 return $headrow; 156 } 157 158 /** 159 * @brief Get the Obligation key from obligation topic and obligation text 160 * @param array $row CSV array with `topic` and `text` keys 161 * @return boolean|int False if not found, key otherwise. 162 */ 163 private function getKeyFromTopicAndText($row) 164 { 165 $req = array($row['topic'], $row['text']); 166 $row = $this->dbManager->getSingleRow('SELECT ob_pk FROM obligation_ref WHERE ob_topic=$1 AND ob_md5=md5($2)',$req); 167 return ($row === false) ? false : $row['ob_pk']; 168 } 169 170 /** 171 * @brief Compare licenses from Database and CSV 172 * @param bool $exists Existing license id 173 * @param array $listFromCsv List of obligations from CSV 174 * @param bool $candidate Is a candidate obligation? 175 * @param array $row Unused 176 * @return number strcmp() diff 177 */ 178 private function compareLicList($exists, $listFromCsv, $candidate, $row) 179 { 180 $getList = $this->obligationMap->getLicenseList($exists, $candidate); 181 $listFromDb = $this->reArrangeString($getList); 182 $listFromCsv = $this->reArrangeString($listFromCsv); 183 $diff = strcmp($listFromDb, $listFromCsv); 184 return $diff; 185 } 186 187 /** 188 * The function takes a string delimited by `;`, explodes it, sort the result 189 * and joins them back using `,` as new delimiter. 190 * @param string $string String to be rearranged. 191 * @return string Rearranged string. 192 */ 193 private function reArrangeString($string) 194 { 195 $string = explode(";", $string); 196 sort($string); 197 $string = implode(",", $string); 198 return $string; 199 } 200 201 /** 202 * @brief Clear all license maps for given obligation 203 * @param int $exists Existing obligation key 204 * @param bool $candidate Is a candidate obligation? 205 * @return boolean Always true 206 */ 207 private function clearListFromDb($exists, $candidate) 208 { 209 $licId = 0; 210 $this->obligationMap->unassociateLicenseFromObligation($exists, $licId, $candidate); 211 return true; 212 } 213 214 /** 215 * @brief Handle a single row from CSV. 216 * 217 * The function checks if the obligation text hash is already in the DB, then 218 * update the license associations. Otherwise make a new entry in the DB. 219 * @param array $row CSV row to be inserted. 220 * @return string Log messages. 221 */ 222 private function handleCsvObligation($row) 223 { 224 /* @var $dbManager DbManager */ 225 $dbManager = $this->dbManager; 226 $exists = $this->getKeyFromTopicAndText($row); 227 $associatedLicenses = ""; 228 $candidateLicenses = ""; 229 $msg = ""; 230 if ($exists !== false) { 231 $msg = "Obligation topic '$row[topic]' already exists in DB (id=".$exists."),"; 232 if ( $this->compareLicList($exists, $row['licnames'], false, $row) === 0 ) { 233 $msg .=" No Changes in AssociateLicense"; 234 } else { 235 $this->clearListFromDb($exists, false); 236 if (!empty($row['licnames'])) { 237 $associatedLicenses .= $this->AssociateWithLicenses($row['licnames'], $exists, false); 238 } 239 $msg .=" Updated AssociatedLicense license"; 240 } 241 if ($this->compareLicList($exists, $row['candidatenames'], true, $row) === 0) { 242 $msg .=" No Changes in CandidateLicense"; 243 } else { 244 $this->clearListFromDb($exists, true); 245 if (!empty($row['candidatenames'])) { 246 $associatedLicenses .= $this->AssociateWithLicenses($row['candidatenames'], $exists, true); 247 } 248 $msg .=" Updated CandidateLicense"; 249 } 250 $this->updateOtherFields($exists, $row); 251 return $msg . "\n" . $associatedLicenses . "\n"; 252 } 253 254 $stmtInsert = __METHOD__.'.insert'; 255 $dbManager->prepare($stmtInsert,'INSERT INTO obligation_ref (ob_type,ob_topic,ob_text,ob_classification,ob_modifications,ob_comment,ob_md5)' 256 . ' VALUES ($1,$2,$3,$4,$5,$6,md5($3)) RETURNING ob_pk'); 257 $resi = $dbManager->execute($stmtInsert,array($row['type'],$row['topic'],$row['text'],$row['classification'],$row['modifications'],$row['comment'])); 258 $new = $dbManager->fetchArray($resi); 259 $dbManager->freeResult($resi); 260 261 if (!empty($row['licnames'])) { 262 $associatedLicenses .= $this->AssociateWithLicenses($row['licnames'], $new['ob_pk']); 263 } 264 if (!empty($row['candidatenames'])) { 265 $candidateLicenses = $this->AssociateWithLicenses($row['candidatenames'], $new['ob_pk'], true); 266 } 267 268 $message = "License association results for obligation '$row[topic]':\n"; 269 $message .= "$associatedLicenses"; 270 $message .= "$candidateLicenses"; 271 $message .= "Obligation with id=$new[ob_pk] was added successfully.\n"; 272 return $message; 273 } 274 275 /** 276 * @brief Associate selected licenses to the obligation 277 * 278 * @param array $licList List of licenses to be associated 279 * @param int $obPk The id of the newly created obligation 280 * @param boolean $candidate Do we handle candidate licenses? 281 * @return string The list of associated licences 282 */ 283 function AssociateWithLicenses($licList, $obPk, $candidate=False) 284 { 285 $associatedLicenses = ""; 286 $message = ""; 287 288 $licenses = explode(";",$licList); 289 foreach ($licenses as $license) { 290 $licIds = $this->obligationMap->getIdFromShortname($license, $candidate); 291 $updated = false; 292 if (empty($licIds)) { 293 $message .= "License $license could not be found in the DB.\n"; 294 } else { 295 $updated = $this->obligationMap->associateLicenseFromLicenseList($obPk, 296 $licIds, $candidate); 297 } 298 if ($updated) { 299 if ($associatedLicenses == "") { 300 $associatedLicenses = "$license"; 301 } else { 302 $associatedLicenses .= ";$license"; 303 } 304 } 305 } 306 307 if (!empty($associatedLicenses)) { 308 $message .= "$associatedLicenses were associated.\n"; 309 } else { 310 $message .= "No "; 311 $message .= $candidate ? "candidate": ""; 312 $message .= "licenses were associated.\n"; 313 } 314 return $message; 315 } 316 317 /** 318 * @brief Update other fields of the obligation. 319 * 320 * Fields updated are: 321 * - classification 322 * - modifications 323 * - comment 324 * @param int $exists Obligation key 325 * @param array $row Row from CSV. 326 */ 327 function updateOtherFields($exists, $row) 328 { 329 $this->dbManager->getSingleRow('UPDATE obligation_ref SET ob_classification=$2, ob_modifications=$3, ob_comment=$4 where ob_pk=$1', 330 array($exists, $row['classification'], $row['modifications'], $row['comment']), 331 __METHOD__ . '.updateOtherOb'); 332 } 333} 334