SectionExport::addActivityToList()   F
last analyzed

Complexity

Conditions 29
Paths 12160

Size

Total Lines 87
Code Lines 64

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 64
c 4
b 0
f 0
dl 0
loc 87
rs 0
cc 29
nc 12160
nop 3

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
/* For licensing terms, see /license.txt */
4
5
namespace moodleexport;
6
7
use Exception;
8
9
/**
10
 * Class SectionExport.
11
 * Handles the export of course sections and their activities.
12
 *
13
 * @package moodleexport
14
 */
15
class SectionExport
16
{
17
    private $course;
18
    private $activitiesBySection = [];
19
20
    /**
21
     * Constructor to initialize the course object.
22
     *
23
     * @param object $course The course object to be exported.
24
     */
25
    public function __construct($course, array $activitiesBySection = [])
26
    {
27
        $this->course = $course;
28
        $this->activitiesBySection = $activitiesBySection;
29
    }
30
31
    /**
32
     * Export a section and its activities to the specified directory.
33
     */
34
    public function exportSection(int $sectionId, string $exportDir): void
35
    {
36
        $sectionDir = $exportDir."/sections/section_{$sectionId}";
37
38
        if (!is_dir($sectionDir)) {
39
            mkdir($sectionDir, api_get_permissions_for_new_directories(), true);
40
        }
41
42
        if ($sectionId > 0) {
43
            $learnpath = $this->getLearnpathById($sectionId);
44
            if ($learnpath === null) {
45
                throw new Exception("Learnpath with ID $sectionId not found.");
46
            }
47
            $sectionData = $this->getSectionData($learnpath);
48
        } else {
49
            $sectionData = [
50
                'id' => 0,
51
                'number' => 0,
52
                'name' => get_lang('General'),
53
                'summary' => get_lang('GeneralResourcesCourse'),
54
                'sequence' => 0,
55
                'visible' => 1,
56
                'timemodified' => time(),
57
                'activities' => $this->getActivitiesForGeneral(),
58
            ];
59
        }
60
61
        $this->createSectionXml($sectionData, $sectionDir);
62
        $this->createInforefXml($sectionData, $sectionDir);
63
        $this->exportActivities($sectionData['activities'], $exportDir, $sectionId);
64
    }
65
66
    /**
67
     * Get all general items not linked to any lesson (learnpath).
68
     */
69
    public function getGeneralItems(): array
70
    {
71
        $generalItems = [];
72
73
        // List of resource types and their corresponding ID keys
74
        $resourceTypes = [
75
            RESOURCE_DOCUMENT => 'source_id',
76
            RESOURCE_QUIZ => 'source_id',
77
            RESOURCE_GLOSSARY => 'glossary_id',
78
            RESOURCE_LINK => 'source_id',
79
            RESOURCE_WORK => 'source_id',
80
            RESOURCE_FORUM => 'source_id',
81
            RESOURCE_SURVEY => 'source_id',
82
            RESOURCE_TOOL_INTRO => 'source_id',
83
        ];
84
85
        foreach ($resourceTypes as $resourceType => $idKey) {
86
            if (!empty($this->course->resources[$resourceType])) {
87
                foreach ($this->course->resources[$resourceType] as $id => $resource) {
88
                    if (!$this->isItemInLearnpath($resource, $resourceType)) {
89
                        $title = $resourceType === RESOURCE_WORK
90
                            ? ($resource->params['title'] ?? '')
91
                            : ($resource->title ?? $resource->name);
92
                        $generalItems[] = [
93
                            'id' => $resource->$idKey,
94
                            'item_type' => $resourceType,
95
                            'path' => $id,
96
                            'title' => $title,
97
                        ];
98
                    }
99
                }
100
            }
101
        }
102
103
        return $generalItems;
104
    }
105
106
    /**
107
     * Get the activities for the general section.
108
     */
109
    public function getActivitiesForGeneral(): array
110
    {
111
        // Preferred path: reuse precomputed activities from MoodleExport
112
        if (isset($this->activitiesBySection[0]) && is_array($this->activitiesBySection[0])) {
113
            return $this->activitiesBySection[0];
114
        }
115
116
        // Fallback to legacy behavior (kept for safety)
117
        $generalLearnpath = (object) [
118
            'items' => $this->getGeneralItems(),
119
            'source_id' => 0,
120
        ];
121
122
        $activities = $this->getActivitiesForSection($generalLearnpath, true);
123
124
        if (!in_array('folder', array_column($activities, 'modulename'), true)) {
125
            $activities[] = [
126
                'id' => 0,
127
                'moduleid' => 0,
128
                'modulename' => 'folder',
129
                'name' => 'Documents',
130
                'sectionid' => 0,
131
            ];
132
        }
133
134
        return $activities;
135
    }
136
137
    /**
138
     * Get the learnpath object by its ID.
139
     */
140
    public function getLearnpathById(int $sectionId): ?object
141
    {
142
        foreach ($this->course->resources[RESOURCE_LEARNPATH] as $learnpath) {
143
            if ($learnpath->source_id == $sectionId) {
144
                return $learnpath;
145
            }
146
        }
147
148
        return null;
149
    }
150
151
    /**
152
     * Get section data for a learnpath.
153
     */
154
    public function getSectionData(object $learnpath): array
155
    {
156
        $sectionId = (int) $learnpath->source_id;
157
158
        return [
159
            'id' => $sectionId,
160
            'number' => $learnpath->display_order,
161
            'name' => $learnpath->name,
162
            'summary' => $learnpath->description,
163
            'sequence' => $sectionId,
164
            'visible' => $learnpath->visibility,
165
            'timemodified' => strtotime($learnpath->modified_on),
166
            'activities' => $this->getActivitiesForSection($learnpath),
167
        ];
168
    }
169
170
    /**
171
     * Get the activities for a specific section.
172
     */
173
    public function getActivitiesForSection(object $learnpath, bool $isGeneral = false): array
174
    {
175
        $sectionId = $isGeneral ? 0 : (int) $learnpath->source_id;
176
177
        // Preferred path: reuse precomputed activities from MoodleExport
178
        if (isset($this->activitiesBySection[$sectionId]) && is_array($this->activitiesBySection[$sectionId])) {
179
            return $this->activitiesBySection[$sectionId];
180
        }
181
182
        // Fallback to legacy behavior
183
        $activities = [];
184
        foreach ($learnpath->items as $item) {
185
            $this->addActivityToList($item, $sectionId, $activities);
186
        }
187
188
        return $activities;
189
    }
190
191
    /**
192
     * Export the activities of a section.
193
     */
194
    private function exportActivities(array $activities, string $exportDir, int $sectionId): void
195
    {
196
        $exportClasses = [
197
            'quiz' => QuizExport::class,
198
            'glossary' => GlossaryExport::class,
199
            'url' => UrlExport::class,
200
            'assign' => AssignExport::class,
201
            'forum' => ForumExport::class,
202
            'page' => PageExport::class,
203
            'resource' => ResourceExport::class,
204
            'folder' => FolderExport::class,
205
            'feedback' => FeedbackExport::class,
206
        ];
207
208
        foreach ($activities as $activity) {
209
            $moduleName = $activity['modulename'];
210
            if (isset($exportClasses[$moduleName])) {
211
                $exportClass = new $exportClasses[$moduleName]($this->course);
212
                $exportClass->export($activity['id'], $exportDir, $activity['moduleid'], $sectionId);
213
            } else {
214
                throw new \Exception("Export for module '$moduleName' is not supported.");
215
            }
216
        }
217
    }
218
219
    /**
220
     * Check if an item is associated with any learnpath.
221
     */
222
    private function isItemInLearnpath(object $item, string $type): bool
223
    {
224
        if (!empty($this->course->resources[RESOURCE_LEARNPATH])) {
225
            foreach ($this->course->resources[RESOURCE_LEARNPATH] as $learnpath) {
226
                if (!empty($learnpath->items)) {
227
                    foreach ($learnpath->items as $learnpathItem) {
228
                        if ($learnpathItem['item_type'] === $type && $learnpathItem['path'] == $item->source_id) {
229
                            return true;
230
                        }
231
                    }
232
                }
233
            }
234
        }
235
236
        return false;
237
    }
238
239
    /**
240
     * Add an activity to the activities list.
241
     * Generic approach: for any activity in a real section (LP sectionId > 0),
242
     * force a unique moduleid per LP item occurrence:
243
     *   moduleid = 900000000 + lp_item_id
244
     */
245
    private function addActivityToList(array $item, int $sectionId, array &$activities): void
246
    {
247
        static $documentsFolderAdded = false;
248
        if (!$documentsFolderAdded && $sectionId === 0) {
249
            $activities[] = [
250
                'id' => 0,
251
                'moduleid' => 0,
252
                'type' => 'folder',
253
                'modulename' => 'folder',
254
                'name' => 'Documents',
255
            ];
256
            $documentsFolderAdded = true;
257
        }
258
259
        $activityData = null;
260
        $activityClassMap = [
261
            'quiz' => QuizExport::class,
262
            'glossary' => GlossaryExport::class,
263
            'url' => UrlExport::class,
264
            'assign' => AssignExport::class,
265
            'forum' => ForumExport::class,
266
            'page' => PageExport::class,
267
            'resource' => ResourceExport::class,
268
            'feedback' => FeedbackExport::class,
269
        ];
270
271
        if (($item['id'] ?? null) == 'course_homepage') {
272
            $item['item_type'] = 'page';
273
            $item['path'] = 0;
274
        }
275
276
        $itemType = ($item['item_type'] ?? '') === 'link' ? 'url' :
277
            (($item['item_type'] ?? '') === 'work' || ($item['item_type'] ?? '') === 'student_publication' ? 'assign' :
278
                (($item['item_type'] ?? '') === 'survey' ? 'feedback' : ($item['item_type'] ?? '')));
279
280
        switch ($itemType) {
281
            case 'quiz':
282
            case 'glossary':
283
            case 'assign':
284
            case 'url':
285
            case 'forum':
286
            case 'feedback':
287
            case 'page':
288
                $activityId = $itemType === 'glossary' ? 1 : (int) ($item['path'] ?? 0);
289
                $exportClass = $activityClassMap[$itemType];
290
                $exportInstance = new $exportClass($this->course);
291
                $activityData = $exportInstance->getData($activityId, $sectionId);
292
                break;
293
294
            case 'document':
295
                $documentId = (int) ($item['path'] ?? 0);
296
                $document = \DocumentManager::get_document_data_by_id($documentId, $this->course->code);
297
298
                if ($document) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $document of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
299
                    $documentType = $this->getDocumentType($document['filetype'], $document['path']);
300
301
                    if ($sectionId > 0 && $documentType && isset($activityClassMap[$documentType])) {
302
                        $activityClass = $activityClassMap[$documentType];
303
                        $exportInstance = new $activityClass($this->course);
304
                        $activityData = $exportInstance->getData($documentId, $sectionId);
305
                    } elseif ($sectionId === 0 && $documentType === 'page') {
306
                        // Keep your original behavior for general section if you want
307
                        $activityClass = $activityClassMap['page'];
308
                        $exportInstance = new $activityClass($this->course);
309
                        $activityData = $exportInstance->getData($documentId, $sectionId);
310
                    }
311
                }
312
                break;
313
        }
314
315
        // Generic override: unique moduleid per LP occurrence (sectionId > 0)
316
        if (!empty($activityData) && $sectionId > 0) {
317
            $lpItemId = isset($item['id']) ? (int)$item['id'] : 0;
318
            $modName = (string)($activityData['modulename'] ?? '');
319
320
            if ($lpItemId > 0 && !in_array($modName, ['folder', 'glossary'], true)) {
321
                $activityData['moduleid'] = 900000000 + $lpItemId;
322
            }
323
        }
324
325
        if ($activityData) {
326
            $activities[] = [
327
                'id' => $activityData['id'],
328
                'moduleid' => $activityData['moduleid'],
329
                'type' => $item['item_type'],
330
                'modulename' => $activityData['modulename'],
331
                'name' => $activityData['name'],
332
            ];
333
        }
334
    }
