Completed
Pull Request — master (#264)
by Robbie
13:29
created

TranslatableTest::assertArrayEqualsAfterSort()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 3
1
<?php
2
3
namespace SilverStripe\Translatable\Tests;
4
5
use Exception;
6
use Page;
7
use SilverStripe\CMS\Controllers\CMSPageEditController;
8
use SilverStripe\CMS\Controllers\RootURLController;
9
use SilverStripe\CMS\Model\RedirectorPage;
10
use SilverStripe\CMS\Model\SiteTree;
11
use SilverStripe\Control\Controller;
12
use SilverStripe\Core\Config\Config;
13
use SilverStripe\Core\Convert;
14
use SilverStripe\Dev\FunctionalTest;
15
use SilverStripe\Dev\TestOnly;
16
use SilverStripe\Forms\DropdownField;
17
use SilverStripe\Forms\LookupField;
18
use SilverStripe\Forms\ReadonlyField;
19
use SilverStripe\Forms\TextField;
20
use SilverStripe\ORM\DataExtension;
21
use SilverStripe\ORM\DataObject;
22
use SilverStripe\Security\Group;
23
use SilverStripe\Security\Member;
24
use SilverStripe\SiteConfig\SiteConfig;
25
use SilverStripe\Translatable\Model\Translatable;
26
use SilverStripe\Translatable\Tests\Stub\EveryoneCanPublish;
27
use SilverStripe\Translatable\Tests\Stub\TranslatableTestData;
28
use SilverStripe\Translatable\Tests\Stub\TranslatableTestDataObject;
29
use SilverStripe\Translatable\Tests\Stub\TranslatableTestExtension;
30
use SilverStripe\Translatable\Tests\Stub\TranslatableTestOneByLocaleDataObject;
31
use SilverStripe\Translatable\Tests\Stub\TranslatableTestPage;
32
use SilverStripe\Versioned\Versioned;
33
34
/**
35
 * @todo Test Versioned getters
36
 *
37
 * @package translatable
38
 */
39
class TranslatableTest extends FunctionalTest
40
{
41
    protected static $fixture_file = 'TranslatableTest.yml';
42
43
    protected static $extra_dataobjects = [
44
        TranslatableTestDataObject::class,
45
        TranslatableTestOneByLocaleDataObject::class,
46
        TranslatableTestPage::class,
47
    ];
48
49
    protected static $required_extensions = [
50
        SiteTree::class => [
51
            Translatable::class,
52
            Versioned::class,
53
            EveryoneCanPublish::class
54
        ],
55
        SiteConfig::class => [Translatable::class],
56
        TranslatableTestDataObject::class => [
57
            Translatable::class,
58
            TranslatableTestExtension::class
59
        ],
60
        TranslatableTestOneByLocaleDataObject::class => [Translatable::class],
61
    ];
62
63
    private $origLocale;
64
65
    protected $autoFollowRedirection = false;
66
67
    protected function setUp()
68
    {
69
        parent::setUp();
70
71
        // whenever a translation is created, canTranslate() is checked
72
        $cmseditor = $this->objFromFixture(Member::class, 'cmseditor');
73
        $cmseditor->logIn();
74
75
        $this->origLocale = Translatable::default_locale();
76
        Translatable::set_default_locale("en_US");
77
    }
78
79
    protected function tearDown()
80
    {
81
        Translatable::set_default_locale($this->origLocale);
82
        Translatable::set_current_locale($this->origLocale);
83
84
        parent::tearDown();
85
    }
86
87
    protected function assertArrayEqualsAfterSort($expected, $actual, $message = null)
88
    {
89
        sort($expected);
90
        sort($actual);
91
        return $this->assertEquals($expected, $actual, $message);
92
    }
93
94
    public function testGetOneByLocale()
95
    {
96
        Translatable::disable_locale_filter();
97
        $this->assertEquals(
98
            0,
99
            TranslatableTestOneByLocaleDataObject::get()->count(),
100
            'should not be any test objects yet'
101
        );
102
        Translatable::enable_locale_filter();
103
104
        $obj = new TranslatableTestOneByLocaleDataObject();
105
        $obj->TranslatableProperty = 'test - en';
0 ignored issues
show
Documentation introduced by
The property TranslatableProperty does not exist on object<SilverStripe\Tran...tOneByLocaleDataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
106
        $obj->write();
107
108
        Translatable::disable_locale_filter();
109
        $this->assertEquals(
110
            1,
111
            TranslatableTestOneByLocaleDataObject::get()->count(),
112
            'should not be any test objects yet'
113
        );
114
        Translatable::enable_locale_filter();
115
116
        $found = Translatable::get_one_by_locale(TranslatableTestOneByLocaleDataObject::class, $obj->Locale);
0 ignored issues
show
Documentation introduced by
The property Locale does not exist on object<SilverStripe\Tran...tOneByLocaleDataObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
117
        $this->assertNotNull($found, 'should have found one for ' . $obj->Locale);
0 ignored issues
show
Documentation introduced by
The property Locale does not exist on object<SilverStripe\Tran...tOneByLocaleDataObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
118
        $this->assertEquals($obj->ID, $found->ID);
119
120
        $translated = $obj->createTranslation('de_DE');
0 ignored issues
show
Documentation Bug introduced by
The method createTranslation does not exist on object<SilverStripe\Tran...tOneByLocaleDataObject>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
121
        $translated->write();
122
123
        Translatable::disable_locale_filter();
124
        $this->assertEquals(
125
            2,
126
            TranslatableTestOneByLocaleDataObject::get()->count(),
127
            'should not be any test objects yet'
128
        );
129
        Translatable::enable_locale_filter();
130
131
        $found = Translatable::get_one_by_locale(
132
            TranslatableTestOneByLocaleDataObject::class,
133
            $translated->Locale
134
        );
135
        $this->assertNotNull($found, 'should have found one for ' . $translated->Locale);
136
        $this->assertEquals($translated->ID, $found->ID);
137
138
        // test again to make sure that get_one_by_locale works when locale filter disabled
139
        Translatable::disable_locale_filter();
140
        $found = Translatable::get_one_by_locale(
141
            TranslatableTestOneByLocaleDataObject::class,
142
            $translated->Locale
143
        );
144
        $this->assertEquals($translated->ID, $found->ID);
145
        Translatable::enable_locale_filter();
146
    }
147
148
    public function testLocaleFilteringEnabledAndDisabled()
149
    {
150
        $this->assertTrue(Translatable::locale_filter_enabled());
151
152
        // get our base page to use for testing
153
        $origPage = $this->objFromFixture(Page::class, 'testpage_en');
154
        $origPage->MenuTitle = 'unique-key-used-in-my-query';
155
        $origPage->write();
156
        $origPage->copyVersionToStage('Stage', 'Live');
157
158
        // create a translation of it so that we can see if translations are filtered
159
        $translatedPage = $origPage->createTranslation('de_DE');
160
        $translatedPage->MenuTitle = $origPage->MenuTitle;
161
        $translatedPage->write();
162
        $translatedPage->copyVersionToStage('Stage', 'Live');
163
164
        $where = sprintf("\"MenuTitle\" = '%s'", Convert::raw2sql($origPage->MenuTitle));
165
166
        // make sure that our query was filtered
167
        $this->assertEquals(1, Page::get()->where($where)->count());
168
169
        // test no filtering with disabled locale filter
170
        Translatable::disable_locale_filter();
171
        $this->assertEquals(2, Page::get()->where($where)->count());
172
        Translatable::enable_locale_filter();
173
174
        // make sure that our query was filtered after re-enabling the filter
175
        $this->assertEquals(1, Page::get()->where($where)->count());
176
177
        // test effectiveness of disabling locale filter with 3.x delayed querying
178
        // see https://github.com/silverstripe/silverstripe-translatable/issues/113
179
        Translatable::disable_locale_filter();
180
        // create the DataList while the locale filter is disabled
181
        $dataList = Page::get()->where($where);
182
        Translatable::enable_locale_filter();
183
        // but don't use it until later - after the filter is re-enabled
184
        $this->assertEquals(2, $dataList->count());
185
    }
186
187
    public function testLocaleGetParamRedirectsToTranslation()
188
    {
189
        $origPage = $this->objFromFixture(Page::class, 'testpage_en');
190
        $origPage->copyVersionToStage('Stage', 'Live');
191
        $translatedPage = $origPage->createTranslation('de_DE');
192
        $translatedPage->URLSegment = 'ueber-uns';
193
        $translatedPage->write();
194
        $translatedPage->copyVersionToStage('Stage', 'Live');
195
196
        // Need to log out, otherwise pages redirect to CMS views
197
        $this->session()->set('loggedInAs', null);
198
199
        $response = $this->get($origPage->URLSegment);
200
        $this->assertEquals(200, $response->getStatusCode(), 'Page request without Locale GET param doesnt redirect');
201
202
        $response = $this->get(Controller::join_links($origPage->URLSegment, '?locale=de_DE'));
203
        $this->assertEquals(301, $response->getStatusCode(), 'Locale GET param causes redirect if it exists');
204
        $this->assertContains($translatedPage->URLSegment, $response->getHeader('Location'));
205
206
        $response = $this->get(Controller::join_links($origPage->URLSegment, '?locale=fr_FR'));
207
        $this->assertEquals(
208
            200,
209
            $response->getStatusCode(),
210
            'Locale GET param without existing translation shows original page'
211
        );
212
    }
213
214
    public function testTranslationGroups()
215
    {
216
        // first in french
217
        $frPage = new SiteTree();
218
        $frPage->Locale = 'fr_FR';
219
        $frPage->write();
220
221
        // second in english (from french "original")
222
        $enPage = $frPage->createTranslation('en_US');
223
224
        // third in spanish (from the english translation)
225
        $esPage = $enPage->createTranslation('es_ES');
226
227
        // test french
228
229
        $this->assertArrayEqualsAfterSort(
230
            array('en_US', 'es_ES'),
231
            $frPage->getTranslations()->column('Locale')
232
        );
233
        $this->assertNotNull($frPage->getTranslation('en_US'));
234
        $this->assertEquals(
235
            $frPage->getTranslation('en_US')->ID,
236
            $enPage->ID
237
        );
238
        $this->assertNotNull($frPage->getTranslation('es_ES'));
239
        $this->assertEquals(
240
            $frPage->getTranslation('es_ES')->ID,
241
            $esPage->ID
242
        );
243
244
        // test english
245
        $this->assertArrayEqualsAfterSort(
246
            array('es_ES', 'fr_FR'),
247
            $enPage->getTranslations()->column('Locale')
248
        );
249
        $this->assertNotNull($frPage->getTranslation('fr_FR'));
250
        $this->assertEquals(
251
            $enPage->getTranslation('fr_FR')->ID,
252
            $frPage->ID
253
        );
254
        $this->assertNotNull($frPage->getTranslation('es_ES'));
255
        $this->assertEquals(
256
            $enPage->getTranslation('es_ES')->ID,
257
            $esPage->ID
258
        );
259
260
        // test spanish
261
        $this->assertArrayEqualsAfterSort(
262
            array('en_US', 'fr_FR'),
263
            $esPage->getTranslations()->column('Locale')
264
        );
265
        $this->assertNotNull($esPage->getTranslation('fr_FR'));
266
        $this->assertEquals(
267
            $esPage->getTranslation('fr_FR')->ID,
268
            $frPage->ID
269
        );
270
        $this->assertNotNull($esPage->getTranslation('en_US'));
271
        $this->assertEquals(
272
            $esPage->getTranslation('en_US')->ID,
273
            $enPage->ID
274
        );
275
    }
276
277
    public function assertClass($class, $node)
278
    {
279
        $this->assertNotNull($node);
280
        $this->assertEquals($class, $node->ClassName);
281
        $this->assertEquals($class, get_class($node));
282
    }
283
284
    public function testChangingClassOfDefaultLocaleTranslationChangesOthers()
285
    {
286
        // see https://github.com/silverstripe/silverstripe-translatable/issues/97
287
        // create an English SiteTree
288
        $enST = new SiteTree();
289
        $enST->Locale = 'en_US';
290
        $enST->write();
291
292
        // create French and Spanish translations
293
        $frST = $enST->createTranslation('fr_FR');
294
        $esST = $enST->createTranslation('es_ES');
295
296
        // change the class name of the default locale's translation (as CMS admin would)
297
        $enST->setClassName(Page::class);
298
        $enST->write();
299
300
        // reload them all to get fresh instances
301
        $enPg = DataObject::get_by_id(Page::class, $enST->ID, $cache = false);
302
        $frPg = DataObject::get_by_id(Page::class, $frST->ID, $cache = false);
303
        $esPg = DataObject::get_by_id(Page::class, $esST->ID, $cache = false);
304
305
        // make sure they are all the right class
306
        $this->assertClass(Page::class, $enPg);
307
        $this->assertClass(Page::class, $frPg);
308
        $this->assertClass(Page::class, $esPg);
309
310
        // test that we get the right translations back from each instance
311
        $this->assertArrayEqualsAfterSort(
312
            array('fr_FR', 'es_ES'),
313
            $enPg->getTranslations()->column('Locale')
314
        );
315
        $this->assertArrayEqualsAfterSort(
316
            array('en_US', 'es_ES'),
317
            $frPg->getTranslations()->column('Locale')
318
        );
319
        $this->assertArrayEqualsAfterSort(
320
            array('en_US', 'fr_FR'),
321
            $esPg->getTranslations()->column('Locale')
322
        );
323
    }
324
325
    public function testChangingClassOfDefaultLocaleTranslationChangesOthersWhenPublished()
326
    {
327
        // create an English SiteTree
328
        $enST = new SiteTree();
329
        $enST->Locale = 'en_US';
330
        $enST->write();
331
        $enST->publishRecursive();
332
333
        // create and publish French and Spanish translations
334
        $frST = $enST->createTranslation('fr_FR');
335
        $this->assertTrue($frST->doPublish(), 'should have been able to publish French translation');
336
        $esST = $enST->createTranslation('es_ES');
337
        $this->assertTrue($esST->doPublish(), 'should have been able to publish Spanish translation');
338
339
        // change the class name of the default locale's translation (as CMS admin would)
340
        // and publish the change - we should see both versions of English change class
341
        $enST->setClassName(Page::class);
342
        $enST->write();
343
        $enST->publishRecursive();
344
        $this->assertClass(Page::class, Versioned::get_one_by_stage(SiteTree::class, 'Stage', '"ID" = ' . $enST->ID));
345
        $this->assertClass(Page::class, Versioned::get_one_by_stage(SiteTree::class, 'Live', '"ID" = ' . $enST->ID));
346
347
        // and all of the draft versions of translations:
348
        $this->assertClass(Page::class, Versioned::get_one_by_stage(SiteTree::class, 'Stage', '"ID" = ' . $frST->ID));
349
        $this->assertClass(Page::class, Versioned::get_one_by_stage(SiteTree::class, 'Stage', '"ID" = ' . $esST->ID));
350
351
        // and all of the live versions of translations as well:
352
        $this->assertClass(Page::class, Versioned::get_one_by_stage(SiteTree::class, 'Live', '"ID" = ' . $frST->ID));
353
        $this->assertClass(Page::class, Versioned::get_one_by_stage(SiteTree::class, 'Live', '"ID" = ' . $esST->ID));
354
    }
355
356
    public function testTranslationGroupsWhenTranslationIsSubclass()
357
    {
358
        // create an English SiteTree
359
        $enST = new SiteTree();
360
        $enST->Locale = 'en_US';
361
        $enST->write();
362
363
        // create French and Spanish translations
364
        $frST = $enST->createTranslation('fr_FR');
365
        $esST = $enST->createTranslation('es_ES');
366
367
        // test that we get the right translations back from each instance
368
        $this->assertArrayEqualsAfterSort(
369
            array('fr_FR', 'es_ES'),
370
            $enST->getTranslations()->column('Locale')
371
        );
372
        $this->assertArrayEqualsAfterSort(
373
            array('en_US', 'es_ES'),
374
            $frST->getTranslations()->column('Locale')
375
        );
376
        $this->assertArrayEqualsAfterSort(
377
            array('en_US', 'fr_FR'),
378
            $esST->getTranslations()->column('Locale')
379
        );
380
381
        // this should be considered an edge-case, but on some sites translations
382
        // may be allowed to be a subclass of the default locale's translation of
383
        // the same page.  In this case, we need to support getTranslations returning
384
        // all of the translations, even if one of the translations is a different
385
        // class from others
386
        $esST->setClassName(Page::class);
387
        $esST->write();
388
        $esPg = DataObject::get_by_id(Page::class, $esST->ID, $cache = false);
389
390
        // make sure we successfully changed the class
391
        $this->assertClass(Page::class, $esPg);
392
393
        // and make sure that the class of the others did not change
394
        $frST = DataObject::get_by_id(SiteTree::class, $frST->ID, $cache = false);
395
        $this->assertClass(SiteTree::class, $frST);
396
        $enST = DataObject::get_by_id(SiteTree::class, $enST->ID, $cache = false);
397
        $this->assertClass(SiteTree::class, $enST);
398
399
        // now that we know our edge case is successfully configured, we need to
400
        // make sure that we get the right translations back from everything
401
        $this->assertArrayEqualsAfterSort(
402
            array('fr_FR', 'es_ES'),
403
            $enST->getTranslations()->column('Locale')
404
        );
405
        $this->assertArrayEqualsAfterSort(
406
            array('en_US', 'es_ES'),
407
            $frST->getTranslations()->column('Locale')
408
        );
409
        $this->assertArrayEqualsAfterSort(
410
            array('en_US', 'fr_FR'),
411
            $esPg->getTranslations()->column('Locale')
412
        );
413
        $this->assertEquals($enST->ID, $esPg->getTranslation('en_US')->ID);
414
        $this->assertEquals($frST->ID, $esPg->getTranslation('fr_FR')->ID);
415
        $this->assertEquals($esPg->ID, $enST->getTranslation('es_ES')->ID);
416
        $this->assertEquals($esPg->ID, $frST->getTranslation('es_ES')->ID);
417
    }
418
419
    public function testTranslationGroupNotRemovedWhenSiteTreeUnpublished()
420
    {
421
        $enPage = new Page();
422
        $enPage->Locale = 'en_US';
423
        $enPage->write();
424
        $enPage->copyVersionToStage('Stage', 'Live');
425
        $enTranslationGroup = $enPage->getTranslationGroup();
426
427
        $frPage = $enPage->createTranslation('fr_FR');
428
        $frPage->write();
429
        $frPage->copyVersionToStage('Stage', 'Live');
430
        $frTranslationGroup = $frPage->getTranslationGroup();
431
432
        $enPage->doUnpublish();
433
        $this->assertEquals($enPage->getTranslationGroup(), $enTranslationGroup);
434
435
        $frPage->doUnpublish();
436
        $this->assertEquals($frPage->getTranslationGroup(), $frTranslationGroup);
437
    }
438
439
    public function testGetTranslationOnSiteTree()
440
    {
441
        $origPage = $this->objFromFixture(Page::class, 'testpage_en');
442
443
        $translatedPage = $origPage->createTranslation('fr_FR');
444
        $getTranslationPage = $origPage->getTranslation('fr_FR');
445
446
        $this->assertNotNull($getTranslationPage);
447
        $this->assertEquals($getTranslationPage->ID, $translatedPage->ID);
448
    }
449
450
    public function testGetTranslatedLanguages()
451
    {
452
        $origPage = $this->objFromFixture(Page::class, 'testpage_en');
453
454
        // through createTranslation()
455
        $translationAf = $origPage->createTranslation('af_ZA');
0 ignored issues
show
Unused Code introduced by
$translationAf 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...
456
457
        // create a new language on an unrelated page which shouldnt be returned from $origPage
458
        $otherPage = new Page();
459
        $otherPage->write();
460
        $otherTranslationEs = $otherPage->createTranslation('es_ES');
0 ignored issues
show
Unused Code introduced by
$otherTranslationEs 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...
461
462
        $this->assertEquals(
463
            $origPage->getTranslatedLocales(),
464
            array(
465
                'af_ZA',
466
                //'en_US', // default language is not included
467
            ),
468
            'Language codes are returned specifically for the queried page through getTranslatedLocales()'
469
        );
470
471
        $pageWithoutTranslations = new Page();
472
        $pageWithoutTranslations->write();
473
        $this->assertEquals(
474
            $pageWithoutTranslations->getTranslatedLocales(),
475
            array(),
476
            'A page without translations returns empty array through getTranslatedLocales(), ' .
477
            'even if translations for other pages exist in the database'
478
        );
479
480
        // manual creation of page without an original link
481
        $translationDeWithoutOriginal = new Page();
482
        $translationDeWithoutOriginal->Locale = 'de_DE';
483
        $translationDeWithoutOriginal->write();
484
        $this->assertEquals(
485
            $translationDeWithoutOriginal->getTranslatedLocales(),
486
            array(),
487
            'A translated page without an original doesn\'t return anything through getTranslatedLocales()'
488
        );
489
    }
490
491
    public function testTranslationCantHaveSameURLSegmentAcrossLanguages()
492
    {
493
        $origPage = $this->objFromFixture(Page::class, 'testpage_en');
494
        $translatedPage = $origPage->createTranslation('de_DE');
495
        $this->assertEquals($translatedPage->URLSegment, 'testpage-de-de');
496
497
        $translatedPage->URLSegment = 'testpage'; // de_DE clashes with en_US
498
        $translatedPage->write();
499
        $this->assertNotEquals($origPage->URLSegment, $translatedPage->URLSegment);
500
501
        Translatable::set_current_locale('de_DE');
502
        Config::modify()->set(Translatable::class, 'enforce_global_unique_urls', false);
503
        $translatedPage->URLSegment = 'testpage'; // de_DE clashes with en_US
504
        $translatedPage->write();
505
        $this->assertEquals('testpage', $translatedPage->URLSegment);
506
        Config::modify()->set(Translatable::class, 'enforce_global_unique_urls', true);
507
        Translatable::set_current_locale('en_US');
508
    }
509
510
    public function testUpdateCMSFieldsOnSiteTree()
511
    {
512
        $pageOrigLang = new TranslatableTestPage();
513
        $pageOrigLang->write();
514
515
        // first test with default language
516
        $fields = $pageOrigLang->getCMSFields();
517
518
        // title
519
        $this->assertInstanceOf(
520
            TextField::class,
521
            $fields->dataFieldByName('Title'),
522
            'Translatable doesnt modify fields if called in default language (e.g. "non-translation mode")'
523
        );
524
        $this->assertNull(
525
            $fields->dataFieldByName('Title_original'),
526
            'Translatable doesnt modify fields if called in default language (e.g. "non-translation mode")'
527
        );
528
        // custom property
529
        $this->assertInstanceOf(
530
            TextField::class,
531
            $fields->dataFieldByName('TranslatableProperty'),
532
            'Has custom field'
533
        );
534
        // custom has_one
535
        $this->assertInstanceOf(
536
            DropdownField::class,
537
            $fields->dataFieldByName('TranslatableObjectID'),
538
            'Has custom dropdown field'
539
        );
540
541
        // then in "translation mode"
542
        $pageTranslated = $pageOrigLang->createTranslation('fr_FR');
543
        $fields = $pageTranslated->getCMSFields();
544
        // title
545
        $this->assertInstanceOf(
546
            TextField::class,
547
            $fields->dataFieldByName('Title'),
548
            'Translatable leaves original formfield intact in "translation mode"'
549
        );
550
        $readonlyField = $fields->dataFieldByName('Title')->performReadonlyTransformation();
551
        $this->assertInstanceOf(
552
            get_class($readonlyField),
553
            $fields->dataFieldByName('Title_original'),
554
            'Translatable adds the original value as a ReadonlyField in "translation mode"'
555
        );
556
        // custom property
557
        $this->assertInstanceOf(
558
            ReadonlyField::class,
559
            $fields->dataFieldByName('TranslatableProperty_original'),
560
            'Adds original value for custom field as ReadonlyField'
561
        );
562
        $this->assertInstanceOf(
563
            TextField::class,
564
            $fields->dataFieldByName('TranslatableProperty'),
565
            'Retains custom field as TextField'
566
        );
567
        // custom has_one
568
        $this->assertInstanceOf(
569
            LookupField::class,
570
            $fields->dataFieldByName('TranslatableObjectID_original'),
571
            'Adds original value for custom dropdown field as LookupField (= readonly version of DropdownField)'
572
        );
573
        $this->assertInstanceOf(
574
            DropdownField::class,
575
            $fields->dataFieldByName('TranslatableObjectID'),
576
            'Retains custom dropdown field as DropdownField'
577
        );
578
    }
579
580
    public function testDataObjectGetWithReadingLanguage()
581
    {
582
        $origTestPage = $this->objFromFixture(Page::class, 'testpage_en');
583
        $otherTestPage = $this->objFromFixture(Page::class, 'othertestpage_en');
584
        $translatedPage = $origTestPage->createTranslation('de_DE');
585
586
        // test in default language
587
        $resultPagesDefaultLang = DataObject::get(
588
            'Page',
589
            sprintf("\"SiteTree\".\"MenuTitle\" = '%s'", 'A Testpage')
590
        );
591
        $resultPagesDefaultLangIDs = $resultPagesDefaultLang->column('ID');
592
        foreach ($resultPagesDefaultLangIDs as $key => $val) {
593
            $resultPagesDefaultLangIDs[$key] = intval($val);
594
        }
595
        $this->assertEquals($resultPagesDefaultLang->Count(), 2);
596
        $this->assertContains((int)$origTestPage->ID, $resultPagesDefaultLangIDs);
597
        $this->assertContains((int)$otherTestPage->ID, $resultPagesDefaultLangIDs);
598
        $this->assertNotContains((int)$translatedPage->ID, $resultPagesDefaultLangIDs);
599
600
        // test in custom language
601
        Translatable::set_current_locale('de_DE');
602
        $resultPagesCustomLang = DataObject::get(
603
            'Page',
604
            sprintf("\"SiteTree\".\"MenuTitle\" = '%s'", 'A Testpage')
605
        );
606
        $resultPagesCustomLangIDs = $resultPagesCustomLang->column('ID');
607
        foreach ($resultPagesCustomLangIDs as $key => $val) {
608
            $resultPagesCustomLangIDs[$key] = intval($val);
609
        }
610
        $this->assertEquals($resultPagesCustomLang->Count(), 1);
611
        $this->assertNotContains((int)$origTestPage->ID, $resultPagesCustomLangIDs);
612
        $this->assertNotContains((int)$otherTestPage->ID, $resultPagesCustomLangIDs);
613
        $this->assertContains((int)$translatedPage->ID, $resultPagesCustomLangIDs);
614
615
        Translatable::set_current_locale('en_US');
616
    }
617
618
    public function testDataObjectGetByIdWithReadingLanguage()
619
    {
620
        $origPage = $this->objFromFixture(Page::class, 'testpage_en');
621
        $translatedPage = $origPage->createTranslation('de_DE');
0 ignored issues
show
Unused Code introduced by
$translatedPage 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...
622
        $compareOrigPage = DataObject::get_by_id(Page::class, $origPage->ID);
623
624
        $this->assertEquals(
625
            $origPage->ID,
626
            $compareOrigPage->ID,
627
            'DataObject::get_by_id() should work independently of the reading language'
628
        );
629
    }
630
631
    public function testDataObjectGetOneWithReadingLanguage()
632
    {
633
        $origPage = $this->objFromFixture(Page::class, 'testpage_en');
634
        $translatedPage = $origPage->createTranslation('de_DE');
635
636
        // running the same query twice with different
637
        Translatable::set_current_locale('de_DE');
638
        $compareTranslatedPage = DataObject::get_one(
639
            'Page',
640
            sprintf("\"SiteTree\".\"Title\" = '%s'", $translatedPage->Title)
641
        );
642
        $this->assertNotNull($compareTranslatedPage);
643
        $this->assertEquals(
644
            $translatedPage->ID,
645
            $compareTranslatedPage->ID,
646
            "Translated page is found through get_one() when reading lang is not the default language"
647
        );
648
649
        // reset language to default
650
        Translatable::set_current_locale('en_US');
651
    }
652
653
    public function testModifyTranslationWithDefaultReadingLang()
654
    {
655
        $origPage = $this->objFromFixture(Page::class, 'testpage_en');
656
        $translatedPage = $origPage->createTranslation('de_DE');
657
658
        Translatable::set_current_locale('en_US');
659
        $translatedPage->Title = 'De Modified';
660
        $translatedPage->write();
661
        $savedTranslatedPage = $origPage->getTranslation('de_DE');
662
        $this->assertEquals(
663
            $savedTranslatedPage->Title,
664
            'De Modified',
665
            'Modifying a record in language which is not the reading language should still write the record correctly'
666
        );
667
        $this->assertEquals(
668
            $origPage->Title,
669
            'Home',
670
            'Modifying a record in language which is not the reading language does not modify the original record'
671
        );
672
    }
673
674
    public function testSiteTreePublication()
675
    {
676
        $origPage = $this->objFromFixture(Page::class, 'testpage_en');
677
        $translatedPage = $origPage->createTranslation('de_DE');
678
679
        Translatable::set_current_locale('en_US');
680
        $origPage->Title = 'En Modified';
681
        $origPage->write();
682
        // modifying a record in language which is not the reading language should still write the record correctly
683
        $translatedPage->Title = 'De Modified';
684
        $translatedPage->write();
685
        $origPage->copyVersionToStage('Stage', 'Live');
686
        $liveOrigPage = Versioned::get_one_by_stage(Page::class, 'Live', "\"SiteTree\".\"ID\" = {$origPage->ID}");
687
        $this->assertEquals(
688
            $liveOrigPage->Title,
689
            'En Modified',
690
            'Publishing a record in its original language publshes correct properties'
691
        );
692
    }
693
694
    public function testDeletingTranslationKeepsOriginal()
695
    {
696
        $origPage = $this->objFromFixture(Page::class, 'testpage_en');
697
        $translatedPage = $origPage->createTranslation('de_DE');
698
        $translatedPageID = $translatedPage->ID;
0 ignored issues
show
Unused Code introduced by
$translatedPageID 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...
699
        $translatedPage->delete();
700
701
        $translatedPage->flushCache();
702
        $origPage->flushCache();
703
704
        $this->assertNull($origPage->getTranslation('de_DE'));
705
        $this->assertNotNull(DataObject::get_by_id(Page::class, $origPage->ID));
706
    }
707
708
    public function testHierarchyChildren()
709
    {
710
        $parentPage = $this->objFromFixture(Page::class, 'parent');
711
        $child1Page = $this->objFromFixture(Page::class, 'child1');
712
        $child2Page = $this->objFromFixture(Page::class, 'child2');
713
        $child3Page = $this->objFromFixture(Page::class, 'child3');
714
        $grandchildPage = $this->objFromFixture(Page::class, 'grandchild1');
0 ignored issues
show
Unused Code introduced by
$grandchildPage 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...
715
716
        $parentPageTranslated = $parentPage->createTranslation('de_DE');
717
        $child4PageTranslated = new SiteTree();
718
        $child4PageTranslated->Locale = 'de_DE';
719
        $child4PageTranslated->ParentID = $parentPageTranslated->ID;
720
        $child4PageTranslated->write();
721
722
        Translatable::set_current_locale('en_US');
723
        $this->assertArrayEqualsAfterSort(
724
            array(
725
                $child1Page->ID,
726
                $child2Page->ID,
727
                $child3Page->ID
728
            ),
729
            $parentPage->Children()->column('ID'),
730
            "Showing Children() in default language doesnt show children in other languages"
731
        );
732
733
        Translatable::set_current_locale('de_DE');
734
        $parentPage->flushCache();
735
        $this->assertEquals(
736
            $parentPageTranslated->Children()->column('ID'),
737
            array($child4PageTranslated->ID),
738
            "Showing Children() in translation mode doesnt show children in default languages"
739
        );
740
741
        // reset language
742
        Translatable::set_current_locale('en_US');
743
    }
744
745
    public function testHierarchyLiveStageChildren()
746
    {
747
        $parentPage = $this->objFromFixture(Page::class, 'parent');
748
        $child1Page = $this->objFromFixture(Page::class, 'child1');
749
        $child1Page->copyVersionToStage('Stage', 'Live');
750
        $child2Page = $this->objFromFixture(Page::class, 'child2');
751
        $child3Page = $this->objFromFixture(Page::class, 'child3');
752
        $grandchildPage = $this->objFromFixture(Page::class, 'grandchild1');
0 ignored issues
show
Unused Code introduced by
$grandchildPage 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...
753
754
        $parentPageTranslated = $parentPage->createTranslation('de_DE');
755
756
        $child4PageTranslated = new SiteTree();
757
        $child4PageTranslated->Locale = 'de_DE';
758
        $child4PageTranslated->ParentID = $parentPageTranslated->ID;
759
        $child4PageTranslated->write();
760
        $child4PageTranslated->copyVersionToStage('Stage', 'Live');
761
762
        $child5PageTranslated = new SiteTree();
763
        $child5PageTranslated->Locale = 'de_DE';
764
        $child5PageTranslated->ParentID = $parentPageTranslated->ID;
765
        $child5PageTranslated->write();
766
767
        Translatable::set_current_locale('en_US');
768
        $this->assertNotNull($parentPage->liveChildren());
769
        $this->assertEquals(
770
            $parentPage->liveChildren()->column('ID'),
771
            array(
772
                $child1Page->ID
773
            ),
774
            "Showing liveChildren() in default language doesnt show children in other languages"
775
        );
776
        $this->assertNotNull($parentPage->stageChildren());
777
        $this->assertArrayEqualsAfterSort(
778
            array(
779
                $child1Page->ID,
780
                $child2Page->ID,
781
                $child3Page->ID
782
            ),
783
            $parentPage->stageChildren()->column('ID'),
784
            "Showing stageChildren() in default language doesnt show children in other languages"
785
        );
786
787
        Translatable::set_current_locale('de_DE');
788
        $parentPage->flushCache();
789
        $this->assertNotNull($parentPageTranslated->liveChildren());
790
        $this->assertEquals(
791
            $parentPageTranslated->liveChildren()->column('ID'),
792
            array($child4PageTranslated->ID),
793
            "Showing liveChildren() in translation mode doesnt show children in default languages"
794
        );
795
        $this->assertNotNull($parentPageTranslated->stageChildren());
796
        $this->assertEquals(
797
            $parentPageTranslated->stageChildren()->column('ID'),
798
            array(
799
                $child4PageTranslated->ID,
800
                $child5PageTranslated->ID,
801
            ),
802
            "Showing stageChildren() in translation mode doesnt show children in default languages"
803
        );
804
805
        // reset language
806
        Translatable::set_current_locale('en_US');
807
    }
808
809
    public function testTranslatablePropertiesOnSiteTree()
810
    {
811
        $origObj = $this->objFromFixture(TranslatableTestPage::class, 'testpage_en');
812
813
        $translatedObj = $origObj->createTranslation('fr_FR');
814
        $translatedObj->TranslatableProperty = 'fr_FR';
815
        $translatedObj->write();
816
817
        $this->assertEquals(
818
            $origObj->TranslatableProperty,
819
            'en_US',
820
            'Creating a translation doesnt affect database field on original object'
821
        );
822
        $this->assertEquals(
823
            $translatedObj->TranslatableProperty,
824
            'fr_FR',
825
            'Translated object saves database field independently of original object'
826
        );
827
    }
828
829 View Code Duplication
    public function testCreateTranslationOnSiteTree()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
830
    {
831
        $origPage = $this->objFromFixture(Page::class, 'testpage_en');
832
        $translatedPage = $origPage->createTranslation('de_DE');
833
834
        $this->assertEquals($translatedPage->Locale, 'de_DE');
835
        $this->assertNotEquals($translatedPage->ID, $origPage->ID);
836
837
        $subsequentTranslatedPage = $origPage->createTranslation('de_DE');
838
        $this->assertEquals(
839
            $translatedPage->ID,
840
            $subsequentTranslatedPage->ID,
841
            'Subsequent calls to createTranslation() dont cause new records in database'
842
        );
843
    }
844
845
    public function testTranslatablePropertiesOnDataObject()
846
    {
847
        $origObj = $this->objFromFixture(TranslatableTestDataObject::class, 'testobject_en');
848
        $translatedObj = $origObj->createTranslation('fr_FR');
849
        $translatedObj->TranslatableProperty = 'fr_FR';
850
        $translatedObj->TranslatableDecoratedProperty = 'fr_FR';
851
        $translatedObj->write();
852
853
        $this->assertEquals(
854
            $origObj->TranslatableProperty,
855
            'en_US',
856
            'Creating a translation doesnt affect database field on original object'
857
        );
858
        $this->assertEquals(
859
            $origObj->TranslatableDecoratedProperty,
860
            'en_US',
861
            'Creating a translation doesnt affect decorated database field on original object'
862
        );
863
        $this->assertEquals(
864
            $translatedObj->TranslatableProperty,
865
            'fr_FR',
866
            'Translated object saves database field independently of original object'
867
        );
868
        $this->assertEquals(
869
            $translatedObj->TranslatableDecoratedProperty,
870
            'fr_FR',
871
            'Translated object saves decorated database field independently of original object'
872
        );
873
    }
874
875
    public function testCreateTranslationWithoutOriginal()
876
    {
877
        $origParentPage = $this->objFromFixture(Page::class, 'testpage_en');
878
        $translatedParentPage = $origParentPage->createTranslation('de_DE');
879
880
        $translatedPageWithoutOriginal = new SiteTree();
881
        $translatedPageWithoutOriginal->ParentID = $translatedParentPage->ID;
882
        $translatedPageWithoutOriginal->Locale = 'de_DE';
883
        $translatedPageWithoutOriginal->write();
884
885
        Translatable::set_current_locale('de_DE');
886
        $this->assertEquals(
887
            $translatedParentPage->stageChildren()->column('ID'),
888
            array(
889
                $translatedPageWithoutOriginal->ID
890
            ),
891
            "Children() still works on a translated page even if no translation group is set"
892
        );
893
894
        Translatable::set_current_locale('en_US');
895
    }
896
897
    public function testCreateTranslationTranslatesUntranslatedParents()
898
    {
899
        $parentPage = $this->objFromFixture(Page::class, 'parent');
900
        $child1Page = $this->objFromFixture(Page::class, 'child1');
901
        $child1PageOrigID = $child1Page->ID;
0 ignored issues
show
Unused Code introduced by
$child1PageOrigID 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...
902
        $grandChild1Page = $this->objFromFixture(Page::class, 'grandchild1');
903
        $grandChild2Page = $this->objFromFixture(Page::class, 'grandchild2');
904
905
        $this->assertFalse($grandChild1Page->hasTranslation('de_DE'));
906
        $this->assertFalse($child1Page->hasTranslation('de_DE'));
907
        $this->assertFalse($parentPage->hasTranslation('de_DE'));
908
909
        $translatedGrandChild1Page = $grandChild1Page->createTranslation('de_DE');
910
        $translatedGrandChild2Page = $grandChild2Page->createTranslation('de_DE');
911
        $translatedChildPage = $child1Page->getTranslation('de_DE');
912
        $translatedParentPage = $parentPage->getTranslation('de_DE');
913
914
        $this->assertTrue($grandChild1Page->hasTranslation('de_DE'));
915
        $this->assertEquals($translatedGrandChild1Page->ParentID, $translatedChildPage->ID);
916
917
        $this->assertTrue($grandChild2Page->hasTranslation('de_DE'));
918
        $this->assertEquals($translatedGrandChild2Page->ParentID, $translatedChildPage->ID);
919
920
        $this->assertTrue($child1Page->hasTranslation('de_DE'));
921
        $this->assertEquals($translatedChildPage->ParentID, $translatedParentPage->ID);
922
923
        $this->assertTrue($parentPage->hasTranslation('de_DE'));
924
    }
925
926
    public function testHierarchyAllChildrenIncludingDeleted()
927
    {
928
        // Original tree in 'en_US':
929
        //   parent
930
        //    child1 (Live only, deleted from stage)
931
        //    child2 (Stage only, never published)
932
        //    child3 (Stage only, never published, untranslated)
933
        // Translated tree in 'de_DE':
934
        //   parent
935
        //    child1 (Live only, deleted from stage)
936
        //    child2 (Stage only)
937
938
        // Create parent
939
        $parentPage = $this->objFromFixture(Page::class, 'parent');
940
        $parentPageID = $parentPage->ID;
0 ignored issues
show
Unused Code introduced by
$parentPageID 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...
941
942
        // Create parent translation
943
        $translatedParentPage = $parentPage->createTranslation('de_DE');
944
        $translatedParentPageID = $translatedParentPage->ID;
0 ignored issues
show
Unused Code introduced by
$translatedParentPageID 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...
945
946
        // Create child1
947
        $child1Page = $this->objFromFixture(Page::class, 'child1');
948
        $child1PageID = $child1Page->ID;
949
        $child1Page->copyVersionToStage('Stage', 'Live');
950
951
        // Create child1 translation
952
        $child1PageTranslated = $child1Page->createTranslation('de_DE');
953
        $child1PageTranslatedID = $child1PageTranslated->ID;
954
        $child1PageTranslated->copyVersionToStage('Stage', 'Live');
955
        $child1PageTranslated->deleteFromStage('Stage'); // deleted from stage only, record still exists on live
956
        $child1Page->deleteFromStage('Stage'); // deleted from stage only, record still exists on live
957
958
        // Create child2
959
        $child2Page = $this->objFromFixture(Page::class, 'child2');
960
        $child2PageID = $child2Page->ID;
961
962
        // Create child2 translation
963
        $child2PageTranslated = $child2Page->createTranslation('de_DE');
964
        $child2PageTranslatedID = $child2PageTranslated->ID;
965
966
        // Create child3
967
        $child3Page = $this->objFromFixture(Page::class, 'child3');
968
        $child3PageID = $child3Page->ID;
969
970
        // on original parent in default language
971
        Translatable::set_current_locale('en_US');
972
        SiteTree::flush_and_destroy_cache();
973
        $parentPage = $this->objFromFixture(Page::class, 'parent');
974
        $children = $parentPage->AllChildrenIncludingDeleted();
0 ignored issues
show
Bug introduced by
The method AllChildrenIncludingDeleted() does not exist on SilverStripe\ORM\DataObject. Did you maybe mean delete()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
Unused Code introduced by
$children 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...
975
        $this->assertArrayEqualsAfterSort(
976
            array(
977
                $child2PageID,
978
                $child3PageID,
979
                $child1PageID // $child1Page was deleted from stage, so the original record doesn't have the ID set
980
            ),
981
            $parentPage->AllChildrenIncludingDeleted()->column('ID'),
0 ignored issues
show
Bug introduced by
The method AllChildrenIncludingDeleted() does not exist on SilverStripe\ORM\DataObject. Did you maybe mean delete()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
982
            "Showing AllChildrenIncludingDeleted() in default language doesnt show deleted children in other languages"
983
        );
984
985
        // on original parent in translation mode
986
        Translatable::set_current_locale('de_DE');
987
        SiteTree::flush_and_destroy_cache();
988
        $parentPage = $this->objFromFixture(Page::class, 'parent');
0 ignored issues
show
Unused Code introduced by
$parentPage 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...
989
        $this->assertEquals(
990
            $translatedParentPage->AllChildrenIncludingDeleted()->column('ID'),
991
            array(
992
                $child2PageTranslatedID,
993
                // $child1PageTranslated was deleted from stage, so the original record doesn't have the ID set
994
                $child1PageTranslatedID
995
            ),
996
            "Showing AllChildrenIncludingDeleted() in translation mode with parent page in " .
997
            "translated language shows children in translated language"
998
        );
999
1000
        Translatable::set_current_locale('de_DE');
1001
        SiteTree::flush_and_destroy_cache();
1002
        $parentPage = $this->objFromFixture(Page::class, 'parent');
1003
        $this->assertEquals(
1004
            $parentPage->AllChildrenIncludingDeleted()->column('ID'),
0 ignored issues
show
Bug introduced by
The method AllChildrenIncludingDeleted() does not exist on SilverStripe\ORM\DataObject. Did you maybe mean delete()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
1005
            array(),
1006
            "Showing AllChildrenIncludingDeleted() in translation mode with parent page in " .
1007
            "translated language shows children in default language"
1008
        );
1009
1010
        // reset language
1011
        Translatable::set_current_locale('en_US');
1012
    }
1013
1014
    public function testRootUrlDefaultsToTranslatedLink()
1015
    {
1016
        $origPage = $this->objFromFixture(Page::class, 'homepage_en');
1017
        $origPage->copyVersionToStage('Stage', 'Live');
1018
        $translationDe = $origPage->createTranslation('de_DE');
1019
        $translationDe->URLSegment = 'heim';
1020
        $translationDe->write();
1021
        $translationDe->copyVersionToStage('Stage', 'Live');
1022
1023
        // test with translatable
1024
        Translatable::set_current_locale('de_DE');
1025
        $this->assertEquals(
1026
            RootURLController::get_homepage_link(),
1027
            'heim',
1028
            'Homepage with different URLSegment in non-default language is found'
1029
        );
1030
1031
        // @todo Fix add/remove extension
1032
        // test with translatable disabled
1033
        // Object::remove_extension(Page::class, 'Translatable');
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1034
        // 		$_SERVER['HTTP_HOST'] = '/';
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1035
        // 		$this->assertEquals(
1036
        // 			RootURLController::get_homepage_urlsegment(),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1037
        // 			'home',
1038
        // 			'Homepage is showing in default language if ?lang GET variable is left out'
1039
        // 		);
1040
        // 		Object::add_extension(Page::class, 'Translatable');
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1041
1042
        // setting back to default
1043
        Translatable::set_current_locale('en_US');
1044
    }
1045
1046
    public function testSiteTreeChangePageTypeInMaster()
1047
    {
1048
        // create original
1049
        $origPage = new SiteTree();
1050
        $origPage->Locale = 'en_US';
1051
        $origPage->write();
1052
        $origPageID = $origPage->ID;
1053
1054
        // create translation
1055
        $translatedPage = $origPage->createTranslation('de_DE');
1056
        $translatedPageID = $translatedPage->ID;
1057
1058
        // change page type
1059
        $newPage = $origPage->newClassInstance(RedirectorPage::class);
1060
        $newPage->write();
1061
1062
        // re-fetch original page with new instance
1063
        $origPageChanged = DataObject::get_by_id(RedirectorPage::class, $origPageID);
1064
        $this->assertEquals(
1065
            $origPageChanged->ClassName,
1066
            RedirectorPage::class,
1067
            'A ClassName change to an original page doesnt change original classname'
1068
        );
1069
1070
        // re-fetch the translation with new instance
1071
        Translatable::set_current_locale('de_DE');
1072
        $translatedPageChanged = DataObject::get_by_id(RedirectorPage::class, $translatedPageID);
0 ignored issues
show
Unused Code introduced by
$translatedPageChanged 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...
1073
        $translatedPageChanged = $origPageChanged->getTranslation('de_DE');
1074
        $this->assertEquals(
1075
            $translatedPageChanged->ClassName,
1076
            RedirectorPage::class,
1077
            'ClassName change on an original page also changes ClassName attribute of translation'
1078
        );
1079
    }
1080
1081
    public function testGetTranslationByStage()
1082
    {
1083
        Versioned::set_stage('Stage');
1084
        $publishedPage = new SiteTree();
1085
        $publishedPage->Locale = 'en_US';
1086
        $publishedPage->Title = 'Published';
1087
        $publishedPage->write();
1088
        $publishedPage->copyVersionToStage('Stage', 'Live');
1089
        $publishedPage->Title = 'Unpublished';
1090
        $publishedPage->write();
1091
1092
        $publishedTranslatedPage = $publishedPage->createTranslation('de_DE');
1093
        $publishedTranslatedPage->Title = 'Publiziert';
1094
        $publishedTranslatedPage->write();
1095
        $publishedTranslatedPage->copyVersionToStage('Stage', 'Live');
1096
        $publishedTranslatedPage->Title = 'Unpubliziert';
1097
        $publishedTranslatedPage->write();
1098
1099
        $compareStage = $publishedPage->getTranslation('de_DE', 'Stage');
1100
        $this->assertNotNull($compareStage);
1101
        $this->assertEquals($compareStage->Title, 'Unpubliziert');
1102
1103
        $compareLive = $publishedPage->getTranslation('de_DE', 'Live');
1104
        $this->assertNotNull($compareLive);
1105
        $this->assertEquals($compareLive->Title, 'Publiziert');
1106
    }
1107
1108
    public function testCanTranslateAllowedLocales()
1109
    {
1110
        $origAllowedLocales = Translatable::get_allowed_locales();
1111
1112
        $cmseditor = $this->objFromFixture(Member::class, 'cmseditor');
1113
1114
        $testPage = $this->objFromFixture(Page::class, 'testpage_en');
1115
        $this->assertTrue(
1116
            $testPage->canTranslate($cmseditor, 'de_DE'),
1117
            "Users with canEdit() and TRANSLATE_ALL permission can create a new translation if locales are not limited"
1118
        );
1119
1120
        Translatable::set_allowed_locales(array('ja_JP'));
1121
        $this->assertTrue(
1122
            $testPage->canTranslate($cmseditor, 'ja_JP'),
1123
            "Users with canEdit() and TRANSLATE_ALL permission can create a new translation " .
1124
            "if locale is in Translatable::get_allowed_locales()"
1125
        );
1126
        $this->assertFalse(
1127
            $testPage->canTranslate($cmseditor, 'de_DE'),
1128
            "Users with canEdit() and TRANSLATE_ALL permission can't create a new translation if " .
1129
            "locale is not in Translatable::get_allowed_locales()"
1130
        );
1131
1132
        $this->assertInstanceOf(
1133
            'Page',
1134
            $testPage->createTranslation('ja_JP')
1135
        );
1136
        try {
1137
            $testPage->createTranslation('de_DE');
1138
            $this->setExpectedException(Exception::class);
0 ignored issues
show
Deprecated Code introduced by
The method PHPUnit_Framework_TestCase::setExpectedException() has been deprecated with message: Method deprecated since Release 5.2.0; use expectException() instead

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1139
        } catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1140
        }
1141
1142
        Translatable::set_allowed_locales($origAllowedLocales);
1143
    }
1144
1145
    public function testCanTranslatePermissionCodes()
1146
    {
1147
        $origAllowedLocales = Translatable::get_allowed_locales();
1148
1149
        Translatable::set_allowed_locales(array('ja_JP', 'de_DE'));
1150
1151
        $cmseditor = $this->objFromFixture(Member::class, 'cmseditor');
1152
1153
        $testPage = $this->objFromFixture(Page::class, 'testpage_en');
1154
        $this->assertTrue(
1155
            $testPage->canTranslate($cmseditor, 'de_DE'),
1156
            "Users with TRANSLATE_ALL permission can create a new translation"
1157
        );
1158
1159
        $translator = $this->objFromFixture(Member::class, 'germantranslator');
1160
1161
        $testPage = $this->objFromFixture(Page::class, 'testpage_en');
1162
        $this->assertTrue(
1163
            $testPage->canTranslate($translator, 'de_DE'),
1164
            "Users with TRANSLATE_<locale> permission can create a new translation"
1165
        );
1166
1167
        $this->assertFalse(
1168
            $testPage->canTranslate($translator, 'ja_JP'),
1169
            "Users without TRANSLATE_<locale> permission can create a new translation"
1170
        );
1171
1172
        Translatable::set_allowed_locales($origAllowedLocales);
1173
    }
1174
1175
    public function testLocalesForMember()
1176
    {
1177
        $origAllowedLocales = Translatable::get_allowed_locales();
1178
        Translatable::set_allowed_locales(array('de_DE', 'ja_JP'));
1179
1180
        $cmseditor = $this->objFromFixture(Member::class, 'cmseditor');
1181
        $translator = $this->objFromFixture(Member::class, 'germantranslator');
1182
1183
        $this->assertEquals(
1184
            array('de_DE', 'ja_JP'),
1185
            singleton(SiteTree::class)->getAllowedLocalesForMember($cmseditor),
1186
            'Members with TRANSLATE_ALL permission can edit all locales'
1187
        );
1188
1189
        $this->assertEquals(
1190
            array('de_DE'),
1191
            singleton(SiteTree::class)->getAllowedLocalesForMember($translator),
1192
            'Members with TRANSLATE_<locale> permission cant edit all locales'
1193
        );
1194
1195
        Translatable::set_allowed_locales($origAllowedLocales);
1196
    }
1197
1198
    public function testSavePageInCMS()
1199
    {
1200
        $adminUser = $this->objFromFixture(Member::class, 'admin');
1201
        $enPage = $this->objFromFixture(Page::class, 'testpage_en');
1202
1203
        $group = new Group();
1204
        $group->Title = 'Example Group';
1205
        $group->write();
1206
1207
        $frPage = $enPage->createTranslation('fr_FR');
1208
        $frPage->write();
1209
1210
        $adminUser->logIn();
1211
1212
        $cmsMain = new CMSPageEditController();
1213
        $cmsMain->setRequest(Controller::curr()->getRequest());
1214
1215
        $origLocale = Translatable::get_current_locale();
1216
        Translatable::set_current_locale('fr_FR');
1217
1218
        $form = $cmsMain->getEditForm($frPage->ID);
1219
        $form->loadDataFrom(array(
1220
            'Title' => 'Translated', // $db field
1221
        ));
1222
        $form->saveInto($frPage);
1223
        $frPage->write();
1224
1225
        $this->assertEquals('Translated', $frPage->Title);
1226
1227
        $adminUser->logOut();
1228
        Translatable::set_current_locale($origLocale);
1229
    }
1230
1231
    public function testAlternateGetByLink()
1232
    {
1233
        $parent     = $this->objFromFixture(Page::class, 'parent');
1234
        $child      = $this->objFromFixture(Page::class, 'child1');
1235
        $grandchild = $this->objFromFixture(Page::class, 'grandchild1');
1236
1237
        $parentTranslation = $parent->createTranslation('en_AU');
1238
        $parentTranslation->write();
1239
1240
        $childTranslation = $child->createTranslation('en_AU');
1241
        $childTranslation->write();
1242
1243
        $grandchildTranslation = $grandchild->createTranslation('en_AU');
1244
        $grandchildTranslation->write();
1245
1246
        Translatable::set_current_locale('en_AU');
1247
1248
        $this->assertEquals(
1249
            $parentTranslation->ID,
1250
            SiteTree::get_by_link($parentTranslation->Link())->ID,
1251
            'Top level pages can be found.'
1252
        );
1253
1254
        $this->assertEquals(
1255
            $childTranslation->ID,
1256
            SiteTree::get_by_link($childTranslation->Link())->ID,
1257
            'Child pages can be found.'
1258
        );
1259
1260
        $this->assertEquals(
1261
            $grandchildTranslation->ID,
1262
            SiteTree::get_by_link($grandchildTranslation->Link())->ID,
1263
            'Grandchild pages can be found.'
1264
        );
1265
1266
        // TODO Re-enable test after clarifying with ajshort (see r88503).
1267
        // Its unclear if this is valid behaviour, and/or necessary for translated nested URLs
1268
        // to work properly
1269
        //
1270
        // $this->assertEquals (
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1271
        // 	$child->ID,
1272
        // 	SiteTree::get_by_link($parentTranslation->Link($child->URLSegment))->ID,
0 ignored issues
show
Unused Code Comprehensibility introduced by
65% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1273
        // 	'Links can be made up of multiple languages'
1274
        // );
1275
    }
1276
1277 View Code Duplication
    public function testSiteTreeGetByLinkFindsTranslationWithoutLocale()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1278
    {
1279
        $parent = $this->objFromFixture(Page::class, 'parent');
1280
1281
        $parentTranslation = $parent->createTranslation('en_AU');
1282
        $parentTranslation->URLSegment = 'parent-en-AU';
1283
        $parentTranslation->write();
1284
1285
        $match = SiteTree::get_by_link($parentTranslation->URLSegment);
1286
        $this->assertNotNull(
1287
            $match,
1288
            'SiteTree::get_by_link() doesnt need a locale setting to find translated pages'
1289
        );
1290
        $this->assertEquals(
1291
            $parentTranslation->ID,
1292
            $match->ID,
1293
            'SiteTree::get_by_link() doesnt need a locale setting to find translated pages'
1294
        );
1295
    }
1296
}
1297