Completed
Push — master ( 17ddfa...362143 )
by Ingo
15:55 queued 07:54
created

testGlobalVariableCallsWithArguments()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 0
dl 0
loc 13
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\View\Tests;
4
5
use SilverStripe\Control\Controller;
6
use SilverStripe\Control\Director;
7
use SilverStripe\Control\ContentNegotiator;
8
use SilverStripe\Control\HTTPResponse;
9
use SilverStripe\Core\Convert;
10
use SilverStripe\Core\Injector\Injector;
11
use SilverStripe\Dev\SapphireTest;
12
use SilverStripe\i18n\i18n;
13
use SilverStripe\ORM\DataObject;
14
use SilverStripe\ORM\FieldType\DBField;
15
use SilverStripe\ORM\ArrayList;
16
use SilverStripe\ORM\PaginatedList;
17
use SilverStripe\Security\Member;
18
use SilverStripe\Security\SecurityToken;
19
use SilverStripe\Security\Permission;
20
use SilverStripe\View\ArrayData;
21
use SilverStripe\View\Requirements_Backend;
22
use SilverStripe\View\Requirements_Minifier;
23
use SilverStripe\View\SSViewer;
24
use SilverStripe\View\Requirements;
25
use SilverStripe\View\Tests\SSViewerTest\SSViewerTestModel;
26
use SilverStripe\View\Tests\SSViewerTest\SSViewerTestModelController;
27
use SilverStripe\View\ViewableData;
28
use SilverStripe\View\SSViewer_FromString;
29
use SilverStripe\View\SSTemplateParser;
30
use SilverStripe\Assets\Tests\Storage\AssetStoreTest\TestAssetStore;
31
use Exception;
32
33
class SSViewerTest extends SapphireTest
34
{
35
36
    /**
37
     * Backup of $_SERVER global
38
     *
39
     * @var array
40
     */
41
    protected $oldServer = array();
42
43
    protected static $extra_dataobjects = array(
44
        SSViewerTest\TestObject::class,
45
    );
46
47
    protected function setUp()
0 ignored issues
show
Coding Style introduced by
setUp uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
48
    {
49
        parent::setUp();
50
        SSViewer::config()->update('source_file_comments', false);
51
        SSViewer_FromString::config()->update('cache_template', false);
52
        TestAssetStore::activate('SSViewerTest');
53
        $this->oldServer = $_SERVER;
54
    }
55
56
    protected function tearDown()
0 ignored issues
show
Coding Style introduced by
tearDown uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
57
    {
58
        $_SERVER = $this->oldServer;
59
        TestAssetStore::reset();
60
        parent::tearDown();
61
    }
62
63
    /**
64
     * Tests for {@link Config::inst()->get('SSViewer', 'theme')} for different behaviour
65
     * of user defined themes via {@link SiteConfig} and default theme
66
     * when no user themes are defined.
67
     */
68
    public function testCurrentTheme()
69
    {
70
        SSViewer::config()->update('theme', 'mytheme');
71
        $this->assertEquals(
72
        'mytheme',
73
        SSViewer::config()->uninherited('theme'),
74
        'Current theme is the default - user has not defined one'
75
        );
76
    }
77
78
    /**
79
     * Test that a template without a <head> tag still renders.
80
     */
81
    public function testTemplateWithoutHeadRenders()
82
    {
83
        $data = new ArrayData(
84
        array(
85
        'Var' => 'var value'
86
        )
87
        );
88
89
        $result = $data->renderWith("SSViewerTestPartialTemplate");
90
        $this->assertEquals('Test partial template: var value', trim(preg_replace("/<!--.*-->/U", '', $result)));
91
    }
92
93
    public function testIncludeScopeInheritance()
94
    {
95
        $data = $this->getScopeInheritanceTestData();
96
        $expected = array(
97
        'Item 1 - First-ODD top:Item 1',
98
        'Item 2 - EVEN top:Item 2',
99
        'Item 3 - ODD top:Item 3',
100
        'Item 4 - EVEN top:Item 4',
101
        'Item 5 - ODD top:Item 5',
102
        'Item 6 - Last-EVEN top:Item 6',
103
        );
104
105
        $result = $data->renderWith('SSViewerTestIncludeScopeInheritance');
106
        $this->assertExpectedStrings($result, $expected);
107
108
        // reset results for the tests that include arguments (the title is passed as an arg)
109
        $expected = array(
110
        'Item 1 _ Item 1 - First-ODD top:Item 1',
111
        'Item 2 _ Item 2 - EVEN top:Item 2',
112
        'Item 3 _ Item 3 - ODD top:Item 3',
113
        'Item 4 _ Item 4 - EVEN top:Item 4',
114
        'Item 5 _ Item 5 - ODD top:Item 5',
115
        'Item 6 _ Item 6 - Last-EVEN top:Item 6',
116
        );
117
118
        $result = $data->renderWith('SSViewerTestIncludeScopeInheritanceWithArgs');
119
        $this->assertExpectedStrings($result, $expected);
120
    }
121
122
    public function testIncludeTruthyness()
123
    {
124
        $data = new ArrayData(
125
        array(
126
        'Title' => 'TruthyTest',
127
        'Items' => new ArrayList(
128
            array(
129
            new ArrayData(array('Title' => 'Item 1')),
130
            new ArrayData(array('Title' => '')),
131
            new ArrayData(array('Title' => true)),
132
            new ArrayData(array('Title' => false)),
133
            new ArrayData(array('Title' => null)),
134
            new ArrayData(array('Title' => 0)),
135
            new ArrayData(array('Title' => 7))
136
            )
137
        )
138
        )
139
        );
140
        $result = $data->renderWith('SSViewerTestIncludeScopeInheritanceWithArgs');
141
142
        // We should not end up with empty values appearing as empty
143
        $expected = array(
144
        'Item 1 _ Item 1 - First-ODD top:Item 1',
145
        'Untitled - EVEN top:',
146
        '1 _ 1 - ODD top:1',
147
        'Untitled - EVEN top:',
148
        'Untitled - ODD top:',
149
        'Untitled - EVEN top:0',
150
        '7 _ 7 - Last-ODD top:7'
151
        );
152
        $this->assertExpectedStrings($result, $expected);
153
    }
154
155
    private function getScopeInheritanceTestData()
156
    {
157
        return new ArrayData(
158
        array(
159
        'Title' => 'TopTitleValue',
160
        'Items' => new ArrayList(
161
            array(
162
            new ArrayData(array('Title' => 'Item 1')),
163
            new ArrayData(array('Title' => 'Item 2')),
164
            new ArrayData(array('Title' => 'Item 3')),
165
            new ArrayData(array('Title' => 'Item 4')),
166
            new ArrayData(array('Title' => 'Item 5')),
167
            new ArrayData(array('Title' => 'Item 6'))
168
            )
169
        )
170
        )
171
        );
172
    }
173
174
    private function assertExpectedStrings($result, $expected)
175
    {
176
        foreach ($expected as $expectedStr) {
177
            $this->assertTrue(
178
            (boolean) preg_match("/{$expectedStr}/", $result),
179
            "Didn't find '{$expectedStr}' in:\n{$result}"
180
            );
181
        }
182
    }
183
184
    /**
185
     * Small helper to render templates from strings
186
     *
187
     * @param  string $templateString
188
     * @param  null   $data
189
     * @param  bool   $cacheTemplate
190
     * @return string
191
     */
192
    public function render($templateString, $data = null, $cacheTemplate = false)
193
    {
194
        $t = SSViewer::fromString($templateString, $cacheTemplate);
195
        if (!$data) {
196
            $data = new SSViewerTest\TestFixture();
197
        }
198
        return trim(''.$t->process($data));
199
    }
200
201
    public function testRequirements()
202
    {
203
        $requirements = $this->getMockBuilder(Requirements_Backend::class)->setMethods(array("javascript", "css"))
204
        ->getMock();
205
        $jsFile = FRAMEWORK_DIR . '/tests/forms/a.js';
206
        $cssFile = FRAMEWORK_DIR . '/tests/forms/a.js';
207
208
        $requirements->expects($this->once())->method('javascript')->with($jsFile);
209
        $requirements->expects($this->once())->method('css')->with($cssFile);
210
211
        $origReq = Requirements::backend();
212
        Requirements::set_backend($requirements);
213
        $template = $this->render(
214
        "<% require javascript($jsFile) %>
215
		<% require css($cssFile) %>"
216
        );
217
        Requirements::set_backend($origReq);
218
219
        $this->assertFalse((bool)trim($template), "Should be no content in this return.");
220
    }
221
222
    public function testRequirementsCombine()
223
    {
224
        $testBackend = Injector::inst()->create(Requirements_Backend::class);
225
        $testBackend->setSuffixRequirements(false);
226
        //$combinedTestFilePath = BASE_PATH . '/' . $testBackend->getCombinedFilesFolder() . '/testRequirementsCombine.js';
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% 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...
227
228
        $jsFile = $this->getCurrentRelativePath() . '/SSViewerTest/javascript/bad.js';
229
        $jsFileContents = file_get_contents(BASE_PATH . '/' . $jsFile);
230
        $testBackend->combineFiles('testRequirementsCombine.js', array($jsFile));
231
232
        // secondly, make sure that requirements is generated, even though minification failed
233
        $testBackend->processCombinedFiles();
234
        $js = array_keys($testBackend->getJavascript());
235
        $combinedTestFilePath = BASE_PATH . reset($js);
236
        $this->assertContains('_combinedfiles/testRequirementsCombine-4c0e97a.js', $combinedTestFilePath);
237
238
        // and make sure the combined content matches the input content, i.e. no loss of functionality
239
        if (!file_exists($combinedTestFilePath)) {
240
            $this->fail('No combined file was created at expected path: '.$combinedTestFilePath);
241
        }
242
        $combinedTestFileContents = file_get_contents($combinedTestFilePath);
243
        $this->assertContains($jsFileContents, $combinedTestFileContents);
244
    }
245
246
    public function testRequirementsMinification()
247
    {
248
        $testBackend = Injector::inst()->create(Requirements_Backend::class);
249
        $testBackend->setSuffixRequirements(false);
250
        $testBackend->setMinifyCombinedFiles(true);
251
        $testFile = $this->getCurrentRelativePath() . '/SSViewerTest/javascript/RequirementsTest_a.js';
252
        $testFileContent = file_get_contents($testFile);
253
254
        $mockMinifier = $this->getMockBuilder(Requirements_Minifier::class)
255
        ->setMethods(['minify'])
256
        ->getMock();
257
258
        $mockMinifier->expects($this->once())
259
        ->method('minify')
260
        ->with(
261
            $testFileContent,
262
            'js',
263
            $testFile
264
        );
265
        $testBackend->setMinifier($mockMinifier);
266
        $testBackend->combineFiles('testRequirementsMinified.js', array($testFile));
267
        $testBackend->processCombinedFiles();
268
269
        $testBackend->setMinifyCombinedFiles(false);
270
        $mockMinifier->expects($this->never())
271
        ->method('minify');
272
        $testBackend->processCombinedFiles();
273
274
        $this->setExpectedExceptionRegExp(
275
        Exception::class,
276
        '/minification service/'
277
        );
278
279
        $testBackend->setMinifyCombinedFiles(true);
280
        $testBackend->setMinifier(null);
281
        $testBackend->processCombinedFiles();
282
    }
283
284
285
286
    public function testComments()
287
    {
288
        $output = $this->render(
289
        <<<SS
290
This is my template<%-- this is a comment --%>This is some content<%-- this is another comment --%>Final content
291
<%-- Alone multi
292
	line comment --%>
293
Some more content
294
Mixing content and <%-- multi
295
	line comment --%> Final final
296
content
297
SS
298
        );
299
        $shouldbe = <<<SS
300
This is my templateThis is some contentFinal content
301
302
Some more content
303
Mixing content and  Final final
304
content
305
SS;
306
307
        $this->assertEquals($shouldbe, $output);
308
    }
309
310
    public function testBasicText()
311
    {
312
        $this->assertEquals('"', $this->render('"'), 'Double-quotes are left alone');
313
        $this->assertEquals("'", $this->render("'"), 'Single-quotes are left alone');
314
        $this->assertEquals('A', $this->render('\\A'), 'Escaped characters are unescaped');
315
        $this->assertEquals('\\A', $this->render('\\\\A'), 'Escaped back-slashed are correctly unescaped');
316
    }
317
318
    public function testBasicInjection()
319
    {
320
        $this->assertEquals('[out:Test]', $this->render('$Test'), 'Basic stand-alone injection');
321
        $this->assertEquals('[out:Test]', $this->render('{$Test}'), 'Basic stand-alone wrapped injection');
322
        $this->assertEquals('A[out:Test]!', $this->render('A$Test!'), 'Basic surrounded injection');
323
        $this->assertEquals('A[out:Test]B', $this->render('A{$Test}B'), 'Basic surrounded wrapped injection');
324
325
        $this->assertEquals('A$B', $this->render('A\\$B'), 'No injection as $ escaped');
326
        $this->assertEquals('A$ B', $this->render('A$ B'), 'No injection as $ not followed by word character');
327
        $this->assertEquals('A{$ B', $this->render('A{$ B'), 'No injection as {$ not followed by word character');
328
329
        $this->assertEquals('{$Test}', $this->render('{\\$Test}'), 'Escapes can be used to avoid injection');
330
        $this->assertEquals(
331
        '{\\[out:Test]}',
332
        $this->render('{\\\\$Test}'),
333
        'Escapes before injections are correctly unescaped'
334
        );
335
    }
336
337
338
    public function testGlobalVariableCalls()
339
    {
340
        $this->assertEquals('automatic', $this->render('$SSViewerTest_GlobalAutomatic'));
341
        $this->assertEquals('reference', $this->render('$SSViewerTest_GlobalReferencedByString'));
342
        $this->assertEquals('reference', $this->render('$SSViewerTest_GlobalReferencedInArray'));
343
    }
344
345
    public function testGlobalVariableCallsWithArguments()
346
    {
347
        $this->assertEquals('zz', $this->render('$SSViewerTest_GlobalThatTakesArguments'));
348
        $this->assertEquals('zFooz', $this->render('$SSViewerTest_GlobalThatTakesArguments("Foo")'));
349
        $this->assertEquals(
350
        'zFoo:Bar:Bazz',
351
        $this->render('$SSViewerTest_GlobalThatTakesArguments("Foo", "Bar", "Baz")')
352
        );
353
        $this->assertEquals(
354
        'zreferencez',
355
        $this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalReferencedByString)')
356
        );
357
    }
