HEX
Server: LiteSpeed
System: Linux eko108.isimtescil.net 4.18.0-477.21.1.lve.1.el8.x86_64 #1 SMP Tue Sep 5 23:08:35 UTC 2023 x86_64
User: uyarreklamcomtr (11202)
PHP: 7.4.33
Disabled: opcache_get_status
Upload Files
File: /var/www/vhosts/uyarreklam.com.tr/httpdocs/Attributes.tar
AbstractAttribute.php000064400000003562151547771740010734 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

defined( 'ABSPATH' ) || exit;

/**
 * Class AbstractAttribute
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
abstract class AbstractAttribute implements AttributeInterface {

	/**
	 * @var mixed
	 */
	protected $value = null;

	/**
	 * AbstractAttribute constructor.
	 *
	 * @param mixed $value
	 */
	public function __construct( $value = null ) {
		$this->set_value( $value );
	}

	/**
	 * Return the attribute type. Must be a valid PHP type.
	 *
	 * @return string
	 *
	 * @link https://www.php.net/manual/en/function.settype.php
	 */
	public static function get_value_type(): string {
		return 'string';
	}

	/**
	 * Returns the attribute value.
	 *
	 * @return mixed
	 */
	public function get_value() {
		return $this->value;
	}

	/**
	 * @param mixed $value
	 *
	 * @return $this
	 */
	public function set_value( $value ): AbstractAttribute {
		$this->value = $this->cast_value( $value );

		return $this;
	}

	/**
	 * Casts the value to the attribute value type and returns the result.
	 *
	 * @param mixed $value
	 *
	 * @return mixed
	 */
	protected function cast_value( $value ) {
		if ( is_string( $value ) ) {
			$value = trim( $value );

			if ( '' === $value ) {
				return null;
			}
		}

		$value_type = static::get_value_type();
		if ( in_array( $value_type, [ 'bool', 'boolean' ], true ) ) {
			$value = wc_string_to_bool( $value );
		} else {
			settype( $value, $value_type );
		}

		return $value;
	}

	/**
	 * Return an array of WooCommerce product types that this attribute can be applied to.
	 *
	 * @return array
	 */
	public static function get_applicable_product_types(): array {
		return [ 'simple', 'variable', 'variation' ];
	}

	/**
	 * @return string
	 */
	public function __toString() {
		return (string) $this->get_value();
	}
}
Adult.php000064400000003630151547771740006352 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input\AdultInput;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\AttributeMapping\Traits\IsEnumTrait;

defined( 'ABSPATH' ) || exit;

/**
 * Class Adult
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
class Adult extends AbstractAttribute implements WithMappingInterface {

	use IsEnumTrait;

	/**
	 * Returns the attribute ID.
	 *
	 * Must be the same as a Google product's property name to be set automatically.
	 *
	 * @return string
	 *
	 * @see \Google\Service\ShoppingContent\Product for the list of properties.
	 */
	public static function get_id(): string {
		return 'adult';
	}

	/**
	 * Return an array of WooCommerce product types that this attribute can be applied to.
	 *
	 * @return array
	 */
	public static function get_applicable_product_types(): array {
		return [ 'simple', 'variable', 'variation' ];
	}

	/**
	 * Return the attribute type. Must be a valid PHP type.
	 *
	 * @return string
	 *
	 * @link https://www.php.net/manual/en/function.settype.php
	 */
	public static function get_value_type(): string {
		return 'boolean';
	}

	/**
	 * Return the attribute's input class. Must be an instance of `AttributeInputInterface`.
	 *
	 * @return string
	 *
	 * @see AttributeInputInterface
	 *
	 * @since 1.5.0
	 */
	public static function get_input_type(): string {
		return AdultInput::class;
	}

	/**
	 * Returns the attribute name
	 *
	 * @return string
	 */
	public static function get_name(): string {
		return __( 'Adult', 'google-listings-and-ads' );
	}

	/**
	 * Returns the attribute sources
	 *
	 * @return array
	 */
	public static function get_sources(): array {
		return [
			'yes' => __( 'Yes', 'google-listings-and-ads' ),
			'no'  => __( 'No', 'google-listings-and-ads' ),
		];
	}
}
AgeGroup.php000064400000003760151547771740007016 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input\AgeGroupInput;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\AttributeMapping\Traits\IsEnumTrait;

defined( 'ABSPATH' ) || exit;

/**
 * Class AgeGroup
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
class AgeGroup extends AbstractAttribute implements WithValueOptionsInterface, WithMappingInterface {

	use IsEnumTrait;

	/**
	 * Returns the attribute ID.
	 *
	 * Must be the same as a Google product's property name to be set automatically.
	 *
	 * @return string
	 *
	 * @see \Google\Service\ShoppingContent\Product for the list of properties.
	 */
	public static function get_id(): string {
		return 'ageGroup';
	}

	/**
	 * Return an array of values available to choose for the attribute.
	 *
	 * Note: array key is used as the option key.
	 *
	 * @return array
	 */
	public static function get_value_options(): array {
		return [
			'newborn' => __( 'Newborn', 'google-listings-and-ads' ),
			'infant'  => __( 'Infant', 'google-listings-and-ads' ),
			'toddler' => __( 'Toddler', 'google-listings-and-ads' ),
			'kids'    => __( 'Kids', 'google-listings-and-ads' ),
			'adult'   => __( 'Adult', 'google-listings-and-ads' ),
		];
	}

	/**
	 * Return an array of WooCommerce product types that this attribute can be applied to.
	 *
	 * @return array
	 */
	public static function get_applicable_product_types(): array {
		return [ 'simple', 'variation' ];
	}

	/**
	 * Return the attribute's input class. Must be an instance of `AttributeInputInterface`.
	 *
	 * @return string
	 *
	 * @see AttributeInputInterface
	 *
	 * @since 1.5.0
	 */
	public static function get_input_type(): string {
		return AgeGroupInput::class;
	}


	/**
	 * Returns the attribute name
	 *
	 * @return string
	 */
	public static function get_name(): string {
		return __( 'Age group', 'google-listings-and-ads' );
	}
}
AttributeInterface.php000064400000002563151547771740011071 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input\AttributeInputInterface;

defined( 'ABSPATH' ) || exit;

/**
 * Interface AttributeInterface
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
interface AttributeInterface {

	/**
	 * Returns the attribute ID.
	 *
	 * Must be the same as a Google product's property name to be set automatically.
	 *
	 * @return string
	 *
	 * @see \Google\Service\ShoppingContent\Product for the list of properties.
	 */
	public static function get_id(): string;

	/**
	 * Return the attribute's value type. Must be a valid PHP type.
	 *
	 * @return string
	 *
	 * @link https://www.php.net/manual/en/function.settype.php
	 */
	public static function get_value_type(): string;

	/**
	 * Return the attribute's input class. Must be an instance of `AttributeInputInterface`.
	 *
	 * @return string
	 *
	 * @see AttributeInputInterface
	 *
	 * @since 1.5.0
	 */
	public static function get_input_type(): string;

	/**
	 * Return an array of WooCommerce product types that this attribute can be applied to.
	 *
	 * @return array
	 */
	public static function get_applicable_product_types(): array;

	/**
	 * Returns the attribute value.
	 *
	 * @return mixed
	 */
	public function get_value();
}
AttributeManager.php000064400000026252151547771740010544 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Proxies\WC;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidClass;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidValue;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\ValidateInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Service;
use Automattic\WooCommerce\GoogleListingsAndAds\PluginHelper;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\WCProductAdapter;
use Automattic\WooCommerce\GoogleListingsAndAds\DB\Query\AttributeMappingRulesQuery;
use WC_Product;
use WC_Product_Variation;

defined( 'ABSPATH' ) || exit;

