[BUGFIX] Quote table and fieldnames in BackendUtility::BEenableFields
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Tests / Unit / Persistence / Generic / Storage / Typo3DbQueryParserTest.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Tests\Unit\Persistence\Generic\Storage;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Prophecy\Argument;
18 use TYPO3\CMS\Core\Database\Connection;
19 use TYPO3\CMS\Core\Database\ConnectionPool;
20 use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
21 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
22 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
23 use TYPO3\CMS\Core\Utility\GeneralUtility;
24 use TYPO3\CMS\Extbase\Persistence\Generic\Exception\InconsistentQuerySettingsException;
25 use TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnsupportedOrderException;
26 use TYPO3\CMS\Extbase\Persistence\Generic\Qom\AndInterface;
27 use TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface;
28 use TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface;
29 use TYPO3\CMS\Extbase\Persistence\Generic\Qom\NotInterface;
30 use TYPO3\CMS\Extbase\Persistence\Generic\Qom\OrInterface;
31 use TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface;
32 use TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser;
33 use TYPO3\CMS\Extbase\Persistence\QueryInterface;
34
35 class Typo3DbQueryParserTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
36 {
37 /**
38 * @var arary
39 */
40 protected $singletonInstances;
41
42 /**
43 * Set up tests
44 */
45 protected function setUp()
46 {
47 parent::setUp();
48 $this->singletonInstances = GeneralUtility::getSingletonInstances();
49 }
50
51 /**
52 * Clean up after tests
53 */
54 protected function tearDown()
55 {
56 GeneralUtility::purgeInstances();
57 GeneralUtility::resetSingletonInstances($this->singletonInstances);
58 parent::tearDown();
59 }
60
61 /**
62 * @test
63 */
64 public function convertQueryToDoctrineQueryBuilderDoesNotAddAndWhereWithEmptyConstraint()
65 {
66 // Prepare subject, turn off initialize qb method and inject qb prophecy revelation
67 $subject = $this->getAccessibleMock(
68 Typo3DbQueryParser::class,
69 // Shut down some methods not important for this test
70 ['initializeQueryBuilder', 'parseOrderings', 'addTypo3Constraints']
71 );
72 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
73 $subject->_set('queryBuilder', $queryBuilderProphecy->reveal());
74
75 $queryProphecy = $this->prophesize(QueryInterface::class);
76 $sourceProphecy = $this->prophesize(SourceInterface::class);
77 $queryProphecy->getSource()->willReturn($sourceProphecy->reveal());
78 $queryProphecy->getOrderings()->willReturn([]);
79
80 // Test part: getConstraint returns no constraint object, andWhere() should not be called
81 $queryProphecy->getConstraint()->willReturn(null);
82 $queryBuilderProphecy->andWhere()->shouldNotBeCalled();
83
84 $subject->convertQueryToDoctrineQueryBuilder($queryProphecy->reveal());
85 }
86
87 /**
88 * @test
89 */
90 public function convertQueryToDoctrineQueryBuilderThrowsExceptionOnNotImplementedConstraint()
91 {
92 // Prepare subject, turn off initialize qb method and inject qb prophecy revelation
93 $subject = $this->getAccessibleMock(
94 Typo3DbQueryParser::class,
95 // Shut down some methods not important for this test
96 ['initializeQueryBuilder', 'parseOrderings', 'addTypo3Constraints', 'parseComparison']
97 );
98 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
99 $subject->_set('queryBuilder', $queryBuilderProphecy->reveal());
100
101 $queryProphecy = $this->prophesize(QueryInterface::class);
102 $sourceProphecy = $this->prophesize(SourceInterface::class);
103 $queryProphecy->getSource()->willReturn($sourceProphecy->reveal());
104 $queryProphecy->getOrderings()->willReturn([]);
105
106 // Test part: getConstraint returns not implemented object
107 $constraintProphecy = $this->prophesize(ConstraintInterface::class);
108 $queryProphecy->getConstraint()->willReturn($constraintProphecy->reveal());
109
110 $this->expectException(\RuntimeException::class);
111 $this->expectExceptionCode(1476199898);
112 $subject->convertQueryToDoctrineQueryBuilder($queryProphecy->reveal());
113 }
114
115 /**
116 * @test
117 */
118 public function convertQueryToDoctrineQueryBuilderAddsSimpleAndWhere()
119 {
120 // Prepare subject, turn off initialize qb method and inject qb prophecy revelation
121 $subject = $this->getAccessibleMock(
122 Typo3DbQueryParser::class,
123 // Shut down some methods not important for this test
124 ['initializeQueryBuilder', 'parseOrderings', 'addTypo3Constraints', 'parseComparison']
125 );
126 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
127 $subject->_set('queryBuilder', $queryBuilderProphecy->reveal());
128
129 $queryProphecy = $this->prophesize(QueryInterface::class);
130 $sourceProphecy = $this->prophesize(SourceInterface::class);
131 $queryProphecy->getSource()->willReturn($sourceProphecy->reveal());
132 $queryProphecy->getOrderings()->willReturn([]);
133
134 // Test part: getConstraint returns simple constraint, and should push to andWhere()
135 $constraintProphecy = $this->prophesize(ComparisonInterface::class);
136 $queryProphecy->getConstraint()->willReturn($constraintProphecy->reveal());
137 $subject->expects($this->once())->method('parseComparison')->willReturn('heinz');
138 $queryBuilderProphecy->andWhere('heinz')->shouldBeCalled();
139
140 $subject->convertQueryToDoctrineQueryBuilder($queryProphecy->reveal());
141 }
142
143 /**
144 * @test
145 */
146 public function convertQueryToDoctrineQueryBuilderAddsNotConstraint()
147 {
148 // Prepare subject, turn off initialize qb method and inject qb prophecy revelation
149 $subject = $this->getAccessibleMock(
150 Typo3DbQueryParser::class,
151 // Shut down some methods not important for this test
152 ['initializeQueryBuilder', 'parseOrderings', 'addTypo3Constraints', 'parseComparison']
153 );
154 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
155 $subject->_set('queryBuilder', $queryBuilderProphecy->reveal());
156
157 $queryProphecy = $this->prophesize(QueryInterface::class);
158 $sourceProphecy = $this->prophesize(SourceInterface::class);
159 $queryProphecy->getSource()->willReturn($sourceProphecy->reveal());
160 $queryProphecy->getOrderings()->willReturn([]);
161
162 $constraintProphecy = $this->prophesize(NotInterface::class);
163 $subConstraintProphecy = $this->prophesize(ComparisonInterface::class);
164 $constraintProphecy->getConstraint()->shouldBeCalled()->willReturn($subConstraintProphecy->reveal());
165 $queryProphecy->getConstraint()->willReturn($constraintProphecy->reveal());
166 $subject->expects($this->once())->method('parseComparison')->willReturn('heinz');
167 $queryBuilderProphecy->andWhere(' NOT(heinz)')->shouldBeCalled();
168
169 $subject->convertQueryToDoctrineQueryBuilder($queryProphecy->reveal());
170 }
171
172 /**
173 * @test
174 */
175 public function convertQueryToDoctrineQueryBuilderAddsAndConstraint()
176 {
177 // Prepare subject, turn off initialize qb method and inject qb prophecy revelation
178 $subject = $this->getAccessibleMock(
179 Typo3DbQueryParser::class,
180 // Shut down some methods not important for this test
181 ['initializeQueryBuilder', 'parseOrderings', 'addTypo3Constraints', 'parseComparison']
182 );
183 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
184 $subject->_set('queryBuilder', $queryBuilderProphecy->reveal());
185
186 $queryProphecy = $this->prophesize(QueryInterface::class);
187 $sourceProphecy = $this->prophesize(SourceInterface::class);
188 $queryProphecy->getSource()->willReturn($sourceProphecy->reveal());
189 $queryProphecy->getOrderings()->willReturn([]);
190
191 $constraintProphecy = $this->prophesize(AndInterface::class);
192 $queryProphecy->getConstraint()->willReturn($constraintProphecy->reveal());
193 $constraint1Prophecy = $this->prophesize(ComparisonInterface::class);
194 $constraintProphecy->getConstraint1()->willReturn($constraint1Prophecy->reveal());
195 $constraint2Prophecy = $this->prophesize(ComparisonInterface::class);
196 $constraintProphecy->getConstraint2()->willReturn($constraint2Prophecy->reveal());
197 $subject->expects($this->any())->method('parseComparison')->willReturn('heinz');
198 $expressionProphecy = $this->prophesize(ExpressionBuilder::class);
199 $queryBuilderProphecy->expr()->shouldBeCalled()->willReturn($expressionProphecy->reveal());
200 $compositeExpressionProphecy = $this->prophesize(CompositeExpression::class);
201 $compositeExpressionProphecy->__toString()->willReturn('heinz AND heinz');
202 $compositeExpressionRevelation = $compositeExpressionProphecy->reveal();
203 $expressionProphecy->andX('heinz', 'heinz')->shouldBeCalled()->willReturn($compositeExpressionRevelation);
204 $queryBuilderProphecy->andWhere($compositeExpressionRevelation)->shouldBeCalled();
205
206 $subject->convertQueryToDoctrineQueryBuilder($queryProphecy->reveal());
207 }
208
209 /**
210 * @test
211 */
212 public function convertQueryToDoctrineQueryBuilderNotAddsInvalidAndConstraint()
213 {
214 // Prepare subject, turn off initialize qb method and inject qb prophecy revelation
215 $subject = $this->getAccessibleMock(
216 Typo3DbQueryParser::class,
217 // Shut down some methods not important for this test
218 ['initializeQueryBuilder', 'parseOrderings', 'addTypo3Constraints', 'parseComparison']
219 );
220 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
221 $subject->_set('queryBuilder', $queryBuilderProphecy->reveal());
222
223 $queryProphecy = $this->prophesize(QueryInterface::class);
224 $sourceProphecy = $this->prophesize(SourceInterface::class);
225 $queryProphecy->getSource()->willReturn($sourceProphecy->reveal());
226 $queryProphecy->getOrderings()->willReturn([]);
227
228 $constraintProphecy = $this->prophesize(AndInterface::class);
229 $queryProphecy->getConstraint()->willReturn($constraintProphecy->reveal());
230 $constraint1Prophecy = $this->prophesize(ComparisonInterface::class);
231 $constraintProphecy->getConstraint1()->willReturn($constraint1Prophecy->reveal());
232 // no result for constraint2
233 $constraintProphecy->getConstraint2()->willReturn(null);
234
235 // not be called
236 $queryBuilderProphecy->andWhere()->shouldNotBeCalled();
237
238 $subject->convertQueryToDoctrineQueryBuilder($queryProphecy->reveal());
239 }
240
241 /**
242 * @test
243 */
244 public function convertQueryToDoctrineQueryBuilderAddsOrConstraint()
245 {
246 // Prepare subject, turn off initialize qb method and inject qb prophecy revelation
247 $subject = $this->getAccessibleMock(
248 Typo3DbQueryParser::class,
249 // Shut down some methods not important for this test
250 ['initializeQueryBuilder', 'parseOrderings', 'addTypo3Constraints', 'parseComparison']
251 );
252 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
253 $subject->_set('queryBuilder', $queryBuilderProphecy->reveal());
254
255 $queryProphecy = $this->prophesize(QueryInterface::class);
256 $sourceProphecy = $this->prophesize(SourceInterface::class);
257 $queryProphecy->getSource()->willReturn($sourceProphecy->reveal());
258 $queryProphecy->getOrderings()->willReturn([]);
259
260 $constraintProphecy = $this->prophesize(OrInterface::class);
261 $queryProphecy->getConstraint()->willReturn($constraintProphecy->reveal());
262 $constraint1Prophecy = $this->prophesize(ComparisonInterface::class);
263 $constraintProphecy->getConstraint1()->willReturn($constraint1Prophecy->reveal());
264 $constraint2Prophecy = $this->prophesize(ComparisonInterface::class);
265 $constraintProphecy->getConstraint2()->willReturn($constraint2Prophecy->reveal());
266 $subject->expects($this->any())->method('parseComparison')->willReturn('heinz');
267 $expressionProphecy = $this->prophesize(ExpressionBuilder::class);
268 $queryBuilderProphecy->expr()->shouldBeCalled()->willReturn($expressionProphecy->reveal());
269 $compositeExpressionProphecy = $this->prophesize(CompositeExpression::class);
270 $compositeExpressionProphecy->__toString()->willReturn('heinz OR heinz');
271 $compositeExpressionRevelation = $compositeExpressionProphecy->reveal();
272 $expressionProphecy->orX('heinz', 'heinz')->shouldBeCalled()->willReturn($compositeExpressionRevelation);
273 $queryBuilderProphecy->andWhere($compositeExpressionRevelation)->shouldBeCalled();
274
275 $subject->convertQueryToDoctrineQueryBuilder($queryProphecy->reveal());
276 }
277
278 /**
279 * @test
280 */
281 public function convertQueryToDoctrineQueryBuilderNotAddsInvalidOrConstraint()
282 {
283 // Prepare subject, turn off initialize qb method and inject qb prophecy revelation
284 $subject = $this->getAccessibleMock(
285 Typo3DbQueryParser::class,
286 // Shut down some methods not important for this test
287 ['initializeQueryBuilder', 'parseOrderings', 'addTypo3Constraints', 'parseComparison']
288 );
289 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
290 $subject->_set('queryBuilder', $queryBuilderProphecy->reveal());
291
292 $queryProphecy = $this->prophesize(QueryInterface::class);
293 $sourceProphecy = $this->prophesize(SourceInterface::class);
294 $queryProphecy->getSource()->willReturn($sourceProphecy->reveal());
295 $queryProphecy->getOrderings()->willReturn([]);
296
297 $constraintProphecy = $this->prophesize(OrInterface::class);
298 $queryProphecy->getConstraint()->willReturn($constraintProphecy->reveal());
299 $constraint1Prophecy = $this->prophesize(ComparisonInterface::class);
300 $constraintProphecy->getConstraint1()->willReturn($constraint1Prophecy->reveal());
301 // no result for constraint2
302 $constraintProphecy->getConstraint2()->willReturn(null);
303
304 // not be called
305 $queryBuilderProphecy->andWhere()->shouldNotBeCalled();
306
307 $subject->convertQueryToDoctrineQueryBuilder($queryProphecy->reveal());
308 }
309
310 /**
311 * @return \Prophecy\Prophecy\ObjectProphecy
312 */
313 protected function getQueryBuilderWithExpressionBuilderProphet()
314 {
315 $connectionProphet = $this->prophesize(Connection::class);
316 $connectionProphet->quoteIdentifier(Argument::cetera())->willReturnArgument(0);
317 $querBuilderProphet = $this->prophesize(QueryBuilder::class, $connectionProphet->reveal());
318 $expr = GeneralUtility::makeInstance(ExpressionBuilder::class, $connectionProphet->reveal());
319 $querBuilderProphet->expr()->willReturn($expr);
320 return $querBuilderProphet;
321 }
322
323 /**
324 * @return \Prophecy\Prophecy\ObjectProphecy
325 */
326 protected function getQueryBuilderProphetWithQueryBuilderForSubselect()
327 {
328 $connectionProphet = $this->prophesize(Connection::class);
329 $connectionProphet->quoteIdentifier(Argument::cetera())->willReturnArgument(0);
330 $queryBuilderProphet = $this->prophesize(QueryBuilder::class, $connectionProphet->reveal());
331 $expr = GeneralUtility::makeInstance(ExpressionBuilder::class, $connectionProphet->reveal());
332 $queryBuilderProphet->expr()->willReturn(
333 $expr
334 );
335 $queryBuilderProphet->getConnection()->willReturn($connectionProphet->reveal());
336 $queryBuilderForSubselectMock = $this->getMockBuilder(QueryBuilder::class)
337 ->setMethods(['expr', 'unquoteSingleIdentifier'])
338 ->setConstructorArgs([$connectionProphet->reveal()])
339 ->getMock();
340 $connectionProphet->createQueryBuilder()->willReturn($queryBuilderForSubselectMock);
341 $queryBuilderForSubselectMock->expects($this->any())->method('expr')->will($this->returnValue($expr));
342 $queryBuilderForSubselectMock->expects($this->any())->method('unquoteSingleIdentifier')->will($this->returnCallback(function ($identifier) {
343 return $identifier;
344 }));
345 return $queryBuilderProphet;
346 }
347
348 /**
349 * @test
350 */
351 public function addSysLanguageStatementWorksForDefaultLanguage()
352 {
353 $table = $this->getUniqueId('tx_coretest_table');
354 $GLOBALS['TCA'][$table]['ctrl'] = [
355 'languageField' => 'sys_language_uid'
356 ];
357 /** @var \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings|\PHPUnit_Framework_MockObject_MockObject $querySettings */
358 $querySettings = $this->createMock(\TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings::class);
359 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
360 $queryBuilderProphet = $this->getQueryBuilderWithExpressionBuilderProphet();
361 $mockTypo3DbQueryParser->_set('queryBuilder', $queryBuilderProphet->reveal());
362 $sql = $mockTypo3DbQueryParser->_callRef('getSysLanguageStatement', $table, $table, $querySettings);
363 $expectedSql = $table . '.sys_language_uid IN (0, -1)';
364 $this->assertSame($expectedSql, $sql);
365 }
366
367 /**
368 * @test
369 */
370 public function addSysLanguageStatementWorksForNonDefaultLanguage()
371 {
372 $table = $this->getUniqueId('tx_coretest_table');
373 $GLOBALS['TCA'][$table]['ctrl'] = [
374 'languageField' => 'sys_language_uid'
375 ];
376 /** @var \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings|\PHPUnit_Framework_MockObject_MockObject $querySettings */
377 $querySettings = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings::class)
378 ->setMethods(['dummy'])
379 ->getMock();
380 $querySettings->setLanguageUid('1');
381 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
382 $queryBuilderProphet = $this->getQueryBuilderWithExpressionBuilderProphet();
383 $mockTypo3DbQueryParser->_set('queryBuilder', $queryBuilderProphet->reveal());
384 $sql = $mockTypo3DbQueryParser->_callRef('getSysLanguageStatement', $table, $table, $querySettings);
385 $result = $table . '.sys_language_uid IN (1, -1)';
386 $this->assertSame($result, $sql);
387 }
388
389 /**
390 * @test
391 */
392 public function addSysLanguageStatementWorksInBackendContextWithNoGlobalTypoScriptFrontendControllerAvailable()
393 {
394 $table = $this->getUniqueId('tx_coretest_table');
395 $GLOBALS['TCA'][$table]['ctrl'] = [
396 'languageField' => 'sys_language_uid'
397 ];
398 $querySettings = new \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings();
399 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
400 $queryBuilderProphet = $this->getQueryBuilderWithExpressionBuilderProphet();
401 $mockTypo3DbQueryParser->_set('queryBuilder', $queryBuilderProphet->reveal());
402 $sql = $mockTypo3DbQueryParser->_callRef('getSysLanguageStatement', $table, $table, $querySettings);
403 $expectedSql = $table . '.sys_language_uid IN (0, -1)';
404 $this->assertSame($expectedSql, $sql);
405 }
406
407 /**
408 * @test
409 */
410 public function addSysLanguageStatementWorksForDefaultLanguageWithoutDeleteStatementReturned()
411 {
412 $table = $this->getUniqueId('tx_coretest_table');
413 $GLOBALS['TCA'][$table]['ctrl'] = [
414 'languageField' => 'sys_language_uid',
415 'delete' => 'deleted'
416 ];
417 $querySettings = new \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings();
418 $querySettings->setLanguageUid(0);
419 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
420 $queryBuilderProphet = $this->getQueryBuilderWithExpressionBuilderProphet();
421 $mockTypo3DbQueryParser->_set('queryBuilder', $queryBuilderProphet->reveal());
422 $sql = $mockTypo3DbQueryParser->_callRef('getSysLanguageStatement', $table, $table, $querySettings);
423 $expectedSql = $table . '.sys_language_uid IN (0, -1)';
424 $this->assertSame($expectedSql, $sql);
425 }
426
427 /**
428 * @test
429 */
430 public function addSysLanguageStatementWorksForForeignLanguageWithoutSubselection()
431 {
432 $table = $this->getUniqueId('tx_coretest_table');
433 $GLOBALS['TCA'][$table]['ctrl'] = [
434 'languageField' => 'sys_language_uid'
435 ];
436 $querySettings = new \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings();
437 $querySettings->setLanguageUid(2);
438 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
439 $queryBuilderProphet = $this->getQueryBuilderWithExpressionBuilderProphet();
440 $mockTypo3DbQueryParser->_set('queryBuilder', $queryBuilderProphet->reveal());
441 $sql = $mockTypo3DbQueryParser->_callRef('getSysLanguageStatement', $table, $table, $querySettings);
442 $expectedSql = $table . '.sys_language_uid IN (2, -1)';
443 $this->assertSame($expectedSql, $sql);
444 }
445
446 /**
447 * @test
448 */
449 public function addSysLanguageStatementWorksForForeignLanguageWithSubselectionWithoutDeleteStatementReturned()
450 {
451 $table = $this->getUniqueId('tx_coretest_table');
452 $GLOBALS['TCA'][$table]['ctrl'] = [
453 'languageField' => 'sys_language_uid',
454 'transOrigPointerField' => 'l10n_parent'
455 ];
456 $querySettings = new \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings();
457 $querySettings->setLanguageUid(2);
458 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
459
460 $queryBuilderProphet = $this->getQueryBuilderProphetWithQueryBuilderForSubselect();
461
462 $mockTypo3DbQueryParser->_set('queryBuilder', $queryBuilderProphet->reveal());
463
464 $compositeExpression = $mockTypo3DbQueryParser->_callRef('getSysLanguageStatement', $table, $table, $querySettings);
465 $expectedSql = '(' . $table . '.sys_language_uid IN (2, -1)) OR ((' . $table . '.sys_language_uid = 0) AND (' . $table . '.uid NOT IN (SELECT ' . $table . '.l10n_parent FROM ' . $table . ' WHERE (' . $table . '.l10n_parent > 0) AND (' . $table . '.sys_language_uid = 2))))';
466 $this->assertSame($expectedSql, $compositeExpression->__toString());
467 }
468
469 /**
470 * @test
471 */
472 public function addSysLanguageStatementWorksForForeignLanguageWithSubselectionTakesDeleteStatementIntoAccountIfNecessary()
473 {
474 $table = $this->getUniqueId('tx_coretest_table');
475 $GLOBALS['TCA'][$table]['ctrl'] = [
476 'languageField' => 'sys_language_uid',
477 'transOrigPointerField' => 'l10n_parent',
478 'delete' => 'deleted'
479 ];
480 $querySettings = new \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings();
481 $querySettings->setLanguageUid(2);
482 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
483 $queryBuilderProphet = $this->getQueryBuilderProphetWithQueryBuilderForSubselect();
484 $mockTypo3DbQueryParser->_set('queryBuilder', $queryBuilderProphet->reveal());
485 $compositeExpression= $mockTypo3DbQueryParser->_callRef('getSysLanguageStatement', $table, $table, $querySettings);
486 $expectedSql = '(' . $table . '.sys_language_uid IN (2, -1))' .
487 ' OR ((' . $table . '.sys_language_uid = 0) AND (' . $table . '.uid NOT IN (' .
488 'SELECT ' . $table . '.l10n_parent FROM ' . $table .
489 ' WHERE (' . $table . '.l10n_parent > 0) AND (' .
490 $table . '.sys_language_uid = 2) AND (' .
491 $table . '.deleted = 0))))';
492 $this->assertSame($expectedSql, $compositeExpression->__toString());
493 }
494
495 /**
496 * @test
497 */
498 public function addSysLanguageStatementWorksInBackendContextWithSubselectionTakesDeleteStatementIntoAccountIfNecessary()
499 {
500 $table = 'tt_content';
501 $GLOBALS['TCA'][$table]['ctrl'] = [
502 'languageField' => 'sys_language_uid',
503 'transOrigPointerField' => 'l10n_parent',
504 'delete' => 'deleted'
505 ];
506 $querySettings = new \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings();
507 $querySettings->setLanguageUid(2);
508 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
509
510 $queryBuilderProphet = $this->getQueryBuilderProphetWithQueryBuilderForSubselect();
511
512 $mockTypo3DbQueryParser->_set('queryBuilder', $queryBuilderProphet->reveal());
513 $compositeExpression = $mockTypo3DbQueryParser->_callRef('getSysLanguageStatement', $table, $table, $querySettings);
514 $expectedSql = '(' . $table . '.sys_language_uid IN (2, -1))' .
515 ' OR ((' . $table . '.sys_language_uid = 0) AND (' . $table . '.uid NOT IN (' .
516 'SELECT ' . $table . '.l10n_parent FROM ' . $table .
517 ' WHERE (' . $table . '.l10n_parent > 0) AND (' .
518 $table . '.sys_language_uid = 2) AND (' .
519 $table . '.deleted = 0))))';
520 $this->assertSame($expectedSql, $compositeExpression->__toString());
521 }
522
523 /**
524 * @test
525 */
526 public function orderStatementGenerationWorks()
527 {
528 $mockSource = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\Selector::class)
529 ->setMethods(['getNodeTypeName'])
530 ->disableOriginalConstructor()
531 ->getMock();
532 $mockSource->expects($this->any())->method('getNodeTypeName')->will($this->returnValue('foo'));
533 $mockDataMapper = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class)
534 ->setMethods(['convertPropertyNameToColumnName', 'convertClassNameToTableName'])
535 ->disableOriginalConstructor()
536 ->getMock();
537 $mockDataMapper->expects($this->once())->method('convertClassNameToTableName')->with('foo')->will($this->returnValue('tx_myext_tablename'));
538 $mockDataMapper->expects($this->once())->method('convertPropertyNameToColumnName')->with('fooProperty', 'foo')->will($this->returnValue('converted_fieldname'));
539 $queryBuilderProphet = $this->prophesize(QueryBuilder::class);
540 $queryBuilderProphet->addOrderBy('tx_myext_tablename.converted_fieldname', 'ASC')->shouldBeCalledTimes(1);
541
542 $orderings = ['fooProperty' => QueryInterface::ORDER_ASCENDING];
543 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
544 $mockTypo3DbQueryParser->_set('dataMapper', $mockDataMapper);
545 $mockTypo3DbQueryParser->_set('queryBuilder', $queryBuilderProphet->reveal());
546 $mockTypo3DbQueryParser->_callRef('parseOrderings', $orderings, $mockSource);
547 }
548
549 /**
550 * @test
551 */
552 public function orderStatementGenerationThrowsExceptionOnUnsupportedOrder()
553 {
554 $this->expectException(UnsupportedOrderException::class);
555 $this->expectExceptionCode(1242816074);
556 $mockSource = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\Selector::class)
557 ->setMethods(['getNodeTypeName'])
558 ->disableOriginalConstructor()
559 ->getMock();
560 $mockSource->expects($this->never())->method('getNodeTypeName');
561 $mockDataMapper = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class)
562 ->setMethods(['convertPropertyNameToColumnName', 'convertClassNameToTableName'])
563 ->disableOriginalConstructor()
564 ->getMock();
565 $mockDataMapper->expects($this->never())->method('convertClassNameToTableName');
566 $mockDataMapper->expects($this->never())->method('convertPropertyNameToColumnName');
567 $orderings = ['fooProperty' => 'unsupported_order'];
568 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
569 $mockTypo3DbQueryParser->_set('dataMapper', $mockDataMapper);
570
571 $mockTypo3DbQueryParser->_callRef('parseOrderings', $orderings, $mockSource);
572 }
573
574 /**
575 * @test
576 */
577 public function orderStatementGenerationWorksWithMultipleOrderings()
578 {
579 $mockSource = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\Selector::class)
580 ->setMethods(['getNodeTypeName'])
581 ->disableOriginalConstructor()
582 ->getMock();
583 $mockSource->expects($this->any())->method('getNodeTypeName')->will($this->returnValue('Tx_MyExt_ClassName'));
584 $mockDataMapper = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class)
585 ->setMethods(['convertPropertyNameToColumnName', 'convertClassNameToTableName'])
586 ->disableOriginalConstructor()
587 ->getMock();
588 $mockDataMapper->expects($this->any())->method('convertClassNameToTableName')->with('Tx_MyExt_ClassName')->will($this->returnValue('tx_myext_tablename'));
589 $mockDataMapper->expects($this->any())->method('convertPropertyNameToColumnName')->will($this->returnValue('converted_fieldname'));
590 $orderings = [
591 'fooProperty' => QueryInterface::ORDER_ASCENDING,
592 'barProperty' => QueryInterface::ORDER_DESCENDING
593 ];
594 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
595 $mockTypo3DbQueryParser->_set('dataMapper', $mockDataMapper);
596
597 $queryBuilder = $this->getMockBuilder(QueryBuilder::class)
598 ->disableOriginalConstructor()
599 ->setMethods(['addOrderBy'])
600 ->getMock();
601 $queryBuilder->expects($this->at(0))->method('addOrderBy')->with('tx_myext_tablename.converted_fieldname', 'ASC');
602 $queryBuilder->expects($this->at(1))->method('addOrderBy')->with('tx_myext_tablename.converted_fieldname', 'DESC');
603
604 $mockTypo3DbQueryParser->_set('queryBuilder', $queryBuilder);
605 $mockTypo3DbQueryParser->_callRef('parseOrderings', $orderings, $mockSource);
606 }
607
608 public function providerForVisibilityConstraintStatement()
609 {
610 return [
611 'in be: include all' => ['BE', true, [], true, ''],
612 'in be: ignore enable fields but do not include deleted' => ['BE', true, [], false, 'tx_foo_table.deleted_column = 0'],
613 'in be: respect enable fields but include deleted' => ['BE', false, [], true, '(tx_foo_table.disabled_column = 0) AND (tx_foo_table.starttime_column <= 123456789)'],
614 'in be: respect enable fields and do not include deleted' => ['BE', false, [], false, '(tx_foo_table.disabled_column = 0) AND (tx_foo_table.starttime_column <= 123456789) AND tx_foo_table.deleted_column = 0'],
615 'in fe: include all' => ['FE', true, [], true, ''],
616 'in fe: ignore enable fields but do not include deleted' => ['FE', true, [], false, 'tx_foo_table.deleted_column=0'],
617 'in fe: ignore only starttime and do not include deleted' => ['FE', true, ['starttime'], false, '(tx_foo_table.deleted_column = 0) AND (tx_foo_table.disabled_column = 0)'],
618 'in fe: respect enable fields and do not include deleted' => ['FE', false, [], false, '(tx_foo_table.deleted_column = 0) AND (tx_foo_table.disabled_column = 0) AND (tx_foo_table.starttime_column <= 123456789)']
619 ];
620 }
621
622 /**
623 * @test
624 * @dataProvider providerForVisibilityConstraintStatement
625 */
626 public function visibilityConstraintStatementIsGeneratedAccordingToTheQuerySettings($mode, $ignoreEnableFields, $enableFieldsToBeIgnored, $deletedValue, $expectedSql)
627 {
628 $tableName = 'tx_foo_table';
629 $GLOBALS['TCA'][$tableName]['ctrl'] = [
630 'enablecolumns' => [
631 'disabled' => 'disabled_column',
632 'starttime' => 'starttime_column'
633 ],
634 'delete' => 'deleted_column'
635 ];
636 $GLOBALS['TSFE'] = new \stdClass();
637 $GLOBALS['TSFE']->sys_page = new \TYPO3\CMS\Frontend\Page\PageRepository();
638 $GLOBALS['SIM_ACCESS_TIME'] = 123456789;
639
640 $connectionProphet = $this->prophesize(Connection::class);
641 $connectionProphet->quoteIdentifier(Argument::cetera())->willReturnArgument(0);
642 $connectionProphet->getExpressionBuilder()->willReturn(
643 GeneralUtility::makeInstance(ExpressionBuilder::class, $connectionProphet->reveal())
644 );
645
646 $queryBuilderProphet = $this->prophesize(QueryBuilder::class);
647 $queryBuilderProphet->expr()->willReturn(
648 GeneralUtility::makeInstance(ExpressionBuilder::class, $connectionProphet->reveal())
649 );
650 $queryBuilderProphet->createNamedParameter(Argument::cetera())->willReturnArgument(0);
651
652 $connectionPoolProphet = $this->prophesize(ConnectionPool::class);
653 $connectionPoolProphet->getConnectionForTable(Argument::any($tableName, 'pages'))->willReturn($connectionProphet->reveal());
654 $connectionPoolProphet->getQueryBuilderForTable(Argument::any($tableName, 'pages'))->willReturn($queryBuilderProphet->reveal());
655 GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphet->reveal());
656 GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphet->reveal());
657
658 $mockQuerySettings = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings::class)
659 ->setMethods(['getIgnoreEnableFields', 'getEnableFieldsToBeIgnored', 'getIncludeDeleted'])
660 ->disableOriginalConstructor()
661 ->getMock();
662 $mockQuerySettings->expects($this->once())->method('getIgnoreEnableFields')->will($this->returnValue($ignoreEnableFields));
663 $mockQuerySettings->expects($this->once())->method('getEnableFieldsToBeIgnored')->will($this->returnValue($enableFieldsToBeIgnored));
664 $mockQuerySettings->expects($this->once())->method('getIncludeDeleted')->will($this->returnValue($deletedValue));
665
666 /** @var $mockEnvironmentService \TYPO3\CMS\Extbase\Service\EnvironmentService | \PHPUnit_Framework_MockObject_MockObject */
667 $mockEnvironmentService = $this->getMockBuilder(\TYPO3\CMS\Extbase\Service\EnvironmentService::class)
668 ->setMethods(['isEnvironmentInFrontendMode'])
669 ->getMock();
670 $mockEnvironmentService->expects($this->any())->method('isEnvironmentInFrontendMode')->will($this->returnValue($mode === 'FE'));
671
672 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
673 $mockTypo3DbQueryParser->_set('environmentService', $mockEnvironmentService);
674 $resultSql = $mockTypo3DbQueryParser->_callRef('getVisibilityConstraintStatement', $mockQuerySettings, $tableName, $tableName);
675 $this->assertSame($expectedSql, $resultSql);
676 unset($GLOBALS['TCA'][$tableName]);
677 }
678
679 public function providerForRespectEnableFields()
680 {
681 return [
682 'in be: respectEnableFields=false' => ['BE', false, ''],
683 'in be: respectEnableFields=true' => ['BE', true, '(tx_foo_table.disabled_column = 0) AND (tx_foo_table.starttime_column <= 123456789) AND tx_foo_table.deleted_column = 0'],
684 'in FE: respectEnableFields=false' => ['FE', false, ''],
685 'in FE: respectEnableFields=true' => ['FE', true, '(tx_foo_table.deleted_column = 0) AND (tx_foo_table.disabled_column = 0) AND (tx_foo_table.starttime_column <= 123456789)']
686 ];
687 }
688
689 /**
690 * @test
691 * @dataProvider providerForRespectEnableFields
692 */
693 public function respectEnableFieldsSettingGeneratesCorrectStatement($mode, $respectEnableFields, $expectedSql)
694 {
695 $tableName = 'tx_foo_table';
696 $GLOBALS['TCA'][$tableName]['ctrl'] = [
697 'enablecolumns' => [
698 'disabled' => 'disabled_column',
699 'starttime' => 'starttime_column'
700 ],
701 'delete' => 'deleted_column'
702 ];
703 $GLOBALS['TSFE'] = new \stdClass();
704 $GLOBALS['TSFE']->sys_page = new \TYPO3\CMS\Frontend\Page\PageRepository();
705 $GLOBALS['SIM_ACCESS_TIME'] = 123456789;
706
707 $connectionProphet = $this->prophesize(Connection::class);
708 $connectionProphet->quoteIdentifier(Argument::cetera())->willReturnArgument(0);
709 $connectionProphet->getExpressionBuilder(Argument::cetera())->willReturn(
710 GeneralUtility::makeInstance(ExpressionBuilder::class, $connectionProphet->reveal())
711 );
712 $queryBuilderProphet = $this->prophesize(QueryBuilder::class);
713 $queryBuilderProphet->expr()->willReturn(
714 GeneralUtility::makeInstance(ExpressionBuilder::class, $connectionProphet->reveal())
715 );
716 $queryBuilderProphet->createNamedParameter(Argument::cetera())->willReturnArgument(0);
717
718 $connectionPoolProphet = $this->prophesize(ConnectionPool::class);
719 $connectionPoolProphet->getQueryBuilderForTable(Argument::any($tableName, 'pages'))->willReturn($queryBuilderProphet->reveal());
720 $connectionPoolProphet->getConnectionForTable(Argument::any($tableName, 'pages'))->willReturn($connectionProphet->reveal());
721 GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphet->reveal());
722 GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphet->reveal());
723
724 /** @var \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings $mockQuerySettings */
725 $mockQuerySettings = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings::class)
726 ->setMethods(['dummy'])
727 ->disableOriginalConstructor()
728 ->getMock();
729 $mockQuerySettings->setIgnoreEnableFields(!$respectEnableFields);
730 $mockQuerySettings->setIncludeDeleted(!$respectEnableFields);
731
732 /** @var $mockEnvironmentService \TYPO3\CMS\Extbase\Service\EnvironmentService | \PHPUnit_Framework_MockObject_MockObject */
733 $mockEnvironmentService = $this->getMockBuilder(\TYPO3\CMS\Extbase\Service\EnvironmentService::class)
734 ->setMethods(['isEnvironmentInFrontendMode'])
735 ->getMock();
736 $mockEnvironmentService->expects($this->any())->method('isEnvironmentInFrontendMode')->will($this->returnValue($mode === 'FE'));
737
738 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
739 $mockTypo3DbQueryParser->_set('environmentService', $mockEnvironmentService);
740 $actualSql = $mockTypo3DbQueryParser->_callRef('getVisibilityConstraintStatement', $mockQuerySettings, $tableName, $tableName);
741 $this->assertSame($expectedSql, $actualSql);
742 unset($GLOBALS['TCA'][$tableName]);
743 }
744
745 /**
746 * @test
747 */
748 public function visibilityConstraintStatementGenerationThrowsExceptionIfTheQuerySettingsAreInconsistent()
749 {
750 $this->expectException(InconsistentQuerySettingsException::class);
751 $this->expectExceptionCode(1460975922);
752 $tableName = 'tx_foo_table';
753 $GLOBALS['TCA'][$tableName]['ctrl'] = [
754 'enablecolumns' => [
755 'disabled' => 'disabled_column'
756 ],
757 'delete' => 'deleted_column'
758 ];
759 $mockQuerySettings = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings::class)
760 ->setMethods(['getIgnoreEnableFields', 'getEnableFieldsToBeIgnored', 'getIncludeDeleted'])
761 ->disableOriginalConstructor()
762 ->getMock();
763 $mockQuerySettings->expects($this->once())->method('getIgnoreEnableFields')->will($this->returnValue(false));
764 $mockQuerySettings->expects($this->once())->method('getEnableFieldsToBeIgnored')->will($this->returnValue([]));
765 $mockQuerySettings->expects($this->once())->method('getIncludeDeleted')->will($this->returnValue(true));
766
767 /** @var $mockEnvironmentService \TYPO3\CMS\Extbase\Service\EnvironmentService | \PHPUnit_Framework_MockObject_MockObject */
768 $mockEnvironmentService = $this->getMockBuilder(\TYPO3\CMS\Extbase\Service\EnvironmentService::class)
769 ->setMethods(['isEnvironmentInFrontendMode'])
770 ->getMock();
771 $mockEnvironmentService->expects($this->any())->method('isEnvironmentInFrontendMode')->will($this->returnValue(true));
772
773 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
774 $mockTypo3DbQueryParser->_set('environmentService', $mockEnvironmentService);
775 $mockTypo3DbQueryParser->_callRef('getVisibilityConstraintStatement', $mockQuerySettings, $tableName, $tableName);
776 unset($GLOBALS['TCA'][$tableName]);
777 }
778
779 /**
780 * DataProvider for addPageIdStatement Tests
781 */
782 public function providerForAddPageIdStatementData()
783 {
784 $table = $this->getUniqueId('tx_coretest_table');
785 return [
786 'set Pid to zero if rootLevel = 1' => [
787 '1',
788 $table,
789 $table . '.pid = 0'
790 ],
791 'set Pid to given Pids if rootLevel = 0' => [
792 '0',
793 $table,
794 $table . '.pid IN (42, 27)'
795 ],
796 'add 0 to given Pids if rootLevel = -1' => [
797 '-1',
798 $table,
799 $table . '.pid IN (42, 27, 0)'
800 ],
801 'set Pid to zero if rootLevel = -1 and no further pids given' => [
802 '-1',
803 $table,
804 $table . '.pid = 0',
805 []
806 ],
807 'set no statement for invalid configuration' => [
808 '2',
809 $table,
810 ''
811 ]
812 ];
813 }
814
815 /**
816 * @test
817 * @dataProvider providerForAddPageIdStatementData
818 */
819 public function addPageIdStatementSetsPidToZeroIfTableDeclaresRootlevel($rootLevel, $table, $expectedSql, $storagePageIds = [42, 27])
820 {
821 $GLOBALS['TCA'][$table]['ctrl'] = [
822 'rootLevel' => $rootLevel
823 ];
824 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
825 $queryBuilderProphet = $this->getQueryBuilderWithExpressionBuilderProphet();
826 $mockTypo3DbQueryParser->_set('queryBuilder', $queryBuilderProphet->reveal());
827 $sql = $mockTypo3DbQueryParser->_callRef('getPageIdStatement', $table, $table, $storagePageIds);
828
829 $this->assertSame($expectedSql, $sql);
830 }
831 }