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 | declare( strict_types = 1 ); |
||
4 | |||
5 | namespace Wikibase\Client\Api; |
||
6 | |||
7 | use ApiQuery; |
||
8 | use ApiQueryBase; |
||
9 | use ApiResult; |
||
10 | use InvalidArgumentException; |
||
11 | use Title; |
||
12 | use Wikibase\Client\WikibaseClient; |
||
13 | use Wikibase\DataAccess\AliasTermBuffer; |
||
14 | use Wikibase\DataModel\Entity\EntityId; |
||
15 | use Wikibase\DataModel\Services\Term\TermBuffer; |
||
16 | use Wikibase\Lib\Store\EntityIdLookup; |
||
17 | use Wikibase\Lib\TermIndexEntry; |
||
18 | |||
19 | /** |
||
20 | * Provides wikibase terms (labels, descriptions, aliases, etc.) for local pages. |
||
21 | * For example, if a data item has the label "Washington" and the description "capital |
||
22 | * city of the US", and has a sitelink to the local page called "Washington DC", calling |
||
23 | * pageterms with titles=Washington_DC would include that label and description |
||
24 | * in the response. |
||
25 | * |
||
26 | * @note This closely mirrors the Repo entityterms API, except for the factory method. |
||
27 | * |
||
28 | * @license GPL-2.0-or-later |
||
29 | * @author Daniel Kinzler |
||
30 | */ |
||
31 | class PageTerms extends ApiQueryBase { |
||
32 | |||
33 | /** |
||
34 | * @todo Use LabelDescriptionLookup for labels/descriptions, so we can apply language fallback. |
||
35 | * @var TermBuffer|AliasTermBuffer |
||
36 | */ |
||
37 | private $termBuffer; |
||
38 | |||
39 | /** |
||
40 | * @var EntityIdLookup |
||
41 | */ |
||
42 | private $idLookup; |
||
43 | |||
44 | public function __construct( |
||
45 | TermBuffer $termBuffer, |
||
46 | EntityIdLookup $idLookup, |
||
47 | ApiQuery $query, |
||
48 | string $moduleName |
||
49 | ) { |
||
50 | parent::__construct( $query, $moduleName, 'wbpt' ); |
||
51 | $this->termBuffer = $termBuffer; |
||
52 | $this->idLookup = $idLookup; |
||
53 | } |
||
54 | |||
55 | public static function factory( ApiQuery $apiQuery, string $moduleName ): self { |
||
56 | $client = WikibaseClient::getDefaultInstance(); |
||
57 | $termBuffer = $client->getTermBuffer(); |
||
58 | $entityIdLookup = $client->getEntityIdLookup(); |
||
59 | |||
60 | return new self( |
||
61 | $termBuffer, |
||
62 | $entityIdLookup, |
||
63 | $apiQuery, |
||
64 | $moduleName |
||
65 | ); |
||
66 | } |
||
67 | |||
68 | public function execute(): void { |
||
69 | $params = $this->extractRequestParams(); |
||
70 | |||
71 | # Only operate on existing pages |
||
72 | $titles = $this->getPageSet()->getGoodTitles(); |
||
73 | if ( !count( $titles ) ) { |
||
74 | # Nothing to do |
||
75 | return; |
||
76 | } |
||
77 | |||
78 | // NOTE: continuation relies on $titles being sorted by page ID. |
||
79 | ksort( $titles ); |
||
80 | |||
81 | $continue = $params['continue']; |
||
82 | $termTypes = $params['terms'] ?? TermIndexEntry::$validTermTypes; |
||
83 | |||
84 | $pagesToEntityIds = $this->getEntityIdsForTitles( $titles, $continue ); |
||
85 | $entityToPageMap = $this->getEntityToPageMap( $pagesToEntityIds ); |
||
86 | |||
87 | $terms = $this->getTermsOfEntities( $pagesToEntityIds, $termTypes, $this->getLanguage()->getCode() ); |
||
88 | |||
89 | $termGroups = $this->groupTermsByPageAndType( $entityToPageMap, $terms ); |
||
90 | |||
91 | $this->addTermsToResult( $pagesToEntityIds, $termGroups ); |
||
92 | } |
||
93 | |||
94 | /** |
||
95 | * @param EntityId[] $entityIds |
||
96 | * @param string[] $termTypes |
||
97 | * @param string $languageCode |
||
98 | * |
||
99 | * @return TermIndexEntry[] |
||
100 | */ |
||
101 | private function getTermsOfEntities( array $entityIds, array $termTypes, string $languageCode ): array { |
||
102 | $this->termBuffer->prefetchTerms( $entityIds, $termTypes, [ $languageCode ] ); |
||
0 ignored issues
–
show
|
|||
103 | |||
104 | $terms = []; |
||
105 | foreach ( $entityIds as $entityId ) { |
||
106 | foreach ( $termTypes as $termType ) { |
||
107 | if ( $termType !== 'alias' ) { |
||
108 | $termText = $this->termBuffer->getPrefetchedTerm( $entityId, $termType, $languageCode ); |
||
0 ignored issues
–
show
The method
getPrefetchedTerm does only exist in Wikibase\DataModel\Services\Term\TermBuffer , but not in Wikibase\DataAccess\AliasTermBuffer .
It seems like the method you are trying to call exists only in some of the possible types. Let’s take a look at an example: class A
{
public function foo() { }
}
class B extends A
{
public function bar() { }
}
/**
* @param A|B $x
*/
function someFunction($x)
{
$x->foo(); // This call is fine as the method exists in A and B.
$x->bar(); // This method only exists in B and might cause an error.
}
Available Fixes
![]() |
|||
109 | if ( $termText !== false && $termText !== null ) { |
||
110 | $terms[] = new TermIndexEntry( [ |
||
111 | TermIndexEntry::FIELD_ENTITY => $entityId, |
||
112 | TermIndexEntry::FIELD_TYPE => $termType, |
||
113 | TermIndexEntry::FIELD_LANGUAGE => $languageCode, |
||
114 | TermIndexEntry::FIELD_TEXT => $termText, |
||
115 | ] ); |
||
116 | } |
||
117 | } else { |
||
118 | $termTexts = $this->termBuffer->getPrefetchedAliases( $entityId, $languageCode ) ?: []; |
||
0 ignored issues
–
show
The method
getPrefetchedAliases does only exist in Wikibase\DataAccess\AliasTermBuffer , but not in Wikibase\DataModel\Services\Term\TermBuffer .
It seems like the method you are trying to call exists only in some of the possible types. Let’s take a look at an example: class A
{
public function foo() { }
}
class B extends A
{
public function bar() { }
}
/**
* @param A|B $x
*/
function someFunction($x)
{
$x->foo(); // This call is fine as the method exists in A and B.
$x->bar(); // This method only exists in B and might cause an error.
}
Available Fixes
![]() |
|||
119 | foreach ( $termTexts as $termText ) { |
||
120 | $terms[] = new TermIndexEntry( [ |
||
121 | TermIndexEntry::FIELD_ENTITY => $entityId, |
||
122 | TermIndexEntry::FIELD_TYPE => $termType, |
||
123 | TermIndexEntry::FIELD_LANGUAGE => $languageCode, |
||
124 | TermIndexEntry::FIELD_TEXT => $termText, |
||
125 | ] ); |
||
126 | } |
||
127 | } |
||
128 | |||
129 | } |
||
130 | } |
||
131 | |||
132 | return $terms; |
||
133 | } |
||
134 | |||
135 | /** |
||
136 | * @param Title[] $titles |
||
137 | * @param int|null $continue |
||
138 | * |
||
139 | * @return array |
||
140 | */ |
||
141 | private function getEntityIdsForTitles( array $titles, $continue = 0 ): array { |
||
142 | $entityIds = $this->idLookup->getEntityIds( $titles ); |
||
143 | |||
144 | // Re-sort, so the order of page IDs matches the order in which $titles |
||
145 | // were given. This is essential for paging to work properly. |
||
146 | // This also skips all page IDs up to $continue. |
||
147 | $sortedEntityId = []; |
||
148 | foreach ( $titles as $pid => $title ) { |
||
149 | if ( $pid >= $continue && isset( $entityIds[$pid] ) ) { |
||
150 | $sortedEntityId[$pid] = $entityIds[$pid]; |
||
151 | } |
||
152 | } |
||
153 | |||
154 | return $sortedEntityId; |
||
155 | } |
||
156 | |||
157 | /** |
||
158 | * @param EntityId[] $entityIds |
||
159 | * |
||
160 | * @return int[] |
||
161 | */ |
||
162 | private function getEntityToPageMap( array $entityIds ): array { |
||
163 | $entityIdsStrings = array_map( |
||
164 | function( EntityId $id ) { |
||
165 | return $id->getSerialization(); |
||
166 | }, |
||
167 | $entityIds |
||
168 | ); |
||
169 | |||
170 | return array_flip( $entityIdsStrings ); |
||
171 | } |
||
172 | |||
173 | /** |
||
174 | * @param int[] $entityToPageMap |
||
175 | * @param TermIndexEntry[] $terms |
||
176 | * |
||
177 | * @return array[] An associative array, mapping pageId + entity type to a list of strings. |
||
178 | */ |
||
179 | private function groupTermsByPageAndType( array $entityToPageMap, array $terms ): array { |
||
180 | $termsPerPage = []; |
||
181 | |||
182 | foreach ( $terms as $term ) { |
||
183 | // Since we construct $terms and $entityToPageMap from the same set of page IDs, |
||
184 | // the entry $entityToPageMap[$key] should really always be set. |
||
185 | $type = $term->getTermType(); |
||
186 | $key = $term->getEntityId()->getSerialization(); |
||
187 | $pageId = $entityToPageMap[$key]; |
||
188 | $text = $term->getText(); |
||
189 | |||
190 | if ( $text !== null ) { |
||
191 | // For each page ID, record a list of terms for each term type. |
||
192 | $termsPerPage[$pageId][$type][] = $text; |
||
193 | } else { |
||
194 | // $text should never be null, but let's be vigilant. |
||
195 | wfWarn( __METHOD__ . ': Encountered null text in TermIndexEntry object!' ); |
||
196 | } |
||
197 | } |
||
198 | |||
199 | return $termsPerPage; |
||
200 | } |
||
201 | |||
202 | /** |
||
203 | * @param EntityId[] $pagesToEntityIds |
||
204 | * @param array[] $termGroups |
||
205 | */ |
||
206 | private function addTermsToResult( array $pagesToEntityIds, array $termGroups ): void { |
||
207 | $result = $this->getResult(); |
||
208 | |||
209 | foreach ( $pagesToEntityIds as $currentPage => $entityId ) { |
||
210 | if ( !isset( $termGroups[$currentPage] ) ) { |
||
211 | // No entity for page, or no terms for entity. |
||
212 | continue; |
||
213 | } |
||
214 | |||
215 | $group = $termGroups[$currentPage]; |
||
216 | |||
217 | if ( !$this->addTermsForPage( $result, $currentPage, $group ) ) { |
||
218 | break; |
||
219 | } |
||
220 | } |
||
221 | } |
||
222 | |||
223 | /** |
||
224 | * Add page term to an ApiResult, adding a continue |
||
225 | * parameter if it doesn't fit. |
||
226 | * |
||
227 | * @param ApiResult $result |
||
228 | * @param int $pageId |
||
229 | * @param array[] $termsByType |
||
230 | * |
||
231 | * @throws InvalidArgumentException |
||
232 | * @return bool True if it fits in the result |
||
233 | */ |
||
234 | private function addTermsForPage( ApiResult $result, int $pageId, array $termsByType ): bool { |
||
235 | ApiResult::setIndexedTagNameRecursive( $termsByType, 'term' ); |
||
236 | |||
237 | $fit = $result->addValue( [ 'query', 'pages', $pageId ], 'terms', $termsByType ); |
||
238 | |||
239 | if ( !$fit ) { |
||
240 | $this->setContinueEnumParameter( 'continue', $pageId ); |
||
241 | } |
||
242 | |||
243 | return $fit; |
||
244 | } |
||
245 | |||
246 | /** |
||
247 | * @see ApiQueryBase::getCacheMode |
||
248 | * |
||
249 | * @param array $params |
||
250 | * @return string |
||
251 | */ |
||
252 | public function getCacheMode( $params ): string { |
||
253 | return 'public'; |
||
254 | } |
||
255 | |||
256 | /** |
||
257 | * @inheritDoc |
||
258 | */ |
||
259 | protected function getAllowedParams(): array { |
||
260 | return [ |
||
261 | 'continue' => [ |
||
262 | self::PARAM_HELP_MSG => 'api-help-param-continue', |
||
263 | self::PARAM_TYPE => 'integer', |
||
264 | ], |
||
265 | 'terms' => [ |
||
266 | self::PARAM_TYPE => TermIndexEntry::$validTermTypes, |
||
267 | self::PARAM_DFLT => implode( '|', TermIndexEntry::$validTermTypes ), |
||
268 | self::PARAM_ISMULTI => true, |
||
269 | self::PARAM_HELP_MSG => 'apihelp-query+pageterms-param-terms', |
||
270 | ], |
||
271 | ]; |
||
272 | } |
||
273 | |||
274 | /** |
||
275 | * @inheritDoc |
||
276 | */ |
||
277 | protected function getExamplesMessages(): array { |
||
278 | return [ |
||
279 | 'action=query&prop=pageterms&titles=London' |
||
280 | => 'apihelp-query+pageterms-example-simple', |
||
281 | 'action=query&prop=pageterms&titles=London&wbptterms=label|alias&uselang=en' |
||
282 | => 'apihelp-query+pageterms-example-label-en', |
||
283 | ]; |
||
284 | } |
||
285 | |||
286 | } |
||
287 |
It seems like the method you are trying to call exists only in some of the possible types.
Let’s take a look at an example:
Available Fixes
Add an additional type-check:
Only allow a single type to be passed if the variable comes from a parameter: