Skip to content

Commit

Permalink
Merge pull request #130 from NMantek/add/backendModule
Browse files Browse the repository at this point in the history
Add administration backend module
  • Loading branch information
NMantek authored Aug 5, 2024
2 parents c2a6044 + 8dab105 commit 12a3d06
Show file tree
Hide file tree
Showing 12 changed files with 677 additions and 6 deletions.
217 changes: 215 additions & 2 deletions Classes/Controller/AdministrationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Backend\Attribute\Controller;
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
use TYPO3\CMS\Core\Http\PropagateResponseException;
use TYPO3\CMS\Core\Imaging\IconFactory;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Pagination\SimplePagination;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
use TYPO3\CMS\Extbase\Pagination\QueryResultPaginator;
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
use Typoheads\Formhandler\Domain\Model\Log;
use Typoheads\Formhandler\Domain\Repository\LogRepository;

#[Controller]
Expand All @@ -25,15 +31,222 @@ public function __construct(
protected readonly ModuleTemplateFactory $moduleTemplateFactory,
protected readonly IconFactory $iconFactory,
protected readonly LogRepository $logRepository,
protected readonly PageRenderer $pageRenderer,
) {
$this->pageRenderer->loadJavaScriptModule('@jakota/formhandler/Backend.js');
}

public function indexAction(): ResponseInterface {
public function detailAction(Log $log, ?int $logPage = null, ?int $formPageId = null, ?string $ip = null, ?string $formName = null, ?string $startDate = null, ?string $endDate = null, int $itemsPerPage = 10): ResponseInterface {
$moduleTemplate = $this->moduleTemplateFactory->create($this->request);

$serializedArray = unserialize($log->getParams());
// fallback. Use empty array in case the serialized data is invalid
// TODO: add better error handling. Maybe exit & display error?
if (!is_array($serializedArray)) {
$serializedArray = [];
}

$moduleTemplate->assignMultiple([
'log' => $log,
'logPage' => $logPage ?? 0,
'submittedValues' => $this->prepareFlatArray($serializedArray),
'indexActionValues' => [
'formPageId' => $formPageId,
'ip' => $ip,
'formName' => $formName,
'startDate' => $startDate ? new \DateTime($startDate) : null,
'endDate' => $endDate ? new \DateTime($endDate) : null,
'itemsPerPage' => $itemsPerPage,
],
]);

return $moduleTemplate->renderResponse('Administration/Detail');
}

/**
* @param array<string> $fields
*/
public function exportAction(?string $logDataUids = null, array $fields = [], string $fileType = ''): ResponseInterface {
if (null !== $logDataUids && !empty($fields)) {
$logEntries = $this->logRepository->findByUids(array_map('intval', explode(',', $logDataUids)));
$exportReadyValueArray = [];

/** @var Log $logDataRow */
foreach ($logEntries as $logDataRow) {
$fullExportArray = [];

$flatDatasetValues = [
'ip' => $logDataRow->getIp(),
'form_name' => $logDataRow->getFormName(),
'submission_date' => $logDataRow->getCrdate()->format('d.m.Y H:i:s'),
'form_page_id' => strval($logDataRow->getFormPageId()),
'key_hash' => $logDataRow->getKeyHash(),
'unique_hash' => $logDataRow->getUniqueHash(),
];

$serializedArray = unserialize($logDataRow->getParams());
// fallback. Use empty array in case the serialized data is invalid
// TODO: add better error handling. Maybe exit & display error?
if (!is_array($serializedArray)) {
$serializedArray = [];
}
$flatUnserialzedValues = $this->prepareFlatArray($serializedArray);

foreach ($fields as $fieldToExport) {
if (array_key_exists($fieldToExport, $flatDatasetValues)) {
$fullExportArray[$fieldToExport] = $flatDatasetValues[$fieldToExport];
} elseif (array_key_exists($fieldToExport, $flatUnserialzedValues)) {
$fullExportArray[$fieldToExport] = $flatUnserialzedValues[$fieldToExport];
} else {
$fullExportArray[$fieldToExport] = '';
}
}

$exportReadyValueArray[] = $fullExportArray;
}

if ('csv' === $fileType) {
$this->arrayToCsvConvert($exportReadyValueArray, $fields);
}
}

return $this->redirect('index');
}

public function indexAction(?int $logPage = null, ?int $formPageId = null, ?string $ip = null, ?string $formName = null, ?string $startDate = null, ?string $endDate = null, int $itemsPerPage = 10): ResponseInterface {
/** @var QueryResultInterface<Log> */
$logEntries = $this->logRepository->getAllEntries($formPageId, $formName, $ip, $startDate, $endDate);

$moduleTemplate = $this->moduleTemplateFactory->create($this->request);
$paginator = new QueryResultPaginator($logEntries, $logPage ?? 1, $itemsPerPage);
$pagination = new SimplePagination($paginator);

$moduleTemplate->assignMultiple([
'title' => $this->request->getAttribute('site')?->getConfiguration()['websiteTitle'] ?? '',
'pagination' => $pagination,
'paginator' => $paginator,
'logPage' => $logPage ?? 1,
'availableFormNames' => $this->prepareFormNamesForSelect($this->logRepository->getAllFormNames()),
'defaultValues' => [
'formPageId' => $formPageId,
'ip' => $ip,
'formName' => $formName,
'startDate' => $startDate ? new \DateTime($startDate) : null,
'endDate' => $endDate ? new \DateTime($endDate) : null,
'itemsPerPage' => $itemsPerPage,
],
]);

return $moduleTemplate->renderResponse('Administration/Index');
}

public function selectFieldsAction(?string $logDataUids = null, string $filetype = ''): ResponseInterface {
$moduleTemplate = $this->moduleTemplateFactory->create($this->request);

$logEntries = $this->logRepository->findByUids(array_map('intval', explode(',', $logDataUids ?? '')));
$fieldsToShow = [
'Global fields' => [
'ip',
'form_name',
'submission_date',
'form_page_id',
],
'Custom fields' => [],
'System fields' => [
'key_hash',
'unique_hash',
],
];

foreach ($logEntries as $logDataRow) {
$serializedArray = unserialize($logDataRow->getParams());
// fallback. Use empty array in case the serialized data is invalid
// TODO: add better error handling. Maybe exit & display error?
if (!is_array($serializedArray)) {
$serializedArray = [];
}

$params = $this->prepareFlatArray($serializedArray);
$fields = array_keys($params);
foreach ($fields as $idx => $rowField) {
if (!in_array($rowField, $fieldsToShow['Custom fields'])) {
$fieldsToShow['Custom fields'][] = $rowField;
}
}
}

$moduleTemplate->assignMultiple([
'fieldsToShow' => $fieldsToShow,
'fileType' => $filetype,
'logDataUids' => $logDataUids,
]);

return $moduleTemplate->renderResponse('Administration/SelectFields');
}

/**
* @param array<int, array<string, string>> $array
* @param array<string> $headers
*/
protected function arrayToCsvConvert(array $array, array $headers, string $filename = 'export.csv', string $delimiter = ','): void {
if (count($array) < 1 || count($array[0]) !== count($headers)) {
return;
}

$csvFile = fopen('php://memory', 'w');
if ($csvFile) {
fputcsv($csvFile, $headers, $delimiter);
foreach ($array as $line) {
// remove linebreaks
$line = array_map(function ($field) {
return str_replace(["\r", "\n"], '', $field);
}, $line);

fputcsv($csvFile, $line, $delimiter);
}

fseek($csvFile, 0);
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="'.$filename.'";');
fpassthru($csvFile);
}

$response = $this->responseFactory->createResponse(200);

throw new PropagateResponseException($response);
}

/**
* TODO: add ability to map language files to forms. Fieldkeys like 1.customer.email should get changed to their translation.
*
* @param array<mixed> $formValues
*
* @return array<string, string>
*/
protected function prepareFlatArray(array $formValues, string $passedName = ''): array {
$returnArray = [];
foreach ($formValues as $formValueKey => $formValueEntry) {
if (is_array($formValueEntry)) {
$returnArray = array_merge($this->prepareFlatArray($formValueEntry, $passedName.strval($formValueKey).'.'), $returnArray);
} else {
$returnArray[$passedName.$formValueKey] = strval($formValueEntry);
}
}

return $returnArray;
}

/**
* @param array<mixed, mixed> $formNames
*
* @return array<mixed>
*/
protected function prepareFormNamesForSelect(array $formNames): array {
$returnArray = ['' => null];

foreach ($formNames as $formName) {
$returnArray[$formName] = $formName;
}

return $returnArray;
}
}
10 changes: 10 additions & 0 deletions Classes/Domain/Model/Log.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;

class Log extends AbstractEntity {
protected \DateTime $crdate;

protected string $formName = '';

protected int $formPageId = 0;
Expand All @@ -29,6 +31,10 @@ class Log extends AbstractEntity {

protected string $uniqueHash = '';

public function getCrdate(): \DateTime {
return $this->crdate;
}

public function getFormName(): string {
return $this->formName;
}
Expand Down Expand Up @@ -57,6 +63,10 @@ public function getUniqueHash(): string {
return $this->uniqueHash;
}

public function setCrdate(\DateTime $crdate): void {
$this->crdate = $crdate;
}

public function setFormName(string $formName): void {
$this->formName = $formName;
}
Expand Down
78 changes: 78 additions & 0 deletions Classes/Domain/Repository/LogRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

namespace Typoheads\Formhandler\Domain\Repository;

use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
use TYPO3\CMS\Extbase\Persistence\Repository;
use Typoheads\Formhandler\Domain\Model\Log;

Expand All @@ -21,4 +24,79 @@
* @extends \TYPO3\CMS\Extbase\Persistence\Repository<Log>
*/
class LogRepository extends Repository {
/**
* @param array<int> $uids
*
* @return QueryResultInterface<Log>
*/
public function findByUids(array $uids) {
$query = $this->createQuery();

$query->getQuerySettings()->setRespectStoragePage(false);

$query->matching(
$query->logicalAnd(
$query->in('uid', $uids),
)
);

return $query->execute();
}

/**
* @return QueryResultInterface<Log>
*/
public function getAllEntries(?int $formPageId = null, ?string $formName = null, ?string $ip = null, ?string $startDate = null, ?string $endDate = null): QueryResultInterface {
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(false);

$directConstraints = [
'formPageId' => $formPageId,
'formName' => $formName,
'ip' => $ip,
];

$matching = [];
foreach ($directConstraints as $fieldName => $constraint) {
if (!$constraint) {
continue;
}

$matching[] = $query->equals($fieldName, $constraint);
}

if ($startDate) {
$time = new \DateTime($startDate);
$matching[] = $query->greaterThanOrEqual('crdate', $time->getTimestamp());
}
if ($endDate) {
$time = new \DateTime($endDate);
$matching[] = $query->lessThanOrEqual('crdate', $time->getTimestamp());
}

$query->matching(
$query->logicalAnd(
...$matching
)
);

return $query->execute();
}

/**
* @return array<mixed>
*/
public function getAllFormNames(): array {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tx_formhandler_domain_model_log');

$queryBuilder
->select('form_name')
->from('tx_formhandler_domain_model_log')
->groupBy('form_name')
;

$results = $queryBuilder->executeQuery()->fetchAllAssociativeIndexed();

return array_keys($results);
}
}
7 changes: 4 additions & 3 deletions Configuration/Backend/Modules.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
'access' => 'user',
'iconIdentifier' => 'formhandler',
'labels' => 'LLL:EXT:formhandler/Resources/Private/Language/locallang_mod.xlf',
'path' => '/module/formhandler/',
'extensionName' => FormhandlerExtensionConfig::EXTENSION_TITLE,
'controllerActions' => [
AdministrationController::class => [
'index',
'index', 'detail', 'selectFields', 'export',
],
],
// 'inheritNavigationComponentFromMainModule' => false,
// 'navigationComponent' => '@typo3/backend/page-tree/page-tree-element-2',
'inheritNavigationComponentFromMainModule' => false,
'navigationComponent' => '',
],
];
8 changes: 8 additions & 0 deletions Configuration/JavaScriptModules.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

return [
'dependencies' => ['backend', 'core'],
'imports' => [
'@jakota/formhandler/' => 'EXT:formhandler/Resources/Public/JavaScript/',
],
];
5 changes: 5 additions & 0 deletions Configuration/TCA/tx_formhandler_domain_model_log.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,10 @@
],
],
],
'crdate' => [
'config' => [
'type' => 'passthrough',
],
],
],
];
Loading

0 comments on commit 12a3d06

Please sign in to comment.