Skip to content

Commit 23defac

Browse files
authored
Merge pull request #454 from mglaman/entity-query-access-missing-errors
Entity query access check rule improvements
2 parents a950fd5 + 10fd123 commit 23defac

10 files changed

+249
-2
lines changed

src/Type/DrupalStaticEntityQueryDynamicReturnTypeExtension.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use mglaman\PHPStanDrupal\Drupal\EntityDataRepository;
66
use mglaman\PHPStanDrupal\Type\EntityQuery\ConfigEntityQueryType;
77
use mglaman\PHPStanDrupal\Type\EntityQuery\ContentEntityQueryType;
8+
use mglaman\PHPStanDrupal\Type\EntityQuery\EntityQueryType;
89
use mglaman\PHPStanDrupal\Type\EntityStorage\ConfigEntityStorageType;
910
use mglaman\PHPStanDrupal\Type\EntityStorage\ContentEntityStorageType;
1011
use PhpParser\Node\Expr\StaticCall;
@@ -80,6 +81,10 @@ public function getTypeFromStaticMethodCall(
8081
);
8182
}
8283

83-
return $returnType;
84+
return new EntityQueryType(
85+
$returnType->getClassName(),
86+
$returnType->getSubtractedType(),
87+
$returnType->getClassReflection()
88+
);
8489
}
8590
}

src/Type/EntityStorage/GetQueryReturnTypeExtension.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Drupal\Core\Entity\EntityStorageInterface;
88
use mglaman\PHPStanDrupal\Type\EntityQuery\ConfigEntityQueryType;
99
use mglaman\PHPStanDrupal\Type\EntityQuery\ContentEntityQueryType;
10+
use mglaman\PHPStanDrupal\Type\EntityQuery\EntityQueryType;
1011
use PhpParser\Node\Expr\MethodCall;
1112
use PHPStan\Analyser\Scope;
1213
use PHPStan\Reflection\MethodReflection;
@@ -56,6 +57,10 @@ public function getTypeFromMethodCall(
5657
$returnType->getClassReflection()
5758
);
5859
}
59-
return $returnType;
60+
return new EntityQueryType(
61+
$returnType->getClassName(),
62+
$returnType->getSubtractedType(),
63+
$returnType->getClassReflection()
64+
);
6065
}
6166
}

tests/src/Rules/EntityQueryHasAccessCheckRuleTest.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,47 @@ public function cases(): \Generator
4444
],
4545
],
4646
];
47+
48+
yield 'bug-438.php' => [
49+
[__DIR__ . '/data/bug-438.php'],
50+
[]
51+
];
52+
yield 'bug-396a1.php' => [
53+
[__DIR__.'/data/bug-396a1.php'],
54+
[
55+
[
56+
'Missing explicit access check on entity query.',
57+
27,
58+
'See https://www.drupal.org/node/3201242',
59+
]
60+
]
61+
];
62+
63+
// @todo 396a2 this passes when run individually, somehow.
64+
/*
65+
yield 'bug-396a2.php' => [
66+
[__DIR__ . '/data/bug-396a2.php'],
67+
[]
68+
];*/
69+
// @todo not chained call, return type extension has no influence.
70+
/*
71+
yield 'bug-396a3.php' => [
72+
[__DIR__ . '/data/bug-396a3.php'],
73+
[]
74+
];*/
75+
yield 'bug-396b.php' => [
76+
[__DIR__ . '/data/bug-396b.php'],
77+
[]
78+
];
79+
yield 'bug-396c.php' => [
80+
[__DIR__ . '/data/bug-396c.php'],
81+
[]
82+
];
83+
// @todo Try to resolve from typed property.
84+
/*
85+
yield 'bug-437.php' => [
86+
[__DIR__ . '/data/bug-437.php'],
87+
[]
88+
];*/
4789
}
4890
}

