

Выведем шорткодом [ avsorder ] заказы в статусе «обработка», «доставка*».
*доставка это мой кастомный статус заказа, вы можете его удалить в сниппете ‘wc-delivery’
Не забудьте включить HPOS в Woocommerce! Если Вы не перешли на HPOS, то лучше перейти, так как почти все плагины уже работают с HPOS.
/**
* Получить ID заказов пользователя по статусам (HPOS only) + кэш (12 часов)
*/
function avs_hpos_get_user_order_ids_by_statuses( $user_id, $statuses = array() ) {
if ( ! $user_id || empty( $statuses ) || ! is_array( $statuses ) ) {
return array();
}
// Проверяем HPOS
if ( ! class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' )
|| ! \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled() ) {
return array();
}
$normalized_statuses = array_values(
array_unique(
array_filter(
array_map(
static function( $status ) {
return strtolower( trim( (string) $status ) );
},
$statuses
)
)
)
);
if ( empty( $normalized_statuses ) ) {
return array();
}
sort( $normalized_statuses, SORT_STRING );
$cache_key = 'avs_hpos_order_ids_' . $user_id . '_' . md5( implode( '|', $normalized_statuses ) );
$cache_ttl = 12 * HOUR_IN_SECONDS;
// Кэш
$order_ids = get_transient( $cache_key );
if ( false !== $order_ids ) {
return $order_ids;
}
global $wpdb;
$table_orders = $wpdb->prefix . 'wc_orders';
$placeholders = implode( ', ', array_fill( 0, count( $normalized_statuses ), '%s' ) );
$query = "SELECT id
FROM {$table_orders}
WHERE type = 'shop_order'
AND status IN ({$placeholders})
AND customer_id = %d
ORDER BY date_created_gmt DESC";
$params = array_merge( $normalized_statuses, array( (int) $user_id ) );
$order_ids = $wpdb->get_col(
$wpdb->prepare( $query, $params )
);
$order_ids = (array) $order_ids;
set_transient( $cache_key, $order_ids, $cache_ttl );
return $order_ids;
}
/**
* Очистка кэша при изменении заказа
*/
function avs_hpos_clear_user_orders_cache( $order ) {
if ( is_numeric( $order ) ) {
$order = wc_get_order( $order );
}
if ( ! $order instanceof WC_Order ) return;
if ( $order->get_type() !== 'shop_order' ) return;
$user_id = (int) $order->get_customer_id();
if ( $user_id <= 0 ) return;
$statuses = array( 'wc-processing', 'wc-delivery' );
sort( $statuses, SORT_STRING );
$cache_key = 'avs_hpos_order_ids_' . $user_id . '_' . md5( implode( '|', $statuses ) );
delete_transient( $cache_key );
}
add_action( 'woocommerce_order_status_changed', function( $oid, $old, $new, $order ) {
avs_hpos_clear_user_orders_cache( $order ?: $oid );
}, 10, 4 );
/**
* Получить данные по заказу
*/
function avs_get_order_preview_data( $order_id ) {
$order = wc_get_order( $order_id );
if ( ! $order ) return false;
$items = $order->get_items( 'line_item' );
if ( empty( $items ) ) return false;
$items_arr = array_values( $items );
$first = $items_arr[0];
$product = wc_get_product( $first->get_product_id() );
$thumb = $product ? wp_get_attachment_image_url( $product->get_image_id(), 'thumbnail' ) : '';
if ( ! $thumb ) {
$thumb = wc_placeholder_img_src( 'thumbnail' );
}
$status = $order->get_status(); // processing, delivery
$status_text = '';
if ( $status === 'processing' ) $status_text = 'Собираем и упаковываем';
if ( $status === 'delivery' ) $status_text = 'Уже в пути';
return array(
'order_number' => $order->get_order_number(),
'order_url' => $order->get_view_order_url(),
'thumb' => $thumb,
'items_count' => count( $items ),
'status_text' => $status_text,
);
}
/**
* ШОРТКОД [avsorder]
*/
add_shortcode( 'avsorder', function() {
$user_id = get_current_user_id();
if ( ! $user_id ) return '';
$order_ids = avs_hpos_get_user_order_ids_by_statuses( $user_id, array( 'wc-processing', 'wc-delivery' ) );
if ( empty( $order_ids ) ) return '';
$hide_nav = count( $order_ids ) <= 1;
$html = '<div class="avs-slider-wrapper">
<div class="avs-slider">
<div class="avs-slider-track">';
foreach ( $order_ids as $oid ) {
$d = avs_get_order_preview_data( $oid );
if ( ! $d ) continue;
$badge = ($d['items_count'] > 1)
? '<span class="avs-order-badge">'.intval($d['items_count']).'</span>'
: '';
$html .= '
<div class="avs-slide">
<a class="avs-order-card" href="'.esc_url( $d['order_url'] ).'">
<div class="avs-order-img-wrap">
<img src="'.esc_url( $d['thumb'] ).'" class="avs-order-img" loading="lazy" decoding="async"/>
'.$badge.'
</div>
<div class="avs-order-info">
<div class="avs-order-status">'.esc_html( $d['status_text'] ).'</div>
<div class="avs-order-title">Заказ №'.esc_html( $d['order_number'] ).'</div>
</div>
</a>
</div>';
}
$html .= '
</div>
</div>';
if ( ! $hide_nav ) {
$html .= '
<div class="avs-slider-arrows">
<button class="avs-prev">‹</button>
<button class="avs-next">›</button>
</div>
<div class="avs-slider-dots"></div>';
}
$html .= '</div>';
return $html;
});
На страницу где выводите шорткод добавьте виджет HTML вниз страницы и вставьте:
<script>
document.addEventListener('DOMContentLoaded', function () {
const slider = document.querySelector('.avs-slider');
if (!slider) return;
const track = slider.querySelector('.avs-slider-track');
const slides = slider.querySelectorAll('.avs-slide');
if (!track || !slides.length) return;
const prev = slider.parentElement.querySelector('.avs-prev');
const next = slider.parentElement.querySelector('.avs-next');
const dots = slider.parentElement.querySelector('.avs-slider-dots');
const hasNav = slides.length > 1;
let index = 0;
if (hasNav && dots) {
dots.innerHTML = '';
slides.forEach((_, i) => {
const dot = document.createElement('span');
dot.className = 'avs-dot' + (i === 0 ? ' active' : '');
dot.dataset.index = i;
dots.appendChild(dot);
});
}
const update = () => {
track.style.transform = 'translateX(' + (-index * 100) + '%)';
if (hasNav && dots) {
dots.querySelectorAll('.avs-dot').forEach(d => d.classList.remove('active'));
const activeDot = dots.querySelector('[data-index="' + index + '"]');
if (activeDot) activeDot.classList.add('active');
}
};
if (hasNav && prev) {
prev.addEventListener('click', () => {
index = (index === 0) ? slides.length - 1 : index - 1;
update();
});
}
if (hasNav && next) {
next.addEventListener('click', () => {
index = (index === slides.length - 1) ? 0 : index + 1;
update();
});
}
if (hasNav && dots) {
dots.addEventListener('click', (e) => {
if (e.target.classList.contains('avs-dot')) {
index = parseInt(e.target.dataset.index, 10);
update();
}
});
}
update();
});
</script>Ну и css:
/* ОСНОВНОЙ СЛАЙДЕР */
.avs-slider-wrapper {
position: relative;
width: 100%;
padding: 0 24px;
box-sizing: border-box;
border:1px solid#D1D1D1;border-radius:10px;
margin:8px 0px;}
.avs-slider {position: relative;
overflow: hidden;
width: 100%;}
.avs-slider-track {display: flex;
transition: transform 0.35s ease;}
.avs-slide {min-width: 100%;}
/* КАРТОЧКИ */
.avs-order-card {display: flex;
gap: 10px;
padding:6px;
align-items: center;}
.avs-order-img-wrap{width:60px;height: 60px;position: relative;}
.avs-order-img-wrap img {width: 60px;
height: 60px;
object-fit: cover;
border-radius: 10px;
border: 1px solid #B3B3B3;}
.avs-order-info {flex: 1;}
.avs-order-title {font-size: 13px;color: #939393;font-weight: 500;line-height: 1.1em;}
.avs-order-status{font-size: 13px;color: #434343;}
.avs-order-badge {position: absolute;
bottom: -3px;
right: -3px;
width: 18px;
height: 18px;
font-size: 11px;
border-radius: 50%;
background: #fff;
border: 1px solid #7F7F7F;
display: flex;
align-items: center;
justify-content: center;}
/* СТРЕЛКИ */
.avs-slider-arrows {position: absolute;
top: 50%;
left: 0;
right: 0;}
.avs-prev,
.avs-next {
pointer-events: all;
position: absolute;
top: 50%;
transform: translateY(-50%);
width:30px;
background:transparent!important;
padding: 0!important;
cursor: pointer;
font-size:34px!important;
color:#B0B0B0!important;
display: flex;
align-items: center;
justify-content: center;}
.avs-prev { left: 0; }
.avs-next { right: 0; }
/* ТОЧКИ */
.avs-slider-dots {display: flex;
justify-content: center;
gap: 6px;
margin-top:1px;margin-bottom:4px;}
.avs-dot {width: 5px;
height: 5px;
background: #D2D2D2;
cursor: pointer;
border-radius: 50%;}
.avs-dot.active {background: #A2A2A2;}
Уточнение, кол-во в круглишке считает кол-во позиций, а не кол-во товаров в штуках, так как товары могут быть 2,5 штуки/кг.
Зачем стоит кеш 12ч в php коде?
Если у пользователя десятки заказов — запрос каждый раз будет нагружать базу. Чтобы уменьшить нагрузку, результат запроса сохраняется во временный кэш WordPress (transient) на 12 часов.
Если шорткод выводите в попапе, то js будет:
<script>
document.addEventListener("DOMContentLoaded", function () {
jQuery(document).on('elementor/popup/show', function (event, popupId) {
if (Number(popupId) !== 62858) return; // только этот попап
const popup = document.querySelector('#elementor-popup-modal-62858');
if (!popup) return;
const slider = popup.querySelector('.avs-slider');
if (!slider) return;
if (slider._avsInstance && typeof slider._avsInstance.reset === 'function') {
slider._avsInstance.reset();
return;
}
const track = slider.querySelector('.avs-slider-track');
const slides = slider.querySelectorAll('.avs-slide');
if (!track || !slides.length) return;
const prev = popup.querySelector('.avs-prev');
const next = popup.querySelector('.avs-next');
const dots = popup.querySelector('.avs-slider-dots');
const hasNav = slides.length > 1;
let index = 0;
/* создаём точки */
if (hasNav && dots && !dots.dataset.ready) {
dots.innerHTML = ""; // защита от повторного заполнения
slides.forEach((s, i) => {
const dot = document.createElement('span');
dot.className = 'avs-dot' + (i === 0 ? ' active' : '');
dot.dataset.index = i;
dots.appendChild(dot);
});
dots.dataset.ready = "1";
}
const update = () => {
track.style.transform = 'translateX(' + (-index * 100) + '%)';
if (hasNav && dots) {
dots.querySelectorAll('.avs-dot').forEach(d => d.classList.remove('active'));
const activeDot = dots.querySelector('[data-index="' + index + '"]');
if (activeDot) {
activeDot.classList.add('active');
}
}
};
/* стрелки */
if (hasNav && prev) {
prev.addEventListener('click', () => {
index = (index === 0) ? slides.length - 1 : index - 1;
update();
});
}
if (hasNav && next) {
next.addEventListener('click', () => {
index = (index === slides.length - 1) ? 0 : index + 1;
update();
});
}
/* точки */
if (hasNav && dots) {
dots.addEventListener('click', (e) => {
if (e.target.classList.contains('avs-dot')) {
index = parseInt(e.target.dataset.index, 10);
update();
}
});
}
slider._avsInstance = {
reset() {
index = 0;
update();
}
};
update();
});
});
</script>