<?php
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\SMTP;
use PHPMailer\PHPMailer\Exception;
use Webklex\PHPIMAP\ClientManager;
use Webklex\PHPIMAP\Client;

require 'vendor/autoload.php';

class Renderer {
    protected string $templateName = 'index';
    private array $menuItems = [];
    protected array $internalMenuItems = [];
    protected $hiddenCount = 0;
    protected array $predefines = [];
    protected array $errors = [];
    protected array $formFields = [];
    protected string $formSendButtonLabel = '';
    protected bool $isAjaxForm = false;
    protected string $encType = 'application/x-www-form-urlencoded';
    protected $dbConnection;
    protected string $encryptionKey = 'CXOoBDbt0qjJ6zlTILGVsFIZTE0aTZUC';
    protected array $content = [];
    protected string $dbTable = '';
    protected bool $dbGenerateSaltField = false;
    protected bool $dbUpdate = false;
    protected bool $cleanFields = false;
    protected array $messages = [];
    protected string $salt = '';
    private string $website = '';
    protected bool $saveAllFields = false;
    protected string $imapServer = 'imap.gmx.net';
    protected int $imapPort = 993;
    protected string $smtpServer = 'mail.gmx.net';
    protected int $smtpPort = 465;
    protected string $emailUser = 'foerderverein-ajs@gmx.de';
    protected string $emailPassword = '16HarheimerWeg';
    protected ClientManager $imapClientManager;
    protected Client $mbox;

    public function __construct(?string $templateName = null) {
        session_start();
        $this->createPublicMenuItems();
        $this->createInternalMenuItems();
        if (!isset($_SESSION['userid'])) {
            $_SESSION['userid'] = 0;
        }
        $scriptName = $this->getScriptName();
        if (!in_array($scriptName, array_merge($this->menuItems, $this->internalMenuItems))) {
            header('Location: /', true, 301);
            exit;
        }
        $this->templateName = $templateName ?: (!in_array($scriptName, ['ffajs', 'fvajs', '', '/']) ? $scriptName : 'index');
        $this->connectDb();
        $this->content['year'] = date('Y');
    }
    
    private function createPublicMenuItems(): void {
        $this->menuItems = $this->getMenuItemsFromFile('publicpages');
    }
    
    private function createInternalMenuItems(): void {
        $this->internalMenuItems = $this->getMenuItemsFromFile('internalpages');
    }
    
    private function getMenuItemsFromFile(string $fileName): array {
        $items = json_decode(file_get_contents('conf/' . $fileName . '.conf'));
        $menu = [];
        foreach ($items as $url => $caption) {
            $menu[($caption !== '' ? $caption : '$hidden:0' . $this->hiddenCount++)] = $url;
        }
        return $menu;
    }
    
    public function getScriptName(): string {
        return $this->getSplittedUri()[0];
    }
    
    public function getUrl(): string {
        return filter_input(INPUT_SERVER, 'REQUEST_URI');
    }
    
    protected function getUriParams(): array {
        $paramPairs = explode('&', $this->getSplittedUri()[1]);
        $result = [];
        foreach ($paramPairs as $paramPair) {
            $pair = explode('=', $paramPair);
            if (count($pair) === 2) {
                $result[$pair[0]] = $pair[1];
            }
        }
        return $result;
    }
    
    protected function getSplittedUri(): array {
        $scriptNameParts = pathinfo(filter_input(INPUT_SERVER, 'REQUEST_URI'));
        $uri = $scriptNameParts['filename'] ?: 'index';
        return explode('?', $uri);
    }

    private function connectDb(): void {
        $user = 'ffajs';
        $password = 'BpAJSis1999xx';
        $host = 'tsschulz.de';
        $port = 3306;
        $db = 'ffajs';
        $this->dbConnection = mysqli_connect($host, $user, $password, $db, $port);        
    }
    
    public function render(): void {
        if (filter_input(INPUT_POST, 'action', FILTER_SANITIZE_FULL_SPECIAL_CHARS) !== NULL && trim(filter_input(INPUT_POST, 'action', FILTER_SANITIZE_FULL_SPECIAL_CHARS)) !== '') {
            $this->formAction();
        }
        $this->website = file_get_contents('templates/page.html');
        $this->header();
        $this->menu();
        if ($_SESSION['userid'] === 0 && in_array($this->getScriptName() , $this->internalMenuItems, false)) {
            $this->templateName = 'login_error';
        }
        $this->body();
        $this->footer();
        echo $this->website;
    }
    
