Commit 04cc1fa8 authored by Carsten  Rose's avatar Carsten Rose
Browse files

F6721_STORE_USER: first implementation

parent 956dbeaf
Pipeline #908 passed with stage
in 1 minute and 42 seconds
......@@ -33,8 +33,7 @@ update-qfq-doc:
cd ../qfq-doc/; \
git commit -a; \
git push
wget -O /tmp/request_rebuild.php https://docs.typo3.org/~mbless/github.com/T3DocumentationStarter/Public-Info-053.git.make/request_rebuild.php
rm /tmp/request_rebuild.php
wget -O /dev/null https://docs.typo3.org/~mbless/github.com/T3DocumentationStarter/Public-Info-053.git.make/request_rebuild.php
git-revision: make-dist-dir
......
......@@ -640,7 +640,7 @@ SIPs
The following is a technical background information. Not needed to just use QFQ.
The SIPs (=Server Id Pairs) are uniq timestamps, created/registered on the fly for a specific browser session (=user). Every SIP is
registered on the server (= stored in a PHP Session) and contains one or more key/value pairs. The key/value pairs never leave
registered on the server (= stored in a browser session) and contains one or more key/value pairs. The key/value pairs never leave
the server. The SIPs will be used:
* to protect values not to be spoofed by anyone,
......@@ -1458,38 +1458,40 @@ Only variables that are known in a specified store can be substituted.
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
|Name |Description | Content |
+=====+========================================================================================+============================================================================+
| F | :ref:`STORE_FORM`: data not saved in database yet. | All native *FormElements*. Recent values from the Browser. |
| B | :ref:`STORE_BEFORE`: Record - the current record loaded in the form before any update. | All columns of the current record from the current table |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
| S | :ref:`STORE_SIP`: Client parameter 's' will indicate the current SIP, which will be | sip, r (recordId), form |
| | loaded from the SESSION repo to the SIP-Store. | |
| C | :ref:`STORE_CLIENT`: POST variable, if not found: GET variable. | Parameter sent from the Client (=Browser). |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
| R | :ref:`STORE_RECORD`: Record - the current record loaded in the form | All columns of the current record from the current table |
| D | Default values column : The *table.column* specified *default value*. | |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
| B | :ref:`STORE_BEFORE`: Record - the current record loaded in the form before any update | All columns of the current record from the current table |
| E | *Empty* - allways an empty string, might be helpful if a variable is empty or undefined| Any key |
| | and will be used in an SQL statement. | |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
| P | Parent record. E.g.: on multi & copy forms the current record of the outer query, | All columns of the MultiSQL Statement from the table for the current row |
| F | :ref:`STORE_FORM`: data not saved in database yet. | All native *FormElements*. Recent values from the Browser. |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
| D | Default values column : The *table.column* specified *default value*. | |
| L | :ref:`STORE_LDAP`: Will be filled on demand during processing of a *FormElement*. | Custom specified list of LDAP attributes |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
| M | Column type: The *table.column* specified *type* | |
| M | Column type: The *table.column* specified *type*. | |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
| C | :ref:`STORE_CLIENT`: POST variable, if not found: GET variable | Parameter sent from the Client (=Browser). |
| P | Parent record. E.g.: on multi & copy forms the current record of the outer query. | All columns of the MultiSQL Statement from the table for the current row |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
| T | :ref:`STORE_TYPO3`: a) Bodytext (ttcontent record), b) Typo3 internal variables | See Typo3 tt_content record configuration |
| R | :ref:`STORE_RECORD`: Record - the current record loaded in the form. | All columns of the current record from the current table |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
| V | :ref:`STORE_VARS`: Generic variables | |
| S | :ref:`STORE_SIP`: Client parameter 's' will indicate the current SIP, which will be | sip, r (recordId), form |
| | loaded from the SESSION repo to the SIP-Store. | |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
| L | :ref:`STORE_LDAP`: Will be filled on demand during processing of a *FormElement* | Custom specified list of LDAP attributes |
| T | :ref:`STORE_TYPO3`: a) Bodytext (ttcontent record), b) Typo3 internal variables. | See Typo3 tt_content record configuration |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
| 0 | *Zero* - allways value: 0, might be helpful if a variable is empty or undefined and | Any key |
| | will be used in an SQL statement. | |
| U | :ref:`STORE_USER`: per user variables, valid as long as the browser session lives. | Set via report: '...' AS '_=<var name>' |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
| E | *Empty* - allways an empty string, might be helpful if a variable is empty or undefined| Any key |
| | and will be used in an SQL statement | |
| V | :ref:`STORE_VARS`: Generic variables. | |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
| Y | :ref:`STORE_SYSTEM`: a) Database, b) helper vars for logging/debugging: | |
| | SYSTEM_SQL_RAW ... SYSTEM_FORM_ELEMENT_COLUMN, c) Any custom fields: CONTACT, HELP, ...| |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
| 0 | *Zero* - allways value: 0, might be helpful if a variable is empty or undefined and | Any key |
| | will be used in an SQL statement. | |
+-----+----------------------------------------------------------------------------------------+----------------------------------------------------------------------------+
* Default *<prio>*: *FSRVD* - Form / SIP / Record / Vars / Table definition.
* Hint: Preferable, parameter should be submitted by SIP, not by Client (=URL).
......@@ -1724,6 +1726,20 @@ Store: *SYSTEM* - Y
See configuration_ for a list of all settings.
.. _STORE_USER:
Store: *USER* - U
-----------------
* Sanitized: *no*
At start of a new browser session (=user calls the website the first time or was logged out before) the store is empty.
As soon as a value is set in the store, it remains as long as the browser session is alive (=until user logs out).
Values can be set via report '... AS "_=<var name>"'
.. _LDAP:
LDAP
......@@ -5272,7 +5288,9 @@ Special column names
+------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| _XLSn |Used for Excel export. Prepend 'n=' and append a `newline` character around the string. See `excel-export`_. |
+------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| _+??? |The content will be wrapped in the tag '???'. Example: SELECT 'example' AS '_+a href="http://example.com"' creates '<a href="http://example.com">example</a>' |
| _+html-tag attributes |The content will be wrapped with '<html-tag attributes>'. Example: SELECT 'example' AS '_+a href="http://example.com"' creates '<a href="http://example.com">example</a>' |
+------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| _=varname |The content will be saved in store 'user' under 'varname'. Retrieve it later via {{varname:U}} |
+------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|_<nonReservedName> |Suppress output. Column names with leading underscore are used to select data from the database and make it available in other parts of the report without generating any output. |
+------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
......@@ -7150,6 +7168,59 @@ FormElement) forms: ::
rsep = ,&ensp;
}
Keep variables per user session
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Two pages (pass variable)
'''''''''''''''''''''''''
Sometimes it's useful to have variables per user (=browser session). Set a variable on page 'A' and retrieve the value
on page 'B'.
Page 'A' - set the variable: ::
10.sql = SELECT 'hello' AS '_=greeting'
Page 'B' - get the value: ::
10.sql = SELECT '{{greeting:UE}}'
If page 'A' has never been opened with the current browser session, nothing is printed (STORE_EMPTY gives an empty string).
If page 'A' is called, page 'B' will print 'hello'.
One page (collect variables)
''''''''''''''''''''''''''''
A page will be called with several SIP variables, but not at all at the same time. To still get all variables at any time: ::
# Normalize
10.sql = SELECT '{{order:USE:::sum}}' AS _=order, '{{step:USE:::5}}' AS _step, '{{direction:USE}}' AS _direction
# Different links
20.sql = SELECT 'p:{{pageAlias:T}}&order=count|t:Order by count|b|s' AS _link,
'p:{{pageAlias:T}}&order=sum|t:Order by sum|b|s' AS _link,
'p:{{pageAlias:T}}&step=10|t:Step=10|b|s' AS _link,
'p:{{pageAlias:T}}&step=50|t:Step=50|b|s' AS _link,
'p:{{pageAlias:T}}&direction=asc|t:Order by up|b|s' AS _link,
'p:{{pageAlias:T}}&direction=desc|t:Order by down|b|s' AS _link
30.sql = SELECT * FROM items ORDER BY {{order:U}} {{direction:U}} LIMIT {{step:U}}
Simulate/switch user: feUser
''''''''''''''''''''''''''''
Just set the STORE_USER variable 'feUser'.
All places with `{{feUser:Y}}` has to be replaced by `{{feUser:UY}}`: ::
# Normalize
10.sql = SELECT '{{feUser:UT}}' AS _=feUser
# Offer switching feUser
20.sql = SELECT 'p:{{pageAlias:T}}&feUser=account1|t:Become "account1"|b|s' AS _link,
'p:{{pageAlias:T}}&feUser={{feUser:T}}|t:Back to own identity|b|s' AS _link,
.. _`system`:
System
......
......@@ -45,9 +45,12 @@ Date: <date>
Notes
^^^^^
* *Existing* QFQ installations: to use the new 'tablesorter' feature, please add 'tablesorter-bootstrap.css',
* *Existing* QFQ installations: update your CSS/JS includes! The new tablesorter jquery plugin might break your
installation, if it isn't included.
To use the new 'tablesorter' feature, please add 'tablesorter-bootstrap.css',
'jquery.tablesorter.combined.min.js', 'jquery.tablesorter.pager.min.js', 'widget-columnSelector.min.js' in your
T3 template. See https://docs.typo3.org/typo3cms/drafts/github/T3DocumentationStarter/Public-Info-053/Manual.html#setup-css-js
Typo3 template record. See https://docs.typo3.org/typo3cms/drafts/github/T3DocumentationStarter/Public-Info-053/Manual.html#setup-css-js
Features
^^^^^^^^
......
......@@ -283,6 +283,9 @@ class AutoCron {
* @throws ShellException
* @throws UserFormException
* @throws UserReportException
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
*/
public function process() {
......
......@@ -338,6 +338,7 @@ const STORE_SYSTEM = "Y"; // various system values like db connection credential
const STORE_EXTRA = 'X'; // Persistent Store: contains arrays! Not Usefull for user. Used by system.
const STORE_ADDITIONAL_FORM_ELEMENTS = 'A'; // Internal Store to collect FormElements. Typically for 'hidden' elements of radio and checkbox. Helps render those elements at the end of the whole form rendering.
const STORE_LDAP = 'L';
const STORE_USER = 'U'; // Like STORE_EXTRA, but for user use.
const STORE_USE_DEFAULT = "FSRVD";
......@@ -1370,6 +1371,7 @@ const COLUMN_EXCEL_STRING = 'XLSs';
const COLUMN_EXCEL_BASE64 = 'XLSb';
const COLUMN_WRAP_TOKEN = '+';
const COLUMN_STORE_USER = '=';
const FORM_NAME_FORM = 'form';
const FORM_NAME_FORM_ELEMENT = 'formElement';
......
......@@ -51,6 +51,7 @@ class Delete {
* @param string $tableName
* @param integer $recordId
*
* @param string $primaryKey
* @throws CodeException
* @throws DbException
* @throws UserFormException
......@@ -98,6 +99,7 @@ class Delete {
* @param array $row
* @param $tableName
*
* @param $primaryKey
* @throws CodeException
* @throws DbException
* @throws UserFormException
......
......@@ -70,6 +70,7 @@ class File {
throw new UserFormException('SIP invalid: ' . $sipUpload, ERROR_SIP_INVALID);
}
$statusUpload = $this->store->getVar($sipUpload, STORE_EXTRA, SANITIZE_ALLOW_ALL);
if ($statusUpload === false) {
$statusUpload = array();
......
......@@ -1462,6 +1462,9 @@ class QuickFormQuery {
* @throws DownloadException
* @throws UserFormException
* @throws UserReportException
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
*/
public function saveForm() {
if ($this->store->getVar(REPORT_SAVE, STORE_SIP . STORE_ZERO) == '1') {
......@@ -1484,6 +1487,9 @@ class QuickFormQuery {
* @throws DownloadException
* @throws UserFormException
* @throws UserReportException
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
*/
public function updateForm() {
......@@ -1501,6 +1507,9 @@ class QuickFormQuery {
* @throws DownloadException
* @throws UserFormException
* @throws UserReportException
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
*/
public function dragAndDrop() {
$fillStoreForm = new FillStoreForm();
......@@ -1548,6 +1557,9 @@ class QuickFormQuery {
* @throws DownloadException
* @throws UserFormException
* @throws UserReportException
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
* @throws \PhpOffice\PhpSpreadsheet\Writer\Exception
*/
public function delete() {
......
......@@ -327,6 +327,8 @@ class Save {
* @throws DbException
* @throws UserFormException
* @throws UserReportException
* @throws \PhpOffice\PhpSpreadsheet\Exception
* @throws \PhpOffice\PhpSpreadsheet\Reader\Exception
*/
public function processAllUploads($recordId) {
......@@ -608,6 +610,7 @@ class Save {
/**
* @param $formElement
* @param $fileName
* @throws CodeException
* @throws DbException
* @throws UserFormException
......
......@@ -215,6 +215,7 @@ class Dirty {
* @param array $recordDirty
* @param string $currentFormDirtyMode
*
* @param $primaryKey
* @return array
*/
private function conflict(array $recordDirty, $currentFormDirtyMode, $primaryKey) {
......@@ -354,6 +355,7 @@ class Dirty {
* @param int $lockTimeout
* @param string $dirtyMode DIRTY_MODE_EXCLUSIVE, DIRTY_MODE_ADVISORY, DIRTY_MODE_NONE
* @param string $tableName
* @param $primaryKey
* @param int $recordId
*
* @param bool $flagCheckModifiedFirst
......
......@@ -964,14 +964,28 @@ class Report {
default :
if ($flagControl) {
// Columnnames starting with '+' will wrap the content. EG "SELECT 'apple' AS '+h1 class=best'" >> <h1 class=best>apple</h1>
if (isset($columnName[0]) && $columnName[0] == COLUMN_WRAP_TOKEN && isset($columnName[1])) {
$content = Support::wrapTag('<' . substr($columnName, 1) . '>', $columnValue);
break;
$flagOutput = false;
$token= isset($columnName[0])? $columnName[0]:'';
switch($token){
case COLUMN_WRAP_TOKEN:
if(isset($columnName[1])){
$content = Support::wrapTag('<' . substr($columnName, 1) . '>', $columnValue);
$flagOutput = true;
}
break;
case COLUMN_STORE_USER:
if(isset($columnName[1])) {
$this->store::setVar(substr($columnName, 1),$columnValue, STORE_USER);
}
break;
default:
break;
}
$flagOutput = false;
} else {
// No special Columnname: just add the column value.
$content .= $columnValue;
......
......@@ -165,13 +165,15 @@ class Store {
STORE_EMPTY => false,
STORE_SYSTEM => false,
STORE_EXTRA => false,
STORE_USER => false,
STORE_LDAP => false,
STORE_ADDITIONAL_FORM_ELEMENTS => false,
];
self::fillStoreTypo3($bodytext); // should be filled before fillStoreSystem() to offer T3 variables
self::fillStoreClient(); // should be filled before fillStoreSystem() to offer Client variables
self::fillStoreExtra(); // should be filled before fillStoreSystem() to restore variables like SYSTEM_SHOW_DEBUG_INFO
self::fillStorePhpSession(STORE_EXTRA); // should be filled before fillStoreSystem() to restore variables like SYSTEM_SHOW_DEBUG_INFO
self::fillStorePhpSession(STORE_USER); // should be filled before fillStoreSystem() to restore variables like SYSTEM_SHOW_DEBUG_INFO
self::fillStoreSystem($fileConfigIni);
self::fillStoreSip();
}
......@@ -516,21 +518,22 @@ class Store {
/**
* Fills the STORE_EXTRA.
*
* @param string $storeName STORE_EXTRA, STORE_USER
* @throws CodeException
* @throws UserFormException
* @throws \qfq\CodeException
*/
private static function fillStoreExtra() {
private static function fillStorePhpSession($storeName) {
$value = Session::get(STORE_EXTRA);
$value = Session::get($storeName);
if (!isset($_SESSION[SESSION_NAME][STORE_EXTRA]) || $_SESSION[SESSION_NAME][STORE_EXTRA] === null) {
if (!isset($_SESSION[SESSION_NAME][$storeName]) || $_SESSION[SESSION_NAME][$storeName] === null) {
$value = false;
}
if ($value === false) {
self::setStore(array(), STORE_EXTRA, true);
self::setStore(array(), $storeName, true);
} else {
self::setStore($_SESSION[SESSION_NAME][STORE_EXTRA], STORE_EXTRA, true);
self::setStore($_SESSION[SESSION_NAME][$storeName], $storeName, true);
}
}
......@@ -617,38 +620,40 @@ class Store {
*
* @param string $key
* @param string|array $value
* @param string $store
* @param string $storeName
* @param bool|true $overWrite
*
* @throws UserFormException
* @throws \qfq\CodeException
*/
public static function setVar($key, $value, $store, $overWrite = true) {
public static function setVar($key, $value, $storeName, $overWrite = true) {
// Check valid Storename
if (!isset(self::$sanitizeStore))
throw new UserFormException("Unknown Store: $store", ERROR_UNNOWN_STORE);
if (!isset(self::$sanitizeStore)) {
throw new UserFormException("Unknown Store: $storeName", ERROR_UNNOWN_STORE);
}
if ($store === STORE_ZERO)
if ($storeName === STORE_ZERO) {
throw new CodeException("setVar() for STORE_ZERO is impossible - there are no values.", ERROR_SET_STORE_ZERO);
}
// Complain if already set and different.
if ($overWrite === false && isset(self::$raw[$store][$key]) && self::$raw[$store][$key] != $value) {
throw new UserFormException("Value of '$key' already set in store '$store'.", ERROR_STORE_KEY_EXIST);
if ($overWrite === false && isset(self::$raw[$storeName][$key]) && self::$raw[$storeName][$key] != $value) {
throw new UserFormException("Value of '$key' already set in store '$storeName'.", ERROR_STORE_KEY_EXIST);
}
self::$raw[$store][$key] = $value;
self::$raw[$storeName][$key] = $value;
// The STORE_EXTRA saves arrays and is persistent
if ($store === STORE_EXTRA) {
// The STORE_EXTRA / STORE_USER saves arrays and is persistent
if ($storeName === STORE_EXTRA || $storeName === STORE_USER) {
$store = Session::get(STORE_EXTRA);
$data = Session::get($storeName);
if ($store === false) {
$store = array();
if ($data === false) {
$data = array();
}
$store[$key] = $value;
Session::set(STORE_EXTRA, $store);
$data[$key] = $value;
Session::set($storeName, $data);
}
}
......
......@@ -70,7 +70,7 @@ abstract class AbstractDatabaseTest extends TestCase {
// Init the store also reads db credential configuration
$this->store = qfq\Store::getInstance('', true);
$this->sip = new qfq\Sip('fakesessionname', true);
$this->sip = new qfq\Sip(true);
$this->sip->sipUniqId('badcaffee1234');
// SWITCH to TestDB
......
......@@ -1046,7 +1046,7 @@ class DirtyTest extends \AbstractDatabaseTest {
$this->executeSQLFile(__DIR__ . '/fixtures/Generic.sql', true);
$this->executeSQLFile(__DIR__ . '/fixtures/TestDirty.sql', true);
$this->sip = new \qfq\Sip('fakesessionname', true);
$this->sip = new \qfq\Sip(true);
$this->sip->sipUniqId('badcaffee1234');
$_COOKIE[SESSION_NAME] = 'SessionCookieAlice';
}
......
......@@ -24,7 +24,7 @@ class SipTest extends TestCase {
public function testUrlparamToSip() {
$sip = new Sip('fakesessionname', true);
$sip = new Sip(true);
$sip->sipUniqId('badcaffee1234');
$result = $sip->queryStringToSip("http://example.com/index.php?id=input&r=1&form=person", RETURN_URL);
......@@ -71,7 +71,7 @@ class SipTest extends TestCase {
* @throws UserReportException
*/
public function testGetVarsFromSip() {
$sip = new Sip('fakesessionname', true);
$sip = new Sip(true);
$sip->sipUniqId('badcaffee1234');
$sip2 = $sip->queryStringToSip("http://example.com/index.php?a=1&b=2&c=3", RETURN_SIP);
......@@ -103,10 +103,10 @@ class SipTest extends TestCase {
*
*/
public function testFakeUniqId() {
$sip = new Sip('fakesessionname', true);
$sip = new Sip(true);
$this->assertEquals('badcaffee1234', $sip->sipUniqId('badcaffee1234'));
$sip = new Sip('fakesessionname', true);
$sip = new Sip(true);
$this->assertEquals('badcaffee5678', $sip->sipUniqId('badcaffee5678'));
}
......@@ -116,7 +116,7 @@ class SipTest extends TestCase {
*/
public function testGetSipFromUrlParam() {
$sip = new Sip('fakesessionname', true);
$sip = new Sip(true);
$sip->sipUniqId('badcaffee1234');
$result = $sip->queryStringToSip("http://example.com/index.php?id=input&r=1&form=person", RETURN_URL);
......@@ -138,7 +138,7 @@ class SipTest extends TestCase {
*
*/
public function testSipUniqId() {
$sip = new Sip('fakesessionname', true);
$sip = new Sip(true);
$sip->sipUniqId('badcaffee1234');
$s = $sip->sipUniqId('badcaffee1234');
......@@ -150,7 +150,7 @@ class SipTest extends TestCase {
* @throws UserFormException
*/
public function testGetQueryStringFromSip() {
$sip = new Sip('fakesessionname', true);
$sip = new Sip(true);
$sip->sipUniqId('badcaffee1234');
$s = $sip->queryStringToSip("http://example.com/index.php?id=input&r=20&form=person", RETURN_SIP);
......
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