FOSSology  4.7.1
Open Source License Compliance by Open Source Software
AdminCustomTextManagement.php
1 <?php
2 /*
3  SPDX-FileCopyrightText: © 2025 Harshit Gandhi <gandhiharshit716@gmail.com>
4  SPDX-FileCopyrightText: © Fossology contributors
5 
6  SPDX-License-Identifier: GPL-2.0-only
7 */
8 
9 namespace Fossology\UI\Page;
10 
16 use Symfony\Component\HttpFoundation\JsonResponse;
17 use Symfony\Component\HttpFoundation\Request;
18 use Symfony\Component\HttpFoundation\Response;
19 use Symfony\Component\HttpFoundation\RedirectResponse;
20 
22 {
23  const NAME = "admin_custom_text_management";
24 
25  function __construct()
26  {
27  parent::__construct(self::NAME, array(
28  self::TITLE => "Add Custom Text",
29  self::MENU_LIST => "Admin::Text Management::Add",
30  self::REQUIRES_LOGIN => true,
31  self::PERMISSION => Auth::PERM_ADMIN
32  ));
33  }
34 
40  protected function handle(Request $request)
41  {
42  $userId = Auth::getUserId();
43  $groupId = Auth::getGroupId();
45  $userDao = $this->getObject('dao.user');
46 
47  // Check if user is admin
48  if (!Auth::isAdmin()) {
49  return $this->flushContent(_('Access denied. Admin privileges required.'));
50  }
51 
52  $action = $request->get('action');
53 
54  // Handle AJAX requests
55  if ($action == 'check_duplicate' && $request->getMethod() == 'POST') {
56  return $this->checkDuplicateAjax($request);
57  }
58 
59  // Handle form submissions
60  if ($request->get('updateit') || $request->get('addit')) {
61  $resultstr = $this->savePhrase($request, $userId, $groupId);
62  if (strpos($resultstr, 'ERROR') !== false) {
63  $vars = $this->getEditFormVars($request->get('cp_pk', 0));
64  $vars['message'] = $resultstr;
65  return $this->render('admin_custom_text_edit.html.twig', $this->mergeWithDefault($vars));
66  } else {
67  // Redirect to list view after successful save
68  $redirectUrl = Traceback_uri() . '?mod=admin_custom_text_list';
69  return new RedirectResponse($redirectUrl);
70  }
71  }
72 
73  // Handle edit form display
74  if ($request->get('edit') !== null) {
75  $cp_pk = intval($request->get('edit'));
76  $vars = $this->getEditFormVars($cp_pk);
77  return $this->render('admin_custom_text_edit.html.twig', $this->mergeWithDefault($vars));
78  }
79 
80  // Default to add form (edit with cp_pk=0)
81  $vars = $this->getEditFormVars(0);
82  return $this->render('admin_custom_text_edit.html.twig', $this->mergeWithDefault($vars));
83  }
84 
88  private function getEditFormVars($cp_pk)
89  {
90  $vars = array();
91 
92  if ($cp_pk > 0) {
93  // Edit existing phrase
94  $phraseData = $this->getPhraseData($cp_pk);
95  if ($phraseData) {
96  $vars = array_merge($vars, $phraseData);
97  $vars['isEdit'] = true;
98  // Get associated licenses for this phrase
99  $vars['selectedLicenses'] = $this->getAssociatedLicenses($cp_pk);
100  }
101  } else {
102  // Add new phrase
103  $vars['isEdit'] = false;
104  $vars['cp_pk'] = 0;
105  $vars['selectedLicenses'] = array();
106  }
107 
108  $vars['formAction'] = Traceback_uri() . '?mod=' . self::NAME;
109  $vars['updateParam'] = $vars['isEdit'] ? 'updateit' : 'addit';
110  $vars['textParam'] = 'text';
111  $vars['acknowledgementParam'] = 'acknowledgement';
112  $vars['commentsParam'] = 'comments';
113  $vars['userFkParam'] = 'user_fk';
114  $vars['groupFkParam'] = 'group_fk';
115  $vars['licensesParam'] = 'licenses';
116  $vars['isActiveParam'] = 'is_active';
117 
118  // Get license options for dropdown
119  $vars['licenseOptions'] = $this->getLicenseOptions();
120 
121  // Get users for bulk data filter dropdown
123  $userDao = $this->getObject('dao.user');
124  $vars['bulkDataUsers'] = $userDao->getUsersByGroup();
125 
126  return $vars;
127  }
128 
132  private function checkDuplicateAjax(Request $request)
133  {
134  $textMd5 = trim($request->get('text_md5'));
135  $currentCpPk = intval($request->get('cp_pk'));
136 
137  if (empty($textMd5)) {
138  return new JsonResponse(array('duplicate' => false));
139  }
140 
141  $isDuplicate = $this->checkDuplicateTextMd5($textMd5, $currentCpPk > 0 ? $currentCpPk : null);
142 
143  return new JsonResponse(array('duplicate' => $isDuplicate));
144  }
145 
149  private function checkDuplicateTextMd5($textMd5, $excludeCpPk = null)
150  {
152  $dbManager = $this->getObject('db.manager');
153 
154  $sql = "SELECT cp_pk FROM custom_phrase WHERE text_md5 = $1";
155  $params = array($textMd5);
156 
157  if ($excludeCpPk) {
158  $sql .= " AND cp_pk != $2";
159  $params[] = $excludeCpPk;
160  }
161 
162  $result = $dbManager->getSingleRow($sql, $params, __METHOD__);
163 
164  return $result !== false;
165  }
166 
170  private function getPhraseData($cp_pk)
171  {
173  $dbManager = $this->getObject('db.manager');
174 
175  $sql = "SELECT * FROM custom_phrase WHERE cp_pk = $1";
176  $row = $dbManager->getSingleRow($sql, array($cp_pk), __METHOD__);
177 
178  if ($row) {
179  $row['is_active'] = $dbManager->booleanFromDb($row['is_active']);
180  }
181 
182  return $row;
183  }
184 
185 
189  private function getAssociatedLicenses($cp_pk)
190  {
192  $dbManager = $this->getObject('db.manager');
193 
194  $sql = "SELECT lr.rf_pk, lr.rf_shortname, cplm.removing
195  FROM custom_phrase_license_map cplm
196  JOIN license_ref lr ON cplm.rf_fk = lr.rf_pk
197  WHERE cplm.cp_fk = $1
198  ORDER BY lr.rf_shortname";
199 
200  $result = $dbManager->getRows($sql, array($cp_pk));
201 
202  $licenses = array();
203  foreach ($result as $row) {
204  $licenses[] = array(
205  'rf_pk' => $row['rf_pk'],
206  'rf_shortname' => $row['rf_shortname'],
207  'removing' => $dbManager->booleanFromDb($row['removing'])
208  );
209  }
210 
211  return $licenses;
212  }
213 
217  private function savePhrase(Request $request, $userId, $groupId)
218  {
219  $cp_pk = intval($request->get('cp_pk'));
220  $text = StringOperation::replaceUnicodeControlChar(trim($request->get('text')));
221  $acknowledgement = StringOperation::replaceUnicodeControlChar(trim($request->get('acknowledgement')));
222  $comments = StringOperation::replaceUnicodeControlChar(trim($request->get('comments')));
223  $user_fk = intval($request->get('user_fk'));
224  $group_fk = intval($request->get('group_fk'));
225  $licenseData = $request->get('license_data'); // JSON data with license add/remove operations
226  $is_active = $request->get('is_active') == 'on' ? 'true' : 'false';
227 
228  if (empty($text)) {
229  return _("ERROR: The text field cannot be empty.");
230  }
231 
232  // Parse license data from JSON (new bulk-style form)
233  $licenseMappings = array();
234  if (!empty($licenseData)) {
235  $decodedData = json_decode($licenseData, true);
236  if (is_array($decodedData)) {
237  foreach ($decodedData as $item) {
238  if (!empty($item['licenseId'])) {
239  $licenseMappings[] = array(
240  'rf_pk' => intval($item['licenseId']),
241  'removing' => ($item['action'] === 'Remove')
242  );
243  }
244  }
245  }
246  }
247 
248  // Validate that at least one license is associated
249  if (empty($licenseMappings)) {
250  return _("ERROR: At least one license must be associated with the custom text.");
251  }
252 
253  // Generate MD5 hash of the text
254  $textMd5 = md5($text);
255 
256  // Check for duplicate text (exclude current record when updating)
257  if ($this->checkDuplicateTextMd5($textMd5, $cp_pk > 0 ? $cp_pk : null)) {
258  return _("ERROR: A custom text with the same content already exists in the database. Please modify the text or use the existing entry.");
259  }
260 
261  // Set defaults for user and group if not provided
262  if (empty($user_fk)) {
263  $user_fk = $userId;
264  }
265  if (empty($group_fk)) {
266  $group_fk = $groupId;
267  }
268 
269  try {
271  $dbManager = $this->getObject('db.manager');
272 
273  // Start transaction
274  $dbManager->begin();
275 
276  if ($cp_pk > 0) {
277  // Update existing phrase
278  $sql = "UPDATE custom_phrase SET
279  text = $2, text_md5 = $3, acknowledgement = $4, comments = $5,
280  user_fk = $6, group_fk = $7, is_active = $8
281  WHERE cp_pk = $1";
282  $params = array($cp_pk, $text, $textMd5, $acknowledgement, $comments,
283  $user_fk, $group_fk, $is_active);
284  $dbManager->prepare($stmt = __METHOD__ . ".update", $sql);
285  $dbManager->freeResult($dbManager->execute($stmt, $params));
286 
287  // Delete existing license associations
288  $deleteSql = "DELETE FROM custom_phrase_license_map WHERE cp_fk = $1";
289  $dbManager->prepare($deleteStmt = __METHOD__ . ".delete_licenses", $deleteSql);
290  $dbManager->freeResult($dbManager->execute($deleteStmt, array($cp_pk)));
291 
292  } else {
293  // Insert new phrase
294  $sql = "INSERT INTO custom_phrase
295  (text, text_md5, acknowledgement, comments, user_fk, group_fk, is_active, created_date)
296  VALUES ($1, $2, $3, $4, $5, $6, $7, CURRENT_TIMESTAMP) RETURNING cp_pk";
297  $params = array($text, $textMd5, $acknowledgement, $comments,
298  $user_fk, $group_fk, $is_active);
299  $dbManager->prepare($stmt = __METHOD__ . ".insert", $sql);
300  $result = $dbManager->execute($stmt, $params);
301  $row = $dbManager->fetchArray($result);
302  $cp_pk = $row['cp_pk'];
303  $dbManager->freeResult($result);
304  }
305 
306  // Insert license associations
307  if (!empty($licenseMappings)) {
308  $insertLicenseSql = "INSERT INTO custom_phrase_license_map (cp_fk, rf_fk, removing) VALUES ($1, $2, $3)";
309  $dbManager->prepare($insertLicenseStmt = __METHOD__ . ".insert_license", $insertLicenseSql);
310 
311  foreach ($licenseMappings as $mapping) {
312  if (!empty($mapping['rf_pk'])) {
313  $removingValue = $mapping['removing'] ? 'true' : 'false';
314  $dbManager->freeResult($dbManager->execute($insertLicenseStmt, array($cp_pk, $mapping['rf_pk'], $removingValue)));
315  }
316  }
317  }
318 
319  // Commit transaction
320  $dbManager->commit();
321 
322  return $cp_pk > 0 ? _("Custom text updated successfully.") :
323  _("Custom text added successfully.");
324 
325  } catch (\Exception $e) {
326  $dbManager->rollback();
327  return _("ERROR: Failed to save custom text: ") . $e->getMessage();
328  }
329  }
330 
331  private function getLicenseOptions()
332  {
334  $dbManager = $this->getObject('db.manager');
335 
336  $sql = "SELECT rf_pk, rf_shortname FROM license_ref WHERE rf_active = true ORDER BY rf_shortname";
337  $result = $dbManager->getRows($sql);
338 
339  $options = array();
340  foreach ($result as $row) {
341  $options[$row['rf_pk']] = $row['rf_shortname'];
342  }
343 
344  return $options;
345  }
346 }
347 
348 register_plugin(new AdminCustomTextManagement());
349 
Contains the constants and helpers for authentication of user.
Definition: Auth.php:24
static getUserId()
Get the current user's id.
Definition: Auth.php:68
static getGroupId()
Get the current user's group id.
Definition: Auth.php:80
static isAdmin()
Check if user is admin.
Definition: Auth.php:92
render($templateName, $vars=null, $headers=null)
static replaceUnicodeControlChar($input, $replace="")
Traceback_uri()
Get the URI without query to this location.
Definition: common-parm.php:97
char * trim(char *ptext)
Trimming whitespace.
Definition: fossconfig.c:690