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
Loading history...
|
|||
| 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
Loading history...
|
|||
| 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: