ContentTest   F
last analyzed

Complexity

Total Complexity 158

Size/Duplication

Total Lines 6185
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 35

Importance

Changes 0
Metric Value
wmc 158
lcom 1
cbo 35
dl 0
loc 6185
rs 0.8
c 0
b 0
f 0

104 Methods

Rating   Name   Duplication   Size   Complexity  
A testLoadVersionInfoByIdAndVersionNumber() 0 41 1
A testLoadVersionInfoByIdThrowsNotFoundException() 0 27 1
A testConstructor() 0 23 1
A testLoadVersionInfoById() 0 42 1
A testLoadVersionInfoByIdThrowsUnauthorizedExceptionNonPublishedVersion() 0 39 1
A testLoadContent() 0 30 1
A testLoadContentUnauthorized() 0 23 1
A testLoadVersionInfoByIdPublishedVersion() 0 39 1
A testLoadVersionInfoByIdNonPublishedVersion() 0 39 1
A testLoadVersionInfo() 0 25 1
A testLoadContentNonPublished() 0 32 1
A testLoadContentNotPublishedStatusUnauthorized() 0 35 1
A testInternalLoadContentById() 0 38 3
A testInternalLoadContentByRemoteId() 0 41 2
A internalLoadContentProviderById() 0 12 1
A internalLoadContentProviderByRemoteId() 0 12 1
A testInternalLoadContentByIdNotFound() 0 29 1
A testInternalLoadContentByRemoteIdNotFound() 0 28 1
A testLoadContentByContentInfo() 0 36 1
A testLoadContentByVersionInfo() 0 32 1
A testDeleteContentThrowsUnauthorizedException() 0 26 1
B testDeleteContent() 0 56 2
A testDeleteContentWithRollback() 0 40 1
A testDeleteVersionThrowsBadStateExceptionLastVersion() 0 53 1
A testCreateContentThrowsInvalidArgumentExceptionMainLanguageCodeNotSet() 0 8 1
A testCreateContentThrowsInvalidArgumentExceptionContentTypeNotSet() 0 11 1
B testCreateContentThrowsUnauthorizedException() 0 56 1
B testCreateContentThrowsInvalidArgumentExceptionDuplicateRemoteId() 0 63 1
A mapStructFieldsForCreate() 0 20 4
B determineValuesForCreate() 0 37 6
A determineLanguageCodesForCreate() 0 16 4
B assertForTestCreateContentNonRedundantFieldSet() 0 222 3
A providerForTestCreateContentNonRedundantFieldSet1() 0 44 1
A testCreateContentNonRedundantFieldSet1() 0 22 1
B providerForTestCreateContentNonRedundantFieldSet2() 0 66 1
A testCreateContentNonRedundantFieldSet2() 0 32 1
B providerForTestCreateContentNonRedundantFieldSetComplex() 0 120 1
A fixturesForTestCreateContentNonRedundantFieldSetComplex() 0 45 1
A testCreateContentNonRedundantFieldSetComplex() 0 11 1
A providerForTestCreateContentWithInvalidLanguage() 0 29 1
B testCreateContentWithInvalidLanguage() 0 87 2
A assertForCreateContentContentValidationException() 0 54 1
A providerForTestCreateContentThrowsContentValidationExceptionFieldDefinition() 0 17 1
A testCreateContentThrowsContentValidationExceptionFieldDefinition() 0 11 1
A providerForTestCreateContentThrowsContentValidationExceptionTranslation() 0 17 1
A testCreateContentThrowsContentValidationExceptionTranslation() 0 24 1
B assertForTestCreateContentRequiredField() 0 113 1
A providerForTestCreateContentThrowsContentValidationExceptionRequiredField() 0 19 1
A testCreateContentRequiredField() 0 39 2
B assertForTestCreateContentThrowsContentFieldValidationException() 0 147 4
A providerForTestCreateContentThrowsContentFieldValidationException() 0 4 1
A testCreateContentThrowsContentFieldValidationException() 0 22 2
B testCreateContentWithLocations() 0 128 1
B testCreateContentWithLocationsDuplicateUnderParent() 0 123 1
B testCreateContentObjectStates() 0 97 1
A testCreateContentWithRollback() 0 44 1
A providerForTestUpdateContentThrowsBadStateException() 0 7 1
A testUpdateContentThrowsBadStateException() 0 43 1
A testUpdateContentThrowsUnauthorizedException() 0 42 1
A determineLanguageCodesForUpdate() 0 17 5
B mapStructFieldsForUpdate() 0 26 6
B determineValuesForUpdate() 0 46 7
A stubValues() 0 10 3
B assertForTestUpdateContentNonRedundantFieldSet() 0 237 2
A providerForTestUpdateContentNonRedundantFieldSet1() 0 52 1
A testUpdateContentNonRedundantFieldSet1() 0 34 1
B providerForTestUpdateContentNonRedundantFieldSet2() 0 167 1
A testUpdateContentNonRedundantFieldSet2() 0 34 1
B providerForTestUpdateContentNonRedundantFieldSet3() 0 216 1
A testUpdateContentNonRedundantFieldSet3() 0 52 1
B providerForTestUpdateContentNonRedundantFieldSet4() 0 241 1
A testUpdateContentNonRedundantFieldSet4() 0 52 1
B providerForTestUpdateContentNonRedundantFieldSetComplex() 0 228 1
B fixturesForTestUpdateContentNonRedundantFieldSetComplex() 0 82 1
A testUpdateContentNonRedundantFieldSetComplex() 0 12 1
A providerForTestUpdateContentWithInvalidLanguage() 0 29 1
B testUpdateContentWithInvalidLanguage() 0 79 2
B assertForUpdateContentContentValidationException() 0 93 2
A providerForTestUpdateContentThrowsContentValidationExceptionFieldDefinition() 0 17 1
A testUpdateContentThrowsContentValidationExceptionFieldDefinition() 0 11 1
A providerForTestUpdateContentThrowsContentValidationExceptionTranslation() 0 17 1
A testUpdateContentThrowsContentValidationExceptionTranslation() 0 24 1
B assertForTestUpdateContentRequiredField() 0 129 1
A providerForTestUpdateContentRequiredField() 0 19 1
A testUpdateContentRequiredField() 0 49 2
B assertForTestUpdateContentThrowsContentFieldValidationException() 0 137 1
B providerForTestUpdateContentThrowsContentFieldValidationException() 0 121 2
A testUpdateContentThrowsContentFieldValidationException() 0 21 2
B testUpdateContentTransactionRollback() 0 57 1
A testCopyContentThrowsUnauthorizedException() 0 42 1
B testCopyContent() 0 124 1
B testCopyContentWithVersionInfo() 0 120 1
B testCopyContentWithRollback() 0 62 1
A mockBuildContentDomainObject() 0 30 1
A mockGetDefaultObjectStates() 0 39 1
A mockSetDefaultObjectStates() 0 19 2
B mockPublishVersion() 0 76 3
A mockPublishUrlAliasesForContent() 0 46 1
A getRelationProcessorMock() 0 8 2
A getNameSchemaServiceMock() 0 8 2
A getContentTypeServiceMock() 0 8 2
A getLocationServiceMock() 0 8 2
A getPartlyMockedContentService() 0 22 2
A getRepositoryMock() 0 10 1

How to fix   Complexity   

Complex Class

Complex classes like ContentTest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ContentTest, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
5
 * @license For full copyright and license information view LICENSE file distributed with this source code.
6
 */
7
namespace eZ\Publish\Core\Repository\Tests\Service\Mock;
8
9
use eZ\Publish\API\Repository\Exceptions\BadStateException;
10
use eZ\Publish\API\Repository\Exceptions\UnauthorizedException;
11
use eZ\Publish\API\Repository\Repository;
12
use eZ\Publish\API\Repository\Values\Content\Language;
13
use eZ\Publish\API\Repository\Values\Content\Content as APIContent;
14
use eZ\Publish\API\Repository\Values\Content\LocationCreateStruct;
15
use eZ\Publish\API\Repository\ContentTypeService as APIContentTypeService;
16
use eZ\Publish\API\Repository\LocationService as APILocationService;
17
use eZ\Publish\API\Repository\Exceptions\NotFoundException as APINotFoundException;
18
use eZ\Publish\API\Repository\Values\Content\ContentInfo as APIContentInfo;
19
use eZ\Publish\API\Repository\Values\ContentType\ContentType as APIContentType;
20
use eZ\Publish\API\Repository\Values\Content\Location as APILocation;
21
use eZ\Publish\API\Repository\Values\ContentType\FieldDefinition as APIFieldDefinition;
22
use eZ\Publish\API\Repository\Values\Content\ContentCreateStruct as APIContentCreateStruct;
23
use eZ\Publish\Core\Base\Exceptions\ContentFieldValidationException;
24
use eZ\Publish\Core\Base\Exceptions\ContentValidationException;
25
use eZ\Publish\Core\Repository\Tests\Service\Mock\Base as BaseServiceMockTest;
26
use eZ\Publish\Core\Repository\ContentService;
27
use eZ\Publish\Core\Repository\Values\Content\Location;
28
use eZ\Publish\Core\Repository\Values\Content\Content;
29
use eZ\Publish\Core\Repository\Values\Content\ContentCreateStruct;
30
use eZ\Publish\Core\Repository\Values\Content\ContentUpdateStruct;
31
use eZ\Publish\Core\Repository\Values\Content\VersionInfo;
32
use eZ\Publish\Core\Repository\Helper\RelationProcessor;
33
use eZ\Publish\Core\Repository\Helper\NameSchemaService;
34
use eZ\Publish\API\Repository\Values\Content\Field;
35
use eZ\Publish\Core\FieldType\Value;
36
use eZ\Publish\API\Repository\Values\Content\ContentInfo;
37
use eZ\Publish\API\Repository\Values\Content\VersionInfo as APIVersionInfo;
38
use eZ\Publish\Core\Repository\Values\ContentType\ContentType;
39
use eZ\Publish\Core\Repository\Values\ContentType\FieldDefinition;
40
use eZ\Publish\Core\Repository\Values\ContentType\FieldDefinitionCollection;
41
use eZ\Publish\SPI\Persistence\Content\Location as SPILocation;
42
use eZ\Publish\SPI\FieldType\FieldType as SPIFieldType;
43
use eZ\Publish\SPI\Persistence\Content as SPIContent;
44
use eZ\Publish\SPI\Persistence\Content\UpdateStruct as SPIContentUpdateStruct;
45
use eZ\Publish\SPI\Persistence\Content\CreateStruct as SPIContentCreateStruct;
46
use eZ\Publish\SPI\Persistence\Content\Field as SPIField;
47
use eZ\Publish\SPI\Persistence\Content\ObjectState\Group as SPIObjectStateGroup;
48
use eZ\Publish\SPI\Persistence\Content\ObjectState as SPIObjectState;
49
use eZ\Publish\SPI\Persistence\Content\VersionInfo as SPIVersionInfo;
50
use eZ\Publish\SPI\Persistence\Content\ContentInfo as SPIContentInfo;
51
use eZ\Publish\SPI\Persistence\Content\MetadataUpdateStruct as SPIMetadataUpdateStruct;
52
use eZ\Publish\Core\Base\Exceptions\NotFoundException;
53
use eZ\Publish\Core\Repository\Values\User\UserReference;
54
use Exception;
55
use PHPUnit\Framework\MockObject\MockObject;
56
57
/**
58
 * Mock test case for Content service.
59
 */
60
class ContentTest extends BaseServiceMockTest
61
{
62
    /**
63
     * Represents empty Field Value.
64
     */
65
    const EMPTY_FIELD_VALUE = 'empty';
66
67
    /**
68
     * Test for the __construct() method.
69
     *
70
     * @covers \eZ\Publish\Core\Repository\ContentService::__construct
71
     */
72
    public function testConstructor(): void
73
    {
74
        $repositoryMock = $this->getRepositoryMock();
75
        /** @var \eZ\Publish\SPI\Persistence\Handler $persistenceHandlerMock */
76
        $persistenceHandlerMock = $this->getPersistenceMockHandler('Handler');
77
        $contentDomainMapperMock = $this->getContentDomainMapperMock();
78
        $relationProcessorMock = $this->getRelationProcessorMock();
79
        $nameSchemaServiceMock = $this->getNameSchemaServiceMock();
80
        $fieldTypeRegistryMock = $this->getFieldTypeRegistryMock();
81
        $permissionResolverMock = $this->getPermissionResolverMock();
82
        $settings = ['default_version_archive_limit' => 10];
83
84
        $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...
85
            $repositoryMock,
0 ignored issues
show
Bug introduced by
It seems like $repositoryMock defined by $this->getRepositoryMock() on line 74 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...
86
            $persistenceHandlerMock,
87
            $contentDomainMapperMock,
88
            $relationProcessorMock,
0 ignored issues
show
Bug introduced by
It seems like $relationProcessorMock defined by $this->getRelationProcessorMock() on line 78 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...
89
            $nameSchemaServiceMock,
0 ignored issues
show
Bug introduced by
It seems like $nameSchemaServiceMock defined by $this->getNameSchemaServiceMock() on line 79 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...
90
            $fieldTypeRegistryMock,
91
            $permissionResolverMock,
92
            $settings
93
        );
94
    }
95
96
    /**
97
     * Test for the loadVersionInfo() method, of published version.
98
     *
99
     * @covers \eZ\Publish\Core\Repository\ContentService::loadVersionInfoById
100
     */
101
    public function testLoadVersionInfoById()
102
    {
103
        $contentServiceMock = $this->getPartlyMockedContentService(['loadContentInfo']);
104
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
105
        $contentHandler = $this->getPersistenceMock()->contentHandler();
106
        $domainMapperMock = $this->getContentDomainMapperMock();
107
        $versionInfoMock = $this->createMock(APIVersionInfo::class);
108
        $permissionResolver = $this->getPermissionResolverMock();
109
110
        $versionInfoMock->expects($this->once())
111
            ->method('isPublished')
112
            ->willReturn(true);
113
114
        $contentServiceMock->expects($this->never())
115
            ->method('loadContentInfo');
116
117
        $contentHandler->expects($this->once())
118
            ->method('loadVersionInfo')
119
            ->with(
120
                $this->equalTo(42),
121
                $this->equalTo(null)
122
            )->will(
123
                $this->returnValue(new SPIVersionInfo())
124
            );
125
126
        $domainMapperMock->expects($this->once())
127
            ->method('buildVersionInfoDomainObject')
128
            ->with(new SPIVersionInfo())
129
            ->will($this->returnValue($versionInfoMock));
130
131
        $permissionResolver->expects($this->once())
132
            ->method('canUser')
133
            ->with(
134
                $this->equalTo('content'),
135
                $this->equalTo('read'),
136
                $this->equalTo($versionInfoMock)
137
            )->will($this->returnValue(true));
138
139
        $result = $contentServiceMock->loadVersionInfoById(42);
140
141
        $this->assertEquals($versionInfoMock, $result);
142
    }
143
144
    /**
145
     * Test for the loadVersionInfo() method, of a draft.
146
     *
147
     * @depends testLoadVersionInfoById
148
     * @covers \eZ\Publish\Core\Repository\ContentService::loadVersionInfoById
149
     */
150
    public function testLoadVersionInfoByIdAndVersionNumber()
151
    {
152
        $permissionResolver = $this->getPermissionResolverMock();
153
        $contentServiceMock = $this->getPartlyMockedContentService(['loadContentInfo']);
154
        /** @var \PHPUnit_Framework_MockObject_MockObject $contentHandler */
155
        $contentHandler = $this->getPersistenceMock()->contentHandler();
156
        $domainMapperMock = $this->getContentDomainMapperMock();
157
        $versionInfoMock = $this->createMock(APIVersionInfo::class);
158
159
        $versionInfoMock->expects($this->any())
160
            ->method('__get')
161
            ->with('status')
162
            ->willReturn(APIVersionInfo::STATUS_DRAFT);
163
164
        $contentServiceMock->expects($this->never())
165
            ->method('loadContentInfo');
166
167
        $contentHandler->expects($this->once())
168
            ->method('loadVersionInfo')
169
            ->with(
170
                $this->equalTo(42),
171
                $this->equalTo(2)
172
            )->willReturn(new SPIVersionInfo());
173
174
        $domainMapperMock->expects($this->once())
175
            ->method('buildVersionInfoDomainObject')
176
            ->with(new SPIVersionInfo())
177
            ->willReturn($versionInfoMock);
178
179
        $permissionResolver->expects($this->once())
180
            ->method('canUser')
181
            ->with(
182
                $this->equalTo('content'),
183
                $this->equalTo('versionread'),
184
                $this->equalTo($versionInfoMock)
185
            )->willReturn(true);
186
187
        $result = $contentServiceMock->loadVersionInfoById(42, 2);
188
189
        $this->assertEquals($versionInfoMock, $result);
190
    }
191
192
    /**
193
     * Test for the loadVersionInfo() method.
194
     *
195
     * @covers \eZ\Publish\Core\Repository\ContentService::loadVersionInfoById
196
     */
197
    public function testLoadVersionInfoByIdThrowsNotFoundException()
198
    {
199
        $this->expectException(NotFoundException::class);
200
201
        $contentServiceMock = $this->getPartlyMockedContentService(['loadContentInfo']);
202
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
203
        $contentHandler = $this->getPersistenceMock()->contentHandler();
204
205
        $contentHandler->expects($this->once())
206
            ->method('loadVersionInfo')
207
            ->with(
208
                $this->equalTo(42),
209
                $this->equalTo(24)
210
            )->will(
211
                $this->throwException(
212
                    new NotFoundException(
213
                        'Content',
214
                        [
215
                            'contentId' => 42,
216
                            'versionNo' => 24,
217
                        ]
218
                    )
219
                )
220
            );
221
222
        $contentServiceMock->loadVersionInfoById(42, 24);
223
    }
224
225
    /**
226
     * Test for the loadVersionInfo() method.
227
     *
228
     * @covers \eZ\Publish\Core\Repository\ContentService::loadVersionInfoById
229
     */
230
    public function testLoadVersionInfoByIdThrowsUnauthorizedExceptionNonPublishedVersion()
231
    {
232
        $this->expectException(\eZ\Publish\Core\Base\Exceptions\UnauthorizedException::class);
233
234
        $contentServiceMock = $this->getPartlyMockedContentService();
235
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
236
        $contentHandler = $this->getPersistenceMock()->contentHandler();
237
        $domainMapperMock = $this->getContentDomainMapperMock();
238
        $versionInfoMock = $this->createMock(APIVersionInfo::class);
239
        $permissionResolver = $this->getPermissionResolverMock();
240
241
        $versionInfoMock->expects($this->any())
242
            ->method('isPublished')
243
            ->willReturn(false);
244
245
        $contentHandler->expects($this->once())
246
            ->method('loadVersionInfo')
247
            ->with(
248
                $this->equalTo(42),
249
                $this->equalTo(24)
250
            )->will(
251
                $this->returnValue(new SPIVersionInfo())
252
            );
253
254
        $domainMapperMock->expects($this->once())
255
            ->method('buildVersionInfoDomainObject')
256
            ->with(new SPIVersionInfo())
257
            ->will($this->returnValue($versionInfoMock));
258
259
        $permissionResolver->expects($this->once())
260
            ->method('canUser')
261
            ->with(
262
                $this->equalTo('content'),
263
                $this->equalTo('versionread'),
264
                $this->equalTo($versionInfoMock)
265
            )->will($this->returnValue(false));
266
267
        $contentServiceMock->loadVersionInfoById(42, 24);
268
    }
269
270
    /**
271
     * Test for the loadVersionInfo() method.
272
     *
273
     * @covers \eZ\Publish\Core\Repository\ContentService::loadVersionInfoById
274
     */
275
    public function testLoadVersionInfoByIdPublishedVersion()
276
    {
277
        $contentServiceMock = $this->getPartlyMockedContentService();
278
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
279
        $contentHandler = $this->getPersistenceMock()->contentHandler();
280
        $domainMapperMock = $this->getContentDomainMapperMock();
281
        $versionInfoMock = $this->createMock(APIVersionInfo::class);
282
        $permissionResolver = $this->getPermissionResolverMock();
283
284
        $versionInfoMock->expects($this->once())
285
            ->method('isPublished')
286
            ->willReturn(true);
287
288
        $contentHandler->expects($this->once())
289
            ->method('loadVersionInfo')
290
            ->with(
291
                $this->equalTo(42),
292
                $this->equalTo(24)
293
            )->will(
294
                $this->returnValue(new SPIVersionInfo())
295
            );
296
297
        $domainMapperMock->expects($this->once())
298
            ->method('buildVersionInfoDomainObject')
299
            ->with(new SPIVersionInfo())
300
            ->will($this->returnValue($versionInfoMock));
301
302
        $permissionResolver->expects($this->once())
303
            ->method('canUser')
304
            ->with(
305
                $this->equalTo('content'),
306
                $this->equalTo('read'),
307
                $this->equalTo($versionInfoMock)
308
            )->will($this->returnValue(true));
309
310
        $result = $contentServiceMock->loadVersionInfoById(42, 24);
311
312
        $this->assertEquals($versionInfoMock, $result);
313
    }
314
315
    /**
316
     * Test for the loadVersionInfo() method.
317
     *
318
     * @covers \eZ\Publish\Core\Repository\ContentService::loadVersionInfoById
319
     */
320
    public function testLoadVersionInfoByIdNonPublishedVersion()
321
    {
322
        $contentServiceMock = $this->getPartlyMockedContentService();
323
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
324
        $contentHandler = $this->getPersistenceMock()->contentHandler();
325
        $domainMapperMock = $this->getContentDomainMapperMock();
326
        $versionInfoMock = $this->createMock(APIVersionInfo::class);
327
        $permissionResolver = $this->getPermissionResolverMock();
328
329
        $versionInfoMock->expects($this->once())
330
            ->method('isPublished')
331
            ->willReturn(false);
332
333
        $contentHandler->expects($this->once())
334
            ->method('loadVersionInfo')
335
            ->with(
336
                $this->equalTo(42),
337
                $this->equalTo(24)
338
            )->will(
339
                $this->returnValue(new SPIVersionInfo())
340
            );
341
342
        $domainMapperMock->expects($this->once())
343
            ->method('buildVersionInfoDomainObject')
344
            ->with(new SPIVersionInfo())
345
            ->will($this->returnValue($versionInfoMock));
346
347
        $permissionResolver->expects($this->once())
348
            ->method('canUser')
349
            ->with(
350
                $this->equalTo('content'),
351
                $this->equalTo('versionread'),
352
                $this->equalTo($versionInfoMock)
353
            )->will($this->returnValue(true));
354
355
        $result = $contentServiceMock->loadVersionInfoById(42, 24);
356
357
        $this->assertEquals($versionInfoMock, $result);
358
    }
359
360
    /**
361
     * Test for the loadVersionInfo() method.
362
     *
363
     * @covers \eZ\Publish\Core\Repository\ContentService::loadVersionInfo
364
     * @depends eZ\Publish\Core\Repository\Tests\Service\Mock\ContentTest::testLoadVersionInfoById
365
     * @depends eZ\Publish\Core\Repository\Tests\Service\Mock\ContentTest::testLoadVersionInfoByIdThrowsNotFoundException
366
     * @depends eZ\Publish\Core\Repository\Tests\Service\Mock\ContentTest::testLoadVersionInfoByIdThrowsUnauthorizedExceptionNonPublishedVersion
367
     * @depends eZ\Publish\Core\Repository\Tests\Service\Mock\ContentTest::testLoadVersionInfoByIdPublishedVersion
368
     * @depends eZ\Publish\Core\Repository\Tests\Service\Mock\ContentTest::testLoadVersionInfoByIdNonPublishedVersion
369
     */
370
    public function testLoadVersionInfo()
371
    {
372
        $expectedResult = $this->createMock(VersionInfo::class);
373
374
        $contentServiceMock = $this->getPartlyMockedContentService(
375
            ['loadVersionInfoById']
376
        );
377
        $contentServiceMock->expects(
378
            $this->once()
379
        )->method(
380
            'loadVersionInfoById'
381
        )->with(
382
            $this->equalTo(42),
383
            $this->equalTo(7)
384
        )->will(
385
            $this->returnValue($expectedResult)
386
        );
387
388
        $result = $contentServiceMock->loadVersionInfo(
389
            new ContentInfo(['id' => 42]),
390
            7
391
        );
392
393
        $this->assertEquals($expectedResult, $result);
394
    }
395
396
    public function testLoadContent()
397
    {
398
        $contentService = $this->getPartlyMockedContentService(['internalLoadContentById']);
399
        $content = $this->createMock(APIContent::class);
400
        $versionInfo = $this->createMock(APIVersionInfo::class);
401
        $permissionResolver = $this->getPermissionResolverMock();
402
403
        $content
404
            ->expects($this->once())
405
            ->method('getVersionInfo')
406
            ->will($this->returnValue($versionInfo));
407
        $versionInfo
408
            ->expects($this->once())
409
            ->method('isPublished')
410
            ->willReturn(true);
411
        $contentId = 123;
412
        $contentService
413
            ->expects($this->once())
414
            ->method('internalLoadContentById')
415
            ->with($contentId)
416
            ->will($this->returnValue($content));
417
418
        $permissionResolver
419
            ->expects($this->once())
420
            ->method('canUser')
421
            ->with('content', 'read', $content)
422
            ->will($this->returnValue(true));
423
424
        $this->assertSame($content, $contentService->loadContent($contentId));
425
    }
426
427
    public function testLoadContentNonPublished()
428
    {
429
        $contentService = $this->getPartlyMockedContentService(['internalLoadContentById']);
430
        $content = $this->createMock(APIContent::class);
431
        $versionInfo = $this->createMock(APIVersionInfo::class);
432
        $permissionResolver = $this->getPermissionResolverMock();
433
434
        $content
435
            ->expects($this->once())
436
            ->method('getVersionInfo')
437
            ->will($this->returnValue($versionInfo));
438
        $contentId = 123;
439
        $contentService
440
            ->expects($this->once())
441
            ->method('internalLoadContentById')
442
            ->with($contentId)
443
            ->will($this->returnValue($content));
444
445
        $permissionResolver
446
            ->expects($this->exactly(2))
447
            ->method('canUser')
448
            ->will(
449
                $this->returnValueMap(
450
                    [
451
                        ['content', 'read', $content, [], true],
452
                        ['content', 'versionread', $content, [], true],
453
                    ]
454
                )
455
            );
456
457
        $this->assertSame($content, $contentService->loadContent($contentId));
458
    }
459
460
    public function testLoadContentUnauthorized()
461
    {
462
        $this->expectException(\eZ\Publish\Core\Base\Exceptions\UnauthorizedException::class);
463
464
        $permissionResolver = $this->getPermissionResolverMock();
465
466
        $contentService = $this->getPartlyMockedContentService(['internalLoadContentById']);
467
        $content = $this->createMock(APIContent::class);
468
        $contentId = 123;
469
        $contentService
470
            ->expects($this->once())
471
            ->method('internalLoadContentById')
472
            ->with($contentId)
473
            ->will($this->returnValue($content));
474
475
        $permissionResolver
476
            ->expects($this->once())
477
            ->method('canUser')
478
            ->with('content', 'read', $content)
479
            ->will($this->returnValue(false));
480
481
        $contentService->loadContent($contentId);
482
    }
483
484
    public function testLoadContentNotPublishedStatusUnauthorized()
485
    {
486
        $this->expectException(\eZ\Publish\Core\Base\Exceptions\UnauthorizedException::class);
487
488
        $permissionResolver = $this->getPermissionResolverMock();
489
        $contentService = $this->getPartlyMockedContentService(['internalLoadContentById']);
490
        $content = $this->createMock(APIContent::class);
491
        $versionInfo = $this
492
            ->getMockBuilder(APIVersionInfo::class)
493
            ->getMockForAbstractClass();
494
        $content
495
            ->expects($this->once())
496
            ->method('getVersionInfo')
497
            ->will($this->returnValue($versionInfo));
498
        $contentId = 123;
499
        $contentService
500
            ->expects($this->once())
501
            ->method('internalLoadContentById')
502
            ->with($contentId)
503
            ->will($this->returnValue($content));
504
505
        $permissionResolver
506
            ->expects($this->exactly(2))
507
            ->method('canUser')
508
            ->will(
509
                $this->returnValueMap(
510
                    [
511
                        ['content', 'read', $content, [], true],
512
                        ['content', 'versionread', $content, [], false],
513
                    ]
514
                )
515
            );
516
517
        $contentService->loadContent($contentId);
518
    }
519
520
    /**
521
     * @dataProvider internalLoadContentProviderById
522
     */
523
    public function testInternalLoadContentById(int $id, ?array $languages, ?int $versionNo, bool $useAlwaysAvailable): void
524
    {
525
        if (!empty($languages) && $useAlwaysAvailable) {
526
            $spiContentInfo = new SPIContentInfo(['id' => $id, 'alwaysAvailable' => false]);
527
        } else {
528
            $spiContentInfo = new SPIContentInfo(['id' => $id]);
529
        }
530
531
        $spiContent = new SPIContent([
532
            'versionInfo' => new VersionInfo([
533
                'contentInfo' => new ContentInfo([
534
                    'id' => 42,
535
                    'contentTypeId' => 123,
536
                ]),
537
            ]),
538
        ]);
539
540
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
541
        $contentHandler = $this->getPersistenceMock()->contentHandler();
542
        $contentHandler
543
            ->expects($this->once())
544
            ->method('loadContentInfo')
545
            ->with($id)
546
            ->will($this->returnValue($spiContentInfo));
547
548
        $contentHandler
549
            ->expects($this->once())
550
            ->method('load')
551
            ->with($id, $versionNo, $languages)
552
            ->willReturn($spiContent);
553
554
        $contentService = $this->getPartlyMockedContentService();
555
556
        $expectedContent = $this->mockBuildContentDomainObject($spiContent, $languages);
557
        $actualContent = $contentService->internalLoadContentById($id, $languages, $versionNo, $useAlwaysAvailable);
558
559
        $this->assertSame($expectedContent, $actualContent);
560
    }
561
562
    /**
563
     * @dataProvider internalLoadContentProviderByRemoteId
564
     */
565
    public function testInternalLoadContentByRemoteId(string $remoteId, ?array $languages, ?int $versionNo, bool $useAlwaysAvailable)
566
    {
567
        $realId = 123;
568
569
        $spiContentInfo = new SPIContentInfo([
570
            'currentVersionNo' => $versionNo ?: 7,
571
            'id' => $realId,
572
        ]);
573
574
        $spiContent = new SPIContent([
575
            'versionInfo' => new VersionInfo([
576
                'contentInfo' => new ContentInfo(['id' => 42, 'contentTypeId' => 123]),
577
            ]),
578
        ]);
579
580
        $contentService = $this->getPartlyMockedContentService();
581
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
582
        $contentHandler = $this->getPersistenceMock()->contentHandler();
583
        $contentHandler
584
            ->expects($this->once())
585
            ->method('loadContentInfoByRemoteId')
586
            ->with($remoteId)
587
            ->will($this->returnValue($spiContentInfo));
588
589
        $contentHandler
590
            ->expects($this->once())
591
            ->method('load')
592
            ->with($realId, $versionNo, $languages)
593
            ->willReturn($spiContent);
594
595
        $expectedContent = $this->mockBuildContentDomainObject($spiContent, $languages);
596
597
        $actualContent = $contentService->internalLoadContentByRemoteId(
598
            $remoteId,
599
            $languages,
600
            $versionNo,
601
            $useAlwaysAvailable
602
        );
603
604
        $this->assertSame($expectedContent, $actualContent);
605
    }
606
607
    public function internalLoadContentProviderById(): array
608
    {
609
        return [
610
            [123, null, null, false],
611
            [123, null, 456, false],
612
            [456, null, 123, true],
613
            [456, null, 2, false],
614
            [456, ['eng-GB'], 2, true],
615
            [456, ['eng-GB', 'fre-FR'], null, false],
616
            [456, ['eng-GB', 'fre-FR', 'nor-NO'], 2, false],
617
        ];
618
    }
619
620
    public function internalLoadContentProviderByRemoteId(): array
621
    {
622
        return [
623
            ['123', null, null, false],
624
            ['someRemoteId', null, 456, false],
625
            ['456', null, 123, false],
626
            ['someRemoteId', null, 2, false],
627
            ['someRemoteId', ['eng-GB'], 2, false],
628
            ['456', ['eng-GB', 'fre-FR'], null, false],
629
            ['someRemoteId', ['eng-GB', 'fre-FR', 'nor-NO'], 2, false],
630
        ];
631
    }
632
633
    public function testInternalLoadContentByIdNotFound(): void
634
    {
635
        $this->expectException(NotFoundException::class);
636
637
        $id = 123;
638
        $versionNo = 7;
639
        $languages = null;
640
641
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
642
        $contentHandler = $this->getPersistenceMock()->contentHandler();
643
        $contentHandler
644
            ->expects($this->once())
645
            ->method('loadContentInfo')
646
            ->with($id)
647
            ->willReturn(new SPIContent\ContentInfo(['id' => $id]));
648
649
        $contentHandler
650
            ->expects($this->once())
651
            ->method('load')
652
            ->with($id, $versionNo, $languages)
653
            ->will(
654
                $this->throwException(
655
                    $this->createMock(APINotFoundException::class)
656
                )
657
            );
658
659
        $contentService = $this->getPartlyMockedContentService();
660
        $contentService->internalLoadContentById($id, $languages, $versionNo);
661
    }
662
663
    public function testInternalLoadContentByRemoteIdNotFound(): void
664
    {
665
        $this->expectException(NotFoundException::class);
666
667
        $remoteId = 'dca290623518d393126d3408b45af6ee';
668
        $id = 123;
669
        $versionNo = 7;
670
        $languages = null;
671
672
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
673
        $contentHandler = $this->getPersistenceMock()->contentHandler();
674
        $contentHandler
675
            ->expects($this->once())
676
            ->method('loadContentInfoByRemoteId')
677
            ->with($remoteId)
678
            ->willReturn(new SPIContent\ContentInfo(['id' => $id]));
679
680
        $contentHandler
681
            ->expects($this->once())
682
            ->method('load')
683
            ->with($id, $versionNo, $languages)
684
            ->willThrowException(
685
                $this->createMock(APINotFoundException::class)
686
            );
687
688
        $contentService = $this->getPartlyMockedContentService();
689
        $contentService->internalLoadContentByRemoteId($remoteId, $languages, $versionNo);
690
    }
691
692
    /**
693
     * Test for the loadContentByContentInfo() method.
694
     *
695
     * @covers \eZ\Publish\Core\Repository\ContentService::loadContentByContentInfo
696
     */
697
    public function testLoadContentByContentInfo()
698
    {
699
        $versionInfo = $this->createMock(APIVersionInfo::class);
700
        $content = $this->createMock(APIContent::class);
701
        $content->method('getVersionInfo')
702
            ->will($this->returnValue($versionInfo));
703
704
        $permissionResolver = $this->getPermissionResolverMock();
705
        $permissionResolver->expects($this->any())
706
            ->method('canUser')
707
            ->will($this->returnValue(true));
708
709
        $contentServiceMock = $this->getPartlyMockedContentService(
710
            ['internalLoadContentById']
711
        );
712
713
        $contentServiceMock
714
            ->method(
715
                'internalLoadContentById'
716
        )->with(
717
            $this->equalTo(42),
718
            $this->equalTo(['cro-HR']),
719
            $this->equalTo(7),
720
            $this->equalTo(false)
721
        )->will(
722
            $this->returnValue($content)
723
        );
724
725
        $result = $contentServiceMock->loadContentByContentInfo(
726
            new ContentInfo(['id' => 42]),
727
            ['cro-HR'],
728
            7
729
        );
730
731
        $this->assertEquals($content, $result);
732
    }
733
734
    /**
735
     * Test for the loadContentByVersionInfo() method.
736
     *
737
     * @covers \eZ\Publish\Core\Repository\ContentService::loadContentByVersionInfo
738
     */
739
    public function testLoadContentByVersionInfo()
740
    {
741
        $expectedResult = $this->createMock(Content::class);
742
743
        $contentServiceMock = $this->getPartlyMockedContentService(
744
            ['loadContent']
745
        );
746
        $contentServiceMock->expects(
747
            $this->once()
748
        )->method(
749
            'loadContent'
750
        )->with(
751
            $this->equalTo(42),
752
            $this->equalTo(['cro-HR']),
753
            $this->equalTo(7),
754
            $this->equalTo(false)
755
        )->will(
756
            $this->returnValue($expectedResult)
757
        );
758
759
        $result = $contentServiceMock->loadContentByVersionInfo(
760
            new VersionInfo(
761
                [
762
                    'contentInfo' => new ContentInfo(['id' => 42]),
763
                    'versionNo' => 7,
764
                ]
765
            ),
766
            ['cro-HR']
767
        );
768
769
        $this->assertEquals($expectedResult, $result);
770
    }
771
772
    /**
773
     * Test for the deleteContent() method.
774
     *
775
     * @covers \eZ\Publish\Core\Repository\ContentService::deleteContent
776
     */
777
    public function testDeleteContentThrowsUnauthorizedException()
778
    {
779
        $this->expectException(\eZ\Publish\Core\Base\Exceptions\UnauthorizedException::class);
780
781
        $permissionResolver = $this->getPermissionResolverMock();
782
        $contentService = $this->getPartlyMockedContentService(['internalLoadContentInfoById']);
783
        $contentInfo = $this->createMock(APIContentInfo::class);
784
785
        $contentInfo->expects($this->any())
786
            ->method('__get')
787
            ->with('id')
788
            ->will($this->returnValue(42));
789
790
        $contentService->expects($this->once())
791
            ->method('internalLoadContentInfoById')
792
            ->with(42)
793
            ->will($this->returnValue($contentInfo));
794
795
        $permissionResolver->expects($this->once())
796
            ->method('canUser')
797
            ->with('content', 'remove')
798
            ->will($this->returnValue(false));
799
800
        /* @var \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo */
801
        $contentService->deleteContent($contentInfo);
802
    }
803
804
    /**
805
     * Test for the deleteContent() method.
806
     *
807
     * @covers \eZ\Publish\Core\Repository\ContentService::deleteContent
808
     */
809
    public function testDeleteContent()
810
    {
811
        $repository = $this->getRepositoryMock();
812
        $permissionResolver = $this->getPermissionResolverMock();
813
814
        $permissionResolver->expects($this->once())
815
            ->method('canUser')
816
            ->with('content', 'remove')
817
            ->will($this->returnValue(true));
818
819
        $contentService = $this->getPartlyMockedContentService(['internalLoadContentInfoById']);
820
        /** @var \PHPUnit\Framework\MockObject\MockObject $urlAliasHandler */
821
        $urlAliasHandler = $this->getPersistenceMock()->urlAliasHandler();
822
        /** @var \PHPUnit\Framework\MockObject\MockObject $locationHandler */
823
        $locationHandler = $this->getPersistenceMock()->locationHandler();
824
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
825
        $contentHandler = $this->getPersistenceMock()->contentHandler();
826
827
        $contentInfo = $this->createMock(APIContentInfo::class);
828
829
        $contentService->expects($this->once())
830
            ->method('internalLoadContentInfoById')
831
            ->with(42)
832
            ->will($this->returnValue($contentInfo));
833
834
        $contentInfo->expects($this->any())
835
            ->method('__get')
836
            ->with('id')
837
            ->will($this->returnValue(42));
838
839
        $repository->expects($this->once())->method('beginTransaction');
840
841
        $spiLocations = [
842
            new SPILocation(['id' => 1]),
843
            new SPILocation(['id' => 2]),
844
        ];
845
        $locationHandler->expects($this->once())
846
            ->method('loadLocationsByContent')
847
            ->with(42)
848
            ->will($this->returnValue($spiLocations));
849
850
        $contentHandler->expects($this->once())
851
            ->method('deleteContent')
852
            ->with(42);
853
854
        foreach ($spiLocations as $index => $spiLocation) {
855
            $urlAliasHandler->expects($this->at($index))
856
                ->method('locationDeleted')
857
                ->with($spiLocation->id);
858
        }
859
860
        $repository->expects($this->once())->method('commit');
861
862
        /* @var \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo */
863
        $contentService->deleteContent($contentInfo);
864
    }
865
866
    /**
867
     * Test for the deleteContent() method.
868
     *
869
     * @covers \eZ\Publish\Core\Repository\ContentService::deleteContent
870
     */
871
    public function testDeleteContentWithRollback()
872
    {
873
        $this->expectException(\Exception::class);
874
875
        $repository = $this->getRepositoryMock();
876
        $permissionResolver = $this->getPermissionResolverMock();
877
878
        $permissionResolver->expects($this->once())
879
            ->method('canUser')
880
            ->with('content', 'remove')
881
            ->will($this->returnValue(true));
882
883
        $contentService = $this->getPartlyMockedContentService(['internalLoadContentInfoById']);
884
        /** @var \PHPUnit\Framework\MockObject\MockObject $locationHandler */
885
        $locationHandler = $this->getPersistenceMock()->locationHandler();
886
887
        $contentInfo = $this->createMock(APIContentInfo::class);
888
889
        $contentService->expects($this->once())
890
            ->method('internalLoadContentInfoById')
891
            ->with(42)
892
            ->will($this->returnValue($contentInfo));
893
894
        $contentInfo->expects($this->any())
895
            ->method('__get')
896
            ->with('id')
897
            ->will($this->returnValue(42));
898
899
        $repository->expects($this->once())->method('beginTransaction');
900
901
        $locationHandler->expects($this->once())
902
            ->method('loadLocationsByContent')
903
            ->with(42)
904
            ->will($this->throwException(new \Exception()));
905
906
        $repository->expects($this->once())->method('rollback');
907
908
        /* @var \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo */
909
        $contentService->deleteContent($contentInfo);
910
    }
911
912
    /**
913
     * Test for the deleteVersion() method.
914
     *
915
     * @covers \eZ\Publish\Core\Repository\ContentService::deleteVersion
916
     */
917
    public function testDeleteVersionThrowsBadStateExceptionLastVersion()
918
    {
919
        $this->expectException(BadStateException::class);
920
921
        $repository = $this->getRepositoryMock();
922
        $permissionResolver = $this->getPermissionResolverMock();
923
924
        $permissionResolver
925
            ->expects($this->once())
926
            ->method('canUser')
927
            ->with('content', 'versionremove')
928
            ->will($this->returnValue(true));
929
        $repository
930
            ->expects($this->never())
931
            ->method('beginTransaction');
932
933
        $contentService = $this->getPartlyMockedContentService();
934
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandler */
935
        $contentHandler = $this->getPersistenceMock()->contentHandler();
936
        $contentInfo = $this->createMock(APIContentInfo::class);
937
        $versionInfo = $this->createMock(APIVersionInfo::class);
938
939
        $contentInfo
940
            ->expects($this->any())
941
            ->method('__get')
942
            ->with('id')
943
            ->will($this->returnValue(42));
944
945
        $versionInfo
946
            ->expects($this->any())
947
            ->method('__get')
948
            ->will(
949
                $this->returnValueMap(
950
                    [
951
                        ['versionNo', 123],
952
                        ['contentInfo', $contentInfo],
953
                    ]
954
                )
955
            );
956
        $versionInfo
957
            ->expects($this->once())
958
            ->method('isPublished')
959
            ->willReturn(false);
960
961
        $contentHandler
962
            ->expects($this->once())
963
            ->method('listVersions')
964
            ->with(42)
965
            ->will($this->returnValue(['version']));
966
967
        /* @var \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfo */
968
        $contentService->deleteVersion($versionInfo);
969
    }
970
971
    /**
972
     * Test for the createContent() method.
973
     *
974
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
975
     */
976
    public function testCreateContentThrowsInvalidArgumentExceptionMainLanguageCodeNotSet()
977
    {
978
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\InvalidArgumentException::class);
979
        $this->expectExceptionMessage('Argument \'$contentCreateStruct\' is invalid: the \'mainLanguageCode\' property must be set');
980
981
        $mockedService = $this->getPartlyMockedContentService();
982
        $mockedService->createContent(new ContentCreateStruct(), []);
983
    }
984
985
    /**
986
     * Test for the createContent() method.
987
     *
988
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
989
     */
990
    public function testCreateContentThrowsInvalidArgumentExceptionContentTypeNotSet()
991
    {
992
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\InvalidArgumentException::class);
993
        $this->expectExceptionMessage('Argument \'$contentCreateStruct\' is invalid: the \'contentType\' property must be set');
994
995
        $mockedService = $this->getPartlyMockedContentService();
996
        $mockedService->createContent(
997
            new ContentCreateStruct(['mainLanguageCode' => 'eng-US']),
998
            []
999
        );
1000
    }
1001
1002
    /**
1003
     * Test for the createContent() method.
1004
     *
1005
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
1006
     */
1007
    public function testCreateContentThrowsUnauthorizedException()
1008
    {
1009
        $this->expectException(UnauthorizedException::class);
1010
1011
        $repositoryMock = $this->getRepositoryMock();
1012
1013
        $permissionResolver = $this->getPermissionResolverMock();
1014
        $permissionResolver->expects($this->once())
1015
            ->method('getCurrentUserReference')
1016
            ->will($this->returnValue(new UserReference(169)));
1017
1018
        $mockedService = $this->getPartlyMockedContentService();
1019
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
1020
        $contentType = new ContentType(
1021
            [
1022
                'id' => 123,
1023
                'fieldDefinitions' => [],
1024
            ]
1025
        );
1026
        $contentCreateStruct = new ContentCreateStruct(
1027
            [
1028
                'ownerId' => 169,
1029
                'alwaysAvailable' => false,
1030
                'mainLanguageCode' => 'eng-US',
1031
                'contentType' => $contentType,
1032
            ]
1033
        );
1034
1035
        $contentTypeServiceMock->expects($this->once())
1036
            ->method('loadContentType')
1037
            ->with($this->equalTo(123))
1038
            ->will($this->returnValue($contentType));
1039
1040
        $repositoryMock->expects($this->once())
1041
            ->method('getContentTypeService')
1042
            ->will($this->returnValue($contentTypeServiceMock));
1043
1044
        $permissionResolver->expects($this->once())
1045
            ->method('canUser')
1046
            ->with(
1047
                $this->equalTo('content'),
1048
                $this->equalTo('create'),
1049
                $this->isInstanceOf(get_class($contentCreateStruct)),
1050
                $this->equalTo([])
1051
            )->will($this->returnValue(false));
1052
1053
        $mockedService->createContent(
1054
            new ContentCreateStruct(
1055
                [
1056
                    'mainLanguageCode' => 'eng-US',
1057
                    'contentType' => $contentType,
1058
                ]
1059
            ),
1060
            []
1061
        );
1062
    }
1063
1064
    /**
1065
     * Test for the createContent() method.
1066
     *
1067
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
1068
     * @exceptionMessage Argument '$contentCreateStruct' is invalid: Another content with remoteId 'faraday' exists
1069
     */
1070
    public function testCreateContentThrowsInvalidArgumentExceptionDuplicateRemoteId()
1071
    {
1072
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\InvalidArgumentException::class);
1073
1074
        $repositoryMock = $this->getRepositoryMock();
1075
        $permissionResolverMock = $this->getPermissionResolverMock();
1076
        $permissionResolverMock
1077
            ->expects($this->once())
1078
            ->method('getCurrentUserReference')
1079
            ->willReturn($this->createMock(UserReference::class));
1080
1081
        $mockedService = $this->getPartlyMockedContentService(['loadContentByRemoteId']);
1082
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
1083
        $contentType = new ContentType(
1084
            [
1085
                'id' => 123,
1086
                'fieldDefinitions' => [],
1087
            ]
1088
        );
1089
        $contentCreateStruct = new ContentCreateStruct(
1090
            [
1091
                'ownerId' => 169,
1092
                'alwaysAvailable' => false,
1093
                'remoteId' => 'faraday',
1094
                'mainLanguageCode' => 'eng-US',
1095
                'contentType' => $contentType,
1096
            ]
1097
        );
1098
1099
        $contentTypeServiceMock->expects($this->once())
1100
            ->method('loadContentType')
1101
            ->with($this->equalTo(123))
1102
            ->will($this->returnValue($contentType));
1103
1104
        $repositoryMock->expects($this->once())
1105
            ->method('getContentTypeService')
1106
            ->will($this->returnValue($contentTypeServiceMock));
1107
1108
        $permissionResolverMock->expects($this->once())
1109
            ->method('canUser')
1110
            ->with(
1111
                $this->equalTo('content'),
1112
                $this->equalTo('create'),
1113
                $this->isInstanceOf(get_class($contentCreateStruct)),
1114
                $this->equalTo([])
1115
            )->will($this->returnValue(true));
1116
1117
        $mockedService->expects($this->once())
1118
            ->method('loadContentByRemoteId')
1119
            ->with($contentCreateStruct->remoteId)
1120
            ->will($this->returnValue($this->createMock(Content::class)));
1121
1122
        $mockedService->createContent(
1123
            new ContentCreateStruct(
1124
                [
1125
                    'remoteId' => 'faraday',
1126
                    'mainLanguageCode' => 'eng-US',
1127
                    'contentType' => $contentType,
1128
                ]
1129
            ),
1130
            []
1131
        );
1132
    }
1133
1134
    /**
1135
     * @param string $mainLanguageCode
1136
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
1137
     * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition[] $fieldDefinitions
1138
     *
1139
     * @return array
1140
     */
1141
    protected function mapStructFieldsForCreate($mainLanguageCode, $structFields, $fieldDefinitions)
1142
    {
1143
        $mappedFieldDefinitions = [];
1144
        foreach ($fieldDefinitions as $fieldDefinition) {
1145
            $mappedFieldDefinitions[$fieldDefinition->identifier] = $fieldDefinition;
1146
        }
1147
1148
        $mappedStructFields = [];
1149
        foreach ($structFields as $structField) {
1150
            if ($structField->languageCode === null) {
1151
                $languageCode = $mainLanguageCode;
1152
            } else {
1153
                $languageCode = $structField->languageCode;
1154
            }
1155
1156
            $mappedStructFields[$structField->fieldDefIdentifier][$languageCode] = (string)$structField->value;
1157
        }
1158
1159
        return $mappedStructFields;
1160
    }
1161
1162
    /**
1163
     * Returns full, possibly redundant array of field values, indexed by field definition
1164
     * identifier and language code.
1165
     *
1166
     * @param string $mainLanguageCode
1167
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
1168
     * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition[] $fieldDefinitions
1169
     * @param array $languageCodes
1170
     *
1171
     * @return array
1172
     *
1173
     * @throws \RuntimeException Method is intended to be used only with consistent fixtures
1174
     */
1175
    protected function determineValuesForCreate(
1176
        $mainLanguageCode,
1177
        array $structFields,
1178
        array $fieldDefinitions,
1179
        array $languageCodes
1180
    ) {
1181
        $mappedStructFields = $this->mapStructFieldsForCreate(
1182
            $mainLanguageCode,
1183
            $structFields,
1184
            $fieldDefinitions
1185
        );
1186
1187
        $values = [];
1188
1189
        foreach ($fieldDefinitions as $fieldDefinition) {
1190
            $identifier = $fieldDefinition->identifier;
1191
            foreach ($languageCodes as $languageCode) {
1192
                if (!$fieldDefinition->isTranslatable) {
1193
                    if (isset($mappedStructFields[$identifier][$mainLanguageCode])) {
1194
                        $values[$identifier][$languageCode] = $mappedStructFields[$identifier][$mainLanguageCode];
1195
                    } else {
1196
                        $values[$identifier][$languageCode] = (string)$fieldDefinition->defaultValue;
1197
                    }
1198
                    continue;
1199
                }
1200
1201
                if (isset($mappedStructFields[$identifier][$languageCode])) {
1202
                    $values[$identifier][$languageCode] = $mappedStructFields[$identifier][$languageCode];
1203
                    continue;
1204
                }
1205
1206
                $values[$identifier][$languageCode] = (string)$fieldDefinition->defaultValue;
1207
            }
1208
        }
1209
1210
        return $this->stubValues($values);
1211
    }
1212
1213
    /**
1214
     * @param string $mainLanguageCode
1215
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
1216
     *
1217
     * @return string[]
1218
     */
1219
    protected function determineLanguageCodesForCreate($mainLanguageCode, array $structFields)
1220
    {
1221
        $languageCodes = [];
1222
1223
        foreach ($structFields as $field) {
1224
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
1225
                continue;
1226
            }
1227
1228
            $languageCodes[$field->languageCode] = true;
1229
        }
1230
1231
        $languageCodes[$mainLanguageCode] = true;
1232
1233
        return array_keys($languageCodes);
1234
    }
1235
1236
    /**
1237
     * Asserts that calling createContent() with given API field set causes calling
1238
     * Handler::createContent() with given SPI field set.
1239
     *
1240
     * @param string $mainLanguageCode
1241
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
1242
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $spiFields
1243
     * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition[] $fieldDefinitions
1244
     * @param \eZ\Publish\API\Repository\Values\Content\LocationCreateStruct[] $locationCreateStructs
1245
     * @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...
1246
     * @param bool $execute
1247
     *
1248
     * @return mixed
1249
     */
