Actualización
This commit is contained in:
254
main/inc/lib/moodleexport/ActivityExport.php
Normal file
254
main/inc/lib/moodleexport/ActivityExport.php
Normal file
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace moodleexport;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class ActivityExport.
|
||||
*
|
||||
* Base class for exporting common activities.
|
||||
*/
|
||||
abstract class ActivityExport
|
||||
{
|
||||
protected $course;
|
||||
|
||||
public function __construct($course)
|
||||
{
|
||||
$this->course = $course;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract method for exporting the activity.
|
||||
* Must be implemented by child classes.
|
||||
*/
|
||||
abstract public function export($activityId, $exportDir, $moduleId, $sectionId);
|
||||
|
||||
/**
|
||||
* Get the section ID for a given activity ID.
|
||||
*/
|
||||
public function getSectionIdForActivity(int $activityId, string $itemType): int
|
||||
{
|
||||
foreach ($this->course->resources[RESOURCE_LEARNPATH] as $learnpath) {
|
||||
foreach ($learnpath->items as $item) {
|
||||
if ($item['item_type'] == $itemType && $item['path'] == $activityId) {
|
||||
return $learnpath->source_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the directory for the activity.
|
||||
*/
|
||||
protected function prepareActivityDirectory(string $exportDir, string $activityType, int $moduleId): string
|
||||
{
|
||||
$activityDir = "{$exportDir}/activities/{$activityType}_{$moduleId}";
|
||||
if (!is_dir($activityDir)) {
|
||||
mkdir($activityDir, 0777, true);
|
||||
}
|
||||
|
||||
return $activityDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a generic XML file.
|
||||
*/
|
||||
protected function createXmlFile(string $fileName, string $xmlContent, string $directory): void
|
||||
{
|
||||
$filePath = $directory.'/'.$fileName.'.xml';
|
||||
if (file_put_contents($filePath, $xmlContent) === false) {
|
||||
throw new Exception("Error creating {$fileName}.xml");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the module.xml file.
|
||||
*/
|
||||
protected function createModuleXml(array $data, string $directory): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<module id="'.$data['moduleid'].'" version="2021051700">'.PHP_EOL;
|
||||
$xmlContent .= ' <modulename>'.$data['modulename'].'</modulename>'.PHP_EOL;
|
||||
$xmlContent .= ' <sectionid>'.$data['sectionid'].'</sectionid>'.PHP_EOL;
|
||||
$xmlContent .= ' <sectionnumber>'.$data['sectionnumber'].'</sectionnumber>'.PHP_EOL;
|
||||
$xmlContent .= ' <idnumber></idnumber>'.PHP_EOL;
|
||||
$xmlContent .= ' <added>'.time().'</added>'.PHP_EOL;
|
||||
$xmlContent .= ' <score>0</score>'.PHP_EOL;
|
||||
$xmlContent .= ' <indent>0</indent>'.PHP_EOL;
|
||||
$xmlContent .= ' <visible>1</visible>'.PHP_EOL;
|
||||
$xmlContent .= ' <visibleoncoursepage>1</visibleoncoursepage>'.PHP_EOL;
|
||||
$xmlContent .= ' <visibleold>1</visibleold>'.PHP_EOL;
|
||||
$xmlContent .= ' <groupmode>0</groupmode>'.PHP_EOL;
|
||||
$xmlContent .= ' <groupingid>0</groupingid>'.PHP_EOL;
|
||||
$xmlContent .= ' <completion>1</completion>'.PHP_EOL;
|
||||
$xmlContent .= ' <completiongradeitemnumber>$@NULL@$</completiongradeitemnumber>'.PHP_EOL;
|
||||
$xmlContent .= ' <completionview>0</completionview>'.PHP_EOL;
|
||||
$xmlContent .= ' <completionexpected>0</completionexpected>'.PHP_EOL;
|
||||
$xmlContent .= ' <availability>$@NULL@$</availability>'.PHP_EOL;
|
||||
$xmlContent .= ' <showdescription>0</showdescription>'.PHP_EOL;
|
||||
$xmlContent .= ' <tags></tags>'.PHP_EOL;
|
||||
$xmlContent .= '</module>'.PHP_EOL;
|
||||
|
||||
$this->createXmlFile('module', $xmlContent, $directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the grades.xml file.
|
||||
*/
|
||||
protected function createGradesXml(array $data, string $directory): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<activity_gradebook>'.PHP_EOL;
|
||||
$xmlContent .= ' <grade_items></grade_items>'.PHP_EOL;
|
||||
$xmlContent .= '</activity_gradebook>'.PHP_EOL;
|
||||
|
||||
$this->createXmlFile('grades', $xmlContent, $directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the inforef.xml file, referencing users and files associated with the activity.
|
||||
*/
|
||||
protected function createInforefXml(array $references, string $directory): void
|
||||
{
|
||||
// Start the XML content
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<inforef>'.PHP_EOL;
|
||||
|
||||
// Add user references if provided
|
||||
if (isset($references['users']) && is_array($references['users'])) {
|
||||
$xmlContent .= ' <userref>'.PHP_EOL;
|
||||
foreach ($references['users'] as $userId) {
|
||||
$xmlContent .= ' <user>'.PHP_EOL;
|
||||
$xmlContent .= ' <id>'.htmlspecialchars($userId).'</id>'.PHP_EOL;
|
||||
$xmlContent .= ' </user>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= ' </userref>'.PHP_EOL;
|
||||
}
|
||||
|
||||
// Add file references if provided
|
||||
if (isset($references['files']) && is_array($references['files'])) {
|
||||
$xmlContent .= ' <fileref>'.PHP_EOL;
|
||||
foreach ($references['files'] as $file) {
|
||||
$xmlContent .= ' <file>'.PHP_EOL;
|
||||
$xmlContent .= ' <id>'.htmlspecialchars($file['id']).'</id>'.PHP_EOL;
|
||||
$xmlContent .= ' </file>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= ' </fileref>'.PHP_EOL;
|
||||
}
|
||||
|
||||
$xmlContent .= '</inforef>'.PHP_EOL;
|
||||
|
||||
$this->createXmlFile('inforef', $xmlContent, $directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the roles.xml file.
|
||||
*/
|
||||
protected function createRolesXml(array $activityData, string $directory): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<roles></roles>'.PHP_EOL;
|
||||
|
||||
$this->createXmlFile('roles', $xmlContent, $directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the filters.xml file for the activity.
|
||||
*/
|
||||
protected function createFiltersXml(array $activityData, string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<filters>'.PHP_EOL;
|
||||
$xmlContent .= ' <filter_actives>'.PHP_EOL;
|
||||
$xmlContent .= ' </filter_actives>'.PHP_EOL;
|
||||
$xmlContent .= ' <filter_configs>'.PHP_EOL;
|
||||
$xmlContent .= ' </filter_configs>'.PHP_EOL;
|
||||
$xmlContent .= '</filters>'.PHP_EOL;
|
||||
|
||||
$this->createXmlFile('filters', $xmlContent, $destinationDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the grade_history.xml file for the activity.
|
||||
*/
|
||||
protected function createGradeHistoryXml(array $activityData, string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<grade_history>'.PHP_EOL;
|
||||
$xmlContent .= ' <grade_grades>'.PHP_EOL;
|
||||
$xmlContent .= ' </grade_grades>'.PHP_EOL;
|
||||
$xmlContent .= '</grade_history>'.PHP_EOL;
|
||||
|
||||
$this->createXmlFile('grade_history', $xmlContent, $destinationDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the completion.xml file.
|
||||
*/
|
||||
protected function createCompletionXml(array $activityData, string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<completion>'.PHP_EOL;
|
||||
$xmlContent .= ' <completiondata>'.PHP_EOL;
|
||||
$xmlContent .= ' <completion>'.PHP_EOL;
|
||||
$xmlContent .= ' <timecompleted>0</timecompleted>'.PHP_EOL;
|
||||
$xmlContent .= ' <completionstate>1</completionstate>'.PHP_EOL;
|
||||
$xmlContent .= ' </completion>'.PHP_EOL;
|
||||
$xmlContent .= ' </completiondata>'.PHP_EOL;
|
||||
$xmlContent .= '</completion>'.PHP_EOL;
|
||||
|
||||
$this->createXmlFile('completion', $xmlContent, $destinationDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the comments.xml file.
|
||||
*/
|
||||
protected function createCommentsXml(array $activityData, string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<comments>'.PHP_EOL;
|
||||
$xmlContent .= ' <comment>'.PHP_EOL;
|
||||
$xmlContent .= ' <content>This is a sample comment</content>'.PHP_EOL;
|
||||
$xmlContent .= ' <author>Professor</author>'.PHP_EOL;
|
||||
$xmlContent .= ' </comment>'.PHP_EOL;
|
||||
$xmlContent .= '</comments>'.PHP_EOL;
|
||||
|
||||
$this->createXmlFile('comments', $xmlContent, $destinationDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the competencies.xml file.
|
||||
*/
|
||||
protected function createCompetenciesXml(array $activityData, string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<competencies>'.PHP_EOL;
|
||||
$xmlContent .= ' <competency>'.PHP_EOL;
|
||||
$xmlContent .= ' <name>Sample Competency</name>'.PHP_EOL;
|
||||
$xmlContent .= ' </competency>'.PHP_EOL;
|
||||
$xmlContent .= '</competencies>'.PHP_EOL;
|
||||
|
||||
$this->createXmlFile('competencies', $xmlContent, $destinationDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the calendar.xml file.
|
||||
*/
|
||||
protected function createCalendarXml(array $activityData, string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<calendar>'.PHP_EOL;
|
||||
$xmlContent .= ' <event>'.PHP_EOL;
|
||||
$xmlContent .= ' <name>Due Date</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <timestart>'.time().'</timestart>'.PHP_EOL;
|
||||
$xmlContent .= ' </event>'.PHP_EOL;
|
||||
$xmlContent .= '</calendar>'.PHP_EOL;
|
||||
|
||||
$this->createXmlFile('calendar', $xmlContent, $destinationDir);
|
||||
}
|
||||
}
|
||||
196
main/inc/lib/moodleexport/AssignExport.php
Normal file
196
main/inc/lib/moodleexport/AssignExport.php
Normal file
@@ -0,0 +1,196 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace moodleexport;
|
||||
|
||||
use DocumentManager;
|
||||
|
||||
/**
|
||||
* Class AssignExport.
|
||||
*
|
||||
* Handles the export of assignments within a course.
|
||||
*/
|
||||
class AssignExport extends ActivityExport
|
||||
{
|
||||
/**
|
||||
* Export all assign data into a single Moodle assign activity.
|
||||
*
|
||||
* @param int $activityId The ID of the assign.
|
||||
* @param string $exportDir The directory where the assign will be exported.
|
||||
* @param int $moduleId The ID of the module.
|
||||
* @param int $sectionId The ID of the section.
|
||||
*/
|
||||
public function export($activityId, $exportDir, $moduleId, $sectionId): void
|
||||
{
|
||||
// Prepare the directory where the assign export will be saved
|
||||
$assignDir = $this->prepareActivityDirectory($exportDir, 'assign', $moduleId);
|
||||
|
||||
// Retrieve assign data
|
||||
$assignData = $this->getData($activityId, $sectionId);
|
||||
|
||||
// Generate XML files for the assign
|
||||
$this->createAssignXml($assignData, $assignDir);
|
||||
$this->createModuleXml($assignData, $assignDir);
|
||||
$this->createGradesXml($assignData, $assignDir);
|
||||
$this->createGradingXml($assignData, $assignDir);
|
||||
$this->createInforefXml($assignData, $assignDir);
|
||||
$this->createGradeHistoryXml($assignData, $assignDir);
|
||||
$this->createRolesXml($assignData, $assignDir);
|
||||
$this->createCommentsXml($assignData, $assignDir);
|
||||
$this->createCalendarXml($assignData, $assignDir);
|
||||
$this->createFiltersXml($assignData, $assignDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the data related to the assign activity.
|
||||
*/
|
||||
public function getData(int $assignId, int $sectionId): ?array
|
||||
{
|
||||
$work = $this->course->resources[RESOURCE_WORK][$assignId];
|
||||
|
||||
$workFiles = getAllDocumentToWork($assignId, $this->course->info['real_id']);
|
||||
$files = [];
|
||||
if (!empty($workFiles)) {
|
||||
foreach ($workFiles as $file) {
|
||||
$docData = DocumentManager::get_document_data_by_id($file['document_id'], $this->course->info['code']);
|
||||
if (!empty($docData)) {
|
||||
$files[] = [
|
||||
'id' => $file['document_id'],
|
||||
'contenthash' => hash('sha1', basename($docData['path'])),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$adminData = MoodleExport::getAdminUserData();
|
||||
$adminId = $adminData['id'];
|
||||
|
||||
return [
|
||||
'id' => (int) $work->params['id'],
|
||||
'moduleid' => (int) $work->params['id'],
|
||||
'modulename' => 'assign',
|
||||
'contextid' => $this->course->info['real_id'],
|
||||
'sectionid' => $sectionId,
|
||||
'sectionnumber' => 0,
|
||||
'name' => htmlspecialchars($work->params['title']),
|
||||
'intro' => $work->params['description'],
|
||||
'duedate' => strtotime($work->params['sent_date']),
|
||||
'gradingduedate' => strtotime($work->params['sent_date']) + 86400 * 7,
|
||||
'allowsubmissionsfromdate' => strtotime($work->params['sent_date']),
|
||||
'timemodified' => time(),
|
||||
'grade_item_id' => 0,
|
||||
'files' => $files,
|
||||
'users' => [$adminId],
|
||||
'area_id' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the grades.xml file for the assign activity.
|
||||
*/
|
||||
protected function createGradesXml(array $data, string $directory): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<activity_gradebook>'.PHP_EOL;
|
||||
$xmlContent .= ' <grade_items>'.PHP_EOL;
|
||||
$xmlContent .= ' <grade_item id="'.$data['grade_item_id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <categoryid>1</categoryid>'.PHP_EOL;
|
||||
$xmlContent .= ' <itemname>'.htmlspecialchars($data['name']).'</itemname>'.PHP_EOL;
|
||||
$xmlContent .= ' <itemtype>mod</itemtype>'.PHP_EOL;
|
||||
$xmlContent .= ' <itemmodule>'.$data['modulename'].'</itemmodule>'.PHP_EOL;
|
||||
$xmlContent .= ' <iteminstance>'.$data['id'].'</iteminstance>'.PHP_EOL;
|
||||
$xmlContent .= ' <itemnumber>0</itemnumber>'.PHP_EOL;
|
||||
$xmlContent .= ' <iteminfo>$@NULL@$</iteminfo>'.PHP_EOL;
|
||||
$xmlContent .= ' <idnumber></idnumber>'.PHP_EOL;
|
||||
$xmlContent .= ' <calculation>$@NULL@$</calculation>'.PHP_EOL;
|
||||
$xmlContent .= ' <gradetype>1</gradetype>'.PHP_EOL;
|
||||
$xmlContent .= ' <grademax>100.00000</grademax>'.PHP_EOL;
|
||||
$xmlContent .= ' <grademin>0.00000</grademin>'.PHP_EOL;
|
||||
$xmlContent .= ' <scaleid>$@NULL@$</scaleid>'.PHP_EOL;
|
||||
$xmlContent .= ' <outcomeid>$@NULL@$</outcomeid>'.PHP_EOL;
|
||||
$xmlContent .= ' <gradepass>0.00000</gradepass>'.PHP_EOL;
|
||||
$xmlContent .= ' <multfactor>1.00000</multfactor>'.PHP_EOL;
|
||||
$xmlContent .= ' <plusfactor>0.00000</plusfactor>'.PHP_EOL;
|
||||
$xmlContent .= ' <aggregationcoef>0.00000</aggregationcoef>'.PHP_EOL;
|
||||
$xmlContent .= ' <aggregationcoef2>0.23810</aggregationcoef2>'.PHP_EOL;
|
||||
$xmlContent .= ' <weightoverride>0</weightoverride>'.PHP_EOL;
|
||||
$xmlContent .= ' <sortorder>5</sortorder>'.PHP_EOL;
|
||||
$xmlContent .= ' <display>0</display>'.PHP_EOL;
|
||||
$xmlContent .= ' <decimals>$@NULL@$</decimals>'.PHP_EOL;
|
||||
$xmlContent .= ' <hidden>0</hidden>'.PHP_EOL;
|
||||
$xmlContent .= ' <locked>0</locked>'.PHP_EOL;
|
||||
$xmlContent .= ' <locktime>0</locktime>'.PHP_EOL;
|
||||
$xmlContent .= ' <needsupdate>0</needsupdate>'.PHP_EOL;
|
||||
$xmlContent .= ' <timecreated>'.$data['timemodified'].'</timecreated>'.PHP_EOL;
|
||||
$xmlContent .= ' <timemodified>'.$data['timemodified'].'</timemodified>'.PHP_EOL;
|
||||
$xmlContent .= ' <grade_grades></grade_grades>'.PHP_EOL;
|
||||
$xmlContent .= ' </grade_item>'.PHP_EOL;
|
||||
$xmlContent .= ' </grade_items>'.PHP_EOL;
|
||||
$xmlContent .= ' <grade_letters></grade_letters>'.PHP_EOL;
|
||||
$xmlContent .= '</activity_gradebook>';
|
||||
|
||||
$this->createXmlFile('grades', $xmlContent, $directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the XML file for the assign activity.
|
||||
*/
|
||||
private function createAssignXml(array $assignData, string $assignDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<activity id="'.$assignData['id'].'" moduleid="'.$assignData['moduleid'].'" modulename="assign" contextid="'.$assignData['contextid'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <assign id="'.$assignData['id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <name>'.htmlspecialchars($assignData['name']).'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <intro><![CDATA['.$assignData['intro'].']]></intro>'.PHP_EOL;
|
||||
$xmlContent .= ' <introformat>1</introformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <alwaysshowdescription>1</alwaysshowdescription>'.PHP_EOL;
|
||||
$xmlContent .= ' <submissiondrafts>0</submissiondrafts>'.PHP_EOL;
|
||||
$xmlContent .= ' <sendnotifications>0</sendnotifications>'.PHP_EOL;
|
||||
$xmlContent .= ' <sendlatenotifications>0</sendlatenotifications>'.PHP_EOL;
|
||||
$xmlContent .= ' <sendstudentnotifications>1</sendstudentnotifications>'.PHP_EOL;
|
||||
$xmlContent .= ' <duedate>'.$assignData['duedate'].'</duedate>'.PHP_EOL;
|
||||
$xmlContent .= ' <cutoffdate>0</cutoffdate>'.PHP_EOL;
|
||||
$xmlContent .= ' <gradingduedate>'.$assignData['gradingduedate'].'</gradingduedate>'.PHP_EOL;
|
||||
$xmlContent .= ' <allowsubmissionsfromdate>'.$assignData['allowsubmissionsfromdate'].'</allowsubmissionsfromdate>'.PHP_EOL;
|
||||
$xmlContent .= ' <grade>100</grade>'.PHP_EOL;
|
||||
$xmlContent .= ' <timemodified>'.$assignData['timemodified'].'</timemodified>'.PHP_EOL;
|
||||
$xmlContent .= ' <completionsubmit>1</completionsubmit>'.PHP_EOL;
|
||||
$xmlContent .= ' <requiresubmissionstatement>0</requiresubmissionstatement>'.PHP_EOL;
|
||||
$xmlContent .= ' <teamsubmission>0</teamsubmission>'.PHP_EOL;
|
||||
$xmlContent .= ' <requireallteammemberssubmit>0</requireallteammemberssubmit>'.PHP_EOL;
|
||||
$xmlContent .= ' <teamsubmissiongroupingid>0</teamsubmissiongroupingid>'.PHP_EOL;
|
||||
$xmlContent .= ' <blindmarking>0</blindmarking>'.PHP_EOL;
|
||||
$xmlContent .= ' <hidegrader>0</hidegrader>'.PHP_EOL;
|
||||
$xmlContent .= ' <revealidentities>0</revealidentities>'.PHP_EOL;
|
||||
$xmlContent .= ' <attemptreopenmethod>none</attemptreopenmethod>'.PHP_EOL;
|
||||
$xmlContent .= ' <maxattempts>1</maxattempts>'.PHP_EOL;
|
||||
$xmlContent .= ' <markingworkflow>0</markingworkflow>'.PHP_EOL;
|
||||
$xmlContent .= ' <markingallocation>0</markingallocation>'.PHP_EOL;
|
||||
$xmlContent .= ' <preventsubmissionnotingroup>0</preventsubmissionnotingroup>'.PHP_EOL;
|
||||
$xmlContent .= ' <userflags></userflags>'.PHP_EOL;
|
||||
$xmlContent .= ' <submissions></submissions>'.PHP_EOL;
|
||||
$xmlContent .= ' <grades></grades>'.PHP_EOL;
|
||||
$xmlContent .= ' </assign>'.PHP_EOL;
|
||||
$xmlContent .= '</activity>';
|
||||
|
||||
$this->createXmlFile('assign', $xmlContent, $assignDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the grading.xml file for the assign activity.
|
||||
*/
|
||||
private function createGradingXml(array $data, string $assignDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<areas>'.PHP_EOL;
|
||||
$xmlContent .= ' <area id="'.$data['area_id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <areaname>submissions</areaname>'.PHP_EOL;
|
||||
$xmlContent .= ' <activemethod>$@NULL@$</activemethod>'.PHP_EOL;
|
||||
$xmlContent .= ' <definitions></definitions>'.PHP_EOL;
|
||||
$xmlContent .= ' </area>'.PHP_EOL;
|
||||
$xmlContent .= '</areas>';
|
||||
|
||||
$this->createXmlFile('grading', $xmlContent, $assignDir);
|
||||
}
|
||||
}
|
||||
325
main/inc/lib/moodleexport/CourseExport.php
Normal file
325
main/inc/lib/moodleexport/CourseExport.php
Normal file
@@ -0,0 +1,325 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace moodleexport;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class CourseExport.
|
||||
*
|
||||
* @package moodleexport
|
||||
*/
|
||||
class CourseExport
|
||||
{
|
||||
private $course;
|
||||
private $courseInfo;
|
||||
private $activities;
|
||||
|
||||
public function __construct($course, $activities)
|
||||
{
|
||||
$this->course = $course;
|
||||
$this->courseInfo = api_get_course_info($course->code);
|
||||
$this->activities = $activities;
|
||||
|
||||
if (!$this->courseInfo) {
|
||||
throw new Exception("Course not found.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the course-related files to the appropriate directory.
|
||||
*/
|
||||
public function exportCourse(string $exportDir): void
|
||||
{
|
||||
$courseDir = $exportDir.'/course';
|
||||
if (!is_dir($courseDir)) {
|
||||
mkdir($courseDir, api_get_permissions_for_new_directories(), true);
|
||||
}
|
||||
|
||||
$this->createCourseXml($courseDir);
|
||||
$this->createEnrolmentsXml($this->courseInfo['enrolments'] ?? [], $courseDir);
|
||||
$this->createInforefXml($courseDir);
|
||||
$this->createRolesXml($this->courseInfo['roles'] ?? [], $courseDir);
|
||||
$this->createCalendarXml($this->courseInfo['calendar'] ?? [], $courseDir);
|
||||
$this->createCommentsXml($this->courseInfo['comments'] ?? [], $courseDir);
|
||||
$this->createCompetenciesXml($this->courseInfo['competencies'] ?? [], $courseDir);
|
||||
$this->createCompletionDefaultsXml($this->courseInfo['completiondefaults'] ?? [], $courseDir);
|
||||
$this->createContentBankXml($this->courseInfo['contentbank'] ?? [], $courseDir);
|
||||
$this->createFiltersXml($this->courseInfo['filters'] ?? [], $courseDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create course.xml based on the course data from MoodleExport.
|
||||
*/
|
||||
private function createCourseXml(string $destinationDir): void
|
||||
{
|
||||
$courseId = $this->courseInfo['real_id'] ?? 0;
|
||||
$contextId = $this->courseInfo['real_id'] ?? 1;
|
||||
$shortname = $this->courseInfo['code'] ?? 'Unknown Course';
|
||||
$fullname = $this->courseInfo['title'] ?? 'Unknown Fullname';
|
||||
$showgrades = $this->courseInfo['showgrades'] ?? 0;
|
||||
$startdate = $this->courseInfo['startdate'] ?? time();
|
||||
$enddate = $this->courseInfo['enddate'] ?? time() + (60 * 60 * 24 * 365);
|
||||
$visible = $this->courseInfo['visible'] ?? 1;
|
||||
$enablecompletion = $this->courseInfo['enablecompletion'] ?? 0;
|
||||
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<course id="'.$courseId.'" contextid="'.$contextId.'">'.PHP_EOL;
|
||||
$xmlContent .= ' <shortname>'.htmlspecialchars($shortname).'</shortname>'.PHP_EOL;
|
||||
$xmlContent .= ' <fullname>'.htmlspecialchars($fullname).'</fullname>'.PHP_EOL;
|
||||
$xmlContent .= ' <idnumber></idnumber>'.PHP_EOL;
|
||||
$xmlContent .= ' <summary></summary>'.PHP_EOL;
|
||||
$xmlContent .= ' <summaryformat>1</summaryformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <format>topics</format>'.PHP_EOL;
|
||||
$xmlContent .= ' <showgrades>'.$showgrades.'</showgrades>'.PHP_EOL;
|
||||
$xmlContent .= ' <newsitems>5</newsitems>'.PHP_EOL;
|
||||
$xmlContent .= ' <startdate>'.$startdate.'</startdate>'.PHP_EOL;
|
||||
$xmlContent .= ' <enddate>'.$enddate.'</enddate>'.PHP_EOL;
|
||||
$xmlContent .= ' <marker>0</marker>'.PHP_EOL;
|
||||
$xmlContent .= ' <maxbytes>0</maxbytes>'.PHP_EOL;
|
||||
$xmlContent .= ' <legacyfiles>0</legacyfiles>'.PHP_EOL;
|
||||
$xmlContent .= ' <showreports>0</showreports>'.PHP_EOL;
|
||||
$xmlContent .= ' <visible>'.$visible.'</visible>'.PHP_EOL;
|
||||
$xmlContent .= ' <groupmode>0</groupmode>'.PHP_EOL;
|
||||
$xmlContent .= ' <groupmodeforce>0</groupmodeforce>'.PHP_EOL;
|
||||
$xmlContent .= ' <defaultgroupingid>0</defaultgroupingid>'.PHP_EOL;
|
||||
$xmlContent .= ' <lang></lang>'.PHP_EOL;
|
||||
$xmlContent .= ' <theme></theme>'.PHP_EOL;
|
||||
$xmlContent .= ' <timecreated>'.time().'</timecreated>'.PHP_EOL;
|
||||
$xmlContent .= ' <timemodified>'.time().'</timemodified>'.PHP_EOL;
|
||||
$xmlContent .= ' <requested>0</requested>'.PHP_EOL;
|
||||
$xmlContent .= ' <showactivitydates>1</showactivitydates>'.PHP_EOL;
|
||||
$xmlContent .= ' <showcompletionconditions>1</showcompletionconditions>'.PHP_EOL;
|
||||
$xmlContent .= ' <enablecompletion>'.$enablecompletion.'</enablecompletion>'.PHP_EOL;
|
||||
$xmlContent .= ' <completionnotify>0</completionnotify>'.PHP_EOL;
|
||||
$xmlContent .= ' <category id="1">'.PHP_EOL;
|
||||
$xmlContent .= ' <name>Miscellaneous</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <description>$@NULL@$</description>'.PHP_EOL;
|
||||
$xmlContent .= ' </category>'.PHP_EOL;
|
||||
$xmlContent .= ' <tags>'.PHP_EOL;
|
||||
$xmlContent .= ' </tags>'.PHP_EOL;
|
||||
$xmlContent .= ' <customfields>'.PHP_EOL;
|
||||
$xmlContent .= ' </customfields>'.PHP_EOL;
|
||||
$xmlContent .= ' <courseformatoptions>'.PHP_EOL;
|
||||
$xmlContent .= ' <courseformatoption>'.PHP_EOL;
|
||||
$xmlContent .= ' <format>topics</format>'.PHP_EOL;
|
||||
$xmlContent .= ' <sectionid>0</sectionid>'.PHP_EOL;
|
||||
$xmlContent .= ' <name>hiddensections</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <value>0</value>'.PHP_EOL;
|
||||
$xmlContent .= ' </courseformatoption>'.PHP_EOL;
|
||||
$xmlContent .= ' <courseformatoption>'.PHP_EOL;
|
||||
$xmlContent .= ' <format>topics</format>'.PHP_EOL;
|
||||
$xmlContent .= ' <sectionid>0</sectionid>'.PHP_EOL;
|
||||
$xmlContent .= ' <name>coursedisplay</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <value>0</value>'.PHP_EOL;
|
||||
$xmlContent .= ' </courseformatoption>'.PHP_EOL;
|
||||
$xmlContent .= ' </courseformatoptions>'.PHP_EOL;
|
||||
$xmlContent .= '</course>';
|
||||
|
||||
file_put_contents($destinationDir.'/course.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create enrolments.xml based on the course data from MoodleExport.
|
||||
*/
|
||||
private function createEnrolmentsXml(array $enrolmentsData, string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<enrolments>'.PHP_EOL;
|
||||
foreach ($enrolmentsData as $enrol) {
|
||||
$id = $enrol['id'] ?? 0;
|
||||
$type = $enrol['type'] ?? 'manual';
|
||||
$status = $enrol['status'] ?? 1;
|
||||
|
||||
$xmlContent .= ' <enrol id="'.$id.'">'.PHP_EOL;
|
||||
$xmlContent .= ' <enrol>'.htmlspecialchars($type).'</enrol>'.PHP_EOL;
|
||||
$xmlContent .= ' <status>'.$status.'</status>'.PHP_EOL;
|
||||
$xmlContent .= ' </enrol>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= '</enrolments>';
|
||||
|
||||
file_put_contents($destinationDir.'/enrolments.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the inforef.xml file with file references, question categories, and role references.
|
||||
*/
|
||||
private function createInforefXml(string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<inforef>'.PHP_EOL;
|
||||
|
||||
$questionCategories = [];
|
||||
foreach ($this->activities as $activity) {
|
||||
if ($activity['modulename'] === 'quiz') {
|
||||
$quizExport = new QuizExport($this->course);
|
||||
$quizData = $quizExport->getData($activity['id'], $activity['sectionid']);
|
||||
foreach ($quizData['questions'] as $question) {
|
||||
$categoryId = $question['questioncategoryid'];
|
||||
if (!in_array($categoryId, $questionCategories, true)) {
|
||||
$questionCategories[] = $categoryId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($questionCategories)) {
|
||||
$xmlContent .= ' <question_categoryref>'.PHP_EOL;
|
||||
foreach ($questionCategories as $categoryId) {
|
||||
$xmlContent .= ' <question_category>'.PHP_EOL;
|
||||
$xmlContent .= ' <id>'.$categoryId.'</id>'.PHP_EOL;
|
||||
$xmlContent .= ' </question_category>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= ' </question_categoryref>'.PHP_EOL;
|
||||
}
|
||||
|
||||
// Add role references
|
||||
$xmlContent .= ' <roleref>'.PHP_EOL;
|
||||
$xmlContent .= ' <role>'.PHP_EOL;
|
||||
$xmlContent .= ' <id>5</id>'.PHP_EOL;
|
||||
$xmlContent .= ' </role>'.PHP_EOL;
|
||||
$xmlContent .= ' </roleref>'.PHP_EOL;
|
||||
|
||||
$xmlContent .= '</inforef>'.PHP_EOL;
|
||||
|
||||
file_put_contents($destinationDir.'/inforef.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the roles.xml file.
|
||||
*/
|
||||
private function createRolesXml(array $rolesData, string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<roles>'.PHP_EOL;
|
||||
foreach ($rolesData as $role) {
|
||||
$roleName = $role['name'] ?? 'Student';
|
||||
$xmlContent .= ' <role>'.PHP_EOL;
|
||||
$xmlContent .= ' <name>'.htmlspecialchars($roleName).'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' </role>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= '</roles>';
|
||||
|
||||
file_put_contents($destinationDir.'/roles.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the calendar.xml file.
|
||||
*/
|
||||
private function createCalendarXml(array $calendarData, string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<calendar>'.PHP_EOL;
|
||||
foreach ($calendarData as $event) {
|
||||
$eventName = $event['name'] ?? 'Event';
|
||||
$timestart = $event['timestart'] ?? time();
|
||||
$duration = $event['duration'] ?? 3600;
|
||||
|
||||
$xmlContent .= ' <event>'.PHP_EOL;
|
||||
$xmlContent .= ' <name>'.htmlspecialchars($eventName).'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <timestart>'.$timestart.'</timestart>'.PHP_EOL;
|
||||
$xmlContent .= ' <duration>'.$duration.'</duration>'.PHP_EOL;
|
||||
$xmlContent .= ' </event>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= '</calendar>';
|
||||
|
||||
file_put_contents($destinationDir.'/calendar.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the comments.xml file.
|
||||
*/
|
||||
private function createCommentsXml(array $commentsData, string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<comments>'.PHP_EOL;
|
||||
foreach ($commentsData as $comment) {
|
||||
$content = $comment['content'] ?? 'No comment';
|
||||
$author = $comment['author'] ?? 'Anonymous';
|
||||
|
||||
$xmlContent .= ' <comment>'.PHP_EOL;
|
||||
$xmlContent .= ' <content>'.htmlspecialchars($content).'</content>'.PHP_EOL;
|
||||
$xmlContent .= ' <author>'.htmlspecialchars($author).'</author>'.PHP_EOL;
|
||||
$xmlContent .= ' </comment>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= '</comments>';
|
||||
|
||||
file_put_contents($destinationDir.'/comments.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the competencies.xml file.
|
||||
*/
|
||||
private function createCompetenciesXml(array $competenciesData, string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<competencies>'.PHP_EOL;
|
||||
foreach ($competenciesData as $competency) {
|
||||
$name = $competency['name'] ?? 'Competency';
|
||||
$xmlContent .= ' <competency>'.PHP_EOL;
|
||||
$xmlContent .= ' <name>'.htmlspecialchars($name).'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' </competency>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= '</competencies>';
|
||||
|
||||
file_put_contents($destinationDir.'/competencies.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the completiondefaults.xml file.
|
||||
*/
|
||||
private function createCompletionDefaultsXml(array $completionData, string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<completiondefaults>'.PHP_EOL;
|
||||
foreach ($completionData as $completion) {
|
||||
$completionState = $completion['state'] ?? 0;
|
||||
$xmlContent .= ' <completion>'.PHP_EOL;
|
||||
$xmlContent .= ' <completionstate>'.$completionState.'</completionstate>'.PHP_EOL;
|
||||
$xmlContent .= ' </completion>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= '</completiondefaults>';
|
||||
|
||||
file_put_contents($destinationDir.'/completiondefaults.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the contentbank.xml file.
|
||||
*/
|
||||
private function createContentBankXml(array $contentBankData, string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<contentbank>'.PHP_EOL;
|
||||
foreach ($contentBankData as $content) {
|
||||
$id = $content['id'] ?? 0;
|
||||
$name = $content['name'] ?? 'Content';
|
||||
$xmlContent .= ' <content id="'.$id.'">'.htmlspecialchars($name).'</content>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= '</contentbank>';
|
||||
|
||||
file_put_contents($destinationDir.'/contentbank.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the filters.xml file.
|
||||
*/
|
||||
private function createFiltersXml(array $filtersData, string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<filters>'.PHP_EOL;
|
||||
foreach ($filtersData as $filter) {
|
||||
$filterName = $filter['name'] ?? 'filter_example';
|
||||
$active = $filter['active'] ?? 1;
|
||||
|
||||
$xmlContent .= ' <filter>'.PHP_EOL;
|
||||
$xmlContent .= ' <filtername>'.htmlspecialchars($filterName).'</filtername>'.PHP_EOL;
|
||||
$xmlContent .= ' <active>'.$active.'</active>'.PHP_EOL;
|
||||
$xmlContent .= ' </filter>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= '</filters>';
|
||||
|
||||
file_put_contents($destinationDir.'/filters.xml', $xmlContent);
|
||||
}
|
||||
}
|
||||
193
main/inc/lib/moodleexport/FeedbackExport.php
Normal file
193
main/inc/lib/moodleexport/FeedbackExport.php
Normal file
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace moodleexport;
|
||||
|
||||
/**
|
||||
* Class FeedbackExport.
|
||||
*
|
||||
* Handles the export of surveys from Chamilo to Moodle Feedback.
|
||||
*/
|
||||
class FeedbackExport extends ActivityExport
|
||||
{
|
||||
/**
|
||||
* Export a survey to Moodle Feedback format.
|
||||
*
|
||||
* @param int $activityId The ID of the survey.
|
||||
* @param string $exportDir The directory where the feedback will be exported.
|
||||
* @param int $moduleId The ID of the module.
|
||||
* @param int $sectionId The ID of the section.
|
||||
*/
|
||||
public function export($activityId, $exportDir, $moduleId, $sectionId): void
|
||||
{
|
||||
// Prepare the directory for export
|
||||
$feedbackDir = $this->prepareActivityDirectory($exportDir, 'feedback', $moduleId);
|
||||
|
||||
// Get survey data from Chamilo
|
||||
$surveyData = $this->getData($activityId, $sectionId);
|
||||
|
||||
// Create XML files for the survey
|
||||
$this->createFeedbackXml($surveyData, $feedbackDir);
|
||||
$this->createModuleXml($surveyData, $feedbackDir);
|
||||
$this->createInforefXml($surveyData, $feedbackDir);
|
||||
$this->createCalendarXml($surveyData, $feedbackDir);
|
||||
$this->createCommentsXml($surveyData, $feedbackDir);
|
||||
$this->createCompetenciesXml($surveyData, $feedbackDir);
|
||||
$this->createCompletionXml($surveyData, $feedbackDir);
|
||||
$this->createFiltersXml($surveyData, $feedbackDir);
|
||||
$this->createGradeHistoryXml($surveyData, $feedbackDir);
|
||||
$this->createGradesXml($surveyData, $feedbackDir);
|
||||
$this->createRolesXml($surveyData, $feedbackDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get survey data including questions and answers from Chamilo.
|
||||
*/
|
||||
public function getData(int $surveyId, int $sectionId): array
|
||||
{
|
||||
$adminData = MoodleExport::getAdminUserData();
|
||||
$adminId = $adminData['id'];
|
||||
|
||||
$survey = $this->course->resources['survey'][$surveyId];
|
||||
$questions = [];
|
||||
foreach ($this->course->resources['survey_question'] as $question) {
|
||||
if ((int) $question->survey_id === $surveyId) {
|
||||
// Debugging
|
||||
$questions[] = [
|
||||
'id' => $question->id,
|
||||
'text' => $question->survey_question,
|
||||
'type' => $question->survey_question_type,
|
||||
'options' => array_map(function ($answer) {
|
||||
return $answer['option_text'];
|
||||
}, $question->answers),
|
||||
'position' => $question->sort,
|
||||
'label' => '', // Default empty label
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $surveyId,
|
||||
'moduleid' => $surveyId,
|
||||
'modulename' => 'feedback',
|
||||
'contextid' => $this->course->info['real_id'],
|
||||
'sectionid' => $sectionId,
|
||||
'sectionnumber' => 0,
|
||||
'name' => $survey->title,
|
||||
'intro' => $survey->intro,
|
||||
'timemodified' => time(),
|
||||
'questions' => $questions,
|
||||
'users' => [$adminId],
|
||||
'files' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the feedback.xml file for the Moodle feedback activity.
|
||||
*/
|
||||
private function createFeedbackXml(array $surveyData, string $feedbackDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<activity id="'.$surveyData['id'].'" moduleid="'.$surveyData['moduleid'].'" modulename="feedback" contextid="'.$surveyData['contextid'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <feedback id="'.$surveyData['id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <name>'.htmlspecialchars($surveyData['name']).'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <intro>'.htmlspecialchars($surveyData['intro']).'</intro>'.PHP_EOL;
|
||||
$xmlContent .= ' <introformat>1</introformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <anonymous>1</anonymous>'.PHP_EOL;
|
||||
$xmlContent .= ' <email_notification>0</email_notification>'.PHP_EOL;
|
||||
$xmlContent .= ' <multiple_submit>0</multiple_submit>'.PHP_EOL;
|
||||
$xmlContent .= ' <autonumbering>1</autonumbering>'.PHP_EOL;
|
||||
$xmlContent .= ' <site_after_submit></site_after_submit>'.PHP_EOL;
|
||||
$xmlContent .= ' <page_after_submit></page_after_submit>'.PHP_EOL;
|
||||
$xmlContent .= ' <page_after_submitformat>1</page_after_submitformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <publish_stats>0</publish_stats>'.PHP_EOL;
|
||||
$xmlContent .= ' <timeopen>0</timeopen>'.PHP_EOL;
|
||||
$xmlContent .= ' <timeclose>0</timeclose>'.PHP_EOL;
|
||||
$xmlContent .= ' <timemodified>'.$surveyData['timemodified'].'</timemodified>'.PHP_EOL;
|
||||
$xmlContent .= ' <completionsubmit>0</completionsubmit>'.PHP_EOL;
|
||||
$xmlContent .= ' <items>'.PHP_EOL;
|
||||
|
||||
// Map Chamilo questions to Moodle Feedback format
|
||||
foreach ($surveyData['questions'] as $question) {
|
||||
$xmlContent .= $this->createQuestionXml($question);
|
||||
}
|
||||
|
||||
$xmlContent .= ' </items>'.PHP_EOL;
|
||||
$xmlContent .= ' </feedback>'.PHP_EOL;
|
||||
$xmlContent .= '</activity>';
|
||||
|
||||
$this->createXmlFile('feedback', $xmlContent, $feedbackDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the XML for a single question in Moodle Feedback format.
|
||||
*/
|
||||
private function createQuestionXml(array $question): string
|
||||
{
|
||||
$name = htmlspecialchars(strip_tags($question['text']), ENT_XML1 | ENT_QUOTES, 'UTF-8');
|
||||
$label = htmlspecialchars(strip_tags($question['label']), ENT_XML1 | ENT_QUOTES, 'UTF-8');
|
||||
$presentation = $this->getPresentation($question);
|
||||
$hasValue = ($question['type'] === 'pagebreak') ? '0' : '1';
|
||||
|
||||
$xmlContent = '<item id="'.$question['id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <template>0</template>'.PHP_EOL;
|
||||
$xmlContent .= ' <name>'.$name.'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <label>'.$label.'</label>'.PHP_EOL;
|
||||
$xmlContent .= ' <presentation>'.$presentation.'</presentation>'.PHP_EOL;
|
||||
$xmlContent .= ' <typ>'.$this->mapQuestionType($question['type']).'</typ>'.PHP_EOL;
|
||||
$xmlContent .= ' <hasvalue>'.$hasValue.'</hasvalue>'.PHP_EOL;
|
||||
$xmlContent .= ' <position>'.$question['position'].'</position>'.PHP_EOL;
|
||||
$xmlContent .= ' <required>0</required>'.PHP_EOL;
|
||||
$xmlContent .= ' <dependitem>0</dependitem>'.PHP_EOL;
|
||||
$xmlContent .= ' <dependvalue></dependvalue>'.PHP_EOL;
|
||||
$xmlContent .= ' <options>h</options>'.PHP_EOL;
|
||||
$xmlContent .= '</item>'.PHP_EOL;
|
||||
|
||||
return $xmlContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get presentation for different question types.
|
||||
*/
|
||||
private function getPresentation(array $question): string
|
||||
{
|
||||
$options = array_map('strip_tags', $question['options']);
|
||||
$sanitizedOptions = array_map(function ($option) {
|
||||
return htmlspecialchars($option, ENT_XML1 | ENT_QUOTES, 'UTF-8');
|
||||
}, $options);
|
||||
|
||||
switch ($question['type']) {
|
||||
case 'yesno':
|
||||
case 'multiplechoice':
|
||||
case 'multiplechoiceother':
|
||||
return 'r>>>>>'.implode(PHP_EOL.'|', $sanitizedOptions);
|
||||
case 'multipleresponse':
|
||||
return 'c>>>>>'.implode(PHP_EOL.'|', $sanitizedOptions);
|
||||
case 'dropdown':
|
||||
return 'd>>>>>'.implode(PHP_EOL.'|', $sanitizedOptions);
|
||||
case 'open':
|
||||
return '30|5'; // Textarea with rows and cols
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map Chamilo question type to Moodle Feedback type.
|
||||
*/
|
||||
private function mapQuestionType(string $chamiloType): string
|
||||
{
|
||||
$typeMap = [
|
||||
'yesno' => 'multichoice',
|
||||
'multiplechoice' => 'multichoice',
|
||||
'multipleresponse' => 'multichoice',
|
||||
'dropdown' => 'multichoice',
|
||||
'multiplechoiceother' => 'multichoice',
|
||||
'open' => 'textarea',
|
||||
'pagebreak' => 'pagebreak',
|
||||
];
|
||||
|
||||
return $typeMap[$chamiloType] ?? 'unknown';
|
||||
}
|
||||
}
|
||||
309
main/inc/lib/moodleexport/FileExport.php
Normal file
309
main/inc/lib/moodleexport/FileExport.php
Normal file
@@ -0,0 +1,309 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace moodleexport;
|
||||
|
||||
use DocumentManager;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class FileExport.
|
||||
* Handles the export of files and metadata from Moodle courses.
|
||||
*
|
||||
* @package moodleexport
|
||||
*/
|
||||
class FileExport
|
||||
{
|
||||
private $course;
|
||||
|
||||
/**
|
||||
* Constructor to initialize course data.
|
||||
*
|
||||
* @param object $course Course object containing resources and path data.
|
||||
*/
|
||||
public function __construct($course)
|
||||
{
|
||||
$this->course = $course;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export files and metadata from files.xml to the specified directory.
|
||||
*/
|
||||
public function exportFiles(array $filesData, string $exportDir): void
|
||||
{
|
||||
$filesDir = $exportDir.'/files';
|
||||
|
||||
if (!is_dir($filesDir)) {
|
||||
mkdir($filesDir, api_get_permissions_for_new_directories(), true);
|
||||
}
|
||||
|
||||
// Create placeholder index.html
|
||||
$this->createPlaceholderFile($filesDir);
|
||||
|
||||
// Export each file
|
||||
foreach ($filesData['files'] as $file) {
|
||||
$this->copyFileToExportDir($file, $filesDir);
|
||||
}
|
||||
|
||||
// Create files.xml in the export directory
|
||||
$this->createFilesXml($filesData, $exportDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file data from course resources. This is for testing purposes.
|
||||
*/
|
||||
public function getFilesData(): array
|
||||
{
|
||||
$adminData = MoodleExport::getAdminUserData();
|
||||
$adminId = $adminData['id'];
|
||||
|
||||
$filesData = ['files' => []];
|
||||
|
||||
foreach ($this->course->resources[RESOURCE_DOCUMENT] as $document) {
|
||||
$filesData = $this->processDocument($filesData, $document);
|
||||
}
|
||||
|
||||
foreach ($this->course->resources[RESOURCE_WORK] as $work) {
|
||||
$workFiles = getAllDocumentToWork($work->params['id'], $this->course->info['real_id']);
|
||||
|
||||
if (!empty($workFiles)) {
|
||||
foreach ($workFiles as $file) {
|
||||
$docData = DocumentManager::get_document_data_by_id($file['document_id'], $this->course->info['code']);
|
||||
if (!empty($docData)) {
|
||||
$filesData['files'][] = [
|
||||
'id' => $file['document_id'],
|
||||
'contenthash' => hash('sha1', basename($docData['path'])),
|
||||
'contextid' => $this->course->info['real_id'],
|
||||
'component' => 'mod_assign',
|
||||
'filearea' => 'introattachment',
|
||||
'itemid' => (int) $work->params['id'],
|
||||
'filepath' => '/',
|
||||
'documentpath' => 'document/'.$docData['path'],
|
||||
'filename' => basename($docData['path']),
|
||||
'userid' => $adminId,
|
||||
'filesize' => $docData['size'],
|
||||
'mimetype' => $this->getMimeType($docData['path']),
|
||||
'status' => 0,
|
||||
'timecreated' => time() - 3600,
|
||||
'timemodified' => time(),
|
||||
'source' => $docData['title'],
|
||||
'author' => 'Unknown',
|
||||
'license' => 'allrightsreserved',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $filesData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a placeholder index.html file to prevent an empty directory.
|
||||
*/
|
||||
private function createPlaceholderFile(string $filesDir): void
|
||||
{
|
||||
$placeholderFile = $filesDir.'/index.html';
|
||||
file_put_contents($placeholderFile, "<!-- Placeholder file to ensure the directory is not empty -->");
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a file to the export directory using its contenthash.
|
||||
*/
|
||||
private function copyFileToExportDir(array $file, string $filesDir): void
|
||||
{
|
||||
if ($file['filepath'] === '.') {
|
||||
return;
|
||||
}
|
||||
|
||||
$contenthash = $file['contenthash'];
|
||||
$subDir = substr($contenthash, 0, 2);
|
||||
$filePath = $this->course->path.$file['documentpath'];
|
||||
$exportSubDir = $filesDir.'/'.$subDir;
|
||||
|
||||
// Ensure the subdirectory exists
|
||||
if (!is_dir($exportSubDir)) {
|
||||
mkdir($exportSubDir, api_get_permissions_for_new_directories(), true);
|
||||
}
|
||||
|
||||
// Copy the file to the export directory
|
||||
$destinationFile = $exportSubDir.'/'.$contenthash;
|
||||
if (file_exists($filePath)) {
|
||||
copy($filePath, $destinationFile);
|
||||
} else {
|
||||
throw new Exception("File {$filePath} not found.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the files.xml with the provided file data.
|
||||
*/
|
||||
private function createFilesXml(array $filesData, string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<files>'.PHP_EOL;
|
||||
|
||||
foreach ($filesData['files'] as $file) {
|
||||
$xmlContent .= $this->createFileXmlEntry($file);
|
||||
}
|
||||
|
||||
$xmlContent .= '</files>'.PHP_EOL;
|
||||
file_put_contents($destinationDir.'/files.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an XML entry for a file.
|
||||
*/
|
||||
private function createFileXmlEntry(array $file): string
|
||||
{
|
||||
return ' <file id="'.$file['id'].'">'.PHP_EOL.
|
||||
' <contenthash>'.htmlspecialchars($file['contenthash']).'</contenthash>'.PHP_EOL.
|
||||
' <contextid>'.$file['contextid'].'</contextid>'.PHP_EOL.
|
||||
' <component>'.htmlspecialchars($file['component']).'</component>'.PHP_EOL.
|
||||
' <filearea>'.htmlspecialchars($file['filearea']).'</filearea>'.PHP_EOL.
|
||||
' <itemid>0</itemid>'.PHP_EOL.
|
||||
' <filepath>'.htmlspecialchars($file['filepath']).'</filepath>'.PHP_EOL.
|
||||
' <filename>'.htmlspecialchars($file['filename']).'</filename>'.PHP_EOL.
|
||||
' <userid>'.$file['userid'].'</userid>'.PHP_EOL.
|
||||
' <filesize>'.$file['filesize'].'</filesize>'.PHP_EOL.
|
||||
' <mimetype>'.htmlspecialchars($file['mimetype']).'</mimetype>'.PHP_EOL.
|
||||
' <status>'.$file['status'].'</status>'.PHP_EOL.
|
||||
' <timecreated>'.$file['timecreated'].'</timecreated>'.PHP_EOL.
|
||||
' <timemodified>'.$file['timemodified'].'</timemodified>'.PHP_EOL.
|
||||
' <source>'.htmlspecialchars($file['source']).'</source>'.PHP_EOL.
|
||||
' <author>'.htmlspecialchars($file['author']).'</author>'.PHP_EOL.
|
||||
' <license>'.htmlspecialchars($file['license']).'</license>'.PHP_EOL.
|
||||
' <sortorder>0</sortorder>'.PHP_EOL.
|
||||
' <repositorytype>$@NULL@$</repositorytype>'.PHP_EOL.
|
||||
' <repositoryid>$@NULL@$</repositoryid>'.PHP_EOL.
|
||||
' <reference>$@NULL@$</reference>'.PHP_EOL.
|
||||
' </file>'.PHP_EOL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a document or folder and add its data to the files array.
|
||||
*/
|
||||
private function processDocument(array $filesData, object $document): array
|
||||
{
|
||||
if ($document->file_type === 'file') {
|
||||
$filesData['files'][] = $this->getFileData($document);
|
||||
} elseif ($document->file_type === 'folder') {
|
||||
$folderFiles = \DocumentManager::getAllDocumentsByParentId($this->course->info, $document->source_id);
|
||||
foreach ($folderFiles as $file) {
|
||||
$filesData['files'][] = $this->getFolderFileData($file, (int) $document->source_id);
|
||||
}
|
||||
}
|
||||
|
||||
return $filesData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file data for a single document.
|
||||
*/
|
||||
private function getFileData(object $document): array
|
||||
{
|
||||
$adminData = MoodleExport::getAdminUserData();
|
||||
$adminId = $adminData['id'];
|
||||
$contenthash = hash('sha1', basename($document->path));
|
||||
$mimetype = $this->getMimeType($document->path);
|
||||
|
||||
return [
|
||||
'id' => $document->source_id,
|
||||
'contenthash' => $contenthash,
|
||||
'contextid' => $document->source_id,
|
||||
'component' => 'mod_resource',
|
||||
'filearea' => 'content',
|
||||
'itemid' => (int) $document->source_id,
|
||||
'filepath' => '/',
|
||||
'documentpath' => $document->path,
|
||||
'filename' => basename($document->path),
|
||||
'userid' => $adminId,
|
||||
'filesize' => $document->size,
|
||||
'mimetype' => $mimetype,
|
||||
'status' => 0,
|
||||
'timecreated' => time() - 3600,
|
||||
'timemodified' => time(),
|
||||
'source' => $document->title,
|
||||
'author' => 'Unknown',
|
||||
'license' => 'allrightsreserved',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file data for files inside a folder.
|
||||
*/
|
||||
private function getFolderFileData(array $file, int $sourceId): array
|
||||
{
|
||||
$adminData = MoodleExport::getAdminUserData();
|
||||
$adminId = $adminData['id'];
|
||||
$contenthash = hash('sha1', basename($file['path']));
|
||||
$mimetype = $this->getMimeType($file['path']);
|
||||
$filename = basename($file['path']);
|
||||
$filepath = $this->ensureTrailingSlash(dirname($file['path']));
|
||||
|
||||
return [
|
||||
'id' => $file['id'],
|
||||
'contenthash' => $contenthash,
|
||||
'contextid' => $sourceId,
|
||||
'component' => 'mod_folder',
|
||||
'filearea' => 'content',
|
||||
'itemid' => (int) $file['id'],
|
||||
'filepath' => $filepath,
|
||||
'documentpath' => 'document/'.$file['path'],
|
||||
'filename' => $filename,
|
||||
'userid' => $adminId,
|
||||
'filesize' => $file['size'],
|
||||
'mimetype' => $mimetype,
|
||||
'status' => 0,
|
||||
'timecreated' => time() - 3600,
|
||||
'timemodified' => time(),
|
||||
'source' => $file['title'],
|
||||
'author' => 'Unknown',
|
||||
'license' => 'allrightsreserved',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the directory path has a trailing slash.
|
||||
*/
|
||||
private function ensureTrailingSlash($path): string
|
||||
{
|
||||
return empty($path) || $path === '.' || $path === '/' ? '/' : rtrim($path, '/').'/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get MIME type based on the file extension.
|
||||
*/
|
||||
private function getMimeType($filePath): string
|
||||
{
|
||||
$extension = pathinfo($filePath, PATHINFO_EXTENSION);
|
||||
$mimeTypes = $this->getMimeTypes();
|
||||
|
||||
return $mimeTypes[$extension] ?? 'application/octet-stream';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of file extensions and their corresponding MIME types.
|
||||
*/
|
||||
private function getMimeTypes(): array
|
||||
{
|
||||
return [
|
||||
'pdf' => 'application/pdf',
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'png' => 'image/png',
|
||||
'gif' => 'image/gif',
|
||||
'html' => 'text/html',
|
||||
'txt' => 'text/plain',
|
||||
'doc' => 'application/msword',
|
||||
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'xls' => 'application/vnd.ms-excel',
|
||||
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'ppt' => 'application/vnd.ms-powerpoint',
|
||||
'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'zip' => 'application/zip',
|
||||
'rar' => 'application/x-rar-compressed',
|
||||
];
|
||||
}
|
||||
}
|
||||
124
main/inc/lib/moodleexport/FolderExport.php
Normal file
124
main/inc/lib/moodleexport/FolderExport.php
Normal file
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace moodleexport;
|
||||
|
||||
/**
|
||||
* Class FolderExport.
|
||||
*
|
||||
* Handles the export of folders within a course.
|
||||
*/
|
||||
class FolderExport extends ActivityExport
|
||||
{
|
||||
/**
|
||||
* Export a folder to the specified directory.
|
||||
*
|
||||
* @param int $activityId The ID of the folder.
|
||||
* @param string $exportDir The directory where the folder will be exported.
|
||||
* @param int $moduleId The ID of the module.
|
||||
* @param int $sectionId The ID of the section.
|
||||
*/
|
||||
public function export($activityId, $exportDir, $moduleId, $sectionId): void
|
||||
{
|
||||
// Prepare the directory where the folder export will be saved
|
||||
$folderDir = $this->prepareActivityDirectory($exportDir, 'folder', $moduleId);
|
||||
|
||||
// Retrieve folder data
|
||||
$folderData = $this->getData($activityId, $sectionId);
|
||||
|
||||
// Generate XML files
|
||||
$this->createFolderXml($folderData, $folderDir);
|
||||
$this->createModuleXml($folderData, $folderDir);
|
||||
$this->createGradesXml($folderData, $folderDir);
|
||||
$this->createFiltersXml($folderData, $folderDir);
|
||||
$this->createGradeHistoryXml($folderData, $folderDir);
|
||||
$this->createInforefXml($this->getFilesForFolder($activityId), $folderDir);
|
||||
$this->createRolesXml($folderData, $folderDir);
|
||||
$this->createCommentsXml($folderData, $folderDir);
|
||||
$this->createCalendarXml($folderData, $folderDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get folder data dynamically from the course.
|
||||
*/
|
||||
public function getData(int $folderId, int $sectionId): array
|
||||
{
|
||||
$folder = $this->course->resources['document'][$folderId];
|
||||
|
||||
return [
|
||||
'id' => $folderId,
|
||||
'moduleid' => $folder->source_id,
|
||||
'modulename' => 'folder',
|
||||
'contextid' => $folder->source_id,
|
||||
'name' => $folder->title,
|
||||
'sectionid' => $sectionId,
|
||||
'timemodified' => time(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the XML file for the folder.
|
||||
*/
|
||||
private function createFolderXml(array $folderData, string $folderDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<activity id="'.$folderData['id'].'" moduleid="'.$folderData['moduleid'].'" modulename="folder" contextid="'.$folderData['contextid'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <folder id="'.$folderData['id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <name>'.htmlspecialchars($folderData['name']).'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <intro></intro>'.PHP_EOL;
|
||||
$xmlContent .= ' <introformat>1</introformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <revision>1</revision>'.PHP_EOL;
|
||||
$xmlContent .= ' <timemodified>'.$folderData['timemodified'].'</timemodified>'.PHP_EOL;
|
||||
$xmlContent .= ' <display>0</display>'.PHP_EOL;
|
||||
$xmlContent .= ' <showexpanded>1</showexpanded>'.PHP_EOL;
|
||||
$xmlContent .= ' <showdownloadfolder>1</showdownloadfolder>'.PHP_EOL;
|
||||
$xmlContent .= ' <forcedownload>1</forcedownload>'.PHP_EOL;
|
||||
$xmlContent .= ' </folder>'.PHP_EOL;
|
||||
$xmlContent .= '</activity>';
|
||||
|
||||
$this->createXmlFile('folder', $xmlContent, $folderDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of files for a folder.
|
||||
*/
|
||||
private function getFilesForFolder(int $folderId): array
|
||||
{
|
||||
$documentData = \DocumentManager::getAllDocumentsByParentId($this->course->info, $folderId);
|
||||
|
||||
$files = [];
|
||||
foreach ($documentData as $doc) {
|
||||
if ($doc['filetype'] === 'file') {
|
||||
$files[] = [
|
||||
'id' => (int) $doc['id'],
|
||||
'contenthash' => 'hash'.$doc['id'],
|
||||
'filename' => $doc['basename'],
|
||||
'filepath' => $doc['path'],
|
||||
'filesize' => (int) $doc['size'],
|
||||
'mimetype' => $this->getMimeType($doc['basename']),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return ['files' => $files];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the MIME type for a given file.
|
||||
*/
|
||||
private function getMimeType(string $filename): string
|
||||
{
|
||||
$ext = pathinfo($filename, PATHINFO_EXTENSION);
|
||||
$mimetypes = [
|
||||
'pdf' => 'application/pdf',
|
||||
'png' => 'image/png',
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'doc' => 'application/msword',
|
||||
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
];
|
||||
|
||||
return $mimetypes[$ext] ?? 'application/octet-stream';
|
||||
}
|
||||
}
|
||||
188
main/inc/lib/moodleexport/ForumExport.php
Normal file
188
main/inc/lib/moodleexport/ForumExport.php
Normal file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace moodleexport;
|
||||
|
||||
/**
|
||||
* Class ForumExport.
|
||||
*
|
||||
* Handles the export of forums within a course.
|
||||
*/
|
||||
class ForumExport extends ActivityExport
|
||||
{
|
||||
/**
|
||||
* Export all forum data into a single Moodle forum activity.
|
||||
*
|
||||
* @param int $activityId The ID of the forum.
|
||||
* @param string $exportDir The directory where the forum will be exported.
|
||||
* @param int $moduleId The ID of the module.
|
||||
* @param int $sectionId The ID of the section.
|
||||
*/
|
||||
public function export($activityId, $exportDir, $moduleId, $sectionId): void
|
||||
{
|
||||
// Prepare the directory where the forum export will be saved
|
||||
$forumDir = $this->prepareActivityDirectory($exportDir, 'forum', $moduleId);
|
||||
|
||||
// Retrieve forum data
|
||||
$forumData = $this->getData($activityId, $sectionId);
|
||||
|
||||
// Generate XML files for the forum
|
||||
$this->createForumXml($forumData, $forumDir);
|
||||
$this->createModuleXml($forumData, $forumDir);
|
||||
$this->createGradesXml($forumData, $forumDir);
|
||||
$this->createGradeHistoryXml($forumData, $forumDir);
|
||||
$this->createInforefXml($forumData, $forumDir);
|
||||
$this->createRolesXml($forumData, $forumDir);
|
||||
$this->createCalendarXml($forumData, $forumDir);
|
||||
$this->createCommentsXml($forumData, $forumDir);
|
||||
$this->createCompetenciesXml($forumData, $forumDir);
|
||||
$this->createFiltersXml($forumData, $forumDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all forum data from the course.
|
||||
*/
|
||||
public function getData(int $forumId, int $sectionId): ?array
|
||||
{
|
||||
$forum = $this->course->resources['forum'][$forumId]->obj;
|
||||
|
||||
$adminData = MoodleExport::getAdminUserData();
|
||||
$adminId = $adminData['id'];
|
||||
|
||||
$threads = [];
|
||||
foreach ($this->course->resources['thread'] as $threadId => $thread) {
|
||||
if ($thread->obj->forum_id == $forumId) {
|
||||
// Get the posts for each thread
|
||||
$posts = [];
|
||||
foreach ($this->course->resources['post'] as $postId => $post) {
|
||||
if ($post->obj->thread_id == $threadId) {
|
||||
$posts[] = [
|
||||
'id' => $post->obj->post_id,
|
||||
'userid' => $adminId,
|
||||
'message' => $post->obj->post_text,
|
||||
'created' => strtotime($post->obj->post_date),
|
||||
'modified' => strtotime($post->obj->post_date),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$threads[] = [
|
||||
'id' => $thread->obj->thread_id,
|
||||
'title' => $thread->obj->thread_title,
|
||||
'userid' => $adminId,
|
||||
'timemodified' => strtotime($thread->obj->thread_date),
|
||||
'usermodified' => $adminId,
|
||||
'posts' => $posts,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$fileIds = [];
|
||||
|
||||
return [
|
||||
'id' => $forumId,
|
||||
'moduleid' => $forumId,
|
||||
'modulename' => 'forum',
|
||||
'contextid' => $this->course->info['real_id'],
|
||||
'name' => $forum->forum_title,
|
||||
'description' => $forum->forum_comment,
|
||||
'timecreated' => time(),
|
||||
'timemodified' => time(),
|
||||
'sectionid' => $sectionId,
|
||||
'sectionnumber' => 1,
|
||||
'userid' => $adminId,
|
||||
'threads' => $threads,
|
||||
'users' => [$adminId],
|
||||
'files' => $fileIds,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the forum.xml file with all forum data.
|
||||
*/
|
||||
private function createForumXml(array $forumData, string $forumDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<activity id="'.$forumData['id'].'" moduleid="'.$forumData['moduleid'].'" modulename="'.$forumData['modulename'].'" contextid="'.$forumData['contextid'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <forum id="'.$forumData['id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <type>general</type>'.PHP_EOL;
|
||||
$xmlContent .= ' <name>'.htmlspecialchars($forumData['name']).'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <intro>'.htmlspecialchars($forumData['description']).'</intro>'.PHP_EOL;
|
||||
$xmlContent .= ' <introformat>1</introformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <duedate>0</duedate>'.PHP_EOL;
|
||||
$xmlContent .= ' <cutoffdate>0</cutoffdate>'.PHP_EOL;
|
||||
$xmlContent .= ' <assessed>0</assessed>'.PHP_EOL;
|
||||
$xmlContent .= ' <assesstimestart>0</assesstimestart>'.PHP_EOL;
|
||||
$xmlContent .= ' <assesstimefinish>0</assesstimefinish>'.PHP_EOL;
|
||||
$xmlContent .= ' <scale>100</scale>'.PHP_EOL;
|
||||
$xmlContent .= ' <maxbytes>512000</maxbytes>'.PHP_EOL;
|
||||
$xmlContent .= ' <maxattachments>9</maxattachments>'.PHP_EOL;
|
||||
$xmlContent .= ' <forcesubscribe>0</forcesubscribe>'.PHP_EOL;
|
||||
$xmlContent .= ' <trackingtype>1</trackingtype>'.PHP_EOL;
|
||||
$xmlContent .= ' <rsstype>0</rsstype>'.PHP_EOL;
|
||||
$xmlContent .= ' <rssarticles>0</rssarticles>'.PHP_EOL;
|
||||
$xmlContent .= ' <timemodified>'.$forumData['timemodified'].'</timemodified>'.PHP_EOL;
|
||||
$xmlContent .= ' <warnafter>0</warnafter>'.PHP_EOL;
|
||||
$xmlContent .= ' <blockafter>0</blockafter>'.PHP_EOL;
|
||||
$xmlContent .= ' <blockperiod>0</blockperiod>'.PHP_EOL;
|
||||
$xmlContent .= ' <completiondiscussions>0</completiondiscussions>'.PHP_EOL;
|
||||
$xmlContent .= ' <completionreplies>0</completionreplies>'.PHP_EOL;
|
||||
$xmlContent .= ' <completionposts>0</completionposts>'.PHP_EOL;
|
||||
$xmlContent .= ' <displaywordcount>0</displaywordcount>'.PHP_EOL;
|
||||
$xmlContent .= ' <lockdiscussionafter>0</lockdiscussionafter>'.PHP_EOL;
|
||||
$xmlContent .= ' <grade_forum>0</grade_forum>'.PHP_EOL;
|
||||
|
||||
// Add forum threads
|
||||
$xmlContent .= ' <discussions>'.PHP_EOL;
|
||||
foreach ($forumData['threads'] as $thread) {
|
||||
$xmlContent .= ' <discussion id="'.$thread['id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <name>'.htmlspecialchars($thread['title']).'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <firstpost>'.$thread['firstpost'].'</firstpost>'.PHP_EOL;
|
||||
$xmlContent .= ' <userid>'.$thread['userid'].'</userid>'.PHP_EOL;
|
||||
$xmlContent .= ' <groupid>-1</groupid>'.PHP_EOL;
|
||||
$xmlContent .= ' <assessed>0</assessed>'.PHP_EOL;
|
||||
$xmlContent .= ' <timemodified>'.$thread['timemodified'].'</timemodified>'.PHP_EOL;
|
||||
$xmlContent .= ' <usermodified>'.$thread['usermodified'].'</usermodified>'.PHP_EOL;
|
||||
$xmlContent .= ' <timestart>0</timestart>'.PHP_EOL;
|
||||
$xmlContent .= ' <timeend>0</timeend>'.PHP_EOL;
|
||||
$xmlContent .= ' <pinned>0</pinned>'.PHP_EOL;
|
||||
$xmlContent .= ' <timelocked>0</timelocked>'.PHP_EOL;
|
||||
|
||||
// Add forum posts to the thread
|
||||
$xmlContent .= ' <posts>'.PHP_EOL;
|
||||
foreach ($thread['posts'] as $post) {
|
||||
$xmlContent .= ' <post id="'.$post['id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <parent>'.$post['parent'].'</parent>'.PHP_EOL;
|
||||
$xmlContent .= ' <userid>'.$post['userid'].'</userid>'.PHP_EOL;
|
||||
$xmlContent .= ' <created>'.$post['created'].'</created>'.PHP_EOL;
|
||||
$xmlContent .= ' <modified>'.$post['modified'].'</modified>'.PHP_EOL;
|
||||
$xmlContent .= ' <mailed>'.$post['mailed'].'</mailed>'.PHP_EOL;
|
||||
$xmlContent .= ' <subject>'.htmlspecialchars($post['subject']).'</subject>'.PHP_EOL;
|
||||
$xmlContent .= ' <message>'.htmlspecialchars($post['message']).'</message>'.PHP_EOL;
|
||||
$xmlContent .= ' <messageformat>1</messageformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <messagetrust>0</messagetrust>'.PHP_EOL;
|
||||
$xmlContent .= ' <attachment></attachment>'.PHP_EOL;
|
||||
$xmlContent .= ' <totalscore>0</totalscore>'.PHP_EOL;
|
||||
$xmlContent .= ' <mailnow>0</mailnow>'.PHP_EOL;
|
||||
$xmlContent .= ' <privatereplyto>0</privatereplyto>'.PHP_EOL;
|
||||
$xmlContent .= ' <ratings>'.PHP_EOL;
|
||||
$xmlContent .= ' </ratings>'.PHP_EOL;
|
||||
$xmlContent .= ' </post>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= ' </posts>'.PHP_EOL;
|
||||
$xmlContent .= ' <discussion_subs>'.PHP_EOL;
|
||||
$xmlContent .= ' <discussion_sub id="'.$thread['id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <userid>'.$thread['userid'].'</userid>'.PHP_EOL;
|
||||
$xmlContent .= ' <preference>'.$thread['timemodified'].'</preference>'.PHP_EOL;
|
||||
$xmlContent .= ' </discussion_sub>'.PHP_EOL;
|
||||
$xmlContent .= ' </discussion_subs>'.PHP_EOL;
|
||||
$xmlContent .= ' </discussion>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= ' </discussions>'.PHP_EOL;
|
||||
$xmlContent .= ' </forum>'.PHP_EOL;
|
||||
$xmlContent .= '</activity>';
|
||||
|
||||
$this->createXmlFile('forum', $xmlContent, $forumDir);
|
||||
}
|
||||
}
|
||||
146
main/inc/lib/moodleexport/GlossaryExport.php
Normal file
146
main/inc/lib/moodleexport/GlossaryExport.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace moodleexport;
|
||||
|
||||
/**
|
||||
* Class GlossaryExport.
|
||||
*
|
||||
* Handles the export of glossaries within a course.
|
||||
*/
|
||||
class GlossaryExport extends ActivityExport
|
||||
{
|
||||
/**
|
||||
* Export all glossary terms into a single Moodle glossary.
|
||||
*
|
||||
* @param int $activityId The ID of the glossary.
|
||||
* @param string $exportDir The directory where the glossary will be exported.
|
||||
* @param int $moduleId The ID of the module.
|
||||
* @param int $sectionId The ID of the section.
|
||||
*/
|
||||
public function export($activityId, $exportDir, $moduleId, $sectionId): void
|
||||
{
|
||||
// Prepare the directory where the glossary export will be saved
|
||||
$glossaryDir = $this->prepareActivityDirectory($exportDir, 'glossary', $moduleId);
|
||||
|
||||
// Retrieve glossary data
|
||||
$glossaryData = $this->getData($activityId, $sectionId);
|
||||
|
||||
// Generate XML files for the glossary
|
||||
$this->createGlossaryXml($glossaryData, $glossaryDir);
|
||||
$this->createModuleXml($glossaryData, $glossaryDir);
|
||||
$this->createGradesXml($glossaryData, $glossaryDir);
|
||||
$this->createGradeHistoryXml($glossaryData, $glossaryDir);
|
||||
$this->createInforefXml($glossaryData, $glossaryDir);
|
||||
$this->createRolesXml($glossaryData, $glossaryDir);
|
||||
$this->createCalendarXml($glossaryData, $glossaryDir);
|
||||
$this->createCommentsXml($glossaryData, $glossaryDir);
|
||||
$this->createCompetenciesXml($glossaryData, $glossaryDir);
|
||||
$this->createFiltersXml($glossaryData, $glossaryDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all terms from the course and group them into a single glossary.
|
||||
*/
|
||||
public function getData(int $glossaryId, int $sectionId): ?array
|
||||
{
|
||||
$adminData = MoodleExport::getAdminUserData();
|
||||
$adminId = $adminData['id'];
|
||||
|
||||
$glossaryEntries = [];
|
||||
foreach ($this->course->resources['glossary'] as $glossary) {
|
||||
$glossaryEntries[] = [
|
||||
'id' => $glossary->glossary_id,
|
||||
'userid' => $adminId,
|
||||
'concept' => $glossary->name,
|
||||
'definition' => $glossary->description,
|
||||
'timecreated' => time(),
|
||||
'timemodified' => time(),
|
||||
];
|
||||
}
|
||||
|
||||
// Return the glossary data with all terms included
|
||||
return [
|
||||
'id' => $glossaryId,
|
||||
'moduleid' => $glossaryId,
|
||||
'modulename' => 'glossary',
|
||||
'contextid' => $this->course->info['real_id'],
|
||||
'name' => get_lang('Glossary'),
|
||||
'description' => '',
|
||||
'timecreated' => time(),
|
||||
'timemodified' => time(),
|
||||
'sectionid' => $sectionId,
|
||||
'sectionnumber' => 0,
|
||||
'userid' => $adminId,
|
||||
'entries' => $glossaryEntries,
|
||||
'users' => [$adminId],
|
||||
'files' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the XML file for the glossary with all terms combined.
|
||||
*/
|
||||
private function createGlossaryXml(array $glossaryData, string $glossaryDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<activity id="'.$glossaryData['id'].'" moduleid="'.$glossaryData['moduleid'].'" modulename="'.$glossaryData['modulename'].'" contextid="'.$glossaryData['contextid'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <glossary id="'.$glossaryData['id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <name>'.htmlspecialchars($glossaryData['name']).'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <intro></intro>'.PHP_EOL;
|
||||
$xmlContent .= ' <introformat>1</introformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <allowduplicatedentries>0</allowduplicatedentries>'.PHP_EOL;
|
||||
$xmlContent .= ' <displayformat>dictionary</displayformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <mainglossary>0</mainglossary>'.PHP_EOL;
|
||||
$xmlContent .= ' <showspecial>1</showspecial>'.PHP_EOL;
|
||||
$xmlContent .= ' <showalphabet>1</showalphabet>'.PHP_EOL;
|
||||
$xmlContent .= ' <showall>1</showall>'.PHP_EOL;
|
||||
$xmlContent .= ' <allowcomments>0</allowcomments>'.PHP_EOL;
|
||||
$xmlContent .= ' <allowprintview>1</allowprintview>'.PHP_EOL;
|
||||
$xmlContent .= ' <usedynalink>1</usedynalink>'.PHP_EOL;
|
||||
$xmlContent .= ' <defaultapproval>1</defaultapproval>'.PHP_EOL;
|
||||
$xmlContent .= ' <globalglossary>0</globalglossary>'.PHP_EOL;
|
||||
$xmlContent .= ' <entbypage>10</entbypage>'.PHP_EOL;
|
||||
$xmlContent .= ' <editalways>0</editalways>'.PHP_EOL;
|
||||
$xmlContent .= ' <rsstype>0</rsstype>'.PHP_EOL;
|
||||
$xmlContent .= ' <rssarticles>0</rssarticles>'.PHP_EOL;
|
||||
$xmlContent .= ' <assessed>0</assessed>'.PHP_EOL;
|
||||
$xmlContent .= ' <assesstimestart>0</assesstimestart>'.PHP_EOL;
|
||||
$xmlContent .= ' <assesstimefinish>0</assesstimefinish>'.PHP_EOL;
|
||||
$xmlContent .= ' <scale>100</scale>'.PHP_EOL;
|
||||
$xmlContent .= ' <timecreated>'.$glossaryData['timecreated'].'</timecreated>'.PHP_EOL;
|
||||
$xmlContent .= ' <timemodified>'.$glossaryData['timemodified'].'</timemodified>'.PHP_EOL;
|
||||
$xmlContent .= ' <completionentries>0</completionentries>'.PHP_EOL;
|
||||
$xmlContent .= ' <entries>'.PHP_EOL;
|
||||
|
||||
// Add glossary terms (entries)
|
||||
foreach ($glossaryData['entries'] as $entry) {
|
||||
$xmlContent .= ' <entry id="'.$entry['id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <userid>'.$entry['userid'].'</userid>'.PHP_EOL;
|
||||
$xmlContent .= ' <concept>'.htmlspecialchars($entry['concept']).'</concept>'.PHP_EOL;
|
||||
$xmlContent .= ' <definition><![CDATA['.$entry['definition'].']]></definition>'.PHP_EOL;
|
||||
$xmlContent .= ' <definitionformat>1</definitionformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <definitiontrust>0</definitiontrust>'.PHP_EOL;
|
||||
$xmlContent .= ' <attachment></attachment>'.PHP_EOL;
|
||||
$xmlContent .= ' <timecreated>'.$entry['timecreated'].'</timecreated>'.PHP_EOL;
|
||||
$xmlContent .= ' <timemodified>'.$entry['timemodified'].'</timemodified>'.PHP_EOL;
|
||||
$xmlContent .= ' <teacherentry>1</teacherentry>'.PHP_EOL;
|
||||
$xmlContent .= ' <sourceglossaryid>0</sourceglossaryid>'.PHP_EOL;
|
||||
$xmlContent .= ' <usedynalink>0</usedynalink>'.PHP_EOL;
|
||||
$xmlContent .= ' <casesensitive>0</casesensitive>'.PHP_EOL;
|
||||
$xmlContent .= ' <fullmatch>0</fullmatch>'.PHP_EOL;
|
||||
$xmlContent .= ' <approved>1</approved>'.PHP_EOL;
|
||||
$xmlContent .= ' <ratings>'.PHP_EOL;
|
||||
$xmlContent .= ' </ratings>'.PHP_EOL;
|
||||
$xmlContent .= ' </entry>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= ' </entries>'.PHP_EOL;
|
||||
$xmlContent .= ' <entriestags></entriestags>'.PHP_EOL;
|
||||
$xmlContent .= ' <categories></categories>'.PHP_EOL;
|
||||
$xmlContent .= ' </glossary>'.PHP_EOL;
|
||||
$xmlContent .= '</activity>';
|
||||
|
||||
$this->createXmlFile('glossary', $xmlContent, $glossaryDir);
|
||||
}
|
||||
}
|
||||
754
main/inc/lib/moodleexport/MoodleExport.php
Normal file
754
main/inc/lib/moodleexport/MoodleExport.php
Normal file
@@ -0,0 +1,754 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace moodleexport;
|
||||
|
||||
use Exception;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use ZipArchive;
|
||||
|
||||
/**
|
||||
* Class MoodleExport.
|
||||
* Handles the export of a Moodle course in .mbz format.
|
||||
*
|
||||
* @package moodleexport
|
||||
*/
|
||||
class MoodleExport
|
||||
{
|
||||
private $course;
|
||||
private static $adminUserData = [];
|
||||
|
||||
/**
|
||||
* Constructor to initialize the course object.
|
||||
*/
|
||||
public function __construct(object $course)
|
||||
{
|
||||
$this->course = $course;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the Moodle course in .mbz format.
|
||||
*/
|
||||
public function export(string $courseId, string $exportDir, int $version)
|
||||
{
|
||||
$tempDir = api_get_path(SYS_ARCHIVE_PATH).$exportDir;
|
||||
|
||||
if (!is_dir($tempDir)) {
|
||||
if (!mkdir($tempDir, api_get_permissions_for_new_directories(), true)) {
|
||||
throw new Exception(get_lang('ErrorCreatingDirectory'));
|
||||
}
|
||||
}
|
||||
|
||||
$courseInfo = api_get_course_info($courseId);
|
||||
if (!$courseInfo) {
|
||||
throw new Exception(get_lang('CourseNotFound'));
|
||||
}
|
||||
|
||||
// Generate the moodle_backup.xml
|
||||
$this->createMoodleBackupXml($tempDir, $version);
|
||||
|
||||
// Get the activities from the course
|
||||
$activities = $this->getActivities();
|
||||
|
||||
// Export course-related files
|
||||
$courseExport = new CourseExport($this->course, $activities);
|
||||
$courseExport->exportCourse($tempDir);
|
||||
|
||||
// Export files-related data and actual files
|
||||
$fileExport = new FileExport($this->course);
|
||||
$filesData = $fileExport->getFilesData();
|
||||
$fileExport->exportFiles($filesData, $tempDir);
|
||||
|
||||
// Export sections of the course
|
||||
$this->exportSections($tempDir);
|
||||
|
||||
// Export all root XML files
|
||||
$this->exportRootXmlFiles($tempDir);
|
||||
|
||||
// Compress everything into a .mbz (ZIP) file
|
||||
$exportedFile = $this->createMbzFile($tempDir);
|
||||
|
||||
// Clean up temporary directory
|
||||
$this->cleanupTempDir($tempDir);
|
||||
|
||||
return $exportedFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export questions data to XML file.
|
||||
*/
|
||||
public function exportQuestionsXml(array $questionsData, string $exportDir): void
|
||||
{
|
||||
$quizExport = new QuizExport($this->course);
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<question_categories>'.PHP_EOL;
|
||||
|
||||
foreach ($questionsData as $quiz) {
|
||||
$categoryId = $quiz['questions'][0]['questioncategoryid'] ?? '0';
|
||||
|
||||
$xmlContent .= ' <question_category id="'.$categoryId.'">'.PHP_EOL;
|
||||
$xmlContent .= ' <name>Default for '.htmlspecialchars($quiz['name'] ?? 'Unknown').'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <contextid>'.($quiz['contextid'] ?? '0').'</contextid>'.PHP_EOL;
|
||||
$xmlContent .= ' <contextlevel>70</contextlevel>'.PHP_EOL;
|
||||
$xmlContent .= ' <contextinstanceid>'.($quiz['moduleid'] ?? '0').'</contextinstanceid>'.PHP_EOL;
|
||||
$xmlContent .= ' <info>The default category for questions shared in context "'.htmlspecialchars($quiz['name'] ?? 'Unknown').'".</info>'.PHP_EOL;
|
||||
$xmlContent .= ' <infoformat>0</infoformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <stamp>moodle+'.time().'+CATEGORYSTAMP</stamp>'.PHP_EOL;
|
||||
$xmlContent .= ' <parent>0</parent>'.PHP_EOL;
|
||||
$xmlContent .= ' <sortorder>999</sortorder>'.PHP_EOL;
|
||||
$xmlContent .= ' <idnumber>$@NULL@$</idnumber>'.PHP_EOL;
|
||||
$xmlContent .= ' <questions>'.PHP_EOL;
|
||||
|
||||
foreach ($quiz['questions'] as $question) {
|
||||
$xmlContent .= $quizExport->exportQuestion($question);
|
||||
}
|
||||
|
||||
$xmlContent .= ' </questions>'.PHP_EOL;
|
||||
$xmlContent .= ' </question_category>'.PHP_EOL;
|
||||
}
|
||||
|
||||
$xmlContent .= '</question_categories>';
|
||||
file_put_contents($exportDir.'/questions.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the admin user data.
|
||||
*/
|
||||
public function setAdminUserData(int $id, string $username, string $email): void
|
||||
{
|
||||
self::$adminUserData = [
|
||||
'id' => $id,
|
||||
'contextid' => $id,
|
||||
'username' => $username,
|
||||
'idnumber' => '',
|
||||
'email' => $email,
|
||||
'phone1' => '',
|
||||
'phone2' => '',
|
||||
'institution' => '',
|
||||
'department' => '',
|
||||
'address' => '',
|
||||
'city' => 'London',
|
||||
'country' => 'GB',
|
||||
'lastip' => '127.0.0.1',
|
||||
'picture' => '0',
|
||||
'description' => '',
|
||||
'descriptionformat' => 1,
|
||||
'imagealt' => '$@NULL@$',
|
||||
'auth' => 'manual',
|
||||
'firstname' => 'Admin',
|
||||
'lastname' => 'User',
|
||||
'confirmed' => 1,
|
||||
'policyagreed' => 0,
|
||||
'deleted' => 0,
|
||||
'lang' => 'en',
|
||||
'theme' => '',
|
||||
'timezone' => 99,
|
||||
'firstaccess' => time(),
|
||||
'lastaccess' => time() - (60 * 60 * 24 * 7),
|
||||
'lastlogin' => time() - (60 * 60 * 24 * 2),
|
||||
'currentlogin' => time(),
|
||||
'mailformat' => 1,
|
||||
'maildigest' => 0,
|
||||
'maildisplay' => 1,
|
||||
'autosubscribe' => 1,
|
||||
'trackforums' => 0,
|
||||
'timecreated' => time(),
|
||||
'timemodified' => time(),
|
||||
'trustbitmask' => 0,
|
||||
'preferences' => [
|
||||
['name' => 'core_message_migrate_data', 'value' => 1],
|
||||
['name' => 'auth_manual_passwordupdatetime', 'value' => time()],
|
||||
['name' => 'email_bounce_count', 'value' => 1],
|
||||
['name' => 'email_send_count', 'value' => 1],
|
||||
['name' => 'login_failed_count_since_success', 'value' => 0],
|
||||
['name' => 'filepicker_recentrepository', 'value' => 5],
|
||||
['name' => 'filepicker_recentlicense', 'value' => 'unknown'],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns hardcoded data for the admin user.
|
||||
*/
|
||||
public static function getAdminUserData(): array
|
||||
{
|
||||
return self::$adminUserData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export root XML files such as badges, completion, gradebook, etc.
|
||||
*/
|
||||
private function exportRootXmlFiles(string $exportDir): void
|
||||
{
|
||||
$this->exportBadgesXml($exportDir);
|
||||
$this->exportCompletionXml($exportDir);
|
||||
$this->exportGradebookXml($exportDir);
|
||||
$this->exportGradeHistoryXml($exportDir);
|
||||
$this->exportGroupsXml($exportDir);
|
||||
$this->exportOutcomesXml($exportDir);
|
||||
|
||||
// Export quizzes and their questions
|
||||
$activities = $this->getActivities();
|
||||
$questionsData = [];
|
||||
foreach ($activities as $activity) {
|
||||
if ($activity['modulename'] === 'quiz') {
|
||||
$quizExport = new QuizExport($this->course);
|
||||
$quizData = $quizExport->getData($activity['id'], $activity['sectionid']);
|
||||
$questionsData[] = $quizData;
|
||||
}
|
||||
}
|
||||
$this->exportQuestionsXml($questionsData, $exportDir);
|
||||
|
||||
$this->exportRolesXml($exportDir);
|
||||
$this->exportScalesXml($exportDir);
|
||||
$this->exportUsersXml($exportDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the moodle_backup.xml file with the required course details.
|
||||
*/
|
||||
private function createMoodleBackupXml(string $destinationDir, int $version): void
|
||||
{
|
||||
// Generate course information and backup metadata
|
||||
$courseInfo = api_get_course_info($this->course->code);
|
||||
$backupId = md5(uniqid(mt_rand(), true));
|
||||
$siteHash = md5(uniqid(mt_rand(), true));
|
||||
$wwwRoot = api_get_path(WEB_PATH);
|
||||
|
||||
$courseStartDate = strtotime($courseInfo['creation_date']);
|
||||
$courseEndDate = $courseStartDate + (365 * 24 * 60 * 60);
|
||||
|
||||
// Build the XML content for the backup
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<moodle_backup>'.PHP_EOL;
|
||||
$xmlContent .= ' <information>'.PHP_EOL;
|
||||
|
||||
$xmlContent .= ' <name>backup-'.htmlspecialchars($courseInfo['code']).'.mbz</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <moodle_version>'.($version === 3 ? '2021051718' : '2022041900').'</moodle_version>'.PHP_EOL;
|
||||
$xmlContent .= ' <moodle_release>'.($version === 3 ? '3.11.18 (Build: 20231211)' : '4.x version here').'</moodle_release>'.PHP_EOL;
|
||||
$xmlContent .= ' <backup_version>'.($version === 3 ? '2021051700' : '2022041900').'</backup_version>'.PHP_EOL;
|
||||
$xmlContent .= ' <backup_release>'.($version === 3 ? '3.11' : '4.x').'</backup_release>'.PHP_EOL;
|
||||
$xmlContent .= ' <backup_date>'.time().'</backup_date>'.PHP_EOL;
|
||||
$xmlContent .= ' <mnet_remoteusers>0</mnet_remoteusers>'.PHP_EOL;
|
||||
$xmlContent .= ' <include_files>1</include_files>'.PHP_EOL;
|
||||
$xmlContent .= ' <include_file_references_to_external_content>0</include_file_references_to_external_content>'.PHP_EOL;
|
||||
$xmlContent .= ' <original_wwwroot>'.$wwwRoot.'</original_wwwroot>'.PHP_EOL;
|
||||
$xmlContent .= ' <original_site_identifier_hash>'.$siteHash.'</original_site_identifier_hash>'.PHP_EOL;
|
||||
$xmlContent .= ' <original_course_id>'.htmlspecialchars($courseInfo['real_id']).'</original_course_id>'.PHP_EOL;
|
||||
$xmlContent .= ' <original_course_format>'.get_lang('Topics').'</original_course_format>'.PHP_EOL;
|
||||
$xmlContent .= ' <original_course_fullname>'.htmlspecialchars($courseInfo['title']).'</original_course_fullname>'.PHP_EOL;
|
||||
$xmlContent .= ' <original_course_shortname>'.htmlspecialchars($courseInfo['code']).'</original_course_shortname>'.PHP_EOL;
|
||||
$xmlContent .= ' <original_course_startdate>'.$courseStartDate.'</original_course_startdate>'.PHP_EOL;
|
||||
$xmlContent .= ' <original_course_enddate>'.$courseEndDate.'</original_course_enddate>'.PHP_EOL;
|
||||
$xmlContent .= ' <original_course_contextid>'.$courseInfo['real_id'].'</original_course_contextid>'.PHP_EOL;
|
||||
$xmlContent .= ' <original_system_contextid>'.api_get_current_access_url_id().'</original_system_contextid>'.PHP_EOL;
|
||||
|
||||
$xmlContent .= ' <details>'.PHP_EOL;
|
||||
$xmlContent .= ' <detail backup_id="'.$backupId.'">'.PHP_EOL;
|
||||
$xmlContent .= ' <type>course</type>'.PHP_EOL;
|
||||
$xmlContent .= ' <format>moodle2</format>'.PHP_EOL;
|
||||
$xmlContent .= ' <interactive>1</interactive>'.PHP_EOL;
|
||||
$xmlContent .= ' <mode>10</mode>'.PHP_EOL;
|
||||
$xmlContent .= ' <execution>1</execution>'.PHP_EOL;
|
||||
$xmlContent .= ' <executiontime>0</executiontime>'.PHP_EOL;
|
||||
$xmlContent .= ' </detail>'.PHP_EOL;
|
||||
$xmlContent .= ' </details>'.PHP_EOL;
|
||||
|
||||
// Contents with activities and sections
|
||||
$xmlContent .= ' <contents>'.PHP_EOL;
|
||||
|
||||
// Export sections dynamically and add them to the XML
|
||||
$sections = $this->getSections();
|
||||
if (!empty($sections)) {
|
||||
$xmlContent .= ' <sections>'.PHP_EOL;
|
||||
foreach ($sections as $section) {
|
||||
$xmlContent .= ' <section>'.PHP_EOL;
|
||||
$xmlContent .= ' <sectionid>'.$section['id'].'</sectionid>'.PHP_EOL;
|
||||
$xmlContent .= ' <title>'.htmlspecialchars($section['name']).'</title>'.PHP_EOL;
|
||||
$xmlContent .= ' <directory>sections/section_'.$section['id'].'</directory>'.PHP_EOL;
|
||||
$xmlContent .= ' </section>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= ' </sections>'.PHP_EOL;
|
||||
}
|
||||
|
||||
$activities = $this->getActivities();
|
||||
if (!empty($activities)) {
|
||||
$xmlContent .= ' <activities>'.PHP_EOL;
|
||||
foreach ($activities as $activity) {
|
||||
$xmlContent .= ' <activity>'.PHP_EOL;
|
||||
$xmlContent .= ' <moduleid>'.$activity['moduleid'].'</moduleid>'.PHP_EOL;
|
||||
$xmlContent .= ' <sectionid>'.$activity['sectionid'].'</sectionid>'.PHP_EOL;
|
||||
$xmlContent .= ' <modulename>'.htmlspecialchars($activity['modulename']).'</modulename>'.PHP_EOL;
|
||||
$xmlContent .= ' <title>'.htmlspecialchars($activity['title']).'</title>'.PHP_EOL;
|
||||
$xmlContent .= ' <directory>activities/'.$activity['modulename'].'_'.$activity['moduleid'].'</directory>'.PHP_EOL;
|
||||
$xmlContent .= ' </activity>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= ' </activities>'.PHP_EOL;
|
||||
}
|
||||
|
||||
// Course directory
|
||||
$xmlContent .= ' <course>'.PHP_EOL;
|
||||
$xmlContent .= ' <courseid>'.$courseInfo['real_id'].'</courseid>'.PHP_EOL;
|
||||
$xmlContent .= ' <title>'.htmlspecialchars($courseInfo['title']).'</title>'.PHP_EOL;
|
||||
$xmlContent .= ' <directory>course</directory>'.PHP_EOL;
|
||||
$xmlContent .= ' </course>'.PHP_EOL;
|
||||
|
||||
$xmlContent .= ' </contents>'.PHP_EOL;
|
||||
|
||||
// Backup settings
|
||||
$xmlContent .= ' <settings>'.PHP_EOL;
|
||||
$settings = $this->exportBackupSettings($sections, $activities);
|
||||
foreach ($settings as $setting) {
|
||||
$xmlContent .= ' <setting>'.PHP_EOL;
|
||||
$xmlContent .= ' <level>'.htmlspecialchars($setting['level']).'</level>'.PHP_EOL;
|
||||
$xmlContent .= ' <name>'.htmlspecialchars($setting['name']).'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <value>'.$setting['value'].'</value>'.PHP_EOL;
|
||||
if (isset($setting['section'])) {
|
||||
$xmlContent .= ' <section>'.htmlspecialchars($setting['section']).'</section>'.PHP_EOL;
|
||||
}
|
||||
if (isset($setting['activity'])) {
|
||||
$xmlContent .= ' <activity>'.htmlspecialchars($setting['activity']).'</activity>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= ' </setting>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= ' </settings>'.PHP_EOL;
|
||||
|
||||
$xmlContent .= ' </information>'.PHP_EOL;
|
||||
$xmlContent .= '</moodle_backup>';
|
||||
|
||||
$xmlFile = $destinationDir.'/moodle_backup.xml';
|
||||
file_put_contents($xmlFile, $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all sections from the course.
|
||||
*/
|
||||
private function getSections(): array
|
||||
{
|
||||
$sectionExport = new SectionExport($this->course);
|
||||
$sections = [];
|
||||
|
||||
foreach ($this->course->resources[RESOURCE_LEARNPATH] as $learnpath) {
|
||||
if ($learnpath->lp_type == '1') {
|
||||
$sections[] = $sectionExport->getSectionData($learnpath);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a general section for resources without a lesson
|
||||
$sections[] = [
|
||||
'id' => 0,
|
||||
'number' => 0,
|
||||
'name' => get_lang('General'),
|
||||
'summary' => get_lang('GeneralResourcesCourse'),
|
||||
'sequence' => 0,
|
||||
'visible' => 1,
|
||||
'timemodified' => time(),
|
||||
'activities' => $sectionExport->getActivitiesForGeneral(),
|
||||
];
|
||||
|
||||
return $sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all activities from the course.
|
||||
*/
|
||||
private function getActivities(): array
|
||||
{
|
||||
$activities = [];
|
||||
$glossaryAdded = false;
|
||||
|
||||
foreach ($this->course->resources as $resourceType => $resources) {
|
||||
foreach ($resources as $resource) {
|
||||
$exportClass = null;
|
||||
$moduleName = '';
|
||||
$title = '';
|
||||
$id = 0;
|
||||
|
||||
// Handle quizzes
|
||||
if ($resourceType === RESOURCE_QUIZ && $resource->obj->iid > 0) {
|
||||
$exportClass = QuizExport::class;
|
||||
$moduleName = 'quiz';
|
||||
$id = $resource->obj->iid;
|
||||
$title = $resource->obj->title;
|
||||
}
|
||||
// Handle links
|
||||
if ($resourceType === RESOURCE_LINK && $resource->source_id > 0) {
|
||||
$exportClass = UrlExport::class;
|
||||
$moduleName = 'url';
|
||||
$id = $resource->source_id;
|
||||
$title = $resource->title;
|
||||
}
|
||||
// Handle glossaries
|
||||
elseif ($resourceType === RESOURCE_GLOSSARY && $resource->glossary_id > 0 && !$glossaryAdded) {
|
||||
$exportClass = GlossaryExport::class;
|
||||
$moduleName = 'glossary';
|
||||
$id = 1;
|
||||
$title = get_lang('Glossary');
|
||||
$glossaryAdded = true;
|
||||
}
|
||||
// Handle forums
|
||||
elseif ($resourceType === RESOURCE_FORUM && $resource->source_id > 0) {
|
||||
$exportClass = ForumExport::class;
|
||||
$moduleName = 'forum';
|
||||
$id = $resource->obj->iid;
|
||||
$title = $resource->obj->forum_title;
|
||||
}
|
||||
// Handle documents (HTML pages)
|
||||
elseif ($resourceType === RESOURCE_DOCUMENT && $resource->source_id > 0) {
|
||||
$document = \DocumentManager::get_document_data_by_id($resource->source_id, $this->course->code);
|
||||
if ('html' === pathinfo($document['path'], PATHINFO_EXTENSION)) {
|
||||
$exportClass = PageExport::class;
|
||||
$moduleName = 'page';
|
||||
$id = $resource->source_id;
|
||||
$title = $document['title'];
|
||||
} elseif ('file' === $resource->file_type) {
|
||||
$exportClass = ResourceExport::class;
|
||||
$moduleName = 'resource';
|
||||
$id = $resource->source_id;
|
||||
$title = $resource->title;
|
||||
} elseif ('folder' === $resource->file_type) {
|
||||
$exportClass = FolderExport::class;
|
||||
$moduleName = 'folder';
|
||||
$id = $resource->source_id;
|
||||
$title = $resource->title;
|
||||
}
|
||||
}
|
||||
// Handle assignments (work)
|
||||
elseif ($resourceType === RESOURCE_WORK && $resource->source_id > 0) {
|
||||
$exportClass = AssignExport::class;
|
||||
$moduleName = 'assign';
|
||||
$id = $resource->source_id;
|
||||
$title = $resource->params['title'] ?? '';
|
||||
}
|
||||
// Handle feedback (survey)
|
||||
elseif ($resourceType === RESOURCE_SURVEY && $resource->source_id > 0) {
|
||||
$exportClass = FeedbackExport::class;
|
||||
$moduleName = 'feedback';
|
||||
$id = $resource->source_id;
|
||||
$title = $resource->params['title'] ?? '';
|
||||
}
|
||||
|
||||
// Add the activity if the class and module name are set
|
||||
if ($exportClass && $moduleName) {
|
||||
$exportInstance = new $exportClass($this->course);
|
||||
$activities[] = [
|
||||
'id' => $id,
|
||||
'sectionid' => $exportInstance->getSectionIdForActivity($id, $resourceType),
|
||||
'modulename' => $moduleName,
|
||||
'moduleid' => $id,
|
||||
'title' => $title,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $activities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the sections of the course.
|
||||
*/
|
||||
private function exportSections(string $exportDir): void
|
||||
{
|
||||
$sections = $this->getSections();
|
||||
|
||||
foreach ($sections as $section) {
|
||||
$sectionExport = new SectionExport($this->course);
|
||||
$sectionExport->exportSection($section['id'], $exportDir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a .mbz (ZIP) file from the exported data.
|
||||
*/
|
||||
private function createMbzFile(string $sourceDir): string
|
||||
{
|
||||
$zip = new ZipArchive();
|
||||
$zipFile = $sourceDir.'.mbz';
|
||||
|
||||
if ($zip->open($zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
|
||||
throw new Exception(get_lang('ErrorCreatingZip'));
|
||||
}
|
||||
|
||||
$files = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($sourceDir),
|
||||
RecursiveIteratorIterator::LEAVES_ONLY
|
||||
);
|
||||
|
||||
foreach ($files as $file) {
|
||||
if (!$file->isDir()) {
|
||||
$filePath = $file->getRealPath();
|
||||
$relativePath = substr($filePath, strlen($sourceDir) + 1);
|
||||
|
||||
if (!$zip->addFile($filePath, $relativePath)) {
|
||||
throw new Exception(get_lang('ErrorAddingFileToZip').": $relativePath");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$zip->close()) {
|
||||
throw new Exception(get_lang('ErrorClosingZip'));
|
||||
}
|
||||
|
||||
return $zipFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the temporary directory used for export.
|
||||
*/
|
||||
private function cleanupTempDir(string $dir): void
|
||||
{
|
||||
$this->recursiveDelete($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively delete a directory and its contents.
|
||||
*/
|
||||
private function recursiveDelete(string $dir): void
|
||||
{
|
||||
$files = array_diff(scandir($dir), ['.', '..']);
|
||||
foreach ($files as $file) {
|
||||
$path = "$dir/$file";
|
||||
is_dir($path) ? $this->recursiveDelete($path) : unlink($path);
|
||||
}
|
||||
rmdir($dir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export badges data to XML file.
|
||||
*/
|
||||
private function exportBadgesXml(string $exportDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<badges>'.PHP_EOL;
|
||||
$xmlContent .= '</badges>';
|
||||
file_put_contents($exportDir.'/badges.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export course completion data to XML file.
|
||||
*/
|
||||
private function exportCompletionXml(string $exportDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<completions>'.PHP_EOL;
|
||||
$xmlContent .= '</completions>';
|
||||
file_put_contents($exportDir.'/completion.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export gradebook data to XML file.
|
||||
*/
|
||||
private function exportGradebookXml(string $exportDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<gradebook>'.PHP_EOL;
|
||||
$xmlContent .= '</gradebook>';
|
||||
file_put_contents($exportDir.'/gradebook.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export grade history data to XML file.
|
||||
*/
|
||||
private function exportGradeHistoryXml(string $exportDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<grade_history>'.PHP_EOL;
|
||||
$xmlContent .= '</grade_history>';
|
||||
file_put_contents($exportDir.'/grade_history.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export groups data to XML file.
|
||||
*/
|
||||
private function exportGroupsXml(string $exportDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<groups>'.PHP_EOL;
|
||||
$xmlContent .= '</groups>';
|
||||
file_put_contents($exportDir.'/groups.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export outcomes data to XML file.
|
||||
*/
|
||||
private function exportOutcomesXml(string $exportDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<outcomes>'.PHP_EOL;
|
||||
$xmlContent .= '</outcomes>';
|
||||
file_put_contents($exportDir.'/outcomes.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export roles data to XML file.
|
||||
*/
|
||||
private function exportRolesXml(string $exportDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<roles_definition>'.PHP_EOL;
|
||||
$xmlContent .= ' <role id="5">'.PHP_EOL;
|
||||
$xmlContent .= ' <name></name>'.PHP_EOL;
|
||||
$xmlContent .= ' <shortname>student</shortname>'.PHP_EOL;
|
||||
$xmlContent .= ' <nameincourse>$@NULL@$</nameincourse>'.PHP_EOL;
|
||||
$xmlContent .= ' <description></description>'.PHP_EOL;
|
||||
$xmlContent .= ' <sortorder>5</sortorder>'.PHP_EOL;
|
||||
$xmlContent .= ' <archetype>student</archetype>'.PHP_EOL;
|
||||
$xmlContent .= ' </role>'.PHP_EOL;
|
||||
$xmlContent .= '</roles_definition>'.PHP_EOL;
|
||||
|
||||
file_put_contents($exportDir.'/roles.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export scales data to XML file.
|
||||
*/
|
||||
private function exportScalesXml(string $exportDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<scales>'.PHP_EOL;
|
||||
$xmlContent .= '</scales>';
|
||||
file_put_contents($exportDir.'/scales.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the user XML with admin user data.
|
||||
*/
|
||||
private function exportUsersXml(string $exportDir): void
|
||||
{
|
||||
$adminData = self::getAdminUserData();
|
||||
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<users>'.PHP_EOL;
|
||||
$xmlContent .= ' <user id="'.$adminData['id'].'" contextid="'.$adminData['contextid'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <username>'.$adminData['username'].'</username>'.PHP_EOL;
|
||||
$xmlContent .= ' <idnumber>'.$adminData['idnumber'].'</idnumber>'.PHP_EOL;
|
||||
$xmlContent .= ' <email>'.$adminData['email'].'</email>'.PHP_EOL;
|
||||
$xmlContent .= ' <phone1>'.$adminData['phone1'].'</phone1>'.PHP_EOL;
|
||||
$xmlContent .= ' <phone2>'.$adminData['phone2'].'</phone2>'.PHP_EOL;
|
||||
$xmlContent .= ' <institution>'.$adminData['institution'].'</institution>'.PHP_EOL;
|
||||
$xmlContent .= ' <department>'.$adminData['department'].'</department>'.PHP_EOL;
|
||||
$xmlContent .= ' <address>'.$adminData['address'].'</address>'.PHP_EOL;
|
||||
$xmlContent .= ' <city>'.$adminData['city'].'</city>'.PHP_EOL;
|
||||
$xmlContent .= ' <country>'.$adminData['country'].'</country>'.PHP_EOL;
|
||||
$xmlContent .= ' <lastip>'.$adminData['lastip'].'</lastip>'.PHP_EOL;
|
||||
$xmlContent .= ' <picture>'.$adminData['picture'].'</picture>'.PHP_EOL;
|
||||
$xmlContent .= ' <description>'.$adminData['description'].'</description>'.PHP_EOL;
|
||||
$xmlContent .= ' <descriptionformat>'.$adminData['descriptionformat'].'</descriptionformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <imagealt>'.$adminData['imagealt'].'</imagealt>'.PHP_EOL;
|
||||
$xmlContent .= ' <auth>'.$adminData['auth'].'</auth>'.PHP_EOL;
|
||||
$xmlContent .= ' <firstname>'.$adminData['firstname'].'</firstname>'.PHP_EOL;
|
||||
$xmlContent .= ' <lastname>'.$adminData['lastname'].'</lastname>'.PHP_EOL;
|
||||
$xmlContent .= ' <confirmed>'.$adminData['confirmed'].'</confirmed>'.PHP_EOL;
|
||||
$xmlContent .= ' <policyagreed>'.$adminData['policyagreed'].'</policyagreed>'.PHP_EOL;
|
||||
$xmlContent .= ' <deleted>'.$adminData['deleted'].'</deleted>'.PHP_EOL;
|
||||
$xmlContent .= ' <lang>'.$adminData['lang'].'</lang>'.PHP_EOL;
|
||||
$xmlContent .= ' <theme>'.$adminData['theme'].'</theme>'.PHP_EOL;
|
||||
$xmlContent .= ' <timezone>'.$adminData['timezone'].'</timezone>'.PHP_EOL;
|
||||
$xmlContent .= ' <firstaccess>'.$adminData['firstaccess'].'</firstaccess>'.PHP_EOL;
|
||||
$xmlContent .= ' <lastaccess>'.$adminData['lastaccess'].'</lastaccess>'.PHP_EOL;
|
||||
$xmlContent .= ' <lastlogin>'.$adminData['lastlogin'].'</lastlogin>'.PHP_EOL;
|
||||
$xmlContent .= ' <currentlogin>'.$adminData['currentlogin'].'</currentlogin>'.PHP_EOL;
|
||||
$xmlContent .= ' <mailformat>'.$adminData['mailformat'].'</mailformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <maildigest>'.$adminData['maildigest'].'</maildigest>'.PHP_EOL;
|
||||
$xmlContent .= ' <maildisplay>'.$adminData['maildisplay'].'</maildisplay>'.PHP_EOL;
|
||||
$xmlContent .= ' <autosubscribe>'.$adminData['autosubscribe'].'</autosubscribe>'.PHP_EOL;
|
||||
$xmlContent .= ' <trackforums>'.$adminData['trackforums'].'</trackforums>'.PHP_EOL;
|
||||
$xmlContent .= ' <timecreated>'.$adminData['timecreated'].'</timecreated>'.PHP_EOL;
|
||||
$xmlContent .= ' <timemodified>'.$adminData['timemodified'].'</timemodified>'.PHP_EOL;
|
||||
$xmlContent .= ' <trustbitmask>'.$adminData['trustbitmask'].'</trustbitmask>'.PHP_EOL;
|
||||
|
||||
// Preferences
|
||||
if (isset($adminData['preferences']) && is_array($adminData['preferences'])) {
|
||||
$xmlContent .= ' <preferences>'.PHP_EOL;
|
||||
foreach ($adminData['preferences'] as $preference) {
|
||||
$xmlContent .= ' <preference>'.PHP_EOL;
|
||||
$xmlContent .= ' <name>'.htmlspecialchars($preference['name']).'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <value>'.htmlspecialchars($preference['value']).'</value>'.PHP_EOL;
|
||||
$xmlContent .= ' </preference>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= ' </preferences>'.PHP_EOL;
|
||||
} else {
|
||||
$xmlContent .= ' <preferences></preferences>'.PHP_EOL;
|
||||
}
|
||||
|
||||
// Roles (empty for now)
|
||||
$xmlContent .= ' <roles>'.PHP_EOL;
|
||||
$xmlContent .= ' <role_overrides></role_overrides>'.PHP_EOL;
|
||||
$xmlContent .= ' <role_assignments></role_assignments>'.PHP_EOL;
|
||||
$xmlContent .= ' </roles>'.PHP_EOL;
|
||||
|
||||
$xmlContent .= ' </user>'.PHP_EOL;
|
||||
$xmlContent .= '</users>';
|
||||
|
||||
// Save the content to the users.xml file
|
||||
file_put_contents($exportDir.'/users.xml', $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the backup settings, including dynamic settings for sections and activities.
|
||||
*/
|
||||
private function exportBackupSettings(array $sections, array $activities): array
|
||||
{
|
||||
// root-level settings
|
||||
$settings = [
|
||||
['level' => 'root', 'name' => 'filename', 'value' => 'backup-moodle-course-'.time().'.mbz'],
|
||||
['level' => 'root', 'name' => 'imscc11', 'value' => '0'],
|
||||
['level' => 'root', 'name' => 'users', 'value' => '1'],
|
||||
['level' => 'root', 'name' => 'anonymize', 'value' => '0'],
|
||||
['level' => 'root', 'name' => 'role_assignments', 'value' => '1'],
|
||||
['level' => 'root', 'name' => 'activities', 'value' => '1'],
|
||||
['level' => 'root', 'name' => 'blocks', 'value' => '1'],
|
||||
['level' => 'root', 'name' => 'files', 'value' => '1'],
|
||||
['level' => 'root', 'name' => 'filters', 'value' => '1'],
|
||||
['level' => 'root', 'name' => 'comments', 'value' => '1'],
|
||||
['level' => 'root', 'name' => 'badges', 'value' => '1'],
|
||||
['level' => 'root', 'name' => 'calendarevents', 'value' => '1'],
|
||||
['level' => 'root', 'name' => 'userscompletion', 'value' => '1'],
|
||||
['level' => 'root', 'name' => 'logs', 'value' => '0'],
|
||||
['level' => 'root', 'name' => 'grade_histories', 'value' => '0'],
|
||||
['level' => 'root', 'name' => 'questionbank', 'value' => '1'],
|
||||
['level' => 'root', 'name' => 'groups', 'value' => '1'],
|
||||
['level' => 'root', 'name' => 'competencies', 'value' => '0'],
|
||||
['level' => 'root', 'name' => 'customfield', 'value' => '1'],
|
||||
['level' => 'root', 'name' => 'contentbankcontent', 'value' => '1'],
|
||||
['level' => 'root', 'name' => 'legacyfiles', 'value' => '1'],
|
||||
];
|
||||
|
||||
// section-level settings
|
||||
foreach ($sections as $section) {
|
||||
$settings[] = [
|
||||
'level' => 'section',
|
||||
'section' => 'section_'.$section['id'],
|
||||
'name' => 'section_'.$section['id'].'_included',
|
||||
'value' => '1',
|
||||
];
|
||||
$settings[] = [
|
||||
'level' => 'section',
|
||||
'section' => 'section_'.$section['id'],
|
||||
'name' => 'section_'.$section['id'].'_userinfo',
|
||||
'value' => '1',
|
||||
];
|
||||
}
|
||||
|
||||
// activity-level settings
|
||||
foreach ($activities as $activity) {
|
||||
$settings[] = [
|
||||
'level' => 'activity',
|
||||
'activity' => $activity['modulename'].'_'.$activity['moduleid'],
|
||||
'name' => $activity['modulename'].'_'.$activity['moduleid'].'_included',
|
||||
'value' => '1',
|
||||
];
|
||||
$settings[] = [
|
||||
'level' => 'activity',
|
||||
'activity' => $activity['modulename'].'_'.$activity['moduleid'],
|
||||
'name' => $activity['modulename'].'_'.$activity['moduleid'].'_userinfo',
|
||||
'value' => '1',
|
||||
];
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
}
|
||||
109
main/inc/lib/moodleexport/PageExport.php
Normal file
109
main/inc/lib/moodleexport/PageExport.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace moodleexport;
|
||||
|
||||
/**
|
||||
* Class PageExport.
|
||||
*
|
||||
* Handles the export of pages within a course.
|
||||
*/
|
||||
class PageExport extends ActivityExport
|
||||
{
|
||||
/**
|
||||
* Export a page to the specified directory.
|
||||
*
|
||||
* @param int $activityId The ID of the page.
|
||||
* @param string $exportDir The directory where the page will be exported.
|
||||
* @param int $moduleId The ID of the module.
|
||||
* @param int $sectionId The ID of the section.
|
||||
*/
|
||||
public function export($activityId, $exportDir, $moduleId, $sectionId): void
|
||||
{
|
||||
// Prepare the directory where the page export will be saved
|
||||
$pageDir = $this->prepareActivityDirectory($exportDir, 'page', $moduleId);
|
||||
|
||||
// Retrieve page data
|
||||
$pageData = $this->getData($activityId, $sectionId);
|
||||
|
||||
// Generate XML files
|
||||
$this->createPageXml($pageData, $pageDir);
|
||||
$this->createModuleXml($pageData, $pageDir);
|
||||
$this->createGradesXml($pageData, $pageDir);
|
||||
$this->createFiltersXml($pageData, $pageDir);
|
||||
$this->createGradeHistoryXml($pageData, $pageDir);
|
||||
$this->createInforefXml($pageData, $pageDir);
|
||||
$this->createRolesXml($pageData, $pageDir);
|
||||
$this->createCommentsXml($pageData, $pageDir);
|
||||
$this->createCalendarXml($pageData, $pageDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page data dynamically from the course.
|
||||
*/
|
||||
public function getData(int $pageId, int $sectionId): ?array
|
||||
{
|
||||
$pageResources = $this->course->resources[RESOURCE_DOCUMENT];
|
||||
|
||||
foreach ($pageResources as $page) {
|
||||
if ($page->source_id == $pageId) {
|
||||
$contextid = $this->course->info['real_id'];
|
||||
|
||||
return [
|
||||
'id' => $page->source_id,
|
||||
'moduleid' => $page->source_id,
|
||||
'modulename' => 'page',
|
||||
'contextid' => $contextid,
|
||||
'name' => $page->title,
|
||||
'intro' => $page->comment ?? '',
|
||||
'content' => $this->getPageContent($page),
|
||||
'sectionid' => $sectionId,
|
||||
'sectionnumber' => 1,
|
||||
'display' => 0,
|
||||
'timemodified' => time(),
|
||||
'users' => [],
|
||||
'files' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the XML file for the page.
|
||||
*/
|
||||
private function createPageXml(array $pageData, string $pageDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<activity id="'.$pageData['id'].'" moduleid="'.$pageData['moduleid'].'" modulename="page" contextid="'.$pageData['contextid'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <page id="'.$pageData['id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <name>'.htmlspecialchars($pageData['name']).'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <intro>'.htmlspecialchars($pageData['intro']).'</intro>'.PHP_EOL;
|
||||
$xmlContent .= ' <introformat>1</introformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <content>'.htmlspecialchars($pageData['content']).'</content>'.PHP_EOL;
|
||||
$xmlContent .= ' <contentformat>1</contentformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <legacyfiles>0</legacyfiles>'.PHP_EOL;
|
||||
$xmlContent .= ' <display>5</display>'.PHP_EOL;
|
||||
$xmlContent .= ' <displayoptions>a:3:{s:12:"printheading";s:1:"1";s:10:"printintro";s:1:"0";s:17:"printlastmodified";s:1:"1";}</displayoptions>'.PHP_EOL;
|
||||
$xmlContent .= ' <revision>1</revision>'.PHP_EOL;
|
||||
$xmlContent .= ' <timemodified>'.$pageData['timemodified'].'</timemodified>'.PHP_EOL;
|
||||
$xmlContent .= ' </page>'.PHP_EOL;
|
||||
$xmlContent .= '</activity>';
|
||||
|
||||
$this->createXmlFile('page', $xmlContent, $pageDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the content of the page.
|
||||
*/
|
||||
private function getPageContent(object $page): string
|
||||
{
|
||||
if ($page->file_type === 'file') {
|
||||
return file_get_contents($this->course->path.$page->path);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
492
main/inc/lib/moodleexport/QuizExport.php
Normal file
492
main/inc/lib/moodleexport/QuizExport.php
Normal file
@@ -0,0 +1,492 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace moodleexport;
|
||||
|
||||
use Exception;
|
||||
use FillBlanks;
|
||||
|
||||
/**
|
||||
* Class QuizExport.
|
||||
*
|
||||
* Handles the export of quizzes within a course.
|
||||
*/
|
||||
class QuizExport extends ActivityExport
|
||||
{
|
||||
/**
|
||||
* Export a quiz to the specified directory.
|
||||
*
|
||||
* @param int $activityId The ID of the quiz.
|
||||
* @param string $exportDir The directory where the quiz will be exported.
|
||||
* @param int $moduleId The ID of the module.
|
||||
* @param int $sectionId The ID of the section.
|
||||
*/
|
||||
public function export($activityId, $exportDir, $moduleId, $sectionId): void
|
||||
{
|
||||
// Prepare the directory where the quiz export will be saved
|
||||
$quizDir = $this->prepareActivityDirectory($exportDir, 'quiz', $moduleId);
|
||||
|
||||
// Retrieve quiz data
|
||||
$quizData = $this->getData($activityId, $sectionId);
|
||||
|
||||
// Generate XML files
|
||||
$this->createQuizXml($quizData, $quizDir);
|
||||
$this->createModuleXml($quizData, $quizDir);
|
||||
$this->createGradesXml($quizData, $quizDir);
|
||||
$this->createCompletionXml($quizData, $quizDir);
|
||||
$this->createCommentsXml($quizData, $quizDir);
|
||||
$this->createCompetenciesXml($quizData, $quizDir);
|
||||
$this->createFiltersXml($quizData, $quizDir);
|
||||
$this->createGradeHistoryXml($quizData, $quizDir);
|
||||
$this->createInforefXml($quizData, $quizDir);
|
||||
$this->createRolesXml($quizData, $quizDir);
|
||||
$this->createCalendarXml($quizData, $quizDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the quiz data.
|
||||
*/
|
||||
public function getData(int $quizId, int $sectionId): array
|
||||
{
|
||||
$quizResources = $this->course->resources[RESOURCE_QUIZ];
|
||||
|
||||
foreach ($quizResources as $quiz) {
|
||||
if ($quiz->obj->iid == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($quiz->obj->iid == $quizId) {
|
||||
$contextid = $quiz->obj->c_id;
|
||||
|
||||
return [
|
||||
'id' => $quiz->obj->iid,
|
||||
'name' => $quiz->obj->title,
|
||||
'intro' => $quiz->obj->description,
|
||||
'timeopen' => $quiz->obj->start_time ?? 0,
|
||||
'timeclose' => $quiz->obj->end_time ?? 0,
|
||||
'timelimit' => $quiz->obj->timelimit ?? 0,
|
||||
'grademethod' => $quiz->obj->grademethod ?? 1,
|
||||
'decimalpoints' => $quiz->obj->decimalpoints ?? 2,
|
||||
'sumgrades' => $quiz->obj->sumgrades ?? 0,
|
||||
'grade' => $quiz->obj->grade ?? 0,
|
||||
'questionsperpage' => $quiz->obj->questionsperpage ?? 1,
|
||||
'preferredbehaviour' => $quiz->obj->preferredbehaviour ?? 'deferredfeedback',
|
||||
'shuffleanswers' => $quiz->obj->shuffleanswers ?? 1,
|
||||
'questions' => $this->getQuestionsForQuiz($quizId),
|
||||
'feedbacks' => $this->getFeedbacksForQuiz($quizId),
|
||||
'sectionid' => $sectionId,
|
||||
'moduleid' => $quiz->obj->iid ?? 0,
|
||||
'modulename' => 'quiz',
|
||||
'contextid' => $contextid,
|
||||
'overduehandling' => $quiz->obj->overduehandling ?? 'autosubmit',
|
||||
'graceperiod' => $quiz->obj->graceperiod ?? 0,
|
||||
'canredoquestions' => $quiz->obj->canredoquestions ?? 0,
|
||||
'attempts_number' => $quiz->obj->attempts_number ?? 0,
|
||||
'attemptonlast' => $quiz->obj->attemptonlast ?? 0,
|
||||
'questiondecimalpoints' => $quiz->obj->questiondecimalpoints ?? 2,
|
||||
'reviewattempt' => $quiz->obj->reviewattempt ?? 0,
|
||||
'reviewcorrectness' => $quiz->obj->reviewcorrectness ?? 0,
|
||||
'reviewmarks' => $quiz->obj->reviewmarks ?? 0,
|
||||
'reviewspecificfeedback' => $quiz->obj->reviewspecificfeedback ?? 0,
|
||||
'reviewgeneralfeedback' => $quiz->obj->reviewgeneralfeedback ?? 0,
|
||||
'reviewrightanswer' => $quiz->obj->reviewrightanswer ?? 0,
|
||||
'reviewoverallfeedback' => $quiz->obj->reviewoverallfeedback ?? 0,
|
||||
'timecreated' => $quiz->obj->insert_date ?? time(),
|
||||
'timemodified' => $quiz->obj->lastedit_date ?? time(),
|
||||
'password' => $quiz->obj->password ?? '',
|
||||
'subnet' => $quiz->obj->subnet ?? '',
|
||||
'browsersecurity' => $quiz->obj->browsersecurity ?? '-',
|
||||
'delay1' => $quiz->obj->delay1 ?? 0,
|
||||
'delay2' => $quiz->obj->delay2 ?? 0,
|
||||
'showuserpicture' => $quiz->obj->showuserpicture ?? 0,
|
||||
'showblocks' => $quiz->obj->showblocks ?? 0,
|
||||
'completionattemptsexhausted' => $quiz->obj->completionattemptsexhausted ?? 0,
|
||||
'completionpass' => $quiz->obj->completionpass ?? 0,
|
||||
'completionminattempts' => $quiz->obj->completionminattempts ?? 0,
|
||||
'allowofflineattempts' => $quiz->obj->allowofflineattempts ?? 0,
|
||||
'users' => [],
|
||||
'files' => [],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a question in XML format.
|
||||
*/
|
||||
public function exportQuestion(array $question): string
|
||||
{
|
||||
$xmlContent = ' <question id="'.($question['id'] ?? '0').'">'.PHP_EOL;
|
||||
$xmlContent .= ' <parent>0</parent>'.PHP_EOL;
|
||||
$xmlContent .= ' <name>'.htmlspecialchars($question['questiontext'] ?? 'No question text').'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <questiontext>'.htmlspecialchars($question['questiontext'] ?? 'No question text').'</questiontext>'.PHP_EOL;
|
||||
$xmlContent .= ' <questiontextformat>1</questiontextformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <generalfeedback></generalfeedback>'.PHP_EOL;
|
||||
$xmlContent .= ' <generalfeedbackformat>1</generalfeedbackformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <defaultmark>'.($question['maxmark'] ?? '0').'</defaultmark>'.PHP_EOL;
|
||||
$xmlContent .= ' <penalty>0.3333333</penalty>'.PHP_EOL;
|
||||
$xmlContent .= ' <qtype>'.htmlspecialchars(str_replace('_nosingle', '', $question['qtype']) ?? 'unknown').'</qtype>'.PHP_EOL;
|
||||
$xmlContent .= ' <length>1</length>'.PHP_EOL;
|
||||
$xmlContent .= ' <stamp>moodle+'.time().'+QUESTIONSTAMP</stamp>'.PHP_EOL;
|
||||
$xmlContent .= ' <version>moodle+'.time().'+VERSIONSTAMP</version>'.PHP_EOL;
|
||||
$xmlContent .= ' <hidden>0</hidden>'.PHP_EOL;
|
||||
$xmlContent .= ' <timecreated>'.time().'</timecreated>'.PHP_EOL;
|
||||
$xmlContent .= ' <timemodified>'.time().'</timemodified>'.PHP_EOL;
|
||||
$xmlContent .= ' <createdby>2</createdby>'.PHP_EOL;
|
||||
$xmlContent .= ' <modifiedby>2</modifiedby>'.PHP_EOL;
|
||||
|
||||
// Add question type-specific content
|
||||
switch ($question['qtype']) {
|
||||
case 'multichoice':
|
||||
$xmlContent .= $this->exportMultichoiceQuestion($question);
|
||||
break;
|
||||
case 'multichoice_nosingle':
|
||||
$xmlContent .= $this->exportMultichoiceNosingleQuestion($question);
|
||||
break;
|
||||
case 'truefalse':
|
||||
$xmlContent .= $this->exportTrueFalseQuestion($question);
|
||||
break;
|
||||
case 'shortanswer':
|
||||
$xmlContent .= $this->exportShortAnswerQuestion($question);
|
||||
break;
|
||||
case 'match':
|
||||
$xmlContent .= $this->exportMatchQuestion($question);
|
||||
break;
|
||||
}
|
||||
|
||||
$xmlContent .= ' </question>'.PHP_EOL;
|
||||
|
||||
return $xmlContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the questions for a specific quiz.
|
||||
*/
|
||||
private function getQuestionsForQuiz(int $quizId): array
|
||||
{
|
||||
$questions = [];
|
||||
$quizResources = $this->course->resources[RESOURCE_QUIZQUESTION] ?? [];
|
||||
|
||||
foreach ($quizResources as $questionId => $questionData) {
|
||||
if (in_array($questionId, $this->course->resources[RESOURCE_QUIZ][$quizId]->obj->question_ids)) {
|
||||
$questions[] = [
|
||||
'id' => $questionData->source_id,
|
||||
'questiontext' => $questionData->question,
|
||||
'qtype' => $this->mapQuestionType($questionData->quiz_type),
|
||||
'questioncategoryid' => $questionData->question_category ?? 0,
|
||||
'answers' => $this->getAnswersForQuestion($questionData->source_id),
|
||||
'maxmark' => $questionData->ponderation ?? 1,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $questions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps the quiz type code to a descriptive string.
|
||||
*/
|
||||
private function mapQuestionType(string $quizType): string
|
||||
{
|
||||
switch ($quizType) {
|
||||
case UNIQUE_ANSWER: return 'multichoice';
|
||||
case MULTIPLE_ANSWER: return 'multichoice_nosingle';
|
||||
case FILL_IN_BLANKS: return 'match';
|
||||
case FREE_ANSWER: return 'shortanswer';
|
||||
case CALCULATED_ANSWER: return 'calculated';
|
||||
case UPLOAD_ANSWER: return 'fileupload';
|
||||
default: return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the answers for a specific question ID.
|
||||
*/
|
||||
private function getAnswersForQuestion(int $questionId): array
|
||||
{
|
||||
$answers = [];
|
||||
$quizResources = $this->course->resources[RESOURCE_QUIZQUESTION] ?? [];
|
||||
|
||||
foreach ($quizResources as $questionData) {
|
||||
if ($questionData->source_id == $questionId) {
|
||||
foreach ($questionData->answers as $answer) {
|
||||
$answers[] = [
|
||||
'text' => $answer['answer'],
|
||||
'fraction' => $answer['correct'] == '1' ? 100 : 0,
|
||||
'feedback' => $answer['comment'],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $answers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves feedbacks for a specific quiz.
|
||||
*/
|
||||
private function getFeedbacksForQuiz(int $quizId): array
|
||||
{
|
||||
$feedbacks = [];
|
||||
$quizResources = $this->course->resources[RESOURCE_QUIZ] ?? [];
|
||||
|
||||
foreach ($quizResources as $quiz) {
|
||||
if ($quiz->obj->iid == $quizId) {
|
||||
$feedbacks[] = [
|
||||
'feedbacktext' => $quiz->obj->description ?? '',
|
||||
'mingrade' => 0.00000,
|
||||
'maxgrade' => $quiz->obj->grade ?? 10.00000,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $feedbacks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the quiz.xml file.
|
||||
*/
|
||||
private function createQuizXml(array $quizData, string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<activity id="'.$quizData['id'].'" moduleid="'.$quizData['moduleid'].'" modulename="quiz" contextid="'.$quizData['contextid'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <quiz id="'.$quizData['id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <name>'.htmlspecialchars($quizData['name']).'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <intro>'.htmlspecialchars($quizData['intro']).'</intro>'.PHP_EOL;
|
||||
$xmlContent .= ' <introformat>1</introformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <timeopen>'.($quizData['timeopen'] ?? 0).'</timeopen>'.PHP_EOL;
|
||||
$xmlContent .= ' <timeclose>'.($quizData['timeclose'] ?? 0).'</timeclose>'.PHP_EOL;
|
||||
$xmlContent .= ' <timelimit>'.($quizData['timelimit'] ?? 0).'</timelimit>'.PHP_EOL;
|
||||
$xmlContent .= ' <overduehandling>'.($quizData['overduehandling'] ?? 'autosubmit').'</overduehandling>'.PHP_EOL;
|
||||
$xmlContent .= ' <graceperiod>'.($quizData['graceperiod'] ?? 0).'</graceperiod>'.PHP_EOL;
|
||||
$xmlContent .= ' <preferredbehaviour>'.htmlspecialchars($quizData['preferredbehaviour']).'</preferredbehaviour>'.PHP_EOL;
|
||||
$xmlContent .= ' <canredoquestions>'.($quizData['canredoquestions'] ?? 0).'</canredoquestions>'.PHP_EOL;
|
||||
$xmlContent .= ' <attempts_number>'.($quizData['attempts_number'] ?? 0).'</attempts_number>'.PHP_EOL;
|
||||
$xmlContent .= ' <attemptonlast>'.($quizData['attemptonlast'] ?? 0).'</attemptonlast>'.PHP_EOL;
|
||||
$xmlContent .= ' <grademethod>'.$quizData['grademethod'].'</grademethod>'.PHP_EOL;
|
||||
$xmlContent .= ' <decimalpoints>'.$quizData['decimalpoints'].'</decimalpoints>'.PHP_EOL;
|
||||
$xmlContent .= ' <questiondecimalpoints>'.($quizData['questiondecimalpoints'] ?? -1).'</questiondecimalpoints>'.PHP_EOL;
|
||||
|
||||
// Review options
|
||||
$xmlContent .= ' <reviewattempt>'.($quizData['reviewattempt'] ?? 69888).'</reviewattempt>'.PHP_EOL;
|
||||
$xmlContent .= ' <reviewcorrectness>'.($quizData['reviewcorrectness'] ?? 4352).'</reviewcorrectness>'.PHP_EOL;
|
||||
$xmlContent .= ' <reviewmarks>'.($quizData['reviewmarks'] ?? 4352).'</reviewmarks>'.PHP_EOL;
|
||||
$xmlContent .= ' <reviewspecificfeedback>'.($quizData['reviewspecificfeedback'] ?? 4352).'</reviewspecificfeedback>'.PHP_EOL;
|
||||
$xmlContent .= ' <reviewgeneralfeedback>'.($quizData['reviewgeneralfeedback'] ?? 4352).'</reviewgeneralfeedback>'.PHP_EOL;
|
||||
$xmlContent .= ' <reviewrightanswer>'.($quizData['reviewrightanswer'] ?? 4352).'</reviewrightanswer>'.PHP_EOL;
|
||||
$xmlContent .= ' <reviewoverallfeedback>'.($quizData['reviewoverallfeedback'] ?? 4352).'</reviewoverallfeedback>'.PHP_EOL;
|
||||
|
||||
// Navigation and presentation settings
|
||||
$xmlContent .= ' <questionsperpage>'.$quizData['questionsperpage'].'</questionsperpage>'.PHP_EOL;
|
||||
$xmlContent .= ' <navmethod>'.htmlspecialchars($quizData['navmethod']).'</navmethod>'.PHP_EOL;
|
||||
$xmlContent .= ' <shuffleanswers>'.$quizData['shuffleanswers'].'</shuffleanswers>'.PHP_EOL;
|
||||
$xmlContent .= ' <sumgrades>'.$quizData['sumgrades'].'</sumgrades>'.PHP_EOL;
|
||||
$xmlContent .= ' <grade>'.$quizData['grade'].'</grade>'.PHP_EOL;
|
||||
|
||||
// Timing and security
|
||||
$xmlContent .= ' <timecreated>'.($quizData['timecreated'] ?? time()).'</timecreated>'.PHP_EOL;
|
||||
$xmlContent .= ' <timemodified>'.($quizData['timemodified'] ?? time()).'</timemodified>'.PHP_EOL;
|
||||
$xmlContent .= ' <password>'.(isset($quizData['password']) ? htmlspecialchars($quizData['password']) : '').'</password>'.PHP_EOL;
|
||||
$xmlContent .= ' <subnet>'.(isset($quizData['subnet']) ? htmlspecialchars($quizData['subnet']) : '').'</subnet>'.PHP_EOL;
|
||||
$xmlContent .= ' <browsersecurity>'.(isset($quizData['browsersecurity']) ? htmlspecialchars($quizData['browsersecurity']) : '-').'</browsersecurity>'.PHP_EOL;
|
||||
$xmlContent .= ' <delay1>'.($quizData['delay1'] ?? 0).'</delay1>'.PHP_EOL;
|
||||
$xmlContent .= ' <delay2>'.($quizData['delay2'] ?? 0).'</delay2>'.PHP_EOL;
|
||||
|
||||
// Additional options
|
||||
$xmlContent .= ' <showuserpicture>'.($quizData['showuserpicture'] ?? 0).'</showuserpicture>'.PHP_EOL;
|
||||
$xmlContent .= ' <showblocks>'.($quizData['showblocks'] ?? 0).'</showblocks>'.PHP_EOL;
|
||||
$xmlContent .= ' <completionattemptsexhausted>'.($quizData['completionattemptsexhausted'] ?? 0).'</completionattemptsexhausted>'.PHP_EOL;
|
||||
$xmlContent .= ' <completionpass>'.($quizData['completionpass'] ?? 0).'</completionpass>'.PHP_EOL;
|
||||
$xmlContent .= ' <completionminattempts>'.($quizData['completionminattempts'] ?? 0).'</completionminattempts>'.PHP_EOL;
|
||||
$xmlContent .= ' <allowofflineattempts>'.($quizData['allowofflineattempts'] ?? 0).'</allowofflineattempts>'.PHP_EOL;
|
||||
|
||||
// Subplugin, if applicable
|
||||
$xmlContent .= ' <subplugin_quizaccess_seb_quiz>'.PHP_EOL;
|
||||
$xmlContent .= ' </subplugin_quizaccess_seb_quiz>'.PHP_EOL;
|
||||
|
||||
// Add question instances
|
||||
$xmlContent .= ' <question_instances>'.PHP_EOL;
|
||||
foreach ($quizData['questions'] as $question) {
|
||||
$xmlContent .= ' <question_instance id="'.$question['id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <slot>'.$question['id'].'</slot>'.PHP_EOL;
|
||||
$xmlContent .= ' <page>1</page>'.PHP_EOL;
|
||||
$xmlContent .= ' <requireprevious>0</requireprevious>'.PHP_EOL;
|
||||
$xmlContent .= ' <questionid>'.$question['id'].'</questionid>'.PHP_EOL;
|
||||
$xmlContent .= ' <questioncategoryid>'.$question['questioncategoryid'].'</questioncategoryid>'.PHP_EOL;
|
||||
$xmlContent .= ' <includingsubcategories>$@NULL@$</includingsubcategories>'.PHP_EOL;
|
||||
$xmlContent .= ' <maxmark>'.$question['maxmark'].'</maxmark>'.PHP_EOL;
|
||||
$xmlContent .= ' </question_instance>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= ' </question_instances>'.PHP_EOL;
|
||||
|
||||
// Quiz sections
|
||||
$xmlContent .= ' <sections>'.PHP_EOL;
|
||||
$xmlContent .= ' <section id="'.$quizData['id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <firstslot>1</firstslot>'.PHP_EOL;
|
||||
$xmlContent .= ' <shufflequestions>0</shufflequestions>'.PHP_EOL;
|
||||
$xmlContent .= ' </section>'.PHP_EOL;
|
||||
$xmlContent .= ' </sections>'.PHP_EOL;
|
||||
|
||||
// Add feedbacks
|
||||
$xmlContent .= ' <feedbacks>'.PHP_EOL;
|
||||
foreach ($quizData['feedbacks'] as $feedback) {
|
||||
$xmlContent .= ' <feedback id="'.$quizData['id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <feedbacktext>'.htmlspecialchars($feedback['feedbacktext']).'</feedbacktext>'.PHP_EOL;
|
||||
$xmlContent .= ' <feedbacktextformat>1</feedbacktextformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <mingrade>'.$feedback['mingrade'].'</mingrade>'.PHP_EOL;
|
||||
$xmlContent .= ' <maxgrade>'.$feedback['maxgrade'].'</maxgrade>'.PHP_EOL;
|
||||
$xmlContent .= ' </feedback>'.PHP_EOL;
|
||||
}
|
||||
$xmlContent .= ' </feedbacks>'.PHP_EOL;
|
||||
|
||||
// Complete with placeholders for attempts and grades
|
||||
$xmlContent .= ' <overrides>'.PHP_EOL.' </overrides>'.PHP_EOL;
|
||||
$xmlContent .= ' <grades>'.PHP_EOL.' </grades>'.PHP_EOL;
|
||||
$xmlContent .= ' <attempts>'.PHP_EOL.' </attempts>'.PHP_EOL;
|
||||
|
||||
// Close the activity tag
|
||||
$xmlContent .= ' </quiz>'.PHP_EOL;
|
||||
$xmlContent .= '</activity>'.PHP_EOL;
|
||||
|
||||
// Save the XML file
|
||||
$xmlFile = $destinationDir.'/quiz.xml';
|
||||
if (file_put_contents($xmlFile, $xmlContent) === false) {
|
||||
throw new Exception(get_lang('ErrorCreatingQuizXml'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a multiple-choice question in XML format.
|
||||
*/
|
||||
private function exportMultichoiceQuestion(array $question): string
|
||||
{
|
||||
$xmlContent = ' <plugin_qtype_multichoice_question>'.PHP_EOL;
|
||||
$xmlContent .= ' <answers>'.PHP_EOL;
|
||||
foreach ($question['answers'] as $answer) {
|
||||
$xmlContent .= $this->exportAnswer($answer);
|
||||
}
|
||||
$xmlContent .= ' </answers>'.PHP_EOL;
|
||||
$xmlContent .= ' <multichoice id="'.($question['id'] ?? '0').'">'.PHP_EOL;
|
||||
$xmlContent .= ' <layout>0</layout>'.PHP_EOL;
|
||||
$xmlContent .= ' <single>1</single>'.PHP_EOL;
|
||||
$xmlContent .= ' <shuffleanswers>1</shuffleanswers>'.PHP_EOL;
|
||||
$xmlContent .= ' <correctfeedback>Your answer is correct.</correctfeedback>'.PHP_EOL;
|
||||
$xmlContent .= ' <correctfeedbackformat>1</correctfeedbackformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <partiallycorrectfeedback>Your answer is partially correct.</partiallycorrectfeedback>'.PHP_EOL;
|
||||
$xmlContent .= ' <partiallycorrectfeedbackformat>1</partiallycorrectfeedbackformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <incorrectfeedback>Your answer is incorrect.</incorrectfeedback>'.PHP_EOL;
|
||||
$xmlContent .= ' <incorrectfeedbackformat>1</incorrectfeedbackformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <answernumbering>abc</answernumbering>'.PHP_EOL;
|
||||
$xmlContent .= ' <shownumcorrect>1</shownumcorrect>'.PHP_EOL;
|
||||
$xmlContent .= ' </multichoice>'.PHP_EOL;
|
||||
$xmlContent .= ' </plugin_qtype_multichoice_question>'.PHP_EOL;
|
||||
|
||||
return $xmlContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a multiple-choice question with single=0 in XML format.
|
||||
*/
|
||||
private function exportMultichoiceNosingleQuestion(array $question): string
|
||||
{
|
||||
// Similar structure to exportMultichoiceQuestion, but with single=0
|
||||
$xmlContent = str_replace('<single>1</single>', '<single>0</single>', $this->exportMultichoiceQuestion($question));
|
||||
|
||||
return $xmlContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a true/false question in XML format.
|
||||
*/
|
||||
private function exportTrueFalseQuestion(array $question): string
|
||||
{
|
||||
$xmlContent = ' <plugin_qtype_truefalse_question>'.PHP_EOL;
|
||||
$xmlContent .= ' <answers>'.PHP_EOL;
|
||||
foreach ($question['answers'] as $answer) {
|
||||
$xmlContent .= $this->exportAnswer($answer);
|
||||
}
|
||||
$xmlContent .= ' </answers>'.PHP_EOL;
|
||||
$xmlContent .= ' <truefalse id="'.($question['id'] ?? '0').'">'.PHP_EOL;
|
||||
$xmlContent .= ' <trueanswer>'.($question['answers'][0]['id'] ?? '0').'</trueanswer>'.PHP_EOL;
|
||||
$xmlContent .= ' <falseanswer>'.($question['answers'][1]['id'] ?? '0').'</falseanswer>'.PHP_EOL;
|
||||
$xmlContent .= ' </truefalse>'.PHP_EOL;
|
||||
$xmlContent .= ' </plugin_qtype_truefalse_question>'.PHP_EOL;
|
||||
|
||||
return $xmlContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a short answer question in XML format.
|
||||
*/
|
||||
private function exportShortAnswerQuestion(array $question): string
|
||||
{
|
||||
$xmlContent = ' <plugin_qtype_shortanswer_question>'.PHP_EOL;
|
||||
$xmlContent .= ' <answers>'.PHP_EOL;
|
||||
foreach ($question['answers'] as $answer) {
|
||||
$xmlContent .= $this->exportAnswer($answer);
|
||||
}
|
||||
$xmlContent .= ' </answers>'.PHP_EOL;
|
||||
$xmlContent .= ' <shortanswer id="'.($question['id'] ?? '0').'">'.PHP_EOL;
|
||||
$xmlContent .= ' <usecase>0</usecase>'.PHP_EOL;
|
||||
$xmlContent .= ' </shortanswer>'.PHP_EOL;
|
||||
$xmlContent .= ' </plugin_qtype_shortanswer_question>'.PHP_EOL;
|
||||
|
||||
return $xmlContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports a matching question in XML format.
|
||||
*/
|
||||
private function exportMatchQuestion(array $question): string
|
||||
{
|
||||
$xmlContent = ' <plugin_qtype_match_question>'.PHP_EOL;
|
||||
$xmlContent .= ' <matchoptions id="'.htmlspecialchars($question['id'] ?? '0').'">'.PHP_EOL;
|
||||
$xmlContent .= ' <shuffleanswers>1</shuffleanswers>'.PHP_EOL;
|
||||
$xmlContent .= ' <correctfeedback>'.htmlspecialchars($question['correctfeedback'] ?? '').'</correctfeedback>'.PHP_EOL;
|
||||
$xmlContent .= ' <correctfeedbackformat>0</correctfeedbackformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <partiallycorrectfeedback>'.htmlspecialchars($question['partiallycorrectfeedback'] ?? '').'</partiallycorrectfeedback>'.PHP_EOL;
|
||||
$xmlContent .= ' <partiallycorrectfeedbackformat>0</partiallycorrectfeedbackformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <incorrectfeedback>'.htmlspecialchars($question['incorrectfeedback'] ?? '').'</incorrectfeedback>'.PHP_EOL;
|
||||
$xmlContent .= ' <incorrectfeedbackformat>0</incorrectfeedbackformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <shownumcorrect>0</shownumcorrect>'.PHP_EOL;
|
||||
$xmlContent .= ' </matchoptions>'.PHP_EOL;
|
||||
$xmlContent .= ' <matches>'.PHP_EOL;
|
||||
|
||||
$res = FillBlanks::getAnswerInfo($question['answers'][0]['text']);
|
||||
$words = $res['words'];
|
||||
$common_words = $res['common_words'];
|
||||
|
||||
for ($i = 0; $i < count($common_words); $i++) {
|
||||
$answer = htmlspecialchars(trim(strip_tags($common_words[$i])));
|
||||
if (!empty(trim($answer))) {
|
||||
$xmlContent .= ' <match id="'.($i + 1).'">'.PHP_EOL;
|
||||
$xmlContent .= ' <questiontext>'.$answer.'</questiontext>'.PHP_EOL;
|
||||
$xmlContent .= ' <questiontextformat>0</questiontextformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <answertext>'.htmlspecialchars(explode('|', $words[$i])[0]).'</answertext>'.PHP_EOL;
|
||||
$xmlContent .= ' </match>'.PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
$xmlContent .= ' </matches>'.PHP_EOL;
|
||||
$xmlContent .= ' </plugin_qtype_match_question>'.PHP_EOL;
|
||||
|
||||
return $xmlContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports an answer in XML format.
|
||||
*/
|
||||
private function exportAnswer(array $answer): string
|
||||
{
|
||||
return ' <answer id="'.($answer['id'] ?? '0').'">'.PHP_EOL.
|
||||
' <answertext>'.htmlspecialchars($answer['text'] ?? 'No answer text').'</answertext>'.PHP_EOL.
|
||||
' <answerformat>1</answerformat>'.PHP_EOL.
|
||||
' <fraction>'.($answer['fraction'] ?? '0').'</fraction>'.PHP_EOL.
|
||||
' <feedback>'.htmlspecialchars($answer['feedback'] ?? '').'</feedback>'.PHP_EOL.
|
||||
' <feedbackformat>1</feedbackformat>'.PHP_EOL.
|
||||
' </answer>'.PHP_EOL;
|
||||
}
|
||||
}
|
||||
111
main/inc/lib/moodleexport/ResourceExport.php
Normal file
111
main/inc/lib/moodleexport/ResourceExport.php
Normal file
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace moodleexport;
|
||||
|
||||
/**
|
||||
* Class ResourceExport.
|
||||
*
|
||||
* Handles the export of resources within a course.
|
||||
*/
|
||||
class ResourceExport extends ActivityExport
|
||||
{
|
||||
/**
|
||||
* Export a resource to the specified directory.
|
||||
*
|
||||
* @param int $activityId The ID of the resource.
|
||||
* @param string $exportDir The directory where the resource will be exported.
|
||||
* @param int $moduleId The ID of the module.
|
||||
* @param int $sectionId The ID of the section.
|
||||
*/
|
||||
public function export($activityId, $exportDir, $moduleId, $sectionId): void
|
||||
{
|
||||
// Prepare the directory where the resource export will be saved
|
||||
$resourceDir = $this->prepareActivityDirectory($exportDir, 'resource', $moduleId);
|
||||
|
||||
// Retrieve resource data
|
||||
$resourceData = $this->getData($activityId, $sectionId);
|
||||
|
||||
// Generate XML files
|
||||
$this->createResourceXml($resourceData, $resourceDir);
|
||||
$this->createModuleXml($resourceData, $resourceDir);
|
||||
$this->createGradesXml($resourceData, $resourceDir);
|
||||
$this->createFiltersXml($resourceData, $resourceDir);
|
||||
$this->createGradeHistoryXml($resourceData, $resourceDir);
|
||||
$this->createInforefXml($resourceData, $resourceDir);
|
||||
$this->createRolesXml($resourceData, $resourceDir);
|
||||
$this->createCommentsXml($resourceData, $resourceDir);
|
||||
$this->createCalendarXml($resourceData, $resourceDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get resource data dynamically from the course.
|
||||
*/
|
||||
public function getData(int $resourceId, int $sectionId): array
|
||||
{
|
||||
$resource = $this->course->resources[RESOURCE_DOCUMENT][$resourceId];
|
||||
|
||||
return [
|
||||
'id' => $resourceId,
|
||||
'moduleid' => $resource->source_id,
|
||||
'modulename' => 'resource',
|
||||
'contextid' => $resource->source_id,
|
||||
'name' => $resource->title,
|
||||
'intro' => $resource->comment ?? '',
|
||||
'sectionid' => $sectionId,
|
||||
'sectionnumber' => 1,
|
||||
'timemodified' => time(),
|
||||
'users' => [],
|
||||
'files' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the inforef.xml file, referencing users and files associated with the activity.
|
||||
*
|
||||
* @param array $references Contains 'users' and 'files' arrays to reference in the XML.
|
||||
* @param string $directory The directory where the XML file will be saved.
|
||||
*/
|
||||
protected function createInforefXml(array $references, string $directory): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<inforef>'.PHP_EOL;
|
||||
|
||||
$xmlContent .= ' <fileref>'.PHP_EOL;
|
||||
$xmlContent .= ' <file>'.PHP_EOL;
|
||||
$xmlContent .= ' <id>'.htmlspecialchars($references['id']).'</id>'.PHP_EOL;
|
||||
$xmlContent .= ' </file>'.PHP_EOL;
|
||||
$xmlContent .= ' </fileref>'.PHP_EOL;
|
||||
|
||||
$xmlContent .= '</inforef>'.PHP_EOL;
|
||||
|
||||
// Save the XML content to the directory
|
||||
$this->createXmlFile('inforef', $xmlContent, $directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the XML file for the resource.
|
||||
*/
|
||||
private function createResourceXml(array $resourceData, string $resourceDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<activity id="'.$resourceData['id'].'" moduleid="'.$resourceData['moduleid'].'" modulename="resource" contextid="'.$resourceData['contextid'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <resource id="'.$resourceData['id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <name>'.htmlspecialchars($resourceData['name']).'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <intro>'.htmlspecialchars($resourceData['intro']).'</intro>'.PHP_EOL;
|
||||
$xmlContent .= ' <introformat>1</introformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <tobemigrated>0</tobemigrated>'.PHP_EOL;
|
||||
$xmlContent .= ' <legacyfiles>0</legacyfiles>'.PHP_EOL;
|
||||
$xmlContent .= ' <legacyfileslast>$@NULL@$</legacyfileslast>'.PHP_EOL;
|
||||
$xmlContent .= ' <display>0</display>'.PHP_EOL;
|
||||
$xmlContent .= ' <displayoptions>a:1:{s:10:"printintro";i:1;}</displayoptions>'.PHP_EOL;
|
||||
$xmlContent .= ' <filterfiles>0</filterfiles>'.PHP_EOL;
|
||||
$xmlContent .= ' <revision>1</revision>'.PHP_EOL;
|
||||
$xmlContent .= ' <timemodified>'.$resourceData['timemodified'].'</timemodified>'.PHP_EOL;
|
||||
$xmlContent .= ' </resource>'.PHP_EOL;
|
||||
$xmlContent .= '</activity>'.PHP_EOL;
|
||||
|
||||
$this->createXmlFile('resource', $xmlContent, $resourceDir);
|
||||
}
|
||||
}
|
||||
322
main/inc/lib/moodleexport/SectionExport.php
Normal file
322
main/inc/lib/moodleexport/SectionExport.php
Normal file
@@ -0,0 +1,322 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace moodleexport;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class SectionExport.
|
||||
* Handles the export of course sections and their activities.
|
||||
*
|
||||
* @package moodleexport
|
||||
*/
|
||||
class SectionExport
|
||||
{
|
||||
private $course;
|
||||
|
||||
/**
|
||||
* Constructor to initialize the course object.
|
||||
*
|
||||
* @param object $course The course object to be exported.
|
||||
*/
|
||||
public function __construct($course)
|
||||
{
|
||||
$this->course = $course;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a section and its activities to the specified directory.
|
||||
*/
|
||||
public function exportSection(int $sectionId, string $exportDir): void
|
||||
{
|
||||
$sectionDir = $exportDir."/sections/section_{$sectionId}";
|
||||
|
||||
if (!is_dir($sectionDir)) {
|
||||
mkdir($sectionDir, api_get_permissions_for_new_directories(), true);
|
||||
}
|
||||
|
||||
if ($sectionId > 0) {
|
||||
$learnpath = $this->getLearnpathById($sectionId);
|
||||
if ($learnpath === null) {
|
||||
throw new Exception("Learnpath with ID $sectionId not found.");
|
||||
}
|
||||
$sectionData = $this->getSectionData($learnpath);
|
||||
} else {
|
||||
$sectionData = [
|
||||
'id' => 0,
|
||||
'number' => 0,
|
||||
'name' => get_lang('General'),
|
||||
'summary' => get_lang('GeneralResourcesCourse'),
|
||||
'sequence' => 0,
|
||||
'visible' => 1,
|
||||
'timemodified' => time(),
|
||||
'activities' => $this->getActivitiesForGeneral(),
|
||||
];
|
||||
}
|
||||
|
||||
$this->createSectionXml($sectionData, $sectionDir);
|
||||
$this->createInforefXml($sectionData, $sectionDir);
|
||||
$this->exportActivities($sectionData['activities'], $exportDir, $sectionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all general items not linked to any lesson (learnpath).
|
||||
*/
|
||||
public function getGeneralItems(): array
|
||||
{
|
||||
$generalItems = [];
|
||||
|
||||
// List of resource types and their corresponding ID keys
|
||||
$resourceTypes = [
|
||||
RESOURCE_DOCUMENT => 'source_id',
|
||||
RESOURCE_QUIZ => 'source_id',
|
||||
RESOURCE_GLOSSARY => 'glossary_id',
|
||||
RESOURCE_LINK => 'source_id',
|
||||
RESOURCE_WORK => 'source_id',
|
||||
RESOURCE_FORUM => 'source_id',
|
||||
RESOURCE_SURVEY => 'source_id',
|
||||
];
|
||||
|
||||
foreach ($resourceTypes as $resourceType => $idKey) {
|
||||
if (!empty($this->course->resources[$resourceType])) {
|
||||
foreach ($this->course->resources[$resourceType] as $id => $resource) {
|
||||
if (!$this->isItemInLearnpath($resource, $resourceType)) {
|
||||
$title = $resourceType === RESOURCE_WORK
|
||||
? ($resource->params['title'] ?? '')
|
||||
: ($resource->title ?? $resource->name);
|
||||
$generalItems[] = [
|
||||
'id' => $resource->$idKey,
|
||||
'item_type' => $resourceType,
|
||||
'path' => $id,
|
||||
'title' => $title,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $generalItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the activities for the general section.
|
||||
*/
|
||||
public function getActivitiesForGeneral(): array
|
||||
{
|
||||
$generalLearnpath = (object) [
|
||||
'items' => $this->getGeneralItems(),
|
||||
'source_id' => 0,
|
||||
];
|
||||
|
||||
return $this->getActivitiesForSection($generalLearnpath, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the learnpath object by its ID.
|
||||
*/
|
||||
public function getLearnpathById(int $sectionId): ?object
|
||||
{
|
||||
foreach ($this->course->resources[RESOURCE_LEARNPATH] as $learnpath) {
|
||||
if ($learnpath->source_id == $sectionId) {
|
||||
return $learnpath;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get section data for a learnpath.
|
||||
*/
|
||||
public function getSectionData(object $learnpath): array
|
||||
{
|
||||
return [
|
||||
'id' => $learnpath->source_id,
|
||||
'number' => $learnpath->display_order,
|
||||
'name' => $learnpath->name,
|
||||
'summary' => $learnpath->description,
|
||||
'sequence' => $learnpath->source_id,
|
||||
'visible' => $learnpath->visibility,
|
||||
'timemodified' => strtotime($learnpath->modified_on),
|
||||
'activities' => $this->getActivitiesForSection($learnpath),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the activities for a specific section.
|
||||
*/
|
||||
public function getActivitiesForSection(object $learnpath, bool $isGeneral = false): array
|
||||
{
|
||||
$activities = [];
|
||||
$sectionId = $isGeneral ? 0 : $learnpath->source_id;
|
||||
|
||||
foreach ($learnpath->items as $item) {
|
||||
$this->addActivityToList($item, $sectionId, $activities);
|
||||
}
|
||||
|
||||
return $activities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the activities of a section.
|
||||
*/
|
||||
private function exportActivities(array $activities, string $exportDir, int $sectionId): void
|
||||
{
|
||||
$exportClasses = [
|
||||
'quiz' => QuizExport::class,
|
||||
'glossary' => GlossaryExport::class,
|
||||
'url' => UrlExport::class,
|
||||
'assign' => AssignExport::class,
|
||||
'forum' => ForumExport::class,
|
||||
'page' => PageExport::class,
|
||||
'resource' => ResourceExport::class,
|
||||
'folder' => FolderExport::class,
|
||||
'feedback' => FeedbackExport::class,
|
||||
];
|
||||
|
||||
foreach ($activities as $activity) {
|
||||
$moduleName = $activity['modulename'];
|
||||
if (isset($exportClasses[$moduleName])) {
|
||||
$exportClass = new $exportClasses[$moduleName]($this->course);
|
||||
$exportClass->export($activity['id'], $exportDir, $activity['moduleid'], $sectionId);
|
||||
} else {
|
||||
throw new \Exception("Export for module '$moduleName' is not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an item is associated with any learnpath.
|
||||
*/
|
||||
private function isItemInLearnpath(object $item, string $type): bool
|
||||
{
|
||||
if (!empty($this->course->resources[RESOURCE_LEARNPATH])) {
|
||||
foreach ($this->course->resources[RESOURCE_LEARNPATH] as $learnpath) {
|
||||
if (!empty($learnpath->items)) {
|
||||
foreach ($learnpath->items as $learnpathItem) {
|
||||
if ($learnpathItem['item_type'] === $type && $learnpathItem['path'] == $item->source_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an activity to the activities list.
|
||||
*/
|
||||
private function addActivityToList(array $item, int $sectionId, array &$activities): void
|
||||
{
|
||||
$activityData = null;
|
||||
$activityClassMap = [
|
||||
'quiz' => QuizExport::class,
|
||||
'glossary' => GlossaryExport::class,
|
||||
'url' => UrlExport::class,
|
||||
'assign' => AssignExport::class,
|
||||
'forum' => ForumExport::class,
|
||||
'page' => PageExport::class,
|
||||
'resource' => ResourceExport::class,
|
||||
'folder' => FolderExport::class,
|
||||
'feedback' => FeedbackExport::class,
|
||||
];
|
||||
|
||||
$itemType = $item['item_type'] === 'link' ? 'url' : ($item['item_type'] === 'work' ? 'assign' : ($item['item_type'] === 'survey' ? 'feedback' : $item['item_type']));
|
||||
|
||||
switch ($itemType) {
|
||||
case 'quiz':
|
||||
case 'glossary':
|
||||
case 'assign':
|
||||
case 'url':
|
||||
case 'forum':
|
||||
case 'feedback':
|
||||
$activityId = $itemType === 'glossary' ? 1 : (int) $item['path'];
|
||||
$exportClass = $activityClassMap[$itemType];
|
||||
$exportInstance = new $exportClass($this->course);
|
||||
$activityData = $exportInstance->getData($activityId, $sectionId);
|
||||
break;
|
||||
|
||||
case 'document':
|
||||
$documentId = (int) $item['path'];
|
||||
$document = \DocumentManager::get_document_data_by_id($documentId, $this->course->code);
|
||||
|
||||
// Determine the type of document and get the corresponding export class
|
||||
$documentType = $this->getDocumentType($document['filetype'], $document['path']);
|
||||
if ($documentType) {
|
||||
$activityClass = $activityClassMap[$documentType];
|
||||
$exportInstance = new $activityClass($this->course);
|
||||
$activityData = $exportInstance->getData($item['path'], $sectionId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Add the activity to the list if the data exists
|
||||
if ($activityData) {
|
||||
$activities[] = [
|
||||
'id' => $activityData['id'],
|
||||
'moduleid' => $activityData['moduleid'],
|
||||
'type' => $item['item_type'],
|
||||
'modulename' => $activityData['modulename'],
|
||||
'name' => $activityData['name'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the document type based on filetype and path.
|
||||
*/
|
||||
private function getDocumentType(string $filetype, string $path): ?string
|
||||
{
|
||||
if ('html' === pathinfo($path, PATHINFO_EXTENSION)) {
|
||||
return 'page';
|
||||
} elseif ('file' === $filetype) {
|
||||
return 'resource';
|
||||
} elseif ('folder' === $filetype) {
|
||||
return 'folder';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the section.xml file.
|
||||
*/
|
||||
private function createSectionXml(array $sectionData, string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<section id="'.$sectionData['id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <number>'.$sectionData['number'].'</number>'.PHP_EOL;
|
||||
$xmlContent .= ' <name>'.htmlspecialchars($sectionData['name']).'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <summary>'.htmlspecialchars($sectionData['summary']).'</summary>'.PHP_EOL;
|
||||
$xmlContent .= ' <summaryformat>1</summaryformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <sequence>'.implode(',', array_column($sectionData['activities'], 'moduleid')).'</sequence>'.PHP_EOL;
|
||||
$xmlContent .= ' <visible>'.$sectionData['visible'].'</visible>'.PHP_EOL;
|
||||
$xmlContent .= ' <timemodified>'.$sectionData['timemodified'].'</timemodified>'.PHP_EOL;
|
||||
$xmlContent .= '</section>'.PHP_EOL;
|
||||
|
||||
$xmlFile = $destinationDir.'/section.xml';
|
||||
file_put_contents($xmlFile, $xmlContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the inforef.xml file for the section.
|
||||
*/
|
||||
private function createInforefXml(array $sectionData, string $destinationDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<inforef>'.PHP_EOL;
|
||||
|
||||
foreach ($sectionData['activities'] as $activity) {
|
||||
$xmlContent .= ' <activity id="'.$activity['id'].'">'.htmlspecialchars($activity['name']).'</activity>'.PHP_EOL;
|
||||
}
|
||||
|
||||
$xmlContent .= '</inforef>'.PHP_EOL;
|
||||
|
||||
$xmlFile = $destinationDir.'/inforef.xml';
|
||||
file_put_contents($xmlFile, $xmlContent);
|
||||
}
|
||||
}
|
||||
89
main/inc/lib/moodleexport/UrlExport.php
Normal file
89
main/inc/lib/moodleexport/UrlExport.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/* For licensing terms, see /license.txt */
|
||||
|
||||
namespace moodleexport;
|
||||
|
||||
/**
|
||||
* Class UrlExport.
|
||||
*
|
||||
* Handles the export of URLs within a course.
|
||||
*/
|
||||
class UrlExport extends ActivityExport
|
||||
{
|
||||
/**
|
||||
* Export all URL resources into a single Moodle activity.
|
||||
*
|
||||
* @param int $activityId The ID of the URL.
|
||||
* @param string $exportDir The directory where the URL will be exported.
|
||||
* @param int $moduleId The ID of the module.
|
||||
* @param int $sectionId The ID of the section.
|
||||
*/
|
||||
public function export($activityId, $exportDir, $moduleId, $sectionId): void
|
||||
{
|
||||
// Prepare the directory where the URL export will be saved
|
||||
$urlDir = $this->prepareActivityDirectory($exportDir, 'url', $moduleId);
|
||||
|
||||
// Retrieve URL data
|
||||
$urlData = $this->getData($activityId, $sectionId);
|
||||
|
||||
// Generate XML file for the URL
|
||||
$this->createUrlXml($urlData, $urlDir);
|
||||
$this->createModuleXml($urlData, $urlDir);
|
||||
$this->createGradesXml($urlData, $urlDir);
|
||||
$this->createGradeHistoryXml($urlData, $urlDir);
|
||||
$this->createInforefXml($urlData, $urlDir);
|
||||
$this->createRolesXml($urlData, $urlDir);
|
||||
$this->createCommentsXml($urlData, $urlDir);
|
||||
$this->createCalendarXml($urlData, $urlDir);
|
||||
$this->createFiltersXml($urlData, $urlDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all URL data for the course.
|
||||
*/
|
||||
public function getData(int $activityId, int $sectionId): ?array
|
||||
{
|
||||
// Extract the URL information from the course data
|
||||
$url = $this->course->resources['link'][$activityId];
|
||||
|
||||
// Return the URL data formatted for export
|
||||
return [
|
||||
'id' => $activityId,
|
||||
'moduleid' => $activityId,
|
||||
'modulename' => 'url',
|
||||
'contextid' => $this->course->info['real_id'],
|
||||
'name' => $url->title,
|
||||
'description' => $url->description,
|
||||
'externalurl' => $url->url,
|
||||
'timecreated' => time(),
|
||||
'timemodified' => time(),
|
||||
'sectionid' => $sectionId,
|
||||
'sectionnumber' => 0,
|
||||
'users' => [],
|
||||
'files' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the XML file for the URL.
|
||||
*/
|
||||
private function createUrlXml(array $urlData, string $urlDir): void
|
||||
{
|
||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||
$xmlContent .= '<activity id="'.$urlData['id'].'" moduleid="'.$urlData['moduleid'].'" modulename="'.$urlData['modulename'].'" contextid="'.$urlData['contextid'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <url id="'.$urlData['id'].'">'.PHP_EOL;
|
||||
$xmlContent .= ' <name>'.htmlspecialchars($urlData['name']).'</name>'.PHP_EOL;
|
||||
$xmlContent .= ' <intro><![CDATA['.htmlspecialchars($urlData['description']).']]></intro>'.PHP_EOL;
|
||||
$xmlContent .= ' <introformat>1</introformat>'.PHP_EOL;
|
||||
$xmlContent .= ' <externalurl>'.htmlspecialchars($urlData['externalurl']).'</externalurl>'.PHP_EOL;
|
||||
$xmlContent .= ' <display>0</display>'.PHP_EOL;
|
||||
$xmlContent .= ' <displayoptions>a:1:{s:10:"printintro";i:1;}</displayoptions>'.PHP_EOL;
|
||||
$xmlContent .= ' <parameters>a:0:{}</parameters>'.PHP_EOL;
|
||||
$xmlContent .= ' <timemodified>'.$urlData['timemodified'].'</timemodified>'.PHP_EOL;
|
||||
$xmlContent .= ' </url>'.PHP_EOL;
|
||||
$xmlContent .= '</activity>';
|
||||
|
||||
$this->createXmlFile('url', $xmlContent, $urlDir);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user