Admidio/admidio

View on GitHub
adm_program/system/classes/TableFolder.php

Summary

Maintainability
F
4 days
Test Coverage
<?php
use Admidio\Exception;

/**
 * @brief Class manages access to database table adm_folders
 *
 * @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
 */
class TableFolder extends TableAccess
{
    /**
     * @var RolesRights|null Object with all roles that could view the current folder
     */
    protected ?RolesRights $folderViewRolesObject;
    /**
     * @var RolesRights|null Object with all roles that could upload files the current folder
     */
    protected ?RolesRights $folderUploadRolesObject;

    /**
     * Constructor that will create an object of a recordset of the table adm_folders.
     * If the id is set than the specific folder will be loaded.
     * @param Database $database Object of the class Database. This should be the default global object **$gDb**.
     * @param int $folId The recordset of the folder with this id will be loaded. If id isn't set than an empty object of the table is created.
     * @throws Exception
     */
    public function __construct(Database $database, int $folId = 0)
    {
        parent::__construct($database, TBL_FOLDERS, 'fol', $folId);
    }

    /**
     * @param array<string,array<int,array<string,mixed>>> $completeFolder
     * @return array<string,array<int,array<string,mixed>>>
     * @throws Exception
     */
    public function addAdditionalToFolderContents(array $completeFolder): array
    {
        global $gCurrentUser;

        // If user hasn't adminDocumentsFiles, don't add more data
        if (!$gCurrentUser->adminDocumentsFiles()) {
            return $completeFolder;
        }

        // Check if folder exists
        $folderPath = $this->getFullFolderPath();
        if (!is_dir($folderPath)) {
            return $completeFolder;
        }

        // User has adminDocumentsFiles and folder exists, so lookup the physical directory for items that aren't in the DB
        $dirHandle = @opendir($folderPath);
        if ($dirHandle) {
            while (($entry = readdir($dirHandle)) !== false) {
                if ($entry === '.' || $entry === '..' || str_starts_with($entry, '.')) {
                    continue;
                }

                $alreadyAdded = false;

                // Check if entry is folder or file
                $entryFolderPath = $folderPath . '/' . $entry;

                if (is_dir($entryFolderPath)) {
                    // Check if folder is already in the regular folders
                    foreach ($completeFolder['folders'] as $folder) {
                        if ($folder['fol_name'] === $entry) {
                            $alreadyAdded = true;
                            break;
                        }
                    }

                    // If isn't already in, add it
                    if (!$alreadyAdded) {
                        $completeFolder['additionalFolders'][] = array('fol_name' => $entry);
                    }
                } elseif (is_file($entryFolderPath)) {
                    // Check if file is already in the regular files
                    foreach ($completeFolder['files'] as $file) {
                        if ($file['fil_name'] === $entry) {
                            $alreadyAdded = true;
                            break;
                        }
                    }

                    // If isn't already in, add it
                    if (!$alreadyAdded) {
                        $completeFolder['additionalFiles'][] = array(
                            'fil_name' => $entry,
                            'fil_size' => filesize($entryFolderPath)
                        );
                    }
                }
            }

            closedir($dirHandle);
        }

        return $completeFolder;
    }

