Passed
Pull Request — 1.11.x (#6923)
by
unknown
10:03
created

MoodleExport::titleFromLp()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 8
rs 10
cc 2
nc 2
nop 5
1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
namespace moodleexport;
6
7
use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
8
use Exception;
9
use RecursiveDirectoryIterator;
10
use RecursiveIteratorIterator;
11
use ZipArchive;
12
13
/**
14
 * Class MoodleExport.
15
 * Handles the export of a Moodle course in .mbz format.
16
 *
17
 * @package moodleexport
18
 */
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
        // Build the complete course object
30
        $cb = new CourseBuilder('complete');
31
        $complete = $cb->build();
32
33
        // Store the selected course
34
        $this->course = $course;
35
36
        // Fill missing resources from learnpath
37
        $this->fillResourcesFromLearnpath($complete);
38
39
        // Fill missing quiz questions
40
        $this->fillQuestionsFromQuiz($complete);
41
    }
42
43
    /**
44
     * Export the Moodle course in .mbz format.
45
     */
46
    public function export(string $courseId, string $exportDir, int $version)
47
    {
48
        $tempDir = api_get_path(SYS_ARCHIVE_PATH).$exportDir;
49
50
        if (!is_dir($tempDir)) {
51
            if (!mkdir($tempDir, api_get_permissions_for_new_directories(), true)) {
52
                throw new Exception(get_lang('ErrorCreatingDirectory'));
53
            }
54
        }
55
56
        $courseInfo = api_get_course_info($courseId);
57
        if (!$courseInfo) {
58
            throw new Exception(get_lang('CourseNotFound'));
59
        }
60
61
        // Generate the moodle_backup.xml
62
        $this->createMoodleBackupXml($tempDir, $version);
63
64
        // Get the activities from the course
65
        $activities = $this->getActivities();
66
67
        // Export course-related files
68
        $courseExport = new CourseExport($this->course, $activities);
69
        $courseExport->exportCourse($tempDir);
70
71
        // Export files-related data and actual files
72
        $pageExport = new PageExport($this->course);
73
        $pageFiles = [];
74
        $pageData = $pageExport->getData(0, 1);
75
        if (!empty($pageData['files'])) {
76
            $pageFiles = $pageData['files'];
77
        }
78
        $fileExport = new FileExport($this->course);
79
        $filesData = $fileExport->getFilesData();
80
        $filesData['files'] = array_merge($filesData['files'], $pageFiles);
81
        $fileExport->exportFiles($filesData, $tempDir);
82
83
        // Export sections of the course
84
        $this->exportSections($tempDir);
85
86
        // Export all root XML files
87
        $this->exportRootXmlFiles($tempDir);
88
89
        // Compress everything into a .mbz (ZIP) file
90
        $exportedFile = $this->createMbzFile($tempDir);
91
92
        // Clean up temporary directory
93
        $this->cleanupTempDir($tempDir);
94
95
        return $exportedFile;
96
    }
97
98
    /**
99
     * Export questions data to XML file.
100
     */
101
    public function exportQuestionsXml(array $questionsData, string $exportDir): void
102
    {
103
        $quizExport = new QuizExport($this->course);
104
        $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
105
        $xmlContent .= '<question_categories>'.PHP_EOL;
106
107
        foreach ($questionsData as $quiz) {
108
            $categoryId = $quiz['questions'][0]['questioncategoryid'] ?? '1';
109
            $hash = md5($categoryId.$quiz['name']);
110
            if (isset($categoryHashes[$hash])) {
111
                continue;
112
            }
113
            $categoryHashes[$hash] = true;
114
            $xmlContent .= '  <question_category id="'.$categoryId.'">'.PHP_EOL;
115
            $xmlContent .= '    <name>Default for '.htmlspecialchars($quiz['name'] ?? 'Unknown').'</name>'.PHP_EOL;
116
            $xmlContent .= '    <contextid>'.($quiz['contextid'] ?? '0').'</contextid>'.PHP_EOL;
117
            $xmlContent .= '    <contextlevel>70</contextlevel>'.PHP_EOL;
118
            $xmlContent .= '    <contextinstanceid>'.($quiz['moduleid'] ?? '0').'</contextinstanceid>'.PHP_EOL;
119
            $xmlContent .= '    <info>The default category for questions shared in context "'.htmlspecialchars($quiz['name'] ?? 'Unknown').'".</info>'.PHP_EOL;
120
            $xmlContent .= '    <infoformat>0</infoformat>'.PHP_EOL;
121
            $xmlContent .= '    <stamp>moodle+'.time().'+CATEGORYSTAMP</stamp>'.PHP_EOL;
122
            $xmlContent .= '    <parent>0</parent>'.PHP_EOL;
123
            $xmlContent .= '    <sortorder>999</sortorder>'.PHP_EOL;
124
            $xmlContent .= '    <idnumber>$@NULL@$</idnumber>'.PHP_EOL;
125
            $xmlContent .= '    <questions>'.PHP_EOL;
126
127
            foreach ($quiz['questions'] as $question) {
128
                $xmlContent .= $quizExport->exportQuestion($question);
129
            }
130
131
            $xmlContent .= '    </questions>'.PHP_EOL;
132
            $xmlContent .= '  </question_category>'.PHP_EOL;
133
        }
134
135
        $xmlContent .= '</question_categories>';
136
        file_put_contents($exportDir.'/questions.xml', $xmlContent);
137
    }
138
139
    /**
140
     * Sets the admin user data.
141
     */
142
    public function setAdminUserData(int $id, string $username, string $email): void
143
    {
144
        self::$adminUserData = [
145
            'id' => $id,
146
            'contextid' => $id,
147
            'username' => $username,
148
            'idnumber' => '',
149
            'email' => $email,
150
            'phone1' => '',
151
            'phone2' => '',
152
            'institution' => '',
153
            'department' => '',
154
            'address' => '',
155
            'city' => 'London',
156
            'country' => 'GB',
157
            'lastip' => '127.0.0.1',
158
            'picture' => '0',
159
            'description' => '',
160
            'descriptionformat' => 1,
161
            'imagealt' => '$@NULL@$',
162
            'auth' => 'manual',
163
            'firstname' => 'Admin',
164
            'lastname' => 'User',
165
            'confirmed' => 1,
166
            'policyagreed' => 0,
167
            'deleted' => 0,
168
            'lang' => 'en',
169
            'theme' => '',
170
            'timezone' => 99,
171
            'firstaccess' => time(),
172
            'lastaccess' => time() - (60 * 60 * 24 * 7),
173
            'lastlogin' => time() - (60 * 60 * 24 * 2),
174
            'currentlogin' => time(),
175
            'mailformat' => 1,
176
            'maildigest' => 0,
177
            'maildisplay' => 1,
178
            'autosubscribe' => 1,
179
            'trackforums' => 0,
180
            'timecreated' => time(),
181
            'timemodified' => time(),
182
            'trustbitmask' => 0,
183
            'preferences' => [
184
                ['name' => 'core_message_migrate_data', 'value' => 1],
185
                ['name' => 'auth_manual_passwordupdatetime', 'value' => time()],
186
                ['name' => 'email_bounce_count', 'value' => 1],
187
                ['name' => 'email_send_count', 'value' => 1],
188
                ['name' => 'login_failed_count_since_success', 'value' => 0],
189
                ['name' => 'filepicker_recentrepository', 'value' => 5],
190
                ['name' => 'filepicker_recentlicense', 'value' => 'unknown'],
191
            ],
192
        ];
193
    }