/**
 * Class AttributeManager
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
class AttributeManager implements Service {

	use PluginHelper;
	use ValidateInterface;

	protected const ATTRIBUTES = [
		GTIN::class,
		MPN::class,
		Brand::class,
		Condition::class,
		Gender::class,
		Size::class,
		SizeSystem::class,
		SizeType::class,
		Color::class,
		Material::class,
		Pattern::class,
		AgeGroup::class,
		Multipack::class,
		IsBundle::class,
		AvailabilityDate::class,
		Adult::class,
	];

	/**
	 * @var array Attribute types mapped to product types
	 */
	protected $attribute_types_map;

	/**
	 * @var AttributeMappingRulesQuery
	 */
	protected $attribute_mapping_rules_query;

	/**
	 * @var WC
	 */
	protected $wc;

	/**
	 * AttributeManager constructor.
	 *
	 * @param AttributeMappingRulesQuery $attribute_mapping_rules_query
	 * @param WC                         $wc
	 */
	public function __construct( AttributeMappingRulesQuery $attribute_mapping_rules_query, WC $wc ) {
		$this->attribute_mapping_rules_query = $attribute_mapping_rules_query;
		$this->wc                            = $wc;
	}

	/**
	 * @param WC_Product         $product
	 * @param AttributeInterface $attribute
	 *
	 * @throws InvalidValue If the attribute is invalid for the given product.
	 */
	public function update( WC_Product $product, AttributeInterface $attribute ) {
		$this->validate( $product, $attribute::get_id() );

		if ( null === $attribute->get_value() || '' === $attribute->get_value() ) {
			$this->delete( $product, $attribute::get_id() );
			return;
		}

		$value = $attribute->get_value();
		if ( in_array( $attribute::get_value_type(), [ 'bool', 'boolean' ], true ) ) {
			$value = wc_bool_to_string( $value );
		}

		$product->update_meta_data( $this->prefix_meta_key( $attribute::get_id() ), $value );
		$product->save_meta_data();
	}

	/**
	 * @param WC_Product $product
	 * @param string     $attribute_id
	 *
	 * @return AttributeInterface|null
	 *
	 * @throws InvalidValue If the attribute ID is invalid for the given product.
	 */
	public function get( WC_Product $product, string $attribute_id ): ?AttributeInterface {
		$this->validate( $product, $attribute_id );

		$value = null;
		if ( $this->exists( $product, $attribute_id ) ) {
			$value = $product->get_meta( $this->prefix_meta_key( $attribute_id ), true );
		}

		if ( null === $value || '' === $value ) {
			return null;
		}

		$attribute_class = $this->get_attribute_types_for_product( $product )[ $attribute_id ];
		return new $attribute_class( $value );
	}

	/**
	 * Return all attribute values for the given product, after the mapping rules, GLA attributes, and filters have been applied.
	 * GLA Attributes has priority over the product attributes.
	 *
	 * @since 2.8.0
	 *
	 * @param WC_Product $product
	 *
	 * @return array of attribute values
	 * @throws InvalidValue When the product does not exist.
	 */
	public function get_all_aggregated_values( WC_Product $product ) {
		$attributes = $this->get_all_values( $product );

		$parent_product = null;
		// merge with parent's attributes if it's a variation product
		if ( $product instanceof WC_Product_Variation ) {
			$parent_product    = $this->wc->get_product( $product->get_parent_id() );
			$parent_attributes = $this->get_all_values( $parent_product );
			$attributes        = array_merge( $parent_attributes, $attributes );
		}

		$mapping_rules = $this->attribute_mapping_rules_query->get_results();

		$adapted_product = new WCProductAdapter(
			[
				'wc_product'        => $product,
				'parent_wc_product' => $parent_product,
				'targetCountry'     => 'US', // targetCountry is required to create a new WCProductAdapter instance, but it's not used in the attributes context.
				'gla_attributes'    => $attributes,
				'mapping_rules'     => $mapping_rules,
			]
		);

		foreach ( self::ATTRIBUTES as $attribute_class ) {
			$attribute_id = $attribute_class::get_id();
			if ( $attribute_id === 'size' ) {
				$attribute_id = 'sizes';
			}

			if ( isset( $adapted_product->$attribute_id ) ) {
				$attributes[ $attribute_id ] = $adapted_product->$attribute_id;
			}
		}

		return $attributes;
	}

	/**
	 * Return attribute value.
	 *
	 * @param WC_Product $product
	 * @param string     $attribute_id
	 *
	 * @return mixed|null
	 */
	public function get_value( WC_Product $product, string $attribute_id ) {
		$attribute = $this->get( $product, $attribute_id );

		return $attribute instanceof AttributeInterface ? $attribute->get_value() : null;
	}

	/**
	 * Return all attributes for the given product
	 *
	 * @param WC_Product $product
	 *
	 * @return AttributeInterface[]
	 */
	public function get_all( WC_Product $product ): array {
		$all_attributes = [];
		foreach ( array_keys( $this->get_attribute_types_for_product( $product ) ) as $attribute_id ) {
			$attribute = $this->get( $product, $attribute_id );
			if ( null !== $attribute ) {
				$all_attributes[ $attribute_id ] = $attribute;
			}
		}

		return $all_attributes;
	}

	/**
	 * Return all attribute values for the given product
	 *
	 * @param WC_Product $product
	 *
	 * @return array of attribute values
	 */
	public function get_all_values( WC_Product $product ): array {
		$all_attributes = [];
		foreach ( array_keys( $this->get_attribute_types_for_product( $product ) ) as $attribute_id ) {
			$attribute = $this->get_value( $product, $attribute_id );
			if ( null !== $attribute ) {
				$all_attributes[ $attribute_id ] = $attribute;
			}
		}

		return $all_attributes;
	}

	/**
	 * @param WC_Product $product
	 * @param string     $attribute_id
	 *
	 * @throws InvalidValue If the attribute ID is invalid for the given product.
	 */
	public function delete( WC_Product $product, string $attribute_id ) {
		$this->validate( $product, $attribute_id );

		$product->delete_meta_data( $this->prefix_meta_key( $attribute_id ) );
		$product->save_meta_data();
	}

	/**
	 * Whether the attribute exists and has been set for the product.
	 *
	 * @param WC_Product $product
	 * @param string     $attribute_id
	 *
	 * @return bool
	 *
	 * @since 1.2.0
	 */
	public function exists( WC_Product $product, string $attribute_id ): bool {
		return $product->meta_exists( $this->prefix_meta_key( $attribute_id ) );
	}

	/**
	 * Returns an array of attribute types for the given product
	 *
	 * @param WC_Product $product
	 *
	 * @return string[] of attribute classes mapped to attribute IDs
	 */
	public function get_attribute_types_for_product( WC_Product $product ): array {
		return $this->get_attribute_types_for_product_types( [ $product->get_type() ] );
	}

	/**
	 * Returns an array of attribute types for the given product types
	 *
	 * @param string[] $product_types array of WooCommerce product types
	 *
	 * @return string[] of attribute classes mapped to attribute IDs
	 */
	public function get_attribute_types_for_product_types( array $product_types ): array {
		// flip the product types array to have them as array keys
		$product_types_keys = array_flip( $product_types );

		// intersect the product types with our stored attributes map to get arrays of attributes matching the given product types
		$match_attributes = array_intersect_key( $this->get_attribute_types_map(), $product_types_keys );

		// re-index the attributes map array to avoid string ($product_type) array keys
		$match_attributes = array_values( $match_attributes );

		if ( empty( $match_attributes ) ) {
			return [];
		}

		// merge all of the attribute arrays from the map (there might be duplicates) and return the results
		return array_merge( ...$match_attributes );
	}

	/**
	 * Returns all available attribute IDs.
	 *
	 * @return array
	 *
	 * @since 1.3.0
	 */
	public static function get_available_attribute_ids(): array {
		$attributes = [];
		foreach ( self::get_available_attribute_types() as $attribute_type ) {
			if ( method_exists( $attribute_type, 'get_id' ) ) {
				$attribute_id                = call_user_func( [ $attribute_type, 'get_id' ] );
				$attributes[ $attribute_id ] = $attribute_id;
			}
		}

		return $attributes;
	}

	/**
	 * Return an array of all available attribute class names.
	 *
	 * @return string[] Attribute class names
	 *
	 * @since 1.3.0
	 */
	public static function get_available_attribute_types(): array {
		/**
		 * Filters the list of available product attributes.
		 *
		 * @param string[] $attributes Array of attribute class names (FQN)
		 */
		return apply_filters( 'woocommerce_gla_product_attribute_types', self::ATTRIBUTES );
	}

	/**
	 * Returns an array of attribute types for all product types
	 *
	 * @return string[][] of attribute classes mapped to product types
	 */
	protected function get_attribute_types_map(): array {
		if ( ! isset( $this->attribute_types_map ) ) {
			$this->map_attribute_types();
		}

		return $this->attribute_types_map;
	}

	/**
	 * @param WC_Product $product
	 * @param string     $attribute_id
	 *
	 * @throws InvalidValue If the attribute type is invalid for the given product.
	 */
	protected function validate( WC_Product $product, string $attribute_id ) {
		$attribute_types = $this->get_attribute_types_for_product( $product );
		if ( ! isset( $attribute_types[ $attribute_id ] ) ) {
			do_action(
				'woocommerce_gla_error',
				sprintf( 'Attribute "%s" is not supported for a "%s" product (ID: %s).', $attribute_id, $product->get_type(), $product->get_id() ),
				__METHOD__
			);

			throw InvalidValue::not_in_allowed_list( 'attribute_id', array_keys( $attribute_types ) );
		}
	}

	/**
	 * @throws InvalidClass If any of the given attribute classes do not implement the AttributeInterface.
	 */
	protected function map_attribute_types(): void {
		$this->attribute_types_map = [];
		foreach ( self::get_available_attribute_types() as $attribute_type ) {
			$this->validate_interface( $attribute_type, AttributeInterface::class );

			$attribute_id     = call_user_func( [ $attribute_type, 'get_id' ] );
			$applicable_types = call_user_func( [ $attribute_type, 'get_applicable_product_types' ] );

			/**
			 * Filters the list of applicable product types for each attribute.
			 *
			 * @param string[] $applicable_types Array of WooCommerce product types
			 * @param string   $attribute_type   Attribute class name (FQN)
			 */
			$applicable_types = apply_filters( "woocommerce_gla_attribute_applicable_product_types_{$attribute_id}", $applicable_types, $attribute_type );

			foreach ( $applicable_types as $product_type ) {
				$this->attribute_types_map[ $product_type ]                  = $this->attribute_types_map[ $product_type ] ?? [];
				$this->attribute_types_map[ $product_type ][ $attribute_id ] = $attribute_type;
			}
		}
	}
}
AvailabilityDate.php000064400000003350151547771740010510 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input\AvailabilityDateInput;

