<?php

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly
}

final class WOOBE_PRODUCTS {

	private $fields_keys     = null;
	private $settings        = null;
	private $storage         = null;
	public $suppress_filters = false; // for example while do woobe_title_autocomplete we not need to apply another filters
	public $cached_products  = array(); // products caching - for 400 percents quicker!!

	public function __construct( $settings, $storage ) {
		$this->settings    = $settings;
		$this->storage     = $storage;
		$this->fields_keys = $this->settings->get_fields_keys();
	}

	public function gets( $args ) {

		if ( ! isset( $args['get_variations'] ) ) {

			if ( isset( $args['order_by'] ) ) {
				$order_by = sanitize_key( $args['order_by'] );
				if ( $order_by === 'id' ) {
					$order_by = 'ID';
				}
			} else {
				$order_by = 'ID';
			}

			// ***
			// fix to avoid notice when seacrhing goig for crossels, upsells, etc ...
			if ( ! isset( $args['offset'] ) ) {
				$args['offset'] = 0;
			}

			$pr = array(
				'post_type'      => isset( $args['post_type'] ) ? $args['post_type'] : array( 'product' ),
				'post_status'    => isset( $args['post_status'] ) ? $args['post_status'] : array_keys( apply_filters( 'woobe_product_statuses', get_post_statuses() ) ),
				'orderby'        => $order_by,
				'order'          => isset( $args['order'] ) ? sanitize_key( $args['order'] ) : 'asc',
				'posts_per_page' => isset( $args['per_page'] ) ? intval( $args['per_page'] ) : 10,
				'paged'          => isset( $args['per_page'] ) ? intval( ( $args['offset'] / $args['per_page'] ) + 1 ) : 1,
			);

			// get one product data
			if ( isset( $args['p'] ) ) {
				$pr['p'] = $args['p'];
			}

			// ***

			if ( isset( $args['nopaging'] ) ) {
				$pr['nopaging'] = $args['nopaging'];
			}

			if ( isset( $args['max_num_pages'] ) ) {
				$pr['max_num_pages'] = $args['max_num_pages'];
			}

			if ( isset( $args['post__not_in'] ) ) {
				$pr['post__not_in'] = $args['post__not_in'];
			}

			// ***
			// for bulk get product count
			if ( isset( $args['fields'] ) ) {
				$pr['fields'] = $args['fields'];
			}

			if ( isset( $args['no_found_rows'] ) ) {
				$pr['no_found_rows']  = $args['no_found_rows'];
				$pr['posts_per_page'] = -1;
				unset( $pr['paged'] );
			}

			// ***

			if ( isset( $this->settings->get_fields()[ $order_by ] ) and $this->settings->get_fields()[ $order_by ]['field_type'] === 'meta' ) {
				$pr['meta_key'] = $order_by;
				if ( in_array( $this->settings->get_fields()[ $order_by ]['type'], array( 'number', 'timestamp', 'unix' ) ) ) {
					$pr['orderby'] = 'meta_value_num';
				} else {
					$pr['orderby'] = 'meta_value';
				}
			}

			// WPML compatibility
			if ( class_exists( 'SitePress' ) && isset( $args['lang'] ) ) {
				// https://wpml.org/forums/topic/passing-language-to-wp_query-2/
				global $sitepress;
				$sitepress->switch_lang( $args['lang'] );
			}
		} else {
			// get variations only
			$pr = array(
				'post_type'      => array( 'product_variation' ),
				'post_status'    => array_keys( apply_filters( 'woobe_product_statuses', get_post_statuses() ) ),
				'posts_per_page' => -1,
				'post_parent'    => $args['get_variations'], // product parent id
				'orderby'        => 'menu_order',
				'order'          => 'ASC',
			);

			return new WP_Query( $pr );
		}

		if ( ! $this->suppress_filters ) {
			$pr = apply_filters( 'woobe_apply_query_filter_data', $pr );
			return new WP_Query( $pr );
		} else {
			return new WP_Query( $pr );
		}
	}

