Completed
Push — master ( a1f74d...502e0c )
by
unknown
12:57
created

ContentTest::testInternalLoadContentByRemoteId()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 1
nop 4
dl 0
loc 41
rs 9.264
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * File contains: eZ\Publish\Core\Repository\Tests\Service\Mock\ContentTest class.
5
 *
6
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
7
 * @license For full copyright and license information view LICENSE file distributed with this source code.
8
 */
9
namespace eZ\Publish\Core\Repository\Tests\Service\Mock;
10
11
use eZ\Publish\API\Repository\Exceptions\BadStateException;
12
use eZ\Publish\API\Repository\Exceptions\UnauthorizedException;
13
use eZ\Publish\API\Repository\Repository;
14
use eZ\Publish\API\Repository\Values\Content\Language;
15
use eZ\Publish\API\Repository\Values\Content\Content as APIContent;
16
use eZ\Publish\API\Repository\Values\Content\LocationCreateStruct;
17
use eZ\Publish\API\Repository\ContentTypeService as APIContentTypeService;
18
use eZ\Publish\API\Repository\LocationService as APILocationService;
19
use eZ\Publish\API\Repository\Exceptions\NotFoundException as APINotFoundException;
20
use eZ\Publish\API\Repository\Values\Content\ContentInfo as APIContentInfo;
21
use eZ\Publish\API\Repository\Values\ContentType\ContentType as APIContentType;
22
use eZ\Publish\API\Repository\Values\Content\Location as APILocation;
23
use eZ\Publish\API\Repository\Values\ContentType\FieldDefinition as APIFieldDefinition;
24
use eZ\Publish\API\Repository\Values\Content\ContentCreateStruct as APIContentCreateStruct;
25
use eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException;
26
use eZ\Publish\Core\Base\Exceptions\ContentValidationException;
27
use eZ\Publish\Core\Repository\Tests\Service\Mock\Base as BaseServiceMockTest;
28
use eZ\Publish\Core\Repository\ContentService;
29
use eZ\Publish\Core\Repository\Values\Content\Location;
30
use eZ\Publish\Core\Repository\Values\Content\Content;
31
use eZ\Publish\Core\Repository\Values\Content\ContentCreateStruct;
32
use eZ\Publish\Core\Repository\Values\Content\ContentUpdateStruct;
33
use eZ\Publish\Core\Repository\Values\Content\VersionInfo;
34
use eZ\Publish\Core\Repository\Helper\RelationProcessor;
35
use eZ\Publish\Core\Repository\Helper\NameSchemaService;
36
use eZ\Publish\API\Repository\Values\Content\Field;
37
use eZ\Publish\Core\FieldType\Value;
38
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
39
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
40
use eZ\Publish\Core\Repository\Values\ContentType\ContentType;
41
use eZ\Publish\Core\Repository\Values\ContentType\FieldDefinition;
42
use eZ\Publish\Core\Repository\Values\ContentType\FieldDefinitionCollection;
43
use eZ\Publish\SPI\Persistence\Content\Location as SPILocation;
44
use eZ\Publish\SPI\FieldType\FieldType as SPIFieldType;
45
use eZ\Publish\SPI\Persistence\Content as SPIContent;
46
use eZ\Publish\SPI\Persistence\Content\UpdateStruct as SPIContentUpdateStruct;
47
use eZ\Publish\SPI\Persistence\Content\CreateStruct as SPIContentCreateStruct;
48
use eZ\Publish\SPI\Persistence\Content\Field as SPIField;
49
use eZ\Publish\SPI\Persistence\Content\ObjectState\Group as SPIObjectStateGroup;
50
use eZ\Publish\SPI\Persistence\Content\ObjectState as SPIObjectState;
51
use eZ\Publish\SPI\Persistence\Content\VersionInfo as SPIVersionInfo;
52
use eZ\Publish\SPI\Persistence\Content\ContentInfo as SPIContentInfo;
53
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct as SPIMetadataUpdateStruct;
54
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
55
use eZ\Publish\Core\Repository\Values\User\UserReference;
56
use Exception;
57
use PHPUnit\Framework\MockObject\MockObject;
58
59
/**
60
 * Mock test case for Content service.
61
 */
62
class ContentTest extends BaseServiceMockTest
63
{
64
    /**
65
     * Represents empty Field Value.
66
     */
67
    const EMPTY_FIELD_VALUE = 'empty';
68
69
    /**
70
     * Test for the __construct() method.
71
     *
72
     * @covers \eZ\Publish\Core\Repository\ContentService::__construct
73
     */
74
    public function testConstructor(): void
75
    {
76
        $repositoryMock = $this->getRepositoryMock();
77
        /** @var \eZ\Publish\SPI\Persistence\Handler $persistenceHandlerMock */
78
        $persistenceHandlerMock = $this->getPersistenceMockHandler('Handler');
79
        $contentDomainMapperMock = $this->getContentDomainMapperMock();
80
        $relationProcessorMock = $this->getRelationProcessorMock();
81
        $nameSchemaServiceMock = $this->getNameSchemaServiceMock();
82
        $fieldTypeRegistryMock = $this->getFieldTypeRegistryMock();
83
        $permissionResolverMock = $this->getPermissionResolverMock();
84
        $settings = ['default_version_archive_limit' => 10];
85
86
        $service = new ContentService(
0 ignored issues
show
Unused Code introduced by
$service is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
87
            $repositoryMock,
0 ignored issues
show
Bug introduced by
It seems like $repositoryMock defined by $this->getRepositoryMock() on line 76 can also be of type object<PHPUnit\Framework\MockObject\MockObject>; however, eZ\Publish\Core\Reposito...tService::__construct() does only seem to accept object<eZ\Publish\API\Repository\Repository>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
88
            $persistenceHandlerMock,
89
            $contentDomainMapperMock,
90
            $relationProcessorMock,
0 ignored issues
show
Bug introduced by
It seems like $relationProcessorMock defined by $this->getRelationProcessorMock() on line 80 can also be of type object<PHPUnit\Framework\MockObject\MockObject>; however, eZ\Publish\Core\Reposito...tService::__construct() does only seem to accept object<eZ\Publish\Core\R...lper\RelationProcessor>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
91
            $nameSchemaServiceMock,
0 ignored issues
show
Bug introduced by
It seems like $nameSchemaServiceMock defined by $this->getNameSchemaServiceMock() on line 81 can also be of type object<PHPUnit\Framework\MockObject\MockObject>; however, eZ\Publish\Core\Reposito...tService::__construct() does only seem to accept object<eZ\Publish\Core\R...lper\NameSchemaService>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
92
            $fieldTypeRegistryMock,
93
            $permissionResolverMock,
94
            $settings
95
        );
96
    }
97
98
    /**
99
     * Test for the loadVersionInfo() method, of published version.
100
     *
101
     * @covers \eZ\Publish\Core\Repository\ContentService::loadVersionInfoById
102
     */
103
    public function testLoadVersionInfoById()
104
    {
105
        $contentServiceMock = $this->getPartlyMockedContentService(['loadContentInfo']);
106
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
107
        $contentHandler = $this->getPersistenceMock()->contentHandler();
108
        $domainMapperMock = $this->getContentDomainMapperMock();
109
        $versionInfoMock = $this->createMock(APIVersionInfo::class);
110
        $permissionResolver = $this->getPermissionResolverMock();
111
112
        $versionInfoMock->expects($this->once())
113
            ->method('isPublished')
114
            ->willReturn(true);
115
116
        $contentServiceMock->expects($this->never())
117
            ->method('loadContentInfo');
118
119
        $contentHandler->expects($this->once())
120
            ->method('loadVersionInfo')
121
            ->with(
122
                $this->equalTo(42),
123
                $this->equalTo(null)
124
            )->will(
125
                $this->returnValue(new SPIVersionInfo())
126
            );
127
128
        $domainMapperMock->expects($this->once())
129
            ->method('buildVersionInfoDomainObject')
130
            ->with(new SPIVersionInfo())
131
            ->will($this->returnValue($versionInfoMock));
132
133
        $permissionResolver->expects($this->once())
134
            ->method('canUser')
135
            ->with(
136
                $this->equalTo('content'),
137
                $this->equalTo('read'),
138
                $this->equalTo($versionInfoMock)
139
            )->will($this->returnValue(true));
140
141
        $result = $contentServiceMock->loadVersionInfoById(42);
142
143
        $this->assertEquals($versionInfoMock, $result);
144
    }
145
146
    /**
147
     * Test for the loadVersionInfo() method, of a draft.
148
     *
149
     * @depends testLoadVersionInfoById
150
     * @covers \eZ\Publish\Core\Repository\ContentService::loadVersionInfoById
151
     */
152
    public function testLoadVersionInfoByIdAndVersionNumber()
153
    {
154
        $permissionResolver = $this->getPermissionResolverMock();
155
        $contentServiceMock = $this->getPartlyMockedContentService(['loadContentInfo']);
156
        /** @var \PHPUnit_Framework_MockObject_MockObject $contentHandler */
157
        $contentHandler = $this->getPersistenceMock()->contentHandler();
158
        $domainMapperMock = $this->getContentDomainMapperMock();
159
        $versionInfoMock = $this->createMock(APIVersionInfo::class);
160
161
        $versionInfoMock->expects($this->any())
162
            ->method('__get')
163
            ->with('status')
164
            ->willReturn(APIVersionInfo::STATUS_DRAFT);
165
166
        $contentServiceMock->expects($this->never())
167
            ->method('loadContentInfo');
168
169
        $contentHandler->expects($this->once())
170
            ->method('loadVersionInfo')
171
            ->with(
172
                $this->equalTo(42),
173
                $this->equalTo(2)
174
            )->willReturn(new SPIVersionInfo());
175
176
        $domainMapperMock->expects($this->once())
177
            ->method('buildVersionInfoDomainObject')
178
            ->with(new SPIVersionInfo())
179
            ->willReturn($versionInfoMock);
180
181
        $permissionResolver->expects($this->once())
182
            ->method('canUser')
183
            ->with(
184
                $this->equalTo('content'),
185
                $this->equalTo('versionread'),
186
                $this->equalTo($versionInfoMock)
187
            )->willReturn(true);
188
189
        $result = $contentServiceMock->loadVersionInfoById(42, 2);
190
191
        $this->assertEquals($versionInfoMock, $result);
192
    }
193
194
    /**
195
     * Test for the loadVersionInfo() method.
196
     *
197
     * @covers \eZ\Publish\Core\Repository\ContentService::loadVersionInfoById
198
     */
199
    public function testLoadVersionInfoByIdThrowsNotFoundException()
200
    {
201
        $this->expectException(NotFoundException::class);
202
203
        $contentServiceMock = $this->getPartlyMockedContentService(['loadContentInfo']);
204
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
205
        $contentHandler = $this->getPersistenceMock()->contentHandler();
206
207
        $contentHandler->expects($this->once())
208
            ->method('loadVersionInfo')
209
            ->with(
210
                $this->equalTo(42),
211
                $this->equalTo(24)
212
            )->will(
213
                $this->throwException(
214
                    new NotFoundException(
215
                        'Content',
216
                        [
217
                            'contentId' => 42,
218
                            'versionNo' => 24,
219
                        ]
220
                    )
221
                )
222
            );
223
224
        $contentServiceMock->loadVersionInfoById(42, 24);
225
    }
226
227
    /**
228
     * Test for the loadVersionInfo() method.
229
     *
230
     * @covers \eZ\Publish\Core\Repository\ContentService::loadVersionInfoById
231
     */
232
    public function testLoadVersionInfoByIdThrowsUnauthorizedExceptionNonPublishedVersion()
233
    {
234
        $this->expectException(\eZ\Publish\Core\Base\Exceptions\UnauthorizedException::class);
235
236
        $contentServiceMock = $this->getPartlyMockedContentService();
237
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
238
        $contentHandler = $this->getPersistenceMock()->contentHandler();
239
        $domainMapperMock = $this->getContentDomainMapperMock();
240
        $versionInfoMock = $this->createMock(APIVersionInfo::class);
241
        $permissionResolver = $this->getPermissionResolverMock();
242
243
        $versionInfoMock->expects($this->any())
244
            ->method('isPublished')
245
            ->willReturn(false);
246
247
        $contentHandler->expects($this->once())
248
            ->method('loadVersionInfo')
249
            ->with(
250
                $this->equalTo(42),
251
                $this->equalTo(24)
252
            )->will(
253
                $this->returnValue(new SPIVersionInfo())
254
            );
255
256
        $domainMapperMock->expects($this->once())
257
            ->method('buildVersionInfoDomainObject')
258
            ->with(new SPIVersionInfo())
259
            ->will($this->returnValue($versionInfoMock));
260
261
        $permissionResolver->expects($this->once())
262
            ->method('canUser')
263
            ->with(
264
                $this->equalTo('content'),
265
                $this->equalTo('versionread'),
266
                $this->equalTo($versionInfoMock)
267
            )->will($this->returnValue(false));
268
269
        $contentServiceMock->loadVersionInfoById(42, 24);
270
    }
271
272
    /**
273
     * Test for the loadVersionInfo() method.
274
     *
275
     * @covers \eZ\Publish\Core\Repository\ContentService::loadVersionInfoById
276
     */
277
    public function testLoadVersionInfoByIdPublishedVersion()
278
    {
279
        $contentServiceMock = $this->getPartlyMockedContentService();
280
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
281
        $contentHandler = $this->getPersistenceMock()->contentHandler();
282
        $domainMapperMock = $this->getContentDomainMapperMock();
283
        $versionInfoMock = $this->createMock(APIVersionInfo::class);
284
        $permissionResolver = $this->getPermissionResolverMock();
285
286
        $versionInfoMock->expects($this->once())
287
            ->method('isPublished')
288
            ->willReturn(true);
289
290
        $contentHandler->expects($this->once())
291
            ->method('loadVersionInfo')
292
            ->with(
293
                $this->equalTo(42),
294
                $this->equalTo(24)
295
            )->will(
296
                $this->returnValue(new SPIVersionInfo())
297
            );
298
299
        $domainMapperMock->expects($this->once())
300
            ->method('buildVersionInfoDomainObject')
301
            ->with(new SPIVersionInfo())
302
            ->will($this->returnValue($versionInfoMock));
303
304
        $permissionResolver->expects($this->once())
305
            ->method('canUser')
306
            ->with(
307
                $this->equalTo('content'),
308
                $this->equalTo('read'),
309
                $this->equalTo($versionInfoMock)
310
            )->will($this->returnValue(true));
311
312
        $result = $contentServiceMock->loadVersionInfoById(42, 24);
313
314
        $this->assertEquals($versionInfoMock, $result);
315
    }
316
317
    /**
318
     * Test for the loadVersionInfo() method.
319
     *
320
     * @covers \eZ\Publish\Core\Repository\ContentService::loadVersionInfoById
321
     */
322
    public function testLoadVersionInfoByIdNonPublishedVersion()
323
    {
324
        $contentServiceMock = $this->getPartlyMockedContentService();
325
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
326
        $contentHandler = $this->getPersistenceMock()->contentHandler();
327
        $domainMapperMock = $this->getContentDomainMapperMock();
328
        $versionInfoMock = $this->createMock(APIVersionInfo::class);
329
        $permissionResolver = $this->getPermissionResolverMock();
330
331
        $versionInfoMock->expects($this->once())
332
            ->method('isPublished')
333
            ->willReturn(false);
334
335
        $contentHandler->expects($this->once())
336
            ->method('loadVersionInfo')
337
            ->with(
338
                $this->equalTo(42),
339
                $this->equalTo(24)
340
            )->will(
341
                $this->returnValue(new SPIVersionInfo())
342
            );
343
344
        $domainMapperMock->expects($this->once())
345
            ->method('buildVersionInfoDomainObject')
346
            ->with(new SPIVersionInfo())
347
            ->will($this->returnValue($versionInfoMock));
348
349
        $permissionResolver->expects($this->once())
350
            ->method('canUser')
351
            ->with(
352
                $this->equalTo('content'),
353
                $this->equalTo('versionread'),
354
                $this->equalTo($versionInfoMock)
355
            )->will($this->returnValue(true));
356
357
        $result = $contentServiceMock->loadVersionInfoById(42, 24);
358
359
        $this->assertEquals($versionInfoMock, $result);
360
    }
361
362
    /**
363
     * Test for the loadVersionInfo() method.
364
     *
365
     * @covers \eZ\Publish\Core\Repository\ContentService::loadVersionInfo
366
     * @depends eZ\Publish\Core\Repository\Tests\Service\Mock\ContentTest::testLoadVersionInfoById
367
     * @depends eZ\Publish\Core\Repository\Tests\Service\Mock\ContentTest::testLoadVersionInfoByIdThrowsNotFoundException
368
     * @depends eZ\Publish\Core\Repository\Tests\Service\Mock\ContentTest::testLoadVersionInfoByIdThrowsUnauthorizedExceptionNonPublishedVersion
369
     * @depends eZ\Publish\Core\Repository\Tests\Service\Mock\ContentTest::testLoadVersionInfoByIdPublishedVersion
370
     * @depends eZ\Publish\Core\Repository\Tests\Service\Mock\ContentTest::testLoadVersionInfoByIdNonPublishedVersion
371
     */
372
    public function testLoadVersionInfo()
373
    {
374
        $expectedResult = $this->createMock(VersionInfo::class);
375
376
        $contentServiceMock = $this->getPartlyMockedContentService(
377
            ['loadVersionInfoById']
378
        );
379
        $contentServiceMock->expects(
380
            $this->once()
381
        )->method(
382
            'loadVersionInfoById'
383
        )->with(
384
            $this->equalTo(42),
385
            $this->equalTo(7)
386
        )->will(
387
            $this->returnValue($expectedResult)
388
        );
389
390
        $result = $contentServiceMock->loadVersionInfo(
391
            new ContentInfo(['id' => 42]),
392
            7
393
        );
394
395
        $this->assertEquals($expectedResult, $result);
396
    }
397
398
    public function testLoadContent()
399
    {
400
        $contentService = $this->getPartlyMockedContentService(['internalLoadContentById']);
401
        $content = $this->createMock(APIContent::class);
402
        $versionInfo = $this->createMock(APIVersionInfo::class);
403
        $permissionResolver = $this->getPermissionResolverMock();
404
405
        $content
406
            ->expects($this->once())
407
            ->method('getVersionInfo')
408
            ->will($this->returnValue($versionInfo));
409
        $versionInfo
410
            ->expects($this->once())
411
            ->method('isPublished')
412
            ->willReturn(true);
413
        $contentId = 123;
414
        $contentService
415
            ->expects($this->once())
416
            ->method('internalLoadContentById')
417
            ->with($contentId)
418
            ->will($this->returnValue($content));
419
420
        $permissionResolver
421
            ->expects($this->once())
422
            ->method('canUser')
423
            ->with('content', 'read', $content)
424
            ->will($this->returnValue(true));
425
426
        $this->assertSame($content, $contentService->loadContent($contentId));
427
    }
428
429
    public function testLoadContentNonPublished()
430
    {
431
        $contentService = $this->getPartlyMockedContentService(['internalLoadContentById']);
432
        $content = $this->createMock(APIContent::class);
433
        $versionInfo = $this->createMock(APIVersionInfo::class);
434
        $permissionResolver = $this->getPermissionResolverMock();
435
436
        $content
437
            ->expects($this->once())
438
            ->method('getVersionInfo')
439
            ->will($this->returnValue($versionInfo));
440
        $contentId = 123;
441
        $contentService
442
            ->expects($this->once())
443
            ->method('internalLoadContentById')
444
            ->with($contentId)
445
            ->will($this->returnValue($content));
446
447
        $permissionResolver
448
            ->expects($this->exactly(2))
449
            ->method('canUser')
450
            ->will(
451
                $this->returnValueMap(
452
                    [
453
                        ['content', 'read', $content, [], true],
454
                        ['content', 'versionread', $content, [], true],
455
                    ]
456
                )
457
            );
458
459
        $this->assertSame($content, $contentService->loadContent($contentId));
460
    }
461
462
    public function testLoadContentUnauthorized()
463
    {
464
        $this->expectException(\eZ\Publish\Core\Base\Exceptions\UnauthorizedException::class);
465
466
        $permissionResolver = $this->getPermissionResolverMock();
467
468
        $contentService = $this->getPartlyMockedContentService(['internalLoadContentById']);
469
        $content = $this->createMock(APIContent::class);
470
        $contentId = 123;
471
        $contentService
472
            ->expects($this->once())
473
            ->method('internalLoadContentById')
474
            ->with($contentId)
475
            ->will($this->returnValue($content));
476
477
        $permissionResolver
478
            ->expects($this->once())
479
            ->method('canUser')
480
            ->with('content', 'read', $content)
481
            ->will($this->returnValue(false));
482
483
        $contentService->loadContent($contentId);
484
    }
485
486
    public function testLoadContentNotPublishedStatusUnauthorized()
487
    {
488
        $this->expectException(\eZ\Publish\Core\Base\Exceptions\UnauthorizedException::class);
489
490
        $permissionResolver = $this->getPermissionResolverMock();
491
        $contentService = $this->getPartlyMockedContentService(['internalLoadContentById']);
492
        $content = $this->createMock(APIContent::class);
493
        $versionInfo = $this
494
            ->getMockBuilder(APIVersionInfo::class)
495
            ->getMockForAbstractClass();
496
        $content
497
            ->expects($this->once())
498
            ->method('getVersionInfo')
499
            ->will($this->returnValue($versionInfo));
500
        $contentId = 123;
501
        $contentService
502
            ->expects($this->once())
503
            ->method('internalLoadContentById')
504
            ->with($contentId)
505
            ->will($this->returnValue($content));
506
507
        $permissionResolver
508
            ->expects($this->exactly(2))
509
            ->method('canUser')
510
            ->will(
511
                $this->returnValueMap(
512
                    [
513
                        ['content', 'read', $content, [], true],
514
                        ['content', 'versionread', $content, [], false],
515
                    ]
516
                )
517
            );
518
519
        $contentService->loadContent($contentId);
520
    }
521
522
    /**
523
     * @dataProvider internalLoadContentProviderById
524
     */
525
    public function testInternalLoadContentById(int $id, ?array $languages, ?int $versionNo, bool $useAlwaysAvailable): void
526
    {
527
        if (!empty($languages) && $useAlwaysAvailable) {
528
            $spiContentInfo = new SPIContentInfo(['id' => $id, 'alwaysAvailable' => false]);
529
        } else {
530
            $spiContentInfo = new SPIContentInfo(['id' => $id]);
531
        }
532
533
        $spiContent = new SPIContent([
534
            'versionInfo' => new VersionInfo([
535
                'contentInfo' => new ContentInfo([
536
                    'id' => 42,
537
                    'contentTypeId' => 123,
538
                ]),
539
            ]),
540
        ]);
541
542
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
543
        $contentHandler = $this->getPersistenceMock()->contentHandler();
544
        $contentHandler
545
            ->expects($this->once())
546
            ->method('loadContentInfo')
547
            ->with($id)
548
            ->will($this->returnValue($spiContentInfo));
549
550
        $contentHandler
551
            ->expects($this->once())
552
            ->method('load')
553
            ->with($id, $versionNo, $languages)
554
            ->willReturn($spiContent);
555
556
        $contentService = $this->getPartlyMockedContentService();
557
558
        $expectedContent = $this->mockBuildContentDomainObject($spiContent, $languages);
559
        $actualContent = $contentService->internalLoadContentById($id, $languages, $versionNo, $useAlwaysAvailable);
560
561
        $this->assertSame($expectedContent, $actualContent);
562
    }
563
564
    /**
565
     * @dataProvider internalLoadContentProviderByRemoteId
566
     */
567
    public function testInternalLoadContentByRemoteId(string $remoteId, ?array $languages, ?int $versionNo, bool $useAlwaysAvailable)
568
    {
569
        $realId = 123;
570
571
        $spiContentInfo = new SPIContentInfo([
572
            'currentVersionNo' => $versionNo ?: 7,
573
            'id' => $realId,
574
        ]);
575
576
        $spiContent = new SPIContent([
577
            'versionInfo' => new VersionInfo([
578
                'contentInfo' => new ContentInfo(['id' => 42, 'contentTypeId' => 123]),
579
            ]),
580
        ]);
581
582
        $contentService = $this->getPartlyMockedContentService();
583
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
584
        $contentHandler = $this->getPersistenceMock()->contentHandler();
585
        $contentHandler
586
            ->expects($this->once())
587
            ->method('loadContentInfoByRemoteId')
588
            ->with($remoteId)
589
            ->will($this->returnValue($spiContentInfo));
590
591
        $contentHandler
592
            ->expects($this->once())
593
            ->method('load')
594
            ->with($realId, $versionNo, $languages)
595
            ->willReturn($spiContent);
596
597
        $expectedContent = $this->mockBuildContentDomainObject($spiContent, $languages);
598
599
        $actualContent = $contentService->internalLoadContentByRemoteId(
600
            $remoteId,
601
            $languages,
602
            $versionNo,
603
            $useAlwaysAvailable
604
        );
605
606
        $this->assertSame($expectedContent, $actualContent);
607
    }
608
609
    public function internalLoadContentProviderById(): array
610
    {
611
        return [
612
            [123, null, null, false],
613
            [123, null, 456, false],
614
            [456, null, 123, true],
615
            [456, null, 2, false],
616
            [456, ['eng-GB'], 2, true],
617
            [456, ['eng-GB', 'fre-FR'], null, false],
618
            [456, ['eng-GB', 'fre-FR', 'nor-NO'], 2, false],
619
        ];
620
    }
621
622
    public function internalLoadContentProviderByRemoteId(): array
623
    {
624
        return [
625
            ['123', null, null, false],
626
            ['someRemoteId', null, 456, false],
627
            ['456', null, 123, false],
628
            ['someRemoteId', null, 2, false],
629
            ['someRemoteId', ['eng-GB'], 2, false],
630
            ['456', ['eng-GB', 'fre-FR'], null, false],
631
            ['someRemoteId', ['eng-GB', 'fre-FR', 'nor-NO'], 2, false],
632
        ];
633
    }
634
635
    public function testInternalLoadContentByIdNotFound(): void
636
    {
637
        $this->expectException(NotFoundException::class);
638
639
        $id = 123;
640
        $versionNo = 7;
641
        $languages = null;
642
643
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
644
        $contentHandler = $this->getPersistenceMock()->contentHandler();
645
        $contentHandler
646
            ->expects($this->once())
647
            ->method('loadContentInfo')
648
            ->with($id)
649
            ->willReturn(new SPIContent\ContentInfo(['id' => $id]));
650
651
        $contentHandler
652
            ->expects($this->once())
653
            ->method('load')
654
            ->with($id, $versionNo, $languages)
655
            ->will(
656
                $this->throwException(
657
                    $this->createMock(APINotFoundException::class)
658
                )
659
            );
660
661
        $contentService = $this->getPartlyMockedContentService();
662
        $contentService->internalLoadContentById($id, $languages, $versionNo);
663
    }
664
665
    public function testInternalLoadContentByRemoteIdNotFound(): void
666
    {
667
        $this->expectException(NotFoundException::class);
668
669
        $remoteId = 'dca290623518d393126d3408b45af6ee';
670
        $id = 123;
671
        $versionNo = 7;
672
        $languages = null;
673
674
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
675
        $contentHandler = $this->getPersistenceMock()->contentHandler();
676
        $contentHandler
677
            ->expects($this->once())
678
            ->method('loadContentInfoByRemoteId')
679
            ->with($remoteId)
680
            ->willReturn(new SPIContent\ContentInfo(['id' => $id]));
681
682
        $contentHandler
683
            ->expects($this->once())
684
            ->method('load')
685
            ->with($id, $versionNo, $languages)
686
            ->willThrowException(
687
                $this->createMock(APINotFoundException::class)
688
            );
689
690
        $contentService = $this->getPartlyMockedContentService();
691
        $contentService->internalLoadContentByRemoteId($remoteId, $languages, $versionNo);
692
    }
693
694
    /**
695
     * Test for the loadContentByContentInfo() method.
696
     *
697
     * @covers \eZ\Publish\Core\Repository\ContentService::loadContentByContentInfo
698
     */
699
    public function testLoadContentByContentInfo()
700
    {
701
        $versionInfo = $this->createMock(APIVersionInfo::class);
702
        $content = $this->createMock(APIContent::class);
703
        $content->method('getVersionInfo')
704
            ->will($this->returnValue($versionInfo));
705
706
        $permissionResolver = $this->getPermissionResolverMock();
707
        $permissionResolver->expects($this->any())
708
            ->method('canUser')
709
            ->will($this->returnValue(true));
710
711
        $contentServiceMock = $this->getPartlyMockedContentService(
712
            ['internalLoadContentById']
713
        );
714
715
        $contentServiceMock
716
            ->method(
717
                'internalLoadContentById'
718
        )->with(
719
            $this->equalTo(42),
720
            $this->equalTo(['cro-HR']),
721
            $this->equalTo(7),
722
            $this->equalTo(false)
723
        )->will(
724
            $this->returnValue($content)
725
        );
726
727
        $result = $contentServiceMock->loadContentByContentInfo(
728
            new ContentInfo(['id' => 42]),
729
            ['cro-HR'],
730
            7
731
        );
732
733
        $this->assertEquals($content, $result);
734
    }
735
736
    /**
737
     * Test for the loadContentByVersionInfo() method.
738
     *
739
     * @covers \eZ\Publish\Core\Repository\ContentService::loadContentByVersionInfo
740
     */
741
    public function testLoadContentByVersionInfo()
742
    {
743
        $expectedResult = $this->createMock(Content::class);
744
745
        $contentServiceMock = $this->getPartlyMockedContentService(
746
            ['loadContent']
747
        );
748
        $contentServiceMock->expects(
749
            $this->once()
750
        )->method(
751
            'loadContent'
752
        )->with(
753
            $this->equalTo(42),
754
            $this->equalTo(['cro-HR']),
755
            $this->equalTo(7),
756
            $this->equalTo(false)
757
        )->will(
758
            $this->returnValue($expectedResult)
759
        );
760
761
        $result = $contentServiceMock->loadContentByVersionInfo(
762
            new VersionInfo(
763
                [
764
                    'contentInfo' => new ContentInfo(['id' => 42]),
765
                    'versionNo' => 7,
766
                ]
767
            ),
768
            ['cro-HR']
769
        );
770
771
        $this->assertEquals($expectedResult, $result);
772
    }
773
774
    /**
775
     * Test for the deleteContent() method.
776
     *
777
     * @covers \eZ\Publish\Core\Repository\ContentService::deleteContent
778
     */
779
    public function testDeleteContentThrowsUnauthorizedException()
780
    {
781
        $this->expectException(\eZ\Publish\Core\Base\Exceptions\UnauthorizedException::class);
782
783
        $permissionResolver = $this->getPermissionResolverMock();
784
        $contentService = $this->getPartlyMockedContentService(['internalLoadContentInfoById']);
785
        $contentInfo = $this->createMock(APIContentInfo::class);
786
787
        $contentInfo->expects($this->any())
788
            ->method('__get')
789
            ->with('id')
790
            ->will($this->returnValue(42));
791
792
        $contentService->expects($this->once())
793
            ->method('internalLoadContentInfoById')
794
            ->with(42)
795
            ->will($this->returnValue($contentInfo));
796
797
        $permissionResolver->expects($this->once())
798
            ->method('canUser')
799
            ->with('content', 'remove')
800
            ->will($this->returnValue(false));
801
802
        /* @var \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo */
803
        $contentService->deleteContent($contentInfo);
804
    }
805
806
    /**
807
     * Test for the deleteContent() method.
808
     *
809
     * @covers \eZ\Publish\Core\Repository\ContentService::deleteContent
810
     */
811
    public function testDeleteContent()
812
    {
813
        $repository = $this->getRepositoryMock();
814
        $permissionResolver = $this->getPermissionResolverMock();
815
816
        $permissionResolver->expects($this->once())
817
            ->method('canUser')
818
            ->with('content', 'remove')
819
            ->will($this->returnValue(true));
820
821
        $contentService = $this->getPartlyMockedContentService(['internalLoadContentInfoById']);
822
        /** @var \PHPUnit\Framework\MockObject\MockObject $urlAliasHandler */
823
        $urlAliasHandler = $this->getPersistenceMock()->urlAliasHandler();
824
        /** @var \PHPUnit\Framework\MockObject\MockObject $locationHandler */
825
        $locationHandler = $this->getPersistenceMock()->locationHandler();
826
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
827
        $contentHandler = $this->getPersistenceMock()->contentHandler();
828
829
        $contentInfo = $this->createMock(APIContentInfo::class);
830
831
        $contentService->expects($this->once())
832
            ->method('internalLoadContentInfoById')
833
            ->with(42)
834
            ->will($this->returnValue($contentInfo));
835
836
        $contentInfo->expects($this->any())
837
            ->method('__get')
838
            ->with('id')
839
            ->will($this->returnValue(42));
840
841
        $repository->expects($this->once())->method('beginTransaction');
842
843
        $spiLocations = [
844
            new SPILocation(['id' => 1]),
845
            new SPILocation(['id' => 2]),
846
        ];
847
        $locationHandler->expects($this->once())
848
            ->method('loadLocationsByContent')
849
            ->with(42)
850
            ->will($this->returnValue($spiLocations));
851
852
        $contentHandler->expects($this->once())
853
            ->method('deleteContent')
854
            ->with(42);
855
856
        foreach ($spiLocations as $index => $spiLocation) {
857
            $urlAliasHandler->expects($this->at($index))
858
                ->method('locationDeleted')
859
                ->with($spiLocation->id);
860
        }
861
862
        $repository->expects($this->once())->method('commit');
863
864
        /* @var \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo */
865
        $contentService->deleteContent($contentInfo);
866
    }
867
868
    /**
869
     * Test for the deleteContent() method.
870
     *
871
     * @covers \eZ\Publish\Core\Repository\ContentService::deleteContent
872
     */
873
    public function testDeleteContentWithRollback()
874
    {
875
        $this->expectException(\Exception::class);
876
877
        $repository = $this->getRepositoryMock();
878
        $permissionResolver = $this->getPermissionResolverMock();
879
880
        $permissionResolver->expects($this->once())
881
            ->method('canUser')
882
            ->with('content', 'remove')
883
            ->will($this->returnValue(true));
884
885
        $contentService = $this->getPartlyMockedContentService(['internalLoadContentInfoById']);
886
        /** @var \PHPUnit\Framework\MockObject\MockObject $locationHandler */
887
        $locationHandler = $this->getPersistenceMock()->locationHandler();
888
889
        $contentInfo = $this->createMock(APIContentInfo::class);
890
891
        $contentService->expects($this->once())
892
            ->method('internalLoadContentInfoById')
893
            ->with(42)
894
            ->will($this->returnValue($contentInfo));
895
896
        $contentInfo->expects($this->any())
897
            ->method('__get')
898
            ->with('id')
899
            ->will($this->returnValue(42));
900
901
        $repository->expects($this->once())->method('beginTransaction');
902
903
        $locationHandler->expects($this->once())
904
            ->method('loadLocationsByContent')
905
            ->with(42)
906
            ->will($this->throwException(new \Exception()));
907
908
        $repository->expects($this->once())->method('rollback');
909
910
        /* @var \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo */
911
        $contentService->deleteContent($contentInfo);
912
    }
913
914
    /**
915
     * Test for the deleteVersion() method.
916
     *
917
     * @covers \eZ\Publish\Core\Repository\ContentService::deleteVersion
918
     */
919
    public function testDeleteVersionThrowsBadStateExceptionLastVersion()
920
    {
921
        $this->expectException(BadStateException::class);
922
923
        $repository = $this->getRepositoryMock();
924
        $permissionResolver = $this->getPermissionResolverMock();
925
926
        $permissionResolver
927
            ->expects($this->once())
928
            ->method('canUser')
929
            ->with('content', 'versionremove')
930
            ->will($this->returnValue(true));
931
        $repository
932
            ->expects($this->never())
933
            ->method('beginTransaction');
934
935
        $contentService = $this->getPartlyMockedContentService();
936
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
937
        $contentHandler = $this->getPersistenceMock()->contentHandler();
938
        $contentInfo = $this->createMock(APIContentInfo::class);
939
        $versionInfo = $this->createMock(APIVersionInfo::class);
940
941
        $contentInfo
942
            ->expects($this->any())
943
            ->method('__get')
944
            ->with('id')
945
            ->will($this->returnValue(42));
946
947
        $versionInfo
948
            ->expects($this->any())
949
            ->method('__get')
950
            ->will(
951
                $this->returnValueMap(
952
                    [
953
                        ['versionNo', 123],
954
                        ['contentInfo', $contentInfo],
955
                    ]
956
                )
957
            );
958
        $versionInfo
959
            ->expects($this->once())
960
            ->method('isPublished')
961
            ->willReturn(false);
962
963
        $contentHandler
964
            ->expects($this->once())
965
            ->method('listVersions')
966
            ->with(42)
967
            ->will($this->returnValue(['version']));
968
969
        /* @var \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo */
970
        $contentService->deleteVersion($versionInfo);
971
    }
972
973
    /**
974
     * Test for the createContent() method.
975
     *
976
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
977
     */
978
    public function testCreateContentThrowsInvalidArgumentExceptionMainLanguageCodeNotSet()
979
    {
980
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\InvalidArgumentException::class);
981
        $this->expectExceptionMessage('Argument \'$contentCreateStruct\' is invalid: the \'mainLanguageCode\' property must be set');
982
983
        $mockedService = $this->getPartlyMockedContentService();
984
        $mockedService->createContent(new ContentCreateStruct(), []);
985
    }
986
987
    /**
988
     * Test for the createContent() method.
989
     *
990
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
991
     */
992
    public function testCreateContentThrowsInvalidArgumentExceptionContentTypeNotSet()
993
    {
994
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\InvalidArgumentException::class);
995
        $this->expectExceptionMessage('Argument \'$contentCreateStruct\' is invalid: the \'contentType\' property must be set');
996
997
        $mockedService = $this->getPartlyMockedContentService();
998
        $mockedService->createContent(
999
            new ContentCreateStruct(['mainLanguageCode' => 'eng-US']),
1000
            []
1001
        );
1002
    }
1003
1004
    /**
1005
     * Test for the createContent() method.
1006
     *
1007
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
1008
     */
1009
    public function testCreateContentThrowsUnauthorizedException()
1010
    {
1011
        $this->expectException(UnauthorizedException::class);
1012
1013
        $repositoryMock = $this->getRepositoryMock();
1014
1015
        $permissionResolver = $this->getPermissionResolverMock();
1016
        $permissionResolver->expects($this->once())
1017
            ->method('getCurrentUserReference')
1018
            ->will($this->returnValue(new UserReference(169)));
1019
1020
        $mockedService = $this->getPartlyMockedContentService();
1021
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
1022
        $contentType = new ContentType(
1023
            [
1024
                'id' => 123,
1025
                'fieldDefinitions' => [],
1026
            ]
1027
        );
1028
        $contentCreateStruct = new ContentCreateStruct(
1029
            [
1030
                'ownerId' => 169,
1031
                'alwaysAvailable' => false,
1032
                'mainLanguageCode' => 'eng-US',
1033
                'contentType' => $contentType,
1034
            ]
1035
        );
1036
1037
        $contentTypeServiceMock->expects($this->once())
1038
            ->method('loadContentType')
1039
            ->with($this->equalTo(123))
1040
            ->will($this->returnValue($contentType));
1041
1042
        $repositoryMock->expects($this->once())
1043
            ->method('getContentTypeService')
1044
            ->will($this->returnValue($contentTypeServiceMock));
1045
1046
        $permissionResolver->expects($this->once())
1047
            ->method('canUser')
1048
            ->with(
1049
                $this->equalTo('content'),
1050
                $this->equalTo('create'),
1051
                $this->isInstanceOf(get_class($contentCreateStruct)),
1052
                $this->equalTo([])
1053
            )->will($this->returnValue(false));
1054
1055
        $mockedService->createContent(
1056
            new ContentCreateStruct(
1057
                [
1058
                    'mainLanguageCode' => 'eng-US',
1059
                    'contentType' => $contentType,
1060
                ]
1061
            ),
1062
            []
1063
        );
1064
    }
1065
1066
    /**
1067
     * Test for the createContent() method.
1068
     *
1069
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
1070
     * @exceptionMessage Argument '$contentCreateStruct' is invalid: Another content with remoteId 'faraday' exists
1071
     */
1072
    public function testCreateContentThrowsInvalidArgumentExceptionDuplicateRemoteId()
1073
    {
1074
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\InvalidArgumentException::class);
1075
1076
        $repositoryMock = $this->getRepositoryMock();
1077
        $permissionResolverMock = $this->getPermissionResolverMock();
1078
        $permissionResolverMock
1079
            ->expects($this->once())
1080
            ->method('getCurrentUserReference')
1081
            ->willReturn($this->createMock(UserReference::class));
1082
1083
        $mockedService = $this->getPartlyMockedContentService(['loadContentByRemoteId']);
1084
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
1085
        $contentType = new ContentType(
1086
            [
1087
                'id' => 123,
1088
                'fieldDefinitions' => [],
1089
            ]
1090
        );
1091
        $contentCreateStruct = new ContentCreateStruct(
1092
            [
1093
                'ownerId' => 169,
1094
                'alwaysAvailable' => false,
1095
                'remoteId' => 'faraday',
1096
                'mainLanguageCode' => 'eng-US',
1097
                'contentType' => $contentType,
1098
            ]
1099
        );
1100
1101
        $contentTypeServiceMock->expects($this->once())
1102
            ->method('loadContentType')
1103
            ->with($this->equalTo(123))
1104
            ->will($this->returnValue($contentType));
1105
1106
        $repositoryMock->expects($this->once())
1107
            ->method('getContentTypeService')
1108
            ->will($this->returnValue($contentTypeServiceMock));
1109
1110
        $permissionResolverMock->expects($this->once())
1111
            ->method('canUser')
1112
            ->with(
1113
                $this->equalTo('content'),
1114
                $this->equalTo('create'),
1115
                $this->isInstanceOf(get_class($contentCreateStruct)),
1116
                $this->equalTo([])
1117
            )->will($this->returnValue(true));
1118
1119
        $mockedService->expects($this->once())
1120
            ->method('loadContentByRemoteId')
1121
            ->with($contentCreateStruct->remoteId)
1122
            ->will($this->returnValue($this->createMock(Content::class)));
1123
1124
        $mockedService->createContent(
1125
            new ContentCreateStruct(
1126
                [
1127
                    'remoteId' => 'faraday',
1128
                    'mainLanguageCode' => 'eng-US',
1129
                    'contentType' => $contentType,
1130
                ]
1131
            ),
1132
            []
1133
        );
1134
    }
1135
1136
    /**
1137
     * @param string $mainLanguageCode
1138
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
1139
     * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition[] $fieldDefinitions
1140
     *
1141
     * @return array
1142
     */
1143
    protected function mapStructFieldsForCreate($mainLanguageCode, $structFields, $fieldDefinitions)
1144
    {
1145
        $mappedFieldDefinitions = [];
1146
        foreach ($fieldDefinitions as $fieldDefinition) {
1147
            $mappedFieldDefinitions[$fieldDefinition->identifier] = $fieldDefinition;
1148
        }
1149
1150
        $mappedStructFields = [];
1151
        foreach ($structFields as $structField) {
1152
            if ($structField->languageCode === null) {
1153
                $languageCode = $mainLanguageCode;
1154
            } else {
1155
                $languageCode = $structField->languageCode;
1156
            }
1157
1158
            $mappedStructFields[$structField->fieldDefIdentifier][$languageCode] = (string)$structField->value;
1159
        }
1160
1161
        return $mappedStructFields;
1162
    }
1163
1164
    /**
1165
     * Returns full, possibly redundant array of field values, indexed by field definition
1166
     * identifier and language code.
1167
     *
1168
     * @param string $mainLanguageCode
1169
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
1170
     * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition[] $fieldDefinitions
1171
     * @param array $languageCodes
1172
     *
1173
     * @return array
1174
     *
1175
     * @throws \RuntimeException Method is intended to be used only with consistent fixtures
1176
     */
1177
    protected function determineValuesForCreate(
1178
        $mainLanguageCode,
1179
        array $structFields,
1180
        array $fieldDefinitions,
1181
        array $languageCodes
1182
    ) {
1183
        $mappedStructFields = $this->mapStructFieldsForCreate(
1184
            $mainLanguageCode,
1185
            $structFields,
1186
            $fieldDefinitions
1187
        );
1188
1189
        $values = [];
1190
1191
        foreach ($fieldDefinitions as $fieldDefinition) {
1192
            $identifier = $fieldDefinition->identifier;
1193
            foreach ($languageCodes as $languageCode) {
1194
                if (!$fieldDefinition->isTranslatable) {
1195
                    if (isset($mappedStructFields[$identifier][$mainLanguageCode])) {
1196
                        $values[$identifier][$languageCode] = $mappedStructFields[$identifier][$mainLanguageCode];
1197
                    } else {
1198
                        $values[$identifier][$languageCode] = (string)$fieldDefinition->defaultValue;
1199
                    }
1200
                    continue;
1201
                }
1202
1203
                if (isset($mappedStructFields[$identifier][$languageCode])) {
1204
                    $values[$identifier][$languageCode] = $mappedStructFields[$identifier][$languageCode];
1205
                    continue;
1206
                }
1207
1208
                $values[$identifier][$languageCode] = (string)$fieldDefinition->defaultValue;
1209
            }
1210
        }
1211
1212
        return $this->stubValues($values);
1213
    }
1214
1215
    /**
1216
     * @param string $mainLanguageCode
1217
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
1218
     *
1219
     * @return string[]
1220
     */
1221
    protected function determineLanguageCodesForCreate($mainLanguageCode, array $structFields)
1222
    {
1223
        $languageCodes = [];
1224
1225
        foreach ($structFields as $field) {
1226
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
1227
                continue;
1228
            }
1229
1230
            $languageCodes[$field->languageCode] = true;
1231
        }
1232
1233
        $languageCodes[$mainLanguageCode] = true;
1234
1235
        return array_keys($languageCodes);
1236
    }
1237
1238
    /**
1239
     * Asserts that calling createContent() with given API field set causes calling
1240
     * Handler::createContent() with given SPI field set.
1241
     *
1242
     * @param string $mainLanguageCode
1243
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
1244
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $spiFields
1245
     * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition[] $fieldDefinitions
1246
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs
1247
     * @param \eZ\Publish\SPI\Persistence\Content\ObjectState\Group[] $objectStateGroups
0 ignored issues
show
Bug introduced by
There is no parameter named $objectStateGroups. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
1248
     * @param bool $execute
1249
     *
1250
     * @return mixed
1251
     */
1252
    protected function assertForTestCreateContentNonRedundantFieldSet(
1253
        $mainLanguageCode,
1254
        array $structFields,
1255
        array $spiFields,
1256
        array $fieldDefinitions,
1257
        array $locationCreateStructs = [],
1258
        $withObjectStates = false,
1259
        $execute = true
1260
    ) {
1261
        $repositoryMock = $this->getRepositoryMock();
1262
        $mockedService = $this->getPartlyMockedContentService();
1263
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandlerMock */
1264
        $contentHandlerMock = $this->getPersistenceMock()->contentHandler();
1265
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
1266
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
1267
        /** @var \PHPUnit\Framework\MockObject\MockObject $objectStateHandlerMock */
1268
        $objectStateHandlerMock = $this->getPersistenceMock()->objectStateHandler();
1269
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
1270
        $domainMapperMock = $this->getContentDomainMapperMock();
1271
        $relationProcessorMock = $this->getRelationProcessorMock();
1272
        $nameSchemaServiceMock = $this->getNameSchemaServiceMock();
1273
        $permissionResolverMock = $this->getPermissionResolverMock();
1274
        $fieldTypeMock = $this->createMock(SPIFieldType::class);
1275
        $languageCodes = $this->determineLanguageCodesForCreate($mainLanguageCode, $structFields);
1276
        $contentType = new ContentType(
1277
            [
1278
                'id' => 123,
1279
                'fieldDefinitions' => new FieldDefinitionCollection($fieldDefinitions),
1280
                'nameSchema' => '<nameSchema>',
1281
            ]
1282
        );
1283
        $contentCreateStruct = new ContentCreateStruct(
1284
            [
1285
                'fields' => $structFields,
1286
                'mainLanguageCode' => $mainLanguageCode,
1287
                'contentType' => $contentType,
1288
                'alwaysAvailable' => false,
1289
                'ownerId' => 169,
1290
                'sectionId' => 1,
1291
            ]
1292
        );
1293
1294
        $languageHandlerMock->expects($this->any())
1295
            ->method('loadByLanguageCode')
1296
            ->with($this->isType('string'))
1297
            ->will(
1298
                $this->returnCallback(
1299
                    function () {
1300
                        return new Language(['id' => 4242]);
1301
                    }
1302
                )
1303
            );
1304
1305
        $repositoryMock->expects($this->once())->method('beginTransaction');
1306
1307
        $contentTypeServiceMock->expects($this->once())
1308
            ->method('loadContentType')
1309
            ->with($this->equalTo($contentType->id))
1310
            ->will($this->returnValue($contentType));
1311
1312
        $repositoryMock->expects($this->once())
1313
            ->method('getContentTypeService')
1314
            ->will($this->returnValue($contentTypeServiceMock));
1315
1316
        $that = $this;
1317
        $permissionResolverMock->expects($this->once())
1318
            ->method('canUser')
1319
            ->with(
1320
                $this->equalTo('content'),
1321
                $this->equalTo('create'),
1322
                $this->isInstanceOf(APIContentCreateStruct::class),
1323
                $this->equalTo($locationCreateStructs)
1324
            )->will(
1325
                $this->returnCallback(
1326
                    function () use ($that, $contentCreateStruct) {
1327
                        $that->assertEquals($contentCreateStruct, func_get_arg(2));
1328
1329
                        return true;
1330
                    }
1331
                )
1332
            );
1333
1334
        $domainMapperMock->expects($this->once())
1335
            ->method('getUniqueHash')
1336
            ->with($this->isInstanceOf(APIContentCreateStruct::class))
1337
            ->will(
1338
                $this->returnCallback(
1339
                    function ($object) use ($that, $contentCreateStruct) {
1340
                        $that->assertEquals($contentCreateStruct, $object);
1341
1342
                        return 'hash';
1343
                    }
1344
                )
1345
            );
1346
1347
        $fieldTypeMock->expects($this->any())
1348
            ->method('acceptValue')
1349
            ->will(
1350
                $this->returnCallback(
1351
                    function ($valueString) {
1352
                        return new ValueStub($valueString);
1353
                    }
1354
                )
1355
            );
1356
1357
        $fieldTypeMock->expects($this->any())
1358
            ->method('toPersistenceValue')
1359
            ->will(
1360
                $this->returnCallback(
1361
                    function (ValueStub $value) {
1362
                        return (string)$value;
1363
                    }
1364
                )
1365
            );
1366
1367
        $emptyValue = self::EMPTY_FIELD_VALUE;
1368
        $fieldTypeMock->expects($this->any())
1369
            ->method('isEmptyValue')
1370
            ->will(
1371
                $this->returnCallback(
1372
                    function (ValueStub $value) use ($emptyValue) {
1373
                        return $emptyValue === (string)$value;
1374
                    }
1375
                )
1376
            );
1377
1378
        $fieldTypeMock->expects($this->any())
1379
            ->method('validate')
1380
            ->will($this->returnValue([]));
1381
1382
        $this->getFieldTypeRegistryMock()->expects($this->any())
1383
            ->method('getFieldType')
1384
            ->will($this->returnValue($fieldTypeMock));
1385
1386
        $relationProcessorMock
1387
            ->expects($this->exactly(count($fieldDefinitions) * count($languageCodes)))
1388
            ->method('appendFieldRelations')
1389
            ->with(
1390
                $this->isType('array'),
1391
                $this->isType('array'),
1392
                $this->isInstanceOf(SPIFieldType::class),
1393
                $this->isInstanceOf(Value::class),
1394
                $this->anything()
1395
            );
1396
1397
        $values = $this->determineValuesForCreate(
1398
            $mainLanguageCode,
1399
            $structFields,
1400
            $fieldDefinitions,
1401
            $languageCodes
1402
        );
1403
        $nameSchemaServiceMock->expects($this->once())
1404
            ->method('resolve')
1405
            ->with(
1406
                $this->equalTo($contentType->nameSchema),
1407
                $this->equalTo($contentType),
1408
                $this->equalTo($values),
1409
                $this->equalTo($languageCodes)
1410
            )->will($this->returnValue([]));
1411
1412
        $relationProcessorMock->expects($this->any())
1413
            ->method('processFieldRelations')
1414
            ->with(
1415
                $this->isType('array'),
1416
                $this->equalTo(42),
1417
                $this->isType('int'),
1418
                $this->equalTo($contentType),
1419
                $this->equalTo([])
1420
            );
1421
1422
        if (!$withObjectStates) {
1423
            $objectStateHandlerMock->expects($this->once())
1424
                ->method('loadAllGroups')
1425
                ->will($this->returnValue([]));
1426
        }
1427
1428
        if ($execute) {
1429
            $spiContentCreateStruct = new SPIContentCreateStruct(
1430
                [
1431
                    'name' => [],
1432
                    'typeId' => 123,
1433
                    'sectionId' => 1,
1434
                    'ownerId' => 169,
1435
                    'remoteId' => 'hash',
1436
                    'fields' => $spiFields,
1437
                    'modified' => time(),
1438
                    'initialLanguageId' => 4242,
1439
                ]
1440
            );
1441
            $spiContentCreateStruct2 = clone $spiContentCreateStruct;
1442
            ++$spiContentCreateStruct2->modified;
1443
1444
            $spiContent = new SPIContent(
1445
                [
1446
                    'versionInfo' => new SPIContent\VersionInfo(
1447
                        [
1448
                            'contentInfo' => new SPIContent\ContentInfo(['id' => 42]),
1449
                            'versionNo' => 7,
1450
                        ]
1451
                    ),
1452
                ]
1453
            );
1454
1455
            $contentHandlerMock->expects($this->once())
1456
                ->method('create')
1457
                ->with($this->logicalOr($spiContentCreateStruct, $spiContentCreateStruct2))
1458
                ->will($this->returnValue($spiContent));
1459
1460
            $repositoryMock->expects($this->once())->method('commit');
1461
            $domainMapperMock->expects($this->once())
1462
                ->method('buildContentDomainObject')
1463
                ->with(
1464
                    $this->isInstanceOf(SPIContent::class),
1465
                    $this->equalTo($contentType)
1466
                )
1467
                ->willReturn($this->createMock(APIContent::class));
1468
1469
            $mockedService->createContent($contentCreateStruct, []);
1470
        }
1471
1472
        return $contentCreateStruct;
1473
    }