    /**
     * Add a new file or subfolder of the current folder to the database. If a folder will be added all files and
     * subfolders of this folder will be added recursively with this method. The configured rights for viewing and
     * uploading will be adapted to the subfolders.
     * @param string $newFolderFileName Name of the folder or file that should be added to the database.
     * @throws Exception
     */
    public function addFolderOrFileToDatabase(string $newFolderFileName)
    {
        $newFolderFileName = urldecode($newFolderFileName);
        $newObjectPath = $this->getFullFolderPath() . '/' . $newFolderFileName;
        $folderId = (int)$this->getValue('fol_id');

        // check if a file or folder should be created
        if (is_file($newObjectPath)) {
            // add file to database
            $newFile = new TableFile($this->db);
            $newFile->setValue('fil_fol_id', $folderId);
            $newFile->setValue('fil_name', $newFolderFileName);
            $newFile->setValue('fil_locked', $this->getValue('fol_locked'));
            $newFile->setValue('fil_counter', 0);
            $newFile->save();

        } elseif (is_dir($newObjectPath)) {
            // add folder to database
            $newFolder = new TableFolder($this->db);
            $newFolder->setValue('fol_fol_id_parent', $folderId);
            $newFolder->setValue('fol_type', 'DOCUMENTS');
            $newFolder->setValue('fol_name', $newFolderFileName);
            $newFolder->setValue('fol_path', $this->getFolderPath());
            $newFolder->setValue('fol_locked', $this->getValue('fol_locked'));
            $newFolder->setValue('fol_public', $this->getValue('fol_public'));
            $newFolder->save();

            // get roles rights of parent folder
            $rightParentFolderView = new RolesRights($this->db, 'folder_view', $folderId);
            $newFolder->addRolesOnFolder('folder_view', $rightParentFolderView->getRolesIds());
            $rightParentFolderUpload = new RolesRights($this->db, 'folder_upload', $folderId);
            $newFolder->addRolesOnFolder('folder_upload', $rightParentFolderUpload->getRolesIds());

            // now look for all files and folder within that new folder and add them also to the database
            $dirHandle = @opendir($newObjectPath);
            if ($dirHandle) {
                while (($entry = readdir($dirHandle)) !== false) {
                    if ($entry === '.' || $entry === '..' || str_starts_with($entry, '.')) {
                        continue;
                    }

                    // call recursively
                    $newFolder->addFolderOrFileToDatabase($entry);
                }
                closedir($dirHandle);
            }
        }
    }

    /**
     * Add all roles of the array to the current folder and all the subfolders. The
     * roles will be assigned to the right that was set through parameter $rolesRightNameIntern.
     * @param string $rolesRightNameIntern Name of the right where the roles should be added
     *                                     e.g. **folder_view** or **folder_upload**
     * @param array<int,int> $rolesArray Array with all role IDs that should be added.
     * @param bool $recursive If set to **true** than the rights will be set recursive to all subfolders
     * @throws Exception
     */
    public function addRolesOnFolder(string $rolesRightNameIntern, array $rolesArray, bool $recursive = true)
    {
        $this->editRolesOnFolder('add', $rolesRightNameIntern, $rolesArray, $recursive);
    }

    /**
     * Legt einen neuen Ordner im Dateisystem an
     * @param string $folderName
     * @return null|array<string,string>
     * @throws Exception
     */
    public function createFolder(string $folderName): ?array
    {
        $baseFolder = $this->getFullFolderPath();

        try {
            FileSystemUtils::createDirectoryIfNotExists($baseFolder . '/' . $folderName);
        } catch (RuntimeException $exception) {
            return array(
                'text' => 'SYS_FOLDER_NOT_CREATED',
                'path' => $baseFolder . '/' . $folderName
            );
        }

        return null;
    }

    /**
     * Deletes the selected record of the table and all references in other tables.
     * Also, all files, subfolders and the selected folder will be deleted in the file system.
     * After that the class will be initialized.
     * @param int $folderId
     * @return bool **true** if no error occurred
     * @throws Exception
     */
    public function delete(int $folderId = 0): bool
    {
        global $gLogger;

        $folId = (int)$this->getValue('fol_id');
        $folderPath = '';

        if ($folderId === 0) {
            if ($this->getValue('fol_name') === '') {
                return false;
            }

            $folderId = (int)$this->getValue('fol_id');
            $folderPath = $this->getFullFolderPath();
        }

        $this->db->startTransaction();

        $subfoldersStatement = $this->getSubfolderStatement($folderId);

        while ($rowFolId = (int)$subfoldersStatement->fetchColumn()) {
            // rekursiver Aufruf mit jedem einzelnen Unterordner
            $this->delete($rowFolId);
        }

        // In the database delete the files of the current folder_id
        $sqlDeleteFiles = 'DELETE FROM ' . TBL_FILES . '
                            WHERE fil_fol_id = ? -- $folderId';
        $this->db->queryPrepared($sqlDeleteFiles, array($folderId));

        // delete all roles assignments that have the right to view this folder
        if ($folderId === $folId) {
            $this->folderViewRolesObject->delete();
            $this->folderUploadRolesObject->delete();
        } else {
            $folderViewRoles = new RolesRights($this->db, 'folder_view', $folderId);
            $folderViewRoles->delete();
            $folderUploadRoles = new RolesRights($this->db, 'folder_upload', $folderId);
            $folderUploadRoles->delete();
        }

        // Delete the entry of the folder itself in the database
        $sqlDeleteFolder = 'DELETE FROM ' . TBL_FOLDERS . '
                             WHERE fol_id = ? -- $folderId';
        $this->db->queryPrepared($sqlDeleteFolder, array($folderId));

        // physically delete the directory from the disk
        if ($folderPath !== '') {
            try {
                FileSystemUtils::deleteDirectoryIfExists($folderPath, true);
            } catch (RuntimeException $exception) {
                $gLogger->error('Could not delete directory!', array('directoryPath' => $folderPath));
                // TODO
            }
        }

        $returnCode = true;

        // Even if the physical deletion fails, everything is deleted in the DB...
        if ($folderId === $folId) {
            $returnCode = parent::delete();
        }

        $this->db->endTransaction();

        return $returnCode;
    }