defined( 'ABSPATH' ) || exit;

/**
 * Class AvailabilityDate
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 *
 * @since 1.5.0
 */
class AvailabilityDate extends AbstractAttribute {

	/**
	 * Returns the attribute ID.
	 *
	 * Must be the same as a Google product's property name to be set automatically.
	 *
	 * @return string
	 *
	 * @see \Google\Service\ShoppingContent\Product for the list of properties.
	 */
	public static function get_id(): string {
		return 'availabilityDate';
	}

	/**
	 * Returns a name for the attribute. Used in attribute's input.
	 *
	 * @return string
	 */
	public static function get_name(): string {
		return __( 'Availability Date', 'google-listings-and-ads' );
	}

	/**
	 * Returns a short description for the attribute. Used in attribute's input.
	 *
	 * @return string
	 */
	public static function get_description(): string {
		return __( 'The date a preordered or backordered product becomes available for delivery. Required if product availability is preorder or backorder', 'google-listings-and-ads' );
	}

	/**
	 * Return an array of WooCommerce product types that this attribute can be applied to.
	 *
	 * @return array
	 */
	public static function get_applicable_product_types(): array {
		return [ 'simple', 'variation' ];
	}

	/**
	 * Return the attribute's input class. Must be an instance of `AttributeInputInterface`.
	 *
	 * @return string
	 *
	 * @see AttributeInputInterface
	 */
	public static function get_input_type(): string {
		return AvailabilityDateInput::class;
	}
}
Brand.php000064400000002673151547771740006335 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input\BrandInput;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\AttributeMapping\Traits\IsFieldTrait;

defined( 'ABSPATH' ) || exit;

/**
 * Class Brand
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
class Brand extends AbstractAttribute implements WithMappingInterface {

	use IsFieldTrait;

	/**
	 * Returns the attribute ID.
	 *
	 * Must be the same as a Google product's property name to be set automatically.
	 *
	 * @return string
	 *
	 * @see \Google\Service\ShoppingContent\Product for the list of properties.
	 */
	public static function get_id(): string {
		return 'brand';
	}

	/**
	 * Return an array of WooCommerce product types that this attribute can be applied to.
	 *
	 * @return array
	 */
	public static function get_applicable_product_types(): array {
		return [ 'simple', 'variable' ];
	}

	/**
	 * Return the attribute's input class. Must be an instance of `AttributeInputInterface`.
	 *
	 * @return string
	 *
	 * @see AttributeInputInterface
	 *
	 * @since 1.5.0
	 */
	public static function get_input_type(): string {
		return BrandInput::class;
	}

	/**
	 * Returns the attribute name
	 *
	 * @return string
	 */
	public static function get_name(): string {
		return __( 'Brand', 'google-listings-and-ads' );
	}
}
Color.php000064400000002674151547771740006366 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input\ColorInput;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\AttributeMapping\Traits\IsFieldTrait;

defined( 'ABSPATH' ) || exit;

/**
 * Class Color
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
class Color extends AbstractAttribute implements WithMappingInterface {

	use IsFieldTrait;

	/**
	 * Returns the attribute ID.
	 *
	 * Must be the same as a Google product's property name to be set automatically.
	 *
	 * @return string
	 *
	 * @see \Google\Service\ShoppingContent\Product for the list of properties.
	 */
	public static function get_id(): string {
		return 'color';
	}

	/**
	 * Return an array of WooCommerce product types that this attribute can be applied to.
	 *
	 * @return array
	 */
	public static function get_applicable_product_types(): array {
		return [ 'simple', 'variation' ];
	}

	/**
	 * Return the attribute's input class. Must be an instance of `AttributeInputInterface`.
	 *
	 * @return string
	 *
	 * @see AttributeInputInterface
	 *
	 * @since 1.5.0
	 */
	public static function get_input_type(): string {
		return ColorInput::class;
	}

	/**
	 * Returns the attribute name
	 *
	 * @return string
	 */
	public static function get_name(): string {
		return __( 'Color', 'google-listings-and-ads' );
	}
}
Condition.php000064400000003613151547771740007230 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input\ConditionInput;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\AttributeMapping\Traits\IsEnumTrait;

defined( 'ABSPATH' ) || exit;

/**
 * Class Condition
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
class Condition extends AbstractAttribute implements WithValueOptionsInterface, WithMappingInterface {

	use IsEnumTrait;

	/**
	 * Returns the attribute ID.
	 *
	 * Must be the same as a Google product's property name to be set automatically.
	 *
	 * @return string
	 *
	 * @see \Google\Service\ShoppingContent\Product for the list of properties.
	 */
	public static function get_id(): string {
		return 'condition';
	}

	/**
	 * Return an array of WooCommerce product types that this attribute can be applied to.
	 *
	 * @return array
	 */
	public static function get_applicable_product_types(): array {
		return [ 'simple', 'variation' ];
	}

	/**
	 * Return an array of values available to choose for the attribute.
	 *
	 * Note: array key is used as the option key.
	 *
	 * @return array
	 */
	public static function get_value_options(): array {
		return [
			'new'         => __( 'New', 'google-listings-and-ads' ),
			'refurbished' => __( 'Refurbished', 'google-listings-and-ads' ),
			'used'        => __( 'Used', 'google-listings-and-ads' ),
		];
	}

	/**
	 * Return the attribute's input class. Must be an instance of `AttributeInputInterface`.
	 *
	 * @return string
	 *
	 * @see AttributeInputInterface
	 *
	 * @since 1.5.0
	 */
	public static function get_input_type(): string {
		return ConditionInput::class;
	}

	/**
	 * Returns the attribute name
	 *
	 * @return string
	 */
	public static function get_name(): string {
		return __( 'Condition', 'google-listings-and-ads' );
	}
}
GTIN.php000064400000002666151547771740006052 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input\GTINInput;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\AttributeMapping\Traits\IsFieldTrait;

defined( 'ABSPATH' ) || exit;

/**
 * Class GTIN
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
class GTIN extends AbstractAttribute implements WithMappingInterface {

	use IsFieldTrait;

	/**
	 * Returns the attribute ID.
	 *
	 * Must be the same as a Google product's property name to be set automatically.
	 *
	 * @return string
	 *
	 * @see \Google\Service\ShoppingContent\Product for the list of properties.
	 */
	public static function get_id(): string {
		return 'gtin';
	}

	/**
	 * Return an array of WooCommerce product types that this attribute can be applied to.
	 *
	 * @return array
	 */
	public static function get_applicable_product_types(): array {
		return [ 'simple', 'variation' ];
	}

	/**
	 * Return the attribute's input class. Must be an instance of `AttributeInputInterface`.
	 *
	 * @return string
	 *
	 * @see AttributeInputInterface
	 *
	 * @since 1.5.0
	 */
	public static function get_input_type(): string {
		return GTINInput::class;
	}

	/**
	 * Returns the attribute name
	 *
	 * @return string
	 */
	public static function get_name(): string {
		return __( 'GTIN', 'google-listings-and-ads' );
	}
}
Gender.php000064400000003550151547771740006506 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input\GenderInput;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\AttributeMapping\Traits\IsEnumTrait;

defined( 'ABSPATH' ) || exit;