1250
    protected function assertForTestCreateContentNonRedundantFieldSet(
1251
        $mainLanguageCode,
1252
        array $structFields,
1253
        array $spiFields,
1254
        array $fieldDefinitions,
1255
        array $locationCreateStructs = [],
1256
        $withObjectStates = false,
1257
        $execute = true
1258
    ) {
1259
        $repositoryMock = $this->getRepositoryMock();
1260
        $mockedService = $this->getPartlyMockedContentService();
1261
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandlerMock */
1262
        $contentHandlerMock = $this->getPersistenceMock()->contentHandler();
1263
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
1264
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
1265
        /** @var \PHPUnit\Framework\MockObject\MockObject $objectStateHandlerMock */
1266
        $objectStateHandlerMock = $this->getPersistenceMock()->objectStateHandler();
1267
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
1268
        $domainMapperMock = $this->getContentDomainMapperMock();
1269
        $relationProcessorMock = $this->getRelationProcessorMock();
1270
        $nameSchemaServiceMock = $this->getNameSchemaServiceMock();
1271
        $permissionResolverMock = $this->getPermissionResolverMock();
1272
        $fieldTypeMock = $this->createMock(SPIFieldType::class);
1273
        $languageCodes = $this->determineLanguageCodesForCreate($mainLanguageCode, $structFields);
1274
        $contentType = new ContentType(
1275
            [
1276
                'id' => 123,
1277
                'fieldDefinitions' => new FieldDefinitionCollection($fieldDefinitions),
1278
                'nameSchema' => '<nameSchema>',
1279
            ]
1280
        );
1281
        $contentCreateStruct = new ContentCreateStruct(
1282
            [
1283
                'fields' => $structFields,
1284
                'mainLanguageCode' => $mainLanguageCode,
1285
                'contentType' => $contentType,
1286
                'alwaysAvailable' => false,
1287
                'ownerId' => 169,
1288
                'sectionId' => 1,
1289
            ]
1290
        );
1291
1292
        $languageHandlerMock->expects($this->any())
1293
            ->method('loadByLanguageCode')
1294
            ->with($this->isType('string'))
1295
            ->will(
1296
                $this->returnCallback(
1297
                    function () {
1298
                        return new Language(['id' => 4242]);
1299
                    }
1300
                )
1301
            );
1302
1303
        $repositoryMock->expects($this->once())->method('beginTransaction');
1304
1305
        $contentTypeServiceMock->expects($this->once())
1306
            ->method('loadContentType')
1307
            ->with($this->equalTo($contentType->id))
1308
            ->will($this->returnValue($contentType));
1309
1310
        $repositoryMock->expects($this->once())
1311
            ->method('getContentTypeService')
1312
            ->will($this->returnValue($contentTypeServiceMock));
1313
1314
        $that = $this;
1315
        $permissionResolverMock->expects($this->once())
1316
            ->method('canUser')
1317
            ->with(
1318
                $this->equalTo('content'),
1319
                $this->equalTo('create'),
1320
                $this->isInstanceOf(APIContentCreateStruct::class),
1321
                $this->equalTo($locationCreateStructs)
1322
            )->will(
1323
                $this->returnCallback(
1324
                    function () use ($that, $contentCreateStruct) {
1325
                        $that->assertEquals($contentCreateStruct, func_get_arg(2));
1326
1327
                        return true;
1328
                    }
1329
                )
1330
            );
1331
1332
        $domainMapperMock->expects($this->once())
1333
            ->method('getUniqueHash')
1334
            ->with($this->isInstanceOf(APIContentCreateStruct::class))
1335
            ->will(
1336
                $this->returnCallback(
1337
                    function ($object) use ($that, $contentCreateStruct) {
1338
                        $that->assertEquals($contentCreateStruct, $object);
1339
1340
                        return 'hash';
1341
                    }
1342
                )
1343
            );
1344
1345
        $fieldTypeMock->expects($this->any())
1346
            ->method('acceptValue')
1347
            ->will(
1348
                $this->returnCallback(
1349
                    function ($valueString) {
1350
                        return new ValueStub($valueString);
1351
                    }
1352
                )
1353
            );
1354
1355
        $fieldTypeMock->expects($this->any())
1356
            ->method('toPersistenceValue')
1357
            ->will(
1358
                $this->returnCallback(
1359
                    function (ValueStub $value) {
1360
                        return (string)$value;
1361
                    }
1362
                )
1363
            );
1364
1365
        $emptyValue = self::EMPTY_FIELD_VALUE;
1366
        $fieldTypeMock->expects($this->any())
1367
            ->method('isEmptyValue')
1368
            ->will(
1369
                $this->returnCallback(
1370
                    function (ValueStub $value) use ($emptyValue) {
1371
                        return $emptyValue === (string)$value;
1372
                    }
1373
                )
1374
            );
1375
1376
        $fieldTypeMock->expects($this->any())
1377
            ->method('validate')
1378
            ->will($this->returnValue([]));
1379
1380
        $this->getFieldTypeRegistryMock()->expects($this->any())
1381
            ->method('getFieldType')
1382
            ->will($this->returnValue($fieldTypeMock));
1383
1384
        $relationProcessorMock
1385
            ->expects($this->exactly(count($fieldDefinitions) * count($languageCodes)))
1386
            ->method('appendFieldRelations')
1387
            ->with(
1388
                $this->isType('array'),
1389
                $this->isType('array'),
1390
                $this->isInstanceOf(SPIFieldType::class),
1391
                $this->isInstanceOf(Value::class),
1392
                $this->anything()
1393
            );
1394
1395
        $values = $this->determineValuesForCreate(
1396
            $mainLanguageCode,
1397
            $structFields,
1398
            $fieldDefinitions,
1399
            $languageCodes
1400
        );
1401
        $nameSchemaServiceMock->expects($this->once())
1402
            ->method('resolve')
1403
            ->with(
1404
                $this->equalTo($contentType->nameSchema),
1405
                $this->equalTo($contentType),
1406
                $this->equalTo($values),
1407
                $this->equalTo($languageCodes)
1408
            )->will($this->returnValue([]));
1409
1410
        $relationProcessorMock->expects($this->any())
1411
            ->method('processFieldRelations')
1412
            ->with(
1413
                $this->isType('array'),
1414
                $this->equalTo(42),
1415
                $this->isType('int'),
1416
                $this->equalTo($contentType),
1417
                $this->equalTo([])
1418
            );
1419
1420
        if (!$withObjectStates) {
1421
            $objectStateHandlerMock->expects($this->once())
1422
                ->method('loadAllGroups')
1423
                ->will($this->returnValue([]));
1424
        }
1425
1426
        if ($execute) {
1427
            $spiContentCreateStruct = new SPIContentCreateStruct(
1428
                [
1429
                    'name' => [],
1430
                    'typeId' => 123,
1431
                    'sectionId' => 1,
1432
                    'ownerId' => 169,
1433
                    'remoteId' => 'hash',
1434
                    'fields' => $spiFields,
1435
                    'modified' => time(),
1436
                    'initialLanguageId' => 4242,
1437
                ]
1438
            );
1439
            $spiContentCreateStruct2 = clone $spiContentCreateStruct;
1440
            ++$spiContentCreateStruct2->modified;
1441
1442
            $spiContent = new SPIContent(
1443
                [
1444
                    'versionInfo' => new SPIContent\VersionInfo(
1445
                        [
1446
                            'contentInfo' => new SPIContent\ContentInfo(['id' => 42]),
1447
                            'versionNo' => 7,
1448
                        ]
1449
                    ),
1450
                ]
1451
            );
1452
1453
            $contentHandlerMock->expects($this->once())
1454
                ->method('create')
1455
                ->with($this->logicalOr($spiContentCreateStruct, $spiContentCreateStruct2))
1456
                ->will($this->returnValue($spiContent));
1457
1458
            $repositoryMock->expects($this->once())->method('commit');
1459
            $domainMapperMock->expects($this->once())
1460
                ->method('buildContentDomainObject')
1461
                ->with(
1462
                    $this->isInstanceOf(SPIContent::class),
1463
                    $this->equalTo($contentType)
1464
                )
1465
                ->willReturn($this->createMock(APIContent::class));
1466
1467
            $mockedService->createContent($contentCreateStruct, []);
1468
        }
1469
1470
        return $contentCreateStruct;
1471
    }
1472
1473
    public function providerForTestCreateContentNonRedundantFieldSet1()
1474
    {
1475
        $spiFields = [
1476
            new SPIField(
1477
                [
1478
                    'fieldDefinitionId' => 'fieldDefinitionId',
1479
                    'type' => 'fieldTypeIdentifier',
1480
                    'value' => 'newValue',
1481
                    'languageCode' => 'eng-US',
1482
                ]
1483
            ),
1484
        ];
1485
1486
        return [
1487
            // 0. Without language set
1488
            [
1489
                'eng-US',
1490
                [
1491
                    new Field(
1492
                        [
1493
                            'fieldDefIdentifier' => 'identifier',
1494
                            'value' => 'newValue',
1495
                            'languageCode' => 'eng-US',
1496
                        ]
1497
                    ),
1498
                ],
1499
                $spiFields,
1500
            ],
1501
            // 1. Without language set
1502
            [
1503
                'eng-US',
1504
                [
1505
                    new Field(
1506
                        [
1507
                            'fieldDefIdentifier' => 'identifier',
1508
                            'value' => 'newValue',
1509
                            'languageCode' => null,
1510
                        ]
1511
                    ),
1512
                ],
1513
                $spiFields,
1514
            ],
1515
        ];
1516
    }
1517
1518
    /**
1519
     * Test for the createContent() method.
1520
     *
1521
     * Testing the simplest use case.
1522
     *
1523
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
1524
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
1525
     * @covers \eZ\Publish\Core\Repository\ContentService::cloneField
1526
     * @covers \eZ\Publish\Core\Repository\ContentService::getDefaultObjectStates
1527
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
1528
     * @dataProvider providerForTestCreateContentNonRedundantFieldSet1
1529
     */
1530
    public function testCreateContentNonRedundantFieldSet1($mainLanguageCode, $structFields, $spiFields)
1531
    {
1532
        $fieldDefinitions = [
1533
            new FieldDefinition(
1534
                [
1535
                    'id' => 'fieldDefinitionId',
1536
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
1537
                    'isTranslatable' => false,
1538
                    'identifier' => 'identifier',
1539
                    'isRequired' => false,
1540
                    'defaultValue' => 'defaultValue',
1541
                ]
1542
            ),
1543
        ];
1544
1545
        $this->assertForTestCreateContentNonRedundantFieldSet(
1546
            $mainLanguageCode,
1547
            $structFields,
1548
            $spiFields,
1549
            $fieldDefinitions
1550
        );
1551
    }
1552
1553
    public function providerForTestCreateContentNonRedundantFieldSet2()
1554
    {
1555
        $spiFields = [
1556
            new SPIField(
1557
                [
1558
                    'fieldDefinitionId' => 'fieldDefinitionId1',
1559
                    'type' => 'fieldTypeIdentifier',
1560
                    'value' => 'newValue1',
1561
                    'languageCode' => 'eng-US',
1562
                ]
1563
            ),
1564
            new SPIField(
1565
                [
1566
                    'fieldDefinitionId' => 'fieldDefinitionId2',
1567
                    'type' => 'fieldTypeIdentifier',
1568
                    'value' => 'newValue2',
1569
                    'languageCode' => 'ger-DE',
1570
                ]
1571
            ),
1572
        ];
1573
1574
        return [
1575
            // 0. With language set
1576
            [
1577
                'eng-US',
1578
                [
1579
                    new Field(
1580
                        [
1581
                            'fieldDefIdentifier' => 'identifier1',
1582
                            'value' => 'newValue1',
1583
                            'languageCode' => 'eng-US',
1584
                        ]
1585
                    ),
1586
                    new Field(
1587
                        [
1588
                            'fieldDefIdentifier' => 'identifier2',
1589
                            'value' => 'newValue2',
1590
                            'languageCode' => 'ger-DE',
1591
                        ]
1592
                    ),
1593
                ],
1594
                $spiFields,
1595
            ],
1596
            // 1. Without language set
1597
            [
1598
                'eng-US',
1599
                [
1600
                    new Field(
1601
                        [
1602
                            'fieldDefIdentifier' => 'identifier1',
1603
                            'value' => 'newValue1',
1604
                            'languageCode' => null,
1605
                        ]
1606
                    ),
1607
                    new Field(
1608
                        [
1609
                            'fieldDefIdentifier' => 'identifier2',
1610
                            'value' => 'newValue2',
1611
                            'languageCode' => 'ger-DE',
1612
                        ]
1613
                    ),
1614
                ],
1615
                $spiFields,
1616
            ],
1617
        ];
1618
    }
1619
1620
    /**
1621
     * Test for the createContent() method.
1622
     *
1623
     * Testing multiple languages with multiple translatable fields with empty default value.
1624
     *
1625
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
1626
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
1627
     * @covers \eZ\Publish\Core\Repository\ContentService::cloneField
1628
     * @covers \eZ\Publish\Core\Repository\ContentService::getDefaultObjectStates
1629
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
1630
     * @dataProvider providerForTestCreateContentNonRedundantFieldSet2
1631
     */
1632
    public function testCreateContentNonRedundantFieldSet2($mainLanguageCode, $structFields, $spiFields)
1633
    {
1634
        $fieldDefinitions = [
1635
            new FieldDefinition(
1636
                [
1637
                    'id' => 'fieldDefinitionId1',
1638
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
1639
                    'isTranslatable' => true,
1640
                    'identifier' => 'identifier1',
1641
                    'isRequired' => false,
1642
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
1643
                ]
1644
            ),
1645
            new FieldDefinition(
1646
                [
1647
                    'id' => 'fieldDefinitionId2',
1648
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
1649
                    'isTranslatable' => true,
1650
                    'identifier' => 'identifier2',
1651
                    'isRequired' => false,
1652
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
1653
                ]
1654
            ),
1655
        ];
1656
1657
        $this->assertForTestCreateContentNonRedundantFieldSet(
1658
            $mainLanguageCode,
1659
            $structFields,
1660
            $spiFields,
1661
            $fieldDefinitions
1662
        );
1663
    }
1664
1665
    public function providerForTestCreateContentNonRedundantFieldSetComplex()
1666
    {
1667
        $spiFields0 = [
1668
            new SPIField(
1669
                [
1670
                    'fieldDefinitionId' => 'fieldDefinitionId2',
1671
                    'type' => 'fieldTypeIdentifier',
1672
                    'value' => 'defaultValue2',
1673
                    'languageCode' => 'eng-US',
1674
                ]
1675
            ),
1676
            new SPIField(
1677
                [
1678
                    'fieldDefinitionId' => 'fieldDefinitionId4',
1679
                    'type' => 'fieldTypeIdentifier',
1680
                    'value' => 'defaultValue4',
1681
                    'languageCode' => 'eng-US',
1682
                ]
1683
            ),
1684
        ];
1685
        $spiFields1 = [
1686
            new SPIField(
1687
                [
1688
                    'fieldDefinitionId' => 'fieldDefinitionId1',
1689
                    'type' => 'fieldTypeIdentifier',
1690
                    'value' => 'newValue1',
1691
                    'languageCode' => 'ger-DE',
1692
                ]
1693
            ),
1694
            new SPIField(
1695
                [
1696
                    'fieldDefinitionId' => 'fieldDefinitionId2',
1697
                    'type' => 'fieldTypeIdentifier',
1698
                    'value' => 'defaultValue2',
1699
                    'languageCode' => 'ger-DE',
1700
                ]
1701
            ),
1702
            new SPIField(
1703
                [
1704
                    'fieldDefinitionId' => 'fieldDefinitionId2',
1705
                    'type' => 'fieldTypeIdentifier',
1706
                    'value' => 'newValue2',
1707
                    'languageCode' => 'eng-US',
1708
                ]
1709
            ),
1710
            new SPIField(
1711
                [
1712
                    'fieldDefinitionId' => 'fieldDefinitionId4',
1713
                    'type' => 'fieldTypeIdentifier',
1714
                    'value' => 'newValue4',
1715
                    'languageCode' => 'eng-US',
1716
                ]
1717
            ),
1718
        ];
1719
1720
        return [
1721
            // 0. Creating by default values only
1722
            [
1723
                'eng-US',
1724
                [],
1725
                $spiFields0,
1726
            ],
1727
            // 1. Multiple languages with language set
1728
            [
1729
                'eng-US',
1730
                [
1731
                    new Field(
1732
                        [
1733
                            'fieldDefIdentifier' => 'identifier1',
1734
                            'value' => 'newValue1',
1735
                            'languageCode' => 'ger-DE',
1736
                        ]
1737
                    ),
1738
                    new Field(
1739
                        [
1740
                            'fieldDefIdentifier' => 'identifier2',
1741
                            'value' => 'newValue2',
1742
                            'languageCode' => 'eng-US',
1743
                        ]
1744
                    ),
1745
                    new Field(
1746
                        [
1747
                            'fieldDefIdentifier' => 'identifier4',
1748
                            'value' => 'newValue4',
1749
                            'languageCode' => 'eng-US',
1750
                        ]
1751
                    ),
1752
                ],
1753
                $spiFields1,
1754
            ],
1755
            // 2. Multiple languages without language set
1756
            [
1757
                'eng-US',
1758
                [
1759
                    new Field(
1760
                        [
1761
                            'fieldDefIdentifier' => 'identifier1',
1762
                            'value' => 'newValue1',
1763
                            'languageCode' => 'ger-DE',
1764
                        ]
1765
                    ),
1766
                    new Field(
1767
                        [
1768
                            'fieldDefIdentifier' => 'identifier2',
1769
                            'value' => 'newValue2',
1770
                            'languageCode' => null,
1771
                        ]
1772
                    ),
1773
                    new Field(
1774
                        [
1775
                            'fieldDefIdentifier' => 'identifier4',
1776
                            'value' => 'newValue4',
1777
                            'languageCode' => null,
1778
                        ]
1779
                    ),
1780
                ],
1781
                $spiFields1,
1782
            ],
1783
        ];
1784
    }
1785
1786
    protected function fixturesForTestCreateContentNonRedundantFieldSetComplex()
1787
    {
1788
        return [
1789
            new FieldDefinition(
1790
                [
1791
                    'id' => 'fieldDefinitionId1',
1792
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
1793
                    'isTranslatable' => true,
1794
                    'identifier' => 'identifier1',
1795
                    'isRequired' => false,
1796
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
1797
                ]
1798
            ),
1799
            new FieldDefinition(
1800
                [
1801
                    'id' => 'fieldDefinitionId2',
1802
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
1803
                    'isTranslatable' => true,
1804
                    'identifier' => 'identifier2',
1805
                    'isRequired' => false,
1806
                    'defaultValue' => 'defaultValue2',
1807
                ]
1808
            ),
1809
            new FieldDefinition(
1810
                [
1811
                    'id' => 'fieldDefinitionId3',
1812
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
1813
                    'isTranslatable' => false,
1814
                    'identifier' => 'identifier3',
1815
                    'isRequired' => false,
1816
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
1817
                ]
1818
            ),
1819
            new FieldDefinition(
1820
                [
1821
                    'id' => 'fieldDefinitionId4',
1822
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
1823
                    'isTranslatable' => false,
1824
                    'identifier' => 'identifier4',
1825
                    'isRequired' => false,
1826
                    'defaultValue' => 'defaultValue4',
1827
                ]
1828
            ),
1829
        ];
1830
    }
1831
1832
    /**
1833
     * Test for the createContent() method.
1834
     *
1835
     * Testing multiple languages with multiple translatable fields with empty default value.
1836
     *
1837
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
1838
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
1839
     * @covers \eZ\Publish\Core\Repository\ContentService::cloneField
1840
     * @covers \eZ\Publish\Core\Repository\ContentService::getDefaultObjectStates
1841
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
1842
     * @dataProvider providerForTestCreateContentNonRedundantFieldSetComplex
1843
     */
1844
    public function testCreateContentNonRedundantFieldSetComplex($mainLanguageCode, $structFields, $spiFields)
1845
    {
1846
        $fieldDefinitions = $this->fixturesForTestCreateContentNonRedundantFieldSetComplex();
1847
1848
        $this->assertForTestCreateContentNonRedundantFieldSet(
1849
            $mainLanguageCode,
1850
            $structFields,
1851
            $spiFields,
1852
            $fieldDefinitions
1853
        );
1854
    }
1855
1856
    public function providerForTestCreateContentWithInvalidLanguage()
1857
    {
1858
        return [
1859
            [
1860
                'eng-GB',
1861
                [
1862
                    new Field(
1863
                        [
1864
                            'fieldDefIdentifier' => 'identifier',
1865
                            'value' => 'newValue',
1866
                            'languageCode' => 'Klingon',
1867
                        ]
1868
                    ),
1869
                ],
1870
            ],
1871
            [
1872
                'Klingon',
1873
                [
1874
                    new Field(
1875
                        [
1876
                            'fieldDefIdentifier' => 'identifier',
1877
                            'value' => 'newValue',
1878
                            'languageCode' => 'eng-GB',
1879
                        ]
1880
                    ),
1881
                ],
1882
            ],
1883
        ];
1884
    }
1885
1886
    /**
1887
     * Test for the updateContent() method.
1888
     *
1889
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
1890
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
1891
     * @dataProvider providerForTestCreateContentWithInvalidLanguage
1892
     */
1893
    public function testCreateContentWithInvalidLanguage($mainLanguageCode, $structFields)
1894
    {
1895
        $this->expectException(APINotFoundException::class);
1896
        $this->expectExceptionMessage('Could not find \'Language\' with identifier \'Klingon\'');
1897
1898
        $repositoryMock = $this->getRepositoryMock();
1899
        $mockedService = $this->getPartlyMockedContentService();
1900
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
1901
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
1902
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
1903
        $domainMapperMock = $this->getContentDomainMapperMock();
1904
        $permissionResolver = $this->getPermissionResolverMock();
1905
1906
        $contentType = new ContentType(
1907
            [
1908
                'id' => 123,
1909
                'fieldDefinitions' => [],
1910
            ]
1911
        );
1912
        $contentCreateStruct = new ContentCreateStruct(
1913
            [
1914
                'fields' => $structFields,
1915
                'mainLanguageCode' => $mainLanguageCode,
1916
                'contentType' => $contentType,
1917
                'alwaysAvailable' => false,
1918
                'ownerId' => 169,
1919
                'sectionId' => 1,
1920
            ]
1921
        );
1922
1923
        $languageHandlerMock->expects($this->any())
1924
            ->method('loadByLanguageCode')
1925
            ->with($this->isType('string'))
1926
            ->will(
1927
                $this->returnCallback(
1928
                    function ($languageCode) {
1929
                        if ($languageCode === 'Klingon') {
1930
                            throw new NotFoundException('Language', 'Klingon');
1931
                        }
1932
1933
                        return new Language(['id' => 4242]);
1934
                    }
1935
                )
1936
            );
1937
1938
        $contentTypeServiceMock->expects($this->once())
1939
            ->method('loadContentType')
1940
            ->with($this->equalTo($contentType->id))
1941
            ->will($this->returnValue($contentType));
1942
1943
        $repositoryMock->expects($this->once())
1944
            ->method('getContentTypeService')
1945
            ->will($this->returnValue($contentTypeServiceMock));
1946
1947
        $that = $this;
1948
        $permissionResolver->expects($this->once())
1949
            ->method('canUser')
1950
            ->with(
1951
                $this->equalTo('content'),
1952
                $this->equalTo('create'),
1953
                $this->isInstanceOf(APIContentCreateStruct::class),
1954
                $this->equalTo([])
1955
            )->will(
1956
                $this->returnCallback(
1957
                    function () use ($that, $contentCreateStruct) {
1958
                        $that->assertEquals($contentCreateStruct, func_get_arg(2));
1959
1960
                        return true;
1961
                    }
1962
                )
1963
            );
1964
1965
        $domainMapperMock->expects($this->once())
1966
            ->method('getUniqueHash')
1967
            ->with($this->isInstanceOf(APIContentCreateStruct::class))
1968
            ->will(
1969
                $this->returnCallback(
1970
                    function ($object) use ($that, $contentCreateStruct) {
1971
                        $that->assertEquals($contentCreateStruct, $object);
1972
1973
                        return 'hash';
1974
                    }
1975
                )
1976
            );
1977
1978
        $mockedService->createContent($contentCreateStruct, []);
1979
    }
1980
1981
    protected function assertForCreateContentContentValidationException(
1982
        $mainLanguageCode,
1983
        $structFields,
1984
        $fieldDefinitions = []
1985
    ) {
1986
        $repositoryMock = $this->getRepositoryMock();
1987
        $mockedService = $this->getPartlyMockedContentService(['loadContentByRemoteId']);
1988
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
1989
        $permissionResolver = $this->getPermissionResolverMock();
1990
1991
        $contentType = new ContentType(
1992
            [
1993
                'id' => 123,
1994
                'fieldDefinitions' => new FieldDefinitionCollection($fieldDefinitions),
1995
            ]
1996
        );
1997
        $contentCreateStruct = new ContentCreateStruct(
1998
            [
1999
                'ownerId' => 169,
2000
                'alwaysAvailable' => false,
2001
                'remoteId' => 'faraday',
2002
                'mainLanguageCode' => $mainLanguageCode,
2003
                'fields' => $structFields,
2004
                'contentType' => $contentType,
2005
            ]
2006
        );
2007
2008
        $contentTypeServiceMock->expects($this->once())
2009
            ->method('loadContentType')
2010
            ->with($this->equalTo(123))
2011
            ->will($this->returnValue($contentType));
2012
2013
        $repositoryMock->expects($this->once())
2014
            ->method('getContentTypeService')
2015
            ->will($this->returnValue($contentTypeServiceMock));
2016
2017
        $permissionResolver->expects($this->once())
2018
            ->method('canUser')
2019
            ->with(
2020
                $this->equalTo('content'),
2021
                $this->equalTo('create'),
2022
                $this->isInstanceOf(get_class($contentCreateStruct)),
2023
                $this->equalTo([])
2024
            )->will($this->returnValue(true));
2025
2026
        $mockedService->expects($this->once())
2027
            ->method('loadContentByRemoteId')
2028
            ->with($contentCreateStruct->remoteId)
2029
            ->will(
2030
                $this->throwException(new NotFoundException('Content', 'faraday'))
2031
            );
2032
2033
        $mockedService->createContent($contentCreateStruct, []);
2034
    }
2035
2036
    public function providerForTestCreateContentThrowsContentValidationExceptionFieldDefinition()
2037
    {
2038
        return [
2039
            [
2040
                'eng-GB',
2041
                [
2042
                    new Field(
2043
                        [
2044
                            'fieldDefIdentifier' => 'identifier',
2045
                            'value' => 'newValue',
2046
                            'languageCode' => 'eng-GB',
2047
                        ]
2048
                    ),
2049
                ],
2050
            ],
2051
        ];
2052
    }
2053
2054
    /**
2055
     * Test for the createContent() method.
2056
     *
2057
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
2058
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
2059
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
2060
     * @dataProvider providerForTestCreateContentThrowsContentValidationExceptionFieldDefinition
2061
     */
2062
    public function testCreateContentThrowsContentValidationExceptionFieldDefinition($mainLanguageCode, $structFields)
2063
    {
2064
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\ContentValidationException::class);
2065
        $this->expectExceptionMessage('Field definition \'identifier\' does not exist in the given Content Type');
2066
2067
        $this->assertForCreateContentContentValidationException(
2068
            $mainLanguageCode,
2069
            $structFields,
2070
            []
2071
        );
2072
    }
2073
2074
    public function providerForTestCreateContentThrowsContentValidationExceptionTranslation()
2075
    {
2076
        return [
2077
            [
2078
                'eng-GB',
2079
                [
2080
                    new Field(
2081
                        [
2082
                            'fieldDefIdentifier' => 'identifier',
2083
                            'value' => 'newValue',
2084
                            'languageCode' => 'eng-US',
2085
                        ]
2086
                    ),
2087
                ],
2088
            ],
2089
        ];
2090
    }
2091
2092
    /**
2093
     * Test for the createContent() method.
2094
     *
2095
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
2096
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
2097
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
2098
     * @dataProvider providerForTestCreateContentThrowsContentValidationExceptionTranslation
2099
     */
2100
    public function testCreateContentThrowsContentValidationExceptionTranslation($mainLanguageCode, $structFields)
2101
    {
2102
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\ContentValidationException::class);
2103
        $this->expectExceptionMessage('You cannot set a value for the non-translatable Field definition \'identifier\' in language \'eng-US\'');
2104
2105
        $fieldDefinitions = [
2106
            new FieldDefinition(
2107
                [
2108
                    'id' => 'fieldDefinitionId1',
2109
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
2110
                    'isTranslatable' => false,
2111
                    'identifier' => 'identifier',
2112
                    'isRequired' => false,
2113
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
2114
                ]
2115
            ),
2116
        ];
2117
2118
        $this->assertForCreateContentContentValidationException(
2119
            $mainLanguageCode,
2120
            $structFields,
2121
            $fieldDefinitions
2122
        );
2123
    }
2124
2125
    /**
2126
     * Asserts behaviour necessary for testing ContentFieldValidationException because of required
2127
     * field being empty.
2128
     *
2129
     * @param string $mainLanguageCode
2130
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
2131
     * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition[] $fieldDefinitions
2132
     *
2133
     * @return mixed
2134
     */