1474
1475
    public function providerForTestCreateContentNonRedundantFieldSet1()
1476
    {
1477
        $spiFields = [
1478
            new SPIField(
1479
                [
1480
                    'fieldDefinitionId' => 'fieldDefinitionId',
1481
                    'type' => 'fieldTypeIdentifier',
1482
                    'value' => 'newValue',
1483
                    'languageCode' => 'eng-US',
1484
                ]
1485
            ),
1486
        ];
1487
1488
        return [
1489
            // 0. Without language set
1490
            [
1491
                'eng-US',
1492
                [
1493
                    new Field(
1494
                        [
1495
                            'fieldDefIdentifier' => 'identifier',
1496
                            'value' => 'newValue',
1497
                            'languageCode' => 'eng-US',
1498
                        ]
1499
                    ),
1500
                ],
1501
                $spiFields,
1502
            ],
1503
            // 1. Without language set
1504
            [
1505
                'eng-US',
1506
                [
1507
                    new Field(
1508
                        [
1509
                            'fieldDefIdentifier' => 'identifier',
1510
                            'value' => 'newValue',
1511
                            'languageCode' => null,
1512
                        ]
1513
                    ),
1514
                ],
1515
                $spiFields,
1516
            ],
1517
        ];
1518
    }
1519
1520
    /**
1521
     * Test for the createContent() method.
1522
     *
1523
     * Testing the simplest use case.
1524
     *
1525
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
1526
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
1527
     * @covers \eZ\Publish\Core\Repository\ContentService::cloneField
1528
     * @covers \eZ\Publish\Core\Repository\ContentService::getDefaultObjectStates
1529
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
1530
     * @dataProvider providerForTestCreateContentNonRedundantFieldSet1
1531
     */
1532
    public function testCreateContentNonRedundantFieldSet1($mainLanguageCode, $structFields, $spiFields)
1533
    {
1534
        $fieldDefinitions = [
1535
            new FieldDefinition(
1536
                [
1537
                    'id' => 'fieldDefinitionId',
1538
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
1539
                    'isTranslatable' => false,
1540
                    'identifier' => 'identifier',
1541
                    'isRequired' => false,
1542
                    'defaultValue' => 'defaultValue',
1543
                ]
1544
            ),
1545
        ];
1546
1547
        $this->assertForTestCreateContentNonRedundantFieldSet(
1548
            $mainLanguageCode,
1549
            $structFields,
1550
            $spiFields,
1551
            $fieldDefinitions
1552
        );
1553
    }
1554
1555
    public function providerForTestCreateContentNonRedundantFieldSet2()
1556
    {
1557
        $spiFields = [
1558
            new SPIField(
1559
                [
1560
                    'fieldDefinitionId' => 'fieldDefinitionId1',
1561
                    'type' => 'fieldTypeIdentifier',
1562
                    'value' => 'newValue1',
1563
                    'languageCode' => 'eng-US',
1564
                ]
1565
            ),
1566
            new SPIField(
1567
                [
1568
                    'fieldDefinitionId' => 'fieldDefinitionId2',
1569
                    'type' => 'fieldTypeIdentifier',
1570
                    'value' => 'newValue2',
1571
                    'languageCode' => 'ger-DE',
1572
                ]
1573
            ),
1574
        ];
1575
1576
        return [
1577
            // 0. With language set
1578
            [
1579
                'eng-US',
1580
                [
1581
                    new Field(
1582
                        [
1583
                            'fieldDefIdentifier' => 'identifier1',
1584
                            'value' => 'newValue1',
1585
                            'languageCode' => 'eng-US',
1586
                        ]
1587
                    ),
1588
                    new Field(
1589
                        [
1590
                            'fieldDefIdentifier' => 'identifier2',
1591
                            'value' => 'newValue2',
1592
                            'languageCode' => 'ger-DE',
1593
                        ]
1594
                    ),
1595
                ],
1596
                $spiFields,
1597
            ],
1598
            // 1. Without language set
1599
            [
1600
                'eng-US',
1601
                [
1602
                    new Field(
1603
                        [
1604
                            'fieldDefIdentifier' => 'identifier1',
1605
                            'value' => 'newValue1',
1606
                            'languageCode' => null,
1607
                        ]
1608
                    ),
1609
                    new Field(
1610
                        [
1611
                            'fieldDefIdentifier' => 'identifier2',
1612
                            'value' => 'newValue2',
1613
                            'languageCode' => 'ger-DE',
1614
                        ]
1615
                    ),
1616
                ],
1617
                $spiFields,
1618
            ],
1619
        ];
1620
    }
1621
1622
    /**
1623
     * Test for the createContent() method.
1624
     *
1625
     * Testing multiple languages with multiple translatable fields with empty default value.
1626
     *
1627
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
1628
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
1629
     * @covers \eZ\Publish\Core\Repository\ContentService::cloneField
1630
     * @covers \eZ\Publish\Core\Repository\ContentService::getDefaultObjectStates
1631
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
1632
     * @dataProvider providerForTestCreateContentNonRedundantFieldSet2
1633
     */
1634
    public function testCreateContentNonRedundantFieldSet2($mainLanguageCode, $structFields, $spiFields)
1635
    {
1636
        $fieldDefinitions = [
1637
            new FieldDefinition(
1638
                [
1639
                    'id' => 'fieldDefinitionId1',
1640
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
1641
                    'isTranslatable' => true,
1642
                    'identifier' => 'identifier1',
1643
                    'isRequired' => false,
1644
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
1645
                ]
1646
            ),
1647
            new FieldDefinition(
1648
                [
1649
                    'id' => 'fieldDefinitionId2',
1650
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
1651
                    'isTranslatable' => true,
1652
                    'identifier' => 'identifier2',
1653
                    'isRequired' => false,
1654
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
1655
                ]
1656
            ),
1657
        ];
1658
1659
        $this->assertForTestCreateContentNonRedundantFieldSet(
1660
            $mainLanguageCode,
1661
            $structFields,
1662
            $spiFields,
1663
            $fieldDefinitions
1664
        );
1665
    }
1666
1667
    public function providerForTestCreateContentNonRedundantFieldSetComplex()
1668
    {
1669
        $spiFields0 = [
1670
            new SPIField(
1671
                [
1672
                    'fieldDefinitionId' => 'fieldDefinitionId2',
1673
                    'type' => 'fieldTypeIdentifier',
1674
                    'value' => 'defaultValue2',
1675
                    'languageCode' => 'eng-US',
1676
                ]
1677
            ),
1678
            new SPIField(
1679
                [
1680
                    'fieldDefinitionId' => 'fieldDefinitionId4',
1681
                    'type' => 'fieldTypeIdentifier',
1682
                    'value' => 'defaultValue4',
1683
                    'languageCode' => 'eng-US',
1684
                ]
1685
            ),
1686
        ];
1687
        $spiFields1 = [
1688
            new SPIField(
1689
                [
1690
                    'fieldDefinitionId' => 'fieldDefinitionId1',
1691
                    'type' => 'fieldTypeIdentifier',
1692
                    'value' => 'newValue1',
1693
                    'languageCode' => 'ger-DE',
1694
                ]
1695
            ),
1696
            new SPIField(
1697
                [
1698
                    'fieldDefinitionId' => 'fieldDefinitionId2',
1699
                    'type' => 'fieldTypeIdentifier',
1700
                    'value' => 'defaultValue2',
1701
                    'languageCode' => 'ger-DE',
1702
                ]
1703
            ),
1704
            new SPIField(
1705
                [
1706
                    'fieldDefinitionId' => 'fieldDefinitionId2',
1707
                    'type' => 'fieldTypeIdentifier',
1708
                    'value' => 'newValue2',
1709
                    'languageCode' => 'eng-US',
1710
                ]
1711
            ),
1712
            new SPIField(
1713
                [
1714
                    'fieldDefinitionId' => 'fieldDefinitionId4',
1715
                    'type' => 'fieldTypeIdentifier',
1716
                    'value' => 'newValue4',
1717
                    'languageCode' => 'eng-US',
1718
                ]
1719
            ),
1720
        ];
1721
1722
        return [
1723
            // 0. Creating by default values only
1724
            [
1725
                'eng-US',
1726
                [],
1727
                $spiFields0,
1728
            ],
1729
            // 1. Multiple languages with language set
1730
            [
1731
                'eng-US',
1732
                [
1733
                    new Field(
1734
                        [
1735
                            'fieldDefIdentifier' => 'identifier1',
1736
                            'value' => 'newValue1',
1737
                            'languageCode' => 'ger-DE',
1738
                        ]
1739
                    ),
1740
                    new Field(
1741
                        [
1742
                            'fieldDefIdentifier' => 'identifier2',
1743
                            'value' => 'newValue2',
1744
                            'languageCode' => 'eng-US',
1745
                        ]
1746
                    ),
1747
                    new Field(
1748
                        [
1749
                            'fieldDefIdentifier' => 'identifier4',
1750
                            'value' => 'newValue4',
1751
                            'languageCode' => 'eng-US',
1752
                        ]
1753
                    ),
1754
                ],
1755
                $spiFields1,
1756
            ],
1757
            // 2. Multiple languages without language set
1758
            [
1759
                'eng-US',
1760
                [
1761
                    new Field(
1762
                        [
1763
                            'fieldDefIdentifier' => 'identifier1',
1764
                            'value' => 'newValue1',
1765
                            'languageCode' => 'ger-DE',
1766
                        ]
1767
                    ),
1768
                    new Field(
1769
                        [
1770
                            'fieldDefIdentifier' => 'identifier2',
1771
                            'value' => 'newValue2',
1772
                            'languageCode' => null,
1773
                        ]
1774
                    ),
1775
                    new Field(
1776
                        [
1777
                            'fieldDefIdentifier' => 'identifier4',
1778
                            'value' => 'newValue4',
1779
                            'languageCode' => null,
1780
                        ]
1781
                    ),
1782
                ],
1783
                $spiFields1,
1784
            ],
1785
        ];
1786
    }
1787
1788
    protected function fixturesForTestCreateContentNonRedundantFieldSetComplex()
1789
    {
1790
        return [
1791
            new FieldDefinition(
1792
                [
1793
                    'id' => 'fieldDefinitionId1',
1794
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
1795
                    'isTranslatable' => true,
1796
                    'identifier' => 'identifier1',
1797
                    'isRequired' => false,
1798
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
1799
                ]
1800
            ),
1801
            new FieldDefinition(
1802
                [
1803
                    'id' => 'fieldDefinitionId2',
1804
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
1805
                    'isTranslatable' => true,
1806
                    'identifier' => 'identifier2',
1807
                    'isRequired' => false,
1808
                    'defaultValue' => 'defaultValue2',
1809
                ]
1810
            ),
1811
            new FieldDefinition(
1812
                [
1813
                    'id' => 'fieldDefinitionId3',
1814
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
1815
                    'isTranslatable' => false,
1816
                    'identifier' => 'identifier3',
1817
                    'isRequired' => false,
1818
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
1819
                ]
1820
            ),
1821
            new FieldDefinition(
1822
                [
1823
                    'id' => 'fieldDefinitionId4',
1824
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
1825
                    'isTranslatable' => false,
1826
                    'identifier' => 'identifier4',
1827
                    'isRequired' => false,
1828
                    'defaultValue' => 'defaultValue4',
1829
                ]
1830
            ),
1831
        ];
1832
    }
1833
1834
    /**
1835
     * Test for the createContent() method.
1836
     *
1837
     * Testing multiple languages with multiple translatable fields with empty default value.
1838
     *
1839
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
1840
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
1841
     * @covers \eZ\Publish\Core\Repository\ContentService::cloneField
1842
     * @covers \eZ\Publish\Core\Repository\ContentService::getDefaultObjectStates
1843
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
1844
     * @dataProvider providerForTestCreateContentNonRedundantFieldSetComplex
1845
     */
1846
    public function testCreateContentNonRedundantFieldSetComplex($mainLanguageCode, $structFields, $spiFields)
1847
    {
1848
        $fieldDefinitions = $this->fixturesForTestCreateContentNonRedundantFieldSetComplex();
1849
1850
        $this->assertForTestCreateContentNonRedundantFieldSet(
1851
            $mainLanguageCode,
1852
            $structFields,
1853
            $spiFields,
1854
            $fieldDefinitions
1855
        );
1856
    }
1857
1858
    public function providerForTestCreateContentWithInvalidLanguage()
1859
    {
1860
        return [
1861
            [
1862
                'eng-GB',
1863
                [
1864
                    new Field(
1865
                        [
1866
                            'fieldDefIdentifier' => 'identifier',
1867
                            'value' => 'newValue',
1868
                            'languageCode' => 'Klingon',
1869
                        ]
1870
                    ),
1871
                ],
1872
            ],
1873
            [
1874
                'Klingon',
1875
                [
1876
                    new Field(
1877
                        [
1878
                            'fieldDefIdentifier' => 'identifier',
1879
                            'value' => 'newValue',
1880
                            'languageCode' => 'eng-GB',
1881
                        ]
1882
                    ),
1883
                ],
1884
            ],
1885
        ];
1886
    }
1887
1888
    /**
1889
     * Test for the updateContent() method.
1890
     *
1891
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
1892
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
1893
     * @dataProvider providerForTestCreateContentWithInvalidLanguage
1894
     */
1895
    public function testCreateContentWithInvalidLanguage($mainLanguageCode, $structFields)
1896
    {
1897
        $this->expectException(APINotFoundException::class);
1898
        $this->expectExceptionMessage('Could not find \'Language\' with identifier \'Klingon\'');
1899
1900
        $repositoryMock = $this->getRepositoryMock();
1901
        $mockedService = $this->getPartlyMockedContentService();
1902
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
1903
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
1904
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
1905
        $domainMapperMock = $this->getContentDomainMapperMock();
1906
        $permissionResolver = $this->getPermissionResolverMock();
1907
1908
        $contentType = new ContentType(
1909
            [
1910
                'id' => 123,
1911
                'fieldDefinitions' => [],
1912
            ]
1913
        );
1914
        $contentCreateStruct = new ContentCreateStruct(
1915
            [
1916
                'fields' => $structFields,
1917
                'mainLanguageCode' => $mainLanguageCode,
1918
                'contentType' => $contentType,
1919
                'alwaysAvailable' => false,
1920
                'ownerId' => 169,
1921
                'sectionId' => 1,
1922
            ]
1923
        );
1924
1925
        $languageHandlerMock->expects($this->any())
1926
            ->method('loadByLanguageCode')
1927
            ->with($this->isType('string'))
1928
            ->will(
1929
                $this->returnCallback(
1930
                    function ($languageCode) {
1931
                        if ($languageCode === 'Klingon') {
1932
                            throw new NotFoundException('Language', 'Klingon');
1933
                        }
1934
1935
                        return new Language(['id' => 4242]);
1936
                    }
1937
                )
1938
            );
1939
1940
        $contentTypeServiceMock->expects($this->once())
1941
            ->method('loadContentType')
1942
            ->with($this->equalTo($contentType->id))
1943
            ->will($this->returnValue($contentType));
1944
1945
        $repositoryMock->expects($this->once())
1946
            ->method('getContentTypeService')
1947
            ->will($this->returnValue($contentTypeServiceMock));
1948
1949
        $that = $this;
1950
        $permissionResolver->expects($this->once())
1951
            ->method('canUser')
1952
            ->with(
1953
                $this->equalTo('content'),
1954
                $this->equalTo('create'),
1955
                $this->isInstanceOf(APIContentCreateStruct::class),
1956
                $this->equalTo([])
1957
            )->will(
1958
                $this->returnCallback(
1959
                    function () use ($that, $contentCreateStruct) {
1960
                        $that->assertEquals($contentCreateStruct, func_get_arg(2));
1961
1962
                        return true;
1963
                    }
1964
                )
1965
            );
1966
1967
        $domainMapperMock->expects($this->once())
1968
            ->method('getUniqueHash')
1969
            ->with($this->isInstanceOf(APIContentCreateStruct::class))
1970
            ->will(
1971
                $this->returnCallback(
1972
                    function ($object) use ($that, $contentCreateStruct) {
1973
                        $that->assertEquals($contentCreateStruct, $object);
1974
1975
                        return 'hash';
1976
                    }
1977
                )
1978
            );
1979
1980
        $mockedService->createContent($contentCreateStruct, []);
1981
    }
1982
1983
    protected function assertForCreateContentContentValidationException(
1984
        $mainLanguageCode,
1985
        $structFields,
1986
        $fieldDefinitions = []
1987
    ) {
1988
        $repositoryMock = $this->getRepositoryMock();
1989
        $mockedService = $this->getPartlyMockedContentService(['loadContentByRemoteId']);
1990
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
1991
        $permissionResolver = $this->getPermissionResolverMock();
1992
1993
        $contentType = new ContentType(
1994
            [
1995
                'id' => 123,
1996
                'fieldDefinitions' => new FieldDefinitionCollection($fieldDefinitions),
1997
            ]
1998
        );
1999
        $contentCreateStruct = new ContentCreateStruct(
2000
            [
2001
                'ownerId' => 169,
2002
                'alwaysAvailable' => false,
2003
                'remoteId' => 'faraday',
2004
                'mainLanguageCode' => $mainLanguageCode,
2005
                'fields' => $structFields,
2006
                'contentType' => $contentType,
2007
            ]
2008
        );
2009
2010
        $contentTypeServiceMock->expects($this->once())
2011
            ->method('loadContentType')
2012
            ->with($this->equalTo(123))
2013
            ->will($this->returnValue($contentType));
2014
2015
        $repositoryMock->expects($this->once())
2016
            ->method('getContentTypeService')
2017
            ->will($this->returnValue($contentTypeServiceMock));
2018
2019
        $permissionResolver->expects($this->once())
2020
            ->method('canUser')
2021
            ->with(
2022
                $this->equalTo('content'),
2023
                $this->equalTo('create'),
2024
                $this->isInstanceOf(get_class($contentCreateStruct)),
2025
                $this->equalTo([])
2026
            )->will($this->returnValue(true));
2027
2028
        $mockedService->expects($this->once())
2029
            ->method('loadContentByRemoteId')
2030
            ->with($contentCreateStruct->remoteId)
2031
            ->will(
2032
                $this->throwException(new NotFoundException('Content', 'faraday'))
2033
            );
2034
2035
        $mockedService->createContent($contentCreateStruct, []);
2036
    }
2037
2038
    public function providerForTestCreateContentThrowsContentValidationExceptionFieldDefinition()
2039
    {
2040
        return [
2041
            [
2042
                'eng-GB',
2043
                [
2044
                    new Field(
2045
                        [
2046
                            'fieldDefIdentifier' => 'identifier',
2047
                            'value' => 'newValue',
2048
                            'languageCode' => 'eng-GB',
2049
                        ]
2050
                    ),
2051
                ],
2052
            ],
2053
        ];
2054
    }
2055
2056
    /**
2057
     * Test for the createContent() method.
2058
     *
2059
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
2060
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
2061
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
2062
     * @dataProvider providerForTestCreateContentThrowsContentValidationExceptionFieldDefinition
2063
     */
2064
    public function testCreateContentThrowsContentValidationExceptionFieldDefinition($mainLanguageCode, $structFields)
2065
    {
2066
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\ContentValidationException::class);
2067
        $this->expectExceptionMessage('Field definition \'identifier\' does not exist in the given Content Type');
2068
2069
        $this->assertForCreateContentContentValidationException(
2070
            $mainLanguageCode,
2071
            $structFields,
2072
            []
2073
        );
2074
    }
2075
2076
    public function providerForTestCreateContentThrowsContentValidationExceptionTranslation()
2077
    {
2078
        return [
2079
            [
2080
                'eng-GB',
2081
                [
2082
                    new Field(
2083
                        [
2084
                            'fieldDefIdentifier' => 'identifier',
2085
                            'value' => 'newValue',
2086
                            'languageCode' => 'eng-US',
2087
                        ]
2088
                    ),
2089
                ],
2090
            ],
2091
        ];
2092
    }
2093
2094
    /**
2095
     * Test for the createContent() method.
2096
     *
2097
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
2098
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
2099
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
2100
     * @dataProvider providerForTestCreateContentThrowsContentValidationExceptionTranslation
2101
     */
2102
    public function testCreateContentThrowsContentValidationExceptionTranslation($mainLanguageCode, $structFields)
2103
    {
2104
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\ContentValidationException::class);
2105
        $this->expectExceptionMessage('You cannot set a value for the non-translatable Field definition \'identifier\' in language \'eng-US\'');
2106
2107
        $fieldDefinitions = [
2108
            new FieldDefinition(
2109
                [
2110
                    'id' => 'fieldDefinitionId1',
2111
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
2112
                    'isTranslatable' => false,
2113
                    'identifier' => 'identifier',
2114
                    'isRequired' => false,
2115
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
2116
                ]
2117
            ),
2118
        ];
2119
2120
        $this->assertForCreateContentContentValidationException(
2121
            $mainLanguageCode,
2122
            $structFields,
2123
            $fieldDefinitions
2124
        );
2125
    }
2126
2127
    /**
2128
     * Asserts behaviour necessary for testing ContentFieldValidationException because of required
2129
     * field being empty.
2130
     *
2131
     * @param string $mainLanguageCode
2132
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
2133
     * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition[] $fieldDefinitions
2134
     *
2135
     * @return mixed
2136
     */
2137
    protected function assertForTestCreateContentRequiredField(
2138
        $mainLanguageCode,
2139
        array $structFields,
2140
        array $fieldDefinitions
2141
    ) {
2142
        $repositoryMock = $this->getRepositoryMock();
2143
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
2144
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
2145
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
2146
        $domainMapperMock = $this->getContentDomainMapperMock();
2147
        $fieldTypeMock = $this->createMock(SPIFieldType::class);
2148
        $permissionResolver = $this->getPermissionResolverMock();
2149
2150
        $contentType = new ContentType(
2151
            [
2152
                'id' => 123,
2153
                'fieldDefinitions' => new FieldDefinitionCollection($fieldDefinitions),
2154
                'nameSchema' => '<nameSchema>',
2155
            ]
2156
        );
2157
        $contentCreateStruct = new ContentCreateStruct(
2158
            [
2159
                'fields' => $structFields,
2160
                'mainLanguageCode' => $mainLanguageCode,
2161
                'contentType' => $contentType,
2162
                'alwaysAvailable' => false,
2163
                'ownerId' => 169,
2164
                'sectionId' => 1,
2165
            ]
2166
        );
2167
2168
        $languageHandlerMock->expects($this->any())
2169
            ->method('loadByLanguageCode')
2170
            ->with($this->isType('string'))
2171
            ->will(
2172
                $this->returnCallback(
2173
                    function () {
2174
                        return new Language(['id' => 4242]);
2175
                    }
2176
                )
2177
            );
2178
2179
        $contentTypeServiceMock->expects($this->once())
2180
            ->method('loadContentType')
2181
            ->with($this->equalTo($contentType->id))
2182
            ->will($this->returnValue($contentType));
2183
2184
        $repositoryMock->expects($this->once())
2185
            ->method('getContentTypeService')
2186
            ->will($this->returnValue($contentTypeServiceMock));
2187
2188
        $that = $this;
2189
        $permissionResolver->expects($this->once())
2190
            ->method('canUser')
2191
            ->with(
2192
                $this->equalTo('content'),
2193
                $this->equalTo('create'),
2194
                $this->isInstanceOf(APIContentCreateStruct::class),
2195
                $this->equalTo([])
2196
            )->will(
2197
                $this->returnCallback(
2198
                    function () use ($that, $contentCreateStruct) {
2199
                        $that->assertEquals($contentCreateStruct, func_get_arg(2));
2200
2201
                        return true;
2202
                    }
2203
                )
2204
            );
2205
2206
        $domainMapperMock->expects($this->once())
2207
            ->method('getUniqueHash')
2208
            ->with($this->isInstanceOf(APIContentCreateStruct::class))
2209
            ->will(
2210
                $this->returnCallback(
2211
                    function ($object) use ($that, $contentCreateStruct) {
2212
                        $that->assertEquals($contentCreateStruct, $object);
2213
2214
                        return 'hash';
2215
                    }
2216
                )
2217
            );
2218
2219
        $fieldTypeMock->expects($this->any())
2220
            ->method('acceptValue')
2221
            ->will(
2222
                $this->returnCallback(
2223
                    function ($valueString) {
2224
                        return new ValueStub($valueString);
2225
                    }
2226
                )
2227
            );
2228
2229
        $emptyValue = self::EMPTY_FIELD_VALUE;
2230
        $fieldTypeMock->expects($this->any())
2231
            ->method('isEmptyValue')
2232
            ->will(
2233
                $this->returnCallback(
2234
                    function (ValueStub $value) use ($emptyValue) {
2235
                        return $emptyValue === (string)$value;
2236
                    }
2237
                )
2238
            );
2239
2240
        $fieldTypeMock->expects($this->any())
2241
            ->method('validate')
2242
            ->will($this->returnValue([]));
2243
2244
        $this->getFieldTypeRegistryMock()->expects($this->any())
2245
            ->method('getFieldType')
2246
            ->will($this->returnValue($fieldTypeMock));
2247
2248
        return $contentCreateStruct;
2249
    }
2250
2251
    public function providerForTestCreateContentThrowsContentValidationExceptionRequiredField()
2252
    {
2253
        return [
2254
            [
2255
                'eng-US',
2256
                [
2257
                    new Field(
2258
                        [
2259
                            'fieldDefIdentifier' => 'identifier',
2260
                            'value' => self::EMPTY_FIELD_VALUE,
2261
                            'languageCode' => null,
2262
                        ]
2263
                    ),
2264
                ],
2265
                'identifier',
2266
                'eng-US',
2267
            ],
2268
        ];
2269
    }
2270
2271
    /**
2272
     * Test for the createContent() method.
2273
     *
2274
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
2275
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
2276
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
2277
     * @dataProvider providerForTestCreateContentThrowsContentValidationExceptionRequiredField
2278
     */
2279
    public function testCreateContentRequiredField(
2280
        $mainLanguageCode,
2281
        $structFields,
2282
        $identifier,
2283
        $languageCode
2284
    ) {
2285
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException::class);
2286
2287
        $fieldDefinitions = [
2288
            new FieldDefinition(
2289
                [
2290
                    'id' => 'fieldDefinitionId',
2291
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
2292
                    'isTranslatable' => true,
2293
                    'identifier' => 'identifier',
2294
                    'isRequired' => true,
2295
                    'defaultValue' => 'defaultValue',
2296
                ]
2297
            ),
2298
        ];
