diff --git a/Documentation/Debug.rst b/Documentation/Debug.rst
index 965bc82914f73f845d62db56c1781c94a81d1bc4..00fc610300abea198e7198b644a44da7060e776c 100644
--- a/Documentation/Debug.rst
+++ b/Documentation/Debug.rst
@@ -103,7 +103,7 @@ Setup in :ref:`configuration`
   * *download*:
 
     * During a download (especially by using wkhtml), temporary files are not deleted automatically. Also the
-      ``wkhtmltopdf`` and ``pdfunite`` command lines will be logged to :ref:`QFQ_LOG`. Use this only to debug problems on download.
+      ``wkhtmltopdf``, ``pdfunite``, ``pdf2img`` command lines will be logged to :ref:`QFQ_LOG`. Use this only to debug problems on download.
 
 .. _MAIL_LOG:
 
diff --git a/Documentation/Installation.rst b/Documentation/Installation.rst
index 6c89648b10a8747d5da1ab4c980564b186f9c571..705eb760dd338e5c039f6471e143956c72bce014 100644
--- a/Documentation/Installation.rst
+++ b/Documentation/Installation.rst
@@ -42,6 +42,7 @@ The following features are only tested / supported on linux hosts:
 * General: QFQ is coded to run on Linux hosts, preferable on Debian derivates like Ubuntu.
 * HTML to PDF conversion - command `wkhtmltopdf`.
 * Concatenation of PDF files - command `pdfunite`.
+* Convert of imges to PDF files - command `img2pdf`.
 * PDF decrypt (used for merge with pdfunite) - command `qpdf`.
 * PDF decrypt (used for merge with pdfunite) - command `gs` - in case `qpdf` is not successful.
 * Mime type detection for uploads - command `file`.
@@ -59,7 +60,7 @@ To normalize UTF8 input, *php-intl* package is needed by
 
 * normalizer::normalize()
 
-For the :ref:`download` function, the programs `pdfunite`, `qpdf`, `gs` and `file` are necessary to concatenate PDF files.
+For the :ref:`download` function, the programs `img2pdf`, `pdfunite`, `qpdf`, `gs` and `file` are necessary to concatenate PDF files.
 
 Preparation for Ubuntu::
 
@@ -409,6 +410,8 @@ Extension Manager: QFQ Configuration
 +-----------------------------------+-------------------------------------------------------+----------------------------------------------------------------------------+
 | cmdPdfunite                       | pdfunite                                              | PathFilename of pdfunite. Optional variables like LD_LIBRARY_PATH=...      |
 +-----------------------------------+-------------------------------------------------------+----------------------------------------------------------------------------+
+| cmdImg2pdf                        | img2pdf                                               | PathFilename of img2pdf. Optional variables like LD_LIBRARY_PATH=...       |
++-----------------------------------+-------------------------------------------------------+----------------------------------------------------------------------------+
 | sendEMailOptions                  | -o tls=yes                                            | General options. Check: http://caspian.dotconf.net/menu/Software/SendEmail |
 +-----------------------------------+-------------------------------------------------------+----------------------------------------------------------------------------+
 | documentation                     | http://docs.typo3.org...                              | Link to the online documentation of QFQ. Every QFQ installation also       |