194
195
    /**
196
     * Returns hardcoded data for the admin user.
197
     */
198
    public static function getAdminUserData(): array
199
    {
200
        return self::$adminUserData;
201
    }
202
203
    /**
204
     * Fills missing resources from the learnpath into the course structure.
205
     *
206
     * This method checks if the course has a learnpath and ensures that all
207
     * referenced resources (documents, quizzes, etc.) exist in the course's
208
     * resources array by pulling them from the complete course object.
209
     */
210
    private function fillResourcesFromLearnpath(object $complete): void
211
    {
212
        // Check if the course has learnpath
213
        if (!isset($this->course->resources['learnpath'])) {
214
            return;
215
        }
216
217
        foreach ($this->course->resources['learnpath'] as $learnpathId => $learnpath) {
218
            if (!isset($learnpath->items)) {
219
                continue;
220
            }
221
222
            foreach ($learnpath->items as $item) {
223
                $type = $item['item_type']; // Resource type (document, quiz, etc.)
224
                $resourceId = $item['path']; // Resource ID in resources
225
226
                // Check if the resource exists in the complete object and is not yet in the course resources
227
                if (isset($complete->resources[$type][$resourceId]) && !isset($this->course->resources[$type][$resourceId])) {
228
                    // Add the resource directly to the original course resources structure
229
                    $this->course->resources[$type][$resourceId] = $complete->resources[$type][$resourceId];
230
                }
231
            }
232
        }
233
    }
234
235
    /**
236
     * Fills missing exercise questions related to quizzes in the course.
237
     *
238
     * This method checks if the course has quizzes and ensures that all referenced
239
     * questions exist in the course's resources array by pulling them from the complete
240
     * course object.
241
     */
242
    private function fillQuestionsFromQuiz(object $complete): void
243
    {
244
        // Check if the course has quizzes
245
        if (!isset($this->course->resources['quiz'])) {
246
            return;
247
        }
248
249
        foreach ($this->course->resources['quiz'] as $quizId => $quiz) {
250
            if (!isset($quiz->obj->question_ids)) {
251
                continue;
252
            }
253
254
            foreach ($quiz->obj->question_ids as $questionId) {
255
                // Check if the question exists in the complete object and is not yet in the course resources
256
                if (isset($complete->resources['Exercise_Question'][$questionId]) && !isset($this->course->resources['Exercise_Question'][$questionId])) {
257
                    // Add the question directly to the original course resources structure
258
                    $this->course->resources['Exercise_Question'][$questionId] = $complete->resources['Exercise_Question'][$questionId];
259
                }
260
            }
261
        }
262
    }
263
264
    /**
265
     * Export root XML files such as badges, completion, gradebook, etc.
266
     */
267
    private function exportRootXmlFiles(string $exportDir): void
268
    {
269
        $this->exportBadgesXml($exportDir);
270
        $this->exportCompletionXml($exportDir);
271
        $this->exportGradebookXml($exportDir);
272
        $this->exportGradeHistoryXml($exportDir);
273
        $this->exportGroupsXml($exportDir);
274
        $this->exportOutcomesXml($exportDir);
275
276
        // Export quizzes and their questions
277
        $activities = $this->getActivities();
278
        $questionsData = [];
279
        foreach ($activities as $activity) {
280
            if ($activity['modulename'] === 'quiz') {
281
                $quizExport = new QuizExport($this->course);
282
                $quizData = $quizExport->getData($activity['id'], $activity['sectionid']);
283
                $questionsData[] = $quizData;
284
            }
285
        }
286
        $this->exportQuestionsXml($questionsData, $exportDir);
287
288
        $this->exportRolesXml($exportDir);
289
        $this->exportScalesXml($exportDir);
290
        $this->exportUsersXml($exportDir);
291
    }
292
293
    /**
294
     * Create the moodle_backup.xml file with the required course details.
295
     */
296
    private function createMoodleBackupXml(string $destinationDir, int $version): void