    private function header(): void {
        $this->website = str_replace('{{header}}', file_get_contents('templates/header.html'), $this->website);
    }
    
    private function menu(): void {
        $menu = '<menu>';
        $scriptName = $this->getScriptName();
        foreach ($this->menuItems as $page => $link) {
            if (substr($page, 0, 8) === '$hidden:') {
                continue;
            }
            $menu .= '<a href="' . $link . '"' . ($link === $scriptName ? 'class="active"' : '') . '>' . $page . '</a>';
        }
        if (isset($_SESSION) && $_SESSION['userid'] === 0) {
            $menu .= '<a href="login" class="login"><img src="' . (filter_input(INPUT_SERVER, 'HTTP_HOST') == 'localhost' ? '/ffajs' : '') . 
                    '/images/icons/key.png" class="menu-icon" alt="login" /></a>';
        } else {
            $menu .= '<div class="internal-menu-main"><span class="intern">Verwaltung</span><div class="internal-menu-sub">';
            foreach ($this->internalMenuItems as $page => $link) {
                if (substr($page, 0, 8) === '$hidden:') {
                    continue;
                }
                $menu .= '<a class="intern" href="' . $link . '"' . ($link === $scriptName ? 'class="active"' : '') . '>' . $page . '</a>';
            }
            $menu .= '</div></div>';
            $menu .= '<a href="logout" class="login"><img src="' . (filter_input(INPUT_SERVER, 'HTTP_HOST') == 'localhost' ? '/ffajs' : '') . 
                    '/images/icons/key.png" class="menu-icon" alt="logout" /></a>';
        }
        $menu .= '</menu>';
        $this->website = str_replace('{{menu}}', $menu, $this->website);
    }
    
    protected function body(): void {
        $this->generateContent();
        $rawContent = file_get_contents('templates/' . $this->templateName . '.html');
        $errorHtml = count($this->errors) > 0
            ? '<div class="error">' . implode('<br />', $this->errors) . '</div>'
            : '';
        $messagesHtml = count($this->messages) > 0
            ? '<div class="messages">' . implode('<br />', $this->messages) . '</div>'
            : '';
        $placeholders = ['{{errors}}', '{{form}}', '{{messages}}'];
        $renderedContent = [$errorHtml, $this->renderForm(), $messagesHtml];
        foreach ($this->content as $placeholder => $html) {
            $placeholders[] = '{{' . $placeholder . '}}';
            $renderedContent[] = $html;
        }
        $content = str_replace($placeholders, $renderedContent, $rawContent);
        $this->website = str_replace('{{content}}', $content, $this->website);
    }
    
    private function footer(): void {
        $footerTemplate = file_get_contents('templates/footer.html');
        $footer = str_replace('{{year}}', date('Y'), $footerTemplate);
        $this->website = str_replace('{{footer}}', $footer, $this->website);
    }
    
    protected function showInputField(array $errors, string $inputType, string $fieldName, int $fieldLength): void {
        echo '<input type="' . $inputType . '" name="' . $fieldName . '" size="' . $fieldLength . '" value="' . filter_input(INPUT_POST, $fieldName, FILTER_SANITIZE_FULL_SPECIAL_CHARS) . '" />';
        if (isset($errors[$fieldName])) {
            echo '<span class="error">' . $errors[$fieldName] . '</span>';
        }
    }
    
    protected function renderForm(): string {
        $form = '<form method="post" action="' . str_replace('.php', '', $this->getUrl()) . '" enctype="' . $this->encType . '"><table class="form">';
        $label = '';
        $input = '';
        $error = '';
        for ($formFieldIndex = 0; $formFieldIndex < count($this->formFields); ++$formFieldIndex) {
            $newLabelExtension = $this->renderLabel($formFieldIndex);
            $label .= (strlen($label) > 0 && strlen($newLabelExtension) > 0 ? ' / ' : '') . $newLabelExtension;
            $input .= $this->renderInput($formFieldIndex);
            if (isset($this->formFields[$formFieldIndex]['name']) && isset($this->errors[$this->formFields[$formFieldIndex]['name'] ])) {
                $error .= '<span class="error">' . $this->errors[$this->formFields[$formFieldIndex]['name'] ] . '</span>';
            }
            if (!isset($this->formFields[$formFieldIndex]['combine_with_next_line'] ) || !$this->formFields[$formFieldIndex]['combine_with_next_line']) {
                $form .= '<tr><th>' . $label . '</th><td>' . $input . '</td><td>' . $error . '</td></tr>';
                $label = '';
                $input = '';
                $error = '';
            }
        }
        $form .= '</table>';
        if (!$this->isAjaxForm) {
            $form .= '<button type="submit" name="action" value="submit">' . $this->formSendButtonLabel . '</button>';
        }
        $form .= '<button type="reset">Zurücksetzen</button>';
        $form .= '</form>';
        return $form;
    }
    
