['openid email profile offline_access assets projects accounting.settings accounting.transactions accounting.contacts accounting.journals.read accounting.reports.read accounting.attachments payroll.employees payroll.payruns payroll.payslip payroll.timesheets payroll.settings files'], 'scope1' => [ 'openid', 'email', 'profile', 'offline_access', 'assets', 'projects', 'accounting.settings', 'accounting.transactions', 'accounting.contacts', 'accounting.journals.read', 'accounting.reports.read', 'accounting.attachments', 'payroll.employees', 'payroll.payruns', 'payroll.payslip', 'payroll.timesheets', 'payroll.settings' ] ]; public $storage; public $config; public $apiAccountingInstance; //accounting instance public $apiPayrollInstance; // payroll au instance public $xeroTenantId; public function __construct($office) { $this->office = $office; add_action('init', array($this, 'init_wp')); add_action('after_setup_theme', array($this, 'boot_carbon')); // add_action('init', array('Carbon_Fields\\Carbon_Fields', 'boot')); add_action( 'plugins_loaded', array( '\\Carbon_Fields\\Carbon_Fields', 'boot' ) ); add_action('carbon_fields_register_fields', array($this, 'build_settings_page')); add_action('carbon_fields_container_activated', array($this, 'field_activated')); add_action('parse_request', array($this, 'xero_callback')); add_shortcode('xero_org_name', array($this, 'xero_org_name')); add_shortcode('xero_org_contacts', array($this, 'xero_org_contacts')); add_shortcode('xero_org_invoices', array($this, 'xero_org_invoices')); add_shortcode('xero_org_pay_items', array($this, 'xero_org_pay_items')); add_shortcode('xero_org_employees', array($this, 'xero_org_employees')); add_shortcode('xero_org_clients', array($this, 'xero_org_clients')); add_shortcode('xero_org_payroll_calendar', array($this, 'xero_org_payroll_calendar')); add_action('init', array($this, "xero_init")); add_action('plugins_loaded', array($this, "load_storage")); // $this->xero_init(); $this->storage = new StorageClass(); } public function __call($method, $args) { if ( method_exists($this->XeroOauth1, $method) ) { syslog(LOG_INFO,"Calling $method"); $this->XeroOauth1->$method($args); } else { error_log("$method is not defined" ); } } public function xero_init() { $this->provider = $this->create_provider(); } private function create_provider() { $provider = new \League\OAuth2\Client\Provider\GenericProvider([ 'clientId' => $this->clientID, 'clientSecret' => $this->clientSecret, 'redirectUri' => $this->getRedirectURL(), 'urlAuthorize' => 'https://login.xero.com/identity/connect/authorize', 'urlAccessToken' => 'https://identity.xero.com/connect/token', 'urlResourceOwnerDetails' => 'https://api.xero.com/api.xro/2.0/Organisation' ]); return $provider; } public function load_storage() { //$this->storage->read_value(); } public function boot_carbon() { \Carbon_Fields\Carbon_Fields::boot(); } function build_settings_page() { Container::make('theme_options', __('Xero Integration')) ->set_page_parent('options-general.php') ->add_fields(array( Field::make('text', 'xero_oauth2state', 'Xero Oauth2 State') ->set_attribute('maxLength', 2048) ->set_attribute('readOnly', true) ->set_default_value($this->storage->getOauth2State()), Field::make('text', 'xero_token', 'Xero Token') ->set_attribute('maxLength', 2048) ->set_attribute('readOnly', true) ->set_default_value($this->storage->getSession()['token']), Field::make('text', 'xero_refresh_token', 'RefreshToken') ->set_attribute('readOnly', true) ->set_default_value($this->storage->getSession()['refresh_token']), Field::make('text', 'xero_id_token', 'ID Token') ->set_attribute('readOnly', true) ->set_default_value($this->storage->getSession()['id_token']), Field::make('text', 'xero_tenant_id', 'Tenant ID') ->set_attribute('readOnly', true) ->set_default_value($this->storage->getSession()['tenant_id']), Field::make('text', 'xero_expires', 'Expires') ->set_attribute('readOnly', true) ->set_default_value($this->storage->getSession()['expires']), Field::make('text', 'xero_expires_human', 'Expires') ->set_attribute('readOnly', true) ->set_default_value($this->storage->getSession()['expires_human']), Field::make('html', 'crb_information_text') ->set_html('

