Link to home
Start Free TrialLog in
Avatar of Bruce Gust
Bruce GustFlag for United States of America

asked on

How does this login script produce the view that you see after everything is validated (Part I)?

I don't want to settle for "that" it works. I want to know "why" it works. That being the philosophical starting point, here's what I've got:

This is the login page:

User generated image
..and here's the script for that page:

<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class PatientPortal extends MY_Controller {

	private $debug = false; 		 	//Setting this to true will trigger a krumo of the session in a small console window at the bottom of your screen.
	private $payment_threshold = 30; 	// The minimum payment we will take through the portal

	public function index(){
		//$this->pay();
		//$this->login();
	}








	/** Login Function ----------------------------------------------------------------------------------------------------------
		*
		*
		*	This endpoint serves the login form of the patient portal.
		*

			|	In the case that there is no session variable 'guarantor_session' set (and we're not accessing via an admin subdomain),
			|	this becomes the root endpoint

		*
		*	Posting in all of these variables will treat this as a login attempt and will try to validate the data:
		*		pin - The Statement Pin / Accountid associated with an account.
		*		lastfour - The last four digits of the guarantor OR patient associated with the account or with a charge
		*		year - The birth year of the guarantor OR patient associated with the account or with a charge
		*
		*	Sending in a pin variable in your access assumes that you've clicked this link from an email and will autopopulate and lock the statement pin field.
		*
		*
	-----------------------------------------------------------------------------------------------------------------------------*/

	public function login($pin = ''){
		//Start with client colors
		$h = explode('.',$_SERVER['HTTP_HOST']);
		$clientcode = $h[0];
		$client = Clients::getByCode($clientcode);
		$clientid = '-1';
        session_start();
		if($client) $clientid = $client['ClientID'];

		$_SESSION['colors'] = ClientPreferences::getClientColors($clientid);

		//$this->setSessionData($accountid);
		//if(isset($_SESSION['guarantor_session'])) redirect('/patientportal/pay'); //On the offchance this is accessed directly with a session, reroute to pay.
		$lockpin = (isset($_REQUEST['lockpin']) && $_REQUEST['lockpin'] === "0")?false:($pin?true:false);
		$pin = $pin?$pin:(isset($_REQUEST['pin'])? $_REQUEST['pin'] : false);
		$lastfour = isset($_REQUEST['lastfour'])? $_REQUEST['lastfour'] : false;
		$year = isset($_REQUEST['year'])? $_REQUEST['year'] : false;
		$redirect = isset($_REQUEST['uri'])?$_REQUEST['uri']:'/';

		$data = array(
			'lockpin' => $lockpin,
			'pin' => $pin,
			'lastfour' => $lastfour,
			'year' => $year,
			'redirect' => $redirect,
			'debug' => $this->debug,
            'new_statement' => ClientPreferences::getClientPreference($clientid, 'NewStatements')
			);

		if($pin && $lastfour && $year){
			//If these variables are all set, assume validation attempt.

			//1 -> get accountid from pin
			if(strpos($pin,'T') !==false || strpos($pin,'t') !==false){
				$statementid = ltrim($pin,"Tt");
				$_SESSION['statementid'] = $statementid;
				$accountid = Accounts::getAccountidFromStatementid($statementid);
			}else{
				$accountid = $pin;
			}

			//2 -> use accountid to get accountdetails
			$accounts = Accounts::getAccountInfoFromPin($accountid);
			//krumo($accounts); exit();
			$ssns = array();
			$dobs = array();

			if($accounts)foreach($accounts as $account){
				//NOTE the getAccountInfoFromPin function returns 3 records, trying to coalesce
				//the information from Patient, Claim, and txn tables.
				//I'm organizing the data here so I can run an in_array on the fields.
				if($account['SSN'] && !in_array($account['SSN'], $ssns)) array_push($ssns, $account['SSN']);
				if($account['PatientDOB'] && !in_array($account['PatientDOB'], $dobs)) array_push($dobs, substr($account['PatientDOB'], 0,4));
			}
	  		//$details = Accounts::getDetails($accountid); <-- Might not need this
	  		//3 -> Check to see if account CAN be verified
	  		if($accounts && count($ssns) && count($dobs)){

	  			//4 -> Check to make sure our data = the provided data
	  			if(in_array($lastfour, $ssns)  && in_array($year, $dobs)){

	  				//Validated!!!
	  				
	  				//Guarantor Session
	  				$this->setSessionData($accountid);
	  				$data['clientcode'] = $_SESSION['guarantor_session']['clientcode'];

	  				//Are we on the right domain?
	  				$host = explode('.',$_SERVER['SERVER_NAME']);
	  				$sub = $host[0];

	  				if(strtolower($sub) == strtolower($data['clientcode']) || base_url() == 'https://dig.local/' || base_url() == 'https://localhost:8080/' || base_url() == 'https://digdev.patientfocus.com/' || base_url() == 'https://dig2.patientfocus.com/' || base_url() == 'https://staging.patientfocus.com/'){
	  					//This is the correct domain.
		              	redirect($redirect);

	  				} else {
	  					//Destroy existing session.
	  					unset($_SESSION['guarantor_session']);
						unset($_SESSION['userdata']);
						unset($_SESSION['roles']);
						unset($_SESSION['rights']);
						unset($_SESSION['colors']);
						unset($_SESSION['statementid']);
	  					session_destroy();

	  					//Post into the correct subdomain.
	  					$this->load->view('patientportal/subdomaingate',$data);
	  				}

	  				
	  				//exit();
	  			}

	  		} else if($accounts) {
	  			//Account cannot be verified, Give a specific call to action.
	  			$data['errorheader'] = 'Account Incomplete';
	  			$data['errormessage'] = 'There is not enough information in your account to verify your identity through the online portal. If you would like to access your account online, please <a class="cantlogin">let us know</a>, and one of our Patient Account Representatives will help you complete your account.';
	  		}

		}
		if($client){
			$data['clientcode'] = $clientcode;
			$data['clientname'] = $client['ClientName'];
		}
        session_write_close();
		$this->load->view('patientportal/login', $data);
	}







	/** Pay Function ----------------------------------------------------------------------------------------------------------
		*
		*
		*	This endpoint serves the ad hoc payment form of the patient portal.
		*

			|	In the case that there IS a session variable 'guarantor_session' set (and we're not accessing via an admin subdomain),
			|	this becomes the root endpoint

		*
		*	Posting in all of these variables will treat this as a payment attempt and will try to process the data:
		*
			REQUIRED (for any form processing)
		*
		*		paymentamount - This is either a dollar amount or a 'specialamount' flag, which then looks to the 'specialamount' manually entered field for the payment amount.
		*		specialamount - Should paymentamount = the string 'specialamount', this process pulls the value from the specialamount variable.
		*		email - the email address of the user, required for sending a receipt.
		*		cardorbank - Set to the string 'card' if this is a credit card process. Set to 'bank' if it's an echeck
		*
			REQUIRED FOR CREDIT CARD (if cardorbank = 'card')
		*		
		*		cardnum - The number on the front of your card. IF THIS BEGINS WITH A * then we process the payment off the stored BRIC if it is available
		*		cvcode - The 3-4 digit security code on the back of the card.
		*		cardname - The name on the front of the card
		*		cardmonth - the two digit card expiration month
		*
			REQUIRED FOR E CHECK (if cardorbank = 'bank')
		*		
		*		accountnum - the bank account number (in the case that this starts with *, use the associated bank BRIC.
		*		routingnumb - the routing number of the bank. If we're using a brick, Use the bric's associated routing number.
		*		accname - The name on the account.
		*
		*

			ON FAILED FORM PROCESSING,
		*		Reload the form view, populated where you left off, and provide an error that states the problem clearly.
		*
			ON SUCCESS,
		*		Load the payment success view.
		*
		*
	---------------------------------------------------------------------------------------------------------------------------*/


	public function pay($refreshdata = true){

		$data = $this->getData($refreshdata);

		if(isset($_REQUEST['paymentamount'])){

			//Process the payment
			$success = false;
			$amount = $_REQUEST['paymentamount'] == 'specialamount' ? str_replace('$', '', $_REQUEST['specialamount']) : $_REQUEST['paymentamount'];
			$amount = number_format($amount,2,'.','');
			$email = isset($_REQUEST['email'])? $_REQUEST['email'] : false;

			$createdby = $_SESSION['userdata']['id'];

			if($amount >= $data['minpayment']){
				if($amount <= $data['lefttopay']){	
					if($_REQUEST['cardorbank'] == "card"){
						$cardinfo = $data['cardinfo'];

						//Process as Credit Card
						$cardnum = isset($_REQUEST['cardnum'])? $_REQUEST['cardnum'] : false; //ACCOUNT FOR ************XXXX STYLE ENTRIES - IMPLYING TO USE THE CARD ON FILE.
						$bric = substr($cardnum,0,1) == '*' && $cardinfo?$cardinfo['bric']:false;
						$cv = isset($_REQUEST['cvcode'])? $_REQUEST['cvcode'] : false;
						$cardname = $bric?$cardinfo['cardname']:(isset($_REQUEST['name'])? $_REQUEST['name'] : false);
						$cardmonth = isset($_REQUEST['cardmonth'])? $_REQUEST['cardmonth'] : false;
						$cardyear = isset($_REQUEST['cardyear'])? $_REQUEST['cardyear'] : false;
						$cardexp = $this->getEPXExpirationString($cardmonth, $cardyear);					

							//Ensure that the pieces are all available.
							if( ($bric || $cardnum) && $cv && $amount && $cardname && $cardexp && $data['accountid'] && $email){
								if(!$bric || ($cardexp == $cardinfo['cardexp'] && $cv == $cardinfo['cardcv'])){

									if(filter_var($email,FILTER_VALIDATE_EMAIL)){
										if(strtolower($email) != strtolower($data['email'])) Accounts::update($data['accountid'], array(array('field'=>'email','newvalue'=>$email)));

										//PROCESS THE EPX TRANSACTION AND TURN THE RESULTING XML INTO AN ARRAY
										$xmlstr = '';

										if($bric){
											//PROCESS FROM BRIC
											$xmlstr = $this->epx->processCreditCardBRIC($bric, $cv, $amount, $cardname, $cardexp,array('AccountID'=>$data['accountid']),$data['dbanbr']);
										} else {
											//PROCESS FROM CARD NUMBER
											$xmlstr = $this->epx->processCreditCard($cardnum, $cv, $amount, $cardname, $cardexp,array('AccountID'=>$data['accountid']),$data['dbanbr'],$data['address']);
										}
										$xmlobj = simplexml_load_string($xmlstr);
										$xmlarr = array();
										if($xmlobj) foreach($xmlobj->FIELDS->FIELD as $field){
							              $xmlarr[(string)$field->attributes()->KEY] = (string)$field;
							            }
							           	
							           	if(isset($xmlarr['AUTH_RESP']) && $xmlarr['AUTH_RESP'] == '00'){
							           		/**
							           			Successfully paid on card
							           		**/
							           		//This is for the paymentmethodcode.
							           		$this->load->library('CreditCard');
							           		$card = $bric?array('abbr'=>$cardinfo['cardabbr'],'name' => 'card'):$this->creditcard->cardNameFromNumber($cardnum);
							           		$cardfour = substr($cardnum, strlen($cardnum)-4);

							           		$description = 'Credit card ending in ' . $cardfour;

							           		//Create the paymentpending entry.
							           		$paymentpending = PaymentPendings::addPaymentPending(date('Y-m-d H:i:s'), $xmlarr['AMOUNT'],$card['abbr'], $data['accountid'], $data['patientid'], 'PR', 'PT', $createdby, $xmlarr['AUTH_GUID'], $xmlarr['AUTH_CODE'],$xmlarr['AUTH_RESP'],$description);
								            if($paymentpending){
								            	$success = true;
								            	$data['successcardtype'] = $card['name'];
								            	$data['successemail'] = $email;
								            	$data['successcardlastfour'] = $cardfour;
								            	$data['confirmation'] = $paymentpending['PaymentPendingID'];
								            }

							           	} else {
							           		//Error -> epx response
							           		$data['errorcode'] = isset($xmlarr['AUTH_RESP'])? $xmlarr['AUTH_RESP'] : 'PF4';
						           			$data['errormsg'] = isset($xmlarr['AUTH_RESP_TEXT'])?ucfirst($xmlarr['AUTH_RESP_TEXT']):'No response from EPX.';

							           	}
							        } else {
							        	$data['errorcode'] = 'PF8';
										$data['errormsg'] = 'Email invalid.';
							        }
							    } else {
					           		//Error -> CARDEXP DOESN'T MATCH ONE ON FILE OR CCV DOESN'T MATCH
					           		$data['errorcode'] = 'PF5';
				           			$data['errormsg'] = 'Card validation mismatch.';

					           	}
							} else {
								//Error Incomplete Form.
								$data['errorcode'] = 'PF3';
								$data['errormsg'] = 'Payment form incomplete.';
							}

						//$this->epx->processCreditcard();

					} else if ($_REQUEST['cardorbank'] == "bank"){
						$bankinfo = $data['bankinfo'];

						//Process as E Check
						$accnum = isset($_REQUEST['accountnum'])? $_REQUEST['accountnum'] : false; //ACCOUNT FOR ************XXXX STYLE ENTRIES - IMPLYING TO USE THE CARD ON FILE.
						$bric = substr($accnum,0,1) == '*' && $bankinfo?$bankinfo['bric']:false;
						$name = $bric?$bankinfo['accname']:(isset($_REQUEST['account_name'])? $_REQUEST['account_name'] : false);
						$routing = $bric?$bankinfo['accrouting']:(isset($_REQUEST['routingnumb'])? $_REQUEST['routingnumb'] : false);
						//$bankname = $bric?$bankinfo['accbankname']:(isset($_REQUEST['cardmonth'])? $_REQUEST['cardmonth'] : false);

						//Ensure that the pieces are all available.
						if(($bric || $accnum) && $routing && $amount && $name && $data['accountid'] && $email){
							if(filter_var($email,FILTER_VALIDATE_EMAIL)){
								if(strtolower($email) != strtolower($data['email'])) Accounts::update($data['accountid'], array(array('field'=>'email','newvalue'=>$email)));
								//PROCESS THE EPX TRANSACTION AND TURN THE RESULTING XML INTO AN ARRAY
								$xmlstr = '';
								if($bric){
									//PROCESS FROM BRIC
									$xmlstr = $this->epx->processCheckBRIC($bric, $routing, $amount, $name, array('AccountID'=>$data['accountid']),$data['dbanbr']);
								} else {
									//PROCESS FROM ACC NUMBER
									$xmlstr = $this->epx->processCheck($accnum, $routing, $amount, $name, array('AccountID'=>$data['accountid']),$data['dbanbr'],$data['address']);
								}
								$xmlobj = simplexml_load_string($xmlstr);
								$xmlarr = array();
								if($xmlobj) foreach($xmlobj->FIELDS->FIELD as $field){
					              $xmlarr[(string)$field->attributes()->KEY] = (string)$field;
					            }
					           	
					           	if(isset($xmlarr['AUTH_RESP']) && $xmlarr['AUTH_RESP'] == '00'){
					           		/**
					           			Successfully paid from bank
					           		**/
					           		$description = 'Bank account ending in ' . substr($accnum,-4);
					           		//Create the paymentpending entry.
					           		$paymentpending = PaymentPendings::addPaymentPending(date('Y-m-d H:i:s'), $xmlarr['AMOUNT'],'EF', $data['accountid'], $data['patientid'], 'PR', 'PT', $createdby, $xmlarr['AUTH_GUID'], $xmlarr['AUTH_CODE'],$xmlarr['AUTH_RESP'], $description);
						            if($paymentpending){
						            	$success = true;
						            	$data['successcardtype'] = 'bank account';
						            	$data['successemail'] = $email;
						            	$data['successcardlastfour'] = substr($accnum,-4);
						            	$data['confirmation'] = $paymentpending['PaymentPendingID'];
						            }

					           	} else {
					           		//Error -> epx response
					           		$data['errorcode'] = isset($xmlarr['AUTH_RESP'])? $xmlarr['AUTH_RESP'] : 'PF4';
					           		$data['errormsg'] = isset($xmlarr['AUTH_RESP_TEXT'])?ucfirst($xmlarr['AUTH_RESP_TEXT']):'No response from EPX.';

					           	}
					        } else {
						        	$data['errorcode'] = 'PF8';
									$data['errormsg'] = 'Email invalid.';
						    }
						} else {
							//Error Incomplete Form.
							$data['errorcode'] = 'PF3';
							$data['errormsg'] = 'Payment form incomplete.';
						}

					} else {
						//Error as unrecognized type
						$data['errorcode'] = 'PF1';
						$data['errormsg'] = 'Could not process transaction type';
					}
				} else {

					//Error as Amount above Maximum
					$data['errorcode'] = 'PF2';
					$data['errormsg'] = 'Can\'t pay more than you owe.';
				}
			} else {

				//Error as Amount Below Minimum
				$data['errorcode'] = 'PF2';
				$data['errormsg'] = 'Amount below the minimum ' . $data['minpaymentformatted'] . ' payment.';
			}

			if($success){
				//On Success load the next view.
				$data['successamount'] = $amount;
				$data = array_merge($data, $this->getData());
				$this->load->library('Mandrill');

				$message['html'] = $this->load->view('email/receipt',$data,true);
				$message['text'] = "Thank you! Your payment of \$$amount was successfully charged to your $data[successcardtype] ending in $data[successcardlastfour]. Your confirmation number is $data[confirmation].";
				$message['subject'] = "Your payment has been received!";
				$message['from_email'] = 'noreply@patientfocus.com';
				$message['from_name'] = $data['clientname'];
				$message['to'][0] = array('email' => $email);

				$mandrill_response = $this->mandrill->sendemail($message);
				if($mandrill_response){

					$e['templateid'] = 0;
					$e['content'] = pfsql::scrub($message['html']);
					$e['email'] = pfsql::scrub($email);
					$e['systememail'] = $message['from_email'];
					$e['response'] = pfsql::scrub(json_encode($mandrill_response));
					$e['direction'] = 'outbound';
					$e['accountid'] = $data['accountid'];
					$data['receiptemailed'] = true;

					$f = Emails::create($e);

				} else $data['receiptemailed'] = false;

				//echo json_encode($mandrill_response); exit();


				$data['view'] = 'paymentsuccess';
			} else {
				//On Fail load same view.
				$data['view'] = 'makeapayment';
			}			

		} else if($data['lefttopay'] <= 0){
			$data['view'] = 'creditbalance';
		}else{
			$data['view'] = 'makeapayment';
		}

		$this->loadView($data);

	}

    protected function getEPXExpirationString($month, $year) {
        
        $exp = false;
        //Month should be 2 digits.
        if($month && $year) {

            $month = str_pad($month, 2, '00', STR_PAD_LEFT);
            $year = substr($year, -2);
        
            if (strlen($year) === 2 && strlen($month) === 2) {
                $exp = $year . $month;
            }

        }
        
        return $exp;
    
    }





	/** PaymentPlan ---------------------------------------------------------------------------------------------------------------------------

		*
		*
		*	This function handles Payment Plans
		*
		*
		*

	--------------------------------------------------------------------------------------------------------------------------- */

	public function paymentplan(){

		$data = $this->getData();

		if(!$data['paymentplan'] && $data['lefttopay'] < 90){
			
			//This is only accessible if you have an active payment plan OR if you have a balance >= 90 dollars.
			//redirect home
			header ("location: /");

		}else{

			//are we processing a form?
			if(isset($_REQUEST['cardorbank'])){ //we are processing a form.

				//Declare Needed Vars
				$payment_amount; 				//NUMBER - The amount of the monthly payment. Must be greater or equal to the minimum payment.
				$is_new_plan;					//BOOL   - If there is an existing paymentplan, this is false. Otherwise this is true.
				$is_autodraft; 					//BOOL 	 - If this is true we are autodrafting.
				$autodraft_type; 				//STRING - either "card" or "bank"
				$bank = array();			//ARRAY
					$bank['name']		= null; //STRING - The name on the account
					$bank['accountnum'] = null;	//NUMBER - The account number
					$bank['acclastfour']= null; //NUMBER - The last four of the account number
					$bank['routingnum'] = null;	//NUMBER - The routing number
				$card = array();			//ARRAY
					$card['name']		= null; //STRING - The name on the card
					$card['number']		= null; //NUMBER - The number on the front of the card
					$card['lastfour']	= null; //NUMBER - The last four of the card number
					$card['exp']		= null; //NUMBER - The expiration date in YYMM format
					$card['cv']			= null; //NUMBER - The three or four digit security code on the back of the card
				$bric;							//STRING - The AUTH GUID provided by EPX
				$plan_date; 					//NUMBER - Between 1 and 28 (inclusive), the date the payment is owed or autodrafted
				$email;							//STRING - User submitted email
				$plan_starting_balance;			//NUMBER - The balance at the start of this particular payment plan. In the case that there is an
				$number_of_payments;			//NUMBER - The number of payments there should be.
				$payment_method_code;			//STRING - EF for check, PP for promise to pay, Card specific versions for card autodrafts.
				$pmtdetails = array();			//ARRAY  - Formatted for payment plan creation function.

				//Collect Data and validate form.
				try	{
					$is_new_plan = $data['paymentplan'] === false ?
						true
					  : false;

					$payment_amount = $is_new_plan ?
						$_REQUEST['paymentplanamount']
					  : $data['paymentplan']['paymentamount'];
					if($payment_amount < $data['minpayment'])
						throw new Exception('Payment Plan amount below minimum '. $data['minpaymentformatted'] . ' payment amount.',1);

					$plan_starting_balance = $is_new_plan ?
						$data['lefttopay']
					  : $data['paymentplan']['startingbalance'];

					$number_of_payments = ceil( $data['lefttopay'] / floatval($payment_amount) );
					$pmtdetails['NbrOfPayments'] = $number_of_payments;

					$is_autodraft = isset($_REQUEST['autodraft']) ?
						true
					  : false;

					$plan_date = $is_autodraft ?
						$_REQUEST['autodraftdate']
					  : (
					  	$is_new_plan ? // If it IS NOT AN AUTODRAFT and it IS A NEW PLAN, set it to today, otherwise if it IS NOT A NEW PLAN, set it to the stored value.
					  		(
					  			intval(Date('d')) > 28? 1 : Date('d')
					  		)
					  	  : $data['paymentplan']['duedate']
					  	);
					if(intval($plan_date) > 28 or intval($plan_date) < 1)
						throw new Exception('Plan date out of range', 2);

					if($is_autodraft){
						if(isset($_REQUEST['email'])){
							$email = $_REQUEST['email'];
						} else throw new Exception('Incomplete Form', 4);
						if(filter_var($email,FILTER_VALIDATE_EMAIL)) Accounts::update($data['accountid'], array(array('field'=>'email','newvalue'=>$email)));
						else throw new Exception('Email Invalid', 8);
					}

					if($is_autodraft){
						switch($_REQUEST['cardorbank']){
							case 'card':
								$autodraft_type = 'card';
								//collect card values
								if(substr($_REQUEST['cardnum'],0,1) == '*' && $data['cardinfo']){
									//assume stored number
									$cardinfo = $data['cardinfo'];

									$card['name'] 		 = $cardinfo['cardname'];
									$card['lastfour'] 	 = $cardinfo['cardlastfour'];
									$card['exp'] 		 = $cardinfo['exp'];
									$card['cv'] 		 = $cardinfo['cardcv'];
									$bric				 = $cardinfo['bric'];
									$payment_method_code = $cardinfo['cardabbr'];

								} else {
									//Check credit card entries
									if(
										isset($_REQUEST['name']) 	 && $_REQUEST['name']
									 && isset($_REQUEST['cardnum'])  && $_REQUEST['cardnum']
									 && isset($_REQUEST['cardmonth'])&& $_REQUEST['cardmonth']
									 && isset($_REQUEST['cardyear']) && $_REQUEST['cardyear']
									 && isset($_REQUEST['cvcode']) 	 && $_REQUEST['cvcode']
									) {
										//use provided number, preauth required.
										$card['name'] 		= $_REQUEST['name'];
										$card['number'] 	= $_REQUEST['cardnum'];
										$card['lastfour']	= substr($card['number'],strlen($card['number'])-4);
										$card['exp'] 		= substr($_REQUEST['cardyear'],2,2) . $_REQUEST['cardmonth'];
										$card['cv'] 		= $_REQUEST['cvcode'];

										$this->load->library('CreditCard');
										$exp = $this->parseEPXResponse($this->epx->preAuthCreditCard($card['number'],$card['cv'],'.01',$card['name'],$card['exp'],$data['accountid'],$data['dbanbr'],$data['address']));
										if(isset($exp['AUTH_RESP']) && $exp['AUTH_RESP'] == '00'){

											//SUCCESSFULLY PREAUTHED!
											$extra = $this->creditcard->cardNameFromNumber($card['number']);
											$payment_method_code = $extra['abbr'];
											$bric = $exp['AUTH_GUID'];

										} else {
											throw new Exception (
												isset($exp['AUTH_RESP_TEXT'])?ucfirst($exp['AUTH_RESP_TEXT']).'.':'Communication error, unable to preauthorize your card.',
												isset($exp['AUTH_RESP'])?intval('90'.$exp['AUTH_RESP']):5
												);
										}

									} else throw new Exception('Incomplete Form', 4);
								}

								//pmtdetails object.

								$pmtdetails['ccname'] = pfsql::scrub($card['name']);
								$pmtdetails['cclast4']= pfsql::scrub($card['lastfour']);
								$pmtdetails['ccv2']	  = pfsql::scrub($card['cv']);
								$pmtdetails['ccyear'] = pfsql::scrub(substr($card['exp'],0,2));
								$pmtdetails['ccmonth']= pfsql::scrub(substr($card['exp'],2));

								break;
							case 'bank':
								$autodraft_type = 'bank';
								$payment_method_code = 'EF';
								//collect bank values
								if(substr($_REQUEST['accountnum'],0,1) == '*'){
									//assume stored number
										$bankinfo = $data['bankinfo'];

									$bank['name']		= $bankinfo['accname'];
									$bank['acclastfour']= $bankinfo['acclastfour'];
									$bank['routingnum'] = $bankinfo['accroutingnum'];
									$bank['bank']		= $bankinfo['accbankname'];
									$bric				= $bankinfo['bric'];

								} else {
									//assume provided number, preauth required.
									if(
										isset($_REQUEST['account_name']) && $_REQUEST['account_name']
									 && isset($_REQUEST['routingnumb'])  && $_REQUEST['routingnumb']
									 && isset($_REQUEST['accountnum'])	 && $_REQUEST['accountnum']
									) {
										//use provided number, preauth required.
										$bank['name'] 		= $_REQUEST['account_name'];
										$bank['accountnum'] = $_REQUEST['accountnum'];
										$bank['acclastfour']= substr($bank['accountnum'],strlen($bank['accountnum'])-4);
										$bank['routingnum'] = $_REQUEST['routingnumb'];
										$bank['bank'] 		= '';

										$exp = $this->parseEPXResponse($this->epx->preAuthCheck($bank['accountnum'],$bank['routingnum'],'.01',$bank['name'],$data['accountid'],$data['dbanbr'],$data['address']));
										if(isset($exp['AUTH_RESP']) && $exp['AUTH_RESP'] == '00'){

											//SUCCESSFULLY PREAUTHED!
											$bric = $exp['AUTH_GUID'];

										} else {
											throw new Exception (
												isset($exp['AUTH_RESP_TEXT'])?ucfirst($exp['AUTH_RESP_TEXT']).'.':'Communication error, unable to preauthorize your card.',
												isset($exp['AUTH_RESP'])?intval('90'.$exp['AUTH_RESP']):5
												);
										}

									} else throw new Exception('Incomplete Form', 4);
								}

								//pmtdetails object.

								$pmtdetails['BankName'] 	= pfsql::scrub($bank['bank']);
								$pmtdetails['accountname']  = pfsql::scrub($bank['name']);
								$pmtdetails['accountlast4'] = pfsql::scrub($bank['acclastfour']);
								$pmtdetails['routingnum'] 	= pfsql::scrub($bank['routingnum']);

								break;
							default:
								throw new Exception('Autodraft source unrecognized.', 3);
								break;
						}
					} else {
						$payment_method_code = 'PP';
						$bric = false;
					}

					//Create paymentplan.
					if(PaymentPlans::endAllPlansForPatient($data['patientid'])){
					} else throw new Exception('Failed closing old payment plan.',6);

					$createdby = $_SESSION['userdata']['id'];
					if(PaymentPlans::create($data['patientid'], $payment_method_code, $pmtdetails, $bric, $plan_starting_balance, $createdby, $plan_date, $payment_amount)){
					} else throw new Exception('Failed starting new payment plan.',7);

					//refresh data.
					$data = $this->getData();

				} catch (Exception $e) {
					$code = $e->getCode();
					$data['errorcode'] = $code >= 90 ? substr($code, 2):'PF'.$code;
					$data['errormsg']	= $e->getMessage();
				}

			}

			//IS THERE AN EXISTING PAYMENTPLAN?
			if($data['paymentplan']){
				//YES
				$data['view'] = 'editpaymentplan';
			} else {
				//NO
				$data['view'] = 'setuppaymentplan';
			}
			//Load the view	

			$this->loadView($data);
		} 
		
		
	}





	/** Details---------------------------------------------------------------------------------------------------------------------------

		*
		*
		*	This is a read only view, that lets you see your existing account activity. That's all.
		*
		*

	--------------------------------------------------------------------------------------------------------------------------- */

	public function details(){

		$data = $this->getData();
		$data['details'] = Accounts::patientPortalBalanceDetails($data['accountid']);
		//krumo($data['details']); exit();
		$data['view'] = 'balancedetails';
		$this->loadView($data);

	}




	/** Statements ---------------------------------------------------------------------------------------------------------------------------

		*
		*
		*	This is a read only view, that lets you access your existing statements.
		*
		*
		*

	--------------------------------------------------------------------------------------------------------------------------- */


	public function statements(){

		$data = $this->getData();
		$data['statements'] = Statements::getStatements($data['accountid'],'S', false, true);
		$data['view'] = 'viewstatements';

		$this->loadView($data);

	}



	/** Statement ---------------------------------------------------------------------------------------------------------------------------

		*
		*
		*	NOTE:: THIS IS ROUTED TO THE /statements/XXXXXXX URI,
		*	This endpoint, when accessed with a statementid, compares the accountid on the statement entry
		*	with the accountid in the current session, and should they match and the file exists, serves the pdf to the browser.
		*
		*
		*

	--------------------------------------------------------------------------------------------------------------------------- */


	public function statement($statementid){
		$statement = Statements::getStatementByID($statementid);
		//krumo($_SESSION);exit();
		if($statement && $_SESSION['guarantor_session']['accountid'] == $statement['AccountID']){
			$data['path'] = '/p/' . (ENVIRONMENT === "production" ? "prod" : "dev") . '/statements/'.$statement['ExportID'].'/'.$statement['AccountID'].'-'.$statement['StatementID'].'.pdf';
     		if(file_exists($data['path'])){
     			$this->load->view('olddig/viewstatement', $data);
     		} else if ($_SESSION['guarantor_session']['clientcode'] == 'demo') {
     			//Generate statement on-the-fly for demo accounts
     			$data2['accountid'] = $_SESSION['guarantor_session']['accountid'];
			    $data2['templateid'] = 4;
			    $_REQUEST = $data2;

     			$template = Templates::getTemplate($data2['templateid']);
			   
     			$this->load->view($template['content'], $data); 
     		} else {
     			show_404();
     		}
		} else {
			show_404();
		}
	}



	/** Contact ---------------------------------------------------------------------------------------------------------------------------

		*
		*
		*	This is a read only view, it uses the practice the current session is associated with to 
		*	deliver the TwilioPhone entry to the view.
		*
		*

	--------------------------------------------------------------------------------------------------------------------------- */


	public function contact(){

		$data = $this->getData();

		$data['view'] = 'contactus';

		$this->loadView($data);

	}
/**
---------------------------------------------------------------------------------------------------------------------------
	



		THESE ARE PRIVATE (and therefore URL inaccessable) FUNCTIONS VVVVV
		(...beyond here monsters be)




---------------------------------------------------------------------------------------------------------------------------
*/

	/** Settings ---------------------------------------------------------------------------------------------------------------------------

		*
		*
		*	THIS ENDPOINT IS CURRENTLY INACTIVE
		*
		*	The intention of this endpoint is to provide an interface for an account to manage their account preferences
		*	and contact information. Currently there is a data model change required to ensure there will be no loss
		*	of data by using this endpoint.
		*
		*

	--------------------------------------------------------------------------------------------------------------------------- */


	public function settings(){
		//This will be public when:
		//1 - The statement backend respects prefs while the account is active (Default behavior if a preference HAS NOT BEEN SET is to respect the CCE choice).
		//2 - When we have a data structure in place that will ensure that Phone Numbers and Mailing Addresses cannot be deleted by the patient ( an email table and an address table ).

        $data = $this->getData();
        if(pf::post('action') === 'save') {
            
            $results = array();

            if(pf::post('email')) array_push($results,$this->saveEmail());

            $data['results'] = $results;
        }
		$data['settings'] = Accounts::getGuarantorControllableValues($data['accountid']);
		$data['view'] = 'settings';

		$this->loadView($data);

	}

    protected function saveEmail(){

        $email = pf::post('email');
        $gogreen = pf::post('gogreen') ? 1 : 0;

        $data = $this->getData();


        if (filter_var($email,FILTER_VALIDATE_EMAIL)) {
            if(strtolower($email) != strtolower($data['email'])) {
                $result = Accounts::update($data['accountid'], array(array('field'=>'email','newvalue'=>$email)));
                if($result) $email = $result[0]['email'];
                else $email = false;
            }
        } else {
            $email = false;
        }

        $success = false;
        if( $email ) {
            $success = true;
            $data['settings'] = Accounts::getGuarantorControllableValues($data['accountid']);
            if ($gogreen !== $data['settings']['gogreen']) {
                $success = false;
                //Make sure this person is pref E
                $this->load->model('Communications');
                if ($gogreen) {
                    if ($this->Communications->prefin($data['accountid'], 'E')) {
                        $success = true;
                    }
                } else {
                    if ($this->Communications->prefout($data['accountid'], 'E')) {
                        $success = true;
                    }
                }
            }

        }

        return array('success'=>$success, 'reason' => $email ? ($success ? 'Everything updated successfully!' : 'There was an error saving your go green preference. Please try again later or <a href="/contact">call us</a> if you continue to experience problems.') : 'There was a problem processing your email... Please make sure you\'re providing a valid email address, and <a href="/contact">call us</a> if you continue to experience problems.');


    }


	/** loadView ---------------------------------------------------------------------------------------------------------------------------

		*
		*
		*	This processes the $data object that we build before loading our views.
		*	(Any nested views use the $_ci_vars object, which represents the same information, for consistant variables)
		*	It's basically a controller-specific template function.
		*
		*

	--------------------------------------------------------------------------------------------------------------------------- */
	private function loadView($data){
		//utility patientportal view loader, takes care of templating.
		$this->load->view('patientportal/include/header', $data);
		$this->load->view('patientportal/'.$data['view'], $data);
		$this->load->view('patientportal/include/footer');
	}


	/** getData ---------------------------------------------------------------------------------------------------------------------------

		*
		*
		*	This snags and returns the data from the session (and from any controller constants).
		*		If $refresh is set to true (default behavior)
		*		Then it uses the accountid in the session to refresh the data from the database.
		*
		*

	--------------------------------------------------------------------------------------------------------------------------- */

	private function getData($refresh = true){
		//Retrieves the appropriate data from the session, formated nicely and ready to go.
		$data;
		if(isset($_SESSION['guarantor_session']) && $_SESSION['guarantor_session']){

			//Refreshes the data
			if($refresh) $this->setSessionData($_SESSION['guarantor_session']['accountid']);
			$data = $_SESSION['guarantor_session'];
			
		}
		$data['view'] = '';
		$data['debug'] = $this->debug;

		return $data;
	}

	/** setSessionData ---------------------------------------------------------------------------------------------------------------------------

		*
		*
		*	This unsets any existing session, then reaches out to the database and creates a nicely formatted session object
		*	with all sorts of account information. This is usually called once per request.
		*
		*

	--------------------------------------------------------------------------------------------------------------------------- */
	private function setSessionData($accountid){

		//Refreshes session data from the database.
		//In the case that there is no session data, it creates the object.

		//Call to the database
		$sess = Accounts::getGuarantorSession($accountid);

		if($sess){

			//Check to see if this patient can use the patient portal first and foremost.
			$can_login = ($sess['status1'] != 'RT' && ClientPreferences::getClientPreference($sess['clientid'], 'PatientsCanLoginToPatientPortal'));

			if($can_login === true){

				//Get Parameters for this client
				$this->load->model('ClientParameters');
				$params = $this->ClientParameters->read(array('name','value'), array('clientid'=>$sess['clientid'], 'voideddate'=>null));
				$sess['parameters'] = array();
				if($params) foreach($params as $p){
					$sess['parameters'][$p['name']][] = $p['value'];
				}

                $sess['preferences'] = array();
                $sess['preferences']['DisplayCreditBalancesInPatientPortal'] = $this->ClientPreferences->getClientPreference($sess['clientid'], 'DisplayCreditBalancesInPatientPortal');

				//Additional fields not needed from the database
                if($sess['preferences']['DisplayCreditBalancesInPatientPortal'] || $sess['lefttopay'] >= 0){
                    $sess['lefttopayformatted'] = pfmoney::toFromCents($sess['lefttopay']);
                } else {
                    $sess['lefttopayformatted'] = pfmoney::toFromCents(0);
                }
				//Sets what the "Default" make-a-payment amount is. If there is no payment plan active, it defaults to the full amount
				$sess['defaultpayment'] = min((($sess['paymentplan']&&$sess['paymentplan']['paymentamount'])?$sess['paymentplan']['paymentamount']:$sess['lefttopay']),$sess['lefttopay']);
				//payment_threshold is the minimum payment we will accept through the portal (assuming the patient owes more than the threshold)
				$sess['paythresh'] = $this->payment_threshold;
				$sess['paythreshformatted'] = pfmoney::toFromCents($this->payment_threshold);

                //Is left to pay representative of the full amount owed?
                if($sess['lefttopay'] != $sess['totalbalance']){
                    $sess['balancesmatch'] = false;
                } else {
                    $sess['balancesmatch'] = true;
                }

				//Minimum payment
				$sess['minpayment'] = ($sess['lefttopay'] < $this->payment_threshold ? $sess['lefttopay'] : $this->payment_threshold);
					//If the default payment is less than this ^^ set it to the default payment instead.
					if($sess['defaultpayment'] < $sess['minpayment']) $sess['defaultpayment'] = $sess['minpayment'];

				//Minimum payment formatted
				$sess['minpaymentformatted'] = pfmoney::toFromCents($sess['minpayment']);

				//Color Scheme
				$sess['colors'] = ClientPreferences::getClientColors($sess['clientid']);

				//Unset any potentially remaining patient session.
				//Might be a bit heavy handed... May want to account for spoofs.
				if(isset($_SESSION['guarantor_session'])){
					unset($_SESSION['guarantor_session']);
					unset($_SESSION['userdata']);
					unset($_SESSION['rights']);
					unset($_SESSION['roles']);
					//unset($_SESSION['colors']);
					//unset($_SESSION['statementid']);
				}
				$_SESSION['guarantor_session'] = $sess;
				//Userdata object
				$ud = Users::getUserByEmail('patient@patientfocus.com');
				$_SESSION['userdata'] = $ud;
				$_SESSION['userdata']['accountid'] = $sess['accountid'];
				//Roles n rights
	          	$_SESSION['roles'][]= array('id'=>'14', 'name' => 'Patient');       
	          	$_SESSION['rights'] = Rights::getRightsForRolesArray($_SESSION['roles']);

	        } else {

	        	//Destroy session
				unset($_SESSION['guarantor_session']);
				unset($_SESSION['userdata']);
				unset($_SESSION['roles']);
				unset($_SESSION['rights']);
				unset($_SESSION['colors']);
				unset($_SESSION['statementid']);
				session_destroy();

				//Get forwarding Message
				$data = array();
				$data['forwarding_message'] = $this->Clients->getForwardingMessageForPortal($sess['clientid']);

				//Show forwarding Message and break the current flow.
				echo $this->load->view('patientportal/forwardingmessage',$data, true);
				//NOTE:: Echoing this here ensures that this doesn't enter the CI output buffer
				// and writes straight to the output, so we can exit on the next line.
				exit();

	        }

		}

	}

	/** parseEPXResponse ---------------------------------------------------------------------------------------------------------------------------

		*
		*
		*	Converts an xml response string from EPX to an associated array.
		*
		*

	--------------------------------------------------------------------------------------------------------------------------- */
	private function parseEPXResponse($xmlstr){

		$xmlobj = simplexml_load_string($xmlstr);
		$xmlarr = array();
		if($xmlobj) foreach($xmlobj->FIELDS->FIELD as $field){
              $xmlarr[(string)$field->attributes()->KEY] = (string)$field;
        }
        return $xmlarr;

	}

}