/**
 * Class Gender
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
class Gender extends AbstractAttribute implements WithValueOptionsInterface, WithMappingInterface {

	use IsEnumTrait;

	/**
	 * Returns the attribute ID.
	 *
	 * Must be the same as a Google product's property name to be set automatically.
	 *
	 * @return string
	 *
	 * @see \Google\Service\ShoppingContent\Product for the list of properties.
	 */
	public static function get_id(): string {
		return 'gender';
	}

	/**
	 * Return an array of values available to choose for the attribute.
	 *
	 * Note: array key is used as the option key.
	 *
	 * @return array
	 */
	public static function get_value_options(): array {
		return [
			'male'   => __( 'Male', 'google-listings-and-ads' ),
			'female' => __( 'Female', 'google-listings-and-ads' ),
			'unisex' => __( 'Unisex', 'google-listings-and-ads' ),
		];
	}

	/**
	 * Return an array of WooCommerce product types that this attribute can be applied to.
	 *
	 * @return array
	 */
	public static function get_applicable_product_types(): array {
		return [ 'simple', 'variation' ];
	}

	/**
	 * Return the attribute's input class. Must be an instance of `AttributeInputInterface`.
	 *
	 * @return string
	 *
	 * @see AttributeInputInterface
	 *
	 * @since 1.5.0
	 */
	public static function get_input_type(): string {
		return GenderInput::class;
	}

	/**
	 * Returns the attribute name
	 *
	 * @return string
	 */
	public static function get_name(): string {
		return __( 'Gender', 'google-listings-and-ads' );
	}
}
IsBundle.php000064400000003640151547771740007007 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input\IsBundleInput;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\AttributeMapping\Traits\IsEnumTrait;

defined( 'ABSPATH' ) || exit;

/**
 * Class IsBundle
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
class IsBundle extends AbstractAttribute implements WithMappingInterface {

	use IsEnumTrait;

	/**
	 * Returns the attribute ID.
	 *
	 * Must be the same as a Google product's property name to be set automatically.
	 *
	 * @return string
	 *
	 * @see \Google\Service\ShoppingContent\Product for the list of properties.
	 */
	public static function get_id(): string {
		return 'isBundle';
	}

	/**
	 * Return an array of WooCommerce product types that this attribute can be applied to.
	 *
	 * @return array
	 */
	public static function get_applicable_product_types(): array {
		return [ 'simple', 'variation' ];
	}

	/**
	 * Return the attribute type. Must be a valid PHP type.
	 *
	 * @return string
	 *
	 * @link https://www.php.net/manual/en/function.settype.php
	 */
	public static function get_value_type(): string {
		return 'boolean';
	}

	/**
	 * Return the attribute's input class. Must be an instance of `AttributeInputInterface`.
	 *
	 * @return string
	 *
	 * @see AttributeInputInterface
	 *
	 * @since 1.5.0
	 */
	public static function get_input_type(): string {
		return IsBundleInput::class;
	}

	/**
	 * Returns the attribute name
	 *
	 * @return string
	 */
	public static function get_name(): string {
		return __( 'Is Bundle', 'google-listings-and-ads' );
	}


	/**
	 * Returns the attribute sources
	 *
	 * @return array
	 */
	public static function get_sources(): array {
		return [
			'yes' => __( 'Yes', 'google-listings-and-ads' ),
			'no'  => __( 'No', 'google-listings-and-ads' ),
		];
	}
}
MPN.php000064400000002660151547771740005735 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input\MPNInput;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\AttributeMapping\Traits\IsFieldTrait;

defined( 'ABSPATH' ) || exit;

/**
 * Class MPN
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
class MPN extends AbstractAttribute implements WithMappingInterface {

	use IsFieldTrait;

	/**
	 * Returns the attribute ID.
	 *
	 * Must be the same as a Google product's property name to be set automatically.
	 *
	 * @return string
	 *
	 * @see \Google\Service\ShoppingContent\Product for the list of properties.
	 */
	public static function get_id(): string {
		return 'mpn';
	}

	/**
	 * Return an array of WooCommerce product types that this attribute can be applied to.
	 *
	 * @return array
	 */
	public static function get_applicable_product_types(): array {
		return [ 'simple', 'variation' ];
	}

	/**
	 * Return the attribute's input class. Must be an instance of `AttributeInputInterface`.
	 *
	 * @return string
	 *
	 * @see AttributeInputInterface
	 *
	 * @since 1.5.0
	 */
	public static function get_input_type(): string {
		return MPNInput::class;
	}

	/**
	 * Returns the attribute name
	 *
	 * @return string
	 */
	public static function get_name(): string {
		return __( 'MPN', 'google-listings-and-ads' );
	}
}
Material.php000064400000002716151547771740007043 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input\MaterialInput;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\AttributeMapping\Traits\IsFieldTrait;

defined( 'ABSPATH' ) || exit;

/**
 * Class Material
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
class Material extends AbstractAttribute implements WithMappingInterface {

	use IsFieldTrait;

	/**
	 * Returns the attribute ID.
	 *
	 * Must be the same as a Google product's property name to be set automatically.
	 *
	 * @return string
	 *
	 * @see \Google\Service\ShoppingContent\Product for the list of properties.
	 */
	public static function get_id(): string {
		return 'material';
	}

	/**
	 * Return an array of WooCommerce product types that this attribute can be applied to.
	 *
	 * @return array
	 */
	public static function get_applicable_product_types(): array {
		return [ 'simple', 'variation' ];
	}

	/**
	 * Return the attribute's input class. Must be an instance of `AttributeInputInterface`.
	 *
	 * @return string
	 *
	 * @see AttributeInputInterface
	 *
	 * @since 1.5.0
	 */
	public static function get_input_type(): string {
		return MaterialInput::class;
	}

	/**
	 * Returns the attribute name
	 *
	 * @return string
	 */
	public static function get_name(): string {
		return __( 'Material', 'google-listings-and-ads' );
	}
}
Multipack.php000064400000003272151547771740007234 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input\MultipackInput;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\AttributeMapping\Traits\IsFieldTrait;

defined( 'ABSPATH' ) || exit;

/**
 * Class Multipack
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
class Multipack extends AbstractAttribute implements WithMappingInterface {

	use IsFieldTrait;

	/**
	 * Returns the attribute ID.
	 *
	 * Must be the same as a Google product's property name to be set automatically.
	 *
	 * @return string
	 *
	 * @see \Google\Service\ShoppingContent\Product for the list of properties.
	 */
	public static function get_id(): string {
		return 'multipack';
	}

	/**
	 * Return an array of WooCommerce product types that this attribute can be applied to.
	 *
	 * @return array
	 */
	public static function get_applicable_product_types(): array {
		return [ 'simple', 'variation' ];
	}

	/**
	 * Return the attribute type. Must be a valid PHP type.
	 *
	 * @return string
	 *
	 * @link https://www.php.net/manual/en/function.settype.php
	 */
	public static function get_value_type(): string {
		return 'integer';
	}

	/**
	 * Return the attribute's input class. Must be an instance of `AttributeInputInterface`.
	 *
	 * @return string
	 *
	 * @see AttributeInputInterface
	 *
	 * @since 1.5.0
	 */
	public static function get_input_type(): string {
		return MultipackInput::class;
	}

	/**
	 * Returns the attribute name
	 *
	 * @return string
	 */
	public static function get_name(): string {
		return __( 'Multipack', 'google-listings-and-ads' );
	}
}
Pattern.php000064400000002710151547771740006714 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input\PatternInput;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\AttributeMapping\Traits\IsFieldTrait;

defined( 'ABSPATH' ) || exit;

/**
 * Class Pattern
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
class Pattern extends AbstractAttribute implements WithMappingInterface {

	use IsFieldTrait;

	/**
	 * Returns the attribute ID.
	 *
	 * Must be the same as a Google product's property name to be set automatically.
	 *
	 * @return string
	 *
	 * @see \Google\Service\ShoppingContent\Product for the list of properties.
	 */
	public static function get_id(): string {
		return 'pattern';
	}

	/**
	 * Return an array of WooCommerce product types that this attribute can be applied to.
	 *
	 * @return array
	 */
	public static function get_applicable_product_types(): array {
		return [ 'simple', 'variation' ];
	}

	/**
	 * Return the attribute's input class. Must be an instance of `AttributeInputInterface`.
	 *
	 * @return string
	 *
	 * @see AttributeInputInterface
	 *
	 * @since 1.5.0
	 */
	public static function get_input_type(): string {
		return PatternInput::class;
	}

	/**
	 * Returns the attribute name
	 *
	 * @return string
	 */
	public static function get_name(): string {
		return __( 'Pattern', 'google-listings-and-ads' );
	}
}
Size.php000064400000002666151547771740006223 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input\SizeInput;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\AttributeMapping\Traits\IsFieldTrait;

