Skip to content

Commit 7340cbc

Browse files
Boegiemglaman
andauthored
Support concatenated callables in RenderCallbackRule (#544)
Co-authored-by: Matt Glaman <[email protected]>
1 parent 266474c commit 7340cbc

File tree

3 files changed

+122
-4
lines changed

3 files changed

+122
-4
lines changed

src/Rules/Drupal/RenderCallbackRule.php

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,18 @@ private function doProcessNode(Node\Expr $node, Scope $scope, string $keyChecked
153153
)->line($errorLine)
154154
->tip('Change record: https://www.drupal.org/node/2966725.')
155155
->build();
156-
} elseif (!$trustedCallbackType->isSuperTypeOf($type)->yes()) {
157-
$errors[] = RuleErrorBuilder::message(
158-
sprintf("%s callback class %s at key '%s' does not implement Drupal\Core\Security\TrustedCallbackInterface.", $keyChecked, $constantStringType->describe(VerbosityLevel::value()), $pos)
159-
)->line($errorLine)->tip('Change record: https://www.drupal.org/node/2966725.')->build();
156+
} else {
157+
// @see \PHPStan\Type\Constant\ConstantStringType::isCallable
158+
preg_match('#^([a-zA-Z_\\x7f-\\xff\\\\][a-zA-Z0-9_\\x7f-\\xff\\\\]*)::([a-zA-Z_\\x7f-\\xff][a-zA-Z0-9_\\x7f-\\xff]*)\\z#', $constantStringType->getValue(), $matches);
159+
if (count($matches) === 0) {
160+
$errors[] = RuleErrorBuilder::message(
161+
sprintf("%s callback %s at key '%s' is not callable.", $keyChecked, $constantStringType->describe(VerbosityLevel::value()), $pos)
162+
)->line($errorLine)->build();
163+
} elseif (!$trustedCallbackType->isSuperTypeOf(new ObjectType($matches[1]))->yes()) {
164+
$errors[] = RuleErrorBuilder::message(
165+
sprintf("%s callback class %s at key '%s' does not implement Drupal\Core\Security\TrustedCallbackInterface.", $keyChecked, $constantStringType->describe(VerbosityLevel::value()), $pos)
166+
)->line($errorLine)->tip('Change record: https://www.drupal.org/node/2966725.')->build();
167+
}
160168
}
161169
}
162170

tests/src/Rules/RenderCallbackRuleTest.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,11 @@ public static function fileData(): \Generator
158158
],
159159
];
160160
}
161+
162+
yield [
163+
__DIR__ . '/data/bug-543.php',
164+
[]
165+
];
161166
}
162167

163168

tests/src/Rules/data/bug-543.php

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
namespace Bug543;
4+
5+
use Drupal\Core\Access\AccessResult;
6+
use Drupal\Core\Security\TrustedCallbackInterface;
7+
8+
/**
9+
* Code snippets from \Drupal\Tests\Core\Render\RendererTest.
10+
*/
11+
class TestClass {
12+
13+
/**
14+
* Provides a list of both booleans.
15+
*
16+
* @return array
17+
*/
18+
public function providerAccessValues() {
19+
return [
20+
[FALSE],
21+
[TRUE],
22+
[AccessResult::forbidden()],
23+
[AccessResult::allowed()],
24+
];
25+
}
26+
27+
/**
28+
* @dataProvider providerAccessValues
29+
*/
30+
public function testRenderWithAccessControllerResolved($access) {
31+
32+
switch ($access) {
33+
case AccessResult::allowed():
34+
$method = 'accessResultAllowed';
35+
break;
36+
37+
case AccessResult::forbidden():
38+
$method = 'accessResultForbidden';
39+
break;
40+
41+
case FALSE:
42+
$method = 'accessFalse';
43+
break;
44+
45+
case TRUE:
46+
$method = 'accessTrue';
47+
break;
48+
}
49+
50+
$build = [
51+
'#access_callback' => TestAccessClass::class . '::' . $method,
52+
];
53+
}
54+
55+
public function bug543AccessResultAllowed(): void {
56+
$build = [
57+
'#access_callback' => TestAccessClass::class . '::accessResultAllowed',
58+
];
59+
}
60+
61+
public function bug543AccessResultForbidden(): void {
62+
$build = [
63+
'#access_callback' => TestAccessClass::class . '::accessResultForbidden',
64+
];
65+
}
66+
67+
public function bug543AccessFalse(): void {
68+
$build = [
69+
'#access_callback' => TestAccessClass::class . '::accessFalse',
70+
];
71+
}
72+
73+
public function bug543AccessTrue(): void {
74+
$build = [
75+
'#access_callback' => TestAccessClass::class . '::accessTrue',
76+
];
77+
}
78+
}
79+
80+
class TestAccessClass implements TrustedCallbackInterface {
81+
82+
public static function accessTrue() {
83+
return TRUE;
84+
}
85+
86+
public static function accessFalse() {
87+
return FALSE;
88+
}
89+
90+
public static function accessResultAllowed() {
91+
return AccessResult::allowed();
92+
}
93+
94+
public static function accessResultForbidden() {
95+
return AccessResult::forbidden();
96+
}
97+
98+
/**
99+
* {@inheritdoc}
100+
*/
101+
public static function trustedCallbacks() {
102+
return ['accessTrue', 'accessFalse', 'accessResultAllowed', 'accessResultForbidden'];
103+
}
104+
105+
}

0 commit comments

Comments
 (0)