diff --git a/.idea/php.xml b/.idea/php.xml index 329d0f0..d3e0cec 100644 --- a/.idea/php.xml +++ b/.idea/php.xml @@ -27,7 +27,9 @@ - + + diff --git a/XeroOauth2.php b/XeroOauth2.php index 1ee308c..c0619ca 100644 --- a/XeroOauth2.php +++ b/XeroOauth2.php @@ -2,7 +2,7 @@ /* xero integration oauth 2 */ -/* required by XeroOauth1 in 2021 */ +/* required by XeroOauth2 in 2021 */ namespace Biukop; require_once(dirname(__FILE__) . '/vendor/autoload.php'); @@ -20,7 +20,8 @@ class XeroOAuth2 private $clientID = '83CC79EEC6A54B4E8C2CA7AD61D1BF69'; private $clientSecret = 'axgKF-Ri60D89conDFhqZsi1wu7uLdQFGvMpino9nI-nfO3f'; - private $clientContactGroupID="48646f3d-cf5e-4fea-8c8b-5812bd540e1b"; + + private $minimum_sync_interval_in_seconds = 600; public $provider; public $options = [ @@ -34,11 +35,13 @@ class XeroOAuth2 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')); @@ -51,18 +54,19 @@ class XeroOAuth2 } 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" ); - } + 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(); - $this->init_wp(); + if ($this->refresh_token() && is_user_logged_in()) { + $this->instant_sync(); + } } public function getTenantId() { @@ -122,7 +126,7 @@ class XeroOAuth2 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

+ or the expire date looks suspicous, please reconnect to XeroOauth2

@@ -161,6 +165,7 @@ class XeroOAuth2 function xero_callback() { + if (isset($_GET['xero_reauth'])) { $this->startAuthorization(); return; @@ -203,6 +208,10 @@ class XeroOAuth2 $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); @@ -219,12 +228,19 @@ class XeroOAuth2 return get_site_url() . "/?xero_callback"; } - function refresh_token($enforced=false) + 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']; - if ($this->storage->getHasExpired() || $enforced) { + update_option( "xero2_auth_refreshing_token_auto", true, true ); //mark a transaction + if ($this->storage->getHasExpired() || $enforced ) { $this->provider = $this->create_provider(); try { @@ -233,8 +249,21 @@ class XeroOAuth2 ]); } catch (\Exception $e) { - $this->startAuthorization(); - return; + 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 @@ -245,15 +274,28 @@ class XeroOAuth2 $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']); + (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(), + new \GuzzleHttp\Client(['debug' => false]), $this->config ); } @@ -265,8 +307,8 @@ class XeroOAuth2 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->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 @@ -281,14 +323,12 @@ class XeroOAuth2 // /* sync xero to wp options */ - public function init_wp(){ + public function instant_sync(){ try{ $this->sync_pay_item(); -// $this->add_new_client(); -// $this->add_new_employee(); $this->sync_payroll_calendar(); }catch(\Exception $e){ - $this->office->log("XeroAuth2\\init_wp() has exception", $e->getMessage()); + $this->office->log("XeroAuth2\\instant_sync() has exception :" . $e->getMessage()); } } @@ -342,15 +382,10 @@ class XeroOAuth2 } } - public function sync_users($mininterval, $employeeonly, $clientsonly){ - echo "not implemented "; //TODO; - } - public function sync_payroll_calendar() { if ($this->too_close_to_sync_payroll_calendar()){ - // return; + return; } - update_option('bts_pay_roll_calendar_last_sync', time()); $pc = $this->get_payroll_calendar(); $start = $pc->getStartDateAsDate()->format('Y-m-d'); @@ -362,6 +397,8 @@ class XeroOAuth2 $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(){ @@ -371,18 +408,7 @@ class XeroOAuth2 return $diff < $this->minimum_sync_interval_in_seconds; //default 10 minutes } - 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(); @@ -392,41 +418,33 @@ class XeroOAuth2 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; - } + $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 @@ -440,4 +458,4 @@ class XeroOAuth2 return $calendars[0]; } -} \ No newline at end of file +} diff --git a/XeroOauth2ShortCode.php b/XeroOauth2ShortCode.php index 8755321..f049075 100644 --- a/XeroOauth2ShortCode.php +++ b/XeroOauth2ShortCode.php @@ -159,110 +159,122 @@ class XeroOauth2ShortCode } public function xero_org_employees(){ - ini_set('display_errors', 'On'); - $api = $this->oauth2->get_payroll_au_instance(); + // $this->oauth2->getEmployees(); + //$this->oauth2->devGetEmployees(); + return get_option('bts_employees_updated_desc'); - $xeroTenantId = $this->oauth2->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; + public function xero_org_clients(){ + //$this->oauth2->getClients(); + return get_option('bts_clients_updated_desc'); + } + + + public function xero_org_payroll_calendar() + { + update_option('bts_pay_roll_calendar_last_sync', time()); + try { + $pc = $this->oauth2->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); + return "start = $start, finish=$finish, paydate=$paydate"; + }catch (\Exception $e) { + echo 'Exception when calling PayrollAuApi->getPayrollCalendar: ', $e->getMessage(), PHP_EOL; + } + + } + + + public function updateClientsShortCode($allClients) { + // ini_set('display_errors', 'On'); try { - $result = $api->getEmployees($xeroTenantId,$ifModifiedSince,$where,$order,$page); - $employees = $result->getEmployees(); + $contacts = $allClients; + $message = " "; $message .= " - + - - - - - + + + "; $count = 1; - foreach ($employees as $r){ + foreach ($contacts as $r){ $message .= " " . " " . " " . - " " . - " " . - " " . - " " . - " " . - " " . - " " . + " " . + " " . + " " . + " " . + " " . ""; $count ++; } $message .= "
# First Name Last Status Email DOB Gender Phone Mobile Start AccountNumber Addresses Updated (UTC)
" . $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->getContactStatus() . " " . $r->getEmailAddress() . " " . $r->getAccountNumber() . " " . $this->oauth2->getClientAddress($r) . " " . $r->getUpdatedDateUtcAsDate()->format("Y-m-d") . "
"; + update_option('bts_clients_updated_desc', $message); return $message; } catch (\Exception $e) { - echo 'Exception when calling PayrollAuApi->getPayItems: ', $e->getMessage(), PHP_EOL; + echo 'Exception when calling PayrollAuApi->getPayContacts: ', $e->getMessage(), PHP_EOL; return; } } - public function xero_org_clients(){ + + public function updateEmployeeShortCode($allEmployees) { ini_set('display_errors', 'On'); + try { - $contacts = $this->oauth2->getClients(); + $employees = $allEmployees; + + $message = " "; $message .= " - + - - + + + + + "; $count = 1; - foreach ($contacts as $r){ - - $message .= " " . + foreach ($employees as $r){ + if ($r->getEmployeeGroupName() !== "Web-Employee" ){ + continue; //dont include them; + } + $message .= " " . " " . " " . - " " . - " " . - " " . - " " . + " " . + " " . + " " . + " " . + " " . + " " . + " " . ""; $count ++; } $message .= "
# Name First Last Status Email AccountNumber Addresses DOB Gender Mobile Start Updated(UTC)
" . $count . " - ". $r->getContactID() . "
" . $count . " " . $r->getFirstName() . " " . $r->getLastName() . " " . $r->getContactStatus() . " " . $r->getEmailAddress() . " " . $r->getAccountNumber() . " " . $r->getAddresses() . " " . $r->getStatus() . " " . $r->getEmail() . " " . $r->getDateOfBirthAsDate()->format("M d Y") . " " . $r->getGender() . " " . $r->getMobile() . " " . $r->getStartDateAsDate()->format("M d Y") . " " . $r->getUpdatedDateUtcAsDate()->format("Y-m-d") . "
"; + update_option('bts_employees_updated_desc', $message); return $message; } catch (\Exception $e) { - echo 'Exception when calling PayrollAuApi->getPayContacts: ', $e->getMessage(), PHP_EOL; + echo 'Exception when calling PayrollAuApi->getPayItems: ', $e->getMessage(), PHP_EOL; return; } } - - public function xero_org_payroll_calendar() - { - update_option('bts_pay_roll_calendar_last_sync', time()); - try { - $pc = $this->oauth2->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; - - return "start = $start, finish=$finish, paydate=$paydate"; - }catch (\Exception $e) { - echo 'Exception when calling PayrollAuApi->getPayrollCalendar: ', $e->getMessage(), PHP_EOL; - } - update_option('bts_pay_roll_calendar', $calendar); - } - } \ No newline at end of file diff --git a/XeroOauth2Sync.php b/XeroOauth2Sync.php new file mode 100644 index 0000000..a1cdc86 --- /dev/null +++ b/XeroOauth2Sync.php @@ -0,0 +1,398 @@ +oauth2 = $oauth2; + } + + /* + * Sync users from Xero to WordPress + * + * Contact group: 48646f3d-cf5e-4fea-8c8b-5812bd540e1b "Clients -need carer" + * Employees: All with "web-employee" or "skip_office_sync" + * + * Since Xero has API call rate limits 60 calls/ minute, we can only sync some of them + * + * Assuming sync_users were called at every 30 minutes, each time we retrieve + * all clients, all employees and tries to update them; + * + * + * Minimum API call quota cost,for each sync + * getContacts retrieves all contacts 500+ + * getContactGroups retrieves all clients around 100 + * getEmployees need multiple request, each request get 100 employee at most. + * + * Each time (every sync) we use at least 4 API calls + * + * We then compare the lastsync user_meta value with the update time of each user + * if lastsync is older than last update, then we do updates + * + */ + public function sync_users($mininterval=600, $employeeonly=false, $clientsonly=false){ + $this->usage(); + $this->minimum_sync_interval_in_seconds = $mininterval; + $msg="Sync users with minimum interval set to $mininterval \n"; + $this->logConsole($msg); + + try{ + $this->sync_clients(); + $this->sync_employees(); + }catch(\XeroAPI\XeroPHP\ApiException $e){ + $msg= "Xero API exceiption encountered during Sync: " . $e->getMessage() ." true " . print_r($e,true); + $this->logConsole($msg); + } + } + + private function get_last_sync($userid){ + $lastsync = get_user_meta($userid, 'lastsync', true); + return (int)($lastsync); + } + private function mark_user_updated($userid, $timestamp){ + update_user_meta($userid, 'lastsync', $timestamp); + } + + private function sync_clients(){ + $allClients = $this->oauth2->getClients(); // by calling oauth2, the cache HTML is updated. + foreach ($allClients as $c) { + $this->update_or_create_clients($c); + } + } + + private function sync_employees(){ + $allEmployees = $this->oauth2->getEmployees(); // by calling oauth2, the cache HTML is updated. + $to_update=[]; + $to_test=[]; + $api = $this->oauth2->get_payroll_au_instance(); + foreach ($allEmployees as $e) { + switch($e->getEmployeeGroupName()){ + case "Web-Employee": + $this->update_or_create_employees($e); + break; + case "skip_office_sync": + $this->update_or_create_employees($e); + // $e->setEmployeeGroupName("Web-Employee"); +// $to_update[]= $e; +// if ( count($to_test) <= 2 ){ +// $to_test[] = $e; +// } + break; + default: + // other employee such as duplicate we just bypass them + ; + } + } + } + + private function update_or_create_clients($contact) { + $login = $contact->getContactId(); + if ( trim( $login) === "" ) { + return; //invalid contact; + } + $user = get_user_by('login', $login); + if ($user === false){ + $this->add_new_contact($contact); + }else{ + $this->update_existing_contact($contact); + } + } + + private function update_or_create_employees($employee) { + $login = $employee->getEmployeeID(); + if ( trim( $login) === "" ) { + return; //invalid contact; + } + switch($employee->getEmployeeGroupName()){ + case "Web-Employee": + case "skip_office_sync": + break; + default: + // other employee such as duplicate we just bypass them + return; + } + $user = get_user_by('login', $login); + if ($user === false){ + $this->add_new_staff($employee); + }else{ + $this->update_existing_staff($employee); + } + } + + public function getClients() { + $contact_group_id = $this->clientContactGroupID; + $apiAcc = $this->oauth2->get_accounting_instance(); + $result = $apiAcc->getContactGroupWithHttpInfo($this->oauth2->getTenantId(), $contact_group_id); + $this->logConsole(print_r($result,true)); + $this->logConsole(print_r($result[2],true)); + $cg = $result[0]->getContactGroups(); + $allClients = $cg[0]->getContacts(); + + $ifModifiedSince = new \DateTime(); + $recent = new \DateInterval("P30000D"); // 30,000 days, means everything + $ifModifiedSince->sub($recent); + $where=null; + $order="UpdatedDateUTC DESC"; + + $allContacts = $apiAcc->getContacts($this->oauth2->getTenantId(), $ifModifiedSince, $where, $order); + $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 getEmployees() { + $api = $this->oauth2->get_payroll_au_instance(); + $xeroTenantId = $this->oauth2->xeroTenantId; + //$xeroTenantId = "e23fd416-3b66-43e9-b908-97fbefa24eb8"; // demo company; + //$xeroTenantId = "4e2521ae-83e6-4895-aa90-b20aa0825ce1"; // Acaresydney ; + + $ifModifiedSince = null;// date("M d Y H:i:s", strtotime("-30 days")); + $where = "Status==\"ACTIVE\""; + $order = "UpdatedDateUTC DESC"; // "null; // "EmailAddress%20DESC"; + $page = 1; + + $employees=[]; + do { + $result = $api->getEmployees($xeroTenantId,$ifModifiedSince,$where,$order,$page); + $thisPage = $result->getEmployees(); + $employees = array_merge($employees, $thisPage); + $page++; + }while (count($thisPage) == 100); + + return $employees; + } + + public function get_client_post_address($client){ + $result = ""; + $addr = $this->get_client_address_by_type($client, 'POBOX'); + + if ( $addr != false){ + if ($addr->getAddressLine1() != ""){ + $result .= $addr->getAddressLine1() . ";"; + } + if ($addr->getAddressLine2() != ""){ + $result .= $addr->getAddressLine2() . ";"; + } + if ($addr->getAddressLine3() != ""){ + $result .= $addr->getAddressLine3() . ";"; + } + if ($addr->getAddressLine4() != ""){ + $result .= $addr->getAddressLine4() . ";"; + } + if ($addr->getCity() != ""){ + $result .= $addr->getCity() . ";"; + } + if ($addr->getPostalCode() != ""){ + $result .= $addr->getPostalCode() . ";"; + } + } + // echo "result for client is " . $result . "\n"; + return $result; + } + + private function get_client_address_by_type($client, $t){ + $addr = false; + foreach( $client->getAddresses() as $a){ + if( $a->getAddressType() == $t){ + $addr = $a; + break; + } + } + return $addr; + } + + private function add_new_contact($contact){ + $login = $contact->getContactId(); + $user = get_user_by('login', $login); + if ($user === false){ + $msg = sprintf("ADD Client name=[%s] {%s} \n", $contact->getName(), $contact->getContactId()); + $this->logConsole($msg); + + $args = $this->xero_contact_profile($contact); + $id = wp_insert_user($args); + + if (! $id instanceof \WP_Error){ + $user = get_user_by('ID', $id); + update_user_meta($user->ID, 'address', $args['address']); + update_user_meta($user->ID, 'account', $args['account']); + $this->mark_user_updated($login, $contact->getUpdatedDateUtcAsDate()->format('U')); + }else{ + $msg = "==(Add Client failed)=="; + $this->logConsole($msg); + $msg = sprintf("ADD Client name=[%s] {%s} Failed\n", $contact->getName(), $contact->getContactId()); + error_log("ACARE add client failed: $msg"); + } + } + } + + private function update_existing_contact($contact){ + $login = $contact->getContactID(); + $user = get_user_by('login', $login); + if ($user !== false) + {//update user - must be existing user; + if (! $this->user_requires_update($user, $contact)){ + $this->logConsole("skip update client (lastSync is up-to-date) : " . $contact->getName() . "\n"); + return; + } + $args = $this->xero_contact_profile($contact); + $args['ID'] = $user->ID; + unset($args['user_pass']); //we don't change password + wp_update_user($args); + update_user_meta($user->ID, 'address', $args['address']); + update_user_meta($user->ID, 'account', $args['account']); + $this->mark_user_updated($user->ID, $contact->getUpdatedDateUtcAsDate()->format('U')); + + } + } + + private function xero_contact_profile($c){ + return [ + 'user_login' => $username = $c->getContactId(), + 'user_pass' => md5(uniqid(rand() + time(), true)), + 'display_name' =>$c->getName(), + 'user_email' => $c->getEmailAddress(), + 'first_name' => $c->getFirstName(), + 'last_name' => $c->getLastName(), + 'nickname' => $c->getName(), + 'account' => $c->getAccountNumber(), + 'address'=> $this->get_client_post_address($c), + 'role' => 'client', + ]; + } + + private function user_requires_update($user, $contact) { + + $lastSync = $this->get_last_sync($user->ID); + $updated = $contact->getUpdatedDateUtcAsDate()->format('U');; + + return $lastSync < $updated; + } + + private function add_new_staff($employee) + { + $login = $employee->getEmployeeID(); + $user = get_user_by('login', $login); + if ($user === false){ + $msg = sprintf("ADD employee name=[%s %s] {%s} \n", + $employee->getFirstName(), + $employee->getLastName(), + $employee->getEmployeeID()); + $this->logConsole($msg); + + $args = $this->xero_employee_profile($employee); + $this->logConsole(print_r($args, true)); + $id = wp_insert_user($args); + $this->logConsole(print_r($id, true)); + if (! $id instanceof \WP_Error){ + $user = get_user_by('ID', $id); + update_user_meta($user->ID, 'mobile', $args['mobile']); + update_user_meta($user->ID, 'address', $args['address']); + $this->mark_user_updated($user->ID, $employee->getUpdatedDateUtcAsDate()->format('U')); + }else{ + $msg = "==(Add staff failed)=="; + $this->logConsole($msg); + + $msg = sprintf("ADD employee name=[%s %s] {%s} Failed\n", + $employee->getFirstName(), + $employee->getLastName(), + $employee->getEmployeeID()); + error_log("ACARE add employee failed: $msg"); + } + } + } + + private function update_existing_staff($employee) + { + $login = $employee->getEmployeeID(); + $user = get_user_by('login', $login); + if ($this->user_requires_update($user, $employee)){ + $this->logConsole("skip update EMPLOYEE (lastSync is up-to-date) : " . + $employee->getFirstName() . " " . $employee->getLastName() . "\n"); + return; + } + if ($user != false) { + $args = $this->xero_employee_profile($employee); + $args['ID'] = $user->ID; + unset($args['user_pass']); + wp_update_user($args); + update_user_meta($user->ID, 'mobile', $args['mobile']); + update_user_meta($user->ID, 'address', $args['address']); + $this->mark_user_updated($user->ID, $employee->getUpdatedDateUtcAsDate()->format('U')); + } + } + + private function xero_employee_profile($e){ + $args = [ + 'user_login' => $e->getEmployeeId(), + 'user_pass' => md5(uniqid(rand() + time(), true)), + 'display_name' =>$e->getFirstName() . " " . $e->getLastName(), + 'user_email' => $e->getEmail(), + 'first_name' => $e->getFirstName(), + 'last_name' => $e->getLastName(), + 'nickname' => $e->getFirstName(), + 'mobile' => $e->getMobile(), + 'address'=> $this->get_employee_home_address($e), + 'role' => 'staff', + ]; + return $args; + } + + public function get_employee_home_address($e){ +// "HomeAddress": { +// "AddressLine1": "16 Quist Avenue", +// "City": "Lurnea", +// "Region": "NSW", +// "PostalCode": "2170", +// "Country": "AUSTRALIA" +// }, + $addr = ""; + $home = $e->getHomeAddress(); + if ( $home == null ) { + return ""; + } + $addr .= $home->getAddressLine1() .","; + $addr .= $home->getAddressLine2() .","; + $addr .= $home->getCity() . ","; + $addr .= $home->getRegion() . " "; + $addr .= $home->getCountry() ." "; + $addr .= $home->getPostalCode(); + return $addr; + } + + private function usage(){ + $msg = "_____________________________________________\n"; + $msg .= "run this command at public_html/, where wp-config.php exist \n"; + $msg .= "wp sync_users \n"; + $msg .= "but it may hit XERO rate limit, 60call/sec, 5000/day \n"; + $msg .= "---------------------------------------------\n"; + $this->logConsole($msg); + } + + private function logConsole($str){ + //if is commandline + if ( defined( 'WP_CLI' ) && WP_CLI ) { + echo $str; + } + } +} diff --git a/js/bts_timesheet.js b/js/bts_timesheet.js index 177c5c2..7dda02c 100644 --- a/js/bts_timesheet.js +++ b/js/bts_timesheet.js @@ -711,7 +711,7 @@ return false; } // if (this.get_rate() != this.data.rate){ -// this.set_err_msg_rate('rate@XeroOauth1 inactive ' + this.data.rate); +// this.set_err_msg_rate('rate@Xero inactive ' + this.data.rate); // this.mark_rate_invalid(); // this.mark_dirty(); // return false; diff --git a/js/xeroc.js b/js/xeroc.js index e7a28fc..28fa99b 100644 --- a/js/xeroc.js +++ b/js/xeroc.js @@ -28,7 +28,7 @@ function on_download_ndis_csv (){} function loading() { - return "