335
336
    /**
337
     * Determine the document type based on filetype and path.
338
     */
339
    private function getDocumentType(string $filetype, string $path): ?string
340
    {
341
        if ('html' === pathinfo($path, PATHINFO_EXTENSION)) {
342
            return 'page';
343
        } elseif ('file' === $filetype) {
344
            return 'resource';
345
        } /*elseif ('folder' === $filetype) {
346
            return 'folder';
347
        }*/
348
349
        return null;
350
    }
351
352
    /**
353
     * Create the section.xml file.
354
     */
355
    private function createSectionXml(array $sectionData, string $destinationDir): void
356
    {
357
        $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
358
        $xmlContent .= '<section id="'.$sectionData['id'].'">'.PHP_EOL;
359
        $xmlContent .= '  <number>'.$sectionData['number'].'</number>'.PHP_EOL;
360
        $xmlContent .= '  <name>'.htmlspecialchars($sectionData['name']).'</name>'.PHP_EOL;
361
        $xmlContent .= '  <summary>'.htmlspecialchars($sectionData['summary']).'</summary>'.PHP_EOL;
362
        $xmlContent .= '  <summaryformat>1</summaryformat>'.PHP_EOL;
363
        $xmlContent .= '  <sequence>'.implode(',', array_column($sectionData['activities'], 'moduleid')).'</sequence>'.PHP_EOL;
364
        $xmlContent .= '  <visible>'.$sectionData['visible'].'</visible>'.PHP_EOL;
365
        $xmlContent .= '  <timemodified>'.$sectionData['timemodified'].'</timemodified>'.PHP_EOL;
366
        $xmlContent .= '</section>'.PHP_EOL;
367
368
        $xmlFile = $destinationDir.'/section.xml';
369
        file_put_contents($xmlFile, $xmlContent);
370
    }
371
372
    /**
373
     * Create the inforef.xml file for the section.
374
     */
375
    private function createInforefXml(array $sectionData, string $destinationDir): void
376
    {
377
        $xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
378
        $xmlContent .= '<inforef>'.PHP_EOL;
379
380
        foreach ($sectionData['activities'] as $activity) {
381
            $refId = $activity['moduleid'] ?? $activity['id'];
382
            $xmlContent .= '  <activity id="'.$refId.'">'.htmlspecialchars($activity['name']).'</activity>'.PHP_EOL;
383
        }
384
385
        $xmlContent .= '</inforef>'.PHP_EOL;
386
387
        $xmlFile = $destinationDir.'/inforef.xml';
388
        file_put_contents($xmlFile, $xmlContent);
389
    }
390
}
391