__( 'Made to the consumer’s specifications or personalised', 'otkazpro' ), 'perishable' => __( 'Liable to deteriorate or expire rapidly', 'otkazpro' ), 'other' => __( 'Other (specify)', 'otkazpro' ), ); } /** * Human-readable reason. * * @param string $reason Reason key. * @param string $text Free text. * @return string */ public static function reason_label( $reason, $text = '' ) { if ( 'other' === $reason ) { return $text ? $text : __( 'Excluded from the right of withdrawal', 'otkazpro' ); } $reasons = self::reasons(); return isset( $reasons[ $reason ] ) ? $reasons[ $reason ] : __( 'Excluded from the right of withdrawal', 'otkazpro' ); } /** * Help text shown next to the fields. * * @return string */ private static function help_text() { return __( 'Do not mark goods that lose the right of withdrawal only once unsealed (hygiene products, cosmetics, sealed audio/video/software) – they remain withdrawable while sealed.', 'otkazpro' ); } /* --------------------------------------------------------------------- * Resolution * ------------------------------------------------------------------ */ /** * Exclusion for a product (variation → parent → categories). * * @param \WC_Product|int|null $product Product. * @return array|false ['reason' => key, 'label' => text, 'source' => product|variation|category] */ public static function for_product( $product ) { if ( ! $product instanceof \WC_Product ) { $product = $product ? wc_get_product( $product ) : null; } if ( ! $product ) { return false; } $result = false; if ( $product->is_type( 'variation' ) ) { $own = self::from_meta( $product, 'variation' ); if ( null !== $own ) { return apply_filters( 'py_otkaz_product_exclusion', $own, $product ); } $parent = wc_get_product( $product->get_parent_id() ); if ( $parent ) { $result = self::resolve_parent( $parent ); } } else { $result = self::resolve_parent( $product ); } return apply_filters( 'py_otkaz_product_exclusion', $result, $product ); } /** * Product (non-variation) own meta, then categories. * * @param \WC_Product $product Product. * @return array|false */ private static function resolve_parent( $product ) { $own = self::from_meta( $product, 'product' ); if ( null !== $own ) { return $own; } foreach ( $product->get_category_ids() as $term_id ) { $flag = get_term_meta( $term_id, self::TERM_FLAG, true ); if ( 'yes' === $flag ) { $reason = (string) get_term_meta( $term_id, self::TERM_REASON, true ); return array( 'reason' => $reason ? $reason : 'other', 'label' => self::reason_label( $reason, (string) get_term_meta( $term_id, self::TERM_TEXT, true ) ), 'source' => 'category', ); } } return false; } /** * Read product/variation meta. Returns null when "inherit". * * @param \WC_Product $product Product. * @param string $source Source label. * @return array|false|null */ private static function from_meta( $product, $source ) { $flag = (string) $product->get_meta( self::META_FLAG ); if ( 'no' === $flag ) { return false; } if ( 'yes' !== $flag ) { return null; } $reason = (string) $product->get_meta( self::META_REASON ); return array( 'reason' => $reason ? $reason : 'other', 'label' => self::reason_label( $reason, (string) $product->get_meta( self::META_TEXT ) ), 'source' => $source, ); } /** * Exclusion for an order line item (product rules + personalisation meta keys). * * @param \WC_Order_Item_Product $item Item. * @param \WC_Order $order Order. * @return array|false */ public static function for_item( $item, $order ) { $result = self::for_product( $item->get_product() ); if ( ! $result ) { $keys = array_filter( array_map( 'trim', preg_split( '/\r\n|\r|\n/', (string) Settings::get( 'exclusion_meta_keys' ) ) ) ); if ( $keys ) { $keys = array_map( 'strtolower', $keys ); foreach ( $item->get_meta_data() as $meta ) { $data = $meta->get_data(); if ( in_array( strtolower( (string) $data['key'] ), $keys, true ) && ! self::is_empty_value( $data['value'] ) ) { $result = array( 'reason' => 'personalized', 'label' => self::reason_label( 'personalized' ), 'source' => 'item_meta', ); break; } } } } /** * Filter whether an order item is excluded from the right of withdrawal. * * @param array|false $result ['reason','label','source'] or false. * @param \WC_Order_Item_Product $item Order item. * @param \WC_Order $order Order. */ return apply_filters( 'py_otkaz_item_is_excluded', $result, $item, $order ); } /** * Empty check for meta values (arrays, whitespace). * * @param mixed $value Value. * @return bool */ private static function is_empty_value( $value ) { if ( is_array( $value ) ) { return ! array_filter( $value ); } return '' === trim( wp_strip_all_tags( (string) $value ) ); } /* --------------------------------------------------------------------- * Admin fields * ------------------------------------------------------------------ */ /** * Product edit → Advanced tab. */ public static function product_fields() { global $product_object; $product = $product_object instanceof \WC_Product ? $product_object : null; echo '
'; } /** * Save product fields. * * @param \WC_Product $product Product. */ public static function save_product( $product ) { if ( ! isset( $_POST['woocommerce_meta_nonce'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['woocommerce_meta_nonce'] ) ), 'woocommerce_save_data' ) ) { return; } if ( ! current_user_can( 'edit_product', $product->get_id() ) || ! isset( $_POST[ self::META_FLAG ] ) ) { return; } self::save_to_product( $product, sanitize_key( wp_unslash( $_POST[ self::META_FLAG ] ) ), isset( $_POST[ self::META_REASON ] ) ? sanitize_key( wp_unslash( $_POST[ self::META_REASON ] ) ) : '', isset( $_POST[ self::META_TEXT ] ) ? sanitize_text_field( wp_unslash( $_POST[ self::META_TEXT ] ) ) : '' ); } /** * Persist values on a product object (no save()). * * @param \WC_Product $product Product. * @param string $flag Flag. * @param string $reason Reason. * @param string $text Text. */ private static function save_to_product( $product, $flag, $reason, $text ) { $flag = in_array( $flag, array( 'yes', 'no' ), true ) ? $flag : ''; $reason = array_key_exists( $reason, self::reasons() ) ? $reason : 'personalized'; if ( '' === $flag ) { $product->delete_meta_data( self::META_FLAG ); $product->delete_meta_data( self::META_REASON ); $product->delete_meta_data( self::META_TEXT ); return; } $product->update_meta_data( self::META_FLAG, $flag ); $product->update_meta_data( self::META_REASON, $reason ); $product->update_meta_data( self::META_TEXT, $text ); } /** * Variation fields. * * @param int $loop Index. * @param array $variation_data Data. * @param \WP_Post $variation Variation post. */ public static function variation_fields( $loop, $variation_data, $variation ) { $product = wc_get_product( $variation->ID ); if ( ! $product ) { return; } echo '