2299
        $contentCreateStruct = $this->assertForTestCreateContentRequiredField(
2300
            $mainLanguageCode,
2301
            $structFields,
2302
            $fieldDefinitions
2303
        );
2304
2305
        $mockedService = $this->getPartlyMockedContentService();
2306
2307
        try {
2308
            $mockedService->createContent($contentCreateStruct, []);
2309
        } catch (ContentValidationException $e) {
2310
            $this->assertEquals(
2311
                "Value for required field definition '{$identifier}' with language '{$languageCode}' is empty",
2312
                $e->getMessage()
2313
            );
2314
2315
            throw $e;
2316
        }
2317
    }
2318
2319
    /**
2320
     * Asserts behaviour necessary for testing ContentFieldValidationException because of
2321
     * field not being valid.
2322
     *
2323
     * @param string $mainLanguageCode
2324
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
2325
     * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition[] $fieldDefinitions
2326
     *
2327
     * @return mixed
2328
     */
2329
    protected function assertForTestCreateContentThrowsContentFieldValidationException(
2330
        $mainLanguageCode,
2331
        array $structFields,
2332
        array $fieldDefinitions
2333
    ) {
2334
        $repositoryMock = $this->getRepositoryMock();
2335
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
2336
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
2337
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
2338
        $domainMapperMock = $this->getContentDomainMapperMock();
2339
        $relationProcessorMock = $this->getRelationProcessorMock();
2340
        $fieldTypeMock = $this->createMock(SPIFieldType::class);
2341
        $languageCodes = $this->determineLanguageCodesForCreate($mainLanguageCode, $structFields);
2342
        $permissionResolver = $this->getPermissionResolverMock();
2343
2344
        $contentType = new ContentType(
2345
            [
2346
                'id' => 123,
2347
                'fieldDefinitions' => new FieldDefinitionCollection($fieldDefinitions),
2348
                'nameSchema' => '<nameSchema>',
2349
            ]
2350
        );
2351
        $contentCreateStruct = new ContentCreateStruct(
2352
            [
2353
                'fields' => $structFields,
2354
                'mainLanguageCode' => $mainLanguageCode,
2355
                'contentType' => $contentType,
2356
                'alwaysAvailable' => false,
2357
                'ownerId' => 169,
2358
                'sectionId' => 1,
2359
            ]
2360
        );
2361
2362
        $languageHandlerMock->expects($this->any())
2363
            ->method('loadByLanguageCode')
2364
            ->with($this->isType('string'))
2365
            ->will(
2366
                $this->returnCallback(
2367
                    function () {
2368
                        return new Language(['id' => 4242]);
2369
                    }
2370
                )
2371
            );
2372
2373
        $contentTypeServiceMock->expects($this->once())
2374
            ->method('loadContentType')
2375
            ->with($this->equalTo($contentType->id))
2376
            ->will($this->returnValue($contentType));
2377
2378
        $repositoryMock->expects($this->once())
2379
            ->method('getContentTypeService')
2380
            ->will($this->returnValue($contentTypeServiceMock));
2381
2382
        $that = $this;
2383
        $permissionResolver->expects($this->once())
2384
            ->method('canUser')
2385
            ->with(
2386
                $this->equalTo('content'),
2387
                $this->equalTo('create'),
2388
                $this->isInstanceOf(APIContentCreateStruct::class),
2389
                $this->equalTo([])
2390
            )->will(
2391
                $this->returnCallback(
2392
                    function () use ($that, $contentCreateStruct) {
2393
                        $that->assertEquals($contentCreateStruct, func_get_arg(2));
2394
2395
                        return true;
2396
                    }
2397
                )
2398
            );
2399
2400
        $domainMapperMock->expects($this->once())
2401
            ->method('getUniqueHash')
2402
            ->with($this->isInstanceOf(APIContentCreateStruct::class))
2403
            ->will(
2404
                $this->returnCallback(
2405
                    function ($object) use ($that, $contentCreateStruct) {
2406
                        $that->assertEquals($contentCreateStruct, $object);
2407
2408
                        return 'hash';
2409
                    }
2410
                )
2411
            );
2412
2413
        $this->getFieldTypeRegistryMock()->expects($this->any())
2414
            ->method('getFieldType')
2415
            ->will($this->returnValue($fieldTypeMock));
2416
2417
        $relationProcessorMock
2418
            ->expects($this->any())
2419
            ->method('appendFieldRelations')
2420
            ->with(
2421
                $this->isType('array'),
2422
                $this->isType('array'),
2423
                $this->isInstanceOf(SPIFieldType::class),
2424
                $this->isInstanceOf(Value::class),
2425
                $this->anything()
2426
            );
2427
2428
        $fieldValues = $this->determineValuesForCreate(
2429
            $mainLanguageCode,
2430
            $structFields,
2431
            $fieldDefinitions,
2432
            $languageCodes
2433
        );
2434
        $allFieldErrors = [];
2435
        $validateCount = 0;
2436
        $emptyValue = self::EMPTY_FIELD_VALUE;
2437
        foreach ($contentType->getFieldDefinitions() as $fieldDefinition) {
2438
            foreach ($fieldValues[$fieldDefinition->identifier] as $languageCode => $value) {
2439
                $fieldTypeMock->expects($this->at($validateCount++))
2440
                    ->method('acceptValue')
2441
                    ->will(
2442
                        $this->returnCallback(
2443
                            function ($valueString) {
2444
                                return new ValueStub($valueString);
2445
                            }
2446
                        )
2447
                    );
2448
2449
                $fieldTypeMock->expects($this->at($validateCount++))
2450
                    ->method('isEmptyValue')
2451
                    ->will(
2452
                        $this->returnCallback(
2453
                            function (ValueStub $value) use ($emptyValue) {
2454
                                return $emptyValue === (string)$value;
2455
                            }
2456
                        )
2457
                    );
2458
2459
                if (self::EMPTY_FIELD_VALUE === (string)$value) {
2460
                    continue;
2461
                }
2462
2463
                $fieldTypeMock->expects($this->at($validateCount++))
2464
                    ->method('validate')
2465
                    ->with(
2466
                        $this->equalTo($fieldDefinition),
2467
                        $this->equalTo($value)
2468
                    )->will($this->returnArgument(1));
2469
2470
                $allFieldErrors[$fieldDefinition->id][$languageCode] = $value;
2471
            }
2472
        }
2473
2474
        return [$contentCreateStruct, $allFieldErrors];
2475
    }
2476
2477
    public function providerForTestCreateContentThrowsContentFieldValidationException()
2478
    {
2479
        return $this->providerForTestCreateContentNonRedundantFieldSetComplex();
2480
    }
2481
2482
    /**
2483
     * Test for the createContent() method.
2484
     *
2485
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
2486
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
2487
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
2488
     * @dataProvider providerForTestCreateContentThrowsContentFieldValidationException
2489
     */
2490
    public function testCreateContentThrowsContentFieldValidationException($mainLanguageCode, $structFields)
2491
    {
2492
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException::class);
2493
        $this->expectExceptionMessage('Content Fields did not validate');
2494
2495
        $fieldDefinitions = $this->fixturesForTestCreateContentNonRedundantFieldSetComplex();
2496
        list($contentCreateStruct, $allFieldErrors) =
2497
            $this->assertForTestCreateContentThrowsContentFieldValidationException(
2498
                $mainLanguageCode,
2499
                $structFields,
2500
                $fieldDefinitions
2501
            );
2502
2503
        $mockedService = $this->getPartlyMockedContentService();
2504
2505
        try {
2506
            $mockedService->createContent($contentCreateStruct);
2507
        } catch (ContentFieldValidationException $e) {
2508
            $this->assertEquals($allFieldErrors, $e->getFieldErrors());
2509
            throw $e;
2510
        }
2511
    }
2512
2513
    /**
2514
     * Test for the createContent() method.
2515
     *
2516
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
2517
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
2518
     * @covers \eZ\Publish\Core\Repository\ContentService::buildSPILocationCreateStructs
2519
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
2520
     */
2521
    public function testCreateContentWithLocations()
2522
    {
2523
        $spiFields = [
2524
            new SPIField(
2525
                [
2526
                    'fieldDefinitionId' => 'fieldDefinitionId',
2527
                    'type' => 'fieldTypeIdentifier',
2528
                    'value' => 'defaultValue',
2529
                    'languageCode' => 'eng-US',
2530
                ]
2531
            ),
2532
        ];
2533
        $fieldDefinitions = [
2534
            new FieldDefinition(
2535
                [
2536
                    'id' => 'fieldDefinitionId',
2537
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
2538
                    'isTranslatable' => false,
2539
                    'identifier' => 'identifier',
2540
                    'isRequired' => false,
2541
                    'defaultValue' => 'defaultValue',
2542
                ]
2543
            ),
2544
        ];
2545
2546
        // Set up a simple case that will pass
2547
        $locationCreateStruct1 = new LocationCreateStruct(['parentLocationId' => 321]);
2548
        $locationCreateStruct2 = new LocationCreateStruct(['parentLocationId' => 654]);
2549
        $locationCreateStructs = [$locationCreateStruct1, $locationCreateStruct2];
2550
        $contentCreateStruct = $this->assertForTestCreateContentNonRedundantFieldSet(
2551
            'eng-US',
2552
            [],
2553
            $spiFields,
2554
            $fieldDefinitions,
2555
            $locationCreateStructs,
2556
            false,
2557
            // Do not execute
2558
            false
2559
        );
2560
2561
        $repositoryMock = $this->getRepositoryMock();
2562
        $mockedService = $this->getPartlyMockedContentService();
2563
        $locationServiceMock = $this->getLocationServiceMock();
2564
        /** @var \PHPUnit\Framework\MockObject\MockObject $handlerMock */
2565
        $handlerMock = $this->getPersistenceMock()->contentHandler();
2566
        $domainMapperMock = $this->getContentDomainMapperMock();
2567
        $spiLocationCreateStruct = new SPILocation\CreateStruct();
2568
        $parentLocation = new Location(['contentInfo' => new ContentInfo(['sectionId' => 1])]);
2569
2570
        $locationServiceMock->expects($this->at(0))
2571
            ->method('loadLocation')
2572
            ->with($this->equalTo(321))
2573
            ->will($this->returnValue($parentLocation));
2574
2575
        $locationServiceMock->expects($this->at(1))
2576
            ->method('loadLocation')
2577
            ->with($this->equalTo(654))
2578
            ->will($this->returnValue($parentLocation));
2579
2580
        $repositoryMock->expects($this->atLeastOnce())
2581
            ->method('getLocationService')
2582
            ->will($this->returnValue($locationServiceMock));
2583
2584
        $domainMapperMock->expects($this->at(1))
2585
            ->method('buildSPILocationCreateStruct')
2586
            ->with(
2587
                $this->equalTo($locationCreateStruct1),
2588
                $this->equalTo($parentLocation),
2589
                $this->equalTo(true),
2590
                $this->equalTo(null),
2591
                $this->equalTo(null)
2592
            )->will($this->returnValue($spiLocationCreateStruct));
2593
2594
        $domainMapperMock->expects($this->at(2))
2595
            ->method('buildSPILocationCreateStruct')
2596
            ->with(
2597
                $this->equalTo($locationCreateStruct2),
2598
                $this->equalTo($parentLocation),
2599
                $this->equalTo(false),
2600
                $this->equalTo(null),
2601
                $this->equalTo(null)
2602
            )->will($this->returnValue($spiLocationCreateStruct));
2603
2604
        $spiContentCreateStruct = new SPIContentCreateStruct(
2605
            [
2606
                'name' => [],
2607
                'typeId' => 123,
2608
                'sectionId' => 1,
2609
                'ownerId' => 169,
2610
                'remoteId' => 'hash',
2611
                'fields' => $spiFields,
2612
                'modified' => time(),
2613
                'initialLanguageId' => 4242,
2614
                'locations' => [$spiLocationCreateStruct, $spiLocationCreateStruct],
2615
            ]
2616
        );
2617
        $spiContentCreateStruct2 = clone $spiContentCreateStruct;
2618
        ++$spiContentCreateStruct2->modified;
2619
2620
        $spiContent = new SPIContent(
2621
            [
2622
                'versionInfo' => new SPIContent\VersionInfo(
2623
                    [
2624
                        'contentInfo' => new SPIContent\ContentInfo(['id' => 42]),
2625
                        'versionNo' => 7,
2626
                    ]
2627
                ),
2628
            ]
2629
        );
2630
2631
        $handlerMock->expects($this->once())
2632
            ->method('create')
2633
            ->with($this->logicalOr($spiContentCreateStruct, $spiContentCreateStruct2))
2634
            ->will($this->returnValue($spiContent));
2635
2636
        $domainMapperMock->expects($this->once())
2637
            ->method('buildContentDomainObject')
2638
            ->with(
2639
                $this->isInstanceOf(SPIContent::class),
2640
                $this->isInstanceOf(APIContentType::class)
2641
            )
2642
            ->willReturn($this->createMock(APIContent::class));
2643
2644
        $repositoryMock->expects($this->once())->method('commit');
2645
2646
        // Execute
2647
        $mockedService->createContent($contentCreateStruct, $locationCreateStructs);
2648
    }
2649
2650
    /**
2651
     * Test for the createContent() method.
2652
     *
2653
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
2654
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
2655
     * @covers \eZ\Publish\Core\Repository\ContentService::buildSPILocationCreateStructs
2656
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
2657
     */
2658
    public function testCreateContentWithLocationsDuplicateUnderParent()
2659
    {
2660
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\InvalidArgumentException::class);
2661
        $this->expectExceptionMessage('You provided multiple LocationCreateStructs with the same parent Location \'321\'');
2662
2663
        $fieldDefinitions = [
2664
            new FieldDefinition(
2665
                [
2666
                    'id' => 'fieldDefinitionId',
2667
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
2668
                    'isTranslatable' => false,
2669
                    'identifier' => 'identifier',
2670
                    'isRequired' => false,
2671
                    'defaultValue' => 'defaultValue',
2672
                ]
2673
            ),
2674
        ];
2675
2676
        $repositoryMock = $this->getRepositoryMock();
2677
        $mockedService = $this->getPartlyMockedContentService();
2678
        $locationServiceMock = $this->getLocationServiceMock();
2679
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
2680
        $domainMapperMock = $this->getContentDomainMapperMock();
2681
        $permissionResolver = $this->getPermissionResolverMock();
2682
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
2683
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
2684
        $spiLocationCreateStruct = new SPILocation\CreateStruct();
2685
        $parentLocation = new Location(['id' => 321]);
2686
        $locationCreateStruct = new LocationCreateStruct(['parentLocationId' => 321]);
2687
        $locationCreateStructs = [$locationCreateStruct, clone $locationCreateStruct];
2688
        $contentType = new ContentType(
2689
            [
2690
                'id' => 123,
2691
                'fieldDefinitions' => $fieldDefinitions,
2692
                'nameSchema' => '<nameSchema>',
2693
            ]
2694
        );
2695
        $contentCreateStruct = new ContentCreateStruct(
2696
            [
2697
                'fields' => [],
2698
                'mainLanguageCode' => 'eng-US',
2699
                'contentType' => $contentType,
2700
                'alwaysAvailable' => false,
2701
                'ownerId' => 169,
2702
                'sectionId' => 1,
2703
            ]
2704
        );
2705
2706
        $languageHandlerMock->expects($this->any())
2707
            ->method('loadByLanguageCode')
2708
            ->with($this->isType('string'))
2709
            ->will(
2710
                $this->returnCallback(
2711
                    function () {
2712
                        return new Language(['id' => 4242]);
2713
                    }
2714
                )
2715
            );
2716
2717
        $contentTypeServiceMock->expects($this->once())
2718
            ->method('loadContentType')
2719
            ->with($this->equalTo($contentType->id))
2720
            ->will($this->returnValue($contentType));
2721
2722
        $repositoryMock->expects($this->once())
2723
            ->method('getContentTypeService')
2724
            ->will($this->returnValue($contentTypeServiceMock));
2725
2726
        $that = $this;
2727
        $permissionResolver->expects($this->once())
2728
            ->method('canUser')
2729
            ->with(
2730
                $this->equalTo('content'),
2731
                $this->equalTo('create'),
2732
                $this->isInstanceOf(APIContentCreateStruct::class),
2733
                $this->equalTo($locationCreateStructs)
2734
            )->will(
2735
                $this->returnCallback(
2736
                    function () use ($that, $contentCreateStruct) {
2737
                        $that->assertEquals($contentCreateStruct, func_get_arg(2));
2738
2739
                        return true;
2740
                    }
2741
                )
2742
            );
2743
2744
        $domainMapperMock->expects($this->once())
2745
            ->method('getUniqueHash')
2746
            ->with($this->isInstanceOf(APIContentCreateStruct::class))
2747
            ->will(
2748
                $this->returnCallback(
2749
                    function ($object) use ($that, $contentCreateStruct) {
2750
                        $that->assertEquals($contentCreateStruct, $object);
2751
2752
                        return 'hash';
2753
                    }
2754
                )
2755
            );
2756
2757
        $locationServiceMock->expects($this->once())
2758
            ->method('loadLocation')
2759
            ->with($this->equalTo(321))
2760
            ->will($this->returnValue($parentLocation));
2761
2762
        $repositoryMock->expects($this->any())
2763
            ->method('getLocationService')
2764
            ->will($this->returnValue($locationServiceMock));
2765
2766
        $domainMapperMock->expects($this->any())
2767
            ->method('buildSPILocationCreateStruct')
2768
            ->with(
2769
                $this->equalTo($locationCreateStruct),
2770
                $this->equalTo($parentLocation),
2771
                $this->equalTo(true),
2772
                $this->equalTo(null),
2773
                $this->equalTo(null)
2774
            )->will($this->returnValue($spiLocationCreateStruct));
2775
2776
        $mockedService->createContent(
2777
            $contentCreateStruct,
2778
            $locationCreateStructs
2779
        );
2780
    }
2781
2782
    /**
2783
     * Test for the createContent() method.
2784
     *
2785
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
2786
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
2787
     * @covers \eZ\Publish\Core\Repository\ContentService::getDefaultObjectStates
2788
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
2789
     */
2790
    public function testCreateContentObjectStates()
2791
    {
2792
        $spiFields = [
2793
            new SPIField(
2794
                [
2795
                    'fieldDefinitionId' => 'fieldDefinitionId',
2796
                    'type' => 'fieldTypeIdentifier',
2797
                    'value' => 'defaultValue',
2798
                    'languageCode' => 'eng-US',
2799
                ]
2800
            ),
2801
        ];
2802
        $fieldDefinitions = [
2803
            new FieldDefinition(
2804
                [
2805
                    'id' => 'fieldDefinitionId',
2806
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
2807
                    'isTranslatable' => false,
2808
                    'identifier' => 'identifier',
2809
                    'isRequired' => false,
2810
                    'defaultValue' => 'defaultValue',
2811
                ]
2812
            ),
2813
        ];
2814
        $objectStateGroups = [
0 ignored issues
show
Unused Code introduced by
$objectStateGroups is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2815
            new SPIObjectStateGroup(['id' => 10]),
2816
            new SPIObjectStateGroup(['id' => 20]),
2817
        ];
2818
2819
        // Set up a simple case that will pass
2820
        $contentCreateStruct = $this->assertForTestCreateContentNonRedundantFieldSet(
2821
            'eng-US',
2822
            [],
2823
            $spiFields,
2824
            $fieldDefinitions,
2825
            [],
2826
            true,
2827
            // Do not execute
2828
            false
2829
        );
2830
        $timestamp = time();
2831
        $contentCreateStruct->modificationDate = new \DateTime("@{$timestamp}");
2832
2833
        $repositoryMock = $this->getRepositoryMock();
2834
        $mockedService = $this->getPartlyMockedContentService();
2835
        /** @var \PHPUnit\Framework\MockObject\MockObject $handlerMock */
2836
        $handlerMock = $this->getPersistenceMock()->contentHandler();
2837
        $domainMapperMock = $this->getContentDomainMapperMock();
2838
2839
        $this->mockGetDefaultObjectStates();
2840
        $this->mockSetDefaultObjectStates();
2841
2842
        $spiContentCreateStruct = new SPIContentCreateStruct(
2843
            [
2844
                'name' => [],
2845
                'typeId' => 123,
2846
                'sectionId' => 1,
2847
                'ownerId' => 169,
2848
                'remoteId' => 'hash',
2849
                'fields' => $spiFields,
2850
                'modified' => $timestamp,
2851
                'initialLanguageId' => 4242,
2852
                'locations' => [],
2853
            ]
2854
        );
2855
        $spiContentCreateStruct2 = clone $spiContentCreateStruct;
2856
        ++$spiContentCreateStruct2->modified;
2857
2858
        $spiContent = new SPIContent(
2859
            [
2860
                'versionInfo' => new SPIContent\VersionInfo(
2861
                    [
2862
                        'contentInfo' => new SPIContent\ContentInfo(['id' => 42]),
2863
                        'versionNo' => 7,
2864
                    ]
2865
                ),
2866
            ]
2867
        );
2868
2869
        $handlerMock->expects($this->once())
2870
            ->method('create')
2871
            ->with($this->equalTo($spiContentCreateStruct))
2872
            ->will($this->returnValue($spiContent));
2873
2874
        $domainMapperMock->expects($this->once())
2875
            ->method('buildContentDomainObject')
2876
            ->with(
2877
                $this->isInstanceOf(SPIContent::class),
2878
                $this->isInstanceOf(APIContentType::class)
2879
            )
2880
            ->willReturn($this->createMock(APIContent::class));
2881
2882
        $repositoryMock->expects($this->once())->method('commit');
2883
2884
        // Execute
2885
        $mockedService->createContent($contentCreateStruct, []);
2886
    }
2887
2888
    /**
2889
     * Test for the createContent() method.
2890
     *
2891
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
2892
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
2893
     * @covers \eZ\Publish\Core\Repository\ContentService::getDefaultObjectStates
2894
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
2895
     * @dataProvider providerForTestCreateContentThrowsContentValidationExceptionTranslation
2896
     */
2897
    public function testCreateContentWithRollback()
2898
    {
2899
        $this->expectException(\Exception::class);
2900
        $this->expectExceptionMessage('Store failed');
2901
2902
        $fieldDefinitions = [
2903
            new FieldDefinition(
2904
                [
2905
                    'id' => 'fieldDefinitionId',
2906
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
2907
                    'isTranslatable' => false,
2908
                    'identifier' => 'identifier',
2909
                    'isRequired' => false,
2910
                    'defaultValue' => 'defaultValue',
2911
                ]
2912
            ),
2913
        ];
2914
2915
        // Setup a simple case that will pass
2916
        $contentCreateStruct = $this->assertForTestCreateContentNonRedundantFieldSet(
2917
            'eng-US',
2918
            [],
2919
            [],
2920
            $fieldDefinitions,
2921
            [],
2922
            false,
2923
            // Do not execute test
2924
            false
2925
        );
2926
2927
        $repositoryMock = $this->getRepositoryMock();
2928
        $repositoryMock->expects($this->never())->method('commit');
2929
        $repositoryMock->expects($this->once())->method('rollback');
2930
2931
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandlerMock */
2932
        $contentHandlerMock = $this->getPersistenceMock()->contentHandler();
2933
        $contentHandlerMock->expects($this->once())
2934
            ->method('create')
2935
            ->with($this->anything())
2936
            ->will($this->throwException(new \Exception('Store failed')));
2937
2938
        // Execute
2939
        $this->partlyMockedContentService->createContent($contentCreateStruct, []);
2940
    }
2941
2942
    public function providerForTestUpdateContentThrowsBadStateException()
2943
    {
2944
        return [
2945
            [VersionInfo::STATUS_PUBLISHED],
2946
            [VersionInfo::STATUS_ARCHIVED],
2947
        ];
2948
    }
2949
2950
    /**
2951
     * Test for the updateContent() method.
2952
     *
2953
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
2954
     * @dataProvider providerForTestUpdateContentThrowsBadStateException
2955
     */
2956
    public function testUpdateContentThrowsBadStateException($status)
2957
    {
2958
        $this->expectException(BadStateException::class);
2959
2960
        $versionInfo = new VersionInfo(
2961
            [
2962
                'contentInfo' => new ContentInfo(['id' => 42]),
2963
                'versionNo' => 7,
2964
                'status' => $status,
2965
            ]
2966
        );
2967
        $content = new Content(
2968
            [
2969
                'versionInfo' => $versionInfo,
2970
                'internalFields' => [],
2971
            ]
2972
        );
2973
2974
        $mockedService = $this->getPartlyMockedContentService(['loadContent', 'internalLoadContentById']);
2975
        $mockedService
2976
            ->method('loadContent')
2977
            ->with(
2978
                $this->equalTo(42),
2979
                $this->equalTo(null),
2980
                $this->equalTo(7)
2981
            )->will(
2982
                $this->returnValue($content)
2983
            );
2984
        $mockedService
2985
            ->method('internalLoadContentById')
2986
            ->will(
2987
                $this->returnValue($content)
2988
            );
2989
2990
        $permissionResolverMock = $this->getPermissionResolverMock();
2991
        $permissionResolverMock
2992
            ->method('canUser')
2993
            ->will($this->returnValue(true));
2994
2995
        $contentUpdateStruct = new ContentUpdateStruct();
2996
2997
        $mockedService->updateContent($versionInfo, $contentUpdateStruct);
2998
    }
2999
3000
    /**
3001
     * Test for the updateContent() method.
3002
     *
3003
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
3004
     */
3005
    public function testUpdateContentThrowsUnauthorizedException()
3006
    {
3007
        $this->expectException(UnauthorizedException::class);
3008
3009
        $permissionResolverMock = $this->getPermissionResolverMock();
3010
        $mockedService = $this->getPartlyMockedContentService(['loadContent']);
3011
        $contentUpdateStruct = new ContentUpdateStruct();
3012
        $versionInfo = new VersionInfo(
3013
            [
3014
                'contentInfo' => new ContentInfo(['id' => 42]),
3015
                'versionNo' => 7,
3016
                'status' => VersionInfo::STATUS_DRAFT,
3017
            ]
3018
        );
3019
        $content = new Content(
3020
            [
3021
                'versionInfo' => $versionInfo,
3022
                'internalFields' => [],
3023
            ]
3024
        );
3025
3026
        $mockedService->expects($this->once())
3027
            ->method('loadContent')
3028
            ->with(
3029
                $this->equalTo(42),
3030
                $this->equalTo(null),
3031
                $this->equalTo(7)
3032
            )->will(
3033
                $this->returnValue($content)
3034
            );
3035
3036
        $permissionResolverMock->expects($this->once())
3037
            ->method('canUser')
3038
            ->with(
3039
                $this->equalTo('content'),
3040
                $this->equalTo('edit'),
3041
                $this->equalTo($content),
3042
                $this->isType('array')
3043
            )->will($this->returnValue(false));
3044
3045
        $mockedService->updateContent($versionInfo, $contentUpdateStruct);
3046
    }
3047
3048
    /**
3049
     * @param string $initialLanguageCode
3050
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
3051
     * @param string[] $existingLanguages
3052
     *
3053
     * @return string[]
3054
     */
