Passed
Push — master ( 24a37f...f52bbb )
by
unknown
24:30 queued 12:07
created

FeedbackExport::normalizeOptions()   D

Complexity

Conditions 19
Paths 14

Size

Total Lines 35
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 19
eloc 22
c 0
b 0
f 0
nc 14
nop 2
dl 0
loc 35
rs 4.5166

How to fix   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 Chamilo\CourseBundle\Component\CourseCopy\Moodle\Builder\MoodleExport;
10
11
use const ENT_QUOTES;
12
use const ENT_XML1;
13
use const PHP_EOL;
14
15
/**
16
 * Exports Chamilo surveys into a single Moodle "feedback" activity.
17
 *
18
 * NOTE:
19
 * - Relies on $this->course structure injected via ActivityExport constructor.
20
 * - Expects a helper providing admin user data. Replace MoodleExport::getAdminUserData()
21
 *   with your actual provider or adapter if it lives elsewhere.
22
 */
23
class FeedbackExport extends ActivityExport
24
{
25
    /**
26
     * Export a survey to Moodle Feedback format.
27
     *
28
     * @param int    $activityId survey ID in Chamilo
29
     * @param string $exportDir  destination root for the export (course temp dir)
30
     * @param int    $moduleId   module ID for Moodle backup structure
31
     * @param int    $sectionId  target section ID within the course
32
     */
33
    public function export($activityId, $exportDir, $moduleId, $sectionId): void
34
    {
35
        // Prepare the activity folder: .../activities/feedback_<moduleId>
36
        $feedbackDir = $this->prepareActivityDirectory($exportDir, 'feedback', (int) $moduleId);
37
38
        // Gather normalized survey data from Chamilo resources
39
        $surveyData = $this->getData((int) $activityId, (int) $sectionId);
40
41
        // Produce all required XML artifacts for Moodle backup
42
        $this->createFeedbackXml($surveyData, $feedbackDir);
43
        $this->createModuleXml($surveyData, $feedbackDir);
44
        $this->createInforefXml($surveyData, $feedbackDir);
45
        $this->createCalendarXml($surveyData, $feedbackDir);
46
        $this->createCommentsXml($surveyData, $feedbackDir);
47
        $this->createCompetenciesXml($surveyData, $feedbackDir);
48
        $this->createCompletionXml($surveyData, $feedbackDir);
49
        $this->createFiltersXml($surveyData, $feedbackDir);
50
        $this->createGradeHistoryXml($surveyData, $feedbackDir);
51
        $this->createGradesXml($surveyData, $feedbackDir);
52
        $this->createRolesXml($surveyData, $feedbackDir);
53
    }
54
55
    /**
56
     * Collect survey data, including questions and options, from Chamilo.
57
     */
58
    public function getData(int $surveyId, int $sectionId): array
59
    {
60
        $adminData = MoodleExport::getAdminUserData();
61
        $adminId = (int) $adminData['id'];
62
63
        $survey = $this->course->resources['survey'][$surveyId] ?? null;
64
65
        $questions = [];
66
        foreach ($this->course->resources['survey_question'] ?? [] as $q) {
67
            if ((int) ($q->survey_id ?? $q->obj->survey_id ?? 0) !== $surveyId) {
68
                continue;
69
            }
70
71
            $qo = (isset($q->obj) && is_object($q->obj)) ? $q->obj : $q;
72
73
            $rawType = (string)($qo->type ?? $qo->survey_question_type ?? '');
74
            $normalizedType = $this->normalizeChamiloType($rawType);
75
76
            $options = $this->normalizeOptions($qo, $normalizedType);
77
78
            $text = (string)($qo->survey_question ?? $q->survey_question ?? 'Question');
79
            $text = trim($text);
80
81
            $questions[] = [
82
                'id'       => (int) ($qo->id ?? $q->id ?? 0),
83
                'text'     => $text,
84
                'type'     => $normalizedType,
85
                'options'  => $options,
86
                'position' => (int) ($qo->sort ?? $q->sort ?? 0),
87
                'label'    => '',
88
            ];
89
        }
90
91
        return [
92
            'id'            => $surveyId,
93
            'moduleid'      => $surveyId,
94
            'modulename'    => 'feedback',
95
            'type'          => 'mod',
96
            'contextid'     => (int) $this->course->info['real_id'],
97
            'sectionid'     => $sectionId,
98
            'sectionnumber' => 0,
99
            'name'          => (string) ($survey->title ?? ('Survey '.$surveyId)),
100
            'intro'         => (string) ($survey->intro ?? ''),
101
            'timemodified'  => time(),
102
            'questions'     => $questions,
103
            'users'         => [$adminId],
104
            'files'         => [],
105
        ];
106
    }
107
108
    /** Converts types used multiple times in Chamilo to the 6–7 we export to Moodle. */
109
    private function normalizeChamiloType(string $t): string
110
    {
111
        $t = strtolower(trim($t));
112
        return match ($t) {
113
            'yes_no', 'yesno'                         => 'yesno',
114
            'multiple_single', 'single', 'radio',
115
            'multiplechoice'                          => 'multiplechoice',
116
            'multiple_multiple', 'multiple', 'checks',
117
            'multipleresponse'                        => 'multipleresponse',
118
            'multiple_dropdown', 'dropdown', 'select' => 'dropdown',
119
            'multiplechoiceother'                     => 'multiplechoiceother',
120
            'open_short', 'short', 'textfield'        => 'textfield',
121
            'open_long', 'open', 'textarea', 'comment'=> 'open',
122
            'pagebreak'                               => 'pagebreak',
123
            'numeric', 'number', 'score'              => 'numeric',
124
            'percentage'                              => 'percentage',
125
            default                                   => 'open',
126
        };
127
    }
128
129
    /** Extract options without breaking if the shape changes (array of objects, string “A|B”, etc.) */
130
    private function normalizeOptions(object $qo, string $normalizedType): array
131
    {
132
        if ($normalizedType === 'yesno') {
133
            if (!empty($qo->answers)) {
134
                $out = [];
135
                foreach ($qo->answers as $a) {
136
                    $txt = is_array($a) ? ($a['option_text'] ?? '') : (is_object($a) ? ($a->option_text ?? '') : (string)$a);
137
                    $txt = trim((string)$txt);
138
                    if ($txt !== '') { $out[] = $txt; }
139
                }
140
                if ($out) { return $out; }
141
            }
142
            return ['Yes','No'];
143
        }
144
145
        if (!empty($qo->answers) && is_iterable($qo->answers)) {
146
            $out = [];
147
            foreach ($qo->answers as $a) {
148
                $txt = is_array($a) ? ($a['option_text'] ?? '') : (is_object($a) ? ($a->option_text ?? '') : (string)$a);
149
                $txt = trim((string)$txt);
150
                if ($txt !== '') { $out[] = $txt; }
151
            }
152
            if ($out) { return $out; }
153
        }
154
155
        if (!empty($qo->options) && is_string($qo->options)) {
156
            $out = array_values(array_filter(array_map('trim', explode('|', $qo->options)), 'strlen'));
157
            if ($out) { return $out; }
0 ignored issues
show
Bug Best Practice introduced by
The expression $out 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...
158
        }
159
160
        if ($normalizedType === 'percentage') {
161
            return range(1, 100);
162
        }
163
164
        return [];
165
    }
166
167
    /**
168
     * Build feedback.xml (the core activity file for Moodle backup).
169
     */
170
    private function createFeedbackXml(array $surveyData, string $feedbackDir): void
171
    {
172
        $xml = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
173
        $xml .= '<activity id="'.$surveyData['id'].'" moduleid="'.$surveyData['moduleid'].'" modulename="feedback" contextid="'.$surveyData['contextid'].'">'.PHP_EOL;
174
        $xml .= '  <feedback id="'.$surveyData['id'].'">'.PHP_EOL;
175
        $xml .= '    <name>'.htmlspecialchars((string) $surveyData['name']).'</name>'.PHP_EOL;
176
        $xml .= '    <intro>'.htmlspecialchars((string) $surveyData['intro']).'</intro>'.PHP_EOL;
177
        $xml .= '    <introformat>1</introformat>'.PHP_EOL;
178
        $xml .= '    <anonymous>1</anonymous>'.PHP_EOL;
179
        $xml .= '    <email_notification>0</email_notification>'.PHP_EOL;
180
        $xml .= '    <multiple_submit>0</multiple_submit>'.PHP_EOL;
181
        $xml .= '    <autonumbering>1</autonumbering>'.PHP_EOL;
182
        $xml .= '    <site_after_submit></site_after_submit>'.PHP_EOL;
183
        $xml .= '    <page_after_submit></page_after_submit>'.PHP_EOL;
184
        $xml .= '    <page_after_submitformat>1</page_after_submitformat>'.PHP_EOL;
185
        $xml .= '    <publish_stats>0</publish_stats>'.PHP_EOL;
186
        $xml .= '    <timeopen>0</timeopen>'.PHP_EOL;
187
        $xml .= '    <timeclose>0</timeclose>'.PHP_EOL;
188
        $xml .= '    <timemodified>'.$surveyData['timemodified'].'</timemodified>'.PHP_EOL;
189
        $xml .= '    <completionsubmit>0</completionsubmit>'.PHP_EOL;
190
        $xml .= '    <items>'.PHP_EOL;
191
192
        foreach ($surveyData['questions'] as $q) {
193
            $xml .= $this->createQuestionXml($q);
194
        }
195
196
        $xml .= '    </items>'.PHP_EOL;
197
        $xml .= '  </feedback>'.PHP_EOL;
198
        $xml .= '</activity>';
199
200
        $this->createXmlFile('feedback', $xml, $feedbackDir);
201
    }
202
203
    private function createQuestionXml(array $q): string
204
    {
205
        $name  = htmlspecialchars(strip_tags((string) ($q['text'] ?? '')), ENT_XML1 | ENT_QUOTES, 'UTF-8');
206
        $label = htmlspecialchars(strip_tags((string) ($q['label'] ?? '')), ENT_XML1 | ENT_QUOTES, 'UTF-8');
207
208
        $typ  = $this->mapQuestionType((string) ($q['type'] ?? ''));
209
        $pres = $this->getPresentation($q);
210
211
        $hasValue = in_array($typ, ['multichoice','textarea','textfield','numeric'], true) ? '1' : '0';
212
213
        $pos = (int) ($q['position'] ?? 0);
214
        $id  = (int) ($q['id'] ?? 0);
215
216
        $xml  = '      <item id="'.$id.'">'.PHP_EOL;
217
        $xml .= '        <template>0</template>'.PHP_EOL;
218
        $xml .= '        <name>'.$name.'</name>'.PHP_EOL;
219
        $xml .= '        <label>'.$label.'</label>'.PHP_EOL;
220
        $xml .= '        <presentation>'.$pres.'</presentation>'.PHP_EOL;
221
        $xml .= '        <typ>'.$typ.'</typ>'.PHP_EOL;
222
        $xml .= '        <hasvalue>'.$hasValue.'</hasvalue>'.PHP_EOL;
223
        $xml .= '        <position>'.$pos.'</position>'.PHP_EOL;
224
        $xml .= '        <required>0</required>'.PHP_EOL;
225
        $xml .= '        <dependitem>0</dependitem>'.PHP_EOL;
226
        $xml .= '        <dependvalue></dependvalue>'.PHP_EOL;
227
        $xml .= '        <options>h</options>'.PHP_EOL;
228
        $xml .= '      </item>'.PHP_EOL;
229
230
        return $xml;
231
    }
232
233
    private function getPresentation(array $q): string
234
    {
235
        $type = (string)($q['type'] ?? '');
236
        $opts = array_map(static fn($o) => htmlspecialchars((string)trim($o), ENT_XML1 | ENT_QUOTES, 'UTF-8'), (array)($q['options'] ?? []));
237
        $joined = implode('|', $opts);
238
239
        return match ($type) {
240
            'yesno', 'multiplechoice'                 => 'r&gt;&gt;&gt;&gt;&gt;'.$joined,
241
            'multiplechoiceother'                     => 'r&gt;&gt;&gt;&gt;&gt;'.$joined,
242
            'multipleresponse'                        => 'c&gt;&gt;&gt;&gt;&gt;'.$joined,
243
            'dropdown'                                => 'd&gt;&gt;&gt;&gt;&gt;'.$joined,
244
            'percentage'                              => 'r&gt;&gt;&gt;&gt;&gt;'.$joined,
245
            'textfield'                               => '30',
246
            'open'                                    => '30|5',
247
            'numeric'                                 => '',
248
            default                                   => '',
249
        };
250
    }
251
252
    private function mapQuestionType(string $chType): string
253
    {
254
        return match ($chType) {
255
            'yesno', 'multiplechoice', 'multiplechoiceother', 'percentage' => 'multichoice',
256
            'multipleresponse'                                             => 'multichoice',
257
            'dropdown'                                                     => 'multichoice',
258
            'textfield'                                                    => 'textfield',
259
            'open', 'comment'                                              => 'textarea',
260
            'numeric', 'score'                                             => 'numeric',
261
            'pagebreak'                                                    => 'pagebreak',
262
            'label'                                                        => 'label',
263
            default                                                        => 'textarea',
264
        };
265
    }
266
}
267