	public function update_page_field( $product_id, $field_key, $value, $field_type = '', $additional_attr = array() ) {

		if ( ! $product_id ) {
			return false;
		}

		$fields = $this->settings->get_fields();

		// ***
		// lets check if current user is not administrator and is he can edit this field
		if ( ! $this->is_current_user_can_edit_field( $field_key ) ) {
			return esc_html__( 'forbidden', 'woo-bulk-editor' );
		}

		// ***

		$value = apply_filters( 'woobe_before_update_product_field', $value, $product_id, $field_key );

		$product = $this->get_product( $product_id );

		$answer = '';
		if ( empty( $field_type ) ) {
			$field_type = $fields[ $field_key ]['field_type'];
		}

		// ***

		do_action( 'woobe_before_update_page_field', $field_key, $product_id, $product->get_parent_id() );

		// ***
		if ( $field_key == 'post_content' ) {
			// fix for description of a variation
			if ( $product->is_type( 'variation' ) ) {
				$field_key  = '_variation_description';
				$field_type = 'meta';
			}
		}
		if ( $field_key == 'attribute_visibility' ) {
			$field_type = 'meta';
		}

		// ***
		if ( isset( $fields[ $field_key ]['sanitize'] ) and $fields[ $field_key ]['sanitize'] == 'array' and ! empty( $value ) ) {

			$value = WOOBE_HELPER::string_to_array( $value ); // in db its keeps as array, so lets conver it
		}

		if ( isset( $_REQUEST['num_rounding'] ) and $_REQUEST['num_rounding'] > 0 and $fields[ $field_key ]['type'] === 'number' ) {
			if ( isset( $fields[ $field_key ]['sanitize'] ) and $fields[ $field_key ]['sanitize'] === 'floatval' ) {
				$round_to = intval( $_REQUEST['num_rounding'] );
				$decimals = wc_get_price_decimals();
				$div      = intval( '1' . str_repeat( '0', $decimals ) );

				if ( $decimals > 0 ) {
					switch ( $round_to ) {
						case 100:
							$value = round( $value, 0 );
							break;
						case 5:
							$value = ceil( $value / 0.05 ) * 0.05;
							break;
						case 10:
							$value = ceil( $value * 10 ) / 10;
							break;

						case 9:
						case 19:
						case 29:
						case 39:
						case 49:
						case 59:
						case 69:
						case 79:
						case 89:
						case 99:
							$value = intval( $value ) + floatval( $round_to / $div );

						default:
							break;
					}
				}
			}
		}

		// ***

		if ( $fields[ $field_key ]['type'] === 'number' and isset( $fields[ $field_key ]['sanitize'] ) and $fields[ $field_key ]['sanitize'] === 'floatval' ) {
			$value = apply_filters( 'woobe_number_field_manipulation', $value, $field_key, $product_id );
		}

		// ***
		// echo $field_type . ' + ' . $value . ' + ';
		switch ( $field_type ) {
			case 'meta':
				if ( $field_key == 'attribute_visibility' ) {

					$meta = get_post_meta( $product_id, '_product_attributes', true );
					if ( ! is_array( $meta ) ) {
						$meta = array();
					}
					if ( ! is_array( $value ) ) {
						$value = array( $value );
					}
					foreach ( $meta as $pa_key => $item ) {
						if ( in_array( $item['name'], $value ) ) {
							$meta[ $pa_key ]['is_visible'] = 1;
						} else {
							$meta[ $pa_key ]['is_visible'] = 0;
						}
					}

					update_post_meta( $product_id, '_product_attributes', $meta );
					break;
				}
				// if ($field_key == "_thumbnail_id") {
				// set_post_thumbnail( $product_id, intval($value) );
				// break;
				// }
				if ( $fields[ $field_key ]['edit_view'] == 'switcher' ) { // do switcher
					$value = WOOBE_HELPER::over_switcher_swicher_to_val( $value, $field_key );
				}
				if ( isset( $_REQUEST['is_serialized'] ) and $_REQUEST['is_serialized'] and is_string( $value ) ) {
					parse_str( $value, $value ); // for serialized meta data saving
				}

				if ( $fields[ $field_key ]['edit_view'] == 'meta_popup_editor' ) {
					$value  = $this->process_jsoned_meta_data( $value );
					$answer = json_encode( $value, JSON_HEX_QUOT | JSON_HEX_TAG );
				} else {
					$answer = $value;
				}
				if ( $fields[ $field_key ]['edit_view'] == 'gallery_popup_editor' ) {

					$value = isset( $value['woobe_gallery_images'] ) ? $value['woobe_gallery_images'] : array();
				}

				update_post_meta( $product_id, $field_key, $value );
				$this->_call_hooks_after_product_update( $product );

				if ( $fields[ $field_key ]['edit_view'] == 'gallery_popup_editor' ) {
					$answer = WOOBE_HELPER::draw_gallery_popup_editor_btn( $field_key, $product_id );
				}
				break;

			case 'prop':
				// fix for stock_quantity + manage_stock
				if ( $field_key == 'stock_quantity' ) {
					$value = $value ? $value : 0;
					// to future php
					// $value = $value ?? 0;
					if ( apply_filters( 'woobe_stock_quantity_dependency', true ) && 0 >= intval( $value ) ) {
						$product->set_props(
							array(
								'manage_stock' => 1,
							)
						);
					}
				}

				// fix IF sale_price > regular_price
				if ( $field_key == 'sale_price' and $value > 0 ) {
					$rp = $product->get_regular_price();
					if ( floatval( $value ) >= floatval( $rp ) ) {
						$div   = intval( '1' . str_repeat( '0', wc_get_price_decimals() ) );
						$value = $rp - floatval( 1 / $div );
					}
				}

				if ( $field_key == 'sale_price' or $field_key == 'regular_price' and $value > 0 ) {
					$value = number_format( floatval( $value ), wc_get_price_decimals(), '.', '' );
					if ( $field_key == 'sale_price' and $value <= 0 ) {
						$value = false;
					}
					// wpml price  sync
					if ( apply_filters( 'woobe_wpml_sync_prices', false ) && function_exists( 'icl_object_id' ) ) {
						// @phpstan-ignore-next-line
						$icl_langs = icl_get_languages();
						foreach ( $icl_langs as $ln => $data ) {
							if ( $icl_langs == $ln ) {
								continue;
							}

							$product_lcl_id = icl_object_id( $product_id, 'post', false, $ln );
							$product_lcl    = $this->get_product( $product_lcl_id );

							if ( $product_lcl ) {
								$product_lcl->set_props(
									array(
										$field_key => $value,
									)
								);
								$product_lcl->save();
							}
						}
					}
				}

				// ***
				// $field_key - catalog_visibility for example
				if ( $field_key == 'date_on_sale_from' ) {
					$product->set_date_on_sale_from( $value ? $value : null );
				} elseif ( $field_key == 'date_on_sale_to' ) {
					$product->set_date_on_sale_to( $value ? $value : null );
				} elseif ( $field_key == 'purchase_note' ) {
					$allowed_html = wp_kses_allowed_html( 'post' );
					$product->set_props(
						array(
							$field_key => wp_kses( $value, $allowed_html ),
						)
					);
				} elseif ( $field_key == 'sku' ) {

					if ( ! empty( $value ) && apply_filters( 'woobe_init_sku_control', false ) ) {
						$id_by_sku = WOOBE_HELPER::get_product_id_by_sku( $value );
						if ( $id_by_sku && $id_by_sku !== $product_id ) {
							return esc_html__( 'Error! SKU already exists', 'woo-bulk-editor' );
						}
					}

					update_post_meta( $product_id, '_sku', $value );
					$product->set_sku( $value );
				} else {

					if ( 'stock_status' == $field_key && empty( $value ) ) {
						$value = 'outofstock';
					}

					$product->set_props(
						array(
							$field_key => wc_clean( $value ),
						)
					);

					// if (function_exists('icl_object_id') && 'stock_quantity' == $field_key) {
					// $icl_langs = icl_get_languages();
					// foreach ($icl_langs as $ln => $data) {
					// if ($icl_langs == $ln) {
					// continue;
					// }
					//
					// $product_lcl_id = icl_object_id($product_id, 'post', false, $ln);
					// $product_lcl = $this->get_product($product_lcl_id);
					//
					// if ($product_lcl) {
					// $product_lcl->set_props(array(
					// $field_key => wc_clean($value)
					// ));
					// $product_lcl->save();
					// }
					// }
					// }
				}

				// +++

				$product->save();

				// Force sync scheduled sales if dates were just set
				if ( $field_key == 'date_on_sale_from' || $field_key == 'date_on_sale_to' ) {
					$date_from     = $product->get_date_on_sale_from( 'edit' );
					$date_to       = $product->get_date_on_sale_to( 'edit' );
					$sale_price    = $product->get_sale_price( 'edit' );
					$regular_price = $product->get_regular_price( 'edit' );
					$now           = time();

					$date_from_ts = $date_from ? $date_from->getTimestamp() : 0;
					$date_to_ts   = $date_to ? $date_to->getTimestamp() : 0;

					// Determine which price should be active NOW
					if ( $sale_price && $date_from_ts && $date_from_ts > $now ) {
						// Sale hasn't started yet - use regular price
						update_post_meta( $product_id, '_price', $regular_price );
					} elseif ( $sale_price && ( ! $date_from_ts || $date_from_ts <= $now ) && ( ! $date_to_ts || $date_to_ts > $now ) ) {
						// Sale is active now - use sale price
						update_post_meta( $product_id, '_price', $sale_price );
					} elseif ( $date_to_ts && $date_to_ts <= $now ) {
						// Sale has ended - use regular price
						update_post_meta( $product_id, '_price', $regular_price );
					} elseif ( ! $sale_price ) {
						// No sale price - use regular price
						update_post_meta( $product_id, '_price', $regular_price );
					}

					// Clear product cache
					wc_delete_product_transients( $product_id );
				}

				// +++

				$func_name = 'get_' . $field_key;
				if ( method_exists( $product, $func_name ) ) {
					$answer = $product->$func_name();
				}

				break;

			case 'taxonomy':
				if ( $fields[ $field_key ]['type'] === 'array' ) {
					if ( ! is_array( $value ) ) {
						$value = array( intval( $value ) );
					} else {
						foreach ( $value as $k => $tid ) {
							$value[ $k ] = intval( $tid );
						}
					}
				} else {
					// string, do nothing
				}

				wp_set_post_terms( $product_id, $value, $fields[ $field_key ]['taxonomy'], false );
				$this->_call_hooks_after_product_update( $product );

				break;

			case 'attribute':
				$this->set_product_attributes( $product_id, $field_key, $value, $additional_attr );

				$this->_call_hooks_after_product_update( $product );
				break;

			case 'downloads':
				if ( ! is_array( $value ) ) {
					// from Product Editor
					parse_str( $value, $value );
				}

				$downloads = $this->prepare_downloads(
					isset( $value['_wc_file_names'] ) ? $value['_wc_file_names'] : array(),
					isset( $value['_wc_file_urls'] ) ? $value['_wc_file_urls'] : array(),
					isset( $value['_wc_file_hashes'] ) ? $value['_wc_file_hashes'] : array()
				);

				// ***
				// for bulk editing operation
				if ( isset( $_REQUEST['action'] ) and $_REQUEST['action'] === 'woobe_bulk_products' ) {

					$woobe_bulk = $this->storage->get_val( 'woobe_bulk_' . WOOBE_HELPER::sanitize_bulk_key( $_REQUEST['bulk_key'] ) );
					$behavior   = $woobe_bulk['download_files']['behavior'];

					if ( $behavior !== 'new' ) {

						$prev_downloads = $product->get_downloads();

						// +++

						switch ( $behavior ) {
							case 'delete':
								if ( ! empty( $downloads ) ) {
									foreach ( $downloads as $d ) {
										foreach ( $prev_downloads as $ked => $ed ) {
											if ( $ed->get_file() === $d['file'] ) {
												unset( $prev_downloads[ $ked ] );
											}
										}
									}
								}

								$downloads = $prev_downloads;

								break;
							case 'add':
								if ( ! empty( $downloads ) ) {
									foreach ( $downloads as $d ) {
										$prev_downloads[] = $d;
									}

									$downloads = $prev_downloads;
								}
								break;
							default:
								// new
								// do nothing
								break;
						}
					}
				}

				// ***

				$product->set_downloads( $downloads );
				$product->save();

				$answer = WOOBE_HELPER::draw_downloads_popup_editor_btn( $field_key, $product->get_id() );

				break;

			case 'gallery':
				if ( ! is_array( $value ) ) {
					// from Product Editor
					parse_str( $value, $value );
				}

				$value = isset( $value['woobe_gallery_images'] ) ? $value['woobe_gallery_images'] : array();

				// ***
				// for bulk editing operation
				if ( isset( $_REQUEST['action'] ) and $_REQUEST['action'] === 'woobe_bulk_products' ) {

					$woobe_bulk = $this->storage->get_val( 'woobe_bulk_' . WOOBE_HELPER::sanitize_bulk_key( $_REQUEST['bulk_key'] ) );
					$behavior   = $woobe_bulk['gallery']['behavior'];

					if ( $behavior !== 'new' ) {

						$prev_ids = $product->get_gallery_image_ids();

						switch ( $behavior ) {
							case 'delete_forever':
								if ( ! empty( $value ) ) {
									foreach ( $value as $attachment_id ) {
										wp_delete_attachment( $attachment_id, true );
									}
								}

								$value = array_diff( $prev_ids, $value );

								break;
							case 'delete':
								$value = array_diff( $prev_ids, $value );

								break;
							case 'add':
								$value = array_merge( $prev_ids, $value );

								break;

							default:
								// new
								// do nothing
								break;
						}
					}
				}

				// ***

				$product->set_gallery_image_ids( $value );
				$product->save();

				$answer = WOOBE_HELPER::draw_gallery_popup_editor_btn( $field_key, $product->get_id() );

				break;

			case 'upsells':
				if ( ! is_array( $value ) ) {
					$value = str_replace( '&amp;', '&', $value );
					// from Product Editor
					parse_str( $value, $value );
				}

				$value = ( isset( $value['woobe_prod_ids'] ) ) ? $value['woobe_prod_ids'] : array();

				// ***
				// for bulk editing operation
				if ( isset( $_REQUEST['action'] ) and $_REQUEST['action'] === 'woobe_bulk_products' ) {

					$woobe_bulk = $this->storage->get_val( 'woobe_bulk_' . WOOBE_HELPER::sanitize_bulk_key( $_REQUEST['bulk_key'] ) );
					$behavior   = $woobe_bulk['upsell_ids']['behavior'];

					if ( $behavior !== 'new' ) {

						$prev_ids = $product->get_upsell_ids();

						switch ( $behavior ) {
							case 'delete':
								$value = array_diff( $prev_ids, $value );
								break;
							case 'add':
								$value = array_merge( $prev_ids, $value );
								break;
							default:
								// new
								// do nothing
								break;
						}
					}
				}

				// ***

				$product->set_upsell_ids( $value );
				$product->save();

				$answer = WOOBE_HELPER::draw_upsells_popup_editor_btn( $field_key, $product->get_id() );

				break;

			case 'cross_sells':
				if ( ! is_array( $value ) ) {
					$value = str_replace( '&amp;', '&', $value );
					// from Product Editor
					parse_str( $value, $value );
				}

				$value = ( isset( $value['woobe_prod_ids'] ) ) ? $value['woobe_prod_ids'] : array();

				// ***
				// for bulk editing operation
				if ( isset( $_REQUEST['action'] ) and $_REQUEST['action'] === 'woobe_bulk_products' ) {

					$woobe_bulk = $this->storage->get_val( 'woobe_bulk_' . WOOBE_HELPER::sanitize_bulk_key( $_REQUEST['bulk_key'] ) );
					$behavior   = $woobe_bulk['cross_sell_ids']['behavior'];

					if ( $behavior !== 'new' ) {

						$prev_ids = $product->get_cross_sell_ids();

						switch ( $behavior ) {
							case 'delete':
								$value = array_diff( $prev_ids, $value );
								break;
							case 'add':
								$value = array_merge( $prev_ids, $value );
								break;
							default:
								// new
								// do nothing
								break;
						}
					}
				}

				// ***

				$product->set_cross_sell_ids( $value );
				$product->save();

				$answer = WOOBE_HELPER::draw_cross_sells_popup_editor_btn( $field_key, $product->get_id() );

				break;

			case 'grouped':
				if ( $product->get_type() === 'grouped' ) {

					if ( ! is_array( $value ) ) {
						// from Product Editor
						parse_str( $value, $value );
					}

					$value = ( isset( $value['woobe_prod_ids'] ) ) ? $value['woobe_prod_ids'] : array();
					// ***
					// for bulk editing operation
					if ( isset( $_REQUEST['action'] ) and $_REQUEST['action'] === 'woobe_bulk_products' ) {

						$woobe_bulk = $this->storage->get_val( 'woobe_bulk_' . WOOBE_HELPER::sanitize_bulk_key( $_REQUEST['bulk_key'] ) );
						$behavior   = $woobe_bulk['grouped_ids']['behavior'];

						if ( $behavior !== 'new' ) {

							$prev_ids = $product->get_children();

							switch ( $behavior ) {
								case 'delete':
									$value = array_diff( $prev_ids, $value );
									break;
								case 'add':
									$value = array_merge( $prev_ids, $value );
									break;
								default:
									// new
									// do nothing
									break;
							}
						}
					}

					// ***

					$product->set_children( $value );
					$product->save();

					$answer = WOOBE_HELPER::draw_grouped_popup_editor_btn( $field_key, $product->get_id() );
				}

				break;

			default:
				// fix for product variations statuses
				if ( $field_key === 'post_status' ) {
					if ( $product->is_type( 'variation' ) ) {
						if ( $value != 'publish' ) {
							$value = 'private';
						}
					}
				}
				if ( 'post_date' == $field_key ) {
					$new_key   = 'post_date_gmt';
					$new_value = gmdate( 'Y-m-d H:i:s', strtotime( $value ) );
					wp_update_post(
						array(
							'ID'     => $product_id,
							$new_key => $new_value,
						)
					);
				}

				// if ($field_key === 'post_content') {
				// if(is_array($value)){
				// $value=implode(" ", $value);
				//
				// $value=strval ($value);
				//
				// }
				// }
				// fix for variation
				if ( $field_key === 'post_author' ) {
					$children = $product->get_children();
					if ( ! empty( $children ) ) {
						foreach ( $children as $var_id ) {
							wp_update_post(
								array(
									'ID'       => $var_id,
									$field_key => $value,
								)
							);
						}
					}
				}
				// ***
				// table field
				wp_update_post(
					array(
						'ID'       => $product_id,
						$field_key => $value,
					)
				);

				$answer = get_post_field( $field_key, $product_id );

				$this->_call_hooks_after_product_update( $product );

				break;
		}

		// ***
		// speedfix comment line -0.5sek
		// wc_delete_product_transients($product_id);
		// ***

		if ( $fields[ $field_key ]['edit_view'] == 'textinput' ) {
			$answer = $this->sanitize_answer_value( $field_key, ( isset( $fields[ $field_key ]['sanitize'] ) ? $fields[ $field_key ]['sanitize'] : '' ), $answer );
		}

		// ***
		// FOR ANY FLEXIBLE COMPATIBILITY
		do_action( 'woobe_after_update_page_field', $product_id, $product, $field_key, $value, $field_type );

		// update products cache
		$this->cached_products[ $product_id ] = $product;
		return $answer;
		// return stripcslashes($answer);
	}

