['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'] ]; public $storage; public $config; public $apiAccountingInstance; //accounting instance public $apiPayrollInstance; // payroll au instance public $xeroTenantId; private $shortcodes; private $sync; public function __construct($office) { $this->office = $office; $this->shortcodes = new XeroOauth2ShortCode($this); $this->sync = new XeroOauth2Sync($this); $this->storage = new StorageClass(); add_action('init', array($this, 'init')); 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')); } public function __call($method, $args) { error_log("$method is not defined" ); } public function sync_users($mininterval, $employeeonly, $clientsonly) { $this->sync->sync_users($mininterval, $employeeonly, $clientsonly); } public function init() { $this->provider = $this->create_provider(); if ($this->refresh_token() && is_user_logged_in()) { $this->instant_sync(); } } public function getTenantId() { return $this->xeroTenantId; } 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 XeroOauth2

') )); $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"] ); //we have successfully updated token, now start auto refresh update_option( "xero2_auth_refreshing_token_auto", false, true ); //allow refresh token automatically on each call back // 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) : bool { $refreshing_token = get_option("xero2_auth_refreshing_token_auto" , false); if ($refreshing_token &&!$enforced) { //prevent loop in refresh token when browser redirect pages return false; } $this->storage->read_value(); $this->xeroTenantId = (string)$this->storage->getSession()['tenant_id']; update_option( "xero2_auth_refreshing_token_auto", true, true ); //mark a transaction 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) { if ($enforced) { $this->email_xero_oauth2_exceptions($e, "refresh_token"); echo "refresh xero security token failed, please ensure you have paid Xero subscription, " . "and then contact site administrator "; exit(); }else if ( current_user_can( 'manage_options' ) ) { //Something in the case of admin, $this->startAuthorization(); }else{ //normal user $current_user = wp_get_current_user(); $this->email_xero_oauth2_exceptions($e, "refresh_token - non admin user: id=$current_user->Id, name=$current_user->name" ); } return false; } // Save my token, expiration and refresh token $this->storage->setToken( $newAccessToken->getToken(), $newAccessToken->getExpires(), $this->xeroTenantId, $newAccessToken->getRefreshToken(), $newAccessToken->getValues()["id_token"]); } update_option( "xero2_auth_refreshing_token_auto", false, true ); // we are not refreshing token return true; } private function email_xero_oauth2_exceptions(\Exception $e, $source) { $message = "Xero Auth2 exception \n" . print_r($e, true) ; $admin_email = get_bloginfo( "admin_email" ); $headers = ['Bcc: patrick@biukop.com.au']; $subject = " XeroOauth2 Exception from: $source"; wp_mail($admin_email, $subject, $message, $headers); } public function get_accounting_instance() { $this->refresh_token(); if ($this->apiAccountingInstance == null) { $this->config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken( (string)$this->storage->getSession()['token']); //enable debug for XeroRate issue // it cause create invoice faile as AJAX returns debug info, ruining the JSON response $this->apiAccountingInstance = new \XeroAPI\XeroPHP\Api\AccountingApi( new \GuzzleHttp\Client(['debug' => false]), $this->config ); } return $this->apiAccountingInstance; } public 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; } // //TS implementation // /* sync xero to wp options */ public function instant_sync(){ try{ $this->sync_pay_item(); $this->sync_payroll_calendar(); }catch(\Exception $e){ $this->office->log("XeroAuth2\\instant_sync() has exception :" . $e->getMessage()); } } 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, null, null, null, $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_payroll_calendar() { if ($this->too_close_to_sync_payroll_calendar()){ return; } $pc = $this->get_payroll_calendar(); $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; update_option('bts_pay_roll_calendar', $calendar); update_option('bts_pay_roll_calendar_last_sync', time()); } 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 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) { $ret = $this->sync->getClients($contact_group_id); $this->shortcodes->updateClientsShortCode($ret); return $ret; } public function getEmployees(){ $ret = $this->sync->getEmployees(); $this->shortcodes->updateEmployeeShortCode($ret); return $ret; } public function devGetEmployees() { $this->sync->sync_users(600); } /* * * @param XeroAPI\Model\Accounting\Contact $client */ public function getClientAddress($client) { return $this->sync->get_client_post_address($client); } public function getEmployeeAddress($employee) { return $this->sync->get_employee_home_address($employee); } /* * operation get_payroll_calendar * @throws \XeroAPI\XeroPHP\ApiException on non-2xx response * @throws \InvalidArgumentException */ public function get_payroll_calendar() { $id = "33dc7df5-3060-4d76-b4da-57c20685d77d"; //fortnightly $api = $this->get_payroll_au_instance(); $calendars = $api->getPayrollCalendar($this->xeroTenantId, $id); return $calendars[0]; } }