Project

General

Profile

Bug #98536 » QueryGenerator.php

Ralph Brugger, 2023-03-16 07:27

 
<?php

/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/

namespace TYPO3\CMS\Core\Database;

use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Authentication\BackendUserAuthentication;
use TYPO3\CMS\Core\Database\Query\QueryHelper;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Localization\LanguageService;
use TYPO3\CMS\Core\Type\Bitmask\Permission;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\MathUtility;
use TYPO3\CMS\Core\Utility\StringUtility;

/**
* Class for generating front end for building queries
*
* @deprecated since v11, will be removed in v12
*/
class QueryGenerator
{
/**
* @var array
*/
public $lang = [
'OR' => 'or',
'AND' => 'and',
'comparison' => [
// Type = text offset = 0
'0_' => 'contains',
'1_' => 'does not contain',
'2_' => 'starts with',
'3_' => 'does not start with',
'4_' => 'ends with',
'5_' => 'does not end with',
'6_' => 'equals',
'7_' => 'does not equal',
// Type = number , offset = 32
'32_' => 'equals',
'33_' => 'does not equal',
'34_' => 'is greater than',
'35_' => 'is less than',
'36_' => 'is between',
'37_' => 'is not between',
'38_' => 'is in list',
'39_' => 'is not in list',
'40_' => 'binary AND equals',
'41_' => 'binary AND does not equal',
'42_' => 'binary OR equals',
'43_' => 'binary OR does not equal',
// Type = multiple, relation, offset = 64
'64_' => 'equals',
'65_' => 'does not equal',
'66_' => 'contains',
'67_' => 'does not contain',
'68_' => 'is in list',
'69_' => 'is not in list',
'70_' => 'binary AND equals',
'71_' => 'binary AND does not equal',
'72_' => 'binary OR equals',
'73_' => 'binary OR does not equal',
// Type = date,time offset = 96
'96_' => 'equals',
'97_' => 'does not equal',
'98_' => 'is greater than',
'99_' => 'is less than',
'100_' => 'is between',
'101_' => 'is not between',
'102_' => 'binary AND equals',
'103_' => 'binary AND does not equal',
'104_' => 'binary OR equals',
'105_' => 'binary OR does not equal',
// Type = boolean, offset = 128
'128_' => 'is True',
'129_' => 'is False',
// Type = binary , offset = 160
'160_' => 'equals',
'161_' => 'does not equal',
'162_' => 'contains',
'163_' => 'does not contain',
],
];

/**
* @var array
*/
public $compSQL = [
// Type = text offset = 0
'0' => '#FIELD# LIKE \'%#VALUE#%\'',
'1' => '#FIELD# NOT LIKE \'%#VALUE#%\'',
'2' => '#FIELD# LIKE \'#VALUE#%\'',
'3' => '#FIELD# NOT LIKE \'#VALUE#%\'',
'4' => '#FIELD# LIKE \'%#VALUE#\'',
'5' => '#FIELD# NOT LIKE \'%#VALUE#\'',
'6' => '#FIELD# = \'#VALUE#\'',
'7' => '#FIELD# != \'#VALUE#\'',
// Type = number, offset = 32
'32' => '#FIELD# = \'#VALUE#\'',
'33' => '#FIELD# != \'#VALUE#\'',
'34' => '#FIELD# > #VALUE#',
'35' => '#FIELD# < #VALUE#',
'36' => '#FIELD# >= #VALUE# AND #FIELD# <= #VALUE1#',
'37' => 'NOT (#FIELD# >= #VALUE# AND #FIELD# <= #VALUE1#)',
'38' => '#FIELD# IN (#VALUE#)',
'39' => '#FIELD# NOT IN (#VALUE#)',
'40' => '(#FIELD# & #VALUE#)=#VALUE#',
'41' => '(#FIELD# & #VALUE#)!=#VALUE#',
'42' => '(#FIELD# | #VALUE#)=#VALUE#',
'43' => '(#FIELD# | #VALUE#)!=#VALUE#',
// Type = multiple, relation, offset = 64
'64' => '#FIELD# = \'#VALUE#\'',
'65' => '#FIELD# != \'#VALUE#\'',
'66' => '#FIELD# LIKE \'%#VALUE#%\' AND #FIELD# LIKE \'%#VALUE1#%\'',
'67' => '(#FIELD# NOT LIKE \'%#VALUE#%\' OR #FIELD# NOT LIKE \'%#VALUE1#%\')',
'68' => '#FIELD# IN (#VALUE#)',
'69' => '#FIELD# NOT IN (#VALUE#)',
'70' => '(#FIELD# & #VALUE#)=#VALUE#',
'71' => '(#FIELD# & #VALUE#)!=#VALUE#',
'72' => '(#FIELD# | #VALUE#)=#VALUE#',
'73' => '(#FIELD# | #VALUE#)!=#VALUE#',
// Type = date, offset = 32
'96' => '#FIELD# = \'#VALUE#\'',
'97' => '#FIELD# != \'#VALUE#\'',
'98' => '#FIELD# > #VALUE#',
'99' => '#FIELD# < #VALUE#',
'100' => '#FIELD# >= #VALUE# AND #FIELD# <= #VALUE1#',
'101' => 'NOT (#FIELD# >= #VALUE# AND #FIELD# <= #VALUE1#)',
'102' => '(#FIELD# & #VALUE#)=#VALUE#',
'103' => '(#FIELD# & #VALUE#)!=#VALUE#',
'104' => '(#FIELD# | #VALUE#)=#VALUE#',
'105' => '(#FIELD# | #VALUE#)!=#VALUE#',
// Type = boolean, offset = 128
'128' => '#FIELD# = \'1\'',
'129' => '#FIELD# != \'1\'',
// Type = binary = 160
'160' => '#FIELD# = \'#VALUE#\'',
'161' => '#FIELD# != \'#VALUE#\'',
'162' => '(#FIELD# & #VALUE#)=#VALUE#',
'163' => '(#FIELD# & #VALUE#)=0',
];

/**
* @var array
*/
public $comp_offsets = [
'text' => 0,
'number' => 1,
'multiple' => 2,
'relation' => 2,
'date' => 3,
'time' => 3,
'boolean' => 4,
'binary' => 5,
];

/**
* @var string
*/
public $noWrap = ' nowrap';

/**
* Form data name prefix
*
* @var string
*/
public $name;

/**
* Table for the query
*
* @var string
*/
public $table;

/**
* @var array
*/
public $tableArray;

/**
* Field list
*
* @var string
*/
public $fieldList;

/**
* Array of the fields possible
*
* @var array
*/
public $fields = [];

/**
* @var array
*/
public $extFieldLists = [];

/**
* The query config
*
* @var array
*/
public $queryConfig = [];

/**
* @var bool
*/
public $enablePrefix = false;

/**
* @var bool
*/
public $enableQueryParts = false;

/**
* @var string
*/
protected $formName = '';

/**
* @var string
*/
protected $fieldName;

/**
* @var array Settings, usually from the controller, previously known as MOD_SETTINGS
*/
protected $settings = [];

public function __construct()
{
trigger_error(__CLASS__ . ' will be removed in TYPO3 v12.', E_USER_DEPRECATED);
}

/**
* Make a list of fields for current table
*
* @return string Separated list of fields
*/
public function makeFieldList()
{
$fieldListArr = [];
if (is_array($GLOBALS['TCA'][$this->table])) {
$fieldListArr = array_keys($GLOBALS['TCA'][$this->table]['columns'] ?? []);
$fieldListArr[] = 'uid';
$fieldListArr[] = 'pid';
$fieldListArr[] = 'deleted';
if ($GLOBALS['TCA'][$this->table]['ctrl']['tstamp'] ?? false) {
$fieldListArr[] = $GLOBALS['TCA'][$this->table]['ctrl']['tstamp'];
}
if ($GLOBALS['TCA'][$this->table]['ctrl']['crdate'] ?? false) {
$fieldListArr[] = $GLOBALS['TCA'][$this->table]['ctrl']['crdate'];
}
if ($GLOBALS['TCA'][$this->table]['ctrl']['cruser_id'] ?? false) {
$fieldListArr[] = $GLOBALS['TCA'][$this->table]['ctrl']['cruser_id'];
}
if ($GLOBALS['TCA'][$this->table]['ctrl']['sortby'] ?? false) {
$fieldListArr[] = $GLOBALS['TCA'][$this->table]['ctrl']['sortby'];
}
}
return implode(',', $fieldListArr);
}

/**
* Init function
*
* @param string $name The name
* @param string $table The table name
* @param string $fieldList The field list
* @param array $settings Module settings like checkboxes in the interface
*/
public function init($name, $table, $fieldList = '', array $settings = [])
{
// Analysing the fields in the table.
if (is_array($GLOBALS['TCA'][$table])) {
$this->name = $name;
$this->table = $table;
$this->fieldList = $fieldList ?: $this->makeFieldList();
$this->settings = $settings;
$fieldArr = GeneralUtility::trimExplode(',', $this->fieldList, true);
foreach ($fieldArr as $fieldName) {
isset($GLOBALS['TCA'][$this->table]['columns'][$fieldName]) ? $fC = $GLOBALS['TCA'][$this->table]['columns'][$fieldName] : $fC = null;
$this->fields[$fieldName]['type'] = nulL;
if ($fC){
$this->fields[$fieldName] = $fC['config'];
$this->fields[$fieldName]['exclude'] = isset($fC['exclude']) ? $fC['exclude'] : '';
if ($this->fields[$fieldName]['type'] === 'user' && !isset($this->fields[$fieldName]['type']['userFunc'])
|| $this->fields[$fieldName]['type'] === 'none'
) {
// Do not list type=none "virtual" fields or query them from db,
// and if type is user without defined userFunc
unset($this->fields[$fieldName]);
continue;
}
if (is_array($fC) && isset($fC['label'])) {
$this->fields[$fieldName]['label'] = rtrim(trim($this->getLanguageService()->sL($fC['label'])), ':');
if (!isset($this->fields[$fieldName]['eval'])){
$this->fields[$fieldName]['eval'] = '';
}
switch ($this->fields[$fieldName]['type']) {
case 'input':
if (preg_match('/int|year/i', $this->fields[$fieldName]['eval'])) {
$this->fields[$fieldName]['type'] = 'number';
} elseif (preg_match('/time/i', $this->fields[$fieldName]['eval'])) {
$this->fields[$fieldName]['type'] = 'time';
} elseif (preg_match('/date/i', $this->fields[$fieldName]['eval'])) {
$this->fields[$fieldName]['type'] = 'date';
} else {
$this->fields[$fieldName]['type'] = 'text';
}
break;
case 'check':
if (
(! isset($this->fields[$fieldName]['items'])) ||
(isset($this->fields[$fieldName]['items']) && (!$this->fields[$fieldName]['items'] || count($this->fields[$fieldName]['items']) <= 1))
) {
$this->fields[$fieldName]['type'] = 'boolean';
} else {
$this->fields[$fieldName]['type'] = 'binary';
}
break;
case 'radio':
$this->fields[$fieldName]['type'] = 'multiple';
break;
case 'select':
case 'category':
$this->fields[$fieldName]['type'] = 'multiple';
if ($this->fields[$fieldName]['foreign_table']) {
$this->fields[$fieldName]['type'] = 'relation';
}
if (isset($this->fields[$fieldName]['special'])) {
$this->fields[$fieldName]['type'] = 'text';
}
break;
case 'group':
if (($this->fields[$fieldName]['internal_type'] ?? '') !== 'folder') {
$this->fields[$fieldName]['type'] = 'relation';
}
break;
case 'user':
case 'flex':
case 'passthrough':
case 'none':
case 'text':
default:
$this->fields[$fieldName]['type'] = 'text';
}
}
if (! $this->fields[$fieldName]['type']){
$this->fields[$fieldName]['label'] = '[FIELD: ' . $fieldName . ']';
switch ($fieldName) {
case 'pid':
$this->fields[$fieldName]['type'] = 'relation';
$this->fields[$fieldName]['allowed'] = 'pages';
break;
case 'cruser_id':
$this->fields[$fieldName]['type'] = 'relation';
$this->fields[$fieldName]['allowed'] = 'be_users';
break;
case 'tstamp':
case 'crdate':
$this->fields[$fieldName]['type'] = 'time';
break;
case 'deleted':
$this->fields[$fieldName]['type'] = 'boolean';
break;
default:
$this->fields[$fieldName]['type'] = 'number';
}
}
}
//echo "$fieldName::" . $this->fields[$fieldName]['type'] . "<br>";
}
}
/* // EXAMPLE:
$this->queryConfig = array(
array(
'operator' => 'AND',
'type' => 'FIELD_space_before_class',
),
array(
'operator' => 'AND',
'type' => 'FIELD_records',
'negate' => 1,
'inputValue' => 'foo foo'
),
array(
'type' => 'newlevel',
'nl' => array(
array(
'operator' => 'AND',
'type' => 'FIELD_space_before_class',
'negate' => 1,
'inputValue' => 'foo foo'
),
array(
'operator' => 'AND',
'type' => 'FIELD_records',
'negate' => 1,
'inputValue' => 'foo foo'
)
)
),
array(
'operator' => 'OR',
'type' => 'FIELD_maillist',
)
);
*/
$this->initUserDef();
}

/**
* Set and clean up external lists
*
* @param string $name The name
* @param string $list The list
* @param string $force
*/
public function setAndCleanUpExternalLists($name, $list, $force = '')
{
$fields = array_unique(GeneralUtility::trimExplode(',', $list . ',' . $force, true));
$reList = [];
foreach ($fields as $fieldName) {
if ($this->fields[$fieldName]) {
$reList[] = $fieldName;
}
}
$this->extFieldLists[$name] = implode(',', $reList);
}

/**
* Process data
*
* @param array $qC Query config
*/
public function procesData($qC = [])
{
$this->queryConfig = $qC;
$POST = GeneralUtility::_POST();
// If delete...
if (isset($POST['qG_del'])) {
// Initialize array to work on, save special parameters
$ssArr = $this->getSubscript($POST['qG_del']);
$workArr = &$this->queryConfig;
$ssArrSize = count($ssArr) - 1;
$i = 0;
for (; $i < $ssArrSize; $i++) {
$workArr = &$workArr[$ssArr[$i]];
}
// Delete the entry and move the other entries
unset($workArr[$ssArr[$i]]);
$workArrSize = count($workArr);
for ($j = $ssArr[$i]; $j < $workArrSize; $j++) {
$workArr[$j] = $workArr[$j + 1];
unset($workArr[$j + 1]);
}
}
// If insert...
if (isset($POST['qG_ins'])) {
// Initialize array to work on, save special parameters
$ssArr = $this->getSubscript($POST['qG_ins']);
$workArr = &$this->queryConfig;
$ssArrSize = count($ssArr) - 1;
$i = 0;
for (; $i < $ssArrSize; $i++) {
$workArr = &$workArr[$ssArr[$i]];
}
// Move all entries above position where new entry is to be inserted
$workArrSize = count($workArr);
for ($j = $workArrSize; $j > $ssArr[$i]; $j--) {
$workArr[$j] = $workArr[$j - 1];
}
// Clear new entry position
unset($workArr[$ssArr[$i] + 1]);
$workArr[$ssArr[$i] + 1]['type'] = 'FIELD_';
}
// If move up...
if (isset($POST['qG_up'])) {
// Initialize array to work on
$ssArr = $this->getSubscript($POST['qG_up']);
$workArr = &$this->queryConfig;
$ssArrSize = count($ssArr) - 1;
$i = 0;
for (; $i < $ssArrSize; $i++) {
$workArr = &$workArr[$ssArr[$i]];
}
// Swap entries
$qG_tmp = $workArr[$ssArr[$i]];
$workArr[$ssArr[$i]] = $workArr[$ssArr[$i] - 1];
$workArr[$ssArr[$i] - 1] = $qG_tmp;
}
// If new level...
if (isset($POST['qG_nl'])) {
// Initialize array to work on
$ssArr = $this->getSubscript($POST['qG_nl']);
$workArr = &$this->queryConfig;
$ssArraySize = count($ssArr) - 1;
$i = 0;
for (; $i < $ssArraySize; $i++) {
$workArr = &$workArr[$ssArr[$i]];
}
// Do stuff:
$tempEl = $workArr[$ssArr[$i]];
if (is_array($tempEl)) {
if ($tempEl['type'] !== 'newlevel') {
$workArr[$ssArr[$i]] = [
'type' => 'newlevel',
'operator' => $tempEl['operator'],
'nl' => [$tempEl],
];
}
}
}
// If collapse level...
if (isset($POST['qG_remnl'])) {
// Initialize array to work on
$ssArr = $this->getSubscript($POST['qG_remnl']);
$workArr = &$this->queryConfig;
$ssArrSize = count($ssArr) - 1;
$i = 0;
for (; $i < $ssArrSize; $i++) {
$workArr = &$workArr[$ssArr[$i]];
}
// Do stuff:
$tempEl = $workArr[$ssArr[$i]];
if (is_array($tempEl)) {
if ($tempEl['type'] === 'newlevel') {
$a1 = array_slice($workArr, 0, $ssArr[$i]);
$a2 = array_slice($workArr, $ssArr[$i]);
array_shift($a2);
$a3 = $tempEl['nl'];
$a3[0]['operator'] = $tempEl['operator'];
$workArr = array_merge($a1, $a3, $a2);
}
}
}
}

/**
* Clean up query config
*
* @param array $queryConfig Query config
* @return array
*/
public function cleanUpQueryConfig($queryConfig)
{
// Since we don't traverse the array using numeric keys in the upcoming while-loop make sure it's fresh and clean before displaying
if (is_array($queryConfig)) {
ksort($queryConfig);
} else {
// queryConfig should never be empty!
if (!isset($queryConfig[0]) || empty($queryConfig[0]['type'])) {
// Make sure queryConfig is an array
$queryConfig = [];
$queryConfig[0] = ['type' => 'FIELD_'];
}
}
// Traverse:
foreach ($queryConfig as $key => $conf) {
$fieldName = '';
if (strpos($conf['type'], 'FIELD_') === 0) {
$fieldName = substr($conf['type'], 6);
$fieldType = isset($this->fields[$fieldName]['type']) ? $this->fields[$fieldName]['type'] : 'ignore';
} elseif ($conf['type'] === 'newlevel') {
$fieldType = $conf['type'];
} else {
$fieldType = 'ignore';
}
switch ($fieldType) {
case 'newlevel':
if (!$queryConfig[$key]['nl']) {
$queryConfig[$key]['nl'][0]['type'] = 'FIELD_';
}
$queryConfig[$key]['nl'] = $this->cleanUpQueryConfig($queryConfig[$key]['nl']);
break;
case 'userdef':
$queryConfig[$key] = $this->userDefCleanUp($queryConfig[$key]);
break;
case 'ignore':
break;
default:
$verifiedName = $this->verifyType($fieldName);
$queryConfig[$key]['type'] = 'FIELD_' . $this->verifyType($verifiedName);
if ($this->comp_offsets[$fieldType] != $conf['comparison'] >> 5) {
$conf['comparison'] = $this->comp_offsets[$fieldType] << 5;
}
$queryConfig[$key]['comparison'] = $this->verifyComparison($conf['comparison'], isset($conf['negate']) ? 1 : 0);
$queryConfig[$key]['inputValue'] = $this->cleanInputVal($queryConfig[$key]);
$queryConfig[$key]['inputValue1'] = $this->cleanInputVal($queryConfig[$key], '1');
}
}
return $queryConfig;
}

/**
* Get form elements
*
* @param int $subLevel
* @param string $queryConfig
* @param string $parent
* @return array
*/
public function getFormElements($subLevel = 0, $queryConfig = '', $parent = '')
{
$codeArr = [];
if (!is_array($queryConfig)) {
$queryConfig = $this->queryConfig;
}
$c = 0;
$arrCount = 0;
$loopCount = 0;
foreach ($queryConfig as $key => $conf) {
$fieldName = '';
$subscript = $parent . '[' . $key . ']';
$lineHTML = [];
$lineHTML[] = $this->mkOperatorSelect($this->name . $subscript, $conf['operator'], $c > 0, $conf['type'] !== 'FIELD_');
if (strpos($conf['type'], 'FIELD_') === 0) {
$fieldName = substr($conf['type'], 6);
$this->fieldName = $fieldName;
$fieldType = isset($this->fields[$fieldName]['type']) ? $this->fields[$fieldName]['type'] : 'ignore';
if (isset($this->comp_offsets[$fieldType]) && $this->comp_offsets[$fieldType] != $conf['comparison'] >> 5) {
$conf['comparison'] = $this->comp_offsets[$fieldType] << 5;
}
//nasty nasty...
//make sure queryConfig contains _actual_ comparevalue.
//mkCompSelect don't care, but getQuery does.
$queryConfig[$key]['comparison'] += isset($conf['negate']) - $conf['comparison'] % 2;
} elseif ($conf['type'] === 'newlevel') {
$fieldType = $conf['type'];
} else {
$fieldType = 'ignore';
}
$fieldPrefix = htmlspecialchars($this->name . $subscript);
switch ($fieldType) {
case 'ignore':
break;
case 'newlevel':
if (!$queryConfig[$key]['nl']) {
$queryConfig[$key]['nl'][0]['type'] = 'FIELD_';
}
$lineHTML[] = '<input type="hidden" name="' . $fieldPrefix . '[type]" value="newlevel">';
$codeArr[$arrCount]['sub'] = $this->getFormElements($subLevel + 1, $queryConfig[$key]['nl'], $subscript . '[nl]');
break;
case 'userdef':
$lineHTML[] = $this->userDef($fieldPrefix, $conf, $fieldName, $fieldType);
break;
case 'date':
$lineHTML[] = '<div class="form-inline">';
$lineHTML[] = $this->makeComparisonSelector($subscript, $fieldName, $conf);
if ($conf['comparison'] === 100 || $conf['comparison'] === 101) {
// between
$lineHTML[] = $this->getDateTimePickerField($fieldPrefix . '[inputValue]', $conf['inputValue'], 'date');
$lineHTML[] = $this->getDateTimePickerField($fieldPrefix . '[inputValue1]', $conf['inputValue1'], 'date');
} else {
$lineHTML[] = $this->getDateTimePickerField($fieldPrefix . '[inputValue]', $conf['inputValue'], 'date');
}
$lineHTML[] = '</div>';
break;
case 'time':
$lineHTML[] = '<div class="form-inline">';
$lineHTML[] = $this->makeComparisonSelector($subscript, $fieldName, $conf);
if ($conf['comparison'] === 100 || $conf['comparison'] === 101) {
// between:
$lineHTML[] = $this->getDateTimePickerField($fieldPrefix . '[inputValue]', $conf['inputValue'], 'datetime');
$lineHTML[] = $this->getDateTimePickerField($fieldPrefix . '[inputValue1]', $conf['inputValue1'], 'datetime');
} else {
$lineHTML[] = $this->getDateTimePickerField($fieldPrefix . '[inputValue]', $conf['inputValue'], 'datetime');
}
$lineHTML[] = '</div>';
break;
case 'multiple':
case 'binary':
case 'relation':
$lineHTML[] = '<div class="form-inline">';
$lineHTML[] = $this->makeComparisonSelector($subscript, $fieldName, $conf);
if ($conf['comparison'] === 68 || $conf['comparison'] === 69 || $conf['comparison'] === 162 || $conf['comparison'] === 163) {
$lineHTML[] = '<select class="form-select" name="' . $fieldPrefix . '[inputValue][]" multiple="multiple">';
} elseif ($conf['comparison'] === 66 || $conf['comparison'] === 67) {
if (is_array($conf['inputValue'])) {
$conf['inputValue'] = implode(',', $conf['inputValue']);
}
$lineHTML[] = '<input class="form-control t3js-clearable" type="text" value="' . htmlspecialchars($conf['inputValue']) . '" name="' . $fieldPrefix . '[inputValue]">';
} elseif ($conf['comparison'] === 64) {
if (is_array($conf['inputValue'])) {
$conf['inputValue'] = $conf['inputValue'][0];
}
$lineHTML[] = '<select class="form-select t3js-submit-change" name="' . $fieldPrefix . '[inputValue]">';
} else {
$lineHTML[] = '<select class="form-select t3js-submit-change" name="' . $fieldPrefix . '[inputValue]">';
}
if ($conf['comparison'] != 66 && $conf['comparison'] != 67) {
$lineHTML[] = $this->makeOptionList($fieldName, $conf, $this->table);
$lineHTML[] = '</select>';
}
$lineHTML[] = '</div>';
break;
case 'boolean':
$lineHTML[] = '<div class="form-inline">';
$lineHTML[] = $this->makeComparisonSelector($subscript, $fieldName, $conf);
$lineHTML[] = '<input type="hidden" value="1" name="' . $fieldPrefix . '[inputValue]">';
$lineHTML[] = '</div>';
break;
default:
$lineHTML[] = '<div class="form-inline">';
$lineHTML[] = $this->makeComparisonSelector($subscript, $fieldName, $conf);
if ($conf['comparison'] === 37 || $conf['comparison'] === 36) {
// between:
$lineHTML[] = '<input class="form-control t3js-clearable" type="text" value="' . htmlspecialchars($conf['inputValue']) . '" name="' . $fieldPrefix . '[inputValue]">';
$lineHTML[] = '<input class="form-control t3js-clearable" type="text" value="' . htmlspecialchars($conf['inputValue1']) . '" name="' . $fieldPrefix . '[inputValue1]">';
} else {
$lineHTML[] = '<input class="form-control t3js-clearable" type="text" value="' . htmlspecialchars($conf['inputValue']) . '" name="' . $fieldPrefix . '[inputValue]">';
}
$lineHTML[] = '</div>';
}
if ($fieldType !== 'ignore') {
$lineHTML[] = '<div class="btn-group" style="margin-top: .5em;">';
$lineHTML[] = $this->updateIcon();
if ($loopCount) {
$lineHTML[] = '<button class="btn btn-default" title="Remove condition" name="qG_del' . htmlspecialchars($subscript) . '"><i class="fa fa-trash fa-fw"></i></button>';
}
$lineHTML[] = '<button class="btn btn-default" title="Add condition" name="qG_ins' . htmlspecialchars($subscript) . '"><i class="fa fa-plus fa-fw"></i></button>';
if ($c != 0) {
$lineHTML[] = '<button class="btn btn-default" title="Move up" name="qG_up' . htmlspecialchars($subscript) . '"><i class="fa fa-chevron-up fa-fw"></i></button>';
}
if ($c != 0 && $fieldType !== 'newlevel') {
$lineHTML[] = '<button class="btn btn-default" title="New level" name="qG_nl' . htmlspecialchars($subscript) . '"><i class="fa fa-chevron-right fa-fw"></i></button>';
}
if ($fieldType === 'newlevel') {
$lineHTML[] = '<button class="btn btn-default" title="Collapse new level" name="qG_remnl' . htmlspecialchars($subscript) . '"><i class="fa fa-chevron-left fa-fw"></i></button>';
}
$lineHTML[] = '</div>';
$codeArr[$arrCount]['html'] = implode(LF, $lineHTML);
$codeArr[$arrCount]['query'] = $this->getQuerySingle($conf, $c === 0);
$arrCount++;
$c++;
}
$loopCount = 1;
}
$this->queryConfig = $queryConfig;
return $codeArr;
}

/**
* @param string $subscript
* @param string $fieldName
* @param array $conf
*
* @return string
*/
protected function makeComparisonSelector($subscript, $fieldName, $conf)
{
if (! isset($conf['negate'])){
$conf['negate'] = '';
}
$fieldPrefix = $this->name . $subscript;
$lineHTML = [];
$lineHTML[] = $this->mkTypeSelect($fieldPrefix . '[type]', $fieldName);
$lineHTML[] = ' <div class="input-group">';
$lineHTML[] = $this->mkCompSelect($fieldPrefix . '[comparison]', $conf['comparison'], $conf['negate'] ? 1 : 0);
$lineHTML[] = ' <div class="input-group-addon">';
$lineHTML[] = ' <input type="checkbox" class="checkbox t3js-submit-click"' . ($conf['negate'] ? ' checked' : '') . ' name="' . htmlspecialchars($fieldPrefix) . '[negate]">';
$lineHTML[] = ' </div>';
$lineHTML[] = ' </div>';
return implode(LF, $lineHTML);
}

/**
* Make option list
*
* @param string $fieldName
* @param array $conf
* @param string $table
* @return string
*/
public function makeOptionList($fieldName, $conf, $table)
{
$from_table_Arr = [];
$out = [];
$fieldSetup = $this->fields[$fieldName];
$languageService = $this->getLanguageService();
if ($fieldSetup['type'] === 'multiple') {
$optGroupOpen = false;
foreach ($fieldSetup['items'] as $val) {
if (strpos($val[0], 'LLL:') === 0) {
$value = $languageService->sL($val[0]);
} else {
$value = $val[0];
}
$itemVal = (string)($val[1] ?? '');
if ($itemVal === '--div--') {
if ($optGroupOpen) {
$out[] = '</optgroup>';
}
$optGroupOpen = true;
$out[] = '<optgroup label="' . htmlspecialchars($value) . '">';
} elseif (GeneralUtility::inList($conf['inputValue'], $itemVal)) {
$out[] = '<option value="' . htmlspecialchars($itemVal) . '" selected>' . htmlspecialchars($value) . '</option>';
} else {
$out[] = '<option value="' . htmlspecialchars($itemVal) . '">' . htmlspecialchars($value) . '</option>';
}
}
if ($optGroupOpen) {
$out[] = '</optgroup>';
}
}
if ($fieldSetup['type'] === 'binary') {
if(isset($fieldSetup['items'])){
foreach ($fieldSetup['items'] as $key => $val) {
if (strpos($val[0], 'LLL:') === 0) {
$value = $languageService->sL($val[0]);
} else {
$value = $val[0];
}
$itemVal = (string)(2 ** $key);
if (GeneralUtility::inList($conf['inputValue'], $itemVal)) {
$out[] = '<option value="' . $itemVal . '" selected>' . htmlspecialchars($value) . '</option>';
} else {
$out[] = '<option value="' . $itemVal . '">' . htmlspecialchars($value) . '</option>';
}
}
}
}
if ($fieldSetup['type'] === 'relation') {
$useTablePrefix = 0;
$dontPrefixFirstTable = 0;
if ($fieldSetup['items']) {
foreach ($fieldSetup['items'] as $val) {
if (strpos($val[0], 'LLL:') === 0) {
$value = $languageService->sL($val[0]);
} else {
$value = $val[0];
}
$outputValue = (string)($val[1] ?? '');
if ($outputValue && GeneralUtility::inList($conf['inputValue'], $outputValue)) {
$out[] = '<option value="' . htmlspecialchars($outputValue) . '" selected>' . htmlspecialchars($value) . '</option>';
} else {
$out[] = '<option value="' . htmlspecialchars($outputValue) . '">' . htmlspecialchars($value) . '</option>';
}
}
}
if (str_contains($fieldSetup['allowed'], ',')) {
$from_table_Arr = explode(',', $fieldSetup['allowed']);
$useTablePrefix = 1;
if (!$fieldSetup['prepend_tname']) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$statement = $queryBuilder->select($fieldName)
->from($table)
->execute();
while ($row = $statement->fetchAssociative()) {
if (str_contains($row[$fieldName], ',')) {
$checkContent = explode(',', $row[$fieldName]);
foreach ($checkContent as $singleValue) {
if (!str_contains($singleValue, '_')) {
$dontPrefixFirstTable = 1;
}
}
} else {
$singleValue = $row[$fieldName];
if ($singleValue !== '' && !str_contains($singleValue, '_')) {
$dontPrefixFirstTable = 1;
}
}
}
}
} else {
$from_table_Arr[0] = $fieldSetup['allowed'];
}
if ($fieldSetup['prepend_tname']) {
$useTablePrefix = 1;
}
if ($fieldSetup['foreign_table']) {
$from_table_Arr[0] = $fieldSetup['foreign_table'];
}
$counter = 0;
$tablePrefix = '';
$backendUserAuthentication = $this->getBackendUserAuthentication();
$outArray = [];
$labelFieldSelect = [];
foreach ($from_table_Arr as $from_table) {
$useSelectLabels = false;
$useAltSelectLabels = false;
if ($useTablePrefix && !$dontPrefixFirstTable && $counter != 1 || $counter === 1) {
$tablePrefix = $from_table . '_';
}
$counter = 1;
if (is_array($GLOBALS['TCA'][$from_table])) {
$labelField = $GLOBALS['TCA'][$from_table]['ctrl']['label'];
$altLabelField = $GLOBALS['TCA'][$from_table]['ctrl']['label_alt'];
if ($GLOBALS['TCA'][$from_table]['columns'][$labelField]['config']['items']) {
foreach ($GLOBALS['TCA'][$from_table]['columns'][$labelField]['config']['items'] as $labelArray) {
if (strpos($labelArray[0], 'LLL:') === 0) {
$labelFieldSelect[$labelArray[1]] = $languageService->sL($labelArray[0]);
} else {
$labelFieldSelect[$labelArray[1]] = $labelArray[0];
}
}
$useSelectLabels = true;
}
$altLabelFieldSelect = [];
if ($GLOBALS['TCA'][$from_table]['columns'][$altLabelField]['config']['items']) {
foreach ($GLOBALS['TCA'][$from_table]['columns'][$altLabelField]['config']['items'] as $altLabelArray) {
if (strpos($altLabelArray[0], 'LLL:') === 0) {
$altLabelFieldSelect[$altLabelArray[1]] = $languageService->sL($altLabelArray[0]);
} else {
$altLabelFieldSelect[$altLabelArray[1]] = $altLabelArray[0];
}
}
$useAltSelectLabels = true;
}

if (!$this->tableArray[$from_table]) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($from_table);
if (isset($this->settings['show_deleted']) && $this->settings['show_deleted']) {
$queryBuilder->getRestrictions()->removeAll();
} else {
$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
}
$selectFields = ['uid', $labelField];
if ($altLabelField) {
$selectFields = array_merge($selectFields, GeneralUtility::trimExplode(',', $altLabelField, true));
}
$queryBuilder->select(...$selectFields)
->from($from_table)
->orderBy('uid');
if (!$backendUserAuthentication->isAdmin()) {
$webMounts = $backendUserAuthentication->returnWebmounts();
$perms_clause = $backendUserAuthentication->getPagePermsClause(Permission::PAGE_SHOW);
$webMountPageTree = '';
$webMountPageTreePrefix = '';
foreach ($webMounts as $webMount) {
if ($webMountPageTree) {
$webMountPageTreePrefix = ',';
}
$webMountPageTree .= $webMountPageTreePrefix
. $this->getTreeList($webMount, 999, 0, $perms_clause);
}
if ($from_table === 'pages') {
$queryBuilder->where(
QueryHelper::stripLogicalOperatorPrefix($perms_clause),
$queryBuilder->expr()->in(
'uid',
$queryBuilder->createNamedParameter(
GeneralUtility::intExplode(',', $webMountPageTree),
Connection::PARAM_INT_ARRAY
)
)
);
} else {
$queryBuilder->where(
$queryBuilder->expr()->in(
'pid',
$queryBuilder->createNamedParameter(
GeneralUtility::intExplode(',', $webMountPageTree),
Connection::PARAM_INT_ARRAY
)
)
);
}
}
$statement = $queryBuilder->execute();
$this->tableArray[$from_table] = [];
while ($row = $statement->fetchAssociative()) {
$this->tableArray[$from_table][] = $row;
}
}

foreach ($this->tableArray[$from_table] as $key => $val) {
if ($useSelectLabels) {
$outArray[$tablePrefix . $val['uid']] = htmlspecialchars($labelFieldSelect[$val[$labelField]]);
} elseif ($val[$labelField]) {
$outArray[$tablePrefix . $val['uid']] = htmlspecialchars($val[$labelField]);
} elseif ($useAltSelectLabels) {
$outArray[$tablePrefix . $val['uid']] = htmlspecialchars($altLabelFieldSelect[$val[$altLabelField]]);
} else {
$outArray[$tablePrefix . $val['uid']] = htmlspecialchars($val[$altLabelField]);
}
}
if (isset($this->settings['options_sortlabel']) && $this->settings['options_sortlabel'] && is_array($outArray)) {
natcasesort($outArray);
}
}
}
foreach ($outArray as $key2 => $val2) {
$key2 = (string)$key2;
if (GeneralUtility::inList($conf['inputValue'], $key2)) {
$out[] = '<option value="' . htmlspecialchars($key2) . '" selected>[' . htmlspecialchars($key2) . '] ' . htmlspecialchars($val2) . '</option>';
} else {
$out[] = '<option value="' . htmlspecialchars($key2) . '">[' . htmlspecialchars($key2) . '] ' . htmlspecialchars($val2) . '</option>';
}
}
}
return implode(LF, $out);
}

