| @@ -27,7 +27,9 @@ | |||
| <path value="$PROJECT_DIR$/xero-php-master/vendor/phpdocumentor/reflection-common" /> | |||
| </include_path> | |||
| </component> | |||
| <component name="PhpProjectSharedConfiguration" php_language_level="5.5.0" /> | |||
| <component name="PhpProjectSharedConfiguration" php_language_level="7.0"> | |||
| <option name="suggestChangeDefaultLanguageLevel" value="false" /> | |||
| </component> | |||
| <component name="PhpUnit"> | |||
| <phpunit_settings> | |||
| <PhpUnitSettings custom_loader_path="$PROJECT_DIR$/xero-php-master/vendor/autoload.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('<h1>Connect/Reconnect</h2><p>if the above field is empty, | |||
| or the expire date looks suspicous, please reconnect to XeroOauth1 </p> | |||
| or the expire date looks suspicous, please reconnect to XeroOauth2 </p> | |||
| <form action="/"> | |||
| <input type="hidden" name="xero_reauth" value="1" /> | |||
| <input type="submit" class="button button-primary button-large" value="Xero Connect" /> | |||
| @@ -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]; | |||
| } | |||
| } | |||
| } | |||
| @@ -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 = "<table> "; | |||
| $message .= "<tr> <td> # </td> | |||
| <td> First </td> | |||
| <td> Name </td> | |||
| <td> Last </td> | |||
| <td> Status </td> | |||
| <td> Email </td> | |||
| <td> DOB </td> | |||
| <td> Gender </td> | |||
| <td> Phone </td> | |||
| <td> Mobile </td> | |||
| <td> Start </td> | |||
| <td> AccountNumber </td> | |||
| <td> Addresses </td> | |||
| <td> Updated (UTC) </td> | |||
| </tr> "; | |||
| $count = 1; | |||
| foreach ($employees as $r){ | |||
| foreach ($contacts as $r){ | |||
| $message .= "<tr> <td>" . $count . "</td> " . | |||
| "<td> " . $r->getFirstName() . "</td> " . | |||
| "<td> " . $r->getLastName() . "</td> " . | |||
| "<td> " . $r->getStatus() . "</td> " . | |||
| "<td> " . $r->getEmail() . "</td> " . | |||
| "<td> " . $r->getDateOfBirthAsDate()->format("M d Y") . "</td> " . | |||
| "<td> " . $r->getGender() . "</td> " . | |||
| "<td> " . $r->getPhone() . "</td> " . | |||
| "<td> " . $r->getMobile() . "</td> " . | |||
| "<td> " . $r->getStartDateAsDate()->format("M d Y") . "</td> " . | |||
| "<td> " . $r->getContactStatus() . "</td> " . | |||
| "<td> " . $r->getEmailAddress() . "</td> " . | |||
| "<td> " . $r->getAccountNumber() . "</td> " . | |||
| "<td> " . $this->oauth2->getClientAddress($r) . "</td> " . | |||
| "<td> " . $r->getUpdatedDateUtcAsDate()->format("Y-m-d") . "</td> " . | |||
| "</tr>"; | |||
| $count ++; | |||
| } | |||
| $message .= "</table>"; | |||
| 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 = "<table> "; | |||
| $message .= "<tr> <td> # </td> | |||
| <td> Name </td> | |||
| <td> First </td> | |||
| <td> Last </td> | |||
| <td> Status </td> | |||
| <td> Email </td> | |||
| <td> AccountNumber </td> | |||
| <td> Addresses </td> | |||
| <td> DOB </td> | |||
| <td> Gender </td> | |||
| <td> Mobile </td> | |||
| <td> Start </td> | |||
| <td> Updated(UTC) </td> | |||
| </tr> "; | |||
| $count = 1; | |||
| foreach ($contacts as $r){ | |||
| $message .= "<tr> <td>" . $count . " - ". $r->getContactID() . "</td> " . | |||
| foreach ($employees as $r){ | |||
| if ($r->getEmployeeGroupName() !== "Web-Employee" ){ | |||
| continue; //dont include them; | |||
| } | |||
| $message .= "<tr> <td>" . $count . "</td> " . | |||
| "<td> " . $r->getFirstName() . "</td> " . | |||
| "<td> " . $r->getLastName() . "</td> " . | |||
| "<td> " . $r->getContactStatus() . "</td> " . | |||
| "<td> " . $r->getEmailAddress() . "</td> " . | |||
| "<td> " . $r->getAccountNumber() . "</td> " . | |||
| "<td> " . $r->getAddresses() . "</td> " . | |||
| "<td> " . $r->getStatus() . "</td> " . | |||
| "<td> " . $r->getEmail() . "</td> " . | |||
| "<td> " . $r->getDateOfBirthAsDate()->format("M d Y") . "</td> " . | |||
| "<td> " . $r->getGender() . "</td> " . | |||
| "<td> " . $r->getMobile() . "</td> " . | |||
| "<td> " . $r->getStartDateAsDate()->format("M d Y") . "</td> " . | |||
| "<td> " . $r->getUpdatedDateUtcAsDate()->format("Y-m-d") . "</td> " . | |||
| "</tr>"; | |||
| $count ++; | |||
| } | |||
| $message .= "</table>"; | |||
| 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); | |||
| } | |||
| } | |||
| @@ -0,0 +1,398 @@ | |||
| <?php | |||
| namespace Biukop; | |||
| class XeroOauth2Sync | |||
| { | |||
| private $oauth2; | |||
| private $clientContactGroupID="48646f3d-cf5e-4fea-8c8b-5812bd540e1b"; | |||
| private $minimum_sync_interval_in_seconds=600; | |||
| public function __construct($oauth2) | |||
| { | |||
| $this->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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -28,7 +28,7 @@ function on_download_ndis_csv (){} | |||
| function loading() | |||
| { | |||
| return "<tr class='loading' ><td colspan=4 class='loading'><img src='"+bts().load_job_img +"'><br><h1>Sync to XeroOauth1</h1></td></tr>"; | |||
| return "<tr class='loading' ><td colspan=4 class='loading'><img src='"+bts().load_job_img +"'><br><h1>Sync to Xero</h1></td></tr>"; | |||
| } | |||
| function display_hour_lines(response) | |||
| @@ -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(); | |||