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
|
|
|
// TODO: Replace this with your own provider if different: |
61
|
|
|
// e.g. $adminId = $this->adminProvider->getAdminUserId(); |
62
|
|
|
$adminData = MoodleExport::getAdminUserData(); |
63
|
|
|
$adminId = (int) $adminData['id']; |
64
|
|
|
|
65
|
|
|
$survey = $this->course->resources['survey'][$surveyId] ?? null; |
66
|
|
|
|
67
|
|
|
$questions = []; |
68
|
|
|
foreach ($this->course->resources['survey_question'] ?? [] as $question) { |
69
|
|
|
if ((int) ($question->survey_id ?? 0) === $surveyId) { |
70
|
|
|
$questions[] = [ |
71
|
|
|
'id' => (int) $question->id, |
72
|
|
|
'text' => (string) $question->survey_question, |
73
|
|
|
'type' => (string) $question->survey_question_type, |
74
|
|
|
'options' => array_map( |
75
|
|
|
static fn ($answer) => $answer['option_text'], |
76
|
|
|
(array) ($question->answers ?? []) |
77
|
|
|
), |
78
|
|
|
'position' => (int) ($question->sort ?? 0), |
79
|
|
|
'label' => '', // Keep empty unless you map labels explicitly |
80
|
|
|
]; |
81
|
|
|
} |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
return [ |
85
|
|
|
'id' => $surveyId, |
86
|
|
|
'moduleid' => $surveyId, |
87
|
|
|
'modulename' => 'feedback', |
88
|
|
|
'contextid' => (int) $this->course->info['real_id'], |
89
|
|
|
'sectionid' => $sectionId, |
90
|
|
|
'sectionnumber' => 0, |
91
|
|
|
'name' => (string) ($survey->title ?? ('Survey '.$surveyId)), |
92
|
|
|
'intro' => (string) ($survey->intro ?? ''), |
93
|
|
|
'timemodified' => time(), |
94
|
|
|
'questions' => $questions, |
95
|
|
|
'users' => [$adminId], |
96
|
|
|
'files' => [], |
97
|
|
|
]; |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* Build feedback.xml (the core activity file for Moodle backup). |
102
|
|
|
*/ |
103
|
|
|
private function createFeedbackXml(array $surveyData, string $feedbackDir): void |
104
|
|
|
{ |
105
|
|
|
$xml = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL; |
106
|
|
|
$xml .= '<activity id="'.$surveyData['id'].'" moduleid="'.$surveyData['moduleid'].'" modulename="feedback" contextid="'.$surveyData['contextid'].'">'.PHP_EOL; |
107
|
|
|
$xml .= ' <feedback id="'.$surveyData['id'].'">'.PHP_EOL; |
108
|
|
|
$xml .= ' <name>'.htmlspecialchars((string) $surveyData['name']).'</name>'.PHP_EOL; |
109
|
|
|
$xml .= ' <intro>'.htmlspecialchars((string) $surveyData['intro']).'</intro>'.PHP_EOL; |
110
|
|
|
$xml .= ' <introformat>1</introformat>'.PHP_EOL; |
111
|
|
|
$xml .= ' <anonymous>1</anonymous>'.PHP_EOL; |
112
|
|
|
$xml .= ' <email_notification>0</email_notification>'.PHP_EOL; |
113
|
|
|
$xml .= ' <multiple_submit>0</multiple_submit>'.PHP_EOL; |
114
|
|
|
$xml .= ' <autonumbering>1</autonumbering>'.PHP_EOL; |
115
|
|
|
$xml .= ' <site_after_submit></site_after_submit>'.PHP_EOL; |
116
|
|
|
$xml .= ' <page_after_submit></page_after_submit>'.PHP_EOL; |
117
|
|
|
$xml .= ' <page_after_submitformat>1</page_after_submitformat>'.PHP_EOL; |
118
|
|
|
$xml .= ' <publish_stats>0</publish_stats>'.PHP_EOL; |
119
|
|
|
$xml .= ' <timeopen>0</timeopen>'.PHP_EOL; |
120
|
|
|
$xml .= ' <timeclose>0</timeclose>'.PHP_EOL; |
121
|
|
|
$xml .= ' <timemodified>'.$surveyData['timemodified'].'</timemodified>'.PHP_EOL; |
122
|
|
|
$xml .= ' <completionsubmit>0</completionsubmit>'.PHP_EOL; |
123
|
|
|
$xml .= ' <items>'.PHP_EOL; |
124
|
|
|
|
125
|
|
|
foreach ($surveyData['questions'] as $q) { |
126
|
|
|
$xml .= $this->createQuestionXml($q); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
$xml .= ' </items>'.PHP_EOL; |
130
|
|
|
$xml .= ' </feedback>'.PHP_EOL; |
131
|
|
|
$xml .= '</activity>'; |
132
|
|
|
|
133
|
|
|
$this->createXmlFile('feedback', $xml, $feedbackDir); |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* Render a single question in Moodle Feedback XML format. |
138
|
|
|
*/ |
139
|
|
|
private function createQuestionXml(array $question): string |
140
|
|
|
{ |
141
|
|
|
$name = htmlspecialchars(strip_tags((string) ($question['text'] ?? '')), ENT_XML1 | ENT_QUOTES, 'UTF-8'); |
142
|
|
|
$label = htmlspecialchars(strip_tags((string) ($question['label'] ?? '')), ENT_XML1 | ENT_QUOTES, 'UTF-8'); |
143
|
|
|
$presentation = $this->getPresentation($question); |
144
|
|
|
$hasValue = (($question['type'] ?? '') === 'pagebreak') ? '0' : '1'; |
145
|
|
|
$pos = (int) ($question['position'] ?? 0); |
146
|
|
|
$id = (int) ($question['id'] ?? 0); |
147
|
|
|
$typ = $this->mapQuestionType((string) ($question['type'] ?? '')); |
148
|
|
|
|
149
|
|
|
$xml = ' <item id="'.$id.'">'.PHP_EOL; |
150
|
|
|
$xml .= ' <template>0</template>'.PHP_EOL; |
151
|
|
|
$xml .= ' <name>'.$name.'</name>'.PHP_EOL; |
152
|
|
|
$xml .= ' <label>'.$label.'</label>'.PHP_EOL; |
153
|
|
|
$xml .= ' <presentation>'.$presentation.'</presentation>'.PHP_EOL; |
154
|
|
|
$xml .= ' <typ>'.$typ.'</typ>'.PHP_EOL; |
155
|
|
|
$xml .= ' <hasvalue>'.$hasValue.'</hasvalue>'.PHP_EOL; |
156
|
|
|
$xml .= ' <position>'.$pos.'</position>'.PHP_EOL; |
157
|
|
|
$xml .= ' <required>0</required>'.PHP_EOL; |
158
|
|
|
$xml .= ' <dependitem>0</dependitem>'.PHP_EOL; |
159
|
|
|
$xml .= ' <dependvalue></dependvalue>'.PHP_EOL; |
160
|
|
|
$xml .= ' <options>h</options>'.PHP_EOL; |
161
|
|
|
$xml .= ' </item>'.PHP_EOL; |
162
|
|
|
|
163
|
|
|
return $xml; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* Encode presentation string depending on question type. |
168
|
|
|
*/ |
169
|
|
|
private function getPresentation(array $question): string |
170
|
|
|
{ |
171
|
|
|
$type = (string) ($question['type'] ?? ''); |
172
|
|
|
$opts = array_map('strip_tags', (array) ($question['options'] ?? [])); |
173
|
|
|
$opts = array_map( |
174
|
|
|
static fn ($o) => htmlspecialchars((string) $o, ENT_XML1 | ENT_QUOTES, 'UTF-8'), |
175
|
|
|
$opts |
176
|
|
|
); |
177
|
|
|
|
178
|
|
|
// Moodle feedback encodes the widget type as a single char: |
179
|
|
|
// r = radio, c = checkbox, d = dropdown, textareas use "<cols>|<rows>" |
180
|
|
|
return match ($type) { |
181
|
|
|
'yesno', 'multiplechoice', 'multiplechoiceother' => 'r>>>>>'.implode(PHP_EOL.'|', $opts), |
182
|
|
|
'multipleresponse' => 'c>>>>>'.implode(PHP_EOL.'|', $opts), |
183
|
|
|
'dropdown' => 'd>>>>>'.implode(PHP_EOL.'|', $opts), |
184
|
|
|
'open' => '30|5', // textarea: cols|rows |
185
|
|
|
default => '', |
186
|
|
|
}; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Map Chamilo survey question types to Moodle feedback types. |
191
|
|
|
*/ |
192
|
|
|
private function mapQuestionType(string $chamiloType): string |
193
|
|
|
{ |
194
|
|
|
return [ |
195
|
|
|
'yesno' => 'multichoice', |
196
|
|
|
'multiplechoice' => 'multichoice', |
197
|
|
|
'multipleresponse' => 'multichoice', |
198
|
|
|
'dropdown' => 'multichoice', |
199
|
|
|
'multiplechoiceother' => 'multichoice', |
200
|
|
|
'open' => 'textarea', |
201
|
|
|
'pagebreak' => 'pagebreak', |
202
|
|
|
][$chamiloType] ?? 'unknown'; |
203
|
|
|
} |
204
|
|
|
} |
205
|
|
|
|