diff --git a/Documentation/Wiki.rst b/Documentation/Wiki.rst
new file mode 100644
index 0000000000000000000000000000000000000000..73046dd13e96a005f1f779b20c782d042c28e30b
--- /dev/null
+++ b/Documentation/Wiki.rst
@@ -0,0 +1,145 @@
+.. ==================================================
+.. ==================================================
+.. ==================================================
+.. Header hierarchy
+.. ==
+..  --
+..   ^^
+..    ""
+..     ;;
+..      ,,
+..
+.. --------------------------------------------used to the update the records specified ------
+.. Best Practice T3 reST: https://docs.typo3.org/m/typo3/docs-how-to-document/master/en-us/WritingReST/CheatSheet.html
+..             Reference: https://docs.typo3.org/m/typo3/docs-how-to-document/master/en-us/WritingReST/Index.html
+.. Italic *italic*
+.. Bold **bold**
+.. Code ``text``
+.. External Links: `Bootstrap <http://getbootstrap.com/>`_
+.. Internal Link: :ref:`downloadButton` (default url text) or :ref:`download Button<downloadButton>` (explicit url text)
+.. Add Images:    .. image:: ./Images/a4.jpg
+..
+..
+.. Admonitions
+..           .. note::   .. important::     .. tip::     .. warning::
+.. Color:   (blue)       (orange)           (green)      (red)
+..
+.. Definition:
+.. some text becomes strong (only one line)
+..      description has to indented
+
+.. -*- coding: utf-8 -*- with BOM.
+
+.. include:: Includes.txt
+
+
+.. _`wiki`:
+
+Wiki
+====
+
+Get started
+-----------
+
+The wiki is a special system report provided by QFQ and is located in the extension directory `typo3conf/ext/qfq/Resources/Private/Report`.
+Setup a *report* to get started using the wiki:
+
+* Create a Typo3 page.
+* Insert a content record of type *qfq*.
+* In the bodytext insert the following code (see explanation of code: :ref:`reportAsFile`)::
+
+    file=_wiki
+
+.. important::
+
+   The root page of the wiki can now be added in the frontend by a logged in Typo3 backend user.
+
+Form: wikiPage
+--------------
+
+Provides options for editing the settings of the wiki page and consists of two pills, `General` and `Access`.
+
+* General:
+
+   * `Name`: Name of the wiki page
+   * `Page slug`: Page slug of the wiki page (readonly)
+   * `Parent page`: Parent page of the wiki page
+
+* Access (can only be accessed by the author of the wiki page and a logged in Typo3 backend user):
+
+   * `Read-only user`: Comma separated string of usernames (`{{feUser:T}}`)
+   * `Read-only group`: Checkboxes of available groups (`{{dbNameT3:Y}}.fe_groups`)
+   * `Read-write user`: Comma separated string of usernames (`{{feUser:T}}`)
+   * `Read-write group`: Checkboxes of available groups (`{{dbNameT3:Y}}.fe_groups`)
+   * `Public`: Radio to set public access (`read-only`, `read-write`, `off`). Default `read-only`.
+   * `Page Lock`: Checkbox to set page lock. Can only be removed by the user who set it.
+
+.. important::
+
+   Do not use spaces in between usernames.
+
+.. note::
+
+    Any user without a page login (no `{{feUser:T}}`) is considered public.
+
+.. tip::
+
+   When a new wiki page is added using the `Add new page` option from the dropdown, the form automatically copies the settings of the parent page it was created from.
+   It is still possible to select a different parent page.
+
+.. note::
+
+   A wiki page can only be deleted if it has no subpages. When a wiki page is deleted, all its previous versions are also deleted.
+
+Form: wikiEditor
+----------------
+
+Once a wiki page has been created, content can be added/edited. In general, there are two ways to edit a page.
+
+* The Edit button at the top of the page allows you to edit the entire content.
+* The edit buttons next to the headings allow you to edit each section separately.
+
+.. note::
+
+   For example, if you click on a `<h2>`, every subsequent `<h3>`, `<h4>`, etc. up to the next `<h2>` or `<h1>` will be copied into the editor.
+   It is important to note that this functionality can only be guaranteed if the headings are not nested within other elements.
+
+Macros
+------
+
+The are macros that can be used within the editor:
+
+* `{{toc}}`: Creates a table of contents with links to the headings on the current page. If no headings are found, `{{toc}}` is returned.
+* `{{childPages}}`: Generates a list of links to the child pages of the current page. If no child pages are found, `{{childPages}}` is returned.
+* `{{<wikiPage>#<Heading>}}`: Creates a link to a wiki page within the same wiki by replacing `<wikiPage>` with the name of the
+  wiki page and `<Heading>` with the title of the paragraph. If the wiki page is not found, `{{<wikiPage>#<Heading>}}` is returned.
+* `{{<pageSlug>/<wikiPage>#<Heading>}}`: Creates a link to a wiki page in another wiki by replacing `<pageSlug>` with the page slug of the wiki, `<wikiPage>` with
+  the name of the wiki page and `<Heading>` with the title of the paragraph. If the wiki page is not found, `{{<pageSlug>/<wikiPage>#<Heading>}}` is returned.
+* `{{collapse([Text is shown])}}`
+  Text is hidden.
+  `{{collapse}}`
+  Creates a toggleable element, that will show/hide text. Text within `[ ]` is optional.
+
+.. important::
+
+   `{{toc}}`, `{{childPages}}`, `{{collapse([Text is shown])}}`, `{{collapse}}` must be used on a separate line or with their own `<p>` tag to ensure their functionality.
+
+.. important::
+
+   `Using links (`{{<wikiPage>#<Heading>}}` and `{{<pageSlug>/<wikiPage>#<Heading>}}`) `Heading` is used as the link text.
+   This means that `#` must be used, but the heading can theoretically be omitted. In this case the full link will be displayed.
+
+.. tip::
+
+   It is possible to upload images to the editor with "drag and drop" or the upload feature. Images are stored under `fileadmin/wiki/<pageSlug>/img`.
+
+History
+-------
+
+The option `History` in the dropdown menu provides a list of changes made to the wiki page. If the author is `anonymous`,
+this means that the changes were made by a public user without a `{{feUser:T}}` account.
+
+Print
+-----
+
+The option `Print` in the dropdown menu creates a PDF of the wiki page.
diff --git a/Documentation/index.rst b/Documentation/index.rst
index 9257f36f174572e4431e13703327a31e0774bb5d..99b484ece205f73cfbed303749515809ea5920b0 100644
--- a/Documentation/index.rst
+++ b/Documentation/index.rst
@@ -83,6 +83,7 @@ This documentation is for the TYPO3 extension **qfq**.
    Form
    Report
    REST
+   Wiki
    System
    ApplicationTest
    GeneralTips
diff --git a/extension/Classes/Core/Constants.php b/extension/Classes/Core/Constants.php
index d95056646ed60ca07e316aa9a3e44c38d65baa2f..8bcd2e8a4482908a9625e63a810c2ea63e623ddc 100644
--- a/extension/Classes/Core/Constants.php
+++ b/extension/Classes/Core/Constants.php
@@ -2480,6 +2480,51 @@ const I_UNCHECKED = 'unchecked';
 const HTTP_STATUS = 'http-status';
 const ERROR_MESSAGE = 'error-message';
 
+// Author: Jan Haller
+// Wiki
+const HTML_TAG_H1 = 'h1';
+const HTML_TAG_H2 = 'h2';
+const HTML_TAG_H3 = 'h3';
+const HTML_TAG_H4 = 'h4';
+const HTML_TAG_H5 = 'h5';
+const HTML_TAG_H6 = 'h6';
+const HTML_TAG_HTML = 'html';
+const HTML_TAG_BODY = 'body';
+const WIKI_PAGE_COLUMN_ID = 'id';
+const WIKI_PAGE_COLUMN_CONTENT = 'content';
+const WIKI_PAGE_COLUMN_PAGE_SLUG = 'pageSlug';
+const WIKI_PAGE_COLUMN_NAME = 'name';
+const WIKI_PAGE_COLUMN_WP_ID_PARENT = 'wpIdParent';
+const WIKI_PAGE_COLUMN_WP_ID_CURRENT = 'wpIdCurrent';
+const WIKI_PAGE_COLUMN_RO_USER = 'roUser';
+const WIKI_PAGE_COLUMN_RO_GROUP = 'roGroup';
+const WIKI_PAGE_COLUMN_RO_PUBLIC = 'roPublic';
+const WIKI_PAGE_COLUMN_RW_USER = 'rwUser';
+const WIKI_PAGE_COLUMN_RW_GROUP = 'rwGroup';
+const WIKI_PAGE_COLUMN_RW_PUBLIC = 'rwPublic';
+const WIKI_PAGE_COLUMN_PAGE_LOCK = 'pageLock';
+const WIKI_PAGE_COLUMN_AUTHOR = 'author';
+const WIKI_PAGE_TABLE_NAME = 'WikiPage';
+const WIKI_PAGE_SECTION_ID = 'sectionId';
+const WIKI_PAGE_SECTION_ID_START = 'sectionIdStart';
+const WIKI_PAGE_SECTION_ID_END = 'sectionIdEnd';
+const WIKI_PAGE_HEADING_TITLE = 'headingTitle';
+const WIKI_RENDER_MODE = 'renderMode';
+const WIKI_PRINT_MODE = 'printMode';
+const WIKI_TOKEN_INCLUDE_TAG = 'includeTag';
+const WIKI_TOKEN_SECTION = 'section';
+const WIKI_TOKEN_ACTION = 'action';
+const WIKI_TOKEN_WIKI_ACCESS = 'wikiAccess';
+const WIKI_TOKEN_CHILD_PAGES = '{{childPages}}';
+const WIKI_TOKEN_TABLE_OF_CONTENTS = '{{toc}}';
+const WIKI_TOKEN_EDIT_SECTION = 'editSection';
+const WIKI_TOKEN_EDIT_ALL = 'editAll';
+const WIKI_TOKEN_ACCESS_READ_ONLY = 'ro';
+const WIKI_TOKEN_ACCESS_READ_WRITE = 'rw';
+const WIKI_TOKEN_ACCESS_OFF = 'off';
+const WIKI_TOKEN_ACCESS_ON = 'on';
+// End author
+
 // Misc
 const BASE_URL_FAKE = 'http://i_am_set_in_constants_php/qfq/';
 const HTTP_EXAMPLE_COM = 'http://example.com/';
diff --git a/extension/Classes/Core/Database/DatabaseUpdateData.php b/extension/Classes/Core/Database/DatabaseUpdateData.php
index 784e50bb3a625bc2a858fd0ff419042dec52c076..8777116f124216b7ef2d06f09800a89e0f9c4ff8 100644
--- a/extension/Classes/Core/Database/DatabaseUpdateData.php
+++ b/extension/Classes/Core/Database/DatabaseUpdateData.php
@@ -227,9 +227,14 @@ $UPDATE_ARRAY = array(
         "ALTER TABLE `FormSubmitLog` ADD INDEX IF NOT EXISTS `createdFeUserFormId` (`created`, `feUser`, `formId`);",
     ],
 
-    '23.12.0' => [
-        "CREATE TABLE IF NOT EXISTS `FileUpload` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `pathFileName` VARCHAR(512) NOT NULL, `grId` INT(11) NOT NULL DEFAULT '0', `xId` INT(11) NOT NULL DEFAULT '0', `uploadId` INT(11) NOT NULL, `size` VARCHAR(32) NOT NULL DEFAULT '0' COMMENT 'Filesize in bytes', `type` VARCHAR(64) NOT NULL DEFAULT '', `ord` INT(11) NOT NULL DEFAULT '0', `modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `uploadId` (`uploadId`), KEY `pathFileNameGrIdXidUploadId` (`pathFileName`, `grId`, `xId`, `uploadId`) USING BTREE ) ENGINE = InnoDB DEFAULT CHARSET = utf8 AUTO_INCREMENT = 0;"
+    '24.1.0.rc1' => [
+        "CREATE TABLE IF NOT EXISTS `FileUpload` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `pathFileName` VARCHAR(512) NOT NULL, `grId` INT(11) NOT NULL DEFAULT '0', `xId` INT(11) NOT NULL DEFAULT '0', `uploadId` INT(11) NOT NULL, `size` VARCHAR(32) NOT NULL DEFAULT '0' COMMENT 'Filesize in bytes', `type` VARCHAR(64) NOT NULL DEFAULT '', `ord` INT(11) NOT NULL DEFAULT '0', `modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `uploadId` (`uploadId`), KEY `pathFileNameGrIdXidUploadId` (`pathFileName`, `grId`, `xId`, `uploadId`) USING BTREE ) ENGINE = InnoDB DEFAULT CHARSET = utf8 AUTO_INCREMENT = 0;",
+        "CREATE TABLE IF NOT EXISTS `WikiPage` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `wpIdParent` INT(11) DEFAULT NULL, `pageSlug` VARCHAR(255) NOT NULL, `name` VARCHAR(255) NOT NULL, `content` TEXT NOT NULL, `author` VARCHAR(64) NOT NULL, `roUser` VARCHAR(512) NOT NULL, `roGroup` VARCHAR(512) NOT NULL, `roPublic` ENUM ('on','off') NOT NULL, `rwUser` VARCHAR(512) NOT NULL, `rwGroup` VARCHAR(512) NOT NULL, `rwPublic` ENUM ('on','off') NOT NULL, `pageLock` VARCHAR(64) NOT NULL, `imageBorder` ENUM ('none','1pxblack','shadow') NOT NULL DEFAULT 'none', `wpIdCurrent` INT(11) DEFAULT NULL, `modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8 AUTO_INCREMENT = 0;",
+        "CREATE TABLE IF NOT EXISTS `WikiAttachment` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `wpId` INT(11) NOT NULL, `feUser` VARCHAR(64) NOT NULL, `pathFileName` VARCHAR(512) NOT NULL, `modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8 AUTO_INCREMENT = 0;",
+        // Save chat conversations.
+        "CREATE TABLE IF NOT EXISTS `Chat` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `xId` INT(11) NOT NULL DEFAULT '0', `pIdCreator` INT(11) NOT NULL DEFAULT '0', `pIdTeam` INT(11) NOT NULL DEFAULT '0', `cIdTopic` INT(11) NOT NULL DEFAULT '0', `cIdThread` INT(11) NOT NULL DEFAULT '0', `cIdTag` INT(11) NOT NULL DEFAULT '0', `cIdLastRead` INT(11) NOT NULL DEFAULT '0', `xGrIdStatus` INT(11) NOT NULL DEFAULT '0', `type` ENUM('message','topic','tag','reminder','read') NOT NULL DEFAULT 'message', `message` TEXT NOT NULL DEFAULT '', `username` VARCHAR(128) NOT NULL DEFAULT '', `emoticon` VARCHAR(255) NOT NULL DEFAULT '', `reference` VARCHAR(255) NOT NULL DEFAULT '', `modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `xId` (`xId`)) ENGINE = InnoDB DEFAULT CHARSET = utf8;"
     ],
+
 );
 
 
diff --git a/extension/Classes/Core/QuickFormQuery.php b/extension/Classes/Core/QuickFormQuery.php
index 9b35765d334e5527f520a68dc9a526049c6d59ba..601f6cc295c547e4ea770c1fbe53d02aede2df74 100644
--- a/extension/Classes/Core/QuickFormQuery.php
+++ b/extension/Classes/Core/QuickFormQuery.php
@@ -588,8 +588,8 @@ class QuickFormQuery {
             }
         }
 
-        //Change recordId from Multiform to 0 - No row exception possible
-        if ($this->formSpec[F_MULTI_MODE] !== 'none' && isset($this->formSpec[F_MULTI_MODE])) {
+        // Change recordId from Multiform to 0 - No row exception possible
+        if (($this->formSpec[F_MULTI_MODE] ?? '') !== 'none') {
             $recordId = 0;
             $this->store->setVar(SIP_RECORD_ID, $recordId, STORE_SIP);
         }
diff --git a/extension/Classes/Sql/function.sql b/extension/Classes/Sql/function.sql
index 17856c81d53ccf283e0344408efdd10112af0c82..9d7de12ab10a984ef93dd0fffa6851a8c55d71ac 100644
--- a/extension/Classes/Sql/function.sql
+++ b/extension/Classes/Sql/function.sql
@@ -401,6 +401,21 @@ BEGIN
     RETURN @highlighted_text;
 END;
 
+###
+#
+# QMANR(manr)
+# Matrikel-Nr. format: 12-345-567
+#
+DROP FUNCTION IF EXISTS `QMANR`;
+CREATE FUNCTION `QMANR`(manr VARCHAR(255)) RETURNS VARCHAR(255) CHARSET utf8mb4
+    DETERMINISTIC
+    SQL SECURITY INVOKER
+BEGIN
+	DECLARE matrikelNr VARCHAR(255);
+    SET matrikelNr = CONCAT(SUBSTRING(manr,1,2), '-', SUBSTRING(manr,3,3), '-', SUBSTRING(manr,6,3));
+RETURN matrikelNr;
+END;
+
 ###
 #
 # QIFPREPEND(separator, input)
diff --git a/extension/Classes/Sql/qfqDefaultTables.sql b/extension/Classes/Sql/qfqDefaultTables.sql
index 6b8b90413af80b5797a1268f31f5255cb420a56d..3d04baf98a3543fcea6a99112a30691e768dbbb9 100644
--- a/extension/Classes/Sql/qfqDefaultTables.sql
+++ b/extension/Classes/Sql/qfqDefaultTables.sql
@@ -310,26 +310,68 @@ CREATE TABLE IF NOT EXISTS `FileUpload`
     DEFAULT CHARSET = utf8
     AUTO_INCREMENT = 0;
 
+CREATE TABLE IF NOT EXISTS `WikiPage`
+(
+    `id`          INT(11)                           NOT NULL AUTO_INCREMENT,
+    `wpIdParent`  INT(11)                                    DEFAULT NULL,
+    `pageSlug`    VARCHAR(255)                      NOT NULL,
+    `name`        VARCHAR(255)                      NOT NULL,
+    `content`     TEXT                              NOT NULL,
+    `author`      VARCHAR(64)                       NOT NULL,
+    `roUser`      VARCHAR(512)                      NOT NULL,
+    `roGroup`     VARCHAR(512)                      NOT NULL,
+    `roPublic`    ENUM ('on','off')                 NOT NULL,
+    `rwUser`      VARCHAR(512)                      NOT NULL,
+    `rwGroup`     VARCHAR(512)                      NOT NULL,
+    `rwPublic`    ENUM ('on','off')                 NOT NULL,
+    `pageLock`    VARCHAR(64)                       NOT NULL,
+    `imageBorder` ENUM ('none','1pxblack','shadow') NOT NULL DEFAULT 'none',
+    `wpIdCurrent` INT(11)                                    DEFAULT NULL,
+    `modified`    DATETIME                          NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    `created`     DATETIME                          NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+    PRIMARY KEY (`id`)
+)
+    ENGINE = InnoDB
+    DEFAULT CHARSET = utf8
+    AUTO_INCREMENT = 0;
+
+CREATE TABLE IF NOT EXISTS `WikiAttachment`
+(
+    `id`           INT(11)      NOT NULL AUTO_INCREMENT,
+    `wpId`         INT(11)      NOT NULL,
+    `feUser`       VARCHAR(64)  NOT NULL,
+    `pathFileName` VARCHAR(512) NOT NULL,
+    `modified`     DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    `created`      DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+    PRIMARY KEY (`id`)
+)
+    ENGINE = InnoDB
+    DEFAULT CHARSET = utf8
+    AUTO_INCREMENT = 0;
+
+
 # Used to save chat conversations.
 CREATE TABLE IF NOT EXISTS `Chat`
 (
-    `id`            INT(11)                                             NOT NULL AUTO_INCREMENT,
-    `xId`           INT(11)                                             NOT NULL DEFAULT '0',
-    `pIdCreator`    INT(11)                                             NOT NULL DEFAULT '0',
-    `pIdTeam`       INT(11)                                             NOT NULL DEFAULT '0',
-    `cIdTopic`      INT(11)                                             NOT NULL DEFAULT '0',
-    `cIdThread`     INT(11)                                             NOT NULL DEFAULT '0',
-    `cIdTag`        INT(11)                                             NOT NULL DEFAULT '0',
-    `cIdLastRead`   INT(11)                                             NOT NULL DEFAULT '0',
-    `xGrIdStatus`   INT(11)                                             NOT NULL DEFAULT '0',
-    `type`          ENUM('message','topic','tag','reminder','read')     NOT NULL DEFAULT 'message',
-    `message`       TEXT                                                NOT NULL DEFAULT '',
-    `username`      VARCHAR(128)                                        NOT NULL DEFAULT '',
-    `emoticon`      VARCHAR(255)                                        NOT NULL DEFAULT '',
-    `reference`     VARCHAR(255)                                        NOT NULL DEFAULT '',
-    `modified`      DATETIME                                            NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-    `created`       DATETIME                                            NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    `id`          INT(11)                                          NOT NULL AUTO_INCREMENT,
+    `xId`         INT(11)                                          NOT NULL DEFAULT '0',
+    `pIdCreator`  INT(11)                                          NOT NULL DEFAULT '0',
+    `pIdTeam`     INT(11)                                          NOT NULL DEFAULT '0',
+    `cIdTopic`    INT(11)                                          NOT NULL DEFAULT '0',
+    `cIdThread`   INT(11)                                          NOT NULL DEFAULT '0',
+    `cIdTag`      INT(11)                                          NOT NULL DEFAULT '0',
+    `cIdLastRead` INT(11)                                          NOT NULL DEFAULT '0',
+    `xGrIdStatus` INT(11)                                          NOT NULL DEFAULT '0',
+    `type`        ENUM ('message','topic','tag','reminder','read') NOT NULL DEFAULT 'message',
+    `message`     TEXT                                             NOT NULL DEFAULT '',
+    `username`    VARCHAR(128)                                     NOT NULL DEFAULT '',
+    `emoticon`    VARCHAR(255)                                     NOT NULL DEFAULT '',
+    `reference`   VARCHAR(255)                                     NOT NULL DEFAULT '',
+    `modified`    DATETIME                                         NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    `created`     DATETIME                                         NOT NULL DEFAULT CURRENT_TIMESTAMP,
     PRIMARY KEY (`id`),
     KEY `xId` (`xId`)
 ) ENGINE = InnoDB
-  DEFAULT CHARSET = utf8mb4;
\ No newline at end of file
+  DEFAULT CHARSET = utf8;
\ No newline at end of file
diff --git a/extension/Resources/Private/Form/wikiEditor.json b/extension/Resources/Private/Form/wikiEditor.json
new file mode 100644
index 0000000000000000000000000000000000000000..0d4e180fce7a025e9201a02d50b0c06449731e4d
--- /dev/null
+++ b/extension/Resources/Private/Form/wikiEditor.json
@@ -0,0 +1,249 @@
+{
+  "title": "{{SELECT IF('{{id:R0}}' = 0, \"Edit wiki content\", \"Add wiki content\") }}",
+  "noteInternal": "wiki content editor",
+  "tableName": "WikiPage",
+  "primaryKey": "",
+  "permitNew": "sip",
+  "permitEdit": "sip",
+  "restMethod": "",
+  "escapeTypeDefault": "c",
+  "render": "bootstrap",
+  "requiredParameterNew": "",
+  "requiredParameterEdit": "",
+  "dirtyMode": "exclusive",
+  "showButton": "close,save",
+  "multiMode": "none",
+  "multiSql": "",
+  "multiDetailForm": "",
+  "multiDetailFormParameter": "",
+  "forwardMode": "url-sip",
+  "forwardPage": "url-sip|{{pageSlug:T}}&wpId={{id:R0}}&action={{action:SE}}&wpIdPrevious={{wpIdPrevious:S0}}&sectionId={{sectionId:S0}}&sectionIdEnd={{sectionIdEnd:S0}}&headingTitle={{headingTitle:SE}}",
+  "labelAlign": "default",
+  "bsLabelColumns": "",
+  "bsInputColumns": "",
+  "bsNoteColumns": "",
+  "parameter": "",
+  "parameterLanguageA": "",
+  "parameterLanguageB": "",
+  "parameterLanguageC": "",
+  "parameterLanguageD": "",
+  "recordLockTimeoutSeconds": 900,
+  "deleted": "no",
+  "modified": "2023-07-18 13:44:34",
+  "created": "2023-05-10 10:48:25",
+  "FormElement_ff": [
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "myEditorPill",
+      "label": "Editor",
+      "mode": "show",
+      "modeSql": "",
+      "class": "container",
+      "type": "pill",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 10,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "",
+      "bsInputColumns": "",
+      "bsNoteColumns": "",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "",
+      "sql1": "",
+      "parameter": "",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-18 13:54:08",
+      "created": "2023-07-18 15:54:02"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "content",
+      "label": "",
+      "mode": "show",
+      "modeSql": "",
+      "class": "native",
+      "type": "editor",
+      "subrecordOption": "",
+      "encode": "none",
+      "checkType": "all",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 20,
+      "tabindex": 0,
+      "size": "400,600",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "0",
+      "bsInputColumns": "12",
+      "bsNoteColumns": "0",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "",
+      "sql1": "",
+      "parameter": "editor-plugins=code link lists searchreplace table textcolor textpattern visualchars image,paste\r\neditor-toolbar=code searchreplace undo redo | styleselect link table | bullist numlist outdent indent | forecolor backcolor bold italic image\r\neditor-menubar=false\r\neditor-statusbar=false\r\neditor-paste_data_images=true\r\nfileUploadPath=fileadmin\/wiki{{pageSlug:T}}\/img\r\nextraButtonInfo=<strong>Macros<\/strong><\/br><ul><li>{{toc}}<\/br>Creates a table of contents with links to the headings on the current page. If no headings are found, '{{toc}}' is returned.<\/li><li>{{childPages}}<\/br> Generates a list of links to the child pages of the current page. If no child pages are found, '{{childPages}}' is returned.<\/li><li>{{<i>wikiPage<\/i>#<i>Heading<\/i>}}<\/br>Creates a link to a wiki page within the same wiki by replacing <i>wikiPage<\/i> with the name of the wiki page and <i>Heading<\/i> with the title of the paragraph. If the wiki page is not found, '{{<i>wikiPage<\/i>#<i>Heading<\/i>}}' is returned.<\/li><li>{{<i>pageSlug<\/i>\/<i>wikiPage<\/i>#<i>Heading<\/i>}}<\/br>Creates a link to a wiki page in another wiki by replacing <i>pageSlug<\/i> with the page slug of the wiki, <i>wikiPage<\/i> with the name of the wiki page and <i>Heading<\/i> with the title of the paragraph. If the wiki page is not found, '{{<i>pageSlug<\/i>\/<i>wikiPage<\/i>#<i>Heading<\/i>}}' is returned.<\/li><li>{{collapse(<i>[Text is shown]<\/i>)}}<\/br>Text is hidden.<\/br>{{collapse}}<\/br>Creates a toggleable element, that will show\/hide text. Text within <i>[ ]<\/i> is optional.<\/li><\/ul><\/br>Important:<\/br>{{toc}}, {{childPages}}, {{collapse([Text is shown])}}, {{collapse}} must be used on a separate line to ensure their functionality.<\/br><\/br><strong>Link to file<\/strong><\/br><ol><li>Copy the path from the \"Upload\" tab<\/li><li>Insert a link in the editor<\/li><li>Paste the path into the field \"Url\"<\/li><\/ol><\/br>For further information, visit <a href=\"https:\/\/docs.qfq.io\/en\/master\/Wiki.html\" target=\"_blank\">QFQ documentation<\/a>.",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-19 12:19:44",
+      "created": "2023-05-10 11:02:27",
+      "containerName_ff": "myEditorPill"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "myLoadContent",
+      "label": "",
+      "mode": "hidden",
+      "modeSql": "",
+      "class": "native",
+      "type": "note",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 30,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "",
+      "bsInputColumns": "",
+      "bsNoteColumns": "",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "#!report\r\n#\r\n#\r\n\r\n10 {\r\n  sql = SELECT 'F:typo3conf\/ext\/qfq\/wikiScript.php|call:loadWikiContent|arg:sectionId={{sectionId:S0}}&id={{wpIdPrevious:S0}}&action={{action:SE}}' AS _script\r\n  \t\t\tFROM DUAL\r\n        WHERE '{{action:SE}}' = 'editSection'\r\n          OR '{{action:SE}}' = 'editAll'\r\n  \r\n  rbeg = <script>window.onload = function() { tinymce.activeEditor.setContent(`\r\n  rend = `);};<\/script>\r\n}",
+      "sql1": "",
+      "parameter": "",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-19 13:24:06",
+      "created": "2023-05-25 12:30:33",
+      "containerName_ff": "myEditorPill"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "myUploadPill",
+      "label": "Upload",
+      "mode": "show",
+      "modeSql": "",
+      "class": "container",
+      "type": "pill",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 40,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "",
+      "bsInputColumns": "",
+      "bsNoteColumns": "",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "",
+      "sql1": "",
+      "parameter": "",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-18 15:54:52",
+      "created": "2023-07-18 15:54:47"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "myUploadReport",
+      "label": "",
+      "mode": "show",
+      "modeSql": "",
+      "class": "native",
+      "type": "note",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 50,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "0",
+      "bsInputColumns": "12",
+      "bsNoteColumns": "0",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "#!report\r\n#\r\n#\r\n\r\n# \"Fake subrecord\" listing all uploads of this wiki page \r\n10 {\r\n  sql = SELECT CONCAT('p:{{pageSlug:T}}&form=wikiUpload&r=', id, '|s|b|E') AS _link\r\n\t\t\t\t\t\t\t, CONCAT('F:', pathFileName\r\n\t\t\t\t\t\t\t\t\t\t\t, '|t:', QLEFT(SUBSTRING_INDEX(pathFileName, '\/', -1), 30)\r\n\t\t\t\t\t\t\t\t\t\t\t, '|d:', SUBSTRING_INDEX(pathFileName, '\/', -1)\r\n\t\t\t\t\t\t\t\t\t\t\t, '|o:', SUBSTRING_INDEX(pathFileName, '\/', -1)\r\n\t\t\t\t\t\t\t\t\t\t\t, '|s|b|g:_blank|G:glyphicon glyphicon-paperclip|M:file'\r\n\t\t\t\t\t\t\t\t\t\t\t) AS _link\r\n\t\t\t\t\t\t\t, QIFEMPTY(feUser, 'anonymous')\r\n\t\t\t\t\t\t\t, created\r\n\t\t\t\t\t\t\t, CONCAT('y:', pathFileName) AS _yank\r\n\t\t\t\t\t\t\t, CONCAT('U:table=WikiAttachment&r=', id, '|q:Do you want to delete <i>', SUBSTRING_INDEX(pathFileName, '\/', -1), '<\/i> ?') AS _paged\r\n\t\t\t\tFROM WikiAttachment\r\n        WHERE wpId = '{{wpIdPrevious:S0}}'\r\n          AND pathFileName != ''\r\n        ORDER BY modified ASC\r\n\r\n  shead = <div style=\"margin-right:-5px; margin-left:-15px;\">\r\n\t\t\t\t\t\t<table class=\"table table-hover qfq-table-100 qfq-subrecord-table qfq-color-grey-2\">\r\n\t\t\t  \t\t<caption class=\"qfq-subrecord-title\">Files<\/caption>\r\n              <thead>\r\n                <tr>\r\n                  <th style=\"width: 40px;\">{{'p:{{pageSlug:T}}&form=wikiUpload&r=0&wpId={{wpIdPrevious:S0}}|s|b|N' AS _link}}<\/th>\r\n  head =\t\t\t\t\t<th>File<\/th>\r\n                  <th>User<\/th>\r\n                  <th>Date<\/th>\r\n                  <th style=\"width: 40px;\">Copy path<\/th><th style=\"width: 40px;\"><\/th>\r\n                <\/tr>\r\n              <\/thead>\r\n              <tbody>\r\n  stail = \t<\/table>\r\n          <\/div>\r\n  tail = <\/tbody>\r\n  fbeg = <td>\r\n  fend = <\/td>\r\n  rbeg = <tr>\r\n  rend = <\/tr>      \r\n}",
+      "sql1": "",
+      "parameter": "",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-19 13:36:48",
+      "created": "2023-07-18 15:55:18",
+      "containerName_ff": "myUploadPill"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/extension/Resources/Private/Form/wikiPage.json b/extension/Resources/Private/Form/wikiPage.json
new file mode 100644
index 0000000000000000000000000000000000000000..a8a0e222b999a86ad5f59b6e6b788b24f42e2095
--- /dev/null
+++ b/extension/Resources/Private/Form/wikiPage.json
@@ -0,0 +1,718 @@
+{
+  "title": "{{SELECT IF('{{id:R0}}' = 0, \"Add new wiki page\", \"Edit wiki page\") }}",
+  "noteInternal": "wikiPage Editor",
+  "tableName": "WikiPage",
+  "primaryKey": "",
+  "permitNew": "sip",
+  "permitEdit": "sip",
+  "restMethod": "",
+  "escapeTypeDefault": "c",
+  "render": "bootstrap",
+  "requiredParameterNew": "",
+  "requiredParameterEdit": "",
+  "dirtyMode": "exclusive",
+  "showButton": "delete,close,save",
+  "multiMode": "none",
+  "multiSql": "",
+  "multiDetailForm": "",
+  "multiDetailFormParameter": "",
+  "forwardMode": "url",
+  "forwardPage": "url|{{pageSlug:T}}?wpId={{id:R0}}",
+  "labelAlign": "default",
+  "bsLabelColumns": "",
+  "bsInputColumns": "",
+  "bsNoteColumns": "",
+  "parameter": "fillStoreVar = {{!SELECT id AS wpIdRoot FROM WikiPage WHERE ISNULL(wpIdParent) AND ISNULL(wpIdCurrent) AND pageSlug = '{{pageSlug:T}}'}}",
+  "parameterLanguageA": "",
+  "parameterLanguageB": "",
+  "parameterLanguageC": "",
+  "parameterLanguageD": "",
+  "recordLockTimeoutSeconds": 900,
+  "deleted": "no",
+  "modified": "2023-07-19 10:58:47",
+  "created": "2023-05-31 13:18:26",
+  "FormElement_ff": [
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "myGeneralPill",
+      "label": "General",
+      "mode": "show",
+      "modeSql": "",
+      "class": "container",
+      "type": "pill",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 10,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "",
+      "bsInputColumns": "",
+      "bsNoteColumns": "",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "",
+      "sql1": "",
+      "parameter": "",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-18 16:13:31",
+      "created": "2023-05-31 13:40:16"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "name",
+      "label": "Name",
+      "mode": "show",
+      "modeSql": "",
+      "class": "native",
+      "type": "text",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 20,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "",
+      "bsInputColumns": "",
+      "bsNoteColumns": "",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "",
+      "sql1": "",
+      "parameter": "",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-18 13:44:34",
+      "created": "2023-05-31 13:19:04",
+      "containerName_ff": "myGeneralPill"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "pageSlug",
+      "label": "Page slug",
+      "mode": "readonly",
+      "modeSql": "",
+      "class": "native",
+      "type": "text",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 30,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "",
+      "bsInputColumns": "",
+      "bsNoteColumns": "",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "{{pageSlug:T}}",
+      "sql1": "",
+      "parameter": "",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-18 13:44:34",
+      "created": "2023-05-31 13:23:21",
+      "containerName_ff": "myGeneralPill"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "wpIdParent",
+      "label": "Parent page",
+      "mode": "show",
+      "modeSql": "",
+      "class": "native",
+      "type": "select",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 40,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "",
+      "bsInputColumns": "",
+      "bsNoteColumns": "",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "{{wpIdParent:SE}}",
+      "sql1": "{{!SELECT id AS id, name AS label FROM WikiPage WHERE pageSlug = '{{pageSlug:T}}' AND ISNULL(wpIdCurrent) AND id != '{{id:R0}}' AND IF('{{id:R0}}' = '{{wpIdRoot:V0}}', 0, 1)\r\nGROUP BY id}}",
+      "parameter": "",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-18 13:44:34",
+      "created": "2023-05-31 13:19:31",
+      "containerName_ff": "myGeneralPill"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "myChildPages",
+      "label": "Child pages",
+      "mode": "show",
+      "modeSql": "",
+      "class": "native",
+      "type": "subrecord",
+      "subrecordOption": "edit,delete,new",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 50,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "",
+      "bsInputColumns": "",
+      "bsNoteColumns": "",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "",
+      "sql1": "{{!SELECT id AS _id, name AS Name, QIFEMPTY(author, 'anonymous') AS Author, CONCAT('p:{{pageSlug:T}}&wpId=', id, '|b|G:glyphicon glyphicon-new-window|t:Show|g:_blank') AS 'link|Link' FROM WikiPage WHERE wpIdParent = '{{id:R0}}' AND ISNULL(wpIdCurrent) AND (author = '{{feUser:TE}}' OR '{{beUser:TE}}' != '' OR (author = '' AND '{{feUser:TE}}' != ''))}}",
+      "parameter": "form=wikiPage",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-19 12:39:08",
+      "created": "2023-07-18 16:17:01",
+      "containerName_ff": "myGeneralPill"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "myAccessPill",
+      "label": "Access",
+      "mode": "show",
+      "modeSql": "{{SELECT IF('{{beUser:TE}}' != '' OR ('{{feUser:TE}}' = '{{author:RE}}' AND '{{feUser:TE}}' != '') OR ('{{id:R0'}}' = 0 AND '{{feUser:TE}}' != '' AND '{{beUser:TE}}' != '') , 'show', 'hidden')}}",
+      "class": "container",
+      "type": "pill",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 60,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "",
+      "bsInputColumns": "",
+      "bsNoteColumns": "",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "",
+      "sql1": "",
+      "parameter": "",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-19 12:24:16",
+      "created": "2023-05-31 13:40:30"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "roUser",
+      "label": "Read-only user",
+      "mode": "show",
+      "modeSql": "",
+      "class": "native",
+      "type": "text",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 70,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "",
+      "bsInputColumns": "",
+      "bsNoteColumns": "",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "{{roUser:SE}}",
+      "sql1": "",
+      "parameter": "extraButtonInfo=Comma separated list of usernames (no whitespaces).",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-19 11:04:19",
+      "created": "2023-05-31 13:32:47",
+      "containerName_ff": "myAccessPill"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "roGroup",
+      "label": "Read-only group",
+      "mode": "show",
+      "modeSql": "",
+      "class": "native",
+      "type": "checkbox",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 80,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "3",
+      "bsInputColumns": "9",
+      "bsNoteColumns": "0",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "",
+      "sql1": "{{!SELECT uid AS id, title AS label FROM {{dbNameT3:Y}}.fe_groups}}",
+      "parameter": "checkBoxMode=multi",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-19 11:05:18",
+      "created": "2023-05-31 13:31:51",
+      "containerName_ff": "myAccessPill"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "rwUser",
+      "label": "Read-write user",
+      "mode": "show",
+      "modeSql": "",
+      "class": "native",
+      "type": "text",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 90,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "",
+      "bsInputColumns": "",
+      "bsNoteColumns": "",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "{{rwUser:SE}}",
+      "sql1": "",
+      "parameter": "extraButtonInfo=Comma separated list of usernames (no whitespaces).",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-19 11:04:54",
+      "created": "2023-05-31 13:34:05",
+      "containerName_ff": "myAccessPill"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "rwGroup",
+      "label": "Read-write group",
+      "mode": "show",
+      "modeSql": "",
+      "class": "native",
+      "type": "checkbox",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 100,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "3",
+      "bsInputColumns": "9",
+      "bsNoteColumns": "0",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "",
+      "sql1": "{{!SELECT uid AS id, title AS label FROM {{dbNameT3:Y}}.fe_groups}}",
+      "parameter": "checkBoxMode=multi",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-19 11:05:45",
+      "created": "2023-05-31 13:34:22",
+      "containerName_ff": "myAccessPill"
+    },
+    {
+      "dynamicUpdate": "yes",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "myPublicAccess",
+      "label": "Public",
+      "mode": "show",
+      "modeSql": "",
+      "class": "native",
+      "type": "radio",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 110,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "3",
+      "labelAlign": "default",
+      "bsLabelColumns": "3",
+      "bsInputColumns": "9",
+      "bsNoteColumns": "0",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "{{SELECT IF('{{roPublic:RSE}}' = 'off' AND '{{rwPublic:RSE}}' = 'off', 'off', IF('{{rwPublic:RSE}}' = 'on', 'rw', 'ro'))}}",
+      "sql1": "",
+      "parameter": "itemList = ro:Read-only,rw:Read-write,off:Off",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-19 11:06:49",
+      "created": "2023-05-31 13:33:05",
+      "containerName_ff": "myAccessPill"
+    },
+    {
+      "dynamicUpdate": "yes",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "pageLock",
+      "label": "Page Lock",
+      "mode": "show",
+      "modeSql": "",
+      "class": "native",
+      "type": "checkbox",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 120,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "",
+      "bsInputColumns": "",
+      "bsNoteColumns": "",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "",
+      "sql1": "",
+      "parameter": "checked = '{{feUser:TE}}'\r\nunchecked = '0'",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-18 17:16:06",
+      "created": "2023-05-31 13:35:56",
+      "containerName_ff": "myAccessPill"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "myActionUpdateAfterFirstInsert",
+      "label": "",
+      "mode": "show",
+      "modeSql": "",
+      "class": "action",
+      "type": "afterInsert",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 130,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "",
+      "bsInputColumns": "",
+      "bsNoteColumns": "",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "",
+      "sql1": "",
+      "parameter": "sqlAfter = {{UPDATE WikiPage SET pageSlug = '{{pageSlug:T}}', author = '{{feUser:TE}}' WHERE id = '{{id:R0}}'}}",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-19 12:26:57",
+      "created": "2023-05-31 13:59:11"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "myActionUpdatePublicAccess",
+      "label": "",
+      "mode": "show",
+      "modeSql": "",
+      "class": "action",
+      "type": "afterSave",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 140,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "",
+      "bsInputColumns": "",
+      "bsNoteColumns": "",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "",
+      "sql1": "",
+      "parameter": "sqlAfter = {{UPDATE WikiPage SET roPublic = (SELECT IF('{{myPublicAccess:FE:alnumx}}' = 'off' OR '{{myPublicAccess:FE:alnumx}}' = 'rw', 'off', 'on')), rwPublic = (SELECT IF('{{myPublicAccess:FE:alnumx}}' = 'off' OR '{{myPublicAccess:FE:alnumx}}' = 'ro', 'off', 'on')) WHERE id = '{{id:R0}}'}}",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-18 17:01:48",
+      "created": "2023-06-01 10:33:29"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "myActionBeforeDelete",
+      "label": "",
+      "mode": "show",
+      "modeSql": "",
+      "class": "action",
+      "type": "beforeDelete",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 150,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "",
+      "bsInputColumns": "",
+      "bsNoteColumns": "",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "",
+      "sql1": "",
+      "parameter": "sqlAfter = {{DELETE FROM WikiPage WHERE wpIdCurrent = '{{id:R0}}' AND 0 = (SELECT COUNT(wp.id) FROM WikiPage AS wp WHERE wp.wpIdParent = '{{id:R0}}')}}",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-18 17:02:01",
+      "created": "2023-06-06 13:23:22"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "myActionValidate",
+      "label": "",
+      "mode": "show",
+      "modeSql": "",
+      "class": "action",
+      "type": "beforeDelete",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 160,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "",
+      "bsInputColumns": "",
+      "bsNoteColumns": "",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "",
+      "sql1": "",
+      "parameter": "sqlValidate = {{!SELECT id FROM WikiPage WHERE wpIdParent = '{{id:R0}}'}}\r\nexpectRecords = 0\r\nmessageFail = Record cannot be deleted because it has child pages.",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-18 17:02:09",
+      "created": "2023-06-06 13:38:58"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/extension/Resources/Private/Form/wikiUpload.json b/extension/Resources/Private/Form/wikiUpload.json
new file mode 100644
index 0000000000000000000000000000000000000000..c188b52dc0e46c83d64fdee44e5948cef154806e
--- /dev/null
+++ b/extension/Resources/Private/Form/wikiUpload.json
@@ -0,0 +1,162 @@
+{
+  "title": "{{SELECT IF('{{id:R0}}' = 0, 'Upload new file', 'Edit uploaded file')}}",
+  "noteInternal": "",
+  "tableName": "WikiAttachment",
+  "primaryKey": "",
+  "permitNew": "sip",
+  "permitEdit": "sip",
+  "restMethod": "",
+  "escapeTypeDefault": "c",
+  "render": "bootstrap",
+  "requiredParameterNew": "",
+  "requiredParameterEdit": "",
+  "dirtyMode": "exclusive",
+  "showButton": "new,delete,close,save",
+  "multiMode": "none",
+  "multiSql": "",
+  "multiDetailForm": "",
+  "multiDetailFormParameter": "",
+  "forwardMode": "auto",
+  "forwardPage": "",
+  "labelAlign": "default",
+  "bsLabelColumns": "",
+  "bsInputColumns": "",
+  "bsNoteColumns": "",
+  "parameter": "",
+  "parameterLanguageA": "",
+  "parameterLanguageB": "",
+  "parameterLanguageC": "",
+  "parameterLanguageD": "",
+  "recordLockTimeoutSeconds": 900,
+  "deleted": "no",
+  "modified": "2023-07-17 09:14:05",
+  "created": "2023-07-17 09:12:54",
+  "FormElement_ff": [
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "pathFileName",
+      "label": "File",
+      "mode": "show",
+      "modeSql": "",
+      "class": "native",
+      "type": "upload",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 10,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "3",
+      "bsInputColumns": "9",
+      "bsNoteColumns": "0",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "",
+      "sql1": "",
+      "parameter": "fileDestination=fileadmin\/wiki{{pageSlug:T}}\/attachments\/{{filename}}\r\naccept=*\r\ndownloadButton=t:{{filenameOnly:VE}}|g:_blank",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-18 14:53:20",
+      "created": "2023-07-17 10:44:13"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "wpId",
+      "label": "Wiki page",
+      "mode": "show",
+      "modeSql": "",
+      "class": "native",
+      "type": "select",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 20,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "",
+      "bsInputColumns": "",
+      "bsNoteColumns": "",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "",
+      "sql1": "{{!SELECT id AS id, name AS label FROM WikiPage WHERE ISNULL(wpIdCurrent) AND pageSlug = '{{pageSlug:T}}'}}",
+      "parameter": "",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-17 12:31:13",
+      "created": "2023-07-17 14:29:14"
+    },
+    {
+      "dynamicUpdate": "no",
+      "encryption": "no",
+      "encryptionMethod": "Default",
+      "enabled": "yes",
+      "name": "myAfterSaveUpdate",
+      "label": "",
+      "mode": "show",
+      "modeSql": "",
+      "class": "action",
+      "type": "afterSave",
+      "subrecordOption": "",
+      "encode": "specialchar",
+      "checkType": "auto",
+      "checkPattern": "",
+      "onChange": "",
+      "ord": 30,
+      "tabindex": 0,
+      "size": "",
+      "maxLength": "",
+      "labelAlign": "default",
+      "bsLabelColumns": "",
+      "bsInputColumns": "",
+      "bsNoteColumns": "",
+      "rowLabelInputNote": "row,label,\/label,input,\/input,note,\/note,\/row",
+      "note": "",
+      "adminNote": "",
+      "tooltip": "",
+      "placeholder": "",
+      "value": "",
+      "sql1": "",
+      "parameter": "sqlAfter={{UPDATE WikiAttachment SET feUser = '{{feUser:TE}}' WHERE id = '{{id:R0}}'}}",
+      "parameterLanguageA": "",
+      "parameterLanguageB": "",
+      "parameterLanguageC": "",
+      "parameterLanguageD": "",
+      "clientJs": "",
+      "feGroup": "",
+      "deleted": "no",
+      "modified": "2023-07-19 13:03:27",
+      "created": "2023-07-17 12:10:32"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/extension/Resources/Private/Report/wiki.qfqr b/extension/Resources/Private/Report/wiki.qfqr
new file mode 100644
index 0000000000000000000000000000000000000000..412fd74f5786e500ac340d339a0ba43303904602
--- /dev/null
+++ b/extension/Resources/Private/Report/wiki.qfqr
@@ -0,0 +1,306 @@
+#
+# Wiki
+#
+# {{action:SE::w}}
+# {{wpId:SC0}}
+# {{wpIdPrevious:S0}}
+# {{sectionId:S0}}
+# {{sectionIdEnd:S0}}
+# {{history:CE}}
+# {{print:SE}}
+
+form={{form:SE}}
+
+# Add variables from S- to R-store
+10 {
+  sql = SELECT '{{action:SE::w}}' AS _action
+  			      , '{{wpId:SC0:alnumx}}' AS _wpId
+              , '{{wpIdPrevious:S0}}' AS _wpIdPrevious
+              , '{{sectionId:S0}}' AS _sectionId
+              , '{{sectionIdEnd:S0}}' AS _sectionIdEnd
+              , '{{headingTitle:SE}}' AS _headingTitle
+              , '{{history:CE:alnumx}}' AS _history
+              , '{{print:SE}}' AS _print
+}
+
+# Only executed if record needs to be updated
+20 {
+  sql = SELECT ''
+  		  FROM DUAL
+        WHERE FIND_IN_SET('{{action:RE}}', 'new,editAll,editSection')
+
+  # Copy information from previous record (except author) into new one
+  30 {
+    sql = UPDATE WikiPage AS wp1
+    	    JOIN WikiPage AS wp2
+            ON wp2.id = '{{wpIdPrevious:R0}}'
+          SET wp1.wpIdParent =  wp2.wpIdParent
+            , wp1.pageSlug =  wp2.pageSlug
+            , wp1.name =  wp2.name
+            , wp1.author =  QIFEMPTY('{{feUser:T}}', 'anonymous')
+            , wp1.roUser =  wp2.roUser
+            , wp1.roGroup =  wp2.roGroup
+            , wp1.roPublic =  wp2.roPublic
+            , wp1.rwUser =  wp2.rwUser
+            , wp1.rwGroup =  wp2.rwGroup
+            , wp1.rwPublic =  wp2.rwPublic
+            , wp1.pageLock =  wp2.pageLock
+            , wp1.imageBorder =  wp2.imageBorder
+          WHERE wp1.id = '{{wpId:R0}}'
+  }
+
+  # Update previous versions with the id of the current record
+  40 {
+    sql = UPDATE WikiPage SET wpIdCurrent = '{{wpId:R0}}'
+    	    WHERE IFNULL(wpIdCurrent, id) = '{{wpIdPrevious:R0}}'
+  }
+
+  # Update all versions with the id of the current record
+  50 {
+    sql = UPDATE WikiPage SET wpIdParent = '{{wpId:R0}}'
+    	    WHERE wpIdParent = '{{wpIdPrevious:R0}}'
+  }
+
+  # Update previous uploads with the current id
+  60 {
+  	sql = UPDATE WikiAttachment SET wpId = '{{wpId:R0}}'
+    	    WHERE wpId = '{{wpIdPrevious:R0}}'
+  }
+
+  # Only executed if a section of the previous record has been edited
+  70 {
+    sql = SELECT ''
+    	    FROM DUAL
+          WHERE '{{action:RE}}' = 'editSection'
+
+  	# Executes the function getWikiContentSection() from the PHP-script
+    # Parses the first section of the content until where the editing began
+    80 {
+      sql = SELECT 'F:typo3conf/ext/qfq/wikiScript.php|call:getWikiContentSection|arg:sectionIdStart=1&sectionIdEnd={{sectionId:R0}}&id={{wpIdPrevious:R0}}&includeTag=true' AS _script
+    }
+
+    # Function returns the first section in V-Store
+    90 {
+      sql = SELECT '{{section:VE}}' AS _contentBefore
+    }
+
+    # Executes the function getWikiContentSection() from the PHP-script
+    # Parses the last section of the content from where the editing ended
+    100 {
+      sql = SELECT 'F:typo3conf/ext/qfq/wikiScript.php|call:getWikiContentSection|arg:sectionIdStart={{sectionIdEnd:R0}}&id={{wpIdPrevious:R0}}&includeTag=false' AS _script
+    }
+
+    # Function returns the last section in V-Store
+    110 {
+      sql = SELECT '{{section:VE}}' AS _contentAfter
+    }
+
+    # Update of the current record with the unchanged content
+    120 {
+      sql = UPDATE WikiPage SET content = CONCAT('{{contentBefore:RE}}', content, '{{contentAfter:RE}}')
+      		  WHERE id = '{{wpId:R0}}'
+    }
+  }
+}
+
+# Sneaky reload of the page to remove SIP-URL after redirect from form
+130 {
+  sql = SELECT '<script>window.location.href="{{pageSlug:T}}?wpId={{wpId:RE}}#{{headingTitle:RE}}";</script>'
+  		  FROM DUAL
+        WHERE '{{action:RE}}' != ''
+}
+
+# If no id is in the R-store, the id of the root page will be used
+140 {
+  sql = SELECT id AS _wpId
+  		  FROM WikiPage
+        WHERE id = '{{wpId:R0}}'
+
+  altsql = SELECT id AS _wpId
+  		     FROM WikiPage
+           WHERE pageSlug = '{{pageSlug:T}}'
+             AND ISNULL(wpIdCurrent)
+             AND ISNULL(wpIdParent)
+           ORDER BY modified DESC
+           LIMIT 1
+}
+
+# Executes the function checkWikiAccess() from the PHP-script
+# Checks the privileges of the user
+150 {
+  sql = SELECT 'F:typo3conf/ext/qfq/wikiScript.php|call:checkWikiAccess|arg:id={{wpId:R0}}' AS _script
+}
+
+# Function returns the user privileges in V-Store and render mode gets set accordingly
+160 {
+  sql = SELECT CASE
+                WHEN '{{wikiAccess:VE}}' = 'ro' THEN '5'
+                WHEN '{{wikiAccess:VE}}' = 'rw' THEN '0'
+				        WHEN '{{wikiAccess:VE}}' = 'off' THEN '5'
+              END AS _renderMode
+}
+
+# Grant access to developers
+170 {
+  sql = SELECT IF('{{beUser:T}}' != '', '0', '{{renderMode:R0}}') AS _renderMode
+}
+
+# Prepare PDF Export
+180 {
+sql = SELECT CONCAT('U:id={{pageSlug:T}}&wpId={{wpId:R0}}&print=true'
+                    , '&--disable-external-links'
+                    , '&--margin-top=20'
+                   	, '&--margin-bottom=20'
+                    , '&--margin-left=20'
+                    , '&--margin-right=20'
+                    , '&--header-left={{pageTitle:TE}}'
+                   	, '&--header-font-name=Open Sans'
+                    , '&--header-spacing=5'
+                    , '&--footer-left=[date]'
+                    , '&--footer-right=[page]/[topage]'
+                    , '&--footer-font-name=Open Sans'
+                    , '&--footer-spacing=5'
+                    , '&--footer-font-size=10'
+                    , '&type=2'
+                    , '&_sip=1'
+                    , '|g:_blank|b:0|t:<span class="glyphicon glyphicon-print"></span> Print|d:Print wiki') AS '_pdf|pdfDownload|_hide'
+}
+
+# Dropdown for "Settings", "History", "Print" and "Add new page"
+190 {
+  sql = SELECT CONCAT('z'
+                      , '||p:{{pageSlug:T}}&form=wikiPage&r=', id, '|s|b:0|G:glyphicon glyphicon-cog|t:Settings|r:{{renderMode:RE}}'
+                      , '||p:{{pageSlug:T}}&wpId={{wpId:R0}}&history=true|b:0|G:glyphicon glyphicon-backward|t:History'
+                      , '||{{pdfDownload:RE}}'
+                      , '||p:{{pageSlug:T}}&form=wikiPage&r=0'
+                        ,'&wpIdParent=', id
+                        , '&roUser=', roUser
+                        , '&roGroup=', roGroup
+                        , '&roPublic=', roPublic
+                        , '&rwUser=', rwUser
+                        , '&rwGroup=', rwGroup
+                        , '&rwPublic=', rwPublic
+                        , '&pageLock=', pageLock
+                        , '|s|b:0|G:glyphicon glyphicon-plus|t:Add new page|r:{{renderMode:RE}}'
+              ) AS _link
+        FROM WikiPage
+        WHERE id = '{{wpId:R0}}'
+          AND '{{history:RE}}' != 'true'
+          AND '{{print:RE}}' != 'true'
+          AND ('{{wikiAccess:VE}}' != 'off'
+            OR '{{beUser:T}}' != '')
+
+  # If no wiki page is found, a new one can be created
+  altsql = SELECT '<div style="float: right;">', 'p:{{pageSlug:T}}&form=wikiPage&r=0|s|b|N' AS _link, '</div>'
+           FROM DUAL
+           WHERE '{{history:RE}}' != 'true'
+             AND '{{beUser:T}}' != ''
+             AND '{{wpId:R0}}' = 0
+
+  rbeg = <div style="float: right;">
+  rend = </div>
+}
+
+# Executes function renderWiki() from the PHP-script
+# Parses the content of the wiki page and returns it with edit links, replaced macros, etc.
+200 {
+  sql = SELECT 'F:typo3conf/ext/qfq/wikiScript.php|call:renderWiki|arg:id={{wpId:R0}}&renderMode={{renderMode:RE}}&printMode={{print:RE}}' AS _script
+        FROM DUAL
+        WHERE '{{history:RE}}' != 'true'
+          AND ('{{wikiAccess:VE}}' != 'off'
+            OR '{{beUser:T}}' != '')
+
+  # User does not have the privileges to view the wiki page
+  altsql = SELECT '<p>Access denied. You don\'t have permissions to view this page.</p>'
+           FROM DUAL
+           WHERE '{{wpId:R0}}' != 0
+             AND '{{history:RE}}' != 'true'
+}
+
+# Style for container containing the files
+210 {
+  sql = SELECT IF('{{print:RE}}' = 'true', 'show', 'none') AS _styleContainer
+}
+
+# Continue only when not in "History"
+220 {
+  sql = SELECT ''
+        FROM DUAL
+        WHERE '{{history:RE}}' != 'true'
+          AND '{{wpId:R0}}' != 0
+
+  # Prepare link and container to toggle files
+  230 {
+    sql = SELECT CONCAT('</br>'
+                        , '<a class="wiki-files-link" style="cursor:pointer;"'
+                        , 'onclick="$(\'#wiki-files-chevron-{{wpId:R0}}\').toggleClass(\'glyphicon-chevron-down glyphicon-chevron-right\');'
+                        , '$(\'#wiki-files-container-{{wpId:R0}}\').fadeToggle(\'fast\');">'
+                        , '<span class="wiki-files-chevron glyphicon glyphicon-chevron-right" id="wiki-files-chevron-{{wpId:R0}}"></span>'
+                        , 'Files ('
+                        , COUNT(id)
+                        , ')'
+                        , '</a>'
+         			          , '<div class="wiki-files-container" style="display:{{styleContainer:RE}};" id="wiki-files-container-{{wpId:R0}}">'
+                )
+		FROM WikiAttachment
+    WHERE wpId = '{{wpId:R0}}'
+      AND pathFileName != ''
+
+    stail = </div>
+
+    # Files
+    240 {
+      sql = SELECT CONCAT('p:{{pageSlug:T}}&form=wikiUpload&r=', id, '|s|b|E') AS _link
+                  , CONCAT('F:', pathFileName
+                          , '|t:', QLEFT(SUBSTRING_INDEX(pathFileName, '/', -1), 30)
+                          , '|d:', SUBSTRING_INDEX(pathFileName, '/', -1)
+                          , '|o:', SUBSTRING_INDEX(pathFileName, '/', -1)
+                          , '|s|b|g:_blank|G:glyphicon glyphicon-paperclip|M:file'
+                  ) AS _link
+                  , QIFEMPTY(feUser, 'anonymous')
+                  , modified
+                  , CONCAT('U:table=WikiAttachment&r=', id, '|q:Do you want to delete <i>', SUBSTRING_INDEX(pathFileName, '/', -1), '</i> ?') AS _paged
+      FROM WikiAttachment
+      WHERE wpId = '{{wpId:R0}}'
+        AND pathFileName != ''
+        AND '{{history:RE}}' != 'true'
+      ORDER BY modified ASC
+
+      shead = <table class="table qfq-table-100">
+                <thead>
+                  <tr>
+                    <th>{{'p:{{pageSlug:T}}&form=wikiUpload&r=0&wpId={{wpId:R0}}|s|b|N' AS _link}}</th>
+                    <th>File</th>
+                    <th>User</th>
+                    <th>Date</th>
+                    <th></th>
+                  </tr>
+                </thead>
+                <tbody>
+      stail =   </tbody>
+              </table>
+      fbeg = <td>
+      fend = </td>
+      rbeg = <tr>
+      rend = </tr>
+    }
+  }
+}
+
+# History of the wiki page
+250 {
+  sql = SELECT created, author
+        FROM WikiPage
+        WHERE IFNULL(wpIdCurrent, id) = '{{wpId:R0}}'
+          AND pageSlug = '{{pageSlug:T}}'
+          AND '{{history:RE}}' = 'true'
+          ORDER BY created DESC
+
+  head = {{'p:{{pageSlug:T}}&wpId={{wpId:R0}}|b|t:Back|G:glyphicon glyphicon-arrow-left' AS _link}}
+          <table class="table qfq-table-50"><thead><tr><th>Updated</th><th>Author</th></thead><tbody>
+  tail = </tbody></table>
+  fbeg = <td>
+  fend = </td>
+  rbeg = <tr>
+  rend = </tr>
+}
\ No newline at end of file
diff --git a/extension/Tests/Unit/Core/Database/DatabaseUpdateTest.php b/extension/Tests/Unit/Core/Database/DatabaseUpdateTest.php
index 6854d7560c98c5d31083c8bb4b1ed4609a4e6f0e..407a43f760934dced89e61ef9f7877584ac8f7ab 100644
--- a/extension/Tests/Unit/Core/Database/DatabaseUpdateTest.php
+++ b/extension/Tests/Unit/Core/Database/DatabaseUpdateTest.php
@@ -34,8 +34,7 @@ class DatabaseUpdateTest extends AbstractDatabaseTest {
      */
     public function testCheckNupdate() {
 
-//        $countQfqTables = 9;
-        $countQfqTables = 12;
+        $countQfqTables = 14;
 
         $store = Store::getInstance();
 
diff --git a/extension/wikiScript.php b/extension/wikiScript.php
new file mode 100644
index 0000000000000000000000000000000000000000..9cb46fa14f029321dbfd9b7792f84df5f004a5c5
--- /dev/null
+++ b/extension/wikiScript.php
@@ -0,0 +1,710 @@
+<?php
+/**
+ * @author Jan Haller <jan.haller@math.uzh.ch>
+ */
+
+use IMATHUZH\Qfq\Core\Report\Link;
+use IMATHUZH\Qfq\Core\Store\Store;
+use IMATHUZH\Qfq\Core\Database\Database;
+
+/**
+ * Creates a DOMNodeList from the HTML content and iterates over each element
+ * If the content is empty, a link gets created to add content to the wiki
+ * The first element gets preceded by a link to edit the content as a whole
+ * <h1> to <h6> tags get preceded by a link to edit the section including its child elements
+ * Macros ({{toc}}, {{childPages}}, [[wikiPage#Heading]], [[pageSlug/wikiPage#Heading]], {{collapse()}}, {{collapse}}) will be replaced
+ * Output to the report is generated by the echo commands
+ *
+ * @param $paramArray
+ * @param $qfq
+ * @return void
+ * @throws CodeException
+ * @throws DbException
+ * @throws UserFormException
+ * @throws UserReportException
+ */
+function renderWiki($paramArray, $qfq) {
+    $headingTags = array(HTML_TAG_H1, HTML_TAG_H2, HTML_TAG_H3, HTML_TAG_H4, HTML_TAG_H5, HTML_TAG_H6);
+    $unwantedTags = array(HTML_TAG_HTML, HTML_TAG_BODY);
+
+    // id of the wiki page record
+    $id = $paramArray[WIKI_PAGE_COLUMN_ID];
+
+    // Will be either '|r:0' or '|r:5'
+    $renderMode = '|r:' . $paramArray[WIKI_RENDER_MODE];
+
+    // Will be either true or false
+    $printMode = (isset($paramArray[WIKI_PRINT_MODE])) ? $paramArray[WIKI_PRINT_MODE] : false;
+
+    $sql = 'SELECT ' . WIKI_PAGE_COLUMN_CONTENT . ', ' . WIKI_PAGE_COLUMN_PAGE_SLUG . ' FROM ' . WIKI_PAGE_TABLE_NAME . ' WHERE ' . WIKI_PAGE_COLUMN_ID . ' = ' . $id ;
+    $resultArray = doSql($sql);
+
+    $wikiPageContent = (isset($resultArray[0][WIKI_PAGE_COLUMN_CONTENT])) ? $resultArray[0][WIKI_PAGE_COLUMN_CONTENT] : '';
+    $pageSlug = (isset($resultArray[0][WIKI_PAGE_COLUMN_PAGE_SLUG])) ? $resultArray[0][WIKI_PAGE_COLUMN_PAGE_SLUG] : '';
+
+    // This is necessary in print mode to hide the button text
+    $buttonText = ($printMode) ? '' : '|t:Edit';
+
+    // No wiki page has been created yet
+    if (empty($wikiPageContent) && empty($pageSlug)) {
+        return;
+
+    // Wiki page has been created but is without content
+    // Creates an 'edit' link and exits the function
+    } elseif (empty($wikiPageContent)) {
+        echo '<div style="float:right;">';
+        echo doQfqLink('p:', $pageSlug, '&form=wikiEditor&r=0&wpIdPrevious=' . $id . '&action=new' . $renderMode . '|s|b|E' . $buttonText);
+        echo '&nbsp;</div>';
+        echo '<div class="wiki-navigation" style="display:inline-block; margin-bottom:10px">' . doNavigation($id) . '</div>';
+        return;
+    }
+
+    // Navigation
+    echo ($printMode) ? '' : '<div class="wiki-navigation">' . doNavigation($id) . '</div>';
+
+    // Will be passed as a parameter in the link and enables the edit of the content as a whole
+    // Always used for the first element
+    $action = WIKI_TOKEN_EDIT_ALL;
+
+    // This DOM will be updated with additional elements
+    $dom = new DomDocument();
+    $dom->loadHTML($wikiPageContent);
+
+    // Used for iterating over the elements
+    $xpath = new DOMXPath($dom);
+    $content = $xpath->query('//*');
+
+    $counter = 1;
+    $firstElement = true;
+
+    // Iterate over each element
+    foreach ($content as $element) {
+        $nodeName = $element->nodeName;
+
+        // The first two elements are always <html> and <body>
+        if (!in_array($nodeName, $unwantedTags)) {
+            $nodeValue = $element->nodeValue;
+
+            // Looks for <h1> - <h6> elements or the first element
+            // Creates a <div> containing the edit link and an <a>-tag
+            // In printMode this will be skipped
+            if ((in_array($nodeName, $headingTags) || $firstElement) && !$printMode) {
+
+                // If the first element is not <h1> - <h6>, then $sectionId = 0
+                $sectionId = (in_array($nodeName, $headingTags)) ? $counter : 0;
+
+                // <div>
+                $divHtml = '<div title="Edit this section" id="wiki-section-' . $sectionId . '" style="float:right;">';
+                $divHtml .= doQfqLink('p:', $pageSlug, '&form=wikiEditor&r=0&sectionId=' . $sectionId . '&wpIdPrevious=' . $id . '&action=' . $action . $renderMode . '|s|b|E' . $buttonText);
+                $divHtml .= (in_array($nodeName, $headingTags)) ? '<a name="' . slugify($nodeValue) . '"></a>&nbsp;</div>' : '&nbsp;</div>';
+
+                // Creates a DOMElement from HTML and inserts it before the current element into the DOM
+                $divElement = doDomElement($divHtml);
+                $divElement = $dom->importNode($divElement, true);
+                $parentNode = $element->parentNode;
+                $parentNode = $parentNode->insertBefore($divElement, $element);
+
+                $counter = ($sectionId === 0) ? $counter : $counter + 1;
+                unset($divHtml, $divElement, $parentNode, $sectionId);
+            }
+
+            // Reset values
+            $buttonText = '';
+            $action = WIKI_TOKEN_EDIT_SECTION;
+            $firstElement = false;
+
+            // Check for macro '{{toc}}'
+            if (trim($nodeValue) === WIKI_TOKEN_TABLE_OF_CONTENTS) {
+
+                // HTML for the list table of contents
+                $tocHtml = doTableOfContents($content);
+
+                // Check if list table of contents has been successfully created
+                if ($tocHtml !== $nodeValue) {
+
+                    // Creates a DOMElement from HTML and inserts it before the current element into the DOM
+                    $ulElement = doDomElement($tocHtml);
+                    $ulElement = $dom->importNode($ulElement, true);
+                    $parentNode = $element->parentNode;
+                    $parentNode = $parentNode->replaceChild($ulElement, $element);
+
+                    unset($tocHtml, $ulElement, $parentNode);
+                }
+            }
+
+            // Check for macro '{{childPages}}'
+            if (trim($nodeValue) === WIKI_TOKEN_CHILD_PAGES) {
+
+                // HTML for the list child pages
+                $cpHtml = doChildPages($id);
+
+                // Check if list child pages has been successfully created
+                if ($cpHtml !== $nodeValue) {
+
+                    // Creates a DOMElement from HTML and inserts it before the current element into the DOM
+                    $ulElement = doDomElement($cpHtml);
+                    $ulElement = $dom->importNode($ulElement, true);
+                    $parentNode = $element->parentNode;
+                    $parentNode = $parentNode->replaceChild($ulElement, $element);
+
+                    unset($cpHtml, $ulElement, $parentNode);
+                }
+            }
+
+            // Check for and replace macro '[[wikiPage#Heading]]' and '[[pageSlug/wikiPage#Heading]]'
+            if (preg_match('<(\[\[[a-zA-Z0-9- :,;/\-]+#{1}[a-zA-Z0-9. :,;\-]*\]\])>', $nodeValue)) {
+                $linkHtml = preg_replace_callback('<(\[\[[a-zA-Z0-9- :,;/\-]+#{1}[a-zA-z0-9. :,;\-]*\]\])>', 'doWikiLink', $nodeValue);
+
+                // Check if link has been successfully created
+                if ($linkHtml !== $nodeValue) {
+
+                    // Creates a DOMElement from HTML and inserts it before the current element into the DOM
+                    $aElement = doDomElement($linkHtml);
+                    $aElement = $dom->importNode($aElement, true);
+                    $parentNode = $element->parentNode;
+                    $parentNode = $parentNode->replaceChild($aElement, $element);
+
+                    unset($linkHtml, $aElement, $parentNode);
+                }
+            }
+        }
+    }
+
+    // Raw HTML
+    $wikiHtml = $dom->saveHTML();
+
+    // HTML is different in print mode
+    if ($printMode) {
+
+        // Remove {{collapse()}} and {{collapse}}
+        // This keeps the content visible on the PDF
+        $wikiHtml = preg_replace('<<[a-zA-Z]+>{{collapse\((.*?)\)}}<[a-zA-Z/]+>>', '', $wikiHtml);
+        $wikiHtml = preg_replace('<(<[a-zA-Z]+>{{collapse}}<[a-zA-Z/]+>)>', '', $wikiHtml);
+    } else {
+
+        // Search and replace opening collapse macro
+        // Opening <div>-tag gets inserted which acts as a parent for the collapsed content
+        // Search: <p>{{collapse(optional text)}}</p>
+        // Replace: <a><span></span>optional text</a></br><div>
+        $wikiHtml = preg_replace_callback('<<[a-zA-Z]+>{{collapse\((.*?)\)}}<[a-zA-Z/]+>>', 'doCollapse', $wikiHtml);
+
+        // Search and replace closing collapse macro
+        // Close previously opened <div>-tag
+        // Search: <p>{{collapse}}</p>
+        // Replace: </div>
+        $wikiHtml = preg_replace('<(<[a-zA-Z]+>{{collapse}}<[a-zA-Z/]+>)>', '</div>', $wikiHtml);
+    }
+
+    // Output
+    echo $wikiHtml;
+}
+
+/**
+ * Creates a DOMNodeList from the HTML content and iterates over each element
+ * Output will only contain the sections that will be loaded into the editor
+ *
+ * @param $paramArray
+ * @param $qfq
+ * @return void
+ */
+function loadWikiContent($paramArray, $qfq) {
+    $headingTags = array(HTML_TAG_H1, HTML_TAG_H2, HTML_TAG_H3, HTML_TAG_H4, HTML_TAG_H5, HTML_TAG_H6);
+
+    // id of the wiki page record
+    $id = $paramArray[WIKI_PAGE_COLUMN_ID];
+
+    // id of the section that will be edited
+    $sectionId = intval($paramArray[WIKI_PAGE_SECTION_ID]);
+
+    // Will be either 'editAll' or 'editSection'
+    $action = $paramArray[WIKI_TOKEN_ACTION];
+
+    $sql = 'SELECT ' . WIKI_PAGE_COLUMN_CONTENT . ' FROM ' . WIKI_PAGE_TABLE_NAME . ' WHERE ' . WIKI_PAGE_COLUMN_ID . ' = ' . $id;
+    $resultArray = doSql($sql);
+
+    $wikiPageContent = $resultArray[0][WIKI_PAGE_COLUMN_CONTENT];
+
+    // If the content as a whole will be edited there is no need to parse
+    if ($action === WIKI_TOKEN_EDIT_ALL) {
+        echo $wikiPageContent;
+        return;
+    }
+
+    $nodeLevelSection = null;
+    $nodeLevelCurrent = null;
+
+    $counter = 1;
+    $dom = new DOMDocument();
+    $dom->loadHTML($wikiPageContent);
+    $body = $dom->getElementsByTagName(HTML_TAG_BODY)->item(0);
+    $content = $body->childNodes;
+
+    // Iterate over each element
+    foreach ($content as $element) {
+        $nodeName = $element->nodeName;
+
+        // Looks for <h1> - <h6> elements
+        if (in_array($nodeName, $headingTags)) {
+
+            // Look for the element to edit
+            if ($counter === $sectionId) {
+
+                // Extract the level from the element
+                // The level consist of the number from the heading tag, e.g. 1 for <h1>
+                $nodeLevelSection = intval(substr($nodeName, 1));
+                $nodeLevelCurrent = intval(substr($nodeName, 1)) + 1;
+
+                // Slugified heading gets set in S-Store to later use it as an anchor in page forward
+                setVar(WIKI_PAGE_HEADING_TITLE, slugify($element->nodeValue), STORE_SIP);
+
+            // Element is not editable
+            } elseif ($counter < $sectionId) {
+                $counter++;
+                continue;
+
+            // Element is editable
+            } else {
+
+                // Extract the level from the element
+                $nodeLevelCurrent = intval(substr($nodeName, 1));
+            }
+
+            // Element is not editable
+            if ($nodeLevelSection >= $nodeLevelCurrent) {
+                break;
+            }
+            $counter++;
+        }
+
+        // Check if element is editable
+        if ($nodeLevelSection < $nodeLevelCurrent) {
+
+            // Output element
+            echo $dom->saveHTML($element);
+        }
+    }
+
+    // Current section gets set in S-Store to later extract the unedited section of the content
+    setVar(WIKI_PAGE_SECTION_ID_END, $counter, STORE_SIP);
+}
+
+/**
+ * Creates a DOMNodeList from the HTML content and iterates over each element
+ * Return will only contain the sections that were not edited
+ *
+ * @param $paramArray
+ * @param $qfq
+ * @return null[]|string[]
+ */
+function getWikiContentSection($paramArray, $qfq) {
+    $headingTags = array(HTML_TAG_H1, HTML_TAG_H2, HTML_TAG_H3, HTML_TAG_H4, HTML_TAG_H5, HTML_TAG_H6);
+
+    // id of the wiki page record
+    $id = $paramArray[WIKI_PAGE_COLUMN_ID];
+
+    // Will be a positive number
+    $sectionIdStart = intval($paramArray[WIKI_PAGE_SECTION_ID_START]);
+
+    // Will either be true or false
+    // Necessary to output non-heading elements
+    $includeTag = ($paramArray[WIKI_TOKEN_INCLUDE_TAG] === "true") ? true : false;
+    $section = null;
+
+    $sql = 'SELECT ' . WIKI_PAGE_COLUMN_CONTENT . ' FROM ' . WIKI_PAGE_TABLE_NAME . ' WHERE ' . WIKI_PAGE_COLUMN_ID . ' = ' . $id;
+    $resultArray = doSql($sql);
+
+    $wikiPageContent = $resultArray[0][WIKI_PAGE_COLUMN_CONTENT];
+
+    $dom = new DOMDocument();
+    $dom->loadHTML($wikiPageContent);
+    $body = $dom->getElementsByTagName(HTML_TAG_BODY)->item(0);
+    $content = $body->childNodes;
+
+    // If not set, that means the section is until the end of the content
+    $sectionIdEnd = (isset($paramArray[WIKI_PAGE_SECTION_ID_END])) ? intval($paramArray[WIKI_PAGE_SECTION_ID_END]) : count($content) + 1;
+    $counter = 1;
+
+    // Iterate over each element
+    foreach ($content as $element) {
+        $nodeName = $element->nodeName;
+
+            // Looks for <h1> - <h6> elements
+            if (in_array($nodeName, $headingTags)) {
+
+                // Checks if the following elements should be included, e.g. <p>, <table>, etc.
+                if ($counter >= $sectionIdStart && $counter < $sectionIdEnd) {
+
+                    // HTML of the element is added to $section
+                    $section .= $dom->saveHTML($element);
+                    $includeTag = true;
+                } else {
+                    $includeTag = false;
+                }
+
+                $counter++;
+
+            } elseif($includeTag) {
+
+                // HTML of the element is added to $section
+                $section .= $dom->saveHTML($element);
+            }
+        }
+
+    // Current section gets set in V-Store, so it can later be used to update the current wiki page content
+    return [WIKI_TOKEN_SECTION => $section];
+}
+
+/**
+ * Creates a DOMElement from given HTML
+ *
+ * @param $html
+ * @return mixed DOMElement
+ */
+function doDomElement($html) {
+    $dom = new DOMDocument();
+    $element = $dom->loadHTML($html);
+    $element = $dom->getElementsByTagName(HTML_TAG_BODY)->item(0)->firstChild;
+
+    return $element;
+}
+
+/**
+ * Recursive function to generate a navigation with links from the current page to its parent page and so on.
+ * Finished version looks like this: grandparent page > parent page > current page
+ *
+ * @param $id
+ * @return string
+ * @throws CodeException
+ * @throws DbException
+ * @throws UserFormException
+ * @throws UserReportException
+ */
+function doNavigation($id) {
+    $sql = 'SELECT ' . WIKI_PAGE_COLUMN_WP_ID_PARENT . ', ' . WIKI_PAGE_COLUMN_PAGE_SLUG . ', ' . WIKI_PAGE_COLUMN_NAME . ' FROM ' . WIKI_PAGE_TABLE_NAME . ' WHERE ' . WIKI_PAGE_COLUMN_ID . ' = ' . $id ;
+    $resultArray = doSql($sql);
+
+    // If no $wpIdParent is found this equals to null
+    $wpIdParent = $resultArray[0][WIKI_PAGE_COLUMN_WP_ID_PARENT];
+    $pageSlug = $resultArray[0][WIKI_PAGE_COLUMN_PAGE_SLUG];
+    $name = $resultArray[0][WIKI_PAGE_COLUMN_NAME];
+
+    $link = doQfqLink('p:', $pageSlug, '&wpId=' . $id . '|t:' . $name);
+    $separator = '';
+    $output = '';
+
+    // Check if the root page has been reached
+    if (!is_null($wpIdParent)) {
+        $output .= doNavigation($wpIdParent);
+        $separator = '<span>&nbsp;»&nbsp;</span>';
+    }
+
+    $output .= $separator . $link;
+    return $output;
+}
+
+/**
+ * Creates a table of contents from all headings of the wiki page
+ * Gets invoked by the macro '{{toc}}'
+ * Every level/element has its own indentation
+ *
+ * @param $content
+ * @return string
+ */
+function doTableOfContents($content) {
+    $headingTags = array(HTML_TAG_H1, HTML_TAG_H2, HTML_TAG_H3, HTML_TAG_H4, HTML_TAG_H5, HTML_TAG_H6);
+    $headings = array();
+
+    // <h6>
+    $highestLevel = 6;
+
+    // <h1>, lowest level
+    $previousLevel = 1;
+    $toc = WIKI_TOKEN_TABLE_OF_CONTENTS;
+
+    // Iterate over each element
+    foreach($content as $element) {
+        $nodeName = $element->nodeName;
+
+        // Looks for <h1> - <h6> elements
+        if (in_array($nodeName, $headingTags)) {
+            $nodeValue = $element->nodeValue;
+
+            // Heading level and name get added to the array
+            $headings[] = intval(substr($nodeName, 1)) . "=" . $nodeValue;
+        }
+    }
+
+    // If no heading elements were found the macro '{{toc}}' will be returned as is
+    if (empty($headings)) {
+        return $toc;
+    }
+
+    // List for the table of contents
+    $toc = '<ul class="wiki-toc"><li><strong>Table of contents</strong></li>';
+
+    // Iterate over each heading
+    foreach($headings as $key => $value) {
+        $headingLevel = explode("=", $value);
+        $currentLevel = $headingLevel[0];
+        $heading = $headingLevel[1];
+
+        // Check if current level is higher than the highest level, update if true
+        $highestLevel = ($currentLevel < $highestLevel) ? $currentLevel : $highestLevel;
+
+        if ($currentLevel > $previousLevel) {
+
+            // 'Indentation' of the <ul> element inside the table of contents
+            $toc .= str_repeat('<ul>', $currentLevel - $previousLevel);
+
+        } elseif ($currentLevel === $previousLevel) {
+            $toc .= '</li>';
+        } else {
+
+            // Closing the 'indented' <ul> element inside the table of contents
+            $toc .= str_repeat('</ul>', $previousLevel - $currentLevel);
+        }
+
+        $toc .= '<li>';
+
+        // Anchor tag that links to the heading
+        $toc .= '<a href="#' . slugify($heading) . '">' . $heading . '</a>';
+        $previousLevel = $currentLevel;
+    }
+
+    // Complete the list
+    $toc .= str_repeat('</li></ul>', $previousLevel);
+    return $toc;
+}
+
+/**
+ * Creates a link to a wiki page within the same wiki or in another wiki
+ * Gets invoked by the macros '[[wikiPage#Heading]]' and/or '[[pageSlug/wikiPage#Heading]]'
+ *
+ * @param $match
+ * @return string
+ */
+function doWikiLink($match) {
+    // e.g. $match = '[[wikiPage#Heading]]'
+    // e.g. $match = '[[pageSlug/wikiPage#Heading]]'
+
+    // Removes '[[' and ']]'
+    $match = substr($match[0], 2, -2);
+
+    // Separates 'wikiPage'/'pageSlug/wikiPage' and 'Heading'
+    $arr = explode("#", $match);
+    $wikiPage = $arr[0];
+    $heading = $arr[1];
+
+    // Looks for the name of the wiki page: '[[wikiPage#Heading]]'
+    $sql = 'SELECT ' . WIKI_PAGE_COLUMN_ID . ', ' . WIKI_PAGE_COLUMN_PAGE_SLUG . ' FROM ' . WIKI_PAGE_TABLE_NAME . ' WHERE ' . WIKI_PAGE_COLUMN_NAME . ' = "' . $wikiPage . '" AND ISNULL(' .WIKI_PAGE_COLUMN_WP_ID_CURRENT . ')';
+    $resultArray = doSql($sql);
+
+    // Looks for the combination of page slug and name of the wiki page: '[[pageSlug/wikiPage#Heading]]'
+    if (empty($resultArray)) {
+        $sql = 'SELECT ' . WIKI_PAGE_COLUMN_ID . ', ' . WIKI_PAGE_COLUMN_PAGE_SLUG .  ' FROM ' . WIKI_PAGE_TABLE_NAME . ' WHERE CONCAT(' . WIKI_PAGE_COLUMN_PAGE_SLUG . ', "/",' . WIKI_PAGE_COLUMN_NAME . ') = "' . $wikiPage . '" AND ISNULL(' . WIKI_PAGE_COLUMN_WP_ID_CURRENT . ')';
+        $resultArray = doSql($sql);
+    }
+
+    // If no wiki page was found the macro will be returned as is
+    if (empty($resultArray)) {
+        return '[[' . $match . ']]';
+    }
+
+    // id of the wiki page
+    $id = $resultArray[0][WIKI_PAGE_COLUMN_ID];
+
+    // page slug of the wiki page
+    $pageSlug = $resultArray[0][WIKI_PAGE_COLUMN_PAGE_SLUG];
+
+    // Link to the wiki page with anchor to the heading
+    $link = doQfqLink('p:', $pageSlug, '&wpId=' . $id . '#' . slugify($heading) . '|t:' . $heading . '|c:wiki-link');
+    return $link;
+}
+
+/**
+ * Creates a list of child pages of the current wiki page
+ * Gets invoked by the macro '{{childPages}}'
+ *
+ * @param $id
+ * @return string
+ */
+function doChildPages($id) {
+    $sql = 'SELECT ' . WIKI_PAGE_COLUMN_ID . ', ' . WIKI_PAGE_COLUMN_NAME . ', ' . WIKI_PAGE_COLUMN_PAGE_SLUG . ' FROM ' . WIKI_PAGE_TABLE_NAME . ' WHERE ' . WIKI_PAGE_COLUMN_WP_ID_PARENT . ' = ' . $id . ' AND ISNULL(' . WIKI_PAGE_COLUMN_WP_ID_CURRENT . ') ORDER BY ' . WIKI_PAGE_COLUMN_NAME . ' ASC';
+    $resultArray = doSql($sql);
+
+    $childPages = '{{childPages}}';
+
+    // If no child pages were found the macro gets returnd as is
+    if (empty($resultArray)) {
+        return $childPages;
+    }
+
+    // List of the child pages
+    $childPages = '<ul class="wiki-pages-hierarchy">';
+
+    // Iterate over each row
+    foreach ($resultArray as $row) {
+        $id = $row[WIKI_PAGE_COLUMN_ID];
+        $name = $row[WIKI_PAGE_COLUMN_NAME];
+        $pageSlug = $row[WIKI_PAGE_COLUMN_PAGE_SLUG];
+
+        // Link to the child page
+        $childPages .= '<li>' . doQfqLink('p:', $pageSlug, '&wpId=' . $id . '|t:' . $name) . '</li>';
+    }
+
+    // Complete the list
+    $childPages .= '</ul>';
+    return $childPages;
+}
+
+/**
+ * Checks the privileges of the user that wants to access the wiki page
+ *
+ * @param $paramArray
+ * @param $qfq
+ * @return string[]
+ * @throws CodeException
+ * @throws DbException
+ * @throws UserFormException
+ * @throws UserReportException
+ */
+function checkWikiAccess($paramArray, $qfq) {
+    $store = Store::getInstance();
+
+    // id of the wiki page
+    $id = $paramArray[WIKI_PAGE_COLUMN_ID];
+
+    $sql = 'SELECT ' . WIKI_PAGE_COLUMN_AUTHOR . ', ' . WIKI_PAGE_COLUMN_RO_USER . ', ' . WIKI_PAGE_COLUMN_RO_GROUP . ', ' . WIKI_PAGE_COLUMN_RO_PUBLIC . ', ' . WIKI_PAGE_COLUMN_RW_USER . ', ' . WIKI_PAGE_COLUMN_RW_GROUP . ', ' . WIKI_PAGE_COLUMN_RW_PUBLIC . ', ' . WIKI_PAGE_COLUMN_PAGE_LOCK . ' FROM ' . WIKI_PAGE_TABLE_NAME . ' WHERE ' . WIKI_PAGE_COLUMN_ID . ' = ' . $id ;
+    $resultArray = doSql($sql);
+
+    $author = (isset($resultArray[0][WIKI_PAGE_COLUMN_AUTHOR])) ? $resultArray[0][WIKI_PAGE_COLUMN_AUTHOR] : '';
+    $roUser = (isset($resultArray[0][WIKI_PAGE_COLUMN_RO_USER])) ? explode(',', $resultArray[0][WIKI_PAGE_COLUMN_RO_USER]) : array();
+    $roGroup = (isset($resultArray[0][WIKI_PAGE_COLUMN_RO_GROUP])) ? explode(',', $resultArray[0][WIKI_PAGE_COLUMN_RO_GROUP]) : array();
+    $roPublic = (isset($resultArray[0][WIKI_PAGE_COLUMN_RO_PUBLIC])) ? $resultArray[0][WIKI_PAGE_COLUMN_RO_PUBLIC] : '';
+    $rwUser = (isset($resultArray[0][WIKI_PAGE_COLUMN_RW_USER])) ? explode(',', $resultArray[0][WIKI_PAGE_COLUMN_RW_USER]) : array();
+    $rwGroup = (isset($resultArray[0][WIKI_PAGE_COLUMN_RW_GROUP])) ? explode(',', $resultArray[0][WIKI_PAGE_COLUMN_RW_GROUP]) : array();
+    $rwPublic = (isset($resultArray[0][WIKI_PAGE_COLUMN_RW_PUBLIC])) ? $resultArray[0][WIKI_PAGE_COLUMN_RW_PUBLIC] : '';
+    $pageLock = (isset($resultArray[0][WIKI_PAGE_COLUMN_PAGE_LOCK])) ? $resultArray[0][WIKI_PAGE_COLUMN_PAGE_LOCK] : '';
+
+    // Get logged-in user from T-store
+    $feUser = $store::getVar(TYPO3_FE_USER, STORE_TYPO3);
+
+    // Get groups of logged-in user from T-store
+    $feUserGroup[] = explode(',', $store::getVar(TYPO3_FE_USER_GROUP, STORE_TYPO3));
+
+    $feUserGroup = (is_array($feUserGroup)) ? $feUserGroup : array();
+    $wikiAccess = WIKI_TOKEN_ACCESS_OFF;
+
+    /* Checks if permissions are set to read-write for either the public, the logged-in user or a group of the logged-in user
+       Also check if the logged-in user is the author of the wiki page */
+    if ($rwPublic === WIKI_TOKEN_ACCESS_ON || (in_array($feUser, $rwUser) && !empty($feUser))  || (array_intersect($feUserGroup[0], $rwGroup) && !in_array('', $feUserGroup[0])) || $feUser === $author) {
+        $wikiAccess = WIKI_TOKEN_ACCESS_READ_WRITE;
+
+        // Checks if permissions are set to read-only for either the public, the logged-in user or a group of the logged-in user
+    } elseif ($roPublic === WIKI_TOKEN_ACCESS_ON || (in_array($feUser, $roUser) && !empty($feUser))  || (array_intersect($feUserGroup[0], $roGroup) && !in_array('', $feUserGroup[0]))) {
+        $wikiAccess = WIKI_TOKEN_ACCESS_READ_ONLY;
+    }
+
+    // If page lock is set but not by the logged-in user who has read-write permissions, they change to read-only
+    $wikiAccess = (!empty($pageLock) && $wikiAccess === WIKI_TOKEN_ACCESS_READ_WRITE && $pageLock !== $feUser) ? WIKI_TOKEN_ACCESS_READ_ONLY : $wikiAccess;
+
+    // Permissions get set in V-Store, so they can later be used to render the wiki accordingly
+    return [WIKI_TOKEN_WIKI_ACCESS => $wikiAccess];
+}
+
+/**
+ * Replaces the macro '{{collapse()}}' and/or '{{collapse(string)}}'
+ *
+ * @param $matches
+ * @return mixed|string
+ */
+function doCollapse($matches) {
+    // $matches[0] = '<p>{{collapse(optional text)}}</p>
+    // $matches[1] = 'optional text'
+    $text = $matches[1];
+    $randomInt = rand();
+    $str = '<a class="wiki-collapse-link" style="cursor:pointer;" onclick="$(\'#wiki-collapse-chevron-'. $randomInt . '\').toggleClass(\'glyphicon-chevron-down glyphicon-chevron-right\'); $(\'#wiki-collapse-content-' . $randomInt . '\').fadeToggle(\'fast\');">
+            <span class="wiki-collapse-chevron glyphicon glyphicon-chevron-right" id="wiki-collapse-chevron-' . $randomInt . '"></span>' . $text . '</a></br>
+            <div class="wiki-collapse-container" style="display: none;" id="wiki-collapse-content-' . $randomInt . '">';
+
+    return $str;
+}
+
+/**
+ * Executes SQL query
+ *
+ * @param $sql
+ * @return array|bool|int|string
+ * @throws CodeException
+ * @throws DbException
+ * @throws UserFormException
+ */
+function doSql($sql) {
+    $db = new Database();
+    $result = $db->sql($sql);
+    return $result;
+}
+
+/**
+ * Creates a QFQ-link
+ *
+ * @param $page
+ * @param $pageSlug
+ * @param $params
+ * @return string
+ * @throws CodeException
+ * @throws DbException
+ * @throws UserFormException
+ * @throws UserReportException
+ */
+function doQfqLink($page, $pageSlug, $params) {
+    $s = Store::getInstance();
+    $sip = $s->getSipInstance();
+    $link = new Link($sip);
+    $str = $link->renderLink($page . $pageSlug . $params);
+    return $str;
+}
+
+/**
+ * Sets a variable to a store
+ *
+ * @param $key
+ * @param $value
+ * @param $store
+ * @return void
+ * @throws CodeException
+ * @throws UserFormException
+ * @throws UserReportException
+ */
+function setVar($key, $value, $store) {
+    $s = Store::getInstance();
+    $s::setVar($key, $value, $store);
+}
+
+/**
+ * Slugify function
+ * E.g. This is my custom heading -> This-is-my-custom-heading
+ *
+ * @param $text
+ * @return array|string|string[]
+ */
+function slugify($text) {
+    $divider = '-';
+
+    // Replace non letter or digits by divider
+    $text = preg_replace('~[^\pL\d]+~u', $divider, $text);
+
+    // Transliterate
+    $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);
+
+    // Remove unwanted characters
+    $text = preg_replace('~[^-\w]+~', '', $text);
+
+    // Trim
+    $text = trim($text, $divider);
+
+    // Remove duplicate divider
+    $text = preg_replace('~-+~', $divider, $text);
+
+    return $text;
+}
\ No newline at end of file
diff --git a/less/qfq-bs.css.less b/less/qfq-bs.css.less
index 1acd5c0066527b3de5187686cd39244fb20f7885..462ee627bd2be5f8ac5f58ded08f385e40249c78 100644
--- a/less/qfq-bs.css.less
+++ b/less/qfq-bs.css.less
@@ -323,11 +323,11 @@ i.@{spinner_class} {
 
 .rebel-yell .form-group.has-error .btn, .btn-rebel {
   background-color: #e72a89;
-  background-image: linear-gradient(to bottom,#e72a89 0,#d30b6f 100%);
+  background-image: linear-gradient(to bottom, #e72a89 0, #d30b6f 100%);
   background-repeat: repeat-x;
   border-color: #d30b6f;
   font-weight: 200;
-  text-shadow: #e72a89 0 1px 0;
+  text-shadow: 0 1px 0 #e72a89;
   color: #333;
 }
 
@@ -1608,6 +1608,25 @@ label[id^="filepond--drop-label-"] {
   left: -3px !important;
 }
 
+.wiki-toc {
+  background-color: #edeff1;
+  border: 1px solid #e4e4e4;
+  padding: 20px;
+  line-height: 1.3;
+  margin-bottom: 12px;
+  margin-right: 20px;
+  margin-left: 0;
+  display: table;
+}
+
+.wiki-toc * {
+  list-style-type: none;
+}
+
+.wiki-navigation {
+  display: inline-block;
+  margin-bottom: 10px;
+}
 
 // QFQ chat rules
 .qfq-chat {