358
359
    public function testGlobalVariablesAreEscaped()
360
    {
361
        $this->assertEquals('<div></div>', $this->render('$SSViewerTest_GlobalHTMLFragment'));
362
        $this->assertEquals('&lt;div&gt;&lt;/div&gt;', $this->render('$SSViewerTest_GlobalHTMLEscaped'));
363
364
        $this->assertEquals(
365
        'z<div></div>z',
366
        $this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLFragment)')
367
        );
368
        $this->assertEquals(
369
        'z&lt;div&gt;&lt;/div&gt;z',
370
        $this->render('$SSViewerTest_GlobalThatTakesArguments($SSViewerTest_GlobalHTMLEscaped)')
371
        );
372
    }
373
374
    public function testCoreGlobalVariableCalls()
375
    {
376
        $this->assertEquals(
377
        Director::absoluteBaseURL(),
378
        $this->render('{$absoluteBaseURL}'),
379
        'Director::absoluteBaseURL can be called from within template'
380
        );
381
        $this->assertEquals(
382
        Director::absoluteBaseURL(),
383
        $this->render('{$AbsoluteBaseURL}'),
384
        'Upper-case %AbsoluteBaseURL can be called from within template'
385
        );
386
387
        $this->assertEquals(
388
        Director::is_ajax(),
389
        $this->render('{$isAjax}'),
390
        'All variations of is_ajax result in the correct call'
391
        );
392
        $this->assertEquals(
393
        Director::is_ajax(),
394
        $this->render('{$IsAjax}'),
395
        'All variations of is_ajax result in the correct call'
396
        );
397
        $this->assertEquals(
398
        Director::is_ajax(),
399
        $this->render('{$is_ajax}'),
400
        'All variations of is_ajax result in the correct call'
401
        );
402
        $this->assertEquals(
403
        Director::is_ajax(),
404
        $this->render('{$Is_ajax}'),
405
        'All variations of is_ajax result in the correct call'
406
        );
407
408
        $this->assertEquals(
409
        i18n::get_locale(),
410
        $this->render('{$i18nLocale}'),
411
        'i18n template functions result correct result'
412
        );
413
        $this->assertEquals(
414
        i18n::get_locale(),
415
        $this->render('{$get_locale}'),
416
        'i18n template functions result correct result'
417
        );
418
419
        $this->assertEquals(
420
        (string)Member::currentUser(),
421
        $this->render('{$CurrentMember}'),
422
        'Member template functions result correct result'
423
        );
424
        $this->assertEquals(
425
        (string)Member::currentUser(),
426
        $this->render('{$CurrentUser}'),
427
        'Member template functions result correct result'
428
        );
429
        $this->assertEquals(
430
        (string)Member::currentUser(),
431
        $this->render('{$currentMember}'),
432
        'Member template functions result correct result'
433
        );
434
        $this->assertEquals(
435
        (string)Member::currentUser(),
436
        $this->render('{$currentUser}'),
437
        'Member template functions result correct result'
438
        );
439
440
        $this->assertEquals(
441
        SecurityToken::getSecurityID(),
442
        $this->render('{$getSecurityID}'),
443
        'SecurityToken template functions result correct result'
444
        );
445
        $this->assertEquals(
446
        SecurityToken::getSecurityID(),
447
        $this->render('{$SecurityID}'),
448
        'SecurityToken template functions result correct result'
449
        );
450
451
        $this->assertEquals(
452
        Permission::check("ADMIN"),
453
        (bool)$this->render('{$HasPerm(\'ADMIN\')}'),
454
        'Permissions template functions result correct result'
455
        );
456
        $this->assertEquals(
457
        Permission::check("ADMIN"),
458
        (bool)$this->render('{$hasPerm(\'ADMIN\')}'),
459
        'Permissions template functions result correct result'
460
        );
461
    }
462
463
    public function testNonFieldCastingHelpersNotUsedInHasValue()
464
    {
465
        // check if Link without $ in front of variable
466
        $result = $this->render(
467
        'A<% if Link %>$Link<% end_if %>B',
468
        new SSViewerTest\TestObject()
469
        );
470
        $this->assertEquals('Asome/url.htmlB', $result, 'casting helper not used for <% if Link %>');
471
472
        // check if Link with $ in front of variable
473
        $result = $this->render(
474
        'A<% if $Link %>$Link<% end_if %>B',
475
        new SSViewerTest\TestObject()
476
        );
477
        $this->assertEquals('Asome/url.htmlB', $result, 'casting helper not used for <% if $Link %>');
478
    }
479
480
    public function testLocalFunctionsTakePriorityOverGlobals()
481
    {
482
        $data = new ArrayData(
483
        array(
484
        'Page' => new SSViewerTest\TestObject()
485
        )
486
        );
487
488
        //call method with lots of arguments
489
        $result = $this->render(
490
        '<% with Page %>$lotsOfArguments11("a","b","c","d","e","f","g","h","i","j","k")<% end_with %>',
491
        $data
492
        );
493
        $this->assertEquals("abcdefghijk", $result, "public function can accept up to 11 arguments");
494
495
        //call method that does not exist
496
        $result = $this->render('<% with Page %><% if IDoNotExist %>hello<% end_if %><% end_with %>', $data);
497
        $this->assertEquals("", $result, "Method does not exist - empty result");
498
499
        //call if that does not exist
500
        $result = $this->render('<% with Page %>$IDoNotExist("hello")<% end_with %>', $data);
501
        $this->assertEquals("", $result, "Method does not exist - empty result");
502
503
        //call method with same name as a global method (local call should take priority)
504
        $result = $this->render('<% with Page %>$absoluteBaseURL<% end_with %>', $data);
505
        $this->assertEquals(
506
        "testLocalFunctionPriorityCalled",
507
        $result,
508
        "Local Object's public function called. Did not return the actual baseURL of the current site"
509
        );
510
    }
511
512
    public function testCurrentScopeLoopWith()