3055
    protected function determineLanguageCodesForUpdate($initialLanguageCode, array $structFields, $existingLanguages)
3056
    {
3057
        $languageCodes = array_fill_keys($existingLanguages, true);
3058
        if ($initialLanguageCode !== null) {
3059
            $languageCodes[$initialLanguageCode] = true;
3060
        }
3061
3062
        foreach ($structFields as $field) {
3063
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
3064
                continue;
3065
            }
3066
3067
            $languageCodes[$field->languageCode] = true;
3068
        }
3069
3070
        return array_keys($languageCodes);
3071
    }
3072
3073
    /**
3074
     * @param string $initialLanguageCode
3075
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
3076
     * @param string $mainLanguageCode
3077
     * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition[] $fieldDefinitions
3078
     *
3079
     * @return array
3080
     */
3081
    protected function mapStructFieldsForUpdate($initialLanguageCode, $structFields, $mainLanguageCode, $fieldDefinitions)
3082
    {
3083
        $initialLanguageCode = $initialLanguageCode ?: $mainLanguageCode;
3084
3085
        $mappedFieldDefinitions = [];
3086
        foreach ($fieldDefinitions as $fieldDefinition) {
3087
            $mappedFieldDefinitions[$fieldDefinition->identifier] = $fieldDefinition;
3088
        }
3089
3090
        $mappedStructFields = [];
3091
        foreach ($structFields as $structField) {
3092
            $identifier = $structField->fieldDefIdentifier;
3093
3094
            if ($structField->languageCode !== null) {
3095
                $languageCode = $structField->languageCode;
3096
            } elseif ($mappedFieldDefinitions[$identifier]->isTranslatable) {
3097
                $languageCode = $initialLanguageCode;
3098
            } else {
3099
                $languageCode = $mainLanguageCode;
3100
            }
3101
3102
            $mappedStructFields[$identifier][$languageCode] = (string)$structField->value;
3103
        }
3104
3105
        return $mappedStructFields;
3106
    }
3107
3108
    /**
3109
     * Returns full, possibly redundant array of field values, indexed by field definition
3110
     * identifier and language code.
3111
     *
3112
     * @param string $initialLanguageCode
3113
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
3114
     * @param \eZ\Publish\Core\Repository\Values\Content\Content $content
3115
     * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition[] $fieldDefinitions
3116
     * @param array $languageCodes
3117
     *
3118
     * @return array
3119
     */
3120
    protected function determineValuesForUpdate(
3121
        $initialLanguageCode,
3122
        array $structFields,
3123
        Content $content,
3124
        array $fieldDefinitions,
3125
        array $languageCodes
3126
    ) {
3127
        $mainLanguageCode = $content->versionInfo->contentInfo->mainLanguageCode;
3128
3129
        $mappedStructFields = $this->mapStructFieldsForUpdate(
3130
            $initialLanguageCode,
3131
            $structFields,
3132
            $mainLanguageCode,
3133
            $fieldDefinitions
3134
        );
3135
3136
        $values = [];
3137
3138
        foreach ($fieldDefinitions as $fieldDefinition) {
3139
            $identifier = $fieldDefinition->identifier;
3140
            foreach ($languageCodes as $languageCode) {
3141
                if (!$fieldDefinition->isTranslatable) {
3142
                    if (isset($mappedStructFields[$identifier][$mainLanguageCode])) {
3143
                        $values[$identifier][$languageCode] = $mappedStructFields[$identifier][$mainLanguageCode];
3144
                    } else {
3145
                        $values[$identifier][$languageCode] = (string)$content->fields[$identifier][$mainLanguageCode];
3146
                    }
3147
                    continue;
3148
                }
3149
3150
                if (isset($mappedStructFields[$identifier][$languageCode])) {
3151
                    $values[$identifier][$languageCode] = $mappedStructFields[$identifier][$languageCode];
3152
                    continue;
3153
                }
3154
3155
                if (isset($content->fields[$identifier][$languageCode])) {
3156
                    $values[$identifier][$languageCode] = (string)$content->fields[$identifier][$languageCode];
3157
                    continue;
3158
                }
3159
3160
                $values[$identifier][$languageCode] = (string)$fieldDefinition->defaultValue;
3161
            }
3162
        }
3163
3164
        return $this->stubValues($values);
3165
    }
3166
3167
    protected function stubValues(array $fieldValues)
3168
    {
3169
        foreach ($fieldValues as &$languageValues) {
3170
            foreach ($languageValues as &$value) {
3171
                $value = new ValueStub($value);
3172
            }
3173
        }
3174
3175
        return $fieldValues;
3176
    }
3177
3178
    /**
3179
     * Asserts that calling updateContent() with given API field set causes calling
3180
     * Handler::updateContent() with given SPI field set.
3181
     *
3182
     * @param string $initialLanguageCode
3183
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
3184
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $spiFields
3185
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $existingFields
3186
     * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition[] $fieldDefinitions
3187
     * @param bool $execute
3188
     *
3189
     * @return mixed
3190
     */
3191
    protected function assertForTestUpdateContentNonRedundantFieldSet(
3192
        $initialLanguageCode,
3193
        array $structFields,
3194
        array $spiFields,
3195
        array $existingFields,
3196
        array $fieldDefinitions,
3197
        $execute = true
3198
    ) {
3199
        $repositoryMock = $this->getRepositoryMock();
3200
        $permissionResolverMock = $this->getPermissionResolverMock();
3201
        $permissionResolverMock
3202
            ->expects($this->once())
3203
            ->method('getCurrentUserReference')
3204
            ->willReturn(new UserReference(169));
3205
        $mockedService = $this->getPartlyMockedContentService(['internalLoadContentById', 'internalLoadRelations'], $permissionResolverMock);
3206
        $permissionResolverMock = $this->getPermissionResolverMock();
3207
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandlerMock */
3208
        $contentHandlerMock = $this->getPersistenceMock()->contentHandler();
3209
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
3210
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
3211
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
3212
        $domainMapperMock = $this->getContentDomainMapperMock();
3213
        $relationProcessorMock = $this->getRelationProcessorMock();
3214
        $nameSchemaServiceMock = $this->getNameSchemaServiceMock();
3215
        $fieldTypeMock = $this->createMock(SPIFieldType::class);
3216
        $existingLanguageCodes = array_map(
3217
            function (Field $field) {
3218
                return $field->languageCode;
3219
            },
3220
            $existingFields
3221
        );
3222
        $languageCodes = $this->determineLanguageCodesForUpdate(
3223
            $initialLanguageCode,
3224
            $structFields,
3225
            $existingLanguageCodes
3226
        );
3227
        $versionInfo = new VersionInfo(
3228
            [
3229
                'contentInfo' => new ContentInfo(
3230
                    [
3231
                        'id' => 42,
3232
                        'contentTypeId' => 24,
3233
                        'mainLanguageCode' => 'eng-GB',
3234
                    ]
3235
                ),
3236
                'versionNo' => 7,
3237
                'languageCodes' => $existingLanguageCodes,
3238
                'status' => VersionInfo::STATUS_DRAFT,
3239
            ]
3240
        );
3241
        $content = new Content(
3242
            [
3243
                'versionInfo' => $versionInfo,
3244
                'internalFields' => $existingFields,
3245
            ]
3246
        );
3247
        $contentType = new ContentType([
3248
            'fieldDefinitions' => new FieldDefinitionCollection($fieldDefinitions),
3249
        ]);
3250
3251
        $languageHandlerMock->expects($this->any())
3252
            ->method('loadByLanguageCode')
3253
            ->with($this->isType('string'))
3254
            ->will(
3255
                $this->returnCallback(
3256
                    function () {
3257
                        return new Language(['id' => 4242]);
3258
                    }
3259
                )
3260
            );
3261
3262
        $mockedService
3263
            ->method('internalLoadContentById')
3264
            ->with(
3265
                $this->equalTo(42),
3266
                $this->equalTo(null),
3267
                $this->equalTo(7)
3268
            )->will(
3269
                $this->returnValue($content)
3270
            );
3271
3272
        $repositoryMock->expects($this->once())->method('beginTransaction');
3273
3274
        $permissionResolverMock->expects($this->any())
3275
            ->method('canUser')
3276
            ->will($this->returnValue(true));
3277
3278
        $contentTypeServiceMock->expects($this->once())
3279
            ->method('loadContentType')
3280
            ->with($this->equalTo(24))
3281
            ->will($this->returnValue($contentType));
3282
3283
        $repositoryMock->expects($this->once())
3284
            ->method('getContentTypeService')
3285
            ->will($this->returnValue($contentTypeServiceMock));
3286
3287
        $fieldTypeMock->expects($this->any())
3288
            ->method('acceptValue')
3289
            ->will(
3290
                $this->returnCallback(
3291
                    function ($valueString) {
3292
                        return new ValueStub($valueString);
3293
                    }
3294
                )
3295
            );
3296
3297
        $emptyValue = self::EMPTY_FIELD_VALUE;
3298
        $fieldTypeMock->expects($this->any())
3299
            ->method('toPersistenceValue')
3300
            ->will(
3301
                $this->returnCallback(
3302
                    function (ValueStub $value) {
3303
                        return (string)$value;
3304
                    }
3305
                )
3306
            );
3307
3308
        $fieldTypeMock->expects($this->any())
3309
            ->method('isEmptyValue')
3310
            ->will(
3311
                $this->returnCallback(
3312
                    function (ValueStub $value) use ($emptyValue) {
3313
                        return $emptyValue === (string)$value;
3314
                    }
3315
                )
3316
            );
3317
3318
        $fieldTypeMock->expects($this->any())
3319
            ->method('validate')
3320
            ->will($this->returnValue([]));
3321
3322
        $this->getFieldTypeRegistryMock()->expects($this->any())
3323
            ->method('getFieldType')
3324
            ->will($this->returnValue($fieldTypeMock));
3325
3326
        $relationProcessorMock
3327
            ->expects($this->exactly(count($fieldDefinitions) * count($languageCodes)))
3328
            ->method('appendFieldRelations')
3329
            ->with(
3330
                $this->isType('array'),
3331
                $this->isType('array'),
3332
                $this->isInstanceOf(SPIFieldType::class),
3333
                $this->isInstanceOf(Value::class),
3334
                $this->anything()
3335
            );
3336
3337
        $values = $this->determineValuesForUpdate(
3338
            $initialLanguageCode,
3339
            $structFields,
3340
            $content,
3341
            $fieldDefinitions,
3342
            $languageCodes
3343
        );
3344
        $nameSchemaServiceMock->expects($this->once())
3345
            ->method('resolveNameSchema')
3346
            ->with(
3347
                $this->equalTo($content),
3348
                $this->equalTo($values),
3349
                $this->equalTo($languageCodes)
3350
            )->will($this->returnValue([]));
3351
3352
        $existingRelations = ['RELATIONS!!!'];
3353
        $mockedService
3354
            ->method('internalLoadRelations')
3355
            ->with($content->versionInfo)
3356
            ->will($this->returnValue($existingRelations));
3357
        $relationProcessorMock->expects($this->any())
3358
            ->method('processFieldRelations')
3359
            ->with(
3360
                $this->isType('array'),
3361
                $this->equalTo(42),
3362
                $this->isType('int'),
3363
                $this->equalTo($contentType),
3364
                $this->equalTo($existingRelations)
3365
            );
3366
3367
        $contentUpdateStruct = new ContentUpdateStruct(
3368
            [
3369
                'fields' => $structFields,
3370
                'initialLanguageCode' => $initialLanguageCode,
3371
            ]
3372
        );
3373
3374
        if ($execute) {
3375
            $spiContentUpdateStruct = new SPIContentUpdateStruct(
3376
                [
3377
                    'creatorId' => 169,
3378
                    'fields' => $spiFields,
3379
                    'modificationDate' => time(),
3380
                    'initialLanguageId' => 4242,
3381
                ]
3382
            );
3383
3384
            // During code coverage runs, timestamp might differ 1-3 seconds
3385
            $spiContentUpdateStructTs1 = clone $spiContentUpdateStruct;
3386
            ++$spiContentUpdateStructTs1->modificationDate;
3387
3388
            $spiContentUpdateStructTs2 = clone $spiContentUpdateStructTs1;
3389
            ++$spiContentUpdateStructTs2->modificationDate;
3390
3391
            $spiContentUpdateStructTs3 = clone $spiContentUpdateStructTs2;
3392
            ++$spiContentUpdateStructTs3->modificationDate;
3393
3394
            $spiContent = new SPIContent(
3395
                [
3396
                    'versionInfo' => new SPIContent\VersionInfo(
3397
                        [
3398
                            'contentInfo' => new SPIContent\ContentInfo(['id' => 42]),
3399
                            'versionNo' => 7,
3400
                        ]
3401
                    ),
3402
                ]
3403
            );
3404
3405
            $contentHandlerMock->expects($this->once())
3406
                ->method('updateContent')
3407
                ->with(
3408
                    42,
3409
                    7,
3410
                    $this->logicalOr($spiContentUpdateStruct, $spiContentUpdateStructTs1, $spiContentUpdateStructTs2, $spiContentUpdateStructTs3)
3411
                )
3412
                ->will($this->returnValue($spiContent));
3413
3414
            $repositoryMock->expects($this->once())->method('commit');
3415
            $domainMapperMock
3416
                ->method('buildContentDomainObject')
3417
                ->with(
3418
                    $this->isInstanceOf(SPIContent::class),
3419
                    $this->isInstanceOf(APIContentType::class)
3420
                )
3421
                ->will($this->returnValue($content));
3422
3423
            $mockedService->updateContent($content->versionInfo, $contentUpdateStruct);
3424
        }
3425
3426
        return [$content->versionInfo, $contentUpdateStruct];
3427
    }
3428
3429
    public function providerForTestUpdateContentNonRedundantFieldSet1()
3430
    {
3431
        $spiFields = [
3432
            new SPIField(
3433
                [
3434
                    'id' => '100',
3435
                    'fieldDefinitionId' => 'fieldDefinitionId',
3436
                    'type' => 'fieldTypeIdentifier',
3437
                    'value' => 'newValue',
3438
                    'languageCode' => 'eng-GB',
3439
                    'versionNo' => 7,
3440
                ]
3441
            ),
3442
        ];
3443
3444
        return [
3445
            // With languages set
3446
            [
3447
                'eng-GB',
3448
                [
3449
                    new Field(
3450
                        [
3451
                            'fieldDefIdentifier' => 'identifier',
3452
                            'value' => 'newValue',
3453
                            'languageCode' => 'eng-GB',
3454
                        ]
3455
                    ),
3456
                ],
3457
                $spiFields,
3458
            ],
3459
            // Without languages set
3460
            [
3461
                null,
3462
                [
3463
                    new Field(
3464
                        [
3465
                            'fieldDefIdentifier' => 'identifier',
3466
                            'value' => 'newValue',
3467
                            'languageCode' => null,
3468
                        ]
3469
                    ),
3470
                ],
3471
                $spiFields,
3472
            ],
3473
            // Adding new language without fields
3474
            [
3475
                'eng-US',
3476
                [],
3477
                [],
3478
            ],
3479
        ];
3480
    }
3481
3482
    /**
3483
     * Test for the updateContent() method.
3484
     *
3485
     * Testing the simplest use case.
3486
     *
3487
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
3488
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
3489
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
3490
     * @dataProvider providerForTestUpdateContentNonRedundantFieldSet1
3491
     */
3492
    public function testUpdateContentNonRedundantFieldSet1($initialLanguageCode, $structFields, $spiFields)
3493
    {
3494
        $existingFields = [
3495
            new Field(
3496
                [
3497
                    'id' => '100',
3498
                    'fieldDefIdentifier' => 'identifier',
3499
                    'value' => 'initialValue',
3500
                    'languageCode' => 'eng-GB',
3501
                ]
3502
            ),
3503
        ];
3504
3505
        $fieldDefinitions = [
3506
            new FieldDefinition(
3507
                [
3508
                    'id' => 'fieldDefinitionId',
3509
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
3510
                    'isTranslatable' => false,
3511
                    'identifier' => 'identifier',
3512
                    'isRequired' => false,
3513
                    'defaultValue' => 'defaultValue',
3514
                ]
3515
            ),
3516
        ];
3517
3518
        $this->assertForTestUpdateContentNonRedundantFieldSet(
3519
            $initialLanguageCode,
3520
            $structFields,
3521
            $spiFields,
3522
            $existingFields,
3523
            $fieldDefinitions
3524
        );
3525
    }
3526
3527
    public function providerForTestUpdateContentNonRedundantFieldSet2()
3528
    {
3529
        $spiFields0 = [
3530
            new SPIField(
3531
                [
3532
                    'id' => '100',
3533
                    'fieldDefinitionId' => 'fieldDefinitionId',
3534
                    'type' => 'fieldTypeIdentifier',
3535
                    'value' => 'newValue',
3536
                    'languageCode' => 'eng-GB',
3537
                    'versionNo' => 7,
3538
                ]
3539
            ),
3540
        ];
3541
        $spiFields1 = [
3542
            new SPIField(
3543
                [
3544
                    'id' => null,
3545
                    'fieldDefinitionId' => 'fieldDefinitionId',
3546
                    'type' => 'fieldTypeIdentifier',
3547
                    'value' => 'newValue',
3548
                    'languageCode' => 'eng-US',
3549
                    'versionNo' => 7,
3550
                ]
3551
            ),
3552
        ];
3553
        $spiFields2 = [
3554
            new SPIField(
3555
                [
3556
                    'id' => 100,
3557
                    'fieldDefinitionId' => 'fieldDefinitionId',
3558
                    'type' => 'fieldTypeIdentifier',
3559
                    'value' => 'newValue2',
3560
                    'languageCode' => 'eng-GB',
3561
                    'versionNo' => 7,
3562
                ]
3563
            ),
3564
            new SPIField(
3565
                [
3566
                    'id' => null,
3567
                    'fieldDefinitionId' => 'fieldDefinitionId',
3568
                    'type' => 'fieldTypeIdentifier',
3569
                    'value' => 'newValue1',
3570
                    'languageCode' => 'eng-US',
3571
                    'versionNo' => 7,
3572
                ]
3573
            ),
3574
        ];
3575
3576
        return [
3577
            // 0. With languages set
3578
            [
3579
                'eng-GB',
3580
                [
3581
                    new Field(
3582
                        [
3583
                            'fieldDefIdentifier' => 'identifier',
3584
                            'value' => 'newValue',
3585
                            'languageCode' => 'eng-GB',
3586
                        ]
3587
                    ),
3588
                ],
3589
                $spiFields0,
3590
            ],
3591
            // 1. Without languages set
3592
            [
3593
                null,
3594
                [
3595
                    new Field(
3596
                        [
3597
                            'fieldDefIdentifier' => 'identifier',
3598
                            'value' => 'newValue',
3599
                            'languageCode' => null,
3600
                        ]
3601
                    ),
3602
                ],
3603
                $spiFields0,
3604
            ],
3605
            // 2. New language with language set
3606
            [
3607
                'eng-GB',
3608
                [
3609
                    new Field(
3610
                        [
3611
                            'fieldDefIdentifier' => 'identifier',
3612
                            'value' => 'newValue',
3613
                            'languageCode' => 'eng-US',
3614
                        ]
3615
                    ),
3616
                ],
3617
                $spiFields1,
3618
            ],
3619
            // 3. New language without language set
3620
            [
3621
                'eng-US',
3622
                [
3623
                    new Field(
3624
                        [
3625
                            'fieldDefIdentifier' => 'identifier',
3626
                            'value' => 'newValue',
3627
                            'languageCode' => null,
3628
                        ]
3629
                    ),
3630
                ],
3631
                $spiFields1,
3632
            ],
3633
            // 4. New language and existing language with language set
3634
            [
3635
                'eng-GB',
3636
                [
3637
                    new Field(
3638
                        [
3639
                            'fieldDefIdentifier' => 'identifier',
3640
                            'value' => 'newValue1',
3641
                            'languageCode' => 'eng-US',
3642
                        ]
3643
                    ),
3644
                    new Field(
3645
                        [
3646
                            'fieldDefIdentifier' => 'identifier',
3647
                            'value' => 'newValue2',
3648
                            'languageCode' => 'eng-GB',
3649
                        ]
3650
                    ),
3651
                ],
3652
                $spiFields2,
3653
            ],
3654
            // 5. New language and existing language without language set
3655
            [
3656
                'eng-US',
3657
                [
3658
                    new Field(
3659
                        [
3660
                            'fieldDefIdentifier' => 'identifier',
3661
                            'value' => 'newValue1',
3662
                            'languageCode' => null,
3663
                        ]
3664
                    ),
3665
                    new Field(
3666
                        [
3667
                            'fieldDefIdentifier' => 'identifier',
3668
                            'value' => 'newValue2',
3669
                            'languageCode' => 'eng-GB',
3670
                        ]
3671
                    ),
3672
                ],
3673
                $spiFields2,
3674
            ],
3675
            // 6. Adding new language without fields
3676
            [
3677
                'eng-US',
3678
                [],
3679
                [
3680
                    new SPIField(
3681
                        [
3682
                            'id' => null,
3683
                            'fieldDefinitionId' => 'fieldDefinitionId',
3684
                            'type' => 'fieldTypeIdentifier',
3685
                            'value' => 'defaultValue',
3686
                            'languageCode' => 'eng-US',
3687
                            'versionNo' => 7,
3688
                        ]
3689
                    ),
3690
                ],
3691
            ],
3692
        ];
3693
    }
3694
3695
    /**
3696
     * Test for the updateContent() method.
3697
     *
3698
     * Testing with translatable field.
3699
     *
3700
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
3701
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
3702
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
3703
     * @dataProvider providerForTestUpdateContentNonRedundantFieldSet2
3704
     */
3705
    public function testUpdateContentNonRedundantFieldSet2($initialLanguageCode, $structFields, $spiFields)
3706
    {
3707
        $existingFields = [
3708
            new Field(
3709
                [
3710
                    'id' => '100',
3711
                    'fieldDefIdentifier' => 'identifier',
3712
                    'value' => 'initialValue',
3713
                    'languageCode' => 'eng-GB',
3714
                ]
3715
            ),
3716
        ];
3717
3718
        $fieldDefinitions = [
3719
            new FieldDefinition(
3720
                [
3721
                    'id' => 'fieldDefinitionId',
3722
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
3723
                    'isTranslatable' => true,
3724
                    'identifier' => 'identifier',
3725
                    'isRequired' => false,
3726
                    'defaultValue' => 'defaultValue',
3727
                ]
3728
            ),
3729
        ];
3730
3731
        $this->assertForTestUpdateContentNonRedundantFieldSet(
3732
            $initialLanguageCode,
3733
            $structFields,
3734
            $spiFields,
3735
            $existingFields,
3736
            $fieldDefinitions
3737
        );
3738
    }
3739
3740
    public function providerForTestUpdateContentNonRedundantFieldSet3()
3741
    {
3742
        $spiFields0 = [
3743
            new SPIField(
3744
                [
3745
                    'id' => null,
3746
                    'fieldDefinitionId' => 'fieldDefinitionId1',
3747
                    'type' => 'fieldTypeIdentifier',
3748
                    'value' => 'newValue1',
3749
                    'languageCode' => 'eng-US',
3750
                    'versionNo' => 7,
3751
                ]
3752
            ),
3753
        ];
3754
        $spiFields1 = [
3755
            new SPIField(
3756
                [
3757
                    'id' => 100,
3758
                    'fieldDefinitionId' => 'fieldDefinitionId1',
3759
                    'type' => 'fieldTypeIdentifier',
3760
                    'value' => 'newValue2',
3761
                    'languageCode' => 'eng-GB',
3762
                    'versionNo' => 7,
3763
                ]
3764
            ),
3765
            new SPIField(
3766
                [
3767
                    'id' => null,
3768
                    'fieldDefinitionId' => 'fieldDefinitionId1',
3769
                    'type' => 'fieldTypeIdentifier',
3770
                    'value' => 'newValue1',
3771
                    'languageCode' => 'eng-US',
3772
                    'versionNo' => 7,
3773
                ]
3774
            ),
3775
        ];
3776
        $spiFields2 = [
3777
            new SPIField(
3778
                [
3779
                    'id' => 100,
3780
                    'fieldDefinitionId' => 'fieldDefinitionId1',
3781
                    'type' => 'fieldTypeIdentifier',
3782
                    'value' => 'newValue2',
3783
                    'languageCode' => 'eng-GB',
3784
                    'versionNo' => 7,
3785
                ]
3786
            ),
3787
            new SPIField(
3788
                [
3789
                    'id' => null,
3790
                    'fieldDefinitionId' => 'fieldDefinitionId1',
3791
                    'type' => 'fieldTypeIdentifier',
3792
                    'value' => 'newValue1',
3793
                    'languageCode' => 'eng-US',
3794
                    'versionNo' => 7,
3795
                ]
3796
            ),
3797
            new SPIField(
3798
                [
3799
                    'id' => 101,
3800
                    'fieldDefinitionId' => 'fieldDefinitionId2',
3801
                    'type' => 'fieldTypeIdentifier',
3802
                    'value' => 'newValue3',
3803
                    'languageCode' => 'eng-GB',
3804
                    'versionNo' => 7,
3805
                ]
3806
            ),
3807
        ];
3808
        $spiFields3 = [
3809
            new SPIField(
3810
                [
3811
                    'id' => null,
3812
                    'fieldDefinitionId' => 'fieldDefinitionId1',
3813
                    'type' => 'fieldTypeIdentifier',
3814
                    'value' => 'defaultValue1',
3815
                    'languageCode' => 'eng-US',
3816
                    'versionNo' => 7,
3817
                ]
3818
            ),
3819
        ];
3820
3821
        return [
3822
            // 0. ew language with language set
3823
            [
3824
                'eng-US',
3825
                [
3826
                    new Field(
3827
                        [
3828
                            'fieldDefIdentifier' => 'identifier1',
3829
                            'value' => 'newValue1',
3830
                            'languageCode' => 'eng-US',
3831
                        ]
3832
                    ),
3833
                ],
3834
                $spiFields0,
3835
            ],
3836
            // 1. New language without language set
3837
            [
3838
                'eng-US',
3839
                [
3840
                    new Field(
3841
                        [
3842
                            'fieldDefIdentifier' => 'identifier1',
3843
                            'value' => 'newValue1',
3844
                            'languageCode' => null,
3845
                        ]
3846
                    ),
3847
                ],
3848
                $spiFields0,
3849
            ],
3850
            // 2. New language and existing language with language set
3851
            [
3852
                'eng-US',
3853
                [
3854
                    new Field(
3855
                        [
3856
                            'fieldDefIdentifier' => 'identifier1',
3857
                            'value' => 'newValue1',
3858
                            'languageCode' => 'eng-US',
3859
                        ]
3860
                    ),
3861
                    new Field(
3862
                        [
3863
                            'fieldDefIdentifier' => 'identifier1',
3864
                            'value' => 'newValue2',
3865
                            'languageCode' => 'eng-GB',
3866
                        ]
3867
                    ),
3868
                ],
3869
                $spiFields1,
3870
            ],
3871
            // 3. New language and existing language without language set
3872
            [
3873
                'eng-US',
3874
                [
3875
                    new Field(
3876
                        [
3877
                            'fieldDefIdentifier' => 'identifier1',
3878
                            'value' => 'newValue1',
3879
                            'languageCode' => null,
3880
                        ]
3881
                    ),
3882
                    new Field(
3883
                        [
3884
                            'fieldDefIdentifier' => 'identifier1',
3885
                            'value' => 'newValue2',
3886
                            'languageCode' => 'eng-GB',
3887
                        ]
3888
                    ),
3889
                ],
3890
                $spiFields1,
3891
            ],
3892
            // 4. New language and existing language with untranslatable field, with language set
3893
            [
3894
                'eng-US',
3895
                [
3896
                    new Field(
3897
                        [
3898
                            'fieldDefIdentifier' => 'identifier1',
3899
                            'value' => 'newValue1',
3900
                            'languageCode' => 'eng-US',
3901
                        ]
3902
                    ),
3903
                    new Field(
3904
                        [
3905
                            'fieldDefIdentifier' => 'identifier1',
3906
                            'value' => 'newValue2',
3907
                            'languageCode' => 'eng-GB',
3908
                        ]
3909
                    ),
3910
                    new Field(
3911
                        [
3912
                            'fieldDefIdentifier' => 'identifier2',
3913
                            'value' => 'newValue3',
3914
                            'languageCode' => 'eng-GB',
3915
                        ]
3916
                    ),
3917
                ],
3918
                $spiFields2,
3919
            ],
3920
            // 5. New language and existing language with untranslatable field, without language set
3921
            [
3922
                'eng-US',
3923
                [
3924
                    new Field(
3925
                        [
3926
                            'fieldDefIdentifier' => 'identifier1',
3927
                            'value' => 'newValue1',
3928
                            'languageCode' => null,
3929
                        ]
3930
                    ),
3931
                    new Field(
3932
                        [
3933
                            'fieldDefIdentifier' => 'identifier1',
3934
                            'value' => 'newValue2',
3935
                            'languageCode' => 'eng-GB',
3936
                        ]
3937
                    ),
3938
                    new Field(
3939
                        [
3940
                            'fieldDefIdentifier' => 'identifier2',
3941
                            'value' => 'newValue3',
3942
                            'languageCode' => null,
3943
                        ]
3944
                    ),
3945
                ],
3946
                $spiFields2,
3947
            ],
3948
            // 6. Adding new language without fields
3949
            [
3950
                'eng-US',
3951
                [],
3952
                $spiFields3,
3953
            ],
3954
        ];
3955
    }
