Upgrade 1-11.38

This commit is contained in:
xesmyd
2026-03-30 14:10:30 +02:00
parent f2a7e6d1fc
commit ac648ef29d
24665 changed files with 69682 additions and 2205004 deletions
+161 -55
View File
@@ -15,15 +15,17 @@ use Exception;
class SectionExport
{
private $course;
private $activitiesBySection = [];
/**
* Constructor to initialize the course object.
*
* @param object $course The course object to be exported.
*/
public function __construct($course)
public function __construct($course, array $activitiesBySection = [])
{
$this->course = $course;
$this->activitiesBySection = $activitiesBySection;
}
/**
@@ -47,8 +49,8 @@ class SectionExport
$sectionData = [
'id' => 0,
'number' => 0,
'name' => get_lang('General'),
'summary' => get_lang('GeneralResourcesCourse'),
'name' => $this->sanitizeText((string) get_lang('General')),
'summary' => (string) get_lang('GeneralResourcesCourse'),
'sequence' => 0,
'visible' => 1,
'timemodified' => time(),
@@ -68,7 +70,6 @@ class SectionExport
{
$generalItems = [];
// List of resource types and their corresponding ID keys
$resourceTypes = [
RESOURCE_DOCUMENT => 'source_id',
RESOURCE_QUIZ => 'source_id',
@@ -85,8 +86,9 @@ class SectionExport
foreach ($this->course->resources[$resourceType] as $id => $resource) {
if (!$this->isItemInLearnpath($resource, $resourceType)) {
$title = $resourceType === RESOURCE_WORK
? ($resource->params['title'] ?? '')
: ($resource->title ?? $resource->name);
? (string) ($resource->params['title'] ?? '')
: (string) ($resource->title ?? $resource->name ?? '');
$generalItems[] = [
'id' => $resource->$idKey,
'item_type' => $resourceType,
@@ -106,6 +108,10 @@ class SectionExport
*/
public function getActivitiesForGeneral(): array
{
if (isset($this->activitiesBySection[0]) && is_array($this->activitiesBySection[0])) {
return $this->activitiesBySection[0];
}
$generalLearnpath = (object) [
'items' => $this->getGeneralItems(),
'source_id' => 0,
@@ -113,7 +119,7 @@ class SectionExport
$activities = $this->getActivitiesForSection($generalLearnpath, true);
if (!in_array('folder', array_column($activities, 'modulename'))) {
if (!in_array('folder', array_column($activities, 'modulename'), true)) {
$activities[] = [
'id' => 0,
'moduleid' => 0,
@@ -132,7 +138,7 @@ class SectionExport
public function getLearnpathById(int $sectionId): ?object
{
foreach ($this->course->resources[RESOURCE_LEARNPATH] as $learnpath) {
if ($learnpath->source_id == $sectionId) {
if ((int) $learnpath->source_id === $sectionId) {
return $learnpath;
}
}
@@ -145,14 +151,16 @@ class SectionExport
*/
public function getSectionData(object $learnpath): array
{
$sectionId = (int) $learnpath->source_id;
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),
'id' => $sectionId,
'number' => (int) ($learnpath->display_order ?? 0),
'name' => $this->sanitizeText((string) ($learnpath->name ?? '')),
'summary' => (string) ($learnpath->description ?? ''),
'sequence' => $sectionId,
'visible' => 1,
'timemodified' => !empty($learnpath->modified_on) ? (int) strtotime((string) $learnpath->modified_on) : time(),
'activities' => $this->getActivitiesForSection($learnpath),
];
}
@@ -162,9 +170,13 @@ class SectionExport
*/
public function getActivitiesForSection(object $learnpath, bool $isGeneral = false): array
{
$activities = [];
$sectionId = $isGeneral ? 0 : $learnpath->source_id;
$sectionId = $isGeneral ? 0 : (int) $learnpath->source_id;
if (isset($this->activitiesBySection[$sectionId]) && is_array($this->activitiesBySection[$sectionId])) {
return $this->activitiesBySection[$sectionId];
}
$activities = [];
foreach ($learnpath->items as $item) {
$this->addActivityToList($item, $sectionId, $activities);
}
@@ -195,22 +207,76 @@ class SectionExport
$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.");
throw new Exception("Export for module '$moduleName' is not supported.");
}
}
}
/**
* Normalize resource / LP item types for reliable comparison.
*/
private function normalizeItemTypeForLpComparison(string $type): string
{
switch ($type) {
case 'student_publication':
case 'work':
return 'work';
case 'link':
case 'url':
return 'link';
case 'survey':
case 'feedback':
return 'survey';
default:
return $type;
}
}
/**
* 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;
if (empty($this->course->resources[RESOURCE_LEARNPATH])) {
return false;
}
$normalizedType = $this->normalizeItemTypeForLpComparison($type);
$itemSourceId = isset($item->source_id) ? (string) $item->source_id : '';
foreach ($this->course->resources[RESOURCE_LEARNPATH] as $learnpath) {
if (empty($learnpath->items)) {
continue;
}
foreach ($learnpath->items as $learnpathItem) {
$lpType = isset($learnpathItem['item_type'])
? $this->normalizeItemTypeForLpComparison((string) $learnpathItem['item_type'])
: '';
if ($lpType !== $normalizedType) {
continue;
}
$lpPath = isset($learnpathItem['path']) ? (string) $learnpathItem['path'] : '';
if ($itemSourceId !== '' && $lpPath === $itemSourceId) {
return true;
}
if ($normalizedType === 'document' && $itemSourceId !== '' && ctype_digit($itemSourceId)) {
$doc = \DocumentManager::get_document_data_by_id((int) $itemSourceId, $this->course->code);
if (!empty($doc['path'])) {
$docPath = (string) $doc['path'];
$candidates = [$docPath, 'document/'.$docPath, '/'.$docPath];
foreach ($candidates as $candidate) {
if ($lpPath === $candidate) {
return true;
}
}
}
}
@@ -249,14 +315,14 @@ class SectionExport
'feedback' => FeedbackExport::class,
];
if ($item['id'] == 'course_homepage') {
if (($item['id'] ?? null) === 'course_homepage') {
$item['item_type'] = 'page';
$item['path'] = 0;
}
$itemType = $item['item_type'] === 'link' ? 'url' :
($item['item_type'] === 'work' || $item['item_type'] === 'student_publication' ? 'assign' :
($item['item_type'] === 'survey' ? 'feedback' : $item['item_type']));
$itemType = ($item['item_type'] ?? '') === 'link' ? 'url'
: ((($item['item_type'] ?? '') === 'work' || ($item['item_type'] ?? '') === 'student_publication') ? 'assign'
: ((($item['item_type'] ?? '') === 'survey') ? 'feedback' : ($item['item_type'] ?? '')));
switch ($itemType) {
case 'quiz':
@@ -266,41 +332,48 @@ class SectionExport
case 'forum':
case 'feedback':
case 'page':
$activityId = $itemType === 'glossary' ? 1 : (int) $item['path'];
$activityId = $itemType === 'glossary' ? 1 : (int) ($item['path'] ?? 0);
$exportClass = $activityClassMap[$itemType];
$exportInstance = new $exportClass($this->course);
$activityData = $exportInstance->getData($activityId, $sectionId);
break;
case 'document':
$documentId = (int) $item['path'];
$documentId = (int) ($item['path'] ?? 0);
$document = \DocumentManager::get_document_data_by_id($documentId, $this->course->code);
if ($document) {
$isRoot = substr_count($document['path'], '/') === 1;
$documentType = $this->getDocumentType($document['filetype'], $document['path']);
if ($documentType === 'page' && $isRoot) {
$activityClass = $activityClassMap['page'];
$exportInstance = new $activityClass($this->course);
$activityData = $exportInstance->getData($item['path'], $sectionId);
}
elseif ($sectionId > 0 && $documentType && isset($activityClassMap[$documentType])) {
$documentType = $this->getDocumentType((string) $document['filetype'], (string) $document['path']);
if ($sectionId > 0 && $documentType && isset($activityClassMap[$documentType])) {
$activityClass = $activityClassMap[$documentType];
$exportInstance = new $activityClass($this->course);
$activityData = $exportInstance->getData($item['path'], $sectionId);
$activityData = $exportInstance->getData($documentId, $sectionId);
} elseif ($sectionId === 0 && $documentType === 'page') {
$activityClass = $activityClassMap['page'];
$exportInstance = new $activityClass($this->course);
$activityData = $exportInstance->getData($documentId, $sectionId);
}
}
break;
}
// Add the activity to the list if the data exists
if (!empty($activityData) && $sectionId > 0) {
$lpItemId = isset($item['id']) ? (int) $item['id'] : 0;
$modName = (string) ($activityData['modulename'] ?? '');
if ($lpItemId > 0 && !in_array($modName, ['folder', 'glossary'], true)) {
$activityData['moduleid'] = 900000000 + $lpItemId;
}
}
if ($activityData) {
$activities[] = [
'id' => $activityData['id'],
'moduleid' => $activityData['moduleid'],
'type' => $item['item_type'],
'modulename' => $activityData['modulename'],
'name' => $activityData['name'],
'name' => $this->sanitizeText((string) ($activityData['name'] ?? '')),
];
}
}
@@ -310,13 +383,15 @@ class SectionExport
*/
private function getDocumentType(string $filetype, string $path): ?string
{
if ('html' === pathinfo($path, PATHINFO_EXTENSION)) {
$ext = strtolower((string) pathinfo($path, PATHINFO_EXTENSION));
if ($ext === 'html' || $ext === 'htm') {
return 'page';
} elseif ('file' === $filetype) {
}
if ($filetype === 'file') {
return 'resource';
} /*elseif ('folder' === $filetype) {
return 'folder';
}*/
}
return null;
}
@@ -326,19 +401,20 @@ class SectionExport
*/
private function createSectionXml(array $sectionData, string $destinationDir): void
{
$sequence = implode(',', array_column($sectionData['activities'], 'moduleid'));
$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 .= ' <name>'.htmlspecialchars((string) $sectionData['name']).'</name>'.PHP_EOL;
$xmlContent .= ' <summary>'.htmlspecialchars((string) $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 .= ' <sequence>'.$sequence.'</sequence>'.PHP_EOL;
$xmlContent .= ' <visible>1</visible>'.PHP_EOL;
$xmlContent .= ' <timemodified>'.$sectionData['timemodified'].'</timemodified>'.PHP_EOL;
$xmlContent .= '</section>'.PHP_EOL;
$xmlFile = $destinationDir.'/section.xml';
file_put_contents($xmlFile, $xmlContent);
file_put_contents($destinationDir.'/section.xml', $xmlContent);
}
/**
@@ -350,12 +426,42 @@ class SectionExport
$xmlContent .= '<inforef>'.PHP_EOL;
foreach ($sectionData['activities'] as $activity) {
$xmlContent .= ' <activity id="'.$activity['id'].'">'.htmlspecialchars($activity['name']).'</activity>'.PHP_EOL;
$refId = $activity['moduleid'] ?? $activity['id'];
$xmlContent .= ' <activity id="'.$refId.'">'.htmlspecialchars((string) ($activity['name'] ?? '')).'</activity>'.PHP_EOL;
}
$xmlContent .= '</inforef>'.PHP_EOL;
$xmlFile = $destinationDir.'/inforef.xml';
file_put_contents($xmlFile, $xmlContent);
file_put_contents($destinationDir.'/inforef.xml', $xmlContent);
}
/**
* Sanitize section or activity titles for Moodle.
*/
private function sanitizeText(string $raw, int $maxLen = 255): string
{
$text = trim($raw);
if ($text === '') {
return '';
}
$text = html_entity_decode($text, ENT_QUOTES | ENT_HTML5, 'UTF-8');
$text = strip_tags($text);
$text = preg_replace('/\s+/u', ' ', $text);
$text = trim($text);
if ($text === '') {
return '';
}
if (function_exists('mb_strlen') && function_exists('mb_substr')) {
if (mb_strlen($text, 'UTF-8') > $maxLen) {
$text = mb_substr($text, 0, $maxLen, 'UTF-8');
}
} elseif (strlen($text) > $maxLen) {
$text = substr($text, 0, $maxLen);
}
return $text;
}
}