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}}§ionId={{sectionId:S0}}§ionIdEnd={{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§ionIdEnd={{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 ' </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§ionId=' . $sectionId . '&wpIdPrevious=' . $id . '&action=' . $action . $renderMode . '|s|b|E' . $buttonText); + $divHtml .= (in_array($nodeName, $headingTags)) ? '<a name="' . slugify($nodeValue) . '"></a> </div>' : ' </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> » </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 {