Автор: vadimfominov

  • Создание кастомной формы регистрации в WordPress с REST API

    В этой статье создадим продвинутую форму регистрации с:

    • Валидацией данных
    • Условными полями (для врачей)
    • Защитой от злоупотреблений
    • Автоматическим входом после регистрации

    Преимущества решения:
    ✔ Полный контроль над процессом регистрации
    ✔ Гибкая система проверки данных
    ✔ Безопасность через nonce и ограничение попыток
    ✔ Профессиональный пользовательский опыт

    Шаг 1: Подготовка HTML-формы

    Разместите этот код там, где должна отображаться форма — в шаблоне страницы, виджете или через шорткод:

    <form id="custom-register-form">
        <!-- Скрытые поля безопасности -->
        <input type="hidden" name="security" value="<?php echo wp_create_nonce('custom-register-nonce'); ?>">
        
        <!-- Основные поля -->
        <div class="form-group">
            <label for="email">E-mail *</label>
            <input type="email" name="email" id="email" required>
        </div>
        
        <div class="form-group">
            <label for="fullname">ФИО *</label>
            <input type="text" name="fullname" id="fullname" required>
        </div>
        
        <!-- Поля пароля с уникальными ID -->
        <div class="form-group">
            <label for="register_password">Пароль *</label>
            <input type="password" name="password" id="register_password" required minlength="6">
        </div>
        
        <div class="form-group">
            <label for="register_password_repeat">Повторите пароль *</label>
            <input type="password" name="password_repeat" id="register_password_repeat" required>
        </div>
        
        <!-- Выбор типа пользователя -->
        <div class="form-group">
            <label for="user_type">Тип пользователя *</label>
            <select name="user_type" id="user_type" required>
                <option value="">Выберите тип</option>
                <option value="student">Студент</option>
                <option value="doctor">Врач</option>
                <option value="user">Пользователь</option>
            </select>
        </div>
        
        <!-- Условное поле для врачей (изначально скрыто) -->
        <div class="form-group" id="file-upload-group" style="display: none;">
            <label for="doctor_file">Документ, подтверждающий статус врача *</label>
            <input type="file" name="doctor_file" id="doctor_file" accept=".pdf,.doc,.docx,.jpg,.png">
            <p class="file-hint">Формат: PDF, DOC, JPG или PNG (макс. 2MB)</p>
        </div>
        
        <button type="submit">Зарегистрироваться</button>
        
        <!-- Контейнер для сообщений -->
        <div id="register-message" class="message"></div>
    </form>

    Шаг 2: Добавляем JavaScript логику

    Создаем файл js/custom-register.js:

    Часть 1: Инициализация и показ полей

    window.addEventListener('load', function() {
        const form = document.getElementById('custom-register-form');
        if (!form) return;
    
        const userTypeSelect = document.getElementById('user_type');
        const fileUploadGroup = document.getElementById('file-upload-group');
        const messageEl = document.getElementById('register-message');
    
        // Показываем/скрываем поле для файла при изменении типа пользователя
        userTypeSelect.addEventListener('change', function() {
            fileUploadGroup.style.display = this.value === 'doctor' ? 'block' : 'none';
        });
    });

    Часть 2: Обработка отправки формы

        form.addEventListener('submit', async function(e) {
            e.preventDefault();
            
            // Сброс состояния сообщений
            messageEl.textContent = '';
            messageEl.className = 'message';
    
            // Получаем значения полей
            const formData = {
                email: form.email.value.trim(),
                fullname: form.fullname.value.trim(),
                password: form.register_password.value.trim(),
                password_repeat: form.register_password_repeat.value.trim(),
                user_type: form.user_type.value,
                security: form.security.value
            };
    
            // Базовая валидация
            if (!formData.email || !formData.fullname || !formData.password || 
                !formData.password_repeat || !formData.user_type) {
                showMessage('Пожалуйста, заполните все обязательные поля', 'error');
                return;
            }
    
            if (formData.password !== formData.password_repeat) {
                showMessage('Пароли не совпадают', 'error');
                return;
            }
    
            if (formData.password.length < 6) {
                showMessage('Пароль должен содержать минимум 6 символов', 'error');
                return;
            }

    Часть 3: Специальная валидация для врачей

            // Валидация для врачей
            if (formData.user_type === 'doctor') {
                const fileInput = document.getElementById('doctor_file');
                if (!fileInput.files || fileInput.files.length === 0) {
                    showMessage('Необходимо загрузить документ', 'error');
                    return;
                }
    
                const file = fileInput.files[0];
                const validTypes = [
                    'application/pdf', 
                    'application/msword',
                    'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
                    'image/jpeg', 
                    'image/png'
                ];
                const maxSize = 2 * 1024 * 1024; // 2MB
    
                if (!validTypes.includes(file.type)) {
                    showMessage('Недопустимый формат файла', 'error');
                    return;
                }
    
                if (file.size > maxSize) {
                    showMessage('Файл слишком большой (макс. 2MB)', 'error');
                    return;
                }
            }

    Часть 4: Отправка данных на сервер

            // Подготовка данных для отправки
            const data = new FormData();
            for (const key in formData) {
                data.append(key, formData[key]);
            }
    
            // Добавляем файл для врачей
            if (formData.user_type === 'doctor') {
                data.append('doctor_file', document.getElementById('doctor_file').files[0]);
            }
    
            // Отправка данных
            try {
                const response = await fetch(customVars.rest_register_url, {
                    method: 'POST',
                    headers: {
                        'X-WP-Nonce': customVars.rest_nonce
                    },
                    body: data
                });
                
                const result = await response.json();
                
                if (result.success) {
                    showMessage(result.message, 'success');
                    if (result.redirect) {
                        setTimeout(() => {
                            window.location.href = result.redirect;
                        }, 2000);
                    }
                } else {
                    showMessage(result.message, 'error');
                }
            } catch (error) {
                showMessage('Ошибка соединения с сервером', 'error');
                console.error('Registration error:', error);
            }
        });
        
        // Функция для показа сообщений
        function showMessage(text, type) {
            messageEl.innerHTML = text;
            messageEl.className = `message ${type}`;
        }
    });

    Шаг 3: Серверная обработка (PHP)

    Добавляем в functions.php:

    Часть 1: Регистрация REST endpoint

    add_action('rest_api_init', function() {
        register_rest_route('wp/v2', '/custom-register', [
            'methods' => 'POST',
            'callback' => 'handle_custom_register',
            'permission_callback' => '__return_true',
        ]);
    });

    Часть 2: Обработчик регистрации

    function handle_custom_register(WP_REST_Request $request) {
        // Проверка nonce
        if (!wp_verify_nonce($request['security'], 'custom-register-nonce')) {
            return new WP_REST_Response([
                'success' => false,
                'message' => 'Ошибка безопасности сессии'
            ], 200);
        }
    
        // Получение и очистка данных
        $email = sanitize_email($request['email']);
        $fullname = sanitize_text_field($request['fullname']);
        $password = $request['password'];
        $user_type = sanitize_text_field($request['user_type']);
    
        // Валидация email
        if (!is_email($email)) {
            return new WP_REST_Response([
                'success' => false,
                'message' => 'Некорректный email'
            ], 200);
        }
    
        if (email_exists($email)) {
            return new WP_REST_Response([
                'success' => false,
                'message' => 'Этот email уже зарегистрирован'
            ], 200);
        }
    
        // Валидация пароля
        if (strlen($password) < 6) {
            return new WP_REST_Response([
                'success' => false,
                'message' => 'Пароль должен содержать минимум 6 символов'
            ], 200);
        }

    Часть 3: Обработка врачей

        // Специальная проверка для врачей
        if ($user_type === 'doctor') {
            if (empty($_FILES['doctor_file'])) {
                return new WP_REST_Response([
                    'success' => false,
                    'message' => 'Необходимо загрузить документ'
                ], 200);
            }
    
            $file = $_FILES['doctor_file'];
            $valid_types = [
                'application/pdf', 
                'application/msword',
                'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
                'image/jpeg', 
                'image/png'
            ];
            $max_size = 2 * 1024 * 1024; // 2MB
    
            if (!in_array($file['type'], $valid_types)) {
                return new WP_REST_Response([
                    'success' => false,
                    'message' => 'Недопустимый формат файла'
                ], 200);
            }
    
            if ($file['size'] > $max_size) {
                return new WP_REST_Response([
                    'success' => false,
                    'message' => 'Файл слишком большой (макс. 2MB)'
                ], 200);
            }
        }

    Часть 4: Создание пользователя

        // Создание пользователя
        $user_id = wp_create_user($email, $password, $email);
        
        if (is_wp_error($user_id)) {
            return new WP_REST_Response([
                'success' => false,
                'message' => $user_id->get_error_message()
            ], 200);
        }
    
        // Сохранение дополнительных полей
        update_user_meta($user_id, 'fullname', $fullname);
        update_user_meta($user_id, 'user_type', $user_type);
    
        // Обработка файла для врачей
        if ($user_type === 'doctor' && !empty($_FILES['doctor_file'])) {
            require_once(ABSPATH . 'wp-admin/includes/file.php');
            
            $upload = wp_handle_upload($_FILES['doctor_file'], ['test_form' => false]);
            
            if ($upload && !isset($upload['error'])) {
                update_user_meta($user_id, 'doctor_document', $upload['url']);
            } else {
                error_log('File upload error: ' . $upload['error']);
            }
        }
    
        // Автоматический вход после регистрации
        $user = wp_signon([
            'user_login' => $email,
            'user_password' => $password,
            'remember' => true
        ], false);
    
        return new WP_REST_Response([
            'success' => true,
            'message' => 'Регистрация прошла успешно!',
            'redirect' => home_url('/')
        ], 200);
    }

    Шаг 4: Подключение скриптов и стилей

    В том же functions.php добавляем:

    // Регистрация скриптов
    add_action('wp_enqueue_scripts', function() {
        wp_enqueue_script(
            'custom-register',
            get_template_directory_uri() . '/js/custom-register.js',
            array(),
            filemtime(get_template_directory() . '/js/custom-register.js'),
            true
        );
        
        wp_localize_script('custom-register', 'customVars', [
            'rest_register_url' => rest_url('wp/v2/custom-register'),
            'rest_nonce' => wp_create_nonce('wp_rest')
        ]);
    });
    
    // Стили формы
    add_action('wp_head', function() {
        echo '<style>
            #custom-register-form {
                max-width: 500px;
                margin: 2rem auto;
                padding: 2rem;
                background: #fff;
                border-radius: 8px;
                box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            }
            .form-group {
                margin-bottom: 1.5rem;
            }
            .message {
                margin-top: 1.5rem;
                padding: 1rem;
                border-radius: 4px;
            }
            .message.success {
                background: #edfaef;
                color: #00a32a;
                border: 1px solid #00a32a;
            }
            .message.error {
                background: #f8ebea;
                color: #d63638;
                border: 1px solid #d63638;
            }
        </style>';
    });

    Итоги

    Мы создали полнофункциональную форму регистрации с:

    • Валидацией на стороне клиента и сервера
    • Условными полями для разных типов пользователей
    • Защитой от злоупотреблений
    • Автоматическим входом после регистрации

    Чтобы внедрить это решение:

    1. Разместите HTML-код формы в нужном шаблоне
    2. Добавьте JavaScript в файл /js/custom-register.js
    3. Вставьте PHP-код в functions.php вашей темы
    4. Проверьте работу всех сценариев

    Дальнейшие улучшения могут включать:

    • Подтверждение email
    • Капчу для защиты от ботов
    • Интеграцию с социальными сетями
    • Двухфакторную аутентификацию

    Форма готова к использованию и обеспечит удобный и безопасный процесс регистрации для ваших пользователей.

  • Создание кастомной формы авторизации в WordPress через REST API

    В этой статье я покажу, как создать собственную форму входа на сайт WordPress с использованием REST API. Это решение обеспечит лучшую производительность, безопасность и контроль над процессом авторизации.

    Преимущества кастомной формы авторизации

    • Полный контроль над дизайном и функционалом
    • Работа без перезагрузки страницы
    • Гибкая система обработки ошибок
    • Можно интегрировать с React/Vue
    • Отсутствие зависимости от стандартных решений WordPress

    Шаг 1: Подготовка HTML-формы

    Разместите этот код там, где должна отображаться форма — в шаблоне страницы, виджете или через шорткод:

    <form id="custom-login-form">
        <input type="hidden" name="redirect_to" value="<?php echo esc_url($_SERVER['REQUEST_URI']); ?>">
        <input type="hidden" name="security" value="<?php echo wp_create_nonce('custom-login-nonce'); ?>">
    
        <div class="form-group">
            <label for="username">E-mail или логин</label>
            <input type="text" name="username" id="username" required>
        </div>
    
        <div class="form-group">
            <label for="password">Пароль</label>
            <input type="password" name="password" id="password" required>
        </div>
    
        <button type="submit">Войти</button>
    
        <div id="login-message" class="message"></div>
    </form>

    Что важно:

    • security – защита от CSRF-атак
    • redirect_to – вернёт пользователя на текущую страницу после входа

    Шаг 2: Добавление JavaScript обработчика

    Создайте файл /js/custom-login.js:

    window.addEventListener('load', function() {
    const form = document.getElementById('custom-login-form');
    const messageEl = document.getElementById('login-message');

    if (!form) return;

    form.addEventListener('submit', async function(e) {
    e.preventDefault();

    // Очистка предыдущих сообщений
    messageEl.textContent = '';
    messageEl.className = 'message';

    // Валидация полей
    const username = form.username.value.trim();
    const password = form.password.value.trim();

    if (!username || !password) {
    showMessage('Пожалуйста, заполните все поля', 'error');
    return;
    }

    // Подготовка данных
    const data = {
    username: username,
    password: password,
    security: form.security.value,
    redirect_to: form.redirect_to.value
    };

    try {
    const response = await fetch(customVars.rest_url, {
    method: 'POST',
    headers: {
    'Content-Type': 'application/json',
    'X-WP-Nonce': customVars.rest_nonce
    },
    body: JSON.stringify(data)
    });

    const result = await response.json();

    if (result.success) {
    // Успешная авторизация
    window.location.href = result.redirect;
    } else {
    // Ошибка авторизации
    showMessage(result.message, 'error');

    // Дополнительная логика по типу ошибки
    if (result.type === 'security') {
    console.warn('Security issue detected');
    }
    }
    } catch (error) {
    // Только реальные сетевые/парсинговые ошибки
    showMessage('Ошибка соединения', 'error');
    console.error('Network error:', error);
    }
    });

    function showMessage(text, type) {
    messageEl.innerHTML = text;
    messageEl.classList.add(type);
    }
    });

    Шаг 3: Регистрация скриптов и REST API

    Добавьте в functions.php вашей темы:

    // Регистрация и подключение скриптов
    function enqueue_login_scripts() {
    wp_enqueue_script(
    'custom-login',
    get_template_directory_uri() . '/js/custom-login.js',
    array(),
    filemtime(get_template_directory() . '/js/custom-login.js'),
    true
    );

    wp_localize_script('custom-login', 'customVars', [
    'rest_url' => rest_url('wp/v2/custom-login'),
    'rest_nonce' => wp_create_nonce('wp_rest')
    ]);
    }
    add_action('wp_enqueue_scripts', 'enqueue_login_scripts');

    // Регистрация REST API endpoint
    add_action('rest_api_init', function () {
    register_rest_route('wp/v2', '/custom-login', [
    'methods' => 'POST',
    'callback' => 'handle_custom_login',
    'permission_callback' => '__return_true',
    ]);
    });

    // Обработчик авторизации
    function handle_custom_login(WP_REST_Request $request) {
    // Проверка nonce
    if (!wp_verify_nonce($request['security'], 'custom-login-nonce')) {
    return new WP_REST_Response([
    'success' => false,
    'message' => 'Ошибка безопасности',
    'type' => 'security'
    ], 200);
    }

    $credentials = [
    'user_login' => sanitize_user($request['username']),
    'user_password' => $request['password'],
    'remember' => true,
    ];

    $user = wp_signon($credentials, false);

    if (is_wp_error($user)) {
    return new WP_REST_Response([
    'success' => false,
    'message' => $user->get_error_message(),
    'type' => 'auth'
    ], 200);
    }

    return new WP_REST_Response([
    'success' => true,
    'redirect' => $request['redirect_to']
    ], 200);
    }

    Шаг 4: Стилизация формы

    Добавьте в style.css вашей темы:

    #custom-login-form {
    max-width: 400px;
    margin: 20px auto;
    padding: 25px;
    background: #ffffff;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    }

    .form-group {
    margin-bottom: 20px;
    }

    .form-group label {
    display: block;
    margin-bottom: 8px;
    font-weight: 500;
    color: #333;
    }

    .form-group input {
    width: 100%;
    padding: 12px;
    border: 1px solid #ddd;
    border-radius: 4px;
    font-size: 16px;
    transition: border-color 0.3s;
    }

    .form-group input:focus {
    border-color: #2271b1;
    outline: none;
    }

    button[type="submit"] {
    width: 100%;
    padding: 12px;
    background: #2271b1;
    color: white;
    border: none;
    border-radius: 4px;
    font-size: 16px;
    cursor: pointer;
    transition: background 0.3s;
    }

    button[type="submit"]:hover {
    background: #135e96;
    }

    .message {
    margin-top: 20px;
    padding: 12px;
    border-radius: 4px;
    text-align: center;
    }

    .message.error {
    color: #d63638;
    background: #f8ebea;
    border: 1px solid #d63638;
    }

    .message.success {
    color: #00a32a;
    background: #edfaef;
    border: 1px solid #00a32a;
    }

    Шаг 5: Тестирование и отладка

    1. Проверьте, что все файлы подключены правильно
    2. Протестируйте следующие сценарии:
      • Вход с правильными данными
      • Вход с неверными данными
      • Попытка входа с пустыми полями

    Дополнительные улучшения

    1. Капча: Добавьте Google reCAPTCHA для защиты от ботов или создайте свою капчу
    2. Социальные сети: Интегрируйте вход через соцсети
    3. Восстановление пароля: Добавьте ссылку на восстановление

    Представленное решение дает полный контроль над процессом авторизации в WordPress. Оно безопасно, производительно и легко настраивается под любые требования дизайна.

    Главные преимущества:

    • Современный подход с использованием REST API
    • Гибкая система обработки ошибок
    • Полная интеграция с WordPress
    • Возможность дальнейшего расширения функционала

    Теперь вы можете легко интегрировать эту форму в любой шаблон WordPress и настроить её внешний вид согласно дизайну вашего сайта.

    Мой телеграм канал: wordpress_by

  • Про оценку стоимости проекта


    Мои 4 этапа, когда был рост в доходе и я менял принцип оценки стоимости проекта.

    Для начала, вот эти 4 этапа:

    1. Научился верстать. HTML, CSS и немного JQuery.
    2. Сделал первый сайт на WordPress + ACF.
    3. Прошел обучение, перестал бояться работать с JS и API.
    4. Разобрался как делать сайты под ключ.


    Ниже я расписал, какие проекты делал и как считал стоимость работы.

    1. Научился верстать. HTML, CSS и немного JQuery.

    На этом этапе делал такие задачи: поправить верстку на работающем сайте, найти в Гугле 10 HTML шаблонов и поменять текст в коде под нужную тематику, сверстать 1-2 экрана для заглуши на домен, на время разработки сайта. С такими задачами мне не приходилось думать о стоимости, просто брал 500р, за каждую мелкую задачу. Тогда я познакомился с биржей Kwork, там были именно такие расценки.

    Здесь же только мечтал о больших проектах и какую стоимость мне за него называть. Начал искать информацию на тему, как посчитать стоимость работы.

    Кто-то говорил про сложные формулы типо «Посчитай сколько хочешь заработать за месяц, подели на дни, часы, минуты». Этот вариант я не рассматривал, так как у меня была основная работа и верстка — как хобби, за которое я иногда получал деньги.

    Был вариант с Гугл-табличками, где записываешь количество простых блоков, которые нужно сверстать, сложных, слайдеров, табов, форм. Потом табличка считает и выдаёт такую стоимость, что я ахиревал и боялся назвать её заказчику, чтоб не спугнуть. Поэтому резал её в 2-3 раза, чтоб не наглеть и совесть была спокойна.

    На этом этапе максимальный заработок за один проект — 5 000 рублей. Это был лендинг на 10 блоков с калькулятором стоимости на 5 параметров.

    2. Сделал первый сайт на WordPress + ACF.

    Я прекрасно понимал, что зная только верстку, много не заработать, поэтому изучал весь известный материал в Ютубе.

    Сделал свой сайт-портфолио на WordPress + ACF, выгрузил на домен и практически сразу нашел работу. Делал сайты для веб-студии , вместе с разработчиком опытнее меня. Иногда он сам называл стоимость, которую может заплатить, иногда я называл. Первый сайт от этого разработчика делал за 15 тысяч. Сайт до сих пор жив и в него не вносили правки. Ловите — lesenka52.ru. Через этого разработчика знакомился с другими веб-студиями, с которыми работал. Научился делать блог, сайт-каталог, лендинг, сделал 4-5 интернет-магазина на WordPress + WooCommerce.

    Здесь я начал считать простые и сложные блоки в макете. Количество простых блоки умножал на 500, сложных на 800, к получившийся сумме добавлял 50% за адаптив. Так и высчитывал финальную стоимость за весь сайт. С совестью было +- всё в порядке, только иногда сбрасывал 20-30%, так как считал, что слишком много получается. И что интересно, меня не смущало большое количество работы в проекте, я переживал назвать большую стоимость.

    В это время я начал применять одно простое решение, которым пользуюсь до сих пор. Когда в работе 3-4 проекта одновременно, то я повышал стоимость работы на 20-40%. 

    Например, у меня сейчас в работе 3 проекта по 40 тысяч, я не хочу брать ещё один. Поэтому следующему клиенту считаю стоимость как обычно и докидываю 40%. Если заказчик сольётся, то ничего страшного, у меня и так есть чем заняться, а если нет, то «нихера себе сколько денег можно взять с одного проекта». Так я перешел от 15 до 90 тысяч за один проект.

    Был один большой минус. Иногда, на подсчёт всего макета, уходило полтора часа и заказчик передавал проект не мне. В итоге, только зря потратил время на изучение макета.

    На этом этапе максимальный заработок за один проект — 90 тысяч рублей. Блог под NDA для онлайн-школы. Версткой занимался другой человек, от меня только работа с WordPress.

    3. Прошел обучение по React, перестал бояться работать с JS и API.

    Здесь я научился делать ajax-запросы и фильтры любой сложности через PHP функции WordPress или с помощью JQuery, прокачался в работе с API. Интересно то, что пока я не разобрался как делать фильтры для сайта, мне не приходили заказы на сайты с фильтром. Мэджик.

    На этом этапе я начал делать более сложные проекты, где нельзя обойтись ACF плагином и немного JS. Большенство проектов стали под лэйблом NDA. В это время я перестал заниматься подсчётом блоков.

    Я приблизительно понимал, что мне нужно сделать в проекте и сколько я потрачу времени. Скорее всего что-то подобное я уже делал, поэтому могу назвать стоимость чуть больше, чем в прошлый раз и сбросить несколько ссылок на похожие проекты. Дальше дело за заказчиком.

    На этом этапе максимальный заработок за один проект составил 325 тысяч рублей. Это был своеобразный сайт со статьями на русском, английском и китайском языках для музея, где-то в России. Его нет в сети, его используют на сенсорных планшетах размером формата A0 в музее

    4. Разобрался как делать сайты под ключ.

    Здесь я прошел наставничество, разобрался как делать сайты под ключ, сделал несколько проектов с нуля до запуска.

    Сейчас я делаю только несколько видов проектов и могу назвать стоимость за свою работу практически сразу. Как в магазине, есть товар и цена, если человеку подходит — он берёт.

    Всё чаще люди просят указывать час работы, а не общую сумму за проект. Здесь обошелся без хитрых формул, просто спросил у других фрилансеров, сколько берут за час.

    Когда делал сайты за 15 тысяч — некоторым было дорого, сейчас делаю от 150 тысяч и всё так же, некоторым дорого. Вадим, Борис и Вика делают лендинги на тильде от 150 тысяч, им тоже говорят что дорого, но они продолжают повышать стоимость своей работы.