2135
    protected function assertForTestCreateContentRequiredField(
2136
        $mainLanguageCode,
2137
        array $structFields,
2138
        array $fieldDefinitions
2139
    ) {
2140
        $repositoryMock = $this->getRepositoryMock();
2141
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
2142
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
2143
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
2144
        $domainMapperMock = $this->getContentDomainMapperMock();
2145
        $fieldTypeMock = $this->createMock(SPIFieldType::class);
2146
        $permissionResolver = $this->getPermissionResolverMock();
2147
2148
        $contentType = new ContentType(
2149
            [
2150
                'id' => 123,
2151
                'fieldDefinitions' => new FieldDefinitionCollection($fieldDefinitions),
2152
                'nameSchema' => '<nameSchema>',
2153
            ]
2154
        );
2155
        $contentCreateStruct = new ContentCreateStruct(
2156
            [
2157
                'fields' => $structFields,
2158
                'mainLanguageCode' => $mainLanguageCode,
2159
                'contentType' => $contentType,
2160
                'alwaysAvailable' => false,
2161
                'ownerId' => 169,
2162
                'sectionId' => 1,
2163
            ]
2164
        );
2165
2166
        $languageHandlerMock->expects($this->any())
2167
            ->method('loadByLanguageCode')
2168
            ->with($this->isType('string'))
2169
            ->will(
2170
                $this->returnCallback(
2171
                    function () {
2172
                        return new Language(['id' => 4242]);
2173
                    }
2174
                )
2175
            );
2176
2177
        $contentTypeServiceMock->expects($this->once())
2178
            ->method('loadContentType')
2179
            ->with($this->equalTo($contentType->id))
2180
            ->will($this->returnValue($contentType));
2181
2182
        $repositoryMock->expects($this->once())
2183
            ->method('getContentTypeService')
2184
            ->will($this->returnValue($contentTypeServiceMock));
2185
2186
        $that = $this;
2187
        $permissionResolver->expects($this->once())
2188
            ->method('canUser')
2189
            ->with(
2190
                $this->equalTo('content'),
2191
                $this->equalTo('create'),
2192
                $this->isInstanceOf(APIContentCreateStruct::class),
2193
                $this->equalTo([])
2194
            )->will(
2195
                $this->returnCallback(
2196
                    function () use ($that, $contentCreateStruct) {
2197
                        $that->assertEquals($contentCreateStruct, func_get_arg(2));
2198
2199
                        return true;
2200
                    }
2201
                )
2202
            );
2203
2204
        $domainMapperMock->expects($this->once())
2205
            ->method('getUniqueHash')
2206
            ->with($this->isInstanceOf(APIContentCreateStruct::class))
2207
            ->will(
2208
                $this->returnCallback(
2209
                    function ($object) use ($that, $contentCreateStruct) {
2210
                        $that->assertEquals($contentCreateStruct, $object);
2211
2212
                        return 'hash';
2213
                    }
2214
                )
2215
            );
2216
2217
        $fieldTypeMock->expects($this->any())
2218
            ->method('acceptValue')
2219
            ->will(
2220
                $this->returnCallback(
2221
                    function ($valueString) {
2222
                        return new ValueStub($valueString);
2223
                    }
2224
                )
2225
            );
2226
2227
        $emptyValue = self::EMPTY_FIELD_VALUE;
2228
        $fieldTypeMock->expects($this->any())
2229
            ->method('isEmptyValue')
2230
            ->will(
2231
                $this->returnCallback(
2232
                    function (ValueStub $value) use ($emptyValue) {
2233
                        return $emptyValue === (string)$value;
2234
                    }
2235
                )
2236
            );
2237
2238
        $fieldTypeMock->expects($this->any())
2239
            ->method('validate')
2240
            ->will($this->returnValue([]));
2241
2242
        $this->getFieldTypeRegistryMock()->expects($this->any())
2243
            ->method('getFieldType')
2244
            ->will($this->returnValue($fieldTypeMock));
2245
2246
        return $contentCreateStruct;
2247
    }
2248
2249
    public function providerForTestCreateContentThrowsContentValidationExceptionRequiredField()
2250
    {
2251
        return [
2252
            [
2253
                'eng-US',
2254
                [
2255
                    new Field(
2256
                        [
2257
                            'fieldDefIdentifier' => 'identifier',
2258
                            'value' => self::EMPTY_FIELD_VALUE,
2259
                            'languageCode' => null,
2260
                        ]
2261
                    ),
2262
                ],
2263
                'identifier',
2264
                'eng-US',
2265
            ],
2266
        ];
2267
    }
2268
2269
    /**
2270
     * Test for the createContent() method.
2271
     *
2272
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
2273
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
2274
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
2275
     * @dataProvider providerForTestCreateContentThrowsContentValidationExceptionRequiredField
2276
     */
2277
    public function testCreateContentRequiredField(
2278
        $mainLanguageCode,
2279
        $structFields,
2280
        $identifier,
2281
        $languageCode
2282
    ) {
2283
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException::class);
2284
2285
        $fieldDefinitions = [
2286
            new FieldDefinition(
2287
                [
2288
                    'id' => 'fieldDefinitionId',
2289
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
2290
                    'isTranslatable' => true,
2291
                    'identifier' => 'identifier',
2292
                    'isRequired' => true,
2293
                    'defaultValue' => 'defaultValue',
2294
                ]
2295
            ),
2296
        ];
2297
        $contentCreateStruct = $this->assertForTestCreateContentRequiredField(
2298
            $mainLanguageCode,
2299
            $structFields,
2300
            $fieldDefinitions
2301
        );
2302
2303
        $mockedService = $this->getPartlyMockedContentService();
2304
2305
        try {
2306
            $mockedService->createContent($contentCreateStruct, []);
2307
        } catch (ContentValidationException $e) {
2308
            $this->assertEquals(
2309
                "Value for required field definition '{$identifier}' with language '{$languageCode}' is empty",
2310
                $e->getMessage()
2311
            );
2312
2313
            throw $e;
2314
        }
2315
    }
2316
2317
    /**
2318
     * Asserts behaviour necessary for testing ContentFieldValidationException because of
2319
     * field not being valid.
2320
     *
2321
     * @param string $mainLanguageCode
2322
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
2323
     * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition[] $fieldDefinitions
2324
     *
2325
     * @return mixed
2326
     */
2327
    protected function assertForTestCreateContentThrowsContentFieldValidationException(
2328
        $mainLanguageCode,
2329
        array $structFields,
2330
        array $fieldDefinitions
2331
    ) {
2332
        $repositoryMock = $this->getRepositoryMock();
2333
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
2334
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
2335
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
2336
        $domainMapperMock = $this->getContentDomainMapperMock();
2337
        $relationProcessorMock = $this->getRelationProcessorMock();
2338
        $fieldTypeMock = $this->createMock(SPIFieldType::class);
2339
        $languageCodes = $this->determineLanguageCodesForCreate($mainLanguageCode, $structFields);
2340
        $permissionResolver = $this->getPermissionResolverMock();
2341
2342
        $contentType = new ContentType(
2343
            [
2344
                'id' => 123,
2345
                'fieldDefinitions' => new FieldDefinitionCollection($fieldDefinitions),
2346
                'nameSchema' => '<nameSchema>',
2347
            ]
2348
        );
2349
        $contentCreateStruct = new ContentCreateStruct(
2350
            [
2351
                'fields' => $structFields,
2352
                'mainLanguageCode' => $mainLanguageCode,
2353
                'contentType' => $contentType,
2354
                'alwaysAvailable' => false,
2355
                'ownerId' => 169,
2356
                'sectionId' => 1,
2357
            ]
2358
        );
2359
2360
        $languageHandlerMock->expects($this->any())
2361
            ->method('loadByLanguageCode')
2362
            ->with($this->isType('string'))
2363
            ->will(
2364
                $this->returnCallback(
2365
                    function () {
2366
                        return new Language(['id' => 4242]);
2367
                    }
2368
                )
2369
            );
2370
2371
        $contentTypeServiceMock->expects($this->once())
2372
            ->method('loadContentType')
2373
            ->with($this->equalTo($contentType->id))
2374
            ->will($this->returnValue($contentType));
2375
2376
        $repositoryMock->expects($this->once())
2377
            ->method('getContentTypeService')
2378
            ->will($this->returnValue($contentTypeServiceMock));
2379
2380
        $that = $this;
2381
        $permissionResolver->expects($this->once())
2382
            ->method('canUser')
2383
            ->with(
2384
                $this->equalTo('content'),
2385
                $this->equalTo('create'),
2386
                $this->isInstanceOf(APIContentCreateStruct::class),
2387
                $this->equalTo([])
2388
            )->will(
2389
                $this->returnCallback(
2390
                    function () use ($that, $contentCreateStruct) {
2391
                        $that->assertEquals($contentCreateStruct, func_get_arg(2));
2392
2393
                        return true;
2394
                    }
2395
                )
2396
            );
2397
2398
        $domainMapperMock->expects($this->once())
2399
            ->method('getUniqueHash')
2400
            ->with($this->isInstanceOf(APIContentCreateStruct::class))
2401
            ->will(
2402
                $this->returnCallback(
2403
                    function ($object) use ($that, $contentCreateStruct) {
2404
                        $that->assertEquals($contentCreateStruct, $object);
2405
2406
                        return 'hash';
2407
                    }
2408
                )
2409
            );
2410
2411
        $this->getFieldTypeRegistryMock()->expects($this->any())
2412
            ->method('getFieldType')
2413
            ->will($this->returnValue($fieldTypeMock));
2414
2415
        $relationProcessorMock
2416
            ->expects($this->any())
2417
            ->method('appendFieldRelations')
2418
            ->with(
2419
                $this->isType('array'),
2420
                $this->isType('array'),
2421
                $this->isInstanceOf(SPIFieldType::class),
2422
                $this->isInstanceOf(Value::class),
2423
                $this->anything()
2424
            );
2425
2426
        $fieldValues = $this->determineValuesForCreate(
2427
            $mainLanguageCode,
2428
            $structFields,
2429
            $fieldDefinitions,
2430
            $languageCodes
2431
        );
2432
        $allFieldErrors = [];
2433
        $validateCount = 0;
2434
        $emptyValue = self::EMPTY_FIELD_VALUE;
2435
        foreach ($contentType->getFieldDefinitions() as $fieldDefinition) {
2436
            foreach ($fieldValues[$fieldDefinition->identifier] as $languageCode => $value) {
2437
                $fieldTypeMock->expects($this->at($validateCount++))
2438
                    ->method('acceptValue')
2439
                    ->will(
2440
                        $this->returnCallback(
2441
                            function ($valueString) {
2442
                                return new ValueStub($valueString);
2443
                            }
2444
                        )
2445
                    );
2446
2447
                $fieldTypeMock->expects($this->at($validateCount++))
2448
                    ->method('isEmptyValue')
2449
                    ->will(
2450
                        $this->returnCallback(
2451
                            function (ValueStub $value) use ($emptyValue) {
2452
                                return $emptyValue === (string)$value;
2453
                            }
2454
                        )
2455
                    );
2456
2457
                if (self::EMPTY_FIELD_VALUE === (string)$value) {
2458
                    continue;
2459
                }
2460
2461
                $fieldTypeMock->expects($this->at($validateCount++))
2462
                    ->method('validate')
2463
                    ->with(
2464
                        $this->equalTo($fieldDefinition),
2465
                        $this->equalTo($value)
2466
                    )->will($this->returnArgument(1));
2467
2468
                $allFieldErrors[$fieldDefinition->id][$languageCode] = $value;
2469
            }
2470
        }
2471
2472
        return [$contentCreateStruct, $allFieldErrors];
2473
    }
2474
2475
    public function providerForTestCreateContentThrowsContentFieldValidationException()
2476
    {
2477
        return $this->providerForTestCreateContentNonRedundantFieldSetComplex();
2478
    }
2479
2480
    /**
2481
     * Test for the createContent() method.
2482
     *
2483
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
2484
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
2485
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
2486
     * @dataProvider providerForTestCreateContentThrowsContentFieldValidationException
2487
     */
2488
    public function testCreateContentThrowsContentFieldValidationException($mainLanguageCode, $structFields)
2489
    {
2490
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException::class);
2491
        $this->expectExceptionMessage('Content Fields did not validate');
2492
2493
        $fieldDefinitions = $this->fixturesForTestCreateContentNonRedundantFieldSetComplex();
2494
        list($contentCreateStruct, $allFieldErrors) =
2495
            $this->assertForTestCreateContentThrowsContentFieldValidationException(
2496
                $mainLanguageCode,
2497
                $structFields,
2498
                $fieldDefinitions
2499
            );
2500
2501
        $mockedService = $this->getPartlyMockedContentService();
2502
2503
        try {
2504
            $mockedService->createContent($contentCreateStruct);
2505
        } catch (ContentFieldValidationException $e) {
2506
            $this->assertEquals($allFieldErrors, $e->getFieldErrors());
2507
            throw $e;
2508
        }
2509
    }
2510
2511
    /**
2512
     * Test for the createContent() method.
2513
     *
2514
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
2515
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
2516
     * @covers \eZ\Publish\Core\Repository\ContentService::buildSPILocationCreateStructs
2517
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
2518
     */
2519
    public function testCreateContentWithLocations()
2520
    {
2521
        $spiFields = [
2522
            new SPIField(
2523
                [
2524
                    'fieldDefinitionId' => 'fieldDefinitionId',
2525
                    'type' => 'fieldTypeIdentifier',
2526
                    'value' => 'defaultValue',
2527
                    'languageCode' => 'eng-US',
2528
                ]
2529
            ),
2530
        ];
2531
        $fieldDefinitions = [
2532
            new FieldDefinition(
2533
                [
2534
                    'id' => 'fieldDefinitionId',
2535
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
2536
                    'isTranslatable' => false,
2537
                    'identifier' => 'identifier',
2538
                    'isRequired' => false,
2539
                    'defaultValue' => 'defaultValue',
2540
                ]
2541
            ),
2542
        ];
2543
2544
        // Set up a simple case that will pass
2545
        $locationCreateStruct1 = new LocationCreateStruct(['parentLocationId' => 321]);
2546
        $locationCreateStruct2 = new LocationCreateStruct(['parentLocationId' => 654]);
2547
        $locationCreateStructs = [$locationCreateStruct1, $locationCreateStruct2];
2548
        $contentCreateStruct = $this->assertForTestCreateContentNonRedundantFieldSet(
2549
            'eng-US',
2550
            [],
2551
            $spiFields,
2552
            $fieldDefinitions,
2553
            $locationCreateStructs,
2554
            false,
2555
            // Do not execute
2556
            false
2557
        );
2558
2559
        $repositoryMock = $this->getRepositoryMock();
2560
        $mockedService = $this->getPartlyMockedContentService();
2561
        $locationServiceMock = $this->getLocationServiceMock();
2562
        /** @var \PHPUnit\Framework\MockObject\MockObject $handlerMock */
2563
        $handlerMock = $this->getPersistenceMock()->contentHandler();
2564
        $domainMapperMock = $this->getContentDomainMapperMock();
2565
        $spiLocationCreateStruct = new SPILocation\CreateStruct();
2566
        $parentLocation = new Location(['contentInfo' => new ContentInfo(['sectionId' => 1])]);
2567
2568
        $locationServiceMock->expects($this->at(0))
2569
            ->method('loadLocation')
2570
            ->with($this->equalTo(321))
2571
            ->will($this->returnValue($parentLocation));
2572
2573
        $locationServiceMock->expects($this->at(1))
2574
            ->method('loadLocation')
2575
            ->with($this->equalTo(654))
2576
            ->will($this->returnValue($parentLocation));
2577
2578
        $repositoryMock->expects($this->atLeastOnce())
2579
            ->method('getLocationService')
2580
            ->will($this->returnValue($locationServiceMock));
2581
2582
        $domainMapperMock->expects($this->at(1))
2583
            ->method('buildSPILocationCreateStruct')
2584
            ->with(
2585
                $this->equalTo($locationCreateStruct1),
2586
                $this->equalTo($parentLocation),
2587
                $this->equalTo(true),
2588
                $this->equalTo(null),
2589
                $this->equalTo(null)
2590
            )->will($this->returnValue($spiLocationCreateStruct));
2591
2592
        $domainMapperMock->expects($this->at(2))
2593
            ->method('buildSPILocationCreateStruct')
2594
            ->with(
2595
                $this->equalTo($locationCreateStruct2),
2596
                $this->equalTo($parentLocation),
2597
                $this->equalTo(false),
2598
                $this->equalTo(null),
2599
                $this->equalTo(null)
2600
            )->will($this->returnValue($spiLocationCreateStruct));
2601
2602
        $spiContentCreateStruct = new SPIContentCreateStruct(
2603
            [
2604
                'name' => [],
2605
                'typeId' => 123,
2606
                'sectionId' => 1,
2607
                'ownerId' => 169,
2608
                'remoteId' => 'hash',
2609
                'fields' => $spiFields,
2610
                'modified' => time(),
2611
                'initialLanguageId' => 4242,
2612
                'locations' => [$spiLocationCreateStruct, $spiLocationCreateStruct],
2613
            ]
2614
        );
2615
        $spiContentCreateStruct2 = clone $spiContentCreateStruct;
2616
        ++$spiContentCreateStruct2->modified;
2617
2618
        $spiContent = new SPIContent(
2619
            [
2620
                'versionInfo' => new SPIContent\VersionInfo(
2621
                    [
2622
                        'contentInfo' => new SPIContent\ContentInfo(['id' => 42]),
2623
                        'versionNo' => 7,
2624
                    ]
2625
                ),
2626
            ]
2627
        );
2628
2629
        $handlerMock->expects($this->once())
2630
            ->method('create')
2631
            ->with($this->logicalOr($spiContentCreateStruct, $spiContentCreateStruct2))
2632
            ->will($this->returnValue($spiContent));
2633
2634
        $domainMapperMock->expects($this->once())
2635
            ->method('buildContentDomainObject')
2636
            ->with(
2637
                $this->isInstanceOf(SPIContent::class),
2638
                $this->isInstanceOf(APIContentType::class)
2639
            )
2640
            ->willReturn($this->createMock(APIContent::class));
2641
2642
        $repositoryMock->expects($this->once())->method('commit');
2643
2644
        // Execute
2645
        $mockedService->createContent($contentCreateStruct, $locationCreateStructs);
2646
    }
2647
2648
    /**
2649
     * Test for the createContent() method.
2650
     *
2651
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
2652
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
2653
     * @covers \eZ\Publish\Core\Repository\ContentService::buildSPILocationCreateStructs
2654
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
2655
     */
2656
    public function testCreateContentWithLocationsDuplicateUnderParent()
2657
    {
2658
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\InvalidArgumentException::class);
2659
        $this->expectExceptionMessage('You provided multiple LocationCreateStructs with the same parent Location \'321\'');
2660
2661
        $fieldDefinitions = [
2662
            new FieldDefinition(
2663
                [
2664
                    'id' => 'fieldDefinitionId',
2665
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
2666
                    'isTranslatable' => false,
2667
                    'identifier' => 'identifier',
2668
                    'isRequired' => false,
2669
                    'defaultValue' => 'defaultValue',
2670
                ]
2671
            ),
2672
        ];
2673
2674
        $repositoryMock = $this->getRepositoryMock();
2675
        $mockedService = $this->getPartlyMockedContentService();
2676
        $locationServiceMock = $this->getLocationServiceMock();
2677
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
2678
        $domainMapperMock = $this->getContentDomainMapperMock();
2679
        $permissionResolver = $this->getPermissionResolverMock();
2680
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
2681
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
2682
        $spiLocationCreateStruct = new SPILocation\CreateStruct();
2683
        $parentLocation = new Location(['id' => 321]);
2684
        $locationCreateStruct = new LocationCreateStruct(['parentLocationId' => 321]);
2685
        $locationCreateStructs = [$locationCreateStruct, clone $locationCreateStruct];
2686
        $contentType = new ContentType(
2687
            [
2688
                'id' => 123,
2689
                'fieldDefinitions' => $fieldDefinitions,
2690
                'nameSchema' => '<nameSchema>',
2691
            ]
2692
        );
2693
        $contentCreateStruct = new ContentCreateStruct(
2694
            [
2695
                'fields' => [],
2696
                'mainLanguageCode' => 'eng-US',
2697
                'contentType' => $contentType,
2698
                'alwaysAvailable' => false,
2699
                'ownerId' => 169,
2700
                'sectionId' => 1,
2701
            ]
2702
        );
2703
2704
        $languageHandlerMock->expects($this->any())
2705
            ->method('loadByLanguageCode')
2706
            ->with($this->isType('string'))
2707
            ->will(
2708
                $this->returnCallback(
2709
                    function () {
2710
                        return new Language(['id' => 4242]);
2711
                    }
2712
                )
2713
            );
2714
2715
        $contentTypeServiceMock->expects($this->once())
2716
            ->method('loadContentType')
2717
            ->with($this->equalTo($contentType->id))
2718
            ->will($this->returnValue($contentType));
2719
2720
        $repositoryMock->expects($this->once())
2721
            ->method('getContentTypeService')
2722
            ->will($this->returnValue($contentTypeServiceMock));
2723
2724
        $that = $this;
2725
        $permissionResolver->expects($this->once())
2726
            ->method('canUser')
2727
            ->with(
2728
                $this->equalTo('content'),
2729
                $this->equalTo('create'),
2730
                $this->isInstanceOf(APIContentCreateStruct::class),
2731
                $this->equalTo($locationCreateStructs)
2732
            )->will(
2733
                $this->returnCallback(
2734
                    function () use ($that, $contentCreateStruct) {
2735
                        $that->assertEquals($contentCreateStruct, func_get_arg(2));
2736
2737
                        return true;
2738
                    }
2739
                )
2740
            );
2741
2742
        $domainMapperMock->expects($this->once())
2743
            ->method('getUniqueHash')
2744
            ->with($this->isInstanceOf(APIContentCreateStruct::class))
2745
            ->will(
2746
                $this->returnCallback(
2747
                    function ($object) use ($that, $contentCreateStruct) {
2748
                        $that->assertEquals($contentCreateStruct, $object);
2749
2750
                        return 'hash';
2751
                    }
2752
                )
2753
            );
2754
2755
        $locationServiceMock->expects($this->once())
2756
            ->method('loadLocation')
2757
            ->with($this->equalTo(321))
2758
            ->will($this->returnValue($parentLocation));
2759
2760
        $repositoryMock->expects($this->any())
2761
            ->method('getLocationService')
2762
            ->will($this->returnValue($locationServiceMock));
2763
2764
        $domainMapperMock->expects($this->any())
2765
            ->method('buildSPILocationCreateStruct')
2766
            ->with(
2767
                $this->equalTo($locationCreateStruct),
2768
                $this->equalTo($parentLocation),
2769
                $this->equalTo(true),
2770
                $this->equalTo(null),
2771
                $this->equalTo(null)
2772
            )->will($this->returnValue($spiLocationCreateStruct));
2773
2774
        $mockedService->createContent(
2775
            $contentCreateStruct,
2776
            $locationCreateStructs
2777
        );
2778
    }
2779
2780
    /**
2781
     * Test for the createContent() method.
2782
     *
2783
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
2784
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
2785
     * @covers \eZ\Publish\Core\Repository\ContentService::getDefaultObjectStates
2786
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
2787
     */
2788
    public function testCreateContentObjectStates()
2789
    {
2790
        $spiFields = [
2791
            new SPIField(
2792
                [
2793
                    'fieldDefinitionId' => 'fieldDefinitionId',
2794
                    'type' => 'fieldTypeIdentifier',
2795
                    'value' => 'defaultValue',
2796
                    'languageCode' => 'eng-US',
2797
                ]
2798
            ),
2799
        ];
2800
        $fieldDefinitions = [
2801
            new FieldDefinition(
2802
                [
2803
                    'id' => 'fieldDefinitionId',
2804
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
2805
                    'isTranslatable' => false,
2806
                    'identifier' => 'identifier',
2807
                    'isRequired' => false,
2808
                    'defaultValue' => 'defaultValue',
2809
                ]
2810
            ),
2811
        ];
2812
        $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...
2813
            new SPIObjectStateGroup(['id' => 10]),
2814
            new SPIObjectStateGroup(['id' => 20]),
2815
        ];
2816
2817
        // Set up a simple case that will pass
2818
        $contentCreateStruct = $this->assertForTestCreateContentNonRedundantFieldSet(
2819
            'eng-US',
2820
            [],
2821
            $spiFields,
2822
            $fieldDefinitions,
2823
            [],
2824
            true,
2825
            // Do not execute
2826
            false
2827
        );
2828
        $timestamp = time();
2829
        $contentCreateStruct->modificationDate = new \DateTime("@{$timestamp}");
2830
2831
        $repositoryMock = $this->getRepositoryMock();
2832
        $mockedService = $this->getPartlyMockedContentService();
2833
        /** @var \PHPUnit\Framework\MockObject\MockObject $handlerMock */
2834
        $handlerMock = $this->getPersistenceMock()->contentHandler();
2835
        $domainMapperMock = $this->getContentDomainMapperMock();
2836
2837
        $this->mockGetDefaultObjectStates();
2838
        $this->mockSetDefaultObjectStates();
2839
2840
        $spiContentCreateStruct = new SPIContentCreateStruct(
2841
            [
2842
                'name' => [],
2843
                'typeId' => 123,
2844
                'sectionId' => 1,
2845
                'ownerId' => 169,
2846
                'remoteId' => 'hash',
2847
                'fields' => $spiFields,
2848
                'modified' => $timestamp,
2849
                'initialLanguageId' => 4242,
2850
                'locations' => [],
2851
            ]
2852
        );
2853
        $spiContentCreateStruct2 = clone $spiContentCreateStruct;
2854
        ++$spiContentCreateStruct2->modified;
2855
2856
        $spiContent = new SPIContent(
2857
            [
2858
                'versionInfo' => new SPIContent\VersionInfo(
2859
                    [
2860
                        'contentInfo' => new SPIContent\ContentInfo(['id' => 42]),
2861
                        'versionNo' => 7,
2862
                    ]
2863
                ),
2864
            ]
2865
        );
2866
2867
        $handlerMock->expects($this->once())
2868
            ->method('create')
2869
            ->with($this->equalTo($spiContentCreateStruct))
2870
            ->will($this->returnValue($spiContent));
2871
2872
        $domainMapperMock->expects($this->once())
2873
            ->method('buildContentDomainObject')
2874
            ->with(
2875
                $this->isInstanceOf(SPIContent::class),
2876
                $this->isInstanceOf(APIContentType::class)
2877
            )
2878
            ->willReturn($this->createMock(APIContent::class));
2879
2880
        $repositoryMock->expects($this->once())->method('commit');
2881
2882
        // Execute
2883
        $mockedService->createContent($contentCreateStruct, []);
2884
    }
2885
2886
    /**
2887
     * Test for the createContent() method.
2888
     *
2889
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForCreate
2890
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForCreate
2891
     * @covers \eZ\Publish\Core\Repository\ContentService::getDefaultObjectStates
2892
     * @covers \eZ\Publish\Core\Repository\ContentService::createContent
2893
     * @dataProvider providerForTestCreateContentThrowsContentValidationExceptionTranslation
2894
     */
2895
    public function testCreateContentWithRollback()
