FOSSology  4.7.1
Open Source License Compliance by Open Source Software
ReuseComparePlugin.php
Go to the documentation of this file.
1 <?php
2 /*
3  SPDX-FileCopyrightText: © 2026 Siemens AG
4 
5  SPDX-License-Identifier: GPL-2.0-only
6 */
7 
14 namespace Fossology\Reuser;
15 
20 use Symfony\Component\HttpFoundation\Request;
21 use Symfony\Component\HttpFoundation\Response;
22 
30 {
31  const NAME = "reusecompare";
32 
34  private $dbManager;
35 
37  private $uploadDao;
38 
39  function __construct()
40  {
41  parent::__construct(self::NAME, [
42  self::TITLE => _("Reuse Compare"),
43  self::DEPENDENCIES => ["browse", "view"],
44  self::PERMISSION => Auth::PERM_READ,
45  self::REQUIRES_LOGIN => true,
46  ]);
47  $this->dbManager = $this->getObject('db.manager');
48  $this->uploadDao = $this->getObject('dao.upload');
49  }
50 
51  function preInstall()
52  {
53  menu_insert("Browse-Pfile::Reuse Compare", 3, self::NAME,
54  _("Compare files with reused upload source."));
55  }
56 
61  protected function handle(Request $request)
62  {
63  $uploadId = (int)$request->get('upload', 0);
64  $item = (int)$request->get('item', 0);
65 
66  if (empty($uploadId) || empty($item)) {
67  return $this->flushContent(
68  "<h3>" . _("Missing upload or item parameter.") . "</h3>");
69  }
70 
71  $groupId = Auth::getGroupId();
72  if (!$this->uploadDao->isAccessible($uploadId, $groupId)) {
73  return $this->flushContent(
74  "<h2>" . _("Permission Denied") . "</h2>");
75  }
76 
77  $tableName = $this->uploadDao->getUploadtreeTableName($uploadId);
78 
79  /* Get reuse sources */
80  $reusedUploads = $this->uploadDao->getReusedUpload($uploadId, $groupId);
81  if (empty($reusedUploads)) {
82  return $this->flushContent(
83  "<h3>" . _("No reused upload sources found for this upload.") . "</h3>");
84  }
85 
86  $selectedReuseId = (int)$request->get('reuse', 0);
87  $reuseUploadId = 0;
88  $reuseSources = [];
89 
90  foreach ($reusedUploads as $ru) {
91  $rid = intval($ru['reused_upload_fk']);
92  $ruUpload = $this->uploadDao->getUpload($rid);
93  $ruName = $ruUpload ? $ruUpload->getFilename() : "unknown";
94  $ruMode = intval($ru['reuse_mode']);
95  $reuseSources[] = [
96  'id' => $rid,
97  'name' => $ruName,
98  'mode' => $ruMode,
99  ];
100  if ($reuseUploadId === 0 ||
101  ($selectedReuseId > 0 && $rid === $selectedReuseId) ||
102  ($selectedReuseId === 0 && $reuseUploadId === 0)) {
103  $reuseUploadId = $rid;
104  }
105  }
106 
107  $reuseTableName = $this->uploadDao->getUploadtreeTableName($reuseUploadId);
108  $reuseRootPk = $this->uploadDao->getUploadParent($reuseUploadId);
109 
110  /* Determine which node to list children from */
111  $itemRow = $this->uploadDao->getUploadEntry($item, $tableName);
112  if (empty($itemRow)) {
113  return $this->flushContent("<h3>" . _("Item not found.") . "</h3>");
114  }
115 
116  /* Get all descendant files (not just one level) using lft/rgt range */
117  $children1 = $this->getAllNonArtifactDescendants($itemRow, $tableName);
118  $reuseRootRow = $this->uploadDao->getUploadEntry($reuseRootPk, $reuseTableName);
119  $children2 = [];
120  if (!empty($reuseRootRow)) {
121  $children2 = $this->getAllNonArtifactDescendants($reuseRootRow, $reuseTableName);
122  }
123  FuzzyName($children1);
124  FuzzyName($children2);
125  $master = MakeMaster($children1, $children2);
126 
127  /* Build diff tree, stats, and license data */
128  $diffTree = [];
129  $stats = [
130  'IDENTICAL' => 0, 'MODIFIED' => 0, 'NEW' => 0, 'REMOVED' => 0
131  ];
132 
133  $licenseMapUploadPfiles = $this->batchGetLicensesForUpload($uploadId, $itemRow['lft'], $itemRow['rgt'], $tableName);
134  $reuseRootLft = $reuseRootRow['lft'] ?? 0;
135  $reuseRootRgt = $reuseRootRow['rgt'] ?? 0;
136  $licenseMapReusePfiles = !empty($reuseRootRow) ?
137  $this->batchGetLicensesForUpload($reuseUploadId, $reuseRootLft, $reuseRootRgt, $reuseTableName) : [];
138 
139  $uploadPathUri = Traceback_uri();
140  $licenseMapUpload = [];
141  $licenseMapReuse = [];
142 
143  foreach ($master as $idx => $pair) {
144  $child1 = !empty($pair[1]) ? $pair[1] : null;
145  $child2 = !empty($pair[2]) ? $pair[2] : null;
146 
147  $row = $this->buildDiffRow($child1, $child2, $uploadId, $reuseUploadId,
148  $tableName, $reuseTableName, $uploadPathUri);
149 
150  $stats[$row['classification']]++;
151  $diffTree[] = $row;
152 
153  /* Collect licenses for comparison (from pre-fetched batch) */
154  if ($child1 && !empty($child1['pfile_fk'])) {
155  $lics1 = $licenseMapUploadPfiles[(int)$child1['pfile_fk']] ?? [];
156  foreach ($lics1 as $lic) {
157  $licenseMapUpload[$lic] = isset($licenseMapUpload[$lic]) ?
158  $licenseMapUpload[$lic] + 1 : 1;
159  }
160  }
161  if ($child2 && !empty($child2['pfile_fk'])) {
162  $lics2 = $licenseMapReusePfiles[(int)$child2['pfile_fk']] ?? [];
163  foreach ($lics2 as $lic) {
164  $licenseMapReuse[$lic] = isset($licenseMapReuse[$lic]) ?
165  $licenseMapReuse[$lic] + 1 : 1;
166  }
167  }
168  }
169 
170  /* Build license comparison stats */
171  $allLicenses = array_unique(array_merge(
172  array_keys($licenseMapUpload), array_keys($licenseMapReuse)));
173  sort($allLicenses);
174 
175  $licenseSummary = ['new' => 0, 'deleted' => 0, 'modified' => 0, 'unchanged' => 0];
176  $licenseCategories = ['new' => [], 'deleted' => [], 'modified' => [], 'unchanged' => []];
177 
178  foreach ($allLicenses as $lic) {
179  $upCount = $licenseMapUpload[$lic] ?? 0;
180  $reCount = $licenseMapReuse[$lic] ?? 0;
181  if ($upCount > 0 && $reCount == 0) {
182  $licenseSummary['new']++;
183  $licenseCategories['new'][] = $lic;
184  } elseif ($upCount == 0 && $reCount > 0) {
185  $licenseSummary['deleted']++;
186  $licenseCategories['deleted'][] = $lic;
187  } elseif ($upCount != $reCount) {
188  $licenseSummary['modified']++;
189  $licenseCategories['modified'][] = $lic;
190  } else {
191  $licenseSummary['unchanged']++;
192  $licenseCategories['unchanged'][] = $lic;
193  }
194  }
195  $licenseFilter = $request->get('license_filter', '');
196  if (!in_array($licenseFilter, ['new', 'deleted', 'modified', 'unchanged', ''])) {
197  $licenseFilter = '';
198  }
199 
200  /* Apply search and status filter server-side before pagination */
201  $searchQuery = $request->get('search', '');
202  $statusFilterQuery = $request->get('statusFilter', '');
203  $queryParams = $request->query->all();
204  $queryParams['search'] = $searchQuery;
205  $queryParams['statusFilter'] = $statusFilterQuery;
206  unset($queryParams['page']);
207 
208  $filteredDiffTree = [];
209  foreach ($diffTree as $row) {
210  if (!empty($statusFilterQuery) && $row['classification'] !== $statusFilterQuery) {
211  continue;
212  }
213  if (!empty($searchQuery)) {
214  $match = false;
215  if (stripos($row['upload_file_name'], $searchQuery) !== false ||
216  stripos($row['reuse_file_name'], $searchQuery) !== false) {
217  $match = true;
218  }
219  if (!$match) {
220  continue;
221  }
222  }
223  $filteredDiffTree[] = $row;
224  }
225  $diffTree = $filteredDiffTree;
226 
227  /* Paginate the diff tree */
228  $page = (int)$request->get('page', 0);
229  if ($page < 0) {
230  $page = 0;
231  }
232  $perPage = 25;
233  $totalDiffRows = count($diffTree);
234  $totalPages = $totalDiffRows > 0 ? (int)ceil($totalDiffRows / $perPage) - 1 : 0;
235  if ($page > $totalPages) {
236  $page = 0;
237  }
238  $offset = $page * $perPage;
239  $pagedDiffTree = array_slice($diffTree, $offset, $perPage);
240  $pageUri = '?' . http_build_query($queryParams);
241 
242  $vars = [
243  'uploadId' => $uploadId,
244  'itemId' => $item,
245  'reuseUploadId' => $reuseUploadId,
246  'reuseSources' => $reuseSources,
247  'stats' => $stats,
248  'diffTree' => $pagedDiffTree,
249  'pageUri' => $pageUri,
250  'currentPage' => $page + 1,
251  'totalPagesView' => $totalPages + 1,
252  'perPage' => $perPage,
253  'totalDiffRows' => $totalDiffRows,
254  'licenseSummary' => $licenseSummary,
255  'licenseCategories'=> $licenseCategories,
256  'licenseFilter' => $licenseFilter,
257  'totalUpload' => count($children1),
258  'totalReuse' => count($children2),
259  'searchQuery' => $searchQuery,
260  'statusFilterQuery'=> $statusFilterQuery,
261  ];
262 
263  $defaultVars = $this->mergeWithDefault([]);
264  $defaultVars['styles'] .= "<link rel='stylesheet' href='css/highlights.css'>\n";
265  $vars = array_merge($defaultVars, $vars);
266 
267  return $this->render('reusecompare.html.twig', $vars);
268  }
269 
273  private function buildDiffRow($child1, $child2, $uploadId, $reuseUploadId,
274  $tableName, $reuseTableName, $uri)
275  {
276  $row = [
277  'upload_file_name' => '',
278  'reuse_file_name' => '',
279  'classification' => '',
280  'upload_pfile_fk' => 0,
281  'reused_pfile_fk' => 0,
282  'upload_href' => '',
283  'reuse_href' => '',
284  ];
285 
286  if ($child1 && $child2) {
287  $row['upload_file_name'] = $child1['ufile_name'];
288  $row['reuse_file_name'] = $child2['ufile_name'];
289  $row['upload_pfile_fk'] = intval($child1['pfile_fk']);
290  $row['reused_pfile_fk'] = intval($child2['pfile_fk']);
291 
292  $upPk = $child1['uploadtree_pk'];
293  $rePk = $child2['uploadtree_pk'];
294  $row['upload_href'] = $uri . "?mod=view-license&upload=$uploadId&item=$upPk";
295  $row['reuse_href'] = $uri . "?mod=view-license&upload=$reuseUploadId&item=$rePk";
296 
297  if ($child1['pfile_fk'] == $child2['pfile_fk']) {
298  $row['classification'] = 'IDENTICAL';
299  } else {
300  $row['classification'] = 'MODIFIED';
301  }
302  } elseif ($child1) {
303  $row['upload_file_name'] = $child1['ufile_name'];
304  $row['upload_pfile_fk'] = intval($child1['pfile_fk']);
305  $row['classification'] = 'NEW';
306  $upPk = $child1['uploadtree_pk'];
307  $row['upload_href'] = $uri . "?mod=view-license&upload=$uploadId&item=$upPk";
308  } elseif ($child2) {
309  $row['reuse_file_name'] = $child2['ufile_name'];
310  $row['reused_pfile_fk'] = intval($child2['pfile_fk']);
311  $row['classification'] = 'REMOVED';
312  $rePk = $child2['uploadtree_pk'];
313  $row['reuse_href'] = $uri . "?mod=view-license&upload=$reuseUploadId&item=$rePk";
314  }
315 
316  return $row;
317  }
318 
322  private function batchGetLicensesForUpload($uploadFk, $lft, $rgt, $tableName)
323  {
324  $sql = "SELECT ut.pfile_fk, lr.rf_shortname
325  FROM $tableName ut
326  JOIN license_file lf ON lf.pfile_fk = ut.pfile_fk
327  JOIN license_ref lr ON lf.rf_fk = lr.rf_pk
328  WHERE ut.upload_fk = $1
329  AND ut.lft BETWEEN $2 AND $3
330  AND ut.ufile_mode & (3<<28) = 0
331  AND ut.pfile_fk IS NOT NULL
332  AND lr.rf_shortname IS NOT NULL
333  AND lr.rf_shortname NOT IN ('Void')
334  ORDER BY lr.rf_shortname";
335  $stmtName = __METHOD__ . '_' . $tableName;
336  $this->dbManager->prepare($stmtName, $sql);
337  $res = $this->dbManager->execute($stmtName, [$uploadFk, $lft, $rgt]);
338  $map = [];
339  while ($row = $this->dbManager->fetchArray($res)) {
340  $pid = (int)$row['pfile_fk'];
341  if (!isset($map[$pid])) {
342  $map[$pid] = [];
343  }
344  $map[$pid][] = $row['rf_shortname'];
345  }
346  $this->dbManager->freeResult($res);
347  return $map;
348  }
349 
359  private function getAllNonArtifactDescendants($itemRow, $tableName)
360  {
361  $lft = (int)$itemRow['lft'];
362  $rgt = (int)$itemRow['rgt'];
363  $pk = (int)$itemRow['uploadtree_pk'];
364  $uploadFk = (int)$itemRow['upload_fk'];
365 
366  $sql = "SELECT ut.*, pfile_size, pfile_mimetypefk
367  FROM $tableName ut
368  LEFT JOIN pfile ON (pfile_pk = ut.pfile_fk)
369  WHERE ut.upload_fk = $1
370  AND ut.lft BETWEEN $2 AND $3
371  AND ut.uploadtree_pk != $4
372  AND ut.ufile_mode & (3<<28) = 0
373  ORDER BY ut.lft";
374  $this->dbManager->prepare($stmt = __METHOD__ . ".$tableName", $sql);
375  $res = $this->dbManager->execute($stmt, [$uploadFk, $lft, $rgt, $pk]);
376  $children = [];
377  while ($row = $this->dbManager->fetchArray($res)) {
378  $children[] = $row;
379  }
380  $this->dbManager->freeResult($res);
381  return $children;
382  }
383 }
384 
385 register_plugin(new ReuseComparePlugin());
Contains the constants and helpers for authentication of user.
Definition: Auth.php:24
static getGroupId()
Get the current user's group id.
Definition: Auth.php:80
render($templateName, $vars=null, $headers=null)
Compare files in the current upload against their counterparts from the reused upload source....
getAllNonArtifactDescendants($itemRow, $tableName)
batchGetLicensesForUpload($uploadFk, $lft, $rgt, $tableName)
buildDiffRow($child1, $child2, $uploadId, $reuseUploadId, $tableName, $reuseTableName, $uri)
MakeMaster($Children1, $Children2)
Generate the master array with aligned children.
FuzzyName(&$Children)
Add fuzzyname and fuzzynameext to $Children.
menu_insert($Path, $LastOrder=0, $URI=NULL, $Title=NULL, $Target=NULL, $HTML=NULL)
Given a Path, order level for the last item, and optional plugin name, insert the menu item.
Traceback_uri()
Get the URI without query to this location.
Definition: common-parm.php:97
fo_dbManager * dbManager
fo_dbManager object
Definition: process.c:16