<?php
/**
 ***********************************************************************************************
 * Category Report
 *
 * Creates a list of all roles and categories a member has.
 *
 * @copyright The Admidio Team
 * @see https://www.admidio.org/
 * @license https://www.gnu.org/licenses/gpl-2.0.html GNU General Public License v2.0 only
 *
 * Parameters:
 *
 * mode            : Output (html, print, xlsx, csv-oo, pdf, pdfl)
 * export_features  : 0 - (Default) No export menu
 *                    1 - Export menu is enabled
 * config            : the selected configuration
 ***********************************************************************************************
 */
use Admidio\Infrastructure\Exception;
use Admidio\Infrastructure\Utils\FileSystemUtils;
use Admidio\Infrastructure\Utils\SecurityUtils;
use Admidio\UI\Component\DataTables;
use Admidio\UI\Presenter\FormPresenter;
use Admidio\UI\Presenter\PagePresenter;
use Admidio\Users\Entity\User;
use Admidio\Changelog\Service\ChangelogService;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;

try {
    require_once(__DIR__ . '/../../system/common.php');

    // check if the module is enabled and disallow access if it's disabled
    if (!$gSettingsManager->getBool('category_report_module_enabled')) {
        throw new Exception('SYS_MODULE_DISABLED');
    }

    // user must have the permission "rol_all_lists_view"
    if (!$gCurrentUser->checkRolesRight('rol_all_lists_view')) {
        throw new Exception('SYS_NO_RIGHTS');
    }

    // Read in the configuration array
    $report = new CategoryReport();
    $config = $report->getConfigArray();

    $getCrtId = admFuncVariableIsValid($_GET, 'crt_id', 'int', array('defaultValue' => $gSettingsManager->get('category_report_default_configuration')));
    $getMode = admFuncVariableIsValid($_GET, 'mode', 'string', array('defaultValue' => 'html', 'validValues' => array('xlsx', 'csv-oo', 'html', 'print', 'pdf', 'pdfl')));
    $getFilter = admFuncVariableIsValid($_GET, 'filter', 'string');
    $getExportAndFilter = admFuncVariableIsValid($_GET, 'export_and_filter', 'bool', array('defaultValue' => false));

    // initialize some special mode parameters
    $separator = '';
    $valueQuotes = '';
    $charset = '';
    $classTable = '';
    $orientation = '';

    switch ($getMode) {
        case 'xlsx':
            $charset = 'utf-8';
            break;
        case 'csv-oo':
            $separator = ',';  // a CSV file should have a comma
            $valueQuotes = '"';  // all values should be set with quotes
            $getMode = 'csv';
            $charset = 'utf-8';
            break;
        case 'pdf':
            $classTable = 'table';
            $orientation = 'P';
            $getMode = 'pdf';
            break;
        case 'pdfl':
            $classTable = 'table';
            $orientation = 'L';
            $getMode = 'pdf';
            break;
        case 'html':
            $classTable = 'table table-condensed table-hover';
            break;
        case 'print':
            $classTable = 'table table-condensed table-striped';
            break;
        default:
            break;
    }

    // CSV file as string
    $csvStr = '';

    // data array
    $data = array('headers' => array(), 'rows' => array(), 'column_align' => array());

    // generate the display list
    $report->setConfiguration($getCrtId);
    $report->generate_listData();

    $numMembers = count($report->listData);

    if ($numMembers == 0) {
        // There is no data available !
        throw new Exception('SYS_NO_USER_FOUND');
    }

    $columnCount = count($report->headerData);

    // define title (html) and headline
    $title = $gL10n->get('SYS_CATEGORY_REPORT');
    $headline = $gL10n->get('SYS_CATEGORY_REPORT');
    $subHeadline = $config[$report->getConfiguration()]['name'];

    $filename = $gCurrentOrganization->getValue('org_shortname') . '-' . $headline . '-' . $subHeadline;

    if ($getMode === 'html') {
        $gNavigation->addStartUrl(CURRENT_URL, $headline, 'bi-list-stars');
    }

    if ($getMode !== 'csv') {
        $page = PagePresenter::withHtmlIDAndHeadline('adm_category_report');
        $smarty = $page->createSmartyObject();
        $smarty->assign('l10n', $gL10n);
        if ($getMode === 'print') {
            $page->setContentFullWidth();
            $page->setPrintMode();
            $page->setTitle($title);
            $page->setHeadline($headline);
            $page->addHtml('<h5 class="admidio-content-subheader">' . $subHeadline . '</h5>');
            $smarty->assign('classTable', $classTable);
        } elseif ($getMode === 'pdf') {
            if (ini_get('max_execution_time') < 600) {
                ini_set('max_execution_time', 600); //600 seconds = 10 minutes
            }

            $pdf = new TCPDF($orientation, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);

            // set document information
            $pdf->SetCreator(PDF_CREATOR);
            $pdf->SetAuthor('Admidio');
            $pdf->SetTitle($headline);

            // remove default header/footer
            $pdf->setPrintHeader(true);
            $pdf->setPrintFooter(false);
            // set header and footer fonts
            $pdf->setHeaderFont(array(PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN));
            $pdf->setFooterFont(array(PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA));

            // set auto page breaks
            $pdf->SetAutoPageBreak(true, PDF_MARGIN_BOTTOM);
            $pdf->SetMargins(10, 20, 10);
            $pdf->setHeaderMargin(10);
            $pdf->setFooterMargin(0);

            // headline for PDF
            $pdf->setHeaderData('', 0, $headline);

            // set font
            $pdf->SetFont('times', '', 10);

            // add a page
            $pdf->AddPage();

            // set subHeadline and class for table
            $smarty->assign('subHeadline', $subHeadline);
            $smarty->assign('classTable', $classTable);
        } elseif ($getMode === 'html') {
            // create html page object
            $page->setContentFullWidth();
            $page->setTitle($title);
            $page->setHeadline($headline);

            $page->addJavascript(
                '
            $("#menu_item_lists_print_view").click(function() {
                window.open("' . SecurityUtils::encodeUrl(ADMIDIO_URL . FOLDER_MODULES . '/category-report/category_report.php', array(
                    'mode' => 'print',
                    'filter' => $getFilter,
                    'export_and_filter' => $getExportAndFilter,
                    'crt_id' => $getCrtId
                )) . '", "_blank");
            });',
                true
            );

            if ($getExportAndFilter) {
                // link to print overlay and exports
                $page->addPageFunctionsMenuItem('menu_item_lists_print_view', $gL10n->get('SYS_PRINT_PREVIEW'), 'javascript:void(0);', 'bi-printer-fill');

                // dropdown menu item with all export possibilities
                $page->addPageFunctionsMenuItem('menu_item_lists_export', $gL10n->get('SYS_EXPORT'), '#', 'bi-download');
                $page->addPageFunctionsMenuItem(
                    'menu_item_lists_xlsx',
                    $gL10n->get('SYS_MICROSOFT_EXCEL') .' (*.xlsx)',
                    SecurityUtils::encodeUrl(ADMIDIO_URL . FOLDER_MODULES . '/category-report/category_report.php', array(
                        'crt_id' => $getCrtId,
                        'filter' => $getFilter,
                        'export_and_filter' => $getExportAndFilter,
                        'mode' => 'xlsx')),
                    'bi-file-earmark-excel',
                    'menu_item_lists_export'
                );
                $page->addPageFunctionsMenuItem(
                    'menu_item_lists_pdf',
                    $gL10n->get('SYS_PDF') . ' (' . $gL10n->get('SYS_PORTRAIT') . ')',
                    SecurityUtils::encodeUrl(ADMIDIO_URL . FOLDER_MODULES . '/category-report/category_report.php', array(
                        'crt_id' => $getCrtId,
                        'filter' => $getFilter,
                        'export_and_filter' => $getExportAndFilter,
                        'mode' => 'pdf')),
                    'bi-file-earmark-pdf',
                    'menu_item_lists_export'
                );
                $page->addPageFunctionsMenuItem(
                    'menu_item_lists_pdfl',
                    $gL10n->get('SYS_PDF') . ' (' . $gL10n->get('SYS_LANDSCAPE') . ')',
                    SecurityUtils::encodeUrl(ADMIDIO_URL . FOLDER_MODULES . '/category-report/category_report.php', array(
                        'crt_id' => $getCrtId,
                        'filter' => $getFilter,
                        'export_and_filter' => $getExportAndFilter,
                        'mode' => 'pdfl')),
                    'bi-file-earmark-pdf',
                    'menu_item_lists_export'
                );
                $page->addPageFunctionsMenuItem(
                    'menu_item_lists_csv',
                    $gL10n->get('SYS_CSV') . ' (' . $gL10n->get('SYS_UTF8') . ')',
                    SecurityUtils::encodeUrl(ADMIDIO_URL . FOLDER_MODULES . '/category-report/category_report.php', array(
                        'crt_id' => $getCrtId,
                        'filter' => $getFilter,
                        'export_and_filter' => $getExportAndFilter,
                        'mode' => 'csv-oo')),
                    'bi-filetype-csv',
                    'menu_item_lists_export'
                );
            } else {
                // if filter is not enabled, reset filterstring
                $getFilter = '';
            }

            if ($gCurrentUser->isAdministrator()) {
                // show link to pluginpreferences
                $page->addPageFunctionsMenuItem(
                    'admMenuItemPreferencesLists',
                    $gL10n->get('SYS_CONFIGURATIONS'),
                    ADMIDIO_URL . FOLDER_MODULES . '/category-report/preferences.php',
                    'bi-gear-fill'
                );
            }

            ChangelogService::displayHistoryButton($page, 'categoryreport', 'category_report');

            // process changes in the navbar form with javascript submit
            $page->addJavascript(
                '
                $("#export_and_filter").change(function() {
                    $("#adm_navbar_filter_form_category_report").submit();
                });
                $("#crt_id").change(function() {
                    $("#adm_navbar_filter_form_category_report").submit();
                });',
                true
            );

            foreach ($config as $key => $item) {
                $selectBoxEntries[$item['id']] = $item['name'];
            }

            // create filter menu with elements for role
            $form = new FormPresenter(
                'adm_navbar_filter_form_category_report',
                'sys-template-parts/form.filter.tpl',
                '',
                $page,
                array('type' => 'navbar', 'setFocus' => false)
            );
            $form->addSelectBox(
                'crt_id',
                $gL10n->get('SYS_SELECT_CONFIGURATION'),
                $selectBoxEntries,
                array('showContextDependentFirstEntry' => false, 'defaultValue' => $getCrtId)
            );
            if ($getExportAndFilter) {
                $form->addInput('filter', $gL10n->get('SYS_FILTER'), $getFilter);
            }
            $form->addCheckbox('export_and_filter', $gL10n->get('SYS_FILTER_TO_EXPORT'), $getExportAndFilter);
            $form->addToHtmlPage();

            $page->addHtml('<h5 class="admidio-content-subheader">' . $subHeadline . '</h5>');

            $smarty->assign('classTable', $classTable);
            if (!$getExportAndFilter) {
                $categoryReportTable = new DataTables($page, 'adm_lists_table');
                $categoryReportTable->setRowsPerPage($gSettingsManager->getInt('groups_roles_members_per_page'));
            }
        }
    }

    $columnAlign = array('right');
    $columnValues = array($gL10n->get('SYS_ABR_NO'));
    $columnNumber = 1;

    foreach ($report->headerData as $columnHeader) {
        // bei Profilfeldern ist in 'id' die usf_id, ansonsten 0
        $usf_id = $columnHeader['id'];

        if ($gProfileFields->getPropertyById($usf_id, 'usf_type') == 'NUMBER'
            || $gProfileFields->getPropertyById($usf_id, 'usf_type') == 'DECIMAL_NUMBER') {
            $columnAlign[] = 'right';
        } elseif ($gProfileFields->getPropertyById($usf_id, 'usf_type') == 'CHECKBOX' || $usf_id === 0) {
            // bei allen Feldern, die kein Profilfeld sind (usf_id = 0) und Checkboxen wird zentriert
            $columnAlign[] = 'center';
        } else {
            $columnAlign[] = 'left';
        }

        if ($getMode == 'csv') {
            if ($columnNumber === 1) {
                // in der ersten Spalte die laufende Nummer noch davorsetzen
                $csvStr .= $valueQuotes . $gL10n->get('SYS_ABR_NO') . $valueQuotes;
            }
            $csvStr .= $separator . $valueQuotes . $columnHeader['data'] . $valueQuotes;
        } elseif ($getMode === "xlsx") {
            // convert html characters to plain text
            $columnValues[] = html_entity_decode($columnHeader['data'], ENT_QUOTES | ENT_HTML5, 'UTF-8');
        } elseif ($getMode == 'html' || $getMode == 'print' || $getMode == 'pdf') {
            $columnValues[] = $columnHeader['data'];
        }
        $columnNumber++;
    }

    if ($getMode === 'csv') {
        $csvStr .= "\n";
    } elseif ($getMode === 'xlsx') {
        $spreadsheet = new Spreadsheet();
        $activeSheet = $spreadsheet->getActiveSheet();
        $activeSheet->fromArray(array_values($columnValues));
    } else {
        $data['headers'] = $columnValues;
        $data['column_align'] = $columnAlign;
    }

    $listRowNumber = 1;
    $user = new User($gDb, $gProfileFields);

    // die Daten einlesen
    foreach ($report->listData as $member => $memberdata) {
        $columnValues = array();
        $tmp_csv = '';

        // Felder zu Datensatz
        $columnNumber = 1;
        foreach ($memberdata as $key => $content) {
            if ($getMode == 'html' || $getMode == 'print' || $getMode == 'pdf' || $getMode == 'xlsx') {
                if ($columnNumber === 1) {
                    // die Laufende Nummer noch davorsetzen
                    $columnValues[] = $listRowNumber;
                }
            } else {
                if ($columnNumber === 1) {
                    // erste Spalte zeigt lfd. Nummer an
                    $tmp_csv .= $valueQuotes . $listRowNumber . $valueQuotes;
                }
            }


            // create output format


            $usf_id = 0;
            $usf_id = $report->headerData[$key]['id'];

            if ($usf_id !== 0
                && in_array($getMode, array('xlsx', 'csv', 'pdf'), true)
                && $content > 0
                && ($gProfileFields->getPropertyById($usf_id, 'usf_type') == 'DROPDOWN'
                    || $gProfileFields->getPropertyById($usf_id, 'usf_type') == 'DROPDOWN_MULTISELECT'
                    || $gProfileFields->getPropertyById($usf_id, 'usf_type') == 'RADIO_BUTTON')) {
                // show selected text of optionfield or combobox
                $arrOptions = $gProfileFields->getPropertyById($usf_id, 'ufo_usf_options', 'text');
                // if the content is an array, then we have to loop through the array
                if (is_array($content)) {
                    $content = array_map(function ($value) use ($arrOptions) {
                        return isset($arrOptions[$value]) ? $arrOptions[$value] : '';
                    }, $content);
                    $content = implode(', ', $content);
                } else {
                    $content = $arrOptions[$content];
                }
            }

            if ($usf_id === 0 && $content === true) {       // alle Spalten außer Profilfelder
                if (in_array($getMode, array('xlsx', 'csv', 'pdf'), true)) {
                    $content = 'X';
                } else {
                    $content = '<i class="bi bi-check-lg"></i>';
                }
            }

            if ($getMode == 'csv') {
                // special case for checkbox profile fields
                if ($usf_id !== 0 && $gProfileFields->getPropertyById($usf_id, 'usf_type') === 'CHECKBOX') {
                    $content = ($content) ? 'X' : '';
                }
                $tmp_csv .= $separator . $valueQuotes . $content . $valueQuotes;
            } // pdf should show only text and not much html content
            elseif ($getMode === 'pdf') {
                // special case for checkbox profile fields
                if ($usf_id !== 0 && $gProfileFields->getPropertyById($usf_id, 'usf_type') === 'CHECKBOX') {
                    $content = ($content) ? 'X' : '';
                }
                $columnValues[] = $content;
            } else {                   // create output in html layout for getMode = html or print
                if ($usf_id !== 0) {     // profile fields
                    $user->readDataById($member);

                    if ($getMode === 'html'
                        && ($usf_id === (int)$gProfileFields->getProperty('LAST_NAME', 'usf_id')
                            || $usf_id === (int)$gProfileFields->getProperty('FIRST_NAME', 'usf_id'))) {
                        $htmlValue = $gProfileFields->getHtmlValue($gProfileFields->getPropertyById($usf_id, 'usf_name_intern'), $content);
                        $columnValues[] = '<a href="' . SecurityUtils::encodeUrl(ADMIDIO_URL . FOLDER_MODULES . '/profile/profile.php', array('user_uuid' => $user->getValue('usr_uuid'))) . '">' . $htmlValue . '</a>';
                    } else {
                        // within print or Excel mode no links should be set
                        if (($getMode === 'print' || $getMode === 'xlsx')
                            && ($gProfileFields->getPropertyById($usf_id, 'usf_type') === 'EMAIL'
                                || $gProfileFields->getPropertyById($usf_id, 'usf_type') === 'PHONE'
                                || $gProfileFields->getPropertyById($usf_id, 'usf_type') === 'URL')) {
                            $columnValues[] = $content;
                        } elseif ($getMode === 'xlsx'
                            && ($gProfileFields->getPropertyById($usf_id, 'usf_type') == 'DROPDOWN'
                                || $gProfileFields->getPropertyById($usf_id, 'usf_type') == 'DROPDOWN_MULTISELECT'
                                || $gProfileFields->getPropertyById($usf_id, 'usf_type') == 'RADIO_BUTTON'
                                || $gProfileFields->getPropertyById($usf_id, 'usf_type') === 'CHECKBOX')) {
                            if ($gProfileFields->getPropertyById($usf_id, 'usf_type') === 'CHECKBOX') {
                                $columnValues[] = ($content) ? 'X' : '';
                            } else {
                                $columnValues[] = $content;
                            }
                        } else {
                            // checkbox must set a sorting value
                            if ($gProfileFields->getPropertyById($usf_id, 'usf_type') === 'CHECKBOX') {
                                $columnValues[] = array('value' => $gProfileFields->getHtmlValue($gProfileFields->getPropertyById($usf_id, 'usf_name_intern'), $content), 'order' => $content);
                            } else {
                                $columnValues[] = $gProfileFields->getHtmlValue($gProfileFields->getPropertyById($usf_id, 'usf_name_intern'), $content, $user->getValue('usr_uuid'));
                            }
                        }
                    }
                } else {            // all other fields except profile fields
                    // if empty string pass a whitespace
                    if (strlen($content) > 0) {
                        $columnValues[] = $content;
                    } else {
                        $columnValues[] = '&nbsp;';
                    }
                }
            }
            $columnNumber++;
        }

        if ($getFilter == '' || ($getFilter != '' && (stristr(implode('', $columnValues), $getFilter) || stristr($tmp_csv, $getFilter)))) {
            if ($getMode == 'csv') {
                $csvStr .= $tmp_csv . "\n";
            } elseif ($getMode === 'xlsx') {
                $currentRow = $listRowNumber + 1; // +1 for headerColumn offset
                foreach ($columnValues as $currentCol => $cell) {
                    $currentCol += 1; // array starting with 0 but first column is 1 in spreadsheet

                    // convert html characters to plain text
                    $cell = html_entity_decode($cell, ENT_QUOTES | ENT_HTML5, 'UTF-8');
                    $colLetter = Coordinate::stringFromColumnIndex($currentCol);
                    $activeSheet->setCellValue($colLetter . $currentRow, $cell);
                }
            } else {
                $data['rows'][] = array('id' => 'row-' . $listRowNumber, 'data' => $columnValues);
            }
            $listRowNumber++;
        }
    }  // End-For (jeder gefundene User)

