get_param( self::ITEM_KEY ); if ( is_string( $token ) && '' !== $token ) { $data['cart_item_data'][ self::ITEM_KEY ] = sanitize_text_field( $token ); } return $data; } /** * @param bool $passed Validation result so far. * @param int $product_id Parent product ID. * @param int $quantity Quantity. * @param int $variation_id Variation ID. * @param array $variation Variation attributes. * @param array $cart_item_data Extra cart item data. */ public static function validate( $passed, $product_id, $quantity, $variation_id = 0, $variation = array(), $cart_item_data = array() ) { $product = wc_get_product( $product_id ); if ( ! $passed || ! $product || ! Product_Settings::is_enabled( $product ) ) { return $passed; } $token = is_array( $cart_item_data ) ? (string) ( $cart_item_data[ self::ITEM_KEY ] ?? '' ) : ''; $design = $token ? Design_Store::find_by_token( $token ) : null; if ( ! $design ) { wc_add_notice( __( 'Design your product before adding it to the cart.', 'printblob' ), 'error' ); return false; } $owner_ok = get_post_meta( $design->ID, Design_Store::META_OWNER, true ) === Design_Store::owner_id(); $product_ok = (int) get_post_meta( $design->ID, Design_Store::META_PRODUCT, true ) === (int) $product_id; if ( ! $owner_ok || ! $product_ok ) { wc_add_notice( __( 'This design belongs to a different product or session. Save your design again.', 'printblob' ), 'error' ); return false; } if ( Design_Store::order_id( $design->ID ) > 0 ) { wc_add_notice( __( 'This design was already ordered. Design the product again to order it once more.', 'printblob' ), 'error' ); return false; } return $passed; } /** * On the cart and checkout pages (both checkouts run this hook): a designed item whose design was deleted while it * sat in the cart (the cleanup, an erasure) leaves the cart with a notice, so no order is placed without its design; * a design still there is marked seen, so the cleanup keeps it. */ public static function check_cart_items(): void { foreach ( WC()->cart->get_cart() as $key => $cart_item ) { if ( empty( $cart_item[ self::ITEM_KEY ] ) ) { continue; } $design = Design_Store::find_by_token( (string) $cart_item[ self::ITEM_KEY ] ); if ( ! $design ) { WC()->cart->remove_cart_item( $key ); wc_add_notice( __( 'Your saved design expired and was removed from the cart. Design the product again.', 'printblob' ), 'error' ); continue; } Design_Store::touch( $design->ID ); } } /** * The printed sides of a cart item's design that its product offers now: a side the shop no longer offers (the * back once PrintBlob Pro is gone) is neither charged nor ordered. * * @return array */ public static function design_sides( array $cart_item ): array { if ( empty( $cart_item[ self::ITEM_KEY ] ) || ! ( ( $cart_item['data'] ?? null ) instanceof \WC_Product ) ) { return array(); } $design = Design_Store::find_by_token( (string) $cart_item[ self::ITEM_KEY ] ); return $design ? array_intersect_key( Design_Store::sides( $design->ID ), Mockups::sides_for( $cart_item['data'] ) ) : array(); } public static function apply_print_prices( \WC_Cart $cart ): void { foreach ( $cart->get_cart() as $cart_item ) { $sides = self::design_sides( $cart_item ); if ( ! $sides ) { continue; } /** @var \WC_Product $product */ $product = $cart_item['data']; // Start from the stored price each time: totals can be calculated more than once per request. $fresh = wc_get_product( $product->get_id() ); $price = $fresh ? (float) $fresh->get_price() : (float) $product->get_price(); foreach ( array_keys( $sides ) as $side ) { $price += Product_Settings::side_price( $product, $side ); } $product->set_price( $price ); } } public static function item_data( array $item_data, array $cart_item ): array { $sides = self::design_sides( $cart_item ); if ( $sides ) { $item_data[] = array( 'key' => __( 'Print', 'printblob' ), 'value' => self::sides_label( array_keys( $sides ) ), ); } return $item_data; } public static function sides_label( array $sides ): string { $labels = array_map( static fn( $side ) => Product_Settings::side_label( $side ), $sides ); return implode( ', ', $labels ); } public static function store_api_images( array $images, array $cart_item ): array { $sides = self::design_sides( $cart_item ); if ( ! $sides ) { return $images; } $designed = array(); foreach ( $sides as $side ) { $id = (int) $side['preview_id']; $full = wp_get_attachment_image_src( $id, 'full' ); $thumb = wp_get_attachment_image_src( $id, 'woocommerce_thumbnail' ); if ( ! $full || ! $thumb ) { continue; } $designed[] = (object) array( 'id' => $id, 'src' => $full[0], 'thumbnail' => $thumb[0], 'srcset' => (string) wp_get_attachment_image_srcset( $id, 'full' ), 'sizes' => (string) wp_get_attachment_image_sizes( $id, 'full' ), 'name' => get_the_title( $id ), 'alt' => __( 'Your design', 'printblob' ), ); } return $designed ? $designed : $images; } public static function classic_thumbnail( string $thumbnail, array $cart_item ): string { $sides = self::design_sides( $cart_item ); if ( ! $sides ) { return $thumbnail; } $first = reset( $sides ); $image = wp_get_attachment_image( (int) $first['preview_id'], 'woocommerce_thumbnail', false, array( 'alt' => __( 'Your design', 'printblob' ) ) ); return $image ? $image : $thumbnail; } }