/**
* Print code array
*
* @param array $codeArr
* @param int $recursionLevel
* @return string
*/
public function printCodeArray($codeArr, $recursionLevel = 0)
{
$out = [];
foreach ($codeArr as $k => $v) {
$out[] = '<div class="card">';
$out[] = '<div class="card-body">';
$out[] = $v['html'];

if ($this->enableQueryParts) {
$out[] = '<pre>';
$out[] = htmlspecialchars($v['query']);
$out[] = '</pre>';
}
if (isset($v['sub']) && is_array($v['sub'])) {
$out[] = '<div>';
$out[] = $this->printCodeArray($v['sub'], $recursionLevel + 1);
$out[] = '</div>';
}
$out[] = '</div>';
$out[] = '</div>';
}
return implode(LF, $out);
}

/**
* Make operator select
*
* @param string $name
* @param string $op
* @param bool $draw
* @param bool $submit
* @return string
*/
public function mkOperatorSelect($name, $op, $draw, $submit)
{
$out = [];
if ($draw) {
$out[] = '<div class="form-inline">';
$out[] = '<select class="form-select' . ($submit ? ' t3js-submit-change' : '') . '" name="' . htmlspecialchars($name) . '[operator]">';
$out[] = ' <option value="AND"' . (!$op || $op === 'AND' ? ' selected' : '') . '>' . htmlspecialchars($this->lang['AND']) . '</option>';
$out[] = ' <option value="OR"' . ($op === 'OR' ? ' selected' : '') . '>' . htmlspecialchars($this->lang['OR']) . '</option>';
$out[] = '</select>';
$out[] = '</div>';
} else {
$out[] = '<input type="hidden" value="' . htmlspecialchars($op) . '" name="' . htmlspecialchars($name) . '[operator]">';
}
return implode(LF, $out);
}