diff --git a/Documentation/SearchDocs.rst b/Documentation/SearchDocs.rst
new file mode 100644
index 0000000000000000000000000000000000000000..4c90156105a7e85e2cadff164db2ecb825a23a7c
--- /dev/null
+++ b/Documentation/SearchDocs.rst
@@ -0,0 +1,90 @@
+Searching Documentation
+=======================
+
+This page was copied from: https://docs.readthedocs.io/en/stable/guides/searching-with-readthedocs.html
+
+Read the Docs uses :doc:`/server-side-search` to power our search.
+This guide explains how to add a "search as you type" feature to your documentation, 
+and how to use advanced query syntax to get more accurate results.
+
+You can find information on the search architecture and how we index documents in our
+:doc:`Search </development/search>` docs.
+
+.. contents:: Table of contents
+   :local:
+   :backlinks: none
+   :depth: 3
+
+
+Search query syntax
+-------------------
+
+Read the Docs uses the `Simple Query String`_ feature from `Elasticsearch`_.
+This means that as the search query becomes more complex,
+the results yielded become more specific.
+
+Exact phrase search
+~~~~~~~~~~~~~~~~~~~
+
+If a query is wrapped in ``"`` (double quotes),
+then only those results where the phrase is exactly matched will be returned.
+
+Example queries:
+
+- https://docs.rea>dthedocs.io/?rtd_search=%22custom%20css%22
+- https://docs.readthedocs.io/?rtd_search=%22adding%20a%20subproject%22
+- https://docs.readthedocs.io/?rtd_search=%22when%20a%20404%20is%20returned%22
+
+Exact phrase search with slop value
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+``~N`` (tilde N) after a phrase signifies slop amount.
+It can be used to match words that are near one another.
+
+Example queries:
+
+- https://docs.readthedocs.io/?rtd_search=%22dashboard%20admin%22~2
+- https://docs.readthedocs.io/?rtd_search=%22single%20documentation%22~1
+- https://docs.readthedocs.io/?rtd_search=%22read%20the%20docs%20story%22~5
+
+Prefix query
+~~~~~~~~~~~~
+
+``*`` (asterisk) at the end of any term signifies a prefix query.
+It returns the results containing the words with specific prefix.
+
+Example queries:
+
+- https://docs.readthedocs.io/?rtd_search=API%20v*
+- https://docs.readthedocs.io/?rtd_search=single%20v*%20doc*
+- https://docs.readthedocs.io/?rtd_search=build*%20and%20c*%20to%20doc*
+
+Fuzzy query
+~~~~~~~~~~~
+
+``~N`` after a word signifies edit distance (fuzziness).
+This type of query is helpful when the exact spelling of the keyword is unknown.
+It returns results that contain terms similar to the search term as measured by a `Levenshtein edit distance`_.
+
+Example queries:
+
+- https://docs.readthedocs.io/?rtd_search=reedthedcs~2
+- https://docs.readthedocs.io/?rtd_search=authentation~3
+- https://docs.readthedocs.io/?rtd_search=configurtion~1
+
+
+Build complex queries
+~~~~~~~~~~~~~~~~~~~~~
+
+The search query syntaxes described in the previous sections can be used with one another to build complex queries.
+
+For example:
+
+- https://docs.readthedocs.io/?rtd_search=auto*%20redirect*
+- https://docs.readthedocs.io/?rtd_search=abandon*%20proj*
+- https://docs.readthedocs.io/?rtd_search=localisation~3%20of%20doc*
+
+.. _Elasticsearch: https://www.elastic.co/products/elasticsearch
+.. _readthedocs-sphinx-search: https://readthedocs-sphinx-search.readthedocs.io/
+.. _Simple Query String: https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-simple-query-string-query.html#
+.. _Levenshtein edit distance: https://en.wikipedia.org/wiki/Levenshtein_distance
\ No newline at end of file
diff --git a/Documentation/index.rst b/Documentation/index.rst
index 417d21aeea1693807aa4e90183ec322045c48554..be57bdf8b7d3a14ce6fb050fd836b6e63a7207aa 100644
--- a/Documentation/index.rst
+++ b/Documentation/index.rst
@@ -90,4 +90,5 @@ This documentation is for the TYPO3 extension **qfq**.
    Links
    License
    Sitemap
+   SearchDocs
 
diff --git a/extension/Classes/Core/Constants.php b/extension/Classes/Core/Constants.php
index 400c65ead4a708f87c287a61a9d096d8b7ca1a09..95809ecbc33f45195cda5395127e5c2dc21c7d85 100644
--- a/extension/Classes/Core/Constants.php
+++ b/extension/Classes/Core/Constants.php
@@ -303,6 +303,7 @@ const ERROR_DOWNLOAD_FILE_NOT_READABLE = 1705;
 const ERROR_DOWNLOAD_FOPEN_BLOCKED = 1706;
 const ERROR_DOWNLOAD_JSON_CONVERT = 1707;
 const ERROR_DOWNLOAD_MERGE_FAILED = 1708;