?>

Open in new window


This is the patientportal.php page that's living in the "controllers" directory of the Code Igniter app that I've been asked to work on.

First question: Line 42, you're grabbing the base url and making that your "clientcode." You're then pumping that to the "getByCode" SQL function - the getByCode on line 44. When I echo that SQL, it looks like this:

select client.ClientID, client.ClientName, client.ClientTetrisId, client.ClientPrimaryContact, client.ClientBankAcctNumber, client.ClientBankRoutingNumber, client.ClientStatusCode, client.Specialty, client.Address, client.PhoneNumber, client.Email, client.PaymentProcessingDBANbr, client.CreatedDate, client.LastModifiedDate, client.ModifiedBy, client.Address1, client.Address2, client.City, client.State, client.Zip, client.clientcode, cast(dbo.ufn_ClientPreference(clientid,'WithholdFeesOnInvoice') as bit) withhold, client.phrase, client.type, client.ClientNotes, client.SitePaymentFee from client where clienttetrisid = 'localhost:8080' 

Open in new window

.

I get an empty recordset, so I don't know what purpose that serves. Furthermore, the "clienttetrisid" value is typically something TONC or PF, so I'm not sure what's going on there.

Is there another value that gets interjected into that SELECT that would make it more logical? I can't tell and I want to figure it out.