/**
* Make type select
*
* @param string $name
* @param string $fieldName
* @param string $prepend
* @return string
*/
public function mkTypeSelect($name, $fieldName, $prepend = 'FIELD_')
{
$out = [];
$out[] = '<select class="form-select t3js-submit-change" name="' . htmlspecialchars($name) . '">';
$out[] = '<option value=""></option>';
foreach ($this->fields as $key => $value) {
if (!isset($value['exclude'])){
$value['exclude'] = false;
}
if (!$value['exclude'] || $this->getBackendUserAuthentication()->check('non_exclude_fields', $this->table . ':' . $key)) {
$label = isset($this->fields[$key]['label']) ? $this->fields[$key]['label'] : $name;
$out[] = '<option value="' . htmlspecialchars($prepend . $key) . '"' . ($key === $fieldName ? ' selected' : '') . '>' . htmlspecialchars($label) . '</option>';
}
}
$out[] = '</select>';
return implode(LF, $out);
}

/**
* Verify type
*
* @param string $fieldName
* @return string
*/
public function verifyType($fieldName)
{
$first = '';
foreach ($this->fields as $key => $value) {
if (!$first) {
$first = $key;
}
if ($key === $fieldName) {
return $key;
}
}
return $first;
}

/**
* Verify comparison
*
* @param string $comparison
* @param int $neg
* @return int
*/
public function verifyComparison($comparison, $neg)
{
$compOffSet = $comparison >> 5;
$first = -1;
for ($i = 32 * $compOffSet + $neg; $i < 32 * ($compOffSet + 1); $i += 2) {
if ($first === -1) {
$first = $i;
}
if ($i >> 1 === $comparison >> 1) {
return $i;
}
}
return $first;
}