3956
3957
    /**
3958
     * Test for the updateContent() method.
3959
     *
3960
     * Testing with new language and untranslatable field.
3961
     *
3962
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
3963
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
3964
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
3965
     * @dataProvider providerForTestUpdateContentNonRedundantFieldSet3
3966
     */
3967
    public function testUpdateContentNonRedundantFieldSet3($initialLanguageCode, $structFields, $spiFields)
3968
    {
3969
        $existingFields = [
3970
            new Field(
3971
                [
3972
                    'id' => '100',
3973
                    'fieldDefIdentifier' => 'identifier1',
3974
                    'value' => 'initialValue1',
3975
                    'languageCode' => 'eng-GB',
3976
                ]
3977
            ),
3978
            new Field(
3979
                [
3980
                    'id' => '101',
3981
                    'fieldDefIdentifier' => 'identifier2',
3982
                    'value' => 'initialValue2',
3983
                    'languageCode' => 'eng-GB',
3984
                ]
3985
            ),
3986
        ];
3987
3988
        $fieldDefinitions = [
3989
            new FieldDefinition(
3990
                [
3991
                    'id' => 'fieldDefinitionId1',
3992
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
3993
                    'isTranslatable' => true,
3994
                    'identifier' => 'identifier1',
3995
                    'isRequired' => false,
3996
                    'defaultValue' => 'defaultValue1',
3997
                ]
3998
            ),
3999
            new FieldDefinition(
4000
                [
4001
                    'id' => 'fieldDefinitionId2',
4002
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
4003
                    'isTranslatable' => false,
4004
                    'identifier' => 'identifier2',
4005
                    'isRequired' => false,
4006
                    'defaultValue' => 'defaultValue2',
4007
                ]
4008
            ),
4009
        ];
4010
4011
        $this->assertForTestUpdateContentNonRedundantFieldSet(
4012
            $initialLanguageCode,
4013
            $structFields,
4014
            $spiFields,
4015
            $existingFields,
4016
            $fieldDefinitions
4017
        );
4018
    }
4019
4020
    public function providerForTestUpdateContentNonRedundantFieldSet4()
4021
    {
4022
        $spiFields0 = [
4023
            new SPIField(
4024
                [
4025
                    'id' => null,
4026
                    'fieldDefinitionId' => 'fieldDefinitionId1',
4027
                    'type' => 'fieldTypeIdentifier',
4028
                    'value' => 'newValue1',
4029
                    'languageCode' => 'eng-US',
4030
                    'versionNo' => 7,
4031
                ]
4032
            ),
4033
        ];
4034
        $spiFields1 = [
4035
            new SPIField(
4036
                [
4037
                    'id' => 100,
4038
                    'fieldDefinitionId' => 'fieldDefinitionId1',
4039
                    'type' => 'fieldTypeIdentifier',
4040
                    'value' => self::EMPTY_FIELD_VALUE,
4041
                    'languageCode' => 'eng-GB',
4042
                    'versionNo' => 7,
4043
                ]
4044
            ),
4045
            new SPIField(
4046
                [
4047
                    'id' => null,
4048
                    'fieldDefinitionId' => 'fieldDefinitionId1',
4049
                    'type' => 'fieldTypeIdentifier',
4050
                    'value' => 'newValue1',
4051
                    'languageCode' => 'eng-US',
4052
                    'versionNo' => 7,
4053
                ]
4054
            ),
4055
        ];
4056
        $spiFields2 = [
4057
            new SPIField(
4058
                [
4059
                    'id' => 100,
4060
                    'fieldDefinitionId' => 'fieldDefinitionId1',
4061
                    'type' => 'fieldTypeIdentifier',
4062
                    'value' => self::EMPTY_FIELD_VALUE,
4063
                    'languageCode' => 'eng-GB',
4064
                    'versionNo' => 7,
4065
                ]
4066
            ),
4067
        ];
4068
4069
        return [
4070
            // 0. New translation with empty field by default
4071
            [
4072
                'eng-US',
4073
                [
4074
                    new Field(
4075
                        [
4076
                            'fieldDefIdentifier' => 'identifier1',
4077
                            'value' => 'newValue1',
4078
                            'languageCode' => 'eng-US',
4079
                        ]
4080
                    ),
4081
                ],
4082
                $spiFields0,
4083
            ],
4084
            // 1. New translation with empty field by default, without language set
4085
            [
4086
                'eng-US',
4087
                [
4088
                    new Field(
4089
                        [
4090
                            'fieldDefIdentifier' => 'identifier1',
4091
                            'value' => 'newValue1',
4092
                            'languageCode' => null,
4093
                        ]
4094
                    ),
4095
                ],
4096
                $spiFields0,
4097
            ],
4098
            // 2. New translation with empty field given
4099
            [
4100
                'eng-US',
4101
                [
4102
                    new Field(
4103
                        [
4104
                            'fieldDefIdentifier' => 'identifier1',
4105
                            'value' => 'newValue1',
4106
                            'languageCode' => 'eng-US',
4107
                        ]
4108
                    ),
4109
                    new Field(
4110
                        [
4111
                            'fieldDefIdentifier' => 'identifier2',
4112
                            'value' => self::EMPTY_FIELD_VALUE,
4113
                            'languageCode' => 'eng-US',
4114
                        ]
4115
                    ),
4116
                ],
4117
                $spiFields0,
4118
            ],
4119
            // 3. New translation with empty field given, without language set
4120
            [
4121
                'eng-US',
4122
                [
4123
                    new Field(
4124
                        [
4125
                            'fieldDefIdentifier' => 'identifier1',
4126
                            'value' => 'newValue1',
4127
                            'languageCode' => null,
4128
                        ]
4129
                    ),
4130
                    new Field(
4131
                        [
4132
                            'fieldDefIdentifier' => 'identifier2',
4133
                            'value' => self::EMPTY_FIELD_VALUE,
4134
                            'languageCode' => null,
4135
                        ]
4136
                    ),
4137
                ],
4138
                $spiFields0,
4139
            ],
4140
            // 4. Updating existing language with empty value
4141
            [
4142
                'eng-US',
4143
                [
4144
                    new Field(
4145
                        [
4146
                            'fieldDefIdentifier' => 'identifier1',
4147
                            'value' => 'newValue1',
4148
                            'languageCode' => 'eng-US',
4149
                        ]
4150
                    ),
4151
                    new Field(
4152
                        [
4153
                            'fieldDefIdentifier' => 'identifier1',
4154
                            'value' => self::EMPTY_FIELD_VALUE,
4155
                            'languageCode' => 'eng-GB',
4156
                        ]
4157
                    ),
4158
                ],
4159
                $spiFields1,
4160
            ],
4161
            // 5. Updating existing language with empty value, without language set
4162
            [
4163
                'eng-US',
4164
                [
4165
                    new Field(
4166
                        [
4167
                            'fieldDefIdentifier' => 'identifier1',
4168
                            'value' => 'newValue1',
4169
                            'languageCode' => null,
4170
                        ]
4171
                    ),
4172
                    new Field(
4173
                        [
4174
                            'fieldDefIdentifier' => 'identifier1',
4175
                            'value' => self::EMPTY_FIELD_VALUE,
4176
                            'languageCode' => 'eng-GB',
4177
                        ]
4178
                    ),
4179
                ],
4180
                $spiFields1,
4181
            ],
4182
            // 6. Updating existing language with empty value and adding new language with empty value
4183
            [
4184
                'eng-US',
4185
                [
4186
                    new Field(
4187
                        [
4188
                            'fieldDefIdentifier' => 'identifier1',
4189
                            'value' => self::EMPTY_FIELD_VALUE,
4190
                            'languageCode' => 'eng-US',
4191
                        ]
4192
                    ),
4193
                    new Field(
4194
                        [
4195
                            'fieldDefIdentifier' => 'identifier1',
4196
                            'value' => self::EMPTY_FIELD_VALUE,
4197
                            'languageCode' => 'eng-GB',
4198
                        ]
4199
                    ),
4200
                ],
4201
                $spiFields2,
4202
            ],
4203
            // 7. Updating existing language with empty value and adding new language with empty value,
4204
            // without language set
4205
            [
4206
                'eng-US',
4207
                [
4208
                    new Field(
4209
                        [
4210
                            'fieldDefIdentifier' => 'identifier1',
4211
                            'value' => self::EMPTY_FIELD_VALUE,
4212
                            'languageCode' => null,
4213
                        ]
4214
                    ),
4215
                    new Field(
4216
                        [
4217
                            'fieldDefIdentifier' => 'identifier1',
4218
                            'value' => self::EMPTY_FIELD_VALUE,
4219
                            'languageCode' => 'eng-GB',
4220
                        ]
4221
                    ),
4222
                ],
4223
                $spiFields2,
4224
            ],
4225
            // 8. Adding new language with no fields given
4226
            [
4227
                'eng-US',
4228
                [],
4229
                [],
4230
            ],
4231
            // 9. Adding new language with fields
4232
            [
4233
                'eng-US',
4234
                [
4235
                    new Field(
4236
                        [
4237
                            'fieldDefIdentifier' => 'identifier1',
4238
                            'value' => self::EMPTY_FIELD_VALUE,
4239
                            'languageCode' => 'eng-US',
4240
                        ]
4241
                    ),
4242
                ],
4243
                [],
4244
            ],
4245
            // 10. Adding new language with fields, without language set
4246
            [
4247
                'eng-US',
4248
                [
4249
                    new Field(
4250
                        [
4251
                            'fieldDefIdentifier' => 'identifier1',
4252
                            'value' => self::EMPTY_FIELD_VALUE,
4253
                            'languageCode' => null,
4254
                        ]
4255
                    ),
4256
                ],
4257
                [],
4258
            ],
4259
        ];
4260
    }
4261
4262
    /**
4263
     * Test for the updateContent() method.
4264
     *
4265
     * Testing with empty values.
4266
     *
4267
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
4268
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
4269
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
4270
     * @dataProvider providerForTestUpdateContentNonRedundantFieldSet4
4271
     */
4272
    public function testUpdateContentNonRedundantFieldSet4($initialLanguageCode, $structFields, $spiFields)
4273
    {
4274
        $existingFields = [
4275
            new Field(
4276
                [
4277
                    'id' => '100',
4278
                    'fieldDefIdentifier' => 'identifier1',
4279
                    'value' => 'initialValue1',
4280
                    'languageCode' => 'eng-GB',
4281
                ]
4282
            ),
4283
            new Field(
4284
                [
4285
                    'id' => '101',
4286
                    'fieldDefIdentifier' => 'identifier2',
4287
                    'value' => 'initialValue2',
4288
                    'languageCode' => 'eng-GB',
4289
                ]
4290
            ),
4291
        ];
4292
4293
        $fieldDefinitions = [
4294
            new FieldDefinition(
4295
                [
4296
                    'id' => 'fieldDefinitionId1',
4297
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
4298
                    'isTranslatable' => true,
4299
                    'identifier' => 'identifier1',
4300
                    'isRequired' => false,
4301
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
4302
                ]
4303
            ),
4304
            new FieldDefinition(
4305
                [
4306
                    'id' => 'fieldDefinitionId2',
4307
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
4308
                    'isTranslatable' => true,
4309
                    'identifier' => 'identifier2',
4310
                    'isRequired' => false,
4311
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
4312
                ]
4313
            ),
4314
        ];
4315
4316
        $this->assertForTestUpdateContentNonRedundantFieldSet(
4317
            $initialLanguageCode,
4318
            $structFields,
4319
            $spiFields,
4320
            $existingFields,
4321
            $fieldDefinitions
4322
        );
4323
    }
4324
4325
    /**
4326
     * @return array
4327
     *
4328
     * @todo add first field empty
4329
     */
4330
    public function providerForTestUpdateContentNonRedundantFieldSetComplex()
4331
    {
4332
        $spiFields0 = [
4333
            new SPIField(
4334
                [
4335
                    'id' => 100,
4336
                    'fieldDefinitionId' => 'fieldDefinitionId1',
4337
                    'type' => 'fieldTypeIdentifier',
4338
                    'value' => 'newValue1-eng-GB',
4339
                    'languageCode' => 'eng-GB',
4340
                    'versionNo' => 7,
4341
                ]
4342
            ),
4343
            new SPIField(
4344
                [
4345
                    'id' => null,
4346
                    'fieldDefinitionId' => 'fieldDefinitionId4',
4347
                    'type' => 'fieldTypeIdentifier',
4348
                    'value' => 'newValue4',
4349
                    'languageCode' => 'eng-US',
4350
                    'versionNo' => 7,
4351
                ]
4352
            ),
4353
        ];
4354
        $spiFields1 = [
4355
            new SPIField(
4356
                [
4357
                    'id' => 100,
4358
                    'fieldDefinitionId' => 'fieldDefinitionId1',
4359
                    'type' => 'fieldTypeIdentifier',
4360
                    'value' => 'newValue1-eng-GB',
4361
                    'languageCode' => 'eng-GB',
4362
                    'versionNo' => 7,
4363
                ]
4364
            ),
4365
            new SPIField(
4366
                [
4367
                    'id' => null,
4368
                    'fieldDefinitionId' => 'fieldDefinitionId2',
4369
                    'type' => 'fieldTypeIdentifier',
4370
                    'value' => 'newValue2',
4371
                    'languageCode' => 'eng-US',
4372
                    'versionNo' => 7,
4373
                ]
4374
            ),
4375
            new SPIField(
4376
                [
4377
                    'id' => null,
4378
                    'fieldDefinitionId' => 'fieldDefinitionId4',
4379
                    'type' => 'fieldTypeIdentifier',
4380
                    'value' => 'defaultValue4',
4381
                    'languageCode' => 'eng-US',
4382
                    'versionNo' => 7,
4383
                ]
4384
            ),
4385
        ];
4386
        $spiFields2 = [
4387
            new SPIField(
4388
                [
4389
                    'id' => 100,
4390
                    'fieldDefinitionId' => 'fieldDefinitionId1',
4391
                    'type' => 'fieldTypeIdentifier',
4392
                    'value' => 'newValue1-eng-GB',
4393
                    'languageCode' => 'eng-GB',
4394
                    'versionNo' => 7,
4395
                ]
4396
            ),
4397
            new SPIField(
4398
                [
4399
                    'id' => null,
4400
                    'fieldDefinitionId' => 'fieldDefinitionId2',
4401
                    'type' => 'fieldTypeIdentifier',
4402
                    'value' => 'newValue2',
4403
                    'languageCode' => 'eng-US',
4404
                    'versionNo' => 7,
4405
                ]
4406
            ),
4407
            new SPIField(
4408
                [
4409
                    'id' => null,
4410
                    'fieldDefinitionId' => 'fieldDefinitionId4',
4411
                    'type' => 'fieldTypeIdentifier',
4412
                    'value' => 'defaultValue4',
4413
                    'languageCode' => 'ger-DE',
4414
                    'versionNo' => 7,
4415
                ]
4416
            ),
4417
            new SPIField(
4418
                [
4419
                    'id' => null,
4420
                    'fieldDefinitionId' => 'fieldDefinitionId4',
4421
                    'type' => 'fieldTypeIdentifier',
4422
                    'value' => 'defaultValue4',
4423
                    'languageCode' => 'eng-US',
4424
                    'versionNo' => 7,
4425
                ]
4426
            ),
4427
        ];
4428
4429
        return [
4430
            // 0. Add new language and update existing
4431
            [
4432
                'eng-US',
4433
                [
4434
                    new Field(
4435
                        [
4436
                            'fieldDefIdentifier' => 'identifier4',
4437
                            'value' => 'newValue4',
4438
                            'languageCode' => 'eng-US',
4439
                        ]
4440
                    ),
4441
                    new Field(
4442
                        [
4443
                            'fieldDefIdentifier' => 'identifier1',
4444
                            'value' => 'newValue1-eng-GB',
4445
                            'languageCode' => 'eng-GB',
4446
                        ]
4447
                    ),
4448
                ],
4449
                $spiFields0,
4450
            ],
4451
            // 1. Add new language and update existing, without language set
4452
            [
4453
                'eng-US',
4454
                [
4455
                    new Field(
4456
                        [
4457
                            'fieldDefIdentifier' => 'identifier4',
4458
                            'value' => 'newValue4',
4459
                            'languageCode' => null,
4460
                        ]
4461
                    ),
4462
                    new Field(
4463
                        [
4464
                            'fieldDefIdentifier' => 'identifier1',
4465
                            'value' => 'newValue1-eng-GB',
4466
                            'languageCode' => 'eng-GB',
4467
                        ]
4468
                    ),
4469
                ],
4470
                $spiFields0,
4471
            ],
4472
            // 2. Add new language and update existing variant
4473
            [
4474
                'eng-US',
4475
                [
4476
                    new Field(
4477
                        [
4478
                            'fieldDefIdentifier' => 'identifier2',
4479
                            'value' => 'newValue2',
4480
                            'languageCode' => 'eng-US',
4481
                        ]
4482
                    ),
4483
                    new Field(
4484
                        [
4485
                            'fieldDefIdentifier' => 'identifier1',
4486
                            'value' => 'newValue1-eng-GB',
4487
                            'languageCode' => 'eng-GB',
4488
                        ]
4489
                    ),
4490
                ],
4491
                $spiFields1,
4492
            ],
4493
            // 3. Add new language and update existing variant, without language set
4494
            [
4495
                'eng-US',
4496
                [
4497
                    new Field(
4498
                        [
4499
                            'fieldDefIdentifier' => 'identifier2',
4500
                            'value' => 'newValue2',
4501
                            'languageCode' => null,
4502
                        ]
4503
                    ),
4504
                    new Field(
4505
                        [
4506
                            'fieldDefIdentifier' => 'identifier1',
4507
                            'value' => 'newValue1-eng-GB',
4508
                            'languageCode' => 'eng-GB',
4509
                        ]
4510
                    ),
4511
                ],
4512
                $spiFields1,
4513
            ],
4514
            // 4. Update with multiple languages
4515
            [
4516
                'ger-DE',
4517
                [
4518
                    new Field(
4519
                        [
4520
                            'fieldDefIdentifier' => 'identifier2',
4521
                            'value' => 'newValue2',
4522
                            'languageCode' => 'eng-US',
4523
                        ]
4524
                    ),
4525
                    new Field(
4526
                        [
4527
                            'fieldDefIdentifier' => 'identifier1',
4528
                            'value' => 'newValue1-eng-GB',
4529
                            'languageCode' => 'eng-GB',
4530
                        ]
4531
                    ),
4532
                ],
4533
                $spiFields2,
4534
            ],
4535
            // 5. Update with multiple languages without language set
4536
            [
4537
                'ger-DE',
4538
                [
4539
                    new Field(
4540
                        [
4541
                            'fieldDefIdentifier' => 'identifier2',
4542
                            'value' => 'newValue2',
4543
                            'languageCode' => 'eng-US',
4544
                        ]
4545
                    ),
4546
                    new Field(
4547
                        [
4548
                            'fieldDefIdentifier' => 'identifier1',
4549
                            'value' => 'newValue1-eng-GB',
4550
                            'languageCode' => null,
4551
                        ]
4552
                    ),
4553
                ],
4554
                $spiFields2,
4555
            ],
4556
        ];
4557
    }
4558
4559
    protected function fixturesForTestUpdateContentNonRedundantFieldSetComplex()
4560
    {
4561
        $existingFields = [
4562
            new Field(
4563
                [
4564
                    'id' => '100',
4565
                    'fieldDefIdentifier' => 'identifier1',
4566
                    'value' => 'initialValue1',
4567
                    'languageCode' => 'eng-GB',
4568
                ]
4569
            ),
4570
            new Field(
4571
                [
4572
                    'id' => '101',
4573
                    'fieldDefIdentifier' => 'identifier2',
4574
                    'value' => 'initialValue2',
4575
                    'languageCode' => 'eng-GB',
4576
                ]
4577
            ),
4578
            new Field(
4579
                [
4580
                    'id' => '102',
4581
                    'fieldDefIdentifier' => 'identifier3',
4582
                    'value' => 'initialValue3',
4583
                    'languageCode' => 'eng-GB',
4584
                ]
4585
            ),
4586
            new Field(
4587
                [
4588
                    'id' => '103',
4589
                    'fieldDefIdentifier' => 'identifier4',
4590
                    'value' => 'initialValue4',
4591
                    'languageCode' => 'eng-GB',
4592
                ]
4593
            ),
4594
        ];
4595
4596
        $fieldDefinitions = [
4597
            new FieldDefinition(
4598
                [
4599
                    'id' => 'fieldDefinitionId1',
4600
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
4601
                    'isTranslatable' => false,
4602
                    'identifier' => 'identifier1',
4603
                    'isRequired' => false,
4604
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
4605
                ]
4606
            ),
4607
            new FieldDefinition(
4608
                [
4609
                    'id' => 'fieldDefinitionId2',
4610
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
4611
                    'isTranslatable' => true,
4612
                    'identifier' => 'identifier2',
4613
                    'isRequired' => false,
4614
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
4615
                ]
4616
            ),
4617
            new FieldDefinition(
4618
                [
4619
                    'id' => 'fieldDefinitionId3',
4620
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
4621
                    'isTranslatable' => false,
4622
                    'identifier' => 'identifier3',
4623
                    'isRequired' => false,
4624
                    'defaultValue' => 'defaultValue3',
4625
                ]
4626
            ),
4627
            new FieldDefinition(
4628
                [
4629
                    'id' => 'fieldDefinitionId4',
4630
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
4631
                    'isTranslatable' => true,
4632
                    'identifier' => 'identifier4',
4633
                    'isRequired' => false,
4634
                    'defaultValue' => 'defaultValue4',
4635
                ]
4636
            ),
4637
        ];
4638
4639
        return [$existingFields, $fieldDefinitions];
4640
    }
4641
4642
    /**
4643
     * Test for the updateContent() method.
4644
     *
4645
     * Testing more complex cases.
4646
     *
4647
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
4648
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
4649
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
4650
     * @dataProvider providerForTestUpdateContentNonRedundantFieldSetComplex
4651
     */
4652
    public function testUpdateContentNonRedundantFieldSetComplex($initialLanguageCode, $structFields, $spiFields)
4653
    {
4654
        list($existingFields, $fieldDefinitions) = $this->fixturesForTestUpdateContentNonRedundantFieldSetComplex();
4655
4656
        $this->assertForTestUpdateContentNonRedundantFieldSet(
4657
            $initialLanguageCode,
4658
            $structFields,
4659
            $spiFields,
4660
            $existingFields,
4661
            $fieldDefinitions
4662
        );
4663
    }
4664
4665
    public function providerForTestUpdateContentWithInvalidLanguage()
4666
    {
4667
        return [
4668
            [
4669
                'eng-GB',
4670
                [
4671
                    new Field(
4672
                        [
4673
                            'fieldDefIdentifier' => 'identifier',
4674
                            'value' => 'newValue',
4675
                            'languageCode' => 'Klingon',
4676
                        ]
4677
                    ),
4678
                ],
4679
            ],
4680
            [
4681
                'Klingon',
4682
                [
4683
                    new Field(
4684
                        [
4685
                            'fieldDefIdentifier' => 'identifier',
4686
                            'value' => 'newValue',
4687
                            'languageCode' => 'eng-GB',
4688
                        ]
4689
                    ),
4690
                ],
4691
            ],
4692
        ];
4693
    }
4694
4695
    /**
4696
     * Test for the updateContent() method.
4697
     *
4698
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
4699
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
4700
     * @dataProvider providerForTestUpdateContentWithInvalidLanguage
4701
     */
4702
    public function testUpdateContentWithInvalidLanguage($initialLanguageCode, $structFields)
