diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..d840afa
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,97 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+
+jobs:
+ check-composer:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: 8.3
+ coverage: none
+ tools: composer:v2
+ env:
+ COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Validate composer.json
+ run: composer validate
+
+ php-linting:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ php-version:
+ - 8.3
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: "${{ matrix.php-version }}"
+ coverage: none
+
+ - name: PHP lint
+ run: "find *.php Classes Configuration Tests -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l"
+
+ xml-linting:
+ runs-on: ubuntu-latest
+ needs:
+ - check-composer
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: "8.3"
+ coverage: none
+ tools: composer:v2
+ env:
+ COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Install xmllint
+ run: sudo apt update && sudo apt-get install libxml2-utils
+
+ - name: Install dependencies
+ run: composer install --no-progress --no-interaction --optimize-autoloader
+
+ - name: Fetch schema for xliff
+ run: mkdir .Build
+
+ - name: Fetch schema for xliff
+ run: wget https://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd --output-document=.Build/xliff-core-1.2-strict.xsd
+
+ - name: TYPO3 language files
+ run: xmllint --schema .Build/xliff-core-1.2-strict.xsd --noout $(find Resources -name '*.xlf')
+
+ coding-guideline:
+ runs-on: ubuntu-latest
+ needs:
+ - check-composer
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: "8.3"
+ coverage: none
+ tools: composer:v2
+ env:
+ COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Install dependencies
+ run: composer install --no-progress --no-interaction --optimize-autoloader
+
+ - name: Coding Guideline
+ run: ./vendor/bin/ecs check
diff --git a/Classes/Controller/UserimportController.php b/Classes/Controller/UserimportController.php
index 8b3a110..0dfafb3 100644
--- a/Classes/Controller/UserimportController.php
+++ b/Classes/Controller/UserimportController.php
@@ -1,7 +1,5 @@
view = $this->moduleTemplateFactory->create($this->request);
- $extensionConfiguration = GeneralUtility::makeInstance(ExtensionConfiguration::class);
- $moduleConfiguration = $extensionConfiguration->get('userimport');
+ $moduleConfiguration = $this->extensionConfiguration->get('userimport');
- if (!empty($moduleConfiguration['uploadStorageFolder'])) {
+ if ($moduleConfiguration['uploadStorageFolder'] !== '') {
$this->view->assign('uploadStorageFolder', $moduleConfiguration['uploadStorageFolder']);
}
- $this->view->assign('importJob', $importJob);
- return $this->htmlResponse();
+ $this->view->assign('importJob', $this->importJob);
+ return $this->view->renderResponse('Userimport/Main');
}
protected function initializeUploadAction()
{
- /** @var PropertyMappingConfiguration $propertyMappingConfiguration */
- $propertyMappingConfiguration = $this->arguments['importJob']->getPropertyMappingConfiguration();
- $uploadConfiguration = [
- UploadedFileReferenceConverter::CONFIGURATION_ALLOWED_FILE_EXTENSIONS => 'xlsx,csv'
- ];
- $propertyMappingConfiguration->allowProperties('file');
- $propertyMappingConfiguration->forProperty('file')
- ->setTypeConverterOptions(
- UploadedFileReferenceConverter::class,
- $uploadConfiguration
- );
+ // As Validators can contain state, do not inject them
+ $mimeTypeValidator = GeneralUtility::makeInstance(MimeTypeValidator::class);
+ $mimeTypeValidator->setOptions([
+ 'allowedMimeTypes' => ['text/csv', 'application/vnd.ms-excel', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'],
+ 'ignoreFileExtensionCheck' => false,
+ 'notAllowedMessage' => 'Not allowed file type',
+ 'invalidExtensionMessage' => 'Invalid file extension',
+ ]);
+
+ $moduleConfiguration = $this->extensionConfiguration->get('userimport');
+
+ $fileHandlingServiceConfiguration = $this->arguments->getArgument('importJob')->getFileHandlingServiceConfiguration();
+ $fileHandlingServiceConfiguration->addFileUploadConfiguration(
+ (new FileUploadConfiguration('file'))
+ ->setRequired()
+ ->addValidator($mimeTypeValidator)
+ ->setMaxFiles(1)
+ ->setUploadFolder($moduleConfiguration['uploadStorageFolder']),
+ );
+
+ // Extbase's property mapping is not handling FileUploads, so it must not operate on this property.
+ // When using the FileUpload attribute/annotation, this internally does the same. This is covered
+ // by the `addFileUploadConfiguration()` functionality.
+ $this->arguments->getArgument('importJob')->getPropertyMappingConfiguration()->skipProperties('file');
}
/**
- * @param ImportJob $importJob
- *
- * @return void
+ * @return ResponseInterface
*/
public function uploadAction(ImportJob $importJob)
{
$this->importJobRepository->add($importJob);
$this->persistenceManager->persistAll();
- $this->redirect('options', null, null, ['importJob' => $importJob]);
+ return $this->redirect('options', null, null, ['importJob' => $importJob]);
}
- /**
- * @param ImportJob $importJob
- */
public function optionsAction(ImportJob $importJob): ResponseInterface
{
+ $this->view = $this->moduleTemplateFactory->create($this->request);
$this->view->assign('importJob', $importJob);
if ($importJob->getFile() instanceof FileReference) {
@@ -119,14 +109,12 @@ public function optionsAction(ImportJob $importJob): ResponseInterface
$this->view->assign('frontendUserFolders', $this->tcaService->getFrontendUserFolders());
$this->view->assign('frontendUserGroups', $this->tcaService->getFrontendUserGroups());
$this->view->assign('frontendUserTableFieldNames', $this->tcaService->getFrontendUserTableUniqueFieldNames());
- return $this->htmlResponse();
+ return $this->view->renderResponse('Userimport/Options');
}
- /**
- * @param ImportJob $importJob
- */
public function fieldMappingAction(ImportJob $importJob): ResponseInterface
{
+ $this->view = $this->moduleTemplateFactory->create($this->request);
$this->view->assign('importJob', $importJob);
// Update ImportJob with options
@@ -137,7 +125,7 @@ public function fieldMappingAction(ImportJob $importJob): ResponseInterface
ImportJob::IMPORT_OPTION_GENERATE_PASSWORD,
ImportJob::IMPORT_OPTION_USER_GROUPS,
ImportJob::IMPORT_OPTION_UPDATE_EXISTING_USERS,
- ImportJob::IMPORT_OPTION_UPDATE_EXISTING_USERS_UNIQUE_FIELD
+ ImportJob::IMPORT_OPTION_UPDATE_EXISTING_USERS_UNIQUE_FIELD,
];
$fieldOptionsArray = [];
foreach ($fieldOptionArguments as $argumentName) {
@@ -159,21 +147,18 @@ public function fieldMappingAction(ImportJob $importJob): ResponseInterface
);
// If username is not generated from e-mail, the field must be mapped
- $usernameMustBeMapped = !(bool)$importJob->getImportOption(ImportJob::IMPORT_OPTION_USE_EMAIL_AS_USERNAME);
+ $usernameMustBeMapped = !(bool) $importJob->getImportOption(ImportJob::IMPORT_OPTION_USE_EMAIL_AS_USERNAME);
$this->view->assign('usernameMustBeMapped', $usernameMustBeMapped);
// If username is generated from e-mail, the field e-mail must be mapped
- $emailMustBeMapped = (bool)$importJob->getImportOption(ImportJob::IMPORT_OPTION_USE_EMAIL_AS_USERNAME);
+ $emailMustBeMapped = (bool) $importJob->getImportOption(ImportJob::IMPORT_OPTION_USE_EMAIL_AS_USERNAME);
$this->view->assign('emailMustBeMapped', $emailMustBeMapped);
- return $this->htmlResponse();
+ return $this->view->renderResponse('Userimport/FieldMapping');
}
- /**
- * @param ImportJob $importJob
- * @param array $fieldMapping
- */
public function importPreviewAction(ImportJob $importJob, array $fieldMapping): ResponseInterface
{
+ $this->view = $this->moduleTemplateFactory->create($this->request);
$this->view->assign('importJob', $importJob);
// Update ImportJob with field mapping
@@ -184,20 +169,17 @@ public function importPreviewAction(ImportJob $importJob, array $fieldMapping):
$previewData = $this->spreadsheetService->generateDataFromImportJob($importJob, true);
$this->view->assign('previewDataHeader', array_keys($previewData[0]));
$this->view->assign('previewData', $previewData);
- return $this->htmlResponse();
+ return $this->view->renderResponse('Userimport/ImportPreview');
}
- /**
- * @param ImportJob $importJob
- */
public function performImportAction(ImportJob $importJob): ResponseInterface
{
+ $this->view = $this->moduleTemplateFactory->create($this->request);
$rowsToImport = $this->spreadsheetService->generateDataFromImportJob($importJob);
$this->view->assign('rowsInSource', count($rowsToImport));
$result = $this->userImportService->performImport($importJob, $rowsToImport);
-
$this->view->assign('updatedRecords', $result['updatedRecords']);
$this->view->assign('insertedRecords', $result['insertedRecords']);
$this->view->assign('log', $result['log']);
@@ -207,41 +189,14 @@ public function performImportAction(ImportJob $importJob): ResponseInterface
// Remove import job
$this->importJobRepository->remove($importJob);
$this->persistenceManager->persistAll();
- return $this->htmlResponse();
+ return $this->view->renderResponse('Userimport/PerformImport');
}
/**
* Deactivate errorFlashMessage
- *
- * @return bool|string
*/
- public function getErrorFlashMessage()
+ public function getErrorFlashMessage(): bool|string
{
return false;
}
-
- public function injectImportJobRepository(ImportJobRepository $importJobRepository): void
- {
- $this->importJobRepository = $importJobRepository;
- }
-
- public function injectPersistenceManager(PersistenceManagerInterface $persistenceManager): void
- {
- $this->persistenceManager = $persistenceManager;
- }
-
- public function injectSpreadsheetService(SpreadsheetService $spreadsheetService): void
- {
- $this->spreadsheetService = $spreadsheetService;
- }
-
- public function injectUserImportService(UserImportService $userImportService): void
- {
- $this->userImportService = $userImportService;
- }
-
- public function injectTcaService(TcaService $tcaService): void
- {
- $this->tcaService = $tcaService;
- }
}
diff --git a/Classes/Domain/Model/ImportJob.php b/Classes/Domain/Model/ImportJob.php
index 8bb50f3..4c07c8d 100644
--- a/Classes/Domain/Model/ImportJob.php
+++ b/Classes/Domain/Model/ImportJob.php
@@ -2,9 +2,10 @@
namespace Visol\Userimport\Domain\Model;
-use TYPO3\CMS\Extbase\Annotation as Extbase;
-use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
+use TYPO3\CMS\Extbase\Annotation\ORM\Transient;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
+use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
+
/***
*
* This file is part of the "Frontend User Import" Extension for TYPO3 CMS.
@@ -17,24 +18,20 @@
***/
class ImportJob extends AbstractEntity
{
+ public const IMPORT_OPTION_TARGET_FOLDER = 'targetFolder';
+ public const IMPORT_OPTION_FIRST_ROW_CONTAINS_FIELD_NAMES = 'firstRowContainsFieldNames';
+ public const IMPORT_OPTION_USE_EMAIL_AS_USERNAME = 'useEmailAsUsername';
+ public const IMPORT_OPTION_GENERATE_PASSWORD = 'generatePassword';
+ public const IMPORT_OPTION_USER_GROUPS = 'userGroups';
+ public const IMPORT_OPTION_UPDATE_EXISTING_USERS = 'updateExistingUsers';
+ public const IMPORT_OPTION_UPDATE_EXISTING_USERS_UNIQUE_FIELD = 'updateExistingUsersUniqueField';
- const IMPORT_OPTION_TARGET_FOLDER = 'targetFolder';
- const IMPORT_OPTION_FIRST_ROW_CONTAINS_FIELD_NAMES = 'firstRowContainsFieldNames';
- const IMPORT_OPTION_USE_EMAIL_AS_USERNAME = 'useEmailAsUsername';
- const IMPORT_OPTION_GENERATE_PASSWORD = 'generatePassword';
- const IMPORT_OPTION_USER_GROUPS = 'userGroups';
- const IMPORT_OPTION_UPDATE_EXISTING_USERS = 'updateExistingUsers';
- const IMPORT_OPTION_UPDATE_EXISTING_USERS_UNIQUE_FIELD = 'updateExistingUsersUniqueField';
-
- /**
- * @var FileReference
- */
- protected $file = null;
+ protected ?FileReference $file = null;
/**
* @var string
- * @TYPO3\CMS\Extbase\Annotation\ORM\Transient
*/
+ #[Transient]
protected $importOptions;
/**
@@ -42,80 +39,51 @@ class ImportJob extends AbstractEntity
*/
protected $fieldMapping;
- /**
- * @return FileReference $file
- */
- public function getFile()
+ public function getFile(): ?FileReference
{
return $this->file;
}
- /**
- * Sets the image
- *
- * @param FileReference $file
- *
- * @return void
- */
- public function setFile(FileReference $file)
+ public function setFile(?FileReference $file): void
{
$this->file = $file;
}
- /**
- * @return string
- */
public function getImportOptions(): string
{
return $this->importOptions;
}
- /**
- * @return array
- */
public function getImportOptionsArray(): array
{
- return !empty($this->importOptions) ? unserialize($this->importOptions) : [];
+ return empty($this->importOptions) ? [] : unserialize($this->importOptions);
}
/**
* @param string $option
- *
* @return mixed
*/
public function getImportOption($option)
{
- return array_key_exists($option, $this->getImportOptionsArray()) ? $this->getImportOptionsArray()[$option] : null;
+ return $this->getImportOptionsArray()[$option] ?? null;
}
- /**
- * @param array $importOptions
- */
- public function setImportOptions(array $importOptions)
+ public function setImportOptions(array $importOptions): void
{
$this->importOptions = serialize($importOptions);
}
- /**
- * @return string
- */
public function getFieldMapping(): string
{
return $this->fieldMapping;
}
- /**
- * @return array
- */
public function getFieldMappingArray(): array
{
- return !empty($this->fieldMapping) ? unserialize($this->fieldMapping) : [];
+ return empty($this->fieldMapping) ? [] : unserialize($this->fieldMapping);
}
- /**
- * @param array $fieldMapping
- */
- public function setFieldMapping(array $fieldMapping)
+ public function setFieldMapping(array $fieldMapping): void
{
$this->fieldMapping = serialize($fieldMapping);
}
diff --git a/Classes/Domain/Repository/ImportJobRepository.php b/Classes/Domain/Repository/ImportJobRepository.php
index d3bae8f..8d69366 100644
--- a/Classes/Domain/Repository/ImportJobRepository.php
+++ b/Classes/Domain/Repository/ImportJobRepository.php
@@ -1,7 +1,9 @@
- */
- protected $sourceTypes = ['array'];
-
- /**
- * @var string
- */
- protected $targetType = 'TYPO3\\CMS\\Extbase\\Domain\\Model\\FileReference';
-
- /**
- * Take precedence over the available FileReferenceConverter
- *
- * @var int
- */
- protected $priority = 30;
-
- /**
- * @var ResourceFactory
- */
- protected $resourceFactory;
-
- /**
- * @var HashService
- */
- protected $hashService;
-
- /**
- * @var PersistenceManager
- */
- protected $persistenceManager;
-
- /**
- * @var FileInterface[]
- */
- protected $convertedResources = [];
-
- /**
- * Actually convert from $source to $targetType, taking into account the fully
- * built $convertedChildProperties and $configuration.
- *
- * @param string|int $source
- * @param string $targetType
- * @param array $convertedChildProperties
- * @param PropertyMappingConfigurationInterface $configuration
- * @throws \TYPO3\CMS\Extbase\Property\Exception
- * @return AbstractFileFolder
- * @api
- */
- public function convertFrom($source, $targetType, array $convertedChildProperties = [], PropertyMappingConfigurationInterface $configuration = null)
- {
- if (!isset($source['error']) || $source['error'] === \UPLOAD_ERR_NO_FILE) {
- if (isset($source['submittedFile']['resourcePointer'])) {
- try {
- $resourcePointer = $this->hashService->validateAndStripHmac($source['submittedFile']['resourcePointer']);
- if (strpos($resourcePointer, 'file:') === 0) {
- $fileUid = substr($resourcePointer, 5);
- return $this->createFileReferenceFromFalFileObject($this->resourceFactory->getFileObject($fileUid));
- } else {
- return $this->createFileReferenceFromFalFileReferenceObject($this->resourceFactory->getFileReferenceObject($resourcePointer), $resourcePointer);
- }
- } catch (\InvalidArgumentException $e) {
- // Nothing to do. No file is uploaded and resource pointer is invalid. Discard!
- }
- }
- return null;
- }
-
- if ($source['error'] !== \UPLOAD_ERR_OK) {
- switch ($source['error']) {
- case \UPLOAD_ERR_INI_SIZE:
- case \UPLOAD_ERR_FORM_SIZE:
- case \UPLOAD_ERR_PARTIAL:
- return new Error('Error Code: ' . $source['error'], 1264440823);
- default:
- return new Error('An error occurred while uploading. Please try again or contact the administrator if the problem remains', 1340193849);
- }
- }
-
- if (isset($this->convertedResources[$source['tmp_name']])) {
- return $this->convertedResources[$source['tmp_name']];
- }
-
- try {
- $resource = $this->importUploadedResource($source, $configuration);
- } catch (\Exception $e) {
- return new Error($e->getMessage(), $e->getCode());
- }
-
- $this->convertedResources[$source['tmp_name']] = $resource;
- return $resource;
- }
-
- /**
- * @param FalFile $file
- * @param int $resourcePointer
- * @return FileReference
- */
- protected function createFileReferenceFromFalFileObject(FalFile $file, $resourcePointer = null)
- {
- $fileReference = $this->resourceFactory->createFileReferenceObject(
- [
- 'uid_local' => $file->getUid(),
- 'uid_foreign' => uniqid('NEW_'),
- 'uid' => uniqid('NEW_'),
- 'crop' => null,
- ]
- );
- return $this->createFileReferenceFromFalFileReferenceObject($fileReference, $resourcePointer);
- }
-
- /**
- * Import a resource and respect configuration given for properties
- *
- * @param array $uploadInfo
- * @param PropertyMappingConfigurationInterface $configuration
- * @return \TYPO3\CMS\Extbase\Domain\Model\FileReference
- * @throws TypeConverterException
- * @throws ExistingTargetFileNameException
- */
- protected function importUploadedResource(array $uploadInfo, PropertyMappingConfigurationInterface $configuration)
- {
- if (!GeneralUtility::makeInstance(FileNameValidator::class)->isValid($uploadInfo['name'])) {
- throw new TypeConverterException('Uploading files with PHP file extensions is not allowed!', 1399312430);
- }
-
- $allowedFileExtensions = $configuration->getConfigurationValue(UploadedFileReferenceConverter::class, self::CONFIGURATION_ALLOWED_FILE_EXTENSIONS);
-
- if ($allowedFileExtensions !== null) {
- $filePathInfo = PathUtility::pathinfo($uploadInfo['name']);
- if (!GeneralUtility::inList($allowedFileExtensions, strtolower($filePathInfo['extension']))) {
- throw new TypeConverterException('File extension is not allowed!', 1399312430);
- }
- }
-
- $extensionConfiguration = GeneralUtility::makeInstance(ExtensionConfiguration::class);
- $moduleConfiguration = $extensionConfiguration->get('userimport');
-
- if (!empty($moduleConfiguration['uploadStorageFolder'])) {
- $uploadStorageFolder = $moduleConfiguration['uploadStorageFolder'];
- } else {
- throw new \Exception('You must configure an upload folder in the Extension Manager configuration.', 1643747930);
- }
-
- $uploadFolderId = $configuration->getConfigurationValue(UploadedFileReferenceConverter::class, self::CONFIGURATION_UPLOAD_FOLDER) ?: $uploadStorageFolder;
- $defaultConflictMode = DuplicationBehavior::RENAME;
- $conflictMode = $configuration->getConfigurationValue(UploadedFileReferenceConverter::class, self::CONFIGURATION_UPLOAD_CONFLICT_MODE) ?: $defaultConflictMode;
-
- $uploadFolder = $this->resourceFactory->retrieveFileOrFolderObject($uploadFolderId);
- $uploadedFile = $uploadFolder->addUploadedFile($uploadInfo, $conflictMode);
-
- $resourcePointer = isset($uploadInfo['submittedFile']['resourcePointer']) && strpos($uploadInfo['submittedFile']['resourcePointer'], 'file:') === false
- ? $this->hashService->validateAndStripHmac($uploadInfo['submittedFile']['resourcePointer'])
- : null;
-
- $fileReferenceModel = $this->createFileReferenceFromFalFileObject($uploadedFile, $resourcePointer);
-
- return $fileReferenceModel;
- }
-
- /**
- * @param FalFileReference $falFileReference
- * @param int $resourcePointer
- * @return FileReference
- */
- protected function createFileReferenceFromFalFileReferenceObject(FalFileReference $falFileReference, $resourcePointer = null)
- {
- if ($resourcePointer === null) {
- /** @var $fileReference FileReference */
- $fileReference = $this->objectManager->get(FileReference::class);
- } else {
- $fileReference = $this->persistenceManager->getObjectByIdentifier($resourcePointer, FileReference::class, false);
- }
-
- $fileReference->setOriginalResource($falFileReference);
-
- return $fileReference;
- }
-
- public function injectResourceFactory(ResourceFactory $resourceFactory): void
- {
- $this->resourceFactory = $resourceFactory;
- }
-
- public function injectHashService(HashService $hashService): void
- {
- $this->hashService = $hashService;
- }
-
- public function injectPersistenceManager(PersistenceManager $persistenceManager): void
- {
- $this->persistenceManager = $persistenceManager;
- }
-}
diff --git a/Classes/Service/SpreadsheetService.php b/Classes/Service/SpreadsheetService.php
index 596ce7f..4d1add3 100644
--- a/Classes/Service/SpreadsheetService.php
+++ b/Classes/Service/SpreadsheetService.php
@@ -1,7 +1,5 @@
, visol digitale Dienstleistungen GmbH
*
***/
-use TYPO3\CMS\Core\Crypto\PasswordHashing\SaltedPasswordsUtility;
-use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
+
+namespace Visol\Userimport\Service;
+
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Crypto\PasswordHashing\PasswordHashFactory;
+use TYPO3\CMS\Core\Crypto\PasswordHashing\SaltedPasswordsUtility;
use TYPO3\CMS\Core\Crypto\Random;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
@@ -26,14 +27,12 @@
class SpreadsheetService implements SingletonInterface
{
-
/**
* Return the content of the spreadsheet's first worksheet
*
* @param string $fileName
- * @param int $numberOfRowsToReturn
+ * @param null|int $numberOfRowsToReturn
* @param bool $skipFirstRow
- *
* @return array
*/
public function getContent($fileName, $numberOfRowsToReturn = null, $skipFirstRow = false)
@@ -72,7 +71,6 @@ public function getContent($fileName, $numberOfRowsToReturn = null, $skipFirstRo
* @param $fileName
* @param bool $firstRowContainsFieldNames
* @param int $numberOfExamples
- *
* @return array
*/
public function getColumnLabelsAndExamples($fileName, $firstRowContainsFieldNames = false, $numberOfExamples = 5)
@@ -118,9 +116,7 @@ public function getColumnLabelsAndExamples($fileName, $firstRowContainsFieldName
/**
* Generate data from import job data and configuration
*
- * @param ImportJob $importJob
* @param bool $isPreview
- *
* @return array
*/
public function generateDataFromImportJob(ImportJob $importJob, $isPreview = false)
@@ -147,8 +143,8 @@ public function generateDataFromImportJob(ImportJob $importJob, $isPreview = fal
// Use tcaDefaultsRow if not empty
$row = empty($tcaDefaultsRow) ? [] : $tcaDefaultsRow;
foreach ($fieldMapping as $columnIndex => $fieldName) {
- $value = $worksheet->getCellByColumnAndRow(Coordinate::columnIndexFromString($columnIndex), $rowIndex)->getValue();
- $row[$fieldName] = !empty($value) ? $value : '';
+ $value = $worksheet->getCell(Coordinate::indexesFromString($columnIndex . $rowIndex))->getValue();
+ $row[$fieldName] = empty($value) ? '' : $value;
}
if (!array_filter($row)) {
@@ -158,7 +154,7 @@ public function generateDataFromImportJob(ImportJob $importJob, $isPreview = fal
$rows[$i] = $row;
// Process import options
- if ((bool)$importJob->getImportOption(ImportJob::IMPORT_OPTION_USE_EMAIL_AS_USERNAME)) {
+ if ((bool) $importJob->getImportOption(ImportJob::IMPORT_OPTION_USE_EMAIL_AS_USERNAME)) {
$rows[$i]['username'] = $rows[$i]['email'];
}
@@ -196,7 +192,7 @@ public function generateDataFromImportJob(ImportJob $importJob, $isPreview = fal
$rows[$i]['password'] = $saltedPassword;
// PID
- $rows[$i]['pid'] = (int)$importJob->getImportOption(ImportJob::IMPORT_OPTION_TARGET_FOLDER);
+ $rows[$i]['pid'] = (int) $importJob->getImportOption(ImportJob::IMPORT_OPTION_TARGET_FOLDER);
// crtime/tstamp
$rows[$i]['crdate'] = time();
@@ -205,7 +201,7 @@ public function generateDataFromImportJob(ImportJob $importJob, $isPreview = fal
// User groups
if (!empty($importJob->getImportOption(ImportJob::IMPORT_OPTION_USER_GROUPS))) {
$rows[$i]['usergroup'] = implode(',', $importJob->getImportOption(ImportJob::IMPORT_OPTION_USER_GROUPS));
- };
+ }
}
$i++;
@@ -219,7 +215,6 @@ public function generateDataFromImportJob(ImportJob $importJob, $isPreview = fal
* Respects the TSConfig applying to the given page
*
* @param $targetFolderUid
- *
* @return array
*/
protected function getTcaDefaultsRow($targetFolderUid)
@@ -248,7 +243,6 @@ protected function getTcaDefaultsRow($targetFolderUid)
/**
* @param string $fileName
- *
* @return Spreadsheet
*/
protected function getSpreadsheet($fileName)
diff --git a/Classes/Service/TcaService.php b/Classes/Service/TcaService.php
index c5f2d2c..e0e5e23 100644
--- a/Classes/Service/TcaService.php
+++ b/Classes/Service/TcaService.php
@@ -2,6 +2,13 @@
namespace Visol\Userimport\Service;
+use TYPO3\CMS\Backend\Utility\BackendUtility;
+use TYPO3\CMS\Core\Database\Connection;
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use TYPO3\CMS\Core\Database\Query\QueryBuilder;
+use TYPO3\CMS\Core\SingletonInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+
/***
*
* This file is part of the "Frontend User Import" Extension for TYPO3 CMS.
@@ -13,15 +20,12 @@
*
***/
-use TYPO3\CMS\Backend\Utility\BackendUtility;
-use TYPO3\CMS\Core\Database\ConnectionPool;
-use TYPO3\CMS\Core\Database\Query\QueryBuilder;
-use TYPO3\CMS\Core\Database\QueryGenerator;
-use TYPO3\CMS\Core\SingletonInterface;
-use TYPO3\CMS\Core\Utility\GeneralUtility;
-
class TcaService implements SingletonInterface
{
+ public function __construct(
+ private readonly ConnectionPool $connectionPool,
+ ) {
+ }
/**
* Return all pages of type folder containing frontend users
@@ -38,11 +42,8 @@ public function getFrontendUserFolders()
->from('pages')
->where(
$queryBuilder->expr()->eq('doktype', 254),
- $queryBuilder->expr()->eq('module', $queryBuilder->createNamedParameter('fe_users', \PDO::PARAM_STR))
- )
- ->addOrderBy('uid', 'DESC')
- ->execute()
- ->fetchAll();
+ $queryBuilder->expr()->eq('module', $queryBuilder->createNamedParameter('fe_users', Connection::PARAM_STR))
+ )->addOrderBy('uid', 'DESC')->executeQuery()->fetchAllAssociative();
$folders = [];
@@ -54,7 +55,7 @@ public function getFrontendUserFolders()
}
$folders[] = [
'uid' => $page['uid'],
- 'title' => !empty($title) ? $title : $page['title']
+ 'title' => $title === '' || $title === '0' ? $page['title'] : $title,
];
}
@@ -70,12 +71,8 @@ public function getFrontendUserGroups()
{
/** @var QueryBuilder $queryBuilder */
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('fe_groups');
- $result = $queryBuilder
- ->select('uid', 'title')
- ->from('fe_groups')
- ->execute()
- ->fetchAll();
- return $result;
+ return $queryBuilder
+ ->select('uid', 'title')->from('fe_groups')->executeQuery()->fetchAllAssociative();
}
/**
@@ -89,16 +86,16 @@ public function getFrontendUserTableUniqueFieldNames()
return [
[
'value' => 'name',
- 'label' => 'name'
+ 'label' => 'name',
],
[
'value' => 'username',
- 'label' => 'username'
+ 'label' => 'username',
],
[
'value' => 'email',
- 'label' => 'email'
- ]
+ 'label' => 'email',
+ ],
];
}
@@ -109,22 +106,25 @@ public function getFrontendUserTableUniqueFieldNames()
*/
public function getFrontendUserTableFieldNames()
{
- /** @var QueryGenerator $queryGenerator */
- $queryGenerator = GeneralUtility::makeInstance(QueryGenerator::class);
- $queryGenerator->table = 'fe_users';
+ $queryBuilder = $this->connectionPool->getQueryBuilderForTable('fe_users');
+ $result = $queryBuilder
+ ->select('*')
+ ->from('fe_users')
+ ->executeQuery()->fetchAssociative();
+
+ $fieldList = array_keys($result);
$fieldsToExclude = ['image', 'TSconfig', 'lastlogin', 'felogin_forgotHash', 'uid', 'pid', 'deleted', 'tstamp', 'crdate', 'cruser_id'];
- $fieldList = $queryGenerator->makeFieldList();
$fieldArray = [];
- foreach (GeneralUtility::trimExplode(',', $fieldList) as $fieldName) {
+ foreach ($fieldList as $fieldName) {
if (in_array($fieldName, $fieldsToExclude)) {
// Ignore senseless or dangerous fields
continue;
}
$fieldArray[] = [
'label' => $fieldName,
- 'value' => $fieldName
+ 'value' => $fieldName,
];
}
return $fieldArray;
diff --git a/Classes/Service/UserImportService.php b/Classes/Service/UserImportService.php
index 84d3dd0..bb4cc90 100644
--- a/Classes/Service/UserImportService.php
+++ b/Classes/Service/UserImportService.php
@@ -21,13 +21,9 @@
***/
class UserImportService implements SingletonInterface
{
-
/**
* Imports/updates all given rows as fe_user records respecting the options in the ImportJob
*
- * @param array $rowsToImport
- * @param ImportJob $importJob
- *
* @return array
*/
public function performImport(ImportJob $importJob, array $rowsToImport = [])
@@ -37,7 +33,7 @@ public function performImport(ImportJob $importJob, array $rowsToImport = [])
/** @var QueryBuilder $queryBuilder */
$queryBuilder = $connectionPool->getQueryBuilderForTable('fe_users');
- $updateExisting = (bool)$importJob->getImportOption(ImportJob::IMPORT_OPTION_UPDATE_EXISTING_USERS);
+ $updateExisting = (bool) $importJob->getImportOption(ImportJob::IMPORT_OPTION_UPDATE_EXISTING_USERS);
$updatedRecords = 0;
$insertedRecords = 0;
@@ -50,7 +46,7 @@ public function performImport(ImportJob $importJob, array $rowsToImport = [])
$rowForLog = LogUtility::formatRowForImportLog($row);
if ($updateExisting) {
- $targetFolder = (int)$importJob->getImportOption(ImportJob::IMPORT_OPTION_TARGET_FOLDER);
+ $targetFolder = (int) $importJob->getImportOption(ImportJob::IMPORT_OPTION_TARGET_FOLDER);
$updateExistingUniqueField = $importJob->getImportOption(ImportJob::IMPORT_OPTION_UPDATE_EXISTING_USERS_UNIQUE_FIELD);
$existing = $feUsersConnection->count(
'uid',
@@ -59,7 +55,7 @@ public function performImport(ImportJob $importJob, array $rowsToImport = [])
$updateExistingUniqueField => $row[$updateExistingUniqueField],
'pid' => $targetFolder,
'deleted' => 0,
- 'disable' => 0
+ 'disable' => 0,
]
);
if ($existing === 1) {
@@ -70,61 +66,55 @@ public function performImport(ImportJob $importJob, array $rowsToImport = [])
$updateExistingUniqueField => $row[$updateExistingUniqueField],
'pid' => $targetFolder,
'deleted' => 0,
- 'disable' => 0
+ 'disable' => 0,
]
);
-
if ($affectedRecords === 1) {
$log[] = [
'action' => 'update.success',
- 'row' => $rowForLog
+ 'row' => $rowForLog,
];
} else {
// Error case
$log[] = [
'action' => 'update.fail',
- 'row' => $rowForLog
+ 'row' => $rowForLog,
];
}
-
$updatedRecords += $affectedRecords;
-
continue;
- } elseif ($existing > 1) {
+ }
+ if ($existing > 1) {
// More than one record, fail
$log[] = [
'action' => 'update.moreThanOneRecordFound',
- 'row' => $rowForLog
+ 'row' => $rowForLog,
];
}
}
// Must be newly imported
$affectedRecords = $queryBuilder
- ->insert('fe_users', null)
- ->values($row)
- ->execute();
+ ->insert('fe_users')->values($row)->executeStatement();
if ($affectedRecords < 1) {
// Error case
$log[] = [
'action' => 'insert.fail',
- 'row' => $rowForLog
+ 'row' => $rowForLog,
];
} else {
$log[] = [
'action' => 'insert.success',
- 'row' => $rowForLog
+ 'row' => $rowForLog,
];
}
$insertedRecords += $affectedRecords;
-
- continue;
}
return [
'insertedRecords' => $insertedRecords,
'updatedRecords' => $updatedRecords,
- 'log' => $log
+ 'log' => $log,
];
}
}
diff --git a/Classes/Utility/LogUtility.php b/Classes/Utility/LogUtility.php
index 4650300..c02fc75 100644
--- a/Classes/Utility/LogUtility.php
+++ b/Classes/Utility/LogUtility.php
@@ -1,35 +1,32 @@
-, visol digitale Dienstleistungen GmbH
- *
- ***/
-
-
-class LogUtility
-{
-
- /**
- * @param $row
- *
- * @return string
- */
- public static function formatRowForImportLog($row)
- {
- // Unset values not to be displayed in the log
- unset($row['password']);
- unset($row['crdate']);
- unset($row['tstamp']);
- unset($row['usergroup']);
-
- return implode(" | ", $row);
- }
-}
+, visol digitale Dienstleistungen GmbH
+ *
+ ***/
+
+class LogUtility
+{
+ /**
+ * @param $row
+ * @return string
+ */
+ public static function formatRowForImportLog($row)
+ {
+ // Unset values not to be displayed in the log
+ unset($row['password']);
+ unset($row['crdate']);
+ unset($row['tstamp']);
+ unset($row['usergroup']);
+
+ return implode(" | ", $row);
+ }
+}
diff --git a/Classes/ViewHelpers/Form/UploadViewHelper.php b/Classes/ViewHelpers/Form/UploadViewHelper.php
deleted file mode 100644
index ba501f5..0000000
--- a/Classes/ViewHelpers/Form/UploadViewHelper.php
+++ /dev/null
@@ -1,112 +0,0 @@
-
- * All rights reserved
- *
- * This script is part of the TYPO3 project. The TYPO3 project is
- * free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * The GNU General Public License can be found at
- * http://www.gnu.org/copyleft/gpl.html.
- * A copy is found in the text file GPL.txt and important notices to the license
- * from the author is found in LICENSE.txt distributed with these scripts.
- *
- *
- * This script is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * This copyright notice MUST APPEAR in all copies of the script!
- ***************************************************************/
-/**
- * Class UploadViewHelper
- */
-class UploadViewHelper extends \TYPO3\CMS\Fluid\ViewHelpers\Form\UploadViewHelper
-{
- /**
- * @var HashService
- */
- protected $hashService;
-
- /**
- * @var PropertyMapper
- */
- protected $propertyMapper;
-
- /**
- * Render the upload field including possible resource pointer
- *
- * @return string
- * @api
- */
- public function render()
- {
- $output = '';
-
- $resource = $this->getUploadedResource();
- if ($resource !== null) {
- $resourcePointerIdAttribute = '';
- if ($this->hasArgument('id')) {
- $resourcePointerIdAttribute = ' id="' . htmlspecialchars($this->arguments['id']) . '-file-reference"';
- }
- $resourcePointerValue = $resource->getUid();
- if ($resourcePointerValue === null) {
- // Newly created file reference which is not persisted yet.
- // Use the file UID instead, but prefix it with "file:" to communicate this to the type converter
- $resourcePointerValue = 'file:' . $resource->getOriginalResource()->getOriginalFile()->getUid();
- }
- $output .= '';
-
- $this->templateVariableContainer->add('resource', $resource);
- $output .= $this->renderChildren();
- $this->templateVariableContainer->remove('resource');
- }
-
- $output .= parent::render();
- return $output;
- }
-
- /**
- * Return a previously uploaded resource.
- * Return NULL if errors occurred during property mapping for this property.
- *
- * @return FileReference
- */
- protected function getUploadedResource()
- {
- if ($this->getMappingResultsForProperty()->hasErrors()) {
- return null;
- }
- if (is_callable([$this, 'getValueAttribute'])) {
- $resource = $this->getValueAttribute();
- } else {
- // @deprecated since 7.6 will be removed once 6.2 support is removed
- $resource = $this->getValue(false);
- }
- if ($resource instanceof FileReference) {
- return $resource;
- }
- return $this->propertyMapper->convert($resource, 'TYPO3\\CMS\\Extbase\\Domain\\Model\\FileReference');
- }
-
- public function injectHashService(HashService $hashService): void
- {
- $this->hashService = $hashService;
- }
-
- public function injectPropertyMapper(PropertyMapper $propertyMapper): void
- {
- $this->propertyMapper = $propertyMapper;
- }
-}
diff --git a/Configuration/Backend/Modules.php b/Configuration/Backend/Modules.php
new file mode 100644
index 0000000..4168f20
--- /dev/null
+++ b/Configuration/Backend/Modules.php
@@ -0,0 +1,22 @@
+ [
+ 'parent' => 'web',
+ 'access' => 'user',
+ 'labels' => 'LLL:EXT:userimport/Resources/Private/Language/locallang_userimport.xlf',
+ 'extensionName' => 'Userimport',
+ 'controllerActions' => [
+ UserimportController::class => [
+ 'main',
+ 'upload',
+ 'options',
+ 'fieldMapping',
+ 'importPreview',
+ 'performImport',
+ ],
+ ],
+ ],
+];
diff --git a/Configuration/Icons.php b/Configuration/Icons.php
new file mode 100644
index 0000000..e4801b7
--- /dev/null
+++ b/Configuration/Icons.php
@@ -0,0 +1,16 @@
+ [
+ 'provider' => SvgIconProvider::class,
+ 'source' => $extIconPath . 'tx_userimport_domain_model_importjob.svg',
+ ],
+];
diff --git a/Configuration/TCA/Overrides/sys_template.php b/Configuration/TCA/Overrides/sys_template.php
new file mode 100644
index 0000000..9b5ae6e
--- /dev/null
+++ b/Configuration/TCA/Overrides/sys_template.php
@@ -0,0 +1,3 @@
+ [
'hideTable' => true,
- 'title' => 'LLL:EXT:userimport/Resources/Private/Language/locallang_db.xlf:tx_userimport_domain_model_importjob',
+ 'title' => $ll . 'tx_userimport_domain_model_importjob',
'label' => 'title',
'tstamp' => 'tstamp',
'crdate' => 'crdate',
- 'cruser_id' => 'cruser_id',
'delete' => 'deleted',
'enablecolumns' => [
- 'disabled' => 'hidden'
+ 'disabled' => 'hidden',
],
'searchFields' => 'title,style,cached_votes,cached_rank,image,votes,',
- 'iconfile' => 'EXT:userimport/Resources/Public/Icons/tx_userimport_domain_model_importjob.gif',
+ 'typeicon_classes' => [
+ 'default' => 'tx_userimport-importjob',
+ ],
],
'types' => [
'1' => ['showitem' => 'hidden,--palette--;;1,file,import_options,field_mapping'],
@@ -38,22 +37,20 @@
'file' => [
'exclude' => true,
'label' => $ll . 'tx_userimport_domain_model_importjob.file',
- 'config' => ExtensionManagementUtility::getFileFieldTCAConfig(
- 'file',
- [
- 'appearance' => [
- 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference'
- ],
- 'foreign_match_fields' => [
- 'fieldname' => 'file',
- 'tablenames' => 'tx_userimport_domain_model_importjob',
- 'table_local' => 'sys_file',
- ],
- 'minitems' => 1,
- 'maxitems' => 1,
+ 'config' => [
+ // !!! Watch out for fieldName different from columnName
+ 'type' => 'file',
+ 'allowed' => 'xlsx,csv',
+ 'appearance' => [
+ 'createNewRelationLinkTitle' => 'LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:images.addFileReference',
+ ],
+ 'foreign_match_fields' => [
+ 'fieldname' => 'file',
+ 'tablenames' => 'tx_userimport_domain_model_importjob',
],
- 'xlsx,csv'
- ),
+ 'minitems' => 1,
+ 'maxitems' => 1,
+ ],
],
'import_options' => [
'exclude' => true,
@@ -62,7 +59,7 @@
'type' => 'text',
'cols' => 60,
'rows' => 5,
- ]
+ ],
],
'field_mapping' => [
'exclude' => true,
@@ -71,7 +68,7 @@
'type' => 'text',
'cols' => 60,
'rows' => 5,
- ]
+ ],
],
],
];
diff --git a/Configuration/TypoScript/constants.typoscript b/Configuration/TypoScript/constants.typoscript
index 5ba3012..967b6c1 100644
--- a/Configuration/TypoScript/constants.typoscript
+++ b/Configuration/TypoScript/constants.typoscript
@@ -1,15 +1,15 @@
module.tx_userimport_userimport {
- view {
- # cat=module.tx_userimport_userimport/file; type=string; label=Path to template root (BE)
- templateRootPath = EXT:userimport/Resources/Private/Backend/Templates/
- # cat=module.tx_userimport_userimport/file; type=string; label=Path to template partials (BE)
- partialRootPath = EXT:userimport/Resources/Private/Backend/Partials/
- # cat=module.tx_userimport_userimport/file; type=string; label=Path to template layouts (BE)
- layoutRootPath = EXT:userimport/Resources/Private/Backend/Layouts/
- }
- persistence {
- # cat=module.tx_userimport_userimport//a; type=string; label=Default storage PID
- storagePid =
- }
+ view {
+ # cat=module.tx_userimport_userimport/file; type=string; label=Path to template root (BE)
+ templateRootPath = EXT:userimport/Resources/Private/Backend/Templates/
+ # cat=module.tx_userimport_userimport/file; type=string; label=Path to template partials (BE)
+ partialRootPath = EXT:userimport/Resources/Private/Backend/Partials/
+ # cat=module.tx_userimport_userimport/file; type=string; label=Path to template layouts (BE)
+ layoutRootPath = EXT:userimport/Resources/Private/Backend/Layouts/
+ }
+ persistence {
+ # cat=module.tx_userimport_userimport//a; type=string; label=Default storage PID
+ storagePid =
+ }
}
diff --git a/Configuration/TypoScript/setup.typoscript b/Configuration/TypoScript/setup.typoscript
index 135c432..1ea8537 100644
--- a/Configuration/TypoScript/setup.typoscript
+++ b/Configuration/TypoScript/setup.typoscript
@@ -1,15 +1,15 @@
# Module configuration
module.tx_userimport_web_userimportuserimport {
- persistence {
- storagePid = {$module.tx_userimport_userimport.persistence.storagePid}
- }
- view {
- templateRootPaths.0 = EXT:userimport/Resources/Private/Backend/Templates/
- templateRootPaths.1 = {$module.tx_userimport_userimport.view.templateRootPath}
- partialRootPaths.0 = EXT:userimport/Resources/Private/Backend/Partials/
- partialRootPaths.1 = {$module.tx_userimport_userimport.view.partialRootPath}
- layoutRootPaths.0 = EXT:userimport/Resources/Private/Backend/Layouts/
- layoutRootPaths.1 = {$module.tx_userimport_userimport.view.layoutRootPath}
- }
+ persistence {
+ storagePid = {$module.tx_userimport_userimport.persistence.storagePid}
+ }
+ view {
+ templateRootPaths.0 = EXT:userimport/Resources/Private/Backend/Templates/
+ templateRootPaths.1 = {$module.tx_userimport_userimport.view.templateRootPath}
+ partialRootPaths.0 = EXT:userimport/Resources/Private/Backend/Partials/
+ partialRootPaths.1 = {$module.tx_userimport_userimport.view.partialRootPath}
+ layoutRootPaths.0 = EXT:userimport/Resources/Private/Backend/Layouts/
+ layoutRootPaths.1 = {$module.tx_userimport_userimport.view.layoutRootPath}
+ }
}
diff --git a/README.md b/README.md
index 31ac300..27bfa18 100644
--- a/README.md
+++ b/README.md
@@ -6,10 +6,11 @@ Make sure to upload your files containing user data to a secure folder!
This package is currently maintained for the following versions:
-| TYPO3 Version | Package Version | Branch | Maintained |
-|-----------------------|-----------------|---------|---------------|
-| TYPO3 11.5.x | 2.x | master | Yes |
-| TYPO3 8.7.x | 1.x | - | No |
+| TYPO3 Version | Package Version | Branch | Maintained |
+|---------------|-----------------|--------|------------|
+| TYPO3 13.4.x | 3.x | master | Yes |
+| TYPO3 11.5.x | 2.x | v11 | No |
+| TYPO3 8.7.x | 1.x | - | No |
### Setup a protected storage folder
diff --git a/Resources/Private/Backend/Layouts/Default.html b/Resources/Private/Backend/Layouts/Default.html
deleted file mode 100644
index 8174273..0000000
--- a/Resources/Private/Backend/Layouts/Default.html
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{f:translate(key: 'moduleTitle')}
-
-
-
-
-
-
diff --git a/Resources/Private/Language/de.locallang.xlf b/Resources/Private/Language/de.locallang.xlf
index 0869286..b8adf90 100644
--- a/Resources/Private/Language/de.locallang.xlf
+++ b/Resources/Private/Language/de.locallang.xlf
@@ -1,141 +1,186 @@
-
-
-
+
+
+
-
+
+ User ImportBenutzer-Import
-
+
+ Select file
+ Datei auswählen
+
+
+ Upload folderUpload-Verzeichnis
-
+
+ You must configure an upload folder in the Extension Manager configuration. See README for more details.Bitte Upload-Verzeichnis im Extension Manager konfigurieren. Siehe README für mehr Details.
-
+
+ (See extension manager)(siehe Extension Manager)
-
- Datei auswählen
-
-
+
+ Allowed file extensions are: xlsx, csvErlaubte Datei-Typen sind: xlsx, csv
-
+
+ UploadHochladen
-
+
+ Preview (File: %s)Vorschau (Datei: %s)
-
+
+ Only the first 5 rows are displayed.Es werden nur die ersten 5 Zeilen dargestellt.
-
+
+ Import optionsImport-Optionen
-
+
+ First row contains field namesErste Zeile enthält Spaltennamen
-
+
+ If selected, the first row is ignored when importing.Falls gewählt, wird die erste Zeile beim Import ignoriert.
-
+
+ Target folderZiel-Ordner
-
+
+ User groupsBenutzergruppen
-
+
+ The selected user groups are assigned to the created users.Die ausgewählten Benutzergruppen werden den importierten Benutzern zugewiesen.
-
+
+ Use e-mail column as usernameE-Mail-Spalte als Benutzername verwenden
-
+
+ The value of the e-mail column is used as username.Der Wert der Spalte E-Mail wird als Benutzername verwendet.
-
+
+ Generate passwordKennwort generieren
-
+
+ A random password is generated for imported users. Alternatively, you can import a password column.Ein zufälliges Kennwort wird für importierte Benutzer generiert. Alternativ kann das Kennwort auch importiert werden.
-
+
+ Update existing usersBestehende Benutzer aktualisieren
-
+
+ Update existing users instead of adding new users.Anstatt neue Benutzer hinzuzufügen, werden bestehende Benutzer aktualisiert.
-
+
+ Field to check for updateFeld für Aktualisierungs-Prüfung
-
+
+ If a record having the same value in the selected field is found, it is updated instead of inserted.Ein Benutzer wird dann aktualisiert, wenn bereits ein Benutzer mit demselben Wert im angegebenen Feld existiert.
-
+
+ ContinueWeiter
-
+
+ Field MappingFeldzuweisung
-
+
+ ColumnSpalte
-
+
+ Field mappingFeldzuweisung
-
+
+ ExamplesBeispiele
-
+
+ Map to field...Zuweisung zu Feld...
-
+
+ The username field must be mapped.Das Feld username muss zugewiesen werden.
-
+
+ The e-mail field must be mapped because the username is generated from it.Das Feld email muss zugewiesen werden, da daraus der Benutzername generiert wird.
-
+
+ ContinueWeiter
-
+
+ Import PreviewImport-Vorschau
-
+
+ Import dataDaten importieren
-
+
+ Import logImport-Log
-
+
+ %s rows in source file.%s Zeilen in der hochgeladenen Datei.
-
+
+ %s records inserted.%s Datensätze importiert.
-
+
+ %s records updated.%s Datensätze aktualisiert.
-
+
+ File extension not allowed. Allowed file types: xlsx, csvDatei-Typ nicht erlaubt. Erlaubte Datei-Typen: xlsx, csv
-
+
+ Update failed for: %sAktualisierung fehlgeschlagen für: %s
-
+
+ Update failed for: %s (Reason: More than one identical record to update was found.)Aktualisierung fehlgeschlagen für: %s (Grund: Mehr als ein identischer Datensatz für die Aktualisierung gefunden.)
-
+
+ Update successful for: %sAktualisierung erfolgreich für: %s
-
+
+ Insert failed for: %sImport fehlgeschlagen für: %s
-
+
+ Insert successful for: %sImport erfolgreich für: %s
-
+
+ Go to target folderZum Zielordner wechseln
diff --git a/Resources/Private/Language/de.locallang_userimport.xlf b/Resources/Private/Language/de.locallang_userimport.xlf
index 3f34b7d..188ccf7 100644
--- a/Resources/Private/Language/de.locallang_userimport.xlf
+++ b/Resources/Private/Language/de.locallang_userimport.xlf
@@ -1,12 +1,14 @@
-
-
-
+
+
+
-
+
+ User ImportBenutzer-Import
-
+
+ Import Excel or CSV data as Frontend UsersImport von Excel- oder CSV-Dateien als Website-Benutzer
diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf
index 3134d3d..82ef3dc 100644
--- a/Resources/Private/Language/locallang.xlf
+++ b/Resources/Private/Language/locallang.xlf
@@ -1,141 +1,141 @@
-
-
-
+
+
+
-
+ User Import
-
+ Select file
-
+ Upload folder
-
+ You must configure an upload folder in the Extension Manager configuration. See README for more details.
-
- (See extension manager)
+
+ (See extension manager)
-
+ Allowed file extensions are: xlsx, csv
-
+ Upload
-
+ Preview (File: %s)
-
+ Only the first 5 rows are displayed.
-
+ Import options
-
+ First row contains field names
-
+ If selected, the first row is ignored when importing.
-
+ Target folder
-
+ User groups
-
+ The selected user groups are assigned to the created users.
-
+ Use e-mail column as username
-
+ The value of the e-mail column is used as username.
-
+ Generate password
-
+ A random password is generated for imported users. Alternatively, you can import a password column.
-
+ Update existing users
-
+ Update existing users instead of adding new users.
-
+ Field to check for update
-
+ If a record having the same value in the selected field is found, it is updated instead of inserted.
-
+ Continue
-
+ Field Mapping
-
+ Column
-
+ Field mapping
-
+ Examples
-
+ Map to field...
-
+ The username field must be mapped.
-
+ The e-mail field must be mapped because the username is generated from it.
-
+ Continue
-
+ Import Preview
-
+ Import data
-
+ Import log
-
+ %s rows in source file.
-
+ %s records inserted.
-
+ %s records updated.
-
+ File extension not allowed. Allowed file types: xlsx, csv
-
+ Update failed for: %s
-
+ Update failed for: %s (Reason: More than one identical record to update was found.)
-
+ Update successful for: %s
-
+ Insert failed for: %s
-
+ Insert successful for: %s
-
+ Go to target folder
diff --git a/Resources/Private/Language/locallang_db.xlf b/Resources/Private/Language/locallang_db.xlf
new file mode 100644
index 0000000..6cdc278
--- /dev/null
+++ b/Resources/Private/Language/locallang_db.xlf
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ User Import
+
+
+ File
+
+
+ Import options
+
+
+ Field mapping
+
+
+
+
diff --git a/Resources/Private/Language/locallang_userimport.xlf b/Resources/Private/Language/locallang_userimport.xlf
index 4880c0a..6bc36c8 100644
--- a/Resources/Private/Language/locallang_userimport.xlf
+++ b/Resources/Private/Language/locallang_userimport.xlf
@@ -1,14 +1,14 @@
-
-
-
+
+
+
-
+ User Import
-
+ Import Excel or CSV data as Frontend Users
-
\ No newline at end of file
+
diff --git a/Resources/Private/Layouts/Default.html b/Resources/Private/Layouts/Default.html
new file mode 100644
index 0000000..8e94ebc
--- /dev/null
+++ b/Resources/Private/Layouts/Default.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
{f:translate(key: 'moduleTitle')}
+
+
+
+
+
+
diff --git a/Resources/Private/Backend/Partials/ClientSideValidationResults.html b/Resources/Private/Partials/ClientSideValidationResults.html
similarity index 100%
rename from Resources/Private/Backend/Partials/ClientSideValidationResults.html
rename to Resources/Private/Partials/ClientSideValidationResults.html
diff --git a/Resources/Private/Backend/Partials/FieldMapping.html b/Resources/Private/Partials/FieldMapping.html
similarity index 100%
rename from Resources/Private/Backend/Partials/FieldMapping.html
rename to Resources/Private/Partials/FieldMapping.html
diff --git a/Resources/Private/Backend/Partials/ImportOptions.html b/Resources/Private/Partials/ImportOptions.html
similarity index 100%
rename from Resources/Private/Backend/Partials/ImportOptions.html
rename to Resources/Private/Partials/ImportOptions.html
diff --git a/Resources/Private/Backend/Partials/ImportPreview.html b/Resources/Private/Partials/ImportPreview.html
similarity index 100%
rename from Resources/Private/Backend/Partials/ImportPreview.html
rename to Resources/Private/Partials/ImportPreview.html
diff --git a/Resources/Private/Backend/Partials/SpreadsheetPreview.html b/Resources/Private/Partials/SpreadsheetPreview.html
similarity index 100%
rename from Resources/Private/Backend/Partials/SpreadsheetPreview.html
rename to Resources/Private/Partials/SpreadsheetPreview.html
diff --git a/Resources/Private/Backend/Partials/ValidationResults.html b/Resources/Private/Partials/ValidationResults.html
similarity index 100%
rename from Resources/Private/Backend/Partials/ValidationResults.html
rename to Resources/Private/Partials/ValidationResults.html
diff --git a/Resources/Private/Backend/Templates/Userimport/FieldMapping.html b/Resources/Private/Templates/Default/Userimport/FieldMapping.html
similarity index 100%
rename from Resources/Private/Backend/Templates/Userimport/FieldMapping.html
rename to Resources/Private/Templates/Default/Userimport/FieldMapping.html
diff --git a/Resources/Private/Backend/Templates/Userimport/ImportPreview.html b/Resources/Private/Templates/Default/Userimport/ImportPreview.html
similarity index 100%
rename from Resources/Private/Backend/Templates/Userimport/ImportPreview.html
rename to Resources/Private/Templates/Default/Userimport/ImportPreview.html
diff --git a/Resources/Private/Backend/Templates/Userimport/Main.html b/Resources/Private/Templates/Default/Userimport/Main.html
similarity index 84%
rename from Resources/Private/Backend/Templates/Userimport/Main.html
rename to Resources/Private/Templates/Default/Userimport/Main.html
index 43ff6f1..f1a193c 100644
--- a/Resources/Private/Backend/Templates/Userimport/Main.html
+++ b/Resources/Private/Templates/Default/Userimport/Main.html
@@ -1,5 +1,3 @@
-{namespace userimport=Visol\Userimport\ViewHelpers}
-
@@ -11,7 +9,7 @@