Commit dc2c9dbd authored by Stefan Bürk's avatar Stefan Bürk Committed by Christian Kuhn
Browse files

[BUGFIX] Provide quoted array to string-list implode support

With #96393 forward-compatible prepared statement
suport has been added, which lacks support for the
broadly known doctrine/dbal 'PARAM_STR_ARRAY' and
'PARAM_INT_ARRAY' types.

Internal methods as alternatives were introduced
as core internal methods with #92493 in the internal
'QueryHelper' class.

This patch now moves these internal implementations
to the 'QueryBuilder' to provide an forward-compatible
public alternative, which is needed to prepare statements
with 'in()' or 'notIn()' expression with array values,
which can only be used as non-placeholder, which is not
supported by PHP PDO implementation.

Further usage of internal implementation which was
introduced with #92493 are replaced with new public
methods and internal methods in QueryHelper removed.

Resolves: #96434
Related: #96393
Related: #92493
Releases: main, 11.5
Change-Id: I1f7f532eb82a248a28d8d7ec707d678d59a73ec0
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/72885

Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
parent b2b124cb
......@@ -1220,6 +1220,74 @@ class QueryBuilder
return $this->getConnection()->quoteColumnValuePairs($input);
}
/**
* Implode array to comma separated list with database int-quoted values to be used as direct
* value list for database 'in(...)' or 'notIn(...') expressions. Empty array will return 'NULL'
* as string to avoid database query failure, as 'IN()' is invalid, but 'IN(NULL)' is fine.
*
* This method should be used with care, the preferred way is to use placeholders. It is however
* useful when dealing with potentially many values, which could reach placeholder limit quickly.
*
* When working with prepared statement from QueryBuilder, use this method to proper quote array
* with integer values.
*
* The method can not be used in queries that re-bind a prepared statement to change values for
* subsequent execution due to a PDO limitation.
*
* Return value should only be used as value list for database queries 'in()' and 'notIn()' .
*/
public function quoteArrayBasedValueListToIntegerList(array $values): string
{
if (empty($values)) {
return 'NULL';
}
// Ensure values are all integer
$values = GeneralUtility::intExplode(',', implode(',', $values));
// Ensure all values are quoted as int for used dbms
$connection = $this;
array_walk($values, static function (&$value) use ($connection) {
$value = $connection->quote($value, Connection::PARAM_INT);
});
return implode(',', $values);
}
/**
* Implode array to comma separated list with database string-quoted values to be used as direct
* value list for database 'in(...)' or 'notIn(...') expressions. Empty array will return 'NULL'
* as string to avoid database query failure, as 'IN()' is invalid, but 'IN(NULL)' is fine.
*
* This method should be used with care, the preferred way is to use placeholders. It is however
* useful when dealing with potentially many values, which could reach placeholder limit quickly.
*
* When working with prepared statement from QueryBuilder, use this method to proper quote array
* with integer values.
*
* The method can not be used in queries that re-bind a prepared statement to change values for
* subsequent execution due to a PDO limitation.
*
* Return value should only be used as value list for database queries 'in()' and 'notIn()' .
*/
public function quoteArrayBasedValueListToStringList(array $values): string
{
if (empty($values)) {
return 'NULL';
}
// Ensure values are all strings
$values = GeneralUtility::trimExplode(',', implode(',', $values));
// Ensure all values are quoted as string values for used dbmns
$connection = $this;
array_walk($values, static function (&$value) use ($connection) {
$value = $connection->quote($value, Connection::PARAM_STR);
});
return implode(',', $values);
}
/**
* Creates a cast of the $fieldName to a text datatype depending on the database management system.
*
......
......@@ -247,6 +247,8 @@ class QueryHelper
*
* Return value should only be used as value list for database query 'IN()' or 'NOTIN()' .
*
* Will be removed in v12, use QueryHelper::quoteArrayBasedValueListToIntegerList() instead.
*
* @param array $values
* @param Connection $connection
* @return string
......@@ -278,6 +280,8 @@ class QueryHelper
*
* Return value should only be used as value list for database query 'IN()' or 'NOTIN()' .
*
* Will be removed in v12, use QueryHelper::quoteArrayBasedValueListToStringList() instead.
*
* @param array $values
* @param Connection $connection
* @return string
......
......@@ -20,7 +20,6 @@ use TYPO3\CMS\Backend\Utility\BackendUtility;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Platform\PlatformInformation;
use TYPO3\CMS\Core\Database\Query\QueryHelper;
use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
use TYPO3\CMS\Core\DataHandling\SoftReference\SoftReferenceParserFactory;
......@@ -176,7 +175,7 @@ class LinkAnalyzer
$statement->where(
$statement->expr()->in(
($table === 'pages' ? 'uid' : 'pid'),
QueryHelper::implodeToIntQuotedValueList($pageIdsChunk, $statement->getConnection())
$statement->quoteArrayBasedValueListToIntegerList($pageIdsChunk)
)
);
$result = $statement->execute();
......
......@@ -222,7 +222,7 @@ class EditableRestriction implements QueryRestrictionInterface
),
$expressionBuilder->in(
'tx_linkvalidator_link.element_type',
QueryHelper::implodeToStringQuotedValueList(array_unique(current($field) ?: []), $this->queryBuilder->getConnection())
$this->queryBuilder->quoteArrayBasedValueListToStringList(array_unique(current($field) ?: []))
)
);
$additionalWhere[] = $expressionBuilder->neq(
......
......@@ -20,7 +20,6 @@ namespace TYPO3\CMS\Linkvalidator\Repository;
use Doctrine\DBAL\Exception\TableNotFoundException;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Database\Platform\PlatformInformation;
use TYPO3\CMS\Core\Database\Query\QueryHelper;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Linkvalidator\QueryRestrictions\EditableRestriction;
......@@ -101,14 +100,14 @@ class BrokenLinkRepository
$queryBuilder->expr()->andX(
$queryBuilder->expr()->in(
'record_uid',
QueryHelper::implodeToIntQuotedValueList($pageIdsChunk, $queryBuilder->getConnection())
$queryBuilder->quoteArrayBasedValueListToIntegerList($pageIdsChunk)
),
$queryBuilder->expr()->eq('table_name', $queryBuilder->quote('pages'))
),
$queryBuilder->expr()->andX(
$queryBuilder->expr()->in(
'record_pid',
QueryHelper::implodeToIntQuotedValueList($pageIdsChunk, $queryBuilder->getConnection())
$queryBuilder->quoteArrayBasedValueListToIntegerList($pageIdsChunk)
),
$queryBuilder->expr()->neq('table_name', $queryBuilder->quote('pages'))
)
......@@ -191,14 +190,14 @@ class BrokenLinkRepository
$queryBuilder->expr()->andX(
$queryBuilder->expr()->in(
'record_uid',
QueryHelper::implodeToIntQuotedValueList($pageIdsChunk, $queryBuilder->getConnection())
$queryBuilder->quoteArrayBasedValueListToIntegerList($pageIdsChunk)
),
$queryBuilder->expr()->eq('table_name', $queryBuilder->quote('pages'))
),
$queryBuilder->expr()->andX(
$queryBuilder->expr()->in(
'record_pid',
QueryHelper::implodeToIntQuotedValueList($pageIdsChunk, $queryBuilder->getConnection())
$queryBuilder->quoteArrayBasedValueListToIntegerList($pageIdsChunk)
),
$queryBuilder->expr()->neq(
'table_name',
......@@ -208,7 +207,7 @@ class BrokenLinkRepository
),
$queryBuilder->expr()->in(
'link_type',
QueryHelper::implodeToStringQuotedValueList($linkTypes, $queryBuilder->getConnection())
$queryBuilder->quoteArrayBasedValueListToStringList($linkTypes)
)
)
->execute();
......@@ -256,28 +255,28 @@ class BrokenLinkRepository
$queryBuilder->expr()->andX(
$queryBuilder->expr()->in(
'record_uid',
QueryHelper::implodeToIntQuotedValueList($pageIdsChunk, $queryBuilder->getConnection())
$queryBuilder->quoteArrayBasedValueListToIntegerList($pageIdsChunk)
),
$queryBuilder->expr()->eq('table_name', $queryBuilder->quote('pages'))
),
$queryBuilder->expr()->andX(
$queryBuilder->expr()->in(
'record_pid',
QueryHelper::implodeToIntQuotedValueList($pageIdsChunk, $queryBuilder->getConnection())
$queryBuilder->quoteArrayBasedValueListToIntegerList($pageIdsChunk)
),
$queryBuilder->expr()->neq('table_name', $queryBuilder->quote('pages'))
)
),
$queryBuilder->expr()->in(
'link_type',
QueryHelper::implodeToStringQuotedValueList($linkTypes, $queryBuilder->getConnection())
$queryBuilder->quoteArrayBasedValueListToStringList($linkTypes)
),
];
if ($languages !== []) {
$constraints[] = $queryBuilder->expr()->in(
'language',
QueryHelper::implodeToIntQuotedValueList($languages, $queryBuilder->getConnection())
$queryBuilder->quoteArrayBasedValueListToIntegerList($languages)
);
}
......
Markdown is supported
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