Odyssey
aMemberMicrs.prg
1 <?php
2 /**
3  * @package MemberHub
4  * @author MGHandy
5  *
6  * @uses admin member micr: edit mirc valus for member accounts
7  *
8  * @param operation string: requested operation for this script
9  * @param payload string: encryption for the selected member
10  * @param mMicrs string: list of micr values to create/update/delete
11  */
12 
13 require_once("$admLibrary/aMemberSupport.i");
14 
15 /**
16  * function GetMicrSize()
17  * This was 9 and is changed to 17.
18  */
19 function GetMicrSize() {
20  return 17;
21 }
22 
23 try {
24  $admVars = array();
25  $admOk = array(
26  "operation" => array("filter" => FILTER_SANITIZE_STRING),
27  "payload" => array("filter" => FILTER_SANITIZE_STRING),
28  "mMicrs" => array("filter" => FILTER_SANITIZE_STRING)
29  );
30 
31  HCU_ImportVars($admVars, "MEMBER_MICR", $admOk);
32 
33  $mOperation = isset($admVars["MEMBER_MICR"]["operation"]) ? $admVars["MEMBER_MICR"]["operation"] : null;
34  $mPayload = isset($admVars["MEMBER_MICR"]["payload"]) ? $admVars["MEMBER_MICR"]["payload"] : null;
35  $mMicrs = isset($admVars["MEMBER_MICR"]["mMicrs"]) ? $admVars["MEMBER_MICR"]["mMicrs"] : null;
36 
37  $mMember = $mPayload ?
38  MemberDecrypt($SYSENV, $Cu, $mPayload) :
39  null;
40 
41  $mContext = $mPayload ?
42  MemberContext($SYSENV, $Cu, $mMember['member']) :
43  MemberContext($SYSENV, $Cu);
44 
45  $aryResult = array();
46  $aryReply = array();
47 
48  switch ($mOperation) {
49  case "":
51  break;
52  case "memberReadMicrs":
53  header('Content-type: application/json');
54 
55  $mRTNumbers = MemberReadRTNumbers($SYSENV, $dbh, $mContext);
56  $mAccounts = MemberReadAccounts($SYSENV, $dbh, $mContext);
57  $mMicrs = MemberReadMicrs($SYSENV, $dbh, $mContext);
58  $aryResult['data']['numbers'] = $mRTNumbers['numbers'];
59  $aryResult['data']['accounts'] = $mAccounts['accounts'];
60  $aryResult['data']['micrs'] = $mMicrs['micrs'];
61  MemberReply($aryResult, $aryReply, $mOperation);
62  break;
63  case "memberUpdateMicrs":
64  header('Content-type: application/json');
65 
66  $mValidateMicrs = MemberValidateMicrs($SYSENV, $mContext, $mMicrs);
67  $mDelete = MemberDeleteMicrs($SYSENV, $dbh, $mContext, $mValidateMicrs['delete']);
68  $mCreate = MemberCreateMicrs($SYSENV, $dbh, $mContext, $mValidateMicrs['create']);
69  $mUpdate = MemberUpdateMicrs($SYSENV, $dbh, $mContext, $mValidateMicrs['update']);
70  $mMicrs = MemberReadMicrs($SYSENV, $dbh, $mContext);
71 
72  $aryResult['data']['micrs'] = $mMicrs['micrs'];
73  $aryResult['info'][] = isset($mDelete['message']) ? $mDelete['message'] : "" ;
74  $aryResult['info'][] = isset($mCreate['message']) ? $mCreate['message'] : "" ;
75  $aryResult['info'][] = isset($mUpdate['message']) ? $mUpdate['message'] : "" ;
76  MemberReply($aryResult, $aryReply, $mOperation);
77  break;
78  default:
79  throw new Exception("Unknown server request: " . $mOperation);
80  break;
81  }
82 
83 } catch (Exception $e) {
84  $aryReply['errors'][] = $e->getMessage();
85  $aryResult['data'] = array();
86  $aryResult['info'] = array();
87  MemberReply($aryResult, $aryReply, $mOperation);
88 }
89 
90 /**
91  * @package MemberValidateMicrs
92  * @uses validate incoming parameters for insertion into database
93  *
94  * @param pEnv array: environment variable for debugging
95  * @param pContext array: array of common data needed accross the member hub
96  * @param pMicrs string: json string list of micrs to validate for database
97  *
98  * @return sqlReturn array: array of micr account overrides
99  */
100 function MemberValidateMicrs($pEnv, $pContext, $pMicrs) {
101  $sList = html_entity_decode($pMicrs, ENT_QUOTES);
102  $dList = HCU_JsonDecode($sList);
103 
104  $mValidate = array();
105 
106  foreach ($dList as $action => $set) {
107  $mValidate[$action] = array();
108  foreach ($set as $key => $value) {
109  $mValidate[$action][] = array(
110  "_action" => "$action",
111  "micrid" => intval($value['mId']),
112  "accounttype" => prep_save($value['mType'], 12),
113  "micraccount" => prep_save($value['mMicr'], 17),
114  "startcheck" => intval($value['mCheck']),
115  "rt" => prep_save($value['mRt'], 12),
116  "cu" => $pContext['cu_code']
117  );
118  }
119  }
120 
121  return $mValidate;
122 }
123 
124 /**
125  * @package MemberCreateMicrs
126  * @uses insert new micr override values into the database
127  *
128  * @param pEnv array: environment variable for debugging
129  * @param pDbh object: database access object
130  * @param pContext array: array of common data needed accross the member hub
131  * @param pMicrs string: json string list of micrs to add to the database
132  *
133  * @return sqlReturn array: array of inserted micr overrides
134  */
135 function MemberCreateMicrs($pEnv, $pDbh, $pContext, $pMicrs) {
136  if (count($pMicrs) == 0) { return array(); }
137 
138  $cuTable = $pContext['cu_table'];
139  $cuCode = $pContext['cu_code'];
140  $cuMember = $pContext['m_account'];
141 
142  $sqlReturn = array();
143  $sqlCreate = "";
144  $sqlColumns = "
145  accountnumber,
146  accounttype,
147  micraccount,
148  startcheck,
149  rt,
150  cu";
151 
152  foreach ($pMicrs as $key => $value) {
153  $sqlValues = "
154  '$cuMember',
155  '{$value['accounttype']}',
156  '{$value['micraccount']}',
157  '{$value['startcheck']}',
158  '{$value['rt']}',
159  '$cuCode'";
160 
161  $sqlCreate .= "
162  INSERT INTO cuovermicr
163  ($sqlColumns) VALUES ($sqlValues);";
164  }
165 
166  $sqlCreateRs = db_query($sqlCreate, $pDbh);
167  if (!$sqlCreateRs) {
168  $pEnv['logger']->error(db_last_error());
169  throw new Exception("Failed to create MICR settings.");
170  }
171 
172  $sqlReturn['message'] = "MICR settings have been created successfully.";
173  return $sqlReturn;
174 }
175 
176 /**
177  * @package MemberUpdateMicrs
178  * @uses update micr override values in the database
179  *
180  * @param pEnv array: environment variable for debugging
181  * @param pDbh object: database access object
182  * @param pContext array: array of common data needed accross the member hub
183  * @param pMicrs string: json string list of micrs to update
184  *
185  * @return sqlReturn array: array of updated micr overrides
186  */
187 function MemberUpdateMicrs($pEnv, $pDbh, $pContext, $pMicrs) {
188  if (count($pMicrs) == 0) { return array(); }
189 
190  $cuTable = $pContext['cu_table'];
191  $cuCode = $pContext['cu_code'];
192  $cuMember = $pContext['m_account'];
193 
194  $sqlReturn = array();
195  $sqlUpdate = "";
196  $sqlColumns = "
197  accounttype,
198  micraccount,
199  startcheck,
200  rt";
201 
202  foreach ($pMicrs as $key => $value) {
203  $sqlValues = "
204  '{$value['accounttype']}',
205  '{$value['micraccount']}',
206  '{$value['startcheck']}',
207  '{$value['rt']}'";
208 
209  $sqlUpdate .= "
210  UPDATE cuovermicr
211  SET ($sqlColumns) = ($sqlValues)
212  WHERE micrid = {$value['micrid']}
213  AND accountnumber = '$cuMember'
214  AND cu = '$cuCode';";
215 
216  }
217 
218  $sqlUpdateRs = db_query($sqlUpdate, $pDbh);
219  if (!$sqlUpdateRs) {
220  $pEnv['logger']->error(db_last_error());
221  throw new Exception("Failed to update MICR settings.");
222  }
223 
224  $sqlReturn['message'] = "MICR settings have been updated successfully.";
225  return $sqlReturn;
226 }
227 
228 /**
229  * @package MemberDeleteMicrs
230  * @uses delete micr override values in the database
231  *
232  * @param pEnv array: environment variable for debugging
233  * @param pDbh object: database access object
234  * @param pContext array: array of common data needed accross the member hub
235  * @param pMicrs string: json string list of micrs to remove
236  *
237  * @return sqlReturn array: array of removed micr overrides
238  */
239 function MemberDeleteMicrs($pEnv, $pDbh, $pContext, $pMicrs) {
240  if (count($pMicrs) == 0) { return array(); }
241 
242  $cuTable = $pContext['cu_table'];
243  $cuCode = $pContext['cu_code'];
244  $cuMember = $pContext['m_account'];
245 
246  $sqlReturn = array();
247  $sqlDelete = "";
248 
249  foreach ($pMicrs as $key => $value) {
250  $sqlDelete .= "
251  DELETE FROM cuovermicr
252  WHERE micrid = {$value['micrid']}
253  AND accountnumber = '$cuMember';";
254  }
255 
256  $sqlDeleteRs = db_query($sqlDelete, $pDbh);
257  if(!$sqlDeleteRs) {
258  $pEnv['logger']->error(db_last_error());
259  throw new Exception("Failed to delete MICR settings.");
260  }
261 
262  $sqlReturn['message'] = "MICR settings have been deleted successfully.";
263  return $sqlReturn;
264 }
265 
266 /**
267  * @package MemberReadRTNumbers
268  * @uses get list of ann routing and transit numbers used by this credit union
269  *
270  * @param pEnv array: environment variable for debugging
271  * @param pDbh object: database access object
272  * @param pContext array: array of common data needed accross the member hub
273  *
274  * @return sqlReturn array: array of r/t number for the cu
275  */
276 function MemberReadRTNumbers($pEnv, $pDbh, $pContext) {
277  $cuTable = $pContext['cu_table'];
278  $cuCode = $pContext['cu_code'];
279  $cuMember = $pContext['m_account'];
280 
281  $sqlReturn = array();
282 
283  $sqlSelect = "
284  SELECT rt, 'P' AS pri FROM cuadmin WHERE cu ='$cuCode'
285  UNION ALL
286  SELECT rt, 'X' AS pri FROM cualtroute WHERE cu ='$cuCode'
287  ORDER BY 2,1";
288 
289  $sqlSelectRs = db_query($sqlSelect, $pDbh);
290  if (!$sqlSelectRs) {
291  $pEnv['logger']->error(db_last_error());
292  throw new Exception("Failed to read member routing / transit numbers.");
293  }
294 
295  $sqlReturn['numbers'] = db_fetch_all($sqlSelectRs);
296  return $sqlReturn;
297 }
298 
299 /**
300  * @package MemberReadAccounts
301  * @uses get list of deposit accounts for this member
302  *
303  * @param pEnv array: environment variable for debugging
304  * @param pDbh object: database access object
305  * @param pContext array: array of common data needed accross the member hub
306  *
307  * @return sqlReturn array: array of deposit accounts
308  */
309 function MemberReadAccounts($pEnv, $pDbh, $pContext) {
310  $cuTable = $pContext['cu_table'];
311  $cuCode = $pContext['cu_code'];
312  $cuMember = $pContext['m_account'];
313 
314  $sqlReturn = array();
315  $sqlColumns = "
316  ab.accounttype AS a_type,
317  ab.description AS a_desc,
318  ab.micraccount AS a_micr";
319  $sqlSelect = "
320  SELECT $sqlColumns
321  FROM {$cuTable}accountbalance ab
322  WHERE ab.accountnumber = '$cuMember'
323  AND ab.deposittype = 'Y'
324  ORDER BY ab.accounttype";
325  $sqlSelectRs = db_query($sqlSelect, $pDbh);
326  if (!$sqlSelectRs) {
327  $pEnv['logger']->error(db_last_error());
328  throw new Exception("Failed to read member sub accounts.");
329  }
330 
331  $sqlReturn['accounts'] = db_fetch_all($sqlSelectRs);
332  return $sqlReturn;
333 }
334 
335 /**
336  * @package MemberReadMicrs
337  * @uses get list of micr overrides for this member account
338  *
339  * @param pEnv array: environment variable for debugging
340  * @param pDbh object: database access object
341  * @param pContext array: array of common data needed accross the member hub
342  *
343  * @return sqlReturn array: array of micr account overrides
344  */
345 function MemberReadMicrs($pEnv, $pDbh, $pContext) {
346  $cuTable = $pContext['cu_table'];
347  $cuCode = $pContext['cu_code'];
348  $cuMember = $pContext['m_account'];
349 
350  $sqlReturn = array();
351  $sqlColumns = "
352  om.micrid AS o_id,
353  om.startcheck AS o_check,
354  om.rt AS o_rt,
355  om.micraccount AS o_micr,
356  ab.accounttype AS a_type,
357  ab.description AS a_desc,
358  ab.micraccount AS a_micr";
359  $sqlSelect = "
360  SELECT $sqlColumns
361  FROM cuovermicr om
362  JOIN {$cuCode}accountbalance ab
363  ON ab.accountnumber = om.accountnumber
364  AND ab.accounttype = om.accounttype
365  WHERE cu = '{$cuCode}'
366  AND om.accountnumber = '$cuMember'
367  ORDER BY om.accounttype, om.startcheck";
368  $sqlSelectRs = db_query($sqlSelect, $pDbh);
369  if (!$sqlSelectRs) {
370  $pEnv['logger']->error(db_last_error());
371  throw new Exception("Failed to read member mict values.");
372  }
373 
374  $sqlReturn['micrs'] = db_fetch_all($sqlSelectRs);
375  return $sqlReturn;
376 }
377 
378 ?>
379 
380 <?php
381 /**
382  * @package PrintMemberMicrs
383  * @uses print neccessary html/javascript to run the selected card
384  */
385 function PrintMemberMicrs() { ?>
386 
387 <style type="text/css">
388 #mmMicrGrid .k-grid-content tr:hover {
389  background: transparent;
390  cursor: pointer;
391 }
392 
393 </style>
394 
395 <div id="mmMicrs">
396  <div id="status"></div>
397 
398  <div class="h4">Core Info</div>
399 
400  <div class="hcu-secondary">
401  <div class="small vsgSecondary">
402  <span>This is the information from your core data provider.</span>
403  </div>
404  </div>
405 
406  <div id="mmCoreInfoGrid"></div>
407 
408  <div>&nbsp;</div>
409  <div class="h4">MICR Overrides</div>
410 
411  <div class="hcu-secondary">
412  <div class="small vsgSecondary">
413  <span>Click inside a row field to edit the selected field.</span><br>
414  <span>Delete a MICR override by clicking the checkbox to the left of each row.</span>
415  </div>
416  </div>
417 
418  <div id="mmMicrGrid"></div>
419 
420  <div class="hcu-template">
421  <div class="hcu-edit-buttons k-state-default">
422  <span class="hcu-icon-delete">
423  </span>
424  <a href="##" id="lnkCancel">Cancel</a>
425  &ensp;
426  <a href="##" id="btnUpdate" class="k-button k-primary">
427  <i class="fa fa-check fa-lg"></i>
428  Update
429  </a>
430  </div>
431  </div>
432 </div>
433 
434 <div id="mmDiscard">
435  <p>This member's MICR settings have been changed.</p>
436  <p>Do you wish to discard the changes?</p>
437 </div>
438 
439 <div id="mmEmpty">
440  <p>There are one or more MICR settings with empty fields.</p>
441  <p>Please review your changes and resubmit the form.</p>
442 </div>
443 
444 <div id="mmDuplicates">
445  <p>There are multiple MICR settings for a sub-account with one or more of the following errors:</p>
446  <ul style="margin-left: 25px;">
447  <li>the routing/transit numbers are the same</li>
448  <li>the starting check numbers are the same</li>
449  </ul>
450  <p>Please review your changes and re-submit the form.</p>
451 </div>
452 
453 <?php
454 /**
455  * @package MemberMICR
456  * @uses this object is used to display and interact with the member MICR feature.
457  *
458  * @var Init public: call to initialize data/view/action objects
459  * @var Open public: call to open the mamber search module/window
460  * @var Close public: call to close the member search module/window
461  * @var Data public: call to load payload and member display into
462  * MemberRelated object for later use.
463  *
464  * @var InitDataSources private: initialize all data sources/objects
465  * @var InitDataViews private: initialize all data views/objects
466  * @var InitDataActions private: initialize all user actions on html.
467  *
468  * @var EventOpenWindow private: open kendoDialog/kendoWindow objects
469  * @var EventCloseWindow private: close kendoDialog/kendoWindow objects
470  * @var EventPopWindow private: remove the correct window from the window stack.
471  *
472  * @var Event* private: other event functions explained by name.
473  * Some are entensions of kendo objects, others just help with events of html objects.
474  *
475  * @var Validate* private: validation functions for fors and other user
476  * input interactions.
477  */
478 ?>
479 <script type="text/javascript">
480 //# sourceURL=micrs.js
481 
482 var MemberMicrs = function() {
483  var mmCardContainer = null;
484  var mmCardWindows = null;
485 
486  var mmPayload = null;
487  var mmMember = null;
488  var mmCall = null;
489  var mmAction = null;
490 
491  var mmDataSource = null;
492  var mmDataAccounts = null;
493  var mmDataNumbers = null;
494  var mmDataMicrs = null;
495  var mmDataCoreInfo= null;
496 
497  var mmMicrGrid = null;
498  var mmCoreInfoGrid= null;
499  var mmMicrTip = null;
500 
501  var mmMicrs = null;
502  var mmDiscard = null;
503  var mmEmpty = null;
504  var mmDuplicates = null;
505  var mmUpdate = null;
506  var mmCancel = null;
507 
508  var DataBuildCoreInfo= function(accounts, rts) {
509  mmDataCoreInfo= [];
510 
511  var rt= rts.length >= 1 ? rts[0].rt.trim() : "";
512  var kendoId= 0;
513  for (var i=0; i!= accounts.length; i++) {
514  var row= accounts[i];
515 
516  for (var j=0; j!= rts.length; j++) {
517  var rrow= rts[j];
518  if ((typeof(rrow['pri']) !== 'undefined') && (rrow['pri'] === 'P')) {
519  mmDataCoreInfo.push({kendoId: ++kendoId, account: row.a_type.trim() + " - " +
520  row.a_desc.trim(), coreMicr: row.a_micr != null ? row.a_micr.trim() : ""});
521  }
522  }
523 
524  }
525 
526  mmCoreInfoGrid.dataSource.data(mmDataCoreInfo);
527  }
528 
529  var DataBuildMicrs = function(data) {
530  mmDataMicrs = [];
531 
532  for (var i = 0; i < data.length; i++) {
533  var micr = {};
534 
535  micr.oId = parseInt(data[i].o_id);
536  micr.oAcct = data[i].a_type.trim() + " - " + data[i].a_desc.trim();
537  micr.oRt = data[i].o_rt.trim();
538  micr.oMicr = data[i].o_micr.trim();
539  micr.oMicr = micr.oMicr == "0" ? "" : micr.oMicr;
540  micr.oCheck = data[i].o_check.trim();
541  micr.eAcct = data[i].a_type.trim() + " - " + data[i].a_desc.trim();
542  micr.eRt = data[i].o_rt.trim();
543  micr.eMicr = data[i].o_micr.trim();
544  micr.eCheck = data[i].o_check.trim();
545  micr.eDirty = false;
546  micr.eDelete = false;
547  micr.eNew = false;
548  micr.eAdd = false;
549  micr.eaMicr= data[i].a_micr != null ? data[i].a_micr.trim() : ""; <?php // Because of internal rules, it must start with e and o version is needed even though it never changes. ?>
550  micr.oaMicr= micr.eaMicr;
551 
552  mmDataMicrs.push(micr);
553  }
554 
555  mmDataMicrs.push({
556  oId: -1,
557  oAcct: " ",
558  oRt: " ",
559  oMicr: " ",
560  oCheck: " ",
561  eAcct: " ",
562  eRt: " ",
563  eMicr: " ",
564  eCheck: " ",
565  eaMicr: " ",
566  oaMicr: " ",
567  eDirty: false,
568  eDelete: false,
569  eNew: false,
570  eAdd: true
571  });
572 
573  mmMicrGrid.dataSource.data(mmDataMicrs);
574  $("#mmMicrGrid").find("input[type=checkbox]").prop("checked", false);
575  }
576 
577  var DataBuildNumbers = function(data) {
578  mmDataNumbers = [{
579  value: " ",
580  text: "Select an r/t number"
581  }];
582 
583  for (var i = 0; i < data.length; i++) {
584  var number = {
585  value: data[i].rt.trim(),
586  text: data[i].rt.trim()
587  };
588 
589  mmDataNumbers.push(number);
590  }
591  }
592 
593  var DataBuildAccounts = function(data) {
594  mmDataAccounts = [{
595  value: " ",
596  text: "Select an account",
597  micr: " "
598  }];
599 
600  for (var i = 0; i < data.length; i++) {
601  var account = {};
602  account.value = data[i].a_type.trim() + " - " + data[i].a_desc.trim();
603  account.text = data[i].a_type.trim() + " - " + data[i].a_desc.trim();
604 
605  var micr= data[i].a_micr.trim();
606  micr= micr == "0" ? "" : micr;
607  account.micr= micr;
608 
609  mmDataAccounts.push(account);
610  }
611  }
612 
613  var ValidateMicrsDuplicate = function() {
614  var valid = true;
615  var data = mmMicrGrid.dataSource.data();
616  var checks = {};
617 
618  for (var i = 0; i < data.length-1; i++) {
619  var item = data[i];
620  if (data[i].eDelete) { continue; }
621  if (data[i].eNew && !data[i].eDirty) { continue; }
622 
623  var acct= item.eAcct.trim();
624  var checkRt= (item.eCheck+"").trim() + "-" + item.eRt.trim(); <?php // Needed because eCheck can now be a number with the trim doesn't work with. ?>
625 
626  if (checks[acct]) {
627  if (checks[acct][checkRt]) {
628  checks[acct][checkRt] ++;
629  } else {
630  checks[acct][checkRt] = 1;
631  }
632  } else {
633  checks[acct] = {};
634  checks[acct][checkRt] = 1;
635  }
636  }
637 
638  for (keyAcct in checks) {
639  for (keyCheck in checks[keyAcct]) {
640  if (checks[keyAcct][keyCheck] > 1) {
641  valid = false;
642  }
643  }
644  }
645 
646  return valid;
647  }
648 
649  var ValidateMicrsEmpty = function() {
650  var valid = true;
651  var data = mmMicrGrid.dataSource.data();
652  for (var i = 0; i < data.length-1; i++) {
653  var item = data[i];
654  if (!item.eDirty) { continue; }
655 
656  if (item.eAcct.trim() == "") { valid = false; }
657  else if (item.eRt.trim() == "") { valid = false; }
658  else if ((item.eMicr+"").trim() == "") { valid = false; }
659  else if ((item.eCheck+"").trim() == "") { valid = false; }
660  else { valid = true; }
661  }
662 
663  return valid;
664  }
665 
666  var ValidateMicrsDirty = function(mode) {
667  var valid = false;
668  var data = mmMicrGrid.dataSource.data();
669 
670  for (var i = 0; i < data.length-1; i++) {
671  var item = data[i];
672  if (item.eNew) { item.eDirty = true; }
673  else if (item.eAcct != item.oAcct) { item.eDirty = true; }
674  else if (item.eRt != item.oRt) { item.eDirty = true; }
675  else if (item.eMicr != item.oMicr) { item.eDirty = true; }
676  else if (item.eCheck != item.oCheck) { item.eDirty = true; }
677  else if (item.eDelete) { item.eDirty = true; }
678  else { item.eDirty = false; }
679 
680  if (!valid && item.eDirty) { valid = true; }
681  }
682 
683  return valid;
684  }
685 
686  var EditorDropDown = function(container, options) {
687  if (container.hasClass("vsgDisabled") || options.model.eAdd) {
688  mmMicrGrid.closeCell();
689  return false;
690  }
691 
692  if (container.attr("name") == "eAcct" && mmDataAccounts.length == 2 ||
693  container.attr("name") == "eRt" && mmDataNumbers.length == 2) {
694 
695  mmMicrGrid.closeCell();
696  return;
697  }
698 
699  $('<input required name="' + options.field + '"/>')
700  .appendTo(container)
701  .kendoDropDownList({
702  dataTextField: "text",
703  dataValueField: "value",
704  dataSource: options.field == "eAcct" ?
705  mmDataAccounts :
706  mmDataNumbers,
707  change: function() {
708  var valueKeyE = options.field;
709  var valueKeyO = options.field.replace("e", "o");
710  var valueO = options.model[valueKeyO];
711  var valueE = options.model[valueKeyE];
712 
713  if (options.field == "eAcct") { <?php // Set the core MICR. ?>
714  var dataItem= this.dataItem();
715  options.model.eaMicr= dataItem.micr;
716  options.model.oaMicr= dataItem.micr;
717  $(this.wrapper).closest("tr").find("[name='eaMicr']").text(dataItem.micr);
718  }
719 
720  if (valueO == valueE) {
721  $(container).removeClass("k-dirty-cell");
722  $(container).find(".k-dirty").remove();
723  } else {
724  $(container).addClass("k-dirty-cell");
725  $(container).prepend("<span class='k-dirty'></span>");
726  }
727 
728  EventPreservState();
729  },
730  select: function() {}
731  }).closest(".k-dropdown.k-widget").on("keydown", function(e) {
732  var key = e.which || e.keyCode;
733  if (key == 9) {
734  e.preventDefault();
735 
736  var gridCell = null;
737  if (options.field == "eAcct") {
738  if (mmDataNumbers.length == 2) {
739  gridCell = $(this).closest("tr").find("td:eq(3)");
740  } else {
741  gridCell = $(this).closest("tr").find("td:eq(2)");
742  }
743  } else if (options.field == "eRt") {
744  gridCell = $(this).closest("tr").find("td:eq(3)");
745  }
746  mmMicrGrid.editCell(gridCell);
747  }
748  });
749 
750  container.find("input").data("kendoDropDownList").open();
751  }
752 
753  var EditorNumeric = function(container, options) {
754  if (container.hasClass("vsgDisabled") || options.model.eAdd) {
755  mmMicrGrid.closeCell();
756  return false;
757  }
758 
759  $('<input name="' + options.field + '" maxlength=\"<?php echo GetMicrSize(); ?>\"/>')
760  .appendTo(container)
761  .on("change", function(e) {
762  var name = $(this).closest("td").attr("name");
763  if (name == "eCheck") {
764  var valueE = $(this).val();
765  valueE = valueE.replace(/^0+/, '');
766  if (valueE == "") {
767  valueE = "0";
768  }
769  $(this).val(valueE);
770  }
771  })
772  .on("keypress", function(e) {
773  var key = e.which || e.keyCode;
774  if (key == 46 || key == 8 || (key > 47 && key < 58) || (key > 36 && key < 41)) { <?php // Adding special characters and numbers. ?>
775 
776  } else {
777  e.preventDefault();
778  }
779  })
780  .on("keydown", function(e) {
781  var key = e.which || e.keyCode;
782  if (key == 9) {
783  e.preventDefault();
784 
785  var gridCell = null;
786  var gridRow = null;
787  if (options.field == "eMicr") {
788  gridCell = $(this).closest("tr").find("td:eq(4)");
789  } else if (options.field == "eCheck") {
790  gridRow = $(this).closest("tr").next("tr");
791 
792  if (mmDataAccounts.length == 2) {
793  if (mmDataNumbers.length == 2) {
794  gridCell = gridRow.find("td:eq(3)");
795  } else {
796  gridCell = gridRow.find("td:eq(2)");
797  }
798  } else {
799  gridCell = gridRow.find("td:eq(1)");
800  }
801  }
802  $(this).trigger("change");
803  mmMicrGrid.editCell(gridCell);
804  }
805  })
806  .on("blur", function() {
807  var valueKeyE = options.field;
808  var valueKeyO = options.field.replace("e", "o");
809  var valueO = options.model[valueKeyO];
810  var valueE = options.model[valueKeyE];
811 
812  if (valueO == valueE) {
813  $(container).removeClass("k-dirty-cell");
814  $(container).find(".k-dirty").remove();
815  } else {
816  $(container).addClass("k-dirty-cell");
817  $(container).prepend("<span class='k-dirty'></span>");
818  }
819 
820  EventPreservState();
821  });
822  }
823 
824  var EventUpdateMicrs = function() {
825  var data = mmMicrGrid.dataSource.data();
826  var dataU = {
827  create: [],
828  update: [],
829  delete: []
830  };
831 
832  for (var i = 0; i < data.length-1; i++) {
833  var item = data[i];
834 
835  if (item.eDirty) {
836  var itemU = {};
837  itemU.mId = item.oId;
838  itemU.mAcct = item.eDelete ? item.oAcct : item.eAcct,
839  itemU.mRt = item.eDelete ? item.oRt : item.eRt,
840  itemU.mMicr = item.eDelete ? item.oMicr : item.eMicr,
841  itemU.mCheck = item.eDelete ? item.oCheck : item.eCheck
842 
843  var account = itemU.mAcct.split("-");
844  var mType = account[0].trim();
845  itemU.mType = mType;
846 
847  if (item.eNew) {
848  dataU.create.push(itemU);
849  } else if (item.eDelete) {
850  dataU.delete.push(itemU);
851  } else {
852  dataU.update.push(itemU);
853  }
854  }
855  }
856 
857  var memberRequest = {
858  operation: "memberUpdateMicrs",
859  payload: mmPayload,
860  mMicrs: JSON.stringify(dataU)
861  };
862 
863  mmDataSource.transport.options.read.type = "POST";
864  mmDataSource.read(memberRequest);
865  }
866 
867  var EventPreservState = function() {
868  var rowCheckAll = true;
869  $("#mmMicrGrid").find("tr").each(function(i) {
870 
871  // skip the header row, the added row and the add row
872  if (i > 0 && i < mmMicrGrid.dataSource.data().length) {
873  var rowItem = mmMicrGrid.dataItem(this);
874  var rowValueO = null;
875  var rowValueE = null;
876 
877  $(this).find("td").each(function(j) {
878  var field = $(this);
879  var fieldKeyE = null;
880  var fieldKeyO = null;
881 
882  if (j > 0) {
883  fieldKeyE = field.attr("name");
884  fieldKeyO = fieldKeyE.replace("e", "o");
885 
886  rowValueO = rowItem[fieldKeyO];
887  rowValueE = rowItem[fieldKeyE];
888 
889  if (rowValueO != rowValueE) {
890  $(this).addClass("k-dirty-cell");
891  $(this).prepend("<span class='k-dirty'></span>");
892  }
893  }
894 
895  if (rowItem.eDelete) {
896  $(this).addClass("vsgDisabled");
897  $(this).addClass("mark-for-delete");
898  $(this).find("input[type=checkbox]").prop("checked", true);
899  }
900  });
901 
902  if (!rowItem.eDelete) {
903  rowCheckAll = false;
904  }
905  }
906  });
907 
908  if (rowCheckAll) {
909  $("#chk_ALL").prop("checked", true);
910  }
911  }
912 
913  var EventShowTip = function(e) {
914  var target = $(e.target);
915  var targetContent = "";
916 
917  if (target.hasClass("mark-for-delete")) {
918  targetContent = "This micr setting has been marked for removal.";
919  } else {
920  targetContent = $(e.target).text().trim();
921  }
922 
923  return targetContent;
924  }
925 
926  var EventCheckBoxChange = function(e) {
927  var box = $(this);
928  var boxId = box.attr("id");
929  var boxValue = box.prop("checked");
930 
931  if (!boxValue) {
932  $("#mmMicrGrid input[id=chk_ALL]").prop("checked", false);
933  }
934 
935  if (boxId == "chk_ALL") {
936  $("#mmMicrGrid input[type=checkbox]").each(function(e) {
937  if (e > 0) {
938  $(this).prop("checked", boxValue);
939  $(this).trigger("change");
940  }
941  });
942  } else {
943  var row = box.closest("tr");
944  var rowIndex = mmDataMicrs[row[0].rowIndex];
945  var rowItem = mmMicrGrid.dataItem(row);
946  var rowFields = $(row).find("td");
947 
948  $(rowFields).each(function(e) {
949  var field = $(this);
950  var fieldClass = "";
951 
952  if (e > 0) {
953 
954  if (boxValue) {
955  field.addClass("vsgDisabled");
956  field.addClass("mark-for-delete");
957  rowItem.eDelete = true;
958 
959  var ntb= field.find("[id^='ntb_']");
960  ntb.length == 0 ? null : ntb.data("kendoNumericTextBox").enable(false);
961 
962  var mtb= field.find("[id^='mtb_']");
963  mtb.length == 0 ? null : mtb.data("kendoMaskedTextBox").enable(false);
964  } else {
965  field.removeClass("vsgDisabled");
966  field.removeClass("mark-for-delete");
967  rowItem.eDelete = false;
968 
969  var ntb= field.find("[id^='ntb_']");
970  ntb.length == 0 ? null : ntb.data("kendoNumericTextBox").enable(true);
971 
972  var mtb= field.find("[id^='mtb_']");
973  mtb.length == 0 ? null : mtb.data("kendoMaskedTextBox").enable(true);
974  }
975  }
976  });
977 
978  EventPreservState();
979  }
980  }
981 
982  var EventOpenWindow = function(e) {
983  var windowElement = this.element[0];
984  var windowId = windowElement.id;
985 
986  switch (windowId) {
987 
988  }
989 
990  mmCardWindows.push(this);
991  }
992 
993  var EventCloseWindow = function(e) {
994  var windowElement = this.element[0];
995  var windowId = windowElement.id;
996 
997  switch (mmAction) {
998  case "micrsUpdate":
999  e.preventDefault();
1000  if (ValidateMicrsDirty()) {
1001 
1002  if (!ValidateMicrsEmpty()) { mmEmpty.open(); }
1003  else if (!ValidateMicrsDuplicate()) { mmDuplicates.open(); }
1004  else { EventUpdateMicrs(); }
1005  }
1006  break;
1007  case "discardConfirm":
1008  EventPopWindow(windowId);
1009  mmMicrGrid.dataSource.data(mmDataMicrs);
1010  mmAction = null;
1011  mmMicrs.close();
1012  break;
1013  default:
1014  if (windowId == "mmMicrs") {
1015  if (ValidateMicrsDirty()) {
1016  e.preventDefault();
1017  mmDiscard.open();
1018  } else {
1019  EventPopWindow(windowId);
1020  $.homecuValidator.setup({
1021  formStatusField: "formStatus",
1022  formValidate: "cardContainerDiv"
1023  });
1024  }
1025  } else {
1026  EventPopWindow(windowId);
1027  }
1028  break;
1029  }
1030 
1031  mmAction = null;
1032  }
1033 
1034  var EventPopWindow = function(windowId) {
1035  var popIndex = -1;
1036  for (var i = 0; i < mmCardWindows.length; i++) {
1037  var openWindow = mmCardWindows[i].element[0];
1038  var openId = openWindow.id;
1039 
1040  if (openId == windowId) {
1041  popIndex = i;
1042  break;
1043  }
1044  }
1045 
1046  if (popIndex > -1) {
1047  mmCardWindows.splice(popIndex, 1);
1048  }
1049  }
1050 
1051  var InitDataSources = function() {
1052  mmDataSource = new kendo.data.DataSource({
1053  transport: {
1054  read: {
1055  url: "main.prg",
1056  dataType: "json",
1057  contentType: "application/x-www-form-urlencoded",
1058  type: "GET",
1059  cache: false,
1060  data: {
1061  ft: "103106"
1062  }
1063  }
1064  },
1065  requestStart: function(request) {
1066  showWaitWindow();
1067  },
1068  requestEnd: function(response) {
1069  setTimeout(hideWaitWindow, 500);
1070 
1071  if (response.hasOwnProperty("response")) {
1072  if (response.response.hasOwnProperty("Results")) {
1073  var results = response.response.Results;
1074 
1075  if (results.hasOwnProperty("error")) {
1076  $.homecuValidator.homecuResetMessage = true;
1077  $.homecuValidator.displayMessage(results.error, $.homecuValidator.settings.statusError);
1078  } else if (results.hasOwnProperty("info")) {
1079  $.homecuValidator.homecuResetMessage = true;
1080  $.homecuValidator.displayMessage(results.info, $.homecuValidator.settings.statusSuccess);
1081  }
1082  } else {
1083  $.homecuValidator.displayMessage("Error Parsing Server", $.homecuValidator.settings.statusError);
1084  }
1085  } else {
1086  $.homecuValidator.displayMessage("Error Parsing Server", $.homecuValidator.settings.statusError);
1087  }
1088  },
1089  schema: {
1090  parse: function(response) {
1091 
1092  var results = null;
1093  var resultData = null;
1094  var resultOperation = null;
1095 
1096  if (response.hasOwnProperty("Results")) {
1097  results = response.Results;
1098  resultData = results.data;
1099  resultOperation = results.operation;
1100  } else {
1101  return [];
1102  }
1103 
1104  if (results.hasOwnProperty("errors")) {
1105  return [];
1106  }
1107 
1108  if (resultData == undefined || resultData == null) {
1109  return [];
1110  }
1111 
1112  setTimeout(function() {
1113  switch (resultOperation) {
1114  case "memberReadMicrs":
1115  if (!resultData.accounts) { <?php // Destroy and give no account message. ?>
1116  kendo.destroy($("#mmMicrs *")); <?php // Destroy everything inside the window. ?>
1117  $("#mmMicrs").html("<div class='vsgSecondary'>No checking accounts available.</div>"
1118  + "<div class='hcu-template'><div class='hcu-edit-buttons k-state-default'><a href='#' id='btnClose' class='k-button k-primary'>"
1119  + "<i class='fa fa-check fa-lg'></i>Close</a></div></div>");
1120  $("#btnClose").click(function() { mmMicrs.close(); });
1121  } else {
1122  DataBuildMicrs(resultData.micrs);
1123  DataBuildNumbers(resultData.numbers);
1124  DataBuildAccounts(resultData.accounts);
1125  DataBuildCoreInfo(resultData.accounts, resultData.numbers);
1126  }
1127 
1128  mmMember.cardTitle= "Member MICR";
1129  var template= kendo.template($("#titleTemplate").html());
1130  mmMicrs.title(template(mmMember)).center().open();
1131  break;
1132  case "memberUpdateMicrs":
1133  DataBuildMicrs(resultData.micrs);
1134  break;
1135  }
1136  }, 500);
1137 
1138  return [];
1139  }
1140  }
1141  });
1142  }
1143 
1144  var InitDataViews = function() {
1145  mmMicrs = $("#mmMicrs").kendoWindow({
1146  title: "Member MICR",
1147  minWidth: "75%",
1148  maxWidth: "90%",
1149  modal: true,
1150  visible: false,
1151  resizable: false,
1152  activate: EventOpenWindow,
1153  close: EventCloseWindow,
1154  open: function() {
1155  this.wrapper.css({ top: 100 });
1156  }
1157  }).data("kendoWindow");
1158 
1159  mmDiscard = $("#mmDiscard").kendoDialog({
1160  title: "Discard Changes",
1161  modal: true,
1162  visible: false,
1163  resizable: false,
1164  minWidth: 300,
1165  maxWidth: 500,
1166  show: EventOpenWindow,
1167  close: EventCloseWindow,
1168  actions: [
1169  { text: "No",
1170  action: function() { mmAction = "discardDeny"; }
1171  },
1172  { text: "Yes", primary: true,
1173  action: function() { mmAction = "discardConfirm"; }
1174  }
1175  ]
1176  }).data("kendoDialog");
1177 
1178  mmEmpty = $("#mmEmpty").kendoDialog({
1179  title: "Empty Fields",
1180  modal: true,
1181  visible: false,
1182  resizable: false,
1183  minWidth: 300,
1184  maxWidth: 500,
1185  show: EventOpenWindow,
1186  close: EventCloseWindow,
1187  actions: [
1188  { text: "Ok", primary: true }
1189  ]
1190  }).data("kendoDialog");
1191 
1192  mmDuplicates = $("#mmDuplicates").kendoDialog({
1193  title: "Duplicate Values",
1194  modal: true,
1195  visible: false,
1196  resizable: false,
1197  minWidth: 300,
1198  maxWidth: 500,
1199  show: EventOpenWindow,
1200  close: EventCloseWindow,
1201  actions: [
1202  { text: "Ok", primary: true }
1203  ]
1204  }).data("kendoDialog");
1205 
1206  mmUpdate = $("#btnUpdate");
1207  mmCancel = $("#lnkCancel");
1208  mmStatus = $("#mmStatus");
1209  mmStatus.hide();
1210 
1211  mmCoreInfoGrid= $("#mmCoreInfoGrid").kendoGrid({
1212  dataSource: {
1213  data: [],
1214  schema: {
1215  model: {
1216  id: "kendoId",
1217  fields: {
1218  kendoId: {type: "number"},
1219  account: {type: "string"},
1220  coreMicr: {type: "string"}
1221  }
1222  }
1223  }
1224  },
1225  columns: [
1226  {field: "account", title: "Account", width: "400px", attributes: {"class":"showEllipsis" }},
1227  {field: "coreMicr", title: "Core MICR"}
1228  ]
1229  }).data("kendoGrid");
1230 
1231  mmMicrGrid = $("#mmMicrGrid").kendoGrid({
1232  dataSource: {
1233  data: []
1234  },
1235  dataBound: function() {
1236  $("#mmMicrGrid input[type=checkbox]").off();
1237  $("#mmMicrGrid input[type=checkbox]").change(EventCheckBoxChange);
1238 
1239  $("td[name='eCheck'] input").each(function() { <?php // Numeric validation. ?>
1240  var name= $(this).closest("td").attr("name");
1241  $(this).kendoNumericTextBox({
1242  spinners: false, <?php // This keeps the numericTextBox looking like a normal input field without any visible effects. ?>
1243  decimals: 0,
1244  restrictDecimals: true,
1245  format: "#", <?php // Number without commas or decimals. ?>
1246  placeholder: "Enter a check number",
1247  min: 0,
1248  max: 2147483647,
1249  change: function() { <?php // Now that the input is divorced from the kendo mechanism for going into edit mode, I need to manually update the dataItem. ?>
1250  var tr= $(this.wrapper).closest("tr");
1251  var dataItem= mmMicrGrid.dataItem(tr);
1252  dataItem.dirty= true;
1253  dataItem.eDirty= true;
1254  dataItem[name]= this.value();
1255  }
1256  });
1257  });
1258 
1259  $("td[name='eMicr'] input").each(function() { <?php // Numeric validation. ?>
1260  var name= $(this).closest("td").attr("name");
1261  $(this).kendoMaskedTextBox({
1262  promptChar: " ",
1263  mask: "<?php echo str_repeat('0', GetMicrSize()); ?>",
1264  placeholder: "Enter a micr value",
1265  change: function() { <?php // Now that the input is divorced from the kendo mechanism for going into edit mode, I need to manually update the dataItem. ?>
1266  var tr= $(this.wrapper).closest("tr");
1267  var dataItem= mmMicrGrid.dataItem(tr);
1268  dataItem.dirty= true;
1269  dataItem.eDirty= true;
1270  dataItem[name]= this.value();
1271  }
1272  });
1273  });
1274  },
1275  change: function() {
1276  var cell = this.select();
1277  var cellIndex = cell.index();
1278  var row = cell.closest("tr");
1279  var rowIndex = row[0].rowIndex;
1280  var rowItem = mmMicrGrid.dataItem(row);
1281 
1282  if (rowItem.eAdd) {
1283  var rowNew = {
1284  oId: -1,
1285  oAcct: " ", oRt: " ", oMicr: " ", oCheck: "0", oaMicr: " ",
1286  eAcct: " ", eRt: " ", eMicr: " ", eCheck: "0", eaMicr: " ",
1287  eDirty: false, eDelete: false,
1288  eNew: true, eAdd: false
1289  }
1290 
1291  if (mmDataAccounts.length == 2) {
1292  rowNew.oAcct = mmDataAccounts[1].value;
1293  rowNew.eAcct = mmDataAccounts[1].value;
1294  }
1295 
1296  if (mmDataNumbers.length == 2) {
1297  rowNew.oRt = mmDataNumbers[1].value;
1298  rowNew.eRt = mmDataNumbers[1].value;
1299  }
1300 
1301  mmMicrGrid.dataSource.insert(rowIndex, rowNew);
1302  EventPreservState();
1303  } else {
1304  if (cellIndex == 0) {
1305 
1306  if (rowItem.eNew) {
1307  mmMicrGrid.dataSource.remove(rowItem);
1308  EventPreservState();
1309  } else {
1310  var box = cell.find("input[type=checkbox]")[0];
1311  $(box).trigger("click");
1312  }
1313 
1314  }
1315  }
1316 
1317  $(cell).removeClass("k-state-selected");
1318  },
1319  editable: true,
1320  selectable: "cell",
1321  columns: [
1322  { width: "30px",
1323  template: "#if(eAdd){#<span class=\"fa fa-plus\"></span>#}else if(eNew){#<span class=\"fa fa-minus\"></span>#}else{#<input type=\"checkbox\" style=\"margin-top: -2px;\" />#}#",
1324  headerTemplate: "<input type=\"checkbox\" style=\"margin-top: -2px;\" id=\"chk_ALL\" />" },
1325  { field: "eAcct", title: "Account", editor: EditorDropDown, width: "400px",
1326  attributes: { "name":"eAcct", "class":"showEllipsis" },
1327  template: "#if(eAdd){#<span>Add Row</span>#}else if(eAcct.trim() == \"\"){#<span>Select an account</span>#}else{##=eAcct##}#" },
1328  { field: "eRt", title: "R/T", editor: EditorDropDown, attributes: { "name":"eRt" },
1329  template: "#if(eAdd){#<span></span>#}else if(eRt.trim() == \"\"){#<span>Select an r/t number</span>#}else{##=eRt##}#"},
1330  { field: "eaMicr", title: "Core MICR", editable: function() { return false; }, template: "#if (eAdd) { #<span></span># } else { # #=eaMicr# # } #", attributes: { "name" : "eaMicr"}},
1331  { field: "eMicr", title: "MICR #", editable: function() { return false; },
1332  attributes: { "name":"eMicr" },
1333  template: "#if(eAdd){#<span></span>#}else {#<span><input id='mtb_#=oId#_1' class='hcu-all-100' value='#=eMicr#'></span>#}#"},
1334  <?php // Just need to reference the input. Rule from kendo: all attributes are copied from the element into the wrapper for the control EXCEPT for the id.
1335  // If I need to reference it generically, I can set the id to something unique and use the JQuery starts with selector. ?>
1336  { field: "eCheck", title: "Starting Check #", editable: function() { return false; },
1337  attributes: { "name":"eCheck" },
1338  template: "#if(eAdd){#<span></span>#}else {#<span><input id='ntb_#=oId#_2' class='hcu-all-100' value='#=eCheck#'></span>#}#"}
1339  ]
1340  }).data("kendoGrid");
1341 
1342 
1343 
1344  // USE THIS TO SELECT OVERFLOW IN JQUERY SELECTORS FOR TOOLTIP BELOW
1345  jQuery.extend(jQuery.expr[':'], {
1346  overflown: function (el) {
1347  return el.offsetHeight < el.scrollHeight || el.offsetWidth < el.scrollWidth;
1348  }
1349  });
1350 
1351  mmMicrTip = homecuTooltip.defaults;
1352  mmMicrTip.filter = ".showEllipsis:overflown, .vsgDisabled";
1353  mmMicrTip.content = EventShowTip
1354  $("#mmMicrGrid").kendoTooltip(mmMicrTip);
1355  }
1356 
1357  var InitDataActions = function() {
1358  mmUpdate.off();
1359  mmUpdate.on("click", function() { mmAction = "micrsUpdate"; mmMicrs.close(); });
1360  mmCancel.off();
1361  mmCancel.on("click", function() { mmMicrs.close(); });
1362  }
1363 
1364  this.Open = function(windowStack) {
1365  // setup validator
1366  $.homecuValidator.setup({
1367  formStatusField: "status",
1368  formValidate: "mmMicrs"
1369  });
1370 
1371  mmCardWindows = windowStack;
1372  var memberRequest = {
1373  operation: "memberReadMicrs",
1374  payload: mmPayload
1375  };
1376 
1377  mmDataSource.transport.options.read.type = "POST";
1378  mmDataSource.read(memberRequest);
1379  }
1380 
1381  this.Close = function() {
1382  mmMicrs.destroy();
1383  mmDiscard.destroy();
1384  mmEmpty.destroy();
1385  mmDuplicates.destroy();
1386  }
1387 
1388  this.Data = function(payload, member) {
1389  mmPayload = payload;
1390  mmMember = member;
1391  }
1392 
1393  this.Init = function(hubCall, cardContainer) {
1394  mmCall = hubCall;
1395  mmCardContainer = cardContainer;
1396 
1397  InitDataSources();
1398  InitDataViews();
1399  InitDataActions();
1400 
1401  mmCall("MemberMicr", this);
1402  }
1403 }
1404 </script>
1405 <?php }