Passed
Pull Request — master (#7027)
by
unknown
12:49 queued 03:07
created

LearnpathMetaExport::exportAll()   C

Complexity

Conditions 14
Paths 128

Size

Total Lines 103
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 14
eloc 59
c 1
b 0
f 1
nc 128
nop 1
dl 0
loc 103
rs 6.0333

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\Activities;
8
9
use function is_array;
10
11
/**
12
 * Dumps raw Learnpath (lessons) metadata as JSON sidecars under chamilo/learnpath/.
13
 * No mapping: we persist exactly the payload produced by build_learnpaths() and build_learnpath_category().
14
 */
15
class LearnpathMetaExport extends ActivityExport
16
{
17
    /** Guard to run exportAll() only once even if export() is called multiple times. */
18
    private bool $ran = false;
19
20
    /**
21
     * Entry point required by ActivityExport.
22
     * This meta-export is not per-activity; we dump the whole learnpath corpus once.
23
     */
24
    public function export(int $activityId, string $exportDir, int $moduleId, int $sectionId): void
25
    {
26
        if ($this->ran) {
27
            return; // Already exported; keep it idempotent.
28
        }
29
        $this->ran = true;
30
        $this->exportAll($exportDir);
31
    }
32
33
    /**
34
     * Export categories, an index for all present learnpaths, and one folder per LP with raw JSON.
35
     * Returns the number of learnpaths exported.
36
     */
37
    public function exportAll(string $exportDir): int
38
    {
39
        $baseDir = rtrim($exportDir, '/').'/chamilo/learnpath';
40
        $this->ensureDir($baseDir);
41
42
        // Resolve resources bag defensively
43
        $res = is_array($this->course->resources ?? null) ? $this->course->resources : [];
44
45
        // ---- Categories (optional but recommended) ----
46
        $catBag =
47
            ($res[\defined('RESOURCE_LEARNPATH_CATEGORY') ? RESOURCE_LEARNPATH_CATEGORY : 'learnpath_category'] ?? null)
48
            ?? ($res['learnpath_category'] ?? [])
49
        ;
50
51
        $categories = [];
52
        if (is_array($catBag)) {
53
            foreach ($catBag as $cid => $cwrap) {
54
                $cobj = $this->unwrapIfObject($cwrap);
55
                $carr = $this->toArray($cobj);
56
                // Normalize minimal shape (id, title) if present
57
                $categories[] = [
58
                    'id'    => (int) ($carr['id']    ?? $cid),
59
                    'title' => (string) ($carr['title'] ?? ($carr['name'] ?? '')),
60
                    'raw'   => $carr, // keep full raw payload as well
61
                ];
62
            }
63
        }
64
        $this->writeJson($baseDir.'/categories.json', ['categories' => $categories]);
65
66
        // Build a map id→title for quick lookup
67
        $catTitle = [];
68
        foreach ($categories as $c) {
69
            $catTitle[(int) $c['id']] = (string) $c['title'];
70
        }
71
72
        // ---- Learnpaths ----
73
        $lpBag =
74
            ($res[\defined('RESOURCE_LEARNPATH') ? RESOURCE_LEARNPATH : 'learnpath'] ?? null)
75
            ?? ($res['learnpath'] ?? [])
76
        ;
77
78
        if (!is_array($lpBag) || empty($lpBag)) {
79
            @error_log('[LearnpathMetaExport] No learnpaths present in resources; skipping.');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for error_log(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

79
            /** @scrutinizer ignore-unhandled */ @error_log('[LearnpathMetaExport] No learnpaths present in resources; skipping.');

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
80
            // still return 0 after writing (possibly empty) categories.json
81
            $this->exportScormIndexIfAny($res, $baseDir);
82
            $this->writeJson($baseDir.'/index.json', ['learnpaths' => []]);
83
            return 0;
84
        }
85
86
        $index = [];
87
        $count = 0;
88
89
        foreach ($lpBag as $lpId => $lpWrap) {
90
            $lpObj = $this->unwrapIfObject($lpWrap); // stdClass payload from builder
91
            $lpArr = $this->toArray($lpObj);         // full raw payload
92
93
            $lpDir = $baseDir.'/lp_'.((int) $lpArr['id'] ?: (int) $lpId);
94
            $this->ensureDir($lpDir);
95
96
            // Resolve category label if possible
97
            $cid = (int) ($lpArr['category_id'] ?? 0);
98
            $lpArr['_context'] = [
99
                'lp_id'         => (int) ($lpArr['id'] ?? $lpId),
100
                'lp_type'       => (int) ($lpArr['lp_type'] ?? 0), // 1=LP,2=SCORM,3=AICC
101
                'category_id'   => $cid,
102
                'category_name' => $catTitle[$cid] ?? null,
103
            ];
104
105
            // Persist learnpath.json (complete raw payload + _context)
106
            $this->writeJson($lpDir.'/learnpath.json', ['learnpath' => $lpArr]);
107
108
            // Persist items.json as a separate, ordered list (if provided)
109
            $items = [];
110
            if (isset($lpArr['items']) && is_array($lpArr['items'])) {
111
                $items = $lpArr['items'];
112
                // Stable sort by display_order if present, otherwise keep builder order
113
                usort($items, static function (array $a, array $b): int {
114
                    return (int) ($a['display_order'] ?? 0) <=> (int) ($b['display_order'] ?? 0);
115
                });
116
            }
117
            $this->writeJson($lpDir.'/items.json', ['items' => $items]);
118
119
            // Add to index
120
            $index[] = [
121
                'id'            => (int) ($lpArr['id'] ?? $lpId),
122
                'title'         => (string) ($lpArr['title'] ?? ''),
123
                'lp_type'       => (int)   ($lpArr['lp_type'] ?? 0),
124
                'category_id'   => $cid,
125
                'category_name' => $catTitle[$cid] ?? null,
126
                'dir'           => 'lp_'.((int) $lpArr['id'] ?: (int) $lpId),
127
            ];
128
129
            $count++;
130
        }
131
132
        // Persist learnpaths index
133
        $this->writeJson($baseDir.'/index.json', ['learnpaths' => $index]);
134
135
        // Optional SCORM index (if present in resources)
136
        $this->exportScormIndexIfAny($res, $baseDir);
137
138
        @error_log('[LearnpathMetaExport] Exported learnpaths='.$count.' categories='.count($categories));
139
        return $count;
140
    }
141
142
    /** If builder added "scorm" bag, also dump a simple index for reference. */
143
    private function exportScormIndexIfAny(array $res, string $baseDir): void
144
    {
145
        $scormBag = $res['scorm'] ?? null;
146
        if (!is_array($scormBag) || empty($scormBag)) {
147
            return;
148
        }
149
        $out = [];
150
        foreach ($scormBag as $sid => $swrap) {
151
            $sobj = $this->unwrapIfObject($swrap);
152
            $sarr = $this->toArray($sobj);
153
            $out[] = [
154
                'id'   => (int) ($sarr['id'] ?? $sid),
155
                'name' => (string) ($sarr['name'] ?? ''),
156
                'path' => (string) ($sarr['path'] ?? ''),
157
                'raw'  => $sarr,
158
            ];
159
        }
160
        $this->writeJson($baseDir.'/scorm_index.json', ['scorm' => $out]);
161
    }
162
163
    /** Ensure directory exists (recursive). */
164
    private function ensureDir(string $dir): void
165
    {
166
        if (!is_dir($dir) && !@mkdir($dir, api_get_permissions_for_new_directories(), true)) {
167
            @error_log('[LearnpathMetaExport] ERROR mkdir failed: '.$dir);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for error_log(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

167
            /** @scrutinizer ignore-unhandled */ @error_log('[LearnpathMetaExport] ERROR mkdir failed: '.$dir);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
168
        }
169
    }
170
171
    /** Write pretty JSON with utf8/slashes preserved. */
172
    private function writeJson(string $file, array $data): void
173
    {
174
        $json = json_encode(
175
            $data,
176
            JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
177
        );
178
        if (false === @file_put_contents($file, (string) $json)) {
179
            @error_log('[LearnpathMetaExport] ERROR writing file: '.$file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for error_log(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

179
            /** @scrutinizer ignore-unhandled */ @error_log('[LearnpathMetaExport] ERROR writing file: '.$file);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
180
        }
181
    }
182
183
    /** Unwrap builder wrappers (->obj) into the raw stdClass payload. */
184
    private function unwrapIfObject($wrap)
185
    {
186
        if (\is_object($wrap) && isset($wrap->obj) && \is_object($wrap->obj)) {
187
            return $wrap->obj;
188
        }
189
        return $wrap;
190
    }
191
192
    /** Deep convert stdClass/objects to arrays. */
193
    private function toArray($value)
194
    {
195
        if (\is_array($value)) {
196
            return array_map([$this, 'toArray'], $value);
197
        }
198
        if (\is_object($value)) {
199
            return array_map([$this, 'toArray'], get_object_vars($value));
200
        }
201
        return $value;
202
    }
203
}
204