Sync to XeroOauth1

"; + return "

Sync to Xero

"; } function display_hour_lines(response) diff --git a/ts.php b/ts.php index 80e0707..006e42d 100644 --- a/ts.php +++ b/ts.php @@ -51,9 +51,7 @@ class AcareOffice{ add_filter('show_admin_bar', '__return_false'); - //ts-xx for sync single user - add_shortcode( 'ts-sync-users', array($this, 'sync_users')); - //bts-xx for webpage + //bts-xx for webpage add_shortcode( 'bts_staff_item', array($this, 'bts_staff_item')); add_shortcode( 'bts_client_item', array($this, 'bts_client_item')); add_shortcode( 'bts_job_item', array($this, 'bts_job_item')); @@ -313,7 +311,10 @@ class AcareOffice{ ///check auth public function check_auth(){ global $pagename; - + + if ($this->is_superadmin()) //skip + return; + switch($pagename){ case 'task': $this->cauth_task(); //for staff @@ -332,6 +333,10 @@ class AcareOffice{ } private function cauth_task(){ + + if ($this->is_superadmin()) //skip + return; + $login = get_query_var( 'bts_user_id' ); $this->bts_job_start = get_query_var( 'bts_job_start' ); $this->bts_job_finish = get_query_var( 'bts_job_finish' ); @@ -363,7 +368,8 @@ class AcareOffice{ //no auto login is required if reach here. $current = wp_get_current_user(); - if ($this->is_admin($current)){ + + if ($this->is_admin($current) ){ wp_redirect("/office/"); return; } @@ -373,7 +379,7 @@ class AcareOffice{ return; } - if (!$this->is_staff($current) && ! $this->is_admin($current)) + if (!$this->is_staff($current) && ! $this->is_admin($current) ) { wp_logout(); wp_redirect("/login/"); @@ -441,6 +447,7 @@ class AcareOffice{ return "/task/week-" . $this->bts_week_id . "/"; if ($this->bts_job_start!="" && $this->bts_job_finish !="") return "/task/start-" . $this->bts_job_start . "/finish-" .$this->bts_job_finish . "/"; + return '/task/'; } @@ -461,6 +468,7 @@ class AcareOffice{ wp_redirect("/wp-login.php?"); return; } + if ($this->is_staff($current)){ wp_redirect("/task"); return; @@ -628,25 +636,29 @@ class AcareOffice{ wp_enqueue_style('jquery-ui-smoothness', $url, false, null); } - public function sync_users() - { - //dummy sync - return; - } - + // Usage: `wp sync_users --mininterval=123 - public function sync_user_cli($args = array(), $assoc_args = array()){ + public function cli_sync_user($args = array(), $assoc_args = array()){ $arguments = wp_parse_args( $assoc_args, array( 'mininterval' => 86400, 'employeeonly' => false, 'clientsonly' => false, ) ); - //TODO: SYNC USER - //$this->xero->sync_users($arguments['mininterval'], $arguments['employeeonly'], $arguments['clientsonly']); + $this->XeroOauth2->sync_users($arguments['mininterval'], $arguments['employeeonly'], $arguments['clientsonly']); return; } - - public function email_jobs($args = array(), $assoc_args = array()){ + + public function cli_refresh_token($args = array(), $assoc_args = array()){ + $this->XeroOauth2->refresh_token(true); + $date = new \DateTime("now", new \DateTimeZone('Australia/Sydney') ); + $strDate = $date->format('Y-m-d H:i:s'); + echo "refresh_token at: $strDate Sydney time \n"; + echo "accessToken: " . $this->XeroOauth2->storage->getAccessToken() . "\n"; + echo "refreshToken: " . $this->XeroOauth2->storage->getRefreshToken() . "\n" ; + echo "expires: " . $this->XeroOauth2->storage->tokenExpiresHumanRedable() . "\n" ; + } + + public function cli_email_jobs($args = array(), $assoc_args = array()){ $users = get_users(array('role' => 'staff')); foreach ($users as $u){ $n = new UserJob($u->user_login); @@ -662,7 +674,7 @@ class AcareOffice{ return; } - public function produce_invoice($args = array(), $assoc_args = array()) + public function cli_produce_invoice($args = array(), $assoc_args = array()) { $users = get_users(array('role' => 'client')); foreach ($users as $u) @@ -672,7 +684,7 @@ class AcareOffice{ } } - public function dev_change_ndis_price ($args = array(), $assoc_args = array()) + public function cli_dev_change_ndis_price ($args = array(), $assoc_args = array()) { echo "list ndis prices\n"; $nd = new NdisPrice(); @@ -1223,6 +1235,12 @@ ZOT; return true; } } + + private function is_superadmin(): bool + { + $current = wp_get_current_user(); + return $current->ID == 1; + } private function is_accountant($user) { @@ -1528,7 +1546,7 @@ ZOT; // $response=[ - 'status'=>success, + 'status'=>'success', 'invoice_number' => '', 'err'=> '', ]; @@ -1693,7 +1711,7 @@ By Carer : %s', } - public function feedback_url() + public function cli_feedback_url() { $users = get_users(array('role'=>'client')); foreach($users as $u){ @@ -1712,11 +1730,12 @@ By Carer : %s', $bb = new AcareOffice(); if ( defined( 'WP_CLI' ) && WP_CLI ) { - \WP_CLI::add_command( 'sync_users', array($bb, 'sync_user_cli')); - \WP_CLI::add_command( 'email_jobs', array($bb, 'email_jobs')); - \WP_CLI::add_command( 'feedback_url', array($bb, 'feedback_url')); - \WP_CLI::add_command( 'produce_invoice', array($bb, 'produce_invoice')); - \WP_CLI::add_command( 'dev_change_ndis_price', array($bb, 'dev_change_ndis_price')); + \WP_CLI::add_command( 'sync_users', array($bb, 'cli_sync_user')); + \WP_CLI::add_command( 'refresh_token', array($bb, 'cli_refresh_token')); +// \WP_CLI::add_command( 'email_jobs', array($bb, 'cli_email_jobs')); +// \WP_CLI::add_command( 'feedback_url', array($bb, 'cli_feedback_url')); +// \WP_CLI::add_command( 'produce_invoice', array($bb, 'cli_produce_invoice')); +// \WP_CLI::add_command( 'dev_change_ndis_price', array($bb, 'cli_dev_change_ndis_price')); } //$bb->class_loader();