[FEATURE] Add Contexts for storing data access modes
[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\Context\Context;
19 use TYPO3\CMS\Core\Database\Connection;
20 use TYPO3\CMS\Core\Database\ConnectionPool;
21 use TYPO3\CMS\Core\Database\Query\Expression\CompositeExpression;
22 use TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder;
23 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
24 use TYPO3\CMS\Core\Utility\GeneralUtility;
25 use TYPO3\CMS\Extbase\Persistence\Generic\Exception\InconsistentQuerySettingsException;
26 use TYPO3\CMS\Extbase\Persistence\Generic\Exception\UnsupportedOrderException;
27 use TYPO3\CMS\Extbase\Persistence\Generic\Qom\AndInterface;
28 use TYPO3\CMS\Extbase\Persistence\Generic\Qom\ComparisonInterface;
29 use TYPO3\CMS\Extbase\Persistence\Generic\Qom\ConstraintInterface;
30 use TYPO3\CMS\Extbase\Persistence\Generic\Qom\NotInterface;
31 use TYPO3\CMS\Extbase\Persistence\Generic\Qom\OrInterface;
32 use TYPO3\CMS\Extbase\Persistence\Generic\Qom\SourceInterface;
33 use TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser;
34 use TYPO3\CMS\Extbase\Persistence\QueryInterface;
35 use TYPO3\CMS\Frontend\Page\PageRepository;
36
37 class Typo3DbQueryParserTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
38 {
39 /**
40 * Subject is not notice free, disable E_NOTICES
41 */
42 protected static $suppressNotices = true;
43
44 /**
45 * Clean up after tests
46 */
47 protected function tearDown()
48 {
49 GeneralUtility::purgeInstances();
50 parent::tearDown();
51 }
52
53 /**
54 * @test
55 */
56 public function convertQueryToDoctrineQueryBuilderDoesNotAddAndWhereWithEmptyConstraint()
57 {
58 // Prepare subject, turn off initialize qb method and inject qb prophecy revelation
59 $subject = $this->getAccessibleMock(
60 Typo3DbQueryParser::class,
61 // Shut down some methods not important for this test
62 ['initializeQueryBuilder', 'parseOrderings', 'addTypo3Constraints']
63 );
64 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
65 $subject->_set('queryBuilder', $queryBuilderProphecy->reveal());
66
67 $queryProphecy = $this->prophesize(QueryInterface::class);
68 $sourceProphecy = $this->prophesize(SourceInterface::class);
69 $queryProphecy->getSource()->willReturn($sourceProphecy->reveal());
70 $queryProphecy->getOrderings()->willReturn([]);
71 $queryProphecy->getStatement()->willReturn(null);
72
73 // Test part: getConstraint returns no constraint object, andWhere() should not be called
74 $queryProphecy->getConstraint()->willReturn(null);
75 $queryBuilderProphecy->andWhere()->shouldNotBeCalled();
76
77 $subject->convertQueryToDoctrineQueryBuilder($queryProphecy->reveal());
78 }
79
80 /**
81 * @test
82 */
83 public function convertQueryToDoctrineQueryBuilderThrowsExceptionOnNotImplementedConstraint()
84 {
85 // Prepare subject, turn off initialize qb method and inject qb prophecy revelation
86 $subject = $this->getAccessibleMock(
87 Typo3DbQueryParser::class,
88 // Shut down some methods not important for this test
89 ['initializeQueryBuilder', 'parseOrderings', 'addTypo3Constraints', 'parseComparison']
90 );
91 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
92 $subject->_set('queryBuilder', $queryBuilderProphecy->reveal());
93
94 $queryProphecy = $this->prophesize(QueryInterface::class);
95 $sourceProphecy = $this->prophesize(SourceInterface::class);
96 $queryProphecy->getSource()->willReturn($sourceProphecy->reveal());
97 $queryProphecy->getOrderings()->willReturn([]);
98 $queryProphecy->getStatement()->willReturn(null);
99
100 // Test part: getConstraint returns not implemented object
101 $constraintProphecy = $this->prophesize(ConstraintInterface::class);
102 $queryProphecy->getConstraint()->willReturn($constraintProphecy->reveal());
103
104 $this->expectException(\RuntimeException::class);
105 $this->expectExceptionCode(1476199898);
106 $subject->convertQueryToDoctrineQueryBuilder($queryProphecy->reveal());
107 }
108
109 /**
110 * @test
111 */
112 public function convertQueryToDoctrineQueryBuilderAddsSimpleAndWhere()
113 {
114 // Prepare subject, turn off initialize qb method and inject qb prophecy revelation
115 $subject = $this->getAccessibleMock(
116 Typo3DbQueryParser::class,
117 // Shut down some methods not important for this test
118 ['initializeQueryBuilder', 'parseOrderings', 'addTypo3Constraints', 'parseComparison']
119 );
120 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
121 $subject->_set('queryBuilder', $queryBuilderProphecy->reveal());
122
123 $queryProphecy = $this->prophesize(QueryInterface::class);
124 $sourceProphecy = $this->prophesize(SourceInterface::class);
125 $queryProphecy->getSource()->willReturn($sourceProphecy->reveal());
126 $queryProphecy->getOrderings()->willReturn([]);
127 $queryProphecy->getStatement()->willReturn(null);
128
129 // Test part: getConstraint returns simple constraint, and should push to andWhere()
130 $constraintProphecy = $this->prophesize(ComparisonInterface::class);
131 $queryProphecy->getConstraint()->willReturn($constraintProphecy->reveal());
132 $subject->expects($this->once())->method('parseComparison')->willReturn('heinz');
133 $queryBuilderProphecy->andWhere('heinz')->shouldBeCalled();
134
135 $subject->convertQueryToDoctrineQueryBuilder($queryProphecy->reveal());
136 }
137
138 /**
139 * @test
140 */
141 public function convertQueryToDoctrineQueryBuilderAddsNotConstraint()
142 {
143 // Prepare subject, turn off initialize qb method and inject qb prophecy revelation
144 $subject = $this->getAccessibleMock(
145 Typo3DbQueryParser::class,
146 // Shut down some methods not important for this test
147 ['initializeQueryBuilder', 'parseOrderings', 'addTypo3Constraints', 'parseComparison']
148 );
149 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
150 $subject->_set('queryBuilder', $queryBuilderProphecy->reveal());
151
152 $queryProphecy = $this->prophesize(QueryInterface::class);
153 $sourceProphecy = $this->prophesize(SourceInterface::class);
154 $queryProphecy->getSource()->willReturn($sourceProphecy->reveal());
155 $queryProphecy->getOrderings()->willReturn([]);
156 $queryProphecy->getStatement()->willReturn(null);
157
158 $constraintProphecy = $this->prophesize(NotInterface::class);
159 $subConstraintProphecy = $this->prophesize(ComparisonInterface::class);
160 $constraintProphecy->getConstraint()->shouldBeCalled()->willReturn($subConstraintProphecy->reveal());
161 $queryProphecy->getConstraint()->willReturn($constraintProphecy->reveal());
162 $subject->expects($this->once())->method('parseComparison')->willReturn('heinz');
163 $queryBuilderProphecy->andWhere(' NOT(heinz)')->shouldBeCalled();
164
165 $subject->convertQueryToDoctrineQueryBuilder($queryProphecy->reveal());
166 }
167
168 /**
169 * @test
170 */
171 public function convertQueryToDoctrineQueryBuilderAddsAndConstraint()
172 {
173 // Prepare subject, turn off initialize qb method and inject qb prophecy revelation
174 $subject = $this->getAccessibleMock(
175 Typo3DbQueryParser::class,
176 // Shut down some methods not important for this test
177 ['initializeQueryBuilder', 'parseOrderings', 'addTypo3Constraints', 'parseComparison']
178 );
179 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
180 $subject->_set('queryBuilder', $queryBuilderProphecy->reveal());
181
182 $queryProphecy = $this->prophesize(QueryInterface::class);
183 $sourceProphecy = $this->prophesize(SourceInterface::class);
184 $queryProphecy->getSource()->willReturn($sourceProphecy->reveal());
185 $queryProphecy->getOrderings()->willReturn([]);
186 $queryProphecy->getStatement()->willReturn(null);
187
188 $constraintProphecy = $this->prophesize(AndInterface::class);
189 $queryProphecy->getConstraint()->willReturn($constraintProphecy->reveal());
190 $constraint1Prophecy = $this->prophesize(ComparisonInterface::class);
191 $constraintProphecy->getConstraint1()->willReturn($constraint1Prophecy->reveal());
192 $constraint2Prophecy = $this->prophesize(ComparisonInterface::class);
193 $constraintProphecy->getConstraint2()->willReturn($constraint2Prophecy->reveal());
194 $subject->expects($this->any())->method('parseComparison')->willReturn('heinz');
195 $expressionProphecy = $this->prophesize(ExpressionBuilder::class);
196 $queryBuilderProphecy->expr()->shouldBeCalled()->willReturn($expressionProphecy->reveal());
197 $compositeExpressionProphecy = $this->prophesize(CompositeExpression::class);
198 $compositeExpressionProphecy->__toString()->willReturn('heinz AND heinz');
199 $compositeExpressionRevelation = $compositeExpressionProphecy->reveal();
200 $expressionProphecy->andX('heinz', 'heinz')->shouldBeCalled()->willReturn($compositeExpressionRevelation);
201 $queryBuilderProphecy->andWhere($compositeExpressionRevelation)->shouldBeCalled();
202
203 $subject->convertQueryToDoctrineQueryBuilder($queryProphecy->reveal());
204 }
205
206 /**
207 * @test
208 */
209 public function convertQueryToDoctrineQueryBuilderNotAddsInvalidAndConstraint()
210 {
211 // Prepare subject, turn off initialize qb method and inject qb prophecy revelation
212 $subject = $this->getAccessibleMock(
213 Typo3DbQueryParser::class,
214 // Shut down some methods not important for this test
215 ['initializeQueryBuilder', 'parseOrderings', 'addTypo3Constraints', 'parseComparison']
216 );
217 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
218 $subject->_set('queryBuilder', $queryBuilderProphecy->reveal());
219
220 $queryProphecy = $this->prophesize(QueryInterface::class);
221 $sourceProphecy = $this->prophesize(SourceInterface::class);
222 $queryProphecy->getSource()->willReturn($sourceProphecy->reveal());
223 $queryProphecy->getOrderings()->willReturn([]);
224 $queryProphecy->getStatement()->willReturn(null);
225
226 $constraintProphecy = $this->prophesize(AndInterface::class);
227 $queryProphecy->getConstraint()->willReturn($constraintProphecy->reveal());
228 $constraint1Prophecy = $this->prophesize(ComparisonInterface::class);
229 $constraintProphecy->getConstraint1()->willReturn($constraint1Prophecy->reveal());
230 // no result for constraint2
231 $constraintProphecy->getConstraint2()->willReturn(null);
232
233 // not be called
234 $queryBuilderProphecy->andWhere()->shouldNotBeCalled();
235
236 $subject->convertQueryToDoctrineQueryBuilder($queryProphecy->reveal());
237 }
238
239 /**
240 * @test
241 */
242 public function convertQueryToDoctrineQueryBuilderAddsOrConstraint()
243 {
244 // Prepare subject, turn off initialize qb method and inject qb prophecy revelation
245 $subject = $this->getAccessibleMock(
246 Typo3DbQueryParser::class,
247 // Shut down some methods not important for this test
248 ['initializeQueryBuilder', 'parseOrderings', 'addTypo3Constraints', 'parseComparison']
249 );
250 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
251 $subject->_set('queryBuilder', $queryBuilderProphecy->reveal());
252
253 $queryProphecy = $this->prophesize(QueryInterface::class);
254 $sourceProphecy = $this->prophesize(SourceInterface::class);
255 $queryProphecy->getSource()->willReturn($sourceProphecy->reveal());
256 $queryProphecy->getOrderings()->willReturn([]);
257 $queryProphecy->getStatement()->willReturn(null);
258
259 $constraintProphecy = $this->prophesize(OrInterface::class);
260 $queryProphecy->getConstraint()->willReturn($constraintProphecy->reveal());
261 $constraint1Prophecy = $this->prophesize(ComparisonInterface::class);
262 $constraintProphecy->getConstraint1()->willReturn($constraint1Prophecy->reveal());
263 $constraint2Prophecy = $this->prophesize(ComparisonInterface::class);
264 $constraintProphecy->getConstraint2()->willReturn($constraint2Prophecy->reveal());
265 $subject->expects($this->any())->method('parseComparison')->willReturn('heinz');
266 $expressionProphecy = $this->prophesize(ExpressionBuilder::class);
267 $queryBuilderProphecy->expr()->shouldBeCalled()->willReturn($expressionProphecy->reveal());
268 $compositeExpressionProphecy = $this->prophesize(CompositeExpression::class);
269 $compositeExpressionProphecy->__toString()->willReturn('heinz OR heinz');
270 $compositeExpressionRevelation = $compositeExpressionProphecy->reveal();
271 $expressionProphecy->orX('heinz', 'heinz')->shouldBeCalled()->willReturn($compositeExpressionRevelation);
272 $queryBuilderProphecy->andWhere($compositeExpressionRevelation)->shouldBeCalled();
273
274 $subject->convertQueryToDoctrineQueryBuilder($queryProphecy->reveal());
275 }
276
277 /**
278 * @test
279 */
280 public function convertQueryToDoctrineQueryBuilderNotAddsInvalidOrConstraint()
281 {
282 // Prepare subject, turn off initialize qb method and inject qb prophecy revelation
283 $subject = $this->getAccessibleMock(
284 Typo3DbQueryParser::class,
285 // Shut down some methods not important for this test
286 ['initializeQueryBuilder', 'parseOrderings', 'addTypo3Constraints', 'parseComparison']
287 );
288 $queryBuilderProphecy = $this->prophesize(QueryBuilder::class);
289 $subject->_set('queryBuilder', $queryBuilderProphecy->reveal());
290
291 $queryProphecy = $this->prophesize(QueryInterface::class);
292 $sourceProphecy = $this->prophesize(SourceInterface::class);
293 $queryProphecy->getSource()->willReturn($sourceProphecy->reveal());
294 $queryProphecy->getOrderings()->willReturn([]);
295 $queryProphecy->getStatement()->willReturn(null);
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 PageRepository(new Context());
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 PageRepository(new Context());
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 }