297
    {
298
        // Generate course information and backup metadata
299
        $courseInfo = api_get_course_info($this->course->code);
300
        $backupId = md5(uniqid(mt_rand(), true));
301
        $siteHash = md5(uniqid(mt_rand(), true));
302
        $wwwRoot = api_get_path(WEB_PATH);
303
304
        $courseStartDate = strtotime($courseInfo['creation_date']);
305
        $courseEndDate = $courseStartDate + (365 * 24 * 60 * 60);
306
307
        // Build the XML content for the backup
308
        $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
309
        $xmlContent .= '<moodle_backup>'.PHP_EOL;
310
        $xmlContent .= '  <information>'.PHP_EOL;
311
312
        $xmlContent .= '    <name>backup-'.htmlspecialchars($courseInfo['code']).'.mbz</name>'.PHP_EOL;
313
        $xmlContent .= '    <moodle_version>'.($version === 3 ? '2021051718' : '2022041900').'</moodle_version>'.PHP_EOL;
314
        $xmlContent .= '    <moodle_release>'.($version === 3 ? '3.11.18 (Build: 20231211)' : '4.x version here').'</moodle_release>'.PHP_EOL;
315
        $xmlContent .= '    <backup_version>'.($version === 3 ? '2021051700' : '2022041900').'</backup_version>'.PHP_EOL;
316
        $xmlContent .= '    <backup_release>'.($version === 3 ? '3.11' : '4.x').'</backup_release>'.PHP_EOL;
317
        $xmlContent .= '    <backup_date>'.time().'</backup_date>'.PHP_EOL;
318
        $xmlContent .= '    <mnet_remoteusers>0</mnet_remoteusers>'.PHP_EOL;
319
        $xmlContent .= '    <include_files>1</include_files>'.PHP_EOL;
320
        $xmlContent .= '    <include_file_references_to_external_content>0</include_file_references_to_external_content>'.PHP_EOL;
321
        $xmlContent .= '    <original_wwwroot>'.$wwwRoot.'</original_wwwroot>'.PHP_EOL;
322
        $xmlContent .= '    <original_site_identifier_hash>'.$siteHash.'</original_site_identifier_hash>'.PHP_EOL;
323
        $xmlContent .= '    <original_course_id>'.htmlspecialchars($courseInfo['real_id']).'</original_course_id>'.PHP_EOL;
324
        $xmlContent .= '    <original_course_format>'.get_lang('Topics').'</original_course_format>'.PHP_EOL;
325
        $xmlContent .= '    <original_course_fullname>'.htmlspecialchars($courseInfo['title']).'</original_course_fullname>'.PHP_EOL;
326
        $xmlContent .= '    <original_course_shortname>'.htmlspecialchars($courseInfo['code']).'</original_course_shortname>'.PHP_EOL;
327
        $xmlContent .= '    <original_course_startdate>'.$courseStartDate.'</original_course_startdate>'.PHP_EOL;
328
        $xmlContent .= '    <original_course_enddate>'.$courseEndDate.'</original_course_enddate>'.PHP_EOL;
329
        $xmlContent .= '    <original_course_contextid>'.$courseInfo['real_id'].'</original_course_contextid>'.PHP_EOL;
330
        $xmlContent .= '    <original_system_contextid>'.api_get_current_access_url_id().'</original_system_contextid>'.PHP_EOL;
331
332
        $xmlContent .= '    <details>'.PHP_EOL;
333
        $xmlContent .= '      <detail backup_id="'.$backupId.'">'.PHP_EOL;
334
        $xmlContent .= '        <type>course</type>'.PHP_EOL;
335
        $xmlContent .= '        <format>moodle2</format>'.PHP_EOL;
336
        $xmlContent .= '        <interactive>1</interactive>'.PHP_EOL;
337
        $xmlContent .= '        <mode>10</mode>'.PHP_EOL;
338
        $xmlContent .= '        <execution>1</execution>'.PHP_EOL;
339
        $xmlContent .= '        <executiontime>0</executiontime>'.PHP_EOL;
340
        $xmlContent .= '      </detail>'.PHP_EOL;
341
        $xmlContent .= '    </details>'.PHP_EOL;
342
343
        // Contents with activities and sections
344
        $xmlContent .= '    <contents>'.PHP_EOL;
345
346
        // Export sections dynamically and add them to the XML
347
        $sections = $this->getSections();
348
        if (!empty($sections)) {
349
            $xmlContent .= '      <sections>'.PHP_EOL;
350
            foreach ($sections as $section) {
351
                $xmlContent .= '        <section>'.PHP_EOL;
352
                $xmlContent .= '          <sectionid>'.$section['id'].'</sectionid>'.PHP_EOL;
353
                $xmlContent .= '          <title>'.htmlspecialchars($section['name']).'</title>'.PHP_EOL;
354
                $xmlContent .= '          <directory>sections/section_'.$section['id'].'</directory>'.PHP_EOL;
355
                $xmlContent .= '        </section>'.PHP_EOL;
356
            }
357
            $xmlContent .= '      </sections>'.PHP_EOL;
358
        }
359
360
        $activities = $this->getActivities();
361
        if (!empty($activities)) {
362
            $xmlContent .= '      <activities>'.PHP_EOL;
363
            foreach ($activities as $activity) {
364
                $xmlContent .= '        <activity>'.PHP_EOL;
365
                $xmlContent .= '          <moduleid>'.$activity['moduleid'].'</moduleid>'.PHP_EOL;
366
                $xmlContent .= '          <sectionid>'.$activity['sectionid'].'</sectionid>'.PHP_EOL;
367
                $xmlContent .= '          <modulename>'.htmlspecialchars($activity['modulename']).'</modulename>'.PHP_EOL;
368
                $xmlContent .= '          <title>'.htmlspecialchars($activity['title']).'</title>'.PHP_EOL;
369
                $xmlContent .= '          <directory>activities/'.$activity['modulename'].'_'.$activity['moduleid'].'</directory>'.PHP_EOL;
370
                $xmlContent .= '        </activity>'.PHP_EOL;
371
            }
372
            $xmlContent .= '      </activities>'.PHP_EOL;
373
        }
374
375
        // Course directory
376
        $xmlContent .= '      <course>'.PHP_EOL;
377
        $xmlContent .= '        <courseid>'.$courseInfo['real_id'].'</courseid>'.PHP_EOL;
378
        $xmlContent .= '        <title>'.htmlspecialchars($courseInfo['title']).'</title>'.PHP_EOL;
379
        $xmlContent .= '        <directory>course</directory>'.PHP_EOL;
380
        $xmlContent .= '      </course>'.PHP_EOL;
381
382
        $xmlContent .= '    </contents>'.PHP_EOL;
383
384
        // Backup settings
385
        $xmlContent .= '    <settings>'.PHP_EOL;
386
        $settings = $this->exportBackupSettings($sections, $activities);
387
        foreach ($settings as $setting) {
388
            $xmlContent .= '      <setting>'.PHP_EOL;
389
            $xmlContent .= '        <level>'.htmlspecialchars($setting['level']).'</level>'.PHP_EOL;
390
            $xmlContent .= '        <name>'.htmlspecialchars($setting['name']).'</name>'.PHP_EOL;
391
            $xmlContent .= '        <value>'.$setting['value'].'</value>'.PHP_EOL;
392
            if (isset($setting['section'])) {
393
                $xmlContent .= '        <section>'.htmlspecialchars($setting['section']).'</section>'.PHP_EOL;
394
            }
395
            if (isset($setting['activity'])) {
396
                $xmlContent .= '        <activity>'.htmlspecialchars($setting['activity']).'</activity>'.PHP_EOL;
397
            }
398
            $xmlContent .= '      </setting>'.PHP_EOL;
399
        }
400
        $xmlContent .= '    </settings>'.PHP_EOL;
401
402
        $xmlContent .= '  </information>'.PHP_EOL;
403
        $xmlContent .= '</moodle_backup>';
404
405
        $xmlFile = $destinationDir.'/moodle_backup.xml';
406
        file_put_contents($xmlFile, $xmlContent);
407
    }
408
409
    /**
410
     * Get all sections from the course ordered by LP display_order.
411
     */
