Commit 201e1510 authored by Rafael Ostertag's avatar Rafael Ostertag
Browse files

Merge remote-tracking branch 'origin/crose_work' into raos_work

parents bd343f14 89879621
...@@ -60,19 +60,31 @@ DELETE ...@@ -60,19 +60,31 @@ DELETE
* class=record-delete * class=record-delete
* Button: data-sip={{SIP}} * Button: data-sip={{SIP}}
* SIP values:
* SIP_RECORD_ID: Mandatory.
* SIP_TABLE: Either SIP_TABLE or SIP_FORM has to be given.
* SIP_FORM: Either SIP_TABLE or SIP_FORM has to be given. Not implemented now.
* SIP_TARGET_URL: Only with SIP_MODE_ANSWER=MODE_HTML - Url to redirect browser to.
* SIP_MODE_ANSWER: MODE_JSON / MODE_HTML. If not given, this means MODE_JSON.
* Three possible variants with delete links: * Three possible variants with delete links:
* Form: main record * (1) Form: main record
* HTML Code: * HTML Code:
<button id="delete-button" type="button" class="btn btn-default navbar-btn" ><span class="glyphicon glyphicon-trash"></span></button> <button id="delete-button" type="button" class="btn btn-default navbar-btn" ><span class="glyphicon glyphicon-trash"></span></button>
* Form: subrecord, one delete button per record * (2) Form: subrecord, one delete button per record
* Report: typially inside a table, but maybe different.
* HTML Code: * HTML Code:
<button type="button" class="record-delete" data-sip={{SIP}} ><span class="glyphicon glyphicon-trash"></span></button> <button type="button" class="record-delete" data-sip={{SIP}} ><span class="glyphicon glyphicon-trash"></span></button>
* (3) Report: typially inside a table, but maybe different.
<button type="button" class="record-delete" data-sip={{SIP}} ><span class="glyphicon glyphicon-trash"></span></button>
Upload Upload
----------------- -----------------
...@@ -87,12 +99,12 @@ Upload ...@@ -87,12 +99,12 @@ Upload
Upload to server, before 'save' Upload to server, before 'save'
............................... ...............................
* If a user open's a file for upload via the browse button, that file is immediately transmitted to the server. The user will see a turning wheel during until the upload finished. * If a user open's a file for upload via the browse button, that file is immediately transmitted to the server. The user will see a turning wheel until the upload finished.
* After successfull upload the 'Browse' button disappears and the filename, plus the delete button, will be displayed (client logic). * After successfull upload the 'Browse' button disappears and the filename, plus the delete button, will be displayed (client logic).
* The uploaded file will be checked: maxsize, mime type, check script. * The uploaded file will be checked: maxsize, mime type, check script.
* The uploaded file is still temporary. It has been renamed from $_SESSION['X'][<uploadSip>][FILES_TMP_NAME] to $_SESSION['X'][<uploadSip>][FILES_TMP_NAME].cached * The uploaded file is still temporary. It has been renamed from $_SESSION['X'][<uploadSip>][FILES_TMP_NAME] to $_SESSION['X'][<uploadSip>][FILES_TMP_NAME].cached
* The upload action will be saved in the user session. * The upload action will be saved in the user session.
* $_SESSION['X'][<uploadSip>][FILES_TMP_NAME|FILES_NAME|FILES_ERROR|FILE_SIZE] * $_SESSION['X'][<uploadSip>][FILES_TMP_NAME|FILES_NAME|FILES_ERROR|FILE_SIZE]
* Clicks the user on delete button: * Clicks the user on delete button:
* In the usersession a flagDelete will be set: $_SESSION['X'][<uploadSip>]['flagDelete']='1' * In the usersession a flagDelete will be set: $_SESSION['X'][<uploadSip>]['flagDelete']='1'
* An optional previous upload file (still not saved on the final place) will be deleted. * An optional previous upload file (still not saved on the final place) will be deleted.
...@@ -100,13 +112,15 @@ Upload to server, before 'save' ...@@ -100,13 +112,15 @@ Upload to server, before 'save'
Form save Form save
......... .........
* Before building the insert/update, process all 'uploads'. * Step 1: insert /update the record.
* Get every uniq sipUpload to every upload formElement. Get the corresponding temporary uploaded filename. * Step 2: process all 'uploads'.
* If $_SESSION['X'][<uploadSip>]['flagDelete']='1' is set, delete prefious uploaded file. * Get every uniq sipUpload to every upload formElement. Get the corresponding temporary uploaded filename.
* Calculate <destination> * If $_SESSION['X'][<uploadSip>]['flagDelete']='1' is set, delete prefious uploaded file.
* mv <file>.cached <destination> * Calculate <destination>
* clientvalue[<feName>] = <destination> * mv <file>.cached <destination>
* delete $_SESSION['X'][<uploadSip>] * clientvalue[<feName>] = <destination>
* delete $_SESSION['X'][<uploadSip>]
* Step 3: update record with final `FileDestination'
Formelement type: DATE / DATETIME / TIME Formelement type: DATE / DATETIME / TIME
---------------------------------------- ----------------------------------------
...@@ -132,8 +146,8 @@ Formelement type: DATE / DATETIME / TIME ...@@ -132,8 +146,8 @@ Formelement type: DATE / DATETIME / TIME
* datetime format: 'DATE TIME' * datetime format: 'DATE TIME'
Debug / Log / Errormessages Debug / Log
=========================== ===========
* Before firing a SQL or doing processing of an FormElement, set some debugging / error variables: * Before firing a SQL or doing processing of an FormElement, set some debugging / error variables:
...@@ -157,6 +171,20 @@ Debug / Log / Errormessages ...@@ -157,6 +171,20 @@ Debug / Log / Errormessages
$this->store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM) === 'yes' $this->store->getVar(SYSTEM_SHOW_DEBUG_INFO, STORE_SYSTEM) === 'yes'
Errormessages & Eceptions
=========================
* Exception types:
* Code
* Db
* User Form
* user Report
* plus an Errorhandler which throws exceptions
* Exceptions inside of an API call delivers the error code and msg as JSON to the client.
* Typo3 suppress E_NOTICE (e.g. undefined index). To catch E_NOTICE in QFQ, it will be temporaly enabled in QfqCongroller.php.
Stores Stores
====== ======
......
Neue Versionsnummer
===================
1) In folgenden Files anpassen:
* extension/Documentation/_make/conf.py: release, version
* extension/Documentation/Settings.yml: version
* extension/ext_emconf.php: version
2) Im Projectverzeichnis:
make t3sphinx (nicht sicher ob das noetig ist)
3) Neuen Tag vergeben: git tag v0.4
4) Alle Files, inkl. Tags, in GIT einchecken.
5) Per PhpStorm Sync aller Files auf VM qfq
6) In T3 Instanz Dokumentation rendern lassen.
T3 6.2: Admin Tools > Extension Manager > QFQ > Doku HTML: rechts oben 'Render Documentation'
...@@ -14,11 +14,17 @@ require_once(__DIR__ . '/../../qfq/qfq/exceptions/CodeException.php'); ...@@ -14,11 +14,17 @@ require_once(__DIR__ . '/../../qfq/qfq/exceptions/CodeException.php');
require_once(__DIR__ . '/../../qfq/qfq/exceptions/DbException.php'); require_once(__DIR__ . '/../../qfq/qfq/exceptions/DbException.php');
class QfqController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController { class QfqController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {
public function showAction() {
public function showAction() {
try { try {
$contentObject = $this->configurationManager->getContentObject(); $contentObject = $this->configurationManager->getContentObject();
// By T3 default 'E_NOTICE' is unset. E.g. 'Undefined Index' will throw an exception.
// QFQ like to see those 'E_NOTICE'
$origErrorReporting = error_reporting();
error_reporting($origErrorReporting | E_NOTICE);
$qfq = new \qfq\QuickFormQuery($contentObject->data); $qfq = new \qfq\QuickFormQuery($contentObject->data);
$html = $qfq->process(); $html = $qfq->process();
...@@ -34,6 +40,9 @@ class QfqController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController { ...@@ -34,6 +40,9 @@ class QfqController extends \TYPO3\CMS\Extbase\Mvc\Controller\ActionController {
$html = "Generic Exception: " . $e->getMessage(); $html = "Generic Exception: " . $e->getMessage();
} }
// Restore has to be outside of try/catch - E_NOTICE needs to unset for further T3 handling after an QFQ Exception.
error_reporting($origErrorReporting);
$this->view->assign('qfqOutput', $html); $this->view->assign('qfqOutput', $html);
return $this->view->render(); return $this->view->render();
} }
......
...@@ -90,8 +90,6 @@ Setup a *report* to manage all *forms*: Create a Typo3 page and insert a content ...@@ -90,8 +90,6 @@ Setup a *report* to manage all *forms*: Create a Typo3 page and insert a content
+------------------------+----------------------------------+----------------------------------------------------------------------------+ +------------------------+----------------------------------+----------------------------------------------------------------------------+
| DB_INIT | DB_INIT=set names utf8 | Global init for using the database. | | DB_INIT | DB_INIT=set names utf8 | Global init for using the database. |
+------------------------+----------------------------------+----------------------------------------------------------------------------+ +------------------------+----------------------------------+----------------------------------------------------------------------------+
| SESSION_NAME | SESSION_NAME=qfq | PHP Session name, by default 'qfq' |
+------------------------+----------------------------------+----------------------------------------------------------------------------+
| SQL_LOG | SQL_LOG=sql.log | Filename to log SQL commands: relative to <ext_dir> or absolute. | | SQL_LOG | SQL_LOG=sql.log | Filename to log SQL commands: relative to <ext_dir> or absolute. |
+------------------------+----------------------------------+----------------------------------------------------------------------------+ +------------------------+----------------------------------+----------------------------------------------------------------------------+
| SQL_LOG_MODE | SQL_LOG_MODE=modify | *all*: every statement will be logged - this is a lot | | SQL_LOG_MODE | SQL_LOG_MODE=modify | *all*: every statement will be logged - this is a lot |
...@@ -117,7 +115,6 @@ Example: *<ext_dir>/config.ini* ...@@ -117,7 +115,6 @@ Example: *<ext_dir>/config.ini*
DB_NAME = qfq_db DB_NAME = qfq_db
DB_NAME_TEST = qfq_db_test DB_NAME_TEST = qfq_db_test
DB_INIT = set names utf8 DB_INIT = set names utf8
SESSION_NAME = qfq
SQL_LOG = sql.log SQL_LOG = sql.log
SHOW_DEBUG_INFO = auto SHOW_DEBUG_INFO = auto
CSS_LINK_CLASS_INTERNAL = internal CSS_LINK_CLASS_INTERNAL = internal
......
...@@ -6,8 +6,8 @@ ...@@ -6,8 +6,8 @@
conf.py: conf.py:
copyright: 2016 copyright: 2016
project: QFQ Extension project: QFQ Extension
version: 0.3 version: 0.4
release: 0.3.0 release: 0.4.0
latex_documents: latex_documents:
- - Index - - Index
- qfq.tex - qfq.tex
......
...@@ -57,9 +57,9 @@ copyright = u'2016, Carsten Rose' ...@@ -57,9 +57,9 @@ copyright = u'2016, Carsten Rose'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.3' version = '0.4'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '0.3.0' release = '0.4.0'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
......
<p> <f:format.raw value="{qfqOutput}"/>
<f:format.raw value="{qfqOutput}"/>
</p>
\ No newline at end of file
...@@ -8,8 +8,6 @@ DB_NAME_TEST = <TESTDB> ...@@ -8,8 +8,6 @@ DB_NAME_TEST = <TESTDB>
DB_INIT = set names utf8 DB_INIT = set names utf8
SESSION_NAME = qfq
SQL_LOG = sql.log SQL_LOG = sql.log
; all, modify ; all, modify
SQL_LOG_MODE = modify SQL_LOG_MODE = modify
......
...@@ -10,5 +10,5 @@ $EM_CONF[$_EXTKEY] = array( ...@@ -10,5 +10,5 @@ $EM_CONF[$_EXTKEY] = array(
'dependencies' => 'fluid,extbase', 'dependencies' => 'fluid,extbase',
'clearcacheonload' => true, 'clearcacheonload' => true,
'state' => 'alpha', 'state' => 'alpha',
'version' => '0.3' 'version' => '0.4'
); );
\ No newline at end of file
...@@ -47,18 +47,14 @@ require_once(__DIR__ . '/../qfq/Constants.php'); ...@@ -47,18 +47,14 @@ require_once(__DIR__ . '/../qfq/Constants.php');
$answer = array(); $answer = array();
$answer[API_REDIRECT] = API_ANSWER_REDIRECT_NO; $answer[API_REDIRECT] = API_ANSWER_REDIRECT_NO;
$answer[API_MESSAGE] = ''; $answer[API_MESSAGE] = 'Something failed';
$answer[API_STATUS] = API_ANSWER_STATUS_ERROR; $answer[API_STATUS] = API_ANSWER_STATUS_ERROR;
try { try {
$qfq = new \qfq\QuickFormQuery(['bodytext' => '']); $qfq = new \qfq\QuickFormQuery(['bodytext' => '']);
$qfq->delete(); $result = $qfq->delete();
$answer[API_MESSAGE] = 'delete: success';
$answer[API_REDIRECT] = API_ANSWER_REDIRECT_CLIENT;
$answer[API_STATUS] = API_ANSWER_STATUS_SUCCESS;
} catch (qfq\UserFormException $e) { } catch (qfq\UserFormException $e) {
$answer[API_MESSAGE] = $e->formatMessage(); $answer[API_MESSAGE] = $e->formatMessage();
...@@ -70,6 +66,16 @@ try { ...@@ -70,6 +66,16 @@ try {
$answer[API_MESSAGE] = "Generic Exception: " . $e->getMessage(); $answer[API_MESSAGE] = "Generic Exception: " . $e->getMessage();
} }
header("Content-Type: application/json"); // Fallbak. in case an exception has been thrown and $result is not filled.
echo json_encode($answer); if (!isset($result[MSG_HEADER]) && !isset($result[MSG_CONTENT])) {
$result[MSG_HEADER] = "Content-Type: application/json";
$result[MSG_CONTENT] = json_encode($answer);
}
if (isset($result[MSG_HEADER]) && $result[MSG_HEADER] !== '')
header($result[MSG_HEADER]);
echo $result[MSG_CONTENT];
This diff is collapsed.
...@@ -12,21 +12,27 @@ const NESTING_TOKEN_OPEN = '#&nesting-open-&#'; ...@@ -12,21 +12,27 @@ const NESTING_TOKEN_OPEN = '#&nesting-open-&#';
const NESTING_TOKEN_CLOSE = '#&nesting-close&#'; const NESTING_TOKEN_CLOSE = '#&nesting-close&#';
const NESTING_TOKEN_LENGTH = 17; const NESTING_TOKEN_LENGTH = 17;
class BodytextParser {
class BodytextParser {
/** /**
* @param $bodytext * @param $bodytext
* @return mixed * @return mixed
*/ */
public function process($bodytext) { public function process($bodytext) {
$bodytext = $this->trimAndRemoveCommentAndEmptyLine($bodytext);
$nestingOpen = '';
$nestingClose = '';
$bodytext = $this->trimAndRemoveCommentAndEmptyLine($bodytext, $nestingOpen, $nestingClose);
// Encrypt double curly braces to prevent false positives with nesting: form = {{form}}\n // Encrypt double curly braces to prevent false positives with nesting: form = {{form}}\n
$bodytext = Support::encryptDoubleCurlyBraces($bodytext); $bodytext = Support::encryptDoubleCurlyBraces($bodytext);
$bodytext = $this->encryptNestingDelimeter($bodytext); $bodytext = $this->joinLine($bodytext, $nestingOpen, $nestingClose);
$bodytext = $this->joinLine($bodytext);
$bodytext = $this->unNest($bodytext); $bodytext = $this->encryptNestingDelimeter($bodytext, $nestingOpen, $nestingClose);
$bodytext = $this->trimAndRemoveCommentAndEmptyLine($bodytext); $bodytext = $this->unNest($bodytext, $nestingOpen, $nestingClose);
$bodytext = $this->trimAndRemoveCommentAndEmptyLine($bodytext, $nestingOpen, $nestingClose);
$bodytext = Support::decryptDoubleCurlyBraces($bodytext); $bodytext = Support::decryptDoubleCurlyBraces($bodytext);
if (strpos($bodytext, NESTING_TOKEN_OPEN) !== false) { if (strpos($bodytext, NESTING_TOKEN_OPEN) !== false) {
...@@ -42,55 +48,125 @@ class BodytextParser { ...@@ -42,55 +48,125 @@ class BodytextParser {
* @return string * @return string
*/ */
private function trimAndRemoveCommentAndEmptyLine($bodytext) { private function trimAndRemoveCommentAndEmptyLine($bodytext, &$nestingOpen, &$nestingClose) {
$data = array(); $data = array();
$src = explode(PHP_EOL, $bodytext); $src = explode(PHP_EOL, $bodytext);
if ($src === false) {
return '';
}
$firstLine = trim($src[0]);
foreach ($src as $row) { foreach ($src as $row) {
$row = trim($row); $row = trim($row);
if ($row === '' || $row[0] === '#') { if ($row === '' || $row[0] === '#') {
continue; continue;
} }
$data[] = $row; $data[] = $row;
} }
$this->setNestingToken($firstLine, $nestingOpen, $nestingClose);
return implode(PHP_EOL, $data); return implode(PHP_EOL, $data);
} }
/** /**
* Encrypt '{\n' and '}\n' by more complex token. * Set the 'nesting token for this tt-conten record. Valid tokens are {}, <>, [], ().
* If the first line of bodytext is a comment line and the last char of that line is a valid token: set that one.
* If not: set {} as nesting token.
* *
* @param $bodytext * Example:
* @return mixed * # Some nice text - no token found, take {}
* # ] - []
* # Powefull QFQ: < - <>
*
* @param $firstLine
* @param $nestingOpen
* @param $nestingClose
*/ */
private function encryptNestingDelimeter($bodytext) { private function setNestingToken($firstLine, &$nestingOpen, &$nestingClose) {
// Take care that a trailing '}' will be recognised: add '\n'
if (substr($bodytext, -1) === '}') { if ($nestingOpen !== '') {
$bodytext .= "\n"; return; // tokens already set or not bodytext: do not change.
} }
$bodytext = str_replace("{\n", NESTING_TOKEN_OPEN, $bodytext); // Nothing defined: set default {}.
$bodytext = str_replace("}\n", NESTING_TOKEN_CLOSE, $bodytext); if ($firstLine === false || $firstLine === '' || $firstLine[0] !== '#') {
return $bodytext; $nestingOpen = '{';
$nestingClose = '}';
return;
}
$pos = 0;
$tokenList = '{}<>[]()';
// Definition: first line of bodytext, has to be a comment line. If the last char is one of the valid token: set that one.
// Nothing found: set {}.
if ($firstLine[0] === '#') {
$token = substr($firstLine, -1);
$pos = strpos($tokenList, $token);
if ($pos === false) {
$pos = 0;
} else {
if ($pos % 2 === 1) {
$pos -= 1;
}
}
}
$nestingOpen = substr($tokenList, $pos, 1);
$nestingClose = substr($tokenList, $pos + 1, 1);
} }
/** /**
* Join lines, which do not begin with '<level>.<keyword>[ ]=' * Join lines. Preservers Nesting.
*
* Iterates over all lines.
* Is a line a 'new line'?
* no: concat it to the last one.
* yes: flush the buffer, start a new 'new line'
*
* New Line Trigger:
* a: {
* b: }
* c: 20
* d: 20.30
*
* e: 5 {
* f: 5.10 {
*
* g: head =
* h: 10.20.head =
*
* c,d,e,f: ^\d+(\.\d+)*(\s*{)?$
* g,h: ^(\d+\.)*(sql|head)\s*=
* *
* @param array $bodytextArray * @param array $bodytextArray
* @return string * @return string
*/ */
private function joinLine($bodytext) { private function joinLine($bodytext, $nestingOpen, $nestingClose) {
$data = array(); $data = array();
$bodytextArray = explode(PHP_EOL, $bodytext); $bodytextArray = explode(PHP_EOL, $bodytext);
$nestingOpenRegexp = $nestingOpen;
if ($nestingOpen === '(' || $nestingOpen === '[') {
$nestingOpenRegexp = '\\' . $nestingOpen;
}
$full = ''; $full = '';
foreach ($bodytextArray as $row) { foreach ($bodytextArray as $row) {
// Valid 'new line' starts indicators: form, <level>, <level.sublevel>, <level>.<keyword>, {, <level> {, }
if ((1 === preg_match('/^\s*(\d*(\.)?)*\s*(head|althead|tail|sql|rbeg|rend|renr|rsep|fbeg|fend|fsep|' .
TYPO3_FORM . '|' . TYPO3_DEBUG_SHOW_BODY_TEXT . '|' . TYPO3_RECORD_ID . ') *=/', $row)) // if ((1 === preg_match('/^\s*(\d*(\.)?)*\s*(' . TOKEN_VALID_LIST . ') *=/', $row))
|| (1 === preg_match('/^\s*(\d*(\.)?)*\s*({|})\s*/', $row)) // || (1 === preg_match('/^\s*(\d*(\.)?)*\s*(' . $nestingOpen . '|' . $nestingClose . ')/', $row))
|| (1 === preg_match('/^\s*(\d+(\.)?)+/', $row)) // || (1 === preg_match('/^\s*(\d+(\.)?)+/', $row))
if (($row == $nestingOpen || $row == $nestingClose)
|| (1 === preg_match('/^\d+(\.\d+)*(\s*' . $nestingOpenRegexp . ')?$/', $row))
|| (1 === preg_match('/^(\d+\.)*(' . TOKEN_VALID_LIST . ')\s*=/', $row))
) { ) {
// if there is already something: save this // if there is already something: save this
...@@ -114,14 +190,50 @@ class BodytextParser { ...@@ -114,14 +190,50 @@ class BodytextParser {
return implode(PHP_EOL, $data); return implode(PHP_EOL, $data);
} }
//PREG_SPLIT_DELIM_CAPTURE /**
* Encrypt $nestingOpen and $nestingClose by a more complex token. This makes it easy to search later for '}' or '{'
*
* Valid open (complete line): {, 10 {, 10.20 {
* Valid close (complete line): }
*
* @param $bodytext
* @return mixed
*/
private function encryptNestingDelimeter($bodytext, $nestingOpen, $nestingClose) {
if ($nestingOpen === '(' || $nestingOpen === '[') {
$nestingOpen = '\\' . $nestingOpen;
$nestingClose = '\\' . $nestingClose;
}
$bodytext = preg_replace('/^((\d+)(\.\d+)*\s*)?(' . $nestingOpen . ')$/m', '$1' . NESTING_TOKEN_OPEN, $bodytext);
$bodytext = preg_replace('/^' . $nestingClose . '$/m', '$1' . NESTING_TOKEN_CLOSE, $bodytext);
return $bodytext;
}
/** /**
* Unnest all level.
*
* Input:
* 10 {
* sql = SELECT
* 20.sql = INSERT ..
* 30 {
* sql = DELETE
* }
* }
*
* Output:
* 10.sql = SELECT
* 10.20.sql = INSERT
* 10.20.30.sql = DELETE
*
* @param $bodytext * @param $bodytext
* @return mixed|string * @return mixed|string
* @throws UserFormException * @throws UserFormException
*/ */
private function unNest($bodytext) {