namespace App\Controller; use App\Entity\CalendarEvent; use App\Entity\Candidate; use App\Entity\Person; use App\Entity\Student; use App\Enum\CalendarEvent\Type; use App\Enum\Candidate\RegistrationType; use App\Enum\Student\Status; use App\Exception\Student\StudentToSubscriberStatusException; use App\Form\PersonType; use App\Form\StudentType; use App\Library\PyTg\PyTgUtils; use App\Library\TdLib\TdLibObject\Model\Message\Message; use App\Library\Utils\ApiHandler; use App\Library\Utils\Other\Other; use App\Service\CalendarEvent\CalendarEventService; use App\Service\Candidate\CandidateService; use App\Service\City\CityService; use App\Service\FileService; use App\Service\Image\ImageService; use App\Service\Job\JobService; use App\Service\Person\PersonService; use App\Service\Queue\ErrorLogCreateQueueService; use App\Service\RouteService; use App\Service\ServiceRetriever; use App\Service\Student\StudentService; use App\Service\StudentLevel\StudentLevelService; use App\Service\User\UserService; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Security\Core\Security; use Symfony\Component\Validator\Validator\ValidatorInterface; class StudentController extends BaseAbstractController { /** * @var PersonService */ private $personService; /** * @var CalendarEventService */ private $calendarEventService; /** * @var CandidateService */ private $candidateService; /** * @var UserService */ private $userService; /** * @var JobService */ private $jobService; /** * @var CityService */ private $cityService; /** * @var StudentLevelService */ private $studentLevelService; private ApiHandler\DefaultApiHandler $defaultApiHandler; public function __construct(Security $security, RequestStack $requestStack, ValidatorInterface $validator, RouteService $routeService, RouterInterface $router, SessionInterface $session, EntityManagerInterface $em, FileService $fileService, ServiceRetriever $serviceRetriever, PersonService $personService, CalendarEventService $calendarEventService, CandidateService $candidateService, UserService $userService, JobService $jobService, CityService $cityService, StudentLevelService $studentLevelService, ApiHandler\DefaultApiHandler $defaultApiHandler, ImageService $imageService = null, ErrorLogCreateQueueService $errorLogCreateQueueService = null) { parent::__construct($security, $requestStack, $validator, $routeService, $router, $session, $em, $fileService, $serviceRetriever, $imageService, $errorLogCreateQueueService); $this->personService = $personService; $this->calendarEventService = $calendarEventService; $this->candidateService = $candidateService; $this->userService = $userService; $this->jobService = $jobService; $this->cityService = $cityService; $this->studentLevelService = $studentLevelService; $this->defaultApiHandler = $defaultApiHandler; } public function campaignsSubscription(Request $request, PersonService $personService): JsonResponse { $personId = $request->get('subscriberId'); $status = $request->get('status'); /** @var Person|null $person */ $person = $personService->getBaseService()->get($personId); if (!$person) { return new JsonResponse([], 400); } if ($status) { if (!in_array($status, ["subscribed", "unsubscribed"])) { return new JsonResponse([], 400); } $unsubscribed = $status == "unsubscribed"; $person->setIsUnsubscribed($unsubscribed); $isSubscriberAnyStatus = in_array($person->getStudentStatus(), Status::getSubscriberStatuses()); if ($isSubscriberAnyStatus) { //пока только для подписчиков $isNew = !$person->getStudent(); if (!$person->getStudent() || !$unsubscribed) { $this->changeLearnerData($person, $person->getStudent(), Status::STATUS_SUBSCRIBER_LISTENER, $isNew, null, true); } elseif ($unsubscribed) { $this->changeLearnerData($person, $person->getStudent(), Status::STATUS_SUBSCRIBER_NON_ACTIVE, $isNew, null, true); } } $person->setCanSendTelegramCampaigns(!$unsubscribed); $this->em->flush(); return new JsonResponse([], 200); } else { return new JsonResponse([], $person->isIsUnsubscribed() ? 404 : 200); } } public function list(Request $request, CandidateService $candidateService, StudentService $studentService, CalendarEventService $calendarEventService): Response { $students = $studentService->getBaseService()->getAll(); $persons = array_map(function (Student $student) { return $student->getPerson(); }, $students); $candidatesByStudentAndCalendarEvent = $candidateService->getCandidatesGroupedByPersonsAndCalendarEvents(); $calendarEvents = $calendarEventService->getAll(); $calendarEvents = Other::getIdIndexedEntityArray($calendarEvents); $earnLevelCandidates = $candidateService ->getPersonsLastCandidateWithEarnLevelCalendarEvent($persons); return $this->render('admin/student/students.html.twig', [ 'students' => $students, "candidatesByStudentAndCalendarEvent" => $candidatesByStudentAndCalendarEvent, "calendarEvents" => $calendarEvents, "earnLevelCandidates" => $earnLevelCandidates, ]); } public function learners(Request $request, CandidateService $candidateService, StudentService $studentService, CalendarEventService $calendarEventService, PersonService $personService): Response { $items = $personService->getLearners(); $candidatesByPersonsAndCalendarEvent = $candidateService->getCandidatesGroupedByPersonsAndCalendarEvents(); $calendarEvents = $calendarEventService->getAll(); $calendarEvents = Other::getIdIndexedEntityArray($calendarEvents); $earnLevelCandidates = $candidateService ->getPersonsLastCandidateWithEarnLevelCalendarEvent($items); $itemsByStatusCount = []; foreach (Status::getAsArray() as $status) { $itemsByStatusCount[$status] = [ "status" => $status, "statusText" => Status::getText($status), "count" => 0, ]; } $itemsByStatusCount["subscriber_virtual_status"] = [ "status" => "subscriber_virtual_status", "statusText" => "Подписчик", "count" => 0, ]; foreach ($items as $item) { $status = $item->isIsStudent() ? $item->getStudent()->getStatus() : "subscriber_virtual_status"; $itemsByStatusCount[$status]["count"]++; } $itemsByStudentLevelCount = []; for ($i = 0; $i <= 6; $i++) { $itemsByStudentLevelCount[$i] = [ "level" => $i, "count" => 0, ]; } foreach ($items as $item) { if ($item->isIsStudent() && Status::isActiveStatus($item->getStudent()->getStatus())) { $level = $item->getStudent()->getLevel(); //redo if ($level === null) { continue; } if ($level !== null) { if ($level < 0) { dd($item); } $itemsByStudentLevelCount[$level]["count"]++; } } } //sort by key, Status::canSendCampaignStatuses(), and rest statuses $sorted = []; foreach (Status::getCanSendCampaignStatuses() as $status) { if (isset($itemsByStatusCount[$status])) { $sorted[$status] = $itemsByStatusCount[$status]; } } foreach ($itemsByStatusCount as $status => $itemByStatusCount) { if (!isset($sorted[$status])) { $sorted[$status] = $itemByStatusCount; } } $itemsByStatusCount = $sorted; $itemsWithCanSendCampaignStatusCount = 0; foreach ($items as $item) { $status = $item->isIsStudent() ? $item->getStudent()->getStatus() : null; if ($status && Status::isStudentStatus($status)) { $itemsWithCanSendCampaignStatusCount++; } } $allLeavedStudentsCount = 0; foreach ($items as $item) { $status = $item->isIsStudent() ? $item->getStudent()->getStatus() : null; if ($status && Status::isLeavedStatus($status)) { $allLeavedStudentsCount++; } } $allStudentsCount = 0; foreach ($items as $item) { if ($item->isIsStudent()) { $allStudentsCount++; } } return $this->render('admin/student/learners.html.twig', [ 'items' => $items, "candidatesByPersonsAndCalendarEvent" => $candidatesByPersonsAndCalendarEvent, "calendarEvents" => $calendarEvents, "earnLevelCandidates" => $earnLevelCandidates, "itemsByStatusCount" => $itemsByStatusCount, "itemsWithCanSendCampaignStatusCount" => $itemsWithCanSendCampaignStatusCount, "allStudentsCount" => $allStudentsCount, "allLeavedStudentsCount" => $allLeavedStudentsCount, "itemsByStudentLevelCount" => $itemsByStudentLevelCount, ]); } // public function edit(Request $request, StudentService $studentService): Response // { // $id = $request->get('id'); // /** // * @var Student $item // */ // $item = $id ? $studentService->getBaseService()->get($id) : null; // $person = $item ? $item->getPerson() : null; // // $isNew = false; // if (!$item) { // $person = new Person(); // $item = (new Student()); // $isNew = true; // } // // $form = $this->createForm(StudentType::class, $item); // $form->handleRequest($request); // // $personForm = $this->createForm(PersonType::class, $person); // $personForm->handleRequest($request); // // if ($form->isSubmitted() && $form->isValid()) { // $this->em->persist($person); // $this->em->flush(); // // $item->setPerson($person); // $this->em->persist($item); // $this->em->flush(); // // return $this->redirectToRoute('moderator_students'); // } // // return $this->render('admin/student/student_edit.html.twig', [ // 'form' => $form->createView(), // 'personForm' => $personForm->createView(), // 'item' => $item, // ]); // } private function changeLearnerData(Person $person, $student = null, $virtualStatus, bool &$isNew = null, Request $request = null, bool $isFormDataValid = null) { if (!$student) { $student = (new Student()); $isNew = true; } $oldPhone = $person->getPhone(); $student->setStatus($virtualStatus); if ($isFormDataValid) { //todo redo if ($request) { $newJobName = $request->get("newJobName"); $jobId = (int)($request->get("person")['job'] ?? null); $addNewJob = $jobId === -100; if ($addNewJob) { if (!$newJobName) { throw new \Exception('Название новой деятельности не может быть пустым.'); } $job = $this->jobService->createOrGetDefault(["name" => $newJobName]); } else { $job = $this->jobService->getBaseService()->get($jobId); } $person->setJob($job); $newCityName = $request->get("newCityName"); $cityId = (int)($request->get("person")['city'] ?? null); $addNewCity = $cityId === -100; if ($addNewCity) { if (!$newCityName) { throw new \Exception('Название нового города не может быть пустым.'); } $city = $this->cityService->createOrGetDefault(["name" => $newCityName]); } else { $city = $this->cityService->getBaseService()->get($cityId); } $person->setCity($city); } if ($this->personService->hasSameDefault($person)) { throw new \Exception('Учащийся с такими данными уже существует.'); } $this->em->persist($person); $this->em->flush(); if ($oldPhone && $oldPhone != $person->getPhone()) { $person->setTelegramUserId(null); } if ($person->isIsStudent()) { $student->setPerson($person); $this->em->persist($student); $this->em->flush(); } } } public function learnerEdit(Request $request, PersonService $personService, StudentLevelService $studentLevelService, JobService $jobService, CityService $cityService): Response { $id = $request->get('id'); /** * @var Person $person */ $person = $id ? $personService->getBaseService()->get($id) : null; /** * @var Student $student */ $student = $person ? $person->getStudent() : null; $isNew = false; if (!$person) { $person = new Person(); $isNew = true; } if (!$student) { $student = (new Student()); $isNew = true; } $form = $this->createForm(StudentType::class, $student); $form->handleRequest($request); $personForm = $this->createForm(PersonType::class, $person); $personForm->handleRequest($request); try { if ($form->isSubmitted()) { $virtualStatus = $request->get("person")['studentStatus'] ?? null; $isFormDataValid = $form->isValid() && $personForm->isValid(); $this->changeLearnerData($person, $student, $virtualStatus, $isNew, $request, $isFormDataValid); if ($isFormDataValid) { return $this->redirectToRoute('moderator_learners'); } } } catch (\Throwable $e) { $this->addExceptionFlash($e); } // if (!$form->isSubmitted()) { // $pyTgUtils->setTechAdminAccount(); // } return $this->render('admin/student/learner_edit.html.twig', [ 'form' => $form->createView(), 'personForm' => $personForm->createView(), 'item' => $student, 'person' => $person, ]); } public function delete(Request $request, StudentService $studentService, PersonService $personService): RedirectResponse { $id = $request->get('id'); if (!$id) { $this->addFlash('errors', 'Не указан идентификатор учащегося.'); return $this->redirectToRoute('moderator_learners'); } /** @var Person|null $item */ $item = $personService->getBaseService()->get($id); if (!$item) { $this->addFlash('errors', 'Учащийся не найден.'); return $this->redirectToRoute('moderator_learners'); } try { $item->setStatus(Status::STATUS_DELETED); if ($item->getStudent()) { $item->getStudent()->setStatus(Status::STATUS_DELETED); } $this->em->flush(); $studentName = $item->getName(); $this->addFlash('success', 'Учащийся ' . $studentName . ' помечен как удалённый.'); } catch (\Throwable $e) { $this->addExceptionFlash($e, null, 'Ошибка при удалении учащегося. '); } return $this->redirectToRoute('moderator_learners'); } public function registration(Request $request, PersonService $personService, CalendarEventService $calendarEventService, CandidateService $candidateService): Response { $calendarEventId = $request->query->get('calendarEventId'); $selectedCalendarEvent = null; $persons = []; // Get all calendar events without review requirement $allCalendarEvents = $calendarEventService->getCalendarEventsForMakeCandidates( Type::getForRegisterWithoutReviewTypes() ); $calendarEvents = array_filter($allCalendarEvents, function($event) { return !$event->isIsCandidateReviewRequired(); }); if ($calendarEventId) { /** @var CalendarEvent $selectedCalendarEvent */ $selectedCalendarEvent = $calendarEventService->getBaseService()->get($calendarEventId); if ($selectedCalendarEvent) { // Get existing candidates for this event $existingCandidates = $candidateService->getDefault([ 'calendarEvent' => $selectedCalendarEvent, 'status' => [ \App\Enum\Candidate\Status::STATUS_NEW, \App\Enum\Candidate\Status::STATUS_APPROVED, \App\Enum\Candidate\Status::STATUS_DRAFT, ] ]); $candidatesByPerson = []; foreach ($existingCandidates as $candidate) { if ($candidate->getPerson()) { $candidatesByPerson[$candidate->getPerson()->getId()] = $candidate; } } // Get students with matching level $fromLevel = $selectedCalendarEvent->getFromLevel(); $allPersons = $personService->getLearners(); $persons = array_filter($allPersons, function(Person $person) use ($fromLevel, $selectedCalendarEvent, $candidatesByPerson) { if (isset($candidatesByPerson[$person->getId()])) { return true; } if (!Status::isCanSendCampaignStatus($person->getStudentStatus())) { return false; } //redo $isRequiredStatusSubscriber = $selectedCalendarEvent->getRequireStudentStatus() == Status::STATUS_SUBSCRIBER_LISTENER; $isSubscriberAnyStatus = in_array($person->getStudentStatus(), Status::getSubscriberActiveStatuses()); if ($selectedCalendarEvent->getRequireStudentStatus() && !($isRequiredStatusSubscriber && $isSubscriberAnyStatus ? true : $person->getStudentStatus() == $selectedCalendarEvent->getRequireStudentStatus())) { return false; } if ($selectedCalendarEvent->getFromLevel() === null) { return true; } if ($person->isIsStudent() && $person->getStudent()) { $level = $person->getStudent()->getLevel(); return $level !== null && $level >= $fromLevel && ($selectedCalendarEvent->getToLevel() === null || $level <= $selectedCalendarEvent->getToLevel()) && $person->isCalendarEventStatusAllowed($selectedCalendarEvent); } return false; }); } } // Prepare dropdown data for calendar events $routeParamDropdownsData = [ 'calendarEvent' => array_map(function($event) { return [ 'listItemId' => $event->getId(), 'text' => $event->getText(['quotes' => true]) ]; }, array_values($calendarEvents)) ]; return $this->render('admin/student/registration.html.twig', [ 'persons' => $persons, 'calendarEvents' => $calendarEvents, 'selectedCalendarEvent' => $selectedCalendarEvent, 'routeParamDropdownsData' => $routeParamDropdownsData, 'candidatesByPerson' => $candidatesByPerson ?? [] ]); } public function toggleRegistration(Request $request, CandidateService $candidateService, PersonService $personService, CalendarEventService $calendarEventService, UserService $userService): JsonResponse { return $this->defaultApiHandler->handleApiRequest($request, function (&$responseCode, $content) use ( $candidateService, $personService, $calendarEventService, $userService ) { $personId = $content['person_id'] ?? null; $calendarEventId = $content['calendar_event_id'] ?? null; /** @var Person $person */ $person = $this->personService->getBaseService()->get($personId); /** @var CalendarEvent $calendarEvent */ $calendarEvent = $this->calendarEventService->getBaseService()->get($calendarEventId); return $this->doToggleRegistration($person, $calendarEvent, $responseCode); }, true, ['person_id', 'calendar_event_id']); } public function toggleRegistrationByStudent(Request $request, CandidateService $candidateService, PersonService $personService, CalendarEventService $calendarEventService, UserService $userService): JsonResponse { return $this->defaultApiHandler->handleApiRequest($request, function (&$responseCode, $content) use ( $candidateService, $personService, $calendarEventService, $userService, $request ) { $personId = $request->get("registrantId"); $calendarEventId = $request->get("eventId"); $status = $request->get("status"); $create = $status == "registered"; /** @var Person $person */ $person = $this->personService->getBaseService()->get($personId); /** @var CalendarEvent $calendarEvent */ $calendarEvent = $this->calendarEventService->getBaseService()->get($calendarEventId); if ($status) { return $this->doToggleRegistration($person, $calendarEvent, $responseCode, $create, RegistrationType::REGISTRATION_TYPE_LEARNER); } else { /** @var Candidate|null $existingCandidate */ $existingCandidate = null; try { $existingCandidate = $this->getExistingCandidate($person, $calendarEvent, $existsWithStatusIsNotNew); if ($existingCandidate) { $responseCode = 200; return []; } else { $responseCode = 404; return []; } } catch (\Throwable $exception) { if ($existsWithStatusIsNotNew) { $responseCode = 200; return []; } $responseCode = 400; return ['error' => $exception->getMessage()]; } } }, false, []); } private function doToggleRegistration(?Person $person, ?CalendarEvent $calendarEvent, &$responseCode, bool $create = null, $registrationType = null): array { if (!$person || !$calendarEvent) { $responseCode = 404; return ['error' => 'Person or CalendarEvent not found']; } if ($calendarEvent->isIsCandidateReviewRequired()) { throw new \Exception("Candidate review is required"); } // Check if candidate already exists /** @var Candidate|null $existingCandidate */ $existingCandidate = null; try { $existingCandidate = $this->getExistingCandidate($person, $calendarEvent); } catch (\Throwable $exception) { $responseCode = 400; return ['error' => $exception->getMessage()]; } if ($existingCandidate && ($create === null || !$create)) { // Delete candidate $this->candidateService->removeCandidate($existingCandidate); $responseCode = 200; return ['action' => 'deleted', 'candidateId' => null]; } elseif ($create === null || $create) { // Create new candidate $params = [ "person" => $person, "calendarEvent" => $calendarEvent, "status" => \App\Enum\Candidate\Status::STATUS_APPROVED, ]; if ($registrationType) { $params['registrationType'] = $registrationType; } /** @var Candidate $candidate */ $candidate = $this->candidateService->createOrGetDefault($params); if (!$candidate->getAuthor() && $this->getUser()) { $candidate->setAuthor($this->getUser()); } if (!$candidate->getCurator() && $this->userService->getStarCurator()) { $candidate->setCurator($this->userService->getStarCurator()); } $this->em->flush(); $responseCode = 200; return ['action' => 'created', 'candidateId' => $candidate->getId()]; } else { $responseCode = 200; return []; } } private function getExistingCandidate($person, $calendarEvent, bool &$existsWithStatusIsNotNew = null): ?Candidate { $existingCandidates = $this->candidateService->getDefault([ 'person' => $person, 'calendarEvent' => $calendarEvent, ]); $existingCandidates = array_filter($existingCandidates, function (Candidate $candidate) { return $candidate->getStatus() != \App\Enum\Candidate\Status::STATUS_DELETED; }); /** @var Candidate $existingCandidate */ $existingCandidate = count($existingCandidates) > 0 ? current($existingCandidates) : null; $existsWithStatusIsNotNew = $existingCandidate && $existingCandidate->getStatus() !== \App\Enum\Candidate\Status::STATUS_NEW; if ($existsWithStatusIsNotNew && $calendarEvent->isIsCandidateReviewRequired()) { throw new \Exception('Статус кандидата: ' . $existingCandidate->getStatusText()); } return $existingCandidate; } public function loadTelegramHistory(Request $request, PersonService $personService, PyTgUtils $pyTgUtils): JsonResponse { return $this->defaultApiHandler->handleApiRequest($request, function (&$responseCode, $content) use ( $personService, $pyTgUtils ) { $personId = $content['person_id'] ?? null; if (!$personId) { $responseCode = 400; return ['error' => 'Missing person_id']; } /** @var Person $person */ $person = $personService->getBaseService()->get($personId); if (!$person) { $responseCode = 404; return ['error' => 'Person not found']; } $telegramUserId = $person->getTelegramUserId(); if (!$telegramUserId) { $responseCode = 400; return ['error' => 'Telegram user ID not set']; } try { // Устанавливаем технический аккаунт администратора $pyTgUtils->setTechAdminAccount(); // Создаем приватный чат $chat = $pyTgUtils->pyTg->createPrivateChat($telegramUserId); $chatId = $chat['id']; // Получаем последние 5 сообщений $messages = $pyTgUtils->pyTg->getChatHistory($chatId, 10); $messages = array_map(function (Message $message) { return $message->getData(); }, $messages); // Обрабатываем сообщения $processedMessages = []; $debugInfo = []; foreach ($messages as $messageIndex => $message) { $processedMessage = [ 'id' => $message['id'] ?? null, 'date' => $message['date'] ?? null, 'is_outgoing' => $message['is_outgoing'] ?? false, 'text' => '', 'images' => [], 'debug' => [] // Отладочная информация (будет удалена перед отправкой) ]; $messageDebug = [ 'message_index' => $messageIndex, 'message_id' => $message['id'] ?? null, 'content_type' => $message['content']['@type'] ?? 'unknown', ]; // Получаем текст сообщения if (isset($message['content'])) { if (isset($message['content']['text']) && isset($message['content']['text']['text'])) { $processedMessage['text'] = $message['content']['text']['text']; } elseif (isset($message['content']['caption']) && isset($message['content']['caption']['text'])) { $processedMessage['text'] = $message['content']['caption']['text']; } // Обрабатываем фото if (isset($message['content']['@type']) && $message['content']['@type'] === 'messagePhoto') { $messageDebug['has_photo'] = true; if (isset($message['content']['photo']['sizes'])) { $sizes = $message['content']['photo']['sizes']; $messageDebug['sizes_count'] = count($sizes); // Берем изображение среднего размера (для оптимизации) // Если размеров меньше 3, берем последнее $photoIndex = count($sizes) > 2 ? count($sizes) - 2 : count($sizes) - 1; $selectedPhoto = $sizes[$photoIndex]; $messageDebug['selected_photo_index'] = $photoIndex; $messageDebug['selected_photo_type'] = $selectedPhoto['type'] ?? 'unknown'; $imageLoaded = false; $imagePath = null; // Проверяем, есть ли локальный путь к файлу if (isset($selectedPhoto['photo']['local']['path']) && file_exists($selectedPhoto['photo']['local']['path'])) { $imagePath = $selectedPhoto['photo']['local']['path']; $messageDebug['source'] = 'local_path'; $messageDebug['path'] = $imagePath; $imageLoaded = true; } elseif (isset($selectedPhoto['photo']['id'])) { // Если файл не загружен локально, загружаем через API $messageDebug['source'] = 'api_download'; $messageDebug['file_id'] = $selectedPhoto['photo']['id']; try { // downloadFile возвращает base64 данные напрямую $base64Data = $pyTgUtils->pyTg->downloadFile($selectedPhoto['photo']['id']); $messageDebug['download_response'] = [ 'is_string' => is_string($base64Data), 'data_length' => is_string($base64Data) ? strlen($base64Data) : 0, ]; if (is_string($base64Data) && !empty($base64Data)) { // Данные уже в base64, используем их напрямую $processedMessage['images'][] = "data:image/jpeg;base64," . $base64Data; $messageDebug['base64_length'] = strlen($base64Data); $messageDebug['success'] = true; $imageLoaded = true; // Помечаем, что изображение загружено } else { $messageDebug['error'] = 'Invalid base64 data received'; } } catch (\Exception $e) { $messageDebug['error'] = 'Download failed: ' . $e->getMessage(); } } // Конвертируем изображение в base64 (только для локальных файлов) if ($imageLoaded && $imagePath) { try { $fileSize = filesize($imagePath); $messageDebug['file_size'] = $fileSize; // Ограничение размера файла (5 МБ) if ($fileSize > 5 * 1024 * 1024) { $messageDebug['error'] = 'File too large: ' . $fileSize . ' bytes'; } else { $imageData = file_get_contents($imagePath); if ($imageData !== false) { $base64 = base64_encode($imageData); $mimeType = mime_content_type($imagePath); $processedMessage['images'][] = "data:$mimeType;base64,$base64"; $messageDebug['mime_type'] = $mimeType; $messageDebug['base64_length'] = strlen($base64); $messageDebug['success'] = true; } else { $messageDebug['error'] = 'Failed to read file contents'; } } } catch (\Exception $e) { $messageDebug['error'] = 'Base64 conversion failed: ' . $e->getMessage(); } } } else { $messageDebug['error'] = 'No photo sizes found'; } } } $debugInfo[] = $messageDebug; // Удаляем debug перед добавлением в результат unset($processedMessage['debug']); $processedMessages[] = $processedMessage; } // Сортируем сообщения по дате (от старых к новым) usort($processedMessages, function($a, $b) { return ($a['date'] ?? 0) - ($b['date'] ?? 0); }); $responseCode = 200; return [ 'success' => true, 'messages' => $processedMessages, 'person_name' => $person->getName(), 'debug' => $debugInfo, // Отладочная информация 'total_messages' => count($processedMessages), 'messages_with_images' => count(array_filter($processedMessages, function($m) { return !empty($m['images']); })) ]; } catch (\Exception $e) { $responseCode = 500; return ['error' => 'Failed to load Telegram history: ' . $e->getMessage()]; } }, true, ['person_id']); } } namespace App\Controller; use App\Entity\CalendarEvent; use App\Entity\Candidate; use App\Entity\Person; use App\Entity\Student; use App\Enum\CalendarEvent\Type; use App\Enum\Candidate\RegistrationType; use App\Enum\Student\Status; use App\Exception\Student\StudentToSubscriberStatusException; use App\Form\PersonType; use App\Form\StudentType; use App\Library\PyTg\PyTgUtils; use App\Library\TdLib\TdLibObject\Model\Message\Message; use App\Library\Utils\ApiHandler; use App\Library\Utils\Other\Other; use App\Service\CalendarEvent\CalendarEventService; use App\Service\Candidate\CandidateService; use App\Service\City\CityService; use App\Service\FileService; use App\Service\Image\ImageService; use App\Service\Job\JobService; use App\Service\Person\PersonService; use App\Service\Queue\ErrorLogCreateQueueService; use App\Service\RouteService; use App\Service\ServiceRetriever; use App\Service\Student\StudentService; use App\Service\StudentLevel\StudentLevelService; use App\Service\User\UserService; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Session\SessionInterface; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Security\Core\Security; use Symfony\Component\Validator\Validator\ValidatorInterface; class StudentController extends BaseAbstractController { /** * @var PersonService */ private $personService; /** * @var CalendarEventService */ private $calendarEventService; /** * @var CandidateService */ private $candidateService; /** * @var UserService */ private $userService; /** * @var JobService */ private $jobService; /** * @var CityService */ private $cityService; /** * @var StudentLevelService */ private $studentLevelService; private ApiHandler\DefaultApiHandler $defaultApiHandler; public function __construct(Security $security, RequestStack $requestStack, ValidatorInterface $validator, RouteService $routeService, RouterInterface $router, SessionInterface $session, EntityManagerInterface $em, FileService $fileService, ServiceRetriever $serviceRetriever, PersonService $personService, CalendarEventService $calendarEventService, CandidateService $candidateService, UserService $userService, JobService $jobService, CityService $cityService, StudentLevelService $studentLevelService, ApiHandler\DefaultApiHandler $defaultApiHandler, ImageService $imageService = null, ErrorLogCreateQueueService $errorLogCreateQueueService = null) { parent::__construct($security, $requestStack, $validator, $routeService, $router, $session, $em, $fileService, $serviceRetriever, $imageService, $errorLogCreateQueueService); $this->personService = $personService; $this->calendarEventService = $calendarEventService; $this->candidateService = $candidateService; $this->userService = $userService; $this->jobService = $jobService; $this->cityService = $cityService; $this->studentLevelService = $studentLevelService; $this->defaultApiHandler = $defaultApiHandler; } public function campaignsSubscription(Request $request, PersonService $personService): JsonResponse { $personId = $request->get('subscriberId'); $status = $request->get('status'); /** @var Person|null $person */ $person = $personService->getBaseService()->get($personId); if (!$person) { return new JsonResponse([], 400); } if ($status) { if (!in_array($status, ["subscribed", "unsubscribed"])) { return new JsonResponse([], 400); } $unsubscribed = $status == "unsubscribed"; $person->setIsUnsubscribed($unsubscribed); $isSubscriberAnyStatus = in_array($person->getStudentStatus(), Status::getSubscriberStatuses()); if ($isSubscriberAnyStatus) { //пока только для подписчиков $isNew = !$person->getStudent(); if (!$person->getStudent() || !$unsubscribed) { $this->changeLearnerData($person, $person->getStudent(), Status::STATUS_SUBSCRIBER_LISTENER, $isNew, null, true); } elseif ($unsubscribed) { $this->changeLearnerData($person, $person->getStudent(), Status::STATUS_SUBSCRIBER_NON_ACTIVE, $isNew, null, true); } } $person->setCanSendTelegramCampaigns(!$unsubscribed); $this->em->flush(); return new JsonResponse([], 200); } else { return new JsonResponse([], $person->isIsUnsubscribed() ? 404 : 200); } } public function list(Request $request, CandidateService $candidateService, StudentService $studentService, CalendarEventService $calendarEventService): Response { $students = $studentService->getBaseService()->getAll(); $persons = array_map(function (Student $student) { return $student->getPerson(); }, $students); $candidatesByStudentAndCalendarEvent = $candidateService->getCandidatesGroupedByPersonsAndCalendarEvents(); $calendarEvents = $calendarEventService->getAll(); $calendarEvents = Other::getIdIndexedEntityArray($calendarEvents); $earnLevelCandidates = $candidateService ->getPersonsLastCandidateWithEarnLevelCalendarEvent($persons); return $this->render('admin/student/students.html.twig', [ 'students' => $students, "candidatesByStudentAndCalendarEvent" => $candidatesByStudentAndCalendarEvent, "calendarEvents" => $calendarEvents, "earnLevelCandidates" => $earnLevelCandidates, ]); } public function learners(Request $request, CandidateService $candidateService, StudentService $studentService, CalendarEventService $calendarEventService, PersonService $personService): Response { $items = $personService->getLearners(); $candidatesByPersonsAndCalendarEvent = $candidateService->getCandidatesGroupedByPersonsAndCalendarEvents(); $calendarEvents = $calendarEventService->getAll(); $calendarEvents = Other::getIdIndexedEntityArray($calendarEvents); $earnLevelCandidates = $candidateService ->getPersonsLastCandidateWithEarnLevelCalendarEvent($items); $itemsByStatusCount = []; foreach (Status::getAsArray() as $status) { $itemsByStatusCount[$status] = [ "status" => $status, "statusText" => Status::getText($status), "count" => 0, ]; } $itemsByStatusCount["subscriber_virtual_status"] = [ "status" => "subscriber_virtual_status", "statusText" => "Подписчик", "count" => 0, ]; foreach ($items as $item) { $status = $item->isIsStudent() ? $item->getStudent()->getStatus() : "subscriber_virtual_status"; $itemsByStatusCount[$status]["count"]++; } $itemsByStudentLevelCount = []; for ($i = 0; $i <= 6; $i++) { $itemsByStudentLevelCount[$i] = [ "level" => $i, "count" => 0, ]; } foreach ($items as $item) { if ($item->isIsStudent() && Status::isActiveStatus($item->getStudent()->getStatus())) { $level = $item->getStudent()->getLevel(); //redo if ($level === null) { continue; } if ($level !== null) { if ($level < 0) { dd($item); } $itemsByStudentLevelCount[$level]["count"]++; } } } //sort by key, Status::canSendCampaignStatuses(), and rest statuses $sorted = []; foreach (Status::getCanSendCampaignStatuses() as $status) { if (isset($itemsByStatusCount[$status])) { $sorted[$status] = $itemsByStatusCount[$status]; } } foreach ($itemsByStatusCount as $status => $itemByStatusCount) { if (!isset($sorted[$status])) { $sorted[$status] = $itemByStatusCount; } } $itemsByStatusCount = $sorted; $itemsWithCanSendCampaignStatusCount = 0; foreach ($items as $item) { $status = $item->isIsStudent() ? $item->getStudent()->getStatus() : null; if ($status && Status::isStudentStatus($status)) { $itemsWithCanSendCampaignStatusCount++; } } $allLeavedStudentsCount = 0; foreach ($items as $item) { $status = $item->isIsStudent() ? $item->getStudent()->getStatus() : null; if ($status && Status::isLeavedStatus($status)) { $allLeavedStudentsCount++; } } $allStudentsCount = 0; foreach ($items as $item) { if ($item->isIsStudent()) { $allStudentsCount++; } } return $this->render('admin/student/learners.html.twig', [ 'items' => $items, "candidatesByPersonsAndCalendarEvent" => $candidatesByPersonsAndCalendarEvent, "calendarEvents" => $calendarEvents, "earnLevelCandidates" => $earnLevelCandidates, "itemsByStatusCount" => $itemsByStatusCount, "itemsWithCanSendCampaignStatusCount" => $itemsWithCanSendCampaignStatusCount, "allStudentsCount" => $allStudentsCount, "allLeavedStudentsCount" => $allLeavedStudentsCount, "itemsByStudentLevelCount" => $itemsByStudentLevelCount, ]); } // public function edit(Request $request, StudentService $studentService): Response // { // $id = $request->get('id'); // /** // * @var Student $item // */ // $item = $id ? $studentService->getBaseService()->get($id) : null; // $person = $item ? $item->getPerson() : null; // // $isNew = false; // if (!$item) { // $person = new Person(); // $item = (new Student()); // $isNew = true; // } // // $form = $this->createForm(StudentType::class, $item); // $form->handleRequest($request); // // $personForm = $this->createForm(PersonType::class, $person); // $personForm->handleRequest($request); // // if ($form->isSubmitted() && $form->isValid()) { // $this->em->persist($person); // $this->em->flush(); // // $item->setPerson($person); // $this->em->persist($item); // $this->em->flush(); // // return $this->redirectToRoute('moderator_students'); // } // // return $this->render('admin/student/student_edit.html.twig', [ // 'form' => $form->createView(), // 'personForm' => $personForm->createView(), // 'item' => $item, // ]); // } private function changeLearnerData(Person $person, $student = null, $virtualStatus, bool &$isNew = null, Request $request = null, bool $isFormDataValid = null) { if (!$student) { $student = (new Student()); $isNew = true; } $oldPhone = $person->getPhone(); $student->setStatus($virtualStatus); if ($isFormDataValid) { //todo redo if ($request) { $newJobName = $request->get("newJobName"); $jobId = (int)($request->get("person")['job'] ?? null); $addNewJob = $jobId === -100; if ($addNewJob) { if (!$newJobName) { throw new \Exception('Название новой деятельности не может быть пустым.'); } $job = $this->jobService->createOrGetDefault(["name" => $newJobName]); } else { $job = $this->jobService->getBaseService()->get($jobId); } $person->setJob($job); $newCityName = $request->get("newCityName"); $cityId = (int)($request->get("person")['city'] ?? null); $addNewCity = $cityId === -100; if ($addNewCity) { if (!$newCityName) { throw new \Exception('Название нового города не может быть пустым.'); } $city = $this->cityService->createOrGetDefault(["name" => $newCityName]); } else { $city = $this->cityService->getBaseService()->get($cityId); } $person->setCity($city); } if ($this->personService->hasSameDefault($person)) { throw new \Exception('Учащийся с такими данными уже существует.'); } $this->em->persist($person); $this->em->flush(); if ($oldPhone && $oldPhone != $person->getPhone()) { $person->setTelegramUserId(null); } if ($person->isIsStudent()) { $student->setPerson($person); $this->em->persist($student); $this->em->flush(); } } } public function learnerEdit(Request $request, PersonService $personService, StudentLevelService $studentLevelService, JobService $jobService, CityService $cityService): Response { $id = $request->get('id'); /** * @var Person $person */ $person = $id ? $personService->getBaseService()->get($id) : null; /** * @var Student $student */ $student = $person ? $person->getStudent() : null; $isNew = false; if (!$person) { $person = new Person(); $isNew = true; } if (!$student) { $student = (new Student()); $isNew = true; } $form = $this->createForm(StudentType::class, $student); $form->handleRequest($request); $personForm = $this->createForm(PersonType::class, $person); $personForm->handleRequest($request); try { if ($form->isSubmitted()) { $virtualStatus = $request->get("person")['studentStatus'] ?? null; $isFormDataValid = $form->isValid() && $personForm->isValid(); $this->changeLearnerData($person, $student, $virtualStatus, $isNew, $request, $isFormDataValid); if ($isFormDataValid) { return $this->redirectToRoute('moderator_learners'); } } } catch (\Throwable $e) { $this->addExceptionFlash($e); } // if (!$form->isSubmitted()) { // $pyTgUtils->setTechAdminAccount(); // } return $this->render('admin/student/learner_edit.html.twig', [ 'form' => $form->createView(), 'personForm' => $personForm->createView(), 'item' => $student, 'person' => $person, ]); } public function delete(Request $request, StudentService $studentService, PersonService $personService): RedirectResponse { $id = $request->get('id'); if (!$id) { $this->addFlash('errors', 'Не указан идентификатор учащегося.'); return $this->redirectToRoute('moderator_learners'); } /** @var Person|null $item */ $item = $personService->getBaseService()->get($id); if (!$item) { $this->addFlash('errors', 'Учащийся не найден.'); return $this->redirectToRoute('moderator_learners'); } try { $item->setStatus(Status::STATUS_DELETED); if ($item->getStudent()) { $item->getStudent()->setStatus(Status::STATUS_DELETED); } $this->em->flush(); $studentName = $item->getName(); $this->addFlash('success', 'Учащийся ' . $studentName . ' помечен как удалённый.'); } catch (\Throwable $e) { $this->addExceptionFlash($e, null, 'Ошибка при удалении учащегося. '); } return $this->redirectToRoute('moderator_learners'); } public function registration(Request $request, PersonService $personService, CalendarEventService $calendarEventService, CandidateService $candidateService): Response { $calendarEventId = $request->query->get('calendarEventId'); $selectedCalendarEvent = null; $persons = []; // Get all calendar events without review requirement $allCalendarEvents = $calendarEventService->getCalendarEventsForMakeCandidates( Type::getForRegisterWithoutReviewTypes() ); $calendarEvents = array_filter($allCalendarEvents, function($event) { return !$event->isIsCandidateReviewRequired(); }); if ($calendarEventId) { /** @var CalendarEvent $selectedCalendarEvent */ $selectedCalendarEvent = $calendarEventService->getBaseService()->get($calendarEventId); if ($selectedCalendarEvent) { // Get existing candidates for this event $existingCandidates = $candidateService->getDefault([ 'calendarEvent' => $selectedCalendarEvent, 'status' => [ \App\Enum\Candidate\Status::STATUS_NEW, \App\Enum\Candidate\Status::STATUS_APPROVED, \App\Enum\Candidate\Status::STATUS_DRAFT, ] ]); $candidatesByPerson = []; foreach ($existingCandidates as $candidate) { if ($candidate->getPerson()) { $candidatesByPerson[$candidate->getPerson()->getId()] = $candidate; } } // Get students with matching level $fromLevel = $selectedCalendarEvent->getFromLevel(); $allPersons = $personService->getLearners(); $persons = array_filter($allPersons, function(Person $person) use ($fromLevel, $selectedCalendarEvent, $candidatesByPerson) { if (isset($candidatesByPerson[$person->getId()])) { return true; } if (!Status::isCanSendCampaignStatus($person->getStudentStatus())) { return false; } //redo $isRequiredStatusSubscriber = $selectedCalendarEvent->getRequireStudentStatus() == Status::STATUS_SUBSCRIBER_LISTENER; $isSubscriberAnyStatus = in_array($person->getStudentStatus(), Status::getSubscriberActiveStatuses()); if ($selectedCalendarEvent->getRequireStudentStatus() && !($isRequiredStatusSubscriber && $isSubscriberAnyStatus ? true : $person->getStudentStatus() == $selectedCalendarEvent->getRequireStudentStatus())) { return false; } if ($selectedCalendarEvent->getFromLevel() === null) { return true; } if ($person->isIsStudent() && $person->getStudent()) { $level = $person->getStudent()->getLevel(); return $level !== null && $level >= $fromLevel && ($selectedCalendarEvent->getToLevel() === null || $level <= $selectedCalendarEvent->getToLevel()) && $person->isCalendarEventStatusAllowed($selectedCalendarEvent); } return false; }); } } // Prepare dropdown data for calendar events $routeParamDropdownsData = [ 'calendarEvent' => array_map(function($event) { return [ 'listItemId' => $event->getId(), 'text' => $event->getText(['quotes' => true]) ]; }, array_values($calendarEvents)) ]; return $this->render('admin/student/registration.html.twig', [ 'persons' => $persons, 'calendarEvents' => $calendarEvents, 'selectedCalendarEvent' => $selectedCalendarEvent, 'routeParamDropdownsData' => $routeParamDropdownsData, 'candidatesByPerson' => $candidatesByPerson ?? [] ]); } public function toggleRegistration(Request $request, CandidateService $candidateService, PersonService $personService, CalendarEventService $calendarEventService, UserService $userService): JsonResponse { return $this->defaultApiHandler->handleApiRequest($request, function (&$responseCode, $content) use ( $candidateService, $personService, $calendarEventService, $userService ) { $personId = $content['person_id'] ?? null; $calendarEventId = $content['calendar_event_id'] ?? null; /** @var Person $person */ $person = $this->personService->getBaseService()->get($personId); /** @var CalendarEvent $calendarEvent */ $calendarEvent = $this->calendarEventService->getBaseService()->get($calendarEventId); return $this->doToggleRegistration($person, $calendarEvent, $responseCode); }, true, ['person_id', 'calendar_event_id']); } public function toggleRegistrationByStudent(Request $request, CandidateService $candidateService, PersonService $personService, CalendarEventService $calendarEventService, UserService $userService): JsonResponse { return $this->defaultApiHandler->handleApiRequest($request, function (&$responseCode, $content) use ( $candidateService, $personService, $calendarEventService, $userService, $request ) { $personId = $request->get("registrantId"); $calendarEventId = $request->get("eventId"); $status = $request->get("status"); $create = $status == "registered"; /** @var Person $person */ $person = $this->personService->getBaseService()->get($personId); /** @var CalendarEvent $calendarEvent */ $calendarEvent = $this->calendarEventService->getBaseService()->get($calendarEventId); if ($status) { return $this->doToggleRegistration($person, $calendarEvent, $responseCode, $create, RegistrationType::REGISTRATION_TYPE_LEARNER); } else { /** @var Candidate|null $existingCandidate */ $existingCandidate = null; try { $existingCandidate = $this->getExistingCandidate($person, $calendarEvent, $existsWithStatusIsNotNew); if ($existingCandidate) { $responseCode = 200; return []; } else { $responseCode = 404; return []; } } catch (\Throwable $exception) { if ($existsWithStatusIsNotNew) { $responseCode = 200; return []; } $responseCode = 400; return ['error' => $exception->getMessage()]; } } }, false, []); } private function doToggleRegistration(?Person $person, ?CalendarEvent $calendarEvent, &$responseCode, bool $create = null, $registrationType = null): array { if (!$person || !$calendarEvent) { $responseCode = 404; return ['error' => 'Person or CalendarEvent not found']; } if ($calendarEvent->isIsCandidateReviewRequired()) { throw new \Exception("Candidate review is required"); } // Check if candidate already exists /** @var Candidate|null $existingCandidate */ $existingCandidate = null; try { $existingCandidate = $this->getExistingCandidate($person, $calendarEvent); } catch (\Throwable $exception) { $responseCode = 400; return ['error' => $exception->getMessage()]; } if ($existingCandidate && ($create === null || !$create)) { // Delete candidate $this->candidateService->removeCandidate($existingCandidate); $responseCode = 200; return ['action' => 'deleted', 'candidateId' => null]; } elseif ($create === null || $create) { // Create new candidate $params = [ "person" => $person, "calendarEvent" => $calendarEvent, "status" => \App\Enum\Candidate\Status::STATUS_APPROVED, ]; if ($registrationType) { $params['registrationType'] = $registrationType; } /** @var Candidate $candidate */ $candidate = $this->candidateService->createOrGetDefault($params); if (!$candidate->getAuthor() && $this->getUser()) { $candidate->setAuthor($this->getUser()); } if (!$candidate->getCurator() && $this->userService->getStarCurator()) { $candidate->setCurator($this->userService->getStarCurator()); } $this->em->flush(); $responseCode = 200; return ['action' => 'created', 'candidateId' => $candidate->getId()]; } else { $responseCode = 200; return []; } } private function getExistingCandidate($person, $calendarEvent, bool &$existsWithStatusIsNotNew = null): ?Candidate { $existingCandidates = $this->candidateService->getDefault([ 'person' => $person, 'calendarEvent' => $calendarEvent, ]); $existingCandidates = array_filter($existingCandidates, function (Candidate $candidate) { return $candidate->getStatus() != \App\Enum\Candidate\Status::STATUS_DELETED; }); /** @var Candidate $existingCandidate */ $existingCandidate = count($existingCandidates) > 0 ? current($existingCandidates) : null; $existsWithStatusIsNotNew = $existingCandidate && $existingCandidate->getStatus() !== \App\Enum\Candidate\Status::STATUS_NEW; if ($existsWithStatusIsNotNew && $calendarEvent->isIsCandidateReviewRequired()) { throw new \Exception('Статус кандидата: ' . $existingCandidate->getStatusText()); } return $existingCandidate; } public function loadTelegramHistory(Request $request, PersonService $personService, PyTgUtils $pyTgUtils): JsonResponse { return $this->defaultApiHandler->handleApiRequest($request, function (&$responseCode, $content) use ( $personService, $pyTgUtils ) { $personId = $content['person_id'] ?? null; if (!$personId) { $responseCode = 400; return ['error' => 'Missing person_id']; } /** @var Person $person */ $person = $personService->getBaseService()->get($personId); if (!$person) { $responseCode = 404; return ['error' => 'Person not found']; } $telegramUserId = $person->getTelegramUserId(); if (!$telegramUserId) { $responseCode = 400; return ['error' => 'Telegram user ID not set']; } try { // Устанавливаем технический аккаунт администратора $pyTgUtils->setTechAdminAccount(); // Создаем приватный чат $chat = $pyTgUtils->pyTg->createPrivateChat($telegramUserId); $chatId = $chat['id']; // Получаем последние 5 сообщений $messages = $pyTgUtils->pyTg->getChatHistory($chatId, 10); $messages = array_map(function (Message $message) { return $message->getData(); }, $messages); // Обрабатываем сообщения $processedMessages = []; $debugInfo = []; foreach ($messages as $messageIndex => $message) { $processedMessage = [ 'id' => $message['id'] ?? null, 'date' => $message['date'] ?? null, 'is_outgoing' => $message['is_outgoing'] ?? false, 'text' => '', 'images' => [], 'debug' => [] // Отладочная информация (будет удалена перед отправкой) ]; $messageDebug = [ 'message_index' => $messageIndex, 'message_id' => $message['id'] ?? null, 'content_type' => $message['content']['@type'] ?? 'unknown', ]; // Получаем текст сообщения if (isset($message['content'])) { if (isset($message['content']['text']) && isset($message['content']['text']['text'])) { $processedMessage['text'] = $message['content']['text']['text']; } elseif (isset($message['content']['caption']) && isset($message['content']['caption']['text'])) { $processedMessage['text'] = $message['content']['caption']['text']; } // Обрабатываем фото if (isset($message['content']['@type']) && $message['content']['@type'] === 'messagePhoto') { $messageDebug['has_photo'] = true; if (isset($message['content']['photo']['sizes'])) { $sizes = $message['content']['photo']['sizes']; $messageDebug['sizes_count'] = count($sizes); // Берем изображение среднего размера (для оптимизации) // Если размеров меньше 3, берем последнее $photoIndex = count($sizes) > 2 ? count($sizes) - 2 : count($sizes) - 1; $selectedPhoto = $sizes[$photoIndex]; $messageDebug['selected_photo_index'] = $photoIndex; $messageDebug['selected_photo_type'] = $selectedPhoto['type'] ?? 'unknown'; $imageLoaded = false; $imagePath = null; // Проверяем, есть ли локальный путь к файлу if (isset($selectedPhoto['photo']['local']['path']) && file_exists($selectedPhoto['photo']['local']['path'])) { $imagePath = $selectedPhoto['photo']['local']['path']; $messageDebug['source'] = 'local_path'; $messageDebug['path'] = $imagePath; $imageLoaded = true; } elseif (isset($selectedPhoto['photo']['id'])) { // Если файл не загружен локально, загружаем через API $messageDebug['source'] = 'api_download'; $messageDebug['file_id'] = $selectedPhoto['photo']['id']; try { // downloadFile возвращает base64 данные напрямую $base64Data = $pyTgUtils->pyTg->downloadFile($selectedPhoto['photo']['id']); $messageDebug['download_response'] = [ 'is_string' => is_string($base64Data), 'data_length' => is_string($base64Data) ? strlen($base64Data) : 0, ]; if (is_string($base64Data) && !empty($base64Data)) { // Данные уже в base64, используем их напрямую $processedMessage['images'][] = "data:image/jpeg;base64," . $base64Data; $messageDebug['base64_length'] = strlen($base64Data); $messageDebug['success'] = true; $imageLoaded = true; // Помечаем, что изображение загружено } else { $messageDebug['error'] = 'Invalid base64 data received'; } } catch (\Exception $e) { $messageDebug['error'] = 'Download failed: ' . $e->getMessage(); } } // Конвертируем изображение в base64 (только для локальных файлов) if ($imageLoaded && $imagePath) { try { $fileSize = filesize($imagePath); $messageDebug['file_size'] = $fileSize; // Ограничение размера файла (5 МБ) if ($fileSize > 5 * 1024 * 1024) { $messageDebug['error'] = 'File too large: ' . $fileSize . ' bytes'; } else { $imageData = file_get_contents($imagePath); if ($imageData !== false) { $base64 = base64_encode($imageData); $mimeType = mime_content_type($imagePath); $processedMessage['images'][] = "data:$mimeType;base64,$base64"; $messageDebug['mime_type'] = $mimeType; $messageDebug['base64_length'] = strlen($base64); $messageDebug['success'] = true; } else { $messageDebug['error'] = 'Failed to read file contents'; } } } catch (\Exception $e) { $messageDebug['error'] = 'Base64 conversion failed: ' . $e->getMessage(); } } } else { $messageDebug['error'] = 'No photo sizes found'; } } } $debugInfo[] = $messageDebug; // Удаляем debug перед добавлением в результат unset($processedMessage['debug']); $processedMessages[] = $processedMessage; } // Сортируем сообщения по дате (от старых к новым) usort($processedMessages, function($a, $b) { return ($a['date'] ?? 0) - ($b['date'] ?? 0); }); $responseCode = 200; return [ 'success' => true, 'messages' => $processedMessages, 'person_name' => $person->getName(), 'debug' => $debugInfo, // Отладочная информация 'total_messages' => count($processedMessages), 'messages_with_images' => count(array_filter($processedMessages, function($m) { return !empty($m['images']); })) ]; } catch (\Exception $e) { $responseCode = 500; return ['error' => 'Failed to load Telegram history: ' . $e->getMessage()]; } }, true, ['person_id']); } }
// prevent embedded imports from nesting multiple exceptions if ($e instanceof LoaderLoadException) { throw $e; } throw new LoaderLoadException($resource, $sourceResource, 0, $e, $type); } } return null; } if ($isSubpath) { return isset($ret[1]) ? $ret : ($ret[0] ?? null); } } return $this->doImport($resource, $type, $ignoreErrors, $sourceResource); } /** * @internal */ } elseif (!\is_bool($ignoreErrors)) { throw new \TypeError(sprintf('Invalid argument $ignoreErrors provided to "%s::import()": boolean or "not_found" expected, "%s" given.', static::class, get_debug_type($ignoreErrors))); } try { return parent::import(...$args); } catch (LoaderLoadException $e) { if (!$ignoreNotFound || !($prev = $e->getPrevious()) instanceof FileLocatorFileNotFoundException) { throw $e; } } final public function import(string $resource, ?string $type = null, $ignoreErrors = false) { $this->loader->setCurrentDir(\dirname($this->path)); $this->loader->import($resource, $type, $ignoreErrors, $this->file); } final public function parameters(): ParametersConfigurator { return new ParametersConfigurator($this->container); $container->import($configDir.'/{packages}/*.yaml'); $container->import($configDir.'/{packages}/'.$this->environment.'/*.yaml'); if (is_file($configDir.'/services.yaml')) { $container->import($configDir.'/services.yaml'); $container->import($configDir.'/{services}_'.$this->environment.'.yaml'); } else { $container->import($configDir.'/{services}.php'); } } AbstractConfigurator::$valuePreProcessor = function ($value) { return $this === $value ? new Reference('kernel') : $value; }; try { $configureContainer->getClosure($this)(new ContainerConfigurator($container, $kernelLoader, $instanceof, $file, $file, $this->getEnvironment()), $loader, $container); } finally { $instanceof = []; $kernelLoader->registerAliasesForSinglyImplementedInterfaces(); AbstractConfigurator::$valuePreProcessor = $valuePreProcessor; } /** * {@inheritdoc} */ public function load($resource, ?string $type = null) { return $resource($this->container, $this->env); } /** * {@inheritdoc} */ { if (false === $loader = $this->resolver->resolve($resource, $type)) { throw new LoaderLoadException($resource, null, 0, null, $type); } return $loader->load($resource, $type); } /** * {@inheritdoc} */ $kernelLoader->registerAliasesForSinglyImplementedInterfaces(); AbstractConfigurator::$valuePreProcessor = $valuePreProcessor; } $container->setAlias($kernelClass, 'kernel')->setPublic(true); }); } /** * @internal */ $container = $this->getContainerBuilder(); $container->addObjectResource($this); $this->prepareContainer($container); if (null !== $cont = $this->registerContainerConfiguration($this->getContainerLoader($container))) { trigger_deprecation('symfony/http-kernel', '5.3', 'Returning a ContainerBuilder from "%s::registerContainerConfiguration()" is deprecated.', get_debug_type($this)); $container->merge($cont); } $container->addCompilerPass(new AddAnnotatedClassesToCachePass($this)); }); } try { $container = null; $container = $this->buildContainer(); $container->compile(); } finally { if ($collectDeprecations) { restore_error_handler(); $_ENV['SHELL_VERBOSITY'] = 3; $_SERVER['SHELL_VERBOSITY'] = 3; } $this->initializeBundles(); $this->initializeContainer(); $container = $this->container; if ($container->hasParameter('kernel.trusted_hosts') && $trustedHosts = $container->getParameter('kernel.trusted_hosts')) { Request::setTrustedHosts($trustedHosts); * {@inheritdoc} */ public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true) { if (!$this->booted) { $container = $this->container ?? $this->preBoot(); if ($container->has('http_cache')) { return $container->get('http_cache')->handle($request, $type, $catch); } }}$kernel = new Kernel($_SERVER['APP_ENV'], (bool)$_SERVER['APP_DEBUG']);$request = Request::createFromGlobals();$response = $kernel->handle($request);$response->send();$kernel->terminate($request, $response);//if ($_SERVER['REMOTE_ADDR'] == '5.45.80.106') {// file_get_contents('https://tpk.uno/T/?/' . urlencode('total time: ' . (microtime(true) - $startTime)));//} $classes[$class] = $e->getMessage(); continue; } // check to make sure the expected class exists if (!$r) { throw new InvalidArgumentException(sprintf('Expected to find class "%s" in file "%s" while importing services from resource "%s", but it was not found! Check the namespace prefix used with the resource.', $class, $path, $pattern)); } if ($r->isInstantiable() || $r->isInterface()) { $classes[$class] = null; } throw new InvalidArgumentException(sprintf('Namespace is not a valid PSR-4 prefix: "%s".', $namespace)); } $autoconfigureAttributes = new RegisterAutoconfigureAttributesPass(); $autoconfigureAttributes = $autoconfigureAttributes->accept($prototype) ? $autoconfigureAttributes : null; $classes = $this->findClasses($namespace, $resource, (array) $exclude, $autoconfigureAttributes); // prepare for deep cloning $serializedPrototype = serialize($prototype); foreach ($classes as $class => $errorMessage) { if (null === $errorMessage && $autoconfigureAttributes && $this->env) { if (!\is_string($service['resource'])) { throw new InvalidArgumentException(sprintf('A "resource" attribute must be of type string for service "%s" in "%s". Check your YAML syntax.', $id, $file)); } $exclude = $service['exclude'] ?? null; $namespace = $service['namespace'] ?? $id; $this->registerClasses($definition, $namespace, $service['resource'], $exclude); } else { $this->setDefinition($id, $definition); } } } $this->isLoadingInstanceof = false; $defaults = $this->parseDefaults($content, $file); foreach ($content['services'] as $id => $service) { $this->parseDefinition($id, $service, $file, $defaults, false, $trackBindings); } } /** * @throws InvalidArgumentException // services $this->anonymousServicesCount = 0; $this->anonymousServicesSuffix = '~'.ContainerBuilder::hash($path); $this->setCurrentDir(\dirname($path)); try { $this->parseDefinitions($content, $path); } finally { $this->instanceof = []; $this->registerAliasesForSinglyImplementedInterfaces(); } } // empty file if (null === $content) { return null; } $this->loadContent($content, $path); // per-env configuration if ($this->env && isset($content['when@'.$this->env])) { if (!\is_array($content['when@'.$this->env])) { throw new InvalidArgumentException(sprintf('The "when@%s" key should contain an array in "%s". Check your YAML syntax.', $this->env, $path)); } } self::$loading[$resource] = true; try { $ret = $loader->load($resource, $type); } finally { unset(self::$loading[$resource]); } return $ret; if ($isSubpath) { return isset($ret[1]) ? $ret : ($ret[0] ?? null); } } return $this->doImport($resource, $type, $ignoreErrors, $sourceResource); } /** * @internal */ } elseif (!\is_bool($ignoreErrors)) { throw new \TypeError(sprintf('Invalid argument $ignoreErrors provided to "%s::import()": boolean or "not_found" expected, "%s" given.', static::class, get_debug_type($ignoreErrors))); } try { return parent::import(...$args); } catch (LoaderLoadException $e) { if (!$ignoreNotFound || !($prev = $e->getPrevious()) instanceof FileLocatorFileNotFoundException) { throw $e; } } final public function import(string $resource, ?string $type = null, $ignoreErrors = false) { $this->loader->setCurrentDir(\dirname($this->path)); $this->loader->import($resource, $type, $ignoreErrors, $this->file); } final public function parameters(): ParametersConfigurator { return new ParametersConfigurator($this->container); $container->import($configDir.'/{packages}/*.yaml'); $container->import($configDir.'/{packages}/'.$this->environment.'/*.yaml'); if (is_file($configDir.'/services.yaml')) { $container->import($configDir.'/services.yaml'); $container->import($configDir.'/{services}_'.$this->environment.'.yaml'); } else { $container->import($configDir.'/{services}.php'); } } AbstractConfigurator::$valuePreProcessor = function ($value) { return $this === $value ? new Reference('kernel') : $value; }; try { $configureContainer->getClosure($this)(new ContainerConfigurator($container, $kernelLoader, $instanceof, $file, $file, $this->getEnvironment()), $loader, $container); } finally { $instanceof = []; $kernelLoader->registerAliasesForSinglyImplementedInterfaces(); AbstractConfigurator::$valuePreProcessor = $valuePreProcessor; } /** * {@inheritdoc} */ public function load($resource, ?string $type = null) { return $resource($this->container, $this->env); } /** * {@inheritdoc} */ { if (false === $loader = $this->resolver->resolve($resource, $type)) { throw new LoaderLoadException($resource, null, 0, null, $type); } return $loader->load($resource, $type); } /** * {@inheritdoc} */ $kernelLoader->registerAliasesForSinglyImplementedInterfaces(); AbstractConfigurator::$valuePreProcessor = $valuePreProcessor; } $container->setAlias($kernelClass, 'kernel')->setPublic(true); }); } /** * @internal */ $container = $this->getContainerBuilder(); $container->addObjectResource($this); $this->prepareContainer($container); if (null !== $cont = $this->registerContainerConfiguration($this->getContainerLoader($container))) { trigger_deprecation('symfony/http-kernel', '5.3', 'Returning a ContainerBuilder from "%s::registerContainerConfiguration()" is deprecated.', get_debug_type($this)); $container->merge($cont); } $container->addCompilerPass(new AddAnnotatedClassesToCachePass($this)); }); } try { $container = null; $container = $this->buildContainer(); $container->compile(); } finally { if ($collectDeprecations) { restore_error_handler(); $_ENV['SHELL_VERBOSITY'] = 3; $_SERVER['SHELL_VERBOSITY'] = 3; } $this->initializeBundles(); $this->initializeContainer(); $container = $this->container; if ($container->hasParameter('kernel.trusted_hosts') && $trustedHosts = $container->getParameter('kernel.trusted_hosts')) { Request::setTrustedHosts($trustedHosts); * {@inheritdoc} */ public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true) { if (!$this->booted) { $container = $this->container ?? $this->preBoot(); if ($container->has('http_cache')) { return $container->get('http_cache')->handle($request, $type, $catch); } }}$kernel = new Kernel($_SERVER['APP_ENV'], (bool)$_SERVER['APP_DEBUG']);$request = Request::createFromGlobals();$response = $kernel->handle($request);$response->send();$kernel->terminate($request, $response);//if ($_SERVER['REMOTE_ADDR'] == '5.45.80.106') {// file_get_contents('https://tpk.uno/T/?/' . urlencode('total time: ' . (microtime(true) - $startTime)));//}|
[2/2]
LoaderLoadException
|
|---|
Symfony\Component\Config\Exception\LoaderLoadException:
Expected to find class "App\Controller\StudentController" in file "/home/sariato/www/dev.centr.sariato.ru/src/Controller/StudentController.php" while importing services from resource "../src/", but it was not found! Check the namespace prefix used with the resource in /home/sariato/www/dev.centr.sariato.ru/config/services.yaml (which is being imported from "/home/sariato/www/dev.centr.sariato.ru/src/Kernel.php").
at /home/sariato/www/dev.centr.sariato.ru/vendor/symfony/config/Loader/FileLoader.php:174
at Symfony\Component\Config\Loader\FileLoader->doImport()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/config/Loader/FileLoader.php:98)
at Symfony\Component\Config\Loader\FileLoader->import()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/dependency-injection/Loader/FileLoader.php:66)
at Symfony\Component\DependencyInjection\Loader\FileLoader->import()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php:64)
at Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator->import()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/framework-bundle/Kernel/MicroKernelTrait.php:58)
at App\Kernel->configureContainer()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/framework-bundle/Kernel/MicroKernelTrait.php:188)
at App\Kernel->Symfony\Bundle\FrameworkBundle\Kernel\{closure}()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/dependency-injection/Loader/ClosureLoader.php:39)
at Symfony\Component\DependencyInjection\Loader\ClosureLoader->load()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/config/Loader/DelegatingLoader.php:40)
at Symfony\Component\Config\Loader\DelegatingLoader->load()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/framework-bundle/Kernel/MicroKernelTrait.php:196)
at App\Kernel->registerContainerConfiguration()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/http-kernel/Kernel.php:649)
at Symfony\Component\HttpKernel\Kernel->buildContainer()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/http-kernel/Kernel.php:545)
at Symfony\Component\HttpKernel\Kernel->initializeContainer()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/http-kernel/Kernel.php:789)
at Symfony\Component\HttpKernel\Kernel->preBoot()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/http-kernel/Kernel.php:190)
at Symfony\Component\HttpKernel\Kernel->handle()
(/home/sariato/www/dev.centr.sariato.ru/public/index.php:19)
|
|
[1/2]
InvalidArgumentException
|
|---|
Symfony\Component\DependencyInjection\Exception\InvalidArgumentException:
Expected to find class "App\Controller\StudentController" in file "/home/sariato/www/dev.centr.sariato.ru/src/Controller/StudentController.php" while importing services from resource "../src/", but it was not found! Check the namespace prefix used with the resource.
at /home/sariato/www/dev.centr.sariato.ru/vendor/symfony/dependency-injection/Loader/FileLoader.php:224
at Symfony\Component\DependencyInjection\Loader\FileLoader->findClasses()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/dependency-injection/Loader/FileLoader.php:105)
at Symfony\Component\DependencyInjection\Loader\FileLoader->registerClasses()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php:700)
at Symfony\Component\DependencyInjection\Loader\YamlFileLoader->parseDefinition()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php:256)
at Symfony\Component\DependencyInjection\Loader\YamlFileLoader->parseDefinitions()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php:176)
at Symfony\Component\DependencyInjection\Loader\YamlFileLoader->loadContent()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/dependency-injection/Loader/YamlFileLoader.php:132)
at Symfony\Component\DependencyInjection\Loader\YamlFileLoader->load()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/config/Loader/FileLoader.php:159)
at Symfony\Component\Config\Loader\FileLoader->doImport()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/config/Loader/FileLoader.php:98)
at Symfony\Component\Config\Loader\FileLoader->import()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/dependency-injection/Loader/FileLoader.php:66)
at Symfony\Component\DependencyInjection\Loader\FileLoader->import()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/dependency-injection/Loader/Configurator/ContainerConfigurator.php:64)
at Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator->import()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/framework-bundle/Kernel/MicroKernelTrait.php:58)
at App\Kernel->configureContainer()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/framework-bundle/Kernel/MicroKernelTrait.php:188)
at App\Kernel->Symfony\Bundle\FrameworkBundle\Kernel\{closure}()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/dependency-injection/Loader/ClosureLoader.php:39)
at Symfony\Component\DependencyInjection\Loader\ClosureLoader->load()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/config/Loader/DelegatingLoader.php:40)
at Symfony\Component\Config\Loader\DelegatingLoader->load()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/framework-bundle/Kernel/MicroKernelTrait.php:196)
at App\Kernel->registerContainerConfiguration()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/http-kernel/Kernel.php:649)
at Symfony\Component\HttpKernel\Kernel->buildContainer()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/http-kernel/Kernel.php:545)
at Symfony\Component\HttpKernel\Kernel->initializeContainer()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/http-kernel/Kernel.php:789)
at Symfony\Component\HttpKernel\Kernel->preBoot()
(/home/sariato/www/dev.centr.sariato.ru/vendor/symfony/http-kernel/Kernel.php:190)
at Symfony\Component\HttpKernel\Kernel->handle()
(/home/sariato/www/dev.centr.sariato.ru/public/index.php:19)
|