2896
    {
2897
        $this->expectException(\Exception::class);
2898
        $this->expectExceptionMessage('Store failed');
2899
2900
        $fieldDefinitions = [
2901
            new FieldDefinition(
2902
                [
2903
                    'id' => 'fieldDefinitionId',
2904
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
2905
                    'isTranslatable' => false,
2906
                    'identifier' => 'identifier',
2907
                    'isRequired' => false,
2908
                    'defaultValue' => 'defaultValue',
2909
                ]
2910
            ),
2911
        ];
2912
2913
        // Setup a simple case that will pass
2914
        $contentCreateStruct = $this->assertForTestCreateContentNonRedundantFieldSet(
2915
            'eng-US',
2916
            [],
2917
            [],
2918
            $fieldDefinitions,
2919
            [],
2920
            false,
2921
            // Do not execute test
2922
            false
2923
        );
2924
2925
        $repositoryMock = $this->getRepositoryMock();
2926
        $repositoryMock->expects($this->never())->method('commit');
2927
        $repositoryMock->expects($this->once())->method('rollback');
2928
2929
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandlerMock */
2930
        $contentHandlerMock = $this->getPersistenceMock()->contentHandler();
2931
        $contentHandlerMock->expects($this->once())
2932
            ->method('create')
2933
            ->with($this->anything())
2934
            ->will($this->throwException(new \Exception('Store failed')));
2935
2936
        // Execute
2937
        $this->partlyMockedContentService->createContent($contentCreateStruct, []);
2938
    }
2939
2940
    public function providerForTestUpdateContentThrowsBadStateException()
2941
    {
2942
        return [
2943
            [VersionInfo::STATUS_PUBLISHED],
2944
            [VersionInfo::STATUS_ARCHIVED],
2945
        ];
2946
    }
2947
2948
    /**
2949
     * Test for the updateContent() method.
2950
     *
2951
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
2952
     * @dataProvider providerForTestUpdateContentThrowsBadStateException
2953
     */
2954
    public function testUpdateContentThrowsBadStateException($status)
2955
    {
2956
        $this->expectException(BadStateException::class);
2957
2958
        $versionInfo = new VersionInfo(
2959
            [
2960
                'contentInfo' => new ContentInfo(['id' => 42]),
2961
                'versionNo' => 7,
2962
                'status' => $status,
2963
            ]
2964
        );
2965
        $content = new Content(
2966
            [
2967
                'versionInfo' => $versionInfo,
2968
                'internalFields' => [],
2969
            ]
2970
        );
2971
2972
        $mockedService = $this->getPartlyMockedContentService(['loadContent', 'internalLoadContentById']);
2973
        $mockedService
2974
            ->method('loadContent')
2975
            ->with(
2976
                $this->equalTo(42),
2977
                $this->equalTo(null),
2978
                $this->equalTo(7)
2979
            )->will(
2980
                $this->returnValue($content)
2981
            );
2982
        $mockedService
2983
            ->method('internalLoadContentById')
2984
            ->will(
2985
                $this->returnValue($content)
2986
            );
2987
2988
        $permissionResolverMock = $this->getPermissionResolverMock();
2989
        $permissionResolverMock
2990
            ->method('canUser')
2991
            ->will($this->returnValue(true));
2992
2993
        $contentUpdateStruct = new ContentUpdateStruct();
2994
2995
        $mockedService->updateContent($versionInfo, $contentUpdateStruct);
2996
    }
2997
2998
    /**
2999
     * Test for the updateContent() method.
3000
     *
3001
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
3002
     */
3003
    public function testUpdateContentThrowsUnauthorizedException()
3004
    {
3005
        $this->expectException(UnauthorizedException::class);
3006
3007
        $permissionResolverMock = $this->getPermissionResolverMock();
3008
        $mockedService = $this->getPartlyMockedContentService(['loadContent']);
3009
        $contentUpdateStruct = new ContentUpdateStruct();
3010
        $versionInfo = new VersionInfo(
3011
            [
3012
                'contentInfo' => new ContentInfo(['id' => 42]),
3013
                'versionNo' => 7,
3014
                'status' => VersionInfo::STATUS_DRAFT,
3015
            ]
3016
        );
3017
        $content = new Content(
3018
            [
3019
                'versionInfo' => $versionInfo,
3020
                'internalFields' => [],
3021
            ]
3022
        );
3023
3024
        $mockedService->expects($this->once())
3025
            ->method('loadContent')
3026
            ->with(
3027
                $this->equalTo(42),
3028
                $this->equalTo(null),
3029
                $this->equalTo(7)
3030
            )->will(
3031
                $this->returnValue($content)
3032
            );
3033
3034
        $permissionResolverMock->expects($this->once())
3035
            ->method('canUser')
3036
            ->with(
3037
                $this->equalTo('content'),
3038
                $this->equalTo('edit'),
3039
                $this->equalTo($content),
3040
                $this->isType('array')
3041
            )->will($this->returnValue(false));
3042
3043
        $mockedService->updateContent($versionInfo, $contentUpdateStruct);
3044
    }
3045
3046
    /**
3047
     * @param string $initialLanguageCode
3048
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
3049
     * @param string[] $existingLanguages
3050
     *
3051
     * @return string[]
3052
     */
3053
    protected function determineLanguageCodesForUpdate($initialLanguageCode, array $structFields, $existingLanguages)
3054
    {
3055
        $languageCodes = array_fill_keys($existingLanguages, true);
3056
        if ($initialLanguageCode !== null) {
3057
            $languageCodes[$initialLanguageCode] = true;
3058
        }
3059
3060
        foreach ($structFields as $field) {
3061
            if ($field->languageCode === null || isset($languageCodes[$field->languageCode])) {
3062
                continue;
3063
            }
3064
3065
            $languageCodes[$field->languageCode] = true;
3066
        }
3067
3068
        return array_keys($languageCodes);
3069
    }
3070
3071
    /**
3072
     * @param string $initialLanguageCode
3073
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
3074
     * @param string $mainLanguageCode
3075
     * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition[] $fieldDefinitions
3076
     *
3077
     * @return array
3078
     */
3079
    protected function mapStructFieldsForUpdate($initialLanguageCode, $structFields, $mainLanguageCode, $fieldDefinitions)
3080
    {
3081
        $initialLanguageCode = $initialLanguageCode ?: $mainLanguageCode;
3082
3083
        $mappedFieldDefinitions = [];
3084
        foreach ($fieldDefinitions as $fieldDefinition) {
3085
            $mappedFieldDefinitions[$fieldDefinition->identifier] = $fieldDefinition;
3086
        }
3087
3088
        $mappedStructFields = [];
3089
        foreach ($structFields as $structField) {
3090
            $identifier = $structField->fieldDefIdentifier;
3091
3092
            if ($structField->languageCode !== null) {
3093
                $languageCode = $structField->languageCode;
3094
            } elseif ($mappedFieldDefinitions[$identifier]->isTranslatable) {
3095
                $languageCode = $initialLanguageCode;
3096
            } else {
3097
                $languageCode = $mainLanguageCode;
3098
            }
3099
3100
            $mappedStructFields[$identifier][$languageCode] = (string)$structField->value;
3101
        }
3102
3103
        return $mappedStructFields;
3104
    }
3105
3106
    /**
3107
     * Returns full, possibly redundant array of field values, indexed by field definition
3108
     * identifier and language code.
3109
     *
3110
     * @param string $initialLanguageCode
3111
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
3112
     * @param \eZ\Publish\Core\Repository\Values\Content\Content $content
3113
     * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition[] $fieldDefinitions
3114
     * @param array $languageCodes
3115
     *
3116
     * @return array
3117
     */
3118
    protected function determineValuesForUpdate(
3119
        $initialLanguageCode,
3120
        array $structFields,
3121
        Content $content,
3122
        array $fieldDefinitions,
3123
        array $languageCodes
3124
    ) {
3125
        $mainLanguageCode = $content->versionInfo->contentInfo->mainLanguageCode;
3126
3127
        $mappedStructFields = $this->mapStructFieldsForUpdate(
3128
            $initialLanguageCode,
3129
            $structFields,
3130
            $mainLanguageCode,
3131
            $fieldDefinitions
3132
        );
3133
3134
        $values = [];
3135
3136
        foreach ($fieldDefinitions as $fieldDefinition) {
3137
            $identifier = $fieldDefinition->identifier;
3138
            foreach ($languageCodes as $languageCode) {
3139
                if (!$fieldDefinition->isTranslatable) {
3140
                    if (isset($mappedStructFields[$identifier][$mainLanguageCode])) {
3141
                        $values[$identifier][$languageCode] = $mappedStructFields[$identifier][$mainLanguageCode];
3142
                    } else {
3143
                        $values[$identifier][$languageCode] = (string)$content->fields[$identifier][$mainLanguageCode];
3144
                    }
3145
                    continue;
3146
                }
3147
3148
                if (isset($mappedStructFields[$identifier][$languageCode])) {
3149
                    $values[$identifier][$languageCode] = $mappedStructFields[$identifier][$languageCode];
3150
                    continue;
3151
                }
3152
3153
                if (isset($content->fields[$identifier][$languageCode])) {
3154
                    $values[$identifier][$languageCode] = (string)$content->fields[$identifier][$languageCode];
3155
                    continue;
3156
                }
3157
3158
                $values[$identifier][$languageCode] = (string)$fieldDefinition->defaultValue;
3159
            }
3160
        }
3161
3162
        return $this->stubValues($values);
3163
    }
3164
3165
    protected function stubValues(array $fieldValues)
3166
    {
3167
        foreach ($fieldValues as &$languageValues) {
3168
            foreach ($languageValues as &$value) {
3169
                $value = new ValueStub($value);
3170
            }
3171
        }
3172
3173
        return $fieldValues;
3174
    }
3175
3176
    /**
3177
     * Asserts that calling updateContent() with given API field set causes calling
3178
     * Handler::updateContent() with given SPI field set.
3179
     *
3180
     * @param string $initialLanguageCode
3181
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $structFields
3182
     * @param \eZ\Publish\SPI\Persistence\Content\Field[] $spiFields
3183
     * @param \eZ\Publish\API\Repository\Values\Content\Field[] $existingFields
3184
     * @param \eZ\Publish\API\Repository\Values\ContentType\FieldDefinition[] $fieldDefinitions
3185
     * @param bool $execute
3186
     *
3187
     * @return mixed
3188
     */
3189
    protected function assertForTestUpdateContentNonRedundantFieldSet(
3190
        $initialLanguageCode,
3191
        array $structFields,
3192
        array $spiFields,
3193
        array $existingFields,
3194
        array $fieldDefinitions,
3195
        $execute = true
3196
    ) {
3197
        $repositoryMock = $this->getRepositoryMock();
3198
        $permissionResolverMock = $this->getPermissionResolverMock();
3199
        $permissionResolverMock
3200
            ->expects($this->once())
3201
            ->method('getCurrentUserReference')
3202
            ->willReturn(new UserReference(169));
3203
        $mockedService = $this->getPartlyMockedContentService(['internalLoadContentById', 'internalLoadRelations'], $permissionResolverMock);
3204
        $permissionResolverMock = $this->getPermissionResolverMock();
3205
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandlerMock */
3206
        $contentHandlerMock = $this->getPersistenceMock()->contentHandler();
3207
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
3208
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
3209
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
3210
        $domainMapperMock = $this->getContentDomainMapperMock();
3211
        $relationProcessorMock = $this->getRelationProcessorMock();
3212
        $nameSchemaServiceMock = $this->getNameSchemaServiceMock();
3213
        $fieldTypeMock = $this->createMock(SPIFieldType::class);
3214
        $existingLanguageCodes = array_map(
3215
            function (Field $field) {
3216
                return $field->languageCode;
3217
            },
3218
            $existingFields
3219
        );
3220
        $languageCodes = $this->determineLanguageCodesForUpdate(
3221
            $initialLanguageCode,
3222
            $structFields,
3223
            $existingLanguageCodes
3224
        );
3225
        $versionInfo = new VersionInfo(
3226
            [
3227
                'contentInfo' => new ContentInfo(
3228
                    [
3229
                        'id' => 42,
3230
                        'contentTypeId' => 24,
3231
                        'mainLanguageCode' => 'eng-GB',
3232
                    ]
3233
                ),
3234
                'versionNo' => 7,
3235
                'languageCodes' => $existingLanguageCodes,
3236
                'status' => VersionInfo::STATUS_DRAFT,
3237
            ]
3238
        );
3239
        $content = new Content(
3240
            [
3241
                'versionInfo' => $versionInfo,
3242
                'internalFields' => $existingFields,
3243
            ]
3244
        );
3245
        $contentType = new ContentType([
3246
            'fieldDefinitions' => new FieldDefinitionCollection($fieldDefinitions),
3247
        ]);
3248
3249
        $languageHandlerMock->expects($this->any())
3250
            ->method('loadByLanguageCode')
3251
            ->with($this->isType('string'))
3252
            ->will(
3253
                $this->returnCallback(
3254
                    function () {
3255
                        return new Language(['id' => 4242]);
3256
                    }
3257
                )
3258
            );
3259
3260
        $mockedService
3261
            ->method('internalLoadContentById')
3262
            ->with(
3263
                $this->equalTo(42),
3264
                $this->equalTo(null),
3265
                $this->equalTo(7)
3266
            )->will(
3267
                $this->returnValue($content)
3268
            );
3269
3270
        $repositoryMock->expects($this->once())->method('beginTransaction');
3271
3272
        $permissionResolverMock->expects($this->any())
3273
            ->method('canUser')
3274
            ->will($this->returnValue(true));
3275
3276
        $contentTypeServiceMock->expects($this->once())
3277
            ->method('loadContentType')
3278
            ->with($this->equalTo(24))
3279
            ->will($this->returnValue($contentType));
3280
3281
        $repositoryMock->expects($this->once())
3282
            ->method('getContentTypeService')
3283
            ->will($this->returnValue($contentTypeServiceMock));
3284
3285
        $fieldTypeMock->expects($this->any())
3286
            ->method('acceptValue')
3287
            ->will(
3288
                $this->returnCallback(
3289
                    function ($valueString) {
3290
                        return new ValueStub($valueString);
3291
                    }
3292
                )
3293
            );
3294
3295
        $emptyValue = self::EMPTY_FIELD_VALUE;
3296
        $fieldTypeMock->expects($this->any())
3297
            ->method('toPersistenceValue')
3298
            ->will(
3299
                $this->returnCallback(
3300
                    function (ValueStub $value) {
3301
                        return (string)$value;
3302
                    }
3303
                )
3304
            );
3305
3306
        $fieldTypeMock->expects($this->any())
3307
            ->method('isEmptyValue')
3308
            ->will(
3309
                $this->returnCallback(
3310
                    function (ValueStub $value) use ($emptyValue) {
3311
                        return $emptyValue === (string)$value;
3312
                    }
3313
                )
3314
            );
3315
3316
        $fieldTypeMock->expects($this->any())
3317
            ->method('validate')
3318
            ->will($this->returnValue([]));
3319
3320
        $this->getFieldTypeRegistryMock()->expects($this->any())
3321
            ->method('getFieldType')
3322
            ->will($this->returnValue($fieldTypeMock));
3323
3324
        $relationProcessorMock
3325
            ->expects($this->exactly(count($fieldDefinitions) * count($languageCodes)))
3326
            ->method('appendFieldRelations')
3327
            ->with(
3328
                $this->isType('array'),
3329
                $this->isType('array'),
3330
                $this->isInstanceOf(SPIFieldType::class),
3331
                $this->isInstanceOf(Value::class),
3332
                $this->anything()
3333
            );
3334
3335
        $values = $this->determineValuesForUpdate(
3336
            $initialLanguageCode,
3337
            $structFields,
3338
            $content,
3339
            $fieldDefinitions,
3340
            $languageCodes
3341
        );
3342
        $nameSchemaServiceMock->expects($this->once())
3343
            ->method('resolveNameSchema')
3344
            ->with(
3345
                $this->equalTo($content),
3346
                $this->equalTo($values),
3347
                $this->equalTo($languageCodes)
3348
            )->will($this->returnValue([]));
3349
3350
        $existingRelations = ['RELATIONS!!!'];
3351
        $mockedService
3352
            ->method('internalLoadRelations')
3353
            ->with($content->versionInfo)
3354
            ->will($this->returnValue($existingRelations));
3355
        $relationProcessorMock->expects($this->any())
3356
            ->method('processFieldRelations')
3357
            ->with(
3358
                $this->isType('array'),
3359
                $this->equalTo(42),
3360
                $this->isType('int'),
3361
                $this->equalTo($contentType),
3362
                $this->equalTo($existingRelations)
3363
            );
3364
3365
        $contentUpdateStruct = new ContentUpdateStruct(
3366
            [
3367
                'fields' => $structFields,
3368
                'initialLanguageCode' => $initialLanguageCode,
3369
            ]
3370
        );
3371
3372
        if ($execute) {
3373
            $spiContentUpdateStruct = new SPIContentUpdateStruct(
3374
                [
3375
                    'creatorId' => 169,
3376
                    'fields' => $spiFields,
3377
                    'modificationDate' => time(),
3378
                    'initialLanguageId' => 4242,
3379
                ]
3380
            );
3381
3382
            // During code coverage runs, timestamp might differ 1-3 seconds
3383
            $spiContentUpdateStructTs1 = clone $spiContentUpdateStruct;
3384
            ++$spiContentUpdateStructTs1->modificationDate;
3385
3386
            $spiContentUpdateStructTs2 = clone $spiContentUpdateStructTs1;
3387
            ++$spiContentUpdateStructTs2->modificationDate;
3388
3389
            $spiContentUpdateStructTs3 = clone $spiContentUpdateStructTs2;
3390
            ++$spiContentUpdateStructTs3->modificationDate;
3391
3392
            $spiContent = new SPIContent(
3393
                [
3394
                    'versionInfo' => new SPIContent\VersionInfo(
3395
                        [
3396
                            'contentInfo' => new SPIContent\ContentInfo(['id' => 42]),
3397
                            'versionNo' => 7,
3398
                        ]
3399
                    ),
3400
                ]
3401
            );
3402
3403
            $contentHandlerMock->expects($this->once())
3404
                ->method('updateContent')
3405
                ->with(
3406
                    42,
3407
                    7,
3408
                    $this->logicalOr($spiContentUpdateStruct, $spiContentUpdateStructTs1, $spiContentUpdateStructTs2, $spiContentUpdateStructTs3)
3409
                )
3410
                ->will($this->returnValue($spiContent));
3411
3412
            $repositoryMock->expects($this->once())->method('commit');
3413
            $domainMapperMock
3414
                ->method('buildContentDomainObject')
3415
                ->with(
3416
                    $this->isInstanceOf(SPIContent::class),
3417
                    $this->isInstanceOf(APIContentType::class)
3418
                )
3419
                ->will($this->returnValue($content));
3420
3421
            $mockedService->updateContent($content->versionInfo, $contentUpdateStruct);
3422
        }
3423
3424
        return [$content->versionInfo, $contentUpdateStruct];
3425
    }
3426
3427
    public function providerForTestUpdateContentNonRedundantFieldSet1()
3428
    {
3429
        $spiFields = [
3430
            new SPIField(
3431
                [
3432
                    'id' => '100',
3433
                    'fieldDefinitionId' => 'fieldDefinitionId',
3434
                    'type' => 'fieldTypeIdentifier',
3435
                    'value' => 'newValue',
3436
                    'languageCode' => 'eng-GB',
3437
                    'versionNo' => 7,
3438
                ]
3439
            ),
3440
        ];
3441
3442
        return [
3443
            // With languages set
3444
            [
3445
                'eng-GB',
3446
                [
3447
                    new Field(
3448
                        [
3449
                            'fieldDefIdentifier' => 'identifier',
3450
                            'value' => 'newValue',
3451
                            'languageCode' => 'eng-GB',
3452
                        ]
3453
                    ),
3454
                ],
3455
                $spiFields,
3456
            ],
3457
            // Without languages set
3458
            [
3459
                null,
3460
                [
3461
                    new Field(
3462
                        [
3463
                            'fieldDefIdentifier' => 'identifier',
3464
                            'value' => 'newValue',
3465
                            'languageCode' => null,
3466
                        ]
3467
                    ),
3468
                ],
3469
                $spiFields,
3470
            ],
3471
            // Adding new language without fields
3472
            [
3473
                'eng-US',
3474
                [],
3475
                [],
3476
            ],
3477
        ];
3478
    }
3479
3480
    /**
3481
     * Test for the updateContent() method.
3482
     *
3483
     * Testing the simplest use case.
3484
     *
3485
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
3486
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
3487
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
3488
     * @dataProvider providerForTestUpdateContentNonRedundantFieldSet1
3489
     */
3490
    public function testUpdateContentNonRedundantFieldSet1($initialLanguageCode, $structFields, $spiFields)
3491
    {
3492
        $existingFields = [
3493
            new Field(
3494
                [
3495
                    'id' => '100',
3496
                    'fieldDefIdentifier' => 'identifier',
3497
                    'value' => 'initialValue',
3498
                    'languageCode' => 'eng-GB',
3499
                ]
3500
            ),
3501
        ];
3502
3503
        $fieldDefinitions = [
3504
            new FieldDefinition(
3505
                [
3506
                    'id' => 'fieldDefinitionId',
3507
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
3508
                    'isTranslatable' => false,
3509
                    'identifier' => 'identifier',
3510
                    'isRequired' => false,
3511
                    'defaultValue' => 'defaultValue',
3512
                ]
3513
            ),
3514
        ];
3515
3516
        $this->assertForTestUpdateContentNonRedundantFieldSet(
3517
            $initialLanguageCode,
3518
            $structFields,
3519
            $spiFields,
3520
            $existingFields,
3521
            $fieldDefinitions
3522
        );
3523
    }
3524
3525
    public function providerForTestUpdateContentNonRedundantFieldSet2()
3526
    {
3527
        $spiFields0 = [
3528
            new SPIField(
3529
                [
3530
                    'id' => '100',
3531
                    'fieldDefinitionId' => 'fieldDefinitionId',
3532
                    'type' => 'fieldTypeIdentifier',
3533
                    'value' => 'newValue',
3534
                    'languageCode' => 'eng-GB',
3535
                    'versionNo' => 7,
3536
                ]
3537
            ),
3538
        ];
3539
        $spiFields1 = [
3540
            new SPIField(
3541
                [
3542
                    'id' => null,
3543
                    'fieldDefinitionId' => 'fieldDefinitionId',
3544
                    'type' => 'fieldTypeIdentifier',
3545
                    'value' => 'newValue',
3546
                    'languageCode' => 'eng-US',
3547
                    'versionNo' => 7,
3548
                ]
3549
            ),
3550
        ];
3551
        $spiFields2 = [
3552
            new SPIField(
3553
                [
3554
                    'id' => 100,
3555
                    'fieldDefinitionId' => 'fieldDefinitionId',
3556
                    'type' => 'fieldTypeIdentifier',
3557
                    'value' => 'newValue2',
3558
                    'languageCode' => 'eng-GB',
3559
                    'versionNo' => 7,
3560
                ]
3561
            ),
3562
            new SPIField(
3563
                [
3564
                    'id' => null,
3565
                    'fieldDefinitionId' => 'fieldDefinitionId',
3566
                    'type' => 'fieldTypeIdentifier',
3567
                    'value' => 'newValue1',
3568
                    'languageCode' => 'eng-US',
3569
                    'versionNo' => 7,
3570
                ]
3571
            ),
3572
        ];
3573
3574
        return [
3575
            // 0. With languages set
3576
            [
3577
                'eng-GB',
3578
                [
3579
                    new Field(
3580
                        [
3581
                            'fieldDefIdentifier' => 'identifier',
3582
                            'value' => 'newValue',
3583
                            'languageCode' => 'eng-GB',
3584
                        ]
3585
                    ),
3586
                ],
3587
                $spiFields0,
3588
            ],
3589
            // 1. Without languages set
3590
            [
3591
                null,
3592
                [
3593
                    new Field(
3594
                        [
3595
                            'fieldDefIdentifier' => 'identifier',
3596
                            'value' => 'newValue',
3597
                            'languageCode' => null,
3598
                        ]
3599
                    ),
3600
                ],
3601
                $spiFields0,
3602
            ],
3603
            // 2. New language with language set
3604
            [
3605
                'eng-GB',
3606
                [
3607
                    new Field(
3608
                        [
3609
                            'fieldDefIdentifier' => 'identifier',
3610
                            'value' => 'newValue',
3611
                            'languageCode' => 'eng-US',
3612
                        ]
3613
                    ),
3614
                ],
3615
                $spiFields1,
3616
            ],
3617
            // 3. New language without language set
3618
            [
3619
                'eng-US',
3620
                [
3621
                    new Field(
3622
                        [
3623
                            'fieldDefIdentifier' => 'identifier',
3624
                            'value' => 'newValue',
3625
                            'languageCode' => null,
3626
                        ]
3627
                    ),
3628
                ],
3629
                $spiFields1,
3630
            ],
3631
            // 4. New language and existing language with language set
3632
            [
3633
                'eng-GB',
3634
                [
3635
                    new Field(
3636
                        [
3637
                            'fieldDefIdentifier' => 'identifier',
3638
                            'value' => 'newValue1',
3639
                            'languageCode' => 'eng-US',
3640
                        ]
3641
                    ),
3642
                    new Field(
3643
                        [
3644
                            'fieldDefIdentifier' => 'identifier',
3645
                            'value' => 'newValue2',
3646
                            'languageCode' => 'eng-GB',
3647
                        ]
3648
                    ),
3649
                ],
3650
                $spiFields2,
3651
            ],
3652
            // 5. New language and existing language without language set
3653
            [
3654
                'eng-US',
3655
                [
3656
                    new Field(
3657
                        [
3658
                            'fieldDefIdentifier' => 'identifier',
3659
                            'value' => 'newValue1',
3660
                            'languageCode' => null,
3661
                        ]
3662
                    ),
3663
                    new Field(
3664
                        [
3665
                            'fieldDefIdentifier' => 'identifier',
3666
                            'value' => 'newValue2',
3667
                            'languageCode' => 'eng-GB',
3668
                        ]
3669
                    ),
3670
                ],
3671
                $spiFields2,
3672
            ],
3673
            // 6. Adding new language without fields
3674
            [
3675
                'eng-US',
3676
                [],
3677
                [
3678
                    new SPIField(
3679
                        [
3680
                            'id' => null,
3681
                            'fieldDefinitionId' => 'fieldDefinitionId',
3682
                            'type' => 'fieldTypeIdentifier',
3683
                            'value' => 'defaultValue',
3684
                            'languageCode' => 'eng-US',
3685
                            'versionNo' => 7,
3686
                        ]
3687
                    ),
3688
                ],
3689
            ],
3690
        ];
3691
    }
3692
3693
    /**
3694
     * Test for the updateContent() method.
3695
     *
3696
     * Testing with translatable field.
3697
     *
3698
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
3699
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
3700
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
3701
     * @dataProvider providerForTestUpdateContentNonRedundantFieldSet2
3702
     */
3703
    public function testUpdateContentNonRedundantFieldSet2($initialLanguageCode, $structFields, $spiFields)
3704
    {
3705
        $existingFields = [
3706
            new Field(
3707
                [
3708
                    'id' => '100',
3709
                    'fieldDefIdentifier' => 'identifier',
3710
                    'value' => 'initialValue',
3711
                    'languageCode' => 'eng-GB',
3712
                ]
3713
            ),
3714
        ];
3715
3716
        $fieldDefinitions = [
3717
            new FieldDefinition(
3718
                [
3719
                    'id' => 'fieldDefinitionId',
3720
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
3721
                    'isTranslatable' => true,
3722
                    'identifier' => 'identifier',
3723
                    'isRequired' => false,
3724
                    'defaultValue' => 'defaultValue',
3725
                ]
3726
            ),
3727
        ];
3728
3729
        $this->assertForTestUpdateContentNonRedundantFieldSet(
3730
            $initialLanguageCode,
3731
            $structFields,
3732
            $spiFields,
3733
            $existingFields,
3734
            $fieldDefinitions
3735
        );
3736
    }
