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