['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 .= " | # |
unique-id |
name |
Supplier |
Customer |
Group |
Status |
";
$count = 1;
foreach ($contacts as $c) {
$group = "";
foreach ($c->getContactGroups() as $g) {
$group .= $g->getName() . " - " . $g->getStatus() . "
";
}
$message .= " | " . $count++ . " | " .
" " . $c->getContactId() . " | " .
" " . $c->getName() . " | " .
" " . ($c->getIsSupplier() ? "yes" : " - ") . " | " .
" " . ($c->getIsCustomer() ? "yes" : " - ") . " | " .
" " . $group . " | " .
" " . $c->getContactStatus() . " | " .
"
";
}
$message .= "
";
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 .= " | # |
invoice num |
date |
contact |
amount |
Type |
Status |
";
$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 .= " | " . $count ++ . " | " .
" " . $c->getInvoiceNumber() . " | " .
" " . $strDate . " | " .
" " . $c->getContact()->getName() . " | " .
" " . $c->getTotal() . " | " .
" " . $c->getType() . " | " .
" " . $c->getStatus() . " | " .
"
";
}
$message .= "
";
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 .= " | # |
Name |
EarningsType |
RateType |
AccountCode |
Multiplier |
IsExemptFromTax |
IsExemptFromSuper |
AccrueLeave |
IsReportableAsW1 |
UpdatedDateUTC |
CurrentRecord |
";
$count = 1;
foreach ( $rates as $r) {
$message .= " | " . $r->getEarningsRateId() . " | " .
" " . $r->getName() . " | " .
" " . $r->getEarningsType() . " | " .
" " . $r->getRateType() . " | " .
" " . $r->getAccountCode() . " | " .
" " . $r->getTypeOfUnits() . " | " .
" " . $r->getRatePerUnit() . " | " .
" " . $r->getIsExemptFromTax() . " | " .
" " . $r->getIsExemptFromSuper() . " | " .
" " . $r->getIsReportableAsW1() . " | " .
" " . $r->getUpdatedDateUtc() . " | " .
" " . $r->getCurrentRecord() . " | " .
"
";
}
$message .= "
";
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 .= " | # |
First |
Last |
Status |
Email |
DOB |
Gender |
Phone |
Mobile |
Start |
Group |
";
$count = 1;
foreach ($employees as $r){
$message .= " | " . $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() . " | " .
"
";
$count ++;
}
$message .= "
";
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 .= " | # |
Name |
Last |
Status |
Email |
AccountNumber |
Addresses |
";
$count = 1;
foreach ($contacts as $r){
$message .= " | " . $count . " - ". $r->getContactID() . " | " .
" " . $r->getFirstName() . " | " .
" " . $r->getLastName() . " | " .
" " . $r->getContactStatus() . " | " .
" " . $r->getEmailAddress() . " | " .
" " . $r->getAccountNumber() . " | " .
" " . $r->getAddresses() . " | " .
"
";
$count ++;
}
$message .= "
";
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;
}
}