defined( 'ABSPATH' ) || exit;

/**
 * Class Size
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
class Size extends AbstractAttribute implements WithMappingInterface {

	use IsFieldTrait;

	/**
	 * Returns the attribute ID.
	 *
	 * Must be the same as a Google product's property name to be set automatically.
	 *
	 * @return string
	 *
	 * @see \Google\Service\ShoppingContent\Product for the list of properties.
	 */
	public static function get_id(): string {
		return 'size';
	}

	/**
	 * Return an array of WooCommerce product types that this attribute can be applied to.
	 *
	 * @return array
	 */
	public static function get_applicable_product_types(): array {
		return [ 'simple', 'variation' ];
	}

	/**
	 * Return the attribute's input class. Must be an instance of `AttributeInputInterface`.
	 *
	 * @return string
	 *
	 * @see AttributeInputInterface
	 *
	 * @since 1.5.0
	 */
	public static function get_input_type(): string {
		return SizeInput::class;
	}

	/**
	 * Returns the attribute name
	 *
	 * @return string
	 */
	public static function get_name(): string {
		return __( 'Size', 'google-listings-and-ads' );
	}
}
SizeSystem.php000064400000004407151547771740007423 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input\SizeSystemInput;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\AttributeMapping\Traits\IsEnumTrait;

defined( 'ABSPATH' ) || exit;

/**
 * Class SizeSystem
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
class SizeSystem extends AbstractAttribute implements WithValueOptionsInterface, WithMappingInterface {

	use IsEnumTrait;

	/**
	 * Returns the attribute ID.
	 *
	 * Must be the same as a Google product's property name to be set automatically.
	 *
	 * @return string
	 *
	 * @see \Google\Service\ShoppingContent\Product for the list of properties.
	 */
	public static function get_id(): string {
		return 'sizeSystem';
	}

	/**
	 * Return an array of WooCommerce product types that this attribute can be applied to.
	 *
	 * @return array
	 */
	public static function get_applicable_product_types(): array {
		return [ 'simple', 'variation' ];
	}

	/**
	 * Return an array of values available to choose for the attribute.
	 *
	 * Note: array key is used as the option key.
	 *
	 * @return array
	 */
	public static function get_value_options(): array {
		return [
			'US'  => __( 'US', 'google-listings-and-ads' ),
			'EU'  => __( 'EU', 'google-listings-and-ads' ),
			'UK'  => __( 'UK', 'google-listings-and-ads' ),
			'DE'  => __( 'DE', 'google-listings-and-ads' ),
			'FR'  => __( 'FR', 'google-listings-and-ads' ),
			'IT'  => __( 'IT', 'google-listings-and-ads' ),
			'AU'  => __( 'AU', 'google-listings-and-ads' ),
			'BR'  => __( 'BR', 'google-listings-and-ads' ),
			'CN'  => __( 'CN', 'google-listings-and-ads' ),
			'JP'  => __( 'JP', 'google-listings-and-ads' ),
			'MEX' => __( 'MEX', 'google-listings-and-ads' ),
		];
	}

	/**
	 * Return the attribute's input class. Must be an instance of `AttributeInputInterface`.
	 *
	 * @return string
	 *
	 * @see AttributeInputInterface
	 *
	 * @since 1.5.0
	 */
	public static function get_input_type(): string {
		return SizeSystemInput::class;
	}

	/**
	 * Returns the attribute name
	 *
	 * @return string
	 */
	public static function get_name(): string {
		return __( 'Size System', 'google-listings-and-ads' );
	}
}
SizeType.php000064400000004160151547771740007054 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input\SizeTypeInput;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\AttributeMapping\Traits\IsEnumTrait;

defined( 'ABSPATH' ) || exit;

/**
 * Class SizeType
 *
 * @see https://support.google.com/merchants/answer/6324497
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
class SizeType extends AbstractAttribute implements WithValueOptionsInterface, WithMappingInterface {

	use IsEnumTrait;

	/**
	 * Returns the attribute ID.
	 *
	 * Must be the same as a Google product's property name to be set automatically.
	 *
	 * @return string
	 *
	 * @see \Google\Service\ShoppingContent\Product for the list of properties.
	 */
	public static function get_id(): string {
		return 'sizeType';
	}

	/**
	 * Return an array of WooCommerce product types that this attribute can be applied to.
	 *
	 * @return array
	 */
	public static function get_applicable_product_types(): array {
		return [ 'simple', 'variation' ];
	}

	/**
	 * Return an array of values available to choose for the attribute.
	 *
	 * Note: array key is used as the option key.
	 *
	 * @return array
	 */
	public static function get_value_options(): array {
		return [
			'regular'   => __( 'Regular', 'google-listings-and-ads' ),
			'petite'    => __( 'Petite', 'google-listings-and-ads' ),
			'plus'      => __( 'Plus', 'google-listings-and-ads' ),
			'tall'      => __( 'Tall', 'google-listings-and-ads' ),
			'big'       => __( 'Big', 'google-listings-and-ads' ),
			'maternity' => __( 'Maternity', 'google-listings-and-ads' ),
		];
	}

	/**
	 * Return the attribute's input class. Must be an instance of `AttributeInputInterface`.
	 *
	 * @return string
	 *
	 * @see AttributeInputInterface
	 *
	 * @since 1.5.0
	 */
	public static function get_input_type(): string {
		return SizeTypeInput::class;
	}

	/**
	 * Returns the attribute name
	 *
	 * @return string
	 */
	public static function get_name(): string {
		return __( 'Size Type', 'google-listings-and-ads' );
	}
}
WithMappingInterface.php000064400000001227151547771740011351 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

defined( 'ABSPATH' ) || exit;

/**
 * Interface with specific options for mapping
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
interface WithMappingInterface {

	/**
	 * Returns the attribute name
	 *
	 * @return string
	 */
	public static function get_name(): string;

	/**
	 * Returns true if the attribute is enum type
	 *
	 * @return boolean
	 */
	public static function is_enum(): bool;

	/**
	 * Returns the available attribute sources
	 *
	 * @return array
	 */
	public static function get_sources(): array;
}
WithValueOptionsInterface.php000064400000000775151547771740012415 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes;

defined( 'ABSPATH' ) || exit;

/**
 * Interface WithValueOptionsInterface
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 */
interface WithValueOptionsInterface {
	/**
	 * Return an array of values available to choose for the attribute.
	 *
	 * Note: array key is used as the option key.
	 *
	 * @return array
	 */
	public static function get_value_options(): array;
}
AttributesForm.php000064400000017455151551723510010250 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\Form;
use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\FormException;
use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\InputInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\Select;
use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\SelectWithTextInput;
use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input\GTINInput;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\InvalidValue;
use Automattic\WooCommerce\GoogleListingsAndAds\Exception\ValidateInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes\AttributeInterface;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes\WithValueOptionsInterface;

defined( 'ABSPATH' ) || exit;

/**
 * Class AttributesForm
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes
 */
class AttributesForm extends Form {

	use ValidateInterface;

	/**
	 * @var string[]
	 */
	protected $attribute_types = [];

	/**
	 * AttributesForm constructor.
	 *
	 * @param string[] $attribute_types The names of the attribute classes extending AttributeInterface.
	 * @param array    $data
	 */
	public function __construct( array $attribute_types, array $data = [] ) {
		foreach ( $attribute_types as $attribute_type ) {
			$this->add_attribute( $attribute_type );
		}

		parent::__construct( $data );
	}

	/**
	 * Return the data used for the input's view.
	 *
	 * @return array
	 */
	public function get_view_data(): array {
		$view_data = parent::get_view_data();

		// add classes to hide/display attributes based on product type
		foreach ( $view_data['children'] as $index => $input ) {
			if ( ! isset( $this->attribute_types[ $index ] ) ) {
				continue;
			}

			$attribute_type          = $this->attribute_types[ $index ];
			$attribute_product_types = self::get_attribute_product_types( $attribute_type );

			$hidden_types  = $attribute_product_types['hidden'];
			$visible_types = $attribute_product_types['visible'];

			$input['gla_wrapper_class'] = $input['gla_wrapper_class'] ?? '';

			if ( ! empty( $visible_types ) ) {
				$input['gla_wrapper_class'] .= ' show_if_' . join( ' show_if_', $visible_types );
			}

			if ( ! empty( $hidden_types ) ) {
				$input['gla_wrapper_class'] .= ' hide_if_' . join( ' hide_if_', $hidden_types );
			}

			$view_data['children'][ $index ] = $input;
		}

		return $view_data;
	}

