/**
  * Utilize WC logger class
  *
  * @since 1.0.0
  * @version 1.0.0
  */
 public static function log($message, $start_time = null, $end_time = null)
 {
     if (empty(self::$logger)) {
         self::$logger = new WC_Logger();
     }
     $settings = get_option('woocommerce_squareconnect_settings', '');
     if (!empty($settings['logging']) && 'yes' !== $settings['logging']) {
         return;
     }
     if (!is_null($start_time)) {
         $formatted_start_time = date_i18n(get_option('date_format') . ' g:ia', $start_time);
         $end_time = is_null($end_time) ? current_time('timestamp') : $end_time;
         $formatted_end_time = date_i18n(get_option('date_format') . ' g:ia', $end_time);
         $elapsed_time = round(abs($end_time - $start_time) / 60, 2);
         $log_entry = '====Start Log ' . $formatted_start_time . '====' . "\n" . $message . "\n";
         $log_entry .= '====End Log ' . $formatted_end_time . ' (' . $elapsed_time . ')====' . "\n\n";
     } else {
         $log_entry = '====Start Log====' . "\n" . $message . "\n" . '====End Log====' . "\n\n";
     }
     self::$logger->add(self::WC_LOG_FILENAME, $log_entry);
 }
 /**
  * Update a Square Item Image for a WC Product
  *
  * @param WC_Product $wc_product
  * @param string     $square_item_id
  * @return bool Success.
  */
 public function update_product_image(WC_Product $wc_product, $square_item_id)
 {
     $image_id = get_post_thumbnail_id($wc_product->id);
     if (empty($image_id)) {
         WC_Square_Sync_Logger::log(sprintf('[WC -> Square] Update Product Image: No thumbnail ID for WC Product %d.', $wc_product->id));
         return true;
     }
     $mime_type = get_post_field('post_mime_type', $image_id, 'raw');
     $image_path = get_attached_file($image_id);
     $result = $this->connect->update_square_product_image($square_item_id, $mime_type, $image_path);
     if ($result && isset($result->id)) {
         WC_Square_Utils::update_wc_product_image_square_id($wc_product->id, $result->id);
         return true;
     } else {
         WC_Square_Sync_Logger::log(sprintf('[WC -> Square] Error updating Product Image for WC Product %d.', $wc_product->id));
         return false;
     }
 }
 /**
  * Helper method to make HTTP requests to the Square API, with retries.
  *
  * @param string $debug_label Description of the request, for logging.
  * @param string $request_url URL to request.
  * @param string $method      HTTP method to use. Defaults to 'GET'.
  * @param mixed  $body        Optional. Request payload - will be JSON encoded if non-scalar.
  *
  * @return bool|object|WP_Error
  */
 private function http_request($debug_label, $request_url, $method = 'GET', $body = null)
 {
     $request_args = $this->get_request_args();
     if (!is_null($body)) {
         if (!empty($request_args['headers']['Content-Type']) && 'application/json' === $request_args['headers']['Content-Type']) {
             $request_args['body'] = json_encode($body);
         } else {
             $request_args['body'] = $body;
         }
     }
     $request_args['method'] = $method;
     // Make actual request in a retry loop
     $try_count = 1;
     $max_retries = $this->request_retries();
     while (true) {
         $start_time = current_time('timestamp');
         $response = wp_remote_request(untrailingslashit($request_url), $request_args);
         $end_time = current_time('timestamp');
         WC_Square_Sync_Logger::log(sprintf('%s', $debug_label), $start_time, $end_time);
         $decoded_response = json_decode(wp_remote_retrieve_body($response));
         // check for bad request and log it
         if (is_object($decoded_response) && !empty($decoded_response->type) && preg_match('/bad_request/', $decoded_response->type)) {
             WC_Square_Sync_Logger::log(sprintf('%s - %s', $decoded_response->type, $decoded_response->message), $start_time, $end_time);
         }
         // handle expired tokens
         if (is_object($decoded_response) && !empty($decoded_response->type) && 'oauth.expired' === $decoded_response->type) {
             $oauth_connect_url = 'https://connect.woocommerce.com/renew/square';
             if (WC_SQUARE_ENABLE_STAGING) {
                 $oauth_connect_url = 'https://connect.woocommerce.com/renew/squaresandbox';
             }
             $args = array('body' => array('token' => $this->access_token));
             $start_time = current_time('timestamp');
             $oauth_response = wp_remote_request($oauth_connect_url, $args);
             $end_time = current_time('timestamp');
             $decoded_oauth_response = json_decode(wp_remote_retrieve_body($oauth_response));
             if (is_wp_error($oauth_response)) {
                 WC_Square_Sync_Logger::log(sprintf('Renewing expired token error - %s', $oauth_response->get_error_message()), $start_time, $end_time);
                 return false;
             } elseif ($decoded_oauth_response->error) {
                 WC_Square_Sync_Logger::log(sprintf('Renewing expired token error - %s', $decoded_oauth_response->type), $start_time, $end_time);
                 return false;
             } else {
                 update_option('woocommerce_square_merchant_access_token', sanitize_text_field(urldecode($decoded_oauth_response->access_token)));
                 // let's set the token instance again so settings option is refreshed
                 $this->set_access_token(sanitize_text_field(urldecode($decoded_oauth_response->access_token)));
                 WC_Square_Sync_Logger::log(sprintf('Retrying with new refreshed token'), $start_time, $end_time);
                 // start at the beginning again
                 continue;
             }
         }
         // handle revoked tokens
         if (is_object($decoded_response) && !empty($decoded_response->type) && 'oauth.revoked' === $decoded_response->type) {
             WC_Square_Sync_Logger::log(sprintf('Token is revoked!'), $start_time, $end_time);
             return false;
         }
         if (is_wp_error($response)) {
             WC_Square_Sync_Logger::log(sprintf('(%s) Try #%d - %s', $debug_label, $try_count, $response->get_error_message()), $start_time, $end_time);
         } else {
             return $response;
         }
         $try_count++;
         if ($try_count > $max_retries) {
             break;
         }
         sleep(1);
     }
     return false;
 }
 /**
  * Process Square to WC ajax
  *
  * @access public
  * @since 1.0.0
  * @version 1.0.0
  * @return bool
  */
 public function square_to_wc_ajax()
 {
     $nonce = $_POST['ajaxSyncNonce'];
     // bail if nonce don't check out
     if (!wp_verify_nonce($nonce, '_wc_square_sync_nonce')) {
         wp_die(__('Cheatin’ huh?', 'woocommerce-square'));
     }
     /**
      * Fires if a valid bulk Square to WC sync is being processed.
      *
      * @since 1.0.0
      */
     do_action('woocommerce_square_bulk_syncing_square_to_wc');
     $settings = get_option('woocommerce_squareconnect_settings');
     $emails = !empty($settings['sync_email']) ? $settings['sync_email'] : '';
     $sync_products = 'yes' === $settings['sync_products'];
     $sync_categories = 'yes' === $settings['sync_categories'];
     $sync_inventory = 'yes' === $settings['sync_inventory'];
     $sync_images = 'yes' === $settings['sync_images'];
     $message = '';
     if (!$sync_products) {
         wp_send_json(array('process' => 'done', 'percentage' => 100, 'type' => 'square-to-wc', 'message' => __('Product Sync is disabled. Sync aborted.', 'woocommerce-square')));
     }
     // if a WC to Square process still needs to be completed reset the caches
     // as the two processes ( WC -> Square and Square -> WC ) use the same cache
     if ('wc_to_square' === get_transient('sq_wc_sync_current_process')) {
         $this->delete_all_caches();
     }
     // set Square->WC as the current active process
     set_transient('sq_wc_sync_current_process', 'square_to_wc', apply_filters('woocommerce_square_syncing_current_process_cache_time', DAY_IN_SECONDS));
     // index for the current item in the process
     $process = $this->get_process_index();
     // ensure this manual update gets the freshest item counts
     delete_transient('wc_square_inventory');
     // only sync categories on the first pass
     if (0 === $process && $sync_categories) {
         $this->square_to_wc->sync_categories();
     }
     // products
     // get all product ids
     $square_item_ids = $this->get_processing_ids();
     // run this only on first process
     if ($process === 0) {
         $square_items = $this->connect->get_square_products();
         $square_item_ids = !empty($square_items) ? array_unique(wp_list_pluck($square_items, 'id')) : array();
         // cache it
         $cache_age = apply_filters('woocommerce_square_syncing_square_ids_cache', DAY_IN_SECONDS);
         set_transient('wc_square_processing_total_count', count($square_item_ids), $cache_age);
         set_transient('wc_square_processing_ids', $square_item_ids, $cache_age);
     }
     if ($square_item_ids && $sync_products) {
         $square_item_id = array_pop($square_item_ids);
         $square_item = $this->connect->get_square_product($square_item_id);
         if ($square_item) {
             $this->square_to_wc->sync_product($square_item, $sync_categories, $sync_inventory, $sync_images);
         } else {
             WC_Square_Sync_Logger::log(sprintf('[Square -> WC] Bulk Sync: Error retrieving Square Item with ID %s.', $square_item_id));
         }
         $process++;
         $percentage = $this->get_process_percentage($process);
         $this->delete_processed_id($square_item_id);
         $remaining_ids = $this->get_processing_ids();
         // run this only on last process
         if (empty($remaining_ids)) {
             $process = 'done';
             $percentage = 100;
             // send sync email
             $this->send_sync_email($emails, __('Sync Completed', 'woocommerce-square'));
             // reset the processed ids
             $this->delete_all_caches();
             $message = __('Sync completed', 'woocommerce-square');
         }
         wp_send_json(array('process' => $process, 'percentage' => $percentage, 'type' => 'square-to-wc', 'message' => $message));
     }
 }
 /**
  * Retrieve the WC Category ID that corresponds to a given Square Category ID.
  *
  * @param string $square_cat_id
  * @return bool|int WC Category ID on successful match, boolean false otherwise.
  */
 public static function get_wc_category_id_for_square_category_id($square_cat_id)
 {
     $categories = get_terms('product_cat', array('parent' => 0, 'hide_empty' => false, 'fields' => 'ids'));
     if (is_wp_error($categories)) {
         WC_Square_Sync_Logger::log(sprintf('%s::%s - Taxonomy "product_cat" not found. Make sure WooCommerce is enabled.', __CLASS__, __FUNCTION__));
         return false;
     }
     foreach ($categories as $wc_category) {
         $wc_square_cat_id = self::get_wc_term_square_id($wc_category);
         if ($wc_square_cat_id && $square_cat_id === $wc_square_cat_id) {
             return $wc_category;
         }
     }
     return false;
 }
 /**
  * Update an existing WC Product using data from Square.
  *
  * @param WC_Product $wc_product
  * @param object     $square_item
  * @param bool       $include_category
  * @param bool       $include_inventory
  * @param bool       $include_image
  *
  * @return bool|WC_Product Updated WC_Product on success, boolean false on failure.
  */
 public function update_product(WC_Product $wc_product, $square_item, $include_category = false, $include_inventory = false, $include_image = false)
 {
     $product_update = WC_Square_Utils::format_square_item_for_wc_api_update($square_item, $wc_product, $include_category, $include_inventory, $include_image);
     $result = $this->connect->wc->edit_product($wc_product->id, array('product' => $product_update));
     if (is_wp_error($result)) {
         WC_Square_Sync_Logger::log(sprintf('[Square -> WC] Error updating WC Product %d for Square ID %s: %s', $square_item->id, $result->get_error_message()));
     } elseif (isset($result['product']['id'])) {
         return wc_get_product($result['product']['id']);
     }
     return false;
 }