    protected function renderLabel(int $index): string {
        if (!isset($this->formFields[$index]['name']) && !isset($this->formFields[$index]['label'])) {
            return '';
        } elseif (isset($this->formFields[$index]['label'])) {
            return $this->formFields[$index]['label'];
        }
        return '<label for="' . $this->formFields[$index]['name'] . '">' . $this->formFields[$index]['label'] . '</label>';
    }
    
    protected function renderInput(int $index): string {
        if ($this->formFields[$index]['type'] === 'combobox') {
            return $this->renderCombobox($index);
        }
        if ($this->formFields[$index]['type'] === 'textarea') {
            return $this->renderTextarea($index);
        }
        if ($this->formFields[$index]['type'] === 'infotext') {
            return '';
        }
        if ($this->formFields[$index]['type'] === 'dbselect') {
            return $this->renderDbSelect($index);
        }
        if ($this->formFields[$index]['type'] === 'spacer') {
            return '&nbsp;';
        }
        if ($this->formFields[$index]['type'] === 'button') {
            return $this->renderButton($index);
        }
        return $this->renderStandardInput($index);
    }
    
    protected function renderCombobox(int $index): string {
        $inputField = '<select size="1" id="' . $this->formFields[$index]['name']
                . '" name="' . $this->formFields[$index]['name'] . '" size="' . $this->formFields[$index]['size'] . '"' 
                . ' value="' . $this->inputValue($index) . '">';
        $inputValue = $this->inputValue($index);
        foreach ($this->formFields[$index]['values'] as $value) {
            $inputField .= '<option value="' . $value . '"';
            if ($value == $inputValue || (isset($this->formFields[$index]['default']) && $inputValue == '' && $value == $this->formFields[$index]['default'])) {
                $inputField .= ' selected';
            }
            $inputField .= '>' . $value . '</option>';
        }
        $inputField .= '</select>';
        return $inputField;
    }
    
    protected function renderStandardInput(int $index): string {
        return '<input type="' . $this->formFields[$index]['type'] . '" id="' . $this->formFields[$index]['name'] . 
                '" name="' . $this->formFields[$index]['name'] . '" size="' . $this->formFields[$index]['size'] . '"' .
                ' value="' . $this->inputValue($index) . '" />';
    }
    
    protected function renderTextarea(int $index): string {
        return '<textarea id="' . $this->formFields[$index]['name'] .'" ' 
                . 'name="' . $this->formFields[$index]['name'] . '" cols="' . $this->formFields[$index]['cols'] . '" '
                . 'rows="' . $this->formFields[$index]['rows'] . '">' . $this->inputValue($index) . '</textarea>';
    }
    
    protected function renderDbSelect(int $index): string {
        $query = 'SELECT `id`, `' . $this->formFields[$index]['optionfield'] . '` as label FROM `' . $this->formFields[$index]['sourcedb'] . '` ORDER BY `id`';
        $dbResult = mysqli_query($this->dbConnection, $query);
        $inputValue = $this->inputValue($index);
        $selectField = '<select id="' . $this->formFields[$index]['name'] . '" '
                . 'name="' . $this->formFields[$index]['name'] . '">';
        if (isset($this->formFields[$index]['with_null_field']) && $this->formFields[$index]['with_null_field'] === true) {
            $selectField .= '<option value="NULL">---</option>';
        }
        while ($row = mysqli_fetch_assoc($dbResult)) {
            $selectField .= '<option value="' . $row['id'] . '"';
            if ($row['id'] == $inputValue) {
                $selectField .= ' selected';
            }
            $selectField .= '>' . $row['label'] . '</option>';
        }
        $selectField .= '</select>';
        return $selectField;
    }
    