+const ERROR_DOWNLOAD_CONVERT_FAILED = 1709;
 
 // Excel
 const ERROR_EXCEL_POSITION_ARGUMENT_EMPTY = 1800;
@@ -649,6 +650,7 @@ const SYSTEM_CMD_CONVERT = 'cmdConvert';
 const SYSTEM_CMD_QPDF = 'cmdQpdf';
 const SYSTEM_CMD_GS = 'cmdGs';
 const SYSTEM_CMD_PDFUNITE = 'cmdPdfunite';
+const SYSTEM_CMD_IMG2PDF = 'cmdImg2pdf';
 
 // Thumbnail
 const SYSTEM_THUMBNAIL_DIR_SECURE = 'thumbnailDirSecure';
diff --git a/extension/Classes/Core/Helper/HelperFile.php b/extension/Classes/Core/Helper/HelperFile.php
index 8789e3c0c184908b80c30b92d5178db77ac3ccd6..5619f9c0c6cf2510d4c215ad5adcbd673690e00d 100644
--- a/extension/Classes/Core/Helper/HelperFile.php
+++ b/extension/Classes/Core/Helper/HelperFile.php
@@ -9,8 +9,6 @@
 namespace IMATHUZH\Qfq\Core\Helper;
 
 
- 
-
 /**
  * Class HelperFile
  * @package qfq
@@ -517,5 +515,30 @@ class HelperFile {
         return $new;
     }
 
+    /**
+     * Joins $pre and $post. If $post is absolute, returns only $post. If $pre ends without '/', a '/' is injected.
+     *
+     * @param $pre
+     * @param $post
+     *
+     * @return string
+     */
+    public static function joinPathFilename($pre, $post) {
+        $separator = '';
+
+        if ($pre == '' || $post == '') {
+            return $pre . $post;
+        }
+
+        if ($post[0] == '/') {
+            return $post;
+        }
+
+        if ((substr($pre, -1) != '/')) {
+            $separator = '/';
+        }
+
+        return $pre . $separator . $post;
+    }
 }
 
