pbQuiz — гибкий компонент квизов на контроллерах PageBlocks
        pbQuiz — это наглядный пример того, как с помощью PageBlocks можно построить гибкую многошаговую форму-квиз с пошаговой валидацией и хранением прогресса в сессии.
Вы можете использовать готовый бесплатный компонент или создать всё вручную — чтобы полностью понять, как работает структура под капотом.

Вы можете быстро запустить квиз двумя способами:
pbQuiz — это готовый бесплатный компонент, который можно установить через PageBlocks.
Что для этого нужно:
Если вы хотите глубже понять, как работает квиз внутри PageBlocks, или адаптировать его под свои задачи — ниже приведён детальный пример, который показывает:
У каждого шага свой заголовок, описание и набор полей.
Режим менеджера
1.1 Создайте необходимые таблицы

1.2 Добавьте меню

Режим разработчика
Для каждого шага квиза добавьте новую строку с полями:

Что они делают?
Этот метод:
Метод getData() — получить данные шага
Достаёт из таблицы все поля для текущего шага.
Метод prev() — шаг назад
Когда пользователь жмёт «Назад»:
Метод next() — шаг вперёд
Когда пользователь жмёт «Далее»:
Метод getStep() — сердце квиза
Здесь всё происходит:
1. Основной шаблон формы
Вот базовый пример формы, которая уже готова к работе через AJAX с PageBlocks:
Как это работает:
pb-form
Этот атрибут подключает автоматическую обработку формы через AJAX
_token
Защищает от CSRF — каждый запрос проходит проверку безопасности.
honeypot
Простая защита от ботов. Если поле заполнено — запрос отклоняется.
pb-success-message
Этот атрибут указывает PageBlocks, куда вставить HTML ответа, который вернёт контроллер. В вашем случае контроллер возвращает сгенерированный чанк step для следующего шага квиза. Поэтому старый шаг автоматически заменяется на новый — без полной перезагрузки страницы.
2. Чанк step для рендеринга шагов
Чанк отвечает за то, как выводятся заголовок, описание и поля для текущего шага. Пример базового чанка:
3. Чанк nav для кнопок «Назад» и «Далее»
Что в итоге
Теперь у вас есть два удобных варианта:
    
    
                                                        Вы можете использовать готовый бесплатный компонент или создать всё вручную — чтобы полностью понять, как работает структура под капотом.

Что умеет pbQuiz
- Создавать квиз из любого количества шагов.
- Гибко задавать поля: radio, checkbox, текстовые и email.
- Проверять введённые данные на каждом шаге.
- Хранить введённые данные между шагами.
- Отправлять результаты менеджеру и пользователю.
- Полностью управляться через таблицы PageBlocks
Как настроить
Вы можете быстро запустить квиз двумя способами:
1. Использовать готовый бесплатный компонент
pbQuiz — это готовый бесплатный компонент, который можно установить через PageBlocks.
Что для этого нужно:
- Установите компонент PageBlocks, если он ещё не установлен.
- Установите pbQuiz — он автоматически создаст:
- все необходимые контроллеры,
- меню с таблицей,
- базовую структуру квиза.
2. Сделать квиз вручную
Если вы хотите глубже понять, как работает квиз внутри PageBlocks, или адаптировать его под свои задачи — ниже приведён детальный пример, который показывает:
- как шаг за шагом собрать таблицу,
- какие маршруты подключить,
- как написать свой QuizController,
- как настроить шаблоны.
Шаг 1. Создайте таблицу для квиза
В этой таблице вы будете создавать шаги квиза.У каждого шага свой заголовок, описание и набор полей.
Режим менеджера
1.1 Создайте необходимые таблицы

1.2 Добавьте меню