3737
3738
    public function providerForTestUpdateContentNonRedundantFieldSet3()
3739
    {
3740
        $spiFields0 = [
3741
            new SPIField(
3742
                [
3743
                    'id' => null,
3744
                    'fieldDefinitionId' => 'fieldDefinitionId1',
3745
                    'type' => 'fieldTypeIdentifier',
3746
                    'value' => 'newValue1',
3747
                    'languageCode' => 'eng-US',
3748
                    'versionNo' => 7,
3749
                ]
3750
            ),
3751
        ];
3752
        $spiFields1 = [
3753
            new SPIField(
3754
                [
3755
                    'id' => 100,
3756
                    'fieldDefinitionId' => 'fieldDefinitionId1',
3757
                    'type' => 'fieldTypeIdentifier',
3758
                    'value' => 'newValue2',
3759
                    'languageCode' => 'eng-GB',
3760
                    'versionNo' => 7,
3761
                ]
3762
            ),
3763
            new SPIField(
3764
                [
3765
                    'id' => null,
3766
                    'fieldDefinitionId' => 'fieldDefinitionId1',
3767
                    'type' => 'fieldTypeIdentifier',
3768
                    'value' => 'newValue1',
3769
                    'languageCode' => 'eng-US',
3770
                    'versionNo' => 7,
3771
                ]
3772
            ),
3773
        ];
3774
        $spiFields2 = [
3775
            new SPIField(
3776
                [
3777
                    'id' => 100,
3778
                    'fieldDefinitionId' => 'fieldDefinitionId1',
3779
                    'type' => 'fieldTypeIdentifier',
3780
                    'value' => 'newValue2',
3781
                    'languageCode' => 'eng-GB',
3782
                    'versionNo' => 7,
3783
                ]
3784
            ),
3785
            new SPIField(
3786
                [
3787
                    'id' => null,
3788
                    'fieldDefinitionId' => 'fieldDefinitionId1',
3789
                    'type' => 'fieldTypeIdentifier',
3790
                    'value' => 'newValue1',
3791
                    'languageCode' => 'eng-US',
3792
                    'versionNo' => 7,
3793
                ]
3794
            ),
3795
            new SPIField(
3796
                [
3797
                    'id' => 101,
3798
                    'fieldDefinitionId' => 'fieldDefinitionId2',
3799
                    'type' => 'fieldTypeIdentifier',
3800
                    'value' => 'newValue3',
3801
                    'languageCode' => 'eng-GB',
3802
                    'versionNo' => 7,
3803
                ]
3804
            ),
3805
        ];
3806
        $spiFields3 = [
3807
            new SPIField(
3808
                [
3809
                    'id' => null,
3810
                    'fieldDefinitionId' => 'fieldDefinitionId1',
3811
                    'type' => 'fieldTypeIdentifier',
3812
                    'value' => 'defaultValue1',
3813
                    'languageCode' => 'eng-US',
3814
                    'versionNo' => 7,
3815
                ]
3816
            ),
3817
        ];
3818
3819
        return [
3820
            // 0. ew language with language set
3821
            [
3822
                'eng-US',
3823
                [
3824
                    new Field(
3825
                        [
3826
                            'fieldDefIdentifier' => 'identifier1',
3827
                            'value' => 'newValue1',
3828
                            'languageCode' => 'eng-US',
3829
                        ]
3830
                    ),
3831
                ],
3832
                $spiFields0,
3833
            ],
3834
            // 1. New language without language set
3835
            [
3836
                'eng-US',
3837
                [
3838
                    new Field(
3839
                        [
3840
                            'fieldDefIdentifier' => 'identifier1',
3841
                            'value' => 'newValue1',
3842
                            'languageCode' => null,
3843
                        ]
3844
                    ),
3845
                ],
3846
                $spiFields0,
3847
            ],
3848
            // 2. New language and existing language with language set
3849
            [
3850
                'eng-US',
3851
                [
3852
                    new Field(
3853
                        [
3854
                            'fieldDefIdentifier' => 'identifier1',
3855
                            'value' => 'newValue1',
3856
                            'languageCode' => 'eng-US',
3857
                        ]
3858
                    ),
3859
                    new Field(
3860
                        [
3861
                            'fieldDefIdentifier' => 'identifier1',
3862
                            'value' => 'newValue2',
3863
                            'languageCode' => 'eng-GB',
3864
                        ]
3865
                    ),
3866
                ],
3867
                $spiFields1,
3868
            ],
3869
            // 3. New language and existing language without language set
3870
            [
3871
                'eng-US',
3872
                [
3873
                    new Field(
3874
                        [
3875
                            'fieldDefIdentifier' => 'identifier1',
3876
                            'value' => 'newValue1',
3877
                            'languageCode' => null,
3878
                        ]
3879
                    ),
3880
                    new Field(
3881
                        [
3882
                            'fieldDefIdentifier' => 'identifier1',
3883
                            'value' => 'newValue2',
3884
                            'languageCode' => 'eng-GB',
3885
                        ]
3886
                    ),
3887
                ],
3888
                $spiFields1,
3889
            ],
3890
            // 4. New language and existing language with untranslatable field, with language set
3891
            [
3892
                'eng-US',
3893
                [
3894
                    new Field(
3895
                        [
3896
                            'fieldDefIdentifier' => 'identifier1',
3897
                            'value' => 'newValue1',
3898
                            'languageCode' => 'eng-US',
3899
                        ]
3900
                    ),
3901
                    new Field(
3902
                        [
3903
                            'fieldDefIdentifier' => 'identifier1',
3904
                            'value' => 'newValue2',
3905
                            'languageCode' => 'eng-GB',
3906
                        ]
3907
                    ),
3908
                    new Field(
3909
                        [
3910
                            'fieldDefIdentifier' => 'identifier2',
3911
                            'value' => 'newValue3',
3912
                            'languageCode' => 'eng-GB',
3913
                        ]
3914
                    ),
3915
                ],
3916
                $spiFields2,
3917
            ],
3918
            // 5. New language and existing language with untranslatable field, without language set
3919
            [
3920
                'eng-US',
3921
                [
3922
                    new Field(
3923
                        [
3924
                            'fieldDefIdentifier' => 'identifier1',
3925
                            'value' => 'newValue1',
3926
                            'languageCode' => null,
3927
                        ]
3928
                    ),
3929
                    new Field(
3930
                        [
3931
                            'fieldDefIdentifier' => 'identifier1',
3932
                            'value' => 'newValue2',
3933
                            'languageCode' => 'eng-GB',
3934
                        ]
3935
                    ),
3936
                    new Field(
3937
                        [
3938
                            'fieldDefIdentifier' => 'identifier2',
3939
                            'value' => 'newValue3',
3940
                            'languageCode' => null,
3941
                        ]
3942
                    ),
3943
                ],
3944
                $spiFields2,
3945
            ],
3946
            // 6. Adding new language without fields
3947
            [
3948
                'eng-US',
3949
                [],
3950
                $spiFields3,
3951
            ],
3952
        ];
3953
    }
3954
3955
    /**
3956
     * Test for the updateContent() method.
3957
     *
3958
     * Testing with new language and untranslatable field.
3959
     *
3960
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
3961
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
3962
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
3963
     * @dataProvider providerForTestUpdateContentNonRedundantFieldSet3
3964
     */
3965
    public function testUpdateContentNonRedundantFieldSet3($initialLanguageCode, $structFields, $spiFields)
3966
    {
3967
        $existingFields = [
3968
            new Field(
3969
                [
3970
                    'id' => '100',
3971
                    'fieldDefIdentifier' => 'identifier1',
3972
                    'value' => 'initialValue1',
3973
                    'languageCode' => 'eng-GB',
3974
                ]
3975
            ),
3976
            new Field(
3977
                [
3978
                    'id' => '101',
3979
                    'fieldDefIdentifier' => 'identifier2',
3980
                    'value' => 'initialValue2',
3981
                    'languageCode' => 'eng-GB',
3982
                ]
3983
            ),
3984
        ];
3985
3986
        $fieldDefinitions = [
3987
            new FieldDefinition(
3988
                [
3989
                    'id' => 'fieldDefinitionId1',
3990
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
3991
                    'isTranslatable' => true,
3992
                    'identifier' => 'identifier1',
3993
                    'isRequired' => false,
3994
                    'defaultValue' => 'defaultValue1',
3995
                ]
3996
            ),
3997
            new FieldDefinition(
3998
                [
3999
                    'id' => 'fieldDefinitionId2',
4000
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
4001
                    'isTranslatable' => false,
4002
                    'identifier' => 'identifier2',
4003
                    'isRequired' => false,
4004
                    'defaultValue' => 'defaultValue2',
4005
                ]
4006
            ),
4007
        ];
4008
4009
        $this->assertForTestUpdateContentNonRedundantFieldSet(
4010
            $initialLanguageCode,
4011
            $structFields,
4012
            $spiFields,
4013
            $existingFields,
4014
            $fieldDefinitions
4015
        );
4016
    }
4017
4018
    public function providerForTestUpdateContentNonRedundantFieldSet4()
4019
    {
4020
        $spiFields0 = [
4021
            new SPIField(
4022
                [
4023
                    'id' => null,
4024
                    'fieldDefinitionId' => 'fieldDefinitionId1',
4025
                    'type' => 'fieldTypeIdentifier',
4026
                    'value' => 'newValue1',
4027
                    'languageCode' => 'eng-US',
4028
                    'versionNo' => 7,
4029
                ]
4030
            ),
4031
        ];
4032
        $spiFields1 = [
4033
            new SPIField(
4034
                [
4035
                    'id' => 100,
4036
                    'fieldDefinitionId' => 'fieldDefinitionId1',
4037
                    'type' => 'fieldTypeIdentifier',
4038
                    'value' => self::EMPTY_FIELD_VALUE,
4039
                    'languageCode' => 'eng-GB',
4040
                    'versionNo' => 7,
4041
                ]
4042
            ),
4043
            new SPIField(
4044
                [
4045
                    'id' => null,
4046
                    'fieldDefinitionId' => 'fieldDefinitionId1',
4047
                    'type' => 'fieldTypeIdentifier',
4048
                    'value' => 'newValue1',
4049
                    'languageCode' => 'eng-US',
4050
                    'versionNo' => 7,
4051
                ]
4052
            ),
4053
        ];
4054
        $spiFields2 = [
4055
            new SPIField(
4056
                [
4057
                    'id' => 100,
4058
                    'fieldDefinitionId' => 'fieldDefinitionId1',
4059
                    'type' => 'fieldTypeIdentifier',
4060
                    'value' => self::EMPTY_FIELD_VALUE,
4061
                    'languageCode' => 'eng-GB',
4062
                    'versionNo' => 7,
4063
                ]
4064
            ),
4065
        ];
4066
4067
        return [
4068
            // 0. New translation with empty field by default
4069
            [
4070
                'eng-US',
4071
                [
4072
                    new Field(
4073
                        [
4074
                            'fieldDefIdentifier' => 'identifier1',
4075
                            'value' => 'newValue1',
4076
                            'languageCode' => 'eng-US',
4077
                        ]
4078
                    ),
4079
                ],
4080
                $spiFields0,
4081
            ],
4082
            // 1. New translation with empty field by default, without language set
4083
            [
4084
                'eng-US',
4085
                [
4086
                    new Field(
4087
                        [
4088
                            'fieldDefIdentifier' => 'identifier1',
4089
                            'value' => 'newValue1',
4090
                            'languageCode' => null,
4091
                        ]
4092
                    ),
4093
                ],
4094
                $spiFields0,
4095
            ],
4096
            // 2. New translation with empty field given
4097
            [
4098
                'eng-US',
4099
                [
4100
                    new Field(
4101
                        [
4102
                            'fieldDefIdentifier' => 'identifier1',
4103
                            'value' => 'newValue1',
4104
                            'languageCode' => 'eng-US',
4105
                        ]
4106
                    ),
4107
                    new Field(
4108
                        [
4109
                            'fieldDefIdentifier' => 'identifier2',
4110
                            'value' => self::EMPTY_FIELD_VALUE,
4111
                            'languageCode' => 'eng-US',
4112
                        ]
4113
                    ),
4114
                ],
4115
                $spiFields0,
4116
            ],
4117
            // 3. New translation with empty field given, without language set
4118
            [
4119
                'eng-US',
4120
                [
4121
                    new Field(
4122
                        [
4123
                            'fieldDefIdentifier' => 'identifier1',
4124
                            'value' => 'newValue1',
4125
                            'languageCode' => null,
4126
                        ]
4127
                    ),
4128
                    new Field(
4129
                        [
4130
                            'fieldDefIdentifier' => 'identifier2',
4131
                            'value' => self::EMPTY_FIELD_VALUE,
4132
                            'languageCode' => null,
4133
                        ]
4134
                    ),
4135
                ],
4136
                $spiFields0,
4137
            ],
4138
            // 4. Updating existing language with empty value
4139
            [
4140
                'eng-US',
4141
                [
4142
                    new Field(
4143
                        [
4144
                            'fieldDefIdentifier' => 'identifier1',
4145
                            'value' => 'newValue1',
4146
                            'languageCode' => 'eng-US',
4147
                        ]
4148
                    ),
4149
                    new Field(
4150
                        [
4151
                            'fieldDefIdentifier' => 'identifier1',
4152
                            'value' => self::EMPTY_FIELD_VALUE,
4153
                            'languageCode' => 'eng-GB',
4154
                        ]
4155
                    ),
4156
                ],
4157
                $spiFields1,
4158
            ],
4159
            // 5. Updating existing language with empty value, without language set
4160
            [
4161
                'eng-US',
4162
                [
4163
                    new Field(
4164
                        [
4165
                            'fieldDefIdentifier' => 'identifier1',
4166
                            'value' => 'newValue1',
4167
                            'languageCode' => null,
4168
                        ]
4169
                    ),
4170
                    new Field(
4171
                        [
4172
                            'fieldDefIdentifier' => 'identifier1',
4173
                            'value' => self::EMPTY_FIELD_VALUE,
4174
                            'languageCode' => 'eng-GB',
4175
                        ]
4176
                    ),
4177
                ],
4178
                $spiFields1,
4179
            ],
4180
            // 6. Updating existing language with empty value and adding new language with empty value
4181
            [
4182
                'eng-US',
4183
                [
4184
                    new Field(
4185
                        [
4186
                            'fieldDefIdentifier' => 'identifier1',
4187
                            'value' => self::EMPTY_FIELD_VALUE,
4188
                            'languageCode' => 'eng-US',
4189
                        ]
4190
                    ),
4191
                    new Field(
4192
                        [
4193
                            'fieldDefIdentifier' => 'identifier1',
4194
                            'value' => self::EMPTY_FIELD_VALUE,
4195
                            'languageCode' => 'eng-GB',
4196
                        ]
4197
                    ),
4198
                ],
4199
                $spiFields2,
4200
            ],
4201
            // 7. Updating existing language with empty value and adding new language with empty value,
4202
            // without language set
4203
            [
4204
                'eng-US',
4205
                [
4206
                    new Field(
4207
                        [
4208
                            'fieldDefIdentifier' => 'identifier1',
4209
                            'value' => self::EMPTY_FIELD_VALUE,
4210
                            'languageCode' => null,
4211
                        ]
4212
                    ),
4213
                    new Field(
4214
                        [
4215
                            'fieldDefIdentifier' => 'identifier1',
4216
                            'value' => self::EMPTY_FIELD_VALUE,
4217
                            'languageCode' => 'eng-GB',
4218
                        ]
4219
                    ),
4220
                ],
4221
                $spiFields2,
4222
            ],
4223
            // 8. Adding new language with no fields given
4224
            [
4225
                'eng-US',
4226
                [],
4227
                [],
4228
            ],
4229
            // 9. Adding new language with fields
4230
            [
4231
                'eng-US',
4232
                [
4233
                    new Field(
4234
                        [
4235
                            'fieldDefIdentifier' => 'identifier1',
4236
                            'value' => self::EMPTY_FIELD_VALUE,
4237
                            'languageCode' => 'eng-US',
4238
                        ]
4239
                    ),
4240
                ],
4241
                [],
4242
            ],
4243
            // 10. Adding new language with fields, without language set
4244
            [
4245
                'eng-US',
4246
                [
4247
                    new Field(
4248
                        [
4249
                            'fieldDefIdentifier' => 'identifier1',
4250
                            'value' => self::EMPTY_FIELD_VALUE,
4251
                            'languageCode' => null,
4252
                        ]
4253
                    ),
4254
                ],
4255
                [],
4256
            ],
4257
        ];
4258
    }
4259
4260
    /**
4261
     * Test for the updateContent() method.
4262
     *
4263
     * Testing with empty values.
4264
     *
4265
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
4266
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
4267
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
4268
     * @dataProvider providerForTestUpdateContentNonRedundantFieldSet4
4269
     */
4270
    public function testUpdateContentNonRedundantFieldSet4($initialLanguageCode, $structFields, $spiFields)
4271
    {
4272
        $existingFields = [
4273
            new Field(
4274
                [
4275
                    'id' => '100',
4276
                    'fieldDefIdentifier' => 'identifier1',
4277
                    'value' => 'initialValue1',
4278
                    'languageCode' => 'eng-GB',
4279
                ]
4280
            ),
4281
            new Field(
4282
                [
4283
                    'id' => '101',
4284
                    'fieldDefIdentifier' => 'identifier2',
4285
                    'value' => 'initialValue2',
4286
                    'languageCode' => 'eng-GB',
4287
                ]
4288
            ),
4289
        ];
4290
4291
        $fieldDefinitions = [
4292
            new FieldDefinition(
4293
                [
4294
                    'id' => 'fieldDefinitionId1',
4295
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
4296
                    'isTranslatable' => true,
4297
                    'identifier' => 'identifier1',
4298
                    'isRequired' => false,
4299
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
4300
                ]
4301
            ),
4302
            new FieldDefinition(
4303
                [
4304
                    'id' => 'fieldDefinitionId2',
4305
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
4306
                    'isTranslatable' => true,
4307
                    'identifier' => 'identifier2',
4308
                    'isRequired' => false,
4309
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
4310
                ]
4311
            ),
4312
        ];
4313
4314
        $this->assertForTestUpdateContentNonRedundantFieldSet(
4315
            $initialLanguageCode,
4316
            $structFields,
4317
            $spiFields,
4318
            $existingFields,
4319
            $fieldDefinitions
4320
        );
4321
    }
4322
4323
    /**
4324
     * @return array
4325
     *
4326
     * @todo add first field empty
4327
     */
4328
    public function providerForTestUpdateContentNonRedundantFieldSetComplex()
4329
    {
4330
        $spiFields0 = [
4331
            new SPIField(
4332
                [
4333
                    'id' => 100,
4334
                    'fieldDefinitionId' => 'fieldDefinitionId1',
4335
                    'type' => 'fieldTypeIdentifier',
4336
                    'value' => 'newValue1-eng-GB',
4337
                    'languageCode' => 'eng-GB',
4338
                    'versionNo' => 7,
4339
                ]
4340
            ),
4341
            new SPIField(
4342
                [
4343
                    'id' => null,
4344
                    'fieldDefinitionId' => 'fieldDefinitionId4',
4345
                    'type' => 'fieldTypeIdentifier',
4346
                    'value' => 'newValue4',
4347
                    'languageCode' => 'eng-US',
4348
                    'versionNo' => 7,
4349
                ]
4350
            ),
4351
        ];
4352
        $spiFields1 = [
4353
            new SPIField(
4354
                [
4355
                    'id' => 100,
4356
                    'fieldDefinitionId' => 'fieldDefinitionId1',
4357
                    'type' => 'fieldTypeIdentifier',
4358
                    'value' => 'newValue1-eng-GB',
4359
                    'languageCode' => 'eng-GB',
4360
                    'versionNo' => 7,
4361
                ]
4362
            ),
4363
            new SPIField(
4364
                [
4365
                    'id' => null,
4366
                    'fieldDefinitionId' => 'fieldDefinitionId2',
4367
                    'type' => 'fieldTypeIdentifier',
4368
                    'value' => 'newValue2',
4369
                    'languageCode' => 'eng-US',
4370
                    'versionNo' => 7,
4371
                ]
4372
            ),
4373
            new SPIField(
4374
                [
4375
                    'id' => null,
4376
                    'fieldDefinitionId' => 'fieldDefinitionId4',
4377
                    'type' => 'fieldTypeIdentifier',
4378
                    'value' => 'defaultValue4',
4379
                    'languageCode' => 'eng-US',
4380
                    'versionNo' => 7,
4381
                ]
4382
            ),
4383
        ];
4384
        $spiFields2 = [
4385
            new SPIField(
4386
                [
4387
                    'id' => 100,
4388
                    'fieldDefinitionId' => 'fieldDefinitionId1',
4389
                    'type' => 'fieldTypeIdentifier',
4390
                    'value' => 'newValue1-eng-GB',
4391
                    'languageCode' => 'eng-GB',
4392
                    'versionNo' => 7,
4393
                ]
4394
            ),
4395
            new SPIField(
4396
                [
4397
                    'id' => null,
4398
                    'fieldDefinitionId' => 'fieldDefinitionId2',
4399
                    'type' => 'fieldTypeIdentifier',
4400
                    'value' => 'newValue2',
4401
                    'languageCode' => 'eng-US',
4402
                    'versionNo' => 7,
4403
                ]
4404
            ),
4405
            new SPIField(
4406
                [
4407
                    'id' => null,
4408
                    'fieldDefinitionId' => 'fieldDefinitionId4',
4409
                    'type' => 'fieldTypeIdentifier',
4410
                    'value' => 'defaultValue4',
4411
                    'languageCode' => 'ger-DE',
4412
                    'versionNo' => 7,
4413
                ]
4414
            ),
4415
            new SPIField(
4416
                [
4417
                    'id' => null,
4418
                    'fieldDefinitionId' => 'fieldDefinitionId4',
4419
                    'type' => 'fieldTypeIdentifier',
4420
                    'value' => 'defaultValue4',
4421
                    'languageCode' => 'eng-US',
4422
                    'versionNo' => 7,
4423
                ]
4424
            ),
4425
        ];
4426
4427
        return [
4428
            // 0. Add new language and update existing
4429
            [
4430
                'eng-US',
4431
                [
4432
                    new Field(
4433
                        [
4434
                            'fieldDefIdentifier' => 'identifier4',
4435
                            'value' => 'newValue4',
4436
                            'languageCode' => 'eng-US',
4437
                        ]
4438
                    ),
4439
                    new Field(
4440
                        [
4441
                            'fieldDefIdentifier' => 'identifier1',
4442
                            'value' => 'newValue1-eng-GB',
4443
                            'languageCode' => 'eng-GB',
4444
                        ]
4445
                    ),
4446
                ],
4447
                $spiFields0,
4448
            ],
4449
            // 1. Add new language and update existing, without language set
4450
            [
4451
                'eng-US',
4452
                [
4453
                    new Field(
4454
                        [
4455
                            'fieldDefIdentifier' => 'identifier4',
4456
                            'value' => 'newValue4',
4457
                            'languageCode' => null,
4458
                        ]
4459
                    ),
4460
                    new Field(
4461
                        [
4462
                            'fieldDefIdentifier' => 'identifier1',
4463
                            'value' => 'newValue1-eng-GB',
4464
                            'languageCode' => 'eng-GB',
4465
                        ]
4466
                    ),
4467
                ],
4468
                $spiFields0,
4469
            ],
4470
            // 2. Add new language and update existing variant
4471
            [
4472
                'eng-US',
4473
                [
4474
                    new Field(
4475
                        [
4476
                            'fieldDefIdentifier' => 'identifier2',
4477
                            'value' => 'newValue2',
4478
                            'languageCode' => 'eng-US',
4479
                        ]
4480
                    ),
4481
                    new Field(
4482
                        [
4483
                            'fieldDefIdentifier' => 'identifier1',
4484
                            'value' => 'newValue1-eng-GB',
4485
                            'languageCode' => 'eng-GB',
4486
                        ]
4487
                    ),
4488
                ],
4489
                $spiFields1,
4490
            ],
4491
            // 3. Add new language and update existing variant, without language set
4492
            [
4493
                'eng-US',
4494
                [
4495
                    new Field(
4496
                        [
4497
                            'fieldDefIdentifier' => 'identifier2',
4498
                            'value' => 'newValue2',
4499
                            'languageCode' => null,
4500
                        ]
4501
                    ),
4502
                    new Field(
4503
                        [
4504
                            'fieldDefIdentifier' => 'identifier1',
4505
                            'value' => 'newValue1-eng-GB',
4506
                            'languageCode' => 'eng-GB',
4507
                        ]
4508
                    ),
4509
                ],
4510
                $spiFields1,
4511
            ],
4512
            // 4. Update with multiple languages
4513
            [
4514
                'ger-DE',
4515
                [
4516
                    new Field(
4517
                        [
4518
                            'fieldDefIdentifier' => 'identifier2',
4519
                            'value' => 'newValue2',
4520
                            'languageCode' => 'eng-US',
4521
                        ]
4522
                    ),
4523
                    new Field(
4524
                        [
4525
                            'fieldDefIdentifier' => 'identifier1',
4526
                            'value' => 'newValue1-eng-GB',
4527
                            'languageCode' => 'eng-GB',
4528
                        ]
4529
                    ),
4530
                ],
4531
                $spiFields2,
4532
            ],
4533
            // 5. Update with multiple languages without language set
4534
            [
4535
                'ger-DE',
4536
                [
4537
                    new Field(
4538
                        [
4539
                            'fieldDefIdentifier' => 'identifier2',
4540
                            'value' => 'newValue2',
4541
                            'languageCode' => 'eng-US',
4542
                        ]
4543
                    ),
4544
                    new Field(
4545
                        [
4546
                            'fieldDefIdentifier' => 'identifier1',
4547
                            'value' => 'newValue1-eng-GB',
4548
                            'languageCode' => null,
4549
                        ]
4550
                    ),
4551
                ],
4552
                $spiFields2,
4553
            ],
4554
        ];
4555
    }
4556
4557
    protected function fixturesForTestUpdateContentNonRedundantFieldSetComplex()