513
    {
514
        // Data to run the loop tests on - one sequence of three items, each with a subitem
515
        $data = new ArrayData(
516
        array(
517
        'Foo' => new ArrayList(
518
            array(
519
            'Subocean' => new ArrayData(
520
                array(
521
                    'Name' => 'Higher'
522
                )
523
            ),
524
            new ArrayData(
525
                array(
526
                'Sub' => new ArrayData(
527
                    array(
528
                    'Name' => 'SubKid1'
529
                    )
530
                )
531
                )
532
            ),
533
            new ArrayData(
534
                array(
535
                'Sub' => new ArrayData(
536
                    array(
537
                    'Name' => 'SubKid2'
538
                    )
539
                )
540
                )
541
            ),
542
            new SSViewerTest\TestObject('Number6')
543
            )
544
        )
545
        )
546
        );
547
548
        $result = $this->render(
549
        '<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>',
550
        $data
551
        );
552
        $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop works");
553
554
        $result = $this->render(
555
        '<% loop Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %><% end_if %><% end_loop %>',
556
        $data
557
        );
558
        $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop works");
559
560
        $result = $this->render('<% with Foo %>$Count<% end_with %>', $data);
561
        $this->assertEquals("4", $result, "4 items in the DataObjectSet");
562
563
        $result = $this->render(
564
        '<% with Foo %><% loop Up.Foo %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>'
565
        . '<% end_if %><% end_loop %><% end_with %>',
566
        $data
567
        );
568
        $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop in with Up.Foo scope works");
569
570
        $result = $this->render(
571
        '<% with Foo %><% loop %>$Number<% if Sub %><% with Sub %>$Name<% end_with %>'
572
        . '<% end_if %><% end_loop %><% end_with %>',
573
        $data
574
        );
575
        $this->assertEquals("SubKid1SubKid2Number6", $result, "Loop in current scope works");
576
    }
577
578
    public function testObjectDotArguments()
579
    {
580
        $this->assertEquals(
581
        '[out:TestObject.methodWithOneArgument(one)]
582
				[out:TestObject.methodWithTwoArguments(one,two)]
583
				[out:TestMethod(Arg1,Arg2).Bar.Val]
584
				[out:TestMethod(Arg1,Arg2).Bar]
585
				[out:TestMethod(Arg1,Arg2)]
586
				[out:TestMethod(Arg1).Bar.Val]
587
				[out:TestMethod(Arg1).Bar]
588
				[out:TestMethod(Arg1)]',
589
        $this->render(
590
            '$TestObject.methodWithOneArgument(one)
591
				$TestObject.methodWithTwoArguments(one,two)
592
				$TestMethod(Arg1, Arg2).Bar.Val
593
				$TestMethod(Arg1, Arg2).Bar
594
				$TestMethod(Arg1, Arg2)
595
				$TestMethod(Arg1).Bar.Val
596
				$TestMethod(Arg1).Bar
597
				$TestMethod(Arg1)'
598
        )
599
        );
600
    }
601
602
    public function testEscapedArguments()
603
    {
604
        $this->assertEquals(
605
        '[out:Foo(Arg1,Arg2).Bar.Val].Suffix
606
				[out:Foo(Arg1,Arg2).Val]_Suffix
607
				[out:Foo(Arg1,Arg2)]/Suffix
608
				[out:Foo(Arg1).Bar.Val]textSuffix
609
				[out:Foo(Arg1).Bar].Suffix
610
				[out:Foo(Arg1)].Suffix
611
				[out:Foo.Bar.Val].Suffix
612
				[out:Foo.Bar].Suffix
613
				[out:Foo].Suffix',
614
        $this->render(
615
            '{$Foo(Arg1, Arg2).Bar.Val}.Suffix
616
				{$Foo(Arg1, Arg2).Val}_Suffix
617
				{$Foo(Arg1, Arg2)}/Suffix
618
				{$Foo(Arg1).Bar.Val}textSuffix
619
				{$Foo(Arg1).Bar}.Suffix
620
				{$Foo(Arg1)}.Suffix
621
				{$Foo.Bar.Val}.Suffix
622
				{$Foo.Bar}.Suffix
623
				{$Foo}.Suffix'
624
        )
625
        );
626
    }
627
628
    public function testLoopWhitespace()
629
    {
630
        $this->assertEquals(
631
        'before[out:SingleItem.Test]after
632
				beforeTestafter',
633
        $this->render(
634
            'before<% loop SingleItem %>$Test<% end_loop %>after
635
				before<% loop SingleItem %>Test<% end_loop %>after'
636
        )
637
        );
638
639
        // The control tags are removed from the output, but no whitespace
640
        // This is a quirk that could be changed, but included in the test to make the current
641
        // behaviour explicit
642
        $this->assertEquals(
643
        'before
644
645
[out:SingleItem.ItemOnItsOwnLine]
646
647
after',
648
        $this->render(
649
            'before
650
<% loop SingleItem %>
651
$ItemOnItsOwnLine
652
<% end_loop %>
653
after'
654
        )
655
        );
656
657
        // The whitespace within the control tags is preserve in a loop
658
        // This is a quirk that could be changed, but included in the test to make the current
659
        // behaviour explicit
660
        $this->assertEquals(
661
        'before
662
663
[out:Loop3.ItemOnItsOwnLine]
664
665
[out:Loop3.ItemOnItsOwnLine]
666
667
[out:Loop3.ItemOnItsOwnLine]
668
669
after',
670
        $this->render(
671
            'before
672
<% loop Loop3 %>
673
$ItemOnItsOwnLine
674
<% end_loop %>
675
after'
676
        )
677
        );
678
    }
679
680
    public function testControls()
681
    {
682
        // Single item controls
683
        $this->assertEquals(
684
        'a[out:Foo.Bar.Item]b
685
				[out:Foo.Bar(Arg1).Item]
686
				[out:Foo(Arg1).Item]
687
				[out:Foo(Arg1,Arg2).Item]
688
				[out:Foo(Arg1,Arg2,Arg3).Item]',
689
        $this->render(
690
            '<% with Foo.Bar %>a{$Item}b<% end_with %>
691
				<% with Foo.Bar(Arg1) %>$Item<% end_with %>
692
				<% with Foo(Arg1) %>$Item<% end_with %>
693
				<% with Foo(Arg1, Arg2) %>$Item<% end_with %>
694
				<% with Foo(Arg1, Arg2, Arg3) %>$Item<% end_with %>'
695
        )
696
        );
697
698
        // Loop controls
699
        $this->assertEquals(
700
        'a[out:Foo.Loop2.Item]ba[out:Foo.Loop2.Item]b',
701
        $this->render('<% loop Foo.Loop2 %>a{$Item}b<% end_loop %>')
702
        );
703
704
        $this->assertEquals(
705
        '[out:Foo.Loop2(Arg1).Item][out:Foo.Loop2(Arg1).Item]',
706
        $this->render('<% loop Foo.Loop2(Arg1) %>$Item<% end_loop %>')
707
        );
708
709
        $this->assertEquals(
710
        '[out:Loop2(Arg1).Item][out:Loop2(Arg1).Item]',
711
        $this->render('<% loop Loop2(Arg1) %>$Item<% end_loop %>')
712
        );
713
714
        $this->assertEquals(
715
        '[out:Loop2(Arg1,Arg2).Item][out:Loop2(Arg1,Arg2).Item]',
716
        $this->render('<% loop Loop2(Arg1, Arg2) %>$Item<% end_loop %>')
717
        );
718
719
        $this->assertEquals(
720
        '[out:Loop2(Arg1,Arg2,Arg3).Item][out:Loop2(Arg1,Arg2,Arg3).Item]',
721
        $this->render('<% loop Loop2(Arg1, Arg2, Arg3) %>$Item<% end_loop %>')
722
        );
723
    }
724
725
    public function testIfBlocks()