/**
* Make field to input select
*
* @param string $name
* @param string $fieldName
* @return string
*/
public function mkFieldToInputSelect($name, $fieldName)
{
$out = [];
$out[] = '<div class="input-group" style="margin-bottom: .5em;">';
$out[] = ' <span class="input-group-btn">';
$out[] = $this->updateIcon();
$out[] = ' </span>';
$out[] = ' <input type="text" class="form-control t3js-clearable" value="' . htmlspecialchars($fieldName) . '" name="' . htmlspecialchars($name) . '">';
$out[] = '</div>';

$out[] = '<select class="form-select t3js-addfield" name="_fieldListDummy" size="5" data-field="' . htmlspecialchars($name) . '">';
foreach ($this->fields as $key => $value) {
if (!$value['exclude'] || $this->getBackendUserAuthentication()->check('non_exclude_fields', $this->table . ':' . $key)) {
$label = $this->fields[$key]['label'];
$out[] = '<option value="' . htmlspecialchars($key) . '"' . ($key === $fieldName ? ' selected' : '') . '>' . htmlspecialchars($label) . '</option>';
}
}
$out[] = '</select>';
return implode(LF, $out);
}

/**
* Make table select
*
* @param string $name
* @param string $cur
* @return string
*/
public function mkTableSelect($name, $cur)
{
$out = [];
$out[] = '<select class="form-select t3js-submit-change" name="' . $name . '">';
$out[] = '<option value=""></option>';
foreach ($GLOBALS['TCA'] as $tN => $value) {
if ($this->getBackendUserAuthentication()->check('tables_select', $tN)) {
$out[] = '<option value="' . htmlspecialchars($tN) . '"' . ($tN === $cur ? ' selected' : '') . '>' . htmlspecialchars($this->getLanguageService()->sL($GLOBALS['TCA'][$tN]['ctrl']['title'])) . '</option>';
}
}
$out[] = '</select>';
return implode(LF, $out);
}