	/**
	 * Get the hidden and visible types of an attribute's applicable product types.
	 *
	 * @param string $attribute_type The name of an attribute class extending AttributeInterface.
	 *
	 * @return array
	 */
	public static function get_attribute_product_types( string $attribute_type ): array {
		$attribute_id             = call_user_func( [ $attribute_type, 'get_id' ] );
		$applicable_product_types = call_user_func( [ $attribute_type, 'get_applicable_product_types' ] );

		/**
		 * This filter is documented in AttributeManager::map_attribute_types
		 *
		 * @see AttributeManager::map_attribute_types
		 */
		$applicable_product_types = apply_filters( "woocommerce_gla_attribute_applicable_product_types_{$attribute_id}", $applicable_product_types, $attribute_type );

		/**
		 * Filters the list of product types to hide the attribute for.
		 */
		$hidden_product_types = apply_filters( "woocommerce_gla_attribute_hidden_product_types_{$attribute_id}", [] );

		$visible_product_types = array_diff( $applicable_product_types, $hidden_product_types );

		return [
			'hidden'  => $hidden_product_types,
			'visible' => $visible_product_types,
		];
	}

	/**
	 * @param InputInterface     $input
	 * @param AttributeInterface $attribute
	 *
	 * @return InputInterface
	 */
	public static function init_input( InputInterface $input, AttributeInterface $attribute ) {
		$input->set_id( $attribute::get_id() )
			->set_name( $attribute::get_id() );

		$value_options = [];
		if ( $attribute instanceof WithValueOptionsInterface ) {
			$value_options = $attribute::get_value_options();
		}
		$value_options = apply_filters( "woocommerce_gla_product_attribute_value_options_{$attribute::get_id()}", $value_options );

		if ( ! empty( $value_options ) ) {
			if ( ! $input instanceof Select && ! $input instanceof SelectWithTextInput ) {
				$new_input = new SelectWithTextInput();
				$new_input->set_label( $input->get_label() )
					->set_description( $input->get_description() );

				// When GTIN uses the SelectWithTextInput field, copy the readonly/hidden attributes from the GTINInput field.
				if ( $input->name === 'gtin' ) {
					$gtin_input = new GTINInput();
					$new_input->set_hidden( $gtin_input->is_hidden() );
					$new_input->set_readonly( $gtin_input->is_readonly() );
				}

				return self::init_input( $new_input, $attribute );
			}

			// add a 'default' value option
			$value_options = [ '' => __( 'Default', 'google-listings-and-ads' ) ] + $value_options;

			$input->set_options( $value_options );
		}

		return $input;
	}

	/**
	 * Add an attribute to the form
	 *
	 * @param string      $attribute_type The name of an attribute class extending AttributeInterface.
	 * @param string|null $input_type     The name of an input class extending InputInterface to use for attribute input.
	 *
	 * @return AttributesForm
	 *
	 * @throws InvalidValue  If the attribute type is invalid or an invalid input type is specified for the attribute.
	 * @throws FormException If form is already submitted.
	 */
	public function add_attribute( string $attribute_type, ?string $input_type = null ): AttributesForm {
		$this->validate_interface( $attribute_type, AttributeInterface::class );

		// use the attribute's default input type if none provided.
		if ( empty( $input_type ) ) {
			$input_type = call_user_func( [ $attribute_type, 'get_input_type' ] );
		}

		$this->validate_interface( $input_type, InputInterface::class );

		$attribute_input = self::init_input( new $input_type(), new $attribute_type() );

		if ( ! $attribute_input->is_hidden() ) {
			$this->add( $attribute_input );

			$attribute_id                           = call_user_func( [ $attribute_type, 'get_id' ] );
			$this->attribute_types[ $attribute_id ] = $attribute_type;
		}

		return $this;
	}

	/**
	 * Remove an attribute from the form
	 *
	 * @param string $attribute_type The name of an attribute class extending AttributeInterface.
	 *
	 * @return AttributesForm
	 *
	 * @throws InvalidValue  If the attribute type is invalid or an invalid input type is specified for the attribute.
	 * @throws FormException If form is already submitted.
	 */
	public function remove_attribute( string $attribute_type ): AttributesForm {
		$this->validate_interface( $attribute_type, AttributeInterface::class );

		$attribute_id = call_user_func( [ $attribute_type, 'get_id' ] );
		unset( $this->attribute_types[ $attribute_id ] );
		$this->remove( $attribute_id );

		return $this;
	}

	/**
	 * Sets the input type for the given attribute.
	 *
	 * @param string $attribute_type The name of an attribute class extending AttributeInterface.
	 * @param string $input_type     The name of an input class extending InputInterface to use for attribute input.
	 *
	 * @return $this
	 *
	 * @throws FormException If form is already submitted.
	 */
	public function set_attribute_input( string $attribute_type, string $input_type ): AttributesForm {
		if ( $this->is_submitted ) {
			throw FormException::cannot_modify_submitted();
		}

		$this->validate_interface( $attribute_type, AttributeInterface::class );
		$this->validate_interface( $input_type, InputInterface::class );

		$attribute_id = call_user_func( [ $attribute_type, 'get_id' ] );
		if ( $this->has( $attribute_id ) ) {
			$this->children[ $attribute_id ] = self::init_input( new $input_type(), new $attribute_type() );
		}

		return $this;
	}
}
AttributesTab.php000064400000011704151551723510010042 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Admin;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\AdminConditional;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Conditional;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Registerable;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Service;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantCenterService;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes\AttributeManager;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\ProductSyncer;
use WC_Product;

defined( 'ABSPATH' ) || exit;

/**
 * Class AttributesTab
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes
 */
class AttributesTab implements Service, Registerable, Conditional {

	use AdminConditional;
	use AttributesTrait;

	/**
	 * @var Admin
	 */
	protected $admin;

	/**
	 * @var AttributeManager
	 */
	protected $attribute_manager;

	/**
	 * @var MerchantCenterService
	 */
	protected $merchant_center;

	/**
	 * AttributesTab constructor.
	 *
	 * @param Admin                 $admin
	 * @param AttributeManager      $attribute_manager
	 * @param MerchantCenterService $merchant_center
	 */
	public function __construct( Admin $admin, AttributeManager $attribute_manager, MerchantCenterService $merchant_center ) {
		$this->admin             = $admin;
		$this->attribute_manager = $attribute_manager;
		$this->merchant_center   = $merchant_center;
	}

	/**
	 * Register a service.
	 */
	public function register(): void {
		// Register the hooks only if Merchant Center is set up.
		if ( ! $this->merchant_center->is_setup_complete() ) {
			return;
		}

		add_action(
			'woocommerce_new_product',
			function ( int $product_id, WC_Product $product ) {
				$this->handle_update_product( $product );
			},
			10,
			2
		);
		add_action(
			'woocommerce_update_product',
			function ( int $product_id, WC_Product $product ) {
				$this->handle_update_product( $product );
			},
			10,
			2
		);

		add_action(
			'woocommerce_product_data_tabs',
			function ( array $tabs ) {
				return $this->add_tab( $tabs );
			}
		);
		add_action(
			'woocommerce_product_data_panels',
			function () {
				$this->render_panel();
			}
		);
	}

	/**
	 * Adds the Google for WooCommerce tab to the WooCommerce product data box.
	 *
	 * @param array $tabs The current product data tabs.
	 *
	 * @return array An array with product tabs with the Yoast SEO tab added.
	 */
	private function add_tab( array $tabs ): array {
		$tabs['gla_attributes'] = [
			'label'  => 'Google for WooCommerce',
			'class'  => 'gla',
			'target' => 'gla_attributes',
		];

		return $tabs;
	}

	/**
	 * Render the product attributes tab.
	 */
	private function render_panel() {
		$product = wc_get_product( get_the_ID() );
		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		echo $this->admin->get_view( 'attributes/tab-panel', [ 'form' => $this->get_form( $product )->get_view_data() ] );
	}

	/**
	 * Handle form submission and update the product attributes.
	 *
	 * @param WC_Product $product
	 */
	private function handle_update_product( WC_Product $product ) {
		/**
		 * Array of `true` values for each product IDs already handled by this method. Used to prevent double submission.
		 *
		 * @var bool[] $already_updated
		 */
		static $already_updated = [];
		if ( isset( $already_updated[ $product->get_id() ] ) ) {
			return;
		}

		$form           = $this->get_form( $product );
		$form_view_data = $form->get_view_data();

		// phpcs:disable WordPress.Security.NonceVerification
		if ( empty( $_POST[ $form_view_data['name'] ] ) ) {
			return;
		}
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$submitted_data = (array) wc_clean( wp_unslash( $_POST[ $form_view_data['name'] ] ) );
		// phpcs:enable WordPress.Security.NonceVerification

		$form->submit( $submitted_data );
		$this->update_data( $product, $form->get_data() );

		$already_updated[ $product->get_id() ] = true;
	}