726
    {
727
        // Basic test
728
        $this->assertEquals(
729
        'AC',
730
        $this->render('A<% if NotSet %>B$NotSet<% end_if %>C')
731
        );
732
733
        // Nested test
734
        $this->assertEquals(
735
        'AB1C',
736
        $this->render('A<% if IsSet %>B$NotSet<% if IsSet %>1<% else %>2<% end_if %><% end_if %>C')
737
        );
738
739
        // else_if
740
        $this->assertEquals(
741
        'ACD',
742
        $this->render('A<% if NotSet %>B<% else_if IsSet %>C<% end_if %>D')
743
        );
744
        $this->assertEquals(
745
        'AD',
746
        $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% end_if %>D')
747
        );
748
        $this->assertEquals(
749
        'ADE',
750
        $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E')
751
        );
752
753
        $this->assertEquals(
754
        'ADE',
755
        $this->render('A<% if NotSet %>B<% else_if AlsoNotset %>C<% else_if IsSet %>D<% end_if %>E')
756
        );
757
758
        // Dot syntax
759
        $this->assertEquals(
760
        'ACD',
761
        $this->render('A<% if Foo.NotSet %>B<% else_if Foo.IsSet %>C<% end_if %>D')
762
        );
763
        $this->assertEquals(
764
        'ACD',
765
        $this->render('A<% if Foo.Bar.NotSet %>B<% else_if Foo.Bar.IsSet %>C<% end_if %>D')
766
        );
767
768
        // Params
769
        $this->assertEquals(
770
        'ACD',
771
        $this->render('A<% if NotSet(Param) %>B<% else %>C<% end_if %>D')
772
        );
773
        $this->assertEquals(
774
        'ABD',
775
        $this->render('A<% if IsSet(Param) %>B<% else %>C<% end_if %>D')
776
        );
777
778
        // Negation
779
        $this->assertEquals(
780
        'AC',
781
        $this->render('A<% if not IsSet %>B<% end_if %>C')
782
        );
783
        $this->assertEquals(
784
        'ABC',
785
        $this->render('A<% if not NotSet %>B<% end_if %>C')
786
        );
787
788
        // Or
789
        $this->assertEquals(
790
        'ABD',
791
        $this->render('A<% if IsSet || NotSet %>B<% else_if A %>C<% end_if %>D')
792
        );
793
        $this->assertEquals(
794
        'ACD',
795
        $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet %>C<% end_if %>D')
796
        );
797
        $this->assertEquals(
798
        'AD',
799
        $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet3 %>C<% end_if %>D')
800
        );
801
        $this->assertEquals(
802
        'ACD',
803
        $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if IsSet || NotSet %>C<% end_if %>D')
804
        );
805
        $this->assertEquals(
806
        'AD',
807
        $this->render('A<% if NotSet || AlsoNotSet %>B<% else_if NotSet2 || NotSet3 %>C<% end_if %>D')
808
        );
809
810
        // Negated Or
811
        $this->assertEquals(
812
        'ACD',
813
        $this->render('A<% if not IsSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D')
814
        );
815
        $this->assertEquals(
816
        'ABD',
817
        $this->render('A<% if not NotSet || AlsoNotSet %>B<% else_if A %>C<% end_if %>D')
818
        );
819
        $this->assertEquals(
820
        'ABD',
821
        $this->render('A<% if NotSet || not AlsoNotSet %>B<% else_if A %>C<% end_if %>D')
822
        );
823
824
        // And
825
        $this->assertEquals(
826
        'ABD',
827
        $this->render('A<% if IsSet && AlsoSet %>B<% else_if A %>C<% end_if %>D')
828
        );
829
        $this->assertEquals(
830
        'ACD',
831
        $this->render('A<% if IsSet && NotSet %>B<% else_if IsSet %>C<% end_if %>D')
832
        );
833
        $this->assertEquals(
834
        'AD',
835
        $this->render('A<% if NotSet && NotSet2 %>B<% else_if NotSet3 %>C<% end_if %>D')
836
        );
837
        $this->assertEquals(
838
        'ACD',
839
        $this->render('A<% if IsSet && NotSet %>B<% else_if IsSet && AlsoSet %>C<% end_if %>D')
840
        );
841
        $this->assertEquals(
842
        'AD',
843
        $this->render('A<% if NotSet && NotSet2 %>B<% else_if IsSet && NotSet3 %>C<% end_if %>D')
844
        );
845
846
        // Equality
847
        $this->assertEquals(
848
        'ABC',
849
        $this->render('A<% if RawVal == RawVal %>B<% end_if %>C')
850
        );
851
        $this->assertEquals(
852
        'ACD',
853
        $this->render('A<% if Right == Wrong %>B<% else_if RawVal == RawVal %>C<% end_if %>D')
854
        );
855
        $this->assertEquals(
856
        'ABC',
857
        $this->render('A<% if Right != Wrong %>B<% end_if %>C')
858
        );
859
        $this->assertEquals(
860
        'AD',
861
        $this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% end_if %>D')
862
        );
863
864
        // test inequalities with simple numbers
865
        $this->assertEquals('ABD', $this->render('A<% if 5 > 3 %>B<% else %>C<% end_if %>D'));
866
        $this->assertEquals('ABD', $this->render('A<% if 5 >= 3 %>B<% else %>C<% end_if %>D'));
867
        $this->assertEquals('ACD', $this->render('A<% if 3 > 5 %>B<% else %>C<% end_if %>D'));
868
        $this->assertEquals('ACD', $this->render('A<% if 3 >= 5 %>B<% else %>C<% end_if %>D'));
869
870
        $this->assertEquals('ABD', $this->render('A<% if 3 < 5 %>B<% else %>C<% end_if %>D'));
871
        $this->assertEquals('ABD', $this->render('A<% if 3 <= 5 %>B<% else %>C<% end_if %>D'));
872
        $this->assertEquals('ACD', $this->render('A<% if 5 < 3 %>B<% else %>C<% end_if %>D'));
873
        $this->assertEquals('ACD', $this->render('A<% if 5 <= 3 %>B<% else %>C<% end_if %>D'));
874
875
        $this->assertEquals('ABD', $this->render('A<% if 4 <= 4 %>B<% else %>C<% end_if %>D'));
876
        $this->assertEquals('ABD', $this->render('A<% if 4 >= 4 %>B<% else %>C<% end_if %>D'));
877
        $this->assertEquals('ACD', $this->render('A<% if 4 > 4 %>B<% else %>C<% end_if %>D'));
878
        $this->assertEquals('ACD', $this->render('A<% if 4 < 4 %>B<% else %>C<% end_if %>D'));
879
880
        // empty else_if and else tags, if this would not be supported,
881
        // the output would stop after A, thereby failing the assert
882
        $this->assertEquals('AD', $this->render('A<% if IsSet %><% else %><% end_if %>D'));
883
        $this->assertEquals(
884
        'AD',
885
        $this->render('A<% if NotSet %><% else_if IsSet %><% else %><% end_if %>D')
886
        );
887
        $this->assertEquals(
888
        'AD',
889
        $this->render('A<% if NotSet %><% else_if AlsoNotSet %><% else %><% end_if %>D')
890
        );
891
892
        // Bare words with ending space
893
        $this->assertEquals(
894
        'ABC',
895
        $this->render('A<% if "RawVal" == RawVal %>B<% end_if %>C')
896
        );
897
898
        // Else
899
        $this->assertEquals(
900
        'ADE',
901
        $this->render('A<% if Right == Wrong %>B<% else_if RawVal != RawVal %>C<% else %>D<% end_if %>E')
902
        );
903
904
        // Empty if with else
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% 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...
905
        $this->assertEquals(
906
        'ABC',
907
        $this->render('A<% if NotSet %><% else %>B<% end_if %>C')
908
        );
909
    }
910
911
    public function testBaseTagGeneration()
912
    {
913
        // XHTML wil have a closed base tag
914
        $tmpl1 = '<?xml version="1.0" encoding="UTF-8"?>
915
			<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'
916
            . ' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
917
			<html>
918
				<head><% base_tag %></head>
919
				<body><p>test</p><body>
920
			</html>';
921
        $this->assertRegExp('/<head><base href=".*" \/><\/head>/', $this->render($tmpl1));
922
923
        // HTML4 and 5 will only have it for IE
924
        $tmpl2 = '<!DOCTYPE html>
925
			<html>
926
				<head><% base_tag %></head>
927
				<body><p>test</p><body>
928
			</html>';
929
        $this->assertRegExp(
930
        '/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/',
931
        $this->render($tmpl2)
932
        );
933
934
935
        $tmpl3 = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
936
			<html>
937
				<head><% base_tag %></head>
938
				<body><p>test</p><body>
939
			</html>';
940
        $this->assertRegExp(
941
        '/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/',
942
        $this->render($tmpl3)
943
        );
944
945
        // Check that the content negotiator converts to the equally legal formats
946
        $negotiator = new ContentNegotiator();
947
948
        $response = new HTTPResponse($this->render($tmpl1));
949
        $negotiator->html($response);
950
        $this->assertRegExp(
951
        '/<head><base href=".*"><!--\[if lte IE 6\]><\/base><!\[endif\]--><\/head>/',
952
        $response->getBody()
953
        );
954
955
        $response = new HTTPResponse($this->render($tmpl1));
956
        $negotiator->xhtml($response);
957
        $this->assertRegExp('/<head><base href=".*" \/><\/head>/', $response->getBody());
958
    }
959
960
    public function testIncludeWithArguments()
961
    {
962
        $this->assertEquals(
963
        $this->render('<% include SSViewerTestIncludeWithArguments %>'),
964
        '<p>[out:Arg1]</p><p>[out:Arg2]</p>'
965
        );
966
967
        $this->assertEquals(
968
        $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A %>'),
969
        '<p>A</p><p>[out:Arg2]</p>'
970
        );
971
972
        $this->assertEquals(
973
        $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A, Arg2=B %>'),
974
        '<p>A</p><p>B</p>'
975
        );
976
977
        $this->assertEquals(
978
        $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A Bare String, Arg2=B Bare String %>'),
979
        '<p>A Bare String</p><p>B Bare String</p>'
980
        );
981
982
        $this->assertEquals(
983
        $this->render(
984
            '<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=$B %>',
985
            new ArrayData(array('B' => 'Bar'))
986
        ),
987
        '<p>A</p><p>Bar</p>'
988
        );
989
990
        $this->assertEquals(
991
        $this->render(
992
            '<% include SSViewerTestIncludeWithArguments Arg1="A" %>',
993
            new ArrayData(array('Arg1' => 'Foo', 'Arg2' => 'Bar'))
994
        ),
995
        '<p>A</p><p>Bar</p>'
996
        );
997
998
        $this->assertEquals(
999
        $this->render(
1000
            '<% include SSViewerTestIncludeScopeInheritanceWithArgsInLoop Title="SomeArg" %>',
1001
            new ArrayData(
1002
                array('Items' => new ArrayList(
1003
                    array(
1004
                    new ArrayData(array('Title' => 'Foo')),
1005
                    new ArrayData(array('Title' => 'Bar'))
1006
                    )
1007
                ))
1008
            )
1009
        ),
1010
        'SomeArg - Foo - Bar - SomeArg'
1011
        );
1012
1013
        $this->assertEquals(
1014
        $this->render(
1015
            '<% include SSViewerTestIncludeScopeInheritanceWithArgsInWith Title="A" %>',
1016
            new ArrayData(array('Item' => new ArrayData(array('Title' =>'B'))))
1017
        ),
1018
        'A - B - A'
1019
        );
1020
1021
        $this->assertEquals(
1022
        $this->render(
1023
            '<% include SSViewerTestIncludeScopeInheritanceWithArgsInNestedWith Title="A" %>',
1024
            new ArrayData(
1025
                array(
1026
                'Item' => new ArrayData(
1027
                    array(
1028
                    'Title' =>'B', 'NestedItem' => new ArrayData(array('Title' => 'C'))
1029
                    )
1030
                ))
1031
            )
1032
        ),
1033
        'A - B - C - B - A'
1034
        );
1035
1036
        $this->assertEquals(
1037
        $this->render(
1038
            '<% include SSViewerTestIncludeScopeInheritanceWithUpAndTop Title="A" %>',
1039
            new ArrayData(
1040
                array(
1041
                'Item' => new ArrayData(
1042
                    array(
1043
                    'Title' =>'B', 'NestedItem' => new ArrayData(array('Title' => 'C'))
1044
                    )
1045
                ))
1046
            )
1047
        ),
1048
        'A - A - A'
1049
        );
1050
1051
        $data = new ArrayData(
1052
        array(
1053
        'Nested' => new ArrayData(
1054
            array(
1055
            'Object' => new ArrayData(array('Key' => 'A'))
1056
            )
1057
        ),
1058
        'Object' => new ArrayData(array('Key' => 'B'))
1059
        )
1060
        );
1061
1062
        $tmpl = SSViewer::fromString('<% include SSViewerTestIncludeObjectArguments A=$Nested.Object, B=$Object %>');
1063
        $res  = $tmpl->process($data);
1064
        $this->assertEqualIgnoringWhitespace('A B', $res, 'Objects can be passed as named arguments');
1065
    }
1066
1067
    public function testNamespaceInclude()
1068
    {
1069
        $data = new ArrayData([]);
1070
1071
        $this->assertEquals(
1072
        "tests:( NamespaceInclude\n )",
1073
        $this->render('tests:( <% include Namespace\NamespaceInclude %> )', $data),
1074
        'Backslashes work for namespace references in includes'
1075
        );
1076
1077
        $this->assertEquals(
1078
        "tests:( NamespaceInclude\n )",
1079
        $this->render('tests:( <% include Namespace/NamespaceInclude %> )', $data),
1080
        'Forward slashes work for namespace references in includes'
1081
        );
1082
    }
1083
1084
1085
    public function testRecursiveInclude()
1086
    {
1087
        $view = new SSViewer(array('Includes/SSViewerTestRecursiveInclude'));
1088
1089
        $data = new ArrayData(
1090
        array(
1091
        'Title' => 'A',
1092
        'Children' => new ArrayList(
1093
            array(
1094
            new ArrayData(
1095
                array(
1096
                'Title' => 'A1',
1097
                'Children' => new ArrayList(
1098
                    array(
1099
                    new ArrayData(array( 'Title' => 'A1 i', )),
1100
                    new ArrayData(array( 'Title' => 'A1 ii', )),
1101
                    )
1102
                ),
1103
                )
1104
            ),
1105
            new ArrayData(array( 'Title' => 'A2', )),
1106
            new ArrayData(array( 'Title' => 'A3', )),
1107
            )
1108
        ),
1109
        )
1110
        );
1111
1112
        $result = $view->process($data);
1113
        // We don't care about whitespace
1114
        $rationalisedResult = trim(preg_replace('/\s+/', ' ', $result));
1115
1116
        $this->assertEquals('A A1 A1 i A1 ii A2 A3', $rationalisedResult);
1117
    }
1118
1119
    public function assertEqualIgnoringWhitespace($a, $b, $message = '')
1120
    {
1121
        $this->assertEquals(preg_replace('/\s+/', '', $a), preg_replace('/\s+/', '', $b), $message);
1122
    }
1123
1124
    /**
1125
     * See {@link ViewableDataTest} for more extensive casting tests,
1126
     * this test just ensures that basic casting is correctly applied during template parsing.
1127
     */
1128
    public function testCastingHelpers()
1129
    {
1130
        $vd = new SSViewerTest\TestViewableData();
1131
        $vd->TextValue = '<b>html</b>';
0 ignored issues
show
Documentation introduced by
The property TextValue does not exist on object<SilverStripe\View...rTest\TestViewableData>. 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...
1132
        $vd->HTMLValue = '<b>html</b>';
0 ignored issues
show
Documentation introduced by
The property HTMLValue does not exist on object<SilverStripe\View...rTest\TestViewableData>. 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...
1133
        $vd->UncastedValue = '<b>html</b>';
0 ignored issues
show
Documentation introduced by
The property UncastedValue does not exist on object<SilverStripe\View...rTest\TestViewableData>. 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...
1134
1135
        // Value casted as "Text"
1136
        $this->assertEquals(
1137
        '&lt;b&gt;html&lt;/b&gt;',
1138
        $t = SSViewer::fromString('$TextValue')->process($vd)
1139
        );
1140
        $this->assertEquals(
1141
        '<b>html</b>',
1142
        $t = SSViewer::fromString('$TextValue.RAW')->process($vd)
1143
        );
1144
        $this->assertEquals(
1145
        '&lt;b&gt;html&lt;/b&gt;',
1146
        $t = SSViewer::fromString('$TextValue.XML')->process($vd)
1147
        );
1148
1149
        // Value casted as "HTMLText"
1150
        $this->assertEquals(
1151
        '<b>html</b>',
1152
        $t = SSViewer::fromString('$HTMLValue')->process($vd)
1153
        );
1154
        $this->assertEquals(
1155
        '<b>html</b>',
1156
        $t = SSViewer::fromString('$HTMLValue.RAW')->process($vd)
1157
        );
1158
        $this->assertEquals(
1159
        '&lt;b&gt;html&lt;/b&gt;',
1160
        $t = SSViewer::fromString('$HTMLValue.XML')->process($vd)
1161
        );
1162
1163
        // Uncasted value (falls back to ViewableData::$default_cast="Text")
1164
        $vd = new SSViewerTest\TestViewableData();
1165
        $vd->UncastedValue = '<b>html</b>';
0 ignored issues
show
Documentation introduced by
The property UncastedValue does not exist on object<SilverStripe\View...rTest\TestViewableData>. 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...
1166
        $this->assertEquals(
1167
        '&lt;b&gt;html&lt;/b&gt;',
1168
        $t = SSViewer::fromString('$UncastedValue')->process($vd)
1169
        );
1170
        $this->assertEquals(
1171
        '<b>html</b>',
1172
        $t = SSViewer::fromString('$UncastedValue.RAW')->process($vd)
1173
        );
1174
        $this->assertEquals(
1175
        '&lt;b&gt;html&lt;/b&gt;',
1176
        $t = SSViewer::fromString('$UncastedValue.XML')->process($vd)
1177
        );
1178
    }
1179
1180
    public function testSSViewerBasicIteratorSupport()
1181
    {
1182
        $data = new ArrayData(
1183
        array(
1184
        'Set' => new ArrayList(
1185
            array(
1186
            new SSViewerTest\TestObject("1"),
1187
            new SSViewerTest\TestObject("2"),
1188
            new SSViewerTest\TestObject("3"),
1189
            new SSViewerTest\TestObject("4"),
1190
            new SSViewerTest\TestObject("5"),
1191
            new SSViewerTest\TestObject("6"),
1192
            new SSViewerTest\TestObject("7"),
1193
            new SSViewerTest\TestObject("8"),
1194
            new SSViewerTest\TestObject("9"),
1195
            new SSViewerTest\TestObject("10"),
1196
            )
1197
        )
1198
        )
1199
        );
1200
1201
        //base test
1202
        $result = $this->render('<% loop Set %>$Number<% end_loop %>', $data);
1203
        $this->assertEquals("12345678910", $result, "Numbers rendered in order");
1204
1205
        //test First
1206
        $result = $this->render('<% loop Set %><% if First %>$Number<% end_if %><% end_loop %>', $data);
1207
        $this->assertEquals("1", $result, "Only the first number is rendered");
1208
1209
        //test Last
1210
        $result = $this->render('<% loop Set %><% if Last %>$Number<% end_if %><% end_loop %>', $data);
1211
        $this->assertEquals("10", $result, "Only the last number is rendered");
1212
1213
        //test Even
1214
        $result = $this->render('<% loop Set %><% if Even() %>$Number<% end_if %><% end_loop %>', $data);
1215
        $this->assertEquals("246810", $result, "Even numbers rendered in order");
1216
1217
        //test Even with quotes
1218
        $result = $this->render('<% loop Set %><% if Even("1") %>$Number<% end_if %><% end_loop %>', $data);
1219
        $this->assertEquals("246810", $result, "Even numbers rendered in order");
1220
1221
        //test Even without quotes
1222
        $result = $this->render('<% loop Set %><% if Even(1) %>$Number<% end_if %><% end_loop %>', $data);
1223
        $this->assertEquals("246810", $result, "Even numbers rendered in order");
1224
1225
        //test Even with zero-based start index
1226
        $result = $this->render('<% loop Set %><% if Even("0") %>$Number<% end_if %><% end_loop %>', $data);
1227
        $this->assertEquals("13579", $result, "Even (with zero-based index) numbers rendered in order");
1228
1229
        //test Odd
1230
        $result = $this->render('<% loop Set %><% if Odd %>$Number<% end_if %><% end_loop %>', $data);
1231
        $this->assertEquals("13579", $result, "Odd numbers rendered in order");
1232
1233
        //test FirstLast
1234
        $result = $this->render('<% loop Set %><% if FirstLast %>$Number$FirstLast<% end_if %><% end_loop %>', $data);
1235
        $this->assertEquals("1first10last", $result, "First and last numbers rendered in order");
1236
1237
        //test Middle
1238
        $result = $this->render('<% loop Set %><% if Middle %>$Number<% end_if %><% end_loop %>', $data);
1239
        $this->assertEquals("23456789", $result, "Middle numbers rendered in order");
1240
1241
        //test MiddleString
1242
        $result = $this->render(
1243
        '<% loop Set %><% if MiddleString == "middle" %>$Number$MiddleString<% end_if %>'
1244
        . '<% end_loop %>',
1245
        $data
1246
        );
1247
        $this->assertEquals(
1248
        "2middle3middle4middle5middle6middle7middle8middle9middle",
1249
        $result,
1250
        "Middle numbers rendered in order"
1251
        );
1252
1253
        //test EvenOdd
1254
        $result = $this->render('<% loop Set %>$EvenOdd<% end_loop %>', $data);
1255
        $this->assertEquals(
1256
        "oddevenoddevenoddevenoddevenoddeven",
1257
        $result,
1258
        "Even and Odd is returned in sequence numbers rendered in order"
1259
        );
1260
1261
        //test Pos
1262
        $result = $this->render('<% loop Set %>$Pos<% end_loop %>', $data);
1263
        $this->assertEquals("12345678910", $result, '$Pos is rendered in order');
1264
1265
        //test Pos
1266
        $result = $this->render('<% loop Set %>$Pos(0)<% end_loop %>', $data);
1267
        $this->assertEquals("0123456789", $result, '$Pos(0) is rendered in order');
1268
1269
        //test FromEnd
1270
        $result = $this->render('<% loop Set %>$FromEnd<% end_loop %>', $data);
1271
        $this->assertEquals("10987654321", $result, '$FromEnd is rendered in order');
1272
1273
        //test FromEnd
1274
        $result = $this->render('<% loop Set %>$FromEnd(0)<% end_loop %>', $data);
1275
        $this->assertEquals("9876543210", $result, '$FromEnd(0) rendered in order');
1276
1277
        //test Total
1278
        $result = $this->render('<% loop Set %>$TotalItems<% end_loop %>', $data);
1279
        $this->assertEquals("10101010101010101010", $result, "10 total items X 10 are returned");
1280
1281
        //test Modulus
1282
        $result = $this->render('<% loop Set %>$Modulus(2,1)<% end_loop %>', $data);
1283
        $this->assertEquals("1010101010", $result, "1-indexed pos modular divided by 2 rendered in order");
1284
1285
        //test MultipleOf 3
1286
        $result = $this->render('<% loop Set %><% if MultipleOf(3) %>$Number<% end_if %><% end_loop %>', $data);
1287
        $this->assertEquals("369", $result, "Only numbers that are multiples of 3 are returned");
1288
1289
        //test MultipleOf 4
1290
        $result = $this->render('<% loop Set %><% if MultipleOf(4) %>$Number<% end_if %><% end_loop %>', $data);
1291
        $this->assertEquals("48", $result, "Only numbers that are multiples of 4 are returned");
1292
1293
        //test MultipleOf 5
1294
        $result = $this->render('<% loop Set %><% if MultipleOf(5) %>$Number<% end_if %><% end_loop %>', $data);
1295
        $this->assertEquals("510", $result, "Only numbers that are multiples of 5 are returned");
1296
1297
        //test MultipleOf 10
1298
        $result = $this->render('<% loop Set %><% if MultipleOf(10,1) %>$Number<% end_if %><% end_loop %>', $data);
1299
        $this->assertEquals("10", $result, "Only numbers that are multiples of 10 (with 1-based indexing) are returned");
1300
1301
        //test MultipleOf 9 zero-based
1302
        $result = $this->render('<% loop Set %><% if MultipleOf(9,0) %>$Number<% end_if %><% end_loop %>', $data);
1303
        $this->assertEquals(
1304
        "110",
1305
        $result,
1306
        "Only numbers that are multiples of 9 with zero-based indexing are returned. (The first and last item)"
1307
        );
1308
1309
        //test MultipleOf 11
1310
        $result = $this->render('<% loop Set %><% if MultipleOf(11) %>$Number<% end_if %><% end_loop %>', $data);
1311
        $this->assertEquals("", $result, "Only numbers that are multiples of 11 are returned. I.e. nothing returned");
1312
    }