    protected  function renderButton(int $index): string {
        return '<button id="' . $this->formFields[$index]['name'] . '" name="' . $this->formFields[$index]['name'] . '">' . $this->formFields[$index]['text'] . '</button>';
    }
    
    protected function inputValue($index): string {
        if (isset($this->formFields[$index]['value'])) {
            return $this->formFields[$index]['value'];
        } elseif ($this->cleanFields) {
            return '';
        }
        $value = filter_input(INPUT_POST, $this->formFields[$index]['name'], FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?: '';
        if (trim($value) === '' && isset($this->predefines[$this->formFields[$index]['name']])) {
            $value = $this->predefines[$this->formFields[$index]['name']];
        }
        return $value;
    }


    protected function formAction(): void {
        
    }

    protected function formCheckFields(): bool {
        foreach ($this->formFields as $field) {
            $value = filter_input(INPUT_POST, $field['name'], isset($field['filter']) ? $field['filter'] : FILTER_SANITIZE_FULL_SPECIAL_CHARS);
            if (isset($field['optional']) && ($field['optional'] == false) && trim($value) === '' && $field['type'] !== 'file') {
                $this->errors[$field['name']] = 'Das Feld darf nicht leer sein';
                continue;
            }
            if (isset($field['regex']) && !preg_match($field['regex'], $value)) {
                $this->errors[$field['name']] = 'Inkorrekte eingabe';
            }
        }
        return count($this->errors) === 0;
    }
    
    protected function createDbInsert(): string {
        $preparedFieldData = $this->createDbKeyValues();
        return 'INSERT INTO `' . $this->dbTable . '` (' . implode(', ', $preparedFieldData['keys'])
                . ') VALUES (' . implode(', ', $preparedFieldData['values']) . ')';
    }

    protected function createKeyValuePairsForUpdate(array $data): string {
        $resultArray = [];
        $numEntries = count($data['keys']);
        for ($i = 0; $i < $numEntries; ++$i) {
            $resultArray[] = $data['keys'][$i] . ' = ' . $data['values'][$i];
        }
        return implode(', ', $resultArray);
    }
    
    protected function createDbUpdate(): string {
        $preparedFieldData = $this->createDbKeyValues();
        $query = 'UPDATE `' . $this->dbTable . '` SET ';
        $query .= $this->createKeyValuePairsForUpdate($preparedFieldData);
        $query .= ' WHERE `id` = ' . $this->getUriParams()['id'];
        return $query;
    }
    
    protected function saveToDb(): void {
        $query = $this->dbUpdate
            ? $this->createDbUpdate()
            : $this->createDbInsert();
        mysqli_query($this->dbConnection, $query);
    }
  
    protected function createDbKeyValues(): array {
        $keysArray = [];
        $valuesArray = [];
        $salt = $this->salt != '' ? $this->salt : $this->generateRandomString();
        foreach ($this->formFields as $field) {
            if (!$this->saveAllFields && isset($field['optional']) && $field['optional'] === true && trim(filter_input(INPUT_POST, $field['name'], $field['filter'])) === '') {
                continue;
            }
            $keysArray[] = '`' . $field['dbfield'] . '`';
            if (isset($field['with_null_field']) && filter_input(INPUT_POST, $field['name']) === 'NULL') {
                $valuesArray[] = 'NULL';
            } else {
                $rawValue = filter_input(INPUT_POST, $field['name'], $field['filter']);
                $dbValue = $rawValue === '' || (isset($field['encryption']) && $field['encryption'] === false) ? $rawValue : $this->encode($rawValue, $salt);
                $valuesArray[] = '"' . $dbValue . '"';
            }
        }
        if ($this->dbGenerateSaltField) {
            $keysArray[] = '`salt`';
            $valuesArray[] = '"' . $salt . '"';
        }
        return ['keys' => $keysArray, 'values' => $valuesArray];
    }
    
    protected function getDbEncryptedValueIfNeeded(array $formData, string $fieldName, string $salt): mixed {
        $formField = $this->getFormField($fieldName);
        if (count($formField) !== 0) {
            if (!$this->fieldHasToBeEncrypted($fieldName)) {
                return $formData[$fieldName];
            }
        }
        return $this->encode($formData[$fieldName], $salt);
    }
    
    protected function getFormField($fieldName): array {
        foreach ($this->formFields as $formField) {
            if (!isset($formField['name'])) {
                continue;
            }
            if ($formField['name'] === $fieldName) {
                return $formField;
            }
        }
        return [];
    }
    
    protected function fieldHasToBeEncrypted(string $fieldName): bool {
        $formField = $this->getFormField($fieldName);
        return !isset($formField['encryption']) || $formField['encryption'] !== false;
    }
    
    protected function generateContent(): void {
        
    }
    
    protected function generateRandomString($length = 16) {
        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $charactersLength = strlen($characters);
        $randomString = '';
        for ($i = 0; $i < $length; $i++) {
            $randomString .= $characters[rand(0, $charactersLength - 1)];
        }
        return $randomString;
    }
    
    protected function encode(string $toEncode, string $salt): string {
        return openssl_encrypt($toEncode, 'aes-256-cbc', $this->encryptionKey, 0, $salt);
    }
    
    protected function decode(string $toDecode, string $salt): string {
        try {
            return $toDecode ? openssl_decrypt($toDecode, 'aes-256-cbc', $this->encryptionKey, 0, $salt) : '';
        } catch (exception $e) {
            return '';
        }
    }
    

    protected function initSmtpMailer(): PHPMailer {
        $mail = new PHPMailer(true);
        $mail->SMTPDebug = SMTP::DEBUG_OFF;
        $mail->isSMTP();
        $mail->Host = $this->smtpServer;
        $mail->Port = $this->smtpPort;
        $mail->SMTPAuth = true;
        $mail->Username = $this->emailUser;
        $mail->Password = $this->emailPassword;
        $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
        $mail->CharSet = PHPMailer::CHARSET_UTF8;
        $mail->isHTML(true);
        return $mail;
    }
    
    protected function sendMail(PHPMailer $mail, string $subject, string $body, string $signature, $attachments = []): void {
        $completeBody = $body . $signature . $this->emailLegalInformation();
        $mail->Subject = $subject;
        $mail->Body = $completeBody;
        $mail->AltBody = 'Diese Email benötigt HTML-Ansicht';
        foreach ($attachments as $fileName => $attachment) {
            $mail->addStringAttachment($attachment['content'], $fileName, 'base64', $attachment['type']);

        }
        try {
            $mail->send();
        } catch (Exception $e) {
            error_log('Mailversand fehlgeschlagen: ' . $e->getMessage());
            $this->errors[] = 'Die Bestätigungs-Email konnte nicht versendet werden. Bitte versuchen Sie es später erneut oder kontaktieren Sie uns direkt.';
        }
    }
    
    protected function connectToImap($folder = ''): bool {
        $this->imapClientManager = new ClientManager('conf/imap.php');
        $this->mbox = $this->imapClientManager->make([
            'host'          => $this->imapServer,
            'port'          => $this->imapPort,
            'encryption'    => 'ssl',
            'validate_cert' => true,
            'username'      => $this->emailUser,
            'password'      => $this->emailPassword,
            'protocol'      => 'imap'
        ]);
        $this->mbox->connect();
        return $this->mbox->isConnected();;
    }

    protected function saveFileLocal(string $newFileName, string $content, string $salt): void {
        $encodedFile = $this->encode($content, $salt);
        file_put_contents('/var/shared/fvajs/' . $newFileName,  $encodedFile);
    }
    
    protected function emailLegalInformation(): string {
        return '';
    }

    protected function generateSignature(string $signatureType): string {
        $sender = ($signatureType === 'Persönliche Signatur') ? $this->generatePersonalSignature() : 'Der Vorstand';
        return '<p>-------------<br/>'
            . $sender .'<br/><br/>'
            . 'Verein der Freunde und F&ouml;rderer der Steffi-Jones-Schule e.V.<br/>'
            . 'Harheimer Weg 16<br/>'
            . '60437 Frankfurt<br/>'
            . 'Email: foerderverein-ajs@gmx.de<br/>'
            . 'Homepage: https://fvajs.de<br/>'
            . 'Registergerich: Amtsgerich Frankfurt a.M. <Registernummer>'
            . 'Vertretungsberechtigt: Lucas Fastabend, Alexandra <nachname>, Torsten Schulz'
            . '</p>';
    }
    
    protected function generatePersonalSignature(): string {
        $query = sprintf('SELECT c.first_name, c.last_name, c.salt, cp.description 
                        FROM clubmember c 
                        JOIN `user` u
                        ON u.id = c.user_id
                        LEFT JOIN clubmember_position cp 
                        ON cp.id = c.position_id 
                        WHERE u.id = %d', $_SESSION['userid']);
        $dbResult = mysqli_query($this->dbConnection, $query);
        $row = mysqli_fetch_assoc($dbResult);
        $name = $this->decode($row['first_name'], $row['salt']) . ' ' . $this->decode($row['last_name'], $row['salt']);
        if (trim($row['description']) === '') {
            return name;
        }
        return htmlspecialchars($name . ' (' . $row['description'] . ')');
    }
        
    function decodeSubject($subject) {
        return $subject;
        // Match the encoding and encoded string using a regular expression
        preg_match_all('/=\?([^?]+)\?([QB])\?([^?]+)\?=/i', $subject, $matches, PREG_SET_ORDER);

        $decoded = '';
        foreach ($matches as $match) {
            $encoding = $match[1];
            $type = $match[2];
            $encoded_string = $match[3];
            if ($type == 'Q') {
                $decoded_part = quoted_printable_decode($encoded_string);
            } elseif ($type == 'B') {
                $decoded_part = base64_decode($encoded_string);
            }
            var_dump($match);
            if (strtolower($encoding) != 'utf-8') {
                $decoded_part = iconv($encoding, 'UTF-8', $decoded_part);
            }
            $decoded .= str_replace('_', ' ', $decoded_part);
        }

        return $decoded;
    }   
    
    protected function getMemberList(bool $withId = false): array {
        $query = <<<queryend
            SELECT c.id, c.first_name, c.last_name , c.street , c.zip , c.town, c.birthdate, c.phone, c.email, c.child_name, 
                c.child_street, c.subscription, c.bank_name, c.iban, c.bic, c.account_member_name, c.membership_status, c.salt, c.membership_start,
                cs.`status_text`, (SELECT ph.`payment_date` FROM `paying_history` ph WHERE ph.`clubmember_id` = c.`id` ORDER BY `payment_date` DESC LIMIT 1) as last_payment,
                cp.description
            FROM `clubmember` c 
            JOIN `clubmember_status` cs 
            ON cs.`id` = c.`membership_status` 
            LEFT JOIN `clubmember_position` cp 
            ON cp.id = c.position_id 
            WHERE cs.`status_text` NOT IN ("Mitgliedschaft abgelehnt", "Mitgliedschaft gekündigt")
queryend;
    $result = mysqli_query($this->dbConnection, $query);
    $entries = [];
    while ($row = mysqli_fetch_assoc($result)) {
            $entry = [
                'last_name' => $this->decode($row['last_name'], $row['salt']),
                'first_name' => $this->decode($row['first_name'], $row['salt']),
                'street' => $this->decode($row['street'], $row['salt']),
                'zip' => $this->decode($row['zip'], $row['salt']),
                'town' => $this->decode($row['town'], $row['salt']),
                'birthdate' => $this->decode($row['birthdate'], $row['salt']),
                'phone' => $this->decode($row['phone'], $row['salt']),
                'email' => $this->decode($row['email'], $row['salt']),
//                'child_name' => $this->decode($row['child_name'], $row['salt']),
//                'child_street' => $this->decode($row['child_street'], $row['salt']),
                'subscription' => $this->decode($row['subscription'], $row['salt']),
                'bank_name' => $this->decode($row['bank_name'], $row['salt']),
                'iban' => $this->decode($row['iban'], $row['salt']),
                'bic' => $this->decode($row['bic'], $row['salt']),
                'account_member_name' => $this->decode($row['account_member_name'], $row['salt']),
                'status_text' => $row['status_text'],
                'membership_start' => $row['membership_start'],
                'last_payment' => $row['last_payment'],
                'description' => $row['description'],
            ];
            if ($withId) {
                $entry['id'] = $row['id'];
            }
            $entries[] = $entry;
        }
        return $this->sortUserList($entries);
    }
    
    protected function sortUserList($userList): array {
        usort($userList, function($a, $b) {
            $nameComparison = strcasecmp($a['last_name'], $b['last_name']);
            return ($nameComparison !== 0) ? $nameComparison : strcasecmp($a['first_name'], $b['first_name']);
        });        
        return $userList;
    }
}