	// util
	private function _call_hooks_after_product_update( &$product ) {
		$product_id = $product->get_id();
		$pp         = get_post( $product_id );
		do_action( 'save_post', $product_id, $pp, true );
		do_action( 'save_post_product', $product_id, $pp, true );
		do_action( 'edit_post', $product_id, $pp );
		$this->clear_caches( $product );
		flush_rewrite_rules();

		// ***

		if ( $product->get_type() === 'variation' ) {
			do_action( 'woocommerce_update_product_variation', $product_id, $product );
		} else {
			do_action( 'woocommerce_update_product', $product_id, $product );
		}
	}

	// service
	public function sanitize_answer_value( $field_key, $sanitize, $val ) {

		$res = $val;

		switch ( $sanitize ) {
			case 'sanitize_key':
				$res = sanitize_key( $val );
				break;
			case 'esc_url':
				$res = esc_url( $val );
				break;
			case 'urldecode':
				$res = urldecode( $val );
				break;
			case 'floatval':
				$val = str_replace( ',', '.', $val );
				$val = str_replace( ' ', '', $val );

				if ( in_array( $field_key, array( 'regular_price', 'sale_price' ) ) ) {
					$res = number_format( floatval( $val ), wc_get_price_decimals() );
				} else {
					$res = floatval( $val );
				}

				break;
			case 'intval':
				$res = intval( $val );
				break;

			case 'array':
				if ( is_array( $val ) and ! empty( $val ) ) {
					$res = WOOBE_HELPER::array_to_string( $val );
				}
				break;
		}

		return $res;
	}

