Odyssey
aBankingAudit.prg
1 <?php
2 /**
3  * @package aBankingAudit.prg
4  * Shows all banking audits.
5  */
6 
7 require_once("$admLibrary/aAudit.i");
8 
9 $string = array("filter" => HCUFILTER_INPUT_STRING);
10 HCU_ImportVars($SYSENV, "IMPORTS", array("operation" => $string, "duration" => $string, "auditUser" => $string, "loggedInUser" => $string, "actionType" => $string,
11  "startOn" => $string, "account" => $string, "limit" => $string, "offset" => $string, "sort" => $string, "showThis" => $string,
12  "changedOnly" => $string, "endOn" => $string));
13 
14 $operation = HCU_array_key_value("operation", $SYSENV["IMPORTS"]);
15 if ($operation !== false) {
16  switch($operation) {
17  case "readAudits":
18  $returnArray = GetAuditRecords($dbh, $Cu, $SYSENV["IMPORTS"], $SYSENV["logger"]);
19  break;
20  default: // Won't get here
21  $returnArray = array("error" => array("Operation not specified: '$operation'"), "record" => array());
22  }
23 
24  header('Content-type: application/json');
25  print HCU_JsonEncode($returnArray);
26 } else {
27  PrintPage("$menu_link?ft=$ft", GatherPageData($dbh, $Cu));
28 }
29 
30 /**
31  * function GatherPageData($dbh, $Cu)
32  * Retrieves the data that the page needs immediately for dropdownlists.
33  *
34  * @param $dbh -- the database connection.
35  * @param $Cu -- the credit union
36  *
37  * @return $status -- "000" if successful, nonzero otherwise.
38  * @return $error -- "" if successful, nonempty otherwise.
39  * @return $data.types -- A list of types available.
40  * @return $data.shows -- A list of all/admin/banking.
41  * @return $data.prints -- data for the print dropdownlist
42  */
43 function GatherPageData($dbh, $Cu) {
44  try {
45 
46  $results = GetAuditTypesList($dbh, $Cu);
47  if ($results["status"] !== "000") {
48  throw new exception($results["error"], 1);
49  }
50  $types = $results["ddl"];
51 
52  $shows = array(
53  array("value" => "all", "text" => "Banking and Admin User Changes"),
54  array("value" => "admin", "text" => "Admin User Changes Only"),
55  array("value" => "banking", "text" => "Banking User Changes")
56  );
57 
58  $prints = array(
59  array("value" => "", "text" => "Print Events"),
60  array("value" => "selected", "text" => "Selected Events Only"),
61  array("value" => "changed", "text" => "All Events (Changes Only)"),
62  array("value" => "all", "text" => "All Events")
63  );
64 
65 
66  $returnArray = array("status" => "000", "error" => "", "data" => array("types" => $types, "shows" => $shows, "prints" => $prints));
67  } catch (exception $e) {
68  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
69  }
70  return $returnArray;
71 }
72 
73 
74 /**
75  * Return the list of audit types. This has changed to a hardcoded list
76  * as returning values from the database returned too many duplicates
77  * from having the same text description for different audit type values.
78  * NOTE: this is formatted for use with a Kendo DropdownList.
79  */
80 function GetAuditTypesList(){
81  $types = [
82  ["text"=>'All Types', "value"=>''],
83  ["text"=>'Account Delete', "value"=>'Account Delete'],
84  ["text"=>'Activate User', "value"=>'Activate User'],
85  ["text"=>'Add New Account', "value"=>'Add New Account'],
86  ["text"=>'Alert Status Update', "value"=>'Alert Status Update'],
87  ["text"=>'Auto Add User Accounts', "value"=>'Auto Add User Accounts'],
88  ["text"=>'Correct Restrictions', "value"=>'Correct Restrictions'],
89  ["text"=>'Delete Account', "value"=>'Delete Account'],
90  ["text"=>'E-Statement Update', "value"=>'E-Statement Update'],
91  ["text"=>'Forgot Password', "value"=>'Forgot Password'],
92  ["text"=>'Group Add', "value"=>'Group Add'],
93  ["text"=>'Group Deleted', "value"=>'Group Deleted'],
94  ["text"=>'Member Account Rights Change', "value"=>'Member Account Rights Change'],
95  ["text"=>'Member Settings', "value"=>'Member Settings'],
96  ["text"=>'Phone Add', "value"=>'Phone Add'],
97  ["text"=>'Primary User Access Add', "value"=>'Primary User Access Add'],
98  ["text"=>'Primary User Access Delete', "value"=>'Primary User Access Delete'],
99  ["text"=>'Required Profile Update', "value"=>'Required Profile Update'],
100  ["text"=>'Reset Password', "value"=>'Reset Password'],
101  ["text"=>'Rights Add', "value"=>'Rights Add'],
102  ["text"=>'Rights Delete', "value"=>'Rights Delete'],
103  ["text"=>'Rights Update', "value"=>'Rights Update'],
104  ["text"=>'Status Update', "value"=>'Status Update'],
105  ["text"=>'Update Account', "value"=>'Update Account'],
106  ["text"=>'Update User Security', "value"=>'Update User Security'],
107  ["text"=>'User Accounts Add', "value"=>'User Accounts Add'],
108  ["text"=>'User Accounts Update', "value"=>'User Accounts Update'],
109  ["text"=>'User Add', "value"=>'User Add'],
110  ["text"=>'User Login Deleted', "value"=>'User Login Deleted'],
111  ["text"=>'User Member Add', "value"=>'User Member Add']
112  ];
113  $returnArray = array("status" => "000", "error" => "", "ddl" => $types);
114  return $returnArray;
115 
116  }
117 
118 
119 /**
120  * function GetAuditRecords($dbh, $Cu, $parameters, $logger)
121  * Gets the audit records
122  *
123  * @param $dbh -- the database connection
124  * @param string $Cu -- the credit union
125  * @param array $parameters -- the parameters for the audit record.
126  * @param object $logger -- logs to kibana
127  * @return [date:"", actioncode:"", user:"", script:"", action:"", rown:0, details: [{table:"", label:"", type:"",
128  * rows: [{type: "", values: [{col: "", before: "", after: "", label: "", same: false}, ...]}, ...]}, ... ]]
129  * Type can be "mixed", "add", "delete", or "update" at the table level. It cannot be "mixed" at the column level.
130  */
131 function GetAuditRecords($dbh, $Cu, $parameters, $logger) {
132  try {
133  extract($parameters);
134 
135  $duration = strtolower(isset($duration) ? trim($duration) : "");
136  $auditUser = strtolower(isset($auditUser) ? trim($auditUser) : "");
137  $account = strtolower(isset($account) ? trim($account) : "");
138  $loggedInUser = strtolower(isset($loggedInUser) ? trim($loggedInUser) : "");
139  $actionType = strtolower(isset($actionType) ? trim($actionType) : "");
140 
141  $startOn = isset($startOn) ? trim($startOn) : "";
142  $endOn = isset($endOn) ? trim($endOn) : "";
143  $showThis = isset($showThis) ? trim($showThis) : "";
144  $changedOnly = isset($changedOnly) ? trim($changedOnly) == "Y" : false;
145 
146  $limit = isset($limit) ? intval($limit) : 0;
147  $offset = isset($offset) ? intval($offset) : 0;
148  $limitSQL = $limit == 0 && $offset == 0 ? "" : "limit $limit offset $offset";
149 
150  $sort = isset($sort) ? trim($sort) : "";
151  $sortSQL = array();
152  if ($sort != "") {
153  $sort = HCU_JsonDecode($sort);
154  foreach($sort as $sortItem) {
155  if (!HCU_array_key_exists("field", $sortItem) || !HCU_array_key_exists("dir", $sortItem)) {
156  throw new exception ("Sort is invalid.", 24);
157  }
158  $field = $sortItem["field"];
159  $dir = $sortItem["dir"];
160 
161  if (!in_array($dir, array("asc", "desc"))) {
162  throw new exception ("Sort is invalid.", 25);
163  }
164 
165  $field = $field == "user" ? "1" : ($field == "account" ? "2" : $field);
166  // There is special logic to show user and account as in the report so have to use the calculated column.
167 
168  $sortSQL[] = "$field $dir";
169  }
170  }
171  $sortSQL = count($sortSQL) > 0 ? "order by " . implode(", ", $sortSQL) : "";
172 
173  $outerWhere = array();
174  $innerWhere = array();
175 
176  if ($duration == "") {
177  throw new exception("Duration is required.", 20);
178  }
179 
180  $cuTzString = GetCreditUnionTimezone($dbh, $Cu);
181  $cuTz = new DateTimeZone($cuTzString);
182 
183  switch($duration) {
184  case "all":
185  break;
186  case "30":
187  case "60":
188  case "90":
189  // In the case that we have a CU on the other side of the world where the date is different than UTC (database.)
190  $today = new DateTime("now", $cuTz);
191  $today->setTime(0,0); // Now that the date is converted to UTC, remove the time part.
192  $today->modify("-$duration days"); // Now go back in time for the starting date. No need to worry about the end date.
193  $today = $today->format("Y-m-d");
194  $innerWhere[] = "aut.auditdate >= '$today'";
195  break;
196  case "on":
197  // Make it a bit more lenient than the frontend so that either the start or end date has to be required.
198 
199  if ($startOn == "" && $endOn == "") {
200  throw new exception("Invalid duration.", 21);
201  }
202 
203  if ($startOn != "") {
204  $startOnDate = DateTime::createFromFormat("Y-m-d", $startOn, $cuTz);
205  if ($startOnDate === false) {
206  throw new exception("Start On is invalid.", 22);
207  }
208  $startOn = $startOnDate->format("Y-m-d");
209 
210  $innerWhere[] = "aut.auditdate >= '$startOn'";
211  }
212 
213  if ($endOn != "") {
214  $endOnDate = DateTime::createFromFormat("Y-m-d", $endOn, $cuTz);
215  if ($endOnDate === false) {
216  throw new exception("End On is invalid.", 27);
217  }
218  $endOnDate->setTime(0,0);
219  $endOnDate->modify("+1 days");
220  $endOn = $endOnDate->format("Y-m-d"); // Does not include this day.
221 
222  $innerWhere[] = "aut.auditdate < '$endOn'";
223  }
224 
225  break;
226  default:
227  throw new exception("Duration is invalid.", 23);
228  break;
229  }
230 
231  switch($showThis) {
232  case "all":
233  break;
234  case "admin":
235  $innerWhere[] = "aut.auditsrctype = 'A'";
236  break;
237  case "banking":
238  $innerWhere[] = "aut.auditsrctype = 'U'";
239  break;
240  default:
241  throw new exception ("Show option is invalid.", 28);
242  }
243 
244  $auditString = "aut.auditrecbefore::text || aut.auditrecafter::text";
245  $lowerAuditString = "lower($auditString)";
246 
247  if ($auditUser != "") {
248 
249  // Get the user_id so that we don't have to join in to the user table in the inner join and save time.
250  $sql = "select user_id from ${Cu}user where lower(user_name) = '" . prep_save($auditUser, 50) . "'";
251  $sth = db_query($sql, $dbh);
252  if (!$sth) {
253  throw new exception ("user query failed.", 26);
254  }
255  $userId = db_num_rows($sth) > 0 ? intval(db_fetch_row($sth, 0)[0]) : -1;
256 
257  $innerWhere[] = "(aut.user_id = $userId
258  or strpos($lowerAuditString, '\"user_name\":\"" . prep_save($auditUser, 50) . "') > 0)";
259  $outerWhere[] = "t.userl ? '" . prep_save($auditUser, 50) . "'";
260  }
261  if ($account != "") {
262  $innerWhere[] = "(aut.accountnumber = '" . prep_save($account, 50) . "'
263  or strpos($lowerAuditString, '\"accountnumber\":\"" . prep_save($account, 50) . "') > 0)";
264  $outerWhere[] = "t.account ? '" . prep_save($account, 50) . "'";
265  }
266  if ($loggedInUser != "") {
267  $innerWhere[] = "lower(aut.auditsrcuser_name) = '" . prep_save($loggedInUser, 50) . "'";
268  }
269  if ($actionType != "") {
270  $innerWhere[] = "lower(aut.auditfulldesc) = '" . prep_save($actionType, 255) . "'";
271  }
272 
273  // This part will make a cartesian product based on the value.
274  // In the case of {"user":[{"user_name":"177701"}]}, there will be two parts: {"user":[{ AND :"177701"}]}. This will cause two records in the resultset.
275  $unnest = 'unnest(string_to_array(AUDIT, \'"VALUE"\'))';
276  $unnestL = str_replace("AUDIT", $lowerAuditString, $unnest);
277  $unnestU = str_replace("AUDIT", $auditString, $unnest);
278  $userUnnest = str_replace("VALUE", "user_name", $unnestU);
279  $userUnnestL = str_replace("VALUE", "user_name", $unnestL);
280  $accountUnnest = str_replace("VALUE", "accountnumber", $unnestU);
281 
282  // For the string parts that start with :", remove that part and return the string before the second ". Otherwise return null.
283  // In the case of {"user":[{"user_name":"177701"}]}, the string is split on "user_name" so it starts with :" if it is valid.
284  $agg = 'jsonb_agg(trim(case when strpos(COLUMN1, \':"\') = 1 then substr(replace(COLUMN1, \':"\', \'\'), 1, strpos(replace(COLUMN1, \':"\', \'\'), \'"\') - 1) else null end))';
285  $agg .= ' || jsonb_agg(trim(COLUMN2))';
286  $userAgg = str_replace("COLUMN2", "u.user_name", str_replace("COLUMN1", "au.userun", $agg));
287  $userAggL = str_replace("COLUMN2", "lower(u.user_name)", str_replace("COLUMN1", "au.userunl", $agg));
288  $accountAgg = str_replace("COLUMN2", "au.accountnumber", str_replace("COLUMN1", "au.accountun", $agg));
289 
290  // This part is for the display.
291  // Coming in the results look like ["177701","177701",null,null,"616888","177701","616888"].
292  // Going out the results look like 177701, 616888. (Nulls are filtered out, results are deduplicated, and it has pretty display.)
293  // Before, this part was done in PHP. It is needed here because account or username could be filtered and there is server-side pagination.
294  $selectCol = "select string_agg(val, ', ')
295  from (select val->>0, row_number() over (partition by lower(val->>0) order by val->>0 = lower(val->>0)) as rown
296  from jsonb_array_elements(COLUMN::jsonb) as t(val)
297  where val->>0 is not null and trim(val->>0) <> ''
298  order by 1) as u (val) where rown = 1";
299 
300  $selectUser = str_replace("COLUMN", "t.user", $selectCol);
301  $selectAccount = str_replace("COLUMN", "t.account", $selectCol);
302 
303  $select = "select ($selectUser) as user, ($selectAccount) as account, t.date, t.actioncode, t.srcuser, t.before, t.after, t.script, t.action, t.accountnumber, t.rown ";
304 
305  $sql = "from (select au.date, au.actioncode, au.srcuser, au.before, au.after, au.script, au.action, au.accountnumber, row_number() over (order by au.date) as rown,
306  $userAgg as user,
307  $userAggL as userl,
308  $accountAgg as account
309  from (
310  select aut.auditdate as date, aut.auditaction as actioncode, aut.auditsrcuser_name as srcuser, aut.auditrecbefore as before, aut.auditrecafter as after,
311  aut.accountnumber, aut.auditsrccode_context as script, aut.auditfulldesc as action, aut.user_id,
312  $userUnnest as userun, $userUnnestL as userunl, $accountUnnest as accountun from ${Cu}audituser aut "
313  . ((count($innerWhere) > 0) ? "where " . implode(" and ", $innerWhere) : "") . "
314  ) au
315  left join ${Cu}user u on au.user_id = u.user_id
316  group by au.date, au.actioncode, au.srcuser, au.before, au.after, au.script, au.action, au.accountnumber) t "
317  . ((count($outerWhere) > 0) ? "where " . implode(" and ", $outerWhere) : "");
318 
319  $countSQL = "select count(*) $sql";
320  $mainSQL = "set time zone '$cuTzString'; $select $sql $sortSQL $limitSQL;";
321 
322  $sth = db_query($countSQL, $dbh);
323  if (!$sth) {
324  throw new exception("Count query failed.", 12);
325  }
326 
327  $count = intval(db_fetch_row($sth, 0)[0]);
328 
329  $sth = db_query($mainSQL, $dbh);
330  if (!$sth) {
331  throw new exception("Audit query failed.", 13);
332  }
333 
334  $auditRecords = array();
335  for($i = 0; $row = db_fetch_assoc($sth, $i); $i++) {
336  $results = ParseUserAuditRow($row, $Cu, $dbh, $changedOnly);
337  if ($results["status"] !== "000") { // Audit row is invalid in the database. DO NOT KILL THE PAGE. Instead DO NOT DISPLAY and log error in Kibana.
338  $logger->error("Banking audit is invalid in the database. CU: $Cu, Audit Date: " . $row["date"]);
339  continue;
340  }
341 
342  unset($row["before"]);
343  unset($row["after"]);
344  $row["details"] = $results["details"];
345 
346  // Do date stuff
347  $dateTime = new DateTime($row["date"], $cuTz);
348  $row["date"] = $dateTime->format("Y-m-d H:i:s.u") . "Z";
349 
350  $auditRecords[] = $row;
351  }
352 
353  $returnArray = array("status" => "000", "error" => "", "auditRecords" => array("total" => $count, "data" => $auditRecords));
354  } catch(exception $e) {
355  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage(), "auditRecords" => array("total" => 0, "data" => array()));
356  }
357  return $returnArray;
358 }
359 
360 /**
361  * function RemoveEmptyValues($string)
362  * Removes strings from an array that are null or are empty.
363  *
364  * @param $string -- the string element to check.
365  * @return boolean -- false if the element is removed from the array, true otherwise.
366  */
367 function RemoveEmptyValues($string) {
368  return isset($string) && trim($string) != "";
369 }
370 
371 /**
372  * function PrintPage($self, $readData)
373  * This function will print out the popup for the audit.
374  *
375  * @param $self -- the URL of this page.
376  * @param $readData -- the data needed for this page's load.
377  */
378 function PrintPage($self, $readData) { ?>
379  <script type="text/javascript">
380  <?php // Library javascript functions
381  getShowWaitFunctions(); ?>
382 
383  <?php
384  /**
385  * Globals (in javascript): this should be a short list
386  */
387  ?>
388  var isInitial = true;
389  var pageSize = 30;
390 
391  <?php
392  /**
393  * function AddDays(date, numberofDays)
394  * @param Date $date -- the starting date
395  * @param Int $numberofDays -- the number of days to add to the date.
396  * @return Date $date -- new date from the previous date + the number of days.
397  */
398  ?>
399  function AddDays(date, numberofDays) {
400  var d = new Date(date);
401  return new Date(d.getFullYear(), d.getMonth(), (d.getDate() + numberofDays));
402  }
403 
404  var gridDataSource = null;
405  var printDataSource = null;
406  var isInitial = true;
407 
408  <?php
409  /**
410  * function InitDataSources()
411  * Sets up the datasources.
412  */
413  ?>
414  function InitDataSources() {
415  var readConf = {
416  url: "<?php echo $self; ?>&operation=readAudits",
417  dataType: "json",
418  type: "POST"
419  };
420 
421  var GetDataFromSearch = function(data) {
422  var duration = $("#durationDDL").data("kendoDropDownList").value();
423  var startOn = $("#startingDatePicker").data("kendoDatePicker").value();
424  var endOn = $("#endingDatePicker").data("kendoDatePicker").value();
425  startOn = startOn == null || duration != "on" ? "" : kendo.toString(startOn, "yyyy-MM-dd");
426  endOn = endOn == null || duration != "on" ? "" : kendo.toString(endOn, "yyyy-MM-dd");
427 
428  data.duration = duration;
429  data.startOn = startOn;
430  data.endOn = endOn;
431  data.auditUser = $("[name='auditUser']").val().trim();
432  data.loggedInUser = $("[name='loggedInUser']").val().trim();
433  data.actionType = $("#actionDDL").data("kendoDropDownList").value();
434  data.account = $("[name='account']").val().trim();
435  data.showThis = $("#showDDL").data("kendoDropDownList").value();
436 
437  return data;
438  };
439 
440  var gridParameterMap = function(data, type) {
441  showWaitWindow();
442 
443  if (isInitial) { <?php // Start out with the default filter and sort. ?>
444  data.duration = "30";
445  data.showThis = "all";
446  isInitial = false;
447  } else {
448  data = GetDataFromSearch(data);
449  }
450 
451  <?php // Return this easier for me ?>
452  data.sort = kendo.stringify(data.sort);
453 
454  <?php // Pagination ?>
455  data.limit = data.pageSize;
456  data.offset = data.skip;
457  delete data.pageSize;
458  delete data.skip;
459  delete data.page;
460  delete data.take;
461 
462  return data;
463  };
464 
465  var printParameterMap = function(data, type) {
466  showWaitWindow();
467 
468  data = GetDataFromSearch(data);
469 
470  <?php // Return this easier for me ?>
471  data.sort = kendo.stringify(gridDataSource.sort());
472 
473  data.limit = 0;
474  data.offset = 0;
475 
476  return data;
477  };
478 
479  gridDataSource = new kendo.data.DataSource({
480  transport: {
481  read: readConf,
482  parameterMap: gridParameterMap
483  },
484  schema: {
485  model: {
486  id: "rown",
487  fields: {
488  rown: {type: "number"},
489  date: {type: "date"},
490  action: {type: "string"},
491  actioncode: {type: "string"},
492  details: {type: "odata"},
493  user: {type: "string"},
494  srcuser: {type: "string"},
495  script: {type: "string"},
496  checked: {type: "boolean"},
497  account: {type: "string"}
498  }
499  },
500  data: "data",
501  total: "total",
502  parse: function (data) {
503  hideWaitWindow();
504 
505  if (data.status !== "000") {
506  $.homecuValidator.displayMessage(data.error, $.homecuValidator.settings.statusError);
507  return [];
508  } else {
509  return data.auditRecords;
510  }
511 
512  }
513  },
514  serverSorting: true,
515  serverPaging: true,
516  pageSize: pageSize,
517  sortable: true,
518  sort: {field: "date", dir: "desc"}
519  });
520 
521  printDataSource = new kendo.data.DataSource({
522  transport: {
523  read: readConf,
524  parameterMap: printParameterMap
525  },
526  schema: {
527  model: {
528  id: "rown",
529  fields: {
530  rown: {type: "number"},
531  date: {type: "date"},
532  action: {type: "string"},
533  actioncode: {type: "string"},
534  details: {type: "odata"},
535  user: {type: "string"},
536  srcuser: {type: "string"},
537  script: {type: "string"},
538  checked: {type: "boolean"},
539  account: {type: "string"}
540  }
541  },
542  parse: function (data) {
543  hideWaitWindow();
544 
545  if (data.status !== "000") {
546  $.homecuValidator.displayMessage(data.error, $.homecuValidator.settings.statusError);
547  } else {
548  PrintAudits(data.auditRecords.data);
549  }
550 
551  $("#printDDL").data("kendoDropDownList").value("");
552  return []; <?php // Don't actually save anything in the dataSource. ?>
553  }
554  }
555  });
556  }
557 
558  <?php
559  /**
560  * function Init()
561  * Initializes all controls. This includes the search bar controls and the grid itself.
562  */
563  ?>
564  function Init() {
565  $.homecuValidator.setup({formValidate: "searchForm", formStatusField: "formValidateDiv"});
566  if ("<?php echo $readData["status"]; ?>" !== "000") {
567  $.homecuValidator.displayMessage("<?php echo $readData["error"]; ?>", $.homecuValidator.settings.statusError);
568  } else {
569  $.homecuValidator.setup({formValidate: "searchForm", formStatusField: "formValidateDiv"});
570 
571  InitDataSources();
572 
573  var grid = $("#auditGrid").kendoGrid({
574  dataSource: gridDataSource,
575  columns: [
576  {template: "<input type='checkbox' class='rowCheckbox'>", attributes: {"class": "checkboxTD"},
577  headerTemplate: "<input type='checkbox' class='allCheckbox'>", width: "45px", sortable: false, filterable: false},
578  {field: "date", title: "Date", format: "{0:G}"},
579  {field: "user", title: "Banking User"},
580  {field: "account", title: "Account"},
581  {field: "srcuser", title: "Changed by"},
582  {field: "action", title: "Action Type"},
583 
584  ],
585  noRecords: {
586  template: "<tr class='noRecordsDiv'><td colspan='4'>No Records Found</td></tr>"
587  },
588  detailTemplate: ReturnAuditDetailTemplate,
589  scrollable: false,
590  sortable: true,
591  toolbar: $("#toolbarTemplate").html(),
592  pageable: {
593  alwaysVisible: false <?php // Do not show pagination when there are 30 or less results. ?>
594  }
595  }).data("kendoGrid");
596 
597  $("#auditGrid").on("click", ".auditDetailContainer .auditShowAllBtn", function() {
598  if ($(this).text().trim() == "Show All") {
599  $(this).closest(".auditDetailContainer").removeClass("hideAA");
600  $(this).text("Show Changed");
601  } else {
602  $(this).closest(".auditDetailContainer").addClass("hideAA");
603  $(this).text("Show All");
604  }
605 
606  return false;
607  });
608 
609  <?php printCheckboxEvents("#auditGrid"); ?>
610 
611  var printDDL = $("#printDDL").kendoDropDownList({
612  dataSource: {
613  data: <?php echo HCU_JsonEncode($readData["data"]["prints"]); ?>,
614  filter: {field: "value", operator: "neq", value: "selected"}
615  },
616  dataTextField: "text",
617  dataValueField: "value",
618  change: function(e) {
619  switch(this.value()) {
620  case "":
621  break;
622  case "selected":
623  var shownData = $("#auditGrid").data("kendoGrid").dataSource.view();
624  var selectedData = $.grep(shownData, function(n, i) { return n.checked; });
625  PrintAudits(selectedData);
626  printDDL.value("");
627  break;
628  case "changed":
629  printDataSource.read({changedOnly: "Y"});
630  break;
631  case "all":
632  printDataSource.read();
633  break;
634  }
635  }
636  }).data("kendoDropDownList");
637 
638  $("#auditGrid").on("click", ".allCheckbox,.rowCheckbox", function() {
639  var checked = $(this).prop("checked");
640  var doFilter = false;
641  if (checked && $(this).is(".rowCheckbox")) {
642  doFilter = false;
643  } else {
644  var gridData = $("#auditGrid").data("kendoGrid");
645  var shownData = grid.dataSource.view();
646  var checkedData = $.grep(shownData, function(n, i) {
647  return n.checked;
648  });
649 
650  doFilter = checkedData.length <= 0;
651  }
652 
653  var ddl = $("#printDDL").data("kendoDropDownList");
654  if (doFilter) {
655  ddl.dataSource.filter({field: "value", operator: "neq", value: "selected"});
656  } else {
657  ddl.dataSource.filter(null);
658  }
659  });
660 
661  $(".k-grid-show").click(function() {
662  if ($(this).text().trim() == "Show Filter") {
663  $(".auditFilterDiv").show();
664  $(this).text("Hide Filter");
665  } else {
666  $(".auditFilterDiv").hide();
667  $(this).text("Show Filter");
668  }
669  });
670  }
671 
672  var actionDDL = $("#actionDDL").kendoDropDownList({
673  dataSource: {
674  data: <?php echo HCU_JsonEncode($readData["data"]["types"]); ?>
675  },
676  dataTextField: "text",
677  dataValueField: "value"
678  }).data("kendoDropDownList");
679 
680  var showDDL = $("#showDDL").kendoDropDownList({
681  dataSource: {
682  data: <?php echo HCU_JsonEncode($readData["data"]["shows"]); ?>
683  },
684  dataTextField: "text",
685  dataValueField: "value"
686  }).data("kendoDropDownList");
687 
688  $("#okayBtn").click(function() {
689  if ($.homecuValidator.validate()) {
690  grid.dataSource.query({page: 1, pageSize: pageSize, sort: grid.dataSource.sort()}); <?php // Make sure to reset the page number when the filter changes. ?>
691  }
692  return false;
693  });
694 
695  $("[name='durationCheck']").click(function() {
696  var thisElement = this;
697  startingDatePicker.enable(($(thisElement).data("val") + "").trim() == "on");
698  });
699 
700  var now = new Date();
701  now.setHours(0, 0, 0, 0);
702  var all = new Date();
703  all.setHours(0, 0, 0, 0);
704  all.setDate(1);
705  all.setMonth((all.getMonth() - 1) % 12);
706  all.setFullYear(all.getFullYear() - 1);
707 
708  var minx = new Date();
709  minx.setHours(0, 0, 0, 0);
710  var min30 = AddDays(minx, -30);
711  var min60 = AddDays(minx, -60);
712  var min90 = AddDays(minx, -90);
713 
714  var startingDatePicker = $("#startingDatePicker").kendoDatePicker({
715  max: now,
716  min: all,
717  format: "MM/dd/yyyy", <?php // Add zeroes so that MTB looks okay. ?>
718  change: function() {
719  $("#durationDDL").data("kendoDropDownList").value("on"); <?php // Now doesn't align with the duration options. ?>
720  }
721  }).data("kendoDatePicker");
722 
723  var endingDatePicker = $("#endingDatePicker").kendoDatePicker({
724  max: now,
725  min: all,
726  format: "MM/dd/yyyy", <?php // Add zeroes so that MTB looks okay. ?>
727  change: function() {
728  $("#durationDDL").data("kendoDropDownList").value("on"); <?php // Now doesn't align with the duration options. ?>
729  }
730  }).data("kendoDatePicker");
731 
732  $("#startingDatePicker").focus(function() { <?php // Clear out when selecting the date. ?>
733  $(this).val(null);
734  startingDatePicker.value(null);
735  });
736 
737  $("#endingDatePicker").focus(function() { <?php // Clear out when selecting the date. ?>
738  $(this).val(null);
739  endingDatePicker.value(null);
740  });
741 
742  $("#startingDatePicker").blur(function() {
743  if ($(this).hasClass("k-invalid")) {
744  $(startingDatePicker.wrapper).css("border", "1px solid #d80000");
745  } else {
746  $(startingDatePicker.wrapper).css("border", "inherit");
747  }
748  });
749 
750  $("#endingDatePicker").blur(function() {
751  if ($(this).hasClass("k-invalid")) {
752  $(endingDatePicker.wrapper).css("border", "1px solid #d80000");
753  } else {
754  $(endingDatePicker.wrapper).css("border", "inherit");
755  }
756  });
757 
758  var durationDDL = $("#durationDDL").kendoDropDownList({
759  dataSource: {
760  data: [
761  {text: "Last 30 days", value: "30"},
762  {text: "Last 60 days", value: "60"},
763  {text: "Last 90 days", value: "90"},
764  {text: "All Available", value: "all"},
765  {text: "Custom", value: "on"}
766  ]
767  },
768  dataTextField: "text",
769  dataValueField: "value",
770  change: function() {
771  switch(this.value()) {
772  case "custom": break; <?php // Nothing to do. The date is whatever they want. ?>
773  case "all": startingDatePicker.value(all); endingDatePicker.value(now); break;
774  case "30": startingDatePicker.value(min30); endingDatePicker.value(now); break;
775  case "60": startingDatePicker.value(min60); endingDatePicker.value(now); break;
776  case "90": startingDatePicker.value(min90); endingDatePicker.value(now); break;
777  }
778  }
779  }).data("kendoDropDownList");
780 
781  startingDatePicker.value(min30); <?php // Starting value: previous 30 days. ?>
782  endingDatePicker.value(now); <?php // It ends today! ?>
783  }
784 
785  var activeWindows = [];
786  $(document).ready(function() {
787  Init();
788  <?php printClickOverlayEvent(); ?>
789  });
790 
791  <?php
792  /**
793  * function ReturnAuditDetailTemplate(row, isPrinting)
794  * This function returns the detail template and is used in the grid.
795  *
796  * @param $row -- the row to display
797  * @param isPrinting -- if true, display/hide some things
798  * @return string of the whole HTML to paste
799  */
800  ?>
801  function ReturnAuditDetailTemplate(row, isPrinting) {
802  var returnString = '<div class="auditMaxHeight"><div class="container-fluid auditDetailContainer hideAA">';
803 
804  for (var i = 0, iLength = row.details.length; i != iLength; i++) {
805  var tableRow = row.details[i];
806  if (iLength > 1) {
807  var message = tableRow.label + (tableRow.type != "mixed" ? "(" + tableRow.type + " records)" : "");
808  returnString += '<div class="row form-group"><div class="col-xs-12"><h4 class="h4 hcuSpacerx">' + message + '</h4></div></div>';
809  }
810 
811  if (tableRow.altDesc != null && tableRow.altDesc != "") {
812  returnString += tableRow.altDesc;
813  continue;
814  }
815 
816  for (var j = 0, jLength = tableRow.rows.length; j != jLength; j++) {
817  var rowRow = tableRow.rows[j];
818 
819  if (rowRow.altDesc != null && rowRow.altDesc != "") {
820  returnString += '<div class="row"><div class="col-xs-12">' + rowRow.altDesc + '</div></div>';
821  continue;
822  }
823 
824  var showBefore = true;
825  var showAfter = true;
826  switch (rowRow.type) {
827  case "add": showBefore = false; break;
828  case "remove": showAfter = false; break;
829  }
830  var theseClasses = showBefore && showAfter ? "col-xs-4 col-md-4" : "col-xs-8 col-md-8";
831  if (tableRow.type == "mixed") {
832  var message = rowRow.type + " record:";
833  returnString += '<div class="row form-group"><div class="col-xs-12"><h4 class="h4 hcuSpacerx">' + message + '</h4></div></div>';
834  }
835  returnString += '<div class="row form-group mockKendoTable">';
836  if (!isPrinting && i == 0 && j == 0) {
837  returnString += '<a href="#" class="auditShowAllBtn">Show All</a><br>';
838  }
839  returnString += '<div class="th">';
840  returnString += '<span class="col-xs-3 col-md-4">Column</span>';
841 
842  if (showBefore) {
843  returnString += '<span class="' + theseClasses + '">Before</span>';
844  }
845  if (showAfter) {
846  returnString += '<span class="' + theseClasses + '">After</span>';
847  }
848 
849  returnString += '<div class="clearfix hidden-xs-block"></div>';
850  returnString += '</div>'; <?php // Ends TH. ?>
851 
852  for (var k = 0, kLength = rowRow.values.length; k != kLength; k++) {
853  var colRow = rowRow.values[k];
854  var same = !isPrinting && colRow.same ? "aa" : "";
855 
856  returnString += '<div class="tr ' + same + '">';
857  returnString += '<span class="col-xs-3 col-md-4">' + colRow.label + '</span>';
858 
859  if (showBefore) {
860  returnString += '<span class="' + theseClasses + '">';
861  if (colRow.before == "") {
862  returnString += "&nbsp;";
863  } else {
864  returnString += colRow.before;
865  }
866  returnString += "</span>";
867  }
868 
869  if (showAfter) {
870  returnString += '<span class="' + theseClasses + '">';
871  if (colRow.after == "") {
872  returnString += "&nbsp;";
873  } else {
874  returnString += colRow.after;
875  }
876  returnString += "</span>";
877  }
878 
879  returnString += '<div class="clearfix hidden-xs-block"></div>';
880  returnString += '</div>'; <?php // Ends TR. ?>
881  }
882 
883  returnString += "</div>"; <?php // Ends mockKendoTable. ?>
884  }
885  }
886 
887  returnString += "</div></div>"; <?php // Ends auditMaxHeight, container-fluid. ?>
888  return returnString;
889  }
890 
891  <?php
892  /**
893  * function PrintAudits()
894  * This function prints the audits.
895  */
896  ?>
897  function PrintAudits(gridData) {
898  $("<div id='tempAudit' style='display:none;'><div class='container'><div class='row form-group mockKendoTable auditInsertion'></div></div></div>").appendTo("body");
899 
900  for(var i = 0; i != gridData.length; i++) {
901  var record = gridData[i];
902  var rowTemplate = "<div class='tr nobottom'><span class='col-xs-4'>" + record.action + "</span><span class='col-xs-4'>"
903  + (record.user == null ? "&nbsp;" : record.user) + "</span>"
904  + "<span class='col-xs-4'>" + kendo.toString(record.date, 'd')
905  + "</span></div><div class='tr notop'><span class='col-xs-12'><div class='container-fluid'><div class=' col-xs-12'>"
906  + ReturnAuditDetailTemplate(record, true) + "</div></div></span></div>";
907  $(".auditInsertion").append(rowTemplate);
908  }
909  var newWindow = window.open("", "auditPrint");
910  $("#auditPrintForm [name='shell']").val($("#tempAudit").html());
911  $("#auditPrintForm [name='title']").val("View User Events");
912  $("#auditPrintForm").submit();
913  $("#tempAudit").remove();
914  }
915  </script>
916  <style>
917  .auditFilterDiv > div {
918  max-width: 950px;
919  }
920  </style>
921  <script type="text/x-kendo-template" id="toolbarTemplate">
922  <div id='printDDL' style='width:200px;'></div>
923  <a role="button" class="k-button k-button-icontext k-grid-show" href="\#">Show Filter</a>
924  </script>
925  <div id="formValidateDiv" class="k-block k-error-colored formValidateDiv" style="display:none"></div>
926  <div class="container-fluid">
927  <form id="searchForm"><div class="well well-sm container-fluid auditFilterDiv" style="display:none;"><div>
928  <div class="col-xs-12 hcuSpacer hcu-no-padding">
929  <div class="container-fluid">
930  <div class="row hcuSpacer"><label class="col-xs-12 col-sm-5">Date Search Range</label>
931  <div class="col-xs-12 col-sm-7"><div id="durationDDL" class="hcu-all-100"></div></div>
932  </div>
933  <div class="row hcuSpacer"><label class="col-xs-12 col-sm-5">From</label>
934  <div class="col-xs-12 col-sm-7">
935  <input type="text" id="startingDatePicker" homecu-match="date" data-homecuCustomMatch-msg="Date is invalid."> &nbsp; To &nbsp;
936  <input type="text" id="endingDatePicker" homecu-match="date" data-homecuCustomMatch-msg="Date is invalid."
937  homecu-dategtvalue="startingDatePicker" homecu-dategttype="field" data-homecuCustomDateGTValue-msg="Date range is invalid.">
938  </div>
939  </div>
940  </div>
941  </div>
942 
943  <div class="col-xs-12 hcuSpacer hcu-no-padding">
944  <div class="container hcu-all-100">
945  <div class="row hcuSpacer">
946  <label class="col-xs-12 col-sm-5">Changes for Banking User</label>
947  <div class="col-xs-12 col-sm-7"><input type="text" class="k-input k-textbox hcu-all-100" name="auditUser"></div>
948  </div>
949  <div class="row hcuSpacer">
950  <label class="col-xs-12 col-sm-5">Changes for Account</label>
951  <div class="col-xs-12 col-sm-7"><input type="text" class="k-input k-textbox hcu-all-100" name="account"></div>
952  </div>
953  <div class="row hcuSpacer">
954  <label class="col-xs-12 col-sm-5">Changed by User</label>
955  <div class="col-xs-12 col-sm-7"><input type="text" class="k-input k-textbox hcu-all-100" name="loggedInUser"></div>
956  </div>
957  <div class="row hcuSpacer">
958  <label class="col-sm-5 col-xs-12">Action Type</label>
959  <div class="col-sm-7 col-xs-12"><div id="actionDDL" class="hcu-all-100"></div></div>
960  </div>
961  <div class="row hcuSpacer">
962  <label class="col-sm-5 col-xs-12">Show</label>
963  <div class="col-sm-7 col-xs-12"><div id="showDDL" class="hcu-all-100"></div></div>
964  </div>
965  </div>
966  </div>
967 
968  <div class="col-xs-12 hcuSpacer hcu-no-padding"><div class="container hcu-all-100"><div class="row"><div class="col-xs-12">
969  <a href="#" class="k-button k-primary floatRight" id="okayBtn">Show</a>
970  </div></div></div></div>
971  </div></div></form>
972  <div id="auditGrid" class="hcu-all-100 pointerGrid"></div>
973  <form id="auditPrintForm" method="post" action="shell.prg" target="auditPrint">
974  <input type="hidden" name="shell" value="">
975  <input type="hidden" name="title" value="">
976  </form>
977  </div>
978  <div id="previewWindow"></div>
979 <?php }
Definition: User.php:7