label. * * @return array */ public static function placements() { return array( 'wc_before_product' => __( 'Product: before product', 'advanced-css3-related-posts-widget' ), 'wc_before_title' => __( 'Product: before title', 'advanced-css3-related-posts-widget' ), 'wc_after_title' => __( 'Product: after title', 'advanced-css3-related-posts-widget' ), 'wc_after_rating' => __( 'Product: after rating', 'advanced-css3-related-posts-widget' ), 'wc_after_price' => __( 'Product: after price', 'advanced-css3-related-posts-widget' ), 'wc_before_excerpt' => __( 'Product: before short description', 'advanced-css3-related-posts-widget' ), 'wc_after_excerpt' => __( 'Product: after short description', 'advanced-css3-related-posts-widget' ), 'wc_before_variations' => __( 'Product: before variations', 'advanced-css3-related-posts-widget' ), 'wc_after_variations' => __( 'Product: after variations', 'advanced-css3-related-posts-widget' ), 'wc_before_add_to_cart' => __( 'Product: before Add to Cart', 'advanced-css3-related-posts-widget' ), 'wc_after_add_to_cart' => __( 'Product: after Add to Cart', 'advanced-css3-related-posts-widget' ), 'wc_after_summary' => __( 'Product: after product summary', 'advanced-css3-related-posts-widget' ), 'wc_before_tabs' => __( 'Product: before product tabs', 'advanced-css3-related-posts-widget' ), 'wc_after_tabs' => __( 'Product: after product tabs', 'advanced-css3-related-posts-widget' ), 'wc_after_product' => __( 'Product: after product page', 'advanced-css3-related-posts-widget' ), 'wc_shop_before_loop' => __( 'Shop / archive: before products', 'advanced-css3-related-posts-widget' ), 'wc_shop_after_loop' => __( 'Shop / archive: after products', 'advanced-css3-related-posts-widget' ), 'wc_cart_before' => __( 'Cart: before cart', 'advanced-css3-related-posts-widget' ), 'wc_cart_collaterals' => __( 'Cart: cart collaterals', 'advanced-css3-related-posts-widget' ), 'wc_cart_after' => __( 'Cart: after cart', 'advanced-css3-related-posts-widget' ), 'wc_cart_empty' => __( 'Cart: empty cart', 'advanced-css3-related-posts-widget' ), 'wc_checkout_before' => __( 'Checkout: before form', 'advanced-css3-related-posts-widget' ), 'wc_checkout_after' => __( 'Checkout: after form', 'advanced-css3-related-posts-widget' ), 'wc_thankyou' => __( 'Order received: after order', 'advanced-css3-related-posts-widget' ), ); } /** * Map of placement key => [ hook, priority, context ]. * * @return array */ public static function hook_map() { return array( 'wc_before_product' => array( 'woocommerce_before_single_product', 20, 'singular' ), 'wc_before_title' => array( 'woocommerce_single_product_summary', 4, 'singular' ), 'wc_after_title' => array( 'woocommerce_single_product_summary', 6, 'singular' ), 'wc_after_rating' => array( 'woocommerce_single_product_summary', 11, 'singular' ), 'wc_after_price' => array( 'woocommerce_single_product_summary', 15, 'singular' ), 'wc_before_excerpt' => array( 'woocommerce_single_product_summary', 19, 'singular' ), 'wc_after_excerpt' => array( 'woocommerce_single_product_summary', 21, 'singular' ), 'wc_before_variations' => array( 'woocommerce_before_variations_form', 10, 'singular' ), 'wc_after_variations' => array( 'woocommerce_after_variations_form', 10, 'singular' ), 'wc_before_add_to_cart' => array( 'woocommerce_before_add_to_cart_form', 10, 'singular' ), 'wc_after_add_to_cart' => array( 'woocommerce_after_add_to_cart_form', 10, 'singular' ), 'wc_after_summary' => array( 'woocommerce_after_single_product_summary', 5, 'singular' ), 'wc_before_tabs' => array( 'woocommerce_after_single_product_summary', 9, 'singular' ), 'wc_after_tabs' => array( 'woocommerce_after_single_product_summary', 11, 'singular' ), 'wc_after_product' => array( 'woocommerce_after_single_product', 10, 'singular' ), 'wc_shop_before_loop' => array( 'woocommerce_before_shop_loop', 40, 'shop' ), 'wc_shop_after_loop' => array( 'woocommerce_after_shop_loop', 20, 'shop' ), 'wc_cart_before' => array( 'woocommerce_before_cart', 20, 'cart' ), 'wc_cart_collaterals' => array( 'woocommerce_cart_collaterals', 20, 'cart' ), 'wc_cart_after' => array( 'woocommerce_after_cart', 10, 'cart' ), 'wc_cart_empty' => array( 'woocommerce_cart_is_empty', 20, 'cart' ), 'wc_checkout_before' => array( 'woocommerce_before_checkout_form', 20, 'checkout' ), 'wc_checkout_after' => array( 'woocommerce_after_checkout_form', 20, 'checkout' ), 'wc_thankyou' => array( 'woocommerce_thankyou', 20, 'order' ), ); } /** * Is this a WooCommerce placement? * * @param string $placement Placement key. * @return bool */ public static function is_woo_placement( $placement ) { return 0 === strpos( (string) $placement, 'wc_' ); } /** * Bind active Displays to their WooCommerce hooks. */ public function attach() { if ( is_admin() ) { return; } $map = self::hook_map(); foreach ( SRPP_Display::active() as $display ) { $placement = $display->opt( 'placement' ); if ( ! isset( $map[ $placement ] ) ) { continue; } list( $hook, $priority, $context ) = $map[ $placement ]; $post_id = ( 'singular' === $context ) ? get_queried_object_id() : 0; if ( ! SRPP_Conditions::passes( $display, $post_id, $context ) ) { continue; } $id = $display->id; add_action( $hook, function () use ( $id, $context, $placement ) { SRPP_Woo::output( $id, $context, $placement ); }, $priority ); } } /** * Render one Display on a WooCommerce hook. * * @param int $display_id Display ID. * @param string $context Context. * @param string $placement Placement key. */ public static function output( $display_id, $context, $placement ) { $display = SRPP_Display::get( $display_id ); if ( ! $display ) { return; } $args = array( 'placement' => $placement ); if ( 'singular' === $context ) { $args['post_id'] = get_queried_object_id(); } else { $args['post_id'] = 0; $args['ids'] = self::context_ids( $display, $context ); if ( empty( $args['ids'] ) && 'shop' !== $context ) { return; } } if ( 'shop' === $context && empty( $args['ids'] ) ) { $args['ids'] = self::archive_ids( $display ); } echo srpp()->render->render( $display, $args ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * Recommendations derived from the cart / checkout / order contents. * * @param SRPP_Display $display Display. * @param string $context Context. * @return array */ public static function context_ids( $display, $context ) { $sources = array(); $owned = array(); if ( in_array( $context, array( 'cart', 'checkout' ), true ) && function_exists( 'WC' ) && WC()->cart ) { foreach ( WC()->cart->get_cart() as $item ) { if ( ! empty( $item['product_id'] ) ) { $sources[] = (int) $item['product_id']; } } $owned = $sources; } if ( 'order' === $context ) { $order_id = absint( get_query_var( 'order-received' ) ); if ( ! $order_id && isset( $GLOBALS['wp']->query_vars['order-received'] ) ) { $order_id = absint( $GLOBALS['wp']->query_vars['order-received'] ); } $order = $order_id ? wc_get_order( $order_id ) : false; if ( $order ) { foreach ( $order->get_items() as $item ) { $sources[] = (int) $item->get_product_id(); } $owned = $sources; } } $sources = array_values( array_unique( array_filter( $sources ) ) ); if ( empty( $sources ) ) { return array(); } $items = (int) $display->opt( 'items' ); $results = array(); foreach ( $sources as $source_id ) { $ids = SRPP_Engine::get_ids( $display, $source_id, array( 'items' => $items, // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- Argument for SRPP_Engine::get_ids(), not a query argument. 'exclude' => array_merge( $owned, $results ), ) ); $results = array_values( array_unique( array_merge( $results, $ids ) ) ); if ( count( $results ) >= $items ) { break; } } return array_slice( $results, 0, $items ); } /** * Recommendations for a shop / product archive page. * * @param SRPP_Display $display Display. * @return array */ public static function archive_ids( $display ) { $term = get_queried_object(); $args = array( 'post_type' => (array) $display->opt( 'target_types' ), 'post_status' => 'publish', 'posts_per_page' => (int) $display->opt( 'items' ), 'fields' => 'ids', 'no_found_rows' => true, 'orderby' => 'random' === $display->opt( 'fallback' ) ? 'rand' : 'date', ); if ( $term instanceof WP_Term ) { $args['tax_query'] = array( // phpcs:ignore WordPress.DB.SlowDBQuery array( 'taxonomy' => $term->taxonomy, 'field' => 'term_id', 'terms' => array( (int) $term->term_id ), ), ); } return get_posts( $args ); } }