	public function set_product_attributes_visible( $product_id, $attributes, $visible ) {

		$meta = get_post_meta( $product_id, '_product_attributes', true );
		if ( ! is_array( $meta ) ) {
			$meta = array();
		}
		if ( ! is_array( $attributes ) ) {
			$attributes = array( $attributes );
		}
		foreach ( $meta as $pa_key => $item ) {
			if ( in_array( $item['name'], $attributes ) ) {
				$meta[ $pa_key ]['is_visible'] = $visible;
			}
		}

		update_post_meta( $product_id, '_product_attributes', $meta );
	}

	public function set_product_attributes( $product_id, $field_key, $value, $mode = 'replace', $attr_data = array() ) {
		if ( ! is_array( $value ) ) {
			$value = array( intval( $value ) );
		} else {
			foreach ( $value as $k => $tid ) {
				$value[ $k ] = intval( $tid );
			}
		}

		// ***

		$product = $this->get_product( $product_id );

		$attributes         = array();
		$product_attributes = $product->get_attributes();

		// *** fix for empty value
		if ( count( $value ) === 1 ) {
			if ( intval( $value[0] ) === 0 ) {
				$value = array();
			}
		}

		// ***

		if ( ! empty( $product_attributes ) ) {
			// wp-content\plugins\woocommerce\includes\admin\meta-boxes\class-wc-meta-box-product-data.php
			// public static function prepare_attributes
			foreach ( $product_attributes as $pa_key => $a ) {

				if ( is_object( $a ) ) {
					$attribute = new WC_Product_Attribute();
					$attribute->set_id( $a->get_id() );
					$attribute->set_name( $a->get_name() );

					if ( $a->get_name() == $field_key ) {

						// detach attributes if there is no selected terms!!
						if ( empty( $value ) ) {
							continue;
						}
						// ***
						switch ( $mode ) {
							case 'append':
								// using in bulk
								$attribute->set_options( array_unique( array_merge( $a->get_options(), $value ) ) );
								break;
							case 'replace':
							case 'new':
								// using in bulk AND in editor
								$attribute->set_options( $value );
								break;
							case 'remove':
								// using in bulk
								$attribute->set_options( array_values( array_diff( $a->get_options(), $value ) ) );

								break;
							case 'visible':
								// using in bulk
								$a->set_visible( $value );
								break;
							default:
								$attribute->set_options( $value );
						}
					} else {
						$attribute->set_options( $a->get_options() );
					}
					$attribute->set_position( $a->get_position() );
					$attribute->set_visible( $a->get_visible() );
					$attribute->set_variation( $a->get_variation() );

					$attributes[] = $attribute;
				}
			}
		}

		// ***
		// if such attribute not applied in the product
		if ( ! isset( $product_attributes[ $field_key ] ) and ! isset( $product_attributes[ strtolower( urlencode( $field_key ) ) ] ) ) {
			$attribute            = new WC_Product_Attribute();
			$attribute_taxonomies = wc_get_attribute_taxonomies();
			foreach ( $attribute_taxonomies as $a ) {
				if ( 'pa_' . $a->attribute_name == $field_key ) {

					if ( ! empty( $value ) ) {
						$attribute->set_id( $a->attribute_id );
						$attribute->set_name( 'pa_' . $a->attribute_name );
						$attribute->set_options( $value );
						$attribute->set_position( count( $attributes ) );
						$attribute->set_visible( 1 );
						if ( in_array( 'set_variation', $attr_data ) ) {
							$attribute->set_variation( true );
						} else {
							$attribute->set_variation( false );
						}
						$attributes[] = $attribute;
					}

					break;
				}
			}
		}

		$product->set_attributes( $attributes );
		$product->save();
	}