Режим разработчика
Menu::make('pbquiz')
    ->title('pbQuiz')
    ->description('Quiz for PageBlocks')
    ->position(5)
    ->parent('pageblocks')
    ->fields([
        Field::make('pbquiz')
            ->label('Quiz')
            ->type('table')
            ->fields([
                Field::make('title')
                    ->label('Title')
                    ->required(),
                
                Field::make('description')
                    ->label('Description')
                    ->type('textarea'),
                
                Field::make('Image')
                    ->label('Image')
                    ->type('image')
                    ->source(1)
                    ->sourcePath('/assets/components/pbquiz/images/'),
                
                Field::make('fields')
                    ->label('Fields')
                    ->type('table')
                    ->fields([
                        Field::make('name')
                            ->label('Name')
                            ->width(50)
                            ->required(),
                        
                        Field::make('label')
                            ->label('Label')
                            ->width(50),
                        
                        Field::make('type')
                            ->label('Field type')
                            ->type('select')
                            ->options([
                                'radio' => 'Radio',
                                'checkbox' => 'Checkbox',
                                'text' => 'Text',
                                'email' => 'Email',
                            ])
                            ->width(50)
                            ->required(),
                        
                        Field::make('placeholder')
                            ->label('Placeholder')
                            ->width(50),
                        
                        Field::make('options')
                            ->label('Values')
                            ->type('keyvalue')
                            ->keyLabel('Value')
                            ->valueLabel('Label'),
                        
                        Field::make('validation')
                            ->label('Validation')
                            ->width(50)
                            ->help('Docs: https://pageblocks.boshnik.com/docs/validation#available-validation-rules'),
                        
                        Field::make('validation_error')
                            ->label('Field Validation Error')
                            ->width(50),
                    ])
                    ->columns([
                        Column::make('Name'),
                        Column::make('Label'),
                        Column::make('Validation'),
                    ])
            ])
        ->columns([
            Column::make('Title'),
        ])
    ]);Шаг 2. Заполните данные квиза
После создания таблицы в админке откройте созданное меню pbQuiz.Для каждого шага квиза добавьте новую строку с полями:
- Title — заголовок шага.
- Description — описание или пояснение.
- Image — иллюстрация (если нужно).
- Fields — список вопросов или полей на шаге.
- системное имя (name),
- подпись для пользователя (label),
- тип поля (Field type) — radio, checkbox, text или email,
- опции значений (для radio/checkbox),
- плейсхолдер (для текстовых полей),
- правила валидации,
- сообщение об ошибке валидации.
- первым шагом указать обложку (приветственный экран),
- последним — финальный экран (сообщение о том, что данные успешно отправлены).