1313
1314
    /**
1315
     * Test $Up works when the scope $Up refers to was entered with a "with" block
1316
     */
1317
    public function testUpInWith()
1318
    {
1319
1320
        // Data to run the loop tests on - three levels deep
1321
        $data = new ArrayData(
1322
        array(
1323
        'Name' => 'Top',
1324
        'Foo' => new ArrayData(
1325
            array(
1326
            'Name' => 'Foo',
1327
            'Bar' => new ArrayData(
1328
                array(
1329
                'Name' => 'Bar',
1330
                'Baz' => new ArrayData(
1331
                    array(
1332
                    'Name' => 'Baz'
1333
                    )
1334
                ),
1335
                'Qux' => new ArrayData(
1336
                    array(
1337
                    'Name' => 'Qux'
1338
                    )
1339
                )
1340
                )
1341
            )
1342
            )
1343
        )
1344
        )
1345
        );
1346
1347
        // Basic functionality
1348
        $this->assertEquals(
1349
        'BarFoo',
1350
        $this->render('<% with Foo %><% with Bar %>{$Name}{$Up.Name}<% end_with %><% end_with %>', $data)
1351
        );
1352
1353
        // Two level with block, up refers to internally referenced Bar
1354
        $this->assertEquals(
1355
        'BarFoo',
1356
        $this->render('<% with Foo.Bar %>{$Name}{$Up.Name}<% end_with %>', $data)
1357
        );
1358
1359
        // Stepping up & back down the scope tree
1360
        $this->assertEquals(
1361
        'BazBarQux',
1362
        $this->render('<% with Foo.Bar.Baz %>{$Name}{$Up.Name}{$Up.Qux.Name}<% end_with %>', $data)
1363
        );
1364
1365
        // Using $Up in a with block
1366
        $this->assertEquals(
1367
        'BazBarQux',
1368
        $this->render(
1369
            '<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}{$Qux.Name}<% end_with %>'
1370
            .'<% end_with %>',
1371
            $data
1372
        )
1373
        );
1374
1375
        // Stepping up & back down the scope tree with with blocks
1376
        $this->assertEquals(
1377
        'BazBarQuxBarBaz',
1378
        $this->render(
1379
            '<% with Foo.Bar.Baz %>{$Name}<% with $Up %>{$Name}<% with Qux %>{$Name}<% end_with %>'
1380
            . '{$Name}<% end_with %>{$Name}<% end_with %>',
1381
            $data
1382
        )
1383
        );
1384
1385
        // Using $Up.Up, where first $Up points to a previous scope entered using $Up, thereby skipping up to Foo
1386
        $this->assertEquals(
1387
        'Foo',
1388
        $this->render(
1389
            '<% with Foo.Bar.Baz %><% with Up %><% with Qux %>{$Up.Up.Name}<% end_with %><% end_with %>'
1390
            . '<% end_with %>',
1391
            $data
1392
        )
1393
        );
1394
1395
        // Using $Up.Up, where first $Up points to an Up used in a local scope lookup, should still skip to Foo
1396
        $this->assertEquals(
1397
        'Foo',
1398
        $this->render('<% with Foo.Bar.Baz.Up.Qux %>{$Up.Up.Name}<% end_with %>', $data)
1399
        );
1400
    }
1401
1402
    /**
1403
     * Test $Up works when the scope $Up refers to was entered with a "loop" block
1404
     */
1405
    public function testUpInLoop()
1406
    {
1407
1408
        // Data to run the loop tests on - one sequence of three items, each with a subitem
1409
        $data = new ArrayData(
1410
        array(
1411
        'Name' => 'Top',
1412
        'Foo' => new ArrayList(
1413
            array(
1414
            new ArrayData(
1415
                array(
1416
                'Name' => '1',
1417
                'Sub' => new ArrayData(
1418
                    array(
1419
                    'Name' => 'Bar'
1420
                    )
1421
                )
1422
                )
1423
            ),
1424
            new ArrayData(
1425
                array(
1426
                'Name' => '2',
1427
                'Sub' => new ArrayData(
1428
                    array(
1429
                    'Name' => 'Baz'
1430
                    )
1431
                )
1432
                )
1433
            ),
1434
            new ArrayData(
1435
                array(
1436
                'Name' => '3',
1437
                'Sub' => new ArrayData(
1438
                    array(
1439
                    'Name' => 'Qux'
1440
                    )
1441
                )
1442
                )
1443
            )
1444
            )
1445
        )
1446
        )
1447
        );
1448
1449
        // Make sure inside a loop, $Up refers to the current item of the loop
1450
        $this->assertEqualIgnoringWhitespace(
1451
        '111 222 333',
1452
        $this->render(
1453
            '<% loop $Foo %>$Name<% with $Sub %>$Up.Name<% end_with %>$Name<% end_loop %>',
1454
            $data
1455
        )
1456
        );
1457
1458
        // Make sure inside a loop, looping over $Up uses a separate iterator,
1459
        // and doesn't interfere with the original iterator
1460
        $this->assertEqualIgnoringWhitespace(
1461
        '1Bar123Bar1 2Baz123Baz2 3Qux123Qux3',
1462
        $this->render(
1463
            '<% loop $Foo %>
1464
					$Name
1465
					<% with $Sub %>
1466
						$Name
1467
						<% loop $Up %>$Name<% end_loop %>
1468
						$Name
1469
					<% end_with %>
1470
					$Name
1471
				<% end_loop %>',
1472
            $data
1473
        )
1474
        );
1475
1476
        // Make sure inside a loop, looping over $Up uses a separate iterator,
1477
        // and doesn't interfere with the original iterator or local lookups
1478
        $this->assertEqualIgnoringWhitespace(
1479
        '1 Bar1 123 1Bar 1   2 Baz2 123 2Baz 2   3 Qux3 123 3Qux 3',
1480
        $this->render(
1481
            '<% loop $Foo %>
1482
					$Name
1483
					<% with $Sub %>
1484
						{$Name}{$Up.Name}
1485
						<% loop $Up %>$Name<% end_loop %>
1486
						{$Up.Name}{$Name}
1487
					<% end_with %>
1488
					$Name
1489
				<% end_loop %>',
1490
            $data
1491
        )
1492
        );
1493
    }
1494
1495
    /**
1496
     * Test that nested loops restore the loop variables correctly when pushing and popping states
1497
     */
1498
    public function testNestedLoops()
1499
    {
1500
1501
        // Data to run the loop tests on - one sequence of three items, one with child elements
1502
        // (of a different size to the main sequence)
1503
        $data = new ArrayData(
1504
        array(
1505
        'Foo' => new ArrayList(
1506
            array(
1507
            new ArrayData(
1508
                array(
1509
                'Name' => '1',
1510
                'Children' => new ArrayList(
1511
                    array(
1512
                    new ArrayData(
1513
                        array(
1514
                        'Name' => 'a'
1515
                        )
1516
                    ),
1517
                    new ArrayData(
1518
                        array(
1519
                        'Name' => 'b'
1520
                        )
1521
                    ),
1522
                    )
1523
                ),
1524
                )
1525
            ),
1526
            new ArrayData(
1527
                array(
1528
                'Name' => '2',
1529
                'Children' => new ArrayList(),
1530
                )
1531
            ),
1532
            new ArrayData(
1533
                array(
1534
                'Name' => '3',
1535
                'Children' => new ArrayList(),
1536
                )
1537
            ),
1538
            )
1539
        ),
1540
        )
1541
        );
1542
1543
        // Make sure that including a loop inside a loop will not destroy the internal count of
1544
        // items, checked by using "Last"
1545
        $this->assertEqualIgnoringWhitespace(
1546
        '1ab23last',
1547
        $this->render(
1548
            '<% loop $Foo %>$Name<% loop Children %>$Name<% end_loop %><% if Last %>last<% end_if %>'
1549
            . '<% end_loop %>',
1550
            $data
1551
        )
1552
        );
1553
    }
1554
1555
    public function testLayout()
1556
    {
1557
        $this->useTestTheme(
1558
        __DIR__.'/SSViewerTest',
1559
        'layouttest',
1560
        function () {
1561
            $template = new SSViewer(array('Page'));
1562
            $this->assertEquals("Foo\n\n", $template->process(new ArrayData(array())));
1563
1564
            $template = new SSViewer(array('Shortcodes', 'Page'));
1565
            $this->assertEquals("[file_link]\n\n", $template->process(new ArrayData(array())));
1566
        }
1567
        );
1568
    }
1569
1570
    /**
1571
     * @covers \SilverStripe\View\SSViewer::get_templates_by_class()
1572
     */
1573
    public function testGetTemplatesByClass()