tests/src/Rules/data/bug-396a1.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Bug396a;
4+
5+
function foo() {
6+
$query = \Drupal::entityQuery('user')
7+
->condition('status', 1)
8+
->condition('uid', 1, '<>')
9+
->condition('field_profile_visibility', 1)
10+
->condition('field_account_type', '', '<>')
11+
->condition('field_last_name.value', '', '<>');
12+
if( isset($qparam['expertise']) && !empty($qparam['expertise']) ) {
13+
$query->condition('field_field_expertise.entity.tid', $qparam['expertise']);
14+
}
15+
if( isset($qparam['origin']) && !empty($qparam['origin']) ) {
16+
$query->condition('field_place_origin.entity:taxonomy_term.field_country_tags', $qparam['origin']);
17+
}
18+
if( isset($qparam['place_residence']) && !empty($qparam['place_residence']) ) {
19+
$query->condition('field_place_residence.entity:taxonomy_term.field_country_tags', $qparam['place_residence']);
20+
}
21+
if( isset($qparam['city']) && !empty($qparam['city']) ) {
22+
$query->condition('field_city', $qparam['city'], 'CONTAINS');
23+
}
24+
if( isset($qparam['user_type']) && !empty($qparam['user_type']) ) {
25+
$query->condition('field_account_type.entity.tid', $qparam['user_type']);
26+
}
27+
$users = $query->execute();
28+
};

tests/src/Rules/data/bug-396a2.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Bug396a;
4+
5+
function bar() {
6+
$query = \Drupal::entityQuery('user')
7+
->accessCheck(FALSE)
8+
->condition('status', 1)
9+
->condition('uid', 1, '<>')
10+
->condition('field_profile_visibility', 1)
11+
->condition('field_account_type', '', '<>')
12+
->condition('field_last_name.value', '', '<>');
13+
if( isset($qparam['expertise']) && !empty($qparam['expertise']) ) {
14+
$query->condition('field_field_expertise.entity.tid', $qparam['expertise']);
15+
}
16+
if( isset($qparam['origin']) && !empty($qparam['origin']) ) {
17+
$query->condition('field_place_origin.entity:taxonomy_term.field_country_tags', $qparam['origin']);
18+
}
19+
if( isset($qparam['place_residence']) && !empty($qparam['place_residence']) ) {
20+
$query->condition('field_place_residence.entity:taxonomy_term.field_country_tags', $qparam['place_residence']);
21+
}
22+
if( isset($qparam['city']) && !empty($qparam['city']) ) {
23+
$query->condition('field_city', $qparam['city'], 'CONTAINS');
24+
}
25+
if( isset($qparam['user_type']) && !empty($qparam['user_type']) ) {
26+
$query->condition('field_account_type.entity.tid', $qparam['user_type']);
27+
}
28+
$users = $query->execute();
29+
};

tests/src/Rules/data/bug-396a3.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Bug396a;
4+
5+
function baz() {
6+
$query = \Drupal::entityQuery('user')
7+
->condition('status', 1)
8+
->condition('uid', 1, '<>')
9+
->condition('field_profile_visibility', 1)
10+
->condition('field_account_type', '', '<>')
11+
->condition('field_last_name.value', '', '<>');
12+
if( isset($qparam['expertise']) && !empty($qparam['expertise']) ) {
13+
$query->condition('field_field_expertise.entity.tid', $qparam['expertise']);
14+
}
15+
if( isset($qparam['origin']) && !empty($qparam['origin']) ) {
16+
$query->condition('field_place_origin.entity:taxonomy_term.field_country_tags', $qparam['origin']);
17+
}
18+
if( isset($qparam['place_residence']) && !empty($qparam['place_residence']) ) {
19+
$query->condition('field_place_residence.entity:taxonomy_term.field_country_tags', $qparam['place_residence']);
20+
}
21+
if( isset($qparam['city']) && !empty($qparam['city']) ) {
22+
$query->condition('field_city', $qparam['city'], 'CONTAINS');
23+
}
24+
if( isset($qparam['user_type']) && !empty($qparam['user_type']) ) {
25+
$query->condition('field_account_type.entity.tid', $qparam['user_type']);
26+
}
27+
$query->accessCheck(FALSE);
28+
$users = $query->execute();
29+
};