412
    private function getSections(): array
413
    {
414
        $sectionExport = new SectionExport($this->course);
415
        $sections = [];
416
417
        // Safety: if there is no learnpath resource, return only the general section
418
        $learnpaths = $this->course->resources[RESOURCE_LEARNPATH] ?? [];
419
420
        // Sort LPs by display_order to respect the order defined in c_lp
421
        usort($learnpaths, static function ($a, $b): int {
422
            $aOrder = (int) ($a->display_order ?? 0);
423
            $bOrder = (int) ($b->display_order ?? 0);
424
425
            return $aOrder <=> $bOrder;
426
        });
427
428
        foreach ($learnpaths as $learnpath) {
429
            // We only export "real" LPs (type 1)
430
            if ((int) $learnpath->lp_type === 1) {
431
                $sections[] = $sectionExport->getSectionData($learnpath);
432
            }
433
        }
434
435
        // Add a general section for resources without a lesson
436
        $sections[] = [
437
            'id' => 0,
438
            'number' => 0,
439
            'name' => get_lang('General'),
440
            'summary' => get_lang('GeneralResourcesCourse'),
441
            'sequence' => 0,
442
            'visible' => 1,
443
            'timemodified' => time(),
444
            'activities' => $sectionExport->getActivitiesForGeneral(),
445
        ];
446
447
        return $sections;
448
    }
449
450
    /**
451
     * Get all activities from the course.
452
     * Activities are ordered by learnpath display_order when available.
453
     */
454
    private function getActivities(): array
455
    {
456
        $activities   = [];
457
        $glossaryAdded = false;
458
459
        // -----------------------------------------------------------------
460
        // 1) Build LP index: titles + display_order per section/type/resource
461
        // -----------------------------------------------------------------
462
        $lpIndex = [];
463
        if (!empty($this->course->resources[RESOURCE_LEARNPATH])) {
464
            foreach ($this->course->resources[RESOURCE_LEARNPATH] as $lp) {
465
                $sid = (int) $lp->source_id;
466
467
                foreach ($lp->items ?? [] as $it) {
468
                    $type = $it['item_type'] ?? '';
469
                    if ($type === 'student_publication') {
470
                        $type = 'assign';
471
                    } elseif ($type === 'link') {
472
                        $type = 'url';
473
                    } elseif ($type === 'survey') {
474
                        $type = 'feedback';
475
                    } elseif ($type === 'document') {
476
                        $type = 'document';
477
                    }
478
479
                    $rid   = $it['path'] ?? '';
480
                    $title = $it['title'] ?? '';
481
                    $order = isset($it['display_order']) ? (int) $it['display_order'] : 0;
482
483
                    $entry = [
484
                        'title' => $title,
485
                        'order' => $order,
486
                    ];
487
488
                    if (ctype_digit((string) $rid)) {
489
                        $rid = (int) $rid;
490
491
                        // If the same resource appears multiple times, keep the lowest order.
492
                        if (!isset($lpIndex[$sid][$type]['id'][$rid])) {
493
                            $lpIndex[$sid][$type]['id'][$rid] = $entry;
494
                        } else {
495
                            $existingOrder = (int) ($lpIndex[$sid][$type]['id'][$rid]['order'] ?? 0);
496
                            if ($order > 0 && ($existingOrder === 0 || $order < $existingOrder)) {
497
                                $lpIndex[$sid][$type]['id'][$rid]['order'] = $order;
498
                            }
499
                            // Keep the first title to avoid random renames.
500
                        }
501
                    } else {
502
                        $rid = (string) $rid;
503
504
                        if (!isset($lpIndex[$sid][$type]['path'][$rid])) {
505
                            $lpIndex[$sid][$type]['path'][$rid] = $entry;
506
                        } else {
507
                            $existingOrder = (int) ($lpIndex[$sid][$type]['path'][$rid]['order'] ?? 0);
508
                            if ($order > 0 && ($existingOrder === 0 || $order < $existingOrder)) {
509
                                $lpIndex[$sid][$type]['path'][$rid]['order'] = $order;
510
                            }
511
                        }
512
                    }
513
                }
514
            }
515
        }
516
517
        // Helper: get title from LP index.
518
        $titleFromLp = function (int $sectionId, string $moduleName, int $resourceId, string $fallback) use ($lpIndex) {
519
            $type = in_array($moduleName, ['page', 'resource'], true) ? 'document' : $moduleName;
520
521
            $entry = $lpIndex[$sectionId][$type]['id'][$resourceId] ?? null;
522
            if (is_array($entry) && !empty($entry['title'])) {
523
                return $entry['title'];
524
            }
525
526
            if ($type === 'assign') {
527
                $entry = $lpIndex[$sectionId]['student_publication']['id'][$resourceId] ?? null;
528
                if (is_array($entry) && !empty($entry['title'])) {
529
                    return $entry['title'];
530
                }
531
            }
532
533
            if ($type === 'document') {
534
                $doc = \DocumentManager::get_document_data_by_id($resourceId, $this->course->code);
535
                if (!empty($doc['path'])) {
536
                    $p = (string) $doc['path'];
537
                    foreach ([$p, 'document/'.$p, '/'.$p] as $cand) {
538
                        $entry = $lpIndex[$sectionId]['document']['path'][$cand] ?? null;
539
                        if (is_array($entry) && !empty($entry['title'])) {
540
                            return $entry['title'];
541
                        }
542
                    }
543
                }
544
            }
545
546
            return $fallback;
547
        };
548
549
        // Helper: get display_order from LP index.
550
        $orderFromLp = function (int $sectionId, string $moduleName, int $resourceId) use ($lpIndex): int {
551
            $type = in_array($moduleName, ['page', 'resource'], true) ? 'document' : $moduleName;
552
553
            $entry = $lpIndex[$sectionId][$type]['id'][$resourceId] ?? null;
554
            if (is_array($entry) && !empty($entry['order'])) {
555
                return (int) $entry['order'];
556
            }
557
558
            if ($type === 'assign') {
559
                $entry = $lpIndex[$sectionId]['student_publication']['id'][$resourceId] ?? null;
560
                if (is_array($entry) && !empty($entry['order'])) {
561
                    return (int) $entry['order'];
562
                }
563
            }
564
565
            if ($type === 'document') {
566
                $doc = \DocumentManager::get_document_data_by_id($resourceId, $this->course->code);
567
                if (!empty($doc['path'])) {
568
                    $p = (string) $doc['path'];
569
                    foreach ([$p, 'document/'.$p, '/'.$p] as $cand) {
570
                        $entry = $lpIndex[$sectionId]['document']['path'][$cand] ?? null;
571
                        if (is_array($entry) && !empty($entry['order'])) {
572
                            return (int) $entry['order'];
573
                        }
574
                    }
575
                }
576
            }
577
578
            return 0;
579
        };
580
581
        // -----------------------------------------------------------------
582
        // 2) "Documents" folder pseudo-activity (section 0)
583
        // -----------------------------------------------------------------
584
        $activities[] = [
585
            'id'        => ActivityExport::DOCS_MODULE_ID,
586
            'sectionid' => 0,
587
            'modulename'=> 'folder',
588
            'moduleid'  => ActivityExport::DOCS_MODULE_ID,
589
            'title'     => 'Documents',
590
            'order'     => 0,
591
        ];
592
593
        // -----------------------------------------------------------------
594
        // 3) Loop over all course resources (original logic)
595
        // -----------------------------------------------------------------
596
        $htmlPageIds = [];
597
        foreach ($this->course->resources as $resourceType => $resources) {
598
            foreach ($resources as $resource) {
599
                $exportClass = null;
600
                $moduleName  = '';
601
                $title       = '';
602
                $id          = 0;
603
                $sectionId   = 0;
604
605
                // QUIZ
606
                if ($resourceType === RESOURCE_QUIZ && $resource->obj->iid > 0) {
607
                    $exportClass = QuizExport::class;
608
                    $moduleName  = 'quiz';
609
                    $id          = (int) $resource->obj->iid;
610
                    $title       = $resource->obj->title ?? '';
611
                    $sectionId   = (new QuizExport($this->course))
612
                        ->getSectionIdForActivity($id, $resourceType);
613
                    $title       = $titleFromLp($sectionId, $moduleName, $id, $title);
614
                }
615
                // URL
616
                elseif ($resourceType === RESOURCE_LINK && $resource->source_id > 0) {
617
                    $exportClass = UrlExport::class;
618
                    $moduleName  = 'url';
619
                    $id          = (int) $resource->source_id;
620
                    $title       = $resource->title ?? '';
621
                    $sectionId   = (new UrlExport($this->course))
622
                        ->getSectionIdForActivity($id, $resourceType);
623
                    $title       = $titleFromLp($sectionId, $moduleName, $id, $title);
624
                }
625
                // GLOSSARY (only one)
626
                elseif ($resourceType === RESOURCE_GLOSSARY && $resource->glossary_id > 0 && !$glossaryAdded) {
627
                    $exportClass   = GlossaryExport::class;
628
                    $moduleName    = 'glossary';
629
                    $id            = 1;
630
                    $title         = get_lang('Glossary');
631
                    $sectionId     = 0;
632
                    $glossaryAdded = true;
633
                }
634
                // FORUM
635
                elseif ($resourceType === RESOURCE_FORUM && $resource->source_id > 0) {
636
                    $exportClass = ForumExport::class;
637
                    $moduleName  = 'forum';
638
                    $id          = (int) $resource->obj->iid;
639
                    $title       = $resource->obj->forum_title ?? '';
640
                    $sectionId   = (new ForumExport($this->course))
641
                        ->getSectionIdForActivity($id, $resourceType);
642
                    $title       = $titleFromLp($sectionId, $moduleName, $id, $title);
643
                }
644
                // DOCUMENTS
645
                elseif ($resourceType === RESOURCE_DOCUMENT && $resource->source_id > 0) {
646
                    $document = \DocumentManager::get_document_data_by_id(
647
                        $resource->source_id,
648
                        $this->course->code
649
                    );
650
                    $ext = strtolower(pathinfo($document['path'] ?? '', PATHINFO_EXTENSION));
651
652
                    // HTML → Moodle "page"
653
                    if ($ext === 'html' || $ext === 'htm') {
654
                        $exportClass = PageExport::class;
655
                        $moduleName  = 'page';
656
                        $id          = (int) $resource->source_id;
657
                        $title       = $document['title'] ?? '';
658
                        $sectionId   = (new PageExport($this->course))
659
                            ->getSectionIdForActivity($id, $resourceType);
660
                        $title       = $titleFromLp($sectionId, $moduleName, $id, $title);
661
                        $htmlPageIds[] = $id;
662
                    }
663
664
                    // Other files → Moodle "resource" (but not if already exported as page)
665
                    if ($resource->file_type === 'file'
666
                        && !in_array($resource->source_id, $htmlPageIds, true)
667
                    ) {
668
                        $resourceExport = new ResourceExport($this->course);
669
                        $sectionTmp     = $resourceExport
670
                            ->getSectionIdForActivity((int) $resource->source_id, $resourceType);
671
672
                        if ($sectionTmp > 0) {
673
                            $exportClass = ResourceExport::class;
674
                            $moduleName  = 'resource';
675
                            $id          = (int) $resource->source_id;
676
                            $title       = $resource->title ?? '';
677
                            $sectionId   = $sectionTmp;
678
                            $title       = $titleFromLp($sectionId, $moduleName, $id, $title);
679
                        }
680
                    }
681
                }
682
                // INTRODUCTION → Moodle "page"
683
                elseif ($resourceType === RESOURCE_TOOL_INTRO
684
                    && $resource->source_id === 'course_homepage'
685
                ) {
686
                    $exportClass = PageExport::class;
687
                    $moduleName  = 'page';
688
                    $id          = 0;
689
                    $title       = get_lang('Introduction');
690
                    $sectionId   = 0;
691
                }
692
                // ASSIGN
693
                elseif ($resourceType === RESOURCE_WORK && $resource->source_id > 0) {
694
                    $exportClass = AssignExport::class;
695
                    $moduleName  = 'assign';
696
                    $id          = (int) $resource->source_id;
697
                    $title       = $resource->params['title'] ?? '';
698
                    $sectionId   = (new AssignExport($this->course))
699
                        ->getSectionIdForActivity($id, $resourceType);
700
                    $title       = $titleFromLp($sectionId, $moduleName, $id, $title);
701
                }
702
                // FEEDBACK / SURVEY
703
                elseif ($resourceType === RESOURCE_SURVEY && $resource->source_id > 0) {
704
                    $exportClass = FeedbackExport::class;
705
                    $moduleName  = 'feedback';
706
                    $id          = (int) $resource->source_id;
707
                    $title       = $resource->params['title'] ?? '';
708
                    $sectionId   = (new FeedbackExport($this->course))
709
                        ->getSectionIdForActivity($id, $resourceType);
710
                    $title       = $titleFromLp($sectionId, $moduleName, $id, $title);
711
                }
712
713
                if ($exportClass && $moduleName) {
714
                    $order = $orderFromLp($sectionId, $moduleName, $id);
715
716
                    $activities[] = [
717
                        'id'        => $id,
718
                        'sectionid' => $sectionId,
719
                        'modulename'=> $moduleName,
720
                        'moduleid'  => $id,
721
                        'title'     => $title,
722
                        'order'     => $order,
723
                    ];
724
                }
725
            }
726
        }
727
728
        // -----------------------------------------------------------------
729
        // 4) Sort activities per section by LP order (display_order)
730
        // -----------------------------------------------------------------
731
        if (!empty($activities)) {
732
            $grouped   = [];
733
            $seqBySec  = [];
734
735
            foreach ($activities as $activity) {
736
                $sid = (int) ($activity['sectionid'] ?? 0);
737
738
                if (!isset($grouped[$sid])) {
739
                    $grouped[$sid]  = [];
740
                    $seqBySec[$sid] = 0;
741
                }
742
743
                $order = (int) ($activity['order'] ?? 0);
744
                if ($order <= 0) {
745
                    // Keep relative insertion order for items without LP order,
746
                    // but make sure they come *after* the LP-ordered items.
747
                    $seqBySec[$sid]++;
748
                    $order = 1000 + $seqBySec[$sid];
749
                }
750
751
                $activity['_sort'] = $order;
752
                $grouped[$sid][]   = $activity;
753
            }
754
755
            $activities = [];
756
            foreach ($grouped as $sid => $list) {
757
                usort(
758
                    $list,
759
                    static function (array $a, array $b): int {
760
                        return $a['_sort'] <=> $b['_sort'];
761
                    }
762
                );
763
764
                foreach ($list as $item) {
765
                    unset($item['order'], $item['_sort']);
766
                    $activities[] = $item;
767
                }
768
            }
769
        }
770
771
        return $activities;
772
    }