// Settings for export file
    if ($getMode === 'csv' || $getMode === 'pdf') {
        $filename = FileSystemUtils::getSanitizedPathEntry($filename) . '.' . $getMode;

        header('Content-Disposition: attachment; filename="' . $filename . '"');

        // necessary for IE6 to 8, because without it the download with SSL has problems
        header('Cache-Control: private');
        header('Pragma: public');
    }

    if ($getMode === 'csv') {
        // download CSV file
        header('Content-Type: text/comma-separated-values; charset=' . $charset);
        echo $csvStr;
    } elseif ($getMode === 'xlsx') {
        $filename = FileSystemUtils::getSanitizedPathEntry($filename) . '.' . $getMode;

        header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=' . $charset);
        header('Content-Disposition: attachment; filename="' . $filename . '"');
        header('Cache-Control: max-age=0');

        formatSpreadsheet($spreadsheet, $columnCount + 1, true);
        $writer = new Xlsx($spreadsheet);
        $writer->save('php://output');
    } elseif ($getMode === 'pdf') {
        // send the new PDF to the User
        // Using Smarty templating engine for exporting table in PDF
        $smarty->assign('attributes', array('border' => '1', 'cellpadding' => '1'));
        $smarty->assign('columnAlign', $data['column_align']);
        $smarty->assign('headers', $data['headers']);
        $smarty->assign('headersStyle', 'font-size:14;background-color:#C7C7C7;');
        $smarty->assign('rows', $data['rows']);
        $smarty->assign('rowsStyle', 'font-size:10;');

        // Fetch the HTML table from our Smarty template
        $smarty->assign('exportMode', true);
        $htmlTable = $smarty->fetch('modules/category-report.list.tpl');

        // output the HTML content
        $pdf->writeHTML($htmlTable, true, false, true);

        $file = ADMIDIO_PATH . FOLDER_TEMP_DATA . '/' . $filename;

        // Save PDF to file
        $pdf->Output($file, 'F');

        // Redirect
        header('Content-Type: application/pdf');

        readfile($file);
        ignore_user_abort(true);

        try {
            FileSystemUtils::deleteFileIfExists($file);
        } catch (\RuntimeException $exception) {
            $gLogger->error('Could not delete file!', array('filePath' => $file));
            // TODO
        }
    } elseif ($getMode == 'html' && $getExportAndFilter) {
        $page->addHtml('<div style="width:100%; height: 500px; overflow:auto; border:20px;">');
        $smarty->assign('columnAlign', $data['column_align']);
        $smarty->assign('headers', $data['headers']);
        $smarty->assign('rows', $data['rows']);

        // Fetch the HTML table from our Smarty template
        $htmlTable = $smarty->fetch('modules/category-report.list.tpl');
        $page->addHtml($htmlTable);
        $page->addHtml('</div><br/>');
        $page->show();
    } elseif (($getMode == 'html' && !$getExportAndFilter) || $getMode == 'print') {
        if (isset($categoryReportTable)) {
            // we are in datatable mode
            $categoryReportTable->createJavascript(count($data['rows']), count($data['headers']));
            $categoryReportTable->setColumnAlignByArray($data['column_align']);
            $page->addHtml('<div class="table-responsive">');
        }

        $smarty->assign('columnAlign', $data['column_align']);
        $smarty->assign('headers', $data['headers']);
        $smarty->assign('rows', $data['rows']);

        // Fetch the HTML table from our Smarty template
        $htmlTable = $smarty->fetch('modules/category-report.list.tpl');
        $page->addHtml($htmlTable);
        if (isset($categoryReportTable)) {
            $page->addHtml('</div>');
        }
        $page->show();
    }
} catch (Exception $e) {
    $gMessage->show($e->getMessage());
}

/**
 * Formats the spreadsheet
 *
 * @param Spreadsheet $spreadsheet
 * @param int $columnCount
 * @param bool $containsHeadline
 * @throws Exception
 */
function formatSpreadsheet(Spreadsheet $spreadsheet, int $columnCount, bool $containsHeadline) : void
{
    $activeSheet = $spreadsheet->getActiveSheet();
    $lastColumn  = Coordinate::stringFromColumnIndex($columnCount);

    if ($containsHeadline) {
        $range = "A1:{$lastColumn}1";
        $style = $activeSheet->getStyle($range);

        $style->getFill()
            ->setFillType(Fill::FILL_SOLID)
            ->getStartColor()
            ->setARGB('FFDDDDDD');
        $style->getFont()
            ->setBold(true);
    }

    for ($i = 1; $i <= $columnCount; $i++) {
        $colLetter = Coordinate::stringFromColumnIndex($i);
        $activeSheet->getColumnDimension($colLetter)->setAutoSize(true);
    }

    try {
        $spreadsheet->getDefaultStyle()->getAlignment()->setWrapText(true);
    } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {
        throw new Exception($e);
    }
}
