Actualización

This commit is contained in:
Xes
2025-04-10 12:49:05 +02:00
parent 4aff98e77b
commit 1cdd00920f
9151 changed files with 1800913 additions and 0 deletions

View 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);
}
}

View 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);
}
}

View 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);
}
}

View 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&gt;&gt;&gt;&gt;&gt;'.implode(PHP_EOL.'|', $sanitizedOptions);
case 'multipleresponse':
return 'c&gt;&gt;&gt;&gt;&gt;'.implode(PHP_EOL.'|', $sanitizedOptions);
case 'dropdown':
return 'd&gt;&gt;&gt;&gt;&gt;'.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';
}
}

View 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',
];
}
}

View 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';
}
}

View 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);
}
}

View 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);
}
}

View 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;
}
}

View 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 '';
}
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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);
}
}