FOSSology  4.4.0
Open Source License Compliance by Open Source Software
CopyrightDao.php
1 <?php
2 /*
3  SPDX-FileCopyrightText: © 2014-2018 Siemens AG
4  Author: Andreas Würl
5 
6  SPDX-License-Identifier: GPL-2.0-only
7 */
8 
9 namespace Fossology\Lib\Dao;
10 
16 use Monolog\Logger;
17 
19 {
21  private $dbManager;
23  private $uploadDao;
25  private $logger;
26 
27  function __construct(DbManager $dbManager, UploadDao $uploadDao)
28  {
29  $this->dbManager = $dbManager;
30  $this->uploadDao = $uploadDao;
31  $this->logger = new Logger(self::class);
32  }
33 
42  public function getHighlights($uploadTreeId, $tableName="copyright", $agentId=array(0),
43  $typeToHighlightTypeMap=array(
44  'statement' => Highlight::COPYRIGHT,
45  'email' => Highlight::EMAIL,
46  'url' => Highlight::URL,
47  'author' => Highlight::AUTHOR)
48  )
49  {
50  $pFileId = 0;
51  $row = $this->uploadDao->getUploadEntry($uploadTreeId);
52 
53  if (!empty($row['pfile_fk'])) {
54  $pFileId = $row['pfile_fk'];
55  } else {
56  $text = _("Could not locate the corresponding pfile.");
57  print $text;
58  }
59 
60  $statementName = __METHOD__.$tableName;
61  $params = array($pFileId);
62  $addAgentValue = "";
63  if (!empty($agentId) && $agentId[0] != 0) {
64  $agentIds = implode(",", $agentId);
65  $statementName .= '.agentId';
66  $addAgentValue = ' AND agent_fk= ANY($2::int[])';
67  $params[] = "{" . $agentIds . "}";
68  }
69  $columnsToSelect = "type, content, copy_startbyte, copy_endbyte";
70  $getHighlightForTableName = "SELECT $columnsToSelect FROM $tableName WHERE copy_startbyte IS NOT NULL AND pfile_fk=$1 $addAgentValue";
71  if ($tableName != "copyright") {
72  $sql = $getHighlightForTableName;
73  } else {
74  $sql = "$getHighlightForTableName UNION SELECT $columnsToSelect FROM author WHERE copy_startbyte IS NOT NULL AND pfile_fk=$1 $addAgentValue";
75  }
76  $this->dbManager->prepare($statementName,$sql);
77  $result = $this->dbManager->execute($statementName, $params);
78 
79  $highlights = array();
80  while ($row = $this->dbManager->fetchArray($result)) {
81  $type = $row['type'];
82  $content = $row['content'];
83  $htmlElement =null;
84  $highlightType = array_key_exists($type, $typeToHighlightTypeMap) ? $typeToHighlightTypeMap[$type] : Highlight::UNDEFINED;
85  $highlights[] = new Highlight($row['copy_startbyte'], $row['copy_endbyte'], $highlightType, -1, -1, $content, $htmlElement);
86  }
87  $this->dbManager->freeResult($result);
88 
89  return $highlights;
90  }
91 
103  public function saveDecision($tableName, $pfileId, $userId , $clearingType,
104  $description, $textFinding, $comment, $decision_pk=-1)
105  {
106  $textFinding = StringOperation::replaceUnicodeControlChar($textFinding);
107  if (empty($textFinding)) {
108  return;
109  }
110  $primaryColumn = $tableName . '_pk';
111  $assocParams = array(
112  'user_fk' => $userId,
113  'pfile_fk' => $pfileId,
114  'clearing_decision_type_fk' => $clearingType,
115  'description' => $description,
116  'textfinding' => $textFinding,
117  'hash' => hash('sha256', $textFinding),
118  'comment'=> $comment
119  );
120 
121  if ($decision_pk <= 0) {
122  $rows = $this->getDecisionsFromHash($tableName, $assocParams['hash']);
123  foreach ($rows as $row) {
124  if ($row['pfile_fk'] == $pfileId) {
125  $decision_pk = $row[$primaryColumn];
126  }
127  }
128  }
129  if ($decision_pk <= 0) {
130  return $this->dbManager->insertTableRow($tableName, $assocParams,
131  __METHOD__.'Insert.'.$tableName, $primaryColumn);
132  } else {
133  $assocParams['is_enabled'] = true;
134  $this->dbManager->updateTableRow($tableName, $assocParams, $primaryColumn,
135  $decision_pk, __METHOD__.'Update.'.$tableName);
136  return $decision_pk;
137  }
138  }
139 
140  public function removeDecision($tableName,$pfileId, $decisionId)
141  {
142  $primaryColumn = $tableName . '_pk';
143  $this->dbManager->prepare(__METHOD__,
144  "UPDATE $tableName
145  SET is_enabled = 'f'
146  WHERE $primaryColumn = $1
147  AND pfile_fk = $2");
148  $this->dbManager->execute(__METHOD__, array($decisionId, $pfileId));
149  }
150 
151  public function undoDecision($tableName,$pfileId, $decisionId)
152  {
153  $primaryColumn = $tableName . '_pk';
154  $this->dbManager->prepare(__METHOD__,
155  "UPDATE $tableName
156  SET is_enabled = 't'
157  WHERE $primaryColumn = $1
158  AND pfile_fk = $2");
159  $this->dbManager->execute(__METHOD__, array($decisionId, $pfileId));
160  }
161 
168  public function getAllEventEntriesForUpload($uploadFk, $agentId, $scope=1)
169  {
170  $statementName = __METHOD__ . $uploadFk;
171  $params[] = $uploadFk;
172  $params[] = $agentId;
173  $params[] = $scope;
174  $sql = "SELECT copyright_pk, CE.is_enabled, C.content, c.hash,
175  CE.content AS contentedited, CE.hash AS hashedited
176  FROM copyright_event CE
177  INNER JOIN copyright C ON C.copyright_pk = CE.copyright_fk
178  WHERE CE.upload_fk=$1 AND scope=$3 AND C.agent_fk = $2";
179  return $this->dbManager->getRows($sql, $params, $statementName);
180  }
181 
191  public function getScannerEntries($tableName, $uploadTreeTableName, $uploadId,
192  $type, $extrawhere, $enabled='true')
193  {
194  $statementName = __METHOD__.$tableName.$uploadTreeTableName;
195  $params = array($uploadId);
196  $extendWClause = null;
197  $tableNameEvent = $tableName.'_event';
198 
199  if ($uploadTreeTableName === "uploadtree_a") {
200  $extendWClause .= " AND UT.upload_fk = $1";
201  $statementName .= ".withUI";
202  }
203 
204  if ($type !== null && $type != "skipcontent") {
205  $params[]= $type;
206  $extendWClause .= " AND C.type = $".count($params);
207  $statementName .= ".withType";
208  }
209 
210  if ($extrawhere !== null) {
211  $extendWClause .= " AND ". $extrawhere;
212  $statementName .= "._".$extrawhere."_";
213  }
214 
215  $activatedClause = "ce.is_enabled = 'false'";
216  if ($enabled != 'false') {
217  $activatedClause = "ce.is_enabled IS NULL OR ce.is_enabled = 'true'";
218  $statementName .= "._"."enabled";
219  }
220 
221  $sql = "SELECT DISTINCT ON(copyright_pk, UT.uploadtree_pk)
222 copyright_pk, UT.uploadtree_pk as uploadtree_pk,
223 (CASE WHEN (CE.content IS NULL OR CE.content = '') THEN C.content ELSE CE.content END) AS content,
224 (CASE WHEN (CE.hash IS NULL OR CE.hash = '') THEN C.hash ELSE CE.hash END) AS hash,
225 C.agent_fk as agent_fk
226  FROM $tableName C
227  INNER JOIN $uploadTreeTableName UT ON C.pfile_fk = UT.pfile_fk
228  LEFT JOIN $tableNameEvent AS CE ON CE.".$tableName."_fk = C.".$tableName."_pk
229  AND CE.upload_fk = $1 AND CE.uploadtree_fk = UT.uploadtree_pk
230  WHERE C.content IS NOT NULL
231  AND C.content!=''
232  AND ($activatedClause)
233  $extendWClause
234 ORDER BY copyright_pk, UT.uploadtree_pk, content DESC";
235  return $this->dbManager->getRows($sql, $params, $statementName);
236  }
237 
246  public function getEditedEntries($tableName, $uploadTreeTableName, $uploadId,
247  $decisionType, $extrawhere="")
248  {
249  $statementName = __METHOD__.$tableName.$uploadTreeTableName;
250  $params = array();
251  $extendWClause = null;
252 
253  if ($uploadTreeTableName === "uploadtree_a") {
254  $params[]= $uploadId;
255  $extendWClause .= " AND UT.upload_fk = $".count($params);
256  $statementName .= ".withUI";
257  }
258 
259  if (!empty($decisionType)) {
260  $params[]= $decisionType;
261  $extendWClause .= " AND clearing_decision_type_fk = $".count($params);
262  $statementName .= ".withDecisionType";
263  }
264 
265  if (!empty($extrawhere)) {
266  $extendWClause .= " AND ". $extrawhere;
267  $statementName .= "._".$extrawhere."_";
268  }
269 
270  $columns = "CD.description as description, CD.textfinding as textfinding, CD.comment as comments, UT.uploadtree_pk as uploadtree_pk";
271 
272  $primaryColumn = $tableName . '_pk';
273  $sql = "SELECT $columns
274  FROM $tableName CD
275  INNER JOIN $uploadTreeTableName UT ON CD.pfile_fk = UT.pfile_fk
276  WHERE CD.is_enabled = 'true'
277  $extendWClause
278  ORDER BY CD.pfile_fk, UT.uploadtree_pk, CD.textfinding, CD.$primaryColumn DESC";
279  $this->dbManager->prepare($statementName, $sql);
280  $sqlResult = $this->dbManager->execute($statementName, $params);
281  $result = $this->dbManager->fetchAll($sqlResult);
282  $this->dbManager->freeResult($sqlResult);
283 
284  return $result;
285  }
286 
298  public function getAllEntriesReport($tableName, $uploadId, $uploadTreeTableName, $type=null, $onlyCleared=false, $decisionType=null, $extrawhere=null, $groupId=null)
299  {
300  $tableNameDecision = $tableName."_decision";
301  if ($tableName == 'copyright') {
302  $scannerEntries = $this->getScannerEntries($tableName, $uploadTreeTableName, $uploadId, $type, $extrawhere);
303  $editedEntries = $this->getEditedEntries($tableNameDecision, $uploadTreeTableName, $uploadId, $decisionType);
304  return array_merge($scannerEntries, $editedEntries);
305  } else {
306  return $this->getEditedEntries($tableNameDecision, $uploadTreeTableName, $uploadId, $decisionType);
307  }
308  }
309 
310  public function getAllEntries($tableName, $uploadId, $uploadTreeTableName, $type=null, $onlyCleared=false, $decisionType=null, $extrawhere=null)
311  {
312  $statementName = __METHOD__.$tableName.$uploadTreeTableName;
313  $tableNameEvent = $tableName.'_event';
314 
315  $params = array($uploadId);
316  $whereClause = "";
317  $distinctContent = "";
318  $tableNameDecision = $tableName."_decision";
319 
320  if ($uploadTreeTableName === "uploadtree_a") {
321  $whereClause .= " AND UT.upload_fk = $1";
322  $statementName .= ".withUI";
323  }
324  if ($type !== null && $type != "skipcontent") {
325  $params []= $type;
326  $whereClause .= " AND C.type = $".count($params);
327  $statementName .= ".withType";
328  }
329 
330  $clearingTypeClause = null;
331  if ($onlyCleared) {
332  $joinType = "INNER";
333  if ($decisionType !== null) {
334  $params []= $decisionType;
335  $clearingTypeClause = "WHERE clearing_decision_type_fk = $".count($params);
336  $statementName .= ".withDecisionType";
337  } else {
338  throw new \Exception("requested only cleared but no type given");
339  }
340  } else {
341  $joinType = "LEFT";
342  if ($decisionType !== null) {
343  $params []= $decisionType;
344  $clearingTypeClause = "WHERE clearing_decision_type_fk IS NULL OR clearing_decision_type_fk = $".count($params);
345  $statementName .= ".withDecisionType";
346  }
347  }
348  $statementName .= ".".$joinType."Join";
349 
350  if ($extrawhere !== null) {
351  $whereClause .= " AND ". $extrawhere;
352  $statementName .= "._".$extrawhere."_";
353  }
354  $decisionTableKey = $tableNameDecision . "_pk";
355 
356  $latestInfo = "SELECT DISTINCT ON(CD.pfile_fk, UT.uploadtree_pk, C.content, CD.textfinding)
357  CD.description as description, CD.textfinding as textfinding,
358  CD.comment as comments, UT.uploadtree_pk as uploadtree_pk,
359  CD.clearing_decision_type_fk AS clearing_decision_type_fk,
360  C.content AS content
361  FROM $tableName C
362  INNER JOIN $uploadTreeTableName UT
363  ON C.pfile_fk = UT.pfile_fk
364  LEFT JOIN $tableNameEvent AS CE
365  ON CE.".$tableName."_fk = C.".$tableName."_pk
366  AND CE.upload_fk = $1 AND CE.uploadtree_fk = UT.uploadtree_pk
367  $joinType JOIN (SELECT * FROM $tableNameDecision WHERE is_enabled='true') AS CD
368  ON C.pfile_fk = CD.pfile_fk
369  WHERE C.content IS NOT NULL
370  AND C.content!=''
371  AND (ce.is_enabled IS NULL OR ce.is_enabled = 'true')
372  $whereClause
373  ORDER BY CD.pfile_fk, UT.uploadtree_pk, C.content, CD.textfinding, CD.$decisionTableKey DESC";
374 
375  if ($clearingTypeClause !== null) {
376  $sql = "SELECT * FROM ($latestInfo) AS latestInfo $clearingTypeClause";
377  } else {
378  $sql = $latestInfo;
379  }
380 
381  $this->dbManager->prepare($statementName, $sql);
382  $sqlResult = $this->dbManager->execute($statementName, $params);
383  $result = $this->dbManager->fetchAll($sqlResult);
384  $this->dbManager->freeResult($sqlResult);
385 
386  return $result;
387  }
388 
394  public function getDecisions($tableName,$pfileId)
395  {
396  $statementName = __METHOD__.$tableName;
397  $orderTablePk = $tableName.'_pk';
398  $sql = "SELECT * FROM $tableName where pfile_fk = $1 order by $orderTablePk desc";
399  $params = array($pfileId);
400 
401  return $this->dbManager->getRows($sql, $params, $statementName);
402  }
403 
417  public function getDecisionsFromHash($tableName, $hash, $upload = null, $uploadtreetable = null)
418  {
419  $statementName = __METHOD__ . ".$tableName";
420  $orderTablePk = $tableName.'_pk';
421  $join = "";
422  $joinWhere = "";
423  $params = [$hash];
424 
425  if ($upload != null) {
426  if (empty($uploadtreetable)) {
427  return -1;
428  }
429  $statementName.= ".filterUpload";
430  $params[] = $upload;
431  $join = "INNER JOIN $uploadtreetable AS ut ON cp.pfile_fk = ut.pfile_fk";
432  $joinWhere = "AND ut.upload_fk = $" . count($params);
433  }
434 
435  $sql = "SELECT * FROM $tableName AS cp $join " .
436  "WHERE cp.hash = $1 $joinWhere ORDER BY $orderTablePk;";
437 
438  return $this->dbManager->getRows($sql, $params, $statementName);
439  }
440 
448  public function updateTable($item, $hash, $content, $userId, $cpTable, $action='', $scope=1)
449  {
450  $cpTablePk = $cpTable."_pk";
451  $cpTableEvent = $cpTable."_event";
452  $cpTableEventFk = $cpTable."_fk";
453  $itemTable = $item->getUploadTreeTableName();
454  $stmt = __METHOD__.".$cpTable.$itemTable";
455  $uploadId = $item->getUploadId();
456  $params = array($item->getLeft(),$item->getRight(),$uploadId);
457  $withHash = "";
458 
459  if (!empty($hash)) {
460  $params[] = $hash;
461  $withHash = " (cp.hash = $4 OR ce.hash = $4) AND ";
462  $stmt .= ".hash";
463  }
464  // get latest agent id for agent
465  $agentName = $this->getAgentName($cpTable);
466  $scanJobProxy = new ScanJobProxy($GLOBALS['container']->get('dao.agent'),
467  $uploadId);
468  if ($agentName == "copyright") {
469  $scanJobProxy->createAgentStatus(array($agentName, 'reso'));
470  } else {
471  $scanJobProxy->createAgentStatus(array($agentName));
472  }
473  $selectedScanners = $scanJobProxy->getLatestSuccessfulAgentIds();
474  if (!array_key_exists($agentName, $selectedScanners)) {
475  return array();
476  }
477  $latestXpAgentId[] = $selectedScanners[$agentName];
478  if (array_key_exists('reso', $selectedScanners)) {
479  $latestXpAgentId[] = $selectedScanners['reso'];
480  }
481  $agentFilter = '';
482  if (!empty($latestXpAgentId)) {
483  $latestAgentIds = implode(",", $latestXpAgentId);
484  $agentFilter = ' AND cp.agent_fk IN ('. $latestAgentIds .')';
485  }
486 
487  $sql = "SELECT DISTINCT ON ($cpTablePk, ut.uploadtree_pk) $cpTablePk, ut.uploadtree_pk, ut.upload_fk, ce." . $cpTableEvent . "_pk
488 FROM $cpTable as cp
489 INNER JOIN $itemTable AS ut ON cp.pfile_fk = ut.pfile_fk
490 LEFT JOIN $cpTableEvent AS ce ON ce.$cpTableEventFk = cp.$cpTablePk
491  AND ce.upload_fk = ut.upload_fk AND ce.uploadtree_fk = ut.uploadtree_pk
492 WHERE $withHash ( ut.lft BETWEEN $1 AND $2 ) $agentFilter AND ut.upload_fk = $3";
493 
494  $rows = $this->dbManager->getRows($sql, $params, $stmt);
495 
496  foreach ($rows as $row) {
497  $paramEvent = array();
498  $paramEvent[] = $row['upload_fk'];
499  $paramEvent[] = $row[$cpTablePk];
500  $paramEvent[] = $row['uploadtree_pk'];
501  $sqlExists = "SELECT exists(SELECT 1 FROM $cpTableEvent WHERE $cpTableEventFk = $1 AND upload_fk = $2 AND uploadtree_fk = $3)::int";
502  $rowExists = $this->dbManager->getSingleRow($sqlExists, array($row[$cpTablePk], $row['upload_fk'], $row['uploadtree_pk']), $stmt.'Exists');
503  $eventExists = $rowExists['exists'];
504  if ($action == "delete") {
505  $paramEvent[] = $scope;
506  if ($eventExists) {
507  $sqlEvent = "UPDATE $cpTableEvent SET scope = $4, is_enabled = false
508  WHERE upload_fk = $1 AND $cpTableEventFk = $2 AND uploadtree_fk = $3";
509  $statement = "$stmt.delete.up";
510  } else {
511  $sqlEvent = "INSERT INTO $cpTableEvent (upload_fk, $cpTableEventFk, uploadtree_fk, is_enabled, scope) VALUES($1, $2, $3, 'f', $4)";
512  $statement = "$stmt.delete";
513  }
514  } else if ($action == "rollback" && $eventExists) {
515  $sqlEvent = "UPDATE $cpTableEvent SET scope = 1, is_enabled = true
516  WHERE upload_fk = $1 AND $cpTableEventFk = $2 AND uploadtree_fk = $3";
517  $statement = "$stmt.rollback.up";
518  } else {
519  $paramEvent[] = StringOperation::replaceUnicodeControlChar($content);
520 
521  if ($eventExists) {
522  $sqlEvent = "UPDATE $cpTableEvent
523  SET upload_fk = $1, content = $4, hash = md5($4)
524  WHERE $cpTableEventFk = $2 AND uploadtree_fk = $3";
525  $statement = "$stmt.update";
526  } else {
527  $sqlEvent = "INSERT INTO $cpTableEvent(upload_fk, uploadtree_fk, $cpTableEventFk, is_enabled, content, hash)
528  VALUES($1, $3, $2, 'true', $4, md5($4))";
529  $statement = "$stmt.insert";
530  }
531  }
532  $this->dbManager->getSingleRow($sqlEvent, $paramEvent, $statement);
533  }
534  }
535 
548  private function getAgentName($table)
549  {
550  if (array_search($table, ["ecc", "keyword", "copyright", "ipra"]) !== false) {
551  return $table;
552  } else if (array_search($table, ["scancode_copyright", "scancode_author"]) !== false) {
553  return "scancode";
554  }
555  return "copyright";
556  }
557 
571  public function getTableName($type)
572  {
573  switch ($type) {
574  case "ipra":
575  $tableName = "ipra";
576  break;
577  case "ecc":
578  $tableName = "ecc";
579  break;
580  case "keyword":
581  $tableName = "keyword";
582  $filter = "none";
583  break;
584  case "statement":
585  $tableName = "copyright";
586  break;
587  case "scancode_statement":
588  $tableName = "scancode_copyright";
589  break;
590  case "scancode_email":
591  case "scancode_author":
592  case "scancode_url":
593  $tableName = "scancode_author";
594  break;
595  default:
596  $tableName = "author";
597  }
598  return $tableName;
599  }
600 
611  public function getTotalCopyrights($upload_pk, $item, $uploadTreeTableName, $agentId, $type, $activated = true)
612  {
613  $tableName = $this->getTableName($type);
614  $tableNameEvent = $tableName . '_event';
615  list($left, $right) = $this->uploadDao->getLeftAndRight($item, $uploadTreeTableName);
616  $sql_upload = "";
617  if ('uploadtree_a' == $uploadTreeTableName) {
618  $sql_upload = " AND UT.upload_fk=$5 ";
619  }
620  $activatedClause = "ce.is_enabled = 'false'";
621  if ($activated) {
622  $activatedClause = "ce.is_enabled IS NULL OR ce.is_enabled = 'true'";
623  }
624  $params = array($left, $right, $type, "{" . $agentId . "}", $upload_pk);
625  $join = " INNER JOIN license_file AS LF on cp.pfile_fk=LF.pfile_fk ";
626  $unorderedQuery = "FROM $tableName AS cp " .
627  "INNER JOIN $uploadTreeTableName AS UT ON cp.pfile_fk = UT.pfile_fk " .
628  "LEFT JOIN $tableNameEvent AS ce ON ce." . $tableName . "_fk = cp." . $tableName . "_pk " .
629  "AND ce.upload_fk = $5 AND ce.uploadtree_fk = UT.uploadtree_pk " .
630  $join .
631  "WHERE cp.content!='' " .
632  "AND ( UT.lft BETWEEN $1 AND $2 ) " .
633  "AND cp.type = $3 " .
634  "AND cp.agent_fk = ANY($4::int[]) " .
635  "AND ($activatedClause)" .
636  $sql_upload;
637  $grouping = " GROUP BY mcontent ";
638  $countAllQuery = "SELECT count(*) FROM (SELECT
639  (CASE WHEN (ce.content IS NULL OR ce.content = '') THEN cp.content ELSE ce.content END) AS mcontent
640  $unorderedQuery$grouping) as K";
641  $iTotalRecordsRow = $this->dbManager->getSingleRow($countAllQuery, $params, __METHOD__, $tableName . "count.all" . ($activated ? '' : '_deactivated'));
642  return $iTotalRecordsRow['count'];
643  }
644 }
getAllEventEntriesForUpload($uploadFk, $agentId, $scope=1)
getAgentName($table)
Get agent name based on table name.
updateTable($item, $hash, $content, $userId, $cpTable, $action='', $scope=1)
getDecisionsFromHash($tableName, $hash, $upload=null, $uploadtreetable=null)
Get all the decisions based on hash.
getAllEntriesReport($tableName, $uploadId, $uploadTreeTableName, $type=null, $onlyCleared=false, $decisionType=null, $extrawhere=null, $groupId=null)
getScannerEntries($tableName, $uploadTreeTableName, $uploadId, $type, $extrawhere, $enabled='true')
getDecisions($tableName, $pfileId)
saveDecision($tableName, $pfileId, $userId, $clearingType, $description, $textFinding, $comment, $decision_pk=-1)
getHighlights($uploadTreeId, $tableName="copyright", $agentId=array(0), $typeToHighlightTypeMap=array('statement'=> Highlight::COPYRIGHT, 'email'=> Highlight::EMAIL, 'url'=> Highlight::URL, 'author'=> Highlight::AUTHOR))
getEditedEntries($tableName, $uploadTreeTableName, $uploadId, $decisionType, $extrawhere="")
getTableName($type)
Get table name based on statement type.
getTotalCopyrights($upload_pk, $item, $uploadTreeTableName, $agentId, $type, $activated=true)
Get total number of copyrights for a uploadtree.
static replaceUnicodeControlChar($input, $replace="")
fo_dbManager * dbManager
fo_dbManager object
Definition: process.c:16
list_t type structure used to keep various lists. (e.g. there are multiple lists).
Definition: nomos.h:308