| Total Complexity | 85 |
| Total Lines | 579 |
| Duplicated Lines | 0 % |
| Changes | 1 | ||
| Bugs | 0 | Features | 0 |
Complex classes like AttendanceController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use AttendanceController, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 37 | #[Route('/attendance')] |
||
| 38 | class AttendanceController extends AbstractController |
||
| 39 | { |
||
| 40 | public function __construct( |
||
| 41 | private readonly CAttendanceCalendarRepository $attendanceCalendarRepository, |
||
| 42 | private readonly EntityManagerInterface $em, |
||
| 43 | private readonly TranslatorInterface $translator |
||
| 44 | ) {} |
||
| 45 | |||
| 46 | #[Route('/full-data', name: 'chamilo_core_attendance_get_full_data', methods: ['GET'])] |
||
| 47 | public function getFullAttendanceData(Request $request): JsonResponse |
||
| 48 | { |
||
| 49 | $attendanceId = (int) $request->query->get('attendanceId', 0); |
||
| 50 | |||
| 51 | if (!$attendanceId) { |
||
| 52 | return $this->json(['error' => 'Attendance ID is required'], 400); |
||
| 53 | } |
||
| 54 | |||
| 55 | $data = $this->attendanceCalendarRepository->findAttendanceWithData($attendanceId); |
||
| 56 | |||
| 57 | return $this->json($data, 200); |
||
| 58 | } |
||
| 59 | |||
| 60 | #[Route('/{id}/users/context', name: 'chamilo_core_get_users_with_faults', methods: ['GET'])] |
||
| 61 | public function getUsersWithFaults( |
||
| 62 | int $id, |
||
| 63 | Request $request, |
||
| 64 | UserRepository $userRepository, |
||
| 65 | CAttendanceCalendarRepository $calendarRepository, |
||
| 66 | CAttendanceSheetRepository $sheetRepository |
||
| 67 | ): JsonResponse { |
||
| 68 | $courseId = (int) $request->query->get('courseId', 0); |
||
| 69 | $sessionId = $request->query->get('sessionId') ? (int) $request->query->get('sessionId') : null; |
||
| 70 | $groupId = $request->query->get('groupId') ? (int) $request->query->get('groupId') : null; |
||
| 71 | |||
| 72 | $attendance = $this->em->getRepository(CAttendance::class)->find($id); |
||
| 73 | if (!$attendance) { |
||
| 74 | return $this->json(['error' => 'Attendance not found'], 404); |
||
| 75 | } |
||
| 76 | |||
| 77 | $calendars = $attendance->getCalendars(); |
||
| 78 | $totalCalendars = count($calendars); |
||
| 79 | |||
| 80 | $users = $userRepository->findUsersByContext($courseId, $sessionId, $groupId); |
||
| 81 | |||
| 82 | $formattedUsers = array_map(function ($user) use ($userRepository, $courseId, $groupId, $calendarRepository, $sheetRepository, $calendars) { |
||
| 83 | |||
| 84 | $absences = 0; |
||
| 85 | |||
| 86 | foreach ($calendars as $calendar) { |
||
| 87 | $sheet = $sheetRepository->findOneBy([ |
||
| 88 | 'user' => $user, |
||
| 89 | 'attendanceCalendar' => $calendar, |
||
| 90 | ]); |
||
| 91 | |||
| 92 | if (!$sheet || $sheet->getPresence() === null) { |
||
| 93 | continue; |
||
| 94 | } |
||
| 95 | |||
| 96 | if ($sheet->getPresence() !== 1) { |
||
| 97 | $absences++; |
||
| 98 | } |
||
| 99 | } |
||
| 100 | |||
| 101 | $percentage = count($calendars) > 0 ? round(($absences * 100) / count($calendars)) : 0; |
||
| 102 | |||
| 103 | return [ |
||
| 104 | 'id' => $user->getId(), |
||
| 105 | 'firstname' => $user->getFirstname(), |
||
| 106 | 'lastname' => $user->getLastname(), |
||
| 107 | 'email' => $user->getEmail(), |
||
| 108 | 'username' => $user->getUsername(), |
||
| 109 | 'photo' => $userRepository->getUserPicture($user->getId()), |
||
| 110 | 'notAttended' => "$absences/" . count($calendars) . " ({$percentage}%)", |
||
| 111 | ]; |
||
| 112 | }, $users); |
||
| 113 | |||
| 114 | return $this->json($formattedUsers, 200); |
||
| 115 | } |
||
| 116 | |||
| 117 | #[Route('/list_with_done_count', name: 'attendance_list_with_done_count', methods: ['GET'])] |
||
| 118 | public function listWithDoneCount(Request $request): JsonResponse |
||
| 119 | { |
||
| 120 | $courseId = (int) $request->query->get('cid', 0); |
||
| 121 | $sessionId = $request->query->get('sid') ? (int) $request->query->get('sid') : null; |
||
| 122 | $groupId = $request->query->get('gid') ? (int) $request->query->get('gid') : null; |
||
| 123 | $parentNode = (int) $request->query->get('resourceNode.parent', 0); |
||
| 124 | |||
| 125 | $attendances = $this->em->getRepository(CAttendance::class)->findBy([ |
||
| 126 | 'active' => 1, |
||
| 127 | ]); |
||
| 128 | |||
| 129 | $result = []; |
||
| 130 | foreach ($attendances as $attendance) { |
||
| 131 | $doneCount = $this->attendanceCalendarRepository->countDoneAttendanceByAttendanceAndGroup($attendance->getIid(), $groupId); |
||
| 132 | |||
| 133 | $result[] = [ |
||
| 134 | 'id' => $attendance->getIid(), |
||
| 135 | 'title' => $attendance->getTitle(), |
||
| 136 | 'description' => $attendance->getDescription(), |
||
| 137 | 'attendanceWeight' => $attendance->getAttendanceWeight(), |
||
| 138 | 'attendanceQualifyTitle' => $attendance->getAttendanceQualifyTitle(), |
||
| 139 | 'resourceLinkListFromEntity' => $attendance->getResourceLinkListFromEntity(), |
||
| 140 | 'doneCalendars' => $doneCount, |
||
| 141 | ]; |
||
| 142 | } |
||
| 143 | |||
| 144 | return $this->json($result); |
||
| 145 | } |
||
| 146 | |||
| 147 | #[Route('/{id}/export/pdf', name: 'attendance_export_pdf', methods: ['GET'])] |
||
| 148 | public function exportToPdf(int $id, Request $request): Response |
||
| 149 | { |
||
| 150 | $courseId = (int) $request->query->get('cid'); |
||
| 151 | $sessionId = ((int) $request->query->get('sid')) ?: null; |
||
| 152 | $groupId = ((int) $request->query->get('gid')) ?: null; |
||
| 153 | |||
| 154 | $attendance = $this->em->getRepository(CAttendance::class)->find($id); |
||
| 155 | if (!$attendance) { |
||
| 156 | throw $this->createNotFoundException('Attendance not found'); |
||
| 157 | } |
||
| 158 | |||
| 159 | $calendars = $attendance->getCalendars(); |
||
| 160 | $totalCalendars = count($calendars); |
||
| 161 | |||
| 162 | $students = $this->em->getRepository(User::class)->findUsersByContext($courseId, $sessionId, $groupId); |
||
| 163 | $sheetRepo = $this->em->getRepository(CAttendanceSheet::class); |
||
| 164 | |||
| 165 | $course = $this->em->getRepository(Course::class)->find($courseId); |
||
| 166 | $teacher = null; |
||
| 167 | |||
| 168 | if ($sessionId) { |
||
| 169 | $session = $this->em->getRepository(Session::class)->find($sessionId); |
||
| 170 | $teacher = $session?->getCourseCoachesSubscriptions() |
||
| 171 | ->filter(fn($rel) => $rel->getCourse()?->getId() === $courseId) |
||
| 172 | ->first()?->getUser()?->getFullname(); |
||
| 173 | } else { |
||
| 174 | $teacher = $course?->getTeachersSubscriptions()?->first()?->getUser()?->getFullname(); |
||
| 175 | } |
||
| 176 | |||
| 177 | // Header |
||
| 178 | $dataTable = []; |
||
| 179 | $header = ['#', 'Last Name', 'First Name', 'Not Attended']; |
||
| 180 | foreach ($calendars as $calendar) { |
||
| 181 | $header[] = $calendar->getDateTime()->format('d/m H:i'); |
||
| 182 | } |
||
| 183 | $dataTable[] = $header; |
||
| 184 | |||
| 185 | // Rows |
||
| 186 | $count = 1; |
||
| 187 | $stateLabels = CAttendanceSheet::getPresenceLabels(); |
||
| 188 | |||
| 189 | foreach ($students as $student) { |
||
| 190 | $row = [ |
||
| 191 | $count++, |
||
| 192 | $student->getLastname(), |
||
| 193 | $student->getFirstname(), |
||
| 194 | '', |
||
| 195 | ]; |
||
| 196 | |||
| 197 | $absences = 0; |
||
| 198 | foreach ($calendars as $calendar) { |
||
| 199 | $sheetEntity = $sheetRepo->findOneBy([ |
||
| 200 | 'user' => $student, |
||
| 201 | 'attendanceCalendar' => $calendar, |
||
| 202 | ]); |
||
| 203 | |||
| 204 | if (!$sheetEntity || $sheetEntity->getPresence() === null) { |
||
| 205 | $row[] = ''; |
||
| 206 | continue; |
||
| 207 | } |
||
| 208 | |||
| 209 | $presence = $sheetEntity->getPresence(); |
||
| 210 | $row[] = $stateLabels[$presence] ?? 'NP'; |
||
| 211 | |||
| 212 | if ($presence === CAttendanceSheet::ABSENT) { |
||
| 213 | $absences++; |
||
| 214 | } |
||
| 215 | } |
||
| 216 | |||
| 217 | $percentage = $totalCalendars > 0 ? round(($absences * 100) / $totalCalendars) : 0; |
||
| 218 | $row[3] = "$absences/$totalCalendars ($percentage%)"; |
||
| 219 | $dataTable[] = $row; |
||
| 220 | } |
||
| 221 | |||
| 222 | // Render HTML |
||
| 223 | $html = ' |
||
| 224 | <style> |
||
| 225 | body { font-family: sans-serif; font-size: 12px; } |
||
| 226 | h2 { text-align: center; margin-bottom: 5px; } |
||
| 227 | table.meta { margin: 0 auto 10px auto; width: 80%; } |
||
| 228 | table.meta td { padding: 2px 5px; } |
||
| 229 | table.attendance { border-collapse: collapse; width: 100%; } |
||
| 230 | .attendance th, .attendance td { border: 1px solid #000; padding: 4px; text-align: center; } |
||
| 231 | .np { color: red; font-weight: bold; } |
||
| 232 | </style> |
||
| 233 | |||
| 234 | <h2>' . htmlspecialchars($attendance->getTitle()) . '</h2> |
||
| 235 | |||
| 236 | <table class="meta"> |
||
| 237 | <tr><td><strong>Trainer:</strong></td><td>' . htmlspecialchars($teacher ?? '-') . '</td></tr> |
||
| 238 | <tr><td><strong>Course:</strong></td><td>' . htmlspecialchars($course?->getTitleAndCode() ?? '-') . '</td></tr> |
||
| 239 | <tr><td><strong>Date:</strong></td><td>' . date('F d, Y \a\t h:i A') . '</td></tr> |
||
| 240 | </table> |
||
| 241 | |||
| 242 | <table class="attendance"> |
||
| 243 | <tr>'; |
||
| 244 | foreach ($dataTable[0] as $cell) { |
||
| 245 | $html .= '<th>' . htmlspecialchars((string) $cell) . '</th>'; |
||
| 246 | } |
||
| 247 | $html .= '</tr>'; |
||
| 248 | |||
| 249 | foreach (array_slice($dataTable, 1) as $row) { |
||
| 250 | $html .= '<tr>'; |
||
| 251 | foreach ($row as $cell) { |
||
| 252 | $class = $cell === 'NP' ? ' class="np"' : ''; |
||
| 253 | $html .= "<td$class>" . htmlspecialchars((string) $cell) . "</td>"; |
||
| 254 | } |
||
| 255 | $html .= '</tr>'; |
||
| 256 | } |
||
| 257 | |||
| 258 | $html .= '</table>'; |
||
| 259 | |||
| 260 | try { |
||
| 261 | $mpdf = new \Mpdf\Mpdf([ |
||
| 262 | 'orientation' => 'L', |
||
| 263 | 'tempDir' => api_get_path(SYS_ARCHIVE_PATH) . 'mpdf/', |
||
| 264 | ]); |
||
| 265 | $mpdf->WriteHTML($html); |
||
| 266 | |||
| 267 | return new Response( |
||
| 268 | $mpdf->Output('', \Mpdf\Output\Destination::INLINE), |
||
| 269 | 200, |
||
| 270 | [ |
||
| 271 | 'Content-Type' => 'application/pdf', |
||
| 272 | 'Content-Disposition' => 'attachment; filename="attendance-' . $id . '.pdf"', |
||
| 273 | ] |
||
| 274 | ); |
||
| 275 | } catch (\Mpdf\MpdfException $e) { |
||
| 276 | throw new \RuntimeException('Failed to generate PDF: ' . $e->getMessage(), 500, $e); |
||
| 277 | } |
||
| 278 | } |
||
| 279 | |||
| 280 | #[Route('/{id}/export/xls', name: 'attendance_export_xls', methods: ['GET'])] |
||
| 281 | public function exportToXls(int $id, Request $request): Response |
||
| 282 | { |
||
| 283 | $courseId = (int) $request->query->get('cid'); |
||
| 284 | $sessionId = $request->query->get('sid') ? (int) $request->query->get('sid') : null; |
||
| 285 | $groupId = $request->query->get('gid') ? (int) $request->query->get('gid') : null; |
||
| 286 | |||
| 287 | $attendance = $this->em->getRepository(CAttendance::class)->find($id); |
||
| 288 | if (!$attendance) { |
||
| 289 | throw $this->createNotFoundException('Attendance not found'); |
||
| 290 | } |
||
| 291 | |||
| 292 | $calendars = $attendance->getCalendars(); |
||
| 293 | $totalCalendars = count($calendars); |
||
| 294 | $students = $this->em->getRepository(User::class)->findUsersByContext($courseId, $sessionId, $groupId); |
||
| 295 | $sheetRepo = $this->em->getRepository(CAttendanceSheet::class); |
||
| 296 | |||
| 297 | $stateLabels = CAttendanceSheet::getPresenceLabels(); |
||
| 298 | |||
| 299 | $spreadsheet = new Spreadsheet(); |
||
| 300 | $sheet = $spreadsheet->getActiveSheet(); |
||
| 301 | $sheet->setTitle('Attendance'); |
||
| 302 | |||
| 303 | // Header |
||
| 304 | $headers = ['#', 'Last Name', 'First Name', 'Not Attended']; |
||
| 305 | foreach ($calendars as $calendar) { |
||
| 306 | $headers[] = $calendar->getDateTime()->format('d/m H:i'); |
||
| 307 | } |
||
| 308 | $sheet->fromArray($headers, null, 'A1'); |
||
| 309 | |||
| 310 | // Rows |
||
| 311 | $rowNumber = 2; |
||
| 312 | $count = 1; |
||
| 313 | foreach ($students as $student) { |
||
| 314 | $row = [$count++, $student->getLastname(), $student->getFirstname()]; |
||
| 315 | $absences = 0; |
||
| 316 | |||
| 317 | foreach ($calendars as $calendar) { |
||
| 318 | $sheetEntity = $sheetRepo->findOneBy([ |
||
| 319 | 'user' => $student, |
||
| 320 | 'attendanceCalendar' => $calendar, |
||
| 321 | ]); |
||
| 322 | |||
| 323 | if (!$sheetEntity || $sheetEntity->getPresence() === null) { |
||
| 324 | $row[] = ''; |
||
| 325 | continue; |
||
| 326 | } |
||
| 327 | |||
| 328 | $presence = $sheetEntity->getPresence(); |
||
| 329 | $row[] = $stateLabels[$presence] ?? 'NP'; |
||
| 330 | |||
| 331 | if ($presence === CAttendanceSheet::ABSENT) { |
||
| 332 | $absences++; |
||
| 333 | } |
||
| 334 | } |
||
| 335 | |||
| 336 | $percentage = $totalCalendars > 0 ? round(($absences * 100) / $totalCalendars) : 0; |
||
| 337 | array_splice($row, 3, 0, "$absences/$totalCalendars ($percentage%)"); |
||
| 338 | |||
| 339 | $sheet->fromArray($row, null, 'A' . $rowNumber++); |
||
| 340 | } |
||
| 341 | |||
| 342 | // Output |
||
| 343 | $writer = new Xls($spreadsheet); |
||
| 344 | $response = new StreamedResponse(fn() => $writer->save('php://output')); |
||
| 345 | $disposition = $response->headers->makeDisposition( |
||
| 346 | ResponseHeaderBag::DISPOSITION_ATTACHMENT, |
||
| 347 | "attendance-$id.xls" |
||
| 348 | ); |
||
| 349 | |||
| 350 | $response->headers->set('Content-Type', 'application/vnd.ms-excel'); |
||
| 351 | $response->headers->set('Content-Disposition', $disposition); |
||
| 352 | return $response; |
||
| 353 | } |
||
| 354 | |||
| 355 | #[Route('/{id}/qrcode', name: 'attendance_qrcode', methods: ['GET'])] |
||
| 389 | } |
||
| 390 | |||
| 391 | |||
| 392 | #[Route('/sheet/save', name: 'chamilo_core_attendance_sheet_save', methods: ['POST'])] |
||
| 393 | public function saveAttendanceSheet( |
||
| 510 | } |
||
| 511 | } |
||
| 512 | |||
| 513 | #[Route('/{id}/student-dates', name: 'attendance_student_dates', methods: ['GET'])] |
||
| 568 | ]); |
||
| 569 | } |
||
| 570 | |||
| 571 | private function updateAttendanceResults(CAttendance $attendance): void |
||
| 602 | } |
||
| 603 | } |
||
| 604 | |||
| 605 | private function saveAttendanceLog(CAttendance $attendance, string $lasteditType, CAttendanceCalendar $calendar): void |
||
| 618 |
This check looks for imports that have been defined, but are not used in the scope.