9 namespace Fossology\UI\Page;
15 use Symfony\Component\HttpFoundation\JsonResponse;
16 use Symfony\Component\HttpFoundation\Request;
17 use Symfony\Component\HttpFoundation\Response;
21 const NAME =
"admin_custom_text_list";
23 function __construct()
25 parent::__construct(self::NAME, array(
26 self::TITLE =>
"Custom Text List",
27 self::MENU_LIST =>
"Admin::Text Management::List",
28 self::REQUIRES_LOGIN =>
true,
38 protected function handle(Request $request)
45 return $this->flushContent(_(
'Access denied. Admin privileges required.'));
48 $action = $request->get(
'action');
51 if ($action ==
'fetchData') {
55 if ($action ==
'get_bulk_data') {
56 return $this->getBulkDataAjax($request);
59 if ($action ==
'delete' && $request->getMethod() ==
'POST') {
60 return $this->deletePhraseAjax($request);
63 if ($action ==
'toggle' && $request->getMethod() ==
'POST') {
64 return $this->togglePhraseStatusAjax($request);
70 'addModuleName' =>
'admin_custom_text_management'
72 return $this->
render(
'admin_custom_text_list.html.twig', $this->mergeWithDefault($vars));
80 $offset = intval($request->query->get(
'start', 0));
81 $limit = intval($request->query->get(
'length', 10));
82 $draw = intval($request->query->get(
'draw', 1));
83 $searchQuery = $request->query->all(
'search')[
'value'] ??
'';
86 if (!empty($searchQuery)) {
87 $filteredQuery =
'%' . $searchQuery .
'%';
90 $totalCount = $this->getTotalPhrasesCount(
'');
91 $filteredCount = empty($filteredQuery) ? $totalCount : $this->getTotalPhrasesCount($filteredQuery);
92 $phraseArray = $this->getPhrasesServerSide($limit, $offset, $filteredQuery);
94 return new JsonResponse([
96 "recordsTotal" => $totalCount,
97 "recordsFiltered" => $filteredCount,
98 "data" => $phraseArray,
99 ], JsonResponse::HTTP_OK);
105 private function deletePhraseAjax(Request $request)
107 $phraseId = intval($request->get(
'id'));
110 return new JsonResponse(array(
'error' =>
'Invalid phrase ID'), 400);
115 $dbManager = $this->
getObject(
'db.manager');
121 $deleteLicensesSql =
"DELETE FROM custom_phrase_license_map WHERE cp_fk = $1";
122 $dbManager->prepare($deleteLicensesStmt = __METHOD__ .
".delete_licenses", $deleteLicensesSql);
123 $dbManager->freeResult($dbManager->execute($deleteLicensesStmt, array($phraseId)));
126 $sql =
"DELETE FROM custom_phrase WHERE cp_pk = $1";
127 $dbManager->prepare($stmt = __METHOD__ .
".delete", $sql);
128 $dbManager->freeResult($dbManager->execute($stmt, array($phraseId)));
131 $dbManager->commit();
133 return new JsonResponse(array(
135 'message' =>
'Custom text deleted successfully'
137 }
catch (\Exception $e) {
138 $dbManager->rollback();
139 return new JsonResponse(array(
'error' =>
'Failed to delete phrase: ' . $e->getMessage()), 500);
146 private function togglePhraseStatusAjax(Request $request)
148 $phraseId = intval($request->get(
'id'));
149 $status = intval($request->get(
'status'));
152 return new JsonResponse(array(
'error' =>
'Invalid phrase ID'), 400);
157 $dbManager = $this->
getObject(
'db.manager');
159 $sql =
"UPDATE custom_phrase SET is_active = $1 WHERE cp_pk = $2";
160 $dbManager->prepare($stmt = __METHOD__ .
".toggle", $sql);
161 $dbManager->freeResult($dbManager->execute($stmt, array($status ?
'true' :
'false', $phraseId)));
163 return new JsonResponse(array(
165 'message' => $status ?
'Custom text activated' :
'Custom text deactivated'
167 }
catch (\Exception $e) {
168 return new JsonResponse(array(
'error' =>
'Failed to toggle status: ' . $e->getMessage()), 500);
172 private function getTotalPhrasesCount($searchQuery =
'')
175 $dbManager = $this->
getObject(
'db.manager');
177 $sql =
"SELECT COUNT(DISTINCT cp.cp_pk) AS count
178 FROM custom_phrase cp
179 LEFT JOIN users u ON cp.user_fk = u.user_pk
180 LEFT JOIN custom_phrase_license_map cplm ON cp.cp_pk = cplm.cp_fk
181 LEFT JOIN license_ref lr ON cplm.rf_fk = lr.rf_pk";
185 if (!empty($searchQuery)) {
186 $sql .=
" WHERE (cp.text ILIKE $1 OR cplm.reportinfo ILIKE $1"
187 .
" OR cplm.acknowledgement ILIKE $1 OR cplm.comment ILIKE $1"
188 .
" OR u.user_name ILIKE $1 OR lr.rf_shortname ILIKE $1)";
189 $params[] = $searchQuery;
192 $stmtName = empty($searchQuery) ? __METHOD__ .
'.unfiltered' : __METHOD__ .
'.filtered';
193 $result = $dbManager->getSingleRow($sql, $params, $stmtName);
194 return $result ? intval($result[
'count']) : 0;
200 private function getPhrasesServerSide($limit, $offset, $searchQuery =
'')
203 $dbManager = $this->
getObject(
'db.manager');
205 $sql =
"SELECT cp.cp_pk, cp.text, cp.created_date, cp.is_active,
207 STRING_AGG(CASE WHEN cplm.removing = false THEN lr.rf_shortname END, ', ' ORDER BY lr.rf_shortname) as licenses_to_add,
208 STRING_AGG(CASE WHEN cplm.removing = true THEN lr.rf_shortname END, ', ' ORDER BY lr.rf_shortname) as licenses_to_remove,
209 STRING_AGG(CASE WHEN cplm.reportinfo IS NOT NULL AND cplm.reportinfo != '' THEN cplm.reportinfo END, '; ') as reportinfo,
210 STRING_AGG(CASE WHEN cplm.acknowledgement IS NOT NULL AND cplm.acknowledgement != '' THEN cplm.acknowledgement END, '; ') as acknowledgement,
211 STRING_AGG(CASE WHEN cplm.comment IS NOT NULL AND cplm.comment != '' THEN cplm.comment END, '; ') as comments
212 FROM custom_phrase cp
213 LEFT JOIN users u ON cp.user_fk = u.user_pk
214 LEFT JOIN custom_phrase_license_map cplm ON cp.cp_pk = cplm.cp_fk
215 LEFT JOIN license_ref lr ON cplm.rf_fk = lr.rf_pk";
221 if (!empty($searchQuery)) {
222 $sql .=
" WHERE cp.cp_pk IN (
223 SELECT cp2.cp_pk FROM custom_phrase cp2
224 LEFT JOIN users u2 ON cp2.user_fk = u2.user_pk
225 LEFT JOIN custom_phrase_license_map cplm2 ON cp2.cp_pk = cplm2.cp_fk
226 LEFT JOIN license_ref lr2 ON cplm2.rf_fk = lr2.rf_pk
227 WHERE (cp2.text ILIKE $1 OR cplm2.reportinfo ILIKE $1"
228 .
" OR cplm2.acknowledgement ILIKE $1 OR cplm2.comment ILIKE $1"
229 .
" OR u2.user_name ILIKE $1 OR lr2.rf_shortname ILIKE $1)
231 $params[] = $searchQuery;
234 $sql .=
" GROUP BY cp.cp_pk, cp.text, cp.created_date, cp.is_active, u.user_name
235 ORDER BY cp.created_date DESC";
237 $sql .=
" LIMIT $" . (count($params) + 1) .
" OFFSET $" . (count($params) + 2);
241 $stmtName = empty($searchQuery) ? __METHOD__ .
'.unfiltered' : __METHOD__ .
'.filtered';
242 $result = $dbManager->getRows($sql, $params, $stmtName);
245 foreach ($result as $row) {
246 $text = mb_strlen($row[
'text'],
'UTF-8') > 100
247 ? mb_substr($row[
'text'], 0, 100,
'UTF-8') .
'...'
250 $licensesToAdd = $row[
'licenses_to_add'] ?:
null;
251 $licensesToRemove = $row[
'licenses_to_remove'] ?:
null;
252 $licensesDisplay =
'';
253 if ($licensesToAdd || $licensesToRemove) {
254 if ($licensesToAdd) {
255 $licensesDisplay .=
'<div><img src="images/icons/add_16.png" alt="+" title="To be added"/> ' . htmlspecialchars($licensesToAdd, ENT_QUOTES,
'UTF-8') .
'</div>';
257 if ($licensesToRemove) {
258 $licensesDisplay .=
'<div><img src="images/icons/remove_16.png" alt="-" title="To be removed"/> ' . htmlspecialchars($licensesToRemove, ENT_QUOTES,
'UTF-8') .
'</div>';
261 $licensesDisplay =
'N/A';
264 $reportinfo =
trim($row[
'reportinfo'] ?:
'');
265 $acknowledgement =
trim($row[
'acknowledgement'] ?:
'');
266 $comments =
trim($row[
'comments'] ?:
'');
268 $makeIndicator =
function($content, $letter, $label, $typeClass) {
269 $has = $content !==
'';
271 ? $label .
': ' . htmlspecialchars(mb_substr($content, 0, 150) . (mb_strlen($content) > 150 ?
'…' :
''), ENT_QUOTES)
272 : $label .
': not set';
273 $class =
'custom-text-indicator ' . ($has ? $typeClass :
'custom-text-indicator-empty');
274 return '<span class="' . $class .
'" title="' . $tip .
'">' . $letter .
'</span>';
277 $detailsCell =
'<div class="custom-text-details-cell">'
278 . $makeIndicator($reportinfo,
'T',
'License Text',
'custom-text-indicator-reportinfo')
279 . $makeIndicator($acknowledgement,
'A',
'Acknowledgement',
'custom-text-indicator-acknowledgement')
280 . $makeIndicator($comments,
'C',
'Comment',
'custom-text-indicator-comment')
283 $isActiveFlag = $dbManager->booleanFromDb($row[
'is_active']);
284 $statusToggle =
'<input type="checkbox" class="phrase-status-toggle"'
285 .
' data-phrase-id="' . intval($row[
'cp_pk']) .
'"'
286 .
' data-current-status="' . ($isActiveFlag ?
'1' :
'0') .
'"'
287 .
' title="' . ($isActiveFlag ?
'Active' :
'Inactive') .
'"'
288 . ($isActiveFlag ?
' checked' :
'') .
'/>';
290 $editLink =
'<a href="?mod=admin_custom_text_management&edit=' . intval($row[
'cp_pk']) .
'"'
291 .
' title="Edit"><img border="0" src="images/button_edit.png" alt="Edit"/></a>';
293 $deleteBtn =
'<button type="button" class="custom-text-delete custom-text-delete-btn btn btn-link btn-sm p-0"'
294 .
' data-phrase-id="' . intval($row[
'cp_pk']) .
'" title="Delete">'
295 .
'×</button>';
299 '<div class="custom-text-cell">' . nl2br(htmlspecialchars($text, ENT_QUOTES,
'UTF-8')) .
'</div>',
301 htmlspecialchars($row[
'user_name'] ?:
'N/A', ENT_QUOTES,
'UTF-8'),
302 '<div class="custom-text-actions-cell">'
303 . $editLink . $statusToggle . $deleteBtn .
'</div>'
316 private function getTotalBulkCount($searchQuery =
'', $userFk = 0)
319 $dbManager = $this->
getObject(
'db.manager');
321 $sql =
"SELECT COUNT(DISTINCT lrb.lrb_pk) AS count
322 FROM license_ref_bulk lrb
323 INNER JOIN license_set_bulk lsb ON lrb.lrb_pk = lsb.lrb_fk
324 INNER JOIN license_ref lr ON lsb.rf_fk = lr.rf_pk
325 LEFT JOIN users u ON lrb.user_fk = u.user_pk
326 LEFT JOIN upload up ON lrb.upload_fk = up.upload_pk
327 WHERE lrb.rf_text IS NOT NULL AND lrb.rf_text != ''";
332 if (!empty($searchQuery)) {
333 $sql .=
" AND (lrb.rf_text ILIKE \$$paramIdx OR lr.rf_shortname ILIKE \$$paramIdx OR u.user_name ILIKE \$$paramIdx OR up.upload_filename ILIKE \$$paramIdx)";
334 $params[] = $searchQuery;
339 $sql .=
" AND lrb.user_fk = \$$paramIdx";
343 $result = $dbManager->getSingleRow($sql, $params,
345 return $result ? intval($result[
'count']) : 0;
353 return $method . (empty($searchQuery) ?
'' :
'.search') . ($userFk > 0 ?
'.user' :
'');
362 private function getBulkDataAjax(Request $request)
364 $page =
max(1, intval($request->query->get(
'page', 1)));
365 $limit =
max(1, intval($request->query->get(
'limit', 10)));
366 $searchTerm =
trim($request->query->get(
'search',
''));
367 $userFk =
max(0, intval($request->query->get(
'user_fk', 0)));
370 if (!empty($searchTerm)) {
371 $searchQuery =
'%' . $searchTerm .
'%';
374 $totalRecords = $this->getTotalBulkCount($searchQuery, $userFk);
375 $totalPages =
max(1, intval(ceil($totalRecords / $limit)));
378 if ($page > $totalPages) {
381 $offset = ($page - 1) * $limit;
384 $dbManager = $this->
getObject(
'db.manager');
388 $whereClause =
"WHERE lrb.rf_text IS NOT NULL AND lrb.rf_text != ''";
392 if (!empty($searchQuery)) {
393 $whereClause .=
" AND (lrb.rf_text ILIKE \$$paramIdx OR lr.rf_shortname ILIKE \$$paramIdx OR u.user_name ILIKE \$$paramIdx OR up.upload_filename ILIKE \$$paramIdx)";
394 $params[] = $searchQuery;
399 $whereClause .=
" AND lrb.user_fk = \$$paramIdx";
405 $subSql =
"SELECT DISTINCT lrb.lrb_pk
406 FROM license_ref_bulk lrb
407 INNER JOIN license_set_bulk lsb ON lrb.lrb_pk = lsb.lrb_fk
408 INNER JOIN license_ref lr ON lsb.rf_fk = lr.rf_pk
409 LEFT JOIN users u ON lrb.user_fk = u.user_pk
410 LEFT JOIN upload up ON lrb.upload_fk = up.upload_pk
412 ORDER BY lrb.lrb_pk DESC
413 LIMIT \$$paramIdx OFFSET \$" . ($paramIdx + 1);
421 lrb.rf_text as bulk_reference_text,
422 lrb.ignore_irrelevant,
426 lr.rf_pk as license_id,
427 lr.rf_shortname as license_shortname,
428 lr.rf_fullname as license_fullname,
429 lr.rf_spdx_id as license_spdx_id,
431 lsb.removing as is_removing_license,
432 lsb.comment as license_comment,
433 lsb.reportinfo as license_reportinfo,
434 lsb.acknowledgement as license_acknowledgement,
436 u.user_name as created_by_user,
437 up.upload_filename as upload_file,
444 FROM license_ref_bulk lrb
445 INNER JOIN license_set_bulk lsb ON lrb.lrb_pk = lsb.lrb_fk
446 INNER JOIN license_ref lr ON lsb.rf_fk = lr.rf_pk
447 LEFT JOIN users u ON lrb.user_fk = u.user_pk
448 LEFT JOIN upload up ON lrb.upload_fk = up.upload_pk
449 WHERE lrb.lrb_pk IN ($subSql)
450 ORDER BY lrb.lrb_pk DESC, lr.rf_shortname";
452 $result = $dbManager->getRows($sql, $params,
457 $groupedData = array();
458 foreach ($result as $row) {
459 $lrbPk = $row[
'lrb_pk'];
461 if (!isset($groupedData[$lrbPk])) {
462 $text = strlen($row[
'bulk_reference_text']) > 200 ?
463 substr($row[
'bulk_reference_text'], 0, 200) .
'...' :
464 $row[
'bulk_reference_text'];
466 $ignoreIrrelevant = $dbManager->booleanFromDb($row[
'ignore_irrelevant']);
467 $scanFindings = $dbManager->booleanFromDb($row[
'scan_findings']);
469 $groupedData[$lrbPk] = array(
471 'user_fk' => $row[
'user_fk'],
472 'group_fk' => $row[
'group_fk'],
473 'bulk_reference_text' => $row[
'bulk_reference_text'],
474 'text_preview' => $text,
475 'upload_fk' => $row[
'upload_fk'],
476 'uploadtree_fk' => $row[
'uploadtree_fk'],
477 'ignore_irrelevant' => $ignoreIrrelevant,
478 'bulk_delimiters' => $row[
'bulk_delimiters'],
479 'scan_findings' => $scanFindings,
480 'created_by_user' => $row[
'created_by_user'] ?:
'Unknown',
481 'upload_file' => $row[
'upload_file'] ?:
'Unknown',
482 'licenses' => array(),
483 'all_acknowledgements' => array(),
484 'all_comments' => array()
489 $isRemoving = $dbManager->booleanFromDb($row[
'is_removing_license']);
490 $licenseInfo = array(
491 'license_id' => $row[
'license_id'],
492 'license_shortname' => $row[
'license_shortname'],
493 'license_fullname' => $row[
'license_fullname'],
494 'license_spdx_id' => $row[
'license_spdx_id'],
495 'is_removing_license' => $isRemoving,
496 'license_comment' => $row[
'license_comment'],
497 'license_reportinfo' => $row[
'license_reportinfo'],
498 'license_acknowledgement' => $row[
'license_acknowledgement']
501 $groupedData[$lrbPk][
'licenses'][] = $licenseInfo;
504 if (!empty($row[
'license_acknowledgement'])) {
505 $groupedData[$lrbPk][
'all_acknowledgements'][] = $row[
'license_acknowledgement'];
507 if (!empty($row[
'license_comment'])) {
508 $groupedData[$lrbPk][
'all_comments'][] = $row[
'license_comment'];
513 foreach ($groupedData as $entry) {
514 $licenseNames = array();
515 $addedLicenses = array();
516 $removedLicenses = array();
518 foreach ($entry[
'licenses'] as $license) {
519 $licenseNames[] = $license[
'license_shortname'];
520 if ($license[
'is_removing_license']) {
521 $removedLicenses[] = $license[
'license_shortname'];
523 $addedLicenses[] = $license[
'license_shortname'];
528 $entry[
'license_summary'] = implode(
', ', $licenseNames);
529 $entry[
'added_licenses'] = implode(
', ', $addedLicenses);
530 $entry[
'removed_licenses'] = implode(
', ', $removedLicenses);
531 $entry[
'acknowledgement_summary'] = implode(
'; ', array_unique($entry[
'all_acknowledgements']));
532 $entry[
'comment_summary'] = implode(
'; ', array_unique($entry[
'all_comments']));
534 $bulkData[] = $entry;
537 return new JsonResponse(array(
539 'totalRecords' => $totalRecords,
540 'totalPages' => $totalPages,
541 'currentPage' => $page,
547 register_plugin(
new AdminCustomTextList());
Contains the constants and helpers for authentication of user.
static getUserId()
Get the current user's id.
static getGroupId()
Get the current user's group id.
static isAdmin()
Check if user is admin.
render($templateName, $vars=null, $headers=null)
bulkStatementName($method, $searchQuery, $userFk)
fetchDataServerSide(Request $request)
Traceback_uri()
Get the URI without query to this location.
char * trim(char *ptext)
Trimming whitespace.
FUNCTION int max(int permGroup, int permPublic)
Get the maximum group privilege.