|
- <?php
-
- /* xero integration oauth 2 */
-
- /* required by XeroOauth2 in 2021 */
-
- namespace Biukop;
- require_once(dirname(__FILE__) . '/vendor/autoload.php');
- // require_once(ABSPATH . 'wp-includes/pluggable.php');
- require_once(dirname(__FILE__) . '/Storage.php');
-
- use \Carbon_Fields\Container;
- use \Carbon_Fields\Field;
- use \Carbon_Fields\Carbon_Fields;
- use phpDocumentor\Reflection\DocBlock\Tags\Method;
-
- class XeroOAuth2
- {
- private $office; // parent
-
- private $clientID = '83CC79EEC6A54B4E8C2CA7AD61D1BF69';
- private $clientSecret = 'axgKF-Ri60D89conDFhqZsi1wu7uLdQFGvMpino9nI-nfO3f';
-
- private $minimum_sync_interval_in_seconds = 600;
-
- public $provider;
- public $options = [
- 'scope' => ['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('<h1>Connect/Reconnect</h2><p>if the above field is empty,
- 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" />
- </form>')
- ));
-
- $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];
- }
-
- }
|