timesheet source code
No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

419 líneas
15KB

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