From b8fdafc9de87862ac9aebeb480aa79182ad3c032 Mon Sep 17 00:00:00 2001
From: Stefan Galinski <stefan@sgalinski.de>
Date: Wed, 6 Sep 2023 19:43:40 +0200
Subject: [PATCH] [FEATURE] Heavy Cleanup, Fix Module Icon Rendering,
 Configurable JobApplication Path

---
 Classes/Controller/Ajax/UploadController.php | 23 ++++--
 Classes/Controller/JoblistController.php     | 22 ++++-
 Configuration/Icons.php                      | 13 ++-
 README.md                                    | 30 +++----
 Resources/Private/Language/de.locallang.xlf  | 68 ++++++++--------
 Resources/Private/Language/locallang.xlf     | 85 ++++++++++----------
 Resources/Private/Language/zh.locallang.xlf  |  4 +-
 UPGRADE.md                                   | 19 +++--
 composer.json                                |  2 +-
 ext_conf_template.txt                        |  2 +
 ext_localconf.php                            | 26 ------
 ext_tables.php                               | 27 +------
 ext_tables.sql                               |  2 +-
 13 files changed, 160 insertions(+), 163 deletions(-)
 create mode 100644 ext_conf_template.txt

diff --git a/Classes/Controller/Ajax/UploadController.php b/Classes/Controller/Ajax/UploadController.php
index ccb0baf6..86508a06 100644
--- a/Classes/Controller/Ajax/UploadController.php
+++ b/Classes/Controller/Ajax/UploadController.php
@@ -38,9 +38,10 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
  * Uploads for the applicationForm
  */
 class UploadController extends AbstractAjaxController {
-	public const JOB_APPLICATION_FOLDER = 'JobApplication';
 	public const JOB_APPLICATION_TEMP_FOLDER = 'temp';
 
+	public $jobFolderPath = 'JobApplication';
+
 	/**
 	 * @var FileAndFolderService
 	 */
@@ -54,6 +55,18 @@ class UploadController extends AbstractAjaxController {
 		$this->fileAndFolderService = $fileAndFolderService;
 	}
 
+	/**
+	 * Initializes the class
+	 */
+	public function __construct() {
+		$extensionConfiguration = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sg_jobs'] ?? [];
+		if (isset($extensionConfiguration['jobFolderPath']) && $extensionConfiguration['jobFolderPath'] !== '') {
+			$this->jobFolderPath = $extensionConfiguration['jobFolderPath'];
+		} else {
+			$this->jobFolderPath = 'JobApplication';
+		}
+	}
+
 	/**
 	 * Returns the calculated max file size.
 	 *
@@ -128,10 +141,10 @@ class UploadController extends AbstractAjaxController {
 				)
 			);
 			// if the job application folder is not existing, create it
-			if (!$storage->hasFolder(self::JOB_APPLICATION_FOLDER)) {
-				$folder = $storage->createFolder(self::JOB_APPLICATION_FOLDER);
+			if (!$storage->hasFolder($this->jobFolderPath)) {
+				$folder = $storage->createFolder($this->jobFolderPath);
 			} else {
-				$folder = $storage->getFolder(self::JOB_APPLICATION_FOLDER);
+				$folder = $storage->getFolder($this->jobFolderPath);
 			}
 			// if temp folder is not existing, create one
 			if ($folder && !$storage->hasFolderInFolder(self::JOB_APPLICATION_TEMP_FOLDER, $folder)) {
@@ -139,7 +152,7 @@ class UploadController extends AbstractAjaxController {
 			}
 
 			$filePath = Environment::getPublicPath() . '/fileadmin/' .
-				self::JOB_APPLICATION_FOLDER . DIRECTORY_SEPARATOR .
+				$this->jobFolderPath . DIRECTORY_SEPARATOR .
 				self::JOB_APPLICATION_TEMP_FOLDER . DIRECTORY_SEPARATOR . $fileName;
 			$tempFilePath = $firstFile['tmp_name'];
 			$success = GeneralUtility::upload_copy_move($tempFilePath, $filePath);
diff --git a/Classes/Controller/JoblistController.php b/Classes/Controller/JoblistController.php
index a85531da..e20a3a60 100644
--- a/Classes/Controller/JoblistController.php
+++ b/Classes/Controller/JoblistController.php
@@ -112,6 +112,8 @@ class JoblistController extends ActionController {
 	 */
 	protected $fileAndFolderService;
 
+	public $jobFolderPath = 'JobApplication';
+
 	/**
 	 * Inject the CompanyRepository
 	 *
@@ -166,6 +168,18 @@ class JoblistController extends ActionController {
 		$this->jobRepository = $jobRepository;
 	}
 
+	/**
+	 * Initializes the class
+	 */
+	public function __construct() {
+		$extensionConfiguration = $GLOBALS['TYPO3_CONF_VARS']['EXTENSIONS']['sg_jobs'] ?? [];
+		if (isset($extensionConfiguration['jobFolderPath']) && $extensionConfiguration['jobFolderPath'] !== '') {
+			$this->jobFolderPath = $extensionConfiguration['jobFolderPath'];
+		} else {
+			$this->jobFolderPath = 'JobApplication';
+		}
+	}
+
 	/**
 	 * Initialize the indexAction to set the currentPageBrowserPage parameter
 	 */
@@ -424,7 +438,7 @@ class JoblistController extends ActionController {
 	 * @return void
 	 * @throws StopActionException
 	 */
-	protected function submitApplicationFiles(JobApplication $applicationData, string $folderName): void {
+	protected function createCsvSummary(JobApplication $applicationData, string $folderName): void {
 		$resourceFactory = $this->objectManager->get(ResourceFactory::class);
 		$newName = \date('Ymd-His') . '_' . $applicationData->getJobId() . '-' . $applicationData->getFirstName()
 			. '-' . $applicationData->getLastName();
@@ -555,7 +569,7 @@ class JoblistController extends ActionController {
 			}
 
 			$this->moveTmpFolder($folderName, $applyData);
-			$this->submitApplicationFiles($applyData, $folderName);
+			$this->createCsvSummary($applyData, $folderName);
 
 			/** @noinspection PhpParamsInspection */
 			$mailService = $this->objectManager->get(
@@ -716,13 +730,13 @@ class JoblistController extends ActionController {
 		$storage = $resourceFactory->getStorageObject(1); // fileadmin
 
 		$newFolder = $tempFolder = NULL;
-		$folder = $storage->getFolder(UploadController::JOB_APPLICATION_FOLDER);
+		$folder = $storage->getFolder($this->jobFolderPath);
 		if ($folder) {
 			$tempFolder = $storage->getFolderInFolder(UploadController::JOB_APPLICATION_TEMP_FOLDER, $folder);
 			if (!$storage->hasFolderInFolder($folderName, $folder)) {
 				$newFolder = $storage->createFolder($folderName, $folder);
 			} else {
-				$newFolder = $storage->getFolder(UploadController::JOB_APPLICATION_FOLDER . $folderName);
+				$newFolder = $storage->getFolder($this->jobFolderPath . $folderName);
 			}
 		}
 
diff --git a/Configuration/Icons.php b/Configuration/Icons.php
index cdd527ba..4fad03d9 100644
--- a/Configuration/Icons.php
+++ b/Configuration/Icons.php
@@ -1,16 +1,21 @@
 <?php
 
-$icons = [];
+/**
+ * Important! Do not return a variable named $icons, because it will result in an error.
+ * The core requires this file and then the variable names will clash.
+ * Either use a closure here, or do not call your variable $icons.
+ */
+
+$iconList = [];
 $sgJobsIcons = [
 	'extension-sg_jobs-module' => 'module-sgjobs.svg',
 ];
 
-
 foreach ($sgJobsIcons as $identifier => $path) {
-	$icons[$identifier] = [
+	$iconList[$identifier] = [
 		'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
 		'source' => 'EXT:sg_jobs/Resources/Public/Icons/' . $path,
 	];
 }
 
-return $icons;
+return $iconList;
diff --git a/README.md b/README.md
index c4b4dc5d..2a84066a 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ This extension provides job application functionality for a TYPO3 installation.
 * Applying for the created jobs
 * Applications include file uploads
 * Every application is saved conveniently in a CSV format, along with the
-uploaded files
+  uploaded files
 * Sends mail notifications when applications are submitted
 * Provides a JobTitleMapper for the TYPO3 9 route enhancers
 
@@ -92,7 +92,7 @@ To enable the job title mapping in TYPO3 9, you need to add this routeEnhancer c
 
 ```yaml
 routeEnhancers:
-	SgJobApplication:
+    SgJobApplication:
         type: Extbase
         extension: SgJobs
         plugin: JobApplication
@@ -107,19 +107,19 @@ routeEnhancers:
         aspects:
             job_title:
                 type: JobTitleMapper
-    SgJobList:
-        type: Extbase
-        extension: SgJobs
-        plugin: Joblist
-        routes:
-            -   routePath: '/job/{job_title}'
-                _controller: 'Joblist::index'
-                _arguments:
-                    job_title: jobId
-        defaultController: 'Joblist::index'
-        aspects:
-            job_title:
-                type: JobTitleMapper
+        SgJobList:
+            type: Extbase
+            extension: SgJobs
+            plugin: Joblist
+            routes:
+                -   routePath: '/job/{job_title}'
+                    _controller: 'Joblist::index'
+                    _arguments:
+                        job_title: jobId
+            defaultController: 'Joblist::index'
+            aspects:
+                job_title:
+                    type: JobTitleMapper
 ```
 
 This configuration will use the JobTitleMapper to map the job title to the jobs is.
diff --git a/Resources/Private/Language/de.locallang.xlf b/Resources/Private/Language/de.locallang.xlf
index a7d786bb..7df721fc 100644
--- a/Resources/Private/Language/de.locallang.xlf
+++ b/Resources/Private/Language/de.locallang.xlf
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
 <xliff version="1.0">
-	<file source-language="en" target-language="de" datatype="plaintext" original="messages" date="2022-06-20T17:21:14Z">
+	<file source-language="en" target-language="de" datatype="plaintext" original="messages" date="2023-09-06T17:30:53Z">
 		<header>
 			<type>module</type>
 			<description>General language labels used in frontend and backend.</description>
@@ -142,33 +142,37 @@
 				<source><![CDATA[Please select one of the following pages:]]></source>
 				<target><![CDATA[Bitte wählen Sie eine der folgenden Seiten aus:]]></target>
 			</trans-unit>
-			<trans-unit id="error.maxFileSizeMessage" approved="yes">
-				<source><![CDATA[The selected file exceeds the maximum file size]]></source>
-				<target><![CDATA[Die ausgewählte Datei ist zu groß!]]></target>
+			<trans-unit id="configuration.jobFolderPath" approved="yes">
+				<source><![CDATA[Job Folder Path (Relative to fileadmin, defaults to "JobApplication")]]></source>
+				<target><![CDATA[Job-Verzeichnis-Pfad (relativ zu fileadmin, Standardwert ist "JobApplication")]]></target>
 			</trans-unit>
-			<trans-unit id="error.NoSuchArgumentException">
+			<trans-unit id="error.NoSuchArgumentException" approved="yes">
 				<source><![CDATA[Some file could not be uploaded. Is it too large?]]></source>
 				<target><![CDATA[Einige Dateien konnten nicht hochgeladen werden. Ist eine Datei zu groß?]]></target>
 			</trans-unit>
-			<trans-unit id="error.TypeConverterException.type">
-				<source><![CDATA[File extension is not allowed!]]></source>
-				<target><![CDATA[Dateityp nicht erlaubt!]]></target>
-			</trans-unit>
-			<trans-unit id="error.TypeConverterException.missing.">
-				<source><![CDATA[Missing file %s]]></source>
+			<trans-unit id="error.TypeConverterException.missing." approved="yes">
+				<source><![CDATA[Missing some file]]></source>
 				<target><![CDATA[Eine Datei fehlt]]></target>
 			</trans-unit>
-			<trans-unit id="error.TypeConverterException.missing.coverLetter">
+			<trans-unit id="error.TypeConverterException.missing.certificate" approved="yes">
+				<source><![CDATA[Missing certificate file]]></source>
+				<target><![CDATA[Datei für Zeugnis fehlt]]></target>
+			</trans-unit>
+			<trans-unit id="error.TypeConverterException.missing.coverLetter" approved="yes">
 				<source><![CDATA[Missing coverletter file]]></source>
 				<target><![CDATA[Datei für Motivationsschreiben fehlt]]></target>
 			</trans-unit>
-			<trans-unit id="error.TypeConverterException.missing.cv">
+			<trans-unit id="error.TypeConverterException.missing.cv" approved="yes">
 				<source><![CDATA[Missing cv file]]></source>
 				<target><![CDATA[Datei für Lebenslauf fehlt]]></target>
 			</trans-unit>
-			<trans-unit id="error.TypeConverterException.missing.certificate">
-				<source><![CDATA[Missing certificate file]]></source>
-				<target><![CDATA[Datei für Zeugnis fehlt]]></target>
+			<trans-unit id="error.TypeConverterException.type" approved="yes">
+				<source><![CDATA[File extension is not allowed!]]></source>
+				<target><![CDATA[Dateityp nicht erlaubt!]]></target>
+			</trans-unit>
+			<trans-unit id="error.maxFileSizeMessage" approved="yes">
+				<source><![CDATA[The selected file exceeds the maximum file size]]></source>
+				<target><![CDATA[Die ausgewählte Datei ist zu groß!]]></target>
 			</trans-unit>
 			<trans-unit id="frontend.allVacancies" approved="yes">
 				<source><![CDATA[All vacancies]]></source>
@@ -183,9 +187,9 @@
 				<target><![CDATA[Bewerbung als]]></target>
 			</trans-unit>
 			<trans-unit id="frontend.apply.applyAsNow" approved="yes">
-                <source><![CDATA[Apply now as %s]]></source>
-                <target><![CDATA[Jetzt als %s bewerben]]></target>
-            </trans-unit>
+				<source><![CDATA[Apply now as %s]]></source>
+				<target><![CDATA[Jetzt als %s bewerben]]></target>
+			</trans-unit>
 			<trans-unit id="frontend.apply.birthDate" approved="yes">
 				<source><![CDATA[Birth date *]]></source>
 				<target><![CDATA[Geburtsdatum *]]></target>
@@ -226,10 +230,6 @@
 				<source><![CDATA[Highest achieved education *]]></source>
 				<target><![CDATA[Höchster Abschluss *]]></target>
 			</trans-unit>
-			<trans-unit id="frontend.apply.relatedJobs" approved="yes">
-				<source><![CDATA[Related Jobs]]></source>
-				<target><![CDATA[Verwandte Stellenanzeigen]]></target>
-			</trans-unit>
 			<trans-unit id="frontend.apply.email" approved="yes">
 				<source><![CDATA[Email *]]></source>
 				<target><![CDATA[E-Mail *]]></target>
@@ -298,6 +298,10 @@
 				<source><![CDATA[Recommend job]]></source>
 				<target><![CDATA[Stelle weiterempfehlen]]></target>
 			</trans-unit>
+			<trans-unit id="frontend.apply.relatedJobs" approved="yes">
+				<source><![CDATA[Related Jobs]]></source>
+				<target><![CDATA[Verwandte Stellenanzeigen]]></target>
+			</trans-unit>
 			<trans-unit id="frontend.apply.salutation" approved="yes">
 				<source><![CDATA[Salutation]]></source>
 				<target><![CDATA[Anrede]]></target>
@@ -450,14 +454,14 @@
 				<source><![CDATA[Location]]></source>
 				<target><![CDATA[Standort]]></target>
 			</trans-unit>
+			<trans-unit id="frontend.locationCountLabel" approved="yes">
+				<source><![CDATA[Locations]]></source>
+				<target><![CDATA[Standorte]]></target>
+			</trans-unit>
 			<trans-unit id="frontend.locationLabel" approved="yes">
 				<source><![CDATA[<b>Location:</b>]]></source>
 				<target><![CDATA[<b>Standort:</b>]]></target>
 			</trans-unit>
-			<trans-unit id="frontend.locationCountLabel">
-				<source><![CDATA[<b>Locations</b>]]></source>
-				<target><![CDATA[Standorte]]></target>
-			</trans-unit>
 			<trans-unit id="frontend.organisation" approved="yes">
 				<source><![CDATA[Organisation]]></source>
 				<target><![CDATA[Organisation]]></target>
@@ -470,6 +474,10 @@
 				<source><![CDATA[Qualification]]></source>
 				<target><![CDATA[Qualifikation]]></target>
 			</trans-unit>
+			<trans-unit id="frontend.remote" approved="yes">
+				<source><![CDATA[Remote]]></source>
+				<target><![CDATA[Remote]]></target>
+			</trans-unit>
 			<trans-unit id="frontend.task" approved="yes">
 				<source><![CDATA[Task]]></source>
 				<target><![CDATA[Aufgabe]]></target>
@@ -478,10 +486,6 @@
 				<source><![CDATA[Vacancies worldwide]]></source>
 				<target><![CDATA[Offene Stellen weltweit]]></target>
 			</trans-unit>
-			<trans-unit id="frontend.remote" approved="yes">
-				<source><![CDATA[Remote]]></source>
-				<target><![CDATA[Remote]]></target>
-			</trans-unit>
 		</body>
 	</file>
-</xliff>
+</xliff>
\ No newline at end of file
diff --git a/Resources/Private/Language/locallang.xlf b/Resources/Private/Language/locallang.xlf
index e5cef9a8..c9b185a1 100644
--- a/Resources/Private/Language/locallang.xlf
+++ b/Resources/Private/Language/locallang.xlf
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
 <xliff version="1.0">
-	<file source-language="en" datatype="plaintext" original="messages" date="2022-06-20T17:21:14Z">
+	<file source-language="en" datatype="plaintext" original="messages" date="2023-09-06T17:30:53Z">
 		<header>
 			<type>module</type>
 			<description>General language labels used in frontend and backend.</description>
@@ -111,26 +111,41 @@
 			<trans-unit id="backend.selectRootPage">
 				<source><![CDATA[Please select one of the following pages:]]></source>
 			</trans-unit>
-			<trans-unit id="error.maxFileSizeMessage">
-				<source><![CDATA[The selected file exceeds the maximum file size]]></source>
+			<trans-unit id="configuration.jobFolderPath">
+				<source><![CDATA[Job Folder Path (Relative to fileadmin, defaults to "JobApplication")]]></source>
 			</trans-unit>
 			<trans-unit id="error.NoSuchArgumentException">
 				<source><![CDATA[Some file could not be uploaded. Is it too large?]]></source>
 			</trans-unit>
-			<trans-unit id="error.TypeConverterException.type">
-				<source><![CDATA[File extension is not allowed!]]></source>
-			</trans-unit>
 			<trans-unit id="error.TypeConverterException.missing.">
 				<source><![CDATA[Missing some file]]></source>
 			</trans-unit>
+			<trans-unit id="error.TypeConverterException.missing.certificate">
+				<source><![CDATA[Missing certificate file]]></source>
+			</trans-unit>
 			<trans-unit id="error.TypeConverterException.missing.coverLetter">
 				<source><![CDATA[Missing coverletter file]]></source>
 			</trans-unit>
 			<trans-unit id="error.TypeConverterException.missing.cv">
 				<source><![CDATA[Missing cv file]]></source>
 			</trans-unit>
-			<trans-unit id="error.TypeConverterException.missing.certificate">
-				<source><![CDATA[Missing certificate file]]></source>
+			<trans-unit id="error.TypeConverterException.type">
+				<source><![CDATA[File extension is not allowed!]]></source>
+			</trans-unit>
+			<trans-unit id="error.maxFileSizeMessage">
+				<source><![CDATA[The selected file exceeds the maximum file size]]></source>
+			</trans-unit>
+			<trans-unit id="frontend.CancelUpload">
+				<source><![CDATA[Stop upload]]></source>
+			</trans-unit>
+			<trans-unit id="frontend.DropFiles">
+				<source><![CDATA[Drop file here]]></source>
+			</trans-unit>
+			<trans-unit id="frontend.FileType">
+				<source><![CDATA[Filetype]]></source>
+			</trans-unit>
+			<trans-unit id="frontend.RemoveFile">
+				<source><![CDATA[Remove file]]></source>
 			</trans-unit>
 			<trans-unit id="frontend.allVacancies">
 				<source><![CDATA[All vacancies]]></source>
@@ -142,8 +157,8 @@
 				<source><![CDATA[Apply as]]></source>
 			</trans-unit>
 			<trans-unit id="frontend.apply.applyAsNow">
-                <source><![CDATA[Apply now as %s]]></source>
-            </trans-unit>
+				<source><![CDATA[Apply now as %s]]></source>
+			</trans-unit>
 			<trans-unit id="frontend.apply.birthDate">
 				<source><![CDATA[Birth date *]]></source>
 			</trans-unit>
@@ -225,6 +240,9 @@
 			<trans-unit id="frontend.apply.recommend">
 				<source><![CDATA[Recommend job]]></source>
 			</trans-unit>
+			<trans-unit id="frontend.apply.relatedJobs">
+				<source><![CDATA[Related Jobs]]></source>
+			</trans-unit>
 			<trans-unit id="frontend.apply.salutation">
 				<source><![CDATA[Salutation]]></source>
 			</trans-unit>
@@ -234,9 +252,6 @@
 			<trans-unit id="frontend.apply.thank_you">
 				<source><![CDATA[Thank you for your application!]]></source>
 			</trans-unit>
-			<trans-unit id="frontend.apply.relatedJobs">
-				<source><![CDATA[Related Jobs]]></source>
-			</trans-unit>
 			<trans-unit id="frontend.apply.title">
 				<source><![CDATA[Job title *]]></source>
 			</trans-unit>
@@ -258,6 +273,15 @@
 			<trans-unit id="frontend.attachment">
 				<source><![CDATA[Download job advertisement]]></source>
 			</trans-unit>
+			<trans-unit id="frontend.certificateUplodadButton">
+				<source><![CDATA[Upload certificate]]></source>
+			</trans-unit>
+			<trans-unit id="frontend.coverLetterUplodadButton">
+				<source><![CDATA[Upload coverletter]]></source>
+			</trans-unit>
+			<trans-unit id="frontend.cvUplodadButton">
+				<source><![CDATA[Upload cv]]></source>
+			</trans-unit>
 			<trans-unit id="frontend.department">
 				<source><![CDATA[Department]]></source>
 			</trans-unit>
@@ -342,12 +366,12 @@
 			<trans-unit id="frontend.location">
 				<source><![CDATA[Location]]></source>
 			</trans-unit>
-			<trans-unit id="frontend.locationLabel">
-				<source><![CDATA[<b>Location:</b>]]></source>
-			</trans-unit>
 			<trans-unit id="frontend.locationCountLabel">
 				<source><![CDATA[Locations]]></source>
 			</trans-unit>
+			<trans-unit id="frontend.locationLabel">
+				<source><![CDATA[<b>Location:</b>]]></source>
+			</trans-unit>
 			<trans-unit id="frontend.organisation">
 				<source><![CDATA[Organisation]]></source>
 			</trans-unit>
@@ -357,36 +381,15 @@
 			<trans-unit id="frontend.qualification">
 				<source><![CDATA[Qualification]]></source>
 			</trans-unit>
+			<trans-unit id="frontend.remote">
+				<source><![CDATA[Remote]]></source>
+			</trans-unit>
 			<trans-unit id="frontend.task">
 				<source><![CDATA[Task]]></source>
 			</trans-unit>
 			<trans-unit id="frontend.teaserHeadline">
 				<source><![CDATA[Vacancies worldwide]]></source>
 			</trans-unit>
-			<trans-unit id="frontend.DropFiles">
-				<source><![CDATA[Drop file here]]></source>
-			</trans-unit>
-			<trans-unit id="frontend.CancelUpload">
-				<source><![CDATA[Stop upload]]></source>
-			</trans-unit>
-			<trans-unit id="frontend.RemoveFile">
-				<source><![CDATA[Remove file]]></source>
-			</trans-unit>
-			<trans-unit id="frontend.FileType">
-				<source><![CDATA[Filetype]]></source>
-			</trans-unit>
-			<trans-unit id="frontend.coverLetterUplodadButton">
-				<source><![CDATA[Upload coverletter]]></source>
-			</trans-unit>
-			<trans-unit id="frontend.cvUplodadButton">
-				<source><![CDATA[Upload cv]]></source>
-			</trans-unit>
-			<trans-unit id="frontend.certificateUplodadButton">
-				<source><![CDATA[Upload certificate]]></source>
-			</trans-unit>
-			<trans-unit id="frontend.remote">
-				<source><![CDATA[Remote]]></source>
-			</trans-unit>
 		</body>
 	</file>
-</xliff>
+</xliff>
\ No newline at end of file
diff --git a/Resources/Private/Language/zh.locallang.xlf b/Resources/Private/Language/zh.locallang.xlf
index ec633834..4f78fba7 100644
--- a/Resources/Private/Language/zh.locallang.xlf
+++ b/Resources/Private/Language/zh.locallang.xlf
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8" standalone="yes" ?>
 <xliff version="1.0">
-	<file source-language="en" target-language="zh" datatype="plaintext" original="messages" date="2022-06-20T17:21:14Z">
+	<file source-language="en" target-language="zh" datatype="plaintext" original="messages" date="2023-09-06T17:30:53Z">
 		<header>
 			<type>module</type>
 			<description>General language labels used in frontend and backend.</description>
@@ -239,4 +239,4 @@
 			</trans-unit>
 		</body>
 	</file>
-</xliff>
+</xliff>
\ No newline at end of file
diff --git a/UPGRADE.md b/UPGRADE.md
index 937f18f7..2e687dc0 100644
--- a/UPGRADE.md
+++ b/UPGRADE.md
@@ -2,15 +2,22 @@
 
 - Dropped TYPO3 9 support
 - Dropped php 7.3 support
-- Dropped city, street, zip and hide_in_frontend fields from the contact table. There is no upgrade wizard. The fields were not used inside the main template/code since some time. The latter one was depcrated since a while. Adjust your local template to use the company fields instead.
-- Job location field is deprecated, use different Companies instead; job.location is unused in default templates/settings
+- Dropped city, street, zip and hide_in_frontend fields from the contact table. There is no upgrade wizard. The fields
+  were not used inside the main template/code since some time. The latter one was depcrated since a while. Adjust your
+  local template to use the company fields instead.
+- Job location field is deprecated, use different Companies instead; job.location is unused in default
+  templates/settings
 - Dropped support for sg_seo < 6.0
 - Dropped support for sg_mail < 8.0
 
-The handling of sorting inside plugins might have changed. Up until this release there was an extension setting, which disabled
-manual sorting by default and made order by title default, while the actual chosen plugin option was using manual sorting.
-In effect the default sorting was always by title. We removed the option to disable manual sorting, and switched the values
-inside the plugin settings. This way the default still is sorting by title, but with clearer intend. If you want manual sorting,
+The handling of sorting inside plugins might have changed. Up until this release there was an extension setting, which
+disabled
+manual sorting by default and made order by title default, while the actual chosen plugin option was using manual
+sorting.
+In effect the default sorting was always by title. We removed the option to disable manual sorting, and switched the
+values
+inside the plugin settings. This way the default still is sorting by title, but with clearer intend. If you want manual
+sorting,
 you will need to edit your Plugins.
 
 ## Version 4 Breaking Changes
diff --git a/composer.json b/composer.json
index abbdeaf7..0f119c98 100644
--- a/composer.json
+++ b/composer.json
@@ -13,7 +13,7 @@
 	"require": {
 		"typo3/cms-core": "^10.4.0 || ^11.5.0",
 		"sgalinski/sg-mail": ">=8.0.0",
-        "sgalinski/sg-ajax": ">=4.0.0",
+		"sgalinski/sg-ajax": ">=4.0.0",
 		"sgalinski/sg-seo": ">=6.0.0"
 	},
 	"replace": {
diff --git a/ext_conf_template.txt b/ext_conf_template.txt
new file mode 100644
index 00000000..d7381d51
--- /dev/null
+++ b/ext_conf_template.txt
@@ -0,0 +1,2 @@
+# cat=general; type=string; label=LLL:EXT:sg_jobs/Resources/Private/Language/locallang.xlf:configuration.jobFolderPath
+jobFolderPath = JobApplication
diff --git a/ext_localconf.php b/ext_localconf.php
index a3747989..588a1eb1 100644
--- a/ext_localconf.php
+++ b/ext_localconf.php
@@ -1,31 +1,5 @@
 <?php
 
-defined('TYPO3') or die();
-
-/***************************************************************
- *  Copyright notice
- *
- *  (c) sgalinski Internet Services (https://www.sgalinski.de)
- *
- *  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 3 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.
- *
- *  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!
- ***************************************************************/
-
 call_user_func(
 	static function () {
 		\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTypoScriptSetup(
diff --git a/ext_tables.php b/ext_tables.php
index 7831a047..b6a52756 100644
--- a/ext_tables.php
+++ b/ext_tables.php
@@ -1,30 +1,5 @@
 <?php
 
-defined('TYPO3') or die();
-/***************************************************************
- *  Copyright notice
- *
- *  (c) sgalinski Internet Services (https://www.sgalinski.de)
- *
- *  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 3 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.
- *
- *  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!
- ***************************************************************/
-
 call_user_func(
 	static function () {
 		\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::allowTableOnStandardPages(
@@ -57,7 +32,7 @@ call_user_func(
 			],
 			[
 				'access' => 'user,group',
-				'iconIdentifier' => 'extension-sg_forms-module',
+				'iconIdentifier' => 'extension-sg_jobs-module',
 				'labels' => 'LLL:EXT:sg_jobs/Resources/Private/Language/locallang_backend.xlf',
 			]
 		);
diff --git a/ext_tables.sql b/ext_tables.sql
index c5c40e5d..37e742d5 100644
--- a/ext_tables.sql
+++ b/ext_tables.sql
@@ -57,7 +57,7 @@ CREATE TABLE tx_sgjobs_domain_model_contact (
 	last_name  varchar(255)     DEFAULT '' NOT NULL,
 	email      varchar(255)     DEFAULT '' NOT NULL,
 	phone      varchar(255)     DEFAULT '' NOT NULL,
-	image      int(11) unsigned DEFAULT '0',
+	image      int(11) unsigned DEFAULT '0'
 );
 
 CREATE TABLE tx_sgjobs_domain_model_job_application (
-- 
GitLab