4558
    {
4559
        $existingFields = [
4560
            new Field(
4561
                [
4562
                    'id' => '100',
4563
                    'fieldDefIdentifier' => 'identifier1',
4564
                    'value' => 'initialValue1',
4565
                    'languageCode' => 'eng-GB',
4566
                ]
4567
            ),
4568
            new Field(
4569
                [
4570
                    'id' => '101',
4571
                    'fieldDefIdentifier' => 'identifier2',
4572
                    'value' => 'initialValue2',
4573
                    'languageCode' => 'eng-GB',
4574
                ]
4575
            ),
4576
            new Field(
4577
                [
4578
                    'id' => '102',
4579
                    'fieldDefIdentifier' => 'identifier3',
4580
                    'value' => 'initialValue3',
4581
                    'languageCode' => 'eng-GB',
4582
                ]
4583
            ),
4584
            new Field(
4585
                [
4586
                    'id' => '103',
4587
                    'fieldDefIdentifier' => 'identifier4',
4588
                    'value' => 'initialValue4',
4589
                    'languageCode' => 'eng-GB',
4590
                ]
4591
            ),
4592
        ];
4593
4594
        $fieldDefinitions = [
4595
            new FieldDefinition(
4596
                [
4597
                    'id' => 'fieldDefinitionId1',
4598
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
4599
                    'isTranslatable' => false,
4600
                    'identifier' => 'identifier1',
4601
                    'isRequired' => false,
4602
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
4603
                ]
4604
            ),
4605
            new FieldDefinition(
4606
                [
4607
                    'id' => 'fieldDefinitionId2',
4608
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
4609
                    'isTranslatable' => true,
4610
                    'identifier' => 'identifier2',
4611
                    'isRequired' => false,
4612
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
4613
                ]
4614
            ),
4615
            new FieldDefinition(
4616
                [
4617
                    'id' => 'fieldDefinitionId3',
4618
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
4619
                    'isTranslatable' => false,
4620
                    'identifier' => 'identifier3',
4621
                    'isRequired' => false,
4622
                    'defaultValue' => 'defaultValue3',
4623
                ]
4624
            ),
4625
            new FieldDefinition(
4626
                [
4627
                    'id' => 'fieldDefinitionId4',
4628
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
4629
                    'isTranslatable' => true,
4630
                    'identifier' => 'identifier4',
4631
                    'isRequired' => false,
4632
                    'defaultValue' => 'defaultValue4',
4633
                ]
4634
            ),
4635
        ];
4636
4637
        return [$existingFields, $fieldDefinitions];
4638
    }
4639
4640
    /**
4641
     * Test for the updateContent() method.
4642
     *
4643
     * Testing more complex cases.
4644
     *
4645
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
4646
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
4647
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
4648
     * @dataProvider providerForTestUpdateContentNonRedundantFieldSetComplex
4649
     */
4650
    public function testUpdateContentNonRedundantFieldSetComplex($initialLanguageCode, $structFields, $spiFields)
4651
    {
4652
        list($existingFields, $fieldDefinitions) = $this->fixturesForTestUpdateContentNonRedundantFieldSetComplex();
4653
4654
        $this->assertForTestUpdateContentNonRedundantFieldSet(
4655
            $initialLanguageCode,
4656
            $structFields,
4657
            $spiFields,
4658
            $existingFields,
4659
            $fieldDefinitions
4660
        );
4661
    }
4662
4663
    public function providerForTestUpdateContentWithInvalidLanguage()
4664
    {
4665
        return [
4666
            [
4667
                'eng-GB',
4668
                [
4669
                    new Field(
4670
                        [
4671
                            'fieldDefIdentifier' => 'identifier',
4672
                            'value' => 'newValue',
4673
                            'languageCode' => 'Klingon',
4674
                        ]
4675
                    ),
4676
                ],
4677
            ],
4678
            [
4679
                'Klingon',
4680
                [
4681
                    new Field(
4682
                        [
4683
                            'fieldDefIdentifier' => 'identifier',
4684
                            'value' => 'newValue',
4685
                            'languageCode' => 'eng-GB',
4686
                        ]
4687
                    ),
4688
                ],
4689
            ],
4690
        ];
4691
    }
4692
4693
    /**
4694
     * Test for the updateContent() method.
4695
     *
4696
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
4697
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
4698
     * @dataProvider providerForTestUpdateContentWithInvalidLanguage
4699
     */
4700
    public function testUpdateContentWithInvalidLanguage($initialLanguageCode, $structFields)
4701
    {
4702
        $this->expectException(APINotFoundException::class);
4703
        $this->expectExceptionMessage('Could not find \'Language\' with identifier \'Klingon\'');
4704
4705
        $permissionResolverMock = $this->getPermissionResolverMock();
4706
        $mockedService = $this->getPartlyMockedContentService(['loadContent', 'internalLoadContentById']);
4707
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
4708
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
4709
        $versionInfo = new VersionInfo(
4710
            [
4711
                'contentInfo' => new ContentInfo(
4712
                    [
4713
                        'id' => 42,
4714
                        'contentTypeId' => 24,
4715
                        'mainLanguageCode' => 'eng-GB',
4716
                    ]
4717
                ),
4718
                'versionNo' => 7,
4719
                'languageCodes' => ['eng-GB'],
4720
                'status' => VersionInfo::STATUS_DRAFT,
4721
            ]
4722
        );
4723
        $content = new Content(
4724
            [
4725
                'versionInfo' => $versionInfo,
4726
                'internalFields' => [],
4727
            ]
4728
        );
4729
4730
        $languageHandlerMock->expects($this->any())
4731
            ->method('loadByLanguageCode')
4732
            ->with($this->isType('string'))
4733
            ->will(
4734
                $this->returnCallback(
4735
                    function ($languageCode) {
4736
                        if ($languageCode === 'Klingon') {
4737
                            throw new NotFoundException('Language', 'Klingon');
4738
                        }
4739
4740
                        return new Language(['id' => 4242]);
4741
                    }
4742
                )
4743
            );
4744
4745
        $mockedService
4746
            ->method('loadContent')
4747
            ->with(
4748
                $this->equalTo(42),
4749
                $this->equalTo(null),
4750
                $this->equalTo(7)
4751
            )->will(
4752
                $this->returnValue($content)
4753
            );
4754
4755
        $mockedService
4756
            ->method('internalLoadContentById')
4757
            ->will(
4758
                $this->returnValue($content)
4759
            );
4760
4761
        $permissionResolverMock
4762
            ->method('canUser')
4763
            ->with(
4764
                $this->equalTo('content'),
4765
                $this->equalTo('edit'),
4766
                $this->equalTo($content),
4767
                $this->isType('array')
4768
            )->will($this->returnValue(true));
4769
4770
        $contentUpdateStruct = new ContentUpdateStruct(
4771
            [
4772
                'fields' => $structFields,
4773
                'initialLanguageCode' => $initialLanguageCode,
4774
            ]
4775
        );
4776
4777
        $mockedService->updateContent($content->versionInfo, $contentUpdateStruct);
4778
    }
4779
4780
    protected function assertForUpdateContentContentValidationException(
4781
        $initialLanguageCode,
4782
        $structFields,
4783
        $fieldDefinitions = []
4784
    ) {
4785
        $repositoryMock = $this->getRepositoryMock();
4786
        $permissionResolverMock = $this->getPermissionResolverMock();
4787
        $mockedService = $this->getPartlyMockedContentService(['internalLoadContentById', 'loadContent']);
4788
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
4789
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
4790
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
4791
        $versionInfo = new VersionInfo(
4792
            [
4793
                'contentInfo' => new ContentInfo(
4794
                    [
4795
                        'id' => 42,
4796
                        'contentTypeId' => 24,
4797
                        'mainLanguageCode' => 'eng-GB',
4798
                    ]
4799
                ),
4800
                'versionNo' => 7,
4801
                'languageCodes' => ['eng-GB'],
4802
                'status' => VersionInfo::STATUS_DRAFT,
4803
            ]
4804
        );
4805
        $content = new Content(
4806
            [
4807
                'versionInfo' => $versionInfo,
4808
                'internalFields' => [],
4809
            ]
4810
        );
4811
        $contentType = new ContentType([
4812
            'fieldDefinitions' => new FieldDefinitionCollection($fieldDefinitions),
4813
        ]);
4814
4815
        $languageHandlerMock->expects($this->any())
4816
            ->method('loadByLanguageCode')
4817
            ->with($this->isType('string'))
4818
            ->will(
4819
                $this->returnCallback(
4820
                    function ($languageCode) {
4821
                        if ($languageCode === 'Klingon') {
4822
                            throw new NotFoundException('Language', 'Klingon');
4823
                        }
4824
4825
                        return new Language(['id' => 4242]);
4826
                    }
4827
                )
4828
            );
4829
4830
        $mockedService
4831
            ->method('loadContent')
4832
            ->with(
4833
                $this->equalTo(42),
4834
                $this->equalTo(null),
4835
                $this->equalTo(7)
4836
            )->will(
4837
                $this->returnValue($content)
4838
            );
4839
4840
        $mockedService
4841
            ->method('internalLoadContentById')
4842
            ->will(
4843
                $this->returnValue($content)
4844
            );
4845
4846
        $permissionResolverMock->expects($this->once())
4847
            ->method('canUser')
4848
            ->with(
4849
                $this->equalTo('content'),
4850
                $this->equalTo('edit'),
4851
                $this->equalTo($content),
4852
                $this->isType('array')
4853
            )->will($this->returnValue(true));
4854
4855
        $contentTypeServiceMock->expects($this->once())
4856
            ->method('loadContentType')
4857
            ->with($this->equalTo(24))
4858
            ->will($this->returnValue($contentType));
4859
4860
        $repositoryMock->expects($this->once())
4861
            ->method('getContentTypeService')
4862
            ->will($this->returnValue($contentTypeServiceMock));
4863
4864
        $contentUpdateStruct = new ContentUpdateStruct(
4865
            [
4866
                'fields' => $structFields,
4867
                'initialLanguageCode' => $initialLanguageCode,
4868
            ]
4869
        );
4870
4871
        $mockedService->updateContent($content->versionInfo, $contentUpdateStruct);
4872
    }
4873
4874
    public function providerForTestUpdateContentThrowsContentValidationExceptionFieldDefinition()
4875
    {
4876
        return [
4877
            [
4878
                'eng-GB',
4879
                [
4880
                    new Field(
4881
                        [
4882
                            'fieldDefIdentifier' => 'identifier',
4883
                            'value' => 'newValue',
4884
                            'languageCode' => 'eng-GB',
4885
                        ]
4886
                    ),
4887
                ],
4888
            ],
4889
        ];
4890
    }
4891
4892
    /**
4893
     * Test for the updateContent() method.
4894
     *
4895
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
4896
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
4897
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
4898
     * @dataProvider providerForTestUpdateContentThrowsContentValidationExceptionFieldDefinition
4899
     */
4900
    public function testUpdateContentThrowsContentValidationExceptionFieldDefinition($initialLanguageCode, $structFields)
4901
    {
4902
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\ContentValidationException::class);
4903
        $this->expectExceptionMessage('Field definition \'identifier\' does not exist in given Content Type');
4904
4905
        $this->assertForUpdateContentContentValidationException(
4906
            $initialLanguageCode,
4907
            $structFields,
4908
            []
4909
        );
4910
    }
4911
4912
    public function providerForTestUpdateContentThrowsContentValidationExceptionTranslation()
4913
    {
4914
        return [
4915
            [
4916
                'eng-US',
4917
                [
4918
                    new Field(
4919
                        [
4920
                            'fieldDefIdentifier' => 'identifier',
4921
                            'value' => 'newValue',
4922
                            'languageCode' => 'eng-US',
4923
                        ]
4924
                    ),
4925
                ],
4926
            ],
4927
        ];
4928
    }
4929
4930
    /**
4931
     * Test for the updateContent() method.
4932
     *
4933
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
4934
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
4935
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
4936
     * @dataProvider providerForTestUpdateContentThrowsContentValidationExceptionTranslation
4937
     */
4938
    public function testUpdateContentThrowsContentValidationExceptionTranslation($initialLanguageCode, $structFields)
4939
    {
4940
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\ContentValidationException::class);
4941
        $this->expectExceptionMessage('You cannot set a value for the non-translatable Field definition \'identifier\' in language \'eng-US\'');
4942
4943
        $fieldDefinitions = [
4944
            new FieldDefinition(
4945
                [
4946
                    'id' => 'fieldDefinitionId1',
4947
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
4948
                    'isTranslatable' => false,
4949
                    'identifier' => 'identifier',
4950
                    'isRequired' => false,
4951
                    'defaultValue' => self::EMPTY_FIELD_VALUE,
4952
                ]
4953
            ),
4954
        ];
4955
4956
        $this->assertForUpdateContentContentValidationException(
4957
            $initialLanguageCode,
4958
            $structFields,
4959
            $fieldDefinitions
4960
        );
4961
    }
4962
4963
    public function assertForTestUpdateContentRequiredField(
4964
        $initialLanguageCode,
4965
        $structFields,
4966
        $existingFields,
4967
        $fieldDefinitions
4968
    ) {
4969
        $repositoryMock = $this->getRepositoryMock();
4970
        $permissionResolver = $this->getPermissionResolverMock();
4971
        $mockedService = $this->getPartlyMockedContentService(['internalLoadContentById', 'loadContent']);
4972
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
4973
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
4974
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
4975
        $fieldTypeMock = $this->createMock(SPIFieldType::class);
4976
        $existingLanguageCodes = array_map(
4977
            function (Field $field) {
4978
                return $field->languageCode;
4979
            },
4980
            $existingFields
4981
        );
4982
        $versionInfo = new VersionInfo(
4983
            [
4984
                'contentInfo' => new ContentInfo(
4985
                    [
4986
                        'id' => 42,
4987
                        'contentTypeId' => 24,
4988
                        'mainLanguageCode' => 'eng-GB',
4989
                    ]
4990
                ),
4991
                'versionNo' => 7,
4992
                'languageCodes' => $existingLanguageCodes,
4993
                'status' => VersionInfo::STATUS_DRAFT,
4994
            ]
4995
        );
4996
        $content = new Content(
4997
            [
4998
                'versionInfo' => $versionInfo,
4999
                'internalFields' => $existingFields,
5000
            ]
5001
        );
5002
        $contentType = new ContentType([
5003
            'fieldDefinitions' => new FieldDefinitionCollection($fieldDefinitions),
5004
        ]);
5005
5006
        $languageHandlerMock->expects($this->any())
5007
            ->method('loadByLanguageCode')
5008
            ->with($this->isType('string'))
5009
            ->will(
5010
                $this->returnCallback(
5011
                    function () {
5012
                        return new Language(['id' => 4242]);
5013
                    }
5014
                )
5015
            );
5016
5017
        $mockedService
5018
            ->method('loadContent')
5019
            ->with(
5020
                $this->equalTo(42),
5021
                $this->equalTo(null),
5022
                $this->equalTo(7)
5023
            )->will(
5024
                $this->returnValue($content)
5025
            );
5026
5027
        $mockedService
5028
            ->method('internalLoadContentById')
5029
            ->will(
5030
                $this->returnValue($content)
5031
            );
5032
5033
        $permissionResolver->expects($this->once())
5034
            ->method('canUser')
5035
            ->with(
5036
                $this->equalTo('content'),
5037
                $this->equalTo('edit'),
5038
                $this->equalTo($content),
5039
                $this->isType('array')
5040
            )->will($this->returnValue(true));
5041
5042
        $contentTypeServiceMock->expects($this->once())
5043
            ->method('loadContentType')
5044
            ->with($this->equalTo(24))
5045
            ->will($this->returnValue($contentType));
5046
5047
        $repositoryMock->expects($this->once())
5048
            ->method('getContentTypeService')
5049
            ->will($this->returnValue($contentTypeServiceMock));
5050
5051
        $fieldTypeMock->expects($this->any())
5052
            ->method('acceptValue')
5053
            ->will(
5054
                $this->returnCallback(
5055
                    function ($valueString) {
5056
                        return new ValueStub($valueString);
5057
                    }
5058
                )
5059
            );
5060
5061
        $emptyValue = self::EMPTY_FIELD_VALUE;
5062
        $fieldTypeMock->expects($this->any())
5063
            ->method('isEmptyValue')
5064
            ->will(
5065
                $this->returnCallback(
5066
                    function (ValueStub $value) use ($emptyValue) {
5067
                        return $emptyValue === (string)$value;
5068
                    }
5069
                )
5070
            );
5071
5072
        $fieldTypeMock->expects($this->any())
5073
            ->method('validate')
5074
            ->with(
5075
                $this->isInstanceOf(APIFieldDefinition::class),
5076
                $this->isInstanceOf(Value::class)
5077
            );
5078
5079
        $this->getFieldTypeRegistryMock()->expects($this->any())
5080
            ->method('getFieldType')
5081
            ->will($this->returnValue($fieldTypeMock));
5082
5083
        $contentUpdateStruct = new ContentUpdateStruct(
5084
            [
5085
                'fields' => $structFields,
5086
                'initialLanguageCode' => $initialLanguageCode,
5087
            ]
5088
        );
5089
5090
        return [$content->versionInfo, $contentUpdateStruct];
5091
    }
5092
5093
    public function providerForTestUpdateContentRequiredField()
5094
    {
5095
        return [
5096
            [
5097
                'eng-US',
5098
                [
5099
                    new Field(
5100
                        [
5101
                            'fieldDefIdentifier' => 'identifier',
5102
                            'value' => self::EMPTY_FIELD_VALUE,
5103
                            'languageCode' => null,
5104
                        ]
5105
                    ),
5106
                ],
5107
                'identifier',
5108
                'eng-US',
5109
            ],
5110
        ];
5111
    }
5112
5113
    /**
5114
     * Test for the updateContent() method.
5115
     *
5116
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
5117
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
5118
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
5119
     * @dataProvider providerForTestUpdateContentRequiredField
5120
     */
5121
    public function testUpdateContentRequiredField(
5122
        $initialLanguageCode,
5123
        $structFields,
5124
        $identifier,
5125
        $languageCode
5126
    ) {
5127
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException::class);
5128
5129
        $existingFields = [
5130
            new Field(
5131
                [
5132
                    'id' => '100',
5133
                    'fieldDefIdentifier' => 'identifier',
5134
                    'value' => 'initialValue',
5135
                    'languageCode' => 'eng-GB',
5136
                ]
5137
            ),
5138
        ];
5139
        $fieldDefinitions = [
5140
            new FieldDefinition(
5141
                [
5142
                    'id' => 'fieldDefinitionId',
5143
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
5144
                    'isTranslatable' => true,
5145
                    'identifier' => 'identifier',
5146
                    'isRequired' => true,
5147
                    'defaultValue' => 'defaultValue',
5148
                ]
5149
            ),
5150
        ];
5151
        list($versionInfo, $contentUpdateStruct) =
5152
            $this->assertForTestUpdateContentRequiredField(
5153
                $initialLanguageCode,
5154
                $structFields,
5155
                $existingFields,
5156
                $fieldDefinitions
5157
            );
5158
5159
        try {
5160
            $this->partlyMockedContentService->updateContent($versionInfo, $contentUpdateStruct);
5161
        } catch (ContentValidationException $e) {
5162
            $this->assertEquals(
5163
                "Value for required field definition '{$identifier}' with language '{$languageCode}' is empty",
5164
                $e->getMessage()
5165
            );
5166
5167
            throw $e;
5168
        }
5169
    }
5170
5171
    public function assertForTestUpdateContentThrowsContentFieldValidationException(
5172
        $initialLanguageCode,
5173
        $structFields,
5174
        $existingFields,
5175
        $fieldDefinitions
5176
    ) {
5177
        $repositoryMock = $this->getRepositoryMock();
5178
        $permissionResolverMock = $this->getPermissionResolverMock();
5179
        $mockedService = $this->getPartlyMockedContentService(['internalLoadContentById', 'loadContent']);
5180
        /** @var \PHPUnit\Framework\MockObject\MockObject $languageHandlerMock */
5181
        $languageHandlerMock = $this->getPersistenceMock()->contentLanguageHandler();
5182
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
5183
        $fieldTypeMock = $this->createMock(SPIFieldType::class);
5184
        $existingLanguageCodes = array_map(
5185
            function (Field $field) {
5186
                return $field->languageCode;
5187
            },
5188
            $existingFields
5189
        );
5190
        $languageCodes = $this->determineLanguageCodesForUpdate(
5191
            $initialLanguageCode,
5192
            $structFields,
5193
            $existingLanguageCodes
5194
        );
5195
        $versionInfo = new VersionInfo(
5196
            [
5197
                'contentInfo' => new ContentInfo(
5198
                    [
5199
                        'id' => 42,
5200
                        'contentTypeId' => 24,
5201
                        'mainLanguageCode' => 'eng-GB',
5202
                    ]
5203
                ),
5204
                'versionNo' => 7,
5205
                'languageCodes' => $existingLanguageCodes,
5206
                'status' => VersionInfo::STATUS_DRAFT,
5207
            ]
5208
        );
5209
        $content = new Content(
5210
            [
5211
                'versionInfo' => $versionInfo,
5212
                'internalFields' => $existingFields,
5213
            ]
5214
        );
5215
        $contentType = new ContentType([
5216
            'fieldDefinitions' => new FieldDefinitionCollection($fieldDefinitions),
5217
        ]);
5218
5219
        $languageHandlerMock->expects($this->any())
5220
            ->method('loadByLanguageCode')
5221
            ->with($this->isType('string'))
5222
            ->will(
5223
                $this->returnCallback(
5224
                    function () {
5225
                        return new Language(['id' => 4242]);
5226
                    }
5227
                )
5228
            );
5229
5230
        $mockedService
5231
            ->method('internalLoadContentById')
5232
            ->will(
5233
                $this->returnValue($content)
5234
            );
5235
5236
        $mockedService
5237
            ->method('loadContent')
5238
            ->with(
5239
                $this->equalTo(42),
5240
                $this->equalTo(null),
5241
                $this->equalTo(7)
5242
            )
5243
            ->will(
5244
                $this->returnValue($content)
5245
            );
5246
5247
        $permissionResolverMock
5248
            ->method('canUser')
5249
            ->will($this->returnValue(true));
5250
5251
        $contentTypeServiceMock->expects($this->once())
5252
            ->method('loadContentType')
5253
            ->with($this->equalTo(24))
5254
            ->will($this->returnValue($contentType));
5255
5256
        $repositoryMock->expects($this->once())
5257
            ->method('getContentTypeService')
5258
            ->will($this->returnValue($contentTypeServiceMock));
5259
5260
        $fieldValues = $this->determineValuesForUpdate(
5261
            $initialLanguageCode,
5262
            $structFields,
5263
            $content,
5264
            $fieldDefinitions,
5265
            $languageCodes
5266
        );
5267
        $allFieldErrors = [];
5268
        $emptyValue = self::EMPTY_FIELD_VALUE;
5269
5270
        $fieldTypeMock->expects($this->exactly(count($fieldValues) * count($languageCodes)))
5271
            ->method('acceptValue')
5272
            ->will(
5273
                $this->returnCallback(
5274
                    function ($valueString) {
5275
                        return new ValueStub($valueString);
5276
                    }
5277
                )
5278
            );
5279
5280
        $fieldTypeMock->expects($this->exactly(count($fieldValues) * count($languageCodes)))
5281
            ->method('isEmptyValue')
5282
            ->will(
5283
                $this->returnCallback(
5284
                    function (ValueStub $value) use ($emptyValue) {
5285
                        return $emptyValue === (string)$value;
5286
                    }
5287
                )
5288
            );
5289
5290
        $fieldTypeMock
5291
            ->expects($this->any())
5292
            ->method('validate')
5293
            ->willReturnArgument(1);
5294
5295
        $this->getFieldTypeRegistryMock()->expects($this->any())
5296
            ->method('getFieldType')
5297
            ->will($this->returnValue($fieldTypeMock));
5298
5299
        $contentUpdateStruct = new ContentUpdateStruct(
5300
            [
5301
                'fields' => $structFields,
5302
                'initialLanguageCode' => $initialLanguageCode,
5303
            ]
5304
        );
5305
5306
        return [$content->versionInfo, $contentUpdateStruct, $allFieldErrors];
5307
    }
5308
5309
    public function providerForTestUpdateContentThrowsContentFieldValidationException()
5310
    {
5311
        $allFieldErrors = [
5312
            [
5313
                'fieldDefinitionId1' => [
5314
                    'eng-GB' => 'newValue1-eng-GB',
5315
                    'eng-US' => 'newValue1-eng-GB',
5316
                ],
5317
                'fieldDefinitionId2' => [
5318
                    'eng-GB' => 'initialValue2',
5319
                ],
5320
                'fieldDefinitionId3' => [
5321
                    'eng-GB' => 'initialValue3',
5322
                    'eng-US' => 'initialValue3',
5323
                ],
5324
                'fieldDefinitionId4' => [
5325
                    'eng-GB' => 'initialValue4',
5326
                    'eng-US' => 'newValue4',
5327
                ],
5328
            ],
5329
            [
5330
                'fieldDefinitionId1' => [
5331
                    'eng-GB' => 'newValue1-eng-GB',
5332
                    'eng-US' => 'newValue1-eng-GB',
5333
                ],
5334
                'fieldDefinitionId2' => [
5335
                    'eng-GB' => 'initialValue2',
5336
                ],
5337
                'fieldDefinitionId3' => [
5338
                    'eng-GB' => 'initialValue3',
5339
                    'eng-US' => 'initialValue3',
5340
                ],
5341
                'fieldDefinitionId4' => [
5342
                    'eng-GB' => 'initialValue4',
5343
                    'eng-US' => 'newValue4',
5344
                ],
5345
            ],
5346
            [
5347
                'fieldDefinitionId1' => [
5348
                    'eng-GB' => 'newValue1-eng-GB',
5349
                    'eng-US' => 'newValue1-eng-GB',
5350
                ],
5351
                'fieldDefinitionId2' => [
5352
                    'eng-GB' => 'initialValue2',
5353
                    'eng-US' => 'newValue2',
5354
                ],
5355
                'fieldDefinitionId3' => [
5356
                    'eng-GB' => 'initialValue3',
5357
                    'eng-US' => 'initialValue3',
5358
                ],
5359
                'fieldDefinitionId4' => [
5360
                    'eng-GB' => 'initialValue4',
5361
                    'eng-US' => 'defaultValue4',
5362
                ],
5363
            ],
5364
            [
5365
                'fieldDefinitionId1' => [
5366
                    'eng-GB' => 'newValue1-eng-GB',
5367
                    'eng-US' => 'newValue1-eng-GB',
5368
                ],
5369
                'fieldDefinitionId2' => [
5370
                    'eng-GB' => 'initialValue2',
5371
                    'eng-US' => 'newValue2',
5372
                ],
5373
                'fieldDefinitionId3' => [
5374
                    'eng-GB' => 'initialValue3',
5375
                    'eng-US' => 'initialValue3',
5376
                ],
5377
                'fieldDefinitionId4' => [
5378
                    'eng-GB' => 'initialValue4',
5379
                    'eng-US' => 'defaultValue4',
5380
                ],
5381
            ],
5382
            [
5383
                'fieldDefinitionId1' => [
5384
                    'eng-GB' => 'newValue1-eng-GB',
5385
                    'ger-DE' => 'newValue1-eng-GB',
5386
                    'eng-US' => 'newValue1-eng-GB',
5387
                ],
5388
                'fieldDefinitionId2' => [
5389
                    'eng-GB' => 'initialValue2',
5390
                    'eng-US' => 'newValue2',
5391
                ],
5392
                'fieldDefinitionId3' => [
5393
                    'eng-GB' => 'initialValue3',
5394
                    'ger-DE' => 'initialValue3',
5395
                    'eng-US' => 'initialValue3',
5396
                ],
5397
                'fieldDefinitionId4' => [
5398
                    'eng-GB' => 'initialValue4',
5399
                    'eng-US' => 'defaultValue4',
5400
                    'ger-DE' => 'defaultValue4',
5401
                ],
5402
            ],
5403
            [
5404
                'fieldDefinitionId1' => [
5405
                    'eng-US' => 'newValue1-eng-GB',
5406
                    'ger-DE' => 'newValue1-eng-GB',
5407
                ],
5408
                'fieldDefinitionId2' => [
5409
                    'eng-US' => 'newValue2',
5410
                ],
5411
                'fieldDefinitionId3' => [
5412
                    'ger-DE' => 'initialValue3',
5413
                    'eng-US' => 'initialValue3',
5414
                ],
5415
                'fieldDefinitionId4' => [
5416
                    'ger-DE' => 'defaultValue4',
5417
                    'eng-US' => 'defaultValue4',
5418
                ],
5419
            ],
5420
        ];
5421
5422
        $data = $this->providerForTestUpdateContentNonRedundantFieldSetComplex();
5423
        $count = count($data);
5424
        for ($i = 0; $i < $count; ++$i) {
5425
            $data[$i][] = $allFieldErrors[$i];
5426
        }
5427
5428
        return $data;
5429
    }
