diff --git a/.github/emass.json b/.github/emass.json new file mode 100644 index 000000000..6fce935cc --- /dev/null +++ b/.github/emass.json @@ -0,0 +1,6 @@ +{ + "systemID": 999, + "systemName": "LEAF", + "systemOwnerName": "Michael Gao", + "systemOwnerEmail": "michael.gao@va.gov" +} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..f34dddd05 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,31 @@ +name: CodeQL +'on': + push: + branches: + - master + pull_request: + branches: + - master + schedule: + - cron: 1 21 * * 2 + workflow_dispatch: null +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + concurrency: ${{ github.workflow }}-${{ matrix.language }}-${{ github.ref }} + permissions: + actions: read + contents: read + security-events: write + strategy: + fail-fast: false + matrix: + language: + - javascript + - java + steps: + - name: Run Code Scanning + uses: department-of-veterans-affairs/codeql-tools/codeql-analysis@main + with: + language: ${{ matrix.language }} diff --git a/LEAF_Nexus/sources/Employee.php b/LEAF_Nexus/sources/Employee.php index cb2979208..485e68c75 100644 --- a/LEAF_Nexus/sources/Employee.php +++ b/LEAF_Nexus/sources/Employee.php @@ -496,7 +496,7 @@ private function isActiveNationally(array $user): bool * * Created at: 6/9/2023, 2:31:07 PM (America/New_York) */ - private function getEmployeeByUserName(array $user_names, Db $db): array + public function getEmployeeByUserName(array $user_names, Db $db): array { $sql = "SELECT `empUID`, `userName`, `lastName`, `firstName`, `middleName`, `phoneticLastName`, `phoneticFirstName`, `domain`, `deleted`, @@ -562,7 +562,7 @@ private function getEmployeeDataByEmpUID(array $empUID, Db $db): array private function updateEmployeeByUserName(string $user_name, array $national_user, Db $db): array { $vars = array( - ':userName' => $user_name, + ':userName' => $national_user['user_name'], ':lastName' => $national_user['lastName'], ':firstName' => $national_user['firstName'], ':midInit' => $national_user['middleName'], @@ -570,10 +570,12 @@ private function updateEmployeeByUserName(string $user_name, array $national_use ':phoneticLname' => $national_user['phoneticLastName'], ':domain' => $national_user['domain'], ':deleted' => $national_user['deleted'], - ':lastUpdated' => $national_user['lastUpdated'] + ':lastUpdated' => $national_user['lastUpdated'], + ':localUserName' => $user_name ); $sql = "UPDATE `employee` - SET `lastName` = :lastName, + SET `userName` = :userName, + `lastName` = :lastName, `firstName` = :firstName, `middleName` = :midInit, `phoneticFirstName` = :phoneticFname, @@ -581,7 +583,7 @@ private function updateEmployeeByUserName(string $user_name, array $national_use `domain` = :domain, `deleted` = :deleted, `lastUpdated` = :lastUpdated - WHERE `userName` = :userName"; + WHERE `userName` = :localUserName"; $result = $db->prepared_query($sql, $vars); $return_value = array( @@ -676,8 +678,8 @@ public function addNew($firstName, $lastName, $middleName = '', $userName = '', ':phoLastName' => metaphone($this->sanitizeInput($lastName)), ':lastUpdated' => time(), ); $this->db->prepared_query('INSERT INTO employee (firstName, lastName, middleName, userName, phoneticFirstName, phoneticLastName, lastUpdated, new_empUUID) - VALUES (:firstName, :lastName, :middleName, :userName, :phoFirstName, :phoLastName, :lastUpdated, UUID()) - ON DUPLICATE KEY UPDATE deleted=0', $vars); + VALUES (:firstName, :lastName, :middleName, :userName, :phoFirstName, :phoLastName, :lastUpdated, UUID()) + ON DUPLICATE KEY UPDATE deleted=0', $vars); $empUID = $this->lookupLogin($this->sanitizeInput($userName))[0]['empUID']; @@ -736,8 +738,8 @@ public function importFromNational($userName) ':timestamp' => time(), ':author' => 'imported', ); $this->db->prepared_query("INSERT INTO {$this->dataTable} ({$this->dataTableUID}, indicatorID, data, timestamp, author) - VALUES (:UID, :indicatorID, :data, :timestamp, :author) - ON DUPLICATE KEY UPDATE data=:data, timestamp=:timestamp, author=:author", $vars); + VALUES (:UID, :indicatorID, :data, :timestamp, :author) + ON DUPLICATE KEY UPDATE data=:data, timestamp=:timestamp, author=:author", $vars); } // Email @@ -747,8 +749,8 @@ public function importFromNational($userName) ':timestamp' => time(), ':author' => 'imported', ); $this->db->prepared_query("INSERT INTO {$this->dataTable} ({$this->dataTableUID}, indicatorID, data, timestamp, author) - VALUES (:UID, :indicatorID, :data, :timestamp, :author) - ON DUPLICATE KEY UPDATE data=:data, timestamp=:timestamp, author=:author", $vars); + VALUES (:UID, :indicatorID, :data, :timestamp, :author) + ON DUPLICATE KEY UPDATE data=:data, timestamp=:timestamp, author=:author", $vars); if ($res[0]['data'][8]['data'] != '') { @@ -759,8 +761,8 @@ public function importFromNational($userName) ':timestamp' => time(), ':author' => 'imported', ); $this->db->prepared_query("INSERT INTO {$this->dataTable} ({$this->dataTableUID}, indicatorID, data, timestamp, author) - VALUES (:UID, :indicatorID, :data, :timestamp, :author) - ON DUPLICATE KEY UPDATE data=:data, timestamp=:timestamp, author=:author", $vars); + VALUES (:UID, :indicatorID, :data, :timestamp, :author) + ON DUPLICATE KEY UPDATE data=:data, timestamp=:timestamp, author=:author", $vars); } if ($res[0]['data'][23]['data'] != '') @@ -772,8 +774,8 @@ public function importFromNational($userName) ':timestamp' => time(), ':author' => 'imported', ); $this->db->prepared_query("INSERT INTO {$this->dataTable} ({$this->dataTableUID}, indicatorID, data, timestamp, author) - VALUES (:UID, :indicatorID, :data, :timestamp, :author) - ON DUPLICATE KEY UPDATE data=:data, timestamp=:timestamp, author=:author", $vars); + VALUES (:UID, :indicatorID, :data, :timestamp, :author) + ON DUPLICATE KEY UPDATE data=:data, timestamp=:timestamp, author=:author", $vars); } return $empUID; @@ -930,7 +932,7 @@ public function lookupEmpUID($empUID) $sqlVars = array(':empUID' => $empUID); $result = $this->db->prepared_query($strSQL, $sqlVars); - $strSQL = "SELECT data AS email FROM {$this->dataTable} WHERE empUID=:empUID AND indicatorID = 6"; + $strSQL = "SELECT data AS email FROM {$this->dataTable} WHERE empUID=:empUID AND indicatorID = 6"; $resEmail = $this->db->prepared_query($strSQL, $sqlVars); if(isset($result[0]) && isset($resEmail[0])) { @@ -1091,12 +1093,13 @@ public function lookupName($lastName, $firstName, $middleName = '') public function lookupEmail($email) { - $sql = "SELECT * FROM {$this->dataTable} - LEFT JOIN {$this->tableName} USING (empUID) - WHERE indicatorID = 6 - AND data = :email - AND deleted = 0 - {$this->limit}"; + $sql = "SELECT * + FROM {$this->dataTable} + LEFT JOIN {$this->tableName} USING (empUID) + WHERE indicatorID = 6 + AND data = :email + AND deleted = 0 + {$this->limit}"; $vars = array(':email' => $email); @@ -1106,11 +1109,11 @@ public function lookupEmail($email) public function lookupPhone($phone) { $sql = "SELECT * FROM {$this->dataTable} - LEFT JOIN {$this->tableName} USING (empUID) - WHERE indicatorID = 5 - AND data LIKE :phone - AND deleted = 0 - {$this->limit}"; + LEFT JOIN {$this->tableName} USING (empUID) + WHERE indicatorID = 5 + AND data LIKE :phone + AND deleted = 0 + {$this->limit}"; $vars = array(':phone' => $this->parseWildcard('*' . $phone)); @@ -1124,8 +1127,8 @@ public function lookupByIndicatorID($indicatorID, $query) ); $res = $this->db->prepared_query("SELECT * FROM {$this->dataTable} - LEFT JOIN {$this->tableName} USING ({$this->dataTableUID}) - WHERE indicatorID = :indicatorID + LEFT JOIN {$this->tableName} USING ({$this->dataTableUID}) + WHERE indicatorID = :indicatorID AND data LIKE :query AND deleted=0", $vars); @@ -1144,9 +1147,9 @@ public function getBackups($empUID) } $vars = array(':empUID' => $empUID); $res = $this->db->prepared_query('SELECT * FROM relation_employee_backup - LEFT JOIN employee ON - relation_employee_backup.backupEmpUID = employee.empUID - WHERE relation_employee_backup.empUID=:empUID', $vars); + LEFT JOIN employee ON + relation_employee_backup.backupEmpUID = employee.empUID + WHERE relation_employee_backup.empUID=:empUID', $vars); $this->cache["getBackups_{$empUID}"] = $res; @@ -1169,8 +1172,8 @@ public function getBackupsFor($empUID) } $vars = array(':empUID' => $empUID); $res = $this->db->prepared_query('SELECT * FROM relation_employee_backup - LEFT JOIN employee USING (empUID) - WHERE relation_employee_backup.backupEmpUID=:empUID', $vars); + LEFT JOIN employee USING (empUID) + WHERE relation_employee_backup.backupEmpUID=:empUID', $vars); $this->cache["getBackupsFor_{$empUID}"] = $res; @@ -1199,7 +1202,7 @@ public function setBackup($primaryEmpUID, $backupEmpUID) ':backupEmpUID' => $backupEmpUID, ':approver' => $this->login->getUserID(), ); $res = $this->db->prepared_query('INSERT INTO relation_employee_backup (empUID, backupEmpUID, approved, approverUserName) - VALUES (:empUID, :backupEmpUID, 1, :approver)', $vars); + VALUES (:empUID, :backupEmpUID, 1, :approver)', $vars); return true; } @@ -1225,7 +1228,7 @@ public function removeBackup($primaryEmpUID, $backupEmpUID) $vars = array(':empUID' => $primaryEmpUID, ':backupEmpUID' => $backupEmpUID, ); $res = $this->db->prepared_query('DELETE FROM relation_employee_backup - WHERE empUID=:empUID AND backupEmpUID=:backupEmpUID', $vars); + WHERE empUID=:empUID AND backupEmpUID=:backupEmpUID', $vars); return true; } diff --git a/LEAF_Request_Portal/admin/ajaxJSON.php b/LEAF_Request_Portal/admin/ajaxJSON.php index bb78884e5..53302f475 100644 --- a/LEAF_Request_Portal/admin/ajaxJSON.php +++ b/LEAF_Request_Portal/admin/ajaxJSON.php @@ -7,6 +7,10 @@ JSON index for legacy ajax endpoints Date Created: August 13, 2009 + This file has been deprecated, as of June 28, 2023 there is nothing in here + that is used in the general LEAF application, It is being left until we can + verify that it is not used in any custom setups. + */ error_reporting(E_ERROR); @@ -29,7 +33,7 @@ case 'mod_groups_getMembers': $group = new Portal\Group($db, $login); - echo json_encode($group->getMembers($_GET['groupID'])); + echo $group->getMembers($_GET['groupID'])['data']; break; case 'directory_lookup': diff --git a/LEAF_Request_Portal/admin/index.php b/LEAF_Request_Portal/admin/index.php index 70469ca3b..49220feb3 100644 --- a/LEAF_Request_Portal/admin/index.php +++ b/LEAF_Request_Portal/admin/index.php @@ -197,6 +197,7 @@ function hasDevConsoleAccess($login, $oc_db) $libsPath.'js/codemirror/lib/codemirror.css', $libsPath.'js/codemirror/addon/display/fullscreen.css', $libsPath.'js/choicesjs/choices.min.css', + $libsPath.'js/vue-dest/form_editor/LEAF_FormEditor.css', $site_paths['orgchart_path'] . '/css/employeeSelector.css', $site_paths['orgchart_path'] . '/css/groupSelector.css', $site_paths['orgchart_path'] . '/css/positionSelector.css' @@ -537,6 +538,37 @@ function hasDevConsoleAccess($login, $oc_db) $main->assign('body', 'You require System Administrator level access to view this section.'); } + break; + case 'site_designer': + $t_form = new Smarty; + $t_form->left_delimiter = ''; + $libsPath = '../../libs/'; + $t_form->assign('CSRFToken', $_SESSION['CSRFToken']); + $t_form->assign('APIroot', '../api/'); + $t_form->assign('libsPath', $libsPath); + $t_form->assign('orgchartPath', '../..'.$site_paths['orgchart_path']); + $t_form->assign('userID', Leaf\XSSHelpers::sanitizeHTML($login->getUserID())); + + $main->assign('javascripts', array( + '../js/form.js', '../js/formGrid.js', '../js/formQuery.js', '../js/formSearch.js', + $libsPath.'js/jquery/chosen/chosen.jquery.min.js', + $libsPath.'js/choicesjs/choices.min.js', + $libsPath.'js/LEAF/XSSHelpers.js', + $libsPath.'js/jquery/jquery-ui.custom.min.js', + $libsPath.'js/jquery/trumbowyg/trumbowyg.min.js' + )); + $main->assign('stylesheets', array( + $libsPath.'js/jquery/chosen/chosen.min.css', + $libsPath.'js/choicesjs/choices.min.css', + $libsPath.'js/vue-dest/site_designer/LEAF_Designer.css' + )); + + if ($login->checkGroup(1)) { + $main->assign('body', $t_form->fetch('site_designer_vue.tpl')); + } else { + $main->assign('body', 'You require System Administrator level access to view this section.'); + } break; default: // $main->assign('useDojo', false); diff --git a/LEAF_Request_Portal/admin/templates/form_editor_vue.tpl b/LEAF_Request_Portal/admin/templates/form_editor_vue.tpl index 78d0601d1..0579e8fe1 100644 --- a/LEAF_Request_Portal/admin/templates/form_editor_vue.tpl +++ b/LEAF_Request_Portal/admin/templates/form_editor_vue.tpl @@ -19,7 +19,7 @@ - + + + + +
The page you are looking for does not exist or may have been moved. Please update your bookmarks.
+ \ No newline at end of file diff --git a/LEAF_Request_Portal/admin/templates/site_elements/generic_OkDialog.tpl b/LEAF_Request_Portal/admin/templates/site_elements/generic_OkDialog.tpl new file mode 100644 index 000000000..f6221b74f --- /dev/null +++ b/LEAF_Request_Portal/admin/templates/site_elements/generic_OkDialog.tpl @@ -0,0 +1,6 @@ + diff --git a/LEAF_Request_Portal/admin/templates/view_admin_menu.tpl b/LEAF_Request_Portal/admin/templates/view_admin_menu.tpl index 1e23aeac3..6afad3878 100644 --- a/LEAF_Request_Portal/admin/templates/view_admin_menu.tpl +++ b/LEAF_Request_Portal/admin/templates/view_admin_menu.tpl @@ -1,35 +1,42 @@
+

