aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Hackl <hackl@data-quest.de>2025-10-01 14:33:20 +0200
committerThomas Hackl <hackl@data-quest.de>2025-10-01 14:33:20 +0200
commit97abcd222884db2dddb88889e253663082e759f5 (patch)
tree82e7390e049b50c450649671ef5e1d99df2d1dee
parentc1a659702122401c9c197aa5fb64d62767c4dead (diff)
Resolve "RTF-Export als Word-Dokument wiederbeleben"
Closes #5887 Merge request studip/studip!4489
-rw-r--r--app/controllers/course/members.php52
-rw-r--r--app/controllers/course/statusgroups.php44
-rw-r--r--composer.json3
-rw-r--r--composer.lock162
-rw-r--r--lib/classes/Services/Export/CourseMemberService.php297
-rw-r--r--lib/classes/Services/Export/StatusGroupsService.php291
6 files changed, 840 insertions, 9 deletions
diff --git a/app/controllers/course/members.php b/app/controllers/course/members.php
index adeb4a8..d016862 100644
--- a/app/controllers/course/members.php
+++ b/app/controllers/course/members.php
@@ -15,6 +15,9 @@
* @since 2.5
*/
+use PhpOffice\PhpWord\PhpWord;
+use Services\Export\CourseMemberService;
+
require_once 'lib/messaging.inc.php'; //Funktionen des Nachrichtensystems
class Course_MembersController extends AuthenticatedController
@@ -1766,7 +1769,7 @@ class Course_MembersController extends AuthenticatedController
$widget->addLink(
_('Als Excel-Datei exportieren'),
- URLHelper::getURL('dispatch.php/course/members/export', [
+ $this->exportURL([
'course_id' => $this->course_id,
'format' => 'xlsx',
]),
@@ -1775,17 +1778,25 @@ class Course_MembersController extends AuthenticatedController
$widget->addLink(
_('Als CSV-Datei exportieren'),
- URLHelper::getURL('dispatch.php/course/members/export', [
+ $this->exportURL([
'course_id' => $this->course_id,
'format' => 'csv',
]),
Icon::create('export')
);
+ $widget->addLink(
+ _('Als Word-Datei exportieren'),
+ $this->export_wordURL([
+ 'course_id' => $this->course_id
+ ]),
+ Icon::create('export')
+ );
+
if (count($this->awaiting) > 0) {
$widget->addLink(
_('Warteliste als Excel-Datei exportieren'),
- URLHelper::getURL('dispatch.php/course/members/export', [
+ $this->exportURL([
'course_id' => $this->course_id,
'format' => 'xlsx',
'status' => $this->waiting_type,
@@ -1794,13 +1805,22 @@ class Course_MembersController extends AuthenticatedController
);
$widget->addLink(
_('Warteliste als CSV-Datei exportieren'),
- URLHelper::getURL('dispatch.php/course/members/export', [
+ $this->exportURL([
'course_id' => $this->course_id,
'format' => 'csv',
'status' => $this->waiting_type,
]),
Icon::create('export')
);
+
+ $widget->addLink(
+ _('Als Word-Datei exportieren'),
+ $this->export_wordURL([
+ 'course_id' => $this->course_id,
+ 'status' => $this->waiting_type
+ ]),
+ Icon::create('export')
+ );
}
}
@@ -1840,12 +1860,34 @@ class Course_MembersController extends AuthenticatedController
}
}
+ /**
+ * Handles the export of the course member list as a Word document.
+ *
+ * @return void
+ * @throws \PhpOffice\PhpWord\Exception\Exception
+ */
+ public function export_word_action(): void
+ {
+ $status = Request::get('status', '');
+ $course = Course::findCurrent();
+
+ $file = new CourseMemberService($course, $status);
+ $file->save();
+
+ $this->response->add_header('Cache-Control', 'cache, must-revalidate');
+ $this->render_temporary_file(
+ $file->getFilePath(),
+ $file->getFilename(),
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
+ );
+ }
+
public function export_action()
{
$export_format = Request::get('format');
$status = Request::get('status');
- if ($export_format !== 'csv' && $export_format !== 'xlsx') {
+ if (!in_array($export_format, ['csv', 'xlsx'])) {
throw new Exception('Wrong format');
}
$header = [
diff --git a/app/controllers/course/statusgroups.php b/app/controllers/course/statusgroups.php
index 7b0a62c..f768f9e 100644
--- a/app/controllers/course/statusgroups.php
+++ b/app/controllers/course/statusgroups.php
@@ -247,7 +247,7 @@ class Course_StatusgroupsController extends AuthenticatedController
$export = new ExportWidget();
$export->addLink(
_('Als Excel-Datei exportieren'),
- URLHelper::getURL('dispatch.php/course/statusgroups/export', [
+ $this->exportURL([
'course_id' => $this->course_id,
'format' => 'xlsx',
]),
@@ -256,7 +256,26 @@ class Course_StatusgroupsController extends AuthenticatedController
$export->addLink(
_('Als CSV-Datei exportieren'),
- URLHelper::getURL('dispatch.php/course/statusgroups/export', [
+ $this->exportURL([
+ 'course_id' => $this->course_id,
+ 'format' => 'csv',
+ ]),
+ Icon::create('export')
+ );
+
+ $export->addLink(
+ _('Als CSV-Datei exportieren'),
+ $this->exportURL([
+ 'course_id' => $this->course_id,
+ 'format' => 'csv',
+ ]),
+ Icon::create('export')
+ );
+
+
+ $export->addLink(
+ _('Als Word-Datei exportieren'),
+ $this->export_wordURL([
'course_id' => $this->course_id,
'format' => 'csv',
]),
@@ -292,6 +311,27 @@ class Course_StatusgroupsController extends AuthenticatedController
}
/**
+ * Handles the export of the course member list as a Word document.
+ *
+ * @return void
+ * @throws \PhpOffice\PhpWord\Exception\Exception
+ */
+ public function export_word_action(): void
+ {
+ $course = Course::findCurrent();
+
+ $file = new \Services\Export\StatusGroupsService($course);
+ $file->save();
+
+ $this->response->add_header('Cache-Control', 'cache, must-revalidate');
+ $this->render_temporary_file(
+ $file->getFilePath(),
+ $file->getFilename(),
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
+ );
+ }
+
+ /**
*
*/
public function export_action()
diff --git a/composer.json b/composer.json
index eb634cd..a7dfd07 100644
--- a/composer.json
+++ b/composer.json
@@ -137,7 +137,8 @@
"phpseclib/phpseclib2_compat": "1.0.6",
"oat-sa/lib-lti1p3-deep-linking": "4.1.0",
"lcobucci/jwt": "^4.3",
- "guzzlehttp/guzzle": "^7.9.2"
+ "guzzlehttp/guzzle": "^7.9.2",
+ "phpoffice/phpword": "^1.4"
},
"replace": {
"symfony/polyfill-php54": "*",
diff --git a/composer.lock b/composer.lock
index f053425..40facba 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "d4ed744c2eaa2b8762e33fab3a5ae288",
+ "content-hash": "c0e4cb48905267aa9a02647b5c741d74",
"packages": [
{
"name": "algo26-matthias/idna-convert",
@@ -3474,6 +3474,58 @@
"time": "2025-04-22T08:53:15+00:00"
},
{
+ "name": "phpoffice/math",
+ "version": "0.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPOffice/Math.git",
+ "reference": "fc31c8f57a7a81f962cbf389fd89f4d9d06fc99a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPOffice/Math/zipball/fc31c8f57a7a81f962cbf389fd89f4d9d06fc99a",
+ "reference": "fc31c8f57a7a81f962cbf389fd89f4d9d06fc99a",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-xml": "*",
+ "php": "^7.1|^8.0"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^0.12.88 || ^1.0.0",
+ "phpunit/phpunit": "^7.0 || ^9.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PhpOffice\\Math\\": "src/Math/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Progi1984",
+ "homepage": "https://lefevre.dev"
+ }
+ ],
+ "description": "Math - Manipulate Math Formula",
+ "homepage": "https://phpoffice.github.io/Math/",
+ "keywords": [
+ "MathML",
+ "officemathml",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/PHPOffice/Math/issues",
+ "source": "https://github.com/PHPOffice/Math/tree/0.3.0"
+ },
+ "time": "2025-05-29T08:31:49+00:00"
+ },
+ {
"name": "phpoffice/phpspreadsheet",
"version": "5.0.0",
"source": {
@@ -3580,6 +3632,114 @@
"time": "2025-08-10T06:18:27+00:00"
},
{
+ "name": "phpoffice/phpword",
+ "version": "1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/PHPOffice/PHPWord.git",
+ "reference": "6d75328229bc93790b37e93741adf70646cea958"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/PHPOffice/PHPWord/zipball/6d75328229bc93790b37e93741adf70646cea958",
+ "reference": "6d75328229bc93790b37e93741adf70646cea958",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-gd": "*",
+ "ext-json": "*",
+ "ext-xml": "*",
+ "ext-zip": "*",
+ "php": "^7.1|^8.0",
+ "phpoffice/math": "^0.3"
+ },
+ "require-dev": {
+ "dompdf/dompdf": "^2.0 || ^3.0",
+ "ext-libxml": "*",
+ "friendsofphp/php-cs-fixer": "^3.3",
+ "mpdf/mpdf": "^7.0 || ^8.0",
+ "phpmd/phpmd": "^2.13",
+ "phpstan/phpstan": "^0.12.88 || ^1.0.0",
+ "phpstan/phpstan-phpunit": "^1.0 || ^2.0",
+ "phpunit/phpunit": ">=7.0",
+ "symfony/process": "^4.4 || ^5.0",
+ "tecnickcom/tcpdf": "^6.5"
+ },
+ "suggest": {
+ "dompdf/dompdf": "Allows writing PDF",
+ "ext-xmlwriter": "Allows writing OOXML and ODF",
+ "ext-xsl": "Allows applying XSL style sheet to headers, to main document part, and to footers of an OOXML template"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "PhpOffice\\PhpWord\\": "src/PhpWord"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL-3.0-only"
+ ],
+ "authors": [
+ {
+ "name": "Mark Baker"
+ },
+ {
+ "name": "Gabriel Bull",
+ "email": "me@gabrielbull.com",
+ "homepage": "http://gabrielbull.com/"
+ },
+ {
+ "name": "Franck Lefevre",
+ "homepage": "https://rootslabs.net/blog/"
+ },
+ {
+ "name": "Ivan Lanin",
+ "homepage": "http://ivan.lanin.org"
+ },
+ {
+ "name": "Roman Syroeshko",
+ "homepage": "http://ru.linkedin.com/pub/roman-syroeshko/34/a53/994/"
+ },
+ {
+ "name": "Antoine de Troostembergh"
+ }
+ ],
+ "description": "PHPWord - A pure PHP library for reading and writing word processing documents (OOXML, ODF, RTF, HTML, PDF)",
+ "homepage": "https://phpoffice.github.io/PHPWord/",
+ "keywords": [
+ "ISO IEC 29500",
+ "OOXML",
+ "Office Open XML",
+ "OpenDocument",
+ "OpenXML",
+ "PhpOffice",
+ "PhpWord",
+ "Rich Text Format",
+ "WordprocessingML",
+ "doc",
+ "docx",
+ "html",
+ "odf",
+ "odt",
+ "office",
+ "pdf",
+ "php",
+ "reader",
+ "rtf",
+ "template",
+ "template processor",
+ "word",
+ "writer"
+ ],
+ "support": {
+ "issues": "https://github.com/PHPOffice/PHPWord/issues",
+ "source": "https://github.com/PHPOffice/PHPWord/tree/1.4.0"
+ },
+ "time": "2025-06-05T10:32:36+00:00"
+ },
+ {
"name": "phpoption/phpoption",
"version": "1.9.3",
"source": {
diff --git a/lib/classes/Services/Export/CourseMemberService.php b/lib/classes/Services/Export/CourseMemberService.php
new file mode 100644
index 0000000..4806ab8
--- /dev/null
+++ b/lib/classes/Services/Export/CourseMemberService.php
@@ -0,0 +1,297 @@
+<?php
+
+namespace Services\Export;
+
+use PhpOffice\PhpWord\Exception\Exception;
+use PhpOffice\PhpWord\SimpleType\Jc;
+use PhpOffice\PhpWord\Style\Language;
+use PhpOffice\PhpWord\Writer\WriterInterface;
+use PhpOffice\PhpWord\PhpWord;
+use PhpOffice\PhpWord\IOFactory;
+use Course;
+
+final class CourseMemberService
+{
+ public const WORD = 'docx';
+ public const EXCEL = 'xlsx';
+ public const CSV = 'csv';
+
+ protected string $filepath;
+ protected string $export_format = CourseMemberService::WORD;
+
+ public function __construct(
+ protected Course $course,
+ protected string $status = ''
+ )
+ {
+ $this->filepath = tempnam($GLOBALS['TMP_PATH'], 'documents');
+ }
+
+ /**
+ * Generates a Word document containing the course member list.
+ *
+ * @return WriterInterface
+ * @throws Exception
+ */
+ public function getWordFile(): WriterInterface
+ {
+ $members = $this->extractMembers();
+
+ $word = new PhpWord();
+ $section = $word->addSection();
+
+ $footer = $section->addFooter();
+ $footer->addText(
+ sprintf(_('Erstellt am: %s'), date('d.m.Y H:i')),
+ ['italic' => true, 'size' => 8],
+ ['alignment' => Jc::END]
+ );
+ $properties = $word->getSettings();
+ $properties->setThemeFontLang(new Language($GLOBALS['_language']));
+
+ $section->addText(
+ $this->getHeadline(),
+ ['bold' => true, 'size' => 14]
+ );
+ $section->addTextBreak();
+
+ $section->addText(
+ $this->getListType(),
+ ['bold' => true, 'size' => 12],
+ );
+
+ $table_style = [
+ 'borderSize' => 4,
+ 'borderColor' => '000000',
+ 'cellMarginTop' => 0,
+ 'cellMarginBottom' => 0,
+ 'cellMarginLeft' => 40,
+ 'cellMarginRight' => 40,
+ ];
+
+ $cell_style = [
+ 'spaceBefore' => 0,
+ 'spaceAfter' => 0
+ ];
+
+ $text_style = ['size' => 8];
+ $firstRowStyle = ['tblHeader' => true];
+
+ $word->addTableStyle('MembersTable', $table_style, $firstRowStyle);
+
+ $word->addNumberingStyle('degree_bullets', [
+ 'type' => 'multilevel',
+ 'levels' => [
+ [
+ 'format' => 'bullet',
+ 'text' => '•',
+ 'left' => 200,
+ 'hanging' => 200,
+ 'tabPos' => 200,
+ ]
+ ],
+ ]);
+
+ $table = $section->addTable('MembersTable');
+
+ $headers = [
+ _('Name'),
+ _('E-Mail'),
+ _('Telefon'),
+ _('Studiengänge')
+ ];
+
+ $table->addRow();
+ foreach ($headers as $h) {
+ $table->addCell(
+ 2500,
+ [
+ 'valign' => 'center',
+ 'bgColor' => 'BFBFBF'
+ ]
+ )->addText(
+ $h,
+ [
+ 'bold' => true,
+ 'size' => 10,
+ ],
+ $cell_style
+ );
+ }
+
+ foreach ($members as $status => $users) {
+ $table->addRow();
+ $table->addCell(
+ 14000,
+ [
+ 'gridSpan' => 4,
+ 'bgColor' => 'D9D9D9'
+ ]
+ )->addText(
+ get_title_for_status($status, count($users)),
+ [
+ 'bold' => true,
+ 'size' => 10
+ ],
+ $cell_style
+ );
+
+ if (!empty($users)) {
+ foreach ($users as $user) {
+ $table->addRow();
+ $table->addCell(3000, ['noWrap' => true])
+ ->addText(
+ htmlReady($user['Nachname'] . ', ' . $user['Vorname']),
+ $text_style,
+ $cell_style
+ );
+ $table->addCell(4500, ['noWrap' => true])
+ ->addText(
+ htmlReady($user['Email']),
+ $text_style,
+ $cell_style
+ );
+ $table->addCell(2000)
+ ->addText(
+ htmlReady($user['privatnr']),
+ $text_style,
+ $cell_style
+ );
+
+ $cell = $table->addCell(2400);
+ if (!empty($user['studiengaenge'])) {
+ $degrees = explode(';', $user['studiengaenge']);
+ if (count($degrees) > 1) {
+ foreach ($degrees as $degree) {
+ $cell->addListItem(
+ trim($degree),
+ 0,
+ $text_style,
+ 'degree_bullets',
+ [
+ 'spaceBefore' => 0,
+ 'spaceAfter' => 60,
+ ]
+ );
+ }
+ } else {
+ $cell->addText($user['studiengaenge'], $text_style, $cell_style);
+ }
+ } else {
+ $cell->addText('', $text_style, $cell_style);
+ }
+ }
+ }
+ }
+
+ return IOFactory::createWriter($word);
+ }
+
+ /**
+ * Extracts and organizes course member data by status.
+ *
+ * @return array
+ */
+ public function extractMembers(): array
+ {
+ $members = $this->course->getMembersData($this->status);
+ $_members = [];
+ foreach ($members as $user_id => $data) {
+ $_members[$data['status']][$user_id] = $data;
+ };
+ return $_members;
+ }
+
+ /**
+ * Saves the generated Word document to the configured file path.
+ *
+ * @return void
+ * @throws Exception
+ */
+
+ public function save(): void
+ {
+ if ($this->export_format === CourseMemberService::WORD) {
+ $this->getWordFile()
+ ->save($this->filepath);
+ }
+ }
+
+ /**
+ * @param string $filepath
+ * @return $this
+ */
+ public function setFilePath(string $filepath): self
+ {
+ $this->filepath = $filepath;
+ return $this;
+ }
+
+ /**
+ * Returns the full path where the Word document will be saved.
+ *
+ * @return string The absolute file path for the generated document.
+ */
+ public function getFilePath(): string
+ {
+ return $this->filepath;
+ }
+
+ /**
+ * Generates the headline for the Word document.
+ *
+ * @return string
+ */
+ private function getHeadline(): string
+ {
+ return sprintf('%s: %s', $this->getListType(), $this->course->getFullName());
+ }
+
+ /**
+ * Returns the type of list based on the current status.
+ *
+ * @return string
+ */
+ public function getListType(): string
+ {
+ if (in_array($this->status, ['awaiting', 'claiming'])) {
+ return _('Warteliste');
+ }
+ return _('Teilnehmendenliste');
+ }
+
+ /**
+ * Generates the filename for the exported Word document.
+ *
+ * @return string
+ */
+ public function getFilename(): string
+ {
+ $file_name = _('Teilnehmendenexport');
+ if (in_array($this->status, ['awaiting', 'claiming'])) {
+ $file_name = _('Wartelistenexport');
+ }
+ return $file_name . '.' . $this->export_format;
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getExportFormat(): string
+ {
+ return $this->export_format;
+ }
+
+ /**
+ * @param string $format
+ * @return $this
+ */
+ public function setExportFormat(string $format): self
+ {
+ $this->export_format = $format;
+
+ return $this;
+ }
+
+}
diff --git a/lib/classes/Services/Export/StatusGroupsService.php b/lib/classes/Services/Export/StatusGroupsService.php
new file mode 100644
index 0000000..d13f21f
--- /dev/null
+++ b/lib/classes/Services/Export/StatusGroupsService.php
@@ -0,0 +1,291 @@
+<?php
+
+namespace Services\Export;
+
+use PhpOffice\PhpWord\Exception\Exception;
+use PhpOffice\PhpWord\SimpleType\Jc;
+use PhpOffice\PhpWord\Style\Language;
+use PhpOffice\PhpWord\Writer\WriterInterface;
+use PhpOffice\PhpWord\PhpWord;
+use PhpOffice\PhpWord\IOFactory;
+use Course;
+
+final class StatusGroupsService
+{
+ public const WORD = 'docx';
+ public const EXCEL = 'xlsx';
+ public const CSV = 'csv';
+
+ protected string $filepath;
+ protected string $export_format = CourseMemberService::WORD;
+
+ public function __construct(
+ protected Course $course
+ ) {
+ $this->filepath = tempnam($GLOBALS['TMP_PATH'], 'documents');
+ }
+
+ /**
+ * Generates a Word document containing the course member list.
+ *
+ * @return WriterInterface
+ * @throws Exception
+ */
+ public function getWordFile(): WriterInterface
+ {
+ $members = $this->extractMembers();
+
+ $word = new PhpWord();
+ $section = $word->addSection();
+ $footer = $section->addFooter();
+ $footer->addText(
+ sprintf(_('Erstellt am: %s'), date('d.m.Y H:i')),
+ ['italic' => true, 'size' => 8],
+ ['alignment' => Jc::END]
+ );
+ $properties = $word->getSettings();
+ $properties->setThemeFontLang(new Language($GLOBALS['_language']));
+
+ $section->addText(
+ $this->course->getFullName(),
+ ['bold' => true, 'size' => 14]
+ );
+ $section->addTextBreak();
+
+ $section->addText(
+ $this->getListType(),
+ ['bold' => true, 'size' => 12],
+ );
+
+ $table_style = [
+ 'borderSize' => 4,
+ 'borderColor' => '000000',
+ 'cellMarginTop' => 0,
+ 'cellMarginBottom' => 0,
+ 'cellMarginLeft' => 40,
+ 'cellMarginRight' => 40,
+ ];
+
+ $cell_style = [
+ 'spaceBefore' => 0,
+ 'spaceAfter' => 0
+ ];
+
+ $text_style = ['size' => 8];
+ $firstRowStyle = ['tblHeader' => true];
+
+ $word->addTableStyle('MembersTable', $table_style, $firstRowStyle);
+
+ $word->addNumberingStyle('degree_bullets', [
+ 'type' => 'multilevel',
+ 'levels' => [
+ [
+ 'format' => 'bullet',
+ 'text' => '•',
+ 'left' => 200,
+ 'hanging' => 200,
+ 'tabPos' => 200,
+ ]
+ ],
+ ]);
+
+ $table = $section->addTable('MembersTable');
+
+ $headers = [
+ _('Name'),
+ _('E-Mail'),
+ _('Telefon'),
+ _('Studiengänge')
+ ];
+
+ $table->addRow();
+ foreach ($headers as $h) {
+ $table->addCell(
+ 2500,
+ [
+ 'valign' => 'center',
+ 'bgColor' => 'BFBFBF'
+ ]
+ )->addText(
+ $h,
+ [
+ 'bold' => true,
+ 'size' => 10,
+ ],
+ $cell_style
+ );
+ }
+
+ foreach ($members as $group_name => $users) {
+ $table->addRow();
+ $table->addCell(
+ 14000,
+ [
+ 'gridSpan' => 4,
+ 'bgColor' => 'D9D9D9'
+ ]
+ )->addText(
+ htmlReady($group_name),
+ [
+ 'bold' => true,
+ 'size' => 10
+ ],
+ $cell_style
+ );
+
+ if (!empty($users)) {
+ foreach ($users as $user) {
+ $table->addRow();
+ $table->addCell(3000, ['noWrap' => true])
+ ->addText(
+ htmlReady($user['Nachname'] . ', ' . $user['Vorname']),
+ $text_style,
+ $cell_style
+ );
+ $table->addCell(4500, ['noWrap' => true])
+ ->addText(
+ htmlReady($user['Email']),
+ $text_style,
+ $cell_style
+ );
+ $table->addCell(2000)
+ ->addText(
+ htmlReady($user['privatnr']),
+ $text_style,
+ $cell_style
+ );
+
+ $cell = $table->addCell(2400);
+ if (!empty($user['studiengaenge'])) {
+ $degrees = explode(';', $user['studiengaenge']);
+ if (count($degrees) > 1) {
+ foreach ($degrees as $degree) {
+ $cell->addListItem(
+ trim($degree),
+ 0,
+ $text_style,
+ 'degree_bullets',
+ [
+ 'spaceBefore' => 0,
+ 'spaceAfter' => 60,
+ ]
+ );
+ }
+ } else {
+ $cell->addText($user['studiengaenge'], $text_style, $cell_style);
+ }
+ } else {
+ $cell->addText('', $text_style, $cell_style);
+ }
+ }
+ }
+ }
+
+ return IOFactory::createWriter($word);
+ }
+
+ /**
+ * Extracts and organizes course member data by status.
+ *
+ * @return array
+ */
+ public function extractMembers(): array
+ {
+ $groups = $this->course->statusgruppen;
+ $result = [];
+ if ($groups) {
+ $assigned_with_group = [];
+ foreach ($groups as $group) {
+ foreach ($group->members->orderBy('nachname,vorname') as $member) {
+ $assigned_with_group[$member->user_id] = true;
+ $result[(string)$group->name][$member->user_id] = $member->getExportData();
+ }
+ }
+ $members = $this->course->members->filter(function ($group_member) use ($assigned_with_group) {
+ return !array_key_exists($group_member->user_id, $assigned_with_group);
+ })->orderBy('nachname,vorname');
+
+ foreach ($members as $member) {
+ $result[_('keiner Funktion oder Gruppe zugeordnet')][$member->user_id] = $member->getExportData();
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Saves the generated Word document to the configured file path.
+ *
+ * @return void
+ * @throws Exception
+ */
+
+ public function save(): void
+ {
+ if ($this->export_format === CourseMemberService::WORD) {
+ $this->getWordFile()
+ ->save($this->filepath);
+ }
+ }
+
+ /**
+ * @param string $filepath
+ * @return $this
+ */
+ public function setFilePath(string $filepath): self
+ {
+ $this->filepath = $filepath;
+ return $this;
+ }
+
+ /**
+ * Returns the full path where the Word document will be saved.
+ *
+ * @return string The absolute file path for the generated document.
+ */
+ public function getFilePath(): string
+ {
+ return $this->filepath;
+ }
+
+ /**
+ * Returns the type of list based on the current status.
+ *
+ * @return string
+ */
+ public function getListType(): string
+ {
+ return _('Gruppenliste');
+ }
+
+ /**
+ * Generates the filename for the exported Word document.
+ *
+ * @return string
+ */
+ public function getFilename(): string
+ {
+ return _('Gruppenliste') . '.' . $this->export_format;
+ }
+
+
+ /**
+ * @return string
+ */
+ public function getExportFormat(): string
+ {
+ return $this->export_format;
+ }
+
+ /**
+ * @param string $format
+ * @return $this
+ */
+ public function setExportFormat(string $format): self
+ {
+ $this->export_format = $format;
+
+ return $this;
+ }
+
+}