tests/src/Rules/data/bug-396b.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Bug396b;
4+
5+
use Drupal\Core\Entity\EntityTypeManagerInterface;
6+
7+
class Foo {
8+
private EntityTypeManagerInterface $entityTypeManager;
9+
private string $entityTypeId;
10+
public function __construct(EntityTypeManagerInterface $entityTypeManager)
11+
{
12+
$this->entityTypeManager = $entityTypeManager;
13+
$this->entityTypeId = 'node';
14+
}
15+
public function a(): int {
16+
/** @var \Drupal\Core\Entity\TranslatableRevisionableStorageInterface|\Drupal\Core\Entity\EntityStorageInterface $storage */
17+
$storage = $this->entityTypeManager->getStorage($this->entityTypeId);
18+
return $storage->getQuery()->accessCheck(FALSE)->count()->execute() + 1;
19+
}
20+
}

tests/src/Rules/data/bug-396c.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
namespace Bug396c;
4+
5+
use Drupal\Core\Entity\EntityStorageInterface;
6+
7+
class EntityQueryTest {
8+
private EntityStorageInterface $storage;
9+
public function setUp(): void {
10+
$this->storage = \Drupal::entityTypeManager()->getStorage('entity_test_mulrev');
11+
}
12+
public function a(): int {
13+
$query = $this->storage
14+
->getQuery('OR')
15+
->accessCheck(FALSE)
16+
->exists('abcfoo', 'tr')
17+
->condition("abd.color", 'red')
18+
->sort('id');
19+
$count_query = clone $query;
20+
return $count_query->count()->execute();
21+
}
22+
public function b(): int {
23+
$query = $this->storage
24+
->getQuery('OR')
25+
->accessCheck(FALSE)
26+
->exists('abcfoo', 'tr')
27+
->condition("abd.color", 'red')
28+
->sort('id');
29+
return $query->count()->execute();
30+
}
31+
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Bug437;
4+
5+
use Drupal\Core\Entity\EntityTypeManagerInterface;
6+
use Drupal\Core\Entity\Query\QueryInterface;
7+
8+
final class Foo {
9+
private QueryInterface $vocabularyQuery;
10+
public function __construct(EntityTypeManagerInterface $entityTypeManager)
11+
{
12+
$this->vocabularyQuery = $entityTypeManager
13+
->getStorage('taxonomy_term')
14+
->getQuery()
15+
->accessCheck(TRUE);
16+
}
17+
public function a(): int {
18+
return $this->vocabularyQuery->count()->execute();
19+
}
20+
}

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace EntityReferenceRevisionsExample;
4+
5+
use Drupal\Core\Entity\ContentEntityInterface;
6+
use Drupal\Core\Entity\EntityTypeManagerInterface;
7+
8+
class Foo {
9+
private EntityTypeManagerInterface $entityTypeManager;
10+
public function __construct(EntityTypeManagerInterface $entityTypeManager)
11+
{
12+
$this->entityTypeManager = $entityTypeManager;
13+
}
14+
public function deleteUnusedRevision(ContentEntityInterface $composite_revision) {
15+
$composite_storage = $this->entityTypeManager->getStorage($composite_revision->getEntityTypeId());
16+
17+
if ($composite_revision->isDefaultRevision()) {
18+
$count = $composite_storage
19+
->getQuery()
20+
->accessCheck(FALSE)
21+
->allRevisions()
22+
->condition($composite_storage->getEntityType()->getKey('id'), $composite_revision->id())
23+
->count()
24+
->execute();
25+
if ($count <= 1) {
26+
$composite_revision->delete();
27+
return TRUE;
28+
}
29+
}
30+
else {
31+
// Delete the revision if this is not the default one.
32+
$composite_storage->deleteRevision($composite_revision->getRevisionId());
33+
return TRUE;
34+
}
35+
36+
return FALSE;
37+
}
38+
}

0 commit comments

Comments
 (0)