PageRenderTime 23ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-content/plugins/google-listings-and-ads/src/API/Google/Settings.php

https://gitlab.com/remyvianne/krowkaramel
PHP | 470 lines | 235 code | 69 blank | 166 comment | 14 complexity | d47b937474fe7d33cef7e9655c0ac558 MD5 | raw file
  1. <?php
  2. declare( strict_types=1 );
  3. namespace Automattic\WooCommerce\GoogleListingsAndAds\API\Google;
  4. use Automattic\WooCommerce\GoogleListingsAndAds\DB\Query\ShippingRateQuery as RateQuery;
  5. use Automattic\WooCommerce\GoogleListingsAndAds\DB\Query\ShippingTimeQuery as TimeQuery;
  6. use Automattic\WooCommerce\GoogleListingsAndAds\Options\OptionsInterface;
  7. use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\WC;
  8. use Google\Service\ShoppingContent;
  9. use Google\Service\ShoppingContent\AccountAddress;
  10. use Google\Service\ShoppingContent\AccountTax;
  11. use Google\Service\ShoppingContent\AccountTaxTaxRule as TaxRule;
  12. use Google\Service\ShoppingContent\DeliveryTime;
  13. use Google\Service\ShoppingContent\Price;
  14. use Google\Service\ShoppingContent\RateGroup;
  15. use Google\Service\ShoppingContent\Service;
  16. use Google\Service\ShoppingContent\ShippingSettings;
  17. use Google\Service\ShoppingContent\Value;
  18. use Psr\Container\ContainerInterface;
  19. defined( 'ABSPATH' ) || exit;
  20. /**
  21. * Class Settings
  22. *
  23. * @package Automattic\WooCommerce\GoogleListingsAndAds\API\Google
  24. */
  25. class Settings {
  26. use LocationIDTrait;
  27. /** @var ContainerInterface */
  28. protected $container;
  29. /**
  30. * Settings constructor.
  31. *
  32. * @param ContainerInterface $container
  33. */
  34. public function __construct( ContainerInterface $container ) {
  35. $this->container = $container;
  36. }
  37. /**
  38. * Sync the shipping settings with Google.
  39. */
  40. public function sync_shipping() {
  41. if ( ! $this->should_sync_shipping() ) {
  42. return;
  43. }
  44. $settings = new ShippingSettings();
  45. $settings->setAccountId( $this->get_account_id() );
  46. $services = [];
  47. foreach ( $this->get_rates() as ['country' => $country, 'currency' => $currency, 'rate' => $rate] ) {
  48. $services[] = $this->create_main_service( $country, $currency, $rate );
  49. if ( $this->has_free_shipping_option() ) {
  50. $services[] = $this->create_free_shipping_service( $country, $currency );
  51. }
  52. }
  53. $settings->setServices( $services );
  54. $this->get_shopping_service()->shippingsettings->update(
  55. $this->get_merchant_id(),
  56. $this->get_account_id(),
  57. $settings
  58. );
  59. }
  60. /**
  61. * Whether we should synchronize settings with the Merchant Center
  62. *
  63. * @return bool
  64. */
  65. protected function should_sync_shipping(): bool {
  66. return 'flat' === $this->get_settings()['shipping_rate'];
  67. }
  68. /**
  69. * Get the current tax settings from the API.
  70. *
  71. * @return AccountTax
  72. */
  73. public function get_taxes(): AccountTax {
  74. return $this->get_shopping_service()->accounttax->get(
  75. $this->get_merchant_id(),
  76. $this->get_account_id()
  77. );
  78. }
  79. /**
  80. * Whether we should sync tax settings.
  81. *
  82. * This depends on the store being in the US
  83. *
  84. * @return bool
  85. */
  86. protected function should_sync_taxes(): bool {
  87. if ( 'US' !== $this->get_store_country() ) {
  88. return false;
  89. }
  90. return 'destination' === ( $this->get_options_object()->get( OptionsInterface::MERCHANT_CENTER )['tax_rate'] ?? 'destination' );
  91. }
  92. /**
  93. * Sync tax setting with Google.
  94. */
  95. public function sync_taxes() {
  96. if ( ! $this->should_sync_taxes() ) {
  97. return;
  98. }
  99. $taxes = new AccountTax();
  100. $taxes->setAccountId( $this->get_account_id() );
  101. $tax_rule = new TaxRule();
  102. $tax_rule->setUseGlobalRate( true );
  103. $tax_rule->setLocationId( $this->get_state_id( $this->get_store_state() ) );
  104. $tax_rule->setCountry( $this->get_store_country() );
  105. $taxes->setRules( [ $tax_rule ] );
  106. $this->get_shopping_service()->accounttax->update(
  107. $this->get_merchant_id(),
  108. $this->get_account_id(),
  109. $taxes
  110. );
  111. }
  112. /**
  113. * Get shipping time data.
  114. *
  115. * @return array
  116. */
  117. protected function get_times(): array {
  118. static $times = null;
  119. if ( null === $times ) {
  120. $time_query = $this->container->get( TimeQuery::class );
  121. $times = array_column( $time_query->get_results(), 'time', 'country' );
  122. }
  123. return $times;
  124. }
  125. /**
  126. * Get shipping rate data.
  127. *
  128. * @return array
  129. */
  130. protected function get_rates(): array {
  131. $rate_query = $this->container->get( RateQuery::class );
  132. return $rate_query->get_results();
  133. }
  134. /**
  135. * Create the DeliveryTime object.
  136. *
  137. * @param int $delivery_days
  138. *
  139. * @return DeliveryTime
  140. */
  141. protected function create_time_object( int $delivery_days ): DeliveryTime {
  142. $time = new DeliveryTime();
  143. $time->setMinHandlingTimeInDays( 0 );
  144. $time->setMaxHandlingTimeInDays( 0 );
  145. $time->setMinTransitTimeInDays( $delivery_days );
  146. $time->setMaxTransitTimeInDays( $delivery_days );
  147. return $time;
  148. }
  149. /**
  150. * Create the array of rate groups for the service.
  151. *
  152. * @param string $currency
  153. * @param mixed $rate
  154. *
  155. * @return array
  156. */
  157. protected function create_rate_groups( string $currency, $rate ): array {
  158. return [ $this->create_rate_group_object( $currency, $rate ) ];
  159. }
  160. /**
  161. * Create a rate group object for the shopping settings.
  162. *
  163. * @param string $currency
  164. * @param mixed $rate
  165. *
  166. * @return RateGroup
  167. */
  168. protected function create_rate_group_object( string $currency, $rate ): RateGroup {
  169. $price = new Price();
  170. $price->setCurrency( $currency );
  171. $price->setValue( $rate );
  172. $value = new Value();
  173. $value->setFlatRate( $price );
  174. $rate_group = new RateGroup();
  175. $rate_group->setSingleValue( $value );
  176. $rate_group->setName(
  177. sprintf(
  178. /* translators: %1 is the shipping rate, %2 is the currency (e.g. USD) */
  179. __( 'Flat rate - %1$s %2$s', 'google-listings-and-ads' ),
  180. $rate,
  181. $currency
  182. )
  183. );
  184. return $rate_group;
  185. }
  186. /**
  187. * Determine whether free shipping is offered.
  188. *
  189. * @return bool
  190. */
  191. protected function has_free_shipping_option(): bool {
  192. return boolval( $this->get_settings()['offers_free_shipping'] ?? false );
  193. }
  194. /**
  195. * Get the free shipping minimum order value.
  196. *
  197. * @return int
  198. */
  199. protected function get_free_shipping_minimum(): int {
  200. return intval( $this->get_settings()['free_shipping_threshold'] );
  201. }
  202. /**
  203. * @return OptionsInterface
  204. */
  205. protected function get_options_object(): OptionsInterface {
  206. return $this->container->get( OptionsInterface::class );
  207. }
  208. /**
  209. * Create the main shipping service object.
  210. *
  211. * @param string $country
  212. * @param string $currency
  213. * @param mixed $rate
  214. *
  215. * @return Service
  216. */
  217. protected function create_main_service( string $country, string $currency, $rate ): Service {
  218. $unique = sprintf( '%04x', mt_rand( 0, 0xffff ) );
  219. $service = new Service();
  220. $service->setActive( true );
  221. $service->setDeliveryCountry( $country );
  222. $service->setCurrency( $currency );
  223. $service->setName(
  224. sprintf(
  225. /* translators: %1 is a random 4-digit string, %2 is the rate, %3 is the currency, %4 is the country code */
  226. __( '[%1$s] Google Listings and Ads generated service - %2$s %3$s to %4$s', 'google-listings-and-ads' ),
  227. $unique,
  228. $rate,
  229. $currency,
  230. $country
  231. )
  232. );
  233. $service->setRateGroups( $this->create_rate_groups( $currency, $rate ) );
  234. $times = $this->get_times();
  235. if ( array_key_exists( $country, $times ) ) {
  236. $service->setDeliveryTime( $this->create_time_object( intval( $times[ $country ] ) ) );
  237. }
  238. return $service;
  239. }
  240. /**
  241. * Create a free shipping service.
  242. *
  243. * @param string $country
  244. * @param string $currency
  245. *
  246. * @return Service
  247. */
  248. protected function create_free_shipping_service( string $country, string $currency ): Service {
  249. $price = new Price();
  250. $price->setValue( $this->get_free_shipping_minimum() );
  251. $price->setCurrency( $currency );
  252. $service = $this->create_main_service( $country, $currency, 0 );
  253. $service->setMinimumOrderValue( $price );
  254. return $service;
  255. }
  256. /**
  257. * Get the Merchant ID
  258. *
  259. * @return int
  260. */
  261. protected function get_merchant_id(): int {
  262. return $this->get_options_object()->get( OptionsInterface::MERCHANT_ID );
  263. }
  264. /**
  265. * Get the account ID.
  266. *
  267. * @return int
  268. */
  269. protected function get_account_id(): int {
  270. // todo: there are some cases where this might be different than the Merchant ID.
  271. return $this->get_merchant_id();
  272. }
  273. /**
  274. * Get the Shopping Service object.
  275. *
  276. * @return ShoppingContent
  277. */
  278. protected function get_shopping_service(): ShoppingContent {
  279. return $this->container->get( ShoppingContent::class );
  280. }
  281. /**
  282. * Get the country for the store.
  283. *
  284. * @return string
  285. */
  286. protected function get_store_country(): string {
  287. /** @var WC $wc */
  288. $wc = $this->container->get( WC::class );
  289. return $wc->get_wc_countries()->get_base_country();
  290. }
  291. /**
  292. * Get the state for the store.
  293. *
  294. * @return string
  295. */
  296. protected function get_store_state(): string {
  297. /** @var WC $wc */
  298. $wc = $this->container->get( WC::class );
  299. return $wc->get_wc_countries()->get_base_state();
  300. }
  301. /**
  302. * Get the WooCommerce store physical address.
  303. *
  304. * @return AccountAddress
  305. *
  306. * @since 1.4.0
  307. */
  308. public function get_store_address(): AccountAddress {
  309. /** @var WC $wc */
  310. $wc = $this->container->get( WC::class );
  311. $countries = $wc->get_wc_countries();
  312. $postal_code = ! empty( $countries->get_base_postcode() ) ? $countries->get_base_postcode() : null;
  313. $locality = ! empty( $countries->get_base_city() ) ? $countries->get_base_city() : null;
  314. $country = ! empty( $countries->get_base_country() ) ? $countries->get_base_country() : null;
  315. $region = ! empty( $countries->get_base_state() ) ? $countries->get_base_state() : null;
  316. $mc_address = new AccountAddress();
  317. $mc_address->setPostalCode( $postal_code );
  318. $mc_address->setLocality( $locality );
  319. $mc_address->setCountry( $country );
  320. if ( ! empty( $region ) && ! empty( $country ) ) {
  321. $mc_address->setRegion( $this->maybe_get_state_name( $region, $country ) );
  322. }
  323. $address = ! empty( $countries->get_base_address() ) ? $countries->get_base_address() : null;
  324. $address_2 = ! empty( $countries->get_base_address_2() ) ? $countries->get_base_address_2() : null;
  325. $separator = ! empty( $address ) && ! empty( $address_2 ) ? "\n" : '';
  326. $address = sprintf( '%s%s%s', $countries->get_base_address(), $separator, $countries->get_base_address_2() );
  327. if ( ! empty( $address ) ) {
  328. $mc_address->setStreetAddress( $address );
  329. }
  330. return $mc_address;
  331. }
  332. /**
  333. * Check whether the address has errors
  334. *
  335. * @param AccountAddress $address to be validated.
  336. *
  337. * @return array
  338. */
  339. public function wc_address_errors( AccountAddress $address ): array {
  340. /** @var WC $wc */
  341. $wc = $this->container->get( WC::class );
  342. $countries = $wc->get_wc_countries();
  343. $locale = $countries->get_country_locale();
  344. $locale_settings = $locale[ $address->getCountry() ] ?? [];
  345. $fields_to_validate = [
  346. 'address_1' => $address->getStreetAddress(),
  347. 'city' => $address->getLocality(),
  348. 'country' => $address->getCountry(),
  349. 'postcode' => $address->getPostalCode(),
  350. ];
  351. return $this->validate_address( $fields_to_validate, $locale_settings );
  352. }
  353. /**
  354. * Check whether the required address fields are empty
  355. *
  356. * @param array $address_fields to be validated.
  357. * @param array $locale_settings locale settings
  358. * @return array
  359. */
  360. public function validate_address( array $address_fields, array $locale_settings ): array {
  361. $errors = array_filter(
  362. $address_fields,
  363. function ( $field ) use ( $locale_settings, $address_fields ) {
  364. $is_required = $locale_settings[ $field ]['required'] ?? true;
  365. return $is_required && empty( $address_fields[ $field ] );
  366. },
  367. ARRAY_FILTER_USE_KEY
  368. );
  369. return array_keys( $errors );
  370. }
  371. /**
  372. * Return a state name.
  373. *
  374. * @param string $state_code State code.
  375. * @param string $country Country code.
  376. *
  377. * @return string
  378. *
  379. * @since 1.4.0
  380. */
  381. protected function maybe_get_state_name( string $state_code, string $country ): string {
  382. /** @var WC $wc */
  383. $wc = $this->container->get( WC::class );
  384. $states = $country ? array_filter( (array) $wc->get_wc_countries()->get_states( $country ) ) : [];
  385. if ( ! empty( $states ) ) {
  386. $state_code = wc_strtoupper( $state_code );
  387. if ( isset( $states[ $state_code ] ) ) {
  388. return $states[ $state_code ];
  389. }
  390. }
  391. return $state_code;
  392. }
  393. /**
  394. * Get the array of settings for the Merchant Center.
  395. *
  396. * @return array
  397. */
  398. protected function get_settings(): array {
  399. return $this->get_options_object()->get( OptionsInterface::MERCHANT_CENTER );
  400. }
  401. }