FOSSology  4.7.1
Open Source License Compliance by Open Source Software
PickerPlugin.php
Go to the documentation of this file.
1 <?php
2 /*
3  SPDX-FileCopyrightText: © 2010-2013 Hewlett-Packard Development Company, L.P.
4  SPDX-FileCopyrightText: © 2026 Siemens AG
5 
6  SPDX-License-Identifier: GPL-2.0-only
7 */
8 
13 use Symfony\Component\HttpFoundation\RedirectResponse;
14 use Symfony\Component\HttpFoundation\Request;
15 use Symfony\Component\HttpFoundation\Response;
16 
23 {
24  const NAME = 'picker';
25 
27  private $dbManager;
29  private $uploadDao;
30 
31  public function __construct()
32  {
33  parent::__construct(self::NAME, [
34  self::TITLE => _("File Picker"),
35  self::DEPENDENCIES => ["browse", "view"],
36  self::PERMISSION => Auth::PERM_READ,
37  self::REQUIRES_LOGIN => true,
38  ]);
39  $this->dbManager = $this->getObject('db.manager');
40  $this->uploadDao = $this->getObject('dao.upload');
41  }
42 
43  public function preInstall(): void
44  {
45  // Browse-Pfile: used by the file-listing rows in ui-browse.php
46  menu_insert("Browse-Pfile::Compare", 4, self::NAME, _("Compare this file to another."));
47  }
48 
49  // ── DB setup ───────────────────────────────────────────────────────────
50 
51  private function createFilePicker(): void
52  {
53  if (!$this->dbManager->existsTable('file_picker')) {
54  $this->dbManager->queryOnce(
55  "CREATE TABLE file_picker (
56  file_picker_pk serial NOT NULL PRIMARY KEY,
57  user_fk integer NOT NULL,
58  uploadtree_fk1 integer NOT NULL,
59  uploadtree_fk2 integer NOT NULL,
60  last_access_date date NOT NULL
61  );
62  ALTER TABLE ONLY file_picker
63  ADD CONSTRAINT file_picker_user_fk_key
64  UNIQUE (user_fk, uploadtree_fk1, uploadtree_fk2)",
65  __METHOD__
66  );
67  }
68  $this->createFilePickerMulti();
69  }
70 
71  private function createFilePickerMulti(): void
72  {
73  if ($this->dbManager->existsTable('file_picker_multi')) {
74  return;
75  }
76  $this->dbManager->queryOnce(
77  "CREATE TABLE file_picker_multi (
78  file_picker_multi_pk serial NOT NULL PRIMARY KEY,
79  user_fk integer NOT NULL,
80  items text NOT NULL,
81  last_access_date date NOT NULL
82  )",
83  __METHOD__
84  );
85  }
86 
87  // ── Data helpers ───────────────────────────────────────────────────────
88 
93  private function getUploadtreeTableName(int $uploadtreePk): string
94  {
95  $row = $this->dbManager->getSingleRow(
96  "SELECT u.uploadtree_tablename
97  FROM upload u
98  JOIN uploadtree ut ON ut.upload_fk = u.upload_pk
99  WHERE ut.uploadtree_pk = \$1",
100  [$uploadtreePk], __METHOD__
101  );
102  return $row['uploadtree_tablename'] ?? 'uploadtree';
103  }
104 
109  private function getFolderUploads(int $folder_pk): array
110  {
111  $groupId = Auth::getGroupId();
112  $stmt = __METHOD__;
113  $this->dbManager->prepare($stmt,
114  "SELECT u.upload_pk, u.upload_filename AS name,
115  u.upload_desc, u.upload_ts, u.uploadtree_tablename,
116  ut.uploadtree_pk
117  FROM foldercontents fc
118  JOIN upload u ON u.upload_pk = fc.child_id
119  AND fc.foldercontents_mode = 2
120  AND (u.upload_mode & (1<<5)) != 0
121  JOIN uploadtree ut ON ut.upload_fk = u.upload_pk AND ut.parent IS NULL
122  WHERE fc.parent_fk = \$1
123  ORDER BY u.upload_filename, u.upload_pk"
124  );
125  $res = $this->dbManager->execute($stmt, [$folder_pk]);
126  $uploads = [];
127  while ($row = $this->dbManager->fetchArray($res)) {
128  if (!$this->uploadDao->isAccessible($row['upload_pk'], $groupId)) {
129  continue;
130  }
131  $uploads[] = [
132  'upload_pk' => intval($row['upload_pk']),
133  'uploadtree_pk' => intval($row['uploadtree_pk']),
134  'name' => htmlspecialchars($row['name']),
135  'desc' => htmlspecialchars((string)($row['upload_desc'] ?? '')),
136  'ts' => !empty($row['upload_ts'])
137  ? htmlspecialchars(Convert2BrowserTime(substr($row['upload_ts'], 0, 19)))
138  : '',
139  ];
140  }
141  $this->dbManager->freeResult($res);
142  return $uploads;
143  }
144 
149  private function historyPick(int $uploadtree_pk, int &$rtncount): string
150  {
151  $user_pk = Auth::getUserId();
152  if (empty($user_pk)) {
153  return "";
154  }
155  $stmt = __METHOD__;
156  $this->dbManager->prepare($stmt,
157  "SELECT file_picker_pk, uploadtree_fk1, uploadtree_fk2 FROM file_picker
158  WHERE user_fk=\$1 AND (\$2=uploadtree_fk1 OR \$2=uploadtree_fk2)"
159  );
160  $result = $this->dbManager->execute($stmt, [$user_pk, $uploadtree_pk]);
161  $pickerRows = [];
162  while ($row = $this->dbManager->fetchArray($result)) {
163  $pickerRows[] = $row;
164  }
165  $this->dbManager->freeResult($result);
166  $rtncount = count($pickerRows);
167 
168  if ($rtncount < 1) {
169  return "";
170  }
171 
172  $PickSelectArray = [];
173  foreach ($pickerRows as $PickRec) {
174  $item2 = ($PickRec['uploadtree_fk1'] == $uploadtree_pk)
175  ? $PickRec['uploadtree_fk2'] : $PickRec['uploadtree_fk1'];
176  $tableName = $this->getUploadtreeTableName($item2);
177  $PathArray = Dir2Path($item2, $tableName);
178  $PickSelectArray[$item2] = $this->uploadtree2PathStr($PathArray);
179  }
180 
181  $Options = "id=HistoryPick onchange='AppJump(this.value)'";
182  return Array2SingleSelect($PickSelectArray, "HistoryPick", "", true, true, $Options);
183  }
184 
185  private function applicationPick(string $SLName, string $selectedVal, string $label): string
186  {
187  $AppList = [
188  "multicompare" => _("Upload Comparison"),
189  "nomosdiff" => _("License Difference (2-way)"),
190  "bucketsdiff" => _("Bucket Difference (2-way)"),
191  ];
192  $Options = "id=apick";
193  $SelectList = Array2SingleSelect($AppList, $SLName, $selectedVal, false, true, $Options);
194  return $label ? "$SelectList $label" : $SelectList;
195  }
196 
197  private function uploadtree2PathStr(array $PathArray): string
198  {
199  $parts = [];
200  foreach ($PathArray as $PathRow) {
201  $parts[] = $PathRow['ufile_name'];
202  }
203  return implode('/', $parts);
204  }
205 
206  // ── Request handler ────────────────────────────────────────────────────
207 
208  protected function handle(Request $request): Response
209  {
210  $this->createFilePicker();
211 
212  $RtnMod = $request->get('rtnmod', 'multicompare');
213  $uploadtree_pk = (int)($request->get('item', 0));
214  $folder_pk = (int)($request->get('folder', 0));
215  $user_pk = Auth::getUserId();
216 
217  /* Parse items (comma-separated, same format as multicompare) */
218  $multiItems = [];
219  $rawItems = $request->get('items', '');
220  if (!empty($rawItems)) {
221  $multiItems = array_values(array_unique(array_filter(
222  array_map('intval', explode(',', $rawItems)), fn($v) => $v > 0
223  )));
224  }
225 
226  /* ── Multi-compare flow ───────────────────────────────────────────── */
227  if ($RtnMod === 'multicompare') {
228  if (!empty($uploadtree_pk) && !in_array($uploadtree_pk, $multiItems)) {
229  array_unshift($multiItems, $uploadtree_pk);
230  }
231 
232  if (empty($multiItems)) {
233  return $this->flushContent(
234  "<div class='alert alert-warning'>"
235  . _("No component selected. Please navigate to an upload and click Compare.")
236  . " <a href='" . Traceback_uri() . "?mod=browse'>" . _("Go to Browse") . "</a>"
237  . "</div>"
238  );
239  }
240 
241  foreach ($multiItems as $idx => $pk) {
242  $row = $this->dbManager->getSingleRow(
243  "SELECT ut.upload_fk, u.uploadtree_tablename
244  FROM uploadtree ut
245  JOIN upload u ON u.upload_pk = ut.upload_fk
246  WHERE ut.uploadtree_pk = \$1",
247  [$pk], __METHOD__ . '.permcheck'
248  );
249  if (!$row || !$this->uploadDao->isAccessible($row['upload_fk'], Auth::getGroupId())) {
250  return $this->flushContent(
251  "<h2>" . _("Permission Denied") . " (item " . ($idx + 1) . ")</h2>"
252  );
253  }
254  }
255 
256  if (empty($folder_pk)) {
257  $folder_pk = GetFolderFromItem("", $multiItems[0]);
258  }
259 
260  if (!empty($user_pk) && count($multiItems) >= 2) {
261  $stmt = __METHOD__ . '.insertMulti';
262  $this->dbManager->prepare($stmt,
263  "INSERT INTO file_picker_multi (user_fk, items, last_access_date)
264  VALUES(\$1, \$2, now()) ON CONFLICT DO NOTHING"
265  );
266  $res = $this->dbManager->execute($stmt, [$user_pk, implode(',', $multiItems)]);
267  $this->dbManager->freeResult($res);
268  }
269 
270  return $this->renderPicker($RtnMod, $multiItems[0], $folder_pk, $multiItems);
271  }
272 
273  /* ── Classic 2-way flow ───────────────────────────────────────────── */
274  if (!$uploadtree_pk) {
275  return $this->flushContent("<h2>" . _("Unidentified item 1") . "</h2>");
276  }
277 
278  $uploadtree_pk2 = (int)($request->get('item2', 0));
279 
280  $Item1Row = $this->dbManager->getSingleRow(
281  "SELECT ut.upload_fk, u.uploadtree_tablename
282  FROM uploadtree ut
283  JOIN upload u ON u.upload_pk = ut.upload_fk
284  WHERE ut.uploadtree_pk = \$1",
285  [$uploadtree_pk], __METHOD__ . '.item1'
286  );
287  if (!$this->uploadDao->isAccessible($Item1Row['upload_fk'], Auth::getGroupId())) {
288  return $this->flushContent("<h2>" . _("Permission Denied") . " item 1</h2>");
289  }
290 
291  if (!empty($uploadtree_pk2)) {
292  $Item2Row = $this->dbManager->getSingleRow(
293  "SELECT ut.upload_fk, u.uploadtree_tablename
294  FROM uploadtree ut
295  JOIN upload u ON u.upload_pk = ut.upload_fk
296  WHERE ut.uploadtree_pk = \$1",
297  [$uploadtree_pk2], __METHOD__ . '.item2'
298  );
299  if (!$this->uploadDao->isAccessible($Item2Row['upload_fk'], Auth::getGroupId())) {
300  return $this->flushContent("<h2>" . _("Permission Denied") . " item 2</h2>");
301  }
302  }
303 
304  /* Redirect to comparison plugin when both items are set */
305  if (!empty($user_pk) && !empty($RtnMod) && $uploadtree_pk && $uploadtree_pk2) {
306  $stmt = __METHOD__ . '.insertPicker';
307  $this->dbManager->prepare($stmt,
308  "INSERT INTO file_picker (user_fk, uploadtree_fk1, uploadtree_fk2, last_access_date)
309  VALUES(\$1, \$2, \$3, now())"
310  );
311  $res = $this->dbManager->execute($stmt, [$user_pk, $uploadtree_pk, $uploadtree_pk2]);
312  $this->dbManager->freeResult($res);
313  $uri = Traceback_uri() . "?mod=$RtnMod&item1=$uploadtree_pk&item2=$uploadtree_pk2";
314  return new RedirectResponse($uri);
315  }
316 
317  if (empty($folder_pk)) {
318  $folder_pk = GetFolderFromItem("", $uploadtree_pk);
319  }
320  return $this->renderPicker($RtnMod, $uploadtree_pk, $folder_pk, []);
321  }
322 
326  private function renderPicker(string $RtnMod, int $anchorPk,
327  int $folder_pk, array $currentItems): Response
328  {
329  $isMulti = ($RtnMod === 'multicompare');
330  $uri = Traceback_uri() . "?mod=" . self::NAME;
331 
332  /* Folder dropdown */
333  $rootFolder = FolderGetTop();
334  $folderOptions = FolderListOption($rootFolder, 0, 1, $folder_pk);
335 
336  /* Upload list — for multi mode, exclude already-selected uploads */
337  $uploads = $this->getFolderUploads($folder_pk);
338  if ($isMulti && !empty($currentItems)) {
339  $selectedUploadPks = [];
340  foreach ($currentItems as $utPk) {
341  $row = $this->dbManager->getSingleRow(
342  "SELECT upload_fk FROM uploadtree WHERE uploadtree_pk = \$1",
343  [$utPk], __METHOD__ . '.uploadfk'
344  );
345  if ($row) {
346  $selectedUploadPks[] = intval($row['upload_fk']);
347  }
348  }
349  $uploads = array_values(array_filter(
350  $uploads, fn($u) => !in_array($u['upload_pk'], $selectedUploadPks)
351  ));
352  }
353 
354  /* App picker HTML */
355  $appPicker = $this->applicationPick(
356  "PickRtnApp", $RtnMod,
357  $isMulti ? "" : _("will run after choosing a file")
358  );
359 
360  $vars = [
361  'isMulti' => $isMulti,
362  'currentItems' => $currentItems,
363  'folderOptions' => $folderOptions,
364  'uploads' => $uploads,
365  'appPicker' => $appPicker,
366  /* JS vars */
367  'currentItemsJson' => "[" . implode(",", array_map('intval', $currentItems)) . "]",
368  'folderPk' => $folder_pk,
369  'anchorPk' => $anchorPk,
370  'rtnmod' => $RtnMod,
371  'pickerUri' => $uri,
372  ];
373 
374  if ($isMulti) {
375  $count = count($currentItems);
376 
377  /* Compare Now button URI */
378  $vars['compareUri'] = $count >= 2
379  ? htmlspecialchars(Traceback_uri() . "?mod=multicompare&items="
380  . implode(",", $currentItems))
381  : '';
382 
383  /* Path strings and Remove URIs for each selected item */
384  $selectedPaths = [];
385  $removeUris = [];
386  foreach ($currentItems as $pk) {
387  $tableName = $this->getUploadtreeTableName($pk);
388  $pathArr = Dir2Path($pk, $tableName);
389  $selectedPaths[$pk] = $this->uploadtree2PathStr($pathArr);
390  $remaining = array_values(array_filter($currentItems, fn($p) => $p !== $pk));
391  $removeUri = $uri . "&rtnmod=$RtnMod&folder=$folder_pk";
392  if (!empty($remaining)) {
393  $removeUri .= "&items=" . implode(',', $remaining);
394  }
395  $removeUris[$pk] = htmlspecialchars($removeUri);
396  }
397  $vars['selectedPaths'] = $selectedPaths;
398  $vars['removeUris'] = $removeUris;
399  $vars['hint'] = $count < 2
400  ? _("Select at least 2 components to compare.")
401  : _("Add more components below, or click Compare Now.");
402  $vars['browseHint'] = _("Choose a folder, then click Add next to an upload.");
403  } else {
404  $tableName = $this->getUploadtreeTableName($anchorPk);
405  $pathStr = $this->uploadtree2PathStr(Dir2Path($anchorPk, $tableName));
406  $vars['pathStr'] = htmlspecialchars($pathStr);
407  $vars['browseHint'] = _("Choose a folder, then click Select to pick File 2.");
408 
409  $historyCount = 0;
410  $historyPick = $this->historyPick($anchorPk, $historyCount);
411  $vars['historyPick'] = $historyPick;
412  $vars['historyCount'] = $historyCount;
413  }
414 
415  return $this->render('picker.html.twig', $this->mergeWithDefault($vars));
416  }
417 }
418 
419 register_plugin(new PickerPlugin());
Contains the constants and helpers for authentication of user.
Definition: Auth.php:24
render($templateName, $vars=null, $headers=null)
getFolderUploads(int $folder_pk)
handle(Request $request)
getUploadtreeTableName(int $uploadtreePk)
renderPicker(string $RtnMod, int $anchorPk, int $folder_pk, array $currentItems)
historyPick(int $uploadtree_pk, int &$rtncount)
Dir2Path($uploadtree_pk, $uploadtree_tablename='uploadtree')
Return the path (without artifacts) of an uploadtree_pk.
Definition: common-dir.php:222
FolderListOption($ParentFolder, $Depth, $IncludeTop=1, $SelectId=-1, $linkParent=false, $OldParent=0)
Create the folder tree, using OPTION tags.
GetFolderFromItem($upload_pk="", $uploadtree_pk="")
Find what folder an item is in.
FolderGetTop()
DEPRECATED! Find the top-of-tree folder_pk for the current user.
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
Array2SingleSelect($KeyValArray, $SLName="unnamed", $SelectedVal="", $FirstEmpty=false, $SelElt=true, $Options="", $ReturnKey=true)
Build a single choice select pulldown.
Definition: common-ui.php:32
Convert2BrowserTime($server_time)
Convert the server time to browser time.
Definition: common-ui.php:312
#define PERM_READ
Read-only permission.
Definition: libfossology.h:32
fo_dbManager * dbManager
fo_dbManager object
Definition: process.c:16