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

LabelExport::normalizeContent()   B

Complexity

Conditions 6
Paths 2

Size

Total Lines 66
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 6
eloc 43
c 1
b 0
f 1
nc 2
nop 1
dl 0
loc 66
rs 8.6097

How to fix   Long Method   

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
declare(strict_types=1);
3
4
/* For licensing terms, see /license.txt */
5
6
namespace Chamilo\CourseBundle\Component\CourseCopy\Moodle\Activities;
7
8
use Chamilo\CourseBundle\Component\CourseCopy\Moodle\Builder\FileExport;
9
use Chamilo\CourseBundle\Component\CourseCopy\Moodle\Builder\MoodleExport;
10
use DocumentManager;
11
12
use const ENT_QUOTES;
13
use const ENT_SUBSTITUTE;
14
use const PHP_EOL;
15
16
/**
17
 * LabelExport — exports legacy Course Descriptions as Moodle "label" activities.
18
 * - Writes activities/label_{moduleId}/{label.xml,module.xml,inforef.xml,...}
19
 * - Uses ActivityExport helpers for module.xml, inforef.xml, etc.
20
 */
21
class LabelExport extends ActivityExport
22
{
23
    /**
24
     * Export this label activity.
25
     *
26
     * @param int    $activityId  source_id of the course_description
27
     * @param string $exportDir   root temp export directory
28
     * @param int    $moduleId    module id used in directory name (usually = $activityId)
29
     * @param int    $sectionId   resolved section (LP) or 0 for General
30
     */
31
    public function export(int $activityId, string $exportDir, int $moduleId, int $sectionId): void
32
    {
33
        // Ensure activity folder
34
        $labelDir = $this->prepareActivityDirectory($exportDir, 'label', $moduleId);
35
36
        // Resolve payload
37
        $data = $this->getData($activityId, $sectionId);
38
        if (null === $data) {
39
            // Nothing to export
40
            return;
41
        }
42
43
        // Write primary XMLs
44
        $this->createLabelXml($data, $labelDir);
45
        $this->createModuleXml($data, $labelDir);
46
        $this->createInforefXml($data, $labelDir);
47
48
        // Optional, but keeps structure consistent with other exporters
49
        $this->createFiltersXml($data, $labelDir);
50
        $this->createGradesXml($data, $labelDir);
51
        $this->createGradeHistoryXml($data, $labelDir);
52
        $this->createCompletionXml($data, $labelDir);
53
        $this->createCommentsXml($data, $labelDir);
54
        $this->createCompetenciesXml($data, $labelDir);
55
        $this->createRolesXml($data, $labelDir);
56
        $this->createCalendarXml($data, $labelDir);
57
58
    }
59
60
    /**
61
     * Build label payload from legacy "course_description" bucket.
62
     */
63
    public function getData(int $labelId, int $sectionId): ?array
64
    {
65
        // Accept both constant and plain string, defensively
66
        $bag =
67
            $this->course->resources[\defined('RESOURCE_COURSEDESCRIPTION') ? RESOURCE_COURSEDESCRIPTION : 'course_description']
68
            ?? $this->course->resources['course_description']
69
            ?? [];
70
71
        if (empty($bag) || !\is_array($bag)) {
72
            return null;
73
        }
74
75
        $wrap = $bag[$labelId] ?? null;
76
        if (!$wrap || !\is_object($wrap)) {
77
            return null;
78
        }
79
80
        // Unwrap ->obj if present
81
        $desc = (isset($wrap->obj) && \is_object($wrap->obj)) ? $wrap->obj : $wrap;
82
83
        $title = $this->resolveTitle($desc);
84
        $introRaw = (string) ($desc->content ?? '');
85
        $intro    = $this->normalizeContent($introRaw);
86
87
        // Collect files referenced by intro (so inforef can point to them)
88
        $files = $this->collectIntroFiles($introRaw, (string) ($this->course->code ?? ''));
89
90
        // Build the minimal dataset required by ActivityExport::createModuleXml()
91
        return [
92
            'id'            => (int) ($desc->source_id ?? $labelId),
93
            'moduleid'      => (int) ($desc->source_id ?? $labelId),
94
            'modulename'    => 'label',
95
            'sectionid'     => $sectionId,
96
            // Use section number = section id; falls back to 0 (General) if not in LP
97
            'sectionnumber' => $sectionId,
98
            'name'          => $title,
99
            'intro'         => $intro,
100
            'introformat'   => 1,
101
            'timemodified'  => time(),
102
            'users'         => [],
103
            'files'         => $files,
104
        ];
105
    }
106
107
    /**
108
     * Title resolver with fallback by description_type.
109
     */
110
    private function resolveTitle(object $desc): string
111
    {
112
        $t = trim((string) ($desc->title ?? ''));
113
        if ('' !== $t) {
114
            return $t;
115
        }
116
        $map = [1 => 'Descripción', 2 => 'Objetivos', 3 => 'Temas'];
117
        return $map[(int) ($desc->description_type ?? 0)] ?? 'Descripción';
118
    }
119
120
    /**
121
     * Normalize HTML: rewrite /document/... to @@PLUGINFILE@@/<file>, including srcset, style url(...), etc.
122
     */
123
    private function normalizeContent(string $html): string
124
    {
125
        if ('' === $html) {
126
            return $html;
127
        }
128
129
        // Handle srcset
130
        $html = (string) preg_replace_callback(
131
            '~\bsrcset\s*=\s*([\'"])(.*?)\1~is',
132
            function (array $m): string {
133
                $q = $m[1]; $val = $m[2];
134
                $parts = array_map('trim', explode(',', $val));
135
                foreach ($parts as &$p) {
136
                    if ($p === '') { continue; }
137
                    $tokens = preg_split('/\s+/', $p, -1, PREG_SPLIT_NO_EMPTY);
138
                    if (!$tokens) { continue; }
139
                    $url = $tokens[0];
140
                    $new = $this->rewriteDocUrl($url);
141
                    if ($new !== $url) {
142
                        $tokens[0] = $new;
143
                        $p = implode(' ', $tokens);
144
                    }
145
                }
146
                return 'srcset='.$q.implode(', ', $parts).$q;
147
            },
148
            $html
149
        );
150
151
        // Generic attributes
152
        $html = (string) preg_replace_callback(
153
            '~\b(src|href|poster|data)\s*=\s*([\'"])([^\'"]+)\2~i',
154
            fn(array $m) => $m[1].'='.$m[2].$this->rewriteDocUrl($m[3]).$m[2],
155
            $html
156
        );
157
158
        // Inline CSS
159
        $html = (string) preg_replace_callback(
160
            '~\bstyle\s*=\s*([\'"])(.*?)\1~is',
161
            function (array $m): string {
162
                $q = $m[1]; $style = $m[2];
163
                $style = (string) preg_replace_callback(
164
                    '~url\((["\']?)([^)\'"]+)\1\)~i',
165
                    fn(array $mm) => 'url('.$mm[1].$this->rewriteDocUrl($mm[2]).$mm[1].')',
166
                    $style
167
                );
168
                return 'style='.$q.$style.$q;
169
            },
170
            $html
171
        );
172
173
        // <style> blocks
174
        $html = (string) preg_replace_callback(
175
            '~(<style\b[^>]*>)(.*?)(</style>)~is',
176
            function (array $m): string {
177
                $open = $m[1]; $css = $m[2]; $close = $m[3];
178
                $css = (string) preg_replace_callback(
179
                    '~url\((["\']?)([^)\'"]+)\1\)~i',
180
                    fn(array $mm) => 'url('.$mm[1].$this->rewriteDocUrl($mm[2]).$mm[1].')',
181
                    $css
182
                );
183
                return $open.$css.$close;
184
            },
185
            $html
186
        );
187
188
        return $html;
189
    }
190
191
    /**
192
     * Rewrite /document/... (or /courses/<code>/document/...) to @@PLUGINFILE@@/<basename>.
193
     */
194
    private function rewriteDocUrl(string $url): string
195
    {
196
        if ($url === '' || str_contains($url, '@@PLUGINFILE@@')) {
197
            return $url;
198
        }
199
        if (preg_match('#/(?:courses/[^/]+/)?document(/[^?\'" )]+)#i', $url, $m)) {
200
            return '@@PLUGINFILE@@/'.basename($m[1]);
201
        }
202
        return $url;
203
    }
204
205
    /**
206
     * Collect referenced intro files for files.xml (component=mod_label, filearea=intro).
207
     *
208
     * @return array<int,array<string,mixed>>
209
     */
210
    private function collectIntroFiles(string $introHtml, string $courseCode): array
211
    {
212
        if ($introHtml === '') {
213
            return [];
214
        }
215
216
        $files = [];
217
        $contextid = (int) ($this->course->info['real_id'] ?? 0);
218
        $adminId   = MoodleExport::getAdminUserData()['id'] ?? ($this->getAdminUserData()['id'] ?? 0);
219
220
        $resources = DocumentManager::get_resources_from_source_html($introHtml);
221
        $courseInfo = api_get_course_info($courseCode);
222
223
        foreach ($resources as [$src]) {
224
            if (preg_match('#/document(/[^"\']+)#', $src, $matches)) {
225
                $path = $matches[1];
226
                $docId = DocumentManager::get_document_id($courseInfo, $path);
227
                if (!$docId) {
228
                    continue;
229
                }
230
                $document = DocumentManager::get_document_data_by_id($docId, $courseCode);
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

230
                $document = /** @scrutinizer ignore-deprecated */ DocumentManager::get_document_data_by_id($docId, $courseCode);

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...
231
                if (!$document) {
232
                    continue;
233
                }
234
235
                $contenthash = hash('sha1', basename($document['path']));
236
                $mimetype = (new FileExport($this->course))->getMimeType($document['path']);
237
238
                $files[] = [
239
                    'id'          => (int) $document['id'],
240
                    'contenthash' => $contenthash,
241
                    'contextid'   => $contextid,
242
                    'component'   => 'mod_label',
243
                    'filearea'    => 'intro',
244
                    'itemid'      => 0,
245
                    'filepath'    => '/',
246
                    'documentpath'=> 'document'.$document['path'],
247
                    'filename'    => basename($document['path']),
248
                    'userid'      => $adminId,
249
                    'filesize'    => (int) $document['size'],
250
                    'mimetype'    => $mimetype,
251
                    'status'      => 0,
252
                    'timecreated' => time() - 3600,
253
                    'timemodified'=> time(),
254
                    'source'      => (string) $document['title'],
255
                    'author'      => 'Unknown',
256
                    'license'     => 'allrightsreserved',
257
                ];
258
            }
259
        }
260
261
        return $files;
262
    }
263
264
    /**
265
     * Write label.xml for the activity.
266
     */
267
    private function createLabelXml(array $data, string $dir): void
268
    {
269
        $xml  = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
270
        $xml .= '<activity id="'.(int) $data['id'].'" moduleid="'.(int) $data['moduleid'].'" modulename="label" contextid="'.(int) ($this->course->info['real_id'] ?? 0).'">'.PHP_EOL;
271
        $xml .= '  <label id="'.(int) $data['id'].'">'.PHP_EOL;
272
        $xml .= '    <name>'.htmlspecialchars((string) $data['name'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8').'</name>'.PHP_EOL;
273
        $xml .= '    <intro><![CDATA['.$data['intro'].']]></intro>'.PHP_EOL;
274
        $xml .= '    <introformat>'.(int) ($data['introformat'] ?? 1).'</introformat>'.PHP_EOL;
275
        $xml .= '    <timemodified>'.(int) $data['timemodified'].'</timemodified>'.PHP_EOL;
276
        $xml .= '  </label>'.PHP_EOL;
277
        $xml .= '</activity>';
278
279
        $this->createXmlFile('label', $xml, $dir);
280
    }
281
}
282