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 '
'; echo '
'; // Prev if ($paged > 1) { echo ''; } // Номера страниц for ($i = 1; $i <= $total_pages; $i++) { $active = $i == $paged ? ' active' : ''; echo '' . $i . ''; } // Next if ($paged < $total_pages) { echo ''; } echo '
'; // Показ "00 товаров из 000" $from = ($paged - 1) * $posts_per_page + 1; $to = min($paged * $posts_per_page, $total); echo '

' . $from . '–' . $to . ' товаров из ' . $total . '

'; 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, ) ); ?>

Настройки скидок от суммы заказа

Настройте автоматические скидки в зависимости от суммы корзины. Скидка применяется автоматически при достижении указанной суммы.

$percent ) : ?>
От суммы (₽) Скидка (%) Действия

'В этом поле вы можете указать график работы склада, необходимость заказа пропуска, а также другие комментарии к заказу', '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 ]; }; ?>

Настройки страницы доставки

Комментарий к заказу

Способ оплаты

Самовывоз

Описания способов доставки (серый текст под названием)

Для «Доставка по Москве» и «Доставка по МО» используйте {cost} — подставится стоимость из WooCommerce. Для МО также доступен {km_rate} — стоимость за 1 км.

Используйте {km_rate} для подстановки стоимости за км.

Тексты-комментарии к адресным формам доставки

- Проверено и активировано * * Применяет скидку на основе общей суммы корзины: * - От 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(); }