What do you think?
Avatar of Bruce Gust
Bruce Gust
Flag of United States of America image

ASKER

There's a script called "subdomaingate.php" and it looks like this:

<!DOCTYPE html>
<html>
	<head>
		<title>Redirecting...</title>
	</head>
	<body>
		<form action="https://<?=$clientcode?>.patientfocus.com/patientportal/login" method="post">
		<!--<form action="patientportal/login" method="post">-->
			<input type="hidden" name="pin" value="<?=$pin?>"/>
			<input type="hidden" name="lastfour" value="<?=$lastfour?>"/>
			<input type="hidden" name="year" value="<?=$year?>"/>
		</form>
		<script type="text/javascript">
			document.forms[0].submit();
		</script>
	</body>
</html>

Open in new window


At that point, the aforementioned "$clientcode" makes perfect sense because its establishing the correct domain. But it looks like it's interjected into the flow of things in the aftermath of destroying the existing session.

How does that work?
Avatar of Olaf Doschke
That script merely is a class definition (class PatientPortal extends MY_Controller).
It is used somewhere, and you should look out where, typically a class is instanciated so somewhere should be a line containing  
new PatientPortal();

Open in new window


There you find out the logic of using login etc.

This code most probably already is a secondary inheritance of the CI_controller, as explained here: http://stevenlu.com/posts/2012/09/01/inheriting-a-base-controller-in-codeigniter/