5430
5431
    /**
5432
     * Test for the updateContent() method.
5433
     *
5434
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
5435
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
5436
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
5437
     * @dataProvider providerForTestUpdateContentThrowsContentFieldValidationException
5438
     */
5439
    public function testUpdateContentThrowsContentFieldValidationException($initialLanguageCode, $structFields, $spiField, $allFieldErrors)
5440
    {
5441
        $this->expectException(\eZ\Publish\API\Repository\Exceptions\ContentFieldValidationException::class);
5442
        $this->expectExceptionMessage('Content Fields did not validate');
5443
5444
        list($existingFields, $fieldDefinitions) = $this->fixturesForTestUpdateContentNonRedundantFieldSetComplex();
5445
        list($versionInfo, $contentUpdateStruct) =
5446
            $this->assertForTestUpdateContentThrowsContentFieldValidationException(
5447
                $initialLanguageCode,
5448
                $structFields,
5449
                $existingFields,
5450
                $fieldDefinitions
5451
            );
5452
5453
        try {
5454
            $this->partlyMockedContentService->updateContent($versionInfo, $contentUpdateStruct);
5455
        } catch (ContentFieldValidationException $e) {
5456
            $this->assertEquals($allFieldErrors, $e->getFieldErrors());
5457
            throw $e;
5458
        }
5459
    }
5460
5461
    /**
5462
     * Test for the updateContent() method.
5463
     *
5464
     * @covers \eZ\Publish\Core\Repository\ContentService::getLanguageCodesForUpdate
5465
     * @covers \eZ\Publish\Core\Repository\ContentService::mapFieldsForUpdate
5466
     * @covers \eZ\Publish\Core\Repository\ContentService::updateContent
5467
     */
5468
    public function testUpdateContentTransactionRollback()
5469
    {
5470
        $this->expectException(\Exception::class);
5471
        $this->expectExceptionMessage('Store failed');
5472
5473
        $existingFields = [
5474
            new Field(
5475
                [
5476
                    'id' => '100',
5477
                    'fieldDefIdentifier' => 'identifier',
5478
                    'value' => 'initialValue',
5479
                    'languageCode' => 'eng-GB',
5480
                ]
5481
            ),
5482
        ];
5483
5484
        $fieldDefinitions = [
5485
            new FieldDefinition(
5486
                [
5487
                    'id' => 'fieldDefinitionId',
5488
                    'fieldTypeIdentifier' => 'fieldTypeIdentifier',
5489
                    'isTranslatable' => false,
5490
                    'identifier' => 'identifier',
5491
                    'isRequired' => false,
5492
                    'defaultValue' => 'defaultValue',
5493
                ]
5494
            ),
5495
        ];
5496
5497
        // Setup a simple case that will pass
5498
        list($versionInfo, $contentUpdateStruct) = $this->assertForTestUpdateContentNonRedundantFieldSet(
5499
            'eng-US',
5500
            [],
5501
            [],
5502
            $existingFields,
5503
            $fieldDefinitions,
5504
            // Do not execute test
5505
            false
5506
        );
5507
5508
        $repositoryMock = $this->getRepositoryMock();
5509
        $repositoryMock->expects($this->never())->method('commit');
5510
        $repositoryMock->expects($this->once())->method('rollback');
5511
5512
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandlerMock */
5513
        $contentHandlerMock = $this->getPersistenceMock()->contentHandler();
5514
        $contentHandlerMock->expects($this->once())
5515
            ->method('updateContent')
5516
            ->with(
5517
                $this->anything(),
5518
                $this->anything(),
5519
                $this->anything()
5520
            )->will($this->throwException(new \Exception('Store failed')));
5521
5522
        // Execute
5523
        $this->partlyMockedContentService->updateContent($versionInfo, $contentUpdateStruct);
5524
    }
5525
5526
    /**
5527
     * Test for the copyContent() method.
5528
     *
5529
     * @covers \eZ\Publish\Core\Repository\ContentService::copyContent
5530
     */
5531
    public function testCopyContentThrowsUnauthorizedException()
5532
    {
5533
        $this->expectException(\eZ\Publish\Core\Base\Exceptions\UnauthorizedException::class);
5534
5535
        $repository = $this->getRepositoryMock();
5536
        $contentService = $this->getPartlyMockedContentService(['internalLoadContentInfo']);
5537
        $contentInfo = $this->createMock(APIContentInfo::class);
5538
        $locationCreateStruct = new LocationCreateStruct();
5539
        $locationCreateStruct->parentLocationId = 1;
5540
        $location = new Location(['id' => $locationCreateStruct->parentLocationId]);
5541
        $locationServiceMock = $this->getLocationServiceMock();
5542
        $permissionResolver = $this->getPermissionResolverMock();
5543
5544
        $repository->expects($this->once())
5545
            ->method('getLocationService')
5546
            ->will($this->returnValue($locationServiceMock));
5547
5548
        $locationServiceMock->expects($this->once())
5549
            ->method('loadLocation')
5550
            ->with(
5551
                $locationCreateStruct->parentLocationId
5552
            )
5553
            ->will($this->returnValue($location));
5554
5555
        $contentInfo->expects($this->any())
5556
            ->method('__get')
5557
            ->with('sectionId')
5558
            ->will($this->returnValue(42));
5559
5560
        $permissionResolver
5561
            ->method('canUser')
5562
            ->with(
5563
                'content',
5564
                'create',
5565
                $contentInfo,
5566
                [$location]
5567
            )
5568
            ->will($this->returnValue(false));
5569
5570
        /* @var \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfo */
5571
        $contentService->copyContent($contentInfo, $locationCreateStruct);
5572
    }
5573
5574
    /**
5575
     * Test for the copyContent() method.
5576
     *
5577
     * @covers \eZ\Publish\Core\Repository\ContentService::copyContent
5578
     * @covers \eZ\Publish\Core\Repository\ContentService::getDefaultObjectStates
5579
     * @covers \eZ\Publish\Core\Repository\ContentService::internalPublishVersion
5580
     */
5581
    public function testCopyContent()
5582
    {
5583
        $repositoryMock = $this->getRepositoryMock();
5584
        $contentService = $this->getPartlyMockedContentService([
5585
            'internalLoadContentInfo',
5586
            'internalLoadContentById',
5587
            'getUnixTimestamp',
5588
        ]);
5589
        $locationServiceMock = $this->getLocationServiceMock();
5590
        $contentInfoMock = $this->createMock(APIContentInfo::class);
5591
        $locationCreateStruct = new LocationCreateStruct();
5592
        $locationCreateStruct->parentLocationId = 2;
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
        $locationCreateStruct->parentLocationId = 2;
5724
        $location = new Location(['id' => $locationCreateStruct->parentLocationId]);
5725
        $user = $this->getStubbedUser(14);
5726
5727
        $permissionResolverMock = $this->getPermissionResolverMock();
5728
5729
        $permissionResolverMock
5730
            ->method('getCurrentUserReference')
5731
            ->willReturn($user);
5732
5733
        $repositoryMock
5734
            ->method('getPermissionResolver')
5735
            ->willReturn($permissionResolverMock);
5736
5737
        $repositoryMock->expects($this->exactly(3))
5738
            ->method('getLocationService')
5739
            ->will($this->returnValue($locationServiceMock));
5740
5741
        $locationServiceMock->expects($this->once())
5742
            ->method('loadLocation')
5743
            ->with($locationCreateStruct->parentLocationId)
5744
            ->will($this->returnValue($location));
5745
5746
        $contentInfoMock->expects($this->any())
5747
            ->method('__get')
5748
            ->will(
5749
                $this->returnValueMap([
5750
                    ['isHidden', true],
5751
                    ['id', 42],
5752
                ])
5753
            );
5754
5755
        $versionInfoMock = $this->createMock(APIVersionInfo::class);
5756
5757
        $versionInfoMock->expects($this->any())
5758
            ->method('__get')
5759
            ->will(
5760
                $this->returnValueMap(
5761
                    [
5762
                        ['versionNo', 123],
5763
                    ]
5764
                )
5765
            );
5766
        $versionInfoMock->expects($this->once())
5767
            ->method('isDraft')
5768
            ->willReturn(true);
5769
        $versionInfoMock->expects($this->once())
5770
            ->method('getContentInfo')
5771
            ->will($this->returnValue($contentInfoMock));
5772
5773
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandlerMock */
5774
        $contentHandlerMock = $this->getPersistenceMock()->contentHandler();
5775
        $domainMapperMock = $this->getContentDomainMapperMock();
5776
5777
        $repositoryMock->expects($this->once())->method('beginTransaction');
5778
        $repositoryMock->expects($this->once())->method('commit');
5779
5780
        $permissionResolverMock
5781
            ->method('canUser')
5782
            ->willReturnMap(
5783
                [
5784
                    ['content', 'create', $contentInfoMock, [$location], true],
5785
                    ['content', 'manage_locations', $contentInfoMock, [$location], true],
5786
                ]
5787
            );
5788
5789
        $spiContentInfo = new SPIContentInfo(['id' => 42]);
5790
        $spiVersionInfo = new SPIVersionInfo(
5791
            [
5792
                'contentInfo' => $spiContentInfo,
5793
                'creationDate' => 123456,
5794
            ]
5795
        );
5796
        $spiContent = new SPIContent(['versionInfo' => $spiVersionInfo]);
5797
        $contentHandlerMock->expects($this->once())
5798
            ->method('copy')
5799
            ->with(42, 123)
5800
            ->will($this->returnValue($spiContent));
5801
5802
        $this->mockGetDefaultObjectStates();
5803
        $this->mockSetDefaultObjectStates();
5804
5805
        $domainMapperMock->expects($this->once())
5806
            ->method('buildVersionInfoDomainObject')
5807
            ->with($spiVersionInfo)
5808
            ->will($this->returnValue($versionInfoMock));
5809
5810
        /* @var \eZ\Publish\API\Repository\Values\Content\VersionInfo $versionInfoMock */
5811
        $content = $this->mockPublishVersion(123456, 126666, true);
5812
        $locationServiceMock->expects($this->once())
5813
            ->method('createLocation')
5814
            ->with(
5815
                $content->getVersionInfo()->getContentInfo(),
5816
                $locationCreateStruct
5817
            );
5818
5819
        $contentService->expects($this->once())
5820
            ->method('internalLoadContentById')
5821
            ->with(
5822
                $content->id
5823
            )
5824
            ->will($this->returnValue($content));
5825
5826
        $contentService->expects($this->once())
5827
            ->method('getUnixTimestamp')
5828
            ->will($this->returnValue(126666));
5829
5830
        /* @var \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfoMock */
5831
        $contentService->copyContent($contentInfoMock, $locationCreateStruct, $versionInfoMock);
5832
    }
5833
5834
    /**
5835
     * Test for the copyContent() method.
5836
     *
5837
     * @covers \eZ\Publish\Core\Repository\ContentService::copyContent
5838
     * @covers \eZ\Publish\Core\Repository\ContentService::getDefaultObjectStates
5839
     * @covers \eZ\Publish\Core\Repository\ContentService::internalPublishVersion
5840
     */
5841
    public function testCopyContentWithRollback()
5842
    {
5843
        $this->expectException(\Exception::class);
5844
        $this->expectExceptionMessage('Handler threw an exception');
5845
5846
        $repositoryMock = $this->getRepositoryMock();
5847
        $contentService = $this->getPartlyMockedContentService();
5848
        /** @var \PHPUnit\Framework\MockObject\MockObject $contentHandlerMock */
5849
        $contentHandlerMock = $this->getPersistenceMock()->contentHandler();
5850
        $locationCreateStruct = new LocationCreateStruct();
5851
        $locationCreateStruct->parentLocationId = 2;
5852
        $location = new Location(['id' => $locationCreateStruct->parentLocationId]);
5853
        $locationServiceMock = $this->getLocationServiceMock();
5854
        $user = $this->getStubbedUser(14);
5855
5856
        $permissionResolverMock = $this->getPermissionResolverMock();
5857
5858
        $permissionResolverMock
5859
            ->method('getCurrentUserReference')
5860
            ->willReturn($user);
5861
5862
        $repositoryMock
5863
            ->method('getPermissionResolver')
5864
            ->willReturn($permissionResolverMock);
5865
5866
        $repositoryMock->expects($this->once())
5867
            ->method('getLocationService')
5868
            ->will($this->returnValue($locationServiceMock));
5869
5870
        $locationServiceMock->expects($this->once())
5871
            ->method('loadLocation')
5872
            ->with($locationCreateStruct->parentLocationId)
5873
            ->will($this->returnValue($location));
5874
5875
        $contentInfoMock = $this->createMock(APIContentInfo::class);
5876
        $contentInfoMock->expects($this->any())
5877
            ->method('__get')
5878
            ->with('id')
5879
            ->will($this->returnValue(42));
5880
5881
        $this->mockGetDefaultObjectStates();
5882
5883
        $repositoryMock->expects($this->once())->method('beginTransaction');
5884
        $repositoryMock->expects($this->once())->method('rollback');
5885
5886
        $permissionResolverMock
5887
            ->method('canUser')
5888
            ->willReturnMap(
5889
                [
5890
                    ['content', 'create', $contentInfoMock, [$location], true],
5891
                    ['content', 'manage_locations', $contentInfoMock, [$location], true],
5892
                ]
5893
            );
5894
5895
        $contentHandlerMock->expects($this->once())
5896
            ->method('copy')
5897
            ->with(42, null)
5898
            ->will($this->throwException(new Exception('Handler threw an exception')));
5899
5900
        /* @var \eZ\Publish\API\Repository\Values\Content\ContentInfo $contentInfoMock */
5901
        $contentService->copyContent($contentInfoMock, $locationCreateStruct, null);
5902
    }
5903
5904
    /**
5905
     * Reusable method for setting exceptions on buildContentDomainObject usage.
5906
     *
5907
     * Plain usage as in when content type is loaded directly.
5908
     *
5909
     * @param \eZ\Publish\SPI\Persistence\Content $spiContent
5910
     * @param array $translations
5911
     * @param bool $useAlwaysAvailable
5912
     *
5913
     * @return \PHPUnit\Framework\MockObject\MockObject|\eZ\Publish\API\Repository\Values\Content\Content
5914
     */
5915
    private function mockBuildContentDomainObject(SPIContent $spiContent, array $translations = null, bool $useAlwaysAvailable = null)
5916
    {
5917
        $contentTypeId = $spiContent->versionInfo->contentInfo->contentTypeId;
5918
        $contentTypeServiceMock = $this->getContentTypeServiceMock();
5919
        $repositoryMock = $this->getRepositoryMock();
5920
5921
        $contentType = new ContentType([
5922
            'id' => $contentTypeId,
5923
            'fieldDefinitions' => [],
5924
        ]);
5925
5926
        $repositoryMock->expects($this->once())
5927
            ->method('getContentTypeService')
5928
            ->willReturn($contentTypeServiceMock);
5929
5930
        $contentTypeServiceMock->expects($this->once())
5931
            ->method('loadContentType')
5932
            ->with($this->equalTo($contentTypeId))
5933
            ->willReturn($contentType);
5934
5935
        $content = $this->createMock(APIContent::class);
5936
5937
        $this->getContentDomainMapperMock()
5938
            ->expects($this->once())
5939
            ->method('buildContentDomainObject')
5940
            ->with($spiContent, $contentType, $translations ?? [], $useAlwaysAvailable)
5941
            ->willReturn($content);
5942
5943
        return $content;
5944
    }
5945
5946
    protected function mockGetDefaultObjectStates()
5947
    {
5948
        /** @var \PHPUnit\Framework\MockObject\MockObject $objectStateHandlerMock */
5949
        $objectStateHandlerMock = $this->getPersistenceMock()->objectStateHandler();
5950
5951
        $objectStateGroups = [
5952
            new SPIObjectStateGroup(['id' => 10]),
5953
            new SPIObjectStateGroup(['id' => 20]),
5954
        ];
5955
5956
        /* @var \PHPUnit\Framework\MockObject\MockObject $objectStateHandlerMock */
5957
        $objectStateHandlerMock->expects($this->once())
5958
            ->method('loadAllGroups')
5959
            ->will($this->returnValue($objectStateGroups));
5960
5961
        $objectStateHandlerMock->expects($this->at(1))
5962
            ->method('loadObjectStates')
5963
            ->with($this->equalTo(10))
5964
            ->will(
5965
                $this->returnValue(
5966
                    [
5967
                        new SPIObjectState(['id' => 11, 'groupId' => 10]),
5968
                        new SPIObjectState(['id' => 12, 'groupId' => 10]),
5969
                    ]
5970
                )
5971
            );
5972
5973
        $objectStateHandlerMock->expects($this->at(2))
5974
            ->method('loadObjectStates')
5975
            ->with($this->equalTo(20))
5976
            ->will(
5977
                $this->returnValue(
5978
                    [
5979
                        new SPIObjectState(['id' => 21, 'groupId' => 20]),
5980
                        new SPIObjectState(['id' => 22, 'groupId' => 20]),
5981
                    ]
5982
                )
5983
            );
5984
    }
5985
5986
    protected function mockSetDefaultObjectStates()
5987
    {
5988
        /** @var \PHPUnit\Framework\MockObject\MockObject $objectStateHandlerMock */
5989
        $objectStateHandlerMock = $this->getPersistenceMock()->objectStateHandler();
5990
5991
        $defaultObjectStates = [
5992
            new SPIObjectState(['id' => 11, 'groupId' => 10]),
5993
            new SPIObjectState(['id' => 21, 'groupId' => 20]),
5994
        ];
5995
        foreach ($defaultObjectStates as $index => $objectState) {
5996
            $objectStateHandlerMock->expects($this->at($index + 3))
5997
                ->method('setContentState')
5998
                ->with(
5999
                    42,
6000
                    $objectState->groupId,
6001
                    $objectState->id
6002
                );
6003
        }
6004
    }
6005
6006
    /**
6007
     * @param int|null $publicationDate
6008
     * @param int|null $modificationDate
6009
     * @param bool $isHidden
6010
     *
6011
     * @return \eZ\Publish\API\Repository\Values\Content\Content
6012
     */
6013
    protected function mockPublishVersion($publicationDate = null, $modificationDate = null, $isHidden = false)
6014
    {
6015
        $versionInfoMock = $this->createMock(APIVersionInfo::class);
6016
        $contentInfoMock = $this->createMock(APIContentInfo::class);
6017
        /* @var \PHPUnit\Framework\MockObject\MockObject $contentHandlerMock */
6018
        $contentHandlerMock = $this->getPersistenceMock()->contentHandler();
6019
        $metadataUpdateStruct = new SPIMetadataUpdateStruct();
6020
6021
        $spiContent = new SPIContent([
6022
            'versionInfo' => new VersionInfo([
6023
                'contentInfo' => new ContentInfo(['id' => 42, 'contentTypeId' => 123]),
6024
            ]),
6025
        ]);
6026
6027
        $contentMock = $this->mockBuildContentDomainObject($spiContent);
6028
        $contentMock->expects($this->any())
6029
            ->method('__get')
6030
            ->will(
6031
                $this->returnValueMap(
6032
                    [
6033
                        ['id', 42],
6034
                        ['contentInfo', $contentInfoMock],
6035
                        ['versionInfo', $versionInfoMock],
6036
                    ]
6037
                )
6038
            );
6039
        $contentMock->expects($this->any())
6040
            ->method('getVersionInfo')
6041
            ->will($this->returnValue($versionInfoMock));
6042
        $versionInfoMock->expects($this->any())
6043
            ->method('getContentInfo')
6044
            ->will($this->returnValue($contentInfoMock));
6045
        $versionInfoMock->expects($this->any())
6046
            ->method('__get')
6047
            ->will(
6048
                $this->returnValueMap(
6049
                    [
6050
                        ['languageCodes', ['eng-GB']],
6051
                    ]
6052
                )
6053
            );
6054
        $contentInfoMock->expects($this->any())
6055
            ->method('__get')
6056
            ->will(
6057
                $this->returnValueMap(
6058
                    [
6059
                        ['alwaysAvailable', true],
6060
                        ['mainLanguageCode', 'eng-GB'],
6061
                    ]
6062
                )
6063
            );
6064
6065
        $currentTime = time();
6066
        if ($publicationDate === null && $versionInfoMock->versionNo === 1) {
6067
            $publicationDate = $currentTime;
6068
        }
6069
6070
        // Account for 1 second of test execution time
6071
        $metadataUpdateStruct->publicationDate = $publicationDate;
6072
        $metadataUpdateStruct->modificationDate = $modificationDate ?? $currentTime;
6073
        $metadataUpdateStruct->isHidden = $isHidden;
6074
6075
        $contentHandlerMock->expects($this->once())
6076
            ->method('publish')
6077
            ->with(
6078
                42,
6079
                123,
6080
                $metadataUpdateStruct
6081
            )
6082
            ->will($this->returnValue($spiContent));
6083
6084
        /* @var \eZ\Publish\API\Repository\Values\Content\Content $contentMock */
6085
        $this->mockPublishUrlAliasesForContent($contentMock);
6086
6087
        return $contentMock;
6088
    }
6089
6090
    /**
6091
     * @param \eZ\Publish\API\Repository\Values\Content\Content $content
6092
     */
6093
    protected function mockPublishUrlAliasesForContent(APIContent $content)
6094
    {
6095
        $nameSchemaServiceMock = $this->getNameSchemaServiceMock();
6096
        /** @var \PHPUnit\Framework\MockObject\MockObject $urlAliasHandlerMock */
6097
        $urlAliasHandlerMock = $this->getPersistenceMock()->urlAliasHandler();
6098
        $locationServiceMock = $this->getLocationServiceMock();
6099
        $location = $this->createMock(APILocation::class);
6100
6101
        $location->expects($this->at(0))
6102
            ->method('__get')
6103
            ->with('id')
6104
            ->will($this->returnValue(123));
6105
        $location->expects($this->at(1))
6106
            ->method('__get')
6107
            ->with('parentLocationId')
6108
            ->will($this->returnValue(456));
6109
6110
        $urlAliasNames = ['eng-GB' => 'hello'];
6111
        $nameSchemaServiceMock->expects($this->once())
6112
            ->method('resolveUrlAliasSchema')
6113
            ->with($content)
6114
            ->will($this->returnValue($urlAliasNames));
6115
6116
        $locationServiceMock->expects($this->once())
6117
            ->method('loadLocations')
6118
            ->with($content->getVersionInfo()->getContentInfo())
6119
            ->will($this->returnValue([$location]));
6120
6121
        $urlAliasHandlerMock->expects($this->once())
6122
            ->method('publishUrlAliasForLocation')
6123
            ->with(123, 456, 'hello', 'eng-GB', true, true);
6124
6125
        $location->expects($this->at(2))
6126
            ->method('__get')
6127
            ->with('id')
6128
            ->will($this->returnValue(123));
6129
6130
        $location->expects($this->at(3))
6131
            ->method('__get')
6132
            ->with('parentLocationId')
6133
            ->will($this->returnValue(456));
6134
6135
        $urlAliasHandlerMock->expects($this->once())
6136
            ->method('archiveUrlAliasesForDeletedTranslations')
6137
            ->with(123, 456, ['eng-GB']);
6138
    }
6139
6140
    protected $relationProcessorMock;
6141
6142
    /**
6143
     * @return \PHPUnit\Framework\MockObject\MockObject|\eZ\Publish\Core\Repository\Helper\RelationProcessor
6144
     */
6145
    protected function getRelationProcessorMock()
6146
    {
6147
        if (!isset($this->relationProcessorMock)) {
6148
            $this->relationProcessorMock = $this->createMock(RelationProcessor::class);
6149
        }
6150
6151
        return $this->relationProcessorMock;
6152
    }
6153
6154
    protected $nameSchemaServiceMock;
6155
6156
    /**
6157
     * @return \PHPUnit\Framework\MockObject\MockObject|\eZ\Publish\Core\Repository\Helper\NameSchemaService
6158
     */
6159
    protected function getNameSchemaServiceMock()
6160
    {
6161
        if (!isset($this->nameSchemaServiceMock)) {
6162
            $this->nameSchemaServiceMock = $this->createMock(NameSchemaService::class);
6163
        }
6164
6165
        return $this->nameSchemaServiceMock;
6166
    }
6167
6168
    protected $contentTypeServiceMock;
6169
6170
    /**
6171
     * @return \PHPUnit\Framework\MockObject\MockObject|\eZ\Publish\API\Repository\ContentTypeService
6172
     */
6173
    protected function getContentTypeServiceMock()
6174
    {
6175
        if (!isset($this->contentTypeServiceMock)) {
6176
            $this->contentTypeServiceMock = $this->createMock(APIContentTypeService::class);
6177
        }
6178
6179
        return $this->contentTypeServiceMock;
6180
    }
6181
6182
    protected $locationServiceMock;
6183
6184
    /**
6185
     * @return \PHPUnit\Framework\MockObject\MockObject|\eZ\Publish\API\Repository\LocationService
6186
     */
6187
    protected function getLocationServiceMock()
6188
    {
6189
        if (!isset($this->locationServiceMock)) {
6190
            $this->locationServiceMock = $this->createMock(APILocationService::class);
6191
        }
6192
6193
        return $this->locationServiceMock;
6194
    }
6195
6196
    /** @var \eZ\Publish\Core\Repository\ContentService */
6197
    protected $partlyMockedContentService;
6198
6199
    /**
6200
     * Returns the content service to test with $methods mocked.
6201
     *
6202
     * Injected Repository comes from {@see getRepositoryMock()} and persistence handler from {@see getPersistenceMock()}
6203
     *
6204
     * @param string[] $methods
6205
     *
6206
     * @return \eZ\Publish\Core\Repository\ContentService|\PHPUnit\Framework\MockObject\MockObject
6207
     */
6208
    protected function getPartlyMockedContentService(array $methods = null)
6209
    {
6210
        if (!isset($this->partlyMockedContentService)) {
6211
            $this->partlyMockedContentService = $this->getMockBuilder(ContentService::class)
6212
                ->setMethods($methods)
6213
                ->setConstructorArgs(
6214
                    [
6215
                        $this->getRepositoryMock(),
6216
                        $this->getPersistenceMock(),
6217
                        $this->getContentDomainMapperMock(),
6218
                        $this->getRelationProcessorMock(),
6219
                        $this->getNameSchemaServiceMock(),
6220
                        $this->getFieldTypeRegistryMock(),
6221
                        $this->getPermissionResolverMock(),
6222
                        [],
6223
                    ]
6224
                )
6225
                ->getMock();
6226
        }
6227
6228
        return $this->partlyMockedContentService;
6229
    }
6230
6231
    /**
6232
     * @return \eZ\Publish\API\Repository\Repository|\PHPUnit\Framework\MockObject\MockObject
6233
     */
6234
    protected function getRepositoryMock(): Repository
6235
    {
6236
        $repositoryMock = parent::getRepositoryMock();
6237
        $repositoryMock
6238
            ->expects($this->any())
6239
            ->method('getPermissionResolver')
6240
            ->willReturn($this->getPermissionResolverMock());
6241
6242
        return $repositoryMock;
6243
    }
6244
}
6245