Custom WooCommerce theme for orgsteklo.ru including: - Product catalog with category/subcategory hierarchy - Custom checkout with delivery calculation - Price calculator - Admin settings panel - Search functionality - User account pages
2408 lines
96 KiB
PHP
2408 lines
96 KiB
PHP
<?php
|
||
//files css & js
|
||
add_action('wp_enqueue_scripts', 'true_styles_and_scripts_frontend');
|
||
function true_styles_and_scripts_frontend() {
|
||
wp_enqueue_style('swiper_css', 'https://cdn.jsdelivr.net/npm/swiper@8/swiper-bundle.min.css');
|
||
wp_enqueue_style('bootstrap_css', 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css', array('swiper_css'));
|
||
wp_enqueue_style('fancybox_css', get_stylesheet_directory_uri() . '/assets/css/fancybox.css', array('bootstrap_css'));
|
||
wp_enqueue_style('style_css', get_stylesheet_directory_uri() . '/assets/css/style.css', array('fancybox_css'));
|
||
|
||
wp_enqueue_script('jquery_js', 'https://code.jquery.com/jquery-3.7.1.js', null, true);
|
||
wp_enqueue_script('cookie_js', 'https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js', array('jquery_js'), null, true);
|
||
wp_enqueue_script('swiper_js', 'https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js', array('cookie_js'), null, true);
|
||
wp_enqueue_script('inputmask_js', 'https://cdnjs.cloudflare.com/ajax/libs/jquery.inputmask/5.0.6/jquery.inputmask.min.js', array('swiper_js'), null, true);
|
||
wp_enqueue_script('bootstrap_js', 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js', array('inputmask_js'), null, true);
|
||
wp_enqueue_script('fancybox_js', get_stylesheet_directory_uri() . '/assets/js/fancybox.min.js', array('bootstrap_js'), '1.0.0', true);
|
||
wp_enqueue_script('main_js', get_stylesheet_directory_uri() . '/assets/js/main.js', array('fancybox_js'), '1.4.0', true);
|
||
|
||
// Передаём данные пользователя для автозаполнения на checkout
|
||
if ( function_exists('is_checkout') && is_checkout() && is_user_logged_in() ) {
|
||
$uid = get_current_user_id();
|
||
$u = wp_get_current_user();
|
||
wp_localize_script( 'main_js', 'orgstekloCheckoutUser', array(
|
||
'isLoggedIn' => 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 '<style>
|
||
.stock_status_field { display: none !important; }
|
||
</style>';
|
||
}
|
||
}
|
||
|
||
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 '<div class="options_group">';
|
||
|
||
// Галочка "Популярный товар"
|
||
woocommerce_wp_checkbox( array(
|
||
'id' => '_is_popular_product',
|
||
'label' => 'Популярный товар',
|
||
'description' => 'Добавляет метку "Популярный" на карточку товара',
|
||
) );
|
||
|
||
// Галочка "Рекомендуемый товар"
|
||
woocommerce_wp_checkbox( array(
|
||
'id' => '_is_recommended_product',
|
||
'label' => 'Рекомендуемый товар',
|
||
'description' => 'Товар будет показываться в блоке "Рекомендуемые товары" у других товаров этой категории',
|
||
) );
|
||
|
||
echo '</div>';
|
||
|
||
// Характеристики для превью карточки товара
|
||
echo '<div class="options_group">';
|
||
|
||
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 '</div>';
|
||
}
|
||
|
||
/**
|
||
* Сохраняем галочки и управляем тегом "Популярный"
|
||
*/
|
||
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);
|
||
?>
|
||
<h3>Дополнительная информация</h3>
|
||
<table class="form-table">
|
||
<tr>
|
||
<th><label for="account_type">Тип аккаунта</label></th>
|
||
<td>
|
||
<select name="account_type" id="account_type">
|
||
<option value="Физическое лицо" <?php selected($account_type, 'Физическое лицо'); ?>>Физическое лицо</option>
|
||
<option value="Юридическое лицо или ИП" <?php selected($account_type, 'Юридическое лицо или ИП'); ?>>Юридическое лицо или ИП</option>
|
||
</select>
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
<?php
|
||
}
|
||
|
||
add_action('personal_options_update', 'save_account_type_admin');
|
||
add_action('edit_user_profile_update', 'save_account_type_admin');
|
||
|
||
function save_account_type_admin($user_id) {
|
||
if (current_user_can('edit_user', $user_id)) {
|
||
update_user_meta($user_id, 'account_type', sanitize_text_field($_POST['account_type']));
|
||
}
|
||
}
|
||
|
||
|
||
|
||
add_action('wp_ajax_delete_my_account', 'delete_my_account');
|
||
function delete_my_account() {
|
||
if (is_user_logged_in()) {
|
||
require_once ABSPATH . 'wp-admin/includes/user.php';
|
||
wp_delete_user(get_current_user_id());
|
||
}
|
||
wp_die();
|
||
}
|
||
|
||
|
||
// Сохранение данных пользователя
|
||
add_action('wp_ajax_save_user_profile_data', 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']);
|
||
|
||
// Обновим имя
|
||
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 '<div class="catalog_page-products__grid">';
|
||
while ($query->have_posts()) {
|
||
$query->the_post();
|
||
wc_get_template_part('content', 'product');
|
||
}
|
||
echo '</div>';
|
||
|
||
// === ПАГИНАЦИЯ ===
|
||
echo '<div class="catalog_page-products__bottom">';
|
||
|
||
// Показ количества
|
||
echo '<div class="catalog_page-count">';
|
||
echo '<p>Количество товаров на странице:</p>';
|
||
// (Можно оставить HTML как есть — JS сам обновляет активную кнопку)
|
||
echo '</div>';
|
||
|
||
echo '<div class="catalog_page-pagination">';
|
||
echo '<div class="catalog_page-pagination__row">';
|
||
|
||
// Prev
|
||
if ($paged > 1) {
|
||
echo '<a href="#" class="catalog_page-pagination-item prev" data-page="' . ($paged - 1) . '"><svg>...</svg></a>';
|
||
}
|
||
|
||
// Номера страниц
|
||
for ($i = 1; $i <= $total_pages; $i++) {
|
||
$active = $i == $paged ? ' active' : '';
|
||
echo '<a href="#" class="catalog_page-pagination-item' . $active . '" data-page="' . $i . '">' . $i . '</a>';
|
||
}
|
||
|
||
// Next
|
||
if ($paged < $total_pages) {
|
||
echo '<a href="#" class="catalog_page-pagination-item next" data-page="' . ($paged + 1) . '"><svg>...</svg></a>';
|
||
}
|
||
|
||
echo '</div>';
|
||
|
||
// Показ "00 товаров из 000"
|
||
$from = ($paged - 1) * $posts_per_page + 1;
|
||
$to = min($paged * $posts_per_page, $total);
|
||
echo '<p>' . $from . '–' . $to . ' товаров <span>из ' . $total . '</span></p>';
|
||
|
||
echo '</div></div>';
|
||
|
||
wp_reset_postdata();
|
||
|
||
echo ob_get_clean();
|
||
} else {
|
||
echo '<p>Нет товаров.</p>';
|
||
}
|
||
|
||
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) {
|
||
?>
|
||
<h2>Юридическая информация</h2>
|
||
<table class="form-table">
|
||
<tr>
|
||
<th><label for="company_name">Наименование организации</label></th>
|
||
<td>
|
||
<input type="text" name="company_name" id="company_name" value="<?php echo esc_attr(get_user_meta($user->ID, 'company_name', true)); ?>" class="regular-text" />
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th><label for="inn">ИНН</label></th>
|
||
<td>
|
||
<input type="text" name="inn" id="inn" value="<?php echo esc_attr(get_user_meta($user->ID, 'inn', true)); ?>" class="regular-text" />
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th><label for="kpp">КПП</label></th>
|
||
<td>
|
||
<input type="text" name="kpp" id="kpp" value="<?php echo esc_attr(get_user_meta($user->ID, 'kpp', true)); ?>" class="regular-text" />
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th><label for="legal_address">Юридический адрес</label></th>
|
||
<td>
|
||
<input type="text" name="legal_address" id="legal_address" value="<?php echo esc_attr(get_user_meta($user->ID, 'legal_address', true)); ?>" class="regular-text" />
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<th><label for="actual_address">Фактический адрес</label></th>
|
||
<td>
|
||
<input type="text" name="actual_address" id="actual_address" value="<?php echo esc_attr(get_user_meta($user->ID, 'actual_address', true)); ?>" class="regular-text" />
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
<?php
|
||
}
|
||
|
||
// Сохраняем поля при обновлении профиля
|
||
add_action('personal_options_update', 'save_custom_user_fields_admin');
|
||
add_action('edit_user_profile_update', 'save_custom_user_fields_admin');
|
||
|
||
function save_custom_user_fields_admin($user_id) {
|
||
if (!current_user_can('edit_user', $user_id)) {
|
||
return false;
|
||
}
|
||
|
||
update_user_meta($user_id, 'company_name', sanitize_text_field($_POST['company_name']));
|
||
update_user_meta($user_id, 'inn', sanitize_text_field($_POST['inn']));
|
||
update_user_meta($user_id, 'kpp', sanitize_text_field($_POST['kpp']));
|
||
update_user_meta($user_id, 'legal_address', sanitize_text_field($_POST['legal_address']));
|
||
update_user_meta($user_id, 'actual_address', sanitize_text_field($_POST['actual_address']));
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
|
||
// ========================================
|
||
// ФУНКЦИИ ДЛЯ РАБОТЫ СО СКИДКАМИ И МЕТКАМИ
|
||
// ========================================
|
||
|
||
/**
|
||
* Извлекает процент скидки из названия метки (тега)
|
||
* Например: "-10%" => 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 '<div class="notice notice-success"><p>Настройки сохранены!</p></div>';
|
||
}
|
||
|
||
$discounts = get_option( 'orgsteklo_cart_discounts', array(
|
||
10000 => 3,
|
||
50000 => 5,
|
||
100000 => 7,
|
||
200000 => 10,
|
||
) );
|
||
|
||
?>
|
||
<div class="wrap">
|
||
<h1>Настройки скидок от суммы заказа</h1>
|
||
<p>Настройте автоматические скидки в зависимости от суммы корзины. Скидка применяется автоматически при достижении указанной суммы.</p>
|
||
|
||
<form method="post" action="">
|
||
<?php wp_nonce_field( 'orgsteklo_discount_settings' ); ?>
|
||
|
||
<table class="wp-list-table widefat fixed striped">
|
||
<thead>
|
||
<tr>
|
||
<th>От суммы (₽)</th>
|
||
<th>Скидка (%)</th>
|
||
<th>Действия</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="discount-rows">
|
||
<?php foreach ( $discounts as $amount => $percent ) : ?>
|
||
<tr>
|
||
<td><input type="number" name="discount_amount[]" value="<?php echo esc_attr( $amount ); ?>" min="0" step="1" style="width: 150px;"></td>
|
||
<td><input type="number" name="discount_percent[]" value="<?php echo esc_attr( $percent ); ?>" min="0" max="100" step="0.1" style="width: 100px;"></td>
|
||
<td><button type="button" class="button delete-discount-row">Удалить</button></td>
|
||
</tr>
|
||
<?php endforeach; ?>
|
||
</tbody>
|
||
</table>
|
||
|
||
<p>
|
||
<button type="button" id="add-discount-row" class="button">Добавить уровень скидки</button>
|
||
</p>
|
||
|
||
<p class="submit">
|
||
<input type="submit" name="orgsteklo_discount_save" class="button button-primary" value="Сохранить настройки">
|
||
</p>
|
||
</form>
|
||
</div>
|
||
|
||
<script>
|
||
jQuery(document).ready(function($) {
|
||
$('#add-discount-row').on('click', function() {
|
||
var row = '<tr>' +
|
||
'<td><input type="number" name="discount_amount[]" value="" min="0" step="1" style="width: 150px;"></td>' +
|
||
'<td><input type="number" name="discount_percent[]" value="" min="0" max="100" step="0.1" style="width: 100px;"></td>' +
|
||
'<td><button type="button" class="button delete-discount-row">Удалить</button></td>' +
|
||
'</tr>';
|
||
$('#discount-rows').append(row);
|
||
});
|
||
|
||
$(document).on('click', '.delete-discount-row', function() {
|
||
$(this).closest('tr').remove();
|
||
});
|
||
});
|
||
</script>
|
||
|
||
<style>
|
||
.wp-list-table input[type="number"] {
|
||
padding: 5px;
|
||
}
|
||
</style>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Настройки страницы оформления заказа (тексты, адрес самовывоза, карта и т.д.)
|
||
*/
|
||
add_action( 'admin_menu', 'orgsteklo_add_checkout_settings_page', 99 );
|
||
function orgsteklo_add_checkout_settings_page() {
|
||
add_submenu_page(
|
||
'woocommerce',
|
||
'Настройки страницы доставки',
|
||
'Настройки доставки',
|
||
'manage_woocommerce',
|
||
'orgsteklo-checkout-settings',
|
||
'orgsteklo_checkout_settings_page'
|
||
);
|
||
}
|
||
|
||
function orgsteklo_checkout_settings_defaults() {
|
||
return array(
|
||
'comment_hint' => 'В этом поле вы можете указать график работы склада, необходимость заказа пропуска, а также другие комментарии к заказу',
|
||
'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 '<div class="notice notice-success"><p>Настройки сохранены!</p></div>';
|
||
}
|
||
|
||
$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 ];
|
||
};
|
||
?>
|
||
<div class="wrap">
|
||
<h1>Настройки страницы доставки</h1>
|
||
<form method="post" action="">
|
||
<?php wp_nonce_field( 'orgsteklo_checkout_settings_nonce' ); ?>
|
||
|
||
<h2>Комментарий к заказу</h2>
|
||
<table class="form-table">
|
||
<tr>
|
||
<th><label for="comment_hint">Подсказка к комментарию</label></th>
|
||
<td><textarea name="comment_hint" id="comment_hint" rows="3" class="large-text"><?php echo esc_textarea( $get('comment_hint') ); ?></textarea></td>
|
||
</tr>
|
||
<tr>
|
||
<th><label for="file_hint">Подсказка к прикреплению файла</label></th>
|
||
<td><textarea name="file_hint" id="file_hint" rows="2" class="large-text"><?php echo esc_textarea( $get('file_hint') ); ?></textarea></td>
|
||
</tr>
|
||
</table>
|
||
|
||
<h2>Способ оплаты</h2>
|
||
<table class="form-table">
|
||
<tr>
|
||
<th><label for="payment_description">Описание способа оплаты «Сформировать счёт»</label></th>
|
||
<td><textarea name="payment_description" id="payment_description" rows="4" class="large-text"><?php echo esc_textarea( $get('payment_description') ); ?></textarea></td>
|
||
</tr>
|
||
<tr>
|
||
<th><label for="online_payment_description">Описание способа оплаты «Оплата онлайн»</label></th>
|
||
<td><textarea name="online_payment_description" id="online_payment_description" rows="4" class="large-text"><?php echo esc_textarea( $get('online_payment_description') ); ?></textarea></td>
|
||
</tr>
|
||
</table>
|
||
|
||
<h2>Самовывоз</h2>
|
||
<table class="form-table">
|
||
<tr>
|
||
<th><label for="pickup_address">Адрес самовывоза</label></th>
|
||
<td><input type="text" name="pickup_address" id="pickup_address" value="<?php echo esc_attr( $get('pickup_address') ); ?>" class="large-text"></td>
|
||
</tr>
|
||
<tr>
|
||
<th><label for="pickup_hours">Часы работы</label></th>
|
||
<td><input type="text" name="pickup_hours" id="pickup_hours" value="<?php echo esc_attr( $get('pickup_hours') ); ?>" class="large-text"></td>
|
||
</tr>
|
||
<tr>
|
||
<th><label for="pickup_description">Дополнительное описание</label></th>
|
||
<td><textarea name="pickup_description" id="pickup_description" rows="2" class="large-text"><?php echo esc_textarea( $get('pickup_description') ); ?></textarea></td>
|
||
</tr>
|
||
<tr>
|
||
<th><label for="pickup_map_link">Ссылка «Показать на карте»</label></th>
|
||
<td><input type="url" name="pickup_map_link" id="pickup_map_link" value="<?php echo esc_attr( $get('pickup_map_link') ); ?>" class="large-text" placeholder="https://yandex.ru/maps/..."></td>
|
||
</tr>
|
||
<tr>
|
||
<th><label for="pickup_map_iframe">URL iframe Яндекс Карты</label></th>
|
||
<td><input type="text" name="pickup_map_iframe" id="pickup_map_iframe" value="<?php echo esc_attr( $get('pickup_map_iframe') ); ?>" class="large-text" placeholder="https://yandex.ru/map-widget/v1/..."></td>
|
||
</tr>
|
||
</table>
|
||
|
||
<h2>Описания способов доставки (серый текст под названием)</h2>
|
||
<p class="description">Для «Доставка по Москве» и «Доставка по МО» используйте <code>{cost}</code> — подставится стоимость из WooCommerce. Для МО также доступен <code>{km_rate}</code> — стоимость за 1 км.</p>
|
||
<table class="form-table">
|
||
<tr>
|
||
<th><label for="samovyvoz_label">Самовывоз</label></th>
|
||
<td><input type="text" name="samovyvoz_label" id="samovyvoz_label" value="<?php echo esc_attr( $get('samovyvoz_label') ); ?>" class="large-text"></td>
|
||
</tr>
|
||
<tr>
|
||
<th><label for="moscow_label">Доставка по Москве</label></th>
|
||
<td><input type="text" name="moscow_label" id="moscow_label" value="<?php echo esc_attr( $get('moscow_label') ); ?>" class="large-text"></td>
|
||
</tr>
|
||
<tr>
|
||
<th><label for="moscow_area_label">Доставка по Московской области</label></th>
|
||
<td><input type="text" name="moscow_area_label" id="moscow_area_label" value="<?php echo esc_attr( $get('moscow_area_label') ); ?>" class="large-text">
|
||
<p class="description">Используйте <code>{km_rate}</code> для подстановки стоимости за км.</p></td>
|
||
</tr>
|
||
<tr>
|
||
<th><label for="moscow_area_km_rate">Стоимость за 1 км от МКАД (₽)</label></th>
|
||
<td><input type="number" name="moscow_area_km_rate" id="moscow_area_km_rate" value="<?php echo esc_attr( $get('moscow_area_km_rate') ); ?>" class="small-text" min="0" step="1"></td>
|
||
</tr>
|
||
<tr>
|
||
<th><label for="region_label">В другой регион</label></th>
|
||
<td><input type="text" name="region_label" id="region_label" value="<?php echo esc_attr( $get('region_label') ); ?>" class="large-text"></td>
|
||
</tr>
|
||
<tr>
|
||
<th><label for="pochta_label">В другой регион Почтой России</label></th>
|
||
<td><input type="text" name="pochta_label" id="pochta_label" value="<?php echo esc_attr( $get('pochta_label') ); ?>" class="large-text"></td>
|
||
</tr>
|
||
</table>
|
||
|
||
<h2>Тексты-комментарии к адресным формам доставки</h2>
|
||
<table class="form-table">
|
||
<tr>
|
||
<th><label for="moscow_area_warning">Доставка по Московской области</label></th>
|
||
<td><textarea name="moscow_area_warning" id="moscow_area_warning" rows="3" class="large-text"><?php echo esc_textarea( $get('moscow_area_warning') ); ?></textarea></td>
|
||
</tr>
|
||
<tr>
|
||
<th><label for="region_description">Доставка в другой регион</label></th>
|
||
<td><textarea name="region_description" id="region_description" rows="3" class="large-text"><?php echo esc_textarea( $get('region_description') ); ?></textarea></td>
|
||
</tr>
|
||
<tr>
|
||
<th><label for="pochta_description">Доставка Почтой России</label></th>
|
||
<td><textarea name="pochta_description" id="pochta_description" rows="3" class="large-text"><?php echo esc_textarea( $get('pochta_description') ); ?></textarea></td>
|
||
</tr>
|
||
</table>
|
||
|
||
<p class="submit">
|
||
<input type="submit" name="orgsteklo_checkout_save" class="button-primary" value="Сохранить настройки">
|
||
</p>
|
||
</form>
|
||
</div>
|
||
<?php
|
||
}
|
||
|
||
/**
|
||
* Скидка от суммы заказа
|
||
* Updated: <?php echo date('d.m.Y'); ?> - Проверено и активировано
|
||
*
|
||
* Применяет скидку на основе общей суммы корзины:
|
||
* - От 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();
|
||
}
|