Bye, Olaf.
ASKER CERTIFIED SOLUTION
Avatar of Ray Paseur
Ray Paseur
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Ray, Julian - I think I've got it figured out and I appreciate your insight. Olaf, I'm thinking in light of this being a Code Igniter dynamic, the logic that I'm looking for is going to be on this page. At least, that's what I've been able to uncoil thus far.

The login function is grabbing the form variables on line 69. I was able to get out on PROD and validate that the "clientcode" referenced on line 44 becomes something akin to "tonc.patientfocus.com," etc. So, that makes sense now.

I can trace the logic of this now, but I'm still kind of fuzzy when you get to line 140. I'm going to make another question, but that would be my next query. You're not "validating" anyone at this point. There's a $client variable in place which suggests that I'm now going to proceed to the rest of the page via an "else." But instead there's a "session_write_close" and a prompt to load a login view.

I've got this at https://www.experts-exchange.com/questions/28964171/How-does-this-login-script-produce-the-view-that-you-see-after-everything-is-validated-Part-III.html

That's at

if($client) $clientid = $client['ClientID'];
The code you look for and want to modify is surely contained in here, but a class definition doesn't run on it's own, it has to be included/rquired somewhere and an instance of the controller has to be established. And that core logic of any MVC framework has some base php script always running. which is not this one. To get an overall picture of how this works, you would need to dig into that Code Igniter base code how it establishes which controller is used.

Anyway, it seems both the login and also the payment view are part of this, that's right. But it's not self contained, it lives in the context of CodeIgniters base controller class and just extends it. To get a full picture of how it works, you'd need to look into this base classes, too, this inherited part of code is not copied into a class extension, the whole point of OOP is to not copy and repeat anything but inherit it, so most likely parts of code giving the full insight of what happens will be elsewhere in CodeIgniter php files.

It'd help to debug this and look in single step mode what code is executed line by line, that'd also jump into base class code, where that's used.

Bye, Olaf.