Skip to content

Commit ff64e2d

Browse files
authored
Fixed 'convertPermissions()' against TranslatableMarkup titles and restored 'drupal_static()' cache. (#333)
1 parent 44b3490 commit ff64e2d

3 files changed

Lines changed: 165 additions & 3 deletions

File tree

rector.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
use Rector\CodingStyle\Rector\Catch_\CatchExceptionNameMatchingTypeRector;
1414
use Rector\CodingStyle\Rector\Stmt\NewlineAfterStatementRector;
1515
use Rector\Config\RectorConfig;
16+
use Rector\EarlyReturn\Rector\StmtsAwareInterface\ReturnEarlyIfVariableRector;
1617
use Rector\Strict\Rector\Empty_\DisallowedEmptyRuleFixerRector;
1718

1819
return RectorConfig::configure()
@@ -36,6 +37,11 @@
3637
CatchExceptionNameMatchingTypeRector::class,
3738
// Too aggressive for mixed-type codebase.
3839
DisallowedEmptyRuleFixerRector::class,
40+
// Breaks 'drupal_static()' caching in 'Drupal8::getAllPermissions()':
41+
// assigning into the static reference is required for subsequent
42+
// calls to hit the cache, so the intermediate variable is not dead
43+
// code even though Rector thinks it is.
44+
ReturnEarlyIfVariableRector::class,
3945
// Legacy test with PHPUnit compatibility issue.
4046
__DIR__ . '/tests/Drupal/Tests/Driver/Drupal7FieldHandlerTest.php',
4147
// Dependencies.

src/Drupal/Driver/Cores/Drupal8.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ protected function getAllPermissions() {
209209
$permissions = &drupal_static(__FUNCTION__);
210210

211211
if (!isset($permissions)) {
212-
return \Drupal::service('user.permissions')->getPermissions();
212+
$permissions = \Drupal::service('user.permissions')->getPermissions();
213213
}
214214

215215
return $permissions;
@@ -225,8 +225,11 @@ protected function convertPermissions(array &$permissions) {
225225
$all_permissions = $this->getAllPermissions();
226226

227227
foreach ($all_permissions as $name => $definition) {
228-
$key = array_search($definition['title'], $permissions, TRUE);
229-
if (FALSE !== $key) {
228+
// Cast the title to string: Drupal returns TranslatableMarkup objects
229+
// for permission titles, which would never strictly equal the plain
230+
// string labels supplied by callers.
231+
$key = array_search((string) $definition['title'], $permissions, TRUE);
232+
if ($key !== FALSE) {
230233
$permissions[$key] = $name;
231234
}
232235
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
<?php
2+
3+
namespace Drupal\Tests\Driver;
4+
5+
use Drupal\Driver\Cores\Drupal8;
6+
use PHPUnit\Framework\TestCase;
7+
8+
/**
9+
* Tests permission label and machine name conversion in the Drupal 8+ driver.
10+
*/
11+
class Drupal8PermissionsTest extends TestCase {
12+
13+
/**
14+
* Tests that human-readable titles are converted to machine names.
15+
*
16+
* Drupal returns permission titles as TranslatableMarkup objects. Strict
17+
* comparison against plain string labels would never match, so the driver
18+
* must cast the title to string before lookup. This guards against
19+
* regressions where automated refactoring flips the comparison to strict
20+
* mode without adding the cast.
21+
*/
22+
public function testConvertPermissionsMapsStringableTitlesToMachineNames() {
23+
$core = new TestDrupal8PermissionsCore(__DIR__, 'default');
24+
$core->setPermissions([
25+
'administer content types' => [
26+
'title' => $this->stringable('Administer content types'),
27+
],
28+
'administer users' => [
29+
'title' => $this->stringable('Administer users'),
30+
],
31+
]);
32+
33+
$permissions = ['Administer content types', 'Administer users'];
34+
$this->callConvertPermissions($core, $permissions);
35+
36+
$this->assertSame(['administer content types', 'administer users'], $permissions);
37+
}
38+
39+
/**
40+
* Tests that titles already in machine name form are left unchanged.
41+
*/
42+
public function testConvertPermissionsLeavesMachineNamesAlone() {
43+
$core = new TestDrupal8PermissionsCore(__DIR__, 'default');
44+
$core->setPermissions([
45+
'administer users' => [
46+
'title' => $this->stringable('Administer users'),
47+
],
48+
]);
49+
50+
$permissions = ['administer users'];
51+
$this->callConvertPermissions($core, $permissions);
52+
53+
$this->assertSame(['administer users'], $permissions);
54+
}
55+
56+
/**
57+
* Tests that 'checkPermissions()' passes for valid machine names.
58+
*/
59+
public function testCheckPermissionsAcceptsValidMachineNames() {
60+
$core = new TestDrupal8PermissionsCore(__DIR__, 'default');
61+
$core->setPermissions([
62+
'administer users' => ['title' => 'Administer users'],
63+
'access content' => ['title' => 'Access content'],
64+
]);
65+
66+
$permissions = ['administer users', 'access content'];
67+
$this->callCheckPermissions($core, $permissions);
68+
69+
$this->assertSame(['administer users', 'access content'], $permissions);
70+
}
71+
72+
/**
73+
* Tests that 'checkPermissions()' throws for unknown machine names.
74+
*/
75+
public function testCheckPermissionsThrowsForUnknownPermission() {
76+
$core = new TestDrupal8PermissionsCore(__DIR__, 'default');
77+
$core->setPermissions([
78+
'administer users' => ['title' => 'Administer users'],
79+
]);
80+
81+
$permissions = ['administer users', 'unknown permission'];
82+
83+
$this->expectException(\RuntimeException::class);
84+
$this->expectExceptionMessage('Invalid permission "unknown permission".');
85+
86+
$this->callCheckPermissions($core, $permissions);
87+
}
88+
89+
/**
90+
* Invokes the protected 'convertPermissions()' method by reference.
91+
*/
92+
protected function callConvertPermissions(Drupal8 $core, array &$permissions) {
93+
$method = new \ReflectionMethod($core, 'convertPermissions');
94+
$method->setAccessible(TRUE);
95+
$method->invokeArgs($core, [&$permissions]);
96+
}
97+
98+
/**
99+
* Invokes the protected 'checkPermissions()' method by reference.
100+
*/
101+
protected function callCheckPermissions(Drupal8 $core, array &$permissions) {
102+
$method = new \ReflectionMethod($core, 'checkPermissions');
103+
$method->setAccessible(TRUE);
104+
$method->invokeArgs($core, [&$permissions]);
105+
}
106+
107+
/**
108+
* Returns an anonymous Stringable that mimics a Drupal TranslatableMarkup.
109+
*/
110+
protected function stringable($label) {
111+
return new class($label) {
112+
113+
public function __construct(private $label) {}
114+
115+
/**
116+
* Renders the stringable into its label.
117+
*/
118+
public function __toString(): string {
119+
return $this->label;
120+
}
121+
122+
};
123+
}
124+
125+
}
126+
127+
/**
128+
* Testable subclass that overrides 'getAllPermissions()'.
129+
*/
130+
class TestDrupal8PermissionsCore extends Drupal8 {
131+
132+
/**
133+
* Stored permissions keyed by machine name.
134+
*
135+
* @var array
136+
*/
137+
protected $permissions = [];
138+
139+
/**
140+
* Sets the permissions returned by 'getAllPermissions()'.
141+
*/
142+
public function setPermissions(array $permissions) {
143+
$this->permissions = $permissions;
144+
}
145+
146+
/**
147+
* {@inheritdoc}
148+
*/
149+
protected function getAllPermissions() {
150+
return $this->permissions;
151+
}
152+
153+
}

0 commit comments

Comments
 (0)