Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Forces WHMCS to only send invoice related emails to the billing contact. This is done by temporarily updating the primary account's email address and contact info to that of the billing contact (ie. it's a hack). Add this to your .../includes/hooks/ folder.
<?php
if (!defined("WHMCS"))
die("This file cannot be accessed directly");
use WHMCS\Database\Capsule;
/*
* Temporarily updates the primary account's email address to that of the
* company's Billing contact upon creation of a new invoice so that invoices
* are not sent to the primary account holder.
*/
function update_account_email_for_invoices($vars=array()){
$debugmode = false;
$debug = 'Inv Emails: ';
$inv_user = 0;
/*if( isset($vars['user']) ){ //this isn't reliable - sometimes just set to admin user ID
$inv_user = $vars['user'];
} else */if( isset($vars['invoiceid']) ) {
$inv_user = Capsule::table('tblinvoices')->where('id', $vars['invoiceid'])->pluck('userid');
if( is_array($inv_user) ){
$inv_user = $inv_user[0];
}
} else {
//non-specific - update all clients with billing contacts
$acc_users = Capsule::table('tblclients')->where('billingcid', '!=', '0')->pluck('id');
$inv_user = $acc_users; //loop through all users with sub-accounts
}
if( !is_array($inv_user) ){
$inv_user = array($inv_user);
}
$saved = Capsule::table('tbltransientdata')->where('name', 'clientEmailUpdates')->pluck('data');
if( is_array($saved) ){ //get saved transient and add to array (in-case multiple invoices are being generated)
$saved = $saved[0];
}
if( '' != $saved && null != $saved ){ //decode serialised string
$saved = unserialize($saved);
} else {
$saved = array();
}
foreach( $inv_user as $inv_usr ){
//lookup this user's account to see if it has a billing contact
$billing_contact = Capsule::table('tblclients')->where('id', $inv_usr)->pluck('billingcid');
if( is_array($billing_contact) ){
$billing_contact = $billing_contact[0];
}
if( 0 == $billing_contact || null == $billing_contact || '' == $billing_contact ){
$debug .= 'No billing contact. ';
$debug .= print_r( $vars, true );
if( $debugmode ) logActivity($debug, 0);
continue; //no billing contact set for this client
}
$billing_contact_info = Capsule::table('tblcontacts')->where('id', $billing_contact)->get();
if( is_array($billing_contact_info) ){
$billing_contact_info = $billing_contact_info[0];
}
$billing_email = $billing_contact_info->email;
$billing_fname = $billing_contact_info->firstname;
$billing_lname = $billing_contact_info->lastname;
if( null === $billing_email || '' === $billing_email ){
$debug .= 'Billing contact has no email address. ';
$debug .= print_r( $vars, true );
if( $debugmode ) logActivity($debug, 0);
continue; //a bit of error handling - don't update if the billing contact has no email address or has been deleted
}
//lookup the current user's primary account email address and save as a transient - this lets us restore it later
$curr_client = Capsule::table('tblclients')->where('id', $inv_usr)->get();
if( is_array($curr_client) ){
$curr_client = $curr_client[0];
}
$curr_email = $curr_client->email;
$curr_fname = $curr_client->firstname;
$curr_lname = $curr_client->lastname;
if( !isset($saved[$inv_usr]) ){ //don't replace current values - they may be the originals
$saved[$inv_usr] = array(
'email' => $curr_email,
'fname' => $curr_fname,
'lname' => $curr_lname
); //add current userID and primary account email address to array
}
//update the primary account contact email to the billing contact's email address
$update = array(
'email' => $billing_email,
'firstname' => $billing_fname,
'lastname' => $billing_lname
);
if( Capsule::table('tblclients')->where('id', $inv_usr)->update( $update ) ){
$debug .= 'Updated client email address to: '.$billing_email.' ';
} else {
$debug .= 'Failed to update client billing info to: '.print_r($update,true).' ';
}
$debug .= print_r( $vars, true );
}
if( $debugmode ) logActivity($debug, 0);
//save the previous contact info as a transient value so that we can restore it later
if( !empty($saved) ){
if( !Capsule::table('tbltransientdata')->where('name', 'clientEmailUpdates')->update( array('data' => serialize($saved), 'expires' => time() + (1*60*60)) ) ){ //1 hour expiry
Capsule::table('tbltransientdata')->insert(array(
'name' => 'clientEmailUpdates',
'data' => serialize($saved),
'expires' => time() + (1*60*60) //1 hour
));
}
}
}
//add_hook("AdminAreaHeaderOutput",2,"update_account_email_for_invoices"); //for testing only
add_hook("PreInvoicingGenerateInvoiceItems",1,"update_account_email_for_invoices"); //for automated generation of invoices
add_hook('PreCronJob', 1, "update_account_email_for_invoices"); //for invoices generated by cron jobs
add_hook("InvoiceCreation",2,"update_account_email_for_invoices"); //for invoices created in admin area
add_hook("InvoicePaidPreEmail",2,"update_account_email_for_invoices"); //for paid notification emails
add_hook("InvoicePaymentReminder",2,"update_account_email_for_invoices"); //for payment reminder notification emails
function fix_auto_invoice_emails( $whatever=array() ){
$debugmode = false;
$debug = 'Inv Emails (restore): ';
//lookup all of the email addresses which need to be updated
$saved = Capsule::table('tbltransientdata')->where('name', 'clientEmailUpdates')->pluck('data');
$expiry = Capsule::table('tbltransientdata')->where('name', 'clientEmailUpdates')->pluck('expires');
Capsule::table('tbltransientdata')->where('name', 'clientEmailUpdates')->update( array('data' => serialize(array()) ) );
if( is_array($saved) ){
$saved = $saved[0];
}
if( '' != $saved && null != $saved ){ //decode serialised string
$saved = unserialize($saved);
} else {
$saved = array();
}
if( !empty($saved) ){
$updated = false;
$debug .= 'Restoring '.count($saved).' email addresses. ';
foreach( $saved as $user_id => $user_info ){ //loop through each account to update
$update = array(
'email' => $user_info['email'],
'firstname' => $user_info['fname'],
'lastname' => $user_info['lname']
);
if( !Capsule::table('tblclients')->where('id', $user_id)->update( $update ) ){ //restore primary email address
//error - maybe do some error recovery here (later)
$debug .= 'Error, failed to restore contact info: '.print_r($update,true).' for user: #'.$user_id.'. ';
} else {
//successfully restored this account - remove it from the transient
unset($saved[$user_id]);
$updated = true;
}
}
$debug .= print_r( $saved, true );
if( $debugmode ) logActivity($debug, 0);
if( $updated && '' != $expiry && null != $expiry ){ //if at least one account was restored (& expiry is valid)
//re-save the updated restoration info as a transient value (failed updates will try again later until transient expires)
if( !Capsule::table('tbltransientdata')->where('name', 'clientEmailUpdates')->update( array('data' => serialize($saved), 'expires' => time() + (6*60*60)) ) ){
Capsule::table('tbltransientdata')->insert(array(
'name' => 'clientEmailUpdates',
'data' => serialize($saved),
'expires' => $expiry
));
}
}
}
}
//lots of hooks to make sure it's run (no idea if all of these are really neccessary, but it works)
add_hook("AdminAreaFooterOutput",2,"fix_auto_invoice_emails"); //for invoices created/updated in admin screen
add_hook("ClientAreaFooterOutput",2,"fix_auto_invoice_emails"); //for invoices generated by the client
add_hook("AfterCronJob",2,"fix_auto_invoice_emails"); //after automation task creates invoices (error recovery for if invoice email is not sent)
add_hook("EmailPreSend",2,"fix_auto_invoice_emails"); //at this point we already have the info saved to variables
add_hook("PostAutomationTask",2,"fix_auto_invoice_emails"); //same as AfterCronJob (we probably don't need both)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.