4703
    {
4704
        $this->expectException(APINotFoundException::class);
4705
        $this->expectExceptionMessage('Could not find \'Language\' with identifier \'Klingon\'');
4706
4707
        $permissionResolverMock = $this->getPermissionResolverMock();
4708
        $mockedService = $this->getPartlyMockedContentService(['loadContent', 'internalLoadContentById']);
4709
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
4710
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
4711
        $versionInfo = new VersionInfo(
4712
            [
4713
                'contentInfo' => new ContentInfo(
4714
                    [
4715
                        'id' => 42,
4716
                        'contentTypeId' => 24,
4717
                        'mainLanguageCode' => 'eng-GB',
4718
                    ]
4719
                ),
4720
                'versionNo' => 7,
4721
                'languageCodes' => ['eng-GB'],
4722
                'status' => VersionInfo::STATUS_DRAFT,
4723
            ]
4724
        );
4725
        $content = new Content(
4726
            [
4727
                'versionInfo' => $versionInfo,
4728
                'internalFields' => [],
4729
            ]
4730
        );
4731
4732
        $languageHandlerMock->expects($this->any())
4733
            ->method('loadByLanguageCode')
4734
            ->with($this->isType('string'))
4735
            ->will(
4736
                $this->returnCallback(
4737
                    function ($languageCode) {
4738
                        if ($languageCode === 'Klingon') {
4739
                            throw new NotFoundException('Language', 'Klingon');
4740
                        }
4741
4742
                        return new Language(['id' => 4242]);
4743
                    }
4744
                )
4745
            );
4746
4747
        $mockedService
4748
            ->method('loadContent')
4749
            ->with(
4750
                $this->equalTo(42),
4751
                $this->equalTo(null),
4752
                $this->equalTo(7)
4753
            )->will(
4754
                $this->returnValue($content)
4755
            );
4756
4757
        $mockedService
4758
            ->method('internalLoadContentById')
4759
            ->will(
4760
                $this->returnValue($content)
4761
            );
4762
4763
        $permissionResolverMock
4764
            ->method('canUser')
4765
            ->with(
4766
                $this->equalTo('content'),
4767
                $this->equalTo('edit'),
4768
                $this->equalTo($content),
4769
                $this->isType('array')
4770
            )->will($this->returnValue(true));
4771
4772
        $contentUpdateStruct = new ContentUpdateStruct(
4773
            [
4774
                'fields' => $structFields,
4775
                'initialLanguageCode' => $initialLanguageCode,
4776
            ]
4777
        );
4778
4779
        $mockedService->updateContent($content->versionInfo, $contentUpdateStruct);
4780
    }
4781
4782
    protected function assertForUpdateContentContentValidationException(
4783
        $initialLanguageCode,
4784
        $structFields,
4785
        $fieldDefinitions = []
4786
    ) {
4787
        $repositoryMock = $this->getRepositoryMock();
4788
        $permissionResolverMock = $this->getPermissionResolverMock();
4789
        $mockedService = $this->getPartlyMockedContentService(['internalLoadContentById', 'loadContent']);
4790
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
4791
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
4792
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
4793
        $versionInfo = new VersionInfo(
4794
            [
4795
                'contentInfo' => new ContentInfo(
4796
                    [
4797
                        'id' => 42,
4798
                        'contentTypeId' => 24,
4799
                        'mainLanguageCode' => 'eng-GB',
4800
                    ]
4801
                ),
4802
                'versionNo' => 7,
4803
                'languageCodes' => ['eng-GB'],
4804
                'status' => VersionInfo::STATUS_DRAFT,
4805
            ]
4806
        );
4807
        $content = new Content(
4808
            [
4809
                'versionInfo' => $versionInfo,
4810
                'internalFields' => [],
4811
            ]
4812
        );
4813
        $contentType = new ContentType([
4814
            'fieldDefinitions' => new FieldDefinitionCollection($fieldDefinitions),
4815
        ]);
4816
4817
        $languageHandlerMock->expects($this->any())
4818
            ->method('loadByLanguageCode')
4819
            ->with($this->isType('string'))
4820
            ->will(
4821
                $this->returnCallback(
4822
                    function ($languageCode) {
4823
                        if ($languageCode === 'Klingon') {
4824
                            throw new NotFoundException('Language', 'Klingon');
4825
                        }
4826
4827
                        return new Language(['id' => 4242]);
4828
                    }
4829
                )
4830
            );
4831
4832
        $mockedService
4833
            ->method('loadContent')
4834
            ->with(
4835
                $this->equalTo(42),
4836
                $this->equalTo(null),
4837
                $this->equalTo(7)
4838
            )->will(
4839
                $this->returnValue($content)
4840
            );
4841
4842
        $mockedService
4843
            ->method('internalLoadContentById')
4844
            ->will(
4845
                $this->returnValue($content)
4846
            );
4847
4848
        $permissionResolverMock->expects($this->once())
4849
            ->method('canUser')
4850
            ->with(
4851
                $this->equalTo('content'),
4852
                $this->equalTo('edit'),
4853
                $this->equalTo($content),
4854
                $this->isType('array')
4855
            )->will($this->returnValue(true));
4856
4857
        $contentTypeServiceMock->expects($this->once())
4858
            ->method('loadContentType')
4859
            ->with($this->equalTo(24))
4860
            ->will($this->returnValue($contentType));
4861
4862
        $repositoryMock->expects($this->once())
4863
            ->method('getContentTypeService')
4864
            ->will($this->returnValue($contentTypeServiceMock));
4865
4866
        $contentUpdateStruct = new ContentUpdateStruct(
4867
            [
4868
                'fields' => $structFields,
4869
                'initialLanguageCode' => $initialLanguageCode,
4870
            ]
4871
        );
4872
4873
        $mockedService->updateContent($content->versionInfo, $contentUpdateStruct);
4874
    }
4875
4876
    public function providerForTestUpdateContentThrowsContentValidationExceptionFieldDefinition()
4877
    {
4878
        return [
4879
            [
4880
                'eng-GB',
4881
                [
4882
                    new Field(
4883
                        [
4884
                            'fieldDefIdentifier' => 'identifier',
4885
                            'value' => 'newValue',
4886
                            'languageCode' => 'eng-GB',
4887
                        ]
4888
                    ),
4889
                ],
4890
            ],
4891
        ];
4892
    }
4893
4894
    /**
4895
     * Test for the updateContent() method.
4896
     *
4897
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
4898
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
4899
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
4900
     * @dataProvider providerForTestUpdateContentThrowsContentValidationExceptionFieldDefinition
4901
     */
4902
    public function testUpdateContentThrowsContentValidationExceptionFieldDefinition($initialLanguageCode, $structFields)
4903
    {
4904
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\ContentValidationException::class);
4905
        $this->expectExceptionMessage('Field definition \'identifier\' does not exist in given Content Type');
4906
4907
        $this->assertForUpdateContentContentValidationException(
4908
            $initialLanguageCode,
4909
            $structFields,
4910
            []
4911
        );
4912
    }
4913
4914
    public function providerForTestUpdateContentThrowsContentValidationExceptionTranslation()
4915
    {
4916
        return [
4917
            [
4918
                'eng-US',
4919
                [
4920
                    new Field(
4921
                        [
4922
                            'fieldDefIdentifier' => 'identifier',
4923
                            'value' => 'newValue',
4924
                            'languageCode' => 'eng-US',
4925
                        ]
4926
                    ),
4927
                ],
4928
            ],
4929
        ];
4930
    }
4931
4932
    /**
4933
     * Test for the updateContent() method.
4934
     *
4935
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
4936
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
4937
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
4938
     * @dataProvider providerForTestUpdateContentThrowsContentValidationExceptionTranslation
4939
     */
4940
    public function testUpdateContentThrowsContentValidationExceptionTranslation($initialLanguageCode, $structFields)
4941
    {
4942
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\ContentValidationException::class);
4943
        $this->expectExceptionMessage('You cannot set a value for the non-translatable Field definition \'identifier\' in language \'eng-US\'');
4944
4945
        $fieldDefinitions = [
4946
            new FieldDefinition(
4947
                [
4948
                    'id' => 'fieldDefinitionId1',
4949
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
4950
                    'isTranslatable' => false,
4951
                    'identifier' => 'identifier',
4952
                    'isRequired' => false,
4953
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
4954
                ]
4955
            ),
4956
        ];
4957
4958
        $this->assertForUpdateContentContentValidationException(
4959
            $initialLanguageCode,
4960
            $structFields,
4961
            $fieldDefinitions
4962
        );
4963
    }
4964
4965
    public function assertForTestUpdateContentRequiredField(
4966
        $initialLanguageCode,
4967
        $structFields,
4968
        $existingFields,
4969
        $fieldDefinitions
4970
    ) {
4971
        $repositoryMock = $this->getRepositoryMock();
4972
        $permissionResolver = $this->getPermissionResolverMock();
4973
        $mockedService = $this->getPartlyMockedContentService(['internalLoadContentById', 'loadContent']);
4974
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
4975
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
4976
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
4977
        $fieldTypeMock = $this->createMock(SPIFieldType::class);
4978
        $existingLanguageCodes = array_map(
4979
            function (Field $field) {
4980
                return $field->languageCode;
4981
            },
4982
            $existingFields
4983
        );
4984
        $versionInfo = new VersionInfo(
4985
            [
4986
                'contentInfo' => new ContentInfo(
4987
                    [
4988
                        'id' => 42,
4989
                        'contentTypeId' => 24,
4990
                        'mainLanguageCode' => 'eng-GB',
4991
                    ]
4992
                ),
4993
                'versionNo' => 7,
4994
                'languageCodes' => $existingLanguageCodes,
4995
                'status' => VersionInfo::STATUS_DRAFT,
4996
            ]
4997
        );
4998
        $content = new Content(
4999
            [
5000
                'versionInfo' => $versionInfo,
5001
                'internalFields' => $existingFields,
5002
            ]
5003
        );
5004
        $contentType = new ContentType([
5005
            'fieldDefinitions' => new FieldDefinitionCollection($fieldDefinitions),
5006
        ]);
5007
5008
        $languageHandlerMock->expects($this->any())
5009
            ->method('loadByLanguageCode')
5010
            ->with($this->isType('string'))
5011
            ->will(
5012
                $this->returnCallback(
5013
                    function () {
5014
                        return new Language(['id' => 4242]);
5015
                    }
5016
                )
5017
            );
5018
5019
        $mockedService
5020
            ->method('loadContent')
5021
            ->with(
5022
                $this->equalTo(42),
5023
                $this->equalTo(null),
5024
                $this->equalTo(7)
5025
            )->will(
5026
                $this->returnValue($content)
5027
            );
5028
5029
        $mockedService
5030
            ->method('internalLoadContentById')
5031
            ->will(
5032
                $this->returnValue($content)
5033
            );
5034
5035
        $permissionResolver->expects($this->once())
5036
            ->method('canUser')
5037
            ->with(
5038
                $this->equalTo('content'),
5039
                $this->equalTo('edit'),
5040
                $this->equalTo($content),
5041
                $this->isType('array')
5042
            )->will($this->returnValue(true));
5043
5044
        $contentTypeServiceMock->expects($this->once())
5045
            ->method('loadContentType')
5046
            ->with($this->equalTo(24))
5047
            ->will($this->returnValue($contentType));
5048
5049
        $repositoryMock->expects($this->once())
5050
            ->method('getContentTypeService')
5051
            ->will($this->returnValue($contentTypeServiceMock));
5052
5053
        $fieldTypeMock->expects($this->any())
5054
            ->method('acceptValue')
5055
            ->will(
5056
                $this->returnCallback(
5057
                    function ($valueString) {
5058
                        return new ValueStub($valueString);
5059
                    }
5060
                )
5061
            );
5062
5063
        $emptyValue = self::EMPTY_FIELD_VALUE;
5064
        $fieldTypeMock->expects($this->any())
5065
            ->method('isEmptyValue')
5066
            ->will(
5067
                $this->returnCallback(
5068
                    function (ValueStub $value) use ($emptyValue) {
5069
                        return $emptyValue === (string)$value;
5070
                    }
5071
                )
5072
            );
5073
5074
        $fieldTypeMock->expects($this->any())
5075
            ->method('validate')
5076
            ->with(
5077
                $this->isInstanceOf(APIFieldDefinition::class),
5078
                $this->isInstanceOf(Value::class)
5079
            );
5080
5081
        $this->getFieldTypeRegistryMock()->expects($this->any())
5082
            ->method('getFieldType')
5083
            ->will($this->returnValue($fieldTypeMock));
5084
5085
        $contentUpdateStruct = new ContentUpdateStruct(
5086
            [
5087
                'fields' => $structFields,
5088
                'initialLanguageCode' => $initialLanguageCode,
5089
            ]
5090
        );
5091
5092
        return [$content->versionInfo, $contentUpdateStruct];
5093
    }
5094
5095
    public function providerForTestUpdateContentRequiredField()
5096
    {
5097
        return [
5098
            [
5099
                'eng-US',
5100
                [
5101
                    new Field(
5102
                        [
5103
                            'fieldDefIdentifier' => 'identifier',
5104
                            'value' => self::EMPTY_FIELD_VALUE,
5105
                            'languageCode' => null,
5106
                        ]
5107
                    ),
5108
                ],
5109
                'identifier',
5110
                'eng-US',
5111
            ],
5112
        ];
5113
    }
5114
5115
    /**
5116
     * Test for the updateContent() method.
5117
     *
5118
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
5119
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
5120
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
5121
     * @dataProvider providerForTestUpdateContentRequiredField
5122
     */
5123
    public function testUpdateContentRequiredField(
5124
        $initialLanguageCode,
5125
        $structFields,
5126
        $identifier,
5127
        $languageCode
5128
    ) {
5129
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException::class);
5130
5131
        $existingFields = [
5132
            new Field(
5133
                [
5134
                    'id' => '100',
5135
                    'fieldDefIdentifier' => 'identifier',
5136
                    'value' => 'initialValue',
5137
                    'languageCode' => 'eng-GB',
5138
                ]
5139
            ),
5140
        ];
5141
        $fieldDefinitions = [
5142
            new FieldDefinition(
5143
                [
5144
                    'id' => 'fieldDefinitionId',
5145
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
5146
                    'isTranslatable' => true,
5147
                    'identifier' => 'identifier',
5148
                    'isRequired' => true,
5149
                    'defaultValue' => 'defaultValue',
5150
                ]
5151
            ),
5152
        ];
5153
        list($versionInfo, $contentUpdateStruct) =
5154
            $this->assertForTestUpdateContentRequiredField(
5155
                $initialLanguageCode,
5156
                $structFields,
5157
                $existingFields,
5158
                $fieldDefinitions
5159
            );
5160
5161
        try {
5162
            $this->partlyMockedContentService->updateContent($versionInfo, $contentUpdateStruct);
5163
        } catch (ContentValidationException $e) {
5164
            $this->assertEquals(
5165
                "Value for required field definition '{$identifier}' with language '{$languageCode}' is empty",
5166
                $e->getMessage()
5167
            );
5168
5169
            throw $e;
5170
        }
5171
    }
5172
5173
    public function assertForTestUpdateContentThrowsContentFieldValidationException(
5174
        $initialLanguageCode,
5175
        $structFields,
5176
        $existingFields,
5177
        $fieldDefinitions
5178
    ) {
5179
        $repositoryMock = $this->getRepositoryMock();
5180
        $permissionResolverMock = $this->getPermissionResolverMock();
5181
        $mockedService = $this->getPartlyMockedContentService(['internalLoadContentById', 'loadContent']);
5182
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
5183
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
5184
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
5185
        $fieldTypeMock = $this->createMock(SPIFieldType::class);
5186
        $existingLanguageCodes = array_map(
5187
            function (Field $field) {
5188
                return $field->languageCode;
5189
            },
5190
            $existingFields
5191
        );
5192
        $languageCodes = $this->determineLanguageCodesForUpdate(
5193
            $initialLanguageCode,
5194
            $structFields,
5195
            $existingLanguageCodes
5196
        );
5197
        $versionInfo = new VersionInfo(
5198
            [
5199
                'contentInfo' => new ContentInfo(
5200
                    [
5201
                        'id' => 42,
5202
                        'contentTypeId' => 24,
5203
                        'mainLanguageCode' => 'eng-GB',
5204
                    ]
5205
                ),
5206
                'versionNo' => 7,
5207
                'languageCodes' => $existingLanguageCodes,
5208
                'status' => VersionInfo::STATUS_DRAFT,
5209
            ]
5210
        );
5211
        $content = new Content(
5212
            [
5213
                'versionInfo' => $versionInfo,
5214
                'internalFields' => $existingFields,
5215
            ]
5216
        );
5217
        $contentType = new ContentType([
5218
            'fieldDefinitions' => new FieldDefinitionCollection($fieldDefinitions),
5219
        ]);
5220
5221
        $languageHandlerMock->expects($this->any())
5222
            ->method('loadByLanguageCode')
5223
            ->with($this->isType('string'))
5224
            ->will(
5225
                $this->returnCallback(
5226
                    function () {
5227
                        return new Language(['id' => 4242]);
5228
                    }
5229
                )
5230
            );
5231
5232
        $mockedService
5233
            ->method('internalLoadContentById')
5234
            ->will(
5235
                $this->returnValue($content)
5236
            );
5237
5238
        $mockedService
5239
            ->method('loadContent')
5240
            ->with(
5241
                $this->equalTo(42),
5242
                $this->equalTo(null),
5243
                $this->equalTo(7)
5244
            )
5245
            ->will(
5246
                $this->returnValue($content)
5247
            );
5248
5249
        $permissionResolverMock
5250
            ->method('canUser')
5251
            ->will($this->returnValue(true));
5252
5253
        $contentTypeServiceMock->expects($this->once())
5254
            ->method('loadContentType')
5255
            ->with($this->equalTo(24))
5256
            ->will($this->returnValue($contentType));
5257
5258
        $repositoryMock->expects($this->once())
5259
            ->method('getContentTypeService')
5260
            ->will($this->returnValue($contentTypeServiceMock));
5261
5262
        $fieldValues = $this->determineValuesForUpdate(
5263
            $initialLanguageCode,
5264
            $structFields,
5265
            $content,
5266
            $fieldDefinitions,
5267
            $languageCodes
5268
        );
5269
        $allFieldErrors = [];
5270
        $emptyValue = self::EMPTY_FIELD_VALUE;
5271
5272
        $fieldTypeMock->expects($this->exactly(count($fieldValues) * count($languageCodes)))
5273
            ->method('acceptValue')
5274
            ->will(
5275
                $this->returnCallback(
5276
                    function ($valueString) {
5277
                        return new ValueStub($valueString);
5278
                    }
5279
                )
5280
            );
5281
5282
        $fieldTypeMock->expects($this->exactly(count($fieldValues) * count($languageCodes)))
5283
            ->method('isEmptyValue')
5284
            ->will(
5285
                $this->returnCallback(
5286
                    function (ValueStub $value) use ($emptyValue) {
5287
                        return $emptyValue === (string)$value;
5288
                    }
5289
                )
5290
            );
5291
5292
        $fieldTypeMock
5293
            ->expects($this->any())
5294
            ->method('validate')
5295
            ->willReturnArgument(1);
5296
5297
        $this->getFieldTypeRegistryMock()->expects($this->any())
5298
            ->method('getFieldType')
5299
            ->will($this->returnValue($fieldTypeMock));
5300
5301
        $contentUpdateStruct = new ContentUpdateStruct(
5302
            [
5303
                'fields' => $structFields,
5304
                'initialLanguageCode' => $initialLanguageCode,
5305
            ]
5306
        );
5307
5308
        return [$content->versionInfo, $contentUpdateStruct, $allFieldErrors];
5309
    }
5310
5311
    public function providerForTestUpdateContentThrowsContentFieldValidationException()
5312
    {
5313
        $allFieldErrors = [
5314
            [
5315
                'fieldDefinitionId1' => [
5316
                    'eng-GB' => 'newValue1-eng-GB',
5317
                    'eng-US' => 'newValue1-eng-GB',
5318
                ],
5319
                'fieldDefinitionId2' => [
5320
                    'eng-GB' => 'initialValue2',
5321
                ],
5322
                'fieldDefinitionId3' => [
5323
                    'eng-GB' => 'initialValue3',
5324
                    'eng-US' => 'initialValue3',
5325
                ],
5326
                'fieldDefinitionId4' => [
5327
                    'eng-GB' => 'initialValue4',
5328
                    'eng-US' => 'newValue4',
5329
                ],
5330
            ],
5331
            [
5332
                'fieldDefinitionId1' => [
5333
                    'eng-GB' => 'newValue1-eng-GB',
5334
                    'eng-US' => 'newValue1-eng-GB',
5335
                ],
5336
                'fieldDefinitionId2' => [
5337
                    'eng-GB' => 'initialValue2',
5338
                ],
5339
                'fieldDefinitionId3' => [
5340
                    'eng-GB' => 'initialValue3',
5341
                    'eng-US' => 'initialValue3',
5342
                ],
5343
                'fieldDefinitionId4' => [
5344
                    'eng-GB' => 'initialValue4',
5345
                    'eng-US' => 'newValue4',
5346
                ],
5347
            ],
5348
            [
5349
                'fieldDefinitionId1' => [
5350
                    'eng-GB' => 'newValue1-eng-GB',
5351
                    'eng-US' => 'newValue1-eng-GB',
5352
                ],
5353
                'fieldDefinitionId2' => [
5354
                    'eng-GB' => 'initialValue2',
5355
                    'eng-US' => 'newValue2',
5356
                ],
5357
                'fieldDefinitionId3' => [
5358
                    'eng-GB' => 'initialValue3',
5359
                    'eng-US' => 'initialValue3',
5360
                ],
5361
                'fieldDefinitionId4' => [
5362
                    'eng-GB' => 'initialValue4',
5363
                    'eng-US' => 'defaultValue4',
5364
                ],
5365
            ],
5366
            [
5367
                'fieldDefinitionId1' => [
5368
                    'eng-GB' => 'newValue1-eng-GB',
5369
                    'eng-US' => 'newValue1-eng-GB',
5370
                ],
5371
                'fieldDefinitionId2' => [
5372
                    'eng-GB' => 'initialValue2',
5373
                    'eng-US' => 'newValue2',
5374
                ],
5375
                'fieldDefinitionId3' => [
5376
                    'eng-GB' => 'initialValue3',
5377
                    'eng-US' => 'initialValue3',
5378
                ],
5379
                'fieldDefinitionId4' => [
5380
                    'eng-GB' => 'initialValue4',
5381
                    'eng-US' => 'defaultValue4',
5382
                ],
5383
            ],
5384
            [
5385
                'fieldDefinitionId1' => [
5386
                    'eng-GB' => 'newValue1-eng-GB',
5387
                    'ger-DE' => 'newValue1-eng-GB',
5388
                    'eng-US' => 'newValue1-eng-GB',
5389
                ],
5390
                'fieldDefinitionId2' => [
5391
                    'eng-GB' => 'initialValue2',
5392
                    'eng-US' => 'newValue2',
5393
                ],
5394
                'fieldDefinitionId3' => [
5395
                    'eng-GB' => 'initialValue3',
5396
                    'ger-DE' => 'initialValue3',
5397
                    'eng-US' => 'initialValue3',
5398
                ],
5399
                'fieldDefinitionId4' => [
5400
                    'eng-GB' => 'initialValue4',
5401
                    'eng-US' => 'defaultValue4',
5402
                    'ger-DE' => 'defaultValue4',
5403
                ],
5404
            ],
5405
            [
5406
                'fieldDefinitionId1' => [
5407
                    'eng-US' => 'newValue1-eng-GB',
5408
                    'ger-DE' => 'newValue1-eng-GB',
5409
                ],
5410
                'fieldDefinitionId2' => [
5411
                    'eng-US' => 'newValue2',
5412
                ],
5413
                'fieldDefinitionId3' => [
5414
                    'ger-DE' => 'initialValue3',
5415
                    'eng-US' => 'initialValue3',
5416
                ],
5417
                'fieldDefinitionId4' => [
5418
                    'ger-DE' => 'defaultValue4',
5419
                    'eng-US' => 'defaultValue4',
5420
                ],
5421
            ],
5422
        ];
5423
5424
        $data = $this->providerForTestUpdateContentNonRedundantFieldSetComplex();
5425
        $count = count($data);
5426
        for ($i = 0; $i < $count; ++$i) {
5427
            $data[$i][] = $allFieldErrors[$i];
5428
        }
5429
5430
        return $data;
5431
    }
5432
5433
    /**
5434
     * Test for the updateContent() method.
5435
     *
5436
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
5437
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
5438
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
5439
     * @dataProvider providerForTestUpdateContentThrowsContentFieldValidationException
5440
     */
5441
    public function testUpdateContentThrowsContentFieldValidationException($initialLanguageCode, $structFields, $spiField, $allFieldErrors)
5442
    {
5443
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException::class);
5444
        $this->expectExceptionMessage('Content Fields did not validate');
5445
5446
        list($existingFields, $fieldDefinitions) = $this->fixturesForTestUpdateContentNonRedundantFieldSetComplex();
5447
        list($versionInfo, $contentUpdateStruct) =
5448
            $this->assertForTestUpdateContentThrowsContentFieldValidationException(
5449
                $initialLanguageCode,
5450
                $structFields,
5451
                $existingFields,
5452
                $fieldDefinitions
5453
            );
5454
5455
        try {
5456
            $this->partlyMockedContentService->updateContent($versionInfo, $contentUpdateStruct);
5457
        } catch (ContentFieldValidationException $e) {
5458
            $this->assertEquals($allFieldErrors, $e->getFieldErrors());
5459
            throw $e;
5460
        }
5461
    }
5462
5463
    /**
5464
     * Test for the updateContent() method.
5465
     *
5466
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
5467
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
5468
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
5469
     */
5470
    public function testUpdateContentTransactionRollback()
5471
    {
5472
        $this->expectException(\Exception::class);
5473
        $this->expectExceptionMessage('Store failed');
5474
5475
        $existingFields = [
5476
            new Field(
5477
                [
5478
                    'id' => '100',
5479
                    'fieldDefIdentifier' => 'identifier',
5480
                    'value' => 'initialValue',
5481
                    'languageCode' => 'eng-GB',
5482
                ]
5483
            ),
5484
        ];
5485
5486
        $fieldDefinitions = [
5487
            new FieldDefinition(
5488
                [
5489
                    'id' => 'fieldDefinitionId',
5490
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
5491
                    'isTranslatable' => false,
5492
                    'identifier' => 'identifier',
5493
                    'isRequired' => false,
5494
                    'defaultValue' => 'defaultValue',
5495
                ]
5496
            ),
5497
        ];
5498
5499
        // Setup a simple case that will pass
5500
        list($versionInfo, $contentUpdateStruct) = $this->assertForTestUpdateContentNonRedundantFieldSet(
5501
            'eng-US',
5502
            [],
5503
            [],
5504
            $existingFields,
5505
            $fieldDefinitions,
5506
            // Do not execute test
5507
            false
5508
        );
5509
5510
        $repositoryMock = $this->getRepositoryMock();
5511
        $repositoryMock->expects($this->never())->method('commit');
5512
        $repositoryMock->expects($this->once())->method('rollback');
5513
5514
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandlerMock */
5515
        $contentHandlerMock = $this->getPersistenceMock()->contentHandler();
5516
        $contentHandlerMock->expects($this->once())
5517
            ->method('updateContent')
5518
            ->with(
5519
                $this->anything(),
5520
                $this->anything(),
5521
                $this->anything()
5522
            )->will($this->throwException(new \Exception('Store failed')));
5523
5524
        // Execute
5525
        $this->partlyMockedContentService->updateContent($versionInfo, $contentUpdateStruct);
5526
    }
5527
5528
    /**
5529
     * Test for the copyContent() method.
5530
     *
5531
     * @covers \eZ\Publish\Core\Repository\ContentService::copyContent
5532
     */
5533
    public function testCopyContentThrowsUnauthorizedException()
