Passed
Push — 1.11.x ( d8c231...69d982 )
by Yannick
09:30 queued 14s
created

Cc13Convert   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 357
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 55
eloc 214
dl 0
loc 357
rs 6
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
C getItemSections() 0 47 12
A processSequence() 0 12 5
A itemIndenter() 0 24 6
C convert() 0 117 11
D getSequence() 0 104 21

How to fix   Complexity   

Complex Class

Complex classes like Cc13Convert often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Cc13Convert, and based on these observations, apply Extract Interface, too.

1
<?php
2
/* For licensing terms, see /license.txt */
3
4
class Cc13Convert
5
{
6
    /**
7
     * Converts a course object into a CC13 package and writes it to disk
8
     * @param string $packagedir
9
     * @param string $outdir
10
     * @param \Chamilo\CourseBundle\Component\CourseCopy\Course $objCourse The Course object is "augmented" with info from the api_get_course_info() function, into the "$objCourse->info" array attribute.
11
     * @return bool
12
     * @throws Exception
13
     */
14
    public static function convert(string $packagedir, string $outdir, \Chamilo\CourseBundle\Component\CourseCopy\Course $objCourse)
15
    {
16
        $dir = realpath($packagedir);
17
        if (empty($dir)) {
18
            throw new InvalidArgumentException('Directory does not exist!');
19
        }
20
        $odir = realpath($outdir);
21
        if (empty($odir)) {
22
            throw new InvalidArgumentException('Directory does not exist!');
23
        }
24
25
        if (!empty($objCourse)) {
26
            //Initialize the manifest metadata class
27
            $meta = new CcMetadataManifest();
28
29
            //Package metadata
30
            $metageneral = new CcMetadataGeneral();
31
            $metageneral->setLanguage($objCourse->info['language']);
0 ignored issues
show
Bug introduced by
The property info does not seem to exist on Chamilo\CourseBundle\Component\CourseCopy\Course.
Loading history...
32
            $metageneral->setTitle($objCourse->info['title'], $objCourse->info['language']);
33
            $metageneral->setDescription('', $objCourse->info['language']);
34
            $metageneral->setCatalog('category');
35
            $metageneral->setEntry($objCourse->info['categoryName']);
36
            $meta->addMetadataGeneral($metageneral);
37
38
            // Create the manifest
39
            $manifest = new CcManifest();
40
41
            $manifest->addMetadataManifest($meta);
42
43
            $organization = null;
44
45
            //Get the course structure - this will be transformed into organization
46
            //Step 1 - Get the list and order of sections/topics
47
48
            $count = 1;
49
            $sections = [];
50
            $resources = $objCourse->resources;
51
52
            // We check the quiz sections
53
            if (isset($resources['quiz'])) {
54
                $quizSections = self::getItemSections(
55
                    $resources['quiz'], // array of quiz IDs generated by the course backup process
56
                    'quiz',
57
                    $count,
58
                    $objCourse->info['code'],
59
                    $resources[RESOURCE_QUIZQUESTION] // selected question IDs from the course export form
60
                );
61
                $sections = array_merge($sections, $quizSections);
62
            }
63
64
            // We check the document sections
65
            if (isset($resources['document'])) {
66
                $documentSections = self::getItemSections(
67
                    $resources['document'],
68
                    'document',
69
                    $count,
70
                    $objCourse->info['code']
71
                );
72
                $sections = array_merge($sections, $documentSections);
73
            }
74
75
            // We check the wiki sections
76
            if (isset($resources['wiki'])) {
77
                $wikiSections = self::getItemSections(
78
                    $resources['wiki'],
79
                    'wiki',
80
                    $count,
81
                    $objCourse->info['code']
82
                );
83
                $sections = array_merge($sections, $wikiSections);
84
            }
85
86
            // We check the forum sections
87
            if (isset($resources['forum'])) {
88
                $forumSections = self::getItemSections(
89
                    $resources['forum'],
90
                    'forum',
91
                    $count,
92
                    $objCourse->info['code'],
93
                    $resources['Forum_Category']
94
                );
95
                $sections = array_merge($sections, $forumSections);
96
            }
97
98
            // We check the link sections
99
            if (isset($resources['link'])) {
100
                $linkSections = self::getItemSections(
101
                    $resources['link'],
102
                    'link',
103
                    $count,
104
                    $objCourse->info['code'],
105
                    $resources['Link_Category']
106
                );
107
                $sections = array_merge($sections, $linkSections);
108
            }
109
110
            //organization title
111
            $organization = new CcOrganization();
112
            foreach ($sections as $sectionid => $values) {
113
                $item = new CcItem();
114
                $item->title = $values[0];
115
                self::processSequence($item, $manifest, $values[1], $dir, $odir);
116
                $organization->addItem($item);
117
            }
118
            $manifest->putNodes();
119
120
            if (!empty($organization)) {
121
                $manifest->addNewOrganization($organization);
122
            }
123
124
            $manifestpath = $outdir.DIRECTORY_SEPARATOR.'imsmanifest.xml';
125
            $saved = $manifest->saveTo($manifestpath);
126
127
            return $saved;
128
        }
129
130
        return false;
131
    }
132
133
    /**
134
     * Return array of type $sections[1]['quiz', [sequence]] where sequence is the result of a call to getSequence()
135
     * @param array $itemData
136
     * @param string $itemType
137
     * @param int $count
138
     * @param string $courseCode The course code litteral
139
     * @param ?array $itmesExtraData If defined, represents the items IDs selected in the course export form
140
     * @return array
141
     * @reference self::getSequence()
142
     */
143
    protected static function getItemSections(array $itemData, string $itemType, int &$count, string $courseCode, ?array $itmesExtraData = null)
144
    {
145
        $sections = [];
146
        switch ($itemType) {
147
            case 'quiz':
148
            case 'document':
149
            case 'wiki':
150
                $convertType = $itemType;
151
                if ($itemType == 'wiki') {
152
                    $convertType = 'Page';
153
                }
154
                $sectionid = $count;
155
                $sectiontitle = ucfirst($itemType);
156
                $sequence = self::getSequence($itemData, 0, $convertType, $courseCode, $itmesExtraData);
157
                $sections[$sectionid] = [$sectiontitle, $sequence];
158
                $count++;
159
                break;
160
            case 'link':
161
                $links = self::getSequence($itemData, null, $itemType);
162
                foreach ($links as $categoryId => $sequence) {
163
                    $sectionid = $count;
164
                    if (isset($itmesExtraData[$categoryId])) {
165
                        $sectiontitle = $itmesExtraData[$categoryId]->title;
166
                    } else {
167
                        $sectiontitle = 'General';
168
                    }
169
                    $sections[$sectionid] = [$sectiontitle, $sequence];
170
                    $count++;
171
                }
172
                break;
173
            case 'forum':
174
                if (isset($itmesExtraData)) {
175
                    foreach ($itmesExtraData as $fcategory) {
176
                        if (isset($fcategory->obj)) {
177
                            $objCategory = $fcategory->obj;
178
                            $sectionid = $count;
179
                            $sectiontitle = $objCategory->cat_title;
180
                            $sequence = self::getSequence($itemData, $objCategory->iid, $itemType);
181
                            $sections[$sectionid] = [$sectiontitle, $sequence];
182
                            $count++;
183
                        }
184
                    }
185
                }
186
                break;
187
        }
188
189
        return $sections;
190
    }
191
192
    /**
193
     * Return array of items by category and item ID ("test" level, not "question" level) with details like title,
194
     * comment (description), type, questions, max_attempt, etc.
195
     * @param array $objItems Array of item objects as returned by the course backup code
196
     * @param ?int $categoryId
197
     * @param ?string $itemType
198
     * @param ?string $coursecode
199
     * @param ?array $itemQuestions
200
     * @return array|mixed
201
     */
202
    protected static function getSequence(array $objItems, ?int $categoryId = null, ?string $itemType = null, ?string $coursecode = null, ?array $itemQuestions = null)
203
    {
204
        $sequences = [];
205
        switch ($itemType) {
206
            case 'quiz':
207
                $sequence = [];
208
                foreach ($objItems as $objItem) {
209
                    if ($categoryId === 0) {
210
                        $questions = [];
211
                        foreach ($objItem->obj->question_ids as $questionId) {
212
                            if (isset($itemQuestions[$questionId])) {
213
                                $questions[$questionId] = $itemQuestions[$questionId];
214
                            }
215
                        }
216
                        // As weird as this may sound, the constructors for the different object types (found in
217
                        // src/CourseBundle/Component/CourseCopy/Resources/[objecttype].php) store the object property
218
                        // in the "obj" attribute. Old code...
219
                        // So here we recover properties from the quiz object, including questions (array built above).
220
                        $sequence[$categoryId][$objItem->obj->iid] = [
221
                            'title' => $objItem->obj->title,
222
                            'comment' => $objItem->obj->description,
223
                            'cc_type' => 'quiz',
224
                            'source_id' => $objItem->obj->iid,
225
                            'questions' => $questions,
226
                            'max_attempt' => $objItem->obj->max_attempt,
227
                            'expired_time' => $objItem->obj->expired_time,
228
                            'pass_percentage' => $objItem->obj->pass_percentage,
229
                            'random_answers' => $objItem->obj->random_answers,
230
                            'course_code' => $coursecode,
231
                        ];
232
                    }
233
                }
234
                $sequences = $sequence[$categoryId];
235
                break;
236
            case 'document':
237
                $sequence = [];
238
                foreach ($objItems as $objItem) {
239
                    if ($categoryId === 0) {
240
                        $sequence[$categoryId][$objItem->source_id] = [
241
                            'title' => $objItem->title,
242
                            'comment' => $objItem->comment,
243
                            'cc_type' => ($objItem->file_type == 'folder' ? 'folder' : 'resource'),
244
                            'source_id' => $objItem->source_id,
245
                            'path' => $objItem->path,
246
                            'file_type' => $objItem->file_type,
247
                            'course_code' => $coursecode,
248
                        ];
249
                    }
250
                }
251
                $sequences = $sequence[$categoryId];
252
                break;
253
            case 'forum':
254
                foreach ($objItems as $objItem) {
255
                    if ($categoryId == $objItem->obj->forum_category) {
256
                        $sequence[$categoryId][$objItem->obj->forum_id] = [
257
                            'title' => $objItem->obj->forum_title,
258
                            'comment' => $objItem->obj->forum_comment,
259
                            'cc_type' => 'forum',
260
                            'source_id' => $objItem->obj->iid,
261
                        ];
262
                    }
263
                }
264
                $sequences = $sequence[$categoryId];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sequence does not seem to be defined for all execution paths leading up to this point.
Loading history...
265
                break;
266
            case 'page':
267
                foreach ($objItems as $objItem) {
268
                    if ($categoryId === 0) {
269
                        $sequence[$categoryId][$objItem->page_id] = [
270
                            'title' => $objItem->title,
271
                            'comment' => $objItem->content,
272
                            'cc_type' => 'page',
273
                            'source_id' => $objItem->page_id,
274
                            'reflink' => $objItem->reflink,
275
                        ];
276
                    }
277
                }
278
                $sequences = $sequence[$categoryId];
279
                break;
280
            case 'link':
281
                if (!isset($categoryId)) {
282
                    $categories = [];
283
                    foreach ($objItems as $objItem) {
284
                        $categories[$objItem->category_id] = self::getSequence($objItems, $objItem->category_id, $itemType);
285
                    }
286
                    $sequences = $categories;
287
                } else {
288
                    foreach ($objItems as $objItem) {
289
                        if ($categoryId == $objItem->category_id) {
290
                            $sequence[$categoryId][$objItem->source_id] = [
291
                                'title' => $objItem->title,
292
                                'comment' => $objItem->description,
293
                                'cc_type' => 'url',
294
                                'source_id' => $objItem->source_id,
295
                                'url' => $objItem->url,
296
                                'target' => $objItem->target,
297
                            ];
298
                        }
299
                    }
300
                    $sequences = $sequence[$categoryId];
301
                }
302
                break;
303
        }
304
305
        return $sequences;
306
    }
307
308
    /**
309
     * Process the different types of activities exported from the course.
310
     * When dealing with tests, the "sequence" provided here is a test, not a question.
311
     * For each sequence, call the corresponding CcConverter[Type] object instantiation method in export/src/converter/.
312
     * @param CcIItem     $item
313
     * @param CcIManifest $manifest
314
     * @param array       $sequence
315
     * @param string      $packageroot
316
     * @param string      $outdir
317
     * @return void
318
     */
319
    protected static function processSequence(CcIItem &$item, CcIManifest &$manifest, array $sequence, string $packageroot, string $outdir)
320
    {
321
        if (!empty($sequence)) {
322
            foreach ($sequence as $seq) {
323
                $activity_type = ucfirst($seq['cc_type']);
324
                $activity_indentation = 0;
325
                $aitem = self::itemIndenter($item, $activity_indentation);
326
                $caller = "CcConverter{$activity_type}";
327
                if (class_exists($caller)) {
328
                    $obj = new $caller($aitem, $manifest, $packageroot, $outdir);
329
                    if (!$obj->convert($outdir, $seq)) {
330
                        throw new RuntimeException("failed to convert {$activity_type}");
331
                    }
332
                }
333
            }
334
        }
335
    }
336
337
    protected static function itemIndenter(CcIItem &$item, $level = 0)
338
    {
339
        $indent = (int) $level;
340
        $indent = ($indent) <= 0 ? 0 : $indent;
341
        $nprev = null;
342
        $nfirst = null;
343
        for ($pos = 0, $size = $indent; $pos < $size; $pos++) {
344
            $nitem = new CcItem();
345
            $nitem->title = '';
346
            if (empty($nfirst)) {
347
                $nfirst = $nitem;
348
            }
349
            if (!empty($nprev)) {
350
                $nprev->addChildItem($nitem);
351
            }
352
            $nprev = $nitem;
353
        }
354
        $result = $item;
355
        if (!empty($nfirst)) {
356
            $item->addChildItem($nfirst);
357
            $result = $nprev;
358
        }
359
360
        return $result;
361
    }
362
}
363