/**
* Make comparison select
*
* @param string $name
* @param string $comparison
* @param int $neg
* @return string
*/
public function mkCompSelect($name, $comparison, $neg)
{
$compOffSet = $comparison >> 5;
$out = [];
$out[] = '<select class="form-select t3js-submit-change" name="' . $name . '">';
for ($i = 32 * $compOffSet + $neg; $i < 32 * ($compOffSet + 1); $i += 2) {
if (isset($this->lang['comparison'][$i . '_'])){
if ($this->lang['comparison'][$i . '_']) {
$out[] = '<option value="' . $i . '"' . ($i >> 1 === $comparison >> 1 ? ' selected' : '') . '>' . htmlspecialchars($this->lang['comparison'][$i . '_']) . '</option>';
}
}
}
$out[] = '</select>';
return implode(LF, $out);
}

/**
* Get subscript
*
* @param array $arr
* @return array
*/
public function getSubscript($arr): array
{
$retArr = [];
while (\is_array($arr)) {
reset($arr);
$key = key($arr);
$retArr[] = $key;
if (isset($arr[$key])) {
$arr = $arr[$key];
} else {
break;
}
}
return $retArr;
}

/**
* Init user definition
*/
public function initUserDef()
{
}

/**
* User definition
*
* @param string $fieldPrefix
* @param array $conf
* @param string $fieldName
* @param string $fieldType
*
* @return string
*/
public function userDef($fieldPrefix, $conf, $fieldName, $fieldType)
{
return '';
}