1574
    {
1575
        $this->useTestTheme(
1576
        __DIR__ . '/SSViewerTest',
1577
        'layouttest',
1578
        function () {
1579
            // Test passing a string
1580
            $templates = SSViewer::get_templates_by_class(
1581
                SSViewerTestModelController::class,
1582
                '',
1583
                Controller::class
1584
            );
1585
            $this->assertEquals(
1586
                [
1587
                    SSViewerTestModelController::class,
1588
                    [
1589
                        'type' => 'Includes',
1590
                        SSViewerTestModelController::class,
1591
                    ],
1592
                    SSViewerTestModel::class,
1593
                    Controller::class,
1594
                    [
1595
                        'type' => 'Includes',
1596
                        Controller::class,
1597
                    ],
1598
                ],
1599
                $templates
1600
            );
1601
1602
            // Test to ensure we're stopping at the base class.
1603
            $templates = SSViewer::get_templates_by_class(
1604
                SSViewerTestModelController::class,
1605
                '',
1606
                SSViewerTestModelController::class
1607
            );
1608
            $this->assertEquals(
1609
                [
1610
                    SSViewerTestModelController::class,
1611
                    [
1612
                        'type' => 'Includes',
1613
                        SSViewerTestModelController::class,
1614
                    ],
1615
                    SSViewerTestModel::class,
1616
                ],
1617
                $templates
1618
            );
1619
1620
            // Make sure we can search templates by suffix.
1621
            $templates = SSViewer::get_templates_by_class(
1622
                SSViewerTestModel::class,
1623
                'Controller',
1624
                DataObject::class
1625
            );
1626
            $this->assertEquals(
1627
                [
1628
                    SSViewerTestModelController::class,
1629
                    [
1630
                        'type' => 'Includes',
1631
                        SSViewerTestModelController::class,
1632
                    ],
1633
                    DataObject::class . 'Controller',
1634
                    [
1635
                        'type' => 'Includes',
1636
                        DataObject::class . 'Controller',
1637
                    ],
1638
                ],
1639
                $templates
1640
            );
1641
1642
            // Let's throw something random in there.
1643
            $this->setExpectedException('InvalidArgumentException');
1644
            SSViewer::get_templates_by_class(array());
1645
        }
1646
        );
1647
    }
1648
1649
    public function testRewriteHashlinks()
0 ignored issues
show
Coding Style introduced by
testRewriteHashlinks uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1650
    {
1651
        SSViewer::config()->update('rewrite_hash_links', true);
1652
1653
        $_SERVER['HTTP_HOST'] = 'www.mysite.com';
1654
        $_SERVER['REQUEST_URI'] = '//file.com?foo"onclick="alert(\'xss\')""';
1655
1656
        // Emulate SSViewer::process()
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% 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...
1657
        // Note that leading double slashes have been rewritten to prevent these being mis-interepreted
1658
        // as protocol-less absolute urls
1659
        $base = Convert::raw2att('/file.com?foo"onclick="alert(\'xss\')""');
1660
1661
        $tmplFile = TEMP_FOLDER . '/SSViewerTest_testRewriteHashlinks_' . sha1(rand()) . '.ss';
1662
1663
        // Note: SSViewer_FromString doesn't rewrite hash links.
1664
        file_put_contents(
1665
        $tmplFile,
1666
        '<!DOCTYPE html>
1667
			<html>
1668
				<head><% base_tag %></head>
1669
				<body>
1670
				<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>
1671
				$ExternalInsertedLink
1672
				<a class="inline" href="#anchor">InlineLink</a>
1673
				$InsertedLink
1674
				<svg><use xlink:href="#sprite"></use></svg>
1675
				<body>
1676
			</html>'
1677
        );
1678
        $tmpl = new SSViewer($tmplFile);
1679
        $obj = new ViewableData();
1680
        $obj->InsertedLink = DBField::create_field(
0 ignored issues
show
Documentation introduced by
The property InsertedLink does not exist on object<SilverStripe\View\ViewableData>. 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...
1681
        'HTMLFragment',
1682
        '<a class="inserted" href="#anchor">InsertedLink</a>'
1683
        );
1684
        $obj->ExternalInsertedLink = DBField::create_field(
0 ignored issues
show
Documentation introduced by
The property ExternalInsertedLink does not exist on object<SilverStripe\View\ViewableData>. 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...
1685
        'HTMLFragment',
1686
        '<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>'
1687
        );
1688
        $result = $tmpl->process($obj);
1689
        $this->assertContains(
1690
        '<a class="inserted" href="' . $base . '#anchor">InsertedLink</a>',
1691
        $result
1692
        );
1693
        $this->assertContains(
1694
        '<a class="external-inserted" href="http://google.com#anchor">ExternalInsertedLink</a>',
1695
        $result
1696
        );
1697
        $this->assertContains(
1698
        '<a class="inline" href="' . $base . '#anchor">InlineLink</a>',
1699
        $result
1700
        );
1701
        $this->assertContains(
1702
        '<a class="external-inline" href="http://google.com#anchor">ExternalInlineLink</a>',
1703
        $result
1704
        );
1705
        $this->assertContains(
1706
        '<svg><use xlink:href="#sprite"></use></svg>',
1707
        $result,
1708
        'SSTemplateParser should only rewrite anchor hrefs'
1709
        );
1710
1711
        unlink($tmplFile);
1712
    }
1713
1714
    public function testRewriteHashlinksInPhpMode()
1715
    {
1716
        SSViewer::config()->update('rewrite_hash_links', 'php');
1717
1718
        $tmplFile = TEMP_FOLDER . '/SSViewerTest_testRewriteHashlinksInPhpMode_' . sha1(rand()) . '.ss';
1719
1720
        // Note: SSViewer_FromString doesn't rewrite hash links.
1721
        file_put_contents(
1722
        $tmplFile,
1723
        '<!DOCTYPE html>
1724
			<html>
1725
				<head><% base_tag %></head>
1726
				<body>
1727
				<a class="inline" href="#anchor">InlineLink</a>
1728
				$InsertedLink
1729
				<svg><use xlink:href="#sprite"></use></svg>
1730
				<body>
1731
			</html>'
1732
        );
1733
        $tmpl = new SSViewer($tmplFile);
1734
        $obj = new ViewableData();
1735
        $obj->InsertedLink = DBField::create_field(
0 ignored issues
show
Documentation introduced by
The property InsertedLink does not exist on object<SilverStripe\View\ViewableData>. 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...
1736
        'HTMLFragment',
1737
        '<a class="inserted" href="#anchor">InsertedLink</a>'
1738
        );
1739
        $result = $tmpl->process($obj);
1740
1741
        $code = <<<'EOC'
1742
<a class="inserted" href="<?php echo \SilverStripe\Core\Convert::raw2att(preg_replace("/^(\/)+/", "/", $_SERVER['REQUEST_URI'])); ?>#anchor">InsertedLink</a>
1743
EOC;
1744
        $this->assertContains($code, $result);
1745
        // TODO Fix inline links in PHP mode
1746
        // $this->assertContains(
1747
        // 	'<a class="inline" href="<?php echo str_replace(',
1748
        // 	$result
1749
        // );
1750
        $this->assertContains(
1751
        '<svg><use xlink:href="#sprite"></use></svg>',
1752
        $result,
1753
        'SSTemplateParser should only rewrite anchor hrefs'
1754
        );
1755
1756
        unlink($tmplFile);
1757
    }
1758
1759
    public function testRenderWithSourceFileComments()
1760
    {
1761
        Director::set_environment_type('dev');
1762
        SSViewer::config()->update('source_file_comments', true);
1763
        $i = __DIR__ . '/SSViewerTest/templates/Includes';
1764
        $f = __DIR__ . '/SSViewerTest/templates/SSViewerTestComments';
1765
        $templates = array(
1766
        array(
1767
            'name' => 'SSViewerTestCommentsFullSource',
1768
            'expected' => ""
1769
                . "<!doctype html>"
1770
                . "<!-- template $f/SSViewerTestCommentsFullSource.ss -->"
1771
                . "<html>"
1772
                . "\t<head></head>"
1773
                . "\t<body></body>"
1774
                . "</html>"
1775
                . "<!-- end template $f/SSViewerTestCommentsFullSource.ss -->",
1776
        ),
1777
        array(
1778
            'name' => 'SSViewerTestCommentsFullSourceHTML4Doctype',
1779
            'expected' => ""
1780
                . "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML "
1781
                . "4.01//EN\"\t\t\"http://www.w3.org/TR/html4/strict.dtd\">"
1782
                . "<!-- template $f/SSViewerTestCommentsFullSourceHTML4Doctype.ss -->"
1783
                . "<html>"
1784
                . "\t<head></head>"
1785
                . "\t<body></body>"
1786
                . "</html>"
1787
                . "<!-- end template $f/SSViewerTestCommentsFullSourceHTML4Doctype.ss -->",
1788
        ),
1789
        array(
1790
            'name' => 'SSViewerTestCommentsFullSourceNoDoctype',
1791
            'expected' => ""
1792
                . "<html><!-- template $f/SSViewerTestCommentsFullSourceNoDoctype.ss -->"
1793
                . "\t<head></head>"
1794
                . "\t<body></body>"
1795
                . "<!-- end template $f/SSViewerTestCommentsFullSourceNoDoctype.ss --></html>",
1796
        ),
1797
        array(
1798
            'name' => 'SSViewerTestCommentsFullSourceIfIE',
1799
            'expected' => ""
1800
                . "<!doctype html>"
1801
                . "<!-- template $f/SSViewerTestCommentsFullSourceIfIE.ss -->"
1802
                . "<!--[if lte IE 8]> <html class='old-ie'> <![endif]-->"
1803
                . "<!--[if gt IE 8]> <html class='new-ie'> <![endif]-->"
1804
                . "<!--[if !IE]><!--> <html class='no-ie'> <!--<![endif]-->"
1805
                . "\t<head></head>"
1806
                . "\t<body></body>"
1807
                . "</html>"
1808
                . "<!-- end template $f/SSViewerTestCommentsFullSourceIfIE.ss -->",
1809
        ),
1810
        array(
1811
            'name' => 'SSViewerTestCommentsFullSourceIfIENoDoctype',
1812
            'expected' => ""
1813
                . "<!--[if lte IE 8]> <html class='old-ie'> <![endif]-->"
1814
                . "<!--[if gt IE 8]> <html class='new-ie'> <![endif]-->"
1815
                . "<!--[if !IE]><!--> <html class='no-ie'>"
1816
                . "<!-- template $f/SSViewerTestCommentsFullSourceIfIENoDoctype.ss -->"
1817
                . " <!--<![endif]-->"
1818
                . "\t<head></head>"
1819
                . "\t<body></body>"
1820
                . "<!-- end template $f/SSViewerTestCommentsFullSourceIfIENoDoctype.ss --></html>",
1821
        ),
1822
        array(
1823
            'name' => 'SSViewerTestCommentsPartialSource',
1824
            'expected' =>
1825
            "<!-- template $f/SSViewerTestCommentsPartialSource.ss -->"
1826
                . "<div class='typography'></div>"
1827
                . "<!-- end template $f/SSViewerTestCommentsPartialSource.ss -->",
1828
        ),
1829
        array(
1830
            'name' => 'SSViewerTestCommentsWithInclude',
1831
            'expected' =>
1832
            "<!-- template $f/SSViewerTestCommentsWithInclude.ss -->"
1833
                . "<div class='typography'>"
1834
                . "<!-- include 'SSViewerTestCommentsInclude' -->"
1835
                . "<!-- template $i/SSViewerTestCommentsInclude.ss -->"
1836
                . "Included"
1837
                . "<!-- end template $i/SSViewerTestCommentsInclude.ss -->"
1838
                . "<!-- end include 'SSViewerTestCommentsInclude' -->"
1839
                . "</div>"
1840
                . "<!-- end template $f/SSViewerTestCommentsWithInclude.ss -->",
1841
        ),
1842
        );
1843
        foreach ($templates as $template) {
1844
            $this->_renderWithSourceFileComments('SSViewerTestComments/'.$template['name'], $template['expected']);
1845
        }
1846
    }
