Odyssey
aAudit.i
1 <?php
2 
3 /**
4  * function _ValidateParseUserAuditRow($row, $Cu, $dbh)
5  * Validates the parameters for ParseUserAuditRow.
6  *
7  * @param $row -- the row from the database.
8  * @param $Cu -- the credit union.
9  * @param $dbh -- the database connection.
10  *
11  * @return $status -- "000" if successful, nonzero otherwise.
12  * @return $error -- "" if successful, nonempty otherwise.
13  * @return $data.details -- empty array
14  * @return $data.before -- the before part of the row.
15  * @return $data.after -- the after part of the row.
16  * @return $data.isDelete -- if delete, the before and after can be null and that is okay.
17  */
18 function _ValidateParseUserAuditRow($row, $Cu, $dbh) {
19  try {
20  if (!isset($row) || !is_array($row)) {
21  throw new exception ("Row is invalid.", 2);
22  }
23  if (!isset($Cu) || !is_string($Cu)) {
24  throw new exception ("Cu is invalid.", 3);
25  }
26  if (!isset($dbh)) {
27  throw new exception ("Dbh is invalid.", 4);
28  }
29  if (!HCU_array_key_exists("before", $row) || !HCU_array_key_exists("after", $row)) {
30  throw new exception ("Row is not valid.", 1);
31  }
32 
33  $auditaction = HCU_array_key_value("actioncode", $row);
34 
35  if (in_array($auditaction, array("U_DEL_U", "U_DEL_G", "U_DEL_A"))) {
36  $isDelete = true;
37  $after = array();
38 
39  $before = HCU_JsonDecode($row["before"], false);
40  if (!is_array($before)) { // NULL in the database which IS NOT an error to Kibana but is the previous style for a "delete."
41  $before = array();
42  } else if (!HCU_array_key_exists("deldata", $before)) {
43  throw new exception ("Delete audit is invalid.", 5);
44  }
45  } else {
46  $isDelete = false;
47 
48  $before = HCU_JsonDecode($row["before"], false);
49  if (!is_array($before)) {
50  throw new exception("Before JSON is not valid.", 13);
51  }
52  $after = HCU_JsonDecode($row["after"], false);
53  if (!is_array($after)) {
54  throw new exception("After JSON is not valid.", 14);
55  }
56  }
57 
58  $details = array();
59 
60  $returnArray = array("status" => "000", "error" => "", "data" => array("details" => $details, "before" => $before, "after" => $after, "isDelete" => $isDelete, "auditaction" => $auditaction));
61  } catch (exception $e) {
62  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
63  }
64  return $returnArray;
65 }
66 
67 /**
68  * function _ValidateTable ($table, $after, $tableDefinition)
69  * Validates the table inside of the before field and compares it with the after.
70  *
71  * @param $table -- the table changed.
72  * @param $after -- the after array.
73  * @param $tableDefinition -- the table definition
74  *
75  * @return $status -- "000" if successful, nonzero otherwise.
76  * @return $error -- "" if successful, nonempty otherwise.
77  * @return $data.afterTable -- the table in the after column.
78  * @return $data.newTableRow -- the row to send to the kendo template to show the audit.
79  * @return $data.dontDoTableForeach -- this is false.
80  */
81 function _ValidateTable ($table, $after, $tableDefinition) {
82  try {
83 
84  if (!isset($table) || !is_string($table)) {
85  throw new exception ("Table is invalid.", 1);
86  }
87  if (!isset($after) || !is_array($after)) {
88  throw new exception ("After is invalid.", 2);
89  }
90  if (!isset($tableDefinition) || !is_array($tableDefinition)) {
91  throw new exception ("Table definition is invalid.", 3);
92  }
93 
94  $afterTable = HCU_array_key_exists($table, $after) ? $after[$table] : null;
95 
96  if (!isset($afterTable) || !is_array($afterTable)) {
97  throw new exception("Table mismatch.", 15);
98  }
99 
100  $label = isset($tableDefinition["_label"]) ? $tableDefinition["_label"] : $table;
101  $newTableRow = array("table" => $table, "label" => $label, "rows" => array());
102 
103  $dontDoTableForeach = false;
104 
105  $returnArray = array("status" => "000", "error" => "", "data" => array("afterTable" => $afterTable, "newTableRow" => $newTableRow, "dontDoTableForeach" => $dontDoTableForeach));
106  } catch (exception $e) {
107  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
108  }
109  return $returnArray;
110 }
111 
112 /**
113  * function _DoGroupAudit ($table, $newTableRow, $changedRows, $afterTable)
114  * Does the group audit
115  *
116  * @param $table -- The table that will be changing.
117  * @param $newTableRow -- Add an alternate description for the group audit.
118  * @param $changedRows -- Get the changed rows for the group name.
119  * @param $afterTable -- The after audits.
120  *
121  * @return $status -- "000" if successful, nonzero otherwise.
122  * @return $error -- "" if successful, nonempty otherwise.
123  * @return $data.newTableRow -- the table row with an alternate description attached.
124  * @return $data.dontDoTableForeach -- this will be true.
125  */
126 function _DoGroupAudit ($table, $newTableRow, $changedRows, $afterTable) {
127  try {
128  if (!isset($table) || $table !== "group") {
129  throw new exception ("Table isn't \"group\".", 4);
130  }
131  if (!isset($newTableRow) || !is_array($newTableRow)) {
132  throw new exception ("New table row isn't valid.", 5);
133  }
134  if (!isset($changedRows) || !is_array($changedRows)) {
135  throw new exception ("Changed Rows isn't valid.", 6);
136  }
137  if (!isset($afterTable) || !is_array($afterTable)) {
138  throw new exception ("After table isn't valid.", 7);
139  }
140  $dontDoTableForeach = true;
141  $groupName = false;
142  $isAdded = false;
143  $isRemoved = false;
144 
145  foreach($changedRows as $changedRow) {
146  $thisGroupName = HCU_array_key_value("group_name", $changedRow);
147  if ($thisGroupName) {
148  $isRemoved = true;
149  }
150  if ($groupName === false && $thisGroupName !== false) {
151  $groupName = $thisGroupName;
152  break;
153  }
154  }
155 
156  foreach($afterTable as $changedRow) {
157  $thisGroupName = HCU_array_key_value("group_name", $changedRow);
158  if ($thisGroupName) {
159  $isAdded = true;
160  }
161  if ($groupName === false && $thisGroupName !== false) {
162  $groupName = $thisGroupName;
163  break;
164  }
165  }
166 
167  if ($groupName !== false) {
168  $newTableRow["altDesc"] = "<b>$groupName</b> " . ($isAdded ? ($isRemoved ? "was changed." : "was added.") : ($isRemoved ? "was removed." : "was changed."));
169  $dontDoTableForeach = true;
170  } else { // Invalid because we are expecting to show the group name.
171  throw new exception ("Group audit is invalid.", 3);
172  }
173 
174  $returnArray = array("status" => "000", "error" => "", "data" => array("newTableRow" => $newTableRow, "dontDoTableForeach" => $dontDoTableForeach));
175  } catch (exception $e) {
176  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
177  }
178  return $returnArray;
179 }
180 
181 /**
182  * function ParseUserAuditRow($row, $Cu, $dbh)
183  * This is the entry point for parsing the before and after snapshots in the audit table for display.
184  * This started as a shared function between the user and admin audits but it has diverged significantly now for clarity with specific audits.
185  *
186  * @param $row -- this is the database row from the audit table. Parse the before and after snapshots and put that back in the row in the "details" attribute.
187  * @param $Cu -- the credit union. This is needed for getting the time specific to the CU.
188  * @param $dbh -- the database connection. This is needed for getting the time specific to the CU as well as the mask.
189  * @param $changedOnly -- If true, the function will only return parts of the audit that were changed.
190  *
191  * @return $status -- "000" if successful, nonzero otherwise.
192  * @return $error -- "" if successful, nonempty otherwise.
193  * @return $data.details -- What we shall be returning to the kendo template.
194  */
195 function ParseUserAuditRow($row, $Cu, $dbh, $changedOnly = false) {
196 
197  try {
198 
199  $results = _ValidateParseUserAuditRow($row, $Cu, $dbh);
200 
201  if ($results["status"] !== "000") {
202  throw new exception ("User Audit Row is invalid.", 1);
203  }
204  extract($results["data"]);
205 
206  if ($isDelete) {
207  $deldata = HCU_array_key_value("deldata", $before);
208  $msg = "";
209  if ($auditaction == "U_DEL_U") {
210 
211  if ($deldata === false) {
212  $msg = "User was deleted.";
213  } else if (!HCU_array_key_exists("user_name", $deldata)) {
214  throw new exception ("Delete user audit is invalid.", 4);
215  } else {
216  $msg = "User <b>" . trim($deldata["user_name"]) . "</b> was deleted.";
217  }
218 
219  } else if ($auditaction == "U_DEL_G") {
220 
221  if ($deldata === false) {
222  $msg = "Group was deleted.";
223  } else if (!HCU_array_key_exists("group_name", $deldata)) {
224  throw new exception ("Delete group audit is invalid.", 5);
225  } else {
226  $msg = "Group <b>" . trim($deldata["group_name"]) . "</b> was deleted.";
227  }
228 
229  } else if ($auditaction == "U_DEL_A") {
230 
231  if ($deldata === false) {
232  $msg = "Account was deleted.";
233  } else if (!HCU_array_key_exists("accountnumber", $deldata)) {
234  throw new exception ("Delete account audit is invalid.", 6);
235  } else {
236  $msg = "Account <b>" . trim($deldata["accountnumber"]) . "</b> was deleted.";
237  }
238 
239  }
240 
241  if ($msg == "") {
242  throw new exception ("Delete audit is invalid.", 7);
243  }
244 
245  $newTableRow["altDesc"] = $msg;
246  $details[] = $newTableRow;
247  } else {
248  foreach($before as $table => $changedRows) {
249  $tableDefinition = GetTableDefinition (array("cu" => $Cu), $table)[$table];
250 
251  $results = _ValidateTable ($table, $after, $tableDefinition);
252  if ($results["status"] !== "000") {
253  throw new exception ("Table row is invalid.", 2);
254  }
255  extract($results["data"]);
256  switch ($table) {
257  case "group": // Only do a description with a group.
258 
259  $results = _DoGroupAudit ($table, $newTableRow, $changedRows, $afterTable);
260  if ($results["status"] !== "000") {
261  throw new exception ("Group audit is invalid.", 3);
262  }
263  extract($results["data"]);
264  break;
265  }
266 
267  if (!$dontDoTableForeach) {
268  $results = ParseUserAuditTable($changedRows, $afterTable, $tableDefinition, $table, $dbh, $Cu, $changedOnly);
269  if ($results["status"] !== "000") {
270  throw new exception($results["error"], $results["status"]);
271  } else {
272  $newTableRow["rows"] = $results["returnedRows"];
273  $newTableRow["type"] = $results["returnedType"];
274  }
275  }
276 
277  $details[] = $newTableRow;
278  }
279  }
280 
281  $returnArray = array("status" => "000", "error" => "", "details" => $details);
282  } catch (exception $e) {
283  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
284  }
285 
286  return $returnArray;
287 }
288 
289 /**
290  * function _ValidateParseUserAuditTable ($changedRows, $afterTable, $tableDefinition, $table, $dbh, $Cu)
291  * Validate parameters for ParseUserAuditTable.
292  *
293  * @param $changedRows -- the changedRows from the before snapshot.
294  * @param $afterTable -- the changedRows from the after snapshot.
295  * @param $tableDefinition -- the definition of the table (defined in cuDataModel.i.)
296  * @param $table -- the table.
297  * @param $dbh -- the database connection.
298  * @param $Cu -- the credit union.
299  *
300  * @return $status -- "000" if successful, nonzero otherwise.
301  * @return $error -- "" if successful, nonempty otherwise.
302  */
303 function _ValidateParseUserAuditTable ($changedRows, $afterTable, $tableDefinition, $table, $dbh, $Cu) {
304  try {
305  if (!isset($changedRows) || !is_array($changedRows)) {
306  throw new exception ("Changed rows is not valid.", 1);
307  }
308  if (!isset($afterTable) || !is_array($afterTable)) {
309  throw new exception ("After table is not valid.", 2);
310  }
311  if (!isset($tableDefinition) || !is_array($tableDefinition)) {
312  throw new exception ("Table definition is not valid.", 3);
313  }
314  if (!isset($table) || !is_string($table)) {
315  throw new exception ("Table is not valid.", 4);
316  }
317  if (!isset($dbh)) {
318  throw new exception ("Dbh is not valid.", 5);
319  }
320  if (!isset($Cu) || !is_string($Cu)) {
321  throw new exception ("Cu is not valid.", 6);
322  }
323 
324  $returnArray = array("status" => "000", "error" => "");
325  } catch (exception $e) {
326  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
327  }
328  return $returnArray;
329 }
330 
331 /**
332  * function ParseUserAuditTable($changedRows, $afterTable, $tableDefinition, $passwordMask, $table, $dbh, $Cu)
333  * This function parses the audit at the table level. It is broken out because there is an audit that just has a description for the audit instead.
334  *
335  * @param $changedRows -- the array of rows changed in the before snapshot.
336  * @param $afterTable -- the array of rows changed in the after snapshot.
337  * @param $tableDefinition -- the table definition in cuDataModel.i. This is used for validation as well as any table or column labels.
338  * @param $table -- the table that the audit is being run on.
339  * @param $dbh -- the database connection. This is needed for getting the time specific to the CU as well as the mask.
340  * @param $Cu -- the credit union. This is needed for getting the time specific to the CU.
341  * @param $changedOnly -- If true, the function will only return parts of the audit that were changed.
342  *
343  * @return array [
344  * status -- "000" if successful, nonzero if there are errors.
345  * error -- blank if successful, nonblank otherwise.
346  * returnedRows -- Rows for the audit.
347  * returnedType -- "mixed" for different audit types, "add" for new records, "remove" for deleted records, "update" for changed records.
348  * ]
349  */
350 function ParseUserAuditTable($changedRows, $afterTable, $tableDefinition, $table, $dbh, $Cu, $changedOnly) {
351 
352  $returnedRows =[];
353  // $returnedType = ""; // not needed, variable immediately overwritten
354  try {
355 
356  $results = _ValidateParseUserAuditTable ($changedRows, $afterTable, $tableDefinition, $table, $dbh, $Cu);
357  if ($results["status"] !== "000") {
358  throw new exception ("Validate failed.", 1);
359  }
360  switch($table) {
361  case "memberacctrights":
362  $results = GetMemberAcctRightsDescrs($changedRows, $afterTable);
363  if ($results["status"] !== "000") {
364  throw new exception($results["error"], $results["status"]);
365  }
366  $returnedType = "desc";
367  $returnedRows = $results["returnedRows"];
368  break;
369 
370  default:
371 
372  // Throws 'current() expects parameter 1 to be array, null given' otherwise
373  $tableTypeMap = [];
374 
375  foreach($changedRows as $j => $beforeRow) {
376  $afterRow = $afterTable[$j];
377 
378  if (!isset($afterRow)) {
379  throw new exception("Table mismatch.", 16);
380  }
381 
382  $beforeEmpty = count($beforeRow) == 0;
383  $afterEmpty = count($afterRow) == 0;
384  if ($beforeEmpty && $afterEmpty) {
385  continue;
386  }
387 
388  $results = ParseAuditValueRow($beforeEmpty, $afterEmpty, $afterRow, $beforeRow, $tableDefinition, $table, $dbh, $Cu, $changedOnly);
389  if ($results["status"] !== "000") {
390  throw new exception($results["error"], $results["status"]);
391  }
392  $combinedRow = $results["combinedRow"];
393 
394  $type = $afterEmpty ? "remove" : ($beforeEmpty ? "add" : "update");
395  $combinedRow = ["type" => $type, "values" => $combinedRow];
396  $tableTypeMap[$type] = $type;
397 
398  $returnedRows[] = $combinedRow;
399  }
400 
401  $returnedType = count($tableTypeMap) > 1 ? "mixed" : current($tableTypeMap);
402  break;
403  }
404 
405  $returnArray = ["status" => "000", "error" => "", "returnedRows" => $returnedRows, "returnedType" => $returnedType];
406  } catch (exception $e) {
407  $returnArray = ["status" => $e->getCode(), "error" => $e->getMessage()];
408  }
409 
410  return $returnArray;
411 }
412 
413 /**
414  * GetMemberAcctRightsDescrs($changedRows, $afterTable)
415  * This gets a series of custom descriptions for the memberacctrights table. This replaces the normal auditing for this table.
416  *
417  * @param $changedRows -- the rows in the before snapshot.
418  * @param $afterTable -- the rows in the after snapshot.
419  *
420  * @return status -- "000" if successful, nonzero if there are errors.
421  * @return error -- blank if successful, nonblank otherwise.
422  * @return returnedRows -- Rows for the audit.
423  */
424 function GetMemberAcctRightsDescrs($changedRows, $afterTable) {
425  $returnedRows = array();
426  try {
427  $rightMap = array();
428  if (!isset($changedRows) || !is_array($changedRows)) {
429  throw new exception ("Changed Rows is not valid.", 1);
430  }
431  if (!isset($afterTable) || !is_array($afterTable)) {
432  throw new exception ("After table is not valid.", 2);
433  }
434  foreach($changedRows as $beforeRow) {
435  if (count($beforeRow) == 0) {
436  continue; // Do not process an empty array
437  }
438  $right = HCU_array_key_value("whichright", $beforeRow);
439  $accountnumber = HCU_array_key_value("accountnumber", $beforeRow);
440  if ($right === false) {
441  throw new exception ("Before right is not found.", 5);
442  }
443  if ($accountnumber === false) {
444  throw new exception ("Before accountnumber is not found.", 10);
445  }
446  $key = trim($accountnumber) . "|" . trim($right);
447 
448  $rightMap[$key] = array("before" => $beforeRow);
449  }
450 
451  foreach($afterTable as $afterRow) {
452  if (count($afterRow) == 0) {
453  continue; // Do not process an empty array
454  }
455  $right = HCU_array_key_value("whichright", $afterRow);
456  $accountnumber = HCU_array_key_value("accountnumber", $afterRow);
457  if ($right === false) {
458  throw new exception ("After right is not found.", 6);
459  }
460  if ($accountnumber === false) {
461  throw new exception ("After accountnumber is not found.", 11);
462  }
463  $key = trim($accountnumber) . "|" . trim($right);
464  if (!HCU_array_key_exists($key, $rightMap)) {
465  $rightMap[$key] = array("before" => array(), "after" => $afterRow);
466  } else {
467  $rightMap[$key]["after"] = $afterRow;
468  }
469  }
470 
471  // I have an guarantee that rightMap is set because I set up the variable.
472  if (count($rightMap) == 0) {
473  throw new exception ("Member acct audit doesn't have any information.", 9);
474  }
475  foreach($rightMap as $key => $rightStuff) {
476  $rightRightMap = array("ACCESS" => "access", "ES" => "eStatement", "RDC" => "RDC", "BP" => "BillPay");
477 
478  $beforeRow = HCU_array_key_value("before", $rightStuff);
479  $afterRow = HCU_array_key_value("after", $rightStuff);
480  $beforeEmpty = $beforeRow === false || count($beforeRow) == 0;
481  $afterEmpty = $afterRow === false || count($afterRow) == 0;
482 
483  // There is a case where the platform is set to NULL. In this case, it is really not restricted.
484  $beforePlatform = $beforeEmpty || !HCU_array_key_exists("platform", $beforeRow) ? array() : (!isset($beforeRow["platform"]) ? array("A","D") : HCU_JsonDecode($beforeRow["platform"]));
485  $afterPlatform = $afterEmpty || !HCU_array_key_exists("platform", $afterRow) ? array() : (!isset($afterRow["platform"]) ? array("A","D") : HCU_JsonDecode($afterRow["platform"]));
486 
487  foreach($beforePlatform as $platformType) {
488  if (!in_array($platformType, array("A","D"))) {
489  throw new exception ("Before platform is invalid.", 7);
490  }
491  }
492 
493  foreach($afterPlatform as $platformType) {
494  if (!in_array($platformType, array("A","D"))) {
495  throw new exception ("After platform is invalid.", 8);
496  }
497  }
498 
499  $whichright = explode("|", $key);
500  $whichright = $whichright[1];
501 
502  $right = HCU_array_key_exists($whichright, $rightRightMap) ? $rightRightMap[$whichright] : false;
503  $account = HCU_array_key_exists("accountnumber", $beforeRow) ? $beforeRow["accountnumber"] : HCU_array_key_value("accountnumber", $afterRow);
504 
505  if ($right === false) {
506  throw new exception ("Right is not valid.", 3);
507  }
508  if ($account === false) {
509  throw new exception ("Account is not valid.", 4);
510  }
511 
512  $altDesc = "<b>" . trim($account) . "</b>: ";
513  if ($whichright == "ACCESS") {
514  $beforeAllowed = HCU_array_key_value("allowed", $beforeRow) == "t"; // Always true for all rights except for the ACCESS right.
515  $afterAllowed = HCU_array_key_value("allowed", $afterRow) == "t";
516  $added = ($beforeEmpty && !$afterEmpty) || (!$beforeAllowed && $afterAllowed);
517  $removed = (!$beforeEmpty && $afterEmpty) || ($beforeAllowed && !$afterAllowed);
518 
519  if ($added) {
520  $altDesc .= "Added $right rights.";
521  } else if ($removed) {
522  $altDesc .= "Removed $right rights.";
523  } else {
524  continue; // No change between ACCESS rights so don't report it.
525  }
526 
527  } else if ($beforeEmpty) {
528  if (!$afterEmpty) {
529  $hasDesktop = in_array("D", $afterPlatform);
530  $hasApps = in_array("A", $afterPlatform);
531  $altDesc .= "Added $right rights" . ($hasDesktop ? ($hasApps ? "." : " for desktop.") : ($hasApps ? " for the apps." : "."));
532  }
533  } else {
534  if ($afterEmpty) {
535  $hasDesktop = in_array("D", $beforePlatform);
536  $hasApps = in_array("A", $beforePlatform);
537  $altDesc .= "Removed $right rights" . ($hasDesktop ? ($hasApps ? "." : " for desktop.") : ($hasApps ? " for the apps." : "."));
538  } else {
539  $hasBDesktop = in_array("D", $beforePlatform);
540  $hasBApps = in_array("A", $beforePlatform);
541  $hasADesktop = in_array("D", $afterPlatform);
542  $hasAApps = in_array("A", $afterPlatform);
543  $fromDesc = $hasBDesktop ? ($hasBApps ? "all" : "desktop") : ($hasBApps ? "apps" : "none");
544  $toDesc = $hasADesktop ? ($hasAApps ? "all" : "desktop") : ($hasAApps ? "apps" : "none");
545  $altDesc .= "Changed $right rights from $fromDesc to $toDesc.";
546  }
547  }
548 
549  $returnedRows[] = array("type" => "desc", "value" => array(), "altDesc" => $altDesc);
550  }
551 
552  $returnArray = array("status" => "000", "error" => "", "returnedRows" => $returnedRows);
553  } catch (exception $e) {
554  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
555  }
556 
557  return $returnArray;
558 }
559 
560 /**
561  * function ParseAuditValueRow($beforeEmpty, $afterEmpty, $afterRow, $beforeRow, $passwordMask, $tableDefinition, $table, $dbh, $Cu)
562  * This function parses the audit as the column level. It is broken up from the original function because there are also custom column descriptions that can happen.
563  *
564  * @param $beforeEmpty -- true if the before snapshot is empty.
565  * @param $afterEmpty -- true if the after snapshot is empty.
566  * @param $afterRow -- This is the table row that was changed in the after snapshot.
567  * @param $beforeRow -- This is the table row that was changed in the before snapshot.
568  * @param $tableDefinition -- this is useful for any column labels.
569  * @param $table -- the table that the audit is done on.
570  * @param $dbh -- the database connection. This is needed for getting the time specific to the CU as well as the mask.
571  * @param $Cu -- the credit union. This is needed for getting the time specific to the CU.
572  * @param $changedOnly -- If true, the function will only return parts of the audit that were changed.
573  *
574  * @return status -- "000" if successful, nonzero if there are errors.
575  * @return error -- blank if successful, nonblank otherwise.
576  * @return combinedRow -- The row for the audit.
577  */
578 function ParseAuditValueRow($beforeEmpty, $afterEmpty, $afterRow, $beforeRow, $tableDefinition, $table, $dbh, $Cu, $changedOnly) {
579 
580  $combinedRow = array();
581 
582  try {
583  if ($beforeEmpty) {
584  if (!$afterEmpty) {
585  if (!isset($afterRow) || !is_array($afterRow)) {
586  throw new exception ("After row is not valid.", 1);
587  }
588  foreach($afterRow as $colName => $colValue) {
589 
590  $colDef = $tableDefinition["_cols"][$colName];
591  if (!isset($colDef)) {
592  throw new exception("Table mismatch.", 17);
593  }
594 
595  $permTable = $table === "cuusers" ? "user" : $table;
596  $visibility = GetAuditColVisibility($colName, $permTable);
597 
598  if ($visibility == "none") {
599  continue;
600  }
601  $restrictVisibility = $visibility == "hidden";
602 
603  $label = isset($colDef["label"]) ? $colDef["label"] : $colName;
604  $results = GetCustomValue($colName, $permTable, null, $colValue, $restrictVisibility, $label, $dbh, $Cu, false);
605 
606  if ($results["isCustom"]) {
607  if ($changedOnly) {
608  foreach($results["customValueArray"] as $row) {
609  if (!$row["same"]) {
610  $combinedRow[] = $row;
611  }
612  }
613  } else {
614  $combinedRow = array_merge($combinedRow, $results["customValueArray"]); // Can be multiple rows in the case of splitting a flagset.
615  }
616 
617  } else {
618 
619  $afterValue = !isset($colValue) || trim($colValue) == "" ? true : $colValue;
620  $same = false;
621 
622  // Empty and null are the same and are set to "--"
623  $beforeValue = "--";
624  $afterValue = $afterValue === true ? "--" : trim($afterValue);
625 
626  // If time or timestamp, then format the output. If timestamp, then remember to also output it in Credit Union time.
627  // NOTE: this doesn't catch the prior login / failed login / current login because that is in a STRING field for some reason.
628  switch($colDef["type"]) {
629  case DBTYPE_DATE:
630  $beforeValue = GetAuditTime($dbh, $beforeValue, $Cu, false, $tz);
631  $afterValue = GetAuditTime($dbh, $afterValue, $Cu, false, $tz);
632  break;
633  case DBTYPE_TIMESTAMPTZ:
634  case DBTYPE_TIMESTAMP:
635  case DBTIMESTAMP_USENOW:
636  $beforeValue = GetAuditTime($dbh, $beforeValue, $Cu, true, $tz);
637  $afterValue = GetAuditTime($dbh, $afterValue, $Cu, true, $tz);
638  break;
639  }
640 
641  // If changed only, only add it if it is changed.
642  if (!$changedOnly || ($changedOnly && !$restrictVisibility)) {
643  $combinedRow[] = array("col" => trim($colName), "before" => $beforeValue, "after" => $afterValue, "label" => trim($label), "same" => $restrictVisibility);
644  }
645 
646  }
647 
648 
649  }
650  }
651  } else {
652  if (!isset($beforeRow) || !is_array($beforeRow)) {
653  throw new exception ("Before row is not valid.", 2);
654  }
655  foreach($beforeRow as $colName => $colValue) {
656  $colDef = $tableDefinition["_cols"][$colName];
657  if (!isset($colDef)) {
658  throw new exception("Table mismatch.", 18);
659  }
660 
661 
662  $permTable = $table === "cuusers" ? "user" : $table;
663  $visibility = GetAuditColVisibility($colName, $permTable);
664  if ($visibility == "none") {
665  continue;
666  }
667  $restrictVisibility = $visibility == "hidden";
668 
669  $beforeValue = trim($colValue) == "" ? "--" : $colValue;
670  if ($afterEmpty) {
671  $afterValue = "";
672  $same = true;
673  } else {
674  if (!HCU_array_key_exists($colName, $afterRow)) {
675  $afterValue = "";
676  $same = true;
677  } else if (trim($afterRow[$colName]) == "") {
678  $afterValue = "--";
679  $same = strcmp($beforeValue, $afterValue) === 0;
680  } else {
681  $afterValue = trim($afterRow[$colName]);
682  $same = strcmp(trim($beforeValue), trim($afterValue)) === 0;
683  }
684  }
685 
686  $label = isset($colDef["label"]) ? $colDef["label"] : $colName;
687  $results = GetCustomValue($colName, $permTable, $colValue, $afterValue, $afterEmpty ? $restrictVisibility : false, $label, $dbh, $Cu, $same);
688 
689  if ($results["isCustom"]) {
690  if ($changedOnly) {
691  foreach($results["customValueArray"] as $row) {
692  if (!$row["same"]) {
693  $combinedRow[] = $row;
694  }
695  }
696  } else {
697  $combinedRow = array_merge($combinedRow, $results["customValueArray"]); // Can be multiple rows in the case of splitting a flagset.
698  }
699  } else {
700 
701  // If time or timestamp, then format the output. If timestamp, then remember to also output it in Credit Union time.
702  // NOTE: this doesn't catch the prior login / failed login / current login because that is in a STRING field for some reason.
703  $tz = null;
704  switch($colDef["type"]) {
705  case DBTYPE_DATE:
706  $beforeValue = GetAuditTime($dbh, $beforeValue, $Cu, false, $tz);
707  $afterValue = GetAuditTime($dbh, $afterValue, $Cu, false, $tz);
708  break;
709  case DBTYPE_TIMESTAMPTZ:
710  case DBTYPE_TIMESTAMP:
711  case DBTIMESTAMP_USENOW:
712  $beforeValue = GetAuditTime($dbh, $beforeValue, $Cu, true, $tz);
713  $afterValue = GetAuditTime($dbh, $afterValue, $Cu, true, $tz);
714  break;
715  }
716 
717  $show = $visibility == "always" ? true : ($afterEmpty ? !$restrictVisibility : !$same);
718  if (!$changedOnly || ($changedOnly && $show)) {
719  $combinedRow[] = array("col" => trim($colName), "before" => $beforeValue, "after" => $afterValue, "label" => trim($label), "same" => !$show);
720  }
721  }
722  }
723  }
724 
725  $returnArray = array("status" => "000", "error" => "", "combinedRow" => $combinedRow);
726  } catch (exception $e) {
727  $returnArray = array("status" => $e->getCode(), "error" => $e->getMessage());
728  }
729 
730  return $returnArray;
731 }
732 
733 /**
734  * function GetAuditColVisibility($col, $table)
735  * Determines the visibility of a column.
736  * Values are:
737  * "hidden" -- On an add, remove or changed audit, it doesn't show up. (It is technically a "changed" value.) You can view it by clicking on the "Show All" button.
738  * "none" -- Column doesn't get returned. You cannot see it with either the normal or "show all" modes.
739  * "visible" -- Default. Column will be visible on an add or remove audit. For a changed audit, it will be visible when there is a change.
740  *
741  * @param $col -- the column to check
742  * @param $table -- the table to check
743  *
744  * @return "hidden", "none", or "visible" depending on the visibility of the column.
745  */
746 function GetAuditColVisibility($col, $table) {
747  $key = "$table | $col";
748  $visibility = "visible";
749  switch($key) {
750  case "user | group_id":
751  case "user | contact":
752  case "user | user_id":
753  case "user | primary_account":
754  case "user | challenge_quest_id": // Only from profile require.
755  case "user | schedule_code":
756  case "user | pkattempt":
757  case "user | is_group_primary":
758  case "useraccounts | display_order":
759  case "useraccounts | user_id":
760  case "useraccounts | display_qty":
761  case "useraccounts | display_qty_type":
762  case "useraccount | display_order": // Typo also is in use.
763  case "useraccount | user_id":
764  case "useraccount | display_qty":
765  case "useraccount | display_qty_type":
766  case "memberacct | primary_user":
767  case "memberacct | balance_stamp":
768  case "memberacct | balance_attempt":
769  case "memberacct | allowenroll":
770  case "userrights | user_id":
771  case "usercontact | contact_id":
772  $visibility = "none";
773  break;
774  case "user | user_name":
775  case "user | email":
776  case "useraccounts | accountnumber":
777  case "useraccounts | accounttype":
778  case "useraccounts | display_name":
779  case "useraccounts | certnumber":
780  case "useraccount | accountnumber": // Typo also is in use.
781  case "useraccount | accounttype":
782  case "useraccount | display_name":
783  case "useraccount | certnumber":
784  case "alert | accountnumber":
785  case "alert | accounttype":
786  case "alert | certnumber":
787  case "alert | emailtype":
788  case "alert | notifyto":
789  case "alert | notifymsg":
790  case "alert | lastalert":
791  case "alert | alertstatus":
792  $visibility = "visible";
793  break;
794  case "userrights | feature_code":
795  $visibility = "always";
796  break;
797  case substr($key, 0, strlen("alert | ")) == "alert | ":
798  $visibility = "none";
799  break;
800  case substr($key, 0, strlen("user | ")) == "user | ":
801  case substr($key, 0, strlen("useraccounts | ")) == "useraccounts | ":
802  case substr($key, 0, strlen("useraccount | ")) == "useraccount | ":
803  case substr($key, 0, strlen("userrights | ")) == "userrights | ":
804  $visibility = "hidden";
805  break;
806  default:
807  $visibility = "visible"; // By default, don't restrict this value.
808  break;
809  }
810  return $visibility;
811 }
812 
813 /**
814  * function GetCustomValue($colName, $table, $beforeValue, $afterValue, $restrictVisibility, $label, $dbh, $Cu)
815  * Gets the custom row for particular columns. This is useful for flagset fields where the meaningful change is a bit.
816  *
817  * @param $colName -- the column
818  * @param $table -- the table
819  * @param $beforeValue -- the value in the before snapshot for the column
820  * @param $afterValue -- the value in the after snapshot for the column
821  * @param $restrictVisibility -- if true, column will be hidden at the start. Otherwise it will be visible at the start if there was a change in the column.
822  * @param $label -- the label of the column
823  * @param $dbh -- the database connection. This is needed for getting the time specific to the CU as well as the mask.
824  * @param $Cu -- the credit union. This is needed for getting the time specific to the CU.
825  *
826  * @return isCustom -- if there is a custom row for the column then it is true. False otherwise.
827  * @return customValueArray -- records to append to the audit for the table.
828  */
829 function GetCustomValue($colName, $table, $beforeValue, $afterValue, $restrictVisibility, $label, $dbh, $Cu, $same) {
830  $key = "$table | $colName";
831  $isCustom = true;
832  $customValueArray = array();
833  $beforeEmpty = !isset($beforeValue) || $beforeValue == "--" || $beforeValue == "";
834  $afterEmpty = !isset($afterValue) || $afterValue == "--" || $afterValue == "";
835 
836  switch($key) {
837  case "user | userflags":
838  $before = $beforeEmpty ? "" : (GetUserFlagsValue("MEM_FORCE_RESET") & intval($beforeValue) == 0 ? "N" : "Y");
839  $after = $afterEmpty ? "" : (GetUserFlagsValue("MEM_FORCE_RESET") & intval($afterValue) == 0 ? "N" : "Y");
840  $show = $restrictVisibility ? false : !$afterEmpty && $before != $after;
841  $customValueArray[] = array("label" => "Force Reset", "before" => $before, "after" => $after, "same" => !$show);
842  $before = $beforeEmpty ? "" : (GetUserFlagsValue("MEM_ASKBPAY") & intval($beforeValue) == 0 ? "N" : "Y");
843  $after = $afterEmpty ? "" : (GetUserFlagsValue("MEM_ASKBPAY") & intval($afterValue) == 0 ? "N" : "Y");
844  $show = $restrictVisibility ? false : !$afterEmpty && $before != $after;
845  $customValueArray[] = array("label" => "Ask BillPay", "before" => $before, "after" => $after, "same" => !$show);
846  break;
847  case "useraccount | recordtype":
848  case "useraccounts | recordtype":
849  $beforeValue = in_array($beforeValue, array("D", "T")) ? "Deposit" : (in_array($beforeValue, array("L", "P")) ? "Loan" : $beforeValue);
850  $afterValue = in_array($afterValue, array("D", "T")) ? "Deposit" : (in_array($afterValue, array("L", "P")) ? "Loan" : $afterValue);
851  $show = $restrictVisibility ? false : !$afterEmpty && $beforeValue != $afterValue;
852  $customValueArray[] = array("col" => trim($colName), "before" => $beforeValue, "after" => $afterValue, "label" => trim($label), "same" => !$show);
853  break;
854  case "user | mfaquest":
855  $before = $beforeEmpty ? array() : HCU_JsonDecode($beforeValue);
856  $after = $afterEmpty ? array() : HCU_JsonDecode($afterValue);
857 
858  $beforeAnswerArray = HCU_array_key_value("answers", $before);
859  $afterAnswerArray = HCU_array_key_value("answers", $after);
860  $challengeChange = array();
861  if ($beforeAnswerArray !== false || $afterAnswerArray !== false) {
862  if ($beforeAnswerArray !== false) {
863  foreach($beforeAnswerArray as $key => $value) {
864  $challengeChange [$key] = array("before" => $value);
865  }
866  }
867 
868  if ($afterAnswerArray !== false) {
869  foreach($afterAnswerArray as $key => $value) {
870  if (!HCU_array_key_exists($key, $challengeChange)) {
871  $challengeChange [$key] = array("after" => $value);
872  } else {
873  $challengeChange [$key]["after"] = $value;
874  }
875  }
876  }
877 
878  // I know that it is set because I control the variable.
879  foreach($challengeChange as $key => $row) {
880  if (!HCU_array_key_exists("before", $row)) {
881  $challengeChange [$key] ["before"] = "";
882  } else if (!isset($row["before"]) || trim($row["before"]) == "") {
883  $challengeChange [$key] ["before"] = "--";
884  }
885 
886  if (!HCU_array_key_exists("after", $row)) {
887  $challengeChange [$key] ["after"] = "";
888  } else if (!isset($row["after"]) || trim($row["after"]) == "") {
889  $challengeChange [$key] ["after"] = "--";
890  }
891 
892  $challengeChange [$key] ["label"] = "Challenge Question #$key";
893  $show = $restrictVisibility ? false : !$afterEmpty && $challengeChange [$key] ["before"] != $challengeChange [$key] ["after"];
894  $challengeChange [$key] ["same"] = !$show;
895  }
896  }
897 
898  $beforeMFADate = HCU_array_key_value("mfadate", $before);
899  $afterMFADate = HCU_array_key_value("mfadate", $after);
900  $tz = null;
901  $dateArray = null;
902  if ($beforeMFADate !== false || $afterMFADate !== false) {
903 
904  if ($beforeMFADate !== false) {
905  if (!isset($beforeMFADate) || trim($beforeMFADate) == "") {
906  $beforeTime = "--";
907  } else if (intval($beforeMFADate) == 0) {
908  $beforeTime = "";
909  } else {
910  $beforeTime = GetAuditTime($dbh, $beforeMFADate, $Cu, true, $tz);
911  }
912 
913  $dateArray = array("before" => $beforeTime);
914  }
915 
916  if ($afterMFADate !== false) {
917  if (!isset($afterMFADate) || trim($afterMFADate) == "") {
918  $afterTime = "--";
919  } else if (intval($afterMFADate) == 0) {
920  $afterTime = "";
921  } else {
922  $afterTime = GetAuditTime($dbh, $afterMFADate, $Cu, true, $tz);
923  }
924 
925  if (isset($dateArray)) {
926  $dateArray["after"] = $afterTime;
927  } else {
928  $dateArray = array("after" => $afterTime);
929  }
930  }
931 
932  if (!HCU_array_key_exists("before", $dateArray)) {
933  $dateArray["before"] = "";
934  }
935 
936  if (!HCU_array_key_exists("after", $dateArray)) {
937  $dateArray["after"] = "";
938  }
939 
940  $dateArray["label"] = "MFA Date";
941  $show = $restrictVisibility ? false : !$afterEmpty && $dateArray["before"] != $dateArray["after"];
942  $dateArray["same"] = !$show;
943  }
944 
945  if (count($challengeChange) > 0) {
946  $customValueArray = array_values($challengeChange);
947  }
948  if (isset($dateArray)) {
949  $customValueArray[] = $dateArray;
950  }
951  break;
952  case "usercontact | phones":
953  $before = $beforeEmpty ? array() : HCU_JsonDecode($beforeValue);
954  $after = $afterEmpty ? array() : HCU_JsonDecode($afterValue);
955  $phoneArray = array();
956  if ($before !== false && HCU_array_key_exists("mobile", $before) && is_array($before["mobile"])) {
957  foreach($before["mobile"] as $phone) {
958  $key = preg_replace('/\D+/', '', $phone);
959  if (!HCU_array_key_exists($key, $phoneArray)) {
960  $phoneArray[$key] = array("before" => $phone);
961  }
962  }
963  }
964  if ($after !== false && HCU_array_key_exists("mobile", $after) && is_array($after["mobile"])) {
965  foreach($after["mobile"] as $phone) {
966  $key = preg_replace('/\D+/', '', $phone);
967  if (!HCU_array_key_exists($key, $phoneArray)) {
968  $phoneArray[$key] = array("after" => $phone);
969  } else {
970  $phoneArray[$key]["after"] = $phone;
971  }
972  }
973  }
974 
975  $index = 1;
976  foreach($phoneArray as $key => $row) {
977  $phoneArray[$key]["label"] = "Phone #" . $index++;
978  if (!isset($row["before"])) {
979  $phoneArray[$key]["before"] = "--";
980  }
981 
982  if (!isset($row["after"])) {
983  $phoneArray[$key]["after"] = "--";
984  }
985  $show = $restrictVisibility ? false : !$afterEmpty && $phoneArray[$key]["before"] != $phoneArray[$key]["after"];
986  $phoneArray[$key]["same"] = !$show;
987  }
988  $customValueArray = array_values($phoneArray);
989  break;
990  case "user | passwd":
991  $passwordMask = "********";
992  $show = $restrictVisibility ? false : !$same;
993  $before = $beforeValue == "--" ? "--" : $passwordMask;
994  $after = $afterValue == "--" ? "--" : $passwordMask;
995 
996  $customValueArray[] = array("col" => trim($colName), "before" => $before, "after" => $after, "label" => trim($label), "same" => !$show);
997  break;
998  case "user | msg_tx":
999  $before = $beforeEmpty ? 0 : GetMsgTxValue("MSGTX_FORCE_EM") & intval($beforeValue);
1000  $after = $afterEmpty ? 0 : GetMsgTxValue("MSGTX_FORCE_EM") & intval($afterValue);
1001  $before = $before === 0 ? "N" : "Y";
1002  $after = $after === 0 ? "N" : "Y";
1003  $show = $restrictVisibility ? false : !$afterEmpty && $before != $after;
1004  $customValueArray[] = array("label" => "Force Email", "before" => $before, "after" => $after, "same" => !$show);
1005  break;
1006  case "alert | alerttype":
1007 
1008  switch ($beforeValue) {
1009  case "B":
1010  $before = "Balance";
1011  break;
1012  case "T":
1013  $before = "Transaction";
1014  break;
1015  case "L":
1016  $before = "Loan";
1017  break;
1018  case "C":
1019  $before = "Check";
1020  break;
1021  default:
1022  $before = $beforeValue;
1023  break;
1024  }
1025  switch ($afterValue) {
1026  case "B":
1027  $after = "Balance";
1028  break;
1029  case "T":
1030  $after = "Transaction";
1031  break;
1032  case "L":
1033  $after = "Loan";
1034  break;
1035  case "C":
1036  $after = "Check";
1037  break;
1038  default:
1039  $after = $afterValue;
1040  break;
1041  }
1042  $show = $restrictVisibility ? false : !$same;
1043  $customValueArray[] = array("col" => trim($colName), "before" => $before, "after" => $after, "label" => trim($label), "same" => !$show);
1044  break;
1045  case "alert | emailtype":
1046  switch ($beforeValue) {
1047  case "E":
1048  $before = "Email";
1049  break;
1050  case "W":
1051  $before = "Phone";
1052  break;
1053  default:
1054  $before = $beforeValue;
1055  break;
1056  }
1057  switch ($afterValue) {
1058  case "E":
1059  $after = "Email";
1060  break;
1061  case "W":
1062  $after = "Phone";
1063  break;
1064  default:
1065  $after = $afterValue;
1066  break;
1067  }
1068  $show = $restrictVisibility ? false : !$same;
1069  $customValueArray[] = array("col" => trim($colName), "before" => $before, "after" => $after, "label" => trim($label), "same" => !$show);
1070  break;
1071  // Booleans using 0 or 1.
1072  case "alert | alertstatus":
1073  switch ($beforeValue) {
1074  case "0":
1075  $before = "N";
1076  break;
1077  case "1":
1078  $before = "Y";
1079  break;
1080  default:
1081  $before = $beforeValue;
1082  break;
1083  }
1084  switch ($afterValue) {
1085  case "0":
1086  $after = "N";
1087  break;
1088  case "1":
1089  $after = "Y";
1090  break;
1091  default:
1092  $after = $afterValue;
1093  break;
1094  }
1095  $show = $restrictVisibility ? false : !$same;
1096  $customValueArray[] = array("col" => trim($colName), "before" => $before, "after" => $after, "label" => trim($label), "same" => !$show);
1097  break;
1098  // Booleans using "t" or "f"
1099  case "useraccount | view_balances":
1100  case "useraccount | view_transactions":
1101  case "useraccount | int_deposit":
1102  case "useraccount | int_withdraw":
1103  case "useraccount | ext_deposit":
1104  case "useraccount | ext_withdraw":
1105  case "useraccounts | view_balances":
1106  case "useraccounts | view_transactions":
1107  case "useraccounts | int_deposit":
1108  case "useraccounts | int_withdraw":
1109  case "useraccounts | ext_deposit":
1110  case "useraccounts | ext_withdraw":
1111  switch ($beforeValue) {
1112  case "t":
1113  $before = "Y";
1114  break;
1115  case "f":
1116  $before = "N";
1117  break;
1118  default:
1119  $before = $beforeValue;
1120  break;
1121  }
1122  switch ($afterValue) {
1123  case "t":
1124  $after = "Y";
1125  break;
1126  case "f":
1127  $after = "N";
1128  break;
1129  default:
1130  $after = $afterValue;
1131  break;
1132  }
1133  $show = $restrictVisibility ? false : !$same;
1134  $customValueArray[] = array("col" => trim($colName), "before" => $before, "after" => $after, "label" => trim($label), "same" => !$show);
1135  break;
1136  default:
1137  $isCustom = false;
1138  $customValueArray = array();
1139  break;
1140  }
1141 
1142  return array("isCustom" => $isCustom, "customValueArray" => $customValueArray);
1143 }
1144 
1145 /**
1146  * function GetAuditTime($dbh, $value, $Cu, $setTimezone, &$tz)
1147  * Gets the time for timezone fields in the audit. If it is not set then --.
1148  *
1149  * @param $dbh -- the database connection
1150  * @param $value -- the raw database version of the timestamp.
1151  * @param $Cu -- the credit union
1152  * @param $setTimezone -- If true, then the data is a timestamp. The date part is shown and it is set to the credit union's time. Otherwise, it shows just the date.
1153  * @param $tz -- handle to the timezone object.
1154  *
1155  * @return string of date/time in credit union time (if timestamp); otherwise, it is a string of the date.
1156  */
1157 function GetAuditTime($dbh, $value, $Cu, $setTimezone, &$tz) {
1158  if ($value == "" || $value == "--") {
1159  return $value;
1160  }
1161  if ($setTimezone && !isset($tz)) {
1162  $tz = GetCreditUnionTimezone($dbh, $Cu);
1163  }
1164 
1165  // Numbers are treated as UTC.
1166  if(is_numeric($value)) {
1167  $value = "@" . intval($value);
1168  }
1169  $dateTime = new DateTime($value);
1170  // If it is a type of "date" in the database, timezone is set to zero so display shows as the previous day.
1171  // If it is a type of "timestamp" in the database, then we do need to adjust.
1172  if ($setTimezone && isset($value)) {
1173  $dateTime->setTimezone(new DateTimeZone($tz));
1174  }
1175 
1176  $format = $setTimezone ? "n/j/Y g:i A" : "n/j/Y";
1177 
1178  return $dateTime->format($format);
1179 }