	/**
	 * @param WC_Product $product
	 *
	 * @return AttributesForm
	 */
	protected function get_form( WC_Product $product ): AttributesForm {
		$attribute_types = $this->attribute_manager->get_attribute_types_for_product_types( $this->get_applicable_product_types() );

		$form = new AttributesForm( $attribute_types, $this->attribute_manager->get_all_values( $product ) );
		$form->set_name( 'attributes' );

		return $form;
	}

	/**
	 * @param WC_Product $product
	 * @param array      $data
	 *
	 * @return void
	 */
	protected function update_data( WC_Product $product, array $data ): void {
		foreach ( $this->attribute_manager->get_attribute_types_for_product( $product ) as $attribute_id => $attribute_type ) {
			if ( isset( $data[ $attribute_id ] ) ) {
				$this->attribute_manager->update( $product, new $attribute_type( $data[ $attribute_id ] ) );
			}
		}
	}
}
AttributesTrait.php000064400000001172151551723510010415 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes;

/**
 * Trait AttributesTrait
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes
 */
trait AttributesTrait {
	/**
	 * Return an array of WooCommerce product types that the Google for WooCommerce tab can be displayed for.
	 *
	 * @return array of WooCommerce product types (e.g. 'simple', 'variable', etc.)
	 */
	protected function get_applicable_product_types(): array {
		return apply_filters( 'woocommerce_gla_attributes_tab_applicable_product_types', [ 'simple', 'variable' ] );
	}
}
Input/AdultInput.php000064400000001267151551723510010460 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\BooleanSelect;

defined( 'ABSPATH' ) || exit;

/**
 * Class Adult
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 *
 * @since 1.5.0
 */
class AdultInput extends BooleanSelect {

	/**
	 * AdultInput constructor.
	 */
	public function __construct() {
		parent::__construct();

		$this->set_label( __( 'Adult content', 'google-listings-and-ads' ) );
		$this->set_description( __( 'Whether the product contains nudity or sexually suggestive content', 'google-listings-and-ads' ) );
	}
}
Input/AgeGroupInput.php000064400000001211151551723510011105 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\Select;

defined( 'ABSPATH' ) || exit;

/**
 * Class AgeGroup
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 *
 * @since 1.5.0
 */
class AgeGroupInput extends Select {

	/**
	 * AgeGroupInput constructor.
	 */
	public function __construct() {
		parent::__construct();

		$this->set_label( __( 'Age Group', 'google-listings-and-ads' ) );
		$this->set_description( __( 'Target age group of the item.', 'google-listings-and-ads' ) );
	}
}
Input/AttributeInputInterface.php000064400000001433151551723510013166 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input;

defined( 'ABSPATH' ) || exit;

/**
 * Class AttributeInputInterface
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input
 *
 * @since 1.5.0
 */
interface AttributeInputInterface {

	/**
	 * Returns a name for the attribute input.
	 *
	 * @return string
	 */
	public static function get_name(): string;

	/**
	 * Returns a short description for the attribute input.
	 *
	 * @return string
	 */
	public static function get_description(): string;

	/**
	 * Returns the input class used for the attribute input.
	 *
	 * Must be an instance of `InputInterface`.
	 *
	 * @return string
	 */
	public static function get_input_type(): string;
}
Input/AvailabilityDateInput.php000064400000001426151551723510012614 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\DateTime;

defined( 'ABSPATH' ) || exit;

/**
 * Class AvailabilityDate
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 *
 * @since 1.5.0
 */
class AvailabilityDateInput extends DateTime {

	/**
	 * AvailabilityDateInput constructor.
	 */
	public function __construct() {
		parent::__construct();

		$this->set_label( __( 'Availability Date', 'google-listings-and-ads' ) );
		$this->set_description( __( 'The date a preordered or backordered product becomes available for delivery. Required if product availability is preorder or backorder', 'google-listings-and-ads' ) );
	}
}
Input/BrandInput.php000064400000001160151551723510010425 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\Text;

defined( 'ABSPATH' ) || exit;

/**
 * Class Brand
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 *
 * @since 1.5.0
 */
class BrandInput extends Text {

	/**
	 * BrandInput constructor.
	 */
	public function __construct() {
		parent::__construct();

		$this->set_label( __( 'Brand', 'google-listings-and-ads' ) );
		$this->set_description( __( 'Brand of the product.', 'google-listings-and-ads' ) );
	}
}
Input/ColorInput.php000064400000001160151551723510010455 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\Text;

defined( 'ABSPATH' ) || exit;

/**
 * Class Color
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 *
 * @since 1.5.0
 */
class ColorInput extends Text {

	/**
	 * ColorInput constructor.
	 */
	public function __construct() {
		parent::__construct();

		$this->set_label( __( 'Color', 'google-listings-and-ads' ) );
		$this->set_description( __( 'Color of the product.', 'google-listings-and-ads' ) );
	}
}
Input/ConditionInput.php000064400000001216151551723510011327 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\Select;

defined( 'ABSPATH' ) || exit;

/**
 * Class Condition
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 *
 * @since 1.5.0
 */
class ConditionInput extends Select {

	/**
	 * ConditionInput constructor.
	 */
	public function __construct() {
		parent::__construct();

		$this->set_label( __( 'Condition', 'google-listings-and-ads' ) );
		$this->set_description( __( 'Condition or state of the item.', 'google-listings-and-ads' ) );
	}
}
Input/GTINInput.php000064400000003125151551723510010143 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\Text;
use Automattic\WooCommerce\GoogleListingsAndAds\HelperTraits\GTINMigrationUtilities;

defined( 'ABSPATH' ) || exit;

/**
 * Class GTIN
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 *
 * @since 1.5.0
 */
class GTINInput extends Text {

	use GTINMigrationUtilities;

	/**
	 * GTINInput constructor.
	 */
	public function __construct() {
		parent::__construct();

		$this->set_label( __( 'Global Trade Item Number (GTIN)', 'google-listings-and-ads' ) );
		$this->set_description( __( 'Global Trade Item Number (GTIN) for your item. These identifiers include UPC (in North America), EAN (in Europe), JAN (in Japan), and ISBN (for books)', 'google-listings-and-ads' ) );
		$this->set_field_visibility();
	}

	/**
	 * Controls the inputs visibility based on the WooCommerce version and the
	 * initial version of Google for WooCommerce at the time of installation.
	 *
	 * @since 2.9.0
	 * @return void
	 */
	public function set_field_visibility(): void {
		if ( $this->is_gtin_available_in_core() ) {
			// For versions after the GTIN changes are published. Hide the GTIN field from G4W tab. Otherwise, set as readonly.
			if ( $this->should_hide_gtin() ) {
				$this->set_hidden( true );
			} else {
				$this->set_readonly( true );
				$this->set_description( __( 'The Global Trade Item Number (GTIN) for your item can now be entered on the "Inventory" tab', 'google-listings-and-ads' ) );
			}
		}
	}
}
Input/GenderInput.php000064400000001221151551723510010601 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\Select;

defined( 'ABSPATH' ) || exit;

/**
 * Class Gender
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 *
 * @since 1.5.0
 */
class GenderInput extends Select {

	/**
	 * GenderInput constructor.
	 */
	public function __construct() {
		parent::__construct();

		$this->set_label( __( 'Gender', 'google-listings-and-ads' ) );
		$this->set_description( __( 'The gender for which your product is intended.', 'google-listings-and-ads' ) );
	}
}
Input/IsBundleInput.php000064400000001377151551723510011116 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\BooleanSelect;

defined( 'ABSPATH' ) || exit;

/**
 * Class IsBundle
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 *
 * @since 1.5.0
 */
class IsBundleInput extends BooleanSelect {

	/**
	 * IsBundleInput constructor.
	 */
	public function __construct() {
		parent::__construct();

		$this->set_label( __( 'Is Bundle?', 'google-listings-and-ads' ) );
		$this->set_description( __( 'Whether the item is a bundle of products. A bundle is a custom grouping of different products sold by a merchant for a single price.', 'google-listings-and-ads' ) );
	}
}
Input/MPNInput.php000064400000001254151551723510010035 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\Text;

defined( 'ABSPATH' ) || exit;

/**
 * Class MPN
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 *
 * @since 1.5.0
 */
class MPNInput extends Text {