/**
* User definition clean up
*
* @param array $queryConfig
* @return array
*/
public function userDefCleanUp($queryConfig)
{
return $queryConfig;
}

/**
* Get query
*
* @param array $queryConfig
* @param string $pad
* @return string
*/
public function getQuery($queryConfig, $pad = '')
{
$qs = '';
// Since we don't traverse the array using numeric keys in the upcoming whileloop make sure it's fresh and clean
ksort($queryConfig);
$first = true;
foreach ($queryConfig as $key => $conf) {
$conf = $this->convertIso8601DatetimeStringToUnixTimestamp($conf);
switch ($conf['type']) {
case 'newlevel':
$qs .= LF . $pad . trim($conf['operator']) . ' (' . $this->getQuery(
$queryConfig[$key]['nl'],
$pad . ' '
) . LF . $pad . ')';
break;
case 'userdef':
$qs .= LF . $pad . $this->getUserDefQuery($conf, $first);
break;
default:
$qs .= LF . $pad . $this->getQuerySingle($conf, $first);
}
$first = false;
}
return $qs;
}

/**
* Convert ISO-8601 timestamp (string) into unix timestamp (int)
*
* @param array $conf
* @return array
*/
protected function convertIso8601DatetimeStringToUnixTimestamp(array $conf): array
{
if ($this->isDateOfIso8601Format($conf['inputValue'])) {
$conf['inputValue'] = strtotime($conf['inputValue']);
if ($this->isDateOfIso8601Format($conf['inputValue1'])) {
$conf['inputValue1'] = strtotime($conf['inputValue1']);
}
}

return $conf;
}

/**
* Checks if the given value is of the ISO 8601 format.
*
* @param mixed $date
* @return bool
*/
protected function isDateOfIso8601Format($date): bool
{
if (!is_int($date) && !is_string($date)) {
return false;
}
$format = 'Y-m-d\\TH:i:s\\Z';
$formattedDate = \DateTime::createFromFormat($format, (string)$date);
return $formattedDate && $formattedDate->format($format) === $date;
}

/**
* Get single query
*
* @param array $conf
* @param bool $first
* @return string
*/
public function getQuerySingle($conf, $first)
{
$qs = '';
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->table);
$prefix = $this->enablePrefix ? $this->table . '.' : '';
if (!$first) {
// Is it OK to insert the AND operator if none is set?
$operator = strtoupper(trim($conf['operator']));
if (!in_array($operator, ['AND', 'OR'], true)) {
$operator = 'AND';
}
$qs .= $operator . ' ';
}
$qsTmp = str_replace('#FIELD#', $prefix . trim(substr($conf['type'], 6)), $this->compSQL[$conf['comparison']]);
$inputVal = $this->cleanInputVal($conf);
if ($conf['comparison'] === 68 || $conf['comparison'] === 69) {
$inputVal = explode(',', $inputVal);
foreach ($inputVal as $key => $fileName) {
$inputVal[$key] = $queryBuilder->quote($fileName);
}
$inputVal = implode(',', $inputVal);
$qsTmp = str_replace('#VALUE#', $inputVal, $qsTmp);
} elseif ($conf['comparison'] === 162 || $conf['comparison'] === 163) {
$inputValArray = explode(',', $inputVal);
$inputVal = 0;
foreach ($inputValArray as $fileName) {
$inputVal += (int)$fileName;
}
$qsTmp = str_replace('#VALUE#', (string)$inputVal, $qsTmp);
} else {
if (is_array($inputVal)) {
$inputVal = $inputVal[0];
}
$qsTmp = str_replace('#VALUE#', trim($queryBuilder->quote($inputVal), '\''), $qsTmp);
}
if ($conf['comparison'] === 37 || $conf['comparison'] === 36 || $conf['comparison'] === 66 || $conf['comparison'] === 67 || $conf['comparison'] === 100 || $conf['comparison'] === 101) {
// between:
$inputVal = $this->cleanInputVal($conf, '1');
$qsTmp = str_replace('#VALUE1#', trim($queryBuilder->quote($inputVal), '\''), $qsTmp);
}
$qs .= trim((string)$qsTmp);
return $qs;
}