Шаг 3. Добавьте маршруты
Для работы квиза нужны три маршрута:Route::get('/', 'Quiz\QuizController@index')->name('pageQuiz');
Route::prefix('/pbquiz')->group(function () {
    Route::post('/prev', 'Quiz\QuizController@prev')->name('quizPrev');
    Route::post('/next', 'Quiz\QuizController@next')->name('quizNext');
});Что они делают?
- / Основной маршрут. Вызывает метод index в QuizController. Именно он выводит первый шаг квиза (или обложку).
- /pbquiz/prev POST-запрос на предыдущий шаг. Если пользователь решил вернуться назад — этот маршрут откатит шаг и покажет предыдущий экран.
- /pbquiz/next POST-запрос на следующий шаг. Пользователь заполнил поля, нажал «Далее» — данные валидируются и записываются в сессию. Если всё ок — грузится следующий шаг.
Шаг 4. Напишите контроллер
Метод index() — стартЭтот метод:
- Очищает старые данные из сессии.
- Получает первый шаг из таблицы.
- Передаёт данные в шаблон квиза.
public function index()
{
    $_SESSION['pbquiz'] = []; // сбрасываем всё
    $data = $this->getData(); // берём первый шаг
    return view('file:quiz/templates/quiz', $data);
}Метод getData() — получить данные шага
Достаёт из таблицы все поля для текущего шага.
public string $className = \pbTableValue::class;
public function getData(int $offset = 0, int $limit = 1)
{
    $query = $this->modx->newQuery($this->className);
    $query->select($this->modx->getSelectColumns($this->className, $this->className));
    $query->where([
        'field_name' => 'pbquiz',
        'published' => 1,
        'deleted' => 0,
    ]);
    $query->limit($limit, $offset);
    $query->sortby('menuindex', 'ASC');
    $query->prepare();
    $query->stmt->execute();
    if ($limit === 1) {
        $result = $query->stmt->fetch(\PDO::FETCH_ASSOC);
        $params = json_decode($result['values'], true) ?? [];
        $params['step'] = $offset;
        $params['total'] = $this->getTotal() - 2; // -2, потому что не считаем обложку и финал
    } else {
        $results = $query->stmt->fetchAll(\PDO::FETCH_ASSOC);
        $params = [];
        foreach ($results as $result) {
            $params[] = json_decode($result['values'], true) ?? [];
        }
    }
    return $params;
}
public function getTotal()
{
    return $this->modx->getCount($this->className, [
        'field_name' => 'pbquiz',
        'published' => 1,
        'deleted' => 0,
    ]);
}Метод prev() — шаг назад
Когда пользователь жмёт «Назад»:
- Шаг уменьшается на 1.
- Из сессии удаляются поля, введённые для этого шага.
- Сбрасывается валидация.
public function prev(Request $request)
{
    $step = $_SESSION['pbquiz']['step'] ?? 0;
    if ($step) {
        $step -= 1;
    }
    if ($_SESSION['pbquiz']['validation']['rules'] ?? false) {
        $_SESSION['pbquiz']['fields'] = array_diff_key(
            $_SESSION['pbquiz']['fields'],
            array_flip(array_keys($_SESSION['pbquiz']['validation']['rules']))
        );
    }
    $_SESSION['pbquiz']['validation'] = [];
    return $this->getStep($step, $request);
}Метод next() — шаг вперёд
Когда пользователь жмёт «Далее»:
- Берутся правила валидации из текущего шага.
- Сохраняются в сессию.
- Передаётся управление в getStep — он проверит данные, сохранит их и покажет следующий шаг.
public function next(Request $request)
{
    $step = $_SESSION['pbquiz']['step'] ?? 0;
    $data = $this->getData($step);
    $_SESSION['pbquiz']['validation'] = [];
    foreach ($data['fields'] ?? [] as $field) {
        $name = $field['name'];
        $_SESSION['pbquiz']['validation']['rules'][$name] = $field['validation'];
        $_SESSION['pbquiz']['validation']['errors'][$name] = $field['validation_error'];
    }
    return $this->getStep($step + 1, $request);
}Метод getStep() — сердце квиза
Здесь всё происходит:
- Валидация данных по правилам.
- Сохранение введённого в сессию.
- Получение следующего шага.
- Если шаг последний — отправка писем менеджеру и пользователю.
public function getStep(int $step, Request $request)
{
    if ($_SESSION['pbquiz']['validation']['rules'] ?? false) {
        $rules = ['honeypot' => 'empty|exclude'];
        foreach ($_SESSION['pbquiz']['validation']['rules'] as $name => $rule) {
            $rules[$name] = $rule;
        }
        $errors = $_SESSION['pbquiz']['validation']['errors'] ?? [];
        $field_errors = [];
        foreach ($errors as $name => $error) {
            $field_errors[$name . '.required'] = $error;
        }
        $validator = validate($request->all(), $rules, $field_errors);
        if ($validator->fails()) {
            return response()->error('', $validator->errors());
        }
        $_SESSION['pbquiz']['fields'] = array_merge(
            $_SESSION['pbquiz']['fields'] ?? [],
            $validator->validated()
        );
    }
    $prevStep = $_SESSION['pbquiz']['step'] ?? 0;
    $_SESSION['pbquiz']['step'] = $step;
    $data = $this->getData($step);
    $html = view('file:quiz/chunks/step', $data);
    if ($step > $data['total']) {
        $emails = config('quiz.email.manager');
        if (!empty($emails)) {
            foreach (explode(',', $emails) as $email) {
                Mail::to(trim($email))
                    ->subject(lang('quiz.email_manager_subject'))
                    ->view('file:quiz/chunks/email_manager', $_SESSION['pbquiz']['fields'])
                    ->send();
            }
        }
        if (!empty($_SESSION['pbquiz']['fields']['email'])) {
            Mail::to($_SESSION['pbquiz']['fields']['email'])
                ->subject(lang('quiz.email_user_subject'))
                ->view('file:quiz/chunks/email_user', $_SESSION['pbquiz']['fields'])
                ->send();
        }
    }
    return response()->append([
        'prevStep' => $prevStep,
        'step' => $step,
        'total' => $data['total'],
    ])->success($html);
}Шаг 5. Настройте шаблоны формы и шагов
После того как таблицы созданы, маршруты подключены, а контроллер написан — нужно сделать так, чтобы данные красиво отображались пользователю. Для этого используются шаблоны и чанки.1. Основной шаблон формы
Вот базовый пример формы, которая уже готова к работе через AJAX с PageBlocks:
html...
<form action="{route 'quizNext'}" method="post" class="quiz-form w-100 h-100" pb-form>
    <input type="hidden" name="_token" value="{csrf_token}">
    <input type="hidden" name="honeypot" value="">
    <div id="quiz-step" pb-success-message>
        {insert 'file:quiz/chunks/step'}
    </div>