Connect/Reconnect

if the above field is empty, or the expire date looks suspicous, please reconnect to XeroOauth1

') )); $this->storage->read_value(); } function field_activated() { $this->storage->read_value(); $xeroTenantId = (string)$this->storage->getSession()['tenant_id']; if ($xeroTenantId == "") { $this->startAuthorization(); } else { $this->refresh_token(); } } public function startAuthorization() { // This returns the authorizeUrl with necessary parameters applied (e.g. state). $authorizationUrl = $this->provider->getAuthorizationUrl($this->options); // Save the state generated for you and store it to the session. // For security, on callback we compare the saved state with the one returned to ensure they match. $this->storage->setOauth2State($this->provider->getState()); // Redirect the user to the authorization URL. header('Location: ' . $authorizationUrl); exit(); } function xero_callback() { if (isset($_GET['xero_reauth'])) { $this->startAuthorization(); return; } if (!isset($_GET['xero_callback'])) { return; } // If we don't have an authorization code then get one if (!isset($_GET['code'])) { echo "Something went wrong, no authorization code found"; exit("Something went wrong, no authorization code found"); // Check given state against previously stored one to mitigate CSRF attack } elseif (empty($_GET['state']) || ($_GET['state'] !== $this->storage->getOauth2State())) { echo "Invalid State"; $this->storage->setOauth2State(""); exit('Invalid state'); } else { try { // Try to get an access token using the authorization code grant. $accessToken = $this->provider->getAccessToken('authorization_code', [ 'code' => $_GET['code'] ]); $config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string)$accessToken->getToken()); $identityInstance = new \XeroAPI\XeroPHP\Api\IdentityApi( new \GuzzleHttp\Client(), $config ); $result = $identityInstance->getConnections(); // Save my tokens, expiration tenant_id $this->storage->setToken( $accessToken->getToken(), $accessToken->getExpires(), $result[0]->getTenantId(), $accessToken->getRefreshToken(), $accessToken->getValues()["id_token"] ); // related to $this->build_settings_page $url = "/wp-admin/options-general.php?page=crb_carbon_fields_container_xero_integration.php"; header('Location: ' . $url); exit(); } catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) { echo "Xero Callback failed"; exit(); } } } private function getRedirectURL() { return get_site_url() . "/?xero_callback"; } function refresh_token($enforced=false) { $this->storage->read_value(); $this->xeroTenantId = (string)$this->storage->getSession()['tenant_id']; if ($this->storage->getHasExpired() || $enforced) { $this->provider = $this->create_provider(); try { $newAccessToken = $this->provider->getAccessToken('refresh_token', [ 'refresh_token' => $this->storage->getRefreshToken() ]); } catch (\Exception $e) { $this->startAuthorization(); return; } // Save my token, expiration and refresh token $this->storage->setToken( $newAccessToken->getToken(), $newAccessToken->getExpires(), $this->xeroTenantId, $newAccessToken->getRefreshToken(), $newAccessToken->getValues()["id_token"]); } } private function get_accounting_instance() { $this->refresh_token(); if ($this->apiAccountingInstance == null) { $this->config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken( (string)$this->storage->getSession()['token']); $this->apiAccountingInstance = new \XeroAPI\XeroPHP\Api\AccountingApi( new \GuzzleHttp\Client(), $this->config ); } return $this->apiAccountingInstance; } private function get_payroll_au_instance() { $this->refresh_token(); if ($this->apiPayrollInstance == null) { $this->config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken( (string)$this->storage->getSession()['token']); $this->config->setDebug(true); $this->config->setDebugFile("/home/acaresydneycom/public_html/wp-content/plugins/ts/debug.log"); $this->apiPayrollInstance = new \XeroAPI\XeroPHP\Api\PayrollAuApi( new \GuzzleHttp\Client(), $this->config ); } return $this->apiPayrollInstance; } public function xero_org_name() { ini_set('display_errors', 'On'); $apiInstance = $this->get_accounting_instance(); $apiResponse = $apiInstance->getOrganisations($this->xeroTenantId); return 'Organisation Name: ' . $apiResponse->getOrganisations()[0]->getName() . " "; } public function xero_org_contacts() { ini_set('display_errors', 'On'); $apiInstance = $this->get_accounting_instance(); $apiResponse = $apiInstance->getContacts($this->xeroTenantId); $contacts = $apiResponse->getContacts(); $message = " "; $message .= " "; $count = 1; foreach ($contacts as $c) { $group = ""; foreach ($c->getContactGroups() as $g) { $group .= $g->getName() . " - " . $g->getStatus() . "
"; } $message .= " " . " " . " " . " " . " " . " " . " " . ""; } $message .= "
# unique-id name Supplier Customer Group Status
" . $count++ . " " . $c->getContactId() . " " . $c->getName() . " " . ($c->getIsSupplier() ? "yes" : " - ") . " " . ($c->getIsCustomer() ? "yes" : " - ") . " " . $group . " " . $c->getContactStatus() . "
"; return $message; } public function xero_org_invoices() { ini_set('display_errors', 'On'); $apiInstance = $this->get_accounting_instance(); $apiResponse = $apiInstance->getInvoices($this->xeroTenantId); $invoices = $apiResponse->getInvoices(); $message = " "; $message .= " "; $count = 1; foreach ( $invoices as $c) { $strDate = ""; $d = $c->getDate(); if ( $d != null) { $da = $c->getDateAsDate(); $strDate = $da->format("Y-m-d"); } else { $strDate = $d; } $message .= " " . " " . " " . " " . " " . " " . " " . ""; } $message .= "
# invoice num date contact amount Type Status
" . $count ++ . " " . $c->getInvoiceNumber() . " " . $strDate . " " . $c->getContact()->getName() . " " . $c->getTotal() . " " . $c->getType() . " " . $c->getStatus() . "
"; return $message; } public function xero_org_pay_items(){ ini_set('display_errors', 'On'); $api = $this->get_payroll_au_instance(); $xeroTenantId = $this->xeroTenantId; //$xeroTenantId = "e23fd416-3b66-43e9-b908-97fbefa24eb8"; // demo company; //$xeroTenantId = "4e2521ae-83e6-4895-aa90-b20aa0825ce1"; // Acaresydney ; $ifModifiedSince = null; $where = null; // "Status==\"ACTIVE\""; $order = null; // "EmailAddress%20DESC"; $page = 1; // $result = null; try { $result = $api->getPayItems ($xeroTenantId, $ifModifiedSince, $where, $order, $page); $rates = $result->getPayItems()->getEarningsRates(); $message = " "; $message .= " "; $count = 1; foreach ( $rates as $r) { $message .= " " . " " . " " . " " . " " . " " . " " . " " . " " . " " . " " . " " . ""; } $message .= "
# Name EarningsType RateType AccountCode Multiplier IsExemptFromTax IsExemptFromSuper AccrueLeave IsReportableAsW1 UpdatedDateUTC CurrentRecord
" . $r->getEarningsRateId() . " " . $r->getName() . " " . $r->getEarningsType() . " " . $r->getRateType() . " " . $r->getAccountCode() . " " . $r->getTypeOfUnits() . " " . $r->getRatePerUnit() . " " . $r->getIsExemptFromTax() . " " . $r->getIsExemptFromSuper() . " " . $r->getIsReportableAsW1() . " " . $r->getUpdatedDateUtc() . " " . $r->getCurrentRecord() . "
"; return $message; } catch (\Exception $e) { echo 'Exception when calling PayrollAuApi->getPayItems: ', $e->getMessage(), PHP_EOL; return; } } public function xero_org_employees(){ ini_set('display_errors', 'On'); $api = $this->get_payroll_au_instance(); $xeroTenantId = $this->xeroTenantId; //$xeroTenantId = "e23fd416-3b66-43e9-b908-97fbefa24eb8"; // demo company; //$xeroTenantId = "4e2521ae-83e6-4895-aa90-b20aa0825ce1"; // Acaresydney ; $ifModifiedSince = date("M d Y H:i:s", strtotime("-30 days")); $where = "Status==\"ACTIVE\""; $order = null; // "EmailAddress%20DESC"; $page = 1; // $result = null; try { $result = $api->getEmployees($xeroTenantId,$ifModifiedSince,$where,$order,$page); $employees = $result->getEmployees(); $message = " "; $message .= " "; $count = 1; foreach ($employees as $r){ $message .= " " . " " . " " . " " . " " . " " . " " . " " . " " . " " . " " . ""; $count ++; } $message .= "
# First Last Status Email DOB Gender Phone Mobile Start Group
" . $count . " " . $r->getFirstName() . " " . $r->getLastName() . " " . $r->getStatus() . " " . $r->getEmail() . " " . $r->getDateOfBirthAsDate()->format("M d Y") . " " . $r->getGender() . " " . $r->getPhone() . " " . $r->getMobile() . " " . $r->getStartDateAsDate()->format("M d Y") . " " . $r->getEmployeeGroupName() . "
"; return $message; } catch (\Exception $e) { echo 'Exception when calling PayrollAuApi->getPayItems: ', $e->getMessage(), PHP_EOL; return; } } public function xero_org_clients(){ ini_set('display_errors', 'On'); try { $contacts = $this->getClients(); $message = " "; $message .= " "; $count = 1; foreach ($contacts as $r){ $message .= " " . " " . " " . " " . " " . " " . " " . ""; $count ++; } $message .= "
# Name Last Status Email AccountNumber Addresses
" . $count . " - ". $r->getContactID() . " " . $r->getFirstName() . " " . $r->getLastName() . " " . $r->getContactStatus() . " " . $r->getEmailAddress() . " " . $r->getAccountNumber() . " " . $r->getAddresses() . "
"; return $message; } catch (\Exception $e) { echo 'Exception when calling PayrollAuApi->getPayContacts: ', $e->getMessage(), PHP_EOL; return; } } public function xero_org_payroll_calendar() { // update_option('bts_pay_roll_calendar_last_sync', time()); try { $result = $this->get_payroll_calendar(); $pc = $result->getPayrollCalendars()[0]; $start = $pc->getStartDateAsDate()->format('Y-m-d'); $finish = new \DateTime($start); $finish = $finish->modify("+13 days")->format('Y-m-d'); $paydate = $pc->getPaymentDateAsDate()->format('Y-m-d'); $calendar["start"] = $start; $calendar["finish"] = $finish; $calendar["paydate"] = $paydate; return print_r ($calendar); }catch (\Exception $e) { echo 'Exception when calling PayrollAuApi->getPayrollCalendar: ', $e->getMessage(), PHP_EOL; } //update_option('bts_pay_roll_calendar', $calendar); } // //TS implementation // /* sync xero to wp options */ public function init_wp(){ try{ error_log("init_wp is empty"); $this->sync_pay_item(); // $this->add_new_client(); // $this->add_new_employee(); // $this->sync_payroll_calendar(); }catch(\Exception $e){ } } private function sync_pay_item() { if ($this->too_close_to_sync_payitem()){ //return; } $api = $this->get_payroll_au_instance(); $xeroTenantId = $this->xeroTenantId; $page = 1; try { $result = $api->getPayItems($xeroTenantId, $ifModifiedSince, $where, $order, $page); foreach ($result->getPayItems()->getEarningsRates() as $e){ // "EarningsRateID": "34e17d08-237a-4ae2-8115-375d1ff8a9ed", // "Name": "Overtime Hours (exempt from super)", // "EarningsType": "OVERTIMEEARNINGS", // "RateType": "MULTIPLE", // "AccountCode": "477", // "Multiplier": 1.5, // "IsExemptFromTax": true, // "IsExemptFromSuper": true, // "AccrueLeave": false, // "IsReportableAsW1": true, // "UpdatedDateUTC": "2019-03-16T13:18:19+00:00", // "CurrentRecord": false if ($e->getCurrentRecord() == "true"){ $payitem_options[]= array( 'EarningsRateID' => $e->getEarningsRateID(), 'Name'=> $e->getName(), 'EarningsType'=> $e->getEarningstype(), 'RatePerUnit' => $e->getRatePerUnit(), 'RateType' => $e->getRateType(), 'AccountCode' => $e->getAccountCode(), "Multiplier"=> $e->getMultiplier(), "IsExemptFromTax" => $e->getIsExemptFromTax(), "IsExemptFromSuper"=> $e->getIsExemptFromSuper(), "AccrueLeave" => $e->getAccrueLeave(), "TypeOfUnits" => $e->getTypeOfUnits(), "CurrentRecord"=> $e->getCurrentRecord(), ); } } update_option('bts_payitem_earnings_rate', $payitem_options); update_option('bts_payitem_last_sync', time()); } catch (Exception $e) { echo 'Exception when calling PayrollAuApi->getPayItems: ', $e->getMessage(), PHP_EOL; } } public function sync_users($mininterval, $employeeonly, $clientsonly){ echo "not implemented "; //TODO; } public function sync_payroll_calendar() { } private function too_close_to_sync_payitem(){ $lastsync = get_option('bts_payitem_last_sync', 0); $now = time(); $diff = $now - (int) $lastsync; return $diff < $this->minimum_sync_interval_in_seconds; //default 10 minutes } private function sync_payitem(){ } private function too_close_to_add_employee(){ $lastsync = get_option('bts_add_employee_last_sync', 0); $now = time(); $diff = $now - (int) $lastsync; return $diff < 1.5 * $this->minimum_sync_interval_in_seconds; //default 1.1 * 10 minutes } private function too_close_to_add_client(){ $lastsync = get_option('bts_add_client_last_sync', 0); $now = time(); $diff = $now - (int) $lastsync; return $diff < 2.0 * $this->minimum_sync_interval_in_seconds; //default 1.2 * 10 minutes } private function too_close_to_sync_payroll_calendar(){ $lastsync = get_option('bts_pay_roll_calendar_last_sync', 0); $now = time(); $diff = $now - (int) $lastsync; return $diff < 3.0 * $this->minimum_sync_interval_in_seconds; //default 1.3 * 10 minutes } public function getClients($contact_group_id = null) { if ( $contact_group_id == null ){ $contact_group_id = $this->clientContactGroupID; } $apiAcc = $this->get_accounting_instance(); $result = $apiAcc->getContactGroup($this->xeroTenantId, $contact_group_id); $cg = $result->getContactGroups(); $allClients = $cg[0]->getContacts(); $ifModifiedSince = new \DateTime(); $recent = new \DateInterval("P30D"); $ifModifiedSince->sub($recent); $allContacts = $apiAcc->getContacts($this->xeroTenantId, $ifModifiedSince); $ret = []; foreach ( $allContacts as $ac ) { //search from within the group $found = false; $id = $ac->getContactID(); foreach ($allClients as $client) { $clientID = $client->getContactID(); if ( $clientID == $id ) { $found = true; break; } } if ( $found ) { $ret[] = $ac; } } return $ret; } public function get_payroll_calendar() { $id = "33dc7df5-3060-4d76-b4da-57c20685d77d"; //fortnightly $api = $this->get_payroll_au_instance(); $pc = $api->getPayrollCalendar($this->xeroTenantId, $id); return $pc; } }