    /**
     * Add all roles of the array to the current folder and all the subfolders. The
     * roles will be assigned to the right that was set through parameter $rolesRightNameIntern.
     * @param string $mode "mode" could be "add" or "remove"
     * @param string $rolesRightNameIntern Name of the right where the roles should be added
     * @param array<int,int> $rolesArray
     * @param bool $recursive If set to **true** than the rights will be set recursive to all subfolders
     * @param int $folderId The folder id of the subfolder if this method is called recursive
     * @throws Exception
     */
    private function editRolesOnFolder(string $mode, string $rolesRightNameIntern, array $rolesArray, bool $recursive, int $folderId = 0)
    {
        if (count($rolesArray) === 0) {
            return;
        }

        if ($folderId === 0) {
            $folderId = (int)$this->getValue('fol_id');
        }

        $this->db->startTransaction();

        if ($recursive) {
            $subfoldersStatement = $this->getSubfolderStatement($folderId);

            while ($folId = (int)$subfoldersStatement->fetchColumn()) {
                // recursive call for every sub-folder
                $this->editRolesOnFolder($mode, $rolesRightNameIntern, $rolesArray, true, $folId);
            }
        }

        // add new rights to folder
        $folderRolesRights = new RolesRights($this->db, $rolesRightNameIntern, $folderId);
        if ($mode === 'add') {
            $folderRolesRights->addRoles($rolesArray);
        } else {
            $folderRolesRights->removeRoles($rolesArray);
        }

        $this->db->endTransaction();
    }

    /**
     * Set the public flag to a folder and all sub-folders.
     * @param bool $publicFlag If set to **1** then all users could see this folder.
     * @param int $folderId The id of the folder where the public flag should be set.
     * @throws Exception
     */
    public function editPublicFlagOnFolder(bool $publicFlag, int $folderId = 0)
    {
        if ($folderId === 0) {
            $folderId = (int)$this->getValue('fol_id');
            $this->setValue('fol_public', (int)$publicFlag);
        }

        $subfoldersStatement = $this->getSubfolderStatement($folderId);

        while ($folId = (int)$subfoldersStatement->fetchColumn()) {
            // recursive call with every single subfolder
            $this->editPublicFlagOnFolder($publicFlag, $folId);
        }

        $sqlUpdate = 'UPDATE ' . TBL_FOLDERS . '
                         SET fol_public = ? -- $publicFlag
                       WHERE fol_id = ? -- $folderId';
        $this->db->queryPrepared($sqlUpdate, array((int)$publicFlag, $folderId));
    }

    /**
     * Gets the path of the folder (with folder-name)
     * @return string
     * @throws Exception
     */
    public function getFolderPath(): string
    {
        return $this->getValue('fol_path') . '/' . $this->getValue('fol_name');
    }

    /**
     * Gets the absolute path of the folder (with folder-name)
     * @return string
     * @throws Exception
     */
    public function getFullFolderPath(): string
    {
        return ADMIDIO_PATH . $this->getFolderPath();
    }