/**
* Clear input value
*
* @param array $conf
* @param string $suffix
* @return string
*/
public function cleanInputVal($conf, $suffix = '')
{
$inputVal = null;
if (isset($conf['inputValue' . $suffix])){
if ($conf['comparison'] >> 5 === 0 || ($conf['comparison'] === 32 || $conf['comparison'] === 33 || $conf['comparison'] === 64 || $conf['comparison'] === 65 || $conf['comparison'] === 66 || $conf['comparison'] === 67 || $conf['comparison'] === 96 || $conf['comparison'] === 97)) {
$inputVal = $conf['inputValue' . $suffix];
} elseif ($conf['comparison'] === 39 || $conf['comparison'] === 38) {
// in list:
$inputVal = implode(',', GeneralUtility::intExplode(',', $conf['inputValue' . $suffix]));
} elseif ($conf['comparison'] === 68 || $conf['comparison'] === 69 || $conf['comparison'] === 162 || $conf['comparison'] === 163) {
// in list:
if (is_array($conf['inputValue' . $suffix])) {
$inputVal = implode(',', $conf['inputValue' . $suffix]);
} elseif ($conf['inputValue' . $suffix]) {
$inputVal = $conf['inputValue' . $suffix];
} else {
$inputVal = 0;
}
} elseif (!is_array($conf['inputValue' . $suffix]) && strtotime($conf['inputValue' . $suffix])) {
$inputVal = $conf['inputValue' . $suffix];
} elseif (!is_array($conf['inputValue' . $suffix]) && MathUtility::canBeInterpretedAsInteger($conf['inputValue' . $suffix])) {
$inputVal = (int)$conf['inputValue' . $suffix];
} else {
// TODO: Six eyes looked at this code and nobody understood completely what is going on here and why we
// fallback to float casting, the whole class smells like it needs a refactoring.
$inputVal = (float)$conf['inputValue' . $suffix];
}
}
return $inputVal;
}

/**
* Get user definition query
*
* @param array $qcArr
* @param bool $first
*/
public function getUserDefQuery($qcArr, $first)
{
}

/**
* Update icon
*
* @return string
*/
public function updateIcon()
{
return '<button class="btn btn-default" title="Update" name="just_update"><i class="fa fa-refresh fa-fw"></i></button>';
}

/**
* Get label column
*
* @return string
*/
public function getLabelCol()
{
return $GLOBALS['TCA'][$this->table]['ctrl']['label'];
}

/**
* Make selector table
*
* @param array $modSettings
* @param string $enableList
* @return string
*/
public function makeSelectorTable($modSettings, $enableList = 'table,fields,query,group,order,limit')
{
$out = [];
$enableArr = explode(',', $enableList);
$userTsConfig = $this->getBackendUserAuthentication()->getTSConfig();

// Make output
if (in_array('table', $enableArr) && ! isset($userTsConfig['mod.']['dbint.']['disableSelectATable'])) {
$out[] = '<div class="form-group">';
$out[] = ' <label for="SET[queryTable]">Select a table:</label>';
$out[] = $this->mkTableSelect('SET[queryTable]', $this->table);
$out[] = '</div>';
}
if ($this->table) {
// Init fields:
if (isset($modSettings['queryFields'])){
$this->setAndCleanUpExternalLists('queryFields', $modSettings['queryFields'], 'uid,' . $this->getLabelCol());
}
if (isset($modSettings['queryGroup'])){
$this->setAndCleanUpExternalLists('queryGroup', $modSettings['queryGroup']);
}
if (isset($modSettings['queryOrder'])){
$this->setAndCleanUpExternalLists('queryOrder', $modSettings['queryOrder'] . ',' . $modSettings['queryOrder2']);
}
// Limit:
isset($modSettings['queryLimit']) ? $this->extFieldLists['queryLimit'] = $modSettings['queryLimit'] : $this->extFieldLists['queryLimit'] = '';
if (!$this->extFieldLists['queryLimit']) {
$this->extFieldLists['queryLimit'] = 100;
}
$parts = GeneralUtility::intExplode(',', $this->extFieldLists['queryLimit']);
$limitBegin = 0;
$limitLength = (int)($this->extFieldLists['queryLimit'] ?? 0);
if (isset($parts[1])) {
$limitBegin = (int)$parts[0];
$limitLength = (int)$parts[1];
}
$this->extFieldLists['queryLimit'] = implode(',', array_slice($parts, 0, 2));
// Insert Descending parts
if (isset($this->extFieldLists['queryOrder'])) {
$descParts = explode(',', $modSettings['queryOrderDesc'] . ',' . $modSettings['queryOrder2Desc']);
$orderParts = explode(',', $this->extFieldLists['queryOrder']);
$reList = [];
foreach ($orderParts as $kk => $vv) {
$reList[] = $vv . ($descParts[$kk] ? ' DESC' : '');
}
$this->extFieldLists['queryOrder_SQL'] = implode(',', $reList);
}
// defaults
$ardefaults = [
'mod.' => [
'dbint.' => [
'disableSelectFields' => false,
'disableMakeQuery'=> false,
'disableGroupBy'=> false,
'disableOrderBy' => false,
]
]
];
$userTsConfig = array_merge_recursive($ardefaults, $userTsConfig);
// Query Generator:
$this->procesData($modSettings['queryConfig'] ? unserialize($modSettings['queryConfig'], ['allowed_classes' => false]) : []);
$this->queryConfig = $this->cleanUpQueryConfig($this->queryConfig);
$this->enableQueryParts = (bool)$modSettings['search_query_smallparts'];
$codeArr = $this->getFormElements();
$queryCode = $this->printCodeArray($codeArr);
if (in_array('fields', $enableArr) && !$userTsConfig['mod.']['dbint.']['disableSelectFields']) {
$out[] = '<div class="form-group form-group-with-button-addon">';
$out[] = ' <label for="SET[queryFields]">Select fields:</label>';
$out[] = $this->mkFieldToInputSelect('SET[queryFields]', $this->extFieldLists['queryFields']);
$out[] = '</div>';
}
if (in_array('query', $enableArr) && !$userTsConfig['mod.']['dbint.']['disableMakeQuery']) {
$out[] = '<div class="form-group">';
$out[] = ' <label>Make Query:</label>';
$out[] = $queryCode;
$out[] = '</div>';
}
if (in_array('group', $enableArr) && !$userTsConfig['mod.']['dbint.']['disableGroupBy']) {
$out[] = '<div class="form-group form-inline">';
$out[] = ' <label for="SET[queryGroup]">Group By:</label>';
$out[] = $this->mkTypeSelect('SET[queryGroup]', $this->extFieldLists['queryGroup'], '');
$out[] = '</div>';
}
if (in_array('order', $enableArr) && !$userTsConfig['mod.']['dbint.']['disableOrderBy']) {
$orderByArr = explode(',', $this->extFieldLists['queryOrder']);
$orderBy = [];
$orderBy[] = $this->mkTypeSelect('SET[queryOrder]', $orderByArr[0], '');
$orderBy[] = '<div class="form-check">';
$orderBy[] = BackendUtility::getFuncCheck(0, 'SET[queryOrderDesc]', $modSettings['queryOrderDesc'], '', '', 'id="checkQueryOrderDesc"');
$orderBy[] = ' <label class="form-check-label" for="checkQueryOrderDesc">';
$orderBy[] = 'Descending';
$orderBy[] = ' </label>';
$orderBy[] = '</div>';

if ($orderByArr[0]) {
$orderBy[] = $this->mkTypeSelect('SET[queryOrder2]', $orderByArr[1], '');
$orderBy[] = '<div class="form-check">';
$orderBy[] = BackendUtility::getFuncCheck(0, 'SET[queryOrder2Desc]', $modSettings['queryOrder2Desc'], '', '', 'id="checkQueryOrder2Desc"');
$orderBy[] = ' <label class="form-check-label" for="checkQueryOrder2Desc">';
$orderBy[] = 'Descending';
$orderBy[] = ' </label>';
$orderBy[] = '</div>';
}
$out[] = '<div class="form-group form-inline">';
$out[] = ' <label>Order By:</label>';
$out[] = implode(LF, $orderBy);
$out[] = '</div>';
}
if (in_array('limit', $enableArr) && !$userTsConfig['mod.']['dbint.']['disableLimit']) {
$limit = [];
$limit[] = '<div class="input-group">';
$limit[] = ' <span class="input-group-btn">';
$limit[] = $this->updateIcon();
$limit[] = ' </span>';
$limit[] = ' <input type="text" class="form-control" value="' . htmlspecialchars($this->extFieldLists['queryLimit']) . '" name="SET[queryLimit]" id="queryLimit">';
$limit[] = '</div>';

$prevLimit = $limitBegin - $limitLength < 0 ? 0 : $limitBegin - $limitLength;
$prevButton = '';
$nextButton = '';

if ($limitBegin) {
$prevButton = '<input type="button" class="btn btn-default" value="previous ' . htmlspecialchars((string)$limitLength) . '" data-value="' . htmlspecialchars($prevLimit . ',' . $limitLength) . '">';
}
if (!$limitLength) {
$limitLength = 100;
}

$nextLimit = $limitBegin + $limitLength;
if ($nextLimit < 0) {
$nextLimit = 0;
}
if ($nextLimit) {
$nextButton = '<input type="button" class="btn btn-default" value="next ' . htmlspecialchars((string)$limitLength) . '" data-value="' . htmlspecialchars($nextLimit . ',' . $limitLength) . '">';
}

$out[] = '<div class="form-group">';
$out[] = ' <label>Limit:</label>';
$out[] = ' <div class="form-inline">';
$out[] = implode(LF, $limit);
$out[] = ' <div class="btn-group t3js-limit-submit">';
$out[] = $prevButton;
$out[] = $nextButton;
$out[] = ' </div>';
$out[] = ' <div class="btn-group t3js-limit-submit">';
$out[] = ' <input type="button" class="btn btn-default" data-value="10" value="10">';
$out[] = ' <input type="button" class="btn btn-default" data-value="20" value="20">';
$out[] = ' <input type="button" class="btn btn-default" data-value="50" value="50">';
$out[] = ' <input type="button" class="btn btn-default" data-value="100" value="100">';
$out[] = ' </div>';
$out[] = ' </div>';
$out[] = '</div>';
}
}
return implode(LF, $out);
}

