Commit 7d13df4c authored by Elias Villiger's avatar Elias Villiger
Browse files

Merge branch '6255-reports-inline-editing' into 'master'

6255 reports inline editing

See merge request !66
parents 0d91f660 579e0f7a
Pipeline #837 passed with stage
in 1 minute and 38 seconds
......@@ -472,6 +472,9 @@ config.qfq.php
| LDAP_1_RDN | LDAP_1_RDN='ou=Admin,ou=example,dc=com' | Credentials for non-anonymous LDAP access. At the moment only one set of |
| LDAP_1_PASSWORD | LDAP_1_PASSWORD=mySecurePassword | |
+-------------------------------+-------------------------------------------------------+----------------------------------------------------------------------------+
| T3_DB_NAME | T3_DB_NAME=specialt3dbname | Only necessary for inline report editing if the t3 database is not |
| | | anologous to the Data db name (but ending in _t3) - see `inline-report`_. |
+-------------------------------+-------------------------------------------------------+----------------------------------------------------------------------------+
......@@ -503,11 +506,11 @@ Example: *typo3conf/config.qfq.php*: ::
After parsing the configuration, the following variables will be set automatically in STORE_SYSTEM:
+----------------+-----------------------------------------------------------------------------------+
| _dbNameData | Can be used to dynamically access the current selected database: {{dbNameData:Y}} |
+----------------+-----------------------------------------------------------------------------------+
| _dbNameQfq | Can be used to dynamically access the current selected database: {{dbNameQfq:Y}} |
+----------------+-----------------------------------------------------------------------------------+
+----------------+------------------------------------------------------------------------------------+
| _dbNameData | Can be used to dynamically access the current selected database: {{_dbNameData:Y}} |
+----------------+------------------------------------------------------------------------------------+
| _dbNameQfq | Can be used to dynamically access the current selected database: {{_dbNameQfq:Y}} |
+----------------+------------------------------------------------------------------------------------+
.. _`CustomVariables`:
......@@ -4992,6 +4995,29 @@ The parsed bodytext could be displayed by activating 'showDebugInfo' (:ref:`debu
A small symbol with a tooltip will be shown, where the content record will be displayed on the webpage.
Note: :ref:`debug` information will only be shown with *showDebugInfo: yes* in configuration_.
.. _`inline-report`:
Inline Report editing
---------------------
For quick changes it can be bothersome to go to the TYPO3 backend to update the page content and reload the page.
For this reason, QFQ offers an inline report editing feature whenever there is a TYPO3 BE user logged in. A small
link symbol will appear on the right-hand side of each report record. Please note that the TYPO3 Frontend cache
is also deleted upon each inline report save.
In order for the inline report editing to work, QFQ needs to be able to access the T3 database. By default this database
is assumed to be accessible with the same credentials as specified with indexData and is assumed to be named similarly to
the indexData db name, but ending in _t3 instead of _db (e.g., mydb_db and mydb_t3).
For a standard installation and db setup, this should be the case.
You can however specify a custom T3 db name in config-qfq-php_:
::
T3_DB_NAME = customT3DbName
Structure
---------
......
......@@ -59,6 +59,11 @@ try {
$data = $qfq->saveForm();
if (isset($data[REPORT_SAVE])) {
// Redirect to previous page
header("Location: {$_SERVER['HTTP_REFERER']}");
}
$arr = $qfq->getForwardMode();
$answer[API_REDIRECT] = $arr[API_REDIRECT];
$answer[API_REDIRECT_URL] = $arr[API_REDIRECT_URL];
......
......@@ -45,6 +45,8 @@ const FORM_BUTTON_DELETE = 'delete';
const FORM_BUTTON_CLOSE = 'close';
const FORM_BUTTON_SAVE = 'save';
const REPORT_SAVE = 'reportSave';
const F_BS_COLUMNS = 'bsColumns';
const F_BS_LABEL_COLUMNS = 'bsLabelColumns';
......@@ -409,6 +411,8 @@ const SYSTEM_DB_1_SERVER = 'DB_1_SERVER';
const SYSTEM_DB_1_PASSWORD = 'DB_1_PASSWORD';
const SYSTEM_DB_1_NAME = 'DB_1_NAME';
const SYSTEM_T3_DB_NAME = 'T3_DB_NAME';
const SYSTEM_DB_INIT = 'init';
const SYSTEM_DB_INDEX_DATA = "indexData";
......@@ -1205,7 +1209,10 @@ const INDEX_PHP = 'index.php';
// QuickFormQuery.php
const T3DATA_BODYTEXT = 'bodytext';
const T3DATA_BODYTEXT_RAW = 'bodytext-raw';
const T3DATA_UID = 'uid';
const T3DATA_HEADER = 'header';
const REPORT_INLINE_BODYTEXT = 'bodytext';
// Special Column to check for uploads
const COLUMN_PATH_FILE_NAME = 'pathFileName';
......
......@@ -23,6 +23,7 @@ use qfq;
require_once(__DIR__ . '/store/Store.php');
require_once(__DIR__ . '/store/Sip.php');
require_once(__DIR__ . '/store/FillStoreForm.php');
require_once(__DIR__ . '/store/Session.php');
require_once(__DIR__ . '/Constants.php');
......@@ -151,6 +152,7 @@ class QuickFormQuery {
}
$btp = new BodytextParser();
$t3data[T3DATA_BODYTEXT_RAW] = $t3data[T3DATA_BODYTEXT];
$t3data[T3DATA_BODYTEXT] = $btp->process($t3data[T3DATA_BODYTEXT]);
$this->t3data = $t3data;
......@@ -1306,7 +1308,7 @@ class QuickFormQuery {
/**
* Load record $id from $table and saves them in $store
*
* @param string $table tablename from where to load record wiht $recordId
* @param array $table tablename
* @param string $recordId record ID of current record
* @param string $store name of store where to save the record
*
......@@ -1362,12 +1364,95 @@ class QuickFormQuery {
private function doReport() {
$report = new Report($this->t3data, $this->eval, $this->phpUnit);
$html = '';
$html = $report->process($this->t3data['bodytext']);
if ($this->store->getVar(TYPO3_BE_USER, STORE_TYPO3, SANITIZE_ALLOW_ALNUMX)) {
$html .= $this->buildInlineReport();
}
$html .= $report->process($this->t3data[T3DATA_BODYTEXT]);
return $html;
}
/** Constructs a form to directly edit qfq content elements inline.
*
* @return string - the html code
* @throws CodeException
* @throws DbException
* @throws DownloadException
* @throws UserFormException
* @throws UserReportException
*/
private function buildInlineReport() {
$uid = $this->t3data[T3DATA_UID];
$bodytext = $this->t3data[T3DATA_BODYTEXT_RAW];
$header = $this->t3data[T3DATA_HEADER];
$icon = Support::renderGlyphIcon(GLYPH_ICON_TASKS);
$showFormJs = '$("#tt-content-edit-' . $uid . '").toggleClass("hidden")';
$toggleBtn = Support::wrapTag("<a href='#' onclick='$showFormJs' style='float:right;'>", $icon);
$saveBtnAttributes = Support::doAttribute('class', 'btn btn-default') .
Support::doAttribute('id', "tt-content-save-$uid") .
Support::doAttribute('type', 'submit') .
Support::doAttribute('style', 'float:right; margin:-5px;') .
Support::doAttribute('title', 'Save & Reload');
$saveBtnIcon = Support::renderGlyphIcon(GLYPH_ICON_CHECK);
$saveBtn = Support::wrapTag("<button $saveBtnAttributes>", $saveBtnIcon);
$header = "QFQ Page Content '$header'";
$headerBar = Support::wrapTag("<div class='col-md-12 qfq-form-title'>", $header . $saveBtn);
$ttContentCode = Support::htmlEntityEncodeDecode(MODE_ENCODE, $bodytext);
$codeBoxAttributes = Support::doAttribute('style', "width:100%;") .
Support::doAttribute('id', "tt-content-code-$uid") .
Support::doAttribute('rows',20) .
Support::doAttribute('name', REPORT_INLINE_BODYTEXT);
$codeBox = Support::wrapTag("<textarea $codeBoxAttributes>", $ttContentCode);
$form = join(' ', [$headerBar, $codeBox]);
$sipObj = new Sip;
$action = $sipObj->queryStringToSip(API_DIR . "/save.php?uid=$uid&" . REPORT_SAVE . "=1");
$formAttributes = Support::doAttribute('id', "tt-content-edit-$uid") .
Support::doAttribute('class', 'hidden') .
Support::doAttribute('method', 'post') .
Support::doAttribute('action', $action);
$form = Support::wrapTag("<form $formAttributes>", $form);
return $toggleBtn . $form;
}
/**
* @throws CodeException
* @throws DbException
* @throws UserFormException
*/
public function saveReport() {
$uid = $this->store->getVar(T3DATA_UID, STORE_SIP . STORE_ZERO, SANITIZE_ALLOW_DIGIT);
if ($uid == 0) {
// Check if it was called with a SIP (containing a uid)
// If not, this might be an attack => cancel.
return;
}
$bodytext = Support::htmlEntityEncodeDecode(MODE_DECODE, $_POST[REPORT_INLINE_BODYTEXT]);
$dbT3 = $this->store->getVar(SYSTEM_T3_DB_NAME, STORE_SYSTEM . STORE_EMPTY, SANITIZE_ALLOW_ALNUMX);
if ($dbT3 == '') {
$dbData = $this->store->getVar(SYSTEM_DB_NAME_DATA, STORE_SYSTEM, SANITIZE_ALLOW_ALNUMX);
$dbT3 = substr($dbData, 0, strrpos($dbData, "_") + 1) . 't3';
}
// Update bodytext
$sql = "UPDATE $dbT3.tt_content SET bodytext = ?, tstamp = UNIX_TIMESTAMP(NOW()) WHERE uid = ?";
$this->dbArray[$this->dbIndexData]->sql($sql, ROW_REGULAR, [$bodytext, $uid]);
// Clear cache
// Need to truncate cf_cache_pages because it is used to restore page-specific cache
$sql = "DELETE FROM $dbT3.cf_cache_pages WHERE 1";
$this->dbArray[$this->dbIndexData]->sql($sql);
$this->formSpec[F_FORWARD_MODE] = 'auto';
}
/**
* Save the current form.
*
......@@ -1379,8 +1464,13 @@ class QuickFormQuery {
* @throws UserReportException
*/
public function saveForm() {
if ($this->store->getVar(REPORT_SAVE, STORE_SIP . STORE_ZERO) == '1') {
$this->saveReport();
$json = array();
$json[REPORT_SAVE] = 1;
} else {
$json = $this->doForm(FORM_SAVE);
}
return $json;
}
......
......@@ -309,7 +309,7 @@ class Save {
$sql .= '`' . $column . '` = ?, ';
}
$sql = substr($sql, 0, strlen($sql) - 2) . ' WHERE id = ?';
$sql = substr($sql, 0, strlen($sql) - 2) . " WHERE id = ?";
$values[] = $recordId;
$rc = $this->db->sql($sql, ROW_REGULAR, array_values($values));
......
......@@ -152,27 +152,26 @@ class Dirty {
$formDirtyMode = $tableVars[F_DIRTY_MODE];
$rcMd5 = '';
if ($formDirtyMode == DIRTY_MODE_NONE) {
$answer = [API_STATUS => 'success', API_MESSAGE => ''];
} else {
// Check for changed record. Compute $rcMd5
$flagModified = $this->isRecordModified($tableName, $recordId, $recordHashMd5, $rcMd5);
if (($recordHashMd5 != '') && $flagModified) {
return [API_STATUS => API_ANSWER_STATUS_CONFLICT, API_MESSAGE => 'The record has been modified in the meantime. Please reload the form, edit and save again.'];
}
$feUser = $this->session->get(SESSION_FE_USER);
// Look for already existing dirty record.
$recordDirty = $this->getRecordDirty($tableName, $recordId);
if (count($recordDirty) == 0) {
if ($formDirtyMode == DIRTY_MODE_NONE) {
$answer = [API_STATUS => 'success', API_MESSAGE => ''];
} else {
// No dirty record found.
$feUser = $this->session->get(SESSION_FE_USER);
$answer = $this->writeDirty($this->client[SIP_SIP], $recordId, $tableVars, $feUser, $rcMd5);
}
} else {
$answer = $this->conflict($recordDirty, $formDirtyMode);
}
}
return $answer;
}
......@@ -277,8 +276,9 @@ class Dirty {
* @param string $tableName
* @param int $recordId
* @param string $recordHashMd5 - timestamp e.g. '2017-07-27 14:06:56'
*
* @param $rcMd5
* @param string $tableId
*
* @return bool true if $recordHashMd5 is different from current record md5 hash.
*/
private function isRecordModified($tableName, $recordId, $recordHashMd5, &$rcMd5) {
......@@ -359,8 +359,9 @@ class Dirty {
$answer = [API_STATUS => API_ANSWER_STATUS_SUCCESS, API_MESSAGE => ''];
if ($recordId == 0) {
return $answer; // New records never have a recordDirty nor a conflict.
if ($recordId == 0 || // New records never have a recordDirty nor a conflict.
$dirtyMode == DIRTY_MODE_NONE) { // mode none -> no lock checking
return $answer;
}
// Check if the record has changed in the meantime.
......@@ -371,13 +372,9 @@ class Dirty {
$lockStatus = $this->getCheckDirty($tableName, $recordId, $rcRecordDirty, $rcMsg);
if (empty($rcRecordDirty)) {
if ($dirtyMode == DIRTY_MODE_NONE) {
return $answer; // only situation where it's ok that there is no dirtyRecord.
}
if ($formMode == FORM_DELETE) {
return $answer;
}
// This is pessimistic, but secure.
// throw new UserFormException("Missing record lock: please reload the form, edit and save again.", ERROR_DIRTY_MISSING_LOCK);
......
......@@ -146,6 +146,10 @@ class Support {
return $tag . $value . $closing;
}
public static function renderGlyphIcon($glyphIcon, $value = '') {
return Support::wrapTag("<span class='" . GLYPH_ICON . " $glyphIcon'>", $value);
}
/**
* Removes '$tag' and closing $tag from $value, if they are the outermost.
* E.g. unWrapTag('<p>', '<p>Hello World</p>') returns 'Hello World'
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment