Commit 9341c03f authored by Carsten  Rose's avatar Carsten Rose

Merge branch 'develop' into 'master'

Develop

See merge request !251
parents 36e6c456 af5b9e6b
Pipeline #3223 passed with stages
in 3 minutes and 27 seconds
......@@ -9,7 +9,7 @@ variables:
stages:
- before
- build
# - selenium
- selenium
documentation:
stage: before
......@@ -48,29 +48,30 @@ release:
- scp qfq_${VERSION}_*.zip w16:qfq/releases/
- mv qfq_${VERSION}_*.zip build/qfq.zip
# selenium:
# stage: selenium
# script:
# - unzip -q build/qfq.zip -d qfq
# - cd docker/
# - ./run_qfq_docker.sh -no-deploy
# - ./deploy_to_container.sh ../qfq
# - ./run_selenium_tests_docker.sh
# - echo "hello"
# after_script:
# # remove containers and move logs to persistent location
# - cd docker; ./remove-containers.sh <<< "y"
# - cd ..
# - umask 002
# - mkdir "$SELENIUM_LOGS_PATH/$CI_COMMIT_SHORT_SHA"
# - cp extension/Tests/selenium/selenium_logs/* "$SELENIUM_LOGS_PATH/$CI_COMMIT_SHORT_SHA/"
# - echo "Selenium Logs copied to $SELENIUM_LOGS_PATH/$CI_COMMIT_SHORT_SHA/"
# - echo "Or download result (log/screenshot) in gitlab under CI/CD > Pipelines <job> > right side 'Artifacts'"
selenium:
stage: selenium
script:
- unzip -q build/qfq.zip -d qfq
- cd docker/
- ./run_qfq_docker.sh -no-deploy
- ./deploy_to_container.sh ../qfq
- ./run_selenium_tests_docker.sh
- echo "hello"
after_script:
# remove containers and move logs to persistent location
- cd docker; ./remove-containers.sh <<< "y"
- cd ..
- umask 002
- mkdir "$SELENIUM_LOGS_PATH/$CI_COMMIT_SHORT_SHA"
- cp extension/Tests/selenium/selenium_logs/* "$SELENIUM_LOGS_PATH/$CI_COMMIT_SHORT_SHA/"
- echo "Selenium Logs copied to $SELENIUM_LOGS_PATH/$CI_COMMIT_SHORT_SHA/"
- echo "Or download result (log/screenshot) in gitlab under CI/CD > Pipelines <job> > right side 'Artifacts'"
# artifacts:
# expire_in: 1 week
# paths:
# - extension/Tests/selenium/selenium_logs/
artifacts:
expire_in: 1 week
when: on_failure
paths:
- extension/Tests/selenium/selenium_logs/
This diff is collapsed.
......@@ -40,4 +40,5 @@ Software distributed together with QFQ
* Twig - https://twig.symfony.com
* Twitter typeahead JS - https://twitter.github.io/typeahead.js/
* bootstrap-validator.js - https://github.com/1000hz/bootstrap-validator
* Event Emitter - https://git.io/ee
\ No newline at end of file
* Event Emitter - https://git.io/ee
* FullCalendar - https://fullcalendar.io/
This diff is collapsed.
......@@ -265,6 +265,38 @@ module.exports = function (grunt) {
}
]
},
fullCalendar: {
files: [
{
cwd: 'node_modules/fullcalendar/dist',
src: '*.min.js',
dest: typo3_js,
expand: true,
flatten: false
},
{
cwd: 'node_modules/fullcalendar/dist',
src: '*.min.css',
dest: typo3_css,
expand: true,
flatten: false
},
{
cwd: 'node_modules/fullcalendar/dist',
src: '*.min.*',
dest: "js/",
expand: true,
flatten: false
},
{
cwd: 'node_modules/moment/min',
src: 'moment.min.js',
dest: typo3_js,
expand: true,
flatten: false
},
]
},
eventEmitter: {
files: [
{
......
This diff is collapsed.
This diff is collapsed.
......@@ -56,14 +56,14 @@ ENGINE=$2
# checks if an engine is not specified
if [ -z $ENGINE ]; then
# defines the default engine to use during tests
export BROWSER=$DEFAULT_ENGINE
export SELENIUM_BROWSER=$DEFAULT_ENGINE
# defines the path to the drivers of the engine
export DRIVER_PATH="${PWD}/${DEFAULT_ENGINE}driver"
export SELENIUM_DRIVER_PATH="${PWD}/${DEFAULT_ENGINE}driver"
else
# defines the engine to use during tests
export BROWSER=$ENGINE
export SELENIUM_BROWSER=$ENGINE
# defines the path to the drivers of the engine
export DRIVER_PATH="${PWD}/${ENGINE}driver"
export SELENIUM_DRIVER_PATH="${PWD}/${ENGINE}driver"
fi
# stores the default headless option
......
......@@ -253,7 +253,7 @@ abstract class AbstractBuildForm {
$this->store->setStore($row, STORE_PARENT_RECORD, true);
$this->store->setVar(F_MULTI_COL_ID, $row[$idName], STORE_PARENT_RECORD); // In case '_id' is used, both '_id' and 'id' should be accessible.
$record = $this->dbArray[$this->dbIndexData]->sql('SELECT * FROM `' . $this->formSpec[F_TABLE_NAME] . '` WHERE id=' . $row[F_MULTI_COL_ID], ROW_EXPECT_1);
$record = $this->dbArray[$this->dbIndexData]->sql('SELECT * FROM `' . $this->formSpec[F_TABLE_NAME] . '` WHERE `id`=' . $row[F_MULTI_COL_ID], ROW_EXPECT_1);
$this->store->setStore($record, STORE_RECORD, true);
$jsonTmp = array();
......@@ -555,7 +555,7 @@ abstract class AbstractBuildForm {
$record = array();
if ($recordId != 0) {
$record = $this->dbArray[$this->dbIndexData]->sql("SELECT * FROM $tableName WHERE $primaryKey=?", ROW_EXPECT_1, [$recordId], "Record to load not found.");
$record = $this->dbArray[$this->dbIndexData]->sql("SELECT * FROM `$tableName` WHERE `$primaryKey`=?", ROW_EXPECT_1, [$recordId], "Record to load not found.");
}
return OnArray::getMd5($record);
......@@ -742,7 +742,7 @@ abstract class AbstractBuildForm {
$primaryKey = $this->formSpec[F_PRIMARY_KEY];
if ($recordId > 0 && $this->store->getVar($primaryKey, STORE_RECORD) === false) {
$tableName = $this->formSpec[F_TABLE_NAME];
$row = $this->dbArray[$this->dbIndexData]->sql("SELECT * FROM $tableName WHERE $primaryKey = ?", ROW_EXPECT_1,
$row = $this->dbArray[$this->dbIndexData]->sql("SELECT * FROM `$tableName` WHERE `$primaryKey` = ?", ROW_EXPECT_1,
array($recordId), "Form '" . $this->formSpec[F_NAME] . "' failed to load record '$primaryKey'='$recordId' from table '" .
$this->formSpec[F_TABLE_NAME] . "'.");
$this->store->setStore($row, STORE_RECORD);
......@@ -2108,7 +2108,7 @@ abstract class AbstractBuildForm {
} elseif (!empty($formElement[SUBRECORD_PARAMETER_FORM])) {
// Read table from form specified in subrecord
$formName = $formElement[SUBRECORD_PARAMETER_FORM];
$form = $this->dbArray[$this->dbIndexQfq]->sql("SELECT * FROM Form AS f WHERE f." . F_NAME . " LIKE ? AND f.deleted='no'",
$form = $this->dbArray[$this->dbIndexQfq]->sql("SELECT * FROM `Form` AS f WHERE `f`.`" . F_NAME . "` LIKE ? AND `f`.`deleted`='no'",
ROW_REGULAR, [$formName]);
if (count($form) > 0) {
$dndTable = $form[0][F_TABLE_NAME];
......@@ -2228,21 +2228,21 @@ abstract class AbstractBuildForm {
* @param array $formElement
* @param array $primaryRecord
* @param string $rcText
* @param string $nameColumnId
* @param string $rcNameColumnId
*
* @return bool
* @throws \UserFormException
*/
private function prepareSubrecord(array $formElement, array $primaryRecord, &$rcText, &$nameColumnId) {
private function prepareSubrecord(array $formElement, array $primaryRecord, &$rcText, &$rcNameColumnId) {
if (!isset($primaryRecord['id'])) {
if (!isset($primaryRecord[$rcNameColumnId])) {
$rcText = 'Please save this record first.';
return false;
}
if (!is_array($formElement[FE_SQL1])) {
throw new \UserFormException('Missing \'sql1\' Query', ERROR_MISSING_SQL1);
throw new \UserFormException('Missing \'sql1\' query', ERROR_MISSING_SQL1);
}
// No records?
......@@ -2253,17 +2253,16 @@ abstract class AbstractBuildForm {
}
// Check if $nameColumnId column exist.
if (!isset($formElement[FE_SQL1][0][$nameColumnId])) {
if (!isset($formElement[FE_SQL1][0][$rcNameColumnId])) {
// no: try fallback.
$nameColumnId = '_id';
$rcNameColumnId = '_' . $rcNameColumnId;
if (!isset($formElement[FE_SQL1][0][$rcNameColumnId])) {
throw new \UserFormException(
json_encode([ERROR_MESSAGE_TO_USER => "Missing column $rcNameColumnId in subrecord query", ERROR_MESSAGE_TO_DEVELOPER => '']),
ERROR_SUBRECORD_MISSING_COLUMN_ID);
if (!isset($formElement[FE_SQL1][0][$nameColumnId])) {
throw new \UserFormException('Missing column \'id\' or \'_id\' in subrecord query', ERROR_SUBRECORD_MISSING_COLUMN_ID);
}
}
if (!isset($formElement[FE_SQL1][0][$nameColumnId])) {
throw new \UserFormException('Missing column \'id\' (or "_id") in \'sql1\' Query', ERROR_DB_MISSING_COLUMN_ID);
}
return true;
......@@ -2372,7 +2371,7 @@ abstract class AbstractBuildForm {
* @throws \UserFormException
*/
private function getFormTable($formName) {
$row = $this->dbArray[$this->dbIndexQfq]->sql("SELECT " . F_TABLE_NAME . " FROM Form AS f WHERE f.name = ?", ROW_EXPECT_0_1, [$formName]);
$row = $this->dbArray[$this->dbIndexQfq]->sql("SELECT `" . F_TABLE_NAME . "` FROM `Form` AS f WHERE `f`.`name` = ?", ROW_EXPECT_0_1, [$formName]);
if (isset($row[F_TABLE_NAME])) {
return $row[F_TABLE_NAME];
}
......
......@@ -252,7 +252,7 @@ class BuildFormBootstrap extends AbstractBuildForm {
break;
case 'formElement':
if (false !== ($formId = $this->store->getVar(FE_FORM_ID, STORE_SIP . STORE_RECORD))) {
$row = $this->dbArray[$this->dbIndexQfq]->sql("SELECT f.name FROM Form AS f WHERE id=" . $formId, ROW_EXPECT_1);
$row = $this->dbArray[$this->dbIndexQfq]->sql("SELECT `f`.`name` FROM `Form` AS f WHERE `id`=" . $formId, ROW_EXPECT_1);
$form = current($row);
}
break;
......
......@@ -63,15 +63,15 @@ const RETURN_URL = 'return_url';
const RETURN_SIP = 'return_sip';
const RETURN_ARRAY = 'return_array';
const SQL_FORM_ELEMENT_BY_ID = "SELECT * FROM FormElement AS fe WHERE fe.id = ?";
const SQL_FORM_ELEMENT_RAW = "SELECT * FROM FormElement AS fe WHERE fe.formId = ? AND fe.deleted = 'no' AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
const SQL_FORM_ELEMENT_SPECIFIC_CONTAINER = "SELECT *, ? AS 'nestedInFieldSet' FROM FormElement AS fe WHERE fe.formId = ? AND fe.deleted = 'no' AND FIND_IN_SET(fe.class, ? ) AND fe.feIdContainer = ? AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
const SQL_FORM_ELEMENT_ALL_CONTAINER = "SELECT *, ? AS 'nestedInFieldSet' FROM FormElement AS fe WHERE fe.formId = ? AND fe.deleted = 'no' AND FIND_IN_SET(fe.class, ? ) AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
const SQL_FORM_ELEMENT_SIMPLE_ALL_CONTAINER = "SELECT fe.id, fe.feIdContainer, fe.name, fe.value, fe.label, fe.type, fe.encode, fe.checkType, fe.checkPattern, fe.mode, fe.modeSql, fe.parameter, fe.dynamicUpdate FROM FormElement AS fe, Form AS f WHERE f.name = ? AND f.id = fe.formId AND fe.deleted = 'no' AND fe.class = 'native' AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
const SQL_FORM_ELEMENT_CONTAINER_TEMPLATE_GROUP = "SELECT fe.id, fe.name, fe.label, fe.maxLength, fe.parameter FROM FormElement AS fe, Form AS f WHERE f.name = ? AND f.id = fe.formId AND fe.deleted = 'no' AND fe.class = 'container' AND fe.type='templateGroup' AND fe.enabled='yes' ORDER BY fe.ord, fe.id";
const SQL_FORM_ELEMENT_TEMPLATE_GROUP_FE_ID = "SELECT * FROM FormElement AS fe WHERE fe.id = ? AND fe.deleted = 'no' AND fe.class = 'container' AND fe.type='templateGroup' AND fe.enabled='yes' ";
const SQL_FORM_ELEMENT_BY_ID = "SELECT * FROM `FormElement` AS fe WHERE `fe`.`id` = ?";
const SQL_FORM_ELEMENT_RAW = "SELECT * FROM `FormElement` AS `fe` WHERE `fe`.`formId` = ? AND `fe`.`deleted` = 'no' AND `fe`.`enabled`='yes' ORDER BY `fe`.`ord`, `fe`.`id`";
const SQL_FORM_ELEMENT_SPECIFIC_CONTAINER = "SELECT *, ? AS 'nestedInFieldSet' FROM `FormElement` AS fe WHERE `fe`.`formId` = ? AND `fe`.`deleted` = 'no' AND FIND_IN_SET(`fe`.`class`, ? ) AND `fe`.`feIdContainer` = ? AND `fe`.`enabled`='yes' ORDER BY `fe`.`ord`, `fe`.`id`";
const SQL_FORM_ELEMENT_ALL_CONTAINER = "SELECT *, ? AS 'nestedInFieldSet' FROM `FormElement` AS `fe` WHERE `fe`.`formId` = ? AND `fe`.`deleted` = 'no' AND FIND_IN_SET(`fe`.`class`, ? ) AND `fe`.`enabled`='yes' ORDER BY `fe`.`ord`, `fe`.`id`";
const SQL_FORM_ELEMENT_SIMPLE_ALL_CONTAINER = "SELECT `fe`.`id`, `fe`.`feIdContainer`, `fe`.`name`, `fe`.`value`, `fe`.`label`, `fe`.`type`, `fe`.`encode`, `fe`.`checkType`, `fe`.`checkPattern`, `fe`.`mode`, `fe`.`modeSql`, `fe`.`parameter`, `fe`.`dynamicUpdate` FROM `FormElement` AS fe, `Form` AS f WHERE `f`.`name` = ? AND `f`.`id` = `fe`.`formId` AND `fe`.`deleted` = 'no' AND `fe`.`class` = 'native' AND `fe`.`enabled`='yes' ORDER BY `fe`.`ord`, `fe`.`id`";
const SQL_FORM_ELEMENT_CONTAINER_TEMPLATE_GROUP = "SELECT `fe`.`id`, `fe`.`name`, `fe`.`label`, `fe`.`maxLength`, `fe`.`parameter` FROM `FormElement` AS fe, `Form` AS f WHERE `f`.`name` = ? AND `f`.`id` = `fe`.`formId` AND `fe`.`deleted` = 'no' AND `fe`.`class` = 'container' AND `fe`.`type`='templateGroup' AND `fe`.`enabled`='yes' ORDER BY `fe`.`ord`, `fe`.`id`";
const SQL_FORM_ELEMENT_TEMPLATE_GROUP_FE_ID = "SELECT * FROM `FormElement` AS fe WHERE `fe`.`id` = ? AND `fe`.`deleted` = 'no' AND `fe`.`class` = 'container' AND `fe`.`type`='templateGroup' AND `fe`.`enabled`='yes' ";
//const SQL_FORM_ELEMENT_NATIVE_TG_COUNT = "SELECT fe.*, IFNULL(feTg.maxLength,0) AS _tgCopies FROM FormElement AS fe LEFT JOIN FormElement AS feTg ON fe.feIdContainer=feTg.id AND feTg.deleted = 'no' AND feTg.class = 'container' AND feTg.type='templateGroup' AND feTg.enabled='yes' WHERE fe.formId = ? AND fe.deleted = 'no' AND fe.class = 'native' AND fe.enabled='yes'";
const SQL_FORM_ELEMENT_NATIVE_TG_COUNT = "SELECT fe.*, IFNULL(feTg.maxLength,0) AS _tgCopies FROM FormElement AS fe LEFT JOIN FormElement AS feTg ON fe.feIdContainer=feTg.id AND feTg.deleted = 'no' AND feTg.class = 'container' AND feTg.type='templateGroup' AND feTg.enabled='yes' WHERE fe.formId = ? AND fe.deleted = 'no' AND (fe.class = 'native' OR (fe.class = 'container' AND fe.type='pill')) AND fe.enabled='yes'";
const SQL_FORM_ELEMENT_NATIVE_TG_COUNT = "SELECT `fe`.*, IFNULL(`feTg`.`maxLength`,0) AS _tgCopies FROM `FormElement` AS fe LEFT JOIN `FormElement` AS feTg ON `fe`.`feIdContainer`=`feTg`.`id` AND `feTg`.`deleted` = 'no' AND `feTg`.`class` = 'container' AND `feTg`.`type`='templateGroup' AND `feTg`.`enabled`='yes' WHERE `fe`.`formId` = ? AND `fe`.`deleted` = 'no' AND (`fe`.`class` = 'native' OR (`fe`.`class` = 'container' AND `fe`.`type`='pill')) AND `fe`.`enabled`='yes'";
const NAME_TG_COPIES = '_tgCopies'; // Number of templatesGroup copies to create on the fly. Also used in SQL_FORM_ELEMENT_NATIVE_TG_COUNT.
const FE_TG_INDEX = '_tgIndex'; // Index of the current copy of a templateGroup FE.
......@@ -325,7 +325,7 @@ const ERROR_DB_CLOSE_MYSQLI_RESULT = 2009;
const ERROR_DB_CLOSE_MYSQLI_STMT = 2010;
const ERROR_DB_UNKNOWN_COLUMN = 2011;
const ERROR_DB_UNKNOWN_COMMAND = 2012;
const ERROR_DB_MISSING_COLUMN_ID = 2013;
const ERROR_DB_COLUMN_NOT_FOUND_IN_TABLE = 2014;
const ERROR_DB_SET_CHARSET = 2015;
const ERROR_DB_MULTI_QUERY_FAILED = 2016;
......@@ -516,6 +516,10 @@ const SYSTEM_REDIRECT_ALL_MAIL_TO = 'redirectAllMailTo';
const SYSTEM_THROW_GENERAL_ERROR = 'throwExceptionGeneralError';
const SYSTEM_FLAG_PRODUCTION = 'flagProduction';
const SYSTEM_RENDER = 'render';
const SYSTEM_RENDER_SINGLE = 'single';
const SYSTEM_RENDER_BOTH = 'both';
const SYSTEM_RENDER_API = 'api';
const SYSTEM_SHOW_DEBUG_INFO = 'showDebugInfo';
const SYSTEM_SHOW_DEBUG_INFO_YES = 'yes';
......@@ -650,10 +654,10 @@ const SYSTEM_THUMBNAIL_DIR_PUBLIC = 'thumbnailDirPublic';
const SYSTEM_THUMBNAIL_DIR_PUBLIC_DEFAULT = 'typo3temp/qfqThumbnail';
const SYSTEM_DOCUMENTATION_QFQ = 'documentation';
const SYSTEM_DOCUMENTATION_QFQ_URL = 'https://docs.typo3.org/p/IMATHUZH/qfq/master/en-us/Manual.html';
const SYSTEM_DOCUMENTATION_QFQ_URL = 'https://qfq.io/doc';
// Not stored in config.qfq.ini, but used in STORE_SYSTEM
// Not stored in config.qfq.php, but used in STORE_SYSTEM
// Information for: Log / Debug / Exception
const SYSTEM_SQL_RAW = 'sqlRaw'; // Type: SANITIZE_ALL / String. SQL Query (before substitute). Useful for error reporting.
const SYSTEM_SQL_FINAL = 'sqlFinal'; // Type: SANITIZE_ALL / String. SQL Query (after substitute). Useful for error reporting.
......@@ -1513,7 +1517,7 @@ const TOKEN_DEBUG_BODYTEXT = TYPO3_DEBUG_SHOW_BODY_TEXT;
const TOKEN_DB_INDEX = F_DB_INDEX;
const TOKEN_CONTENT = 'content';
const TOKEN_VALID_LIST = 'sql|twig|head|althead|altsql|tail|shead|stail|rbeg|rend|renr|rsep|fbeg|fend|fsep|fskipwrap|rbgd|debug|form|r|debugShowBodyText|dbIndex|sqlLog|sqlLogMode|content';
const TOKEN_VALID_LIST = 'sql|twig|head|althead|altsql|tail|shead|stail|rbeg|rend|renr|rsep|fbeg|fend|fsep|fskipwrap|rbgd|debug|form|r|debugShowBodyText|dbIndex|sqlLog|sqlLogMode|content|render';
const TOKEN_COLUMN_CTRL = '_';
......@@ -1581,9 +1585,18 @@ const COLUMN_MAILTO = "mailto";
const COLUMN_SENDMAIL = "sendmail";
const COLUMN_VERTICAL = "vertical";
const COLUMN_NO_WRAP = "noWrap";
const COLUMN_HIDE = "hide";
const C_FULL = 'full';
const C_TITLE = 'title';
const C_NO_WRAP = 'noWrap';
const C_SPECIAL = 'special';
const C_HIDE = 'hide';
const COLUMN_WRAP_TOKEN = '+';
const COLUMN_STORE_USER = '=';
const FORM_NAME_FORM = 'form';
const FORM_NAME_FORM_ELEMENT = 'formElement';
const FORM_LOG_MODE = '_formLogMode'; // Variable to call the form in debug mode.
......
......@@ -1055,7 +1055,7 @@ class Database {
*/
public function deleteSplitFileAndRecord($xId, $tableName) {
$sql = 'SELECT pathFileName FROM ' . TABLE_NAME_SPLIT . ' WHERE tableName=? AND xId=?';
$sql = 'SELECT `pathFileName` FROM `' . TABLE_NAME_SPLIT . '` WHERE `tableName`=? AND `xId`=?';
$data = $this->sql($sql, ROW_REGULAR, [$tableName, $xId]);
foreach ($data AS $row) {
......@@ -1064,7 +1064,7 @@ class Database {
}
}
$this->sql('DELETE FROM ' . TABLE_NAME_SPLIT . ' WHERE tableName=? AND xId=?', ROW_REGULAR, [$tableName, $xId]);
$this->sql('DELETE FROM `' . TABLE_NAME_SPLIT . '` WHERE `tableName`=? AND `xId`=?', ROW_REGULAR, [$tableName, $xId]);
}
}
\ No newline at end of file
......@@ -10,6 +10,7 @@ namespace IMATHUZH\Qfq\Core\Database;
use IMATHUZH\Qfq\Core\Helper\Logger;
use IMATHUZH\Qfq\Core\Store\Store;
use IMATHUZH\Qfq\Core\Typo3\T3Handler;
/*
......@@ -129,14 +130,17 @@ class DatabaseUpdate {
*/
public function checkNupdate($dbUpdate) {
if ($dbUpdate === SYSTEM_DB_UPDATE_NEVER) {
return;
}
$new = $this->getExtensionVersion();
$versionInfo = $this->getDatabaseVersion();
$old = $versionInfo[QFQ_VERSION_KEY] ?? false;
$this->checkT3QfqConfig($old, $new);
if ($dbUpdate === SYSTEM_DB_UPDATE_NEVER) {
return;
}
if ($dbUpdate === SYSTEM_DB_UPDATE_ALWAYS || ($dbUpdate === SYSTEM_DB_UPDATE_AUTO && $new != $old)) {
$newFunctionHash = $this->updateSqlFunctions($versionInfo[QFQ_VERSION_KEY_FUNCTION_HASH] ?? '');
......@@ -167,6 +171,23 @@ class DatabaseUpdate {
}
/**
* Check Typo3 config if values needs to be updated.
* This is typically necessary if default config values change, to guarantee existing installations behave in legacy mode.
*
* @param $old
* @param $new
*/
private function checkT3QfqConfig($old, $new) {
if ($new == $old) {
return;
}
if (version_compare($old, '20.2.0') == -1) {
T3Handler::updateT3QfqConfig(SYSTEM_RENDER, SYSTEM_RENDER_BOTH); //Legacy behaviour.
}
}
/**
* Check if there are special columns without prepended underscore in the QFQ application. If yes, then throw an error.
* A link is provided to automatically prepend all found special columns. And another link to skip the auto-replacement.
......@@ -201,14 +222,14 @@ class DatabaseUpdate {
if (defined('PHPUNIT_QFQ')) {
$res = array();
} else {
$res = $this->db->sql("SELECT uid, header, bodytext FROM " . $dbT3 . ".tt_content WHERE CType='qfq_qfq' AND deleted=0;");
$res = $this->db->sql("SELECT `uid`, `header`, `bodytext` FROM `" . $dbT3 . "`.`tt_content` WHERE `CType`='qfq_qfq' AND `deleted`=0;");
}
foreach ($res as $i => $tt_content) {
$replaced_placeholder = preg_replace($patterns, '${1}' . $placeholder . '${2}', $tt_content['bodytext']);
if (strpos($replaced_placeholder, $placeholder) !== false) {
if ($actionSpecialColumn === ACTION_SPECIAL_COLUMN_DO_REPLACE) {
$replace = str_replace($placeholder, '_', $replaced_placeholder);
$query = "UPDATE " . $dbT3 . ".tt_content SET bodytext='" . addslashes($replace) . "' WHERE uid='" . $tt_content['uid'] . "'";
$query = "UPDATE `" . $dbT3 . "`.`tt_content` SET `bodytext`='" . addslashes($replace) . "' WHERE `uid`='" . $tt_content['uid'] . "'";
$this->db->sql($query);
}
$message_fe .= '<hr><b>' . $tt_content['header'] . ' [uid:' . $tt_content['uid'] . ']</b><br><br>';
......@@ -226,7 +247,7 @@ class DatabaseUpdate {
if (defined('PHPUNIT_QFQ')) {
$res = array();
} else {
$res = $this->db->sql("SELECT fe.id, fe.name, fe.value, fe.note FROM FormElement as fe WHERE fe.type='note' AND fe.value LIKE '#!report%' OR fe.note LIKE '%#!report%';");
$res = $this->db->sql("SELECT `fe`.`id`, `fe`.`name`, `fe`.`value`, `fe`.`note` FROM `FormElement` AS fe WHERE `fe`.`type`='note' AND `fe`.`value` LIKE '#!report%' OR `fe`.`note` LIKE '%#!report%';");
}
foreach ($res as $i => $tt_content) {
......@@ -235,7 +256,7 @@ class DatabaseUpdate {
if (strpos($replaced_placeholder, $placeholder) !== false) {
if ($actionSpecialColumn === ACTION_SPECIAL_COLUMN_DO_REPLACE) {
$replace = str_replace($placeholder, '_', $replaced_placeholder);
$query = "UPDATE FormElement SET " . $columnName . "='" . addslashes($replace) . "' WHERE id='" . $tt_content['id'] . "'";
$query = "UPDATE `FormElement` SET `" . $columnName . "`='" . addslashes($replace) . "' WHERE `id`='" . $tt_content['id'] . "'";
$this->db->sql($query);
}
$message_ttc .= '<hr><b>' . $tt_content['name'] . ' [id:' . $tt_content['id'] . '] (FormElement.' . $columnName . ')</b><br><br>';
......
......@@ -23,7 +23,7 @@ $UPDATE_ARRAY = array(
],
'0.15.0' => [
"UPDATE FormElement SET parameter = REPLACE(parameter, 'typeAheadLdapKeyPrintf', 'typeAheadLdapIdPrintf')",
"UPDATE `FormElement` SET `parameter` = REPLACE(parameter, 'typeAheadLdapKeyPrintf', 'typeAheadLdapIdPrintf')",
"ALTER TABLE `FormElement` CHANGE `placeholder` `placeholder` VARCHAR( 2048 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' ",
],
......@@ -44,7 +44,7 @@ $UPDATE_ARRAY = array(
'0.18.0' => [
"ALTER TABLE `Form` CHANGE `forwardMode` `forwardMode` ENUM( 'client', 'no', 'page', 'url', 'url-skip-history' ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'client'",
"UPDATE Form SET forwardMode='url' WHERE forwardMode='page'",
"UPDATE `Form` SET `forwardMode`='url' WHERE `forwardMode`='page'",
"ALTER TABLE `Form` CHANGE `forwardMode` `forwardMode` ENUM( 'client', 'no', 'url', 'url-skip-history' ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'client'",
],
......@@ -57,7 +57,7 @@ $UPDATE_ARRAY = array(
"ALTER TABLE `Form` ADD `dirtyMode` ENUM( 'exclusive', 'advisory', 'none' ) NOT NULL DEFAULT 'exclusive' AFTER `requiredParameter`",
"ALTER TABLE `Form` ADD `recordLockTimeoutSeconds` INT NOT NULL DEFAULT '900' AFTER `parameter`",
"CREATE TABLE IF NOT EXISTS `Period` (`id` INT(11) NOT NULL AUTO_INCREMENT, `start` DATETIME NOT NULL, `name` VARCHAR(255) NOT NULL, `modified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `created` DATETIME NOT NULL, PRIMARY KEY (`id`), KEY `start` (`start`)) ENGINE = InnoDB DEFAULT CHARSET = utf8 AUTO_INCREMENT = 0;",
"INSERT INTO Period (start, name, created) VALUES (NOW(), 'dummy', NOW());"
"INSERT INTO `Period` (`start`, `name`, `created`) VALUES (NOW(), 'dummy', NOW());"
],
'0.19.2' => [
......@@ -79,7 +79,7 @@ $UPDATE_ARRAY = array(
'0.21.0' => [
"ALTER TABLE `Form` CHANGE `requiredParameter` `requiredParameterNew` VARCHAR( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT ''",
"ALTER TABLE `Form` ADD `requiredParameterEdit` VARCHAR( 255 ) NOT NULL AFTER `requiredParameterNew`",
"UPDATE Form SET requiredParameterEdit=requiredParameterNew",
"UPDATE `Form` SET `requiredParameterEdit`=requiredParameterNew",
],
'0.24.0' => [
......@@ -103,7 +103,7 @@ $UPDATE_ARRAY = array(
],
'0.25.11' => [
"UPDATE FormElement SET checkType = 'alnumx', checkPattern = '', parameter = CONCAT(parameter, '\nmin = ', SUBSTRING_INDEX(checkPattern, '|', 1), '\nmax = ', SUBSTRING_INDEX(checkPattern, '|', -1)) WHERE checkType LIKE 'min|max%' AND checkPattern <> ''",
"UPDATE `FormElement` SET `checkType` = 'alnumx', checkPattern = '', parameter = CONCAT(parameter, '\nmin = ', SUBSTRING_INDEX(checkPattern, '|', 1), '\nmax = ', SUBSTRING_INDEX(checkPattern, '|', -1)) WHERE checkType LIKE 'min|max%' AND checkPattern <> ''",
"ALTER TABLE `FormElement` CHANGE `checkType` `checkType` ENUM('alnumx','digit','numerical','email','pattern','allbut','all') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'alnumx';",
],
......@@ -120,7 +120,7 @@ $UPDATE_ARRAY = array(
'18.6.0' => [
"ALTER TABLE `Form` CHANGE `forwardMode` `forwardMode` ENUM('auto', 'client','no','url','url-skip-history','url-sip') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'client'",
"UPDATE `Form` SET forwardMode='auto' WHERE forwardMode='client'",
"UPDATE `Form` SET `forwardMode`='auto' WHERE `forwardMode`='client'",
"ALTER TABLE `Form` CHANGE `forwardMode` `forwardMode` ENUM('auto', 'close', 'no','url','url-skip-history','url-sip') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'auto';",
],
......@@ -147,7 +147,7 @@ $UPDATE_ARRAY = array(
'19.3.2' => [
"ALTER TABLE `Form` CHANGE `forwardMode` `forwardMode` ENUM('auto','close','no','url','url-skip-history','url-sip','url-sip-skip-history' ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'auto';",
"UPDATE `Form` SET forwardMode='url-sip-skip-history' WHERE forwardMode='url-sip'",
"UPDATE `Form` SET `forwardMode`='url-sip-skip-history' WHERE `forwardMode`='url-sip'",
],
'19.7.2' => [
......
......@@ -79,12 +79,12 @@ class Delete {
}
// Read record first.
$row = $this->db->sql("SELECT * FROM $tableName WHERE $primaryKey=?", ROW_EXPECT_0_1, [$recordId]);
$row = $this->db->sql("SELECT * FROM `$tableName` WHERE `$primaryKey`=?", ROW_EXPECT_0_1, [$recordId]);
if (count($row) > 0) {
$this->deleteReferencedFiles($row, $tableName, $primaryKey);
$this->db->sql("DELETE FROM $tableName WHERE $primaryKey =? LIMIT 1", ROW_REGULAR, [$recordId]);
$this->db->sql("DELETE FROM `$tableName` WHERE `$primaryKey` =? LIMIT 1", ROW_REGULAR, [$recordId]);
} else {
throw new \UserFormException(
json_encode([ERROR_MESSAGE_TO_USER => 'Record not found in table', ERROR_MESSAGE_TO_DEVELOPER => "Record $recordId not found in table '$tableName'."]),
......@@ -124,7 +124,7 @@ class Delete {
// check if there are other records referencing the same file: do not delete the file now.
// This check won't find duplicates, if they are spread over different columns or tables.
$samePathFileName = $this->db->sql("SELECT COUNT($primaryKey) AS cnt FROM $tableName WHERE $key LIKE ?", ROW_EXPECT_1, [$file]);
$samePathFileName = $this->db->sql("SELECT COUNT($primaryKey) AS cnt FROM `$tableName` WHERE `$key` LIKE ?", ROW_EXPECT_1, [$file]);
if ($samePathFileName['cnt'] === 1) {
HelperFile::unlink($file);
$this->db->deleteSplitFileAndRecord($row[$primaryKey], $tableName);
......
......@@ -16,7 +16,7 @@ use IMATHUZH\Qfq\Core\Report\Link;
use IMATHUZH\Qfq\Core\Report\Tablesorter;
use IMATHUZH\Qfq\Core\Store\Sip;
use IMATHUZH\Qfq\Core\Store\Store;
use IMATHUZH\Qfq\Core\Typo3\Password;
use IMATHUZH\Qfq\Core\Typo3\T3Handler;
const EVALUATE_DB_INDEX_DEFAULT = 0;
......@@ -423,7 +423,7 @@ class Evaluate {
case TOKEN_ESCAPE_NONE: // do nothing
break;
case TOKEN_ESCAPE_PASSWORD_T3FE:
$value = Password::getHash($value);
$value = T3Handler::getHash($value);
break;
case TOKEN_ESCAPE_STOP_REPLACE:
$value = Support::encryptDoubleCurlyBraces($value);
......
......@@ -279,7 +279,7 @@ class AbstractException extends \Exception {
$linkFormElement = '';
try {
$db = new Database();
$sql = "SELECT id FROM Form WHERE name='" . $storeSystem[SYSTEM_FORM] . "'";
$sql = "SELECT `id` FROM `Form` WHERE `name`='" . $storeSystem[SYSTEM_FORM] . "'";
$r = $db->sql($sql, ROW_EXPECT_0_1);
if (!is_numeric($r[F_ID])) {
......
......@@ -125,7 +125,7 @@ class Dirty {
$this->dbIndexData = empty($sipVars[PARAM_DB_INDEX_DATA]) ? $this->store->getVar(SYSTEM_DB_INDEX_DATA, STORE_SYSTEM) : $sipVars[PARAM_DB_INDEX_DATA];
$this->doDbArray($this->dbIndexData, $this->dbIndexQfq);
$tableVars = $this->dbArray[$this->dbIndexQfq]->sql("SELECT tableName, primaryKey, dirtyMode, recordLockTimeoutSeconds FROM Form WHERE name=?", ROW_EXPECT_1, [$sipVars[SIP_FORM]], "Form not found: '" . $sipVars[SIP_FORM] . "'");
$tableVars = $this->dbArray[$this->dbIndexQfq]->sql("SELECT `tableName`, `primaryKey`, `dirtyMode`, `recordLockTimeoutSeconds` FROM `Form` WHERE `name`=?", ROW_EXPECT_1, [$sipVars[SIP_FORM]], "Form not found: '" . $sipVars[SIP_FORM] . "'");
if (empty($tableVars[F_PRIMARY_KEY])) {
$tableVars[F_PRIMARY_KEY] = F_PRIMARY_KEY_DEFAULT;
}
......@@ -208,7 +208,7 @@ class Dirty {
*/
private function getRecordDirty($tableName, $recordId) {
$recordDirty = $this->dbArray[$this->dbIndexQfq]->sql("SELECT * FROM Dirty AS d WHERE d.tableName LIKE ? AND recordId=? ",
$recordDirty = $this->dbArray[$this->dbIndexQfq]->sql("SELECT * FROM `Dirty` AS d WHERE `d`.`tableName` LIKE ? AND `recordId`=? ",
ROW_EXPECT_0_1, [$tableName, $recordId]);
// Check if the record is timed out - owner doesn't matter.
......@@ -286,12 +286,12 @@ class Dirty {
$primaryKey = $tableVars[F_PRIMARY_KEY];
$formDirtyMode = $tableVars[F_DIRTY_MODE];
$record = $this->dbArray[$this->dbIndexData]->sql("SELECT * FROM $tableName WHERE $primaryKey=?", ROW_EXPECT_1, [$recordId], "Record to lock not found.");
$record = $this->dbArray[$this->dbIndexData]->sql("SELECT * FROM `$tableName` WHERE `$primaryKey`=?", ROW_EXPECT_1, [$recordId], "Record to lock not found.");
# Dirty workaround: setting the 'expired timestamp' minus 1 second guarantees that the client ask for relock always if the timeout is expired.
$expire = date('Y-m-d H:i:s', strtotime("+" . $tableVars[F_RECORD_LOCK_TIMEOUT_SECONDS] - 1 . " seconds"));
// Write 'dirty' record
$this->dbArray[$this->dbIndexQfq]->sql("INSERT INTO Dirty (`sip`, `tableName`, `recordId`, `expire`, `recordHashMd5`, `tabUniqId`, `feUser`, `qfqUserSessionCookie`, `dirtyMode`, `remoteAddress`, `created`) " .
$this->dbArray[$this->dbIndexQfq]->sql("INSERT INTO `Dirty` (`sip`, `tableName`, `recordId`, `expire`, `recordHashMd5`, `tabUniqId`, `feUser`, `qfqUserSessionCookie`, `dirtyMode`, `remoteAddress`, `created`) " .
"VALUES ( ?,?,?,?,?,?,?,?,?,?,? )", ROW_REGULAR,
[$s, $tableName, $recordId, $expire, $recordHashMd5, $tabUniqId, $feUser, $this->client[CLIENT_COOKIE_QFQ], $formDirtyMode,
$this->client[CLIENT_REMOTE_ADDRESS], date('YmdHis')]);
......@@ -320,7 +320,7 @@ class Dirty {
return false; // If there is no recordHashMd5, the check is not possible. Always return 'not modified' (=ok)
}
$record = $this->dbArray[$this->dbIndexData]->sql("SELECT * FROM $tableName WHERE $primaryKey=?", ROW_EXPECT_1, [$recordId], "Record to lock not found.");
$record = $this->dbArray[$this->dbIndexData]->sql("SELECT * FROM `$tableName` WHERE `$primaryKey`=?", ROW_EXPECT_1, [$recordId], "Record to lock not found.");
$rcMd5 = OnArray::getMd5($record);
......@@ -477,7 +477,7 @@ class Dirty {
*/
private function deleteDirtyRecord($recordDirtyId) {
$cnt = $this->dbArray[$this->dbIndexQfq]->sql('DELETE FROM Dirty WHERE id=? LIMIT 1', ROW_REGULAR, [$recordDirtyId]);
$cnt = $this->dbArray[$this->dbIndexQfq]->sql('DELETE FROM `Dirty` WHERE `id`=? LIMIT 1', ROW_REGULAR, [$recordDirtyId]);
if ($cnt != 1) {
throw new \CodeException("Failed to delete dirty record id=" . $recordDirtyId, ERROR_DIRTY_DELETE_RECORD);
}
......
......@@ -204,7 +204,7 @@ class DragAndDrop {
return $data;
}
$this->db->sql("UPDATE $tableName SET $orderColumn=? WHERE id=?", ROW_REGULAR, [$ordNew, $id]);
$this->db->sql("UPDATE `$tableName` SET `$orderColumn`=? WHERE `id`=?", ROW_REGULAR, [$ordNew, $id]);
// Converting to string is necessary: JSON detects int else.
$data[API_ELEMENT_UPDATE][DND_ORD_HTML_ID_PREFIX . $id][API_ELEMENT_CONTENT] = (string)$ordNew;
......
......@@ -430,7 +430,7 @@ class FormAction {
// Check if there is a column with the same name as the 'action'-FormElement.
if ($flagFeAction && false !== $this->store->getVar($fe[FE_NAME], STORE_RECORD)) {
// After an insert or update, propagate the (new) slave id to the master record.
$this->db->sql("UPDATE " . $this->primaryTableName . " SET " . $fe[FE_NAME] . " = $slaveId WHERE id = ? LIMIT 1", ROW_REGULAR, [$recordId]);
$this->db->sql("UPDATE `" . $this->primaryTableName . "` SET `" . $fe[FE_NAME] . "` = $slaveId WHERE `id` = ? LIMIT 1", ROW_REGULAR, [$recordId]);
}
}
......@@ -552,7 +552,7 @@ class FormAction {
// will be used in sub paste's
// $clipboard["_src_id"] = $newColumns[COLUMN_ID];
$rowSrc = $this->db->sql("SELECT * FROM $recordSourceTable WHERE id=?", ROW_EXPECT_1, [$newColumns[COLUMN_ID]]);
$rowSrc = $this->db->sql("SELECT * FROM `$recordSourceTable` WHERE `id`=?", ROW_EXPECT_1, [$newColumns[COLUMN_ID]]);
$this->checkNCopyFiles($rowSrc, $newColumns);
......@@ -603,11 +603,11 @@ class FormAction {
foreach ($translateMap as $oldId => $newId) {
$row = $this->db->sql("SELECT $translateIdColumn FROM $tableName WHERE id=$newId", ROW_EXPECT_1);
$row = $this->db->sql("SELECT `$translateIdColumn` FROM `$tableName` WHERE `id`=$newId", ROW_EXPECT_1);
if (!empty($row[$translateIdColumn])) {
$newNewId = $translateMap[$row[$translateIdColumn]];
$this->db->sql("UPDATE $tableName SET $translateIdColumn=$newNewId WHERE id=$newId LIMIT 1");
$this->db->sql("UPDATE `$tableName` SET `$translateIdColumn`=$newNewId WHERE `id`=$newId LIMIT 1");
}
}
......@@ -691,10 +691,10 @@ class FormAction {
return (0);
}
$keyString = implode(',', $keys);
$keyString = '`' . implode('`,`', $keys) . '`';
$valueString = implode(',', $placeholder);
$sql = "INSERT INTO $destTable ($keyString) VALUES ($valueString)";
$sql = "INSERT INTO `$destTable` ($keyString) VALUES ($valueString)";
return $this->db->sql($sql, ROW_REGULAR, $values);
......
......@@ -1572,4 +1572,12 @@ class Support {
return $formModeGlobal;
}
/**
* Set QFQ Error Handler.
* Should not be active if T3 code runs.
*/
public static function setQfqErrorHandler() {
set_error_handler("\\IMATHUZH\\Qfq\\Core\\Exception\\ErrorHandler::exception_error_handler");
}
}
\ No newline at end of file
......@@ -129,7 +129,8 @@ class QuickFormQuery {
// Refresh the session even if no new data saved.
Session::set(SESSION_LAST_ACTIVITY, time());
set_error_handler("\\IMATHUZH\\Qfq\\Core\\Exception\\ErrorHandler::exception_error_handler");
Support::setQfqErrorHandler();
// PHPExcel
set_include_path(get_include_path() . PATH_SEPARATOR . '../../Resources/Private/Classes/');
......@@ -242,13 +243,20 @@ class QuickFormQuery {
public function process() {
$html = '';
$render = $this->store->getVar(SYSTEM_RENDER, STORE_TYPO3 . STORE_SYSTEM);
if ($render == SYSTEM_RENDER_API && isset($GLOBALS['TYPO3_CONF_VARS'])) {
return '';
}
if ($this->store->getVar(TYPO3_DEBUG_SHOW_BODY_TEXT, STORE_TYPO3) === 'yes') {
$htmlId = HelperFormElement::buildFormElementId($this->formSpec[F_ID], 0, 0, 0);
$html .= Support::doTooltip($htmlId . HTML_ID_EXTENSION_TOOLTIP, $this->t3data['bodytext']);
}