</form>
...Как это работает:
pb-form
Этот атрибут подключает автоматическую обработку формы через AJAX
_token
Защищает от CSRF — каждый запрос проходит проверку безопасности.
honeypot
Простая защита от ботов. Если поле заполнено — запрос отклоняется.
pb-success-message
Этот атрибут указывает PageBlocks, куда вставить HTML ответа, который вернёт контроллер. В вашем случае контроллер возвращает сгенерированный чанк step для следующего шага квиза. Поэтому старый шаг автоматически заменяется на новый — без полной перезагрузки страницы.
2. Чанк step для рендеринга шагов
Чанк отвечает за то, как выводятся заголовок, описание и поля для текущего шага. Пример базового чанка:
{if $title}
  <h1>{$title}</h1>
{/if}
{if $description}
  <p>{$description}</p>
{/if}
{if $fields}
  <div class="quiz-fields">
    {foreach $fields as $field}
      {if $field['options']}
        <!-- Radio или Checkbox -->
        {foreach $field['options'] as $option}
          <label>
            <input type="{$field['type']}" 
                   name="{$field['name']}{if $field['type']=='checkbox'}[]{/if}" 
                   value="{$option['key']}">
            {$option['value']}
          </label>
        {/foreach}
      {else}
        <!-- Текстовое или Email -->
        <label>{$field['label']}</label>
        <input type="{$field['type']}" 
               name="{$field['name']}" 
               placeholder="{$field['placeholder']}">
      {/if}
    {/foreach}
  </div>
{/if}
<!-- Навигация -->
{if $step <= $total}
  {include 'file:quiz/chunks/nav'}
{/if}3. Чанк nav для кнопок «Назад» и «Далее»
<div class="form-nav d-flex gap-3 justify-content-between justify-content-md-start mt-5 mt-md-auto">
    {if $step == 0}
        <div class="col-12">
            <button type="button" class="btn btn-lg" pb-post="{route 'quizNext'}" pb-target="#pb-quiz" pb-expect="json">{lang 'quiz.start'}</button>
        </div>
    {/if}
    {if $step > 1}
        <div class="col-auto">
            <button type='button' class='btn btn-lg btn-nav btn-prev' pb-post="{route 'quizPrev'}" pb-target="#pb-quiz" pb-expect="json">{lang 'quiz.prev'}</button>
        </div>
    {/if}
    {if $step && $step < $total}
        <div class="col-auto">
            <button type='submit' class='btn btn-lg btn-nav btn-next'>{lang 'quiz.next'}</button>
        </div>
    {/if}
    {if $step == $total}
        <div class="col-auto">
            <button type='submit' class='btn btn-lg btn-nav btn-next'>{lang 'quiz.submit'}</button>
        </div>
    {/if}
</div>Что в итоге
- Один шаблон формы — для всего квиза.
- Один чанк — для рендеринга шагов и полей.
- Один чанк навигации — для управления переходами.
- Контроллер возвращает HTML шагов, валидацию и сохраняет прогресс.
- Всё хранится в сессии — пользователь может возвращаться назад.
Заключение
Теперь у вас есть два удобных варианта:
- Если вам нужно быстро запустить квиз без лишней разработки — используйте готовый бесплатный компонент pbQuiz. Он сразу создаст все необходимые таблицы, контроллеры и структуру квиза. Вам останется только заполнить данные через меню и запустить квиз на вашем сайте.
- Если вы хотите гибко настроить всё под свои задачи или лучше разобраться в работе PageBlocks — используйте пошаговую инструкцию из этого руководства. Она показывает, как создать таблицу, подключить маршруты, написать контроллер и настроить шаблоны формы.
            
                Поблагодарить автора            
            
                 Отправить деньги            
        
        
            Комментарии: 3
                Ветвления есть?            
                    
                Статью читали? В контроллерах вы сами указываете какой шаг вам нужно показывать, да что угодно, вы можете на любом шаге сделать редирект в зависимости от ответа.
Не хотите писать код, вот готовый компонент — Quiz
                    Не хотите писать код, вот готовый компонент — Quiz
                Вся экосистема PageBlocks вызывает огромное впечатление            
                    
                            Авторизуйтесь или зарегистрируйтесь, чтобы оставлять комментарии.
                    
             
                    
                    
                