	/**
	 * MPNInput constructor.
	 */
	public function __construct() {
		parent::__construct();

		$this->set_label( __( 'Manufacturer Part Number (MPN)', 'google-listings-and-ads' ) );
		$this->set_description( __( 'This code uniquely identifies the product to its manufacturer.', 'google-listings-and-ads' ) );
	}
}
Input/MaterialInput.php000064400000001216151551723510011137 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\Text;

defined( 'ABSPATH' ) || exit;

/**
 * Class Material
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 *
 * @since 1.5.0
 */
class MaterialInput extends Text {

	/**
	 * MaterialInput constructor.
	 */
	public function __construct() {
		parent::__construct();

		$this->set_label( __( 'Material', 'google-listings-and-ads' ) );
		$this->set_description( __( 'The material of which the item is made.', 'google-listings-and-ads' ) );
	}
}
Input/MultipackInput.php000064400000001500151551723510011326 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\Integer;

defined( 'ABSPATH' ) || exit;

/**
 * Class Multipack
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 *
 * @since 1.5.0
 */
class MultipackInput extends Integer {

	/**
	 * MultipackInput constructor.
	 */
	public function __construct() {
		parent::__construct();

		$this->set_label( __( 'Multipack', 'google-listings-and-ads' ) );
		$this->set_description( __( 'The number of identical products in a multipack. Use this attribute to indicate that you\'ve grouped multiple identical products for sale as one item.', 'google-listings-and-ads' ) );
		$this->set_block_attribute( 'min', [ 'value' => 0 ] );
	}
}
Input/PatternInput.php000064400000001211151551723510011011 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\Text;

defined( 'ABSPATH' ) || exit;

/**
 * Class Pattern
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 *
 * @since 1.5.0
 */
class PatternInput extends Text {

	/**
	 * PatternInput constructor.
	 */
	public function __construct() {
		parent::__construct();

		$this->set_label( __( 'Pattern', 'google-listings-and-ads' ) );
		$this->set_description( __( 'The item\'s pattern (e.g. polka dots).', 'google-listings-and-ads' ) );
	}
}
Input/SizeInput.php000064400000001153151551723510010313 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\Text;

defined( 'ABSPATH' ) || exit;

/**
 * Class Size
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 *
 * @since 1.5.0
 */
class SizeInput extends Text {

	/**
	 * SizeInput constructor.
	 */
	public function __construct() {
		parent::__construct();

		$this->set_label( __( 'Size', 'google-listings-and-ads' ) );
		$this->set_description( __( 'Size of the product.', 'google-listings-and-ads' ) );
	}
}
Input/SizeSystemInput.php000064400000001271151551723510011521 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\Select;

defined( 'ABSPATH' ) || exit;

/**
 * Class SizeSystem
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 *
 * @since 1.5.0
 */
class SizeSystemInput extends Select {

	/**
	 * SizeSystemInput constructor.
	 */
	public function __construct() {
		parent::__construct();

		$this->set_label( __( 'Size system', 'google-listings-and-ads' ) );
		$this->set_description( __( 'System in which the size is specified. Recommended for apparel items.', 'google-listings-and-ads' ) );
	}
}
Input/SizeTypeInput.php000064400000001237151551723510011160 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes\Input;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\Select;

defined( 'ABSPATH' ) || exit;

/**
 * Class SizeType
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes
 *
 * @since 1.5.0
 */
class SizeTypeInput extends Select {

	/**
	 * SizeTypeInput constructor.
	 */
	public function __construct() {
		parent::__construct();

		$this->set_label( __( 'Size type', 'google-listings-and-ads' ) );
		$this->set_description( __( 'The cut of the item. Recommended for apparel items.', 'google-listings-and-ads' ) );
	}
}
VariationsAttributes.php000064400000012114151551723510011447 0ustar00<?php
declare( strict_types=1 );

namespace Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes;

use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Admin;
use Automattic\WooCommerce\GoogleListingsAndAds\Admin\Input\Form;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\AdminConditional;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Conditional;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Registerable;
use Automattic\WooCommerce\GoogleListingsAndAds\Infrastructure\Service;
use Automattic\WooCommerce\GoogleListingsAndAds\MerchantCenter\MerchantCenterService;
use Automattic\WooCommerce\GoogleListingsAndAds\Product\Attributes\AttributeManager;
use WC_Product_Variation;
use WP_Post;

defined( 'ABSPATH' ) || exit;

/**
 * Class VariationsAttributes
 *
 * @package Automattic\WooCommerce\GoogleListingsAndAds\Admin\Product\Attributes
 */
class VariationsAttributes implements Service, Registerable, Conditional {

	use AdminConditional;

	/**
	 * @var Admin
	 */
	protected $admin;

	/**
	 * @var AttributeManager
	 */
	protected $attribute_manager;

	/**
	 * @var MerchantCenterService
	 */
	protected $merchant_center;

	/**
	 * VariationsAttributes constructor.
	 *
	 * @param Admin                 $admin
	 * @param AttributeManager      $attribute_manager
	 * @param MerchantCenterService $merchant_center
	 */
	public function __construct( Admin $admin, AttributeManager $attribute_manager, MerchantCenterService $merchant_center ) {
		$this->admin             = $admin;
		$this->attribute_manager = $attribute_manager;
		$this->merchant_center   = $merchant_center;
	}
	/**
	 * Register a service.
	 */
	public function register(): void {
		// Register the hooks only if Merchant Center is set up.
		if ( ! $this->merchant_center->is_setup_complete() ) {
			return;
		}

		add_action(
			'woocommerce_product_after_variable_attributes',
			function ( int $variation_index, array $variation_data, WP_Post $variation ) {
				$this->render_attributes_form( $variation_index, $variation );
			},
			90,
			3
		);
		add_action(
			'woocommerce_save_product_variation',
			function ( int $variation_id, int $variation_index ) {
				$this->handle_save_variation( $variation_id, $variation_index );
			},
			10,
			2
		);
	}

	/**
	 * Render the attributes form for variations.
	 *
	 * @param int     $variation_index Position in the loop.
	 * @param WP_Post $variation       Post data.
	 */
	private function render_attributes_form( int $variation_index, WP_Post $variation ) {
		/**
		 * @var WC_Product_Variation $product
		 */
		$product = wc_get_product( $variation->ID );

		$data = $this->get_form( $product, $variation_index )->get_view_data();

		// Do not render the form if it doesn't contain any child attributes.
		$attributes = reset( $data['children'] );
		if ( empty( $data['children'] ) || empty( $attributes['children'] ) ) {
			return;
		}

		// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		echo $this->admin->get_view( 'attributes/variations-form', $data );
	}

	/**
	 * Handle form submission and update the product attributes.
	 *
	 * @param int $variation_id
	 * @param int $variation_index
	 */
	private function handle_save_variation( int $variation_id, int $variation_index ) {
		/**
		 * @var WC_Product_Variation $variation
		 */
		$variation = wc_get_product( $variation_id );

		$form           = $this->get_form( $variation, $variation_index );
		$form_view_data = $form->get_view_data();

		// phpcs:disable WordPress.Security.NonceVerification
		if ( empty( $_POST[ $form_view_data['name'] ] ) ) {
			return;
		}
		// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$submitted_data = (array) wc_clean( wp_unslash( $_POST[ $form_view_data['name'] ] ) );
		// phpcs:enable WordPress.Security.NonceVerification

		$form->submit( $submitted_data );
		$form_data = $form->get_data();

		if ( ! empty( $form_data[ $variation_index ] ) ) {
			$this->update_data( $variation, $form_data[ $variation_index ] );
		}
	}

	/**
	 * @param WC_Product_Variation $variation
	 * @param int                  $variation_index
	 *
	 * @return Form
	 */
	protected function get_form( WC_Product_Variation $variation, int $variation_index ): Form {
		$attribute_types = $this->attribute_manager->get_attribute_types_for_product( $variation );
		$attribute_form  = new AttributesForm( $attribute_types );
		$attribute_form->set_name( (string) $variation_index );

		$form = new Form();
		$form->set_name( 'variation_attributes' )
			->add( $attribute_form )
			->set_data( [ (string) $variation_index => $this->attribute_manager->get_all_values( $variation ) ] );

		return $form;
	}

	/**
	 * @param WC_Product_Variation $variation
	 * @param array                $data
	 *
	 * @return void
	 */
	protected function update_data( WC_Product_Variation $variation, array $data ): void {
		foreach ( $this->attribute_manager->get_attribute_types_for_product( $variation ) as $attribute_id => $attribute_type ) {
			if ( isset( $data[ $attribute_id ] ) ) {
				$this->attribute_manager->update( $variation, new $attribute_type( $data[ $attribute_id ] ) );
			}
		}
	}
}