	// for saving downloads
	private function prepare_downloads( $file_names, $file_urls, $file_hashes ) {
		$downloads = array();

		if ( ! empty( $file_urls ) ) {
			$file_url_size = sizeof( $file_urls );

			for ( $i = 0; $i < $file_url_size; $i++ ) {
				if ( ! empty( $file_urls[ $i ] ) ) {
					$downloads[] = array(
						'name'          => wc_clean( $file_names[ $i ] ),
						'file'          => wp_unslash( trim( $file_urls[ $i ] ) ),
						'previous_hash' => wc_clean( $file_hashes[ $i ] ),
					);
				}
			}
		}
		return $downloads;
	}

	// ++++++++++++++++++++++++++++++++++++++++

	public function get_post_field( $product_id, $field_key, $post_parent = 0 ) {
		if ( ! $product_id ) {
			return false;
		}

		// ***
		$res        = '';
		$field_type = $this->settings->get_fields()[ $field_key ]['field_type'];

		// fix for description of one variation
		if ( $field_key == 'post_content' and $post_parent > 0 ) {
			$field_type = 'meta';
			$field_key  = '_variation_description';
		}

		// ***

		switch ( $field_type ) {
			case 'meta':
				// $this->settings->get_fields()[$field_key]["edit_view"]=="calendar";
				$res = get_post_meta( $product_id, $field_key, true );
				break;

			case 'field':
				$res = get_post_field( $field_key, $product_id );
				break;

			case 'prop':
				$product = $this->get_product( $product_id );

				// $field_key - for example: catalog_visibility
				$func_name = 'get_' . $field_key;

				// for example get_product_url() and get_button_text() exists only for external products
				if ( method_exists( $product, $func_name ) ) {
					$res = $product->$func_name();
				}

				break;

			case 'taxonomy':
				$res = wp_get_post_terms(
					$product_id,
					$this->settings->get_fields()[ $field_key ]['taxonomy'],
					array(
						// 'fields' => 'ids',
								'hide_empty' => false,
					)
				);
				break;

			case 'attribute':
				$product    = $this->get_product( $product_id );
				$attributes = $product->get_attributes();

				if ( ! empty( $attributes ) ) {

					if ( ! $product->is_type( 'variation' ) ) {
						$res = array();

						foreach ( $attributes as $a ) {
							if ( is_object( $a ) ) {
								if ( $a->get_name() == $field_key ) {
									$res = $a->get_options();
									break;
								}
							}
						}
					} else {
						// a variation has NOT attributes as it is an attribute self
						// echo get_class($product);
						foreach ( $attributes as $a ) {
							if ( is_object( $a ) and method_exists( $a, 'get_name' ) ) {
								if ( $a->get_name() == $field_key ) {
									$t = get_term_by( 'slug', $a, $field_key );
									if ( ! empty( $t ) ) {
										$res = array( $t->term_id );
									}
									break;
								}
							}
						}
					}
				}

				break;
			case 'attribute_visibile':
				$product    = $this->get_product( $product_id );
				$attributes = $product->get_attributes();

				if ( ! empty( $attributes ) ) {

					if ( ! $product->is_type( 'variation' ) ) {
						$res = array();

						foreach ( $attributes as $a ) {
							if ( is_object( $a ) ) {
								if ( $a->get_visible() ) {
									$res[] = $a->get_visible();
								}
							}
						}
					}
					// else {
					// a variation has NOT attributes as it is an attribute self
					// echo get_class($product);
					// foreach ($attributes as $a) {
					// if (is_object($a) AND method_exists($a, 'get_name')) {
					// if ($a->get_name() == $field_key) {
					// $t = get_term_by('slug', $a, $field_key);
					// if (!empty($t)) {
					// $res = array($t->term_id);
					// }
					// break;
					// }
					// }
					// }
					// }
				}
				break;
			case 'gallery':
				$product = $this->get_product( $product_id );
				try {
					$res = $product->get_gallery_image_ids();
				} catch ( Exception $e ) {
					$res = array();
				}
				break;

			case 'downloads':
				try {
					$product = $this->get_product( $product_id );
					$res     = $product->get_downloads( 'edit' );
				} catch ( Exception $e ) {
					$res = array();
				}
				break;

			case 'upsells':
				try {
					$product = $this->get_product( $product_id );
					$res     = $product->get_upsell_ids();
				} catch ( Exception $e ) {
					$res = array();
				}
				break;

			case 'cross_sells':
				try {
					$product = $this->get_product( $product_id );
					$res     = $product->get_cross_sell_ids();
				} catch ( Exception $e ) {
					$res = array();
				}
				break;

			case 'grouped':
				try {
					$product = $this->get_product( $product_id );
					$res     = $product->get_children( 'edit' );
				} catch ( Exception $e ) {
					$res = array();
				}
				break;
		}

		return $res;
	}

