timesheet source code
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

443 rindas
16KB

  1. <?php
  2. /* xero integration oauth 2 */
  3. /* required by XeroOauth1 in 2021 */
  4. namespace Biukop;
  5. require_once(dirname(__FILE__) . '/vendor/autoload.php');
  6. // require_once(ABSPATH . 'wp-includes/pluggable.php');
  7. require_once(dirname(__FILE__) . '/Storage.php');
  8. use \Carbon_Fields\Container;
  9. use \Carbon_Fields\Field;
  10. use \Carbon_Fields\Carbon_Fields;
  11. use phpDocumentor\Reflection\DocBlock\Tags\Method;
  12. class XeroOAuth2
  13. {
  14. private $office; // parent
  15. private $clientID = '83CC79EEC6A54B4E8C2CA7AD61D1BF69';
  16. private $clientSecret = 'axgKF-Ri60D89conDFhqZsi1wu7uLdQFGvMpino9nI-nfO3f';
  17. private $clientContactGroupID="48646f3d-cf5e-4fea-8c8b-5812bd540e1b";
  18. public $provider;
  19. public $options = [
  20. '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']
  21. ];
  22. public $storage;
  23. public $config;
  24. public $apiAccountingInstance; //accounting instance
  25. public $apiPayrollInstance; // payroll au instance
  26. public $xeroTenantId;
  27. private $shortcodes;
  28. public function __construct($office)
  29. {
  30. $this->office = $office;
  31. $this->shortcodes = new XeroOauth2ShortCode($this);
  32. $this->storage = new StorageClass();
  33. add_action('init', array($this, 'init'));
  34. add_action( 'plugins_loaded', array( '\\Carbon_Fields\\Carbon_Fields', 'boot' ) );
  35. add_action('carbon_fields_register_fields', array($this, 'build_settings_page'));
  36. add_action('carbon_fields_container_activated', array($this, 'field_activated'));
  37. add_action('parse_request', array($this, 'xero_callback'));
  38. }
  39. public function __call($method, $args) {
  40. if ( method_exists($this->XeroOauth1, $method) ) {
  41. syslog(LOG_INFO,"Calling $method");
  42. $this->XeroOauth1->$method($args);
  43. } else {
  44. error_log("$method is not defined" );
  45. }
  46. }
  47. public function init()
  48. {
  49. $this->provider = $this->create_provider();
  50. $this->init_wp();
  51. }
  52. public function getTenantId() {
  53. return $this->xeroTenantId;
  54. }
  55. private function create_provider() {
  56. $provider = new \League\OAuth2\Client\Provider\GenericProvider([
  57. 'clientId' => $this->clientID,
  58. 'clientSecret' => $this->clientSecret,
  59. 'redirectUri' => $this->getRedirectURL(),
  60. 'urlAuthorize' => 'https://login.xero.com/identity/connect/authorize',
  61. 'urlAccessToken' => 'https://identity.xero.com/connect/token',
  62. 'urlResourceOwnerDetails' => 'https://api.xero.com/api.xro/2.0/Organisation'
  63. ]);
  64. return $provider;
  65. }
  66. public function load_storage()
  67. {
  68. //$this->storage->read_value();
  69. }
  70. public function boot_carbon()
  71. {
  72. \Carbon_Fields\Carbon_Fields::boot();
  73. }
  74. function build_settings_page()
  75. {
  76. Container::make('theme_options', __('Xero Integration'))
  77. ->set_page_parent('options-general.php')
  78. ->add_fields(array(
  79. Field::make('text', 'xero_oauth2state', 'Xero Oauth2 State')
  80. ->set_attribute('maxLength', 2048)
  81. ->set_attribute('readOnly', true)
  82. ->set_default_value($this->storage->getOauth2State()),
  83. Field::make('text', 'xero_token', 'Xero Token')
  84. ->set_attribute('maxLength', 2048)
  85. ->set_attribute('readOnly', true)
  86. ->set_default_value($this->storage->getSession()['token']),
  87. Field::make('text', 'xero_refresh_token', 'RefreshToken')
  88. ->set_attribute('readOnly', true)
  89. ->set_default_value($this->storage->getSession()['refresh_token']),
  90. Field::make('text', 'xero_id_token', 'ID Token')
  91. ->set_attribute('readOnly', true)
  92. ->set_default_value($this->storage->getSession()['id_token']),
  93. Field::make('text', 'xero_tenant_id', 'Tenant ID')
  94. ->set_attribute('readOnly', true)
  95. ->set_default_value($this->storage->getSession()['tenant_id']),
  96. Field::make('text', 'xero_expires', 'Expires')
  97. ->set_attribute('readOnly', true)
  98. ->set_default_value($this->storage->getSession()['expires']),
  99. Field::make('text', 'xero_expires_human', 'Expires')
  100. ->set_attribute('readOnly', true)
  101. ->set_default_value($this->storage->getSession()['expires_human']),
  102. Field::make('html', 'crb_information_text')
  103. ->set_html('<h1>Connect/Reconnect</h2><p>if the above field is empty,
  104. or the expire date looks suspicous, please reconnect to XeroOauth1 </p>
  105. <form action="/">
  106. <input type="hidden" name="xero_reauth" value="1" />
  107. <input type="submit" class="button button-primary button-large" value="Xero Connect" />
  108. </form>')
  109. ));
  110. $this->storage->read_value();
  111. }
  112. function field_activated()
  113. {
  114. $this->storage->read_value();
  115. $xeroTenantId = (string)$this->storage->getSession()['tenant_id'];
  116. if ($xeroTenantId == "") {
  117. $this->startAuthorization();
  118. } else {
  119. $this->refresh_token();
  120. }
  121. }
  122. public function startAuthorization()
  123. {
  124. // This returns the authorizeUrl with necessary parameters applied (e.g. state).
  125. $authorizationUrl = $this->provider->getAuthorizationUrl($this->options);
  126. // Save the state generated for you and store it to the session.
  127. // For security, on callback we compare the saved state with the one returned to ensure they match.
  128. $this->storage->setOauth2State($this->provider->getState());
  129. // Redirect the user to the authorization URL.
  130. header('Location: ' . $authorizationUrl);
  131. exit();
  132. }
  133. function xero_callback()
  134. {
  135. if (isset($_GET['xero_reauth'])) {
  136. $this->startAuthorization();
  137. return;
  138. }
  139. if (!isset($_GET['xero_callback'])) {
  140. return;
  141. }
  142. // If we don't have an authorization code then get one
  143. if (!isset($_GET['code'])) {
  144. echo "Something went wrong, no authorization code found";
  145. exit("Something went wrong, no authorization code found");
  146. // Check given state against previously stored one to mitigate CSRF attack
  147. } elseif (empty($_GET['state']) || ($_GET['state'] !== $this->storage->getOauth2State())) {
  148. echo "Invalid State";
  149. $this->storage->setOauth2State("");
  150. exit('Invalid state');
  151. } else {
  152. try {
  153. // Try to get an access token using the authorization code grant.
  154. $accessToken = $this->provider->getAccessToken('authorization_code', [
  155. 'code' => $_GET['code']
  156. ]);
  157. $config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken((string)$accessToken->getToken());
  158. $identityInstance = new \XeroAPI\XeroPHP\Api\IdentityApi(
  159. new \GuzzleHttp\Client(),
  160. $config
  161. );
  162. $result = $identityInstance->getConnections();
  163. // Save my tokens, expiration tenant_id
  164. $this->storage->setToken(
  165. $accessToken->getToken(),
  166. $accessToken->getExpires(),
  167. $result[0]->getTenantId(),
  168. $accessToken->getRefreshToken(),
  169. $accessToken->getValues()["id_token"]
  170. );
  171. // related to $this->build_settings_page
  172. $url = "/wp-admin/options-general.php?page=crb_carbon_fields_container_xero_integration.php";
  173. header('Location: ' . $url);
  174. exit();
  175. } catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
  176. echo "Xero Callback failed";
  177. exit();
  178. }
  179. }
  180. }
  181. private function getRedirectURL() {
  182. return get_site_url() . "/?xero_callback";
  183. }
  184. function refresh_token($enforced=false)
  185. {
  186. $this->storage->read_value();
  187. $this->xeroTenantId = (string)$this->storage->getSession()['tenant_id'];
  188. if ($this->storage->getHasExpired() || $enforced) {
  189. $this->provider = $this->create_provider();
  190. try {
  191. $newAccessToken = $this->provider->getAccessToken('refresh_token', [
  192. 'refresh_token' => $this->storage->getRefreshToken()
  193. ]);
  194. } catch (\Exception $e) {
  195. $this->startAuthorization();
  196. return;
  197. }
  198. // Save my token, expiration and refresh token
  199. $this->storage->setToken(
  200. $newAccessToken->getToken(),
  201. $newAccessToken->getExpires(),
  202. $this->xeroTenantId,
  203. $newAccessToken->getRefreshToken(),
  204. $newAccessToken->getValues()["id_token"]);
  205. }
  206. }
  207. public function get_accounting_instance() {
  208. $this->refresh_token();
  209. if ($this->apiAccountingInstance == null) {
  210. $this->config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken(
  211. (string)$this->storage->getSession()['token']);
  212. $this->apiAccountingInstance = new \XeroAPI\XeroPHP\Api\AccountingApi(
  213. new \GuzzleHttp\Client(),
  214. $this->config
  215. );
  216. }
  217. return $this->apiAccountingInstance;
  218. }
  219. public function get_payroll_au_instance() {
  220. $this->refresh_token();
  221. if ($this->apiPayrollInstance == null) {
  222. $this->config = \XeroAPI\XeroPHP\Configuration::getDefaultConfiguration()->setAccessToken(
  223. (string)$this->storage->getSession()['token']);
  224. $this->config->setDebug(true);
  225. $this->config->setDebugFile("/home/acaresydneycom/public_html/wp-content/plugins/ts/debug.log");
  226. $this->apiPayrollInstance = new \XeroAPI\XeroPHP\Api\PayrollAuApi(
  227. new \GuzzleHttp\Client(),
  228. $this->config
  229. );
  230. }
  231. return $this->apiPayrollInstance;
  232. }
  233. //
  234. //TS implementation
  235. //
  236. /* sync xero to wp options */
  237. public function init_wp(){
  238. try{
  239. $this->sync_pay_item();
  240. // $this->add_new_client();
  241. // $this->add_new_employee();
  242. $this->sync_payroll_calendar();
  243. }catch(\Exception $e){
  244. $this->office->log("XeroAuth2\\init_wp() has exception", $e->getMessage());
  245. }
  246. }
  247. private function sync_pay_item() {
  248. if ($this->too_close_to_sync_payitem()){
  249. return;
  250. }
  251. $api = $this->get_payroll_au_instance();
  252. $xeroTenantId = $this->xeroTenantId;
  253. $page = 1;
  254. try {
  255. $result = $api->getPayItems($xeroTenantId, null, null, null, $page);
  256. foreach ($result->getPayItems()->getEarningsRates() as $e){
  257. // "EarningsRateID": "34e17d08-237a-4ae2-8115-375d1ff8a9ed",
  258. // "Name": "Overtime Hours (exempt from super)",
  259. // "EarningsType": "OVERTIMEEARNINGS",
  260. // "RateType": "MULTIPLE",
  261. // "AccountCode": "477",
  262. // "Multiplier": 1.5,
  263. // "IsExemptFromTax": true,
  264. // "IsExemptFromSuper": true,
  265. // "AccrueLeave": false,
  266. // "IsReportableAsW1": true,
  267. // "UpdatedDateUTC": "2019-03-16T13:18:19+00:00",
  268. // "CurrentRecord": false
  269. if ($e->getCurrentRecord() == "true"){
  270. $payitem_options[]= array(
  271. 'EarningsRateID' => $e->getEarningsRateID(),
  272. 'Name'=> $e->getName(),
  273. 'EarningsType'=> $e->getEarningstype(),
  274. 'RatePerUnit' => $e->getRatePerUnit(),
  275. 'RateType' => $e->getRateType(),
  276. 'AccountCode' => $e->getAccountCode(),
  277. "Multiplier"=> $e->getMultiplier(),
  278. "IsExemptFromTax" => $e->getIsExemptFromTax(),
  279. "IsExemptFromSuper"=> $e->getIsExemptFromSuper(),
  280. "AccrueLeave" => $e->getAccrueLeave(),
  281. "TypeOfUnits" => $e->getTypeOfUnits(),
  282. "CurrentRecord"=> $e->getCurrentRecord(),
  283. );
  284. }
  285. }
  286. update_option('bts_payitem_earnings_rate', $payitem_options);
  287. update_option('bts_payitem_last_sync', time());
  288. } catch (Exception $e) {
  289. echo 'Exception when calling PayrollAuApi->getPayItems: ', $e->getMessage(), PHP_EOL;
  290. }
  291. }
  292. public function sync_users($mininterval, $employeeonly, $clientsonly){
  293. echo "not implemented "; //TODO;
  294. }
  295. public function sync_payroll_calendar() {
  296. if ($this->too_close_to_sync_payroll_calendar()){
  297. // return;
  298. }
  299. update_option('bts_pay_roll_calendar_last_sync', time());
  300. $pc = $this->get_payroll_calendar();
  301. $start = $pc->getStartDateAsDate()->format('Y-m-d');
  302. $finish = new \DateTime($start);
  303. $finish = $finish->modify("+13 days")->format('Y-m-d');
  304. $paydate = $pc->getPaymentDateAsDate()->format('Y-m-d');
  305. $calendar["start"] = $start;
  306. $calendar["finish"] = $finish;
  307. $calendar["paydate"] = $paydate;
  308. update_option('bts_pay_roll_calendar', $calendar);
  309. }
  310. private function too_close_to_sync_payitem(){
  311. $lastsync = get_option('bts_payitem_last_sync', 0);
  312. $now = time();
  313. $diff = $now - (int) $lastsync;
  314. return $diff < $this->minimum_sync_interval_in_seconds; //default 10 minutes
  315. }
  316. private function too_close_to_add_employee(){
  317. $lastsync = get_option('bts_add_employee_last_sync', 0);
  318. $now = time();
  319. $diff = $now - (int) $lastsync;
  320. return $diff < 1.5 * $this->minimum_sync_interval_in_seconds; //default 1.1 * 10 minutes
  321. }
  322. private function too_close_to_add_client(){
  323. $lastsync = get_option('bts_add_client_last_sync', 0);
  324. $now = time();
  325. $diff = $now - (int) $lastsync;
  326. return $diff < 2.0 * $this->minimum_sync_interval_in_seconds; //default 1.2 * 10 minutes
  327. }
  328. private function too_close_to_sync_payroll_calendar(){
  329. $lastsync = get_option('bts_pay_roll_calendar_last_sync', 0);
  330. $now = time();
  331. $diff = $now - (int) $lastsync;
  332. return $diff < 3.0 * $this->minimum_sync_interval_in_seconds; //default 1.3 * 10 minutes
  333. }
  334. public function getClients($contact_group_id = null) {
  335. if ( $contact_group_id == null ){
  336. $contact_group_id = $this->clientContactGroupID;
  337. }
  338. $apiAcc = $this->get_accounting_instance();
  339. $result = $apiAcc->getContactGroup($this->xeroTenantId, $contact_group_id);
  340. $cg = $result->getContactGroups();
  341. $allClients = $cg[0]->getContacts();
  342. $ifModifiedSince = new \DateTime();
  343. $recent = new \DateInterval("P30D");
  344. $ifModifiedSince->sub($recent);
  345. $allContacts = $apiAcc->getContacts($this->xeroTenantId, $ifModifiedSince);
  346. $ret = [];
  347. foreach ( $allContacts as $ac ) {
  348. //search from within the group
  349. $found = false;
  350. $id = $ac->getContactID();
  351. foreach ($allClients as $client) {
  352. $clientID = $client->getContactID();
  353. if ( $clientID == $id ) {
  354. $found = true;
  355. break;
  356. }
  357. }
  358. if ( $found ) {
  359. $ret[] = $ac;
  360. }
  361. }
  362. return $ret;
  363. }
  364. /*
  365. * operation get_payroll_calendar
  366. * @throws \XeroAPI\XeroPHP\ApiException on non-2xx response
  367. * @throws \InvalidArgumentException
  368. */
  369. public function get_payroll_calendar()
  370. {
  371. $id = "33dc7df5-3060-4d76-b4da-57c20685d77d"; //fortnightly
  372. $api = $this->get_payroll_au_instance();
  373. $calendars = $api->getPayrollCalendar($this->xeroTenantId, $id);
  374. return $calendars[0];
  375. }
  376. }