773
774
    /**
775
     * Export the sections of the course.
776
     */
777
    private function exportSections(string $exportDir): void
778
    {
779
        $sections = $this->getSections();
780
781
        foreach ($sections as $section) {
782
            $sectionExport = new SectionExport($this->course);
783
            $sectionExport->exportSection($section['id'], $exportDir);
784
        }
785
    }
786
787
    /**
788
     * Create a .mbz (ZIP) file from the exported data.
789
     */
790
    private function createMbzFile(string $sourceDir): string
791
    {
792
        $zip = new ZipArchive();
793
        $zipFile = $sourceDir.'.mbz';
794
795
        if ($zip->open($zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
796
            throw new Exception(get_lang('ErrorCreatingZip'));
797
        }
798
799
        $files = new RecursiveIteratorIterator(
800
            new RecursiveDirectoryIterator($sourceDir),
801
            RecursiveIteratorIterator::LEAVES_ONLY
802
        );
803
804
        foreach ($files as $file) {
805
            if (!$file->isDir()) {
806
                $filePath = $file->getRealPath();
807
                $relativePath = substr($filePath, strlen($sourceDir) + 1);
808
809
                if (!$zip->addFile($filePath, $relativePath)) {
810
                    throw new Exception(get_lang('ErrorAddingFileToZip').": $relativePath");
811
                }
812
            }
813
        }
814
815
        if (!$zip->close()) {
816
            throw new Exception(get_lang('ErrorClosingZip'));
817
        }
818
819
        return $zipFile;
820
    }
821
822
    /**
823
     * Clean up the temporary directory used for export.
824
     */
825
    private function cleanupTempDir(string $dir): void
826
    {
827
        $this->recursiveDelete($dir);
828
    }
829
830
    /**
831
     * Recursively delete a directory and its contents.
832
     */
833
    private function recursiveDelete(string $dir): void
834
    {
835
        $files = array_diff(scandir($dir), ['.', '..']);
836
        foreach ($files as $file) {
837
            $path = "$dir/$file";
838
            is_dir($path) ? $this->recursiveDelete($path) : unlink($path);
839
        }
840
        rmdir($dir);
841
    }
842
843
    /**
844
     * Export badges data to XML file.
845
     */
846
    private function exportBadgesXml(string $exportDir): void
847
    {
848
        $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
849
        $xmlContent .= '<badges>'.PHP_EOL;
850
        $xmlContent .= '</badges>';
851
        file_put_contents($exportDir.'/badges.xml', $xmlContent);
852
    }
853
854
    /**
855
     * Export course completion data to XML file.
856
     */
857
    private function exportCompletionXml(string $exportDir): void
858
    {
859
        $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
860
        $xmlContent .= '<completions>'.PHP_EOL;
861
        $xmlContent .= '</completions>';
862
        file_put_contents($exportDir.'/completion.xml', $xmlContent);
863
    }
864
865
    /**
866
     * Export gradebook data to XML file.
867
     */
868
    private function exportGradebookXml(string $exportDir): void
869
    {
870
        $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
871
        $xmlContent .= '<gradebook>'.PHP_EOL;
872
        $xmlContent .= '</gradebook>';
873
        file_put_contents($exportDir.'/gradebook.xml', $xmlContent);
874
    }
875
876
    /**
877
     * Export grade history data to XML file.
878
     */
879
    private function exportGradeHistoryXml(string $exportDir): void
880
    {
881
        $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
882
        $xmlContent .= '<grade_history>'.PHP_EOL;
883
        $xmlContent .= '</grade_history>';
884
        file_put_contents($exportDir.'/grade_history.xml', $xmlContent);
885
    }
886
887
    /**
888
     * Export groups data to XML file.
889
     */
890
    private function exportGroupsXml(string $exportDir): void
891
    {
892
        $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
893
        $xmlContent .= '<groups>'.PHP_EOL;
894
        $xmlContent .= '</groups>';
895
        file_put_contents($exportDir.'/groups.xml', $xmlContent);
896
    }
897
898
    /**
899
     * Export outcomes data to XML file.
900
     */
901
    private function exportOutcomesXml(string $exportDir): void
902
    {
903
        $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
904
        $xmlContent .= '<outcomes>'.PHP_EOL;
905
        $xmlContent .= '</outcomes>';
906
        file_put_contents($exportDir.'/outcomes.xml', $xmlContent);
907
    }
908
909
    /**
910
     * Export roles data to XML file.
911
     */
912
    private function exportRolesXml(string $exportDir): void
913
    {
914
        $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
915
        $xmlContent .= '<roles_definition>'.PHP_EOL;
916
        $xmlContent .= '  <role id="5">'.PHP_EOL;
917
        $xmlContent .= '    <name></name>'.PHP_EOL;
918
        $xmlContent .= '    <shortname>student</shortname>'.PHP_EOL;
919
        $xmlContent .= '    <nameincourse>$@NULL@$</nameincourse>'.PHP_EOL;
920
        $xmlContent .= '    <description></description>'.PHP_EOL;
921
        $xmlContent .= '    <sortorder>5</sortorder>'.PHP_EOL;
922
        $xmlContent .= '    <archetype>student</archetype>'.PHP_EOL;
923
        $xmlContent .= '  </role>'.PHP_EOL;
924
        $xmlContent .= '</roles_definition>'.PHP_EOL;
925
926
        file_put_contents($exportDir.'/roles.xml', $xmlContent);
927
    }
928
929
    /**
930
     * Export scales data to XML file.
931
     */
932
    private function exportScalesXml(string $exportDir): void
933
    {
934
        $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
935
        $xmlContent .= '<scales>'.PHP_EOL;
936
        $xmlContent .= '</scales>';
937
        file_put_contents($exportDir.'/scales.xml', $xmlContent);
938
    }
939
940
    /**
941
     * Export the user XML with admin user data.
942
     */
943
    private function exportUsersXml(string $exportDir): void
944
    {
945
        $adminData = self::getAdminUserData();
946
947
        $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
948
        $xmlContent .= '<users>'.PHP_EOL;
949
        $xmlContent .= '  <user id="'.$adminData['id'].'" contextid="'.$adminData['contextid'].'">'.PHP_EOL;
950
        $xmlContent .= '    <username>'.$adminData['username'].'</username>'.PHP_EOL;
951
        $xmlContent .= '    <idnumber>'.$adminData['idnumber'].'</idnumber>'.PHP_EOL;
952
        $xmlContent .= '    <email>'.$adminData['email'].'</email>'.PHP_EOL;
953
        $xmlContent .= '    <phone1>'.$adminData['phone1'].'</phone1>'.PHP_EOL;
954
        $xmlContent .= '    <phone2>'.$adminData['phone2'].'</phone2>'.PHP_EOL;
955
        $xmlContent .= '    <institution>'.$adminData['institution'].'</institution>'.PHP_EOL;
956
        $xmlContent .= '    <department>'.$adminData['department'].'</department>'.PHP_EOL;
957
        $xmlContent .= '    <address>'.$adminData['address'].'</address>'.PHP_EOL;
958
        $xmlContent .= '    <city>'.$adminData['city'].'</city>'.PHP_EOL;
959
        $xmlContent .= '    <country>'.$adminData['country'].'</country>'.PHP_EOL;
960
        $xmlContent .= '    <lastip>'.$adminData['lastip'].'</lastip>'.PHP_EOL;
961
        $xmlContent .= '    <picture>'.$adminData['picture'].'</picture>'.PHP_EOL;
962
        $xmlContent .= '    <description>'.$adminData['description'].'</description>'.PHP_EOL;
963
        $xmlContent .= '    <descriptionformat>'.$adminData['descriptionformat'].'</descriptionformat>'.PHP_EOL;
964
        $xmlContent .= '    <imagealt>'.$adminData['imagealt'].'</imagealt>'.PHP_EOL;
965
        $xmlContent .= '    <auth>'.$adminData['auth'].'</auth>'.PHP_EOL;
966
        $xmlContent .= '    <firstname>'.$adminData['firstname'].'</firstname>'.PHP_EOL;
967
        $xmlContent .= '    <lastname>'.$adminData['lastname'].'</lastname>'.PHP_EOL;
968
        $xmlContent .= '    <confirmed>'.$adminData['confirmed'].'</confirmed>'.PHP_EOL;
969
        $xmlContent .= '    <policyagreed>'.$adminData['policyagreed'].'</policyagreed>'.PHP_EOL;
970
        $xmlContent .= '    <deleted>'.$adminData['deleted'].'</deleted>'.PHP_EOL;
971
        $xmlContent .= '    <lang>'.$adminData['lang'].'</lang>'.PHP_EOL;
972
        $xmlContent .= '    <theme>'.$adminData['theme'].'</theme>'.PHP_EOL;
973
        $xmlContent .= '    <timezone>'.$adminData['timezone'].'</timezone>'.PHP_EOL;
974
        $xmlContent .= '    <firstaccess>'.$adminData['firstaccess'].'</firstaccess>'.PHP_EOL;
975
        $xmlContent .= '    <lastaccess>'.$adminData['lastaccess'].'</lastaccess>'.PHP_EOL;
976
        $xmlContent .= '    <lastlogin>'.$adminData['lastlogin'].'</lastlogin>'.PHP_EOL;
977
        $xmlContent .= '    <currentlogin>'.$adminData['currentlogin'].'</currentlogin>'.PHP_EOL;
978
        $xmlContent .= '    <mailformat>'.$adminData['mailformat'].'</mailformat>'.PHP_EOL;
979
        $xmlContent .= '    <maildigest>'.$adminData['maildigest'].'</maildigest>'.PHP_EOL;
980
        $xmlContent .= '    <maildisplay>'.$adminData['maildisplay'].'</maildisplay>'.PHP_EOL;
981
        $xmlContent .= '    <autosubscribe>'.$adminData['autosubscribe'].'</autosubscribe>'.PHP_EOL;
982
        $xmlContent .= '    <trackforums>'.$adminData['trackforums'].'</trackforums>'.PHP_EOL;
983
        $xmlContent .= '    <timecreated>'.$adminData['timecreated'].'</timecreated>'.PHP_EOL;
984
        $xmlContent .= '    <timemodified>'.$adminData['timemodified'].'</timemodified>'.PHP_EOL;
985
        $xmlContent .= '    <trustbitmask>'.$adminData['trustbitmask'].'</trustbitmask>'.PHP_EOL;
986
987
        // Preferences
988
        if (isset($adminData['preferences']) && is_array($adminData['preferences'])) {
989
            $xmlContent .= '    <preferences>'.PHP_EOL;
990
            foreach ($adminData['preferences'] as $preference) {
991
                $xmlContent .= '      <preference>'.PHP_EOL;
992
                $xmlContent .= '        <name>'.htmlspecialchars($preference['name']).'</name>'.PHP_EOL;
993
                $xmlContent .= '        <value>'.htmlspecialchars($preference['value']).'</value>'.PHP_EOL;
994
                $xmlContent .= '      </preference>'.PHP_EOL;
995
            }
996
            $xmlContent .= '    </preferences>'.PHP_EOL;
997
        } else {
998
            $xmlContent .= '    <preferences></preferences>'.PHP_EOL;
999
        }
1000
1001
        // Roles (empty for now)
1002
        $xmlContent .= '    <roles>'.PHP_EOL;
1003
        $xmlContent .= '      <role_overrides></role_overrides>'.PHP_EOL;
1004
        $xmlContent .= '      <role_assignments></role_assignments>'.PHP_EOL;
1005
        $xmlContent .= '    </roles>'.PHP_EOL;
1006
1007
        $xmlContent .= '  </user>'.PHP_EOL;
1008
        $xmlContent .= '</users>';
1009
1010
        // Save the content to the users.xml file
1011
        file_put_contents($exportDir.'/users.xml', $xmlContent);
1012
    }
1013
1014
    /**
1015
     * Export the backup settings, including dynamic settings for sections and activities.
1016
     */
1017
    private function exportBackupSettings(array $sections, array $activities): array
1018
    {
1019
        // root-level settings
1020
        $settings = [
1021
            ['level' => 'root', 'name' => 'filename', 'value' => 'backup-moodle-course-'.time().'.mbz'],
1022
            ['level' => 'root', 'name' => 'imscc11', 'value' => '0'],
1023
            ['level' => 'root', 'name' => 'users', 'value' => '1'],
1024
            ['level' => 'root', 'name' => 'anonymize', 'value' => '0'],
1025
            ['level' => 'root', 'name' => 'role_assignments', 'value' => '1'],
1026
            ['level' => 'root', 'name' => 'activities', 'value' => '1'],
1027
            ['level' => 'root', 'name' => 'blocks', 'value' => '1'],
1028
            ['level' => 'root', 'name' => 'files', 'value' => '1'],
1029
            ['level' => 'root', 'name' => 'filters', 'value' => '1'],
1030
            ['level' => 'root', 'name' => 'comments', 'value' => '1'],
1031
            ['level' => 'root', 'name' => 'badges', 'value' => '1'],
1032
            ['level' => 'root', 'name' => 'calendarevents', 'value' => '1'],
1033
            ['level' => 'root', 'name' => 'userscompletion', 'value' => '1'],
1034
            ['level' => 'root', 'name' => 'logs', 'value' => '0'],
1035
            ['level' => 'root', 'name' => 'grade_histories', 'value' => '0'],
1036
            ['level' => 'root', 'name' => 'questionbank', 'value' => '1'],
1037
            ['level' => 'root', 'name' => 'groups', 'value' => '1'],
1038
            ['level' => 'root', 'name' => 'competencies', 'value' => '0'],
1039
            ['level' => 'root', 'name' => 'customfield', 'value' => '1'],
1040
            ['level' => 'root', 'name' => 'contentbankcontent', 'value' => '1'],
1041
            ['level' => 'root', 'name' => 'legacyfiles', 'value' => '1'],
1042
        ];
1043
1044
        // section-level settings
1045
        foreach ($sections as $section) {
1046
            $settings[] = [
1047
                'level' => 'section',
1048
                'section' => 'section_'.$section['id'],
1049
                'name' => 'section_'.$section['id'].'_included',
1050
                'value' => '1',
1051
            ];
1052
            $settings[] = [
1053
                'level' => 'section',
1054
                'section' => 'section_'.$section['id'],
1055
                'name' => 'section_'.$section['id'].'_userinfo',
1056
                'value' => '1',
1057
            ];
1058
        }
1059
1060
        // activity-level settings
1061
        foreach ($activities as $activity) {
1062
            $settings[] = [
1063
                'level' => 'activity',
1064
                'activity' => $activity['modulename'].'_'.$activity['moduleid'],
1065
                'name' => $activity['modulename'].'_'.$activity['moduleid'].'_included',
1066
                'value' => '1',
1067
            ];
1068
            $settings[] = [
1069
                'level' => 'activity',
1070
                'activity' => $activity['modulename'].'_'.$activity['moduleid'],
1071
                'name' => $activity['modulename'].'_'.$activity['moduleid'].'_userinfo',
1072
                'value' => '1',
1073
            ];
1074
        }
1075
1076
        return $settings;
1077
    }
1078
1079
    /**
1080
     * Maps module name to item_type of c_lp_item.
1081
     * (c_lp_item.item_type: document, quiz, link, forum, student_publication, survey)
1082
     */
1083
    private function mapToLpItemType(string $moduleOrItemType): string
1084
    {
1085
        switch ($moduleOrItemType) {
1086
            case 'page':
1087
            case 'resource':
1088
                return 'document';
1089
            case 'assign':
1090
                return 'student_publication';
1091
            case 'url':
1092
                return 'link';
1093
            case 'feedback':
1094
                return 'survey';
1095
            default:
1096
                return $moduleOrItemType; // quiz, forum...
1097
        }
1098
    }
1099
1100
    /** Index titles by section + type + id from the LP items (c_lp_item.title). */
1101
    private function buildLpTitleIndex(): array
0 ignored issues
show
Unused Code introduced by
The method buildLpTitleIndex() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
1102
    {
1103
        $idx = [];
1104
        if (empty($this->course->resources[RESOURCE_LEARNPATH])) {
1105
            return $idx;
1106
        }
1107
        foreach ($this->course->resources[RESOURCE_LEARNPATH] as $lp) {
1108
            $sid = (int) $lp->source_id;
1109
            if (empty($lp->items)) {
1110
                continue;
1111
            }
1112
            foreach ($lp->items as $it) {
1113
                $type = $this->mapToLpItemType($it['item_type']);
1114
                $rid  = (string) $it['path'];
1115
                $idx[$sid][$type][$rid] = $it['title'] ?? '';
1116
            }
1117
        }
1118
        return $idx;
1119
    }
1120
1121
    /** Returns the LP title if it exists; otherwise, use the fallback. */
1122
    private function titleFromLp(array $idx, int $sectionId, string $moduleName, int $resourceId, string $fallback): string
0 ignored issues
show
Unused Code introduced by
The method titleFromLp() is not used, and could be removed.

This check looks for private methods that have been defined, but are not used inside the class.

Loading history...
1123
    {
1124
        if ($sectionId <= 0) {
1125
            return $fallback;
1126
        }
1127
        $type = $this->mapToLpItemType($moduleName);
1128
        $rid  = (string) $resourceId;
1129
        return $idx[$sectionId][$type][$rid] ?? $fallback;
1130
    }
1131
}
1132