5534
    {
5535
        $this->expectException(\eZ\Publish\Core\Base\Exceptions\UnauthorizedException::class);
5536
5537
        $repository = $this->getRepositoryMock();
5538
        $contentService = $this->getPartlyMockedContentService(['internalLoadContentInfo']);
5539
        $contentInfo = $this->createMock(APIContentInfo::class);
5540
        $locationCreateStruct = new LocationCreateStruct();
5541
        $location = new Location(['id' => $locationCreateStruct->parentLocationId]);
5542
        $locationServiceMock = $this->getLocationServiceMock();
5543
        $permissionResolver = $this->getPermissionResolverMock();
5544
5545
        $repository->expects($this->once())
5546
            ->method('getLocationService')
5547
            ->will($this->returnValue($locationServiceMock));
5548
5549
        $locationServiceMock->expects($this->once())
5550
            ->method('loadLocation')
5551
            ->with(
5552
                $locationCreateStruct->parentLocationId
5553
            )
5554
            ->will($this->returnValue($location));
5555
5556
        $contentInfo->expects($this->any())
5557
            ->method('__get')
5558
            ->with('sectionId')
5559
            ->will($this->returnValue(42));
5560
5561
        $permissionResolver
5562
            ->method('canUser')
5563
            ->with(
5564
                'content',
5565
                'create',
5566
                $contentInfo,
5567
                [$location]
5568
            )
5569
            ->will($this->returnValue(false));
5570
5571
        /* @var \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo */
5572
        $contentService->copyContent($contentInfo, $locationCreateStruct);
5573
    }
5574
5575
    /**
5576
     * Test for the copyContent() method.
5577
     *
5578
     * @covers \eZ\Publish\Core\Repository\ContentService::copyContent
5579
     * @covers \eZ\Publish\Core\Repository\ContentService::getDefaultObjectStates
5580
     * @covers \eZ\Publish\Core\Repository\ContentService::internalPublishVersion
5581
     */
5582
    public function testCopyContent()
5583
    {
5584
        $repositoryMock = $this->getRepositoryMock();
5585
        $contentService = $this->getPartlyMockedContentService([
5586
            'internalLoadContentInfo',
5587
            'internalLoadContentById',
5588
            'getUnixTimestamp',
5589
        ]);
5590
        $locationServiceMock = $this->getLocationServiceMock();
5591
        $contentInfoMock = $this->createMock(APIContentInfo::class);
5592
        $locationCreateStruct = new LocationCreateStruct();
5593
        $location = new Location(['id' => $locationCreateStruct->parentLocationId]);
5594
        $user = $this->getStubbedUser(14);
5595
5596
        $permissionResolverMock = $this->getPermissionResolverMock();
5597
5598
        $permissionResolverMock
5599
            ->method('getCurrentUserReference')
5600
            ->willReturn($user);
5601
5602
        $repositoryMock
5603
            ->method('getPermissionResolver')
5604
            ->willReturn($permissionResolverMock);
5605
5606
        $repositoryMock->expects($this->exactly(3))
5607
            ->method('getLocationService')
5608
            ->will($this->returnValue($locationServiceMock));
5609
5610
        $locationServiceMock->expects($this->once())
5611
            ->method('loadLocation')
5612
            ->with($locationCreateStruct->parentLocationId)
5613
            ->will($this->returnValue($location));
5614
5615
        $contentInfoMock->expects($this->any())
5616
            ->method('__get')
5617
            ->will(
5618
                $this->returnValueMap(
5619
                    [
5620
                        ['isHidden', true],
5621
                        ['id', 42],
5622
                    ]
5623
                )
5624
            );
5625
        $versionInfoMock = $this->createMock(APIVersionInfo::class);
5626
5627
        $versionInfoMock->expects($this->any())
5628
            ->method('__get')
5629
            ->will(
5630
                $this->returnValueMap(
5631
                    [
5632
                        ['versionNo', 123],
5633
                    ]
5634
                )
5635
            );
5636
5637
        $versionInfoMock->expects($this->once())
5638
            ->method('isDraft')
5639
            ->willReturn(true);
5640
5641
        $versionInfoMock->expects($this->once())
5642
            ->method('getContentInfo')
5643
            ->will($this->returnValue($contentInfoMock));
5644
5645
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandlerMock */
5646
        $contentHandlerMock = $this->getPersistenceMock()->contentHandler();
5647
        $domainMapperMock = $this->getContentDomainMapperMock();
5648
5649
        $repositoryMock->expects($this->once())->method('beginTransaction');
5650
        $repositoryMock->expects($this->once())->method('commit');
5651
5652
        $permissionResolverMock
5653
            ->method('canUser')
5654
            ->willReturnMap(
5655
                [
5656
                    ['content', 'create', $contentInfoMock, [$location], true],
5657
                    ['content', 'manage_locations', $contentInfoMock, [$location], true],
5658
                ]
5659
            );
5660
5661
        $spiContentInfo = new SPIContentInfo(['id' => 42]);
5662
        $spiVersionInfo = new SPIVersionInfo(
5663
            [
5664
                'contentInfo' => $spiContentInfo,
5665
                'creationDate' => 123456,
5666
            ]
5667
        );
5668
        $spiContent = new SPIContent(['versionInfo' => $spiVersionInfo]);
5669
        $contentHandlerMock->expects($this->once())
5670
            ->method('copy')
5671
            ->with(42, null)
5672
            ->will($this->returnValue($spiContent));
5673
5674
        $this->mockGetDefaultObjectStates();
5675
        $this->mockSetDefaultObjectStates();
5676
5677
        $domainMapperMock->expects($this->once())
5678
            ->method('buildVersionInfoDomainObject')
5679
            ->with($spiVersionInfo)
5680
            ->will($this->returnValue($versionInfoMock));
5681
5682
        /* @var \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfoMock */
5683
        $content = $this->mockPublishVersion(123456, 126666, true);
5684
        $locationServiceMock->expects($this->once())
5685
            ->method('createLocation')
5686
            ->with(
5687
                $content->getVersionInfo()->getContentInfo(),
5688
                $locationCreateStruct
5689
            );
5690
5691
        $contentService->expects($this->once())
5692
            ->method('internalLoadContentById')
5693
            ->with(
5694
                $content->id
5695
            )
5696
            ->will($this->returnValue($content));
5697
5698
        $contentService->expects($this->once())
5699
            ->method('getUnixTimestamp')
5700
            ->will($this->returnValue(126666));
5701
5702
        /* @var \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfoMock */
5703
        $contentService->copyContent($contentInfoMock, $locationCreateStruct, null);
5704
    }
5705
5706
    /**
5707
     * Test for the copyContent() method.
5708
     *
5709
     * @covers \eZ\Publish\Core\Repository\ContentService::copyContent
5710
     * @covers \eZ\Publish\Core\Repository\ContentService::getDefaultObjectStates
5711
     * @covers \eZ\Publish\Core\Repository\ContentService::internalPublishVersion
5712
     */
5713
    public function testCopyContentWithVersionInfo()
5714
    {
5715
        $repositoryMock = $this->getRepositoryMock();
5716
        $contentService = $this->getPartlyMockedContentService([
5717
            'internalLoadContentById',
5718
            'getUnixTimestamp',
5719
        ]);
5720
        $locationServiceMock = $this->getLocationServiceMock();
5721
        $contentInfoMock = $this->createMock(APIContentInfo::class);
5722
        $locationCreateStruct = new LocationCreateStruct();
5723
        $location = new Location(['id' => $locationCreateStruct->parentLocationId]);
5724
        $user = $this->getStubbedUser(14);
5725
5726
        $permissionResolverMock = $this->getPermissionResolverMock();
5727
5728
        $permissionResolverMock
5729
            ->method('getCurrentUserReference')
5730
            ->willReturn($user);
5731
5732
        $repositoryMock
5733
            ->method('getPermissionResolver')
5734
            ->willReturn($permissionResolverMock);
5735
5736
        $repositoryMock->expects($this->exactly(3))
5737
            ->method('getLocationService')
5738
            ->will($this->returnValue($locationServiceMock));
5739
5740
        $locationServiceMock->expects($this->once())
5741
            ->method('loadLocation')
5742
            ->with($locationCreateStruct->parentLocationId)
5743
            ->will($this->returnValue($location));
5744
5745
        $contentInfoMock->expects($this->any())
5746
            ->method('__get')
5747
            ->will(
5748
                $this->returnValueMap([
5749
                    ['isHidden', true],
5750
                    ['id', 42],
5751
                ])
5752
            );
5753
5754
        $versionInfoMock = $this->createMock(APIVersionInfo::class);
5755
5756
        $versionInfoMock->expects($this->any())
5757
            ->method('__get')
5758
            ->will(
5759
                $this->returnValueMap(
5760
                    [
5761
                        ['versionNo', 123],
5762
                    ]
5763
                )
5764
            );
5765
        $versionInfoMock->expects($this->once())
5766
            ->method('isDraft')
5767
            ->willReturn(true);
5768
        $versionInfoMock->expects($this->once())
5769
            ->method('getContentInfo')
5770
            ->will($this->returnValue($contentInfoMock));
5771
5772
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandlerMock */
5773
        $contentHandlerMock = $this->getPersistenceMock()->contentHandler();
5774
        $domainMapperMock = $this->getContentDomainMapperMock();
5775
5776
        $repositoryMock->expects($this->once())->method('beginTransaction');
5777
        $repositoryMock->expects($this->once())->method('commit');
5778
5779
        $permissionResolverMock
5780
            ->method('canUser')
5781
            ->willReturnMap(
5782
                [
5783
                    ['content', 'create', $contentInfoMock, [$location], true],
5784
                    ['content', 'manage_locations', $contentInfoMock, [$location], true],
5785
                ]
5786
            );
5787
5788
        $spiContentInfo = new SPIContentInfo(['id' => 42]);
5789
        $spiVersionInfo = new SPIVersionInfo(
5790
            [
5791
                'contentInfo' => $spiContentInfo,
5792
                'creationDate' => 123456,
5793
            ]
5794
        );
5795
        $spiContent = new SPIContent(['versionInfo' => $spiVersionInfo]);
5796
        $contentHandlerMock->expects($this->once())
5797
            ->method('copy')
5798
            ->with(42, 123)
5799
            ->will($this->returnValue($spiContent));
5800
5801
        $this->mockGetDefaultObjectStates();
5802
        $this->mockSetDefaultObjectStates();
5803
5804
        $domainMapperMock->expects($this->once())
5805
            ->method('buildVersionInfoDomainObject')
5806
            ->with($spiVersionInfo)
5807
            ->will($this->returnValue($versionInfoMock));
5808
5809
        /* @var \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfoMock */
5810
        $content = $this->mockPublishVersion(123456, 126666, true);
5811
        $locationServiceMock->expects($this->once())
5812
            ->method('createLocation')
5813
            ->with(
5814
                $content->getVersionInfo()->getContentInfo(),
5815
                $locationCreateStruct
5816
            );
5817
5818
        $contentService->expects($this->once())
5819
            ->method('internalLoadContentById')
5820
            ->with(
5821
                $content->id
5822
            )
5823
            ->will($this->returnValue($content));
5824
5825
        $contentService->expects($this->once())
5826
            ->method('getUnixTimestamp')
5827
            ->will($this->returnValue(126666));
5828
5829
        /* @var \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfoMock */
5830
        $contentService->copyContent($contentInfoMock, $locationCreateStruct, $versionInfoMock);
5831
    }
5832
5833
    /**
5834
     * Test for the copyContent() method.
5835
     *
5836
     * @covers \eZ\Publish\Core\Repository\ContentService::copyContent
5837
     * @covers \eZ\Publish\Core\Repository\ContentService::getDefaultObjectStates
5838
     * @covers \eZ\Publish\Core\Repository\ContentService::internalPublishVersion
5839
     */
5840
    public function testCopyContentWithRollback()
5841
    {
5842
        $this->expectException(\Exception::class);
5843
        $this->expectExceptionMessage('Handler threw an exception');
5844
5845
        $repositoryMock = $this->getRepositoryMock();
5846
        $contentService = $this->getPartlyMockedContentService();
5847
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandlerMock */
5848
        $contentHandlerMock = $this->getPersistenceMock()->contentHandler();
5849
        $locationCreateStruct = new LocationCreateStruct();
5850
        $location = new Location(['id' => $locationCreateStruct->parentLocationId]);
5851
        $locationServiceMock = $this->getLocationServiceMock();
5852
        $user = $this->getStubbedUser(14);
5853
5854
        $permissionResolverMock = $this->getPermissionResolverMock();
5855
5856
        $permissionResolverMock
5857
            ->method('getCurrentUserReference')
5858
            ->willReturn($user);
5859
5860
        $repositoryMock
5861
            ->method('getPermissionResolver')
5862
            ->willReturn($permissionResolverMock);
5863
5864
        $repositoryMock->expects($this->once())
5865
            ->method('getLocationService')
5866
            ->will($this->returnValue($locationServiceMock));
5867
5868
        $locationServiceMock->expects($this->once())
5869
            ->method('loadLocation')
5870
            ->with($locationCreateStruct->parentLocationId)
5871
            ->will($this->returnValue($location));
5872
5873
        $contentInfoMock = $this->createMock(APIContentInfo::class);
5874
        $contentInfoMock->expects($this->any())
5875
            ->method('__get')
5876
            ->with('id')
5877
            ->will($this->returnValue(42));
5878
5879
        $this->mockGetDefaultObjectStates();
5880
5881
        $repositoryMock->expects($this->once())->method('beginTransaction');
5882
        $repositoryMock->expects($this->once())->method('rollback');
5883
5884
        $permissionResolverMock
5885
            ->method('canUser')
5886
            ->willReturnMap(
5887
                [
5888
                    ['content', 'create', $contentInfoMock, [$location], true],
5889
                    ['content', 'manage_locations', $contentInfoMock, [$location], true],
5890
                ]
5891
            );
5892
5893
        $contentHandlerMock->expects($this->once())
5894
            ->method('copy')
5895
            ->with(42, null)
5896
            ->will($this->throwException(new Exception('Handler threw an exception')));
5897
5898
        /* @var \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfoMock */
5899
        $contentService->copyContent($contentInfoMock, $locationCreateStruct, null);
5900
    }
5901
5902
    /**
5903
     * Reusable method for setting exceptions on buildContentDomainObject usage.
5904
     *
5905
     * Plain usage as in when content type is loaded directly.
5906
     *
5907
     * @param \eZ\Publish\SPI\Persistence\Content $spiContent
5908
     * @param array $translations
5909
     * @param bool $useAlwaysAvailable
5910
     *
5911
     * @return \PHPUnit\Framework\MockObject\MockObject|\eZ\Publish\API\Repository\Values\Content\Content
5912
     */
5913
    private function mockBuildContentDomainObject(SPIContent $spiContent, array $translations = null, bool $useAlwaysAvailable = null)
5914
    {
5915
        $contentTypeId = $spiContent->versionInfo->contentInfo->contentTypeId;
5916
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
5917
        $repositoryMock = $this->getRepositoryMock();
5918
5919
        $contentType = new ContentType([
5920
            'id' => $contentTypeId,
5921
            'fieldDefinitions' => [],
5922
        ]);
5923
5924
        $repositoryMock->expects($this->once())
5925
            ->method('getContentTypeService')
5926
            ->willReturn($contentTypeServiceMock);
5927
5928
        $contentTypeServiceMock->expects($this->once())
5929
            ->method('loadContentType')
5930
            ->with($this->equalTo($contentTypeId))
5931
            ->willReturn($contentType);
5932
5933
        $content = $this->createMock(APIContent::class);
5934
5935
        $this->getContentDomainMapperMock()
5936
            ->expects($this->once())
5937
            ->method('buildContentDomainObject')
5938
            ->with($spiContent, $contentType, $translations ?? [], $useAlwaysAvailable)
5939
            ->willReturn($content);
5940
5941
        return $content;
5942
    }
5943
5944
    protected function mockGetDefaultObjectStates()
5945
    {
5946
        /** @var \PHPUnit\Framework\MockObject\MockObject $objectStateHandlerMock */
5947
        $objectStateHandlerMock = $this->getPersistenceMock()->objectStateHandler();
5948
5949
        $objectStateGroups = [
5950
            new SPIObjectStateGroup(['id' => 10]),
5951
            new SPIObjectStateGroup(['id' => 20]),
5952
        ];
5953
5954
        /* @var \PHPUnit\Framework\MockObject\MockObject $objectStateHandlerMock */
5955
        $objectStateHandlerMock->expects($this->once())
5956
            ->method('loadAllGroups')
5957
            ->will($this->returnValue($objectStateGroups));
5958
5959
        $objectStateHandlerMock->expects($this->at(1))
5960
            ->method('loadObjectStates')
5961
            ->with($this->equalTo(10))
5962
            ->will(
5963
                $this->returnValue(
5964
                    [
5965
                        new SPIObjectState(['id' => 11, 'groupId' => 10]),
5966
                        new SPIObjectState(['id' => 12, 'groupId' => 10]),
5967
                    ]
5968
                )
5969
            );
5970
5971
        $objectStateHandlerMock->expects($this->at(2))
5972
            ->method('loadObjectStates')
5973
            ->with($this->equalTo(20))
5974
            ->will(
5975
                $this->returnValue(
5976
                    [
5977
                        new SPIObjectState(['id' => 21, 'groupId' => 20]),
5978
                        new SPIObjectState(['id' => 22, 'groupId' => 20]),
5979
                    ]
5980
                )
5981
            );
5982
    }
5983
5984
    protected function mockSetDefaultObjectStates()
5985
    {
5986
        /** @var \PHPUnit\Framework\MockObject\MockObject $objectStateHandlerMock */
5987
        $objectStateHandlerMock = $this->getPersistenceMock()->objectStateHandler();
5988
5989
        $defaultObjectStates = [
5990
            new SPIObjectState(['id' => 11, 'groupId' => 10]),
5991
            new SPIObjectState(['id' => 21, 'groupId' => 20]),
5992
        ];
5993
        foreach ($defaultObjectStates as $index => $objectState) {
5994
            $objectStateHandlerMock->expects($this->at($index + 3))
5995
                ->method('setContentState')
5996
                ->with(
5997
                    42,
5998
                    $objectState->groupId,
5999
                    $objectState->id
6000
                );
6001
        }
6002
    }
6003
6004
    /**
6005
     * @param int|null $publicationDate
6006
     * @param int|null $modificationDate
6007
     * @param bool $isHidden
6008
     *
6009
     * @return \eZ\Publish\API\Repository\Values\Content\Content
6010
     */
6011
    protected function mockPublishVersion($publicationDate = null, $modificationDate = null, $isHidden = false)
6012
    {
6013
        $versionInfoMock = $this->createMock(APIVersionInfo::class);
6014
        $contentInfoMock = $this->createMock(APIContentInfo::class);
6015
        /* @var \PHPUnit\Framework\MockObject\MockObject $contentHandlerMock */
6016
        $contentHandlerMock = $this->getPersistenceMock()->contentHandler();
6017
        $metadataUpdateStruct = new SPIMetadataUpdateStruct();
6018
6019
        $spiContent = new SPIContent([
6020
            'versionInfo' => new VersionInfo([
6021
                'contentInfo' => new ContentInfo(['id' => 42, 'contentTypeId' => 123]),
6022
            ]),
6023
        ]);
6024
6025
        $contentMock = $this->mockBuildContentDomainObject($spiContent);
6026
        $contentMock->expects($this->any())
6027
            ->method('__get')
6028
            ->will(
6029
                $this->returnValueMap(
6030
                    [
6031
                        ['id', 42],
6032
                        ['contentInfo', $contentInfoMock],
6033
                        ['versionInfo', $versionInfoMock],
6034
                    ]
6035
                )
6036
            );
6037
        $contentMock->expects($this->any())
6038
            ->method('getVersionInfo')
6039
            ->will($this->returnValue($versionInfoMock));
6040
        $versionInfoMock->expects($this->any())
6041
            ->method('getContentInfo')
6042
            ->will($this->returnValue($contentInfoMock));
6043
        $versionInfoMock->expects($this->any())
6044
            ->method('__get')
6045
            ->will(
6046
                $this->returnValueMap(
6047
                    [
6048
                        ['languageCodes', ['eng-GB']],
6049
                    ]
6050
                )
6051
            );
6052
        $contentInfoMock->expects($this->any())
6053
            ->method('__get')
6054
            ->will(
6055
                $this->returnValueMap(
6056
                    [
6057
                        ['alwaysAvailable', true],
6058
                        ['mainLanguageCode', 'eng-GB'],
6059
                    ]
6060
                )
6061
            );
6062
6063
        $currentTime = time();
6064
        if ($publicationDate === null && $versionInfoMock->versionNo === 1) {
6065
            $publicationDate = $currentTime;
6066
        }
6067
6068
        // Account for 1 second of test execution time
6069
        $metadataUpdateStruct->publicationDate = $publicationDate;
6070
        $metadataUpdateStruct->modificationDate = $modificationDate ?? $currentTime;
6071
        $metadataUpdateStruct->isHidden = $isHidden;
6072
6073
        $contentHandlerMock->expects($this->once())
6074
            ->method('publish')
6075
            ->with(
6076
                42,
6077
                123,
6078
                $metadataUpdateStruct
6079
            )
6080
            ->will($this->returnValue($spiContent));
6081
6082
        /* @var \eZ\Publish\API\Repository\Values\Content\Content $contentMock */
6083
        $this->mockPublishUrlAliasesForContent($contentMock);
6084
6085
        return $contentMock;
6086
    }
6087
6088
    /**
6089
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
6090
     */
6091
    protected function mockPublishUrlAliasesForContent(APIContent $content)
6092
    {
6093
        $nameSchemaServiceMock = $this->getNameSchemaServiceMock();
6094
        /** @var \PHPUnit\Framework\MockObject\MockObject $urlAliasHandlerMock */
6095
        $urlAliasHandlerMock = $this->getPersistenceMock()->urlAliasHandler();
6096
        $locationServiceMock = $this->getLocationServiceMock();
6097
        $location = $this->createMock(APILocation::class);
6098
6099
        $location->expects($this->at(0))
6100
            ->method('__get')
6101
            ->with('id')
6102
            ->will($this->returnValue(123));
6103
        $location->expects($this->at(1))
6104
            ->method('__get')
6105
            ->with('parentLocationId')
6106
            ->will($this->returnValue(456));
6107
6108
        $urlAliasNames = ['eng-GB' => 'hello'];
6109
        $nameSchemaServiceMock->expects($this->once())
6110
            ->method('resolveUrlAliasSchema')
6111
            ->with($content)
6112
            ->will($this->returnValue($urlAliasNames));
6113
6114
        $locationServiceMock->expects($this->once())
6115
            ->method('loadLocations')
6116
            ->with($content->getVersionInfo()->getContentInfo())
6117
            ->will($this->returnValue([$location]));
6118
6119
        $urlAliasHandlerMock->expects($this->once())
6120
            ->method('publishUrlAliasForLocation')
6121
            ->with(123, 456, 'hello', 'eng-GB', true, true);
6122
6123
        $location->expects($this->at(2))
6124
            ->method('__get')
6125
            ->with('id')
6126
            ->will($this->returnValue(123));
6127
6128
        $location->expects($this->at(3))
6129
            ->method('__get')
6130
            ->with('parentLocationId')
6131
            ->will($this->returnValue(456));
6132
6133
        $urlAliasHandlerMock->expects($this->once())
6134
            ->method('archiveUrlAliasesForDeletedTranslations')
6135
            ->with(123, 456, ['eng-GB']);
6136
    }
6137
6138
    protected $relationProcessorMock;
6139
6140
    /**
6141
     * @return \PHPUnit\Framework\MockObject\MockObject|\eZ\Publish\Core\Repository\Helper\RelationProcessor
6142
     */
6143
    protected function getRelationProcessorMock()
6144
    {
6145
        if (!isset($this->relationProcessorMock)) {
6146
            $this->relationProcessorMock = $this->createMock(RelationProcessor::class);
6147
        }
6148
6149
        return $this->relationProcessorMock;
6150
    }
6151
6152
    protected $nameSchemaServiceMock;
6153
6154
    /**
6155
     * @return \PHPUnit\Framework\MockObject\MockObject|\eZ\Publish\Core\Repository\Helper\NameSchemaService
6156
     */
6157
    protected function getNameSchemaServiceMock()
6158
    {
6159
        if (!isset($this->nameSchemaServiceMock)) {
6160
            $this->nameSchemaServiceMock = $this->createMock(NameSchemaService::class);
6161
        }
6162
6163
        return $this->nameSchemaServiceMock;
6164
    }
6165
6166
    protected $contentTypeServiceMock;
6167
6168
    /**
6169
     * @return \PHPUnit\Framework\MockObject\MockObject|\eZ\Publish\API\Repository\ContentTypeService
6170
     */
6171
    protected function getContentTypeServiceMock()
6172
    {
6173
        if (!isset($this->contentTypeServiceMock)) {
6174
            $this->contentTypeServiceMock = $this->createMock(APIContentTypeService::class);
6175
        }
6176
6177
        return $this->contentTypeServiceMock;
6178
    }
6179
6180
    protected $locationServiceMock;
6181
6182
    /**
6183
     * @return \PHPUnit\Framework\MockObject\MockObject|\eZ\Publish\API\Repository\LocationService
6184
     */
6185
    protected function getLocationServiceMock()
6186
    {
6187
        if (!isset($this->locationServiceMock)) {
6188
            $this->locationServiceMock = $this->createMock(APILocationService::class);
6189
        }
6190
6191
        return $this->locationServiceMock;
6192
    }
6193
6194
    /** @var \eZ\Publish\Core\Repository\ContentService */
6195
    protected $partlyMockedContentService;
6196
6197
    /**
6198
     * Returns the content service to test with $methods mocked.
6199
     *
6200
     * Injected Repository comes from {@see getRepositoryMock()} and persistence handler from {@see getPersistenceMock()}
6201
     *
6202
     * @param string[] $methods
6203
     *
6204
     * @return \eZ\Publish\Core\Repository\ContentService|\PHPUnit\Framework\MockObject\MockObject
6205
     */
6206
    protected function getPartlyMockedContentService(array $methods = null)
6207
    {
6208
        if (!isset($this->partlyMockedContentService)) {
6209
            $this->partlyMockedContentService = $this->getMockBuilder(ContentService::class)
6210
                ->setMethods($methods)
6211
                ->setConstructorArgs(
6212
                    [
6213
                        $this->getRepositoryMock(),
6214
                        $this->getPersistenceMock(),
6215
                        $this->getContentDomainMapperMock(),
6216
                        $this->getRelationProcessorMock(),
6217
                        $this->getNameSchemaServiceMock(),
6218
                        $this->getFieldTypeRegistryMock(),
6219
                        $this->getPermissionResolverMock(),
6220
                        [],
6221
                    ]
6222
                )
6223
                ->getMock();
6224
        }
6225
6226
        return $this->partlyMockedContentService;
6227
    }
6228
6229
    /**
6230
     * @return \eZ\Publish\API\Repository\Repository|\PHPUnit\Framework\MockObject\MockObject
6231
     */
6232
    protected function getRepositoryMock(): Repository
6233
    {
6234
        $repositoryMock = parent::getRepositoryMock();
6235
        $repositoryMock
6236
            ->expects($this->any())
6237
            ->method('getPermissionResolver')
6238
            ->willReturn($this->getPermissionResolverMock());
6239
6240
        return $repositoryMock;
6241
    }
6242
}
6243