<?php

/*
 * This file is part of the "news" Extension for TYPO3 CMS.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 */

namespace GeorgRinger\News\Service;

use GeorgRinger\News\Domain\Model\Dto\EmConfiguration;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Query\Restriction\EndTimeRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\StartTimeRestriction;
use TYPO3\CMS\Core\Utility\GeneralUtility;

/**
 * Service for access control related stuff
 */
class AccessControlService
{
    /**
     * Check if a user has access to all categories of a news record
     */
    public static function userHasCategoryPermissionsForRecord(array $newsRecord): bool
    {
        $settings = GeneralUtility::makeInstance(EmConfiguration::class);
        if (!$settings->getCategoryBeGroupTceFormsRestriction()) {
            return true;
        }

        if (self::getBackendUser()->isAdmin()) {
            // an admin may edit all news
            return true;
        }

        // If there are any categories with denied access, the user has no permission
        if (count(self::getAccessDeniedCategories($newsRecord))) {
            return false;
        }

        return true;
    }

    /**
     * Get an array with the uid and title of all categories the user doesn't have access to
     */
    public static function getAccessDeniedCategories(array $newsRecord): array
    {
        if (self::getBackendUser()->isAdmin()) {
            // an admin may edit all news so no categories without access
            return [];
        }

        // no category mounts set means access to all
        $backendUserCategories = self::getBackendUser()->getCategoryMountPoints();
        if ($backendUserCategories === []) {
            return [];
        }

        $catService = GeneralUtility::makeInstance(CategoryService::class);
        $subCategories = $catService::getChildrenCategories(implode(',', $backendUserCategories));
        if (!empty($subCategories)) {
            $backendUserCategories = explode(',', $subCategories);
        }

        $newsRecordCategories = self::getCategoriesForNewsRecord($newsRecord);

        // Remove categories the user has access to
        foreach ($newsRecordCategories as $key => $newsRecordCategory) {
            if (in_array($newsRecordCategory['uid'], $backendUserCategories)) {
                unset($newsRecordCategories[$key]);
            }
        }

        return $newsRecordCategories;
    }

    /**
     * Get all categories for a news record respecting l10n_mode
     *
     * @param array $newsRecord
     */
    public static function getCategoriesForNewsRecord($newsRecord): array
    {
        // determine localization overlay mode to select categories either from parent or localized record
        if (($newsRecord['sys_language_uid'] ?? 0) > 0 && ($newsRecord['l10n_parent'] ?? 0) > 0) {
            // localized version of a news record
            $categoryL10nMode = $GLOBALS['TCA']['tx_news_domain_model_news']['columns']['categories']['l10n_mode'] ?? '';
            if ($categoryL10nMode === 'mergeIfNotBlank') {
                $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
                    ->getQueryBuilderForTable('sys_category_record_mm');
                $newsRecordCategoriesCount = $queryBuilder->count('*')
                    ->from('sys_category_record_mm')
                    ->where(
                        $queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($newsRecord['uid'], Connection::PARAM_INT)),
                        $queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('tx_news_domain_model_news', Connection::PARAM_STR)),
                        $queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('categories', Connection::PARAM_STR))
                    )
                    ->executeQuery()->fetchOne();
                if ($newsRecordCategoriesCount > 0) {
                    // take categories from localized version
                    $newsRecordUid = $newsRecord['uid'];
                } else {
                    // inherit categories from parent
                    $newsRecordUid = $newsRecord['l10n_parent'];
                }
            } elseif ($categoryL10nMode === 'exclude') {
                // exclude: The localized version inherits the categories of the parent
                $newsRecordUid = $newsRecord['l10n_parent'];
            } else {
                // noCopy/prefixLangTitle: no inheritance
                $newsRecordUid = $newsRecord['uid'];
            }
        } else {
            $newsRecordUid = $newsRecord['uid'];
        }

        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
            ->getQueryBuilderForTable('sys_category');
        $queryBuilder->getRestrictions()
            ->removeByType(StartTimeRestriction::class)
            ->removeByType(HiddenRestriction::class)
            ->removeByType(EndTimeRestriction::class);
        $res = $queryBuilder
            ->select('sys_category_record_mm.uid_local', 'sys_category.title')
            ->from('sys_category')
            ->leftJoin(
                'sys_category',
                'sys_category_record_mm',
                'sys_category_record_mm',
                $queryBuilder->expr()->eq('sys_category_record_mm.uid_local', $queryBuilder->quoteIdentifier('sys_category.uid'))
            )
            ->where(
                $queryBuilder->expr()->eq('sys_category_record_mm.tablenames', $queryBuilder->createNamedParameter('tx_news_domain_model_news', Connection::PARAM_STR)),
                $queryBuilder->expr()->eq('sys_category_record_mm.fieldname', $queryBuilder->createNamedParameter('categories', Connection::PARAM_STR)),
                $queryBuilder->expr()->eq('sys_category_record_mm.uid_foreign', $queryBuilder->createNamedParameter($newsRecordUid, Connection::PARAM_INT))
            )
            ->executeQuery();

        $categories = [];
        while ($row = $res->fetchAssociative()) {
            $categories[] = [
                'uid' => $row['uid_local'],
                'title' => $row['title'],
            ];
        }
        return $categories;
    }

    /**
     * Returns the current BE user.
     */
    protected static function getBackendUser(): BackendUserAuthentication
    {
        return $GLOBALS['BE_USER'];
    }
}