	public function get_product( $product_id, $use_cache = true ) {

		$product_id = intval( $product_id );

		if ( $use_cache ) {
			if ( ! isset( $this->cached_products[ $product_id ] ) ) {
				$this->cached_products[ $product_id ] = wc_get_product( $product_id );
			}

			return $this->cached_products[ $product_id ];
		} else {
			return wc_get_product( $product_id );
		}

		/*
			$product_type = WC_Product_Factory::get_product_type($product_id);
			$classname = WC_Product_Factory::get_product_classname($product_id, $product_type);
			return new $classname($product_id);
		 *
		 */
	}

	public function normalize_calendar_date( $value, $field_key ) {
		// fix: return empty string immediately if clearing the field
		if ( $value === '' || $value === null ) {
			return '';
		}

		if ( $field_key == 'post_date' ) {
			$date = new DateTime();
			$date->setTimestamp( strtotime( $value ) );
			$value = $date->format( 'Y-m-d H:i:s' );
			return $value;
		}

		if ( $value != 0 && $value != null ) {// if not clearing
			// $value = explode('-', $value);
			if ( isset( $this->settings->active_fields[ $field_key ]['set_day_end'] ) and $this->settings->active_fields[ $field_key ]['set_day_end'] ) {
				// $value = mktime(23, 59, 59, intval($value['1']), intval($value['2']), intval($value['0']));
				$value = gmdate( 'Y-m-d 23:59:59', intval( strtotime( $value ) ) );
			} else {
				// $value = mktime(0, 0, 0, intval($value['1']), intval($value['2']), intval($value['0']));
				$value = gmdate( 'Y-m-d 00:00:00', intval( strtotime( $value ) ) );
			}
			$value = strtotime( $value ) - intval( get_option( 'gmt_offset' ) ) * 3600;

			// ***
			// $gmt_offset = intval(get_option('gmt_offset')) * 3600;
			// $value += $gmt_offset;
			// ***

			if ( $this->settings->active_fields[ $field_key ]['type'] === 'timestamp'
					and $this->settings->active_fields[ $field_key ]['field_type'] === 'field' ) {
				$date = new DateTime();
				$date->setTimestamp( $value );
				$value = $date->format( 'Y-m-d H:i:s' );
			}
		}

		return $value;
	}

	public function get_attributes( $product_id, $type = 'all' ) {
		$product    = $this->get_product( $product_id );
		$attributes = $product->get_attributes();
		$res        = array();
		if ( ! empty( $attributes ) ) {

			if ( $type == 'visible' ) {
				$res = array();
				foreach ( $attributes as $a ) {
					if ( is_object( $a ) ) {
						if ( $a->get_visible() ) {
							$res[] = $a;
						}
					}
				}
			} else {
				$res = $attributes;
			}
		}
		return $res;
	}

