1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
/* For licensing terms, see /license.txt */ |
6
|
|
|
|
7
|
|
|
namespace Chamilo\CourseBundle\Component\CourseCopy\Moodle\Builder; |
8
|
|
|
|
9
|
|
|
use Chamilo\CourseBundle\Component\CourseCopy\Moodle\Activities\AssignExport; |
10
|
|
|
use Chamilo\CourseBundle\Component\CourseCopy\Moodle\Activities\FeedbackExport; |
11
|
|
|
use Chamilo\CourseBundle\Component\CourseCopy\Moodle\Activities\FolderExport; |
12
|
|
|
use Chamilo\CourseBundle\Component\CourseCopy\Moodle\Activities\ForumExport; |
13
|
|
|
use Chamilo\CourseBundle\Component\CourseCopy\Moodle\Activities\GlossaryExport; |
14
|
|
|
use Chamilo\CourseBundle\Component\CourseCopy\Moodle\Activities\PageExport; |
15
|
|
|
use Chamilo\CourseBundle\Component\CourseCopy\Moodle\Activities\QuizExport; |
16
|
|
|
use Chamilo\CourseBundle\Component\CourseCopy\Moodle\Activities\ResourceExport; |
17
|
|
|
use Chamilo\CourseBundle\Component\CourseCopy\Moodle\Activities\UrlExport; |
18
|
|
|
use DocumentManager; |
19
|
|
|
use Exception; |
20
|
|
|
|
21
|
|
|
use const PATHINFO_EXTENSION; |
22
|
|
|
use const PHP_EOL; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Handles the export of course sections and their activities. |
26
|
|
|
*/ |
27
|
|
|
class SectionExport |
28
|
|
|
{ |
29
|
|
|
/** |
30
|
|
|
* @var object |
31
|
|
|
*/ |
32
|
|
|
private $course; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @param object $course the course object to be exported |
36
|
|
|
*/ |
37
|
|
|
public function __construct(object $course) |
38
|
|
|
{ |
39
|
|
|
$this->course = $course; |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Export a section and its activities to the specified directory. |
44
|
|
|
*/ |
45
|
|
|
public function exportSection(int $sectionId, string $exportDir): void |
46
|
|
|
{ |
47
|
|
|
$sectionDir = $exportDir."/sections/section_{$sectionId}"; |
48
|
|
|
if (!is_dir($sectionDir)) { |
49
|
|
|
mkdir($sectionDir, api_get_permissions_for_new_directories(), true); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
if ($sectionId > 0) { |
53
|
|
|
$learnpath = $this->getLearnpathById($sectionId); |
54
|
|
|
if (null === $learnpath) { |
55
|
|
|
throw new Exception("Learnpath with ID $sectionId not found."); |
56
|
|
|
} |
57
|
|
|
$sectionData = $this->getSectionData($learnpath); |
58
|
|
|
} else { |
59
|
|
|
$sectionData = [ |
60
|
|
|
'id' => 0, |
61
|
|
|
'number' => 0, |
62
|
|
|
'name' => get_lang('General'), |
63
|
|
|
'summary' => get_lang('GeneralResourcesCourse'), |
64
|
|
|
'sequence' => 0, |
65
|
|
|
'visible' => 1, |
66
|
|
|
'timemodified' => time(), |
67
|
|
|
'activities' => $this->getActivitiesForGeneral(), |
68
|
|
|
]; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
$this->exportActivities($sectionData['activities'], $exportDir, (int) $sectionData['id']); |
72
|
|
|
$this->createSectionXml($sectionData, $sectionDir); |
73
|
|
|
$this->createInforefXml($sectionData, $sectionDir); |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* Get all general items not linked to any learnpath. |
78
|
|
|
* |
79
|
|
|
* @return array<int,array<string,mixed>> |
80
|
|
|
*/ |
81
|
|
|
public function getGeneralItems(): array |
82
|
|
|
{ |
83
|
|
|
$generalItems = []; |
84
|
|
|
|
85
|
|
|
$resourceTypes = [ |
86
|
|
|
RESOURCE_DOCUMENT => 'source_id', |
87
|
|
|
RESOURCE_QUIZ => 'source_id', |
88
|
|
|
RESOURCE_GLOSSARY => 'glossary_id', |
89
|
|
|
RESOURCE_LINK => 'source_id', |
90
|
|
|
RESOURCE_WORK => 'source_id', |
91
|
|
|
RESOURCE_FORUM => 'source_id', |
92
|
|
|
RESOURCE_SURVEY => 'source_id', |
93
|
|
|
RESOURCE_TOOL_INTRO => 'source_id', |
94
|
|
|
]; |
95
|
|
|
|
96
|
|
|
foreach ($resourceTypes as $resourceType => $idKey) { |
97
|
|
|
if (!empty($this->course->resources[$resourceType])) { |
98
|
|
|
foreach ($this->course->resources[$resourceType] as $id => $resource) { |
99
|
|
|
if (!$this->isItemInLearnpath($resource, (string) $resourceType)) { |
100
|
|
|
$title = RESOURCE_WORK === $resourceType |
101
|
|
|
? ($resource->params['title'] ?? '') |
102
|
|
|
: ($resource->title ?? $resource->name); |
103
|
|
|
$generalItems[] = [ |
104
|
|
|
'id' => $resource->{$idKey}, |
105
|
|
|
'item_type' => $resourceType, |
106
|
|
|
'path' => $id, |
107
|
|
|
'title' => $title, |
108
|
|
|
]; |
109
|
|
|
} |
110
|
|
|
} |
111
|
|
|
} |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
return $generalItems; |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* Get the activities for the general section. |
119
|
|
|
* |
120
|
|
|
* @return array<int,array<string,mixed>> |
121
|
|
|
*/ |
122
|
|
|
public function getActivitiesForGeneral(): array |
123
|
|
|
{ |
124
|
|
|
$generalLearnpath = (object) [ |
125
|
|
|
'items' => $this->getGeneralItems(), |
126
|
|
|
'source_id' => 0, |
127
|
|
|
]; |
128
|
|
|
|
129
|
|
|
$activities = $this->getActivitiesForSection($generalLearnpath, true); |
130
|
|
|
|
131
|
|
|
if (!\in_array('folder', array_column($activities, 'modulename'), true)) { |
132
|
|
|
$activities[] = [ |
133
|
|
|
'id' => 0, |
134
|
|
|
'moduleid' => 0, |
135
|
|
|
'modulename' => 'folder', |
136
|
|
|
'name' => 'Documents', |
137
|
|
|
'sectionid' => 0, |
138
|
|
|
]; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
return $activities; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* Get the learnpath object by its ID. |
146
|
|
|
*/ |
147
|
|
|
public function getLearnpathById(int $sectionId): ?object |
148
|
|
|
{ |
149
|
|
|
foreach (($this->course->resources[RESOURCE_LEARNPATH] ?? []) as $learnpath) { |
150
|
|
|
if (($learnpath->source_id ?? null) == $sectionId) { |
151
|
|
|
return $learnpath; |
152
|
|
|
} |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
return null; |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* Get section data for a learnpath. |
160
|
|
|
* |
161
|
|
|
* @return array<string,mixed> |
162
|
|
|
*/ |
163
|
|
|
public function getSectionData(object $learnpath): array |
164
|
|
|
{ |
165
|
|
|
return [ |
166
|
|
|
'id' => $learnpath->source_id, |
167
|
|
|
'number' => $learnpath->display_order, |
168
|
|
|
'name' => $learnpath->name, |
169
|
|
|
'summary' => $learnpath->description, |
170
|
|
|
'sequence' => $learnpath->source_id, |
171
|
|
|
'visible' => $learnpath->visibility, |
172
|
|
|
'timemodified' => strtotime($learnpath->modified_on), |
173
|
|
|
'activities' => $this->getActivitiesForSection($learnpath), |
174
|
|
|
]; |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
/** |
178
|
|
|
* Get the activities for a specific section. |
179
|
|
|
* |
180
|
|
|
* @return array<int,array<string,mixed>> |
181
|
|
|
*/ |
182
|
|
|
public function getActivitiesForSection(object $learnpath, bool $isGeneral = false): array |
183
|
|
|
{ |
184
|
|
|
$activities = []; |
185
|
|
|
$sectionId = $isGeneral ? 0 : (int) $learnpath->source_id; |
186
|
|
|
|
187
|
|
|
foreach ($learnpath->items as $item) { |
188
|
|
|
$this->addActivityToList($item, $sectionId, $activities); |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
return $activities; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* Export the activities of a section. |
196
|
|
|
* |
197
|
|
|
* @param array<int,array<string,mixed>> $activities |
198
|
|
|
*/ |
199
|
|
|
private function exportActivities(array $activities, string $exportDir, int $sectionId): void |
200
|
|
|
{ |
201
|
|
|
$exportClasses = [ |
202
|
|
|
'quiz' => QuizExport::class, |
203
|
|
|
'glossary' => GlossaryExport::class, |
204
|
|
|
'url' => UrlExport::class, |
205
|
|
|
'assign' => AssignExport::class, |
206
|
|
|
'forum' => ForumExport::class, |
207
|
|
|
'page' => PageExport::class, |
208
|
|
|
'resource' => ResourceExport::class, |
209
|
|
|
'folder' => FolderExport::class, |
210
|
|
|
'feedback' => FeedbackExport::class, |
211
|
|
|
]; |
212
|
|
|
|
213
|
|
|
foreach ($activities as $activity) { |
214
|
|
|
$moduleName = $activity['modulename']; |
215
|
|
|
if (isset($exportClasses[$moduleName])) { |
216
|
|
|
$exportClass = $exportClasses[$moduleName]; |
217
|
|
|
$exporter = new $exportClass($this->course); |
218
|
|
|
$exporter->export((int) $activity['id'], $exportDir, (int) $activity['moduleid'], $sectionId); |
219
|
|
|
} else { |
220
|
|
|
throw new Exception("Export for module '$moduleName' is not supported."); |
221
|
|
|
} |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* Check if an item is associated with any learnpath. |
227
|
|
|
*/ |
228
|
|
|
private function isItemInLearnpath(object $item, string $type): bool |
229
|
|
|
{ |
230
|
|
|
foreach (($this->course->resources[RESOURCE_LEARNPATH] ?? []) as $learnpath) { |
231
|
|
|
foreach (($learnpath->items ?? []) as $learnpathItem) { |
232
|
|
|
$lpType = ($learnpathItem['item_type'] ?? '') === 'student_publication' ? 'work' : ($learnpathItem['item_type'] ?? ''); |
233
|
|
|
if ($lpType === $type && (string) ($learnpathItem['path'] ?? '') === (string) ($item->source_id ?? '')) { |
234
|
|
|
return true; |
235
|
|
|
} |
236
|
|
|
} |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
return false; |
240
|
|
|
} |
241
|
|
|
|
242
|
|
|
/** |
243
|
|
|
* Add an activity to the activities list. |
244
|
|
|
* |
245
|
|
|
* @param array<string,mixed> $item |
246
|
|
|
* @param array<int,array<string,mixed>> $activities (by ref) |
247
|
|
|
*/ |
248
|
|
|
private function addActivityToList(array $item, int $sectionId, array &$activities): void |
249
|
|
|
{ |
250
|
|
|
static $documentsFolderAdded = false; |
251
|
|
|
if (!$documentsFolderAdded && 0 === $sectionId) { |
252
|
|
|
$activities[] = [ |
253
|
|
|
'id' => 0, |
254
|
|
|
'moduleid' => 0, |
255
|
|
|
'type' => 'folder', |
256
|
|
|
'modulename' => 'folder', |
257
|
|
|
'name' => 'Documents', |
258
|
|
|
]; |
259
|
|
|
$documentsFolderAdded = true; |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
$activityData = null; |
263
|
|
|
|
264
|
|
|
$activityClassMap = [ |
265
|
|
|
'quiz' => QuizExport::class, |
266
|
|
|
'glossary' => GlossaryExport::class, |
267
|
|
|
'url' => UrlExport::class, |
268
|
|
|
'assign' => AssignExport::class, |
269
|
|
|
'forum' => ForumExport::class, |
270
|
|
|
'page' => PageExport::class, |
271
|
|
|
'resource' => ResourceExport::class, |
272
|
|
|
'feedback' => FeedbackExport::class, |
273
|
|
|
]; |
274
|
|
|
|
275
|
|
|
if ('course_homepage' == $item['id']) { |
276
|
|
|
$item['item_type'] = 'page'; |
277
|
|
|
$item['path'] = 0; |
278
|
|
|
} |
279
|
|
|
|
280
|
|
|
$itemType = 'link' === $item['item_type'] ? 'url' |
281
|
|
|
: (('work' === $item['item_type'] || 'student_publication' === $item['item_type']) ? 'assign' |
282
|
|
|
: ('survey' === $item['item_type'] ? 'feedback' : $item['item_type'])); |
283
|
|
|
|
284
|
|
|
switch ($itemType) { |
285
|
|
|
case 'quiz': |
286
|
|
|
case 'glossary': |
287
|
|
|
case 'assign': |
288
|
|
|
case 'url': |
289
|
|
|
case 'forum': |
290
|
|
|
case 'feedback': |
291
|
|
|
case 'page': |
292
|
|
|
$activityId = 'glossary' === $itemType ? 1 : (int) $item['path']; |
293
|
|
|
$exportClass = $activityClassMap[$itemType]; |
294
|
|
|
$exportInstance = new $exportClass($this->course); |
295
|
|
|
$activityData = $exportInstance->getData($activityId, $sectionId); |
296
|
|
|
|
297
|
|
|
break; |
298
|
|
|
|
299
|
|
|
case 'document': |
300
|
|
|
$documentId = (int) $item['path']; |
301
|
|
|
$document = DocumentManager::get_document_data_by_id($documentId, $this->course->code); |
|
|
|
|
302
|
|
|
|
303
|
|
|
if ($document) { |
304
|
|
|
$isRoot = 1 === substr_count($document['path'], '/'); |
305
|
|
|
$documentType = $this->getDocumentType($document['filetype'], $document['path']); |
306
|
|
|
if ('page' === $documentType && $isRoot) { |
307
|
|
|
$exportInstance = new PageExport($this->course); |
308
|
|
|
$activityData = $exportInstance->getData((int) $item['path'], $sectionId); |
309
|
|
|
} elseif ($sectionId > 0 && $documentType && isset($activityClassMap[$documentType])) { |
310
|
|
|
$exportClass = $activityClassMap[$documentType]; |
311
|
|
|
$exportInstance = new $exportClass($this->course); |
312
|
|
|
$activityData = $exportInstance->getData((int) $item['path'], $sectionId); |
313
|
|
|
} |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
break; |
317
|
|
|
} |
318
|
|
|
|
319
|
|
|
if ($activityData) { |
320
|
|
|
$activities[] = [ |
321
|
|
|
'id' => (int) $activityData['id'], |
322
|
|
|
'moduleid' => (int) $activityData['moduleid'], |
323
|
|
|
'type' => (string) $item['item_type'], |
324
|
|
|
'modulename' => (string) $activityData['modulename'], |
325
|
|
|
'name' => (string) $activityData['name'], |
326
|
|
|
]; |
327
|
|
|
} |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
/** |
331
|
|
|
* Determine the document type based on filetype and path. |
332
|
|
|
*/ |
333
|
|
|
private function getDocumentType(string $filetype, string $path): ?string |
334
|
|
|
{ |
335
|
|
|
if ('html' === pathinfo($path, PATHINFO_EXTENSION)) { |
336
|
|
|
return 'page'; |
337
|
|
|
} |
338
|
|
|
if ('file' === $filetype) { |
339
|
|
|
return 'resource'; |
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
// if ('folder' === $filetype) return 'folder'; |
343
|
|
|
return null; |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* Create the section.xml file. |
348
|
|
|
* |
349
|
|
|
* @param array<string,mixed> $sectionData |
350
|
|
|
*/ |
351
|
|
|
private function createSectionXml(array $sectionData, string $destinationDir): void |
352
|
|
|
{ |
353
|
|
|
$seen = []; |
354
|
|
|
$cmIds = []; |
355
|
|
|
foreach ($sectionData['activities'] as $a) { |
356
|
|
|
$name = (string) ($a['modulename'] ?? ''); |
357
|
|
|
$mid = isset($a['moduleid']) ? (int) $a['moduleid'] : null; |
358
|
|
|
if ('' === $name || null === $mid || $mid < 0) { |
359
|
|
|
continue; |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
$key = $name.':'.$mid; |
363
|
|
|
if (isset($seen[$key])) { |
364
|
|
|
continue; |
365
|
|
|
} |
366
|
|
|
$seen[$key] = true; |
367
|
|
|
|
368
|
|
|
$cmIds[] = (string) $mid; |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL; |
372
|
|
|
$xmlContent .= '<section id="'.$sectionData['id'].'">'.PHP_EOL; |
373
|
|
|
$xmlContent .= ' <number>'.$sectionData['number'].'</number>'.PHP_EOL; |
374
|
|
|
$xmlContent .= ' <name>'.htmlspecialchars((string) $sectionData['name']).'</name>'.PHP_EOL; |
375
|
|
|
$xmlContent .= ' <summary>'.htmlspecialchars((string) $sectionData['summary']).'</summary>'.PHP_EOL; |
376
|
|
|
$xmlContent .= ' <summaryformat>1</summaryformat>'.PHP_EOL; |
377
|
|
|
$xmlContent .= ' <sequence>'.implode(',', $cmIds).'</sequence>'.PHP_EOL; |
378
|
|
|
$xmlContent .= ' <visible>'.$sectionData['visible'].'</visible>'.PHP_EOL; |
379
|
|
|
$xmlContent .= ' <timemodified>'.$sectionData['timemodified'].'</timemodified>'.PHP_EOL; |
380
|
|
|
$xmlContent .= '</section>'.PHP_EOL; |
381
|
|
|
|
382
|
|
|
file_put_contents($destinationDir.'/section.xml', $xmlContent); |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
/** |
386
|
|
|
* Create the inforef.xml file for the section. |
387
|
|
|
* |
388
|
|
|
* @param array<string,mixed> $sectionData |
389
|
|
|
*/ |
390
|
|
|
private function createInforefXml(array $sectionData, string $destinationDir): void |
391
|
|
|
{ |
392
|
|
|
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL; |
393
|
|
|
$xmlContent .= '<inforef>'.PHP_EOL; |
394
|
|
|
|
395
|
|
|
foreach ($sectionData['activities'] as $activity) { |
396
|
|
|
$xmlContent .= ' <activity id="'.(int) $activity['id'].'">' |
397
|
|
|
.htmlspecialchars((string) $activity['name']).'</activity>'.PHP_EOL; |
398
|
|
|
} |
399
|
|
|
|
400
|
|
|
$xmlContent .= '</inforef>'.PHP_EOL; |
401
|
|
|
|
402
|
|
|
file_put_contents($destinationDir.'/inforef.xml', $xmlContent); |
403
|
|
|
} |
404
|
|
|
} |
405
|
|
|
|
This function has been deprecated. The supplier of the function has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.