960771d6ebe8fcf3a64e8a6de02f83f13ecb81bf
[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
643 $queryBuilderProphet = $this->prophesize(QueryBuilder::class);
644 $queryBuilderProphet->expr()->willReturn(
645 GeneralUtility::makeInstance(ExpressionBuilder::class, $connectionProphet->reveal())
646 );
647 $queryBuilderProphet->createNamedParameter(Argument::cetera())->willReturnArgument(0);
648
649 $connectionPoolProphet = $this->prophesize(ConnectionPool::class);
650 $connectionPoolProphet->getQueryBuilderForTable(Argument::any($tableName, 'pages'))->willReturn($queryBuilderProphet->reveal());
651 GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphet->reveal());
652
653 $mockQuerySettings = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings::class)
654 ->setMethods(['getIgnoreEnableFields', 'getEnableFieldsToBeIgnored', 'getIncludeDeleted'])
655 ->disableOriginalConstructor()
656 ->getMock();
657 $mockQuerySettings->expects($this->once())->method('getIgnoreEnableFields')->will($this->returnValue($ignoreEnableFields));
658 $mockQuerySettings->expects($this->once())->method('getEnableFieldsToBeIgnored')->will($this->returnValue($enableFieldsToBeIgnored));
659 $mockQuerySettings->expects($this->once())->method('getIncludeDeleted')->will($this->returnValue($deletedValue));
660
661 /** @var $mockEnvironmentService \TYPO3\CMS\Extbase\Service\EnvironmentService | \PHPUnit_Framework_MockObject_MockObject */
662 $mockEnvironmentService = $this->getMockBuilder(\TYPO3\CMS\Extbase\Service\EnvironmentService::class)
663 ->setMethods(['isEnvironmentInFrontendMode'])
664 ->getMock();
665 $mockEnvironmentService->expects($this->any())->method('isEnvironmentInFrontendMode')->will($this->returnValue($mode === 'FE'));
666
667 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
668 $mockTypo3DbQueryParser->_set('environmentService', $mockEnvironmentService);
669 $resultSql = $mockTypo3DbQueryParser->_callRef('getVisibilityConstraintStatement', $mockQuerySettings, $tableName, $tableName);
670 $this->assertSame($expectedSql, $resultSql);
671 unset($GLOBALS['TCA'][$tableName]);
672 }
673
674 public function providerForRespectEnableFields()
675 {
676 return [
677 'in be: respectEnableFields=false' => ['BE', false, ''],
678 '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'],
679 'in FE: respectEnableFields=false' => ['FE', false, ''],
680 '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)']
681 ];
682 }
683
684 /**
685 * @test
686 * @dataProvider providerForRespectEnableFields
687 */
688 public function respectEnableFieldsSettingGeneratesCorrectStatement($mode, $respectEnableFields, $expectedSql)
689 {
690 $tableName = 'tx_foo_table';
691 $GLOBALS['TCA'][$tableName]['ctrl'] = [
692 'enablecolumns' => [
693 'disabled' => 'disabled_column',
694 'starttime' => 'starttime_column'
695 ],
696 'delete' => 'deleted_column'
697 ];
698 $GLOBALS['TSFE'] = new \stdClass();
699 $GLOBALS['TSFE']->sys_page = new \TYPO3\CMS\Frontend\Page\PageRepository();
700 $GLOBALS['SIM_ACCESS_TIME'] = 123456789;
701
702 $connectionProphet = $this->prophesize(Connection::class);
703 $connectionProphet->quoteIdentifier(Argument::cetera())->willReturnArgument(0);
704
705 $queryBuilderProphet = $this->prophesize(QueryBuilder::class);
706 $queryBuilderProphet->expr()->willReturn(
707 GeneralUtility::makeInstance(ExpressionBuilder::class, $connectionProphet->reveal())
708 );
709 $queryBuilderProphet->createNamedParameter(Argument::cetera())->willReturnArgument(0);
710
711 $connectionPoolProphet = $this->prophesize(ConnectionPool::class);
712 $connectionPoolProphet->getQueryBuilderForTable(Argument::any($tableName, 'pages'))->willReturn($queryBuilderProphet->reveal());
713 GeneralUtility::addInstance(ConnectionPool::class, $connectionPoolProphet->reveal());
714
715 /** @var \TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings $mockQuerySettings */
716 $mockQuerySettings = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings::class)
717 ->setMethods(['dummy'])
718 ->disableOriginalConstructor()
719 ->getMock();
720 $mockQuerySettings->setIgnoreEnableFields(!$respectEnableFields);
721 $mockQuerySettings->setIncludeDeleted(!$respectEnableFields);
722
723 /** @var $mockEnvironmentService \TYPO3\CMS\Extbase\Service\EnvironmentService | \PHPUnit_Framework_MockObject_MockObject */
724 $mockEnvironmentService = $this->getMockBuilder(\TYPO3\CMS\Extbase\Service\EnvironmentService::class)
725 ->setMethods(['isEnvironmentInFrontendMode'])
726 ->getMock();
727 $mockEnvironmentService->expects($this->any())->method('isEnvironmentInFrontendMode')->will($this->returnValue($mode === 'FE'));
728
729 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
730 $mockTypo3DbQueryParser->_set('environmentService', $mockEnvironmentService);
731 $actualSql = $mockTypo3DbQueryParser->_callRef('getVisibilityConstraintStatement', $mockQuerySettings, $tableName, $tableName);
732 $this->assertSame($expectedSql, $actualSql);
733 unset($GLOBALS['TCA'][$tableName]);
734 }
735
736 /**
737 * @test
738 */
739 public function visibilityConstraintStatementGenerationThrowsExceptionIfTheQuerySettingsAreInconsistent()
740 {
741 $this->expectException(InconsistentQuerySettingsException::class);
742 $this->expectExceptionCode(1460975922);
743 $tableName = 'tx_foo_table';
744 $GLOBALS['TCA'][$tableName]['ctrl'] = [
745 'enablecolumns' => [
746 'disabled' => 'disabled_column'
747 ],
748 'delete' => 'deleted_column'
749 ];
750 $mockQuerySettings = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Typo3QuerySettings::class)
751 ->setMethods(['getIgnoreEnableFields', 'getEnableFieldsToBeIgnored', 'getIncludeDeleted'])
752 ->disableOriginalConstructor()
753 ->getMock();
754 $mockQuerySettings->expects($this->once())->method('getIgnoreEnableFields')->will($this->returnValue(false));
755 $mockQuerySettings->expects($this->once())->method('getEnableFieldsToBeIgnored')->will($this->returnValue([]));
756 $mockQuerySettings->expects($this->once())->method('getIncludeDeleted')->will($this->returnValue(true));
757
758 /** @var $mockEnvironmentService \TYPO3\CMS\Extbase\Service\EnvironmentService | \PHPUnit_Framework_MockObject_MockObject */
759 $mockEnvironmentService = $this->getMockBuilder(\TYPO3\CMS\Extbase\Service\EnvironmentService::class)
760 ->setMethods(['isEnvironmentInFrontendMode'])
761 ->getMock();
762 $mockEnvironmentService->expects($this->any())->method('isEnvironmentInFrontendMode')->will($this->returnValue(true));
763
764 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
765 $mockTypo3DbQueryParser->_set('environmentService', $mockEnvironmentService);
766 $mockTypo3DbQueryParser->_callRef('getVisibilityConstraintStatement', $mockQuerySettings, $tableName, $tableName);
767 unset($GLOBALS['TCA'][$tableName]);
768 }
769
770 /**
771 * DataProvider for addPageIdStatement Tests
772 */
773 public function providerForAddPageIdStatementData()
774 {
775 $table = $this->getUniqueId('tx_coretest_table');
776 return [
777 'set Pid to zero if rootLevel = 1' => [
778 '1',
779 $table,
780 $table . '.pid = 0'
781 ],
782 'set Pid to given Pids if rootLevel = 0' => [
783 '0',
784 $table,
785 $table . '.pid IN (42, 27)'
786 ],
787 'add 0 to given Pids if rootLevel = -1' => [
788 '-1',
789 $table,
790 $table . '.pid IN (42, 27, 0)'
791 ],
792 'set Pid to zero if rootLevel = -1 and no further pids given' => [
793 '-1',
794 $table,
795 $table . '.pid = 0',
796 []
797 ],
798 'set no statement for invalid configuration' => [
799 '2',
800 $table,
801 ''
802 ]
803 ];
804 }
805
806 /**
807 * @test
808 * @dataProvider providerForAddPageIdStatementData
809 */
810 public function addPageIdStatementSetsPidToZeroIfTableDeclaresRootlevel($rootLevel, $table, $expectedSql, $storagePageIds = [42, 27])
811 {
812 $GLOBALS['TCA'][$table]['ctrl'] = [
813 'rootLevel' => $rootLevel
814 ];
815 $mockTypo3DbQueryParser = $this->getAccessibleMock(Typo3DbQueryParser::class, ['dummy'], [], '', false);
816 $queryBuilderProphet = $this->getQueryBuilderWithExpressionBuilderProphet();
817 $mockTypo3DbQueryParser->_set('queryBuilder', $queryBuilderProphet->reveal());
818 $sql = $mockTypo3DbQueryParser->_callRef('getPageIdStatement', $table, $table, $storagePageIds);
819
820 $this->assertSame($expectedSql, $sql);
821 }
822 }