Passed
Push — master ( 68f627...939207 )
by
unknown
12:17 queued 03:34
created

SectionExport::addActivityToList()   F

Complexity

Conditions 24
Paths 2432

Size

Total Lines 78
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 24
eloc 59
c 1
b 0
f 0
nc 2432
nop 3
dl 0
loc 78
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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);
0 ignored issues
show
Deprecated Code introduced by
The function DocumentManager::get_document_data_by_id() has been deprecated: use $repo->find() ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

301
                $document = /** @scrutinizer ignore-deprecated */ DocumentManager::get_document_data_by_id($documentId, $this->course->code);

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.

Loading history...
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