true,
'fio' => $u->display_name,
'phone' => get_user_meta( $uid, 'billing_phone', true ),
'email' => $u->user_email,
'company_name' => get_user_meta( $uid, 'company_name', true ),
'inn' => get_user_meta( $uid, 'inn', true ),
'kpp' => get_user_meta( $uid, 'kpp', true ),
'legal_address' => get_user_meta( $uid, 'legal_address', true ),
'actual_address' => get_user_meta( $uid, 'actual_address', true ),
'account_type' => get_user_meta( $uid, 'account_type', true ),
));
} else {
wp_localize_script( 'main_js', 'orgstekloCheckoutUser', array(
'isLoggedIn' => false,
));
}
}
function custom_theme_setup() {
add_theme_support('post-thumbnails');
}
add_action('after_setup_theme', 'custom_theme_setup');
//woocommerce
add_action( 'after_setup_theme', 'woocommerce_support' );
function woocommerce_support() {
add_theme_support( 'woocommerce' );
}
## Определение шаблона для страницы Магазина shop.php
add_filter( 'woocommerce_template_loader_files','qfurs_add_shop_template_file', 10, 1 );
function qfurs_add_shop_template_file($default_file){
if( is_shop()){
$default_file[] = WC()->template_path() .'shop.php';
}
return $default_file;
}
function custom_product_category_template($template) {
if ( is_product_category() || ( is_search() && isset( $_GET['post_type'] ) && $_GET['post_type'] === 'product' ) ) {
$new_template = locate_template(array('woocommerce/shop.php'));
if (!empty($new_template)) {
return $new_template;
}
}
return $template;
}
add_filter('template_include', 'custom_product_category_template', 99);
//login
add_action('wp_ajax_nopriv_login_by_phone', 'handle_login_by_phone');
function handle_login_by_phone() {
$phone = isset($_POST['phone']) ? sanitize_text_field($_POST['phone']) : '';
$password = isset($_POST['password']) ? $_POST['password'] : '';
if (!$phone || !$password) {
wp_send_json_error('Поля не заполнены');
}
// Найти пользователя по мета-данным billing_phone
$users = get_users([
'meta_key' => 'billing_phone',
'meta_value' => $phone,
'number' => 1,
'fields' => ['ID']
]);
if (empty($users)) {
wp_send_json_error('Пользователь не найден');
}
$user_id = $users[0]->ID;
$user = get_user_by('id', $user_id);
if (wp_check_password($password, $user->user_pass, $user_id)) {
wp_set_current_user($user_id);
wp_set_auth_cookie($user_id, true);
wp_send_json_success();
} else {
wp_send_json_error('Неверный пароль');
}
}
//recovey
function custom_password_reset_handler() {
$email = sanitize_email($_POST['user_email']);
if (!email_exists($email)) {
wp_send_json_error(['message' => 'User not found.']);
}
$user = get_user_by('email', $email);
$new_password = wp_generate_password(12, true);
wp_set_password($new_password, $user->ID);
$subject = 'Ваш новый пароль';
$message = "Здравствуйте!\n\nВаш новый пароль: $new_password\n\nВы можете войти здесь: " . wp_login_url();
$sent = wp_mail($email, $subject, $message);
if ($sent) {
wp_send_json_success();
} else {
wp_send_json_error(['message' => 'Не удалось отправить письмо.']);
}
}
add_action('wp_ajax_custom_password_reset', 'custom_password_reset_handler');
add_action('wp_ajax_nopriv_custom_password_reset', 'custom_password_reset_handler');
//search
function custom_search_by_attribute( $search, $wp_query ) {
global $wpdb;
if ( ! empty( $wp_query->query_vars['s'] ) ) {
$attribute_search = $wpdb->esc_like( $wp_query->query_vars['s'] );
$attribute_search = '%' . $attribute_search . '%';
$args = array(
'posts_per_page' => -1,
'post_type' => 'product',
'meta_query' => array(
'relation' => 'OR',
array(
'key' => '_sku',
'value' => $attribute_search,
'compare' => 'LIKE'
),
array(
'key' => '_product_attributes',
'value' => $attribute_search,
'compare' => 'LIKE'
),
),
);
$products = get_posts( $args );
if ( $products ) {
$product_ids = wp_list_pluck( $products, 'ID' );
$search .= " OR {$wpdb->posts}.ID IN (" . implode( ',', $product_ids ) . ")";
}
}
return $search;
}
add_filter( 'posts_search', 'custom_search_by_attribute', 10, 2 );
// Переопределение текста статусов наличия
add_action('admin_head', 'hide_default_stock_status');
function hide_default_stock_status() {
$screen = get_current_screen();
if ($screen->post_type === 'product') {
echo '';
}
}
add_action('woocommerce_product_options_stock_status', 'custom_stock_status_dropdown');
function custom_stock_status_dropdown() {
woocommerce_wp_select([
'id' => '_custom_stock_status',
'label' => 'Статус наличия (ручной)',
'options' => [
'in_stock' => 'В наличии',
'low_stock' => 'Осталось мало',
'on_order' => 'Под заказ',
]
]);
}
add_action('woocommerce_process_product_meta', 'save_custom_stock_status');
function save_custom_stock_status($post_id) {
if (isset($_POST['_custom_stock_status'])) {
update_post_meta($post_id, '_custom_stock_status', sanitize_text_field($_POST['_custom_stock_status']));
}
}
// ========================================
// ГАЛОЧКИ "ПОПУЛЯРНЫЙ" И "РЕКОМЕНДУЕМЫЙ"
// ========================================
/**
* Добавляем галочки в редактирование товара (вкладка "Основные")
*/
add_action( 'woocommerce_product_options_general_product_data', 'orgsteklo_add_popular_recommended_fields' );
function orgsteklo_add_popular_recommended_fields() {
global $post;
echo '
';
// Галочка "Популярный товар"
woocommerce_wp_checkbox( array(
'id' => '_is_popular_product',
'label' => 'Популярный товар',
'description' => 'Добавляет метку "Популярный" на карточку товара',
) );
// Галочка "Рекомендуемый товар"
woocommerce_wp_checkbox( array(
'id' => '_is_recommended_product',
'label' => 'Рекомендуемый товар',
'description' => 'Товар будет показываться в блоке "Рекомендуемые товары" у других товаров этой категории',
) );
echo '
';
// Характеристики для превью карточки товара
echo '';
woocommerce_wp_text_input( array(
'id' => '_preview_char_1',
'label' => 'Характеристика 1',
'description' => 'Первая строка характеристики в карточке товара (каталог, рекомендации)',
'desc_tip' => true,
'placeholder' => 'Например: Коэффициент пропускания света 92%',
) );
woocommerce_wp_text_input( array(
'id' => '_preview_char_2',
'label' => 'Характеристика 2',
'description' => 'Вторая строка характеристики в карточке товара (каталог, рекомендации)',
'desc_tip' => true,
'placeholder' => 'Например: Устойчив к УФ-излучению',
) );
echo '
';
}
/**
* Сохраняем галочки и управляем тегом "Популярный"
*/
add_action( 'woocommerce_process_product_meta', 'orgsteklo_save_popular_recommended_fields' );
function orgsteklo_save_popular_recommended_fields( $post_id ) {
// Сохраняем "Популярный"
$is_popular = isset( $_POST['_is_popular_product'] ) ? 'yes' : 'no';
update_post_meta( $post_id, '_is_popular_product', $is_popular );
// Управляем тегом "Популярный"
$popular_tag = get_term_by( 'name', 'Популярный', 'product_tag' );
if ( $is_popular === 'yes' ) {
// Добавляем тег если его нет
if ( ! $popular_tag ) {
// Создаём тег если не существует
$new_tag = wp_insert_term( 'Популярный', 'product_tag' );
if ( ! is_wp_error( $new_tag ) ) {
wp_set_object_terms( $post_id, $new_tag['term_id'], 'product_tag', true );
}
} else {
wp_set_object_terms( $post_id, $popular_tag->term_id, 'product_tag', true );
}
} else {
// Удаляем тег
if ( $popular_tag ) {
wp_remove_object_terms( $post_id, $popular_tag->term_id, 'product_tag' );
}
}
// Сохраняем "Рекомендуемый"
$is_recommended = isset( $_POST['_is_recommended_product'] ) ? 'yes' : 'no';
update_post_meta( $post_id, '_is_recommended_product', $is_recommended );
// Сохраняем характеристики превью
if ( isset( $_POST['_preview_char_1'] ) ) {
update_post_meta( $post_id, '_preview_char_1', sanitize_text_field( $_POST['_preview_char_1'] ) );
}
if ( isset( $_POST['_preview_char_2'] ) ) {
update_post_meta( $post_id, '_preview_char_2', sanitize_text_field( $_POST['_preview_char_2'] ) );
}
}
add_filter('woocommerce_get_availability_text', 'custom_availability_text', 10, 2);
function custom_availability_text($text, $product) {
$status = get_post_meta($product->get_id(), '_custom_stock_status', true);
switch ($status) {
case 'in_stock':
return 'В наличии';
case 'low_stock':
return 'Осталось мало';
case 'on_order':
return 'Под заказ';
default:
return $text;
}
}
add_action('wp_ajax_send_sms_code', 'send_sms_code');
add_action('wp_ajax_nopriv_send_sms_code', 'send_sms_code');
function send_sms_code() {
$phone = sanitize_text_field($_POST['phone']);
if (empty($phone)) {
wp_send_json_error(['message' => 'Телефон не указан']);
}
$code = rand(1000, 9999);
set_transient('sms_verification_' . $phone, $code, 5 * MINUTE_IN_SECONDS);
$api_id = '2DA8FFB5-365F-4A68-B369-14E40F5E81C4';
$msg = "Код подтверждения: $code";
$response = wp_remote_post('https://sms.ru/sms/send', [
'body' => [
'api_id' => $api_id,
'to' => $phone,
'msg' => $msg,
'json' => 1
]
]);
if (is_wp_error($response)) {
wp_send_json_error(['message' => 'Ошибка отправки SMS']);
}
// ⚠️ в проде не стоит возвращать код, здесь — только для тестов
wp_send_json_success(['message' => 'Код отправлен', 'code' => $code]);
}
add_action('wp_ajax_check_email_exists', 'check_email_exists');
add_action('wp_ajax_nopriv_check_email_exists', 'check_email_exists');
function check_email_exists() {
$email = sanitize_email($_POST['email']);
if (!is_email($email)) {
wp_send_json_error(['message' => 'Неверный формат email']);
}
if (email_exists($email)) {
wp_send_json_error(['message' => 'Email уже зарегистрирован']);
}
wp_send_json_success(['message' => 'Email доступен']);
}
add_action('wp_ajax_custom_register_user', 'custom_register_user');
add_action('wp_ajax_nopriv_custom_register_user', 'custom_register_user');
function custom_register_user() {
$email = sanitize_email($_POST['email']);
$phone = sanitize_text_field($_POST['phone']);
$password = $_POST['password'];
$type = sanitize_text_field($_POST['type']);
if (email_exists($email)) {
wp_send_json_error(['message' => 'Email уже существует']);
}
$code = rand(1000, 9999);
set_transient('sms_verification_' . $phone, $code, 5 * MINUTE_IN_SECONDS);
// Отправка SMS через SMS.ru
$api_id = '2DA8FFB5-365F-4A68-B369-14E40F5E81C4';
$msg = "Код подтверждения: $code";
$response = wp_remote_post('https://sms.ru/sms/send', [
'body' => [
'api_id' => $api_id,
'to' => $phone,
'msg' => $msg,
'json' => 1
]
]);
wp_send_json_success(['code' => $code]);
}
add_action('wp_ajax_register_user', 'register_user');
add_action('wp_ajax_nopriv_register_user', 'register_user');
function register_user() {
$email = sanitize_email($_POST['email']);
$phone = sanitize_text_field($_POST['phone']);
$password = $_POST['password'];
$type = sanitize_text_field($_POST['registration_type']);
$entered_code = sanitize_text_field($_POST['code']);
$saved_code = get_transient('sms_verification_' . $phone);
if (!$saved_code || $saved_code != $entered_code) {
wp_send_json_error(['message' => 'Неверный или просроченный код']);
}
if (email_exists($email)) {
wp_send_json_error(['message' => 'Email уже зарегистрирован']);
}
$user_id = wp_create_user($email, $password, $email);
if (is_wp_error($user_id)) {
wp_send_json_error(['message' => 'Ошибка регистрации']);
}
$user = new WP_User($user_id);
$user->set_role('customer');
// Сохраняем телефон в billing_phone (WooCommerce)
update_user_meta($user_id, 'billing_phone', $phone);
// Сохраняем тип аккаунта
update_user_meta($user_id, 'account_type', $type);
delete_transient('sms_verification_' . $phone);
wp_set_current_user($user_id);
wp_set_auth_cookie($user_id);
wp_send_json_success(['message' => 'Пользователь зарегистрирован']);
}
add_action('wp_ajax_resend_sms_code', 'resend_sms_code');
add_action('wp_ajax_nopriv_resend_sms_code', 'resend_sms_code');
function resend_sms_code() {
$phone = sanitize_text_field($_POST['phone']);
$code = rand(1000, 9999);
set_transient('sms_verification_' . $phone, $code, 5 * MINUTE_IN_SECONDS);
$api_id = '2DA8FFB5-365F-4A68-B369-14E40F5E81C4';
$msg = "Код подтверждения: $code";
$response = wp_remote_post('https://sms.ru/sms/send', [
'body' => [
'api_id' => $api_id,
'to' => $phone,
'msg' => $msg,
'json' => 1
]
]);
wp_send_json_success(['code' => $code]);
}
add_action('show_user_profile', 'add_account_type_field_admin');
add_action('edit_user_profile', 'add_account_type_field_admin');
function add_account_type_field_admin($user) {
$account_type = get_user_meta($user->ID, 'account_type', true);
?>
Дополнительная информация
'Не авторизован']);
}
$name = sanitize_text_field($_POST['name']);
$phone = sanitize_text_field($_POST['phone']);
$email = sanitize_email($_POST['email']);
// Обновим имя
wp_update_user([
'ID' => $user_id,
'display_name' => $name
]);
// Обновим номер телефона в billing_phone (WooCommerce)
update_user_meta($user_id, 'billing_phone', $phone);
wp_send_json_success();
});
add_action('wp_ajax_save_user_profile_data2', function () {
$user_id = get_current_user_id();
if (!$user_id) {
wp_send_json_error(['message' => 'Не авторизован']);
}
// Получаем данные
$name = sanitize_text_field($_POST['name']);
$phone = sanitize_text_field($_POST['phone']);
$email = sanitize_email($_POST['email']);
$company_name = sanitize_text_field($_POST['company_name']);
$inn = sanitize_text_field($_POST['inn']);
$kpp = sanitize_text_field($_POST['kpp']);
$legal_address = sanitize_text_field($_POST['legal_address']);
$actual_address = sanitize_text_field($_POST['actual_address']);
// Обновляем пользователя
$user_update = wp_update_user([
'ID' => $user_id,
'display_name' => $name,
'user_email' => $email,
]);
if (is_wp_error($user_update)) {
wp_send_json_error(['message' => $user_update->get_error_message()]);
}
update_user_meta($user_id, 'billing_phone', $phone);
update_user_meta($user_id, 'company_name', $company_name);
update_user_meta($user_id, 'inn', $inn);
update_user_meta($user_id, 'kpp', $kpp);
update_user_meta($user_id, 'legal_address', $legal_address);
update_user_meta($user_id, 'actual_address', $actual_address);
wp_send_json_success(['message' => 'Данные успешно сохранены']);
});
add_action('wp_ajax_send_email_verification_link', function () {
$user_id = get_current_user_id();
if (!$user_id) {
wp_send_json_error(['message' => 'Вы не авторизованы']);
}
$user = wp_get_current_user();
$email = $user->user_email;
$token = bin2hex(random_bytes(16));
update_user_meta($user_id, 'email_verification_token', $token);
$link = add_query_arg([
'verify_email' => 1,
'user_id' => $user_id,
'token' => $token
], site_url());
wp_mail($email, 'Подтверждение Email', "Подтвердите email, перейдя по ссылке: $link");
wp_send_json_success(['message' => 'Письмо отправлено']);
});
add_action('init', function () {
if (
isset($_GET['verify_email']) &&
$_GET['verify_email'] == 1 &&
isset($_GET['user_id']) &&
isset($_GET['token'])
) {
$user_id = intval($_GET['user_id']);
$token = sanitize_text_field($_GET['token']);
$saved_token = get_user_meta($user_id, 'email_verification_token', true);
if ($token === $saved_token) {
update_user_meta($user_id, 'email_verified', 1);
delete_user_meta($user_id, 'email_verification_token');
wp_redirect(home_url('/?email_verified=1'));
exit;
} else {
wp_die('Неверная или просроченная ссылка.');
}
}
});
add_filter('manage_users_columns', function ($columns) {
$columns['email_verified'] = 'Email подтвержден';
return $columns;
});
add_filter('manage_users_custom_column', function ($value, $column_name, $user_id) {
if ($column_name === 'email_verified') {
return get_user_meta($user_id, 'email_verified', true) === '1' ? '✅ Да' : '❌ Нет';
}
return $value;
}, 10, 3);
add_action('wp_ajax_check_user_password', 'check_user_password_callback');
function check_user_password_callback() {
if (!is_user_logged_in()) {
wp_send_json_error();
}
$user = wp_get_current_user();
$password = $_POST['password'];
if (wp_check_password($password, $user->data->user_pass, $user->ID)) {
wp_send_json_success();
} else {
wp_send_json_error();
}
}
add_action('wp_ajax_change_user_password', 'change_user_password_callback');
function change_user_password_callback() {
if (!is_user_logged_in()) {
wp_send_json_error(['message' => 'Вы не авторизованы']);
}
$user = wp_get_current_user();
$old_password = $_POST['old_password'];
$new_password = $_POST['new_password'];
if (!wp_check_password($old_password, $user->data->user_pass, $user->ID)) {
wp_send_json_error(['message' => 'Старый пароль неверен']);
}
wp_set_password($new_password, $user->ID);
wp_send_json_success();
}
function find_variation_id_by_attribute($product_id, $attribute_name, $attribute_value) {
$product = wc_get_product($product_id);
if (!$product || !$product->is_type('variable')) {
return false;
}
$available_variations = $product->get_available_variations();
foreach ($available_variations as $variation) {
$attributes = $variation['attributes'];
if (
isset($attributes['attribute_' . $attribute_name]) &&
$attributes['attribute_' . $attribute_name] === $attribute_value
) {
return $variation['variation_id'];
}
}
return false;
}
add_action('wp_ajax_add_variation_by_attribute', 'add_variation_by_attribute');
add_action('wp_ajax_nopriv_add_variation_by_attribute', 'add_variation_by_attribute');
function add_variation_by_attribute() {
try {
$product_id = isset($_POST['product_id']) ? absint($_POST['product_id']) : 0;
$attribute_name = isset($_POST['attribute_name']) ? wc_clean($_POST['attribute_name']) : '';
$attribute_value = isset($_POST['attribute_value']) ? sanitize_text_field(urldecode($_POST['attribute_value'])) : '';
$quantity = isset($_POST['quantity']) ? absint($_POST['quantity']) : 1;
$custom_price = isset($_POST['custom_price']) ? floatval($_POST['custom_price']) : 0;
if (!$product_id || !$attribute_name || !$attribute_value) {
throw new Exception('Не указаны обязательные параметры');
}
$variation_id = find_variation_id_by_attribute($product_id, $attribute_name, $attribute_value);
if (!$variation_id) {
throw new Exception('Вариация с указанными параметрами не найдена');
}
$variation_product = wc_get_product($variation_id);
if (!$variation_product) {
throw new Exception('Вариация не найдена');
}
if (!$variation_product->is_purchasable()) {
throw new Exception('Эта вариация недоступна для покупки');
}
if (!$variation_product->is_in_stock()) {
throw new Exception('Эта вариация нет в наличии');
}
// Автоматически соберём все атрибуты вариации
$variation_attributes = $variation_product->get_attributes();
$attributes = array();
foreach ($variation_attributes as $key => $value) {
$attributes['attribute_' . $key] = $value;
}
// Передаём кастомную цену из карточки товара
$cart_item_data = array();
if ($custom_price > 0) {
$cart_item_data['custom_display_price'] = $custom_price;
}
$cart_item_key = WC()->cart->add_to_cart(
$product_id,
$quantity,
$variation_id,
$attributes,
$cart_item_data
);
if (!$cart_item_key) {
throw new Exception('Не удалось добавить товар в корзину');
}
$data = array(
'success' => true,
'added' => true,
'message' => 'Товар успешно добавлен в корзину',
'cart_item_count' => WC()->cart->get_cart_contents_count(),
'fragments' => apply_filters('woocommerce_add_to_cart_fragments', array()),
'cart_hash' => WC()->cart->get_cart_hash()
);
wp_send_json($data);
} catch (Exception $e) {
wp_send_json_error($e->getMessage());
}
}
// Сохраняем кастомную цену в сессии корзины
add_filter('woocommerce_add_cart_item_data', 'save_custom_price_to_cart_item', 10, 3);
function save_custom_price_to_cart_item($cart_item_data, $product_id, $variation_id) {
if (isset($cart_item_data['custom_display_price'])) {
$cart_item_data['custom_display_price'] = floatval($cart_item_data['custom_display_price']);
}
return $cart_item_data;
}
// Восстанавливаем кастомную цену из сессии
add_filter('woocommerce_get_cart_item_from_session', 'restore_custom_price_from_session', 10, 3);
function restore_custom_price_from_session($cart_item, $values, $key) {
if (isset($values['custom_display_price'])) {
$cart_item['custom_display_price'] = $values['custom_display_price'];
}
return $cart_item;
}
// Применяем кастомную цену при расчете корзины
add_action('woocommerce_before_calculate_totals', 'apply_custom_display_price', 20, 1);
function apply_custom_display_price($cart) {
if (is_admin() && !defined('DOING_AJAX')) {
return;
}
foreach ($cart->get_cart() as $cart_item_key => $cart_item) {
if (isset($cart_item['custom_display_price']) && $cart_item['custom_display_price'] > 0) {
$cart_item['data']->set_price($cart_item['custom_display_price']);
}
}
}
add_action('wp_ajax_get_cart_contents', 'custom_get_cart_contents');
add_action('wp_ajax_nopriv_get_cart_contents', 'custom_get_cart_contents');
function custom_get_cart_contents() {
$items = [];
$regular_total = 0;
$sale_total = 0;
foreach (WC()->cart->get_cart() as $cart_item) {
$product = $cart_item['data'];
$variation_id = $cart_item['variation_id'];
$quantity = (int) $cart_item['quantity'];
// Use same logic as cart.php - get_regular_price and get_sale_price
$regular_price = $product->get_regular_price();
$sale_price = $product->get_sale_price();
if ( $sale_price === '' ) {
$sale_price = $regular_price;
}
$line_regular = $regular_price * $quantity;
$line_sale = $sale_price * $quantity;
$regular_total += $line_regular;
$sale_total += $line_sale;
$line_subtotal = number_format( $line_sale, 0, '', ' ' ) . ' ₽';
$old_price = '';
if ( $line_regular > $line_sale ) {
$old_price = number_format( $line_regular, 0, '', ' ' ) . ' ₽';
}
// Определяем единицу измерения
$unit = 'ШТ';
if ( isset( $cart_item['orgsteklo_calculator'] ) ) {
$unit = 'ЛИСТ';
}
// Получаем название товара без вариаций
$product_name = $product->get_name();
if ( $product->is_type('variation') ) {
$parent_product = wc_get_product( $cart_item['product_id'] );
if ( $parent_product ) {
$product_name = $parent_product->get_name();
}
}
$items[] = [
'id' => (int) $cart_item['product_id'],
'variation_id' => (int) $variation_id,
'name' => $product_name,
'sku' => $product->get_sku(),
'price' => $line_subtotal,
'oldPrice' => $old_price,
'quantity' => $quantity,
'unit' => $unit,
'image' => get_the_post_thumbnail_url($cart_item['product_id'], 'thumbnail'),
'url' => get_permalink($cart_item['product_id']),
];
}
$count = WC()->cart->get_cart_contents_count();
// Применяем скидку от суммы заказа (порог проверяется по сумме СО скидками на товары)
$cart_discount = orgsteklo_calculate_cart_discount( $sale_total );
$final_total = $sale_total - $cart_discount['amount'];
$old_total = '';
if ($regular_total > $final_total) {
$old_total = number_format($regular_total, 0, '', ' ') . ' ₽';
}
wp_send_json([
'items' => $items,
'total' => number_format($final_total, 0, '', ' ') . ' ₽',
'oldTotal' => $old_total,
'count' => (int) $count
]);
}
add_action('wp_ajax_remove_from_cart', 'custom_remove_from_cart');
add_action('wp_ajax_nopriv_remove_from_cart', 'custom_remove_from_cart');
function custom_remove_from_cart() {
$product_id = absint($_POST['product_id']);
foreach (WC()->cart->get_cart() as $cart_item_key => $cart_item) {
if ($cart_item['product_id'] == $product_id) {
WC()->cart->remove_cart_item($cart_item_key);
break;
}
}
wp_send_json(['removed' => true, 'total' => WC()->cart->get_total()]);
}
add_action('wp_ajax_update_cart_quantity', 'custom_update_cart_quantity');
add_action('wp_ajax_nopriv_update_cart_quantity', 'custom_update_cart_quantity');
function custom_update_cart_quantity() {
$product_id = absint($_POST['product_id']);
$quantity = isset($_POST['quantity']) ? absint($_POST['quantity']) : 1;
// Найдем ключ товара в корзине
$cart = WC()->cart->get_cart();
$updated = false;
foreach ($cart as $cart_item_key => $cart_item) {
if ($cart_item['product_id'] == $product_id) {
// Обновим количество
WC()->cart->set_quantity($cart_item_key, $quantity, true);
$updated = true;
break;
}
}
wp_send_json([
'updated' => $updated
]);
}
add_action('template_redirect', function() {
// Не обрабатываем обновление количества, если это запрос на удаление выбранных товаров
if (isset($_POST['delete_selected']) && $_POST['delete_selected'] == '1') {
return;
}
if (isset($_POST['update_checkout_cart']) && !empty($_POST['cart'])) {
foreach ($_POST['cart'] as $cart_item_key => $values) {
if (isset($values['qty'])) {
WC()->cart->set_quantity($cart_item_key, (int) $values['qty'], true);
}
}
WC()->cart->calculate_totals(); // Пересчет корзины
wp_safe_redirect(wc_get_cart_url()); // Обновляем страницу
exit;
}
});
add_action('template_redirect', function() {
if ( isset($_POST['delete_selected']) && $_POST['delete_selected'] == '1' && !empty($_POST['cart_product_keys']) && is_array($_POST['cart_product_keys']) ) {
foreach ($_POST['cart_product_keys'] as $cart_item_key) {
WC()->cart->remove_cart_item($cart_item_key);
}
wc_clear_notices(); // Чистим сообщения об ошибках/успехе
// Редирект на страницу корзины для обновления состояния
wp_safe_redirect(wc_get_cart_url());
exit;
}
});
add_filter( 'woocommerce_checkout_fields', 'custom_checkout_fields' );
function custom_checkout_fields( $fields ) {
// Billing fields
$fields['billing'] = array(
'billing_country' => array(
'type' => 'hidden',
'default' => 'RU',
'required' => false,
'priority' => 1,
),
'billing_email' => array(
'label' => __('Email', 'woocommerce'),
'required' => true,
'class' => array('form-row-wide'),
'priority' => 10,
),
'billing_phone' => array(
'label' => __('Номер телефона', 'woocommerce'),
'required' => true,
'class' => array('form-row-wide'),
'priority' => 20,
),
'billing_first_name' => array(
'label' => __('ФИО', 'woocommerce'),
'required' => true,
'class' => array('form-row-wide'),
'priority' => 30,
),
);
// Shipping fields — необязательные, т.к. при самовывозе адрес не нужен
$fields['shipping'] = array(
'shipping_address_1' => array(
'label' => __('Адрес доставки', 'woocommerce'),
'required' => false,
'class' => array('form-row-wide'),
'priority' => 10,
'placeholder' => __('Улица, дом, квартира', 'woocommerce'),
),
);
return $fields;
}
/**
* Принудительно устанавливаем страну RU для всех клиентов,
* чтобы WooCommerce всегда показывал способы доставки.
*/
add_action( 'woocommerce_before_checkout_form', 'orgsteklo_set_default_country', 5 );
function orgsteklo_set_default_country() {
if ( WC()->customer && ! WC()->customer->get_billing_country() ) {
WC()->customer->set_billing_country( 'RU' );
WC()->customer->set_shipping_country( 'RU' );
}
}
add_action( 'woocommerce_checkout_create_order', 'save_custom_shipping_field_to_order', 20, 2 );
function save_custom_shipping_field_to_order( $order, $data ) {
if ( isset( $_POST['shipping_address_1'] ) ) {
$order->set_shipping_address_1( sanitize_text_field( $_POST['shipping_address_1'] ) );
}
}
// Сохраняем доп. поля заказа (тип лица, ИНН, КПП, адреса юр. лица)
add_action( 'woocommerce_checkout_create_order', 'orgsteklo_save_custom_billing_fields', 30, 2 );
function orgsteklo_save_custom_billing_fields( $order, $data ) {
$fields = array(
'billing_person_type' => '_billing_person_type',
'billing_company_name_val' => '_billing_company_name',
'billing_inn_val' => '_billing_inn',
'billing_kpp_val' => '_billing_kpp',
'billing_legal_address_val'=> '_billing_legal_address',
'billing_actual_address_val'=> '_billing_actual_address',
'shipping_km_mkad' => '_shipping_km_mkad',
);
foreach ( $fields as $post_key => $meta_key ) {
if ( isset( $_POST[ $post_key ] ) && $_POST[ $post_key ] !== '' ) {
$order->update_meta_data( $meta_key, sanitize_text_field( $_POST[ $post_key ] ) );
}
}
}
add_action('wp_ajax_load_products', 'custom_ajax_load_products');
add_action('wp_ajax_nopriv_load_products', 'custom_ajax_load_products');
function custom_ajax_load_products() {
$paged = isset($_POST['page']) ? intval($_POST['page']) : 1;
$posts_per_page = isset($_POST['per_page']) ? intval($_POST['per_page']) : 12;
$args = array(
'post_type' => 'product',
'post_status' => array( 'publish', 'draft' ),
'paged' => $paged,
'posts_per_page' => $posts_per_page,
);
$query = new WP_Query($args);
$total = $query->found_posts;
$total_pages = $query->max_num_pages;
if ($query->have_posts()) {
ob_start();
echo '';
while ($query->have_posts()) {
$query->the_post();
wc_get_template_part('content', 'product');
}
echo '
';
// === ПАГИНАЦИЯ ===
echo '';
// Показ количества
echo '
';
echo '
Количество товаров на странице:
';
// (Можно оставить HTML как есть — JS сам обновляет активную кнопку)
echo '
';
echo '
';
wp_reset_postdata();
echo ob_get_clean();
} else {
echo 'Нет товаров.
';
}
wp_die();
}
// Добавляем поле "Длина" в вариации
add_action( 'woocommerce_variation_options_pricing', 'add_custom_length_field_to_variations', 10, 3 );
function add_custom_length_field_to_variations( $loop, $variation_data, $variation ) {
woocommerce_wp_text_input( array(
'id' => 'variation_length[' . $loop . ']',
'class' => 'short',
'label' => __('Длина', 'woocommerce') . ' (мм)',
'value' => get_post_meta( $variation->ID, '_variation_length', true )
) );
}
// Добавляем поле "Ширина" в вариации
add_action( 'woocommerce_variation_options_pricing', 'add_custom_width_field_to_variations', 10, 3 );
function add_custom_width_field_to_variations( $loop, $variation_data, $variation ) {
woocommerce_wp_text_input( array(
'id' => 'variation_width[' . $loop . ']',
'class' => 'short',
'label' => __('Ширина', 'woocommerce') . ' (мм)',
'value' => get_post_meta( $variation->ID, '_variation_width', true )
) );
}
// Сохраняем значение "Длина"
add_action( 'woocommerce_save_product_variation', 'save_custom_length_field_variations', 10, 2 );
function save_custom_length_field_variations( $variation_id, $i ) {
if ( isset( $_POST['variation_length'][$i] ) ) {
update_post_meta( $variation_id, '_variation_length', sanitize_text_field( $_POST['variation_length'][$i] ) );
}
}
// Сохраняем значение "Ширина"
add_action( 'woocommerce_save_product_variation', 'save_custom_width_field_variations', 10, 2 );
function save_custom_width_field_variations( $variation_id, $i ) {
if ( isset( $_POST['variation_width'][$i] ) ) {
update_post_meta( $variation_id, '_variation_width', sanitize_text_field( $_POST['variation_width'][$i] ) );
}
}
// Добавляем мета-данные в объект вариации
add_filter( 'woocommerce_available_variation', 'add_length_to_available_variations', 10, 3 );
function add_length_to_available_variations( $variation, $product, $variation_obj ) {
// Сначала проверяем кастомное поле
$length = get_post_meta( $variation['variation_id'], '_variation_length', true );
// Если кастомное пустое, берём стандартное поле WooCommerce
if ( empty( $length ) && $variation_obj ) {
$length = $variation_obj->get_length();
}
if ( $length ) {
$variation['length'] = $length;
}
return $variation;
}
// Добавляем ширину в объект вариации
add_filter( 'woocommerce_available_variation', 'add_width_to_available_variations', 10, 3 );
function add_width_to_available_variations( $variation, $product, $variation_obj ) {
// Сначала проверяем кастомное поле
$width = get_post_meta( $variation['variation_id'], '_variation_width', true );
// Если кастомное пустое, берём стандартное поле WooCommerce
if ( empty( $width ) && $variation_obj ) {
$width = $variation_obj->get_width();
}
if ( $width ) {
$variation['width'] = $width;
}
return $variation;
}
// Добавляем вес в данные вариации для отображения в шаблоне
add_filter( 'woocommerce_available_variation', 'add_weight_to_available_variations', 20 );
function add_weight_to_available_variations( $variation ) {
$variation_obj = wc_get_product( $variation['variation_id'] );
if ( $variation_obj ) {
$weight_raw = $variation_obj->get_weight();
if ( $weight_raw !== '' ) {
$weight_num = (float) wc_format_decimal( $weight_raw );
$variation['weight_formatted'] = number_format( $weight_num, 3, '.', '' ) . ' кг';
} else {
$variation['weight_formatted'] = '';
}
}
return $variation;
}
// Добавляем точные цены (как в корзине) в данные вариации
add_filter( 'woocommerce_available_variation', 'add_raw_prices_to_variations', 25 );
function add_raw_prices_to_variations( $variation ) {
$variation_obj = wc_get_product( $variation['variation_id'] );
if ( $variation_obj ) {
// Точные цены как их использует корзина, округлённые до целых рублей
$variation['raw_price'] = round( (float) $variation_obj->get_price() );
$variation['raw_regular_price'] = round( (float) $variation_obj->get_regular_price() );
$sale_price = $variation_obj->get_sale_price();
$variation['raw_sale_price'] = $sale_price !== '' ? round( (float) $sale_price ) : null;
}
return $variation;
}
// Показываем дополнительные поля в админке на странице редактирования пользователя
add_action('show_user_profile', 'add_custom_user_fields_admin');
add_action('edit_user_profile', 'add_custom_user_fields_admin');
function add_custom_user_fields_admin($user) {
?>
Юридическая информация
10, "-5%" => 5, "10%" => 10, "10 %" => 10
* @return int|false процент скидки или false если не найден
*/
function get_discount_from_tag_name( $tag_name ) {
// Убираем пробелы по краям
$tag_name = trim( $tag_name );
// Ищем число с опциональным минусом и символом процента (с пробелом или без)
// Поддерживает: "-10%", "10%", "-10 %", "10 %", "- 10%", и т.д.
if ( preg_match( '/^-?\s*(\d+)\s*%/', $tag_name, $matches ) ) {
return absint( $matches[1] );
}
return false;
}
/**
* Получает процент скидки для товара из его меток (тегов)
* @param int $product_id ID товара
* @return int|false процент скидки или false
*/
function get_product_discount_percent( $product_id ) {
$tags = get_the_terms( $product_id, 'product_tag' );
if ( ! $tags || is_wp_error( $tags ) ) {
return false;
}
foreach ( $tags as $tag ) {
$discount = get_discount_from_tag_name( $tag->name );
if ( $discount !== false ) {
return $discount;
}
}
return false;
}
/**
* Рассчитывает цену со скидкой на основе процента
* @param float $price исходная цена
* @param int $discount_percent процент скидки
* @return float цена со скидкой
*/
function calculate_discounted_price( $price, $discount_percent ) {
// Округляем до целых рублей для консистентности между PHP и JS
return round( $price * ( 100 - $discount_percent ) / 100 );
}
/**
* Рассчитывает процент скидки между двумя ценами
* @param float $regular_price обычная цена
* @param float $sale_price акционная цена
* @return int процент скидки (округленный)
*/
function calculate_discount_percentage( $regular_price, $sale_price ) {
if ( $regular_price <= 0 || $sale_price <= 0 || $sale_price >= $regular_price ) {
return 0;
}
return round( ( ( $regular_price - $sale_price ) / $regular_price ) * 100 );
}
/**
* Получает отсортированные метки (теги) товара
* Сначала идут метки статуса (без дефиса и процента), затем метки со скидками (с "-" и "%")
* @param int $product_id ID товара
* @return array массив объектов WP_Term, отсортированных
*/
function get_sorted_product_tags( $product_id ) {
$tags = get_the_terms( $product_id, 'product_tag' );
if ( ! $tags || is_wp_error( $tags ) ) {
return [];
}
$status_tags = [];
$discount_tags = [];
foreach ( $tags as $tag ) {
// Если метка содержит "-" и "%", это скидка
if ( get_discount_from_tag_name( $tag->name ) !== false ) {
$discount_tags[] = $tag;
} else {
$status_tags[] = $tag;
}
}
// Возвращаем: сначала статусные, потом скидки
return array_merge( $status_tags, $discount_tags );
}
/**
* Проверяет, является ли метка скидкой (содержит "-" и "%")
* @param string $tag_name название метки
* @return bool
*/
function is_discount_tag( $tag_name ) {
return get_discount_from_tag_name( $tag_name ) !== false;
}
// ========================================
// АВТОМАТИЧЕСКИЙ РАСЧЁТ СКИДОК ПО МЕТКАМ
// ========================================
/**
* Автоматически рассчитывает sale price для простого товара на основе метки со скидкой
* Хук срабатывает при получении цены со скидкой
*/
add_filter( 'woocommerce_product_get_sale_price', 'auto_calculate_sale_price_from_tag', 10, 2 );
add_filter( 'woocommerce_product_get_price', 'auto_apply_discount_to_price', 10, 2 );
function auto_calculate_sale_price_from_tag( $sale_price, $product ) {
// ПРИОРИТЕТ: Если вручную установлена sale price - используем её
// Читаем напрямую из мета, чтобы избежать рекурсии
$manual_sale_price = get_post_meta( $product->get_id(), '_sale_price', true );
if ( $manual_sale_price !== '' && $manual_sale_price !== null && $manual_sale_price !== false ) {
return $manual_sale_price; // Вручную установленная цена имеет приоритет
}
// Получаем процент скидки из меток товара
$discount_percent = get_product_discount_percent( $product->get_id() );
if ( $discount_percent === false ) {
return $sale_price; // Нет метки со скидкой - возвращаем как есть
}
// Если скидка есть, рассчитываем sale price от regular price
$regular_price = $product->get_regular_price();
if ( empty( $regular_price ) || $regular_price <= 0 ) {
return $sale_price; // Нет обычной цены - не можем рассчитать
}
// Рассчитываем цену со скидкой
$calculated_sale = calculate_discounted_price( $regular_price, $discount_percent );
return $calculated_sale;
}
function auto_apply_discount_to_price( $price, $product ) {
// Применяем скидку к финальной цене
$sale_price = $product->get_sale_price();
if ( $sale_price !== '' && $sale_price !== null ) {
return $sale_price;
}
return $price;
}
/**
* Автоматически рассчитывает sale price для вариаций на основе метки родителя или самой вариации
*/
add_filter( 'woocommerce_product_variation_get_sale_price', 'auto_calculate_variation_sale_price_from_tag', 10, 2 );
add_filter( 'woocommerce_product_variation_get_price', 'auto_apply_variation_discount_to_price', 10, 2 );
function auto_calculate_variation_sale_price_from_tag( $sale_price, $variation ) {
// ПРИОРИТЕТ: Если вручную установлена sale price на вариации - используем её
$manual_sale_price = get_post_meta( $variation->get_id(), '_sale_price', true );
if ( $manual_sale_price !== '' && $manual_sale_price !== null && $manual_sale_price !== false ) {
return $manual_sale_price; // Вручную установленная цена имеет приоритет
}
// Сначала проверяем метку на самой вариации (если есть)
$discount_percent = get_product_discount_percent( $variation->get_id() );
// Если на вариации нет метки, проверяем родительский товар
if ( $discount_percent === false ) {
$parent_id = $variation->get_parent_id();
if ( $parent_id ) {
$discount_percent = get_product_discount_percent( $parent_id );
}
}
if ( $discount_percent === false ) {
return $sale_price; // Нет метки со скидкой
}
// Рассчитываем sale price от regular price вариации
$regular_price = $variation->get_regular_price();
if ( empty( $regular_price ) || $regular_price <= 0 ) {
return $sale_price;
}
$calculated_sale = calculate_discounted_price( $regular_price, $discount_percent );
return $calculated_sale;
}
function auto_apply_variation_discount_to_price( $price, $variation ) {
$sale_price = $variation->get_sale_price();
if ( $sale_price !== '' && $sale_price !== null ) {
return $sale_price;
}
return $price;
}
/**
* Помечаем товар как "on sale" если есть метка со скидкой
*/
add_filter( 'woocommerce_product_is_on_sale', 'mark_product_on_sale_if_has_discount_tag', 10, 2 );
function mark_product_on_sale_if_has_discount_tag( $on_sale, $product ) {
// Проверяем есть ли метка со скидкой
$discount_percent = get_product_discount_percent( $product->get_id() );
if ( $discount_percent !== false ) {
return true; // Есть метка со скидкой = товар на распродаже
}
// Для вариативных товаров проверяем метку на родителе
if ( $product->is_type( 'variation' ) ) {
$parent_id = $product->get_parent_id();
if ( $parent_id ) {
$parent_discount = get_product_discount_percent( $parent_id );
if ( $parent_discount !== false ) {
return true;
}
}
}
return $on_sale;
}
// Глобальная сортировка вариантов по ЧИСЛАМ: по УБЫВАНИЮ (lexicographic: [x1,x2,…] desc)
add_filter('woocommerce_dropdown_variation_attribute_options_args', function ($args) {
if (empty($args['options']) || empty($args['product']) || empty($args['attribute'])) {
return $args;
}
// <= поменяйте на 'asc' если нужно по возрастанию
$direction = 'desc'; // 'desc' или 'asc'
$attribute = $args['attribute'];
$options = (array) $args['options'];
// Берём видимую метку для опции
$get_label_for_option = function($opt) use ($attribute) {
static $cache = [];
$key = $attribute . '|' . (string)$opt;
if (isset($cache[$key])) return $cache[$key];
if (taxonomy_exists($attribute)) {
$field = is_numeric($opt) ? 'id' : 'slug';
$term = get_term_by($field, $opt, $attribute);
if ($term && !is_wp_error($term)) {
return $cache[$key] = (string)$term->name;
}
}
// fallback (кастомный атрибут или слаг)
$label = (string)$opt;
$label = str_replace(['-', '_'], ' ', $label);
$label = preg_replace('~\s+~u', ' ', $label);
return $cache[$key] = trim($label);
};
// Извлекаем ВСЕ числа из строки (поддержка "1,5" как 1.5)
$extract_numbers = function(string $label): array {
$normalized = str_replace(',', '.', $label);
preg_match_all('/\d+(?:\.\d+)?/u', $normalized, $m);
return array_map('floatval', $m[0] ?? []);
};
// Подготовка данных
$rows = [];
foreach ($options as $i => $opt) {
$label = ($opt === '') ? '' : $get_label_for_option($opt);
$nums = ($opt === '') ? [] : $extract_numbers($label);
$rows[] = [
'value' => $opt,
'label' => $label,
'nums' => $nums,
'i' => $i, // исходный индекс для стабильности
];
}
// Оставим пустую опцию первой
$firstEmpty = null;
$data = [];
foreach ($rows as $r) {
if ($r['value'] === '' && $firstEmpty === null) {
$firstEmpty = $r;
continue;
}
$data[] = $r;
}
// Сортировка по кортежам чисел (лексикографически), направление: asc/desc
usort($data, function($a, $b) use ($direction) {
$na = $a['nums']; $nb = $b['nums'];
$aHas = !empty($na); $bHas = !empty($nb);
if ($aHas && !$bHas) return -1; // числовые — выше текстовых
if (!$aHas && $bHas) return 1;
if (!$aHas && !$bHas) {
$c = strnatcasecmp($a['label'], $b['label']);
return ($c !== 0) ? $c : ($a['i'] <=> $b['i']);
}
$len = max(count($na), count($nb));
for ($k = 0; $k < $len; $k++) {
$ai = $na[$k] ?? null;
$bi = $nb[$k] ?? null;
if ($ai === null && $bi === null) break;
if ($ai === null) return 1; // короче — НИЖЕ (напр. [50] после [50,10])
if ($bi === null) return -1;
if ($ai != $bi) {
// КОМБИНИРОВАННАЯ СОРТИРОВКА:
// Первое число: от меньшего к большему (ASC)
// Второе и последующие: от большего к меньшему (DESC)
if ($k === 0) {
// Первое число: ASC (5 → 6 → 10)
return ($ai < $bi) ? -1 : 1; // меньше — выше
} else {
// Второе+ число: DESC (при одинаковом первом: 7 → 6 → 4)
return ($ai < $bi) ? 1 : -1; // больше — выше
}
}
}
// Полное равенство чисел — по метке, затем по исходному индексу
$c = strnatcasecmp($a['label'], $b['label']);
return ($c !== 0) ? $c : ($a['i'] <=> $b['i']);
});
$ordered = array_map(fn($r) => $r['value'], $data);
if ($firstEmpty !== null) array_unshift($ordered, '');
$args['options'] = $ordered;
return $args;
}, 20);
// АВТОМАТИЧЕСКАЯ СОРТИРОВКА ТЕРМИНОВ АТРИБУТОВ ПО ЧИСЛАМ (ОТ БОЛЬШЕГО К МЕНЬШЕМУ)
// Применяется ВЕЗДЕ: в админке, на фронте, в вариациях
// Решает проблему: после импорта термины в правильном порядке БЕЗ ручного вмешательства
add_filter( 'get_terms', 'auto_sort_attribute_terms_by_numbers', 10, 4 );
function auto_sort_attribute_terms_by_numbers( $terms, $taxonomies, $args, $term_query ) {
// Применяем только к таксономиям атрибутов WooCommerce
if ( empty( $terms ) || is_wp_error( $terms ) ) {
return $terms;
}
// Проверяем, что это атрибут товара
$is_product_attribute = false;
foreach ( (array) $taxonomies as $taxonomy ) {
if ( strpos( $taxonomy, 'pa_' ) === 0 ) {
$is_product_attribute = true;
break;
}
}
if ( ! $is_product_attribute ) {
return $terms;
}
// Направление сортировки (как в вариациях)
$direction = 'desc'; // от большего к меньшему
// Функция извлечения чисел (ТОЧНО как в вариациях)
$extract_numbers = function( $label ) {
$normalized = str_replace( ',', '.', $label );
preg_match_all( '/\d+(?:\.\d+)?/u', $normalized, $m );
return array_map( 'floatval', $m[0] ?? [] );
};
// Подготовка данных
$data = [];
foreach ( $terms as $i => $term ) {
// Пропускаем, если термин не объект (может быть строка в некоторых случаях)
if ( ! is_object( $term ) || ! isset( $term->name ) ) {
continue;
}
$label = $term->name;
$nums = $extract_numbers( $label );
$data[] = [
'term' => $term,
'label' => $label,
'nums' => $nums,
'i' => $i,
];
}
// Сортировка по кортежам чисел (КОМБИНИРОВАННАЯ: первое число ASC, второе+ DESC)
usort( $data, function( $a, $b ) {
$na = $a['nums'];
$nb = $b['nums'];
$aHas = ! empty( $na );
$bHas = ! empty( $nb );
if ( $aHas && ! $bHas ) return -1;
if ( ! $aHas && $bHas ) return 1;
if ( ! $aHas && ! $bHas ) {
$c = strnatcasecmp( $a['label'], $b['label'] );
return ( $c !== 0 ) ? $c : ( $a['i'] <=> $b['i'] );
}
$len = max( count( $na ), count( $nb ) );
for ( $k = 0; $k < $len; $k++ ) {
$ai = $na[$k] ?? null;
$bi = $nb[$k] ?? null;
if ( $ai === null && $bi === null ) break;
if ( $ai === null ) return 1;
if ( $bi === null ) return -1;
if ( $ai != $bi ) {
// КОМБИНИРОВАННАЯ СОРТИРОВКА:
// Первое число: от меньшего к большему (ASC)
// Второе и последующие: от большего к меньшему (DESC)
if ($k === 0) {
return ($ai < $bi) ? -1 : 1; // меньше — выше
} else {
return ($ai < $bi) ? 1 : -1; // больше — выше
}
}
}
$c = strnatcasecmp( $a['label'], $b['label'] );
return ( $c !== 0 ) ? $c : ( $a['i'] <=> $b['i'] );
} );
// Возвращаем отсортированные термины
return array_map( function( $item ) {
return $item['term'];
}, $data );
}
// СОХРАНЕНИЕ ПОЛЬЗОВАТЕЛЬСКОГО ПОРЯДКА АТРИБУТОВ ПРИ ОБНОВЛЕНИИ ТОВАРА
// Решает проблему: порядок самих атрибутов (не терминов!) сбрасывается
add_action( 'woocommerce_process_product_meta', 'preserve_attribute_order_on_save', 20 );
function preserve_attribute_order_on_save( $product_id ) {
// Получаем текущие атрибуты из POST (если они были отправлены)
if ( ! isset( $_POST['attribute_names'] ) || ! is_array( $_POST['attribute_names'] ) ) {
return;
}
// Получаем существующие атрибуты товара
$product_attributes = get_post_meta( $product_id, '_product_attributes', true );
if ( ! is_array( $product_attributes ) ) {
return;
}
// Обновляем позиции атрибутов согласно порядку из формы
$position = 0;
foreach ( $_POST['attribute_names'] as $attribute_name ) {
if ( isset( $product_attributes[ $attribute_name ] ) ) {
$product_attributes[ $attribute_name ]['position'] = $position;
$position++;
}
}
// Сохраняем обновлённые атрибуты напрямую в meta (без вызова save)
update_post_meta( $product_id, '_product_attributes', $product_attributes );
}
// ПЕРЕИМЕНОВАНИЕ АТРИБУТА "ВЕС" В "ВЕС 1 ШТ" ДЛЯ ВАРИАТИВНЫХ ТОВАРОВ
// Применяется только на странице товара с вариациями
add_filter( 'woocommerce_attribute_label', 'rename_weight_attribute_for_variations', 10, 3 );
function rename_weight_attribute_for_variations( $label, $name, $product ) {
// Проверяем что это атрибут "Вес"
// Проверяем и slug (pa_ves) и оригинальное название (Вес)
if (
$name === 'pa_ves' ||
$name === 'Вес' ||
$label === 'Вес' ||
strpos( $name, 'ves' ) !== false
) {
// Если есть product и это вариативный товар
if ( $product && is_object( $product ) && $product->is_type( 'variable' ) ) {
return 'Вес 1 шт';
}
// Если мы на странице вариативного товара (даже если $product не передан)
if ( is_product() ) {
global $post;
if ( $post ) {
$current_product = wc_get_product( $post->ID );
if ( $current_product && $current_product->is_type( 'variable' ) ) {
return 'Вес 1 шт';
}
}
}
}
return $label;
}
// Убираем дублирование "Вес 1 шт" у клея и труб в корзине
add_filter( 'woocommerce_get_item_data', 'remove_duplicate_weight_from_cart', 10, 2 );
function remove_duplicate_weight_from_cart( $item_data, $cart_item ) {
// Проверяем что это вариативный товар
if ( ! isset( $cart_item['variation_id'] ) || empty( $cart_item['variation_id'] ) ) {
return $item_data;
}
// Получаем product_id родительского товара
$product_id = $cart_item['product_id'];
$product_template = get_field('product_template', $product_id);
// Если это клей или трубы, убираем атрибут веса из отображения
if ( in_array( $product_template, array( 'Клей', 'Трубы' ) ) ) {
// Фильтруем массив, убирая элементы с ключом содержащим "вес" или "Вес"
$item_data = array_filter( $item_data, function( $data ) {
$key = isset( $data['key'] ) ? $data['key'] : ( isset( $data['name'] ) ? $data['name'] : '' );
// Убираем если ключ содержит "вес" в любом регистре
return stripos( $key, 'вес' ) === false;
});
}
return $item_data;
}
// Подключаем админ-панель для калькулятора цен на оргстекло
require_once get_template_directory() . '/inc/price-calculator-admin.php';
// Подключаем функции расчета цен на оргстекло
require_once get_template_directory() . '/inc/price-calculator-functions.php';
/**
* Создание атрибута "Толщина листа" для вариаций без калькулятора
*/
add_action( 'after_switch_theme', 'orgsteklo_create_thickness_attribute' );
function orgsteklo_create_thickness_attribute() {
// Проверяем, существует ли уже атрибут
$attribute_taxonomy = 'pa_tolschina_lista';
if ( ! taxonomy_exists( $attribute_taxonomy ) ) {
// Создаем атрибут через WordPress
$attribute_data = array(
'slug' => 'tolschina_lista',
'name' => __( 'Толщина листа', 'woocommerce' ),
'type' => 'select',
'order_by' => 'menu_order',
'has_archives' => false,
);
// Используем функцию WooCommerce для создания атрибута
if ( function_exists( 'wc_create_attribute' ) ) {
$attribute_id = wc_create_attribute( $attribute_data );
if ( ! is_wp_error( $attribute_id ) ) {
// Регистрируем таксономию
register_taxonomy(
$attribute_taxonomy,
'product',
array(
'labels' => array(
'name' => __( 'Толщина листа', 'woocommerce' ),
),
'hierarchical' => false,
'show_ui' => false,
'query_var' => true,
'rewrite' => false,
)
);
}
}
}
}
// Регистрируем таксономию атрибута при каждой загрузке
add_action( 'init', 'orgsteklo_register_thickness_taxonomy', 5 );
function orgsteklo_register_thickness_taxonomy() {
if ( taxonomy_exists( 'pa_tolschina_lista' ) ) {
return;
}
register_taxonomy(
'pa_tolschina_lista',
'product',
array(
'labels' => array(
'name' => __( 'Толщина листа', 'woocommerce' ),
'singular_name' => __( 'Толщина листа', 'woocommerce' ),
),
'hierarchical' => false,
'show_ui' => false,
'query_var' => true,
'rewrite' => false,
'public' => true,
)
);
}
/**
* Убираем вариации из названий товаров в корзине и оформлении заказа
*/
add_filter( 'woocommerce_cart_item_name', 'orgsteklo_remove_variation_from_cart_item_name', 10, 3 );
add_filter( 'woocommerce_order_item_name', 'orgsteklo_remove_variation_from_order_item_name', 10, 2 );
function orgsteklo_remove_variation_from_cart_item_name( $product_name, $cart_item, $cart_item_key ) {
$_product = $cart_item['data'];
if ( $_product && $_product->is_type('variation') ) {
$parent_product = wc_get_product( $cart_item['product_id'] );
if ( $parent_product ) {
$product_name = $parent_product->get_name();
}
}
return $product_name;
}
function orgsteklo_remove_variation_from_order_item_name( $product_name, $item ) {
$product = $item->get_product();
if ( $product && $product->is_type('variation') ) {
$parent_product = wc_get_product( $product->get_parent_id() );
if ( $parent_product ) {
$product_name = $parent_product->get_name();
}
}
return $product_name;
}
/**
* Страница настроек скидок от суммы заказа
*/
add_action( 'admin_menu', 'orgsteklo_add_cart_discount_page', 99 );
function orgsteklo_add_cart_discount_page() {
add_submenu_page(
'woocommerce',
'Скидки от суммы заказа',
'Скидки от суммы',
'manage_woocommerce',
'orgsteklo-cart-discounts',
'orgsteklo_cart_discount_settings_page'
);
}
function orgsteklo_cart_discount_settings_page() {
// Сохранение настроек
if ( isset( $_POST['orgsteklo_discount_save'] ) && check_admin_referer( 'orgsteklo_discount_settings' ) ) {
$discounts = array();
if ( isset( $_POST['discount_amount'] ) && is_array( $_POST['discount_amount'] ) ) {
foreach ( $_POST['discount_amount'] as $index => $amount ) {
$amount = floatval( $amount );
$percent = floatval( $_POST['discount_percent'][$index] ?? 0 );
if ( $amount > 0 && $percent > 0 ) {
$discounts[$amount] = $percent;
}
}
}
// Сортируем по возрастанию суммы
ksort( $discounts );
update_option( 'orgsteklo_cart_discounts', $discounts );
echo '';
}
$discounts = get_option( 'orgsteklo_cart_discounts', array(
10000 => 3,
50000 => 5,
100000 => 7,
200000 => 10,
) );
?>
Настройки скидок от суммы заказа
Настройте автоматические скидки в зависимости от суммы корзины. Скидка применяется автоматически при достижении указанной суммы.
'В этом поле вы можете указать график работы склада, необходимость заказа пропуска, а также другие комментарии к заказу',
'file_hint' => '',
'payment_description' => '*Ваш заказ, информация о желаемом способе доставки и Ваши контакты будут направлены менеджеру компании "ОРГСТЕКЛО" для дальнейшей обработки заказа и выставления счета на оплату. Счет на оплату будет направлен Вам на указанный e-mail.',
'pickup_address' => 'Московская обл., г. Реутов, ул. Победы, д. 1',
'pickup_hours' => 'Часы работы: с 10:00 до 20:00',
'pickup_description' => '(заезд грузового транспорта только со стороны шоссе Энтузиастов по проспекту Мира)',
'pickup_map_link' => '',
'pickup_map_iframe' => 'https://yandex.ru/map-widget/v1/?um=constructor%3A292408e4d7b13e325054d9d40cfd751bedc6dcb36775466b6e852ed33b3d2d1f&source=constructor',
'samovyvoz_label' => 'Московская обл., г. Реутов, ул. Победы, д. 1',
'moscow_label' => 'Стоимость: {cost}',
'moscow_area_label' => 'Стоимость: {cost} + {km_rate} ₽ за каждый километр за МКАД',
'moscow_area_km_rate' => '30',
'region_label' => 'Транспортной компанией',
'pochta_label' => 'Стоимость зависит от веса и габаритов заказа',
'moscow_area_warning' => '*Если Вы введете недостоверные данные и не доплатите за доставку по области, продавец оставляет за собой право не доставлять товар.',
'region_description' => 'Стоимость и условия доставки в другой регион рассчитываются индивидуально, после оформления заказа с вами свяжется менеджер для уточнения деталей доставки.',
'pochta_description' => 'Стоимость и условия доставки в другой регион Почтой России рассчитываются индивидуально, после оформления заказа с вами свяжется менеджер для уточнения деталей доставки.',
'online_payment_description' => '',
);
}
function orgsteklo_get_checkout_setting( $key ) {
$settings = get_option( 'orgsteklo_checkout_settings', array() );
$defaults = orgsteklo_checkout_settings_defaults();
return isset( $settings[ $key ] ) && $settings[ $key ] !== '' ? $settings[ $key ] : ( isset( $defaults[ $key ] ) ? $defaults[ $key ] : '' );
}
function orgsteklo_checkout_settings_page() {
if ( isset( $_POST['orgsteklo_checkout_save'] ) && check_admin_referer( 'orgsteklo_checkout_settings_nonce' ) ) {
$fields = array_keys( orgsteklo_checkout_settings_defaults() );
$settings = array();
foreach ( $fields as $field ) {
$settings[ $field ] = isset( $_POST[ $field ] ) ? wp_kses_post( wp_unslash( $_POST[ $field ] ) ) : '';
}
update_option( 'orgsteklo_checkout_settings', $settings );
echo '';
}
$defaults = orgsteklo_checkout_settings_defaults();
$settings = get_option( 'orgsteklo_checkout_settings', array() );
$get = function( $key ) use ( $settings, $defaults ) {
return isset( $settings[ $key ] ) ? $settings[ $key ] : $defaults[ $key ];
};
?>
Настройки страницы доставки
- Проверено и активировано
*
* Применяет скидку на основе общей суммы корзины:
* - От 10 000 ₽ - скидка 3%
* - От 50 000 ₽ - скидка 5%
* - От 100 000 ₽ - скидка 7%
* - От 200 000 ₽ - скидка 10%
*
* Настройки можно изменить в админке: WooCommerce → Скидки от суммы
*/
/**
* Вспомогательная функция для расчета скидки от суммы заказа
* Возвращает массив: ['percent' => %, 'amount' => сумма скидки]
*/
function orgsteklo_calculate_cart_discount( $cart_total ) {
$discount_tiers = get_option( 'orgsteklo_cart_discounts', array(
10000 => 3,
50000 => 5,
100000 => 7,
200000 => 10,
) );
if ( empty( $discount_tiers ) ) {
return array( 'percent' => 0, 'amount' => 0 );
}
$discount_percent = 0;
krsort( $discount_tiers );
foreach ( $discount_tiers as $threshold => $percent ) {
if ( $cart_total >= $threshold && $percent > $discount_percent ) {
$discount_percent = $percent;
break;
}
}
$discount_amount = 0;
if ( $discount_percent > 0 ) {
$discount_amount = round( ( $cart_total * $discount_percent ) / 100 );
}
return array( 'percent' => $discount_percent, 'amount' => $discount_amount );
}
add_action( 'woocommerce_cart_calculate_fees', 'orgsteklo_cart_discount_based_on_total', 10 );
function orgsteklo_cart_discount_based_on_total() {
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
return;
}
// Получаем настройки скидок из админки
$discount_tiers = get_option( 'orgsteklo_cart_discounts', array(
10000 => 3,
50000 => 5,
100000 => 7,
200000 => 10,
) );
if ( empty( $discount_tiers ) ) {
return; // Если настройки пусты, не применяем скидку
}
// Считаем сумму СО скидками на товары для проверки порога и применения скидки
$sale_total = 0;
foreach ( WC()->cart->get_cart() as $item ) {
$product = $item['data'];
$qty = $item['quantity'];
$sale_price = $product->get_sale_price();
if ( $sale_price === '' ) {
$sale_price = $product->get_regular_price();
}
$sale_total += $sale_price * $qty;
}
$discount_percent = 0;
// Определяем скидку по сумме СО скидками на товары
krsort( $discount_tiers );
foreach ( $discount_tiers as $threshold => $percent ) {
if ( $sale_total >= $threshold && $percent > $discount_percent ) {
$discount_percent = $percent;
break;
}
}
// Применяем скидку
if ( $discount_percent > 0 ) {
$discount_amount = round( ( $sale_total * $discount_percent ) / 100 );
WC()->cart->add_fee( 'Скидка от суммы заказа (' . $discount_percent . '%)', -$discount_amount );
}
}
/**
* Включаем черновики (draft) в поиск и каталог товаров
*/
add_action( 'pre_get_posts', 'orgsteklo_include_drafts_in_product_queries' );
function orgsteklo_include_drafts_in_product_queries( $query ) {
if ( is_admin() || ! $query->is_main_query() ) {
return;
}
// Поиск по товарам
if ( $query->is_search() && isset( $query->query_vars['post_type'] ) && $query->query_vars['post_type'] === 'product' ) {
$query->set( 'post_status', array( 'publish', 'draft' ) );
}
// Каталог / категории товаров
if ( $query->is_post_type_archive( 'product' ) || $query->is_tax( 'product_cat' ) ) {
$query->set( 'post_status', array( 'publish', 'draft' ) );
}
}
/**
* Переключатель количества товаров на странице (?per_page=24)
*/
add_filter( 'loop_shop_per_page', 'orgsteklo_products_per_page', 20 );
function orgsteklo_products_per_page( $cols ) {
if ( isset( $_GET['per_page'] ) ) {
$val = sanitize_text_field( $_GET['per_page'] );
if ( $val === 'all' ) {
return 9999;
}
$num = intval( $val );
if ( in_array( $num, array( 12, 24, 48 ), true ) ) {
return $num;
}
}
return 12;
}
/**
* Делаем черновики видимыми в каталоге/поиске,
* чтобы is_visible() не скрывала их в шаблоне content-product.php
*/
add_filter( 'woocommerce_product_is_visible', 'orgsteklo_draft_products_visible', 10, 2 );
function orgsteklo_draft_products_visible( $visible, $product_id ) {
if ( ! $visible && get_post_status( $product_id ) === 'draft' ) {
return true;
}
return $visible;
}
/**
* AJAX обработчик для добавления простых товаров в корзину
*/
add_action( 'wp_ajax_woocommerce_ajax_add_to_cart', 'woocommerce_ajax_add_to_cart' );
add_action( 'wp_ajax_nopriv_woocommerce_ajax_add_to_cart', 'woocommerce_ajax_add_to_cart' );
function woocommerce_ajax_add_to_cart() {
$product_id = isset( $_POST['product_id'] ) ? absint( $_POST['product_id'] ) : 0;
$product_id = apply_filters( 'woocommerce_add_to_cart_product_id', $product_id );
$quantity = empty( $_POST['quantity'] ) ? 1 : wc_stock_amount( $_POST['quantity'] );
$variation_id = isset( $_POST['variation_id'] ) ? absint( $_POST['variation_id'] ) : 0;
// Проверяем что product_id передан
if ( ! $product_id ) {
wp_send_json( array(
'error' => true,
'message' => 'ID товара не указан'
) );
wp_die();
}
// Получаем товар
$product = wc_get_product( $product_id );
if ( ! $product ) {
wp_send_json( array(
'error' => true,
'message' => 'Товар не найден'
) );
wp_die();
}
// Проверяем можно ли купить товар
if ( ! $product->is_purchasable() ) {
wp_send_json( array(
'error' => true,
'message' => 'Товар нельзя купить'
) );
wp_die();
}
// Проверяем наличие
if ( ! $product->is_in_stock() ) {
wp_send_json( array(
'error' => true,
'message' => 'Товар не в наличии'
) );
wp_die();
}
$passed_validation = apply_filters( 'woocommerce_add_to_cart_validation', true, $product_id, $quantity );
if ( ! $passed_validation ) {
wp_send_json( array(
'error' => true,
'message' => 'Ошибка валидации товара'
) );
wp_die();
}
// Добавляем в корзину
$cart_item_key = WC()->cart->add_to_cart( $product_id, $quantity, $variation_id );
if ( $cart_item_key ) {
do_action( 'woocommerce_ajax_added_to_cart', $product_id );
if ( 'yes' === get_option( 'woocommerce_cart_redirect_after_add' ) ) {
wc_add_to_cart_message( array( $product_id => $quantity ), true );
}
WC_AJAX::get_refreshed_fragments();
} else {
wp_send_json( array(
'error' => true,
'message' => 'Не удалось добавить товар в корзину',
'product_url' => apply_filters( 'woocommerce_cart_redirect_after_error', get_permalink( $product_id ), $product_id )
) );
}
wp_die();
}