I got a problem with many TYPO3 extensions with ordering query results by Uid´s which come from a flexform Plugin setting in the Backend. I try to create a query what gives me the result uid´s in the same order like the flexform is from the Plugin setting. Like i choose data.uid 5 7 and 3 and my query results give me those in this order.
For Example:
Siteinfo:
This Function is called from the Controller.
$partners = $this->partnerRepository->findByUids($this->settings['showMainSponsor']);
in $this->settings['showMainSponsor'] is the value ="3, 4 ,1".
These are the Uid´s from the selected area in the TYPO3 Plugin Settings.
The repository function " findByUids " looks like this.
public function findByUids($uids){
if(!isset($uids) || empty($uids)){
return NULL;
}
$uidListString = $uids;
if(!is_array($uids)){
$uidListString = explode(',', $uids);
}
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(FALSE);
//here i set the orderings
$orderings = $this->orderByField('uid', $uidListString);
$query->setOrderings($orderings);
$query->matching(
$query->logicalAnd(
$query->in('uid', $uidListString)
)
);
return $query->execute();
}
A function called " orderByField " is called here which sets all the orderings.
/**
* @param string $field
* @param array $values
*
* @return array
*/
protected function orderByField($field, $values) {
$orderings = array();
foreach ($values as $value) {
$orderings["$field={$value}"] = \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING;
}
return $orderings;
}
These method of ordering the queryresult by the given uid list from the Flexform works in TYPO3 6.2 and 7.6 . Now i tried to attach this extension to a TYPO3 8.6 project but this method doesnt work anymore. I tried to debug it and looked up in the query. There i found what broke this query. The query which doesnt work looks like this:
SELECT `tx_partner_domain_model_partner`.* FROM `tx_partner_domain_model_partner` `tx_partner_domain_model_partner` WHERE (`tx_partner_domain_model_partner`.`uid` IN (3, 4, 1)) AND (`tx_partner_domain_model_partner`.`sys_language_uid` IN (0, -1)) AND ((`tx_partner_domain_model_partner`.`deleted` = 0) AND (`tx_partner_domain_model_partner`.`t3ver_state` <= 0) AND (`tx_partner_domain_model_partner`.`pid` <> -1) AND (`tx_partner_domain_model_partner`.`hidden` = 0) AND (`tx_partner_domain_model_partner`.`starttime` <= 1506603780) AND ((`tx_partner_domain_model_partner`.`endtime` = 0) OR (`tx_partner_domain_model_partner`.`endtime` > 1506603780))) ORDER BY `tx_partner_domain_model_partner`.`uid=3` DESC, `tx_partner_domain_model_partner`.`uid=4` DESC, `tx_partner_domain_model_partner`.`uid=1` DESC
I tried this on my DBMS and it failed. The reason are the last 3 statements.
`tx_partner_domain_model_partner`.`uid=3` DESC, `tx_partner_domain_model_partner`.`uid=4` DESC, `tx_partner_domain_model_partner`.`uid=1` DESC
TYPO3 escaped the uid with `` like
`tx_partner_domain_model_partner`.`uid=4` DESC
if we do the call like this without these `` arround the uid=3 ..
`tx_partner_domain_model_partner`.uid=3 DESC, `tx_partner_domain_model_partner`.uid=4 DESC, `tx_brapartner_domain_model_partner`.uid=1 DESC
it works fine. Maybe there is a security reason why TYPO3 does this on his newest version but i dont find any other good solution for this basic case. At the moment i got a foreach where i query every uid by his own by findByUid but this dont seem to me like a "best practice" way. Does anybody got a cleaner way for this case of getting data from the db? Or maybe this is a Bug ?
I Hope someone can help me.
best regards
Fanor
Without Overwrite:
Clear default Orderings:
$query->setOrderings(array());
Transform your QueryBuilder to the new Doctrine QB:
/** @var Typo3DbQueryParser $queryParser */
$queryParser = $this->objectManager->get(Typo3DbQueryParser::class);
/** @var QueryBuilder $doctrineQueryBuilder */
$doctrineQueryBuilder = $queryParser->convertQueryToDoctrineQueryBuilder($query);
Add the UIDs via concreteQb
$concreteQb = $doctrineQueryBuilder->getConcreteQueryBuilder();
foreach ($uidList as $uid) {
$concreteQb->addOrderBy("$key={$uid}", QueryInterface::ORDER_DESCENDING);
}
Get the mapped results:
/** @var DataMapper $dataMapper */
$dataMapper = $this->objectManager->get(DataMapper::class);
return $dataMapper->map(YourDataClass::class, $$doctrineQueryBuilder->execute()->fetchAll());
I think the problem is in \\TYPO3\\CMS\\Core\\Database\\Query\\QueryBuilder::orderBy
where the fieldName gets quoted. You could overwrite this class and split the fieldName by = and build a quoted string and intval() the remaining string like:
public function orderBy(string $fieldName, string $order = null): QueryBuilder
{
if (strpos($fieldName, '=') !== false) {
list($field, $value) = GeneralUtility::trimExplode('=', $fieldName);
$field = $this->connection->quoteIdentifier($field);
$value = intval($value);
$fieldName = $field . $value;
}
else {
$fieldName = $this->connection->quoteIdentifier($fieldName);
}
$this->concreteQueryBuilder->orderBy($fieldName, $order);
return $this;
}
an alternative for the controller:
$ids = explode(',',$this->settings['showMainSponsor'])
foreach($ids as $key => $id){
$partners[$id] = $this->partnerRepository->findByUid($id);
}
It´s the little dirty way, because this logic is on the wrong place, but it works :)
In addition, it´s also not performant, like your solution. Your solution works fine in 7.6 of cause. But i haven´t no idea why its break in version 8. Maybe "Fix of security issue" in this ordering system of TYPO3.
public function findByUidList($uidList)
{
$uids = GeneralUtility::intExplode(',', $uidList, true);
if ($uidList === '' || count($uids) === 0) {
return [];
}
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($this->getTableName());
$queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
$records = $queryBuilder
->select('*')
->from($this->getTableName())
->where($queryBuilder->expr()->in('uid', $uids))
->add('orderBy', 'FIELD('.$this->getTableName().'.uid,' . implode(',', $uids) . ')')
->execute()
->fetchAll();
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$dataMapper = $objectManager->get(DataMapper::class);
$result = $dataMapper->map($this->objectType, $records);
return $result;
}
/**
* Return the current table name
*
* @return string
*/
protected function getTableName()
{
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$dataMapper = $objectManager->get(DataMapper::class);
$tableName = $dataMapper->getDataMap($this->objectType)->getTableName();
return $tableName;
}
This works great with TYPO3 v10.4! :)
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.