	// service
	public function string_replacer( $val, $product_id ) {

		if ( is_string( $val ) ) {
			if ( stripos( $val, '{TITLE}' ) !== false ) {
				$val = str_ireplace( '{TITLE}', $this->get_post_field( $product_id, 'post_title' ), $val );
			}

			if ( stripos( $val, '{ID}' ) !== false ) {
				$val = str_ireplace( '{ID}', $product_id, $val );
			}

			if ( stripos( $val, '{SKU}' ) !== false ) {
				$val = str_ireplace( '{SKU}', $this->get_post_field( $product_id, 'sku' ), $val );
			}

			// ***

			if ( stripos( $val, '{PARENT_TITLE}' ) !== false ) {
				$p = $this->get_product( $product_id );
				if ( $p->is_type( 'variation' ) ) {
					$val = str_ireplace( '{PARENT_TITLE}', $this->get_post_field( $p->get_parent_id(), 'post_title' ), $val );
				} else {
					$val = str_ireplace( '{PARENT_TITLE}', $this->get_post_field( $product_id, 'post_title' ), $val );
				}
			}

			if ( stripos( $val, '{PARENT_SKU}' ) !== false ) {
				$p = $this->get_product( $product_id );
				if ( $p->is_type( 'variation' ) ) {
					$val = str_ireplace( '{PARENT_SKU}', $this->get_post_field( $p->get_parent_id(), 'sku' ), $val );
				} else {
					$val = str_ireplace( '{PARENT_SKU}', $this->get_post_field( $product_id, 'sku' ), $val );
				}
			}

			if ( stripos( $val, '{PARENT_ID}' ) !== false ) {
				$p = $this->get_product( $product_id );
				if ( $p->is_type( 'variation' ) ) {
					$val = str_ireplace( '{PARENT_ID}', $p->get_parent_id(), $val );
				} else {
					$val = str_ireplace( '{PARENT_ID}', $product_id, $val );
				}
			}

			if ( stripos( $val, '{MENU_ORDER}' ) !== false ) {
				$val = str_ireplace( '{MENU_ORDER}', $this->get_post_field( $product_id, 'menu_order' ), $val );
			}

			if ( stripos( $val, '{REGULAR_PRICE}' ) !== false ) {
				$val = str_ireplace( '{REGULAR_PRICE}', $this->get_post_field( $product_id, 'regular_price' ), $val );
			}

			if ( stripos( $val, '{SALE_PRICE}' ) !== false ) {
				$val = str_ireplace( '{SALE_PRICE}', $this->get_post_field( $product_id, 'sale_price' ), $val );
			}

			// ***

			if ( stripos( $val, '{meta:' ) !== false ) {
				// {meta:_woocs_regular_price_USD} - lets take data from metafield
				$matches = array();
				preg_match( '/(?<={meta:)(.*)(?=})/', $val, $matches );
				if ( isset( $matches[1] ) ) {
					// $meta = $this->get_post_field($product_id, trim($matches[1]));
					$meta = get_post_meta( $product_id, trim( $matches[1] ), true );
					if ( ! empty( $meta ) ) {
						$val = str_ireplace( '{meta:' . $matches[1] . '}', $meta, $val );
					} else {
						$val = str_ireplace( '{meta:' . $matches[1] . '}', '', $val );
					}
				}
			}

			if ( stripos( $val, '{attribute:' ) !== false ) {

				// {attribute:pa_size}
				$matches = array();
				preg_match( '/(?<={attribute:)(.*)(?=})/', $val, $matches );

				if ( isset( $matches[1] ) ) {
					$prod = $this->get_product( $product_id );
					$attr = $prod->get_attribute( trim( $matches[1] ) );

					if ( ! empty( $attr ) ) {
						$val = str_ireplace( '{attribute:' . $matches[1] . '}', $attr, $val );
					} else {
						$val = str_ireplace( '{attribute:' . $matches[1] . '}', '', $val );
					}
				}
			}

			if ( stripos( $val, '{tags}' ) !== false ) {

				$tags = wp_get_post_terms( $product_id, 'product_tag' );

				$tags_array = array();
				if ( ! empty( $tags ) && ! is_wp_error( $tags ) ) {
					foreach ( $tags as $tag ) {
						$tags_array[] = $tag->name;
					}
				}

				$val = str_ireplace( '{tags}', implode( ', ', $tags_array ), $val );
			}

			if ( stripos( $val, '{product_cat}' ) !== false ) {

				$product_cats = wp_get_post_terms( $product_id, 'product_cat' );

				$terms_array = array();
				if ( ! empty( $product_cats ) && ! is_wp_error( $product_cats ) ) {
					foreach ( $product_cats as $cat ) {
						$terms_array[] = $cat->name;
					}
				}

				$val = str_ireplace( '{product_cat}', implode( ', ', $terms_array ), $val );
			}
		}

		return apply_filters( 'woobe_apply_string_replacer', $val );
	}

	public function string_macros( $val, $field_key, $product_id ) {
		if ( ! is_string( $val ) ) {
			return $val;
		}
		$original_val = $this->get_post_field( $product_id, $field_key );

		if ( is_string( $original_val ) ) {
			if ( stripos( $val, '{DO_STRING_UP}' ) !== false ) {
				$val = str_ireplace( '{DO_STRING_UP}', mb_strtoupper( $original_val ), $val );
			}
			if ( stripos( $val, '{DO_STRING_DOWN}' ) !== false ) {
				$val = str_ireplace( '{DO_STRING_DOWN}', mb_strtolower( $original_val ), $val );
			}
			if ( stripos( $val, '{DO_STRING_TITLE}' ) !== false ) {
				$val = str_ireplace( '{DO_STRING_TITLE}', mb_convert_case( $original_val, MB_CASE_TITLE, 'UTF-8' ), $val );
			}
			if ( stripos( $val, '{DO_STRING_UP_FIRST}' ) !== false ) {
				$fc           = mb_strtoupper( mb_substr( $original_val, 0, 1 ) );
				$original_val = $fc . mb_substr( $original_val, 1 );
				$val          = str_ireplace( '{DO_STRING_UP_FIRST}', $original_val, $val );
			}
		}
		return $val;
	}

