Odyssey
VendorByCu.i
1 <?php
2 
3 /**
4  * Class VendorByCu
5  * Copyright 07/2019 HomeCu LLC
6  *
7  * Outputs a basic report showing what vendors are used by which credit unions.
8  * Aid in migration tool.
9  *
10  * Usage:
11  *
12  * $obj = new VendorByCu();
13  * echo $obj
14  * ->set('dbh', $dbh)
15  * ->set('SYS_TYPE_UPG_TEST', $SYS_TYPE_UPG_TEST)
16  * ->set('SYS_TYPE_CLOSED', $SYS_TYPE_CLOSED)
17  * ->VendorByCuReport();
18  *
19  * Odyssey uses Google Oauth. TO TEST LOCALLY get your my.homecu.net
20  * auth cookie and in your code that calls this class add
21  *
22  * ->set('dev_oauth', 'your homecu_dev_oauth2_proxy cookie value')
23  *
24  * . . . or all your my.homecu.net CU's will show no trusted vendors.
25  *
26  * @param object $dbh database resource
27  * @param int $SYS_TYPE_UPG_TEST
28  * @param int $SYS_TYPE_CLOSED
29  * @return string HTML
30  */
32 {
33  /** @var object|null $dbh DB resource */
34  protected $dbh;
35 
36  /** @var int $SYS_TYPE_UPG_TEST S/B 128 */
37  protected $SYS_TYPE_UPG_TEST;
38 
39  /** @var int $SYS_TYPE_CLOSED S/B 64 */
40  protected $SYS_TYPE_CLOSED;
41 
42  /** @var bool $download obvious */
43  protected $download = false;
44 
45  /** @var string|null, see comments above */
46  protected $dev_oauth = null;
47 
48  /** @var string $cu_index for links on CU names */
49  protected $cu_index = 'hcuadm/cuindex.prg';
50 
51  /** @var string Remote server query url, prepended with protocol and server from below */
52  protected $cu_trusted_url = 'homecu.net/hcuadm/CuVendorListApi.prg';
53 
54  /** @var string $trusted_master the T.V. table name so it's not hard coded down in the code */
55  protected $trusted_master = 'cutrustedmaster';
56 
57  /** @var string $cu_admin the table containing the CU ID */
58  protected $cu_admin = 'cuadmin';
59 
60  /** @var string the table containing the CU Info, which is where we actually get the list */
61  protected $cu_info = 'cuinfo';
62 
63  /** @var array $cu_data store CU's and their T.V. data ['cu'] => [vendor array] */
64  protected $cu_data = [];
65 
66  /** @var array $trusted_vendors store T.V.'s internally for our headers and columns */
67  protected $trusted_vendors = [];
68 
69  /** @var array $external_servers to query - don't need dev www4, and no valid CU's on www */
70  protected $external_servers = ['my', 'www3', 'www5', 'www6'];
71 
72  /**
73  * All we're doing in the constructor is setting the download property if it exists in $_GET.
74  * @return void
75  * @throws Exception
76  */
77  public function __construct()
78  {
79  $this->InitDownloadFlag();
80  }
81 
82  /**
83  * Entry point.
84  * @throws Exception
85  * @return string (HTML) or csv (download)
86  */
87  public function VendorByCuReport()
88  {
89  $this
90  ->GetMasterData()
91  ->GetCuLocalData()
92  ->GetTvClients();
93 
94  $content =
95  $this->OutputReportHeader() .
96  $this->OutputReportBody() .
97  $this->OutputReportFooter();
98 
99  if ($this->IsDownload()) {
100  return $this->DownloadCsv($content);
101  }
102 
103  return $content;
104  }
105 
106  /**
107  * Because of this tightly coupled system that mixes logic and output, we need a
108  * pubic method to avoid HTML being printed if we are trying to download.
109  * @return bool
110  */
111  public function IsDownload()
112  {
113  return $this->download === true;
114  }
115 
116  /**
117  * If they've clicked download, set the flag.
118  * @return $this
119  */
120  protected function InitDownloadFlag()
121  {
122  if (isset($_GET['download'])) {
123  $this->download = true;
124  }
125 
126  return $this;
127  }
128 
129  /**
130  * Output the data for download.
131  * @param $content
132  * @return string
133  */
134  protected function DownloadCsv($content)
135  {
136  $length = strlen($content) + 1;
137  header("Content-type: bad/type");
138  header("Content-length: " . $length);
139  header("Content-Disposition: attachment; filename=\"vendor-by-cu-". date('Y-m-d') . ".csv\"");
140  return $content;
141  }
142 
143  /**
144  * Get the master data record.
145  * any DB query, count excepted, should always be an array.
146  * @return $this
147  * @throws Exception
148  */
149  protected function GetMasterData()
150  {
151  $sql = "select * from {$this->trusted_master} order by trustedvendor";
152  try {
153  $res = db_query($sql, $this->dbh);
154  } catch (Exception $e) {
155  throw new Exception("Exception getting master data: {$e->getMessage()} " . pg_last_error($this->dbh));
156  }
157 
158  while ($row = db_fetch_assoc($res)) {
159  $this->trusted_vendors[$row['trustedid']] = $row['trustedvendor'];
160  }
161 
162  return $this;
163  }
164 
165  /**
166  * Query each of $this->external_servers at the CuVendorListApi.prg URL for which T.V.'s are
167  * associated with which CU's. Set into the existing trusted vendors array.
168  * @return $this
169  */
170  protected function GetTvClients()
171  {
172  $trusted_arr = array_keys($this->trusted_vendors);
173 
174  foreach ($this->external_servers as $server) {
175 
176  $ext = $this->GetExternalTrustedData($server, $trusted_arr);
177  $this->MergeClientsToCuData($server, $ext);
178  }
179 
180  return $this;
181  }
182 
183  /**
184  * Get the CU data from info; we need the CU Id from cuadmin and and the trusted vendor data
185  * from cu trusted vendors. Join admin and vendors on info to get all CU's.
186  * @return $this
187  * @throws Exception
188  */
189  protected function GetCuLocalData()
190  {
191  try {
192  $res = db_query($this->LocalDataSql(), $this->dbh);
193 
194  while ($row = db_fetch_assoc($res)) {
195 
196  // Why the white space in DB values?
197  $cu = trim($row['cu']);
198 
199  if (! isset($this->cu_data[$cu]['vendor'])) {
200  $this->cu_data[$cu]['vendor'] = trim($row['vendor']);
201  $this->cu_data[$cu]['server'] = trim($row['www_server']);
202  $this->cu_data[$cu]['in_setup'] = $row['in_setup'];
203  }
204  }
205  } catch (Exception $e) {
206  throw new Exception("Exception getting CU data: {$e->getMessage()} " . db_last_error());
207  }
208 
209  return $this;
210  }
211 
212  /**
213  * Helper for GetCuLocalData().
214  * @return string
215  */
216  protected function LocalDataSql()
217  {
218  return "select"
219  ." coalesce ("
220  . " {$this->cu_admin}.cu,"
221  . " upper({$this->cu_info}.user_name),"
222  . " 'none'"
223  . " ) as cu,"
224  . "((coalesce({$this->cu_info}.system_options, 0) & {$this->SYS_TYPE_UPG_TEST}) <> 0)::int as in_setup,"
225  . " {$this->cu_info}.vendor as vendor,"
226  . " {$this->cu_info}.www_server as www_server"
227  . " from {$this->cu_info}"
228  . " left join {$this->cu_admin}"
229  . " on upper({$this->cu_admin}.user_name) = upper({$this->cu_info}.user_name)"
230  . " where {$this->cu_info}.system_options & {$this->SYS_TYPE_CLOSED} = 0"
231  . " order by {$this->cu_info}.user_name asc";
232  }
233 
234  /**
235  * Using the array of trusted ID's, query a server for the associated CU's.
236  * @param string $server
237  * @param array $ids
238  * @return array
239  */
240  protected function GetExternalTrustedData($server, $ids)
241  {
242  $params = http_build_query(['Cu' => '', 'trustedids' => json_encode($ids)]);
243  $protocol = ($server == 'localhost')? 'http' : 'https';
244  $cmd = "$protocol://$server.{$this->cu_trusted_url}";
245 
246  $ch = curl_init($cmd);
247  curl_setopt($ch,CURLOPT_COOKIE, $this->setCurlCookies($server));
248  curl_setopt($ch,CURLOPT_USERPWD,"nobody:no1home");
249  curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
250  curl_setopt($ch,CURLOPT_POST, true);
251  curl_setopt($ch,CURLOPT_POSTFIELDS, $params);
252  //curl_setopt($ch, CURLOPT_VERBOSE, true);
253 
254  $rawresp = curl_exec($ch);
255 
256  return HCU_JsonDecode($rawresp, true);
257  }
258 
259  /**
260  * Allows a dev cookie to test locally via the setter, see comments at head.
261  * @param string $server
262  * @return string
263  */
264  protected function setCurlCookies($server)
265  {
266  $cookies = "HCUTicket={$_COOKIE['HCUTicket']};";
267 
268  if ($server == 'my') {
269 
270  if ($this->dev_oauth) {
271  $cookies .= ";homecu_dev_oauth2_proxy={$this->dev_oauth}";
272  return $cookies;
273  }
274 
275  if (isset($_COOKIE['homecu_dev_oauth2_proxy'])) {
276  $cookies .= ";homecu_dev_oauth2_proxy={$_COOKIE['homecu_dev_oauth2_proxy']}";
277  return $cookies;
278  }
279  }
280 
281  return $cookies;
282  }
283 
284  /**
285  * What it says, merge external server results into $this->cu_data.
286  * @param string $server
287  * @param array $external_data
288  * @return $this
289  */
290  protected function MergeClientsToCuData($server, $external_data)
291  {
292  if (! $this->isSuccessResult($external_data)) {
293  return $this;
294  }
295 
296  foreach ($external_data['data'] as $key => $vendor_arr) {
297 
298  // Funky key value, but it allows us to not have to walk the array. See comments.
299  list ($cu, $vendor) = explode('|', $key);
300 
301  if (
302  ! $this->isRealServer($server, $cu) ||
303  ! $this->CuExistsInData($cu) ||
304  $this->VendorExistsInData($cu, strtoupper($vendor))
305  ) {
306  continue;
307  }
308 
309  // Store as all upper and then do strtoupper on output so we always have a match.
310  $this->cu_data[$cu]['trustedid'][] = strtoupper($vendor);
311  }
312 
313  return $this;
314  }
315 
316  /**
317  * When querying external servers, there may be old implementations of the current CU. The
318  * local record will always tell us what the **real** server is. Use to ignore others.
319  * @param string $server
320  * @param string $cu
321  * @return bool
322  */
323  protected function isRealServer($server, $cu)
324  {
325  if ($server == 'my') {
326  $server .= '.homecu.net';
327  }
328 
329  if (isset($this->cu_data[$cu]['server'])) {
330  return $this->cu_data[$cu]['server'] == $server;
331  }
332 
333  return false;
334  }
335 
336  /**
337  * Helper for MergeClientsToCuData(), make sure the array members we need are all present.
338  * @param array $result
339  * @return bool
340  */
341  function isSuccessResult($result)
342  {
343  return
344  array_key_exists('status', $result) &&
345  array_key_exists('Response', $result['status']) &&
346  ($result['status']['Response'] == true) &&
347  array_key_exists('data', $result) &&
348  is_array($result['data']);
349  }
350 
351  /**
352  * Helper for MergeClientsToCuData(). Does this CU exist in $this->cu_data?
353  * @TODO Hmm, might we need to use this to create a new node?
354  * @param string $cu
355  * @return bool
356  */
357  protected function CuExistsInData($cu)
358  {
359  return array_key_exists($cu, $this->cu_data);
360  }
361 
362  /**
363  * Helper for MergeClientsToCuData(). Vendor already in the cu_data array? The external
364  * query may return duplicates, and we've already ensured the cu_data trustedid member
365  * is an array.
366  * @param string $cu
367  * @param string $vendor
368  * @return bool
369  */
370  protected function VendorExistsInData($cu, $vendor)
371  {
372  if (
373  isset($this->cu_data[$cu]['trustedid']) &&
374  is_array($this->cu_data[$cu]['trustedid'])
375  ) {
376  return in_array($vendor, $this->cu_data[$cu]['trustedid']);
377  }
378 
379  return false;
380  }
381 
382  /**
383  * Walk the trusted vendors and output the report head.
384  * @return string
385  */
386  protected function OutputReportHeader()
387  {
388  return ($this->IsDownload())? $this->CsvHeader() : $this->HtmlHeader();
389  }
390 
391  /**
392  * Helper for OutputReportHeader(), prepare the text header for download.
393  * @return string
394  */
395  protected function CsvHeader()
396  {
397  $header = '"Credit Union"' . ',';
398 
399  foreach ($this->trusted_vendors as $vendor_id => $name) {
400  $header .= '"' . $vendor_id . '",';
401  }
402 
403  return rtrim($header, "\t") . PHP_EOL;
404  }
405 
406  /**
407  * Helper for OutputReportHeader(), prepare the HTML output header.
408  * @return string
409  */
410  protected function HtmlHeader()
411  {
412  $id = 1;
413 
414  // looks bad if we print "Credit Union" in first col, no reason to overstate the obvious.
415  $header = "
416  <p style=\"text-align:center\"><strong><em>If the CU does not have trusted vendors listed,
417  there are no trusted vendors set up in their Trusted Details.</em></strong></p>
418  <table id=\"report-by-vendor-table\">
419  <tr>
420  <th id=\"tv-hd-$id\" class=\"cu-name\">&nbsp;</th>
421  ";
422 
423  foreach ($this->trusted_vendors as $vendor_id => $name) {
424 
425  $id++;
426  $title = (! empty($name))? $name : str_replace('_', ' ', $vendor_id);
427 
428  $header .= "
429  <th id=\"tv-hd-$id\" class=\"rotate\" title=\"$title\"><div><span>"
430  . str_replace('_', ' ', $vendor_id) . "</span></div></th>";
431  }
432 
433  $header .= '
434  </tr>
435  ';
436 
437  return $header;
438  }
439 
440  /**
441  * Output the body HTML.
442  * @throws Exception
443  * @return string
444  */
445  protected function OutputReportBody()
446  {
447  return ($this->IsDownload())? $this->CsvBody() : $this->HtmlBody();
448  }
449 
450  /**
451  * Output the body for CSV download.
452  * @return string
453  */
454  protected function CsvBody()
455  {
456  $body = null;
457 
458  foreach ($this->cu_data as $cu => $trusted_array) {
459  $body .= $this->OutputCsvDataRow($cu);
460  }
461 
462  return $body;
463  }
464 
465  /**
466  * Output a row of data for download.
467  * @param string $cu
468  * @return string|null
469  */
470  protected function OutputCsvDataRow($cu)
471  {
472  $row = '"' . $cu . '",';
473 
474  foreach ($this->trusted_vendors as $vendor_id => $name) {
475 
476  $used = $this->setCsvCellContent($vendor_id, $cu);
477  $row .= '"' . $used . '",';
478  }
479 
480  $row = rtrim($row, ',') . PHP_EOL;
481 
482  return $row;
483  }
484 
485  /**
486  * Helper for OutputCsvDataRow(), make the cell content.
487  * @param string $vendor_id
488  * @param string $cu
489  * @return string YES|-
490  */
491  protected function setCsvCellContent($vendor_id, $cu)
492  {
493  if (
494  isset($this->cu_data[$cu]['trustedid']) &&
495  is_array($this->cu_data[$cu]['trustedid']) &&
496  in_array(strtoupper($vendor_id), $this->cu_data[$cu]['trustedid'])
497  ) {
498  return 'YES';
499  }
500 
501  return '-';
502  }
503 
504  /**
505  * Helper for OutputReportBody(), return the body for HTML output.
506  * @return string
507  */
508  protected function HtmlBody()
509  {
510  $body = null;
511  $toggle = 'odd';
512 
513  foreach ($this->cu_data as $cu => $vendors_array) {
514  $toggle = ($toggle == 'even')? 'odd' : 'even';
515 
516  $body .= $this->OutputHtmlDataRow($cu, $toggle);
517  }
518 
519  return $body;
520  }
521 
522  /**
523  * Output a row of data from tr to /tr.
524  * @param string $cu
525  * @param string $toggle even|odd
526  * @return string|null
527  */
528  protected function OutputHtmlDataRow($cu, $toggle)
529  {
530  $link = $this->GetCuEditLink($cu);
531 
532  $row = "
533  <tr class=\"$toggle\">
534  <td><a href=\"$link\">$cu</a></td>";
535 
536  foreach ($this->trusted_vendors as $vendor_id => $name) {
537 
538  list ($class, $txt, $usage) = $this->SetCellAttributes($cu, $vendor_id);
539  $row .= "<td class=\"$class\" title=\"$vendor_id $usage by $cu\">$txt</td>";
540  }
541 
542  $row .= '
543  </tr>
544  ';
545 
546  return $row;
547  }
548 
549  /**
550  * Set a link from the HTML display to CU info.
551  * @param string $cu
552  * @return string
553  */
554  protected function GetCuEditLink($cu)
555  {
556  if (! (
557  isset($this->cu_data[$cu]['vendor']) &&
558  isset($this->cu_data[$cu]['server'])
559  )) {
560  return '#';
561  }
562 
563  return $this->SetServerProtocol() . "?action=fetch&rowid=$cu"
564  . "&vc={$this->cu_data[$cu]['vendor']}"
565  . "&wc={$this->cu_data[$cu]['server']}";
566  }
567 
568  /**
569  * Going to try this without HB_ENV. If it causes problems, we'll inject it.
570  * @return string
571  */
572  protected function SetServerProtocol()
573  {
574  $protocol = (
575  ($_SERVER['REQUEST_SCHEME'] != 'https') &&
576  (getenv('HTTP_X_FORWARDED_PROTO') != 'https')
577  ) ?
578  'http' : 'https';
579 
580  return $protocol . '://' . strtolower($_SERVER['HTTP_HOST']) . '/' . $this->cu_index;
581 
582  }
583 
584  /**
585  * Helper for outputDataRow.
586  * @param string $cu
587  * @param string $vendor_id
588  * @return array
589  */
590  protected function SetCellAttributes($cu, $vendor_id)
591  {
592  if (
593  isset($this->cu_data[$cu]['trustedid']) &&
594  is_array($this->cu_data[$cu]['trustedid']) &&
595  in_array(strtoupper($vendor_id), $this->cu_data[$cu]['trustedid'])
596  ) {
597  return ['used', '&#10004;', 'used'];
598  }
599 
600  return ['not-used', 'x', 'not used'];
601 
602  }
603 
604  /**
605  * Simple single responsibility or, do we want footer data and row here?
606  * @return string|null;
607  */
608  protected function OutputReportFooter()
609  {
610 
611  if ($this->IsDownload()) {
612  return null;
613  }
614 
615  $count = count($this->trusted_vendors) + 1;
616 
617  return '
618  <tr>
619  <td id="vendor-by-cu-footer" colspan="' . $count . '">
620  <a href="?download" target="_blank">Download</a>
621  </td>
622  </tr>
623  </table>
624  ';
625  }
626 
627  /**
628  * Typical setter
629  * @param string $prop
630  * @param mixed $value
631  * @return $this
632  */
633  public function set ($prop, $value)
634  {
635  $this->{$prop} = $value;
636  return $this;
637  }
638 
639 }
640 
VendorByCuReport()
Definition: VendorByCu.i:87
SetServerProtocol()
Definition: VendorByCu.i:572
GetCuLocalData()
Definition: VendorByCu.i:189
isSuccessResult($result)
Definition: VendorByCu.i:341
setCurlCookies($server)
Definition: VendorByCu.i:264
SetCellAttributes($cu, $vendor_id)
Definition: VendorByCu.i:590
GetMasterData()
Definition: VendorByCu.i:149
__construct()
Definition: VendorByCu.i:77
isRealServer($server, $cu)
Definition: VendorByCu.i:323
OutputReportFooter()
Definition: VendorByCu.i:608
OutputReportBody()
Definition: VendorByCu.i:445
setCsvCellContent($vendor_id, $cu)
Definition: VendorByCu.i:491
DownloadCsv($content)
Definition: VendorByCu.i:134
VendorExistsInData($cu, $vendor)
Definition: VendorByCu.i:370
OutputHtmlDataRow($cu, $toggle)
Definition: VendorByCu.i:528
CuExistsInData($cu)
Definition: VendorByCu.i:357
OutputCsvDataRow($cu)
Definition: VendorByCu.i:470
OutputReportHeader()
Definition: VendorByCu.i:386
InitDownloadFlag()
Definition: VendorByCu.i:120
MergeClientsToCuData($server, $external_data)
Definition: VendorByCu.i:290
GetCuEditLink($cu)
Definition: VendorByCu.i:554
GetExternalTrustedData($server, $ids)
Definition: VendorByCu.i:240
GetTvClients()
Definition: VendorByCu.i:170
LocalDataSql()
Definition: VendorByCu.i:216