New_Micro_Learning/backend/app/Modules/Courses/Application/BlockRegistry.php

181 خطوط
17 KiB
PHP
خام سرزنش تاریخچه

مخزن ambiguous runes header

راهنمای مخزن ambiguous runes توضیحات

<?php
namespace App\Modules\Courses\Application;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
final class BlockRegistry
{
/** @var array<string, BlockDefinition> */
private array $definitions;
public function __construct(private readonly RichTextSanitizer $richText)
{
$this->definitions = collect($this->definitions())
->keyBy(fn (BlockDefinition $definition) => $definition->type)
->all();
}
/** @return list<array<string, mixed>> */
public function metadata(): array
{
return array_values(array_map(
fn (BlockDefinition $definition) => $definition->metadata(),
$this->definitions,
));
}
public function definition(string $type): BlockDefinition
{
$definition = $this->definitions[$type] ?? null;
if (! $definition) {
throw ValidationException::withMessages(['type' => ['The selected block type is not registered.']]);
}
return $definition;
}
/** @param array<string, mixed> $data @return array<string, mixed> */
public function validate(string $type, int $schemaVersion, array $data): array
{
$definition = $this->definition($type);
if ($schemaVersion !== $definition->schemaVersion) {
throw ValidationException::withMessages([
'schemaVersion' => ["Schema version {$schemaVersion} is not supported for {$type}."],
]);
}
$allowedFields = collect(array_keys($definition->rules))
->map(fn (string $field): string => str($field)->before('.')->toString())
->unique()
->all();
$unknownFields = array_diff(array_keys($data), $allowedFields);
if ($unknownFields !== []) {
throw ValidationException::withMessages(collect($unknownFields)
->mapWithKeys(fn (string $field): array => ["data.{$field}" => ['This field is not defined by the registered block schema.']])
->all());
}
$validated = Validator::make($data, $definition->rules)->validate();
if ($type === 'text') {
$validated['html'] = $this->richText->sanitize($validated['html']);
}
return $validated;
}
/** @param array<string, mixed> $data @return array{version: int, data: array<string, mixed>} */
public function migrate(string $type, int $schemaVersion, array $data): array
{
return $this->definition($type)->migrate($schemaVersion, $data);
}
/** @return list<BlockDefinition> */
private function definitions(): array
{
return [
new BlockDefinition('heading', 'basic', 'عنوان', 'heading', 1,
['text' => 'عنوان بخش', 'level' => 2],
['text' => ['required', 'string', 'max:240'], 'level' => ['required', 'integer', 'between:1,3']]),
new BlockDefinition('text', 'basic', 'متن', 'text', 1,
['html' => '<p>متن خود را اینجا بنویسید.</p>'],
['html' => ['required', 'string', 'max:50000']], ['searchable']),
new BlockDefinition('quote', 'basic', 'نقل‌قول', 'quote', 1,
['text' => 'متن نقل‌قول', 'cite' => ''],
['text' => ['required', 'string', 'max:3000'], 'cite' => ['nullable', 'string', 'max:240']]),
new BlockDefinition('key_point', 'basic', 'نکته کلیدی', 'lightbulb', 1,
['title' => 'نکته کلیدی', 'body' => 'پیام مهم این بخش'],
['title' => ['required', 'string', 'max:160'], 'body' => ['required', 'string', 'max:2000']]),
new BlockDefinition('divider', 'basic', 'جداکننده', 'minus', 1,
['label' => ''], ['label' => ['nullable', 'string', 'max:120']]),
new BlockDefinition('button', 'basic', 'دکمه', 'mouse-pointer-click', 1,
['label' => 'ادامه', 'url' => null, 'openInNewTab' => false],
['label' => ['required', 'string', 'max:120'], 'url' => ['nullable', 'url', 'max:2048'], 'openInNewTab' => ['required', 'boolean']], ['click']),
new BlockDefinition('image', 'media', 'تصویر', 'image', 1,
['assetId' => null, 'url' => null, 'alt' => '', 'caption' => '', 'decorative' => false],
['assetId' => ['nullable', 'string'], 'url' => ['nullable', 'url', 'max:2048'], 'alt' => ['nullable', 'string', 'max:300'], 'caption' => ['nullable', 'string', 'max:500'], 'decorative' => ['required', 'boolean']], ['downloadable']),
new BlockDefinition('gallery', 'media', 'گالری', 'images', 1,
['items' => [], 'layout' => 'grid'],
['items' => ['present', 'array', 'max:12'], 'items.*.assetId' => ['required', 'string'], 'items.*.alt' => ['nullable', 'string', 'max:300'], 'items.*.caption' => ['nullable', 'string', 'max:500'], 'layout' => ['required', 'in:grid,carousel']], ['interaction']),
new BlockDefinition('video', 'media', 'ویدئو', 'video', 1,
['assetId' => null, 'url' => null, 'title' => '', 'transcript' => '', 'captionsUrl' => null],
['assetId' => ['nullable', 'string'], 'url' => ['nullable', 'url', 'max:2048'], 'title' => ['nullable', 'string', 'max:240'], 'transcript' => ['nullable', 'string', 'max:50000'], 'captionsUrl' => ['nullable', 'url', 'max:2048']], ['completion_tracking']),
new BlockDefinition('audio', 'media', 'صوت', 'audio-lines', 1,
['assetId' => null, 'url' => null, 'title' => '', 'transcript' => ''],
['assetId' => ['nullable', 'string'], 'url' => ['nullable', 'url', 'max:2048'], 'title' => ['nullable', 'string', 'max:240'], 'transcript' => ['nullable', 'string', 'max:50000']], ['completion_tracking']),
new BlockDefinition('document', 'media', 'سند', 'file-text', 1,
['assetId' => null, 'title' => 'سند', 'description' => ''],
['assetId' => ['nullable', 'string'], 'title' => ['required', 'string', 'max:240'], 'description' => ['nullable', 'string', 'max:1000']], ['downloadable']),
new BlockDefinition('embed', 'media', 'محتوای تعبیه‌شده', 'code-xml', 1,
['url' => null, 'title' => '', 'aspectRatio' => '16/9'],
['url' => ['nullable', 'url', 'max:2048'], 'title' => ['nullable', 'string', 'max:240'], 'aspectRatio' => ['required', 'in:16/9,4/3,1/1']], [], ['sandboxed_iframe'], ['pdf' => 'link', 'scorm' => 'native', 'mp4' => 'poster']),
new BlockDefinition('accordion', 'learning', 'آکاردئون', 'list-collapse', 1,
['items' => [['title' => 'عنوان', 'body' => 'توضیحات']]],
['items' => ['required', 'array', 'min:1', 'max:20'], 'items.*.title' => ['required', 'string', 'max:240'], 'items.*.body' => ['required', 'string', 'max:5000']]),
new BlockDefinition('flashcard', 'learning', 'فلش‌کارت', 'cards', 1,
['front' => 'پرسش', 'back' => 'پاسخ'],
['front' => ['required', 'string', 'max:2000'], 'back' => ['required', 'string', 'max:5000']], ['interaction']),
new BlockDefinition('tabs', 'learning', 'زبانه‌ها', 'panel-top', 1,
['items' => [['title' => 'زبانه اول', 'body' => 'محتوا']]],
['items' => ['required', 'array', 'min:1', 'max:8'], 'items.*.title' => ['required', 'string', 'max:120'], 'items.*.body' => ['required', 'string', 'max:5000']], ['interaction']),
new BlockDefinition('timeline', 'learning', 'خط زمانی', 'git-commit-horizontal', 1,
['items' => [['title' => 'مرحله اول', 'body' => 'توضیحات', 'date' => '']]],
['items' => ['required', 'array', 'min:1', 'max:20'], 'items.*.title' => ['required', 'string', 'max:160'], 'items.*.body' => ['nullable', 'string', 'max:3000'], 'items.*.date' => ['nullable', 'string', 'max:80']]),
new BlockDefinition('steps', 'learning', 'مراحل', 'list-ordered', 1,
['items' => [['title' => 'مرحله اول', 'body' => 'توضیحات']]],
['items' => ['required', 'array', 'min:1', 'max:20'], 'items.*.title' => ['required', 'string', 'max:160'], 'items.*.body' => ['nullable', 'string', 'max:3000']], ['progress']),
new BlockDefinition('process', 'learning', 'فرایند', 'workflow', 1,
['items' => [['title' => 'شروع', 'body' => 'توضیحات']]],
['items' => ['required', 'array', 'min:1', 'max:12'], 'items.*.title' => ['required', 'string', 'max:160'], 'items.*.body' => ['nullable', 'string', 'max:2000']]),
new BlockDefinition('checklist', 'learning', 'چک‌لیست', 'list-checks', 1,
['items' => [['text' => 'مورد اول', 'required' => true]]],
['items' => ['required', 'array', 'min:1', 'max:30'], 'items.*.text' => ['required', 'string', 'max:500'], 'items.*.required' => ['required', 'boolean']], ['interaction', 'completion_tracking']),
new BlockDefinition('single_choice', 'assessment', 'تک‌گزینه‌ای', 'circle-check', 1,
['prompt' => 'پرسش را وارد کنید', 'options' => ['گزینه اول', 'گزینه دوم'], 'answerIndex' => 0],
['prompt' => ['required', 'string', 'max:2000'], 'options' => ['required', 'array', 'min:2', 'max:10'], 'options.*' => ['required', 'string', 'max:500'], 'answerIndex' => ['required', 'integer', 'min:0']], ['assessment', 'evidence']),
new BlockDefinition('true_false', 'assessment', 'درست / نادرست', 'toggle-left', 1,
['prompt' => 'عبارت را وارد کنید', 'answer' => true],
['prompt' => ['required', 'string', 'max:2000'], 'answer' => ['required', 'boolean']], ['assessment', 'evidence']),
new BlockDefinition('multiple_choice', 'assessment', 'چندگزینه‌ای', 'list-checks', 1,
['prompt' => 'پرسش را وارد کنید', 'options' => ['گزینه اول', 'گزینه دوم'], 'answerIndexes' => [0]],
['prompt' => ['required', 'string', 'max:2000'], 'options' => ['required', 'array', 'min:2', 'max:12'], 'options.*' => ['required', 'string', 'max:500'], 'answerIndexes' => ['required', 'array', 'min:1'], 'answerIndexes.*' => ['integer', 'min:0']], ['assessment', 'evidence']),
new BlockDefinition('matching', 'assessment', 'تطبیقی', 'rows-3', 1,
['prompt' => 'موارد مرتبط را به هم وصل کنید', 'pairs' => [['left' => 'عبارت اول', 'right' => 'پاسخ اول'], ['left' => 'عبارت دوم', 'right' => 'پاسخ دوم']]],
['prompt' => ['required', 'string', 'max:2000'], 'pairs' => ['required', 'array', 'min:2', 'max:12'], 'pairs.*.left' => ['required', 'string', 'max:500'], 'pairs.*.right' => ['required', 'string', 'max:500']], ['assessment', 'evidence', 'interaction']),
new BlockDefinition('sorting', 'assessment', 'مرتب‌سازی', 'arrow-down-up', 1,
['prompt' => 'موارد را به ترتیب صحیح بچینید', 'items' => [['text' => 'مرحله اول'], ['text' => 'مرحله دوم']]],
['prompt' => ['required', 'string', 'max:2000'], 'items' => ['required', 'array', 'min:2', 'max:12'], 'items.*.text' => ['required', 'string', 'max:500']], ['assessment', 'evidence', 'interaction']),
new BlockDefinition('drag_drop', 'assessment', 'کشیدن و رهاکردن', 'move', 1,
['prompt' => 'هر مورد را در مقصد صحیح قرار دهید', 'items' => [['text' => 'آیتم اول', 'target' => 'گروه اول'], ['text' => 'آیتم دوم', 'target' => 'گروه دوم']]],
['prompt' => ['required', 'string', 'max:2000'], 'items' => ['required', 'array', 'min:2', 'max:16'], 'items.*.text' => ['required', 'string', 'max:500'], 'items.*.target' => ['required', 'string', 'max:160']], ['assessment', 'evidence', 'interaction']),
new BlockDefinition('hotspot', 'assessment', 'نقطه داغ', 'scan', 1,
['prompt' => 'نقطه صحیح را روی تصویر انتخاب کنید', 'assetId' => null, 'hotspots' => [['label' => 'نقطه صحیح', 'x' => 50, 'y' => 50, 'radius' => 10, 'correct' => true]]],
['prompt' => ['required', 'string', 'max:2000'], 'assetId' => ['nullable', 'string'], 'hotspots' => ['required', 'array', 'min:1', 'max:12'], 'hotspots.*.label' => ['required', 'string', 'max:160'], 'hotspots.*.x' => ['required', 'numeric', 'between:0,100'], 'hotspots.*.y' => ['required', 'numeric', 'between:0,100'], 'hotspots.*.radius' => ['required', 'numeric', 'between:1,40'], 'hotspots.*.correct' => ['required', 'boolean']], ['assessment', 'evidence', 'interaction']),
new BlockDefinition('scenario', 'assessment', 'سناریو', 'messages-square', 1,
['prompt' => 'در این موقعیت چه می‌کنید؟', 'context' => 'موقعیت را شرح دهید.', 'choices' => [['text' => 'انتخاب اول', 'feedback' => 'بازخورد انتخاب اول', 'score' => 1], ['text' => 'انتخاب دوم', 'feedback' => 'بازخورد انتخاب دوم', 'score' => 0]]],
['prompt' => ['required', 'string', 'max:2000'], 'context' => ['required', 'string', 'max:5000'], 'choices' => ['required', 'array', 'min:2', 'max:8'], 'choices.*.text' => ['required', 'string', 'max:1000'], 'choices.*.feedback' => ['required', 'string', 'max:3000'], 'choices.*.score' => ['required', 'numeric', 'between:0,1']], ['assessment', 'evidence', 'interaction']),
new BlockDefinition('branching_scenario', 'assessment', 'سناریوی شاخه‌ای', 'git-branch', 1,
['prompt' => 'سناریوی شاخه‌ای', 'startNodeId' => 'start', 'nodes' => [['id' => 'start', 'type' => 'scene', 'title' => 'شروع', 'body' => 'صحنه آغازین', 'targetNodeId' => 'result'], ['id' => 'result', 'type' => 'result', 'title' => 'نتیجه', 'body' => 'پایان سناریو', 'targetNodeId' => '']]],
['prompt' => ['required', 'string', 'max:2000'], 'startNodeId' => ['required', 'string', 'max:80'], 'nodes' => ['required', 'array', 'min:2', 'max:80'], 'nodes.*.id' => ['required', 'string', 'distinct', 'max:80'], 'nodes.*.type' => ['required', 'in:scene,question,result'], 'nodes.*.title' => ['required', 'string', 'max:240'], 'nodes.*.body' => ['nullable', 'string', 'max:5000'], 'nodes.*.targetNodeId' => ['nullable', 'string', 'max:80']], ['assessment', 'evidence', 'interaction']),
new BlockDefinition('interactive_image', 'assessment', 'تصویر تعاملی', 'image-up', 1,
['assetId' => null, 'alt' => '', 'markers' => [['label' => 'نقطه ۱', 'body' => 'توضیحات', 'x' => 50, 'y' => 50]]],
['assetId' => ['nullable', 'string'], 'alt' => ['nullable', 'string', 'max:300'], 'markers' => ['required', 'array', 'min:1', 'max:20'], 'markers.*.label' => ['required', 'string', 'max:160'], 'markers.*.body' => ['nullable', 'string', 'max:2000'], 'markers.*.x' => ['required', 'numeric', 'between:0,100'], 'markers.*.y' => ['required', 'numeric', 'between:0,100']], ['interaction']),
new BlockDefinition('before_after', 'assessment', 'قبل و بعد', 'columns-2', 1,
['beforeAssetId' => null, 'afterAssetId' => null, 'beforeLabel' => 'قبل', 'afterLabel' => 'بعد', 'alt' => ''],
['beforeAssetId' => ['nullable', 'string'], 'afterAssetId' => ['nullable', 'string'], 'beforeLabel' => ['required', 'string', 'max:80'], 'afterLabel' => ['required', 'string', 'max:80'], 'alt' => ['nullable', 'string', 'max:300']], ['interaction']),
new BlockDefinition('columns', 'layout', 'ستون‌ها', 'columns-2', 1,
['preset' => '50/50', 'gap' => 'medium', 'mobileOrder' => 'logical', 'columns' => [['title' => 'ستون ۱', 'content' => ''], ['title' => 'ستون ۲', 'content' => '']]],
['preset' => ['required', 'in:50/50,33/67,67/33,three'], 'gap' => ['required', 'in:small,medium,large'], 'mobileOrder' => ['required', 'in:logical,reverse'], 'columns' => ['required', 'array', 'min:2', 'max:3'], 'columns.*.title' => ['nullable', 'string', 'max:120'], 'columns.*.content' => ['nullable', 'string', 'max:5000']]),
new BlockDefinition('section', 'layout', 'بخش', 'panel-top', 1,
['title' => 'بخش جدید', 'description' => ''],
['title' => ['required', 'string', 'max:240'], 'description' => ['nullable', 'string', 'max:1000']]),
new BlockDefinition('controlled_grid', 'layout', 'شبکه کنترل‌شده', 'layout-grid', 1,
['columns' => 3, 'items' => [['title' => 'کارت اول', 'body' => 'محتوا']]],
['columns' => ['required', 'integer', 'between:2,4'], 'items' => ['required', 'array', 'min:1', 'max:12'], 'items.*.title' => ['required', 'string', 'max:160'], 'items.*.body' => ['nullable', 'string', 'max:3000']]),
];
}
}