	/**
	 * Generates a title with attribute information for a variation.
	 * Products will get a title of the form "Name - Value, Value" or just "Name".
	 *
	 * @since 3.0.0
	 * @param WC_Product
	 * @return string
	 */
	public function generate_product_title( $product ) {
		$attributes = (array) $product->get_attributes();

		// Do not include attributes if the product has 10+ attributes.
		$should_include_attributes = count( $attributes ) < 10;

		// Do not include attributes if an attribute name has 2+ words and the
		// product has multiple attributes.
		if ( $should_include_attributes && 1 < count( $attributes ) ) {
			foreach ( $attributes as $name => $value ) {
				if ( false !== strpos( $name, '-' ) ) {
					$should_include_attributes = false;
					break;
				}
			}
		}

		$should_include_attributes = apply_filters( 'woocommerce_product_variation_title_include_attributes', $should_include_attributes, $product );
		$separator                 = apply_filters( 'woocommerce_product_variation_title_attributes_separator', ' - ', $product );

		// $parent_product = $this->get_product($product->get_parent_id());
		$title_base = get_post_field( 'post_title', $product->get_parent_id() );
		if ( $should_include_attributes ) {
			$title_suffix = wc_get_formatted_variation( $product, true, false );

			if ( ! empty( $attributes ) ) {
				$title_suffix .= ' <small>[';
				$title_suffix .= implode(
					', ',
					array_map(
						function ( $str ) {
							return str_replace( 'pa_', '', $str );
						},
						array_map( 'urldecode', array_keys( $attributes ) )
					)
				);
				$title_suffix .= ']</small>';
			}
		} else {
			$title_suffix = '';
		}

		// $title_suffix = $should_include_attributes ? wc_get_formatted_variation($product, true, false) : '';

		return apply_filters( 'woocommerce_product_variation_title', $title_suffix ? $title_base . $separator . $title_suffix : $title_base, $product, $title_base, $title_suffix );
	}

	public function is_current_user_can_edit_field( $field_key ) {

		if ( ! in_array( $this->settings->current_user_role, apply_filters( 'woobe_permit_special_roles', array( 'administrator' ) ) ) ) {
			$shop_manager_visibility = $this->settings->get_shop_manager_visibility();

			$user_can = apply_filters( 'woobe_user_can_edit', $shop_manager_visibility[ $field_key ], $field_key, $shop_manager_visibility );
			if ( ! intval( $user_can ) ) {
				return false;
			}
		}

		return true;
	}

	// service function to process serialized meta data
	public function process_jsoned_meta_data( $raw_data ) {
		$result = array();
		// *** //for js arrays
		if ( isset( $raw_data['keys'] ) and ! empty( $raw_data['keys'] ) ) {
			$tmp = array();
			foreach ( $raw_data['keys'] as $kk => $kv ) {
				if ( ! is_null( $kv ) ) {
					if ( ! is_array( $kv ) and isset( $raw_data['keys'][ $kv ] ) and is_array( $raw_data['keys'][ $kv ] ) ) {
						if ( ! empty( $raw_data['keys'][ $kv ] ) ) {
							$tmp[ WOOBE_HELPER::prepare_meta_keys( $kv ) ] = array();
							foreach ( $raw_data['keys'][ $kv ] as $kkk => $vvv ) {
								if ( isset( $raw_data['values'][ $kv ][ $kkk ] ) ) {
									$tmp[ WOOBE_HELPER::prepare_meta_keys( $kv ) ][ WOOBE_HELPER::prepare_meta_keys( $vvv ) ] = $raw_data['values'][ $kv ][ $kkk ];
								}
							}
						}
					} elseif ( ! is_array( $kv ) ) {
						if ( isset( $raw_data['values'][ WOOBE_HELPER::prepare_meta_keys( $kk ) ] ) ) {
							$tmp[ WOOBE_HELPER::prepare_meta_keys( $kv ) ] = $raw_data['values'][ WOOBE_HELPER::prepare_meta_keys( $kk ) ];
						}
					}
				}
			} $result = $tmp;
		}
		// *** //for js objects
		if ( isset( $raw_data['keys2'] ) and ! empty( $raw_data['keys2'] ) ) {
			$tmp = array();
			foreach ( $raw_data['keys2'] as $k => $keys ) {
				if ( ! empty( $keys ) ) {
					$o = array();
					foreach ( $keys as $kk => $key ) {
						if ( $this->is_json( $raw_data['values2'][ $k ][ $kk ] ) ) {
							$o[ $key ] = json_decode( $raw_data['values2'][ $k ][ $kk ], ARRAY_A );
						} else {
							$o[ $key ] = $raw_data['values2'][ $k ][ $kk ];
						}
					} $tmp[ $k ] = $o;
				}
			} $result = array_merge( $result, $tmp );
		}
		// *** //if meta value is just string or number
		if ( empty( $result ) ) {
			$result = $raw_data;
		}

		return $result;
	}

	protected function clear_caches( &$product ) {
		wc_delete_product_transients( $product->get_id() );
		if ( $product->get_parent_id( 'edit' ) ) {
			wc_delete_product_transients( $product->get_parent_id( 'edit' ) );
			WC_Cache_Helper::invalidate_cache_group( 'product_' . $product->get_parent_id( 'edit' ) );
		}
		WC_Cache_Helper::invalidate_attribute_count( array_keys( $product->get_attributes() ) );
		WC_Cache_Helper::invalidate_cache_group( 'product_' . $product->get_id() );
	}

	function is_json( $string ) {
		json_decode( $string );
		return ( json_last_error() == JSON_ERROR_NONE );
	}

	public function get_product_type( $pr ) {

		if ( is_object( $pr ) ) {
			$pr = $pr->get_id();
		}
		$type  = 'simple';
		$terms = wp_get_object_terms( $pr, 'product_type' );
		if ( is_array( $terms ) ) {
			$type = $terms[0]->slug;
		}

		return $type;
	}
}
