48 private $mapDBColumns = array(
49 "reviewedBy" =>
"ri_reviewed",
50 "department" =>
"ri_department",
51 "reportRel" =>
"ri_report_rel",
52 "community" =>
"ri_community",
53 "component" =>
"ri_component",
54 "version" =>
"ri_version",
55 "relDate" =>
"ri_release_date",
56 "sw360Link" =>
"ri_sw360_link",
57 "componentType" =>
"ri_component_type",
58 "componentId" =>
"ri_component_id",
59 "footerNote" =>
"ri_footer",
60 "generalAssesment" =>
"ri_general_assesment",
61 "gaAdditional" =>
"ri_ga_additional",
62 "gaRisk" =>
"ri_ga_risk",
63 "dependencyBinarySource" =>
"ri_depnotes",
64 "exportRestrictionText" =>
"ri_exportnotes",
65 "copyrightRestrictionText" =>
"ri_copyrightnotes"
71 private $radioListUR = array(
72 "nonCritical" =>
"critical",
73 "critical" =>
"critical",
74 "noDependency" =>
"dependencySourceBinary",
75 "dependencySource" =>
"dependencySourceBinary",
76 "dependencyBinary" =>
"dependencySourceBinary",
77 "noExportRestriction" =>
"exportRestriction",
78 "exportRestriction" =>
"exportRestriction",
79 "noRestriction" =>
"restrictionForUse",
80 "restrictionForUse" =>
"restrictionForUse"
86 private $checkBoxListSPDX = array(
87 "spdxLicenseComment" =>
"spdxLicenseComment",
88 "ignoreFilesWOInfo" =>
"ignoreFilesWOInfo",
89 "osselotExport" =>
"osselotExport"
95 $this->Name =
"report_conf";
96 $this->Title = _(
"Report Configuration");
97 $this->Dependency = array(
"browse");
100 parent::__construct();
101 $this->uploadDao = $GLOBALS[
'container']->get(
'dao.upload');
102 $this->
dbManager = $GLOBALS[
'container']->get(
'db.manager');
103 $this->userDao = $GLOBALS[
'container']->get(
'dao.user');
104 $this->clearingDao = $GLOBALS[
'container']->get(
'dao.clearing');
105 $this->licenseDao = $GLOBALS[
'container']->get(
'dao.license');
113 $tooltipText = _(
"Report Configuration");
114 menu_insert(
"Browse-Pfile::Conf",5,$this->Name,$tooltipText);
117 $URI = $this->Name . $Parm;
127 menu_insert(
"View-Meta::{$menuText}", $menuPosition);
131 $tooltipText = _(
"Report Configuration");
134 menu_insert(
"View::{$menuText}", $menuPosition, $URI, $tooltipText);
137 menu_insert(
"View-Meta::{$menuText}", $menuPosition, $URI, $tooltipText);
139 menu_insert(
"Browse::Conf", -3, $URI, $tooltipText);
152 $row = $this->uploadDao->getReportInfo($uploadId);
153 foreach ($this->mapDBColumns as $key => $value) {
154 $vars[$key] = $row[$value];
156 $textAreaNoneStyle =
' style="display:none;overflow:auto;width:98%;height:80px;"';
157 $textAreaStyle =
' style="overflow:auto;width:98%;height:80px;"';
158 $vars[
'styleDependencyTA'] = $vars[
'styleExportTA'] = $vars[
'styleRestrictionTA'] = $textAreaStyle;
159 if ($row[
'ri_depnotes'] ==
'NA' || empty($row[
'ri_depnotes'])) {
160 $vars[
'styleDependencyTA'] = $textAreaNoneStyle;
163 if ($row[
'ri_exportnotes'] ==
'NA' || empty($row[
'ri_exportnotes'])) {
164 $vars[
'styleExportTA'] = $textAreaNoneStyle;
167 if ($row[
'ri_copyrightnotes'] ==
'NA' || empty($row[
'ri_copyrightnotes'])) {
168 $vars[
'styleRestrictionTA'] = $textAreaNoneStyle;
171 if (!empty($row[
'ri_ga_checkbox_selection'])) {
172 $listURCheckbox = explode(
',', $row[
'ri_ga_checkbox_selection']);
173 foreach (array_keys($this->radioListUR) as $key => $value) {
174 $vars[$value] = $listURCheckbox[$key];
178 if (!empty($row[
'ri_spdx_selection'])) {
179 $listSPDXCheckbox = explode(
',', $row[
'ri_spdx_selection']);
180 foreach (array_keys($this->checkBoxListSPDX) as $key => $value) {
181 $vars[$value] = $listSPDXCheckbox[$key];
184 foreach (array_keys($this->checkBoxListSPDX) as $key) {
185 $vars[$key] =
'unchecked';
189 $vars[
'cyclonedx'] = $this->uploadDao->getCyclonedxSettings($uploadId);
190 $vars[
'cyclonedxSupportedVersions'] = CycloneDxSettings::SUPPORTED_VERSIONS;
192 $uploadTreeTableName = $this->uploadDao->getUploadtreeTableName($uploadId);
193 $itemTreeBounds = $this->uploadDao->getParentItemBounds($uploadId, $uploadTreeTableName);
194 $allClearingDecisions = $this->clearingDao->getFileClearingsFolder($itemTreeBounds, $groupId);
197 $excludedObligations = array();
198 $excludedObligations = (array) json_decode($row[
'ri_excluded_obligations'],
true);
200 $tableRows .=
'<tr><td style="width:35%">'.$obTopic.
'</td>';
201 $tableRows .=
'<td><textarea readonly="readonly" style="overflow:auto;width:98%;height:80px;">'.
202 $obData[
'text'].
'</textarea></td><td>';
203 foreach ($obData[
'license'] as $value) {
204 if (!empty($excludedObligations[$obTopic]) && in_array($value, $excludedObligations[$obTopic])) {
205 $tableRows .=
'<input class="browse-upload-checkbox view-license-rc-size" type="checkbox" name="obLicenses['.urlencode($obTopic).
'][]" value="'.$value.
'" checked> '.$value.
'<br />';
207 $tableRows .=
'<input class="browse-upload-checkbox view-license-rc-size" type="checkbox" name="obLicenses['.urlencode($obTopic).
'][]" value="'.$value.
'"> '.$value.
'<br />';
210 $tableRows .=
'</td></tr>';
212 $tableRowsUnifiedReport =
"";
213 $unifiedColumns = array();
214 if (!empty($row[
'ri_unifiedcolumns'])) {
215 $unifiedColumns = (array) json_decode($row[
'ri_unifiedcolumns'],
true);
217 $unifiedColumns = UploadDao::UNIFIED_REPORT_HEADINGS;
219 foreach ($unifiedColumns as $name => $unifiedReportColumns) {
220 foreach ($unifiedReportColumns as $columnName => $isenabled) {
221 $tableRowsUnifiedReport .=
'<tr>';
222 $tableRowsUnifiedReport .=
'<td><input class="form-control" type="text" style="width:95%" name="'.$name.
'[]" value="'.$columnName.
'"></td>';
225 $checked =
'checked';
227 $tableRowsUnifiedReport .=
'<td style="vertical-align:middle"><input class="browse-upload-checkbox view-license-rc-size" type="checkbox" style="width:95%" name="'.$name.
'[]" '.$checked.
'></td>';
228 $tableRowsUnifiedReport .=
'</tr>';
232 $tableRowsClixmlReport =
"";
233 $clixmlColumns = array();
234 if (!empty($row[
'ri_clixmlcolumns'])) {
235 $clixmlColumns = (array) json_decode($row[
'ri_clixmlcolumns'],
true);
237 $clixmlColumns = UploadDao::CLIXML_REPORT_HEADINGS;
239 foreach ($clixmlColumns as $name => $clixmlReportColumns) {
240 foreach ($clixmlReportColumns as $columnName => $isenabled) {
241 $tableRowsClixmlReport .=
'<tr>';
242 $tableRowsClixmlReport .=
'<td><input class="form-control" type="text" style="width:95%" name="'.$name.
'[]" value="'.$columnName.
'" readonly></td>';
245 $checked =
'checked';
247 $tableRowsClixmlReport .=
'<td style="vertical-align:middle"><input class="browse-upload-checkbox view-license-rc-size" type="checkbox" style="width:95%" name="'.$name.
'[]" '.$checked.
'></td>';
248 $tableRowsClixmlReport .=
'</tr>';
252 if (!empty($row[
'ri_globaldecision'])) {
253 $vars[
'applyGlobal'] =
"checked";
255 $vars[
'tableRows'] = $tableRows;
256 $vars[
'tableRowsUnifiedReport'] = $tableRowsUnifiedReport;
257 $vars[
'tableRowsClixmlReport'] = $tableRowsClixmlReport;
272 foreach ($clearingDecisions as $clearingDecision) {
273 if ($clearingDecision->getType() == DecisionTypes::IRRELEVANT) {
276 $uploadTreeId = $clearingDecision->getUploadTreeId();
277 foreach ($clearingDecision->getClearingLicenses() as $clearingLicense) {
278 if ($clearingLicense->isRemoved()) {
281 $ack = $clearingLicense->getAcknowledgement();
282 if (empty(
trim($ack))) {
285 if (!isset($ackData[$ack])) {
286 $ackData[$ack] = array(
'files' => array(),
'licenses' => array());
288 $ackData[$ack][
'files'][$uploadTreeId] =
true;
289 $ackData[$ack][
'licenses'][$clearingLicense->getShortName()] =
true;
293 foreach ($ackData as $ack => $data) {
294 $result[$ack] = array(
295 'count' => count($data[
'files']),
296 'licenses' => array_keys($data[
'licenses'])
312 foreach ($acknowledgements as $ackText => $ackData) {
313 $ackHtml = htmlspecialchars($ackText, ENT_QUOTES);
314 $licensesHtml = htmlspecialchars(implode(
', ', $ackData[
'licenses']));
316 $rows .=
'<td style="width:5%;text-align:center">'
317 .
'<a href="#" class="ack-count-link" data-upload="' . $uploadId
318 .
'" data-ack="' . $ackHtml .
'">' . $ackData[
'count'] .
'</a></td>';
319 $rows .=
'<td style="width:60%"><textarea class="ack-text-area" readonly="readonly"'
320 .
' data-original-ack="' . $ackHtml .
'"'
321 .
' style="overflow:auto;width:98%;height:60px;">'
322 . $ackHtml .
'</textarea></td>';
323 $rows .=
'<td style="width:25%">' . $licensesHtml .
'</td>';
324 $rows .=
'<td style="width:10%;text-align:center">'
325 .
'<button type="button" class="btn btn-danger btn-sm delete-ack-btn"'
326 .
' data-upload="' . $uploadId .
'" data-ack="' . $ackHtml .
'">'
327 .
'Delete</button></td>';
341 $allClearedLicenses = array();
343 foreach ($clearingDecisions as $clearingDecision) {
344 if ($clearingDecision->getType() == DecisionTypes::IRRELEVANT) {
347 foreach ($clearingDecision->getClearingLicenses() as $clearingLicense) {
348 if ($clearingLicense->isRemoved()) {
351 $allClearedLicenses[$licenseMap->getProjectedId($clearingLicense->getLicenseId())] =
true;
354 $uniqueLicenseIds = array_keys($allClearedLicenses);
355 $allObligations = array_merge(
356 $this->licenseDao->getLicenseObligations($uniqueLicenseIds) ?: array(),
357 $this->licenseDao->getLicenseObligations($uniqueLicenseIds,
true) ?: array()
359 $groupedObligations = array();
360 foreach ($allObligations as $obligation) {
361 $groupBy = $obligation[
'ob_topic'];
362 $licenseName = LicenseRef::convertToSpdxId($obligation[
'rf_shortname'],
363 $obligation[
'rf_spdx_id']);
364 if (array_key_exists($groupBy, $groupedObligations)) {
365 $groupedObligations[$groupBy][
'license'][$licenseName] =
true;
367 $groupedObligations[$groupBy] = array(
368 "topic" => $obligation[
'ob_topic'],
369 "text" => $obligation[
'ob_text'],
370 "license" => array($licenseName =>
true)
374 foreach ($groupedObligations as &$ob) {
375 $ob[
'license'] = array_keys($ob[
'license']);
377 return $groupedObligations;
387 foreach ($listParams as $listkey => $listValue) {
389 if ($ret != $listkey) {
390 $cbList[] =
"unchecked";
392 $cbList[] =
"checked";
395 $cbSelectionList = implode(
",", $cbList);
397 return $cbSelectionList;
409 $stored = $this->uploadDao->getCyclonedxSettings($uploadId);
411 $namespace = $namespaceInput ===
'' ? CycloneDxSettings::DEFAULT_NAMESPACE :
412 CycloneDxSettings::normaliseNamespace($namespaceInput);
413 if ($namespace ===
null) {
414 $namespace = $stored->getTagNamespace();
415 $this->vars[
'message'] = sprintf(_(
"CycloneDX tag namespace not saved: " .
416 "use letters, digits, '.', '_', '-' and ':' (at most %d characters)."),
417 CycloneDxSettings::MAX_NAMESPACE_LENGTH);
419 $specVersion = CycloneDxSettings::normaliseSpecVersion(
425 $namespace, $specVersion);
431 $groupId = Auth::getGroupId();
432 $userId = Auth::getUserId();
433 if (!$this->uploadDao->isAccessible($uploadId, $groupId)) {
438 $this->vars[
'micromenu'] =
Dir2Browse(
"browse", $itemId,
null, $showBox = 0,
"View-Meta");
439 $this->vars[
'globalClearingAvailable'] = Auth::isClearingAdmin();
443 if (isset($submitReportConf)) {
444 $applyGlobal = @$_POST[
"applyGlobal"];
445 $applyGlobal = !empty($applyGlobal) ? 1 : 0;
447 $obLicensesEncoded = @$_POST[
"obLicenses"];
448 $obLicensesEncoded = !empty($obLicensesEncoded) ? $obLicensesEncoded : array();
449 $obLicenses = array();
450 array_walk($obLicensesEncoded,
451 function (&$licArray, $obTopic) use (&$obLicenses) {
452 $obLicenses[urldecode($obTopic)] = $licArray;
457 foreach ($this->mapDBColumns as $key => $value) {
458 $columns .= $value.
" = $".$i.
", ";
464 $unifiedReportColumnsForJson = array();
465 foreach (UploadDao::UNIFIED_REPORT_HEADINGS as $columnName => $columnValue) {
466 $columnResult = @$_POST[$columnName];
467 $unifiedReportColumnsForJson[$columnName] = array($columnResult[0] => isset($columnResult[1]) ? $columnResult[1] :
null);
469 $clixmlColumnsForJson = array();
470 foreach (UploadDao::CLIXML_REPORT_HEADINGS as $columnName => $columnValue) {
471 $columnResult = @$_POST[$columnName];
472 $clixmlColumnsForJson[$columnName] = array($columnResult[0] => isset($columnResult[1]) ? $columnResult[1] :
null);
474 $checkBoxUrPos = count($parms);
476 $checkBoxSpdxPos = count($parms);
478 $cyclonedxPos = count($parms);
479 $parms[] = json_encode($obLicenses);
480 $excludeObligationPos = count($parms);
481 $parms[] = json_encode($unifiedReportColumnsForJson);
482 $unifiedColumnsPos = count($parms);
483 $parms[] = json_encode($clixmlColumnsForJson);
484 $clixmlColumnsPos = count($parms);
485 $parms[] = $applyGlobal;
486 $applyGlobalPos = count($parms);
487 $parms[] = $uploadId;
488 $uploadIdPos = count($parms);
490 $SQL =
"UPDATE report_info SET $columns" .
491 "ri_ga_checkbox_selection = $$checkBoxUrPos, " .
492 "ri_spdx_selection = $$checkBoxSpdxPos, " .
493 "ri_cyclonedx_selection = $$cyclonedxPos, " .
494 "ri_excluded_obligations = $$excludeObligationPos, " .
495 "ri_unifiedcolumns = $$unifiedColumnsPos, " .
496 "ri_clixmlcolumns = $$clixmlColumnsPos, " .
497 "ri_globaldecision = $$applyGlobalPos " .
498 "WHERE upload_fk = $$uploadIdPos;";
499 $this->
dbManager->getSingleRow($SQL, $parms,
500 __METHOD__ .
"updateReportInfoData");
502 if (@$_POST[
'markGlobal']) {
503 $upload = $this->uploadDao->getUpload($uploadId);
504 $uploadName = $upload->getFilename();
505 $jobId = JobAddJob($userId, $groupId, $uploadName, $uploadId);
508 $conflictStrategyId =
"global";
510 $deciderPlugin->AgentAdd($jobId, $uploadId, $errorMsg, array(), $conflictStrategyId);
513 $text = _(
"Your jobs have been added to job queue.");
514 $linkText = _(
"View Jobs");
515 $this->vars[
'message'] =
"$schedulerMsg" .
"$text <a href=\"$url\">$linkText</a>";
519 $this->vars[
'typemap'] = [];
520 foreach (ComponentType::TYPE_MAP as $key => $name) {
521 if ($key == ComponentType::PACKAGEURL) {
524 $this->vars[
'typemap'][] = [
'key' => $key,
'name' => $name];
528 public function getTemplateName()
530 return "ui-report-conf.html.twig";
541 var reportTabCookie = 'stickyReportTab';
543 $(document).ready(function() {
544 $(\"#confTabs\").tabs({
545 active: ($.cookie(reportTabCookie) || 0),
546 activate: function(e, ui){
547 // Get active tab index and update cookie
548 var idString = $(e.currentTarget).attr('id');
549 idString = parseInt(idString.slice(-1)) - 1;
550 $.cookie(reportTabCookie, idString);
551 // Hide submit button on acknowledgements tab (AJAX-only tab)
552 if (ui.newPanel.attr('id') === 'acknowledgementsConfTab') {
553 $('#reportConfSubmit').hide();
555 $('#reportConfSubmit').show();
560 var activeIdx = $.cookie(reportTabCookie) || 0;
561 if ($(\"#confTabs .ui-tabs-panel\").eq(activeIdx).attr('id') === 'acknowledgementsConfTab') {
562 $('#reportConfSubmit').hide();
564 $(\"input[name='dependencySourceBinary']\").change(function(){
565 var val = $(\"input[name='dependencySourceBinary']:checked\").val();
566 if (val == 'noDependency') {
567 $('#dependencyBinarySource').hide();
568 $('#dependencyBinarySource').val('');
570 $('#dependencyBinarySource').css('display', 'block');
573 $(\"input[name='exportRestriction']\").change(function(){
574 var val = $(\"input[name='exportRestriction']:checked\").val();
575 if (val == 'noExportRestriction') {
576 $('#exportRestrictionText').hide();
577 $('#exportRestrictionText').val('');
579 $('#exportRestrictionText').css('display', 'block');
582 $(\"input[name='restrictionForUse']\").change(function(){
583 var val = $(\"input[name='restrictionForUse']:checked\").val();
584 if (val == 'noRestriction') {
585 $('#copyrightRestrictionText').hide();
586 $('#copyrightRestrictionText').val('');
588 $('#copyrightRestrictionText').css('display', 'block');
592 $('#osselotExportCheckbox').change(function() {
593 if ($(this).is(':checked')) {
594 $('#spdxLicenseCommentCheckbox').prop('checked', true);
597 $('#spdxLicenseCommentCheckbox').change(function() {
599 $('#osselotExportCheckbox').prop('checked', false);
603 $('#cyclonedxOsselotExportCheckbox').change(function() {
604 if ($(this).is(':checked')) {
605 $('#cyclonedxLicenseCommentCheckbox').prop('checked', true);
608 $('#cyclonedxLicenseCommentCheckbox').change(function() {
610 $('#cyclonedxOsselotExportCheckbox').prop('checked', false);
614 $(\"[data-toggle='tooltip']\").tooltip();
616 var ackAjaxUrl = '?mod=ajax-acknowledgement-conf';
618 $(document).on('click', '.ack-count-link', function(e) {
620 var upload = $(this).data('upload');
621 var ack = $(this).data('ack');
622 $('#ackFilesModalBody').html('<p>Loading...</p>');
623 $('#ackFilesModalCount').text('');
624 $('#ackFilesModal').modal('show');
625 $.get(ackAjaxUrl, {action: 'getFiles', upload: upload, ack: ack},
627 if (!data || data.length === 0) {
628 $('#ackFilesModalCount').text('');
629 $('#ackFilesModalBody').html('<p>No files found.</p>');
632 $('#ackFilesModalCount').text('(' + data.length + ' files)');
633 var html = '<table class=\"table table-condensed table-hover\" style=\"margin:0;\">'
634 + '<thead><tr><th>#</th><th>File</th></tr></thead><tbody>';
635 $.each(data, function(i, file) {
636 html += '<tr><td>' + (i + 1) + '</td>'
637 + '<td><a href=\"' + file.url + '\" target=\"_blank\">'
638 + $('<span/>').text(file.name).html()
641 html += '</tbody></table>';
642 $('#ackFilesModalBody').html(html);
645 $('#ackFilesModalCount').text('');
646 $('#ackFilesModalBody').html('<p>Error loading files.</p>');
650 $(document).on('dblclick', '.ack-text-area', function() {
651 if (!$(this).prop('readonly')) {
655 var td = ta.closest('td');
656 ta.prop('readonly', false).css('border', '2px solid #337ab7').focus();
657 td.append('<div class=\"ack-edit-actions\" style=\"margin-top:4px;\">'
658 + '<button type=\"button\" class=\"btn btn-primary btn-xs ack-save-btn\">Save</button> '
659 + '<button type=\"button\" class=\"btn btn-light btn-xs ack-cancel-btn\">Cancel</button>'
663 $(document).on('click', '.ack-save-btn', function() {
664 var row = $(this).closest('tr');
665 var td = $(this).closest('td');
666 var ta = td.find('.ack-text-area');
667 var newAck = ta.val();
668 var oldAck = ta.data('original-ack');
669 if (newAck === oldAck) {
670 ta.prop('readonly', true).css('border', '');
671 td.find('.ack-edit-actions').remove();
674 var upload = row.find('.ack-count-link').data('upload');
676 {action: 'updateAcknowledgement', upload: upload, oldAck: oldAck, newAck: newAck},
678 if (data && data.status === 'success') {
679 $('#acknowledgementsConfTab tbody tr').each(function() {
680 if ($(this).find('.ack-text-area').data('original-ack') === newAck
681 && this !== row[0]) {
686 .data('original-ack', newAck)
687 .prop('readonly', true)
689 row.find('.ack-count-link').text(data.count).data('ack', newAck);
690 row.find('.delete-ack-btn').data('ack', newAck);
691 td.find('.ack-edit-actions').remove();
693 alert('Failed to update acknowledgement.');
697 alert('Error communicating with server.');
701 $(document).on('click', '.ack-cancel-btn', function() {
702 var td = $(this).closest('td');
703 var ta = td.find('.ack-text-area');
704 ta.val(ta.data('original-ack'))
705 .prop('readonly', true)
707 td.find('.ack-edit-actions').remove();
710 $(document).on('click', '.delete-ack-btn', function() {
711 var upload = $(this).data('upload');
712 var ack = $(this).data('ack');
713 if (!confirm('Are you sure you want to clear this acknowledgement from all files?')) {
716 var row = $(this).closest('tr');
718 {action: 'deleteAcknowledgement', upload: upload, ack: ack},
720 if (data && data.status === 'success') {
721 row.fadeOut(400, function() { row.remove(); });
723 alert('Failed to delete acknowledgement.');
727 alert('Error communicating with server.');
736 $NewPlugin->Initialize();
This is the Plugin class. All plugins should:
Output()
This function is called when user output is requested. This function is responsible for content....
Contains the constants and helpers for authentication of user.
Wrapper class for license map.
CycloneDX export settings of a user (defaults) or of an upload.
getAllObligationsForGivenUploadId($groupId, array $clearingDecisions)
get all the obligations for cleared licenses
createScriptBlock()
Create Script block for conf.
buildAcknowledgementRows($uploadId, array $clearingDecisions)
Build HTML table rows for acknowledgements tab.
getAllAcknowledgementsForUpload(array $clearingDecisions)
Get acknowledgements for cleared licenses grouped by text.
allReportConfiguration($uploadId, $groupId)
list all the options for Report Configuration
RegisterMenus()
Customize submenus.
getCheckBoxSelectionList($listParams)
__construct()
base constructor. Most plugins will just use this
getCycloneDxSettingsFromRequest($uploadId)
CycloneDX settings submitted with the form.
Dir2Browse($Mod, $UploadtreePk, $LinkLast=NULL, $ShowBox=1, $ShowMicro=NULL, $Enumerate=-1, $PreText='', $PostText='', $uploadtree_tablename="uploadtree")
Get an html linked string of a file browse path.
Traceback_uri()
Get the URI without query to this location.
GetParm($parameterName, $parameterType)
This function will retrieve the variables and check data types.
Traceback_parm_keep($List)
Create a new URI, keeping only these items.
plugin_find($pluginName)
Given the official name of a plugin, return the $Plugins object.
GetRunnableJobList()
Get runnable job list, the process is below:
char * trim(char *ptext)
Trimming whitespace.
#define PLUGIN_DB_READ
Plugin requires read permission on DB.
fo_dbManager * dbManager
fo_dbManager object