diff --git a/extension/Classes/Core/Report/Download.php b/extension/Classes/Core/Report/Download.php
index 3820e521f0bf36d8ad12a82ad944f1115e6892fb..97c146f7170a93549a098475dc133f7b6f308109 100644
--- a/extension/Classes/Core/Report/Download.php
+++ b/extension/Classes/Core/Report/Download.php
@@ -101,6 +101,7 @@ class Download {
         $this->qpdf = $this->store->getVar(SYSTEM_CMD_QPDF, STORE_SYSTEM);
         $this->gs = $this->store->getVar(SYSTEM_CMD_GS, STORE_SYSTEM);
         $this->pdfunite = $this->store->getVar(SYSTEM_CMD_PDFUNITE, STORE_SYSTEM);
+        $this->img2Pdf = $this->store->getVar(SYSTEM_CMD_IMG2PDF, STORE_SYSTEM);
 
         if (Support::findInSet(SYSTEM_SHOW_DEBUG_INFO_DOWNLOAD, $this->store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM))) {
             $this->downloadDebugLog = $this->store->getVar(SYSTEM_SQL_LOG, STORE_SYSTEM);
@@ -109,38 +110,47 @@ class Download {
 
     /**
      * Concatenate all named files to one PDF file. Return name of new full PDF.
+     * If a source file is an image, it will be converted to a PDF.
      *
-     * @param array $files
+     * @param array $files Array of source files
+     * @param array $filesCleanLater Array of temporarily created files
      *
      * @return string  - fileName of concatenated file
      * @throws \CodeException
      * @throws \DownloadException
      * @throws \UserFormException
      */
-    private function concatPdfFiles(array $files) {
+    private function concatFilesToPdf(array $files, array &$filesCleanLater) {
 
         // Remove empty entries. Might happen if there was no upload
         $files = OnArray::removeEmptyElementsFromArray($files);
 
-        // Check that all files exist and are readable
-        foreach ($files AS $filename) {
+        // Check that all files are of type 'application/pdf'
+        foreach ($files as $key => $filename) {
+            // Check that all files exist and are readable
             if (!is_readable($filename)) {
                 throw new \DownloadException("Error reading file $filename. Not found or no permission", ERROR_DOWNLOAD_FILE_NOT_READABLE);
             }
-        }
 
-        if (count($files) === 0) {
-            return '';
-        }
-
-        // Check that all files are of type 'application/pdf'
-        foreach ($files AS $filename) {
             $mimetype = mime_content_type($filename);
+
+            // If file is an image, convert to PDF
+            if (substr($mimetype, 0, 6) == 'image/') {
+                $tmpName = $this->doImgToPdf($filename);
+                $filesCleanLater[] = $tmpName; // remember to purge later.
+                $files[$key] = $tmpName; // replace original reference against temporary one.
+                $mimetype = 'application/pdf';
+            }
+
             if ($mimetype != 'application/pdf') {
                 throw new \DownloadException("Error concat file $filename. Mimetype 'application/pdf' expected, got: $mimetype", ERROR_DOWNLOAD_UNEXPECTED_MIME_TYPE);
             }
         }
 
+        if (count($files) === 0) {
+            return '';
+        }
+
         if (count($files) == 1) {
             return $files[0];
         }
@@ -176,6 +186,36 @@ class Download {
         return $concatFile;
     }
 
+    /**
+     * Convert an image to PDF
+     *
+     * @param $fileImage
+     * @throws \DownloadException
+     */
+    private function doImgToPdf($fileImage) {
+
+        $filePdf = HelperFile::tempnam();
+        if (false === $filePdf) {
+            throw new \DownloadException('Error creating output file.', ERROR_DOWNLOAD_CREATE_NEW_FILE);
+        }
+
+        // img2pdf --pagesize A4 -o out.pdf *.jpg
+        $cmd = $this->img2Pdf . ' --pagesize A4 -o ' . $filePdf . ' ' . escapeshellarg($fileImage);
+
+        if ($this->downloadDebugLog != '') {
+            Logger::logMessage("Download: $cmd", $this->downloadDebugLog);
+        }
+
+        exec($cmd, $rcOutput, $rc);
+        if ($rc != 0) {
+            throw new \DownloadException (json_encode([ERROR_MESSAGE_TO_USER => "Failed to convert image to PDF.",
+                    ERROR_MESSAGE_TO_DEVELOPER => "CMD: $cmd<br>RC: $rc<br>Output: " . implode("<br>", $rcOutput)])
+                , ERROR_DOWNLOAD_CONVERT_FAILED);
+        }
+
+        return $filePdf;
+    }
+
     /**
      * Fires the merge command.
      * If for any reason the command fails: check if the reason is 'unencrypted files'.
@@ -457,7 +497,7 @@ class Download {
 
         $len = strlen(TMP_FILE_PREFIX);
         $ii = 1;
-        foreach ($files AS $filename) {
+        foreach ($files as $filename) {
             $localName = substr($filename, strrpos($filename, '/') + 1);
 
             if (substr($localName, 0, $len) == TMP_FILE_PREFIX) {
@@ -499,7 +539,8 @@ class Download {
      */
     private function doElements(array $vars, $outputMode) {
 
-        $tmpFiles = array();
+        $srcFiles = array();
+        $filesCleanLater = array();
 
         $workDir = $this->store->getVar(SYSTEM_SITE_PATH, STORE_SYSTEM);
         HelperFile::chdir($workDir);
@@ -525,7 +566,7 @@ class Download {
         $tmpData = array();
         foreach ($elements as $element) {
             $data = '';
-            $tmpFiles[] = $this->getElement($element, $downloadMode, $data);
+            $srcFiles[] = $this->getElement($element, $downloadMode, $data);
             if (!empty($data)) {
                 $tmpData[] = $data;
             }
@@ -534,7 +575,7 @@ class Download {
         // Export, Concat File(s)
         switch ($downloadMode) {
             case DOWNLOAD_MODE_ZIP:
-                $filename = $this->zipFiles($tmpFiles);
+                $filename = $this->zipFiles($srcFiles);
                 if (empty($vars[DOWNLOAD_EXPORT_FILENAME])) {
                     $vars[DOWNLOAD_EXPORT_FILENAME] = basename($filename);
                 }
@@ -542,7 +583,7 @@ class Download {
 
             case DOWNLOAD_MODE_EXCEL:
                 $excel = new Excel();
-                $filename = $excel->process($tmpFiles, $tmpData);
+                $filename = $excel->process($srcFiles, $tmpData);
 
                 if (empty($filename) || !file_exists($filename)) {
                     throw new \DownloadException(json_encode(
@@ -560,7 +601,7 @@ class Download {
                 break;
 
             case DOWNLOAD_MODE_FILE:
-                $filename = $tmpFiles[0];
+                $filename = $srcFiles[0];
                 if (empty($vars[DOWNLOAD_EXPORT_FILENAME])) {
                     $vars[DOWNLOAD_EXPORT_FILENAME] = basename($filename);
                 }
@@ -568,11 +609,11 @@ class Download {
 
             case DOWNLOAD_MODE_PDF:
 
-                $filename = $this->concatPdfFiles($tmpFiles);
+                $filename = $this->concatFilesToPdf($srcFiles, $filesCleanLater);
 
                 // try to find a meaningful filename
                 if (empty($vars[DOWNLOAD_EXPORT_FILENAME])) {
-                    if (count($tmpFiles) > 1) {
+                    if (count($srcFiles) > 1) {
                         $vars[DOWNLOAD_EXPORT_FILENAME] = DOWNLOAD_OUTPUT_FILENAME . ".pdf";
                     } else {
                         if (HelperFile::isQfqTemp($filename)) {
@@ -595,6 +636,8 @@ class Download {
                     ERROR_MESSAGE_TO_DEVELOPER => "File: $filename"]), ERROR_IO_FILE_EXIST);
         }
 
+        $filesCleanLater[] = $filename;
+
         switch ($outputMode) {
 
             case OUTPUT_MODE_FILE:
@@ -602,12 +645,10 @@ class Download {
 
             case OUTPUT_MODE_COPY_TO_FILE:
                 HelperFile::copy($filename, $vars[DOWNLOAD_EXPORT_FILENAME]);
-                HelperFile::cleanTempFiles([$filename]);
                 break;
 
             case OUTPUT_MODE_DIRECT:
                 $this->outputFile($filename, $vars[DOWNLOAD_EXPORT_FILENAME]);
-                HelperFile::cleanTempFiles([$filename]);
                 $filename = '';
                 break;
 
@@ -615,6 +656,8 @@ class Download {
                 throw new \CodeException('Unkown mode: ' . $outputMode, ERROR_UNKNOWN_MODE);
         }
 
+        HelperFile::cleanTempFiles($filesCleanLater);
+
         return $filename;
     }
 
diff --git a/extension/Classes/Core/Report/Report.php b/extension/Classes/Core/Report/Report.php
index 13bd0939090f6f1bf2006b56dacaccc0667368bf..dda49138af3404759c9fda97aa9829068bb30d41 100644
--- a/extension/Classes/Core/Report/Report.php
+++ b/extension/Classes/Core/Report/Report.php
@@ -191,10 +191,7 @@ class Report {
 
         $sqlLog = $this->store->getVar(TYPO3_SQL_LOG, STORE_TYPO3);
         if (false !== $sqlLog) {
-            if ($sqlLog != '' && $sqlLog[0] !== '/') {
-                $sqlLog = $this->store->getVar(SYSTEM_EXT_PATH, STORE_SYSTEM) . '/' . $sqlLog;
-            }
-
+            $sqlLog = HelperFile::joinPathFilename($this->store->getVar(SYSTEM_EXT_PATH, STORE_SYSTEM), $sqlLog);
             $this->store->setVar(SYSTEM_SQL_LOG, $sqlLog, STORE_SYSTEM);
         }
 
@@ -249,7 +246,7 @@ class Report {
      * Split line in level, command, content and fill 'frArray', 'levelCount', 'indexArray'
      * Example: 10.50.5.sql = select * from person
      *
-     * @param    string $ttLine : line to split in level, command, content
+     * @param string $ttLine : line to split in level, command, content
      *
      * @throws \UserReportException
      */
@@ -810,11 +807,11 @@ class Report {
      * 3) if none above take default
      * Set value on $full_level
      *
-     * @param    string $level_key - 'db' or 'debug'
-     * @param    string $full_super_level - f.e.: 10.10.
-     * @param    string $full_level - f.e.: 10.10.10.
-     * @param    string $cur_level - f.e.: 2
-     * @param    string $default - f.e.: 0
+     * @param string $level_key - 'db' or 'debug'
+     * @param string $full_super_level - f.e.: 10.10.
+     * @param string $full_level - f.e.: 10.10.10.
+     * @param string $cur_level - f.e.: 2
+     * @param string $default - f.e.: 0
      *
      * @return   string  The calculated value.
      */
@@ -1219,8 +1216,8 @@ class Report {
     /**
      * Renders PageX: convert position content to token content. Respect default values depending on PageX
      *
-     * @param    string $columnName
-     * @param    string $columnValue
+     * @param string $columnName
+     * @param string $columnValue
      * @return string rendered link
      *
      * $columnValue:
@@ -1347,9 +1344,9 @@ class Report {
     /**
      * If there is a value (or a defaultValue): compose it together with qualifier and delimiter.
      *
-     * @param    string $qualifier
-     * @param    string $value
-     * @param    string $defaultValue
+     * @param string $qualifier
+     * @param string $value
+     * @param string $defaultValue
      *
      * @return    string        rendered link
      */
@@ -1369,8 +1366,8 @@ class Report {
     /**
      * Renders _pageX: extract token and determine if any default value has to be applied
      *
-     * @param    string $columnName
-     * @param    string $columnValue
+     * @param string $columnName
+     * @param string $columnValue
      *
      * @return    string        rendered link
      */
diff --git a/extension/Classes/Core/Save.php b/extension/Classes/Core/Save.php
index fea100a401cae9e63e0cde32a675a18d0f995c84..886444cae4b6274f6ee793ced7195c907f2799be 100644
--- a/extension/Classes/Core/Save.php
+++ b/extension/Classes/Core/Save.php
@@ -72,8 +72,9 @@ class Save {
         $this->evaluate = new Evaluate($this->store, $this->db);
         $this->formAction = new FormAction($formSpec, $this->db);
 
-        $this->qfqLogFilename = $this->store->getVar(SYSTEM_SITE_PATH, STORE_SYSTEM) . '/' . $this->store->getVar(SYSTEM_QFQ_LOG, STORE_SYSTEM);
-
+        $this->qfqLogFilename = HelperFile::joinPathFilename(
+            $this->store->getVar(SYSTEM_SITE_PATH, STORE_SYSTEM),
+            $this->store->getVar(SYSTEM_QFQ_LOG, STORE_SYSTEM));
     }
 
     /**
diff --git a/extension/Classes/Core/Store/Config.php b/extension/Classes/Core/Store/Config.php
index 29a638c9b7d7fce5175f78d83347e9364807ee92..4b7123aff676c676e9d2f61446c6c5164f4fa28e 100644
--- a/extension/Classes/Core/Store/Config.php
+++ b/extension/Classes/Core/Store/Config.php
@@ -377,6 +377,7 @@ class Config {
             SYSTEM_CMD_QPDF => 'qpdf',
             SYSTEM_CMD_GS => 'gs',
             SYSTEM_CMD_PDFUNITE => 'pdfunite',
+            SYSTEM_CMD_IMG2PDF => 'img2pdf',
 
             SYSTEM_THUMBNAIL_DIR_SECURE => SYSTEM_THUMBNAIL_DIR_SECURE_DEFAULT,
             SYSTEM_THUMBNAIL_DIR_PUBLIC => SYSTEM_THUMBNAIL_DIR_PUBLIC_DEFAULT,
diff --git a/extension/Classes/Core/Store/Store.php b/extension/Classes/Core/Store/Store.php
index b3b298e036c844fae96818db467366d5333a82f2..60dec91a3140c2d079c54434b5c2ef482547204b 100644
--- a/extension/Classes/Core/Store/Store.php
+++ b/extension/Classes/Core/Store/Store.php
@@ -14,6 +14,7 @@ use IMATHUZH\Qfq\Core\Helper\Logger;
 use IMATHUZH\Qfq\Core\Helper\OnArray;
 use IMATHUZH\Qfq\Core\Helper\Sanitize;
 use IMATHUZH\Qfq\Core\Helper\Support;
+use IMATHUZH\Qfq\Core\Helper\HelperFile;
 
 /*
  * Stores:
@@ -297,7 +298,7 @@ class Store {
         // Make path absolute
         foreach ([SYSTEM_MAIL_LOG, SYSTEM_QFQ_LOG, SYSTEM_SQL_LOG] AS $key) {
             if (!empty($config[$key]) && $config[$key][0] != '/') {
-                $config[$key] = $config[SYSTEM_SITE_PATH] . '/' . $config[$key];
+                $config[$key] = HelperFile::joinPathFilename($config[SYSTEM_SITE_PATH], $config[$key]);
             }
         }
 
diff --git a/extension/Tests/Unit/Core/Helper/HelperFileTest.php b/extension/Tests/Unit/Core/Helper/HelperFileTest.php
index aa6da1c7eb215c29572c205aab92ad667cea9530..98ae13a4f53166a76e8847a522f6e1c37a74f69b 100644
--- a/extension/Tests/Unit/Core/Helper/HelperFileTest.php
+++ b/extension/Tests/Unit/Core/Helper/HelperFileTest.php
@@ -38,15 +38,34 @@ class HelperFileTest extends TestCase {
         $this->assertEquals(DIR_HIGHLIGHT_JSON . '/highlight.m.json', HelperFile::getFileTypeHighlight(FE_HIGHLIGHT_MATLAB,''));
 
         $this->assertEquals(DIR_HIGHLIGHT_JSON . '/javascript.json', HelperFile::getFileTypeHighlight(FE_HIGHLIGHT_JAVASCRIPT,'fileadmin/test.js'));
-        $this->assertEquals(DIR_HIGHLIGHT_JSON . '/highlight.qfq.json', HelperFile::getFileTypeHighlight(FE_HIGHLIGHT_QFQ,'fileadmin/test.js'));
-        $this->assertEquals(DIR_HIGHLIGHT_JSON . '/highlight.py.json', HelperFile::getFileTypeHighlight(FE_HIGHLIGHT_PYTHON,'fileadmin/test.js'));
-        $this->assertEquals(DIR_HIGHLIGHT_JSON . '/highlight.m.json', HelperFile::getFileTypeHighlight(FE_HIGHLIGHT_MATLAB,'fileadmin/test.js'));
-
-        $this->assertEquals(DIR_HIGHLIGHT_JSON . '/javascript.json', HelperFile::getFileTypeHighlight(FE_HIGHLIGHT_AUTO,'fileadmin/test.js'));
-        $this->assertEquals(DIR_HIGHLIGHT_JSON . '/highlight.php.json', HelperFile::getFileTypeHighlight(FE_HIGHLIGHT_AUTO,'fileadmin/test.php'));
-        $this->assertEquals(DIR_HIGHLIGHT_JSON . '/highlight.qfq.json', HelperFile::getFileTypeHighlight(FE_HIGHLIGHT_AUTO,'fileadmin/test.qfq'));
-        $this->assertEquals(DIR_HIGHLIGHT_JSON . '/highlight.py.json', HelperFile::getFileTypeHighlight(FE_HIGHLIGHT_AUTO,'fileadmin/test.py'));
-        $this->assertEquals(DIR_HIGHLIGHT_JSON . '/highlight.m.json', HelperFile::getFileTypeHighlight(FE_HIGHLIGHT_AUTO,'fileadmin/test.m'));
+        $this->assertEquals(DIR_HIGHLIGHT_JSON . '/highlight.qfq.json', HelperFile::getFileTypeHighlight(FE_HIGHLIGHT_QFQ, 'fileadmin/test.js'));
+        $this->assertEquals(DIR_HIGHLIGHT_JSON . '/highlight.py.json', HelperFile::getFileTypeHighlight(FE_HIGHLIGHT_PYTHON, 'fileadmin/test.js'));
+        $this->assertEquals(DIR_HIGHLIGHT_JSON . '/highlight.m.json', HelperFile::getFileTypeHighlight(FE_HIGHLIGHT_MATLAB, 'fileadmin/test.js'));
+
+        $this->assertEquals(DIR_HIGHLIGHT_JSON . '/javascript.json', HelperFile::getFileTypeHighlight(FE_HIGHLIGHT_AUTO, 'fileadmin/test.js'));
+        $this->assertEquals(DIR_HIGHLIGHT_JSON . '/highlight.php.json', HelperFile::getFileTypeHighlight(FE_HIGHLIGHT_AUTO, 'fileadmin/test.php'));
+        $this->assertEquals(DIR_HIGHLIGHT_JSON . '/highlight.qfq.json', HelperFile::getFileTypeHighlight(FE_HIGHLIGHT_AUTO, 'fileadmin/test.qfq'));
+        $this->assertEquals(DIR_HIGHLIGHT_JSON . '/highlight.py.json', HelperFile::getFileTypeHighlight(FE_HIGHLIGHT_AUTO, 'fileadmin/test.py'));
+        $this->assertEquals(DIR_HIGHLIGHT_JSON . '/highlight.m.json', HelperFile::getFileTypeHighlight(FE_HIGHLIGHT_AUTO, 'fileadmin/test.m'));
     }
+
+    public function testJoinPathFilename() {
+
+        $this->assertEquals('', HelperFile::joinPathFilename('', ''));
+        $this->assertEquals('/', HelperFile::joinPathFilename('/', ''));
+        $this->assertEquals('/', HelperFile::joinPathFilename('', '/'));
+        $this->assertEquals('/', HelperFile::joinPathFilename('/', '/'));
+
+        $this->assertEquals('a/b', HelperFile::joinPathFilename('a', 'b'));
+        $this->assertEquals('/a/b', HelperFile::joinPathFilename('/a', 'b'));
+        $this->assertEquals('/b', HelperFile::joinPathFilename('a', '/b'));
+        $this->assertEquals('/b', HelperFile::joinPathFilename('/a', '/b'));
+
+        $this->assertEquals('a/b', HelperFile::joinPathFilename('a/', 'b'));
+        $this->assertEquals('a/b', HelperFile::joinPathFilename('a', 'b'));
+        $this->assertEquals('/b', HelperFile::joinPathFilename('a/', '/b'));
+        $this->assertEquals('/b', HelperFile::joinPathFilename('a', '/b'));
+    }
+
 }
 
diff --git a/extension/Tests/Unit/Core/Store/StoreTest.php b/extension/Tests/Unit/Core/Store/StoreTest.php
index ec661206eb506fa5f593094b5f83c0f087544220..ea15d5bbbfc4ae6fc64d08a54056691f7ed6832c 100644
--- a/extension/Tests/Unit/Core/Store/StoreTest.php
+++ b/extension/Tests/Unit/Core/Store/StoreTest.php
@@ -413,6 +413,7 @@ class StoreTest extends TestCase {
             SYSTEM_CMD_QPDF => 'qpdf',
             SYSTEM_CMD_GS => 'gs',
             SYSTEM_CMD_PDFUNITE => 'pdfunite',
+            SYSTEM_CMD_IMG2PDF => 'img2pdf',
         ];
 
         $body = <<< EOT
diff --git a/extension/ext_conf_template.txt b/extension/ext_conf_template.txt
index 979b6616dea698527b3b65e1a1e072645f694a33..9bf76675dec6c5a30fc80f2fba4f5549f82b1a0b 100644
--- a/extension/ext_conf_template.txt
+++ b/extension/ext_conf_template.txt
@@ -37,6 +37,9 @@ cmdGs = gs
 # cat=config/config; type=string; label=Command 'pdfunite':Default is 'pdfunite'. Will be used to merge PDFs.
 cmdPdfunite = pdfunite
 
+# cat=config/config; type=string; label=Command 'img2pdf':Default is 'img2pdf'. Will be used to convert images to PDFs.
+cmdImg2pdf = img2pdf
+
 # cat=config/email; type=string; label=Options for SendEMail:Default is empty. General options. Check: http://caspian.dotconf.net/menu/Software/SendEmail. E.g.: 'sendEMail=-o tls=yes'
 sendEMailOptions =