1847
    private function _renderWithSourceFileComments($name, $expected)
1848
    {
1849
        $viewer = new SSViewer(array($name));
1850
        $data = new ArrayData(array());
1851
        $result = $viewer->process($data);
1852
        $expected = str_replace(array("\r", "\n"), '', $expected);
1853
        $result = str_replace(array("\r", "\n"), '', $result);
1854
        $this->assertEquals($result, $expected);
1855
    }
1856
1857
    public function testLoopIteratorIterator()
1858
    {
1859
        $list = new PaginatedList(new ArrayList());
1860
        $viewer = new SSViewer_FromString('<% loop List %>$ID - $FirstName<br /><% end_loop %>');
1861
        $result = $viewer->process(new ArrayData(array('List' => $list)));
1862
        $this->assertEquals($result, '');
1863
    }
1864
1865
    public function testProcessOnlyIncludesRequirementsOnce()
1866
    {
1867
        $template = new SSViewer(array('SSViewerTestProcess'));
1868
        $basePath = $this->getCurrentRelativePath() . '/SSViewerTest';
1869
1870
        $backend = Injector::inst()->create(Requirements_Backend::class);
1871
        $backend->setCombinedFilesEnabled(false);
1872
        $backend->combineFiles(
1873
        'RequirementsTest_ab.css',
1874
        array(
1875
            $basePath . '/css/RequirementsTest_a.css',
1876
            $basePath . '/css/RequirementsTest_b.css'
1877
        )
1878
        );
1879
1880
        Requirements::set_backend($backend);
1881
1882
        $this->assertEquals(1, substr_count($template->process(array()), "a.css"));
1883
        $this->assertEquals(1, substr_count($template->process(array()), "b.css"));
1884
1885
        // if we disable the requirements then we should get nothing
1886
        $template->includeRequirements(false);
1887
        $this->assertEquals(0, substr_count($template->process(array()), "a.css"));
1888
        $this->assertEquals(0, substr_count($template->process(array()), "b.css"));
1889
    }
1890
1891
    public function testRequireCallInTemplateInclude()
1892
    {
1893
        //TODO undo skip test on the event that templates ever obtain the ability to reference MODULE_DIR (or something to that effect)
1894
        if (FRAMEWORK_DIR === 'framework') {
1895
            $template = new SSViewer(array('SSViewerTestProcess'));
1896
1897
            Requirements::set_suffix_requirements(false);
1898
1899
            $this->assertEquals(
1900
            1,
1901
            substr_count(
1902
                $template->process(array()),
1903
                "tests/php/View/SSViewerTest/javascript/RequirementsTest_a.js"
1904
            )
1905
            );
1906
        } else {
1907
            $this->markTestSkipped(
1908
            'Requirement will always fail if the framework dir is not '.
1909
            'named \'framework\', since templates require hard coded paths'
1910
            );
1911
        }
1912
    }
1913
1914
    public function testCallsWithArguments()
1915
    {
1916
        $data = new ArrayData(
1917
        array(
1918
        'Set' => new ArrayList(
1919
            array(
1920
            new SSViewerTest\TestObject("1"),
1921
            new SSViewerTest\TestObject("2"),
1922
            new SSViewerTest\TestObject("3"),
1923
            new SSViewerTest\TestObject("4"),
1924
            new SSViewerTest\TestObject("5"),
1925
            )
1926
        ),
1927
        'Level' => new SSViewerTest\LevelTestData(1),
1928
        'Nest' => array(
1929
            'Level' => new SSViewerTest\LevelTestData(2),
1930
        ),
1931
        )
1932
        );
1933
1934
        $tests = array(
1935
        '$Level.output(1)' => '1-1',
1936
        '$Nest.Level.output($Set.First.Number)' => '2-1',
1937
        '<% with $Set %>$Up.Level.output($First.Number)<% end_with %>' => '1-1',
1938
        '<% with $Set %>$Top.Nest.Level.output($First.Number)<% end_with %>' => '2-1',
1939
        '<% loop $Set %>$Up.Nest.Level.output($Number)<% end_loop %>' => '2-12-22-32-42-5',
1940
        '<% loop $Set %>$Top.Level.output($Number)<% end_loop %>' => '1-11-21-31-41-5',
1941
        '<% with $Nest %>$Level.output($Top.Set.First.Number)<% end_with %>' => '2-1',
1942
        '<% with $Level %>$output($Up.Set.Last.Number)<% end_with %>' => '1-5',
1943
        '<% with $Level.forWith($Set.Last.Number) %>$output("hi")<% end_with %>' => '5-hi',
1944
        '<% loop $Level.forLoop($Set.First.Number) %>$Number<% end_loop %>' => '!0',
1945
        '<% with $Nest %>
1946
				<% with $Level.forWith($Up.Set.First.Number) %>$output("hi")<% end_with %>
1947
			<% end_with %>' => '1-hi',
1948
        '<% with $Nest %>
1949
				<% loop $Level.forLoop($Top.Set.Last.Number) %>$Number<% end_loop %>
1950
			<% end_with %>' => '!0!1!2!3!4',
1951
        );
1952
1953
        foreach ($tests as $template => $expected) {
1954
            $this->assertEquals($expected, trim($this->render($template, $data)));
1955
        }
1956
    }
1957
1958
    public function testRepeatedCallsAreCached()
1959
    {
1960
        $data = new SSViewerTest\CacheTestData();
1961
        $template = '
1962
			<% if $TestWithCall %>
1963
				<% with $TestWithCall %>
1964
					{$Message}
1965
				<% end_with %>
1966
1967
				{$TestWithCall.Message}
1968
			<% end_if %>';
1969
1970
        $this->assertEquals('HiHi', preg_replace('/\s+/', '', $this->render($template, $data)));
1971
        $this->assertEquals(
1972
        1,
1973
        $data->testWithCalls,
1974
        'SSViewerTest_CacheTestData::TestWithCall() should only be called once. Subsequent calls should be cached'
1975
        );
1976
1977
        $data = new SSViewerTest\CacheTestData();
1978
        $template = '
1979
			<% if $TestLoopCall %>
1980
				<% loop $TestLoopCall %>
1981
					{$Message}
1982
				<% end_loop %>
1983
			<% end_if %>';
1984
1985
        $this->assertEquals('OneTwo', preg_replace('/\s+/', '', $this->render($template, $data)));
1986
        $this->assertEquals(
1987
        1,
1988
        $data->testLoopCalls,
1989
        'SSViewerTest_CacheTestData::TestLoopCall() should only be called once. Subsequent calls should be cached'
1990
        );
1991
    }
1992
1993
    public function testClosedBlockExtension()
1994
    {
1995
        $count = 0;
1996
        $parser = new SSTemplateParser();
1997
        $parser->addClosedBlock(
1998
        'test',
1999
        function ($res) use (&$count) {
0 ignored issues
show
Unused Code introduced by
The parameter $res is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2000
            $count++;
2001
        }
2002
        );
2003
2004
        $template = new SSViewer_FromString("<% test %><% end_test %>", $parser);
2005
        $template->process(new SSViewerTest\TestFixture());
2006
2007
        $this->assertEquals(1, $count);
2008
    }
2009
2010
    public function testOpenBlockExtension()
2011
    {
2012
        $count = 0;
2013
        $parser = new SSTemplateParser();
2014
        $parser->addOpenBlock(
2015
        'test',
2016
        function ($res) use (&$count) {
0 ignored issues
show
Unused Code introduced by
The parameter $res is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2017
            $count++;
2018
        }
2019
        );
2020
2021
        $template = new SSViewer_FromString("<% test %>", $parser);
2022
        $template->process(new SSViewerTest\TestFixture());
2023
2024
        $this->assertEquals(1, $count);
2025
    }
2026
2027
    /**
2028
     * Tests if caching for SSViewer_FromString is working
2029
     */
2030
    public function testFromStringCaching()
2031
    {
2032
        $content = 'Test content';
2033
        $cacheFile = TEMP_FOLDER . '/.cache.' . sha1($content);
2034
        if (file_exists($cacheFile)) {
2035
            unlink($cacheFile);
2036
        }
2037
2038
        // Test global behaviors
2039
        $this->render($content, null, null);
2040
        $this->assertFalse(file_exists($cacheFile), 'Cache file was created when caching was off');
2041
2042
        SSViewer_FromString::config()->update('cache_template', true);
2043
        $this->render($content, null, null);
2044
        $this->assertTrue(file_exists($cacheFile), 'Cache file wasn\'t created when it was meant to');
2045
        unlink($cacheFile);
2046
2047
        // Test instance behaviors
2048
        $this->render($content, null, false);
2049
        $this->assertFalse(file_exists($cacheFile), 'Cache file was created when caching was off');
2050
2051
        $this->render($content, null, true);
2052
        $this->assertTrue(file_exists($cacheFile), 'Cache file wasn\'t created when it was meant to');
2053
        unlink($cacheFile);
2054
    }
2055
}
2056