Odyssey
contMaint.prg
1 <?php
2 /**
3  * @package welcome.prg -- Shows survey questions and messages from HomeCU as well as after hour support and special email notification for the CU.
4  * This replaces welcome.prg, cu_notify.prg, ContMaint.prg
5  */
6 $main= $menu_link;
7 $self= "$menu_link?ft=$ft";
8 
9 $parameters= array("a" => array("gridData" => ""));
10 $string= array("filter" => FILTER_DEFAULT);
11 HCU_ImportVars($parameters, "a", array("gridData" => $string));
12 extract($parameters["a"]); // dms_import doesn't work anymore because this isn't in global scope anymore.
13 $showSQL= $SYSENV["devmode"];
14 
15 if ($operation != "")
16 {
17  switch($operation)
18  {
19  case "readAfterHours":
20  $sqls= array();
21  $isValid= true;
22  try
23  {
24  $records= readAfterHours($dbh, $sqls, $Cu, true);
25  }
26  catch(exception $e)
27  {
28  $returnArray= array("error" => array($e->getMessage()), "sql" => $sqls, "record" => array());
29  $isValid= false;
30  }
31  if ($isValid)
32  $returnArray= array("error" => array(), "sql" => $sqls, "record" => $records);
33  break;
34  case "saveAfterHours":
35  $returnArray= saveAfterHours($dbh, $Cu, $Cn, $gridData);
36  break;
37  default: $returnArray= array("sql" => array(), "error" => array("Operation not recognized: $operation."), "record" => array());
38  break;
39  }
40 
41  header('Content-type: application/json');
42  if (!$showSQL)
43  unset($returnArray["sql"]);
44  print json_encode($returnArray);
45 }
46 else
47 {
48  printAfterHoursPage($self);
49 }
50 
51 /**
52  * function saveAfterHours($dbh, $Cu, $Cn)
53  * Saves CU contacts
54  *
55  * @param integer $dbh -- the database connection
56  * @param string $Cu -- the credit union
57  * @param string $Cn -- the logged in user
58  * @return array --
59  * $error -- one or zero errors encountered
60  * $sql -- the SQLs used
61  * $returnData -- the new data for the grid. A requery is needed to capture the commentid for any added records.
62  */
63 function saveAfterHours($dbh, $Cu, $Cn, $gridData)
64 {
65  $sqls= array();
66  try
67  {
68  if ($gridData == "")
69  throw new exception("Grid Data is required!", 1);
70  $gridData= HCU_JsonDecode($gridData);
71  if (!is_array($gridData))
72  throw new exception("Grid Data is not correct!", 2);
73 
74  $createArray= array();
75  $updateArray= array();
76  $deleteArray= array();
77  $priorityMap= array();
78  foreach($gridData as $record)
79  {
80  if (!isset($record["contactid"]) || !isset($record["priority"]) || !isset($record["name"]) || !isset($record["phone"]) || !isset($record["comments"]))
81  throw new exception("gridData is not correct!", 3);
82  if (isset($record["removed"]) && $record["removed"])
83  {
84  if ($record["contactid"] == 0)
85  continue; // Do nothing to this record. It was added and then removed from the grid.
86  $deleteArray[]= array("_action" => "delete", "contactid" => $record["contactid"], "user_name" => strtolower($Cu));
87  }
88  else
89  {
90  $priority= intval($record["priority"]);
91  if (isset($priorityMap[$priority]))
92  throw new exception("Priority needs to be unique!", 7);
93  $priorityMap[$priority]= true;
94 
95  $phone= trim($record["phone"]);
96  if (!preg_match('/^\([0-9]{3}\) [0-9]{3}-[0-9]{4}$/', $phone))
97  throw new exception("Phone is not formatted correctly!", 8);
98 
99  $thisRecord= array("contactid" => $record["contactid"], "priority" => $priority, "name" => $record["name"], "phone" => $phone,
100  "comments" => $record["comments"], "user_name" => strtolower($Cu));
101  if ($record["contactid"] == 0)
102  {
103  $thisRecord["_action"]= "insert";
104  $createArray[]= $thisRecord;
105  }
106  else
107  {
108  $thisRecord["_action"]= "update";
109  $updateArray[]= $thisRecord;
110  }
111  }
112  }
113 
114  $sql= "select email from cuadminusers where user_name= '$Cn' and cu= '$Cu'";
115  $sqls[]= $sql;
116  $sth = db_query($sql, $dbh);
117  if (!$sth)
118  throw new exception("Email query failed!", 6);
119  $email= db_fetch_row($sth)[0];
120 
121  // Delete and update needs to be first so unique index doesn't fail.
122  if (count($deleteArray) > 0 && DataAdminTableUpdate($dbh, array("cu" => $Cu), array("cucontact" => $deleteArray), $Cn,
123  "CCON_DEL", "notificationMaintenance.prg", "A", "Contact removed", $Cn, $email, trim($_SERVER["REMOTE_ADDR"])) === false)
124  throw new exception("Delete audit function failed!", 5);
125  if (count($updateArray) > 0 && DataAdminTableUpdate($dbh, array("cu" => $Cu), array("cucontact" => $updateArray), $Cn,
126  "CCON_UPD", "notificationMaintenance.prg", "A", "Contact updated", $Cn, $email, trim($_SERVER["REMOTE_ADDR"])) === false)
127  throw new exception("Update audit function failed!", 4);
128  if (count($createArray) > 0 && DataAdminTableUpdate($dbh, array("cu" => $Cu), array("cucontact" => $createArray), $Cn,
129  "CCON_ADD", "notificationMaintenance.prg", "A", "Contact added", $Cn, $email, trim($_SERVER["REMOTE_ADDR"])) === false)
130  throw new exception("Create audit function failed!", 3);
131 
132  $returnData= readAfterHours($dbh, $sqls, $Cu, true);
133  }
134  catch(exception $e)
135  {
136  return array("error" => array($e->getMessage()), "sql" => $sqls, "returnData" => array());
137  }
138  return array("error" => array(), "sql" => $sqls, "returnData" => $returnData);
139 }
140 
141 /**
142  * function readAfterHours($dbh, &$sqls, $Cu, $editable)
143  * Gets the after hour support data
144  *
145  * @param integer $dbh -- the database connection
146  * @param array $sqls -- the array to append the SQLs to
147  * @param string $Cu -- the credit union
148  * @param boolean $editable -- if true, we need an index apparently
149  * @return array -- the data for the after hours support grid
150  * @throws "phone is invalid" or "After Hours Support query failed!"
151  */
152 function readAfterHours($dbh, &$sqls, $Cu, $editable)
153 {
154  $sql= "select contactid, priority, name, phone, comments from cucontact where user_name = '" . strtolower($Cu) . "' order by user_name, priority";
155  $sqls[]= $sql;
156  $sth= db_query($sql, $dbh);
157  if (!$sth)
158  throw new exception("After Hours Support query failed!", 3);
159  $afterHoursSupport= array();
160  for($i=0; $row= db_fetch_assoc($sth, $i); $i++)
161  {
162  $row["phone"]= preg_replace('/[^0-9]*/', "", $row["phone"]);
163  if ($editable)
164  $row["pid"]= $i+1;
165  $length= strlen($row["phone"]);
166  $row["phone"]= $length == 10 ? "(" . substr($row["phone"], 0, 3) . ") " . substr($row["phone"], 3, 3) . "-" . substr($row["phone"], 6)
167  : ($length == 7 ? substr($row["phone"], 0, 3) . "-" . substr($row["phone"], 3) : "");
168 
169  if (trim($row["phone"]) == "")
170  throw new exception("Phone is invalid.", 4);
171  $row["name"]= trim($row["name"]);
172  $row["comments"]= trim($row["comments"]);
173  $afterHoursSupport[]= $row;
174  }
175  return $afterHoursSupport;
176 }
177 
178 /**
179  * function printAfterHoursPage($self)
180  * This prints out the after hours page.
181  *
182  * @param string $self -- the URL to this script.
183  */
184 function printAfterHoursPage($self)
185 { ?>
186  <style>
187  <?php printTopCenterCss(200); ?>
188  </style>
189  <script type="text/javascript">
190  <?php // Library javascript functions
191  getShowWaitFunctions(); ?>
192  var nextPriority= 1;
193  var nextPid= 1;
194 
195  <?php
196  /**
197  * function init()
198  * This initializes the kendoGrid for the after hours page. It is an incell-edited grid. The priority DDL is a bit noncommon.
199  */
200  ?>
201  function init()
202  {
203  var grid= $("#grid").kendoGrid({
204  dataSource: {
205  transport: {
206  read: {
207  type: "POST",
208  dataType: "json",
209  url: "<?php echo $self; ?>&operation=readAfterHours"
210  },
211  parameterMap: function(data, type)
212  {
213  showWaitWindow();
214  return data;
215  }
216  },
217  schema: {
218  model: {
219  id: "pid",
220  fields: {
221  contactid: {type: "number"},
222  pid: {type: "number", defaultValue: function() { return nextPid++; }},
223  priority: {type: "number", defaultValue: function() { return nextPriority++; }},
224  name: {type: "string"},
225  phone: {type: "string", validation: {phone: function(input) {
226  if (!$(input).is("[name='phone']"))
227  return true;
228  if (!$(input).val().trim().match(/^\([0-9]{3}\) [0-9]{3}-[0-9]{4}$/))
229  {
230  $(input).attr("data-phone-msg", "Phone Number is invalid!");
231  return false;
232  }
233  return true;
234  }}},
235  comments: {type: "string"},
236  removed: {type: "boolean", defaultValue: false},
237  changed: {type: "boolean", defaultValue: true}
238  }
239  },
240  parse: function(data)
241  {
242  hideWaitWindow();
243 
244  if (data.error.length > 0)
245  $.homecuValidator.displayMessage(data.error, $.homecuValidator.settings.statusError );
246  else if (data.record.length > 0)
247  {
248  var lastRecord= data.record[data.record.length-1];
249  nextPriority= Number(lastRecord.priority);
250  nextPid= Number(lastRecord.pid);
251  }
252 
253  return data.record;
254  }
255  },
256  sort: {field: "priority", dir: "asc"},
257  filter: {field: "removed", operator: "neq", value: true}
258  },
259  editable: {
260  mode: "incell",
261  createAt: "bottom"
262  },
263  toolbar: ["create"],
264  columns: [
265  {command: [{name: "custRemove", text: "Delete"}]},
266  {field: "priority", title: "Priority", editor: function(container, options) {
267  var oldPriority= options.model.priority;
268  var oldPid= options.model.pid;
269  var gridData= $("#grid").data("kendoGrid").dataSource.data();
270  var ddlOptions= [];
271 
272  <?php
273  /* This is creating the DDL for the priority edit. It is going to look like (if there are 5 records and the middle one is being changed):
274  * Keep Priority
275  * Move Top
276  * Move Bottom
277  * Move up 1
278  * Move up 2
279  * Move down 1
280  * Move down 2
281  */
282  ?>
283  for(var i=0; i!= gridData.length; i++)
284  {
285  if (gridData[i].removed)
286  continue;
287  var newPriority= gridData[i].priority;
288  var newPid= gridData[i].pid;
289  var difference= newPriority - oldPriority;
290  if (newPriority < oldPriority)
291  ddlOptions.push({text: "Move up " + (-difference), value: newPriority, pid: newPid});
292  else if (newPriority > oldPriority)
293  ddlOptions.push({text: "Move down " + difference, value: newPriority, pid: newPid});
294  }
295 
296  <?php /* Sorts like the comment above.*/ ?>
297  ddlOptions.sort(function(a, b) {
298  return (a.value < 0 ? (b.value < 0 ? b.value - a.value : 1) : (b.value >= 0 ? a.value - b.value : -1));
299  });
300  if (oldPriority != gridData.length)
301  ddlOptions.unshift({text: "Move Bottom", value: gridData.length});
302  if (oldPriority != 1)
303  ddlOptions.unshift({text: "Move Top", value: 1});
304  ddlOptions.unshift({text: "Keep Priority", value: -1});
305 
306  $("<div class='priorityDDL'></div>").appendTo(container).kendoDropDownList({
307  dataSource: {
308  data: ddlOptions
309  },
310  dataTextField: "text",
311  dataValueField: "value",
312  change: function()
313  {
314  var dataItem= this.dataItem();
315  if (dataItem.value == -1)
316  return;
317 
318  <?php
319  /* This is changing the whole grid based on changing the priority. Through experimentation, I found that the priorities will change between the oldPriority
320  and the newPriority. This is only going to be one more or one less depending on if the newPriority is higher or lower than the oldPriority. Show this as changed.
321  */
322  ?>
323  for(var i=0; i!= gridData.length; i++)
324  {
325  if (gridData[i].removed)
326  continue;
327  if (oldPid == gridData[i].pid)
328  {
329  gridData[i].priority= dataItem.value;
330  gridData[i].changed= true;
331  }
332  else if (dataItem.value <= gridData[i].priority && oldPriority >= gridData[i].priority)
333  {
334  gridData[i].priority++;
335  gridData[i].changed= true;
336  }
337  else if (oldPriority <= gridData[i].priority && dataItem.value >= gridData[i].priority)
338  {
339  gridData[i].priority--;
340  gridData[i].changed= true;
341  }
342  }
343  $("#grid").data("kendoGrid").dataSource.data(gridData);
344  }
345  }).data("kendoDropDownList");
346  }},
347  {field: "name", title: "Name", editor: function(container, options) {
348  var input= $("<input type='text' class='k-input k-textbox' name='" + options.field + "' required data-required-msg='Name is required!' maxlength=35>").appendTo(container);
349  }},
350  {field: "phone", title: "Phone", editor: function(container, options) {
351  var input= $("<input name='" + options.field + "'>").appendTo(container).kendoMaskedTextBox({
352  mask: "(000) 000-0000"
353  }).data("kendoMaskedTextBox")
354  }},
355  {field: "comments", title: "Comments", width: "40%", editor: function(container, options) {
356  var input= $("<input type='text' class='k-input k-textbox' name='" + options.field + "' maxlength=255>").appendTo(container);
357  }}
358  ]
359  }).data("kendoGrid");
360 
361  <?php
362  /* This is creating a delete dialog when the delete button is clicked. On a "yes" click, the record IS NOT removed from the grid. Instead there is a removed boolean that is set to true.
363  This is necessary because the record won't actually be deleted until the save button is clicked. */
364  ?>
365  $("#grid").on("click", ".k-grid-custRemove", function() {
366  $("#formInfoDiv").hide();
367  var deleteDialog= $("#deleteDialog").data("kendoWindow");
368  if (deleteDialog == null)
369  {
370  var template= "<div>\
371  <div>\
372  Are you sure you want to delete this record?\
373  </div>\
374  <div>\
375  <a href='\\#' class='k-button okBtn'>Yes</a>\
376  <a href='\\#' class='k-button cancelBtn'>No</a>\
377  </div>\
378  </div>";
379 
380  deleteDialog= $("<div id='deleteDialog' style='text-align:left;'></div>").appendTo("body").kendoWindow({
381  content: {
382  template: template
383  },
384  modal: true,
385  title: "Delete?",
386  visible: false,
387  resizable: false,
388  width: 400,
389  close: function() {
390  activeWindows.pop();
391  }
392  }).data("kendoWindow");
393 
394  <?php /* On this button click, the record is filtered out of the grid. Now, any records with a priority lower (well, higher in number) are reduced by one.*/ ?>
395  $("#deleteDialog .okBtn").click(function() {
396  var grid= $("#grid").data("kendoGrid");
397 
398  var gridData= grid.dataSource.data();
399  var dataItem= $("#deleteDialog").data("dataItem");
400  dataItem.removed= true;
401  dataItem.changed= true;
402 
403  for(var i=0; i!= gridData.length; i++)
404  {
405  if (gridData[i].pid == dataItem.pid || gridData[i].removed)
406  continue;
407  if (gridData[i].priority >= dataItem.priority)
408  {
409  gridData[i].changed= true;
410  gridData[i].priority--;
411  }
412 
413  }
414  nextPriority--;
415  grid.dataSource.data(gridData);
416  deleteDialog.close();
417  return false;
418  });
419 
420  $("#deleteDialog .cancelBtn").click(function() {
421  deleteDialog.close();
422  return false;
423  });
424  }
425 
426  var grid= $("#grid").data("kendoGrid");
427  $("#deleteDialog").data("dataItem", grid.dataItem($(this).closest("tr")));
428  deleteDialog.open();
429  activeWindows.push(deleteDialog);
430  return false;
431  });
432 
433  <?php // Adds the save button click event. The grid data is filtered by changed == true. This is stringified and then set to the save call. The save call returns the updated data. ?>
434  $("#savePageBtn").click(function() {
435  $("#formInfoDiv").hide();
436 
437  var changedData= [];
438  var data= grid.dataSource.data();
439  for(var i=0; i!=data.length; i++)
440  {
441  if (data[i].changed)
442  changedData.push({contactid: data[i].contactid, priority: Number(data[i].priority), name: data[i].name, phone: data[i].phone, comments: data[i].comments,
443  removed: data[i].removed});
444  }
445 
446  if (changedData.length > 0)
447  {
448  showWaitWindow();
449  $.post("<?php echo $self; ?>&operation=saveAfterHours", {gridData: kendo.stringify(changedData)}, function(data) {
450  hideWaitWindow();
451  if (data.error.length > 0)
452  $.homecuValidator.displayMessage(data.error, $.homecuValidator.settings.statusError );
453  else
454  {
455  $.homecuValidator.displayMessage("Changes were saved successfully!", $.homecuValidator.settings.statusInfo );
456 
457  $("#grid").data("kendoGrid").dataSource.data(data.returnData);
458  }
459  });
460  }
461 
462  return false;
463  });
464  }
465 
466  var activeWindows= [];
467  $(document).ready(function() {
468  init();
469  <?php printClickOverlayEvent(); ?>
470  });
471  </script>
472  <div class="container-fluid">
473  <div id="formValidateDiv" class="k-block k-error-colored formValidateDiv" style="display:none;"></div>
474  <div class="form-horizontal form-widgets">
475  <div id="grid" style="margin-bottom: 5px;"></div>
476  <a href="#" id="savePageBtn" class="k-button">Save Changes</a>
477  </div>
478  </div>
479 <?php }