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.

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