This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Wikibase\Repo\Api; |
||
4 | |||
5 | use Wikibase\DataModel\Entity\EntityId; |
||
6 | use Wikibase\DataModel\Entity\EntityIdParser; |
||
7 | use Wikibase\DataModel\Entity\EntityIdParsingException; |
||
8 | use Wikibase\DataModel\Services\Lookup\EntityLookup; |
||
9 | use Wikibase\DataModel\Services\Lookup\LabelDescriptionLookup; |
||
10 | use Wikibase\DataModel\Term\Term; |
||
11 | use Wikibase\Lib\Interactors\TermSearchResult; |
||
12 | use Wikimedia\Assert\Assert; |
||
13 | |||
14 | /** |
||
15 | * Helper class to search for entities by ID |
||
16 | * |
||
17 | * @license GPL-2.0-or-later |
||
18 | */ |
||
19 | class EntityIdSearchHelper implements EntitySearchHelper { |
||
20 | |||
21 | /** |
||
22 | * @var EntityIdParser |
||
23 | */ |
||
24 | private $idParser; |
||
25 | |||
26 | /** |
||
27 | * @var LabelDescriptionLookup |
||
28 | */ |
||
29 | private $labelDescriptionLookup; |
||
30 | |||
31 | /** |
||
32 | * @var EntityLookup |
||
33 | */ |
||
34 | private $entityLookup; |
||
35 | |||
36 | /** |
||
37 | * @var string[][] Associative array mapping entity type names (strings) to names of |
||
38 | * repositories providing entities of this type. |
||
39 | */ |
||
40 | private $entityTypeToRepositoryMapping; |
||
41 | |||
42 | /** |
||
43 | * @param EntityLookup $entityLookup |
||
44 | * @param EntityIdParser $idParser |
||
45 | * @param LabelDescriptionLookup $labelDescriptionLookup |
||
46 | * @param string[][] $entityTypeToRepositoryMapping Associative array (string => string[][]) |
||
47 | * mapping entity types to a list of repository names which provide entities of the given type. |
||
48 | */ |
||
49 | public function __construct( |
||
50 | EntityLookup $entityLookup, |
||
51 | EntityIdParser $idParser, |
||
52 | LabelDescriptionLookup $labelDescriptionLookup, |
||
53 | array $entityTypeToRepositoryMapping |
||
54 | ) { |
||
55 | foreach ( $entityTypeToRepositoryMapping as $entityType => $repositoryNames ) { |
||
56 | Assert::parameter( |
||
57 | count( $repositoryNames ) === 1, |
||
58 | '$entityTypeToRepositoryMapping', |
||
59 | 'Expected entities of type: "' . $entityType . '" to only be provided by single repository.' |
||
60 | ); |
||
61 | } |
||
62 | |||
63 | $this->entityLookup = $entityLookup; |
||
64 | $this->idParser = $idParser; |
||
65 | $this->labelDescriptionLookup = $labelDescriptionLookup; |
||
66 | $this->entityTypeToRepositoryMapping = $entityTypeToRepositoryMapping; |
||
67 | } |
||
68 | |||
69 | /** |
||
70 | * Gets exact matches. If there are not enough exact matches, it gets prefixed matches. |
||
71 | * |
||
72 | * @param string $text |
||
73 | * @param string $languageCode |
||
74 | * @param string $entityType |
||
75 | * @param int $limit |
||
76 | * @param bool $strictLanguage |
||
77 | * |
||
78 | * @return TermSearchResult[] Key: string Serialized EntityId |
||
79 | */ |
||
80 | public function getRankedSearchResults( $text, $languageCode, $entityType, $limit, $strictLanguage ) { |
||
81 | $allSearchResults = []; |
||
82 | |||
83 | // If $text is the ID of an existing item (with repository prefix or without), include it in the result. |
||
84 | $entityId = $this->getEntityIdMatchingSearchTerm( $text, $entityType ); |
||
85 | if ( !$entityId ) { |
||
86 | return $allSearchResults; |
||
87 | } |
||
88 | |||
89 | // This is nothing to do with terms, but make it look a normal result so everything is easier |
||
90 | $displayTerms = $this->getDisplayTerms( $entityId ); |
||
91 | $allSearchResults[$entityId->getSerialization()] = new TermSearchResult( |
||
92 | new Term( 'qid', $entityId->getSerialization() ), |
||
93 | 'entityId', |
||
94 | $entityId, |
||
95 | $displayTerms['label'], |
||
96 | $displayTerms['description'] |
||
97 | ); |
||
98 | |||
99 | return $allSearchResults; |
||
0 ignored issues
–
show
|
|||
100 | } |
||
101 | |||
102 | /** |
||
103 | * Returns EntityIds matching the search term (possibly with some repository prefix). |
||
104 | * If search term is a serialized entity id of the requested type, and multiple repositories provide |
||
105 | * entities of the type, prefixes of each of repositories are added to the search term and those repositories |
||
106 | * are searched for the result entity ID. If such concatenated entity IDs are found in several respective |
||
107 | * repositories, this returns all relevant matches. |
||
108 | * |
||
109 | * @param string $term |
||
110 | * @param string $entityType |
||
111 | * |
||
112 | * @return EntityId|null |
||
113 | */ |
||
114 | private function getEntityIdMatchingSearchTerm( $term, $entityType ) { |
||
115 | $entityId = null; |
||
116 | foreach ( $this->getEntityIdCandidatesForSearchTerm( $term ) as $candidate ) { |
||
117 | try { |
||
118 | $entityId = $this->idParser->parse( $candidate ); |
||
119 | break; |
||
120 | } catch ( EntityIdParsingException $ex ) { |
||
121 | continue; |
||
122 | } |
||
123 | } |
||
124 | |||
125 | if ( $entityId === null ) { |
||
126 | return null; |
||
127 | } |
||
128 | |||
129 | if ( $entityId->getEntityType() !== $entityType ) { |
||
130 | return null; |
||
131 | } |
||
132 | |||
133 | return $this->getMatchingId( $entityId ); |
||
134 | } |
||
135 | |||
136 | /** |
||
137 | * Returns a generator for candidates of entity ID serializations from a search term. |
||
138 | * Callers should attempt to parse each candidate in turn |
||
139 | * and use the first one that does not result in an {@link EntityIdParsingException}. |
||
140 | * |
||
141 | * @param string $term |
||
142 | * @return \Generator |
||
143 | */ |
||
144 | private function getEntityIdCandidatesForSearchTerm( $term ) { |
||
145 | // Trim whitespace. |
||
146 | $term = trim( $term ); |
||
147 | yield $term; |
||
148 | |||
149 | // Uppercase the search term. Entity IDs are *usually* all-uppercase, |
||
150 | // and we want to allow case-insensitive search for them. |
||
151 | $term = strtoupper( $term ); |
||
152 | yield $term; |
||
153 | |||
154 | // Extract the last (ASCII-only) word. This covers URIs and input strings like "(Q42)". |
||
155 | // Note that this only supports single-word IDs and not IDs like "L1-F1". |
||
156 | if ( preg_match( '/.*(\b\w{2,})/s', $term, $matches ) ) { |
||
157 | $term = $matches[1]; |
||
158 | yield $term; |
||
159 | } |
||
160 | } |
||
161 | |||
162 | /** |
||
163 | * Returns a list of entity IDs matching the pattern defined by $entityId: existing entities |
||
164 | * of type of $entityId, and serialized id equal to $entityId, possibly including prefixes |
||
165 | * of configured repositories. |
||
166 | * |
||
167 | * @param EntityId $entityId |
||
168 | * |
||
169 | * @return EntityId|null |
||
170 | */ |
||
171 | private function getMatchingId( EntityId $entityId ) { |
||
172 | $entityType = $entityId->getEntityType(); |
||
173 | if ( !array_key_exists( $entityType, $this->entityTypeToRepositoryMapping ) ) { |
||
174 | // Unknown entity type, nothing we can do here. |
||
175 | return null; |
||
176 | } |
||
177 | |||
178 | // NOTE: this assumes entities of the particular type are only provided by a single repository |
||
179 | // This assumption is currently valid but might change in the future. |
||
180 | $repositoryPrefix = $this->entityTypeToRepositoryMapping[$entityType][0]; |
||
181 | |||
182 | if ( $entityId->getRepositoryName() !== '' && $repositoryPrefix !== $entityId->getRepositoryName() ) { |
||
183 | // If a repository is explicitly specified and it is not the one (and only) we know about abort. |
||
184 | return null; |
||
185 | } |
||
186 | |||
187 | // Note: EntityLookup::hasEntity may return true even if the getRepositoryName of the entity id is |
||
188 | // unknown, as the lookup doesn't about its entity source setting. |
||
189 | if ( $this->entityLookup->hasEntity( $entityId ) ) { |
||
190 | return $entityId; |
||
191 | } |
||
192 | |||
193 | // Entity ID without repository prefix, let's try prepending known prefixes |
||
194 | $unprefixedIdPart = $entityId->getLocalPart(); |
||
195 | |||
196 | try { |
||
197 | $id = $this->idParser->parse( EntityId::joinSerialization( [ |
||
198 | $repositoryPrefix, |
||
199 | '', |
||
200 | $unprefixedIdPart |
||
201 | ] ) ); |
||
202 | } catch ( EntityIdParsingException $ex ) { |
||
203 | return null; |
||
204 | } |
||
205 | |||
206 | if ( $this->entityLookup->hasEntity( $id ) ) { |
||
207 | return $id; |
||
208 | } |
||
209 | |||
210 | return null; |
||
211 | } |
||
212 | |||
213 | /** |
||
214 | * @param EntityId $entityId |
||
215 | * |
||
216 | * @return Term[] array with keys 'label' and 'description' |
||
217 | */ |
||
218 | private function getDisplayTerms( EntityId $entityId ) { |
||
219 | $displayTerms = []; |
||
220 | |||
221 | $displayTerms['label'] = $this->labelDescriptionLookup->getLabel( $entityId ); |
||
222 | $displayTerms['description'] = $this->labelDescriptionLookup->getDescription( $entityId ); |
||
223 | |||
224 | return $displayTerms; |
||
225 | } |
||
226 | |||
227 | } |
||
228 |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.