Total Complexity | 78 |
Total Lines | 734 |
Duplicated Lines | 0 % |
Changes | 1 | ||
Bugs | 0 | Features | 0 |
Complex classes like MoodleExport 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 MoodleExport, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
19 | class MoodleExport |
||
20 | { |
||
21 | private $course; |
||
22 | private static $adminUserData = []; |
||
23 | |||
24 | /** |
||
25 | * Constructor to initialize the course object. |
||
26 | */ |
||
27 | public function __construct(object $course) |
||
28 | { |
||
29 | $this->course = $course; |
||
30 | } |
||
31 | |||
32 | /** |
||
33 | * Export the Moodle course in .mbz format. |
||
34 | */ |
||
35 | public function export(string $courseId, string $exportDir, int $version) |
||
78 | } |
||
79 | |||
80 | /** |
||
81 | * Export root XML files such as badges, completion, gradebook, etc. |
||
82 | */ |
||
83 | private function exportRootXmlFiles(string $exportDir): void |
||
84 | { |
||
85 | $this->exportBadgesXml($exportDir); |
||
86 | $this->exportCompletionXml($exportDir); |
||
87 | $this->exportGradebookXml($exportDir); |
||
88 | $this->exportGradeHistoryXml($exportDir); |
||
89 | $this->exportGroupsXml($exportDir); |
||
90 | $this->exportOutcomesXml($exportDir); |
||
91 | |||
92 | // Export quizzes and their questions |
||
93 | $activities = $this->getActivities(); |
||
94 | $questionsData = []; |
||
95 | foreach ($activities as $activity) { |
||
96 | if ($activity['modulename'] === 'quiz') { |
||
97 | $quizExport = new QuizExport($this->course); |
||
98 | $quizData = $quizExport->getData($activity['id'], $activity['sectionid']); |
||
99 | $questionsData[] = $quizData; |
||
100 | } |
||
101 | } |
||
102 | $this->exportQuestionsXml($questionsData, $exportDir); |
||
103 | |||
104 | $this->exportRolesXml($exportDir); |
||
105 | $this->exportScalesXml($exportDir); |
||
106 | $this->exportUsersXml($exportDir); |
||
107 | } |
||
108 | |||
109 | /** |
||
110 | * Create the moodle_backup.xml file with the required course details. |
||
111 | */ |
||
112 | private function createMoodleBackupXml(string $destinationDir, int $version): void |
||
220 | } |
||
221 | |||
222 | /** |
||
223 | * Get all sections from the course. |
||
224 | */ |
||
225 | private function getSections(): array |
||
249 | } |
||
250 | |||
251 | /** |
||
252 | * Get all activities from the course. |
||
253 | */ |
||
254 | private function getActivities(): array |
||
255 | { |
||
256 | $activities = []; |
||
257 | $glossaryAdded = false; |
||
258 | |||
259 | foreach ($this->course->resources as $resourceType => $resources) { |
||
260 | foreach ($resources as $resource) { |
||
261 | $exportClass = null; |
||
262 | $moduleName = ''; |
||
263 | $title = ''; |
||
264 | $id = 0; |
||
265 | |||
266 | // Handle quizzes |
||
267 | if ($resourceType === RESOURCE_QUIZ && $resource->obj->iid > 0) { |
||
268 | $exportClass = QuizExport::class; |
||
269 | $moduleName = 'quiz'; |
||
270 | $id = $resource->obj->iid; |
||
271 | $title = $resource->obj->title; |
||
272 | } |
||
273 | // Handle links |
||
274 | if ($resourceType === RESOURCE_LINK && $resource->source_id > 0) { |
||
275 | $exportClass = UrlExport::class; |
||
276 | $moduleName = 'url'; |
||
277 | $id = $resource->source_id; |
||
278 | $title = $resource->title; |
||
279 | } |
||
280 | // Handle glossaries |
||
281 | elseif ($resourceType === RESOURCE_GLOSSARY && $resource->glossary_id > 0 && !$glossaryAdded) { |
||
282 | $exportClass = GlossaryExport::class; |
||
283 | $moduleName = 'glossary'; |
||
284 | $id = 1; |
||
285 | $title = get_lang('Glossary'); |
||
286 | $glossaryAdded = true; |
||
287 | } |
||
288 | // Handle forums |
||
289 | elseif ($resourceType === RESOURCE_FORUM && $resource->source_id > 0) { |
||
290 | $exportClass = ForumExport::class; |
||
291 | $moduleName = 'forum'; |
||
292 | $id = $resource->obj->iid; |
||
293 | $title = $resource->obj->forum_title; |
||
294 | } |
||
295 | // Handle documents (HTML pages) |
||
296 | elseif ($resourceType === RESOURCE_DOCUMENT && $resource->source_id > 0) { |
||
297 | $document = \DocumentManager::get_document_data_by_id($resource->source_id, $this->course->code); |
||
298 | if ('html' === pathinfo($document['path'], PATHINFO_EXTENSION)) { |
||
299 | $exportClass = PageExport::class; |
||
300 | $moduleName = 'page'; |
||
301 | $id = $resource->source_id; |
||
302 | $title = $document['title']; |
||
303 | } elseif ('file' === $resource->file_type) { |
||
304 | $exportClass = ResourceExport::class; |
||
305 | $moduleName = 'resource'; |
||
306 | $id = $resource->source_id; |
||
307 | $title = $resource->title; |
||
308 | } elseif ('folder' === $resource->file_type) { |
||
309 | $exportClass = FolderExport::class; |
||
310 | $moduleName = 'folder'; |
||
311 | $id = $resource->source_id; |
||
312 | $title = $resource->title; |
||
313 | } |
||
314 | } |
||
315 | // Handle assignments (work) |
||
316 | elseif ($resourceType === RESOURCE_WORK && $resource->source_id > 0) { |
||
317 | $exportClass = AssignExport::class; |
||
318 | $moduleName = 'assign'; |
||
319 | $id = $resource->source_id; |
||
320 | $title = $resource->params['title'] ?? ''; |
||
321 | } |
||
322 | // Handle feedback (survey) |
||
323 | elseif ($resourceType === RESOURCE_SURVEY && $resource->source_id > 0) { |
||
324 | $exportClass = FeedbackExport::class; |
||
325 | $moduleName = 'feedback'; |
||
326 | $id = $resource->source_id; |
||
327 | $title = $resource->params['title'] ?? ''; |
||
328 | } |
||
329 | |||
330 | // Add the activity if the class and module name are set |
||
331 | if ($exportClass && $moduleName) { |
||
332 | $exportInstance = new $exportClass($this->course); |
||
333 | $activities[] = [ |
||
334 | 'id' => $id, |
||
335 | 'sectionid' => $exportInstance->getSectionIdForActivity($id, $resourceType), |
||
336 | 'modulename' => $moduleName, |
||
337 | 'moduleid' => $id, |
||
338 | 'title' => $title, |
||
339 | ]; |
||
340 | } |
||
341 | } |
||
342 | } |
||
343 | |||
344 | return $activities; |
||
345 | } |
||
346 | |||
347 | /** |
||
348 | * Export the sections of the course. |
||
349 | */ |
||
350 | private function exportSections(string $exportDir): void |
||
351 | { |
||
352 | $sections = $this->getSections(); |
||
353 | |||
354 | foreach ($sections as $section) { |
||
355 | $sectionExport = new SectionExport($this->course); |
||
356 | $sectionExport->exportSection($section['id'], $exportDir); |
||
357 | } |
||
358 | } |
||
359 | |||
360 | /** |
||
361 | * Create a .mbz (ZIP) file from the exported data. |
||
362 | */ |
||
363 | private function createMbzFile(string $sourceDir): string |
||
364 | { |
||
365 | $zip = new ZipArchive(); |
||
366 | $zipFile = $sourceDir . '.mbz'; |
||
367 | |||
368 | if ($zip->open($zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) { |
||
369 | throw new Exception(get_lang('ErrorCreatingZip')); |
||
370 | } |
||
371 | |||
372 | $files = new RecursiveIteratorIterator( |
||
373 | new RecursiveDirectoryIterator($sourceDir), |
||
374 | RecursiveIteratorIterator::LEAVES_ONLY |
||
375 | ); |
||
376 | |||
377 | foreach ($files as $file) { |
||
378 | if (!$file->isDir()) { |
||
379 | $filePath = $file->getRealPath(); |
||
380 | $relativePath = substr($filePath, strlen($sourceDir) + 1); |
||
381 | |||
382 | if (!$zip->addFile($filePath, $relativePath)) { |
||
383 | throw new Exception(get_lang('ErrorAddingFileToZip') . ": $relativePath"); |
||
384 | } |
||
385 | } |
||
386 | } |
||
387 | |||
388 | if (!$zip->close()) { |
||
389 | throw new Exception(get_lang('ErrorClosingZip')); |
||
390 | } |
||
391 | |||
392 | return $zipFile; |
||
393 | } |
||
394 | |||
395 | /** |
||
396 | * Clean up the temporary directory used for export. |
||
397 | */ |
||
398 | private function cleanupTempDir(string $dir): void |
||
399 | { |
||
400 | $this->recursiveDelete($dir); |
||
401 | } |
||
402 | |||
403 | /** |
||
404 | * Recursively delete a directory and its contents. |
||
405 | */ |
||
406 | private function recursiveDelete(string $dir): void |
||
407 | { |
||
408 | $files = array_diff(scandir($dir), ['.', '..']); |
||
409 | foreach ($files as $file) { |
||
410 | $path = "$dir/$file"; |
||
411 | is_dir($path) ? $this->recursiveDelete($path) : unlink($path); |
||
412 | } |
||
413 | rmdir($dir); |
||
414 | } |
||
415 | |||
416 | /** |
||
417 | * Export badges data to XML file. |
||
418 | */ |
||
419 | private function exportBadgesXml(string $exportDir): void |
||
420 | { |
||
421 | $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL; |
||
422 | $xmlContent .= '<badges>' . PHP_EOL; |
||
423 | $xmlContent .= '</badges>'; |
||
424 | file_put_contents($exportDir . '/badges.xml', $xmlContent); |
||
425 | } |
||
426 | |||
427 | /** |
||
428 | * Export course completion data to XML file. |
||
429 | */ |
||
430 | private function exportCompletionXml(string $exportDir): void |
||
431 | { |
||
432 | $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL; |
||
433 | $xmlContent .= '<completions>' . PHP_EOL; |
||
434 | $xmlContent .= '</completions>'; |
||
435 | file_put_contents($exportDir . '/completion.xml', $xmlContent); |
||
436 | } |
||
437 | |||
438 | /** |
||
439 | * Export gradebook data to XML file. |
||
440 | */ |
||
441 | private function exportGradebookXml(string $exportDir): void |
||
442 | { |
||
443 | $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL; |
||
444 | $xmlContent .= '<gradebook>' . PHP_EOL; |
||
445 | $xmlContent .= '</gradebook>'; |
||
446 | file_put_contents($exportDir . '/gradebook.xml', $xmlContent); |
||
447 | } |
||
448 | |||
449 | /** |
||
450 | * Export grade history data to XML file. |
||
451 | */ |
||
452 | private function exportGradeHistoryXml(string $exportDir): void |
||
453 | { |
||
454 | $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL; |
||
455 | $xmlContent .= '<grade_history>' . PHP_EOL; |
||
456 | $xmlContent .= '</grade_history>'; |
||
457 | file_put_contents($exportDir . '/grade_history.xml', $xmlContent); |
||
458 | } |
||
459 | |||
460 | /** |
||
461 | * Export groups data to XML file. |
||
462 | */ |
||
463 | private function exportGroupsXml(string $exportDir): void |
||
464 | { |
||
465 | $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL; |
||
466 | $xmlContent .= '<groups>' . PHP_EOL; |
||
467 | $xmlContent .= '</groups>'; |
||
468 | file_put_contents($exportDir . '/groups.xml', $xmlContent); |
||
469 | } |
||
470 | |||
471 | /** |
||
472 | * Export outcomes data to XML file. |
||
473 | */ |
||
474 | private function exportOutcomesXml(string $exportDir): void |
||
475 | { |
||
476 | $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL; |
||
477 | $xmlContent .= '<outcomes>' . PHP_EOL; |
||
478 | $xmlContent .= '</outcomes>'; |
||
479 | file_put_contents($exportDir . '/outcomes.xml', $xmlContent); |
||
480 | } |
||
481 | |||
482 | /** |
||
483 | * Export questions data to XML file. |
||
484 | */ |
||
485 | public function exportQuestionsXml(array $questionsData, string $exportDir): void |
||
517 | } |
||
518 | |||
519 | /** |
||
520 | * Export roles data to XML file. |
||
521 | */ |
||
522 | private function exportRolesXml(string $exportDir): void |
||
537 | } |
||
538 | |||
539 | /** |
||
540 | * Export scales data to XML file. |
||
541 | */ |
||
542 | private function exportScalesXml(string $exportDir): void |
||
548 | } |
||
549 | |||
550 | /** |
||
551 | * Sets the admin user data. |
||
552 | */ |
||
553 | public function setAdminUserData(int $id, string $username, string $email): void |
||
554 | { |
||
555 | self::$adminUserData = [ |
||
556 | 'id' => $id, |
||
557 | 'contextid' => $id, |
||
558 | 'username' => $username, |
||
559 | 'idnumber' => '', |
||
560 | 'email' => $email, |
||
561 | 'phone1' => '', |
||
562 | 'phone2' => '', |
||
563 | 'institution' => '', |
||
564 | 'department' => '', |
||
565 | 'address' => '', |
||
566 | 'city' => 'London', |
||
567 | 'country' => 'GB', |
||
568 | 'lastip' => '127.0.0.1', |
||
569 | 'picture' => '0', |
||
570 | 'description' => '', |
||
571 | 'descriptionformat' => 1, |
||
572 | 'imagealt' => '$@NULL@$', |
||
573 | 'auth' => 'manual', |
||
574 | 'firstname' => 'Admin', |
||
575 | 'lastname' => 'User', |
||
576 | 'confirmed' => 1, |
||
577 | 'policyagreed' => 0, |
||
578 | 'deleted' => 0, |
||
579 | 'lang' => 'en', |
||
580 | 'theme' => '', |
||
581 | 'timezone' => 99, |
||
582 | 'firstaccess' => time(), |
||
583 | 'lastaccess' => time() - (60 * 60 * 24 * 7), |
||
584 | 'lastlogin' => time() - (60 * 60 * 24 * 2), |
||
585 | 'currentlogin' => time(), |
||
586 | 'mailformat' => 1, |
||
587 | 'maildigest' => 0, |
||
588 | 'maildisplay' => 1, |
||
589 | 'autosubscribe' => 1, |
||
590 | 'trackforums' => 0, |
||
591 | 'timecreated' => time(), |
||
592 | 'timemodified' => time(), |
||
593 | 'trustbitmask' => 0, |
||
594 | 'preferences' => [ |
||
595 | ['name' => 'core_message_migrate_data', 'value' => 1], |
||
596 | ['name' => 'auth_manual_passwordupdatetime', 'value' => time()], |
||
597 | ['name' => 'email_bounce_count', 'value' => 1], |
||
598 | ['name' => 'email_send_count', 'value' => 1], |
||
599 | ['name' => 'login_failed_count_since_success', 'value' => 0], |
||
600 | ['name' => 'filepicker_recentrepository', 'value' => 5], |
||
601 | ['name' => 'filepicker_recentlicense', 'value' => 'unknown'], |
||
602 | ], |
||
603 | ]; |
||
604 | } |
||
605 | |||
606 | /** |
||
607 | * Returns hardcoded data for the admin user. |
||
608 | * |
||
609 | * @return array |
||
610 | */ |
||
611 | public static function getAdminUserData(): array |
||
612 | { |
||
613 | return self::$adminUserData; |
||
614 | } |
||
615 | |||
616 | /** |
||
617 | * Export the user XML with admin user data. |
||
618 | */ |
||
619 | private function exportUsersXml(string $exportDir): void |
||
620 | { |
||
621 | $adminData = self::getAdminUserData(); |
||
622 | |||
623 | $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL; |
||
624 | $xmlContent .= '<users>' . PHP_EOL; |
||
625 | $xmlContent .= ' <user id="' . $adminData['id'] . '" contextid="' . $adminData['contextid'] . '">' . PHP_EOL; |
||
626 | $xmlContent .= ' <username>' . $adminData['username'] . '</username>' . PHP_EOL; |
||
627 | $xmlContent .= ' <idnumber>' . $adminData['idnumber'] . '</idnumber>' . PHP_EOL; |
||
628 | $xmlContent .= ' <email>' . $adminData['email'] . '</email>' . PHP_EOL; |
||
629 | $xmlContent .= ' <phone1>' . $adminData['phone1'] . '</phone1>' . PHP_EOL; |
||
630 | $xmlContent .= ' <phone2>' . $adminData['phone2'] . '</phone2>' . PHP_EOL; |
||
631 | $xmlContent .= ' <institution>' . $adminData['institution'] . '</institution>' . PHP_EOL; |
||
632 | $xmlContent .= ' <department>' . $adminData['department'] . '</department>' . PHP_EOL; |
||
633 | $xmlContent .= ' <address>' . $adminData['address'] . '</address>' . PHP_EOL; |
||
634 | $xmlContent .= ' <city>' . $adminData['city'] . '</city>' . PHP_EOL; |
||
635 | $xmlContent .= ' <country>' . $adminData['country'] . '</country>' . PHP_EOL; |
||
636 | $xmlContent .= ' <lastip>' . $adminData['lastip'] . '</lastip>' . PHP_EOL; |
||
637 | $xmlContent .= ' <picture>' . $adminData['picture'] . '</picture>' . PHP_EOL; |
||
638 | $xmlContent .= ' <description>' . $adminData['description'] . '</description>' . PHP_EOL; |
||
639 | $xmlContent .= ' <descriptionformat>' . $adminData['descriptionformat'] . '</descriptionformat>' . PHP_EOL; |
||
640 | $xmlContent .= ' <imagealt>' . $adminData['imagealt'] . '</imagealt>' . PHP_EOL; |
||
641 | $xmlContent .= ' <auth>' . $adminData['auth'] . '</auth>' . PHP_EOL; |
||
642 | $xmlContent .= ' <firstname>' . $adminData['firstname'] . '</firstname>' . PHP_EOL; |
||
643 | $xmlContent .= ' <lastname>' . $adminData['lastname'] . '</lastname>' . PHP_EOL; |
||
644 | $xmlContent .= ' <confirmed>' . $adminData['confirmed'] . '</confirmed>' . PHP_EOL; |
||
645 | $xmlContent .= ' <policyagreed>' . $adminData['policyagreed'] . '</policyagreed>' . PHP_EOL; |
||
646 | $xmlContent .= ' <deleted>' . $adminData['deleted'] . '</deleted>' . PHP_EOL; |
||
647 | $xmlContent .= ' <lang>' . $adminData['lang'] . '</lang>' . PHP_EOL; |
||
648 | $xmlContent .= ' <theme>' . $adminData['theme'] . '</theme>' . PHP_EOL; |
||
649 | $xmlContent .= ' <timezone>' . $adminData['timezone'] . '</timezone>' . PHP_EOL; |
||
650 | $xmlContent .= ' <firstaccess>' . $adminData['firstaccess'] . '</firstaccess>' . PHP_EOL; |
||
651 | $xmlContent .= ' <lastaccess>' . $adminData['lastaccess'] . '</lastaccess>' . PHP_EOL; |
||
652 | $xmlContent .= ' <lastlogin>' . $adminData['lastlogin'] . '</lastlogin>' . PHP_EOL; |
||
653 | $xmlContent .= ' <currentlogin>' . $adminData['currentlogin'] . '</currentlogin>' . PHP_EOL; |
||
654 | $xmlContent .= ' <mailformat>' . $adminData['mailformat'] . '</mailformat>' . PHP_EOL; |
||
655 | $xmlContent .= ' <maildigest>' . $adminData['maildigest'] . '</maildigest>' . PHP_EOL; |
||
656 | $xmlContent .= ' <maildisplay>' . $adminData['maildisplay'] . '</maildisplay>' . PHP_EOL; |
||
657 | $xmlContent .= ' <autosubscribe>' . $adminData['autosubscribe'] . '</autosubscribe>' . PHP_EOL; |
||
658 | $xmlContent .= ' <trackforums>' . $adminData['trackforums'] . '</trackforums>' . PHP_EOL; |
||
659 | $xmlContent .= ' <timecreated>' . $adminData['timecreated'] . '</timecreated>' . PHP_EOL; |
||
660 | $xmlContent .= ' <timemodified>' . $adminData['timemodified'] . '</timemodified>' . PHP_EOL; |
||
661 | $xmlContent .= ' <trustbitmask>' . $adminData['trustbitmask'] . '</trustbitmask>' . PHP_EOL; |
||
662 | |||
663 | // Preferences |
||
664 | if (isset($adminData['preferences']) && is_array($adminData['preferences'])) { |
||
665 | $xmlContent .= ' <preferences>' . PHP_EOL; |
||
666 | foreach ($adminData['preferences'] as $preference) { |
||
667 | $xmlContent .= ' <preference>' . PHP_EOL; |
||
668 | $xmlContent .= ' <name>' . htmlspecialchars($preference['name']) . '</name>' . PHP_EOL; |
||
669 | $xmlContent .= ' <value>' . htmlspecialchars($preference['value']) . '</value>' . PHP_EOL; |
||
670 | $xmlContent .= ' </preference>' . PHP_EOL; |
||
671 | } |
||
672 | $xmlContent .= ' </preferences>' . PHP_EOL; |
||
673 | } else { |
||
674 | $xmlContent .= ' <preferences></preferences>' . PHP_EOL; |
||
675 | } |
||
676 | |||
677 | // Roles (empty for now) |
||
678 | $xmlContent .= ' <roles>' . PHP_EOL; |
||
679 | $xmlContent .= ' <role_overrides></role_overrides>' . PHP_EOL; |
||
680 | $xmlContent .= ' <role_assignments></role_assignments>' . PHP_EOL; |
||
681 | $xmlContent .= ' </roles>' . PHP_EOL; |
||
682 | |||
683 | $xmlContent .= ' </user>' . PHP_EOL; |
||
684 | $xmlContent .= '</users>'; |
||
685 | |||
686 | // Save the content to the users.xml file |
||
687 | file_put_contents($exportDir . '/users.xml', $xmlContent); |
||
688 | } |
||
689 | |||
690 | /** |
||
691 | * Export the backup settings, including dynamic settings for sections and activities. |
||
692 | */ |
||
693 | private function exportBackupSettings(array $sections, array $activities): array |
||
753 | } |
||
754 | } |
||
755 |