Обучение. Рефакторинг
This commit is contained in:
parent
037d43857d
commit
bae941e155
@ -2,12 +2,47 @@
|
||||
|
||||
namespace DarksLight2\Training;
|
||||
|
||||
use DarksLight2\Helpers\Str;
|
||||
use User;
|
||||
|
||||
abstract class StepFactory
|
||||
{
|
||||
|
||||
abstract public function getTitle(): string;
|
||||
abstract public function getMessage(): string;
|
||||
abstract public function getShortName(): string;
|
||||
abstract public function getRewards(): array;
|
||||
public function onLocations(): array
|
||||
{
|
||||
return ['all'];
|
||||
}
|
||||
|
||||
public function getShortName(): string
|
||||
{
|
||||
return Str::snakeCase(get_class($this));
|
||||
}
|
||||
public function getAnswer()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function allowedToMove(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function isInfo(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function databaseRecord()
|
||||
{
|
||||
return [
|
||||
'completed' => false,
|
||||
'progress' => [
|
||||
'current' => 0,
|
||||
'need' => 1,
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
}
|
@ -3,26 +3,129 @@
|
||||
namespace DarksLight2\Training;
|
||||
|
||||
use Core\Db;
|
||||
use DarksLight2\Training\Steps\ChatFirstStep;
|
||||
use DarksLight2\Training\Steps\MyUserFirstQuest;
|
||||
use DarksLight2\Training\Steps\MyUserFirstStep;
|
||||
use DarksLight2\Training\Steps\MyUserFourthQuest;
|
||||
use DarksLight2\Training\Steps\MyUserFourthStep;
|
||||
use DarksLight2\Training\Steps\MyUserSecondQuest;
|
||||
use DarksLight2\Training\Steps\MyUserSecondStep;
|
||||
use DarksLight2\Training\Steps\MyUserThirdQuest;
|
||||
use DarksLight2\Training\Steps\MyUserThirdStep;
|
||||
use DarksLight2\Traits\Singleton;
|
||||
use PassGen;
|
||||
use PDO;
|
||||
use stdClass;
|
||||
use User;
|
||||
|
||||
class TrainingManager
|
||||
{
|
||||
|
||||
use Singleton;
|
||||
|
||||
private $database_records = false;
|
||||
private array $steps_data;
|
||||
private int $user_id;
|
||||
private array $steps;
|
||||
public string $current_step = '';
|
||||
private array $registered_steps;
|
||||
private array $completed_steps;
|
||||
private array $active_steps;
|
||||
|
||||
public function __construct(int $user_id)
|
||||
private $database;
|
||||
|
||||
public function __construct(int $user_id, $refresh_token = true)
|
||||
{
|
||||
try {
|
||||
$this->register([
|
||||
new ChatFirstStep(),
|
||||
new MyUserFirstStep(),
|
||||
new MyUserFirstQuest(),
|
||||
new MyUserSecondStep(),
|
||||
new MyUserSecondQuest(),
|
||||
new MyUserThirdStep(),
|
||||
new MyUserThirdQuest(),
|
||||
new MyUserFourthStep(),
|
||||
new MyUserFourthQuest(),
|
||||
]);
|
||||
} catch (TrainingException $e) {
|
||||
}
|
||||
|
||||
$this->user_id = $user_id;
|
||||
|
||||
if($refresh_token) {
|
||||
$this->generateToken();
|
||||
}
|
||||
|
||||
$this->database = Db::getRow('SELECT * FROM user_training WHERE user_id = ?', [$user_id]);
|
||||
|
||||
$this->createDatabaseRecord();
|
||||
|
||||
$this->database['data'] = json_decode($this->database['data'], true);
|
||||
|
||||
$this->selectActiveSteps();
|
||||
}
|
||||
|
||||
public function getCurrentStepName(): string
|
||||
{
|
||||
if(empty($this->current_step)) {
|
||||
$this->current_step = array_key_first($this->active_steps) ?? '';
|
||||
}
|
||||
|
||||
return $this->current_step;
|
||||
}
|
||||
public function getRegistered(): array
|
||||
{
|
||||
return $this->registered_steps;
|
||||
}
|
||||
|
||||
public function getDatabaseData()
|
||||
{
|
||||
return $this->database;
|
||||
}
|
||||
|
||||
public function addPoint($short_name, $closure = null)
|
||||
{
|
||||
if($short_name === $this->getCurrentStepName()) {
|
||||
$this->database['data'][$short_name]['progress']['current']++;
|
||||
if(isset($closure)) {
|
||||
$closure($this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function nextStep()
|
||||
{
|
||||
|
||||
if($this->database['data'][$this->getCurrentStepName()]['progress']['need'] <= $this->database['data'][$this->getCurrentStepName()]['progress']['current']) {
|
||||
$this->database['data'][$this->getCurrentStepName()]['progress']['current'] = 0;
|
||||
$this->database['data'][$this->getCurrentStepName()]['completed'] = true;
|
||||
unset($this->active_steps[$this->getCurrentStepName()]);
|
||||
$this->current_step = array_key_first($this->active_steps) ?? '';
|
||||
$this->database['current'] = $this->getCurrentStepName();
|
||||
}
|
||||
}
|
||||
|
||||
public function previousStep()
|
||||
{
|
||||
$this->current_step = end($this->completed_steps) ?? '';
|
||||
$this->database['current'] = $this->current_step;
|
||||
$this->database['data'][$this->getCurrentStepName()]['completed'] = false;
|
||||
}
|
||||
|
||||
public function selectActiveSteps()
|
||||
{
|
||||
foreach ($this->database['data'] as $step_name => $data) {
|
||||
if($data['completed'] === false) {
|
||||
$this->active_steps[$step_name] = $data;
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->completed_steps[] = $step_name;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
Db::sql('UPDATE user_training SET data = ?, current = ? WHERE user_id = ?', [json_encode($this->database['data']), $this->database['current'], $this->user_id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StepFactory[] $steps
|
||||
*
|
||||
@ -37,77 +140,14 @@ class TrainingManager
|
||||
|
||||
foreach ($steps as $step) {
|
||||
if($step instanceof StepFactory) {
|
||||
$this->steps[$step->getShortName()] = new class (
|
||||
$step->getMessage(),
|
||||
$step->getTitle(),
|
||||
$step->getRewards(),
|
||||
$this->stepData($step->getShortName())->complete,
|
||||
$step->getShortName(),
|
||||
$this->stepData($step->getShortName())->progress
|
||||
) {
|
||||
public string $message;
|
||||
public string $title;
|
||||
public string $short_name;
|
||||
public array $rewards;
|
||||
public bool $isComplete;
|
||||
|
||||
public stdClass $progress;
|
||||
|
||||
public function __construct(
|
||||
string $message,
|
||||
string $title,
|
||||
array $rewards,
|
||||
bool $isComplete,
|
||||
string $short_name,
|
||||
stdClass $progress
|
||||
) {
|
||||
$this->rewards = $rewards;
|
||||
$this->title = $title;
|
||||
$this->isComplete = $isComplete;
|
||||
$this->message = $message;
|
||||
$this->short_name = $short_name;
|
||||
$this->progress = $progress;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
if(!$this->isComplete) {
|
||||
$path = $_SERVER['DOCUMENT_ROOT'] . '/modules_data/steps/' . $this->short_name . '.php';
|
||||
|
||||
if (file_exists($path)) {
|
||||
require $path;
|
||||
return;
|
||||
}
|
||||
|
||||
throw TrainingException::noRenderingFile();
|
||||
}
|
||||
}
|
||||
};
|
||||
$this->registered_steps[$step->getShortName()] = $step;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function stepData(string $short_name): object
|
||||
{
|
||||
$data = $this->getDatabaseRecords()->data;
|
||||
|
||||
if(!isset($data->$short_name)) {
|
||||
$this->updateDatabaseRecord($short_name);
|
||||
$data = $this->getDatabaseRecords()->data;
|
||||
}
|
||||
|
||||
return $data->$short_name;
|
||||
}
|
||||
|
||||
private function generateToken($length = 16)
|
||||
{
|
||||
$letters = 'abcdefgmikiHGJKLjkGASysj7603456';
|
||||
|
||||
$token = '';
|
||||
|
||||
for ($i = 0; $i <= $length; $i++) {
|
||||
$token .= $letters[rand(0, strlen($letters))];
|
||||
}
|
||||
$token = PassGen::new($length);
|
||||
|
||||
Db::run('UPDATE user_training SET api_token = ? WHERE user_id = ?', [
|
||||
$token,
|
||||
@ -115,87 +155,42 @@ class TrainingManager
|
||||
]);
|
||||
}
|
||||
|
||||
public function getDatabaseRecords(): stdClass
|
||||
{
|
||||
if(!$this->database_records) {
|
||||
$data = Db::run('SELECT * FROM user_training WHERE user_id = ?', [$this->user_id])
|
||||
->fetch(PDO::FETCH_OBJ);
|
||||
|
||||
$this->database_records = new stdClass();
|
||||
|
||||
$this->database_records->user_id = $data->user_id;
|
||||
$this->database_records->api_token = $data->api_token;
|
||||
$this->database_records->data = json_decode($data->data);
|
||||
}
|
||||
|
||||
return $this->database_records;
|
||||
}
|
||||
|
||||
public function updateDatabaseRecord(string $short_name)
|
||||
{
|
||||
if($this->getDatabaseRecords()) {
|
||||
|
||||
$this->database_records->data->$short_name = $this->firstRecordData()->$short_name;
|
||||
|
||||
Db::run('UPDATE user_training SET data = ? WHERE user_id = ?', [
|
||||
json_encode($this->database_records->data),
|
||||
$this->user_id
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function createDatabaseRecord()
|
||||
{
|
||||
if(!$this->getDatabaseRecords()) {
|
||||
Db::run('INSERT INTO user_training (user_id, data) VALUES (?, ?)', [
|
||||
if(!$this->database) {
|
||||
|
||||
$data = [];
|
||||
|
||||
foreach ($this->registered_steps as $step) {
|
||||
$data[$step->getShortName()] = $step->databaseRecord();
|
||||
}
|
||||
|
||||
Db::sql('INSERT INTO user_training (user_id, data, current) VALUES (?, ?, ?)', [
|
||||
$this->user_id,
|
||||
json_encode($this->firstRecordData())
|
||||
json_encode($data, true),
|
||||
array_key_first($data)
|
||||
]);
|
||||
|
||||
$this->database = Db::getRow('SELECT * FROM user_training WHERE user_id = ?', [$this->user_id]);
|
||||
}
|
||||
}
|
||||
|
||||
private function firstRecordData(): stdClass
|
||||
/**
|
||||
* @throws \DarksLight2\Training\TrainingException
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
$data = new stdClass();
|
||||
$short_names = [
|
||||
'first_step',
|
||||
'second_step'
|
||||
];
|
||||
if(in_array(User::start()->room['file'], $this->registered_steps[$this->getCurrentStepName()]->onLocations()) || in_array('all', $this->registered_steps[$this->getCurrentStepName()]->onLocations())) {
|
||||
$path = $_SERVER['DOCUMENT_ROOT'] . '/modules_data/steps/step.php';
|
||||
|
||||
foreach ($short_names as $name) {
|
||||
$data->$name->complete = false;
|
||||
$data->$name->progress->current = 0;
|
||||
$data->$name->progress->need = 1;
|
||||
if (file_exists($path)) {
|
||||
$short_name = $this->getCurrentStepName();
|
||||
$answer = $this->registered_steps[$this->getCurrentStepName()]->getAnswer();
|
||||
require $path;
|
||||
return;
|
||||
}
|
||||
|
||||
return $data;
|
||||
throw TrainingException::noRenderingFile();
|
||||
}
|
||||
public function __get(string $name)
|
||||
{
|
||||
return $this->steps[$name];
|
||||
}
|
||||
|
||||
public function addPoint(string $short_name)
|
||||
{
|
||||
$this->database_records->data->$short_name->progress->current++;
|
||||
}
|
||||
|
||||
public function nextStep()
|
||||
{
|
||||
foreach ($this->database_records->data as $step) {
|
||||
if($step->complete === false && $step->progress->current >= $step->progress->need) {
|
||||
$step->complete = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function store()
|
||||
{
|
||||
$this->nextStep();
|
||||
|
||||
Db::run('UPDATE user_training SET data = ? WHERE user_id = ?', [
|
||||
json_encode($this->database_records->data),
|
||||
$this->user_id
|
||||
]);
|
||||
}
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
<?php
|
||||
|
||||
use DarksLight2\Training\Steps\ChatFirstStep;
|
||||
use DarksLight2\Training\Steps\MyUserFirstQuest;
|
||||
use DarksLight2\Training\TrainingException;
|
||||
use DarksLight2\Training\TrainingManager;
|
||||
|
||||
header('Content-type: application/json');
|
||||
@ -16,10 +19,19 @@ if($input = file_get_contents("php://input")) {
|
||||
|
||||
if(!empty($user->info)) {
|
||||
|
||||
$training = TrainingManager::getInstance($user->info['id']);
|
||||
$training = TrainingManager::getInstance($user->info['id'], false);
|
||||
|
||||
try {
|
||||
$training->createDatabaseRecord();
|
||||
$training->register([
|
||||
new MyUserFirstQuest(),
|
||||
new ChatFirstStep()
|
||||
]);
|
||||
} catch (TrainingException $e) {
|
||||
}
|
||||
|
||||
if (!isset($data->token) || !password_verify(
|
||||
$training->getDatabaseRecords()->api_token . $data->time . $user->info['id'],
|
||||
$training->getDatabaseData()->api_token . $data->time . $user->info['id'],
|
||||
$data->token
|
||||
)) {
|
||||
http_response_code(401);
|
||||
@ -32,16 +44,27 @@ if(!empty($user->info)) {
|
||||
http_response_code(400);
|
||||
die;
|
||||
case 'complete':
|
||||
if ($training->{$data->short_name}->isComplete) {
|
||||
if ($training->getDatabaseData()['data'][$training->getCurrentStepName()]['completed']) {
|
||||
http_response_code(400);
|
||||
die;
|
||||
}
|
||||
|
||||
$training->addPoint($data->short_name);
|
||||
if($training->getRegistered()[$data->short_name]->isInfo()) {
|
||||
|
||||
$training->addPoint($training->getCurrentStepName());
|
||||
}
|
||||
|
||||
$training->nextStep();
|
||||
|
||||
$training->store();
|
||||
|
||||
die(json_encode(['status' => 'ok']));
|
||||
|
||||
case 'go_back':
|
||||
$training->previousStep();
|
||||
$training->store();
|
||||
die(json_encode(['status' => 'ok', 'message' => 'Вы указал не верный ответ!']));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +1,44 @@
|
||||
const request = new XMLHttpRequest();
|
||||
|
||||
const training_handler = () => {
|
||||
request.open('POST', '/api/training/complete')
|
||||
|
||||
const url = (condition) => {
|
||||
return condition ? '/api/training/complete' : '/api/training/go_back';
|
||||
};
|
||||
|
||||
request.open('POST', url(training_data().answer === null || training_data().answer === get_user_answer()))
|
||||
request.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
||||
request.onreadystatechange = function() {//Call a function when the state changes.
|
||||
if(request.readyState === 4 && request.status === 200) {
|
||||
request.onreadystatechange = function () {//Call a function when the state changes.
|
||||
if (request.readyState === 4) {
|
||||
if(request.status === 200) {
|
||||
parent.frames['main'].location.reload();
|
||||
}
|
||||
|
||||
if(request.responseText !== '') {
|
||||
let response = JSON.parse(request.responseText);
|
||||
|
||||
if (response.message !== undefined) {
|
||||
alert(response.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
request.send(JSON.stringify({
|
||||
time: training_data().time,
|
||||
token: training_data().token,
|
||||
short_name: training_data().short_name
|
||||
short_name: training_data().short_name,
|
||||
answer: get_user_answer(),
|
||||
}))
|
||||
|
||||
}
|
||||
|
||||
let active_training = null;
|
||||
function get_user_answer() {
|
||||
if(training_data().answer === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return document.getElementsByName('user_answer')[0].value;
|
||||
}
|
||||
|
||||
(function () {
|
||||
if (typeof window.CustomEvent === "function") return false;
|
||||
@ -44,20 +65,24 @@ $modal = function (options) {
|
||||
elemModal = document.createElement('div'),
|
||||
modalTemplate = '<div class="modal__backdrop" data-dismiss="modal"><div class="modal__content"><div class="modal__header"><div class="modal__title" data-modal="title">{{title}}</div><span class="modal__btn-close" data-dismiss="modal" title="Закрыть">×</span></div><div id="modal_content" class="modal__body" data-modal="content">{{content}}</div>{{footer}}</div></div>',
|
||||
modalFooterTemplate = '<div class="modal__footer">{{buttons}}</div>',
|
||||
modalInputAnswer = '',
|
||||
modalButtonTemplate = '<button type="button" class="{{button_class}}" data-handler={{button_handler}}>{{button_text}}</button>',
|
||||
modalHTML,
|
||||
modal_content,
|
||||
modalFooterHTML = '';
|
||||
|
||||
if(training_data().answer !== '') {
|
||||
modalInputAnswer = '<br><br><input name="user_answer" placeholder="Укажите ответ"/>';
|
||||
}
|
||||
|
||||
elemModal.classList.add('modal');
|
||||
modalHTML = modalTemplate.replace('{{title}}', options.title || 'Новое окно');
|
||||
//modalHTML = modalHTML.replace('{{content}}', options.content || '');
|
||||
|
||||
if (options.footerButtons) {
|
||||
for (var i = 0, length = options.footerButtons.length; i < length; i++) {
|
||||
var modalFooterButton = modalButtonTemplate.replace('{{button_class}}', options.footerButtons[i].class);
|
||||
modalFooterButton = modalFooterButton.replace('{{button_handler}}', options.footerButtons[i].handler);
|
||||
modalFooterButton = modalFooterButton.replace('{{button_text}}', options.footerButtons[i].text);
|
||||
modalFooterButton = modalFooterButton.replace('{{button_text}}', options.footerButtons[i].text) + modalInputAnswer;
|
||||
modalFooterHTML += modalFooterButton;
|
||||
}
|
||||
modalFooterHTML = modalFooterTemplate.replace('{{buttons}}', modalFooterHTML);
|
||||
|
@ -1,16 +1,42 @@
|
||||
<?php
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
/**
|
||||
* @var string $short_name
|
||||
* @var $answer
|
||||
* @var \DarksLight2\Training\StepFactory $step
|
||||
*/
|
||||
|
||||
|
||||
$user = User::start();
|
||||
|
||||
use DarksLight2\Training\TrainingManager;
|
||||
|
||||
$manager = TrainingManager::getInstance();
|
||||
$step = $manager->$short_name;
|
||||
$step = $manager->getRegistered()[$short_name];
|
||||
$button_text = 'Продолжить';
|
||||
$time = time();
|
||||
|
||||
$token = password_hash($manager->getDatabaseRecords()->api_token . $time . $user->info['id'], PASSWORD_DEFAULT);
|
||||
$token = password_hash($manager->getDatabaseData()->api_token . $time . $user->info['id'], PASSWORD_DEFAULT);
|
||||
?>
|
||||
|
||||
<script>
|
||||
const training_data = () => {
|
||||
return {
|
||||
content: `<?=$step->getMessage()?>`,
|
||||
title: `<?=$step->getTitle()?>`,
|
||||
button_text: `<?=$button_text?>`,
|
||||
time: <?=$time?>,
|
||||
token: '<?=$token?>',
|
||||
short_name: '<?=$short_name?>',
|
||||
answer: '<?=$answer?>'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script src="/js/training/modal.js"></script>
|
||||
|
||||
<script>
|
||||
modal.show()
|
||||
</script>
|
||||
|
Loading…
Reference in New Issue
Block a user