Project

General

Profile

Feature #55959 ยป PatchedTypo3DbBackend.php

Philipp Wrann, 2014-02-13 14:36

 
<?php

namespace Vendor\Extension\Persistence\Generic\Storage;

/**
* This is needed because TYPO3.Extbase has a few bugs in its Database Driver
*
* @author Philipp Wrann <wrann@pixelpoint.at>
* @package TYPO3.CMS
* @subpackage Vendor.Extension
*/
class PatchedTypo3DbBackend extends \TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbBackend {

/**
* Here we added the possibility of makeing joins on the same table,
* This is important to filter regionalobjects for their location
* as locations as well as regionalobjects are stored in the same table.
*
* @param string &$className
* @param string &$tableName
* @param array &$propertyPath
* @param array &$sql
* @param string &$parent
* @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception
* @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidRelationConfigurationException
* @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\MissingColumnMapException
*/
protected function addUnionStatement(&$className, &$tableName, &$propertyPath, array &$sql, &$parent) {
$explodedPropertyPath = explode('.', $propertyPath, 2);
$propertyName = $explodedPropertyPath[0];
$columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
$tableName = $this->dataMapper->convertClassNameToTableName($className);
$columnMap = $this->dataMapper->getDataMap($className)->getColumnMap($propertyName);

if ($columnMap === NULL) {
throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\MissingColumnMapException('The ColumnMap for property "' . $propertyName . '" of class "' . $className . '" is missing.', 1355142232);
}

// MODIFICATION START
$parentKeyFieldName = $columnMap->getParentKeyFieldName();
$childTableName = $realTableName = $columnMap->getChildTableName();
/**
* @todo find better solution for this
*/
$alias = ($childTableName == $tableName || isset($sql['tables'][$childTableName]) || isset($sql['unions'][$childTableName])) ? $propertyName : $childTableName;
$parent = ($parent=='') ? $tableName : $parent;

if ($childTableName === NULL) {
throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\InvalidRelationConfigurationException('The relation information for property "' . $propertyName . '" of class "' . $className . '" is missing.', 1353170925);
}
if ($columnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_ONE) {
if (isset($parentKeyFieldName)) {
// should work as expected
$sql['unions'][$alias] = 'LEFT JOIN ' . $realTableName . ' AS ' . $alias . ' ON ' . $parent . '.uid=' . $alias . '.' . $parentKeyFieldName;
} else {
// should work as expected
$sql['unions'][$alias] = 'LEFT JOIN ' . $realTableName . ' AS '. $alias . ' ON ' . $parent . '.' . $columnName . '=' . $alias . '.uid';
}
$className = $this->dataMapper->getType($className, $propertyName);
} elseif ($columnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
if (isset($parentKeyFieldName)) {
// should work as expected
$sql['unions'][$alias] = 'LEFT JOIN ' . $realTableName . ' AS ' . $alias . ' ON ' . $parent . '.uid=' . $alias . '.' . $parentKeyFieldName;
} else {
// @todo !!!needs tesing!!!
$onStatement = '(FIND_IN_SET(' . $childTableName . '.uid, ' . $parent . '.' . $columnName . '))';
$sql['unions'][$alias] = 'LEFT JOIN ' . $alias . ' ON ' . $onStatement;
}
$className = $this->dataMapper->getType($className, $propertyName);
} elseif ($columnMap->getTypeOfRelation() === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
$relationTableName = $columnMap->getRelationTableName();
// @todo !!!needs tesing!!!
$sql['unions'][$relationTableName] = 'LEFT JOIN ' . $relationTableName . ' ON ' . $parent . '.uid=' . $relationTableName . '.' . $columnMap->getParentKeyFieldName();
$sql['unions'][$alias] = 'LEFT JOIN ' . $childTableName . ' ON ' . $relationTableName . '.' . $columnMap->getChildKeyFieldName() . '=' . $childTableName . '.uid';
$className = $this->dataMapper->getType($className, $propertyName);
} else {
throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception('Could not determine type of relation.', 1252502725);
}
$parent = $alias;
$sql['keywords']['distinct'] = 'DISTINCT';
$propertyPath = $explodedPropertyPath[1];
$tableName = $alias;
}

/**
* Parse a DynamicOperand into SQL and parameter arrays.
*
* @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\DynamicOperandInterface $operand
* @param string $operator One of the JCR_OPERATOR_* constants
* @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source The source
* @param array &$sql The query parts
* @param array &$parameters The parameters that will replace the markers
* @param string $valueFunction an optional SQL function to apply to the operand value
* @param null $operand2
* @return void
*/
protected function parseDynamicOperand(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\DynamicOperandInterface $operand, $operator, \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source, array &$sql, array &$parameters, $valueFunction = NULL, $operand2 = NULL) {
if ($operand instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\LowerCaseInterface) {
$this->parseDynamicOperand($operand->getOperand(), $operator, $source, $sql, $parameters, 'LOWER');
} elseif ($operand instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\UpperCaseInterface) {
$this->parseDynamicOperand($operand->getOperand(), $operator, $source, $sql, $parameters, 'UPPER');
} elseif ($operand instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\PropertyValueInterface) {
$propertyName = $operand->getPropertyName();
if ($source instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SelectorInterface) {
// FIXME Only necessary to differ from Join
$className = $source->getNodeTypeName();
$tableName = $this->dataMapper->convertClassNameToTableName($className);
$parent = '';
while (strpos($propertyName, '.') !== FALSE) {
$this->addUnionStatement($className, $tableName, $propertyName, $sql, $parent);
}
} elseif ($source instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\JoinInterface) {
$tableName = $source->getJoinCondition()->getSelector1Name();
}
$columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
$operator = $this->resolveOperator($operator);
$constraintSQL = '';
if ($valueFunction === NULL) {
$constraintSQL .= (!empty($tableName) ? $tableName . '.' : '') . $columnName . ' ' . $operator . ' ?';
} else {
$constraintSQL .= $valueFunction . '(' . (!empty($tableName) ? $tableName . '.' : '') . $columnName . ') ' . $operator . ' ?';
}
$sql['where'][] = $constraintSQL;
}
}

/**
* Parse a Comparison into SQL and parameter arrays.
*
* @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface $comparison The comparison to parse
* @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source The source
* @param array &$sql SQL query parts to add to
* @param array &$parameters Parameters to bind to the SQL
* @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\RepositoryException
* @return void
*/
protected function parseComparison(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface $comparison, \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source, array &$sql, array &$parameters) {
$operand1 = $comparison->getOperand1();
$operator = $comparison->getOperator();
$operand2 = $comparison->getOperand2();
if ($operator === \TYPO3\CMS\Extbase\Persistence\QueryInterface::OPERATOR_IN) {
$items = array();
$hasValue = FALSE;
foreach ($operand2 as $value) {
$value = $this->getPlainValue($value);
if ($value !== NULL) {
$items[] = $value;
$hasValue = TRUE;
}
}
if ($hasValue === FALSE) {
$sql['where'][] = '1<>1';
} else {
$this->parseDynamicOperand($operand1, $operator, $source, $sql, $parameters, NULL, $operand2);
$parameters[] = $items;
}
} elseif ($operator === \TYPO3\CMS\Extbase\Persistence\QueryInterface::OPERATOR_CONTAINS) {
if ($operand2 === NULL) {
$sql['where'][] = '1<>1';
} else {
$className = $source->getNodeTypeName();
$tableName = $this->dataMapper->convertClassNameToTableName($className);
$propertyName = $operand1->getPropertyName();
$parent = '';
while (strpos($propertyName, '.') !== FALSE) {
$this->addUnionStatement($className, $tableName, $propertyName, $sql, $parent);
}
$columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
$dataMap = $this->dataMapper->getDataMap($className);
$columnMap = $dataMap->getColumnMap($propertyName);
$typeOfRelation = $columnMap instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap ? $columnMap->getTypeOfRelation() : NULL;
if ($typeOfRelation === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_AND_BELONGS_TO_MANY) {
// $relationTableName = $columnMap->getRelationTableName();
// $sql['where'][] = $tableName . '.uid IN (SELECT ' . $columnMap->getParentKeyFieldName() . ' FROM ' . $relationTableName . ' WHERE ' . $columnMap->getChildKeyFieldName() . '=?)';
// MODIFICATION START
$relationTableName = $columnMap->getRelationTableName();
if($matchFields = $columnMap->getRelationTableMatchFields()) {
$matchFieldsQuery = '';
foreach ($matchFields as $matchField => $matchValue) {
$matchFieldsQuery .= ' '.$matchField.' = \''.$matchValue.'\' AND ';
}
}
$sql['where'][] = $tableName . '.uid IN (SELECT ' . $columnMap->getParentKeyFieldName() . ' FROM ' . $relationTableName . ' WHERE ' . $matchFieldsQuery . $columnMap->getChildKeyFieldName() . '=?)';
$parameters[] = intval($this->getPlainValue($operand2));
// MODIFICATION END
} elseif ($typeOfRelation === \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\ColumnMap::RELATION_HAS_MANY) {
$parentKeyFieldName = $columnMap->getParentKeyFieldName();
if (isset($parentKeyFieldName)) {
$childTableName = $columnMap->getChildTableName();
$sql['where'][] = $tableName . '.uid=(SELECT ' . $childTableName . '.' . $parentKeyFieldName . ' FROM ' . $childTableName . ' WHERE ' . $childTableName . '.uid=?)';
$parameters[] = intval($this->getPlainValue($operand2));
} else {
$sql['where'][] = 'FIND_IN_SET(?,' . $tableName . '.' . $columnName . ')';
$parameters[] = intval($this->getPlainValue($operand2));
}
} else {
throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\RepositoryException('Unsupported or non-existing property name "' . $propertyName . '" used in relation matching.', 1327065745);
}
}
} else {
if ($operand2 === NULL) {
if ($operator === \TYPO3\CMS\Extbase\Persistence\QueryInterface::OPERATOR_EQUAL_TO) {
$operator = self::OPERATOR_EQUAL_TO_NULL;
} elseif ($operator === \TYPO3\CMS\Extbase\Persistence\QueryInterface::OPERATOR_NOT_EQUAL_TO) {
$operator = self::OPERATOR_NOT_EQUAL_TO_NULL;
}
}
$this->parseDynamicOperand($operand1, $operator, $source, $sql, $parameters);
$parameters[] = $this->getPlainValue($operand2);
}
}

/**
* Transforms orderings into SQL.
*
* @param array $orderings An array of orderings (Tx_Extbase_Persistence_QOM_Ordering)
* @param \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source The source
* @param array &$sql The query parts
* @throws \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnsupportedOrderException
* @return void
*/
protected function parseOrderings(array $orderings, \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface $source, array &$sql) {
foreach ($orderings as $propertyName => $order) {
switch ($order) {
case \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelConstantsInterface::JCR_ORDER_ASCENDING:

case \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING:
$order = 'ASC';
break;
case \TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelConstantsInterface::JCR_ORDER_DESCENDING:

case \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING:
$order = 'DESC';
break;
default:
throw new \TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnsupportedOrderException('Unsupported order encountered.', 1242816074);
}
$className = '';
$tableName = '';
/**
* MODIFICATION START
* This way we enable the use of a function inside ordering
* this is mysql specific but its needed for our listMode Priority feature
* This way we can get a ordered list but put some specific objects on top of the list
*/
if (stripos($propertyName,'FIELD(') !== FALSE) {
$parts = explode(',',str_replace(')','',$propertyName));
$property = str_ireplace('FIELD(','',array_shift($parts));
$values = array_filter(array_filter($parts,function($value){
/**
* Just to make sure nothing is injected here
* maybe simply use an escape function on values
*/
return (preg_match("/[^\w]/",$value)==0);
}));
$className = $source->getNodeTypeName();
$tableName = $this->dataMapper->convertClassNameToTableName($className);
$columnName = $this->dataMapper->convertPropertyNameToColumnName($property, $className);
$fieldString = 'FIELD('.$tableName.'.'.$columnName.','.implode(',',$values).')';
$sql['orderings'][] = $fieldString. ' ' . $order;
} else {
/**
* MODIFICATION END
*/
if ($source instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\SelectorInterface) {
$className = $source->getNodeTypeName();
$tableName = $this->dataMapper->convertClassNameToTableName($className);
$parent = '';
while (strpos($propertyName, '.') !== FALSE) {
$this->addUnionStatement($className, $tableName, $propertyName, $sql, $parent);
}
} elseif ($source instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Qom\JoinInterface) {
$tableName = $source->getLeft()->getSelectorName();
}
$columnName = $this->dataMapper->convertPropertyNameToColumnName($propertyName, $className);
if (strlen($tableName) > 0) {
$sql['orderings'][] = $tableName . '.' . $columnName . ' ' . $order;
} else {
$sql['orderings'][] = $columnName . ' ' . $order;
}
}
}
}
}

?>
    (1-1/1)