/**
* Recursively fetch all descendants of a given page
*
* @param int $id uid of the page
* @param int $depth
* @param int $begin
* @param string $permClause
* @return string comma separated list of descendant pages
*/
public function getTreeList($id, $depth, $begin = 0, $permClause = '')
{
$depth = (int)$depth;
$begin = (int)$begin;
$id = (int)$id;
if ($id < 0) {
$id = abs($id);
}
if ($begin === 0) {
$theList = $id;
} else {
$theList = '';
}
if ($id && $depth > 0) {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
$queryBuilder->select('uid')
->from('pages')
->where(
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($id, \PDO::PARAM_INT)),
$queryBuilder->expr()->eq('sys_language_uid', 0)
)
->orderBy('uid');
if ($permClause !== '') {
$queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($permClause));
}
$statement = $queryBuilder->execute();
while ($row = $statement->fetchAssociative()) {
if ($begin <= 0) {
$theList .= ',' . $row['uid'];
}
if ($depth > 1) {
$theSubList = $this->getTreeList($row['uid'], $depth - 1, $begin - 1, $permClause);
if (!empty($theList) && !empty($theSubList) && ($theSubList[0] !== ',')) {
$theList .= ',';
}
$theList .= $theSubList;
}
}
}
return $theList;
}

/**
* Get select query
*
* @param string $qString
* @return string
*/
public function getSelectQuery($qString = ''): string
{
$backendUserAuthentication = $this->getBackendUserAuthentication();
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->table);
if (isset($this->settings['show_deleted']) && $this->settings['show_deleted']) {
$queryBuilder->getRestrictions()->removeAll();
} else {
$queryBuilder->getRestrictions()->removeAll()->add(GeneralUtility::makeInstance(DeletedRestriction::class));
}
$fieldList = GeneralUtility::trimExplode(
',',
$this->extFieldLists['queryFields']
. ',pid'
. ($GLOBALS['TCA'][$this->table]['ctrl']['delete'] ? ',' . $GLOBALS['TCA'][$this->table]['ctrl']['delete'] : '')
);
$queryBuilder->select(...$fieldList)
->from($this->table);

if (isset($this->extFieldLists['queryGroup'])) {
$queryBuilder->groupBy(...QueryHelper::parseGroupBy($this->extFieldLists['queryGroup']));
}
if (isset($this->extFieldLists['queryOrder'])) {
foreach (QueryHelper::parseOrderBy($this->extFieldLists['queryOrder_SQL']) as $orderPair) {
[$fieldName, $order] = $orderPair;
$queryBuilder->addOrderBy($fieldName, $order);
}
}
if (isset($this->extFieldLists['queryLimit'])) {
// Explode queryLimit to fetch the limit and a possible offset
$parts = GeneralUtility::intExplode(',', $this->extFieldLists['queryLimit']);
if ($parts[1] ?? null) {
// Offset and limit are given
$queryBuilder->setFirstResult($parts[0]);
$queryBuilder->setMaxResults($parts[1]);
} else {
// Only the limit is given
$queryBuilder->setMaxResults($parts[0]);
}
}

if (!$backendUserAuthentication->isAdmin()) {
$webMounts = $backendUserAuthentication->returnWebmounts();
$perms_clause = $backendUserAuthentication->getPagePermsClause(Permission::PAGE_SHOW);
$webMountPageTree = '';
$webMountPageTreePrefix = '';
foreach ($webMounts as $webMount) {
if ($webMountPageTree) {
$webMountPageTreePrefix = ',';
}
$webMountPageTree .= $webMountPageTreePrefix
. $this->getTreeList($webMount, 999, 0, $perms_clause);
}
// createNamedParameter() is not used here because the SQL fragment will only include
// the :dcValueX placeholder when the query is returned as a string. The value for the
// placeholder would be lost in the process.
if ($this->table === 'pages') {
$queryBuilder->where(
QueryHelper::stripLogicalOperatorPrefix($perms_clause),
$queryBuilder->expr()->in(
'uid',
GeneralUtility::intExplode(',', $webMountPageTree)
)
);
} else {
$queryBuilder->where(
$queryBuilder->expr()->in(
'pid',
GeneralUtility::intExplode(',', $webMountPageTree)
)
);
}
}
if (!$qString) {
$qString = $this->getQuery($this->queryConfig);
}
$queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($qString));

return $queryBuilder->getSQL();
}

/**
* @param string $name the field name
* @param string $timestamp ISO-8601 timestamp
* @param string $type [datetime, date, time, timesec, year]
*
* @return string
*/
protected function getDateTimePickerField($name, $timestamp, $type)
{
$value = strtotime($timestamp) ? date($GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'], (int)strtotime($timestamp)) : '';
$id = StringUtility::getUniqueId('dt_');
$html = [];
$html[] = '<div class="input-group" id="' . $id . '-wrapper">';
$html[] = ' <input data-formengine-input-name="' . htmlspecialchars($name) . '" value="' . $value . '" class="form-control t3js-datetimepicker t3js-clearable" data-date-type="' . htmlspecialchars($type) . '" type="text" id="' . $id . '">';
$html[] = ' <input name="' . htmlspecialchars($name) . '" value="' . htmlspecialchars($timestamp) . '" type="hidden">';
$html[] = ' <span class="input-group-btn">';
$html[] = ' <label class="btn btn-default" for="' . $id . '">';
$html[] = ' <span class="fa fa-calendar"></span>';
$html[] = ' </label>';
$html[] = ' </span>';
$html[] = '</div>';
return implode(LF, $html);
}

/**
* Sets the current name of the input form.
*
* @param string $formName The name of the form.
*/
public function setFormName($formName)
{
$this->formName = trim($formName);
}

/**
* @return BackendUserAuthentication
*/
protected function getBackendUserAuthentication()
{
return $GLOBALS['BE_USER'];
}

/**
* @return LanguageService
*/
protected function getLanguageService()
{
return $GLOBALS['LANG'];
}
}
(3-3/5)