src/Controller/StudentController.php line 54

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Entity\CalendarEvent;
  4. use App\Entity\Candidate;
  5. use App\Entity\Person;
  6. use App\Entity\Student;
  7. use App\Entity\StudentLevel;
  8. use App\Enum\Student\Status;
  9. use App\Enum\Student\VirtualStatus;
  10. use App\Form\PersonType;
  11. use App\Form\StudentType;
  12. use App\Library\PyTg\PyTgUtils;
  13. use App\Library\TdLib\TdLibObject\Model\Message\Message;
  14. use App\Library\Utils\Other\Other;
  15. use App\Service\CalendarEvent\CalendarEventService;
  16. use App\Service\Candidate\CandidateService;
  17. use App\Service\Job\JobService;
  18. use App\Service\Person\PersonService;
  19. use App\Service\Student\StudentService;
  20. use App\Service\StudentLevel\StudentLevelService;
  21. use App\Service\User\UserService;
  22. use Symfony\Component\HttpFoundation\JsonResponse;
  23. use Symfony\Component\HttpFoundation\RedirectResponse;
  24. use Symfony\Component\HttpFoundation\Request;
  25. use Symfony\Component\HttpFoundation\Response;
  26. class StudentController extends BaseAbstractController
  27. {
  28.     public function list(Request $requestCandidateService $candidateService,
  29.                          StudentService $studentServiceCalendarEventService $calendarEventService): Response
  30.     {
  31.         $students $studentService->getBaseService()->getAll();
  32.         $persons array_map(function (Student $student) {
  33.             return $student->getPerson();
  34.         }, $students);
  35.         $candidatesByStudentAndCalendarEvent =
  36.             $candidateService->getCandidatesGroupedByStudentsAndCalendarEvents();
  37.         $calendarEvents $calendarEventService->getAll();
  38.         $calendarEvents Other::getIdIndexedEntityArray($calendarEvents);
  39.         $earnLevelCandidates $candidateService
  40.             ->getPersonsLastCandidateWithEarnLevelCalendarEvent($persons);
  41.         return $this->render('admin/student/students.html.twig', [
  42.             'students' => $students,
  43.             "candidatesByStudentAndCalendarEvent" => $candidatesByStudentAndCalendarEvent,
  44.             "calendarEvents" => $calendarEvents,
  45.             "earnLevelCandidates" => $earnLevelCandidates,
  46.         ]);
  47.     }
  48.     public function learners(Request $requestCandidateService $candidateService,
  49.                              StudentService $studentServiceCalendarEventService $calendarEventService,
  50.                              PersonService $personService): Response
  51.     {
  52.         $items $personService->getLearners();
  53.         $candidatesByStudentAndCalendarEvent =
  54.             $candidateService->getCandidatesGroupedByStudentsAndCalendarEvents();
  55.         $calendarEvents $calendarEventService->getAll();
  56.         $calendarEvents Other::getIdIndexedEntityArray($calendarEvents);
  57.         $earnLevelCandidates $candidateService
  58.             ->getPersonsLastCandidateWithEarnLevelCalendarEvent($items);
  59.         $itemsByStatusCount = [];
  60.         foreach (Status::getAsArray() as $status) {
  61.             $itemsByStatusCount[$status] = [
  62.                 "status" => $status,
  63.                 "statusText" => Status::getText($status),
  64.                 "count" => 0,
  65.             ];
  66.         }
  67.         $itemsByStatusCount["subscriber_virtual_status"] = [
  68.             "status" => "subscriber_virtual_status",
  69.             "statusText" => "Подписчик",
  70.             "count" => 0,
  71.         ];
  72.         foreach ($items as $item) {
  73.             $status $item->isIsStudent() ? $item->getStudent()->getStatus() : "subscriber_virtual_status";
  74.             $itemsByStatusCount[$status]["count"]++;
  75.         }
  76.         $itemsByStudentLevelCount = [];
  77.         for ($i 0$i <= 6$i++) {
  78.             $itemsByStudentLevelCount[$i] = [
  79.                 "level" => $i,
  80.                 "count" => 0,
  81.             ];
  82.         }
  83.         foreach ($items as $item) {
  84.             if ($item->isIsStudent() && Status::isListedStatus($item->getStudent()->getStatus())) {
  85.                 $level $item->getStudent()->getLevel();
  86.                 if ($level !== null) {
  87.                     if ($level 0) {
  88.                         dd($item);
  89.                     }
  90.                     $itemsByStudentLevelCount[$level]["count"]++;
  91.                 }
  92.             }
  93.         }
  94.         //sort by key, Status::canSendCampaignStatuses(), and rest statuses
  95.         $sorted = [];
  96.         foreach (Status::getCanSendCampaignStatuses() as $status) {
  97.             if (isset($itemsByStatusCount[$status])) {
  98.                 $sorted[$status] = $itemsByStatusCount[$status];
  99.             }
  100.         }
  101.         foreach ($itemsByStatusCount as $status => $itemByStatusCount) {
  102.             if (!isset($sorted[$status])) {
  103.                 $sorted[$status] = $itemByStatusCount;
  104.             }
  105.         }
  106.         $itemsByStatusCount $sorted;
  107.         $itemsWithCanSendCampaignStatusCount 0;
  108.         foreach ($items as $item) {
  109.             $status $item->isIsStudent() ? $item->getStudent()->getStatus() : null;
  110.             if ($status && Status::isStudentStatus($status)) {
  111.                 $itemsWithCanSendCampaignStatusCount++;
  112.             }
  113.         }
  114.         $allLeavedStudentsCount 0;
  115.         foreach ($items as $item) {
  116.             $status $item->isIsStudent() ? $item->getStudent()->getStatus() : null;
  117.             if ($status && Status::isLeavedStatus($status)) {
  118.                 $allLeavedStudentsCount++;
  119.             }
  120.         }
  121.         $allStudentsCount 0;
  122.         foreach ($items as $item) {
  123.             if ($item->isIsStudent()) {
  124.                 $allStudentsCount++;
  125.             }
  126.         }
  127.         return $this->render('admin/student/learners.html.twig', [
  128.             'items' => $items,
  129.             "candidatesByStudentAndCalendarEvent" => $candidatesByStudentAndCalendarEvent,
  130.             "calendarEvents" => $calendarEvents,
  131.             "earnLevelCandidates" => $earnLevelCandidates,
  132.             "itemsByStatusCount" => $itemsByStatusCount,
  133.             "itemsWithCanSendCampaignStatusCount" => $itemsWithCanSendCampaignStatusCount,
  134.             "allStudentsCount" => $allStudentsCount,
  135.             "allLeavedStudentsCount" => $allLeavedStudentsCount,
  136.             "itemsByStudentLevelCount" => $itemsByStudentLevelCount,
  137.         ]);
  138.     }
  139.     public function edit(Request $requestStudentService $studentService): Response
  140.     {
  141.         $id $request->get('id');
  142.         /**
  143.          * @var Student $item
  144.          */
  145.         $item $id $studentService->getBaseService()->get($id) : null;
  146.         $person $item $item->getPerson() : null;
  147.         $isNew false;
  148.         if (!$item) {
  149.             $person = new Person();
  150.             $item = (new Student());
  151.             $isNew true;
  152.         }
  153.         $form $this->createForm(StudentType::class, $item);
  154.         $form->handleRequest($request);
  155.         $personForm $this->createForm(PersonType::class, $person);
  156.         $personForm->handleRequest($request);
  157.         if ($form->isSubmitted() && $form->isValid()) {
  158.             $this->em->persist($person);
  159.             $this->em->flush();
  160.             $item->setPerson($person);
  161.             $this->em->persist($item);
  162.             $this->em->flush();
  163.             if ($isNew) {
  164.                 $studentLevel = (new StudentLevel())
  165.                     ->setStudent($item)
  166.                     ->setValue((int)$request->get("student")['level'])
  167.                     ->setComment('Уровень при добавлении ученика')
  168.                     ->setAuthor($this->getUser());
  169.                 $this->em->persist($studentLevel);
  170.                 $this->em->flush();
  171.             }
  172.             return $this->redirectToRoute('moderator_students');
  173.         }
  174.         return $this->render('admin/student/student_edit.html.twig', [
  175.             'form' => $form->createView(),
  176.             'personForm' => $personForm->createView(),
  177.             'item' => $item,
  178.         ]);
  179.     }
  180.     public function learnerEdit(Request $request,
  181.                                 PersonService $personServiceStudentLevelService $studentLevelService,
  182.                                 JobService $jobService): Response
  183.     {
  184.         $id $request->get('id');
  185.         /**
  186.          * @var Person $person
  187.          */
  188.         $person $id $personService->getBaseService()->get($id) : null;
  189.         /**
  190.          * @var Student $student
  191.          */
  192.         $student $person $person->getStudent() : null;
  193.         $isNew false;
  194.         if (!$person) {
  195.             $person = new Person();
  196.             $isNew true;
  197.         }
  198.         if (!$student) {
  199.             $student = (new Student());
  200.             $isNew true;
  201.         }
  202.         $oldStatus $student->getStatus();
  203.         $oldPhone $person->getPhone();
  204.         $form $this->createForm(StudentType::class, $student);
  205.         $form->handleRequest($request);
  206.         $personForm $this->createForm(PersonType::class, $person);
  207.         $personForm->handleRequest($request);
  208.         $virtualStatus null;
  209.         try {
  210.             if ($form->isSubmitted()) {
  211.                 $virtualStatus $request->get("person")['virtualStudentStatus'] ?? null;
  212.                 if ($virtualStatus === VirtualStatus::VIRTUAL_STATUS_SUBSCRIBER) {
  213.                     $person->setIsSubscriber(true);
  214.                     $person->setIsStudent(false);
  215.                 } elseif (Status::isCorrect($virtualStatus)) {
  216.                     $person->setIsStudent(true);
  217.                     $person->setIsSubscriber(false);
  218.                     $student->setStatus($virtualStatus);
  219.                 } else {
  220.                     throw new \Exception("Unknown: " $virtualStatus);
  221.                 }
  222.             }
  223.             if ($form->isSubmitted() && $form->isValid() && $personForm->isValid()) {
  224.                //todo redo
  225.                 $newJobName $request->get("newJobName");
  226.                 $jobId = (int)($request->get("person")['job'] ?? null);
  227.                 $addNewJob $jobId === -100;
  228.                 if ($addNewJob) {
  229.                     if (!$newJobName) {
  230.                         throw new \Exception('Название новой деятельности не может быть пустым.');
  231.                     }
  232.                     $job $jobService->createOrGetDefault(["name" => $newJobName]);
  233.                 } else {
  234.                     $job $jobService->getBaseService()->get($jobId);
  235.                 }
  236.                 $person->setJob($job);
  237.                 if ($person->isIsStudent() && $studentLevelService->calcStudentLevel($student) == -1) {
  238.                     throw new \Exception('Нельзя через редактор восстановить статус ученика подписчику.');
  239.                 }
  240.                 //обнуение студента при создании подписчика
  241.                 if ($person->isIsSubscriber() && $isNew) {
  242.                     $student null;
  243.                     $person->setStudent(null);
  244.                 }
  245.                 if ($personService->hasSameDefault($person)) {
  246.                     throw new \Exception('Учащийся с такими данными уже существует.');
  247.                 }
  248.                 $this->em->persist($person);
  249.                 $this->em->flush();
  250.                 if ($isNew && $student && !$student->getId()) {
  251.                     $student->setPerson($person)
  252.                         ->setLevel((int)$request->get("student")['level']);
  253.                 }
  254.                 if ($student && $student->getLevel() === null) {
  255.                     $student->setLevel(0);
  256.                 }
  257.                 //задание старого статуса студента при переводе студента в
  258.                 //подписчики или после редактирования подписчика,
  259.                 //который ранее был студентом
  260.                 if ($student && $oldStatus && !$person->isIsStudent() && !$isNew) {
  261.                     $student->setStatus($oldStatus);
  262.                 }
  263.                 //уровень студента -1 при переводе из студента в подписчики
  264.                 if ($person->isIsSubscriber() && $student && $student->getLevels()->count()) {
  265.                     $level $studentLevelService->calcStudentLevel($student);
  266.                     if ($level > -1) {
  267.                         $studentLevel = (new StudentLevel())
  268.                             ->setStudent($student)
  269.                             ->setValue($studentLevelService->calcStudentLevel($student) * -1)
  270.                             ->setComment('Перевод в подписчики')
  271.                             ->setAuthor($this->getUser());
  272.                         $this->em->persist($studentLevel);
  273.                     }
  274.                     //если по какой-то причине уровень неверно рассчитан
  275.                     if ($student && $student->getLevel() != -1) {
  276.                         $student->setLevel($studentLevelService->calcStudentLevel($student));
  277.                         $this->em->flush();
  278.                     }
  279.                 }
  280.                 if ($oldPhone && $oldPhone != $person->getPhone()) {
  281.                     $person->setTelegramUserId(null);
  282.                 }
  283.                 if ($person->isIsStudent()) {
  284.                     $student->setPerson($person);
  285.                     if ($student->getLevel() === null) {
  286.                         $student->setLevel(0);
  287.                     }
  288.                     $this->em->persist($student);
  289.                     $this->em->flush();
  290.                 }
  291.                 if ($isNew) {
  292.                     if ($person->isIsStudent()) {
  293.                         $studentLevel = (new StudentLevel())
  294.                             ->setStudent($student)
  295.                             ->setValue((int)$request->get("student")['level'])
  296.                             ->setComment('Уровень при добавлении ученика')
  297.                             ->setAuthor($this->getUser());
  298.                         $this->em->persist($studentLevel);
  299.                         $this->em->flush();
  300.                     }
  301.                 }
  302.                 return $this->redirectToRoute('moderator_learners');
  303.             }
  304.         } catch (\Throwable $e) {
  305.             $this->addFlash('errors'$e->getMessage());
  306.         }
  307.         return $this->render('admin/student/learner_edit.html.twig', [
  308.             'form' => $form->createView(),
  309.             'personForm' => $personForm->createView(),
  310.             'item' => $student,
  311.             'person' => $person,
  312.         ]);
  313.     }
  314.     public function delete(Request $requestStudentService $studentService,
  315.         PersonService $personService): RedirectResponse
  316.     {
  317.         $id $request->get('id');
  318.         if (!$id) {
  319.             $this->addFlash('errors''Не указан идентификатор учащегося.');
  320.             return $this->redirectToRoute('moderator_learners');
  321.         }
  322.         /** @var Person|null $item */
  323.         $item $personService->getBaseService()->get($id);
  324.         if (!$item) {
  325.             $this->addFlash('errors''Учащийся не найден.');
  326.             return $this->redirectToRoute('moderator_learners');
  327.         }
  328.         try {
  329.             $item->setStatus(Status::STATUS_DELETED);
  330.             if ($item->getStudent()) {
  331.                 $item->getStudent()->setStatus(Status::STATUS_DELETED);
  332.             }
  333.             $this->em->flush();
  334.             $studentName $item->getName();
  335.             $this->addFlash('success''Учащийся ' $studentName ' помечен как удалённый.');
  336.         } catch (\Throwable $e) {
  337.             $this->addFlash('errors''Ошибка при удалении учащегося. ' $e->getMessage() . '.');
  338.         }
  339.         return $this->redirectToRoute('moderator_learners');
  340.     }
  341.     public function registration(Request $requestPersonService $personService,
  342.                                  CalendarEventService $calendarEventServiceCandidateService $candidateService): Response
  343.     {
  344.         $calendarEventId $request->query->get('calendarEventId');
  345.         $selectedCalendarEvent null;
  346.         $students = [];
  347.         // Get all calendar events without review requirement
  348.         $allCalendarEvents $calendarEventService->getAll();
  349.         $calendarEvents array_filter($allCalendarEvents, function($event) {
  350.             return !$event->isIsCandidateReviewRequired();
  351.         });
  352.         if ($calendarEventId) {
  353.             /** @var CalendarEvent $selectedCalendarEvent */
  354.             $selectedCalendarEvent $calendarEventService->getBaseService()->get($calendarEventId);
  355.             if ($selectedCalendarEvent) {
  356.                 // Get students with matching level
  357.                 $fromLevel $selectedCalendarEvent->getFromLevel();
  358.                 $allStudents $personService->getLearners();
  359.                 $students array_filter($allStudents, function($person) use ($fromLevel$selectedCalendarEvent) {
  360.                     if (!Status::isCanSendCampaignStatus($person->getVirtualStudentStatus())) {
  361.                         return false;
  362.                     }
  363.                     if ($selectedCalendarEvent->getRequireStudentStatus()
  364.                         && $person->getVirtualStudentStatus() != $selectedCalendarEvent->getRequireStudentStatus()) {
  365.                         return false;
  366.                     }
  367.                     if ($selectedCalendarEvent->getFromLevel() === null) {
  368.                         return true;
  369.                     }
  370.                     if ($person->isIsStudent() && $person->getStudent()) {
  371.                         $level $person->getStudent()->getLevel();
  372.                         return $level !== null && $level >= $fromLevel
  373.                             && ($selectedCalendarEvent->getToLevel() === null || $level <= $selectedCalendarEvent->getToLevel())
  374.                             && $person->isCalendarEventStatusAllowed($selectedCalendarEvent);
  375.                     }
  376.                     return false;
  377.                 });
  378.                 // Get existing candidates for this event
  379.                 $existingCandidates $candidateService->getDefault([
  380.                     'calendarEvent' => $selectedCalendarEvent,
  381.                     'status' => [\App\Enum\Candidate\Status::STATUS_NEW,
  382.                         \App\Enum\Candidate\Status::STATUS_APPROVED,
  383.                         \App\Enum\Candidate\Status::STATUS_DRAFT]
  384.                 ]);
  385.                 $candidatesByPerson = [];
  386.                 foreach ($existingCandidates as $candidate) {
  387.                     if ($candidate->getPerson()) {
  388.                         $candidatesByPerson[$candidate->getPerson()->getId()] = $candidate;
  389.                     }
  390.                 }
  391.             }
  392.         }
  393.         // Prepare dropdown data for calendar events
  394.         $routeParamDropdownsData = [
  395.             'calendarEvent' => array_map(function($event) {
  396.                 return [
  397.                     'listItemId' => $event->getId(),
  398.                     'text' => $event->getText(['quotes' => true])
  399.                 ];
  400.             }, array_values($calendarEvents))
  401.         ];
  402.         return $this->render('admin/student/registration.html.twig', [
  403.             'students' => $students,
  404.             'calendarEvents' => $calendarEvents,
  405.             'selectedCalendarEvent' => $selectedCalendarEvent,
  406.             'routeParamDropdownsData' => $routeParamDropdownsData,
  407.             'candidatesByPerson' => $candidatesByPerson ?? []
  408.         ]);
  409.     }
  410.     public function toggleRegistration(Request $requestCandidateService $candidateService,
  411.                                        PersonService $personServiceCalendarEventService $calendarEventService,
  412.                                        UserService $userService): JsonResponse
  413.     {
  414.         return \App\Library\Utils\ApiHandler::handleApiRequest($request, function (&$responseCode$content) use (
  415.             $candidateService$personService$calendarEventService$userService
  416.         ) {
  417.             $personId $content['person_id'] ?? null;
  418.             $calendarEventId $content['calendar_event_id'] ?? null;
  419.             if (!$personId || !$calendarEventId) {
  420.                 $responseCode 400;
  421.                 return ['error' => 'Missing person_id or calendar_event_id'];
  422.             }
  423.             /** @var Person $person */
  424.             $person $personService->getBaseService()->get($personId);
  425.             /** @var CalendarEvent $calendarEvent */
  426.             $calendarEvent $calendarEventService->getBaseService()->get($calendarEventId);
  427.             if (!$person || !$calendarEvent) {
  428.                 $responseCode 404;
  429.                 return ['error' => 'Person or CalendarEvent not found'];
  430.             }
  431.             // Check if candidate already exists
  432.             $existingCandidates $candidateService->getDefault([
  433.                 'person' => $person,
  434.                 'calendarEvent' => $calendarEvent,
  435.             ]);
  436.             /** @var Candidate $existingCandidate */
  437.             $existingCandidate count($existingCandidates) > $existingCandidates[0] : null;
  438.             $existsWithStatusNotNew $existingCandidate && $existingCandidate->getStatus() !== \App\Enum\Candidate\Status::STATUS_NEW;
  439.             if ($existsWithStatusNotNew && $calendarEvent->isIsCandidateReviewRequired()) {
  440.                 $responseCode 400;
  441.                 return ['error' => 'Статус кандидата: ' $existingCandidate->getStatusText()];
  442.             }
  443.             if ($existingCandidate) {
  444.                 // Delete candidate
  445.                 $this->em->remove($existingCandidate);
  446.                 $this->em->flush();
  447.                 $responseCode 200;
  448.                 return ['action' => 'deleted''candidateId' => null];
  449.             } else {
  450.                 // Create new candidate
  451.                 $candidate = new \App\Entity\Candidate();
  452.                 $candidate->setPerson($person);
  453.                 $candidate->setCalendarEvent($calendarEvent);
  454.                 $candidate->setAuthor($this->getUser());
  455.                 $candidate->setCurator($userService->getStarCurator());
  456.                 $candidate->setStatus(\App\Enum\Candidate\Status::STATUS_APPROVED);
  457.                 $candidate->setUpdatedAt(new \DateTime());
  458.                 $this->em->persist($candidate);
  459.                 $this->em->flush();
  460.                 $responseCode 200;
  461.                 return ['action' => 'created''candidateId' => $candidate->getId()];
  462.             }
  463.         }, true, ['person_id''calendar_event_id']);
  464.     }
  465.     public function loadTelegramHistory(Request $requestPersonService $personService,
  466.                                         PyTgUtils $pyTgUtils): JsonResponse
  467.     {
  468.         return \App\Library\Utils\ApiHandler::handleApiRequest($request, function (&$responseCode$content) use (
  469.             $personService$pyTgUtils
  470.         ) {
  471.             $personId $content['person_id'] ?? null;
  472.             if (!$personId) {
  473.                 $responseCode 400;
  474.                 return ['error' => 'Missing person_id'];
  475.             }
  476.             /** @var Person $person */
  477.             $person $personService->getBaseService()->get($personId);
  478.             if (!$person) {
  479.                 $responseCode 404;
  480.                 return ['error' => 'Person not found'];
  481.             }
  482.             $telegramUserId $person->getTelegramUserId();
  483.             if (!$telegramUserId) {
  484.                 $responseCode 400;
  485.                 return ['error' => 'Telegram user ID not set'];
  486.             }
  487.             try {
  488.                 // Устанавливаем технический аккаунт администратора
  489.                 $pyTgUtils->setTechAdminAccount();
  490.                 // Создаем приватный чат
  491.                 $chat $pyTgUtils->pyTg->createPrivateChat($telegramUserId);
  492.                 $chatId $chat['id'];
  493.                 // Получаем последние 5 сообщений
  494.                 $messages $pyTgUtils->pyTg->getChatHistory($chatId10);
  495.                 $messages array_map(function (Message $message) {
  496.                     return $message->getData();
  497.                 }, $messages);
  498.                 // Обрабатываем сообщения
  499.                 $processedMessages = [];
  500.                 $debugInfo = [];
  501.                 foreach ($messages as $messageIndex => $message) {
  502.                     $processedMessage = [
  503.                         'id' => $message['id'] ?? null,
  504.                         'date' => $message['date'] ?? null,
  505.                         'is_outgoing' => $message['is_outgoing'] ?? false,
  506.                         'text' => '',
  507.                         'images' => [],
  508.                         'debug' => [] // Отладочная информация (будет удалена перед отправкой)
  509.                     ];
  510.                     $messageDebug = [
  511.                         'message_index' => $messageIndex,
  512.                         'message_id' => $message['id'] ?? null,
  513.                         'content_type' => $message['content']['@type'] ?? 'unknown',
  514.                     ];
  515.                     // Получаем текст сообщения
  516.                     if (isset($message['content'])) {
  517.                         if (isset($message['content']['text']) && isset($message['content']['text']['text'])) {
  518.                             $processedMessage['text'] = $message['content']['text']['text'];
  519.                         } elseif (isset($message['content']['caption']) && isset($message['content']['caption']['text'])) {
  520.                             $processedMessage['text'] = $message['content']['caption']['text'];
  521.                         }
  522.                         // Обрабатываем фото
  523.                         if (isset($message['content']['@type']) && $message['content']['@type'] === 'messagePhoto') {
  524.                             $messageDebug['has_photo'] = true;
  525.                             if (isset($message['content']['photo']['sizes'])) {
  526.                                 $sizes $message['content']['photo']['sizes'];
  527.                                 $messageDebug['sizes_count'] = count($sizes);
  528.                                 // Берем изображение среднего размера (для оптимизации)
  529.                                 // Если размеров меньше 3, берем последнее
  530.                                 $photoIndex count($sizes) > count($sizes) - count($sizes) - 1;
  531.                                 $selectedPhoto $sizes[$photoIndex];
  532.                                 $messageDebug['selected_photo_index'] = $photoIndex;
  533.                                 $messageDebug['selected_photo_type'] = $selectedPhoto['type'] ?? 'unknown';
  534.                                 $imageLoaded false;
  535.                                 $imagePath null;
  536.                                 // Проверяем, есть ли локальный путь к файлу
  537.                                 if (isset($selectedPhoto['photo']['local']['path']) &&
  538.                                     file_exists($selectedPhoto['photo']['local']['path'])) {
  539.                                     $imagePath $selectedPhoto['photo']['local']['path'];
  540.                                     $messageDebug['source'] = 'local_path';
  541.                                     $messageDebug['path'] = $imagePath;
  542.                                     $imageLoaded true;
  543.                                 } elseif (isset($selectedPhoto['photo']['id'])) {
  544.                                     // Если файл не загружен локально, загружаем через API
  545.                                     $messageDebug['source'] = 'api_download';
  546.                                     $messageDebug['file_id'] = $selectedPhoto['photo']['id'];
  547.                                     try {
  548.                                         // downloadFile возвращает base64 данные напрямую
  549.                                         $base64Data $pyTgUtils->pyTg->downloadFile($selectedPhoto['photo']['id']);
  550.                                         $messageDebug['download_response'] = [
  551.                                             'is_string' => is_string($base64Data),
  552.                                             'data_length' => is_string($base64Data) ? strlen($base64Data) : 0,
  553.                                         ];
  554.                                         if (is_string($base64Data) && !empty($base64Data)) {
  555.                                             // Данные уже в base64, используем их напрямую
  556.                                             $processedMessage['images'][] = "data:image/jpeg;base64," $base64Data;
  557.                                             $messageDebug['base64_length'] = strlen($base64Data);
  558.                                             $messageDebug['success'] = true;
  559.                                             $imageLoaded true// Помечаем, что изображение загружено
  560.                                         } else {
  561.                                             $messageDebug['error'] = 'Invalid base64 data received';
  562.                                         }
  563.                                     } catch (\Exception $e) {
  564.                                         $messageDebug['error'] = 'Download failed: ' $e->getMessage();
  565.                                     }
  566.                                 }
  567.                                 // Конвертируем изображение в base64 (только для локальных файлов)
  568.                                 if ($imageLoaded && $imagePath) {
  569.                                     try {
  570.                                         $fileSize filesize($imagePath);
  571.                                         $messageDebug['file_size'] = $fileSize;
  572.                                         // Ограничение размера файла (5 МБ)
  573.                                         if ($fileSize 1024 1024) {
  574.                                             $messageDebug['error'] = 'File too large: ' $fileSize ' bytes';
  575.                                         } else {
  576.                                             $imageData file_get_contents($imagePath);
  577.                                             if ($imageData !== false) {
  578.                                                 $base64 base64_encode($imageData);
  579.                                                 $mimeType mime_content_type($imagePath);
  580.                                                 $processedMessage['images'][] = "data:$mimeType;base64,$base64";
  581.                                                 $messageDebug['mime_type'] = $mimeType;
  582.                                                 $messageDebug['base64_length'] = strlen($base64);
  583.                                                 $messageDebug['success'] = true;
  584.                                             } else {
  585.                                                 $messageDebug['error'] = 'Failed to read file contents';
  586.                                             }
  587.                                         }
  588.                                     } catch (\Exception $e) {
  589.                                         $messageDebug['error'] = 'Base64 conversion failed: ' $e->getMessage();
  590.                                     }
  591.                                 }
  592.                             } else {
  593.                                 $messageDebug['error'] = 'No photo sizes found';
  594.                             }
  595.                         }
  596.                     }
  597.                     $debugInfo[] = $messageDebug;
  598.                     // Удаляем debug перед добавлением в результат
  599.                     unset($processedMessage['debug']);
  600.                     $processedMessages[] = $processedMessage;
  601.                 }
  602.                 // Сортируем сообщения по дате (от старых к новым)
  603.                 usort($processedMessages, function($a$b) {
  604.                     return ($a['date'] ?? 0) - ($b['date'] ?? 0);
  605.                 });
  606.                 $responseCode 200;
  607.                 return [
  608.                     'success' => true,
  609.                     'messages' => $processedMessages,
  610.                     'person_name' => $person->getName(),
  611.                     'debug' => $debugInfo// Отладочная информация
  612.                     'total_messages' => count($processedMessages),
  613.                     'messages_with_images' => count(array_filter($processedMessages, function($m) {
  614.                         return !empty($m['images']);
  615.                     }))
  616.                 ];
  617.             } catch (\Exception $e) {
  618.                 $responseCode 500;
  619.                 return ['error' => 'Failed to load Telegram history: ' $e->getMessage()];
  620.             }
  621.         }, true, ['person_id']);
  622.     }
  623. }