    /**
     * @return array<int,array<string,mixed>> All files with their properties
     * @throws Exception
     */
    public function getFilesWithProperties(): array
    {
        global $gCurrentUser;

        $files = array();

        // Get all files of the current folder
        $sqlFiles = 'SELECT *
                       FROM ' . TBL_FILES . '
                      INNER JOIN ' . TBL_FOLDERS . ' ON fol_id = fil_fol_id
                      WHERE fil_fol_id = ? -- $this->getValue(\'fol_id\')
                   ORDER BY fil_name';
        $filesStatement = $this->db->queryPrepared($sqlFiles, array((int)$this->getValue('fol_id')));

        // jetzt noch die Dateien ins Array packen:
        while ($rowFiles = $filesStatement->fetch()) {
            $filePath = $this->getFullFolderPath() . '/' . $rowFiles['fil_name'];
            $fileExists = is_file($filePath);

            $fileSize = 0;
            if ($fileExists) {
                $fileSize = filesize($filePath);
            }

            $addToArray = false;

            // If file exists and file isn't locked or user has adminDocumentsFiles, show it
            if (($fileExists && !$rowFiles['fil_locked']) || $gCurrentUser->adminDocumentsFiles()) {
                $addToArray = true;
            }

            if ($addToArray) {
                $files[] = array(
                    'fil_id' => $rowFiles['fil_id'],
                    'fil_uuid' => $rowFiles['fil_uuid'],
                    'fil_name' => $rowFiles['fil_name'],
                    'fil_description' => $rowFiles['fil_description'],
                    'fil_timestamp' => $rowFiles['fil_timestamp'],
                    'fil_locked' => $rowFiles['fil_locked'],
                    'fil_exists' => $fileExists,
                    'fil_size' => $fileSize,
                    'fil_counter' => $rowFiles['fil_counter'],
                    'fol_id' => $rowFiles['fol_id'],
                    'fol_name' => $rowFiles['fol_name'],
                    'fol_path' => $rowFiles['fol_path']
                );
            }
        }

        return $files;
    }

    /**
     * Reads the folder recordset from database table **adm_folders** and throws an
     * Exception if the user has no right to see the folder or the folder id doesn't exist.
     * @param string $folderUuid The UUID of the folder. If the UUID is empty then the root folder will be shown.
     * @return true Returns **true** if everything is ok otherwise an Exception is thrown.
     * @throws Exception Exception with the relevant message text. If message text = 'LOGIN' than
     *                      login page should be shown.
     * @throws Exception
     */
    public function getFolderForDownload(string $folderUuid): bool
    {
        global $gCurrentUser, $gCurrentOrgId, $gValidLogin;

        if ($folderUuid !== '') {
            // get folder of the parameter
            $condition = ' fol_uuid   = ? -- $folderUuid
                       AND fol_type = \'DOCUMENTS\' ';
            $queryParams = array($folderUuid);
        } else {
            // get first folder of current organization
            $condition = ' fol_fol_id_parent IS NULL
                       AND fol_org_id = ? -- $gCurrentOrgId
                       AND fol_type   = \'DOCUMENTS\' ';
            $queryParams = array($gCurrentOrgId);
        }
        $this->readData($condition, $queryParams);

        // Check if a dataset is found
        if ((int)$this->getValue('fol_id') === 0) {
            throw new Exception('SYS_FOLDER_NOT_FOUND', array($folderUuid));
        }

        // If current user has download-admin-rights => allow
        if ($gCurrentUser->adminDocumentsFiles()) {
            return true;
        }

        // If folder is public (and file is not locked) => allow
        if ($this->getValue('fol_public') && !$this->getValue('fol_locked')) {
            return true;
        }

        // check if user has a membership in a role that is assigned to the current folder
        if ($this->folderViewRolesObject->hasRight($gCurrentUser->getRoleMemberships())
            && !$this->getValue('fol_locked')) {
            return true;
        }

        $this->clear();
        if ($gValidLogin) {
            throw new Exception('SYS_FOLDER_NO_RIGHTS');
        } else {
            if ($folderUuid !== '') {
                throw new Exception('LOGIN');
            } else {
                throw new Exception('SYS_FOLDER_NO_FILES_VISITOR');
            }
        }
    }

    /**
     * Create a unique folder name for the root folder of the download module that contains
     * the shortname of the current organization.
     * @param string $type The folder type of which the root should be determined.
     *                                      If no type is set than **documents** will be set.
     * @param string $organizationShortname The shortname of the organization for which the folder name should be returned
     *                                      If no shortname is set than shortname of the current organization will be set.
     * @return string Returns the root folder name for the download module.
     * @throws Exception
     */
    public static function getRootFolderName(string $type = 'documents', string $organizationShortname = ''): string
    {
        global $gCurrentOrganization;

        if ($organizationShortname === '') {
            $organizationShortname = $gCurrentOrganization->getValue('org_shortname');
        }

        $organizationShortname = FileSystemUtils::getSanitizedPathEntry($organizationShortname);

        return StringUtils::strToLower($type) . '_' . strtolower($organizationShortname);
    }

    /**
     * Returns an array with all role IDs that have the right to view the folder.
     * @return array<int,int> Returns an array with all role ids that have the right to view the folder.
     */
    public function getViewRolesIds(): array
    {
        return $this->folderViewRolesObject->getRolesIds();
    }

    /**
     * Returns an array with all role names that have the right to view the folder. If no role is assigned to the
     * folder than everyone (also visitors) can view the folder. In this case the array will contain 1 entry with
     * "All (also visitors)".
     * @return array<int,int> Returns an array with all role names that have the right to view the folder.
     * @throws Exception
     */
    public function getViewRolesNames(): array
    {
        global $gL10n;

        $roleNames = $this->folderViewRolesObject->getRolesNames();
        if (count($roleNames) === 0) {
            $roleNames[] = $gL10n->get('SYS_ALL') . ' (' . $gL10n->get('SYS_ALSO_VISITORS') . ')';
        }
        return $roleNames;
    }

    /**
     * Returns an array with all role IDs that have the right to upload files to the folder.
     * @return array<int,int> Returns an array with all role ids that have the right to upload files to the folder.
     */
    public function getUploadRolesIds(): array
    {
        return $this->folderUploadRolesObject->getRolesIds();
    }

    /**
     * Return PDOStatement with all subfolders of a parent folder id
     * @param int $folderId Folder ID
     * @param array<int,string> $columns The columns that should be in the statement
     * @return false|PDOStatement Sub-folder statement with fol_id column
     * @throws Exception
     */
    private function getSubfolderStatement(int $folderId, array $columns = array('fol_id'))
    {
        // select all sub-folders of the current folder
        $sql = 'SELECT ' . implode(',', $columns) . '
                  FROM ' . TBL_FOLDERS . '
                 WHERE fol_fol_id_parent = ? -- $folderId';

        return $this->db->queryPrepared($sql, array($folderId));
    }

    /**
     * @return array<int,array<string,mixed>> All sub-folders with their properties
     * @throws Exception
     */
    public function getSubfoldersWithProperties(): array
    {
        global $gCurrentUser, $gValidLogin;

        // Get all subfolder of the current folder
        $sqlFolders = 'SELECT *
                         FROM ' . TBL_FOLDERS . '
                        WHERE fol_type          = \'DOCUMENTS\'
                          AND fol_fol_id_parent = ? -- $this->getValue(\'fol_id\')
                          AND fol_org_id        = ? -- $GLOBALS[\'gCurrentOrgId\']
                     ORDER BY fol_name';
        $foldersStatement = $this->db->queryPrepared($sqlFolders, array((int)$this->getValue('fol_id'), $GLOBALS['gCurrentOrgId']));

        $folders = array();

        while ($rowFolders = $foldersStatement->fetch()) {
            $folderExists = is_dir(ADMIDIO_PATH . $rowFolders['fol_path'] . '/' . $rowFolders['fol_name']);

            $addToArray = false;

            // If user has adminDocumentsFiles, show it
            if ($gCurrentUser->adminDocumentsFiles()) {
                $addToArray = true;
            } // If user hasn't adminDocumentsFiles, only show if folder exists
            elseif ($folderExists) {
                // If folder is public and not locked, show it
                if ($rowFolders['fol_public'] && !$rowFolders['fol_locked']) {
                    $addToArray = true;
                } // If user has a membership in a role that is assigned to the current subfolder, show it
                elseif ($gValidLogin) {
                    $subfolderViewRolesObject = new RolesRights($this->db, 'folder_view', $rowFolders['fol_id']);

                    if ($subfolderViewRolesObject->hasRight($gCurrentUser->getRoleMemberships())) {
                        $addToArray = true;
                    }
                }
            }

            if ($addToArray) {
                $folders[] = array(
                    'fol_id' => $rowFolders['fol_id'],
                    'fol_uuid' => $rowFolders['fol_uuid'],
                    'fol_name' => $rowFolders['fol_name'],
                    'fol_description' => $rowFolders['fol_description'],
                    'fol_path' => $rowFolders['fol_path'],
                    'fol_timestamp' => $rowFolders['fol_timestamp'],
                    'fol_public' => $rowFolders['fol_public'],
                    'fol_exists' => $folderExists,
                    'fol_locked' => $rowFolders['fol_locked']
                );
            }
        }

        return $folders;
    }

    /**
     * Get the value of a column of the database table.
     * If the value was manipulated before with **setValue** than the manipulated value is returned.
     * @param string $columnName The name of the database column whose value should be read
     * @param string $format For date or timestamp columns the format should be the date/time format e.g. **d.m.Y = '02.04.2011'**.
     *                           For text columns the format can be **database** that would return the original database value without any transformations
     * @return mixed Returns the value of the database column.
     *         If the value was manipulated before with **setValue** than the manipulated value is returned.
     * @throws Exception
     */
    public function getValue(string $columnName, string $format = '')
    {
        $value = parent::getValue($columnName, $format);

        if ($columnName === 'fol_name') {
            // Convert HTML-entity back to letters
            $value = html_entity_decode($value, ENT_QUOTES, 'UTF-8');
        }

        return $value;
    }

    /**
     * Checks if the current user has the right to upload files to the current folder.
     * @return bool Return **true** if the user has the right to upload files
     * @throws Exception
     */
    public function hasUploadRight(): bool
    {
        global $gCurrentUser;

        return $this->folderUploadRolesObject->hasRight($gCurrentUser->getRoleMemberships()) || $gCurrentUser->adminDocumentsFiles();
    }

    /**
     * Checks if the current user has the right to view files of the current folder.
     * @return bool Return **true** if the user has the right to view files
     * @throws Exception
     */
    public function hasViewRight(): bool
    {
        global $gCurrentUser;

        return $this->folderViewRolesObject->hasRight($gCurrentUser->getRoleMemberships()) || $gCurrentUser->adminDocumentsFiles();
    }

    /**
     * Move this folder to the folder that is set with the parameter $destFolderUUID. The method
     * will check if the user has the right to upload files to that folder and then move the folder
     * within the file system and the database structure. The role rights for viewing and uploading will then be
     * adopted recursively from the destination folder.
     * @param string $destFolderUUID UUID of the destination folder to which this folder is to be moved.
     * @return void
     * @throws Exception
     * @throws RuntimeException
     * @throws UnexpectedValueException
     * @throws Exception
     */
    public function moveToFolder(string $destFolderUUID)
    {
        $folder = new TableFolder($this->db);
        $folder->readDataByUuid($destFolderUUID);

        if ($folder->hasUploadRight()) {
            FileSystemUtils::moveDirectory($this->getFullFolderPath(), $folder->getFullFolderPath() . '/' . $this->getValue('fol_name'));

            $this->db->startTransaction();
            // save new parent folder
            $this->setValue('fol_fol_id_parent', $folder->getValue('fol_id'));
            $this->setValue('fol_path', $folder->getValue('fol_path') . '/' . $folder->getValue('fol_name'));
            $this->setValue('fol_public', $folder->getValue('fol_public'));
            $this->setValue('fol_locked', $folder->getValue('fol_locked'));
            $this->save();

            // adopt the role rights of the new parent folder
            $this->removeRolesOnFolder('folder_view', $this->getViewRolesIds());
            $this->removeRolesOnFolder('folder_upload', $this->getUploadRolesIds());
            $this->addRolesOnFolder('folder_view', $folder->getViewRolesIds());
            $this->addRolesOnFolder('folder_upload', $folder->getUploadRolesIds());
            $this->db->endTransaction();
        }
    }

    /**
     * Reads a record out of the table in database selected by the conditions of the param **$sqlWhereCondition** out of the table.
     * If the sql find more than one record the method returns **false**.
     * Per default all columns of the default table will be read and stored in the object.
     * @param string $sqlWhereCondition Conditions for the table to select one record
     * @param array<int,mixed> $queryParams The query params for the prepared statement
     * @return bool Returns **true** if one record is found
     * @throws Exception
     * @see TableAccess#readDataByUuid
     * @see TableAccess#readDataByColumns
     * @see TableAccess#readDataById
     */
    protected function readData(string $sqlWhereCondition, array $queryParams = array()): bool
    {
        if (parent::readData($sqlWhereCondition, $queryParams)) {
            $folId = (int)$this->getValue('fol_id');
            $this->folderViewRolesObject = new RolesRights($this->db, 'folder_view', $folId);
            $this->folderUploadRolesObject = new RolesRights($this->db, 'folder_upload', $folId);

            return true;
        }

        return false;
    }

    /**
     * Remove all roles of the array from the current folder and all the subfolders. The
     * roles will be removed from the right that was set through parameter $rolesRightNameIntern.
     * @param string $rolesRightNameIntern Name of the right where the roles should be removed
     *                                     e.g. **folder_view** or **folder_upload**
     * @param array<int,int> $rolesArray Array with all role IDs that should be removed.
     * @param bool $recursive If set to **true** than the rights will be set recursive to all subfolders.
     * @throws Exception
     */
    public function removeRolesOnFolder(string $rolesRightNameIntern, array $rolesArray, bool $recursive = true)
    {
        $this->editRolesOnFolder('remove', $rolesRightNameIntern, $rolesArray, $recursive);
    }

    /**
     * Benennt eine Ordnerinstanz um und sorgt dafür das bei allen Unterordnern der Pfad angepasst wird
     * @param string $newName
     * @param string $newPath
     * @param int $folderId
     * @throws Exception
     */
    public function rename(string $newName, string $newPath, int $folderId = 0)
    {
        if ($folderId === 0) {
            $folderId = (int)$this->getValue('fol_id');
            $this->setValue('fol_name', $newName);
            $this->save();
        }

        $this->db->startTransaction();

        // Set new path in database for folderId
        $sqlUpdate = 'UPDATE ' . TBL_FOLDERS . '
                         SET fol_path = ? -- $newPath
                       WHERE fol_id = ? -- $folderId';
        $this->db->queryPrepared($sqlUpdate, array($newPath, $folderId));

        $subfoldersStatement = $this->getSubfolderStatement($folderId, array('fol_id', 'fol_name'));

        while ($rowSubfolders = $subfoldersStatement->fetch()) {
            // recursive call with every subfolder
            $this->rename($rowSubfolders['fol_name'], $newPath . '/' . $newName, $rowSubfolders['fol_id']);
        }

        $this->db->endTransaction();
    }

    /**
     * Save all changed columns of the recordset in table of database. Therefore, the class remembers if it's
     * a new record or if only an update is necessary. The update statement will only update
     * the changed columns. If the table has columns for creator or editor than these column
     * with their timestamp will be updated.
     * For new records the user, organization and timestamp will be set per default.
     * @param bool $updateFingerPrint Default **true**. Will update the creator or editor of the recordset if table has columns like **usr_id_create** or **usr_id_changed**
     * @return bool If an update or insert into the database was done then return true, otherwise false.
     * @throws Exception
     */
    public function save(bool $updateFingerPrint = true): bool
    {
        if ($this->newRecord) {
            $this->setValue('fol_timestamp', DATETIME_NOW);
            $this->setValue('fol_usr_id', $GLOBALS['gCurrentUserId']);
            $this->setValue('fol_org_id', $GLOBALS['gCurrentOrgId']);
        }

        return parent::save($updateFingerPrint);
    }
}