diff --git a/Classes/Controller/LogController.php b/Classes/Controller/LogController.php index e261d20..769065a 100644 --- a/Classes/Controller/LogController.php +++ b/Classes/Controller/LogController.php @@ -25,8 +25,6 @@ use TYPO3\CMS\Core\Configuration\ExtensionConfiguration; use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Page\PageRenderer; -use TYPO3\CMS\Core\Pagination\ArrayPaginator; -use TYPO3\CMS\Core\Pagination\SimplePagination; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Mvc\Controller\ActionController; @@ -65,29 +63,36 @@ public function listAction(?Filter $filter = null): ResponseInterface $pageId = (int)(array_key_exists('id', $this->request->getQueryParams()) ? $this->request->getQueryParams()['id'] : 0); $filter->setPageId($pageId); - $logEntries = $this->logRepository->findByFilter($filter); $this->persistFilterInBeUserData($filter); $this->resetFilterOnMemoryExhaustionError(); $itemsPerPage = 20; - $currentPage = (int)array_key_exists('currentPage', $this->request->getQueryParams()) && $this->request->getQueryParams()['currentPage'] > 0 ? $this->request->getQueryParams()['currentPage'] : 1; + $currentPage = (int)(array_key_exists('currentPage', $this->request->getQueryParams()) && $this->request->getQueryParams()['currentPage'] > 0 ? $this->request->getQueryParams()['currentPage'] : 1); + $logEntries = $this->logRepository->findByFilter($filter, $currentPage, $itemsPerPage); - $paginator = new ArrayPaginator($logEntries->toArray(), $currentPage, $itemsPerPage); - $pagination = new SimplePagination($paginator); + $totalResultsCount = $this->logRepository->countByFilter($filter); + $totalPages = (int)(ceil($totalResultsCount / $itemsPerPage)); + + $statistic = new Statistic(); + $statistic->calc($filter, $this->logRepository); $moduleTemplate = $this->moduleTemplateFactory->create($this->request); $moduleTemplate->assignMultiple([ 'loggingEnabled' => $extensionConfigurationLogging, - 'logs' => $paginator->getPaginatedItems(), + 'logs' => $logEntries, 'page' => BackendUtility::getRecord('pages', $pageId), 'users' => $this->getUsers(), 'fileTypes' => $this->getFileTypes(), 'filter' => $filter, - 'statistic' => new Statistic($logEntries), - 'paginator' => $paginator, - 'pagination' => $pagination, - 'totalResultCount' => count($logEntries), + 'statistic' => $statistic, + 'pagination' => [ + 'totalPages' => $totalPages, + 'currentPage' => $currentPage, + 'previousPage' => ($currentPage - 1) > 0 ? $currentPage - 1 : 0, + 'nextPage' => $totalPages > $currentPage ? $currentPage + 1 : 0, + ], + 'totalResultCount' => $totalResultsCount, 'isRoot' => $pageId == 0, ]); return $moduleTemplate->renderResponse('List'); diff --git a/Classes/Domain/Repository/LogRepository.php b/Classes/Domain/Repository/LogRepository.php index 1f98230..6cd2fd0 100644 --- a/Classes/Domain/Repository/LogRepository.php +++ b/Classes/Domain/Repository/LogRepository.php @@ -17,154 +17,149 @@ use Leuchtfeuer\SecureDownloads\Domain\Model\Log; use Leuchtfeuer\SecureDownloads\Domain\Transfer\Filter; use Leuchtfeuer\SecureDownloads\Domain\Transfer\Token\AbstractToken; +use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\ConnectionPool; +use TYPO3\CMS\Core\Database\Query\QueryBuilder; use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException; use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Extbase\Persistence\Exception\InvalidQueryException; -use TYPO3\CMS\Extbase\Persistence\QueryInterface; -use TYPO3\CMS\Extbase\Persistence\QueryResultInterface; +use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper; use TYPO3\CMS\Extbase\Persistence\Repository; class LogRepository extends Repository { - protected $defaultOrderings = [ - 'tstamp' => QueryInterface::ORDER_DESCENDING, - ]; + const TABLENAME = 'tx_securedownloads_domain_model_log'; - /** - * Initializes the query and applies default options. - * - * @return QueryInterface The generated query object. - */ - public function createQuery(): QueryInterface + public function __construct( + private readonly ConnectionPool $connectionPool, + private readonly DataMapper $dataMapper + ) { + } + + public function createQueryBuilder(): QueryBuilder { - $query = parent::createQuery(); - $querySettings = $query->getQuerySettings(); - $querySettings->setRespectStoragePage(false); - $querySettings->setRespectSysLanguage(false); - $query->setQuerySettings($querySettings); + $queryBuilder = $this->connectionPool->getQueryBuilderForTable(self::TABLENAME); + $queryBuilder->getRestrictions()->removeAll(); + $queryBuilder + ->from(self::TABLENAME) + ->orderBy('tstamp', 'DESC'); - return $query; + return $queryBuilder; } - /** - * Finds log data and applies filter. - * - * @param Filter|null $filter The filter object. - * - * @return QueryResultInterface The query result. - */ - public function findByFilter(?Filter $filter): QueryResultInterface + public function findByFilter(?Filter $filter, int $currentPage = 1, int $itemsPerPage = 20): array { - $query = $this->createQuery(); + $queryBuilder = $this->createQueryBuilder(); - if ($filter instanceof Filter) { - try { - $this->applyFilter($query, $filter); - } catch (InvalidQueryException $exception) { - // Do nothing for now. - } - } + $this->applyFilter($queryBuilder, $filter); - return $query->execute(); + $result = $queryBuilder + ->select('*') + ->setMaxResults($itemsPerPage) + ->setFirstResult($itemsPerPage * ($currentPage - 1)) + ->executeQuery() + ->fetchAllAssociative() ?? []; + return $this->dataMapper->map(Log::class, $result); } - /** - * Applies the filter to a query object. - * - * @param QueryInterface $query The query object - * @param Filter $filter The filter object - * @throws InvalidQueryException - */ - protected function applyFilter(QueryInterface &$query, Filter $filter): void + public function countByFilter(?Filter $filter): int + { + $queryBuilder = $this->createQueryBuilder(); + + $this->applyFilter($queryBuilder, $filter); + + return (int)($queryBuilder + ->count('uid') + ->executeQuery() + ->fetchOne() ?? 0); + } + + public function getFirstTimestampByFilter(?Filter $filter, bool $reverse = false): int + { + $queryBuilder = $this->createQueryBuilder(); + + $this->applyFilter($queryBuilder, $filter); + + return (int)($queryBuilder + ->select('tstamp') + ->orderBy('tstamp', $reverse ? 'DESC' : 'ASC') + ->executeQuery() + ->fetchOne() ?? 0); + } + + public function getTrafficSumByFilter(?Filter $filter): float + { + $queryBuilder = $this->createQueryBuilder(); + + $this->applyFilter($queryBuilder, $filter); + + return (float)($queryBuilder + ->selectLiteral('SUM(file_size) AS sum') + ->executeQuery() + ->fetchOne() ?? 0.0); + } + + protected function applyFilter(QueryBuilder &$queryBuilder, Filter $filter): void { $constraints = []; - // FileType - $this->applyFileTypePropertyToFilter($filter->getFileType(), $query, $constraints); + if ($filter instanceof Filter) { + try { + // FileType + $this->applyFileTypePropertyToFilter($filter->getFileType(), $queryBuilder, $constraints); - // User Type - $this->applyUserTypePropertyToFilter($filter, $query, $constraints); + // User Type + $this->applyUserTypePropertyToFilter($filter, $queryBuilder, $constraints); - // Period - $this->applyPeriodPropertyToFilter($filter, $query, $constraints); + // Period + $this->applyPeriodPropertyToFilter($filter, $queryBuilder, $constraints); - // User and Page - $this->applyEqualPropertyToFilter((int)$filter->getFeUserId(), 'user', $query, $constraints); - $this->applyEqualPropertyToFilter((int)$filter->getPageId(), 'page', $query, $constraints); + // User and Page + $this->applyEqualPropertyToFilter((int)$filter->getFeUserId(), 'user', $queryBuilder, $constraints); + $this->applyEqualPropertyToFilter((int)$filter->getPageId(), 'page', $queryBuilder, $constraints); - if (count($constraints) > 0) { - $query->matching($query->logicalAnd(...$constraints)); + if (count($constraints) > 0) { + $queryBuilder->where(...$constraints); + } + } catch (InvalidQueryException $exception) { + // Do nothing for now. + } } } - /** - * Applies the file type property of the filter to the query object. - * - * @param mixed $fileType Identifier of the file type - * @param QueryInterface $query The query object - * @param array $constraints Array containing all previously applied constraints - */ - protected function applyFileTypePropertyToFilter($fileType, QueryInterface $query, array &$constraints): void + protected function applyFileTypePropertyToFilter(string $fileType, QueryBuilder $queryBuilder, array &$constraints): void { if ($fileType !== '' && $fileType !== '0') { - $constraints[] = $query->equals('mediaType', $fileType); + $constraints[] = $queryBuilder->expr()->eq('media_type', $queryBuilder->createNamedParameter($fileType)); } } - /** - * Applies the user type property of the filter to the query object. - * - * @param Filter $filter The filter object - * @param QueryInterface $query The query object - * @param array $constraints Array containing all previously applied constraints - */ - protected function applyUserTypePropertyToFilter(Filter $filter, QueryInterface $query, array &$constraints): void + protected function applyUserTypePropertyToFilter(Filter $filter, QueryBuilder $queryBuilder, array &$constraints): void { - if ($filter->getUserType() != 0) { - $userQuery = $query->equals('user', null); - - if ($filter->getUserType() === Filter::USER_TYPE_LOGGED_ON) { - $constraints[] = $query->logicalNot($userQuery); - } - if ($filter->getUserType() === Filter::USER_TYPE_LOGGED_OFF) { - $constraints[] = $userQuery; - } + if ($filter->getUserType() === Filter::USER_TYPE_LOGGED_ON) { + $constraints[] = $queryBuilder->expr()->gt('user', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)); + } + if ($filter->getUserType() === Filter::USER_TYPE_LOGGED_OFF) { + $constraints[] = $queryBuilder->expr()->eq('user', $queryBuilder->createNamedParameter(0, Connection::PARAM_INT)); } } - /** - * Applies the period properties of the filter to the query object. - * - * @param Filter $filter The filter object - * @param QueryInterface $query The query object - * @param array $constraints Array containing all previously applied constraints - * @throws InvalidQueryException - */ - protected function applyPeriodPropertyToFilter(Filter $filter, QueryInterface $query, array &$constraints): void + protected function applyPeriodPropertyToFilter(Filter $filter, QueryBuilder $queryBuilder, array &$constraints): void { if ((int)$filter->getFrom() !== 0) { - $constraints[] = $query->greaterThanOrEqual('tstamp', $filter->getFrom()); + $constraints[] = $queryBuilder->expr()->gte('tstamp', $queryBuilder->createNamedParameter($filter->getFrom(), Connection::PARAM_INT)); } if ((int)$filter->getTill() !== 0) { - $constraints[] = $query->lessThanOrEqual('tstamp', $filter->getTill()); + $constraints[] = $queryBuilder->expr()->lte('tstamp', $queryBuilder->createNamedParameter($filter->getTill(), Connection::PARAM_INT)); } } - /** - * Applies given property of the filter to the query object. - * - * @param int $property The value of the property - * @param string $propertyName The property name - * @param QueryInterface $query The query object - * @param array $constraints Array containing all previously applied constraints - */ - protected function applyEqualPropertyToFilter(int $property, string $propertyName, QueryInterface $query, array &$constraints): void + protected function applyEqualPropertyToFilter(int $property, string $propertyName, QueryBuilder $queryBuilder, array &$constraints): void { if ($property !== 0) { - $constraints[] = $query->equals($propertyName, $property); + $constraints[] = $queryBuilder->expr()->eq($propertyName, $queryBuilder->createNamedParameter($property, Connection::PARAM_INT)); } } @@ -196,7 +191,10 @@ public function logDownload(AbstractToken $token, int $fileSize, string $mimeTyp $log->setFileId((string)$fileObject->getUid()); } - $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_securedownloads_domain_model_log'); - $queryBuilder->insert('tx_securedownloads_domain_model_log')->values($log->toArray())->executeStatement(); + $queryBuilder = $this->createQueryBuilder(); + $queryBuilder + ->insert(self::TABLENAME) + ->values($log->toArray()) + ->executeStatement(); } } diff --git a/Classes/Domain/Transfer/Statistic.php b/Classes/Domain/Transfer/Statistic.php index 4cc952b..d5f5de9 100644 --- a/Classes/Domain/Transfer/Statistic.php +++ b/Classes/Domain/Transfer/Statistic.php @@ -14,45 +14,33 @@ * ***/ -use Leuchtfeuer\SecureDownloads\Domain\Model\Log; -use TYPO3\CMS\Extbase\Persistence\QueryResultInterface; +use Leuchtfeuer\SecureDownloads\Domain\Repository\LogRepository; class Statistic { - /** - * @var float - */ - protected $traffic = 0.00; - - /** - * @var \DateTime - */ - protected $from; - - /** - * @var \DateTime - */ - protected $till; - - public function __construct(QueryResultInterface $logEntries) + public function __construct( + protected \DateTime $from = new \DateTime(), + protected \DateTime $till = new \DateTime(), + protected float $traffic = 0.00 + ) { - $this->from = new \DateTime(); - $this->till = new \DateTime(); - $count = $logEntries->count(); + } - if ($count > 0) { - $this->till->setTimestamp($logEntries->getFirst()->getTstamp()); - $i = 1; + public function calc(Filter $filter, LogRepository $logRepository): void + { + if ($filter->getFrom() !== null) { + $this->from->setTimestamp($filter->getFrom()); + } else { + $this->from->setTimestamp($logRepository->getFirstTimestampByFilter($filter)); + } - /** @var Log $logEntry */ - foreach ($logEntries as $logEntry) { - $this->traffic += $logEntry->getFileSize(); - if ($i === $count) { - $this->from->setTimestamp($logEntry->getTstamp()); - } - $i++; - } + if ($filter->getTill() !== null) { + $this->till->setTimestamp($filter->getTill()); + } elseif ($logRepository->getFirstTimestampByFilter($filter, true) > 0) { + $this->till->setTimestamp($logRepository->getFirstTimestampByFilter($filter, true)); } + + $this->traffic = $logRepository->getTrafficSumByFilter($filter); } public function getTraffic(): float diff --git a/Documentation/About/ChangeLog/6-0-2.rst b/Documentation/About/ChangeLog/6-0-2.rst new file mode 100755 index 0000000..65bb7b0 --- /dev/null +++ b/Documentation/About/ChangeLog/6-0-2.rst @@ -0,0 +1,51 @@ +.. include:: ../../Includes.txt + +========================== +Version 6.0.2 - 2024/03/22 +========================== + +This release is a bugfix and maintenance release. + +Download +======== + +Download this version from the `TYPO3 extension repository `__ or from +`GitHub `__. + +Added +===== +* Add scheduler task to clean up download logs + +Changed +======= +* Improve performance of backend module with many log entries + +Deprecated +========== + +Removed +======= + +All Changes +=========== +This is a list of all changes in this release:: + + 2024-03-22 Merge branch 'master' into feature/TER-187-v12 (Commit af70d1f by Niklas Grieger) + 2024-03-22 [TASK] Implement code improvements [TER-187] [TER-188] (Commit 6b933c1 by Niklas Grieger) + 2024-03-21 Merge pull request #199 from ste101/patch-1 (Commit d58136d by Marcus Balasch) + 2024-03-21 Error because of a space (Commit 913017a by Stephan Bauer) + 2024-03-20 [TASK] Run PHP code style fixer [TER-187] [TER-188] (Commit 5c5b5f5 by Niklas Grieger) + 2024-03-20 [TASK] Improve performance of backend module with many log entries [TER-187] [TER-188] (Commit 8f76b59 by Niklas Grieger) + 2024-03-12 Merge pull request #194 from ste101/patch-1 (Commit 9cd4b2d by Marcus Balasch) + 2024-03-11 Add scheduler task (Commit 2edc707 by Stephan Bauer) + 2024-01-10 [TASK] Prepare release of version 6.0.1 [TER-166] [TER-167] (#193) (Commit f499325 by Niklas Grieger) + +Contributors +============ +Following people have contributed to this release: + +* Stephan Bauer +* Marcus Balasch +* Niklas Grieger + +Thank you very much for your support. The next drink is on us! 🍻 diff --git a/Documentation/About/ChangeLog/Index.rst b/Documentation/About/ChangeLog/Index.rst index f8698da..e642145 100755 --- a/Documentation/About/ChangeLog/Index.rst +++ b/Documentation/About/ChangeLog/Index.rst @@ -17,6 +17,7 @@ List of versions :titlesonly: :glob: + 6-0-2 6-0-1 6-0-0 5-0-3 diff --git a/Resources/Private/Partials/Backend/View.html b/Resources/Private/Partials/Backend/View.html index b2bdddf..d6c0296 100644 --- a/Resources/Private/Partials/Backend/View.html +++ b/Resources/Private/Partials/Backend/View.html @@ -39,69 +39,68 @@ - - +
    - +
  • - + Previous
  • - +
  • 1
  • - +
  • ...
  • - +
  • - - {pagination.previousPageNumber} + + {pagination.previousPage}
  • - {paginator.currentPageNumber} + {pagination.currentPage}
  • - +
  • - - {pagination.nextPageNumber} + + {pagination.nextPage}
  • - +
  • ...
  • - +
  • - - {pagination.lastPageNumber} + + {pagination.totalPages}
  • - +
  • - + Next
-
\ No newline at end of file +
diff --git a/Resources/Private/Templates/Log/List.html b/Resources/Private/Templates/Log/List.html index 7fcde3d..73216b2 100644 --- a/Resources/Private/Templates/Log/List.html +++ b/Resources/Private/Templates/Log/List.html @@ -31,7 +31,7 @@

- + {f:translate(key: 'module.subheading', arguments: '{0: totalResultCount, 1: traffic, 2: from, 3: till}') -> f:format.raw()} @@ -61,4 +61,4 @@

Logging is disabled

- \ No newline at end of file + diff --git a/ext_emconf.php b/ext_emconf.php index ebf0f5e..9d26485 100644 --- a/ext_emconf.php +++ b/ext_emconf.php @@ -4,7 +4,7 @@ 'title' => 'Secure Downloads', 'description' => '"Secure Download": Apply TYPO3 access rights to ALL file assets (PDFs, TGZs or JPGs etc. - configurable) - protect them from direct access.', 'category' => 'fe', - 'version' => '6.0.1', + 'version' => '6.0.2', 'state' => 'stable', 'clearCacheOnLoad' => true, 'author' => 'Dev Leuchtfeuer',