Get Help

+ + + + LEAF Support + Access VA LEAF Support Services + +

+

User Access

- + User Access Groups Modify users and groups - + Service Chiefs Review service chiefs and set backups - + Access Matrix Configure group access to tasks +

Site Configuration

- + Workflow Editor Edit flowcharts for workflows @@ -37,8 +44,7 @@ - + Form Editor Create and Modify Forms @@ -46,15 +52,14 @@ - + LEAF Library Use a form made by the LEAF community - + Site Settings Edit site name, time zone, and other labels @@ -62,126 +67,124 @@ - + Site Distribution Deploy changes to subordinate sites +

Admin Oversight Tools

- + Unresolved Requests Examine potential delays - + Timeline Explorer Analyze timeline data - + Report Builder Create custom reports +

LEAF Developer Console

- + Template Editor Edit HTML Templates - + Email Template Editor Add and Edit Email Templates - + LEAF Programmer Advanced Reports and Custom Pages - + File Manager Upload custom images and documents - + Search Database Perform custom queries - + Sync Services Update Service listing from Org Chart - + Update Database Updates the system database, if available +

Toolbox

- + Import Spreadsheet Rows to requests, columns as fields - + Mass Actions Apply bulk actions to requests - + New Account Updater Update records with new account + - + Sitemap Editor Edit portal Sitemap links + + - + Combined Inbox Editor Edit combined inbox - + Grid Splitter Export grid form data to Excel spreadsheet diff --git a/LEAF_Request_Portal/api/controllers/GroupController.php b/LEAF_Request_Portal/api/controllers/GroupController.php index 7171c12fb..19b12fb81 100644 --- a/LEAF_Request_Portal/api/controllers/GroupController.php +++ b/LEAF_Request_Portal/api/controllers/GroupController.php @@ -47,6 +47,11 @@ public function get($act) }); $this->index['GET']->register('group/[digit]/members', function ($args) use ($group) { + $members = $group->getMembers($args[0]); + return $members['data']; + }); + + $this->index['GET']->register('group/[digit]/list_members', function ($args) use ($group) { return $group->getMembers($args[0]); }); diff --git a/LEAF_Request_Portal/api/controllers/SiteController.php b/LEAF_Request_Portal/api/controllers/SiteController.php index 3c99d3379..3e60d1cb3 100644 --- a/LEAF_Request_Portal/api/controllers/SiteController.php +++ b/LEAF_Request_Portal/api/controllers/SiteController.php @@ -48,6 +48,18 @@ public function post($act) $this->index['POST']->register('site/settings/sitemap_json', function ($args) use ($site) { return $site->setSitemapJSON(); }); + $this->index['POST']->register('site/settings/homepage_design_json', function ($args) use ($site) { + $list = $_POST['home_menu_list'] ?? []; + $direction = $_POST['menu_direction'] ?? 'v'; + return $site->setHomeDesignJSON($list, $direction); + }); + $this->index['POST']->register('site/settings/search_design_json', function ($args) use ($site) { + $list = $_POST['chosen_headers'] ?? []; + return $site->setSearchDesignJSON($list); + }); + $this->index['POST']->register('site/settings/enable_homepage', function ($args) use ($site) { + return $site->enableNoCodeHomepage((int)$_POST['enabled']); + }); return $this->index['POST']->runControl($act['key'], $act['args']); } diff --git a/LEAF_Request_Portal/index.php b/LEAF_Request_Portal/index.php index bf2e3224a..137e549ea 100644 --- a/LEAF_Request_Portal/index.php +++ b/LEAF_Request_Portal/index.php @@ -503,7 +503,7 @@ function customTemplate($tpl) { exit(); default: - $main->assign('javascripts', array('js/form.js', 'js/formGrid.js', 'js/formQuery.js', 'js/formSearch.js')); + $main->assign('javascripts', array('js/form.js', 'js/formGrid.js', 'js/formQuery.js', 'js/formSearch.js','../libs/js/LEAF/XSSHelpers.js',)); $main->assign('useLiteUI', true); $o_login = $t_login->fetch('login.tpl'); @@ -521,14 +521,19 @@ function customTemplate($tpl) { $t_form->assign('orgchartPath', $site_paths['orgchart_path']); $t_form->assign('CSRFToken', $_SESSION['CSRFToken']); - $t_form->assign('tpl_search', customTemplate('view_search.tpl')); - $inbox = new Portal\Inbox($db, $login); //$t_form->assign('inbox_status', $inbox->getInboxStatus()); // see Inbox.php -> getInboxStatus() $t_form->assign('inbox_status', 1); - - $main->assign('body', $t_form->fetch(customTemplate('view_homepage.tpl'))); + if (isset($settings['homepage_enabled']) && $settings['homepage_enabled'] == 1) { + $t_form->assign('homeDesignJSON', json_encode($settings['homepage_design_json'])); + $t_form->assign('searchDesignJSON', json_encode($settings['search_design_json'])); + $t_form->assign('tpl_search', 'nocode_templates/view_search.tpl'); + $main->assign('body', $t_form->fetch('./templates/nocode_templates/view_homepage.tpl')); + } else { + $t_form->assign('tpl_search', customTemplate('view_search.tpl')); + $main->assign('body', $t_form->fetch(customTemplate('view_homepage.tpl'))); + } if ($action != 'menu' && $action != '' && $action != 'dosubmit') { $main->assign('status', 'The page you are looking for does not exist or may have been moved. Please update your bookmarks.'); diff --git a/LEAF_Request_Portal/js/formSearch.js b/LEAF_Request_Portal/js/formSearch.js index 0fbf0b537..e96b77740 100644 --- a/LEAF_Request_Portal/js/formSearch.js +++ b/LEAF_Request_Portal/js/formSearch.js @@ -579,6 +579,7 @@ var LeafFormSearch = function (containerID) { * @memberOf LeafFormSearch */ function renderWidget(widgetID, callback) { + let url; switch ($("#" + prefixID + "widgetTerm_" + widgetID).val()) { case "title": $("#" + prefixID + "widgetCondition_" + widgetID).html( @@ -612,9 +613,10 @@ var LeafFormSearch = function (containerID) { \ ' ); + url = rootURL === '' ? './api/system/services' : rootURL + 'api/system/services'; $.ajax({ type: "GET", - url: "./api/system/services", + url, dataType: "json", success: function (res) { var services = @@ -680,9 +682,10 @@ var LeafFormSearch = function (containerID) { \ ' ); + url = rootURL === '' ? './api/workflow/categoriesUnabridged' : rootURL + 'api/workflow/categoriesUnabridged'; $.ajax({ type: "GET", - url: "./api/workflow/categoriesUnabridged", + url, dataType: "json", success: function (res) { var categories = @@ -738,9 +741,10 @@ var LeafFormSearch = function (containerID) { widgetID + '" value="=" /> =' ); + url = rootURL === '' ? './api/workflow/dependencies' : rootURL + 'api/workflow/dependencies'; $.ajax({ type: "GET", - url: "./api/workflow/dependencies", + url, dataType: "json", success: function (res) { var dependencies = @@ -796,9 +800,10 @@ var LeafFormSearch = function (containerID) { \ ' ); + url = rootURL === '' ? './api/workflow/steps' : rootURL + 'api/workflow/steps'; $.ajax({ type: "GET", - url: "./api/workflow/steps", + url, dataType: "json", success: function (res) { var categories = @@ -833,9 +838,10 @@ var LeafFormSearch = function (containerID) { }); break; case "data": + url = rootURL === '' ? './api/form/indicator/list' : rootURL + 'api/form/indicator/list'; $.ajax({ type: "GET", - url: "./api/form/indicator/list", + url, dataType: "json", success: function (res) { var indicators = diff --git a/LEAF_Request_Portal/js/formSearchMultisite.js b/LEAF_Request_Portal/js/formSearchMultisite.js new file mode 100644 index 000000000..797b9b74f --- /dev/null +++ b/LEAF_Request_Portal/js/formSearchMultisite.js @@ -0,0 +1,1111 @@ +/************************ + Form Search Widget for multiple sites + Search features are limited to common denominators across all LEAF sites. + */ + +var LeafFormSearchMultisite = function (containerID) { + let containerID = containerID; + let prefixID = "LeafFormSearchMultisite" + Math.floor(Math.random() * 1000) + "_"; + let localStorageNamespace = "LeafFormSearchMultisite" + getLocalStorageHash(); + let orgchartPath = ""; + let timer = 0; + let q = ""; + let intervalID = null; + let currRequest = null; + let numResults = 0; + let searchFunc = null; + let leafFormQuery = new LeafFormQuery(); + let widgetCounter = 0; + let rootURL = ""; + + // constants + const ALL_DATA_FIELDS = "0"; + const ALL_OC_EMPLOYEE_DATA_FIELDS = "0.0"; + + function renderUI() { + $("#" + containerID).html( + '
\ + search\ + \ + \ + \ + \ + \ +
\ +
\ +
' + ); + + let searchOrigWidth = 0; + $("#" + prefixID + "advancedOptionsClose").on("click", function () { + localStorage.setItem(localStorageNamespace + ".search", ""); + $("#" + prefixID + "searchtxt").val(""); + search(""); + $("#" + prefixID + "advancedOptionsClose").css("display", "none"); + $("#" + prefixID + "advancedOptions").slideUp(function () { + $("#" + prefixID + "advancedSearchButton").fadeIn(); + $("#" + prefixID + "searchtxt").css("display", "inline"); + $("#" + prefixID + "searchtxt").animate( + { width: searchOrigWidth }, + 400, + "swing" + ); + $("#" + prefixID + "searchtxt").focus(); + }); + }); + //added for keyboard navigation and accessibility to close advanced search options + + $("#" + prefixID + "advancedOptionsClose").on("keydown", function (e) { + if (e.keyCode == 13) { + localStorage.setItem(localStorageNamespace + ".search", ""); + $("#" + prefixID + "searchtxt").val(""); + search(""); + $("#" + prefixID + "advancedOptionsClose").css("display", "none"); + $("#" + prefixID + "advancedOptions").slideUp(function () { + $("#" + prefixID + "advancedSearchButton").fadeIn(); + $("#" + prefixID + "searchtxt").css("display", "inline"); + $("#" + prefixID + "searchtxt").animate( + { width: searchOrigWidth }, + 400, + "swing" + ); + $("#" + prefixID + "searchtxt").focus(); + }); + } + }); + + $("#" + prefixID + "advancedSearchButton").on("click", function () { + searchOrigWidth = $("#" + prefixID + "searchtxt").width(); + $("#" + prefixID + "advancedSearchButton").fadeOut(); + $("#" + prefixID + "searchtxt").animate( + { width: "0px" }, + 400, + "swing", + function () { + $("#" + prefixID + "searchtxt").css("display", "none"); + $("#" + prefixID + "advancedOptions").slideDown(function () { + $("#" + prefixID + "advancedOptionsClose").fadeIn(); + }); + $("#" + prefixID + "advancedOptions").css("display", "inline"); + chosenOptions(); + renderPreviousAdvancedSearch(); + $("#" + prefixID + "widgetMat_0").focus(); + } + ); + }); + + $("#" + prefixID + "advancedSearchApply").on("click", function () { + showBusy(); + generateSearchQuery(); + }); + $("#" + prefixID + "addTerm").on("click", function () { + newSearchWidget("AND"); + chosenOptions(); + }); + $("#" + prefixID + "orTerm").on("click", function () { + newSearchWidget("OR"); + chosenOptions(); + }); + + $("#" + prefixID + "searchtxt").on("keydown", function (e) { + showBusy(); + timer = 0; + if (e.keyCode == 13) { + // enter key + search($("#" + prefixID + "searchtxt").val()); + } + }); + + newSearchWidget(); + } + + /** + * @memberOf LeafFormSearchMultisite + */ + function init() { + renderUI(); + + intervalID = setInterval(function () { + inputLoop(); + }, 200); + if ( + !/Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( + navigator.userAgent + ) && + window.location.search !== "" + ) { + focus(); + } + if (getLastSearch() == null) { + search("*"); + } else { + let lastSearch = getLastSearch(); + + let isJSON = true; + let advSearch = {}; + try { + advSearch = JSON.parse(lastSearch); + } catch (err) { + isJSON = false; + } + + if (lastSearch.substr(0, 1) != "[") { + isJSON = false; + } + + if (isJSON) { + $("#" + prefixID + "advancedSearchButton").click(); + search(lastSearch); + } else { + if (lastSearch == "") { + search("*"); + } + $("#" + prefixID + "searchtxt").val(lastSearch); + } + } + } + + /** + * @memberOf LeafFormSearchMultisite + * prevQuery - optional JSON object + */ + function renderPreviousAdvancedSearch(prevQuery) { + let isJSON = true; + let advSearch = {}; + try { + if (prevQuery != undefined) { + advSearch = prevQuery; + } else { + advSearch = JSON.parse(getLastSearch()); + } + } catch (err) { + isJSON = false; + } + if (isJSON && advSearch != null && widgetCounter <= advSearch.length) { + for (let i = 1; i < advSearch.length; i++) { + newSearchWidget(advSearch[i].gate); + firstChild(); + } + for (let i = 0; i < advSearch.length; i++) { + $("#" + prefixID + "widgetTerm_" + i).val(advSearch[i].id); + $("#" + prefixID + "widgetTerm_" + i).trigger("chosen:updated"); + if ( + advSearch[i].indicatorID != undefined || + advSearch[i].id == "serviceID" || + advSearch[i].id == "categoryID" || + advSearch[i].id == "stepID" + ) { + renderWidget( + i, + (function (widgetID, indicatorID, operator, match, gate) { + return function () { + $("#" + prefixID + "widgetIndicator_" + widgetID).val( + indicatorID + ); + $("#" + prefixID + "widgetIndicator_" + widgetID).trigger( + "chosen:updated" + ); + $("#" + prefixID + "widgetCod_" + widgetID).val(operator); + $("#" + prefixID + "widgetCod_" + widgetID).trigger("change"); + $("#" + prefixID + "widgetCod_" + widgetID).trigger( + "chosen:updated" + ); + $("#" + prefixID + "widgetMat_" + widgetID).val( + match.replace(/\*/g, "") + ); + $("#" + prefixID + "widgetMat_" + widgetID).trigger( + "chosen:updated" + ); + }; + })( + i, + advSearch[i].indicatorID, + advSearch[i].operator, + advSearch[i].match, + advSearch[i].gate + ) + ); + } else { + renderWidget(i); + } + $("#" + prefixID + "widgetCod_" + i).val(advSearch[i].operator); + if (typeof advSearch[i].match == "string") { + $("#" + prefixID + "widgetMat_" + i).val( + advSearch[i].match.replace(/\*/g, "") + ); + } + } + } + } + + /** + * @memberOf LeafFormSearchMultisite + * From: http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/ + */ + function getLocalStorageHash() { + let hash = 0, + i, + chr, + len; + if (document.URL.length == 0) return hash; + for (i = 0, len = document.URL.length; i < len; i++) { + chr = document.URL.charCodeAt(i); + hash = (hash << 5) - hash + chr; + hash |= 0; // Convert to 32bit integer + } + return hash; + } + + /** + * @memberOf LeafFormSearchMultisite + */ + function setOrgchartPath(path) { + orgchartPath = path; + } + + /** + * @memberOf LeafFormSearchMultisite + */ + function getLastSearch() { + return localStorage.getItem(localStorageNamespace + ".search"); + } + + /** + * @memberOf LeafFormSearchMultisite + */ + function setSearchFunc(func) { + searchFunc = func; + } + + /** + * @memberOf LeafFormSearchMultisite + */ + function search(txt) { + if (txt != "*") { + localStorage.setItem(localStorageNamespace + ".search", txt); + } + return searchFunc(txt); + } + + /** + * @memberOf LeafFormSearchMultisite + */ + function inputLoop() { + if ($("#" + prefixID + "searchtxt") == null) { + clearInterval(intervalID); + return false; + } + timer += timer > 5000 ? 0 : 200; + if (timer > 400) { + let txt = $("#" + prefixID + "searchtxt").val(); + + if (txt != "" && txt != q) { + q = txt; + + if ( + currRequest != null && + currRequest.abort != undefined && + typeof currRequest.abort == "function" + ) { + currRequest.abort(); + } + + currRequest = search(txt); + } else if (txt == "") { + if (txt != q) { + search(""); + } + q = txt; + $("#" + this.prefixID + "_result").html(""); + numResults = 0; + showNotBusy(); + } else { + showNotBusy(); + } + } + } + + /** + * @memberOf LeafFormSearchMultisite + */ + function focus() { + $("#" + prefixID + "searchtxt").focus(); + } + + /** + * @memberOf LeafFormSearchMultisite + */ + function showBusy() { + $("#" + prefixID + "searchIcon").css("display", "none"); + $("#" + prefixID + "searchIconBusy").css("display", "inline"); + $(".status").text("Loading"); + } + + /** + * @memberOf LeafFormSearchMultisite + */ + function showNotBusy() { + $("#" + prefixID + "searchIcon").css("display", "inline"); + $("#" + prefixID + "searchIconBusy").css("display", "none"); + } + + /** + * @memberOf LeafFormSearchMultisite + */ + function createEmployeeSelectorWidget(widgetID, type) { + if (type == undefined) { + type = "userName"; + } + if (typeof employeeSelector == "undefined") { + $("head").append( + '' + ); + $.ajax({ + type: "GET", + url: orgchartPath + "/js/employeeSelector.js", + dataType: "script", + success: function () { + let empSel = new employeeSelector(prefixID + "widgetEmp_" + widgetID); + empSel.apiPath = orgchartPath + "/api/"; + empSel.rootPath = orgchartPath + "/"; + empSel.outputStyle = "micro"; + + empSel.setSelectHandler(function () { + if (empSel.selectionData[empSel.selection] != undefined) { + selection = + type == "empUID" + ? empSel.selection + : empSel.selectionData[empSel.selection].userName; + $("#" + prefixID + "widgetMat_" + widgetID).val(selection); + //uses id. report builder/search will not take userName: + $("#" + empSel.prefixID + "input").val("#" + empSel.selection); + } + }); + empSel.setResultHandler(function () { + if (empSel.selectionData[empSel.selection] != undefined) { + selection = + type == "empUID" + ? empSel.selection + : empSel.selectionData[empSel.selection].userName; + $("#" + prefixID + "widgetMat_" + widgetID).val(selection); + } + }); + empSel.initialize(); + }, + }); + } else { + let empSel = new employeeSelector(prefixID + "widgetEmp_" + widgetID); + empSel.apiPath = orgchartPath + "/api/"; + empSel.rootPath = orgchartPath + "/"; + empSel.outputStyle = "micro"; + + empSel.setSelectHandler(function () { + if (empSel.selectionData[empSel.selection] != undefined) { + selection = + type == "empUID" + ? empSel.selection + : empSel.selectionData[empSel.selection].userName; + $("#" + prefixID + "widgetMat_" + widgetID).val(selection); + $("#" + empSel.prefixID + "input").val("#" + empSel.selection); + } + }); + empSel.setResultHandler(function () { + if (empSel.selectionData[empSel.selection] != undefined) { + selection = + type == "empUID" + ? empSel.selection + : empSel.selectionData[empSel.selection].userName; + $("#" + prefixID + "widgetMat_" + widgetID).val(selection); + } + }); + empSel.initialize(); + } + } + + /** + * @memberOf LeafFormSearchMultisite + */ + function createPositionSelectorWidget(widgetID) { + if (typeof positionSelector == "undefined") { + $("head").append( + '' + ); + $.ajax({ + type: "GET", + url: orgchartPath + "/js/positionSelector.js", + dataType: "script", + success: function () { + let posSel = new positionSelector(prefixID + "widgetPos_" + widgetID); + posSel.apiPath = orgchartPath + "/api/"; + posSel.rootPath = orgchartPath + "/"; + + posSel.setSelectHandler(function () { + $("#" + prefixID + "widgetMat_" + widgetID).val(posSel.selection); + $("#" + posSel.prefixID + "input").val("#" + posSel.selection); + }); + posSel.setResultHandler(function () { + $("#" + prefixID + "widgetMat_" + widgetID).val(posSel.selection); + }); + posSel.initialize(); + }, + }); + } else { + let posSel = new positionSelector(prefixID + "widgetPos_" + widgetID); + posSel.apiPath = orgchartPath + "/api/"; + posSel.rootPath = orgchartPath + "/"; + + posSel.setSelectHandler(function () { + $("#" + prefixID + "widgetMat_" + widgetID).val(posSel.selection); + $("#" + posSel.prefixID + "input").val("#" + posSel.selection); + }); + posSel.setResultHandler(function () { + $("#" + prefixID + "widgetMat_" + widgetID).val(posSel.selection); + }); + posSel.initialize(); + } + } + + /** + * @memberOf LeafFormSearchMultisite + */ + function createGroupSelectorWidget(widgetID) { + if (typeof groupSelector == "undefined") { + $("head").append( + '' + ); + $.ajax({ + type: "GET", + url: orgchartPath + "/js/groupSelector.js", + dataType: "script", + success: function () { + let grpSel = new groupSelector(prefixID + "widgetGrp_" + widgetID); + grpSel.apiPath = orgchartPath + "/api/"; + grpSel.rootPath = orgchartPath + "/"; + + grpSel.setSelectHandler(function () { + $("#" + prefixID + "widgetMat_" + widgetID).val(grpSel.selection); + $("#" + grpSel.prefixID + "input").val("group#" + grpSel.selection); + }); + grpSel.setResultHandler(function () { + $("#" + prefixID + "widgetMat_" + widgetID).val(grpSel.selection); + }); + grpSel.initialize(); + }, + }); + } else { + let grpSel = new groupSelector(prefixID + "widgetGrp_" + widgetID); + grpSel.apiPath = orgchartPath + "/api/"; + grpSel.rootPath = orgchartPath + "/"; + + grpSel.setSelectHandler(function () { + $("#" + prefixID + "widgetMat_" + widgetID).val(grpSel.selection); + $("#" + grpSel.prefixID + "input").val("group#" + grpSel.selection); + }); + grpSel.setResultHandler(function () { + $("#" + prefixID + "widgetMat_" + widgetID).val(grpSel.selection); + }); + grpSel.initialize(); + } + } + + /** + * Render the query match condition's input type for dropdown and radio fields + * @param widgetID + * @param options ' + ); + break; + default: + $("#" + prefixID + "widgetMatch_" + widgetID).html(options); + chosenOptions(); + break; + } + } + + /** + * @memberOf LeafFormSearchMultisite + */ + function renderWidget(widgetID, callback) { + switch ($("#" + prefixID + "widgetTerm_" + widgetID).val()) { + case "title": + $("#" + prefixID + "widgetCondition_" + widgetID).html( + '' + ); + $("#" + prefixID + "widgetMatch_" + widgetID).html( + '' + ); + break; + case "serviceID": + $("#" + prefixID + "widgetCondition_" + widgetID).html( + '' + ); + $.ajax({ + type: "GET", + url: "./api/system/services", + dataType: "json", + success: function (res) { + let services = + '"; + $("#" + prefixID + "widgetMatch_" + widgetID).html(services); + chosenOptions(); + if (callback != undefined) { + callback(); + } + }, + }); + break; + case "date": + case "dateInitiated": + case "dateSubmitted": + $("#" + prefixID + "widgetCondition_" + widgetID).html( + '' + ); + $("#" + prefixID + "widgetMatch_" + widgetID).html( + '' + ); + if (!jQuery.ui) { + $.getScript("../libs/js/jquery/jquery-ui.custom.min.js", function () { + $("#" + prefixID + "widgetMat_" + widgetID).datepicker(); + }); + } else { + $("#" + prefixID + "widgetMat_" + widgetID).datepicker(); + } + break; + case "categoryID": + $("#" + prefixID + "widgetCondition_" + widgetID).html( + '' + ); + $.ajax({ + type: "GET", + url: "./api/workflow/categoriesUnabridged", + dataType: "json", + success: function (res) { + let categories = + '"; + $("#" + prefixID + "widgetMatch_" + widgetID).html(categories); + chosenOptions(); + if (callback != undefined) { + callback(); + } + }, + cache: false, + }); + break; + case "userID": + $("#" + prefixID + "widgetCondition_" + widgetID).html( + ' IS' + ); + $("#" + prefixID + "widgetMatch_" + widgetID).html( + '
' + ); + createEmployeeSelectorWidget(widgetID); + break; + case "dependencyID": + $("#" + prefixID + "widgetCondition_" + widgetID).html( + ' =' + ); + $.ajax({ + type: "GET", + url: "./api/workflow/dependencies", + dataType: "json", + success: function (res) { + let dependencies = + '"; + $("#" + prefixID + "widgetTerm_" + widgetID).after(dependencies); + + let options = + '"; + $("#" + prefixID + "widgetMatch_" + widgetID).html(options); + + chosenOptions(); + $("#" + prefixID + "widgetTerm_" + widgetID + "_chosen").css( + "display", + "none" + ); + if (callback != undefined) { + callback(); + } + }, + cache: false, + }); + break; + case "stepID": + $("#" + prefixID + "widgetCondition_" + widgetID).html( + '' + ); + let categories = + '"; + $("#" + prefixID + "widgetMatch_" + widgetID).html(categories); + chosenOptions(); + if (callback != undefined) { + callback(); + } + break; + case "data": + let indicators = + '
"; + $("#" + prefixID + "widgetTerm_" + widgetID).after(indicators); + chosenOptions(); + $("#" + prefixID + "widgetIndicator_" + widgetID).css( + "float", + "right" + ); + $("#" + prefixID + "widgetIndicator_" + widgetID).on( + "change chosen:updated", + function () { + iID = $("#" + prefixID + "widgetIndicator_" + widgetID).val(); + + // set default conditions for "any data field" + if (iID == ALL_DATA_FIELDS) { + $("#" + prefixID + "widgetCondition_" + widgetID).html( + '' + ); + $("#" + prefixID + "widgetMatch_" + widgetID).html( + '' + ); + chosenOptions(); + } else if (iID == ALL_OC_EMPLOYEE_DATA_FIELDS) { + // set conditions for orgchart employee fields + $("#" + prefixID + "widgetCondition_" + widgetID).html( + ' IS' + ); + $("#" + prefixID + "widgetMatch_" + widgetID).html( + '
' + ); + createEmployeeSelectorWidget(widgetID, "empUID"); + } + } + ); + $("#" + prefixID + "widgetTerm_" + widgetID + "_chosen").css( + "display", + "none" + ); + if (callback != undefined) { + callback(); + } + break; + default: + $("#" + prefixID + "widgetCondition_" + widgetID).html( + '' + ); + $("#" + prefixID + "widgetMatch_" + widgetID).html( + '' + ); + break; + } + } + + /** + * @memberOf LeafFormSearchMultisite + */ + function newSearchWidget(gate) { + // @TODO IE Fix (No overloading) + if (gate === undefined) { + gate = "AND"; + } + let widget = + '\ + \ + ' + + gate + + '\ + \ + \ + \ + '; + $(widget).appendTo("#" + prefixID + "searchTerms"); + renderWidget(widgetCounter); + firstChild(); + + $("#" + prefixID + "widgetTerm_" + widgetCounter).on( + "change", + "", + widgetCounter, + function (e) { + renderWidget(e.data); + chosenOptions(); + } + ); + $("#" + prefixID + "widgetRemove_" + widgetCounter).on( + "click", + "", + widgetCounter, + function (e) { + $("#" + prefixID + "widget_" + e.data).remove(); + $("#" + prefixID + "widgetOp_" + e.data).remove(); + firstChild(); + } + ); + + widgetCounter++; + } + + /** + * @memberOf LeafFormSearchMultisite + */ + function generateSearchQuery() { + leafFormQuery.clearTerms(); + for (let i = 0; i < widgetCounter; i++) { + if ($("#" + prefixID + "widgetTerm_" + i).val() != undefined) { + term = $("#" + prefixID + "widgetTerm_" + i).val(); + if (term != "data" && term != "dependencyID") { + id = $("#" + prefixID + "widgetTerm_" + i).val(); + cod = $("#" + prefixID + "widgetCod_" + i).val(); + match = $("#" + prefixID + "widgetMat_" + i).val(); + gate = document.getElementById( + prefixID + "widgetGate_" + i + ).innerHTML; // Assign Operator + if (cod == "LIKE") { + match = "*" + match + "*"; + } + leafFormQuery.addTerm(id, cod, match, gate); + } else { + id = $("#" + prefixID + "widgetTerm_" + i).val(); + indicatorID = $("#" + prefixID + "widgetIndicator_" + i).val(); + cod = $("#" + prefixID + "widgetCod_" + i).val(); + match = $("#" + prefixID + "widgetMat_" + i).val(); + gate = document.getElementById( + prefixID + "widgetGate_" + i + ).innerHTML; // Assign Operator + if (cod == "LIKE") { + match = "*" + match + "*"; + } + leafFormQuery.addDataTerm(id, indicatorID, cod, match, gate); + } + } + } + if (leafFormQuery.getQuery().terms.length > 0) { + $("#" + prefixID + "searchtxt").val( + JSON.stringify(leafFormQuery.getQuery().terms) + ); + } else { + $("#" + prefixID + "searchtxt").val("*"); + } + } + + /** + * Purpose: Update Chosen Options for Fields + * @memberOf LeafFormSearchMultisite + */ + function chosenOptions() { + $(".chosen").chosen({ + disable_search_threshold: 6, + width: "100%", + }); // needs to be here due to chosen issue with display:none + } + + /** + * Purpose: Refresh First Child in Search + * @memberOf LeafFormSearchMultisite + */ + function firstChild() { + if ( + document.getElementById(prefixID + "searchTerms").children[0] != undefined + ) { + document.getElementById( + prefixID + "searchTerms" + ).children[0].children[1].style.display = "none"; // Hide First Operator + document + .getElementById(prefixID + "searchTerms") + .children[0].children[2].setAttribute("colspan", "2"); // Resize col + document.getElementById( + prefixID + "searchTerms" + ).children[0].children[2].style.width = "175px"; + document.getElementById( + prefixID + "searchTerms" + ).children[0].children[3].style.width = "130px"; + } + } + + return { + init: init, + renderUI: renderUI, + setOrgchartPath: setOrgchartPath, + focus: focus, + getPrefixID: function () { + return prefixID; + }, + getSearchInput: function () { + return $("#" + prefixID + "searchtxt").val(); + }, + getResultContainerID: function () { + return prefixID + "_result"; + }, + getLastSearch: getLastSearch, + generateQuery: generateSearchQuery, + getLeafFormQuery: function () { + return leafFormQuery; + }, + renderPreviousAdvancedSearch: renderPreviousAdvancedSearch, + setSearchFunc: setSearchFunc, + search: search, + showBusy: showBusy, + showNotBusy: showNotBusy, + setRootURL: function (url) { + rootURL = url; + }, + }; +}; diff --git a/LEAF_Request_Portal/js/vue_conditions_editor/LEAF_conditions_editor.js b/LEAF_Request_Portal/js/vue_conditions_editor/LEAF_conditions_editor.js index f1b302b83..8afccb929 100644 --- a/LEAF_Request_Portal/js/vue_conditions_editor/LEAF_conditions_editor.js +++ b/LEAF_Request_Portal/js/vue_conditions_editor/LEAF_conditions_editor.js @@ -707,9 +707,8 @@ const ConditionsEditor = Vue.createApp({ * @returns {Array} of condition objects */ savedConditions() { - return this.childIndicator.conditions - ? JSON.parse(this.childIndicator.conditions) - : []; + return typeof this.childIndicator.conditions === 'string' && this.childIndicator.conditions[0] === '[' ? + JSON.parse(this.childIndicator.conditions) : []; }, /** * @returns {Object} with arrays of conditions by type diff --git a/LEAF_Request_Portal/sources/Group.php b/LEAF_Request_Portal/sources/Group.php index fbecc36f4..cf4a0b150 100644 --- a/LEAF_Request_Portal/sources/Group.php +++ b/LEAF_Request_Portal/sources/Group.php @@ -197,39 +197,49 @@ public function cleanDb(): void */ public function getMembers($groupID, bool $searchDeleted = false): array|string { - if (!is_numeric($groupID)) - { - $return_value = "invalid group ID"; + if (!is_numeric($groupID)) { + $return_value = array ( + 'status' => array ( + 'code' => 4, + 'message' => 'invalid group ID' + ) + ); } else { - $sql_vars = array(':groupID' => $groupID); - $res = $this->db->prepared_query('SELECT * FROM users WHERE groupID=:groupID ORDER BY userID', $sql_vars); + $vars = array(':groupID' => $groupID); + $sql = 'SELECT `userID`, `groupID`, `backupID`, `primary_admin`, + `locallyManaged`, `active` + FROM `users` + WHERE `groupID` = :groupID + ORDER BY `userID`'; + + $res = $this->db->pdo_select_query($sql, $vars); $members = array(); - if (count($res) > 0) - { + if ($res['status']['code'] == 2) { $dir = new VAMC_Directory(); - foreach ($res as $member) - { + + foreach ($res['data'] as $member) { $dirRes = $dir->lookupLogin($member['userID'], false, true, $searchDeleted); - if (isset($dirRes[0])) - { + if (isset($dirRes[0])) { $dirRes[0]['regionallyManaged'] = false; - foreach ($dirRes[0]['groups'] as $group) - { - if ($groupID == $group['groupID']){ + + foreach ($dirRes[0]['groups'] as $group) { + if ($groupID == $group['groupID']) { $dirRes[0]['regionallyManaged'] = true; } } - if($groupID == 1) - { + + if ($groupID == 1) { $dirRes[0]['primary_admin'] = $member['primary_admin']; } - if($member['locallyManaged'] == 1) { + + if ($member['locallyManaged'] == 1) { $dirRes[0]['backupID'] = null; } else { $dirRes[0]['backupID'] = $member['backupID']; } + $dirRes[0]['locallyManaged'] = $member['locallyManaged']; $dirRes[0]['active'] = $member['active']; @@ -241,7 +251,13 @@ public function getMembers($groupID, bool $searchDeleted = false): array|string $col = array_column( $members, "lastName" ); array_multisort( $col, SORT_ASC, $members ); - $return_value = $members; + $return_value = array ( + 'status' => array ( + 'code' => 2, + 'message' => '' + ), + 'data' => $members + ); } return $return_value; @@ -448,7 +464,7 @@ public function getGroupsAndMembers(bool $searchDeleted = false): array { if ($group['groupID'] > 0) { - $group['members'] = $this->getMembers($group['groupID'], $searchDeleted); + $group['members'] = $this->getMembers($group['groupID'], $searchDeleted)['data']; $list[] = $group; } } diff --git a/LEAF_Request_Portal/sources/Site.php b/LEAF_Request_Portal/sources/Site.php index fcd6a026a..052c8854c 100644 --- a/LEAF_Request_Portal/sources/Site.php +++ b/LEAF_Request_Portal/sources/Site.php @@ -43,6 +43,75 @@ public function setSitemapJSON() return 1; } + public function setHomeDesignJSON(array $menuItems = [], string $direction = 'v'): array { + $status = array(); + if (!$this->login->checkGroup(1)) { + $status['code'] = 0; + $status['message'] = "Admin access required"; + return $status; + } + foreach ($menuItems as $i => $item) { + $menuItems[$i]['title'] = \Leaf\XSSHelpers::sanitizer($item['title']); + $menuItems[$i]['subtitle'] = \Leaf\XSSHelpers::sanitizer($item['subtitle']); + $menuItems[$i]['link'] = \Leaf\XSSHelpers::scrubNewLinesFromURL(\Leaf\XSSHelpers::xscrub($item['link'])); + $menuItems[$i]['icon'] = \Leaf\XSSHelpers::scrubFilename($item['icon']); + } + $home_design_data = array(); + $home_design_data['menuCards'] = $menuItems; + $home_design_data['direction'] = $direction === 'v' ? 'v' : 'h'; + $homepage_design_json = json_encode($home_design_data); + + $strSQL = 'INSERT INTO settings (setting, `data`) + VALUES ("homepage_design_json", :homepage_design_json) + ON DUPLICATE KEY UPDATE `data`=:homepage_design_json'; + $vars = array(':homepage_design_json' => $homepage_design_json); + + $this->db->prepared_query($strSQL, $vars); + $status['code'] = 1; + $status['message'] = "success"; + return $status; + } + public function setSearchDesignJSON(array $chosenHeaders = []): array { + $status = array(); + if (!$this->login->checkGroup(1)) { + $status['code'] = 0; + $status['message'] = "Admin access required"; + return $status; + } + $search_design_data = array(); + $search_design_data['chosenHeaders'] = \Leaf\XSSHelpers::scrubObjectOrArray($chosenHeaders); + $search_design_json = json_encode($search_design_data); + + $strSQL = 'INSERT INTO settings (setting, `data`) + VALUES ("search_design_json", :search_design_json) + ON DUPLICATE KEY UPDATE `data`=:search_design_json'; + $vars = array(':search_design_json' => $search_design_json); + + $this->db->prepared_query($strSQL, $vars); + + $status['code'] = 1; + $status['message'] = ""; + return $status; + } + public function enableNoCodeHomepage(int $isEnabled = 0): array { + $status = array(); + if (!$this->login->checkGroup(1)) { + $status['code'] = 0; + $status['message'] = "Admin access required"; + return $status; + } + $homepage_enabled = $isEnabled === 1 ? '1' : '0'; + $strSQL = 'INSERT INTO settings (setting, `data`) + VALUES ("homepage_enabled", :homepage_enabled) + ON DUPLICATE KEY UPDATE `data`=:homepage_enabled'; + $vars = array(':homepage_enabled' => $homepage_enabled); + + $this->db->prepared_query($strSQL, $vars); + + $status['code'] = 1; + $status['message'] = "success"; + return $status; + } public function getSitemapJSON() { $settings = $this->db->prepared_query('SELECT data from settings WHERE setting="sitemap_json"', null); diff --git a/LEAF_Request_Portal/sources/System.php b/LEAF_Request_Portal/sources/System.php index e12e272b7..75cd8e26e 100644 --- a/LEAF_Request_Portal/sources/System.php +++ b/LEAF_Request_Portal/sources/System.php @@ -146,100 +146,405 @@ public function updateService($serviceID) return "groupID: {$serviceID} updated"; } - public function updateGroup($groupID) + /** + * @param int $groupID + * + * @return array + * + * Created at: 6/30/2023, 1:24:51 PM (America/New_York) + */ + public function updateGroup(int $groupID): array { - if (!is_numeric($groupID)) - { - return 'Invalid Group'; + if (!is_numeric($groupID)) { + $return_value = array( + 'status' => array( + 'code' => 4, + 'message' => 'Invalid Group Id.' + ) + ); + } elseif ($groupID == 1) { + $return_value = array( + 'status' => array( + 'code' => 4, + 'message' => 'You are not authorized to update admin groups.' + ) + ); + } else { + $oc_db = new \Leaf\Db(\DIRECTORY_HOST, \DIRECTORY_USER, \DIRECTORY_PASS, \ORGCHART_DB); + $group = new \Orgchart\Group($oc_db, $this->login); + $position = new \Orgchart\Position($oc_db, $this->login); + $employee = new \Orgchart\Employee($oc_db, $this->login); + $tag = new \Orgchart\Tag($oc_db, $this->login); + + // clear out old data first + $delete_groups = $this->clearGroups($groupID); + + if ($delete_groups['status']['code'] == 2) { + // find quadrad/ELT tag name + $upperLevelTag = $tag->getParent('service'); + $isQuadrad = false; + + if (array_search($upperLevelTag, $group->getAllTags($groupID)) !== false) { + $isQuadrad = true; + } + + $resGroup = $group->getGroup($groupID)[0]; + + $insert_group = $this->insertGroup($groupID, $isQuadrad, $resGroup['groupTitle']); + + if ($insert_group['status']['code'] == 2) { + $delete_user_backups = $this->deleteUserBackups($groupID); + + if ($delete_user_backups['status']['code'] == 2) { + $resEmp = array(); + $positions = $group->listGroupPositions($groupID); + $resEmp = $group->listGroupEmployees($groupID); + + if (!empty($positions) && is_array($positions)){ + foreach ($positions as $tposition) { + $resEmp = array_merge($resEmp, $position->getEmployees($tposition['positionID'])); + } + } + + if (!empty($resEmp) && is_array($resEmp)) { + foreach ($resEmp as $emp) { + $insert_user = $this->insertUser($groupID, $emp); + + if ($insert_user['status']['code'] == 2) { + // nothing to be done, all is good + } else { + $return_value = array ( + 'status' => array ( + 'code' => 4, + 'message' => 'Action failed to add users.' + ) + ); + break; + } + } + } + + $backups = $this->addBackups($groupID); + + if ($backups['status']['code'] == 2) { + $privs = $this->updateCatPrivs($groupID); + + if ($privs['status']['code'] == 2) { + // at this point everything updated as expected + $return_value = array ( + 'status' => array ( + 'code' => 2, + 'message' => 'Everything updated as expected.' + ) + ); + } else { + // something happened updating category privs + $return_value = array ( + 'status' => array ( + 'code' => 4, + 'message' => 'There was an error updating category privs.' + ) + ); + } + } else { + $return_value = array ( + 'status' => array ( + 'code' => 4, + 'message' => 'There was an arror adding backups.' + ) + ); + } + } else { + // something happened deleting user backups + $return_value = array ( + 'status' => array ( + 'code' => 4, + 'message' => 'There was an error deleting user backups.' + ) + ); + } + } else { + // something happened with the inserting of groups + $return_value = array ( + 'status' => array ( + 'code' => 4, + 'message' => 'There was an error inserting groups.' + ) + ); + } + } else { + // something happened with the delete groups + $return_value = array ( + 'status' => array ( + 'code' => 4, + 'message' => 'There was an error when deleting groups.' + ) + ); + } } - if ($groupID == 1) - { - return 'Cannot update admin group'; + + return $return_value; + } + + /** + * @param int $groupID + * + * @return array + * + * Created at: 6/30/2023, 1:25:07 PM (America/New_York) + */ + private function updateCatPrivs(int $groupID): array + { + $cat_privs = $this->getCatPrivs($groupID); + + if ($cat_privs['status']['code'] == 2) { + $return_value = $this->deleteCatPrivs($groupID); + } else { + $return_value = array ( + 'status' => array ( + 'code' => 4, + 'message' => 'Action failed to add backups.' + ) + ); } - // clear out old data first + return $return_value; + } + + /** + * @param int $groupID + * + * @return array + * + * Created at: 6/30/2023, 1:25:25 PM (America/New_York) + */ + private function deleteCatPrivs(int $groupID): array + { $vars = array(':groupID' => $groupID); - //$this->db->prepared_query('DELETE FROM users WHERE groupID=:groupID AND backupID IS NULL', $vars); - $this->db->prepared_query('DELETE FROM `groups` WHERE groupID=:groupID', $vars); + $sql = 'DELETE + FROM `category_privs` + WHERE `groupID` = :groupID'; - $oc_db = new \Leaf\Db(\DIRECTORY_HOST, \DIRECTORY_USER, \DIRECTORY_PASS, \ORGCHART_DB); - $group = new \Orgchart\Group($oc_db, $this->login); - $position = new \Orgchart\Position($oc_db, $this->login); - $employee = new \Orgchart\Employee($oc_db, $this->login); - $tag = new \Orgchart\Tag($oc_db, $this->login); + $return_value = $this->db->pdo_delete_query($sql, $vars); - // find quadrad/ELT tag name - $upperLevelTag = $tag->getParent('service'); - $isQuadrad = false; - if (array_search($upperLevelTag, $group->getAllTags($groupID)) !== false) - { - $isQuadrad = true; - } + return $return_value; + } - $resGroup = $group->getGroup($groupID)[0]; - $vars = array(':groupID' => $groupID, - ':parentGroupID' => ($isQuadrad == true ? -1 : null), - ':name' => $resGroup['groupTitle'], - ':groupDescription' => '', ); + /** + * @param int $groupID + * + * @return array + * + * Created at: 6/30/2023, 1:25:38 PM (America/New_York) + */ + private function getCatPrivs(int $groupID): array + { + $vars = array(':groupID' => $groupID); + $sql = 'SELECT `categoryID` + FROM `category_privs` + LEFT JOIN `groups` USING (`groupID`) + WHERE `category_privs`.`groupID` = :groupID + AND `groups`.`groupID` IS NULL'; - $this->db->prepared_query('INSERT INTO `groups` (groupID, parentGroupID, name, groupDescription) - VALUES (:groupID, :parentGroupID, :name, :groupDescription)', $vars); + $return_value = $this->db->pdo_select_query($sql, $vars); - // build list of member employees - $resEmp = array(); - $positions = $group->listGroupPositions($groupID); - $resEmp = $group->listGroupEmployees($groupID); - foreach ($positions as $tposition) - { - $resEmp = array_merge($resEmp, $position->getEmployees($tposition['positionID'])); - } + return $return_value; + } - // clear backups in case of updates - $vars = array(':groupID' => $groupID); - $this->db->prepared_query('DELETE FROM users WHERE backupID IS NOT NULL AND groupID=:groupID', $vars); - foreach ($resEmp as $emp) - { - if ($emp['userName'] != '') - { - $vars = array(':userID' => $emp['userName'], - ':groupID' => $groupID, ); + /** + * @param int $groupID + * + * @return array + * + * Created at: 6/30/2023, 1:25:53 PM (America/New_York) + */ + private function addBackups(int $groupID): array + { + $oc_db = new \Leaf\Db(\DIRECTORY_HOST, \DIRECTORY_USER, \DIRECTORY_PASS, \ORGCHART_DB); + $employee = new \Orgchart\Employee($oc_db, $this->login); - $this->db->prepared_query('INSERT INTO users (userID, groupID, active) - VALUES (:userID, :groupID, 0) - ON DUPLICATE KEY UPDATE userID=:userID, groupID=:groupID', $vars); + // get all users for this group + $group_users = $this->getGroupUsers($groupID); - // include the backups of employees - $res = $this->db->prepared_query('SELECT * FROM users WHERE userID=:userID AND groupID=:groupID', $vars); - if ($res[0]['active'] == 1) { - $backups = $employee->getBackups($emp['empUID']); - foreach ($backups as $backup) { - $vars = array(':userID' => $backup['userName'], - ':groupID' => $groupID, - ':backupID' => $emp['userName'],); + // loop through group_users to add backups + if ($group_users['status']['code'] == 2){ + $userNames = array(); - // Add backupID check for updates - $this->db->prepared_query('INSERT INTO users (userID, groupID, backupID) - VALUES (:userID, :groupID, :backupID) - ON DUPLICATE KEY UPDATE userID=:userID, groupID=:groupID', $vars); + foreach ($group_users['data'] as $user) { + $userNames[] = $user['userID']; + } + + $employee_list = $employee->getEmployeeByUserName($userNames, $oc_db); + foreach ($employee_list['data'] as $user) { + // if active user, then get backups and add them + if ($user['deleted'] == 0) { + $backups = $employee->getBackups($user['empUID']); + + if (!empty($backups)) { + foreach ($backups as $backup) { + $backup_added = $this->addBackup($groupID, $backup['userName'], $user['userName']); + + if ($backup_added['status']['code'] == 2) { + continue; + } else { + $return_value = array ( + 'status' => array ( + 'code' => 4, + 'message' => 'Action failed to add backups.' + ) + ); + break; + } + } } } } + $return_value = array ( + 'status' => array ( + 'code' => 2, + 'message' => '' + ) + ); + } else { + $return_value = $group_users; } - //if the group is removed, also remove the category_privs + return $return_value; + } + + /** + * @param int $groupID + * @param string $backup_user + * @param string $user + * + * @return array + * + * Created at: 6/30/2023, 1:26:30 PM (America/New_York) + */ + private function addBackup(int $groupID, string $backup_user, string $user): array + { + $vars = array(':userID' => $backup_user, + ':groupID' => $groupID, + ':backupID' => $user); + $sql = 'INSERT INTO `users` (`userID`, `groupID`, `backupID`) + VALUES (:userID, :groupID, :backupID) + ON DUPLICATE KEY UPDATE `userID` = :userID, `groupID` = :groupID'; + + $return_value = $this->db->pdo_insert_query($sql, $vars); + + return $return_value; + } + + /** + * @param int $groupID + * + * @return array + * + * Created at: 6/30/2023, 1:26:53 PM (America/New_York) + */ + private function getGroupUsers(int $groupID): array + { $vars = array(':groupID' => $groupID); - $res = $this->db->prepared_query('SELECT * - FROM category_privs - LEFT JOIN `groups` USING (groupID) - WHERE category_privs.groupID = :groupID - AND `groups`.groupID is null;', $vars); - if(count($res) > 0) - { - $this->db->prepared_query('DELETE FROM category_privs WHERE groupID=:groupID', $vars); + $sql = 'SELECT `userID` + FROM `users` + WHERE `groupID` = :groupID'; + + $return_value = $this->db->pdo_select_query($sql, $vars); + + return $return_value; + } + + /** + * @param int $groupID + * @param array $emp + * + * @return array + * + * Created at: 6/30/2023, 1:27:17 PM (America/New_York) + */ + private function insertUser(int $groupID, array $emp): array + { + if (!empty($emp['userName'])) { + $vars = array(':userID' => $emp['userName'], + ':groupID' => $groupID, ); + $sql = 'INSERT INTO `users` (`userID`, `groupID`, `active`) + VALUES (:userID, :groupID, 0) + ON DUPLICATE KEY UPDATE `userID` = :userID, `groupID` = :groupID'; + + $return_value = $this->db->pdo_insert_query($sql, $vars); + } else { + $return_value = array ( + 'status' => array ( + 'code' => 4, + 'message' => 'Improperly formatted data.' + ) + ); } + return $return_value; + } + + /** + * @param int $groupID + * + * @return array + * + * Created at: 6/30/2023, 1:27:47 PM (America/New_York) + */ + private function deleteUserBackups(int $groupID): array + { + $vars = array(':groupID' => $groupID); + $sql = 'DELETE + FROM `users` + WHERE `backupID` IS NOT NULL + AND `groupID` = :groupID'; + + $return_value = $this->db->pdo_delete_query($sql , $vars); + + return $return_value; + } + + /** + * @param int $groupID + * @param bool $isQuadrad + * @param string $title + * + * @return array + * + * Created at: 6/30/2023, 1:28:03 PM (America/New_York) + */ + private function insertGroup(int $groupID, bool $isQuadrad, string $title): array + { + $vars = array(':groupID' => $groupID, + ':parentGroupID' => ($isQuadrad == true ? -1 : null), + ':name' => $title, + ':groupDescription' => '', ); + $sql = 'INSERT INTO `groups` (`groupID`, `parentGroupID`, `name`, + `groupDescription`) + VALUES (:groupID, :parentGroupID, :name, :groupDescription)'; + + $return_value = $this->db->pdo_insert_query($sql, $vars); + + return $return_value; + } + + /** + * @param int $groupID + * + * @return array + * + * Created at: 6/30/2023, 1:28:34 PM (America/New_York) + */ + private function clearGroups(int $groupID): array + { + $vars = array(':groupID' => $groupID); + $sql = 'DELETE + FROM `groups` + WHERE `groupID` = :groupID'; + + $return_value = $this->db->pdo_delete_query($sql, $vars); - return "groupID: {$groupID} updated"; + return $return_value; } /** diff --git a/LEAF_Request_Portal/templates/nocode_templates/view_homepage.tpl b/LEAF_Request_Portal/templates/nocode_templates/view_homepage.tpl new file mode 100644 index 000000000..20ea1a5c4 --- /dev/null +++ b/LEAF_Request_Portal/templates/nocode_templates/view_homepage.tpl @@ -0,0 +1,95 @@ + + +
+
+
+ +
+
+ + \ No newline at end of file diff --git a/LEAF_Request_Portal/templates/nocode_templates/view_search.tpl b/LEAF_Request_Portal/templates/nocode_templates/view_search.tpl new file mode 100644 index 000000000..1166ab74c --- /dev/null +++ b/LEAF_Request_Portal/templates/nocode_templates/view_search.tpl @@ -0,0 +1,301 @@ + + + + + diff --git a/LEAF_Request_Portal/templates/reports/LEAF_Sitemap_Search.tpl b/LEAF_Request_Portal/templates/reports/LEAF_Sitemap_Search.tpl new file mode 100644 index 000000000..3064ec0df --- /dev/null +++ b/LEAF_Request_Portal/templates/reports/LEAF_Sitemap_Search.tpl @@ -0,0 +1,1422 @@ + + + + + + + + + + + + + + + diff --git a/LEAF_Request_Portal/templates/view_reports.tpl b/LEAF_Request_Portal/templates/view_reports.tpl index df8d1e12e..c570d2f93 100644 --- a/LEAF_Request_Portal/templates/view_reports.tpl +++ b/LEAF_Request_Portal/templates/view_reports.tpl @@ -290,7 +290,7 @@ function addHeader(column) { callback: function(data, blob) { let daysSinceAction; let recordBlob = blob[data.recordID]; - if(recordBlob.action_history != undefined) { + if(recordBlob.action_history != undefined && recordBlob.action_history.length > 0) { // Get Last Action no matter what (could change for non-comment) let lastActionRecord = recordBlob.action_history.length - 1; let lastAction = recordBlob.action_history[lastActionRecord]; diff --git a/LEAF_Request_Portal/templates/view_search.tpl b/LEAF_Request_Portal/templates/view_search.tpl index f602fb56f..9b38a9d13 100644 --- a/LEAF_Request_Portal/templates/view_search.tpl +++ b/LEAF_Request_Portal/templates/view_search.tpl @@ -1,5 +1,7 @@ -
- +
+
+ +