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\Hooks; |
||
| 4 | |||
| 5 | use Html; |
||
| 6 | use HtmlArmor; |
||
| 7 | use InvalidArgumentException; |
||
| 8 | use Language; |
||
| 9 | use MediaWiki\Search\Hook\ShowSearchHitHook; |
||
| 10 | use MediaWiki\Search\Hook\ShowSearchHitTitleHook; |
||
| 11 | use MWException; |
||
| 12 | use RequestContext; |
||
| 13 | use SearchResult; |
||
| 14 | use SpecialSearch; |
||
| 15 | use Title; |
||
| 16 | use Wikibase\DataModel\Entity\EntityDocument; |
||
| 17 | use Wikibase\DataModel\Entity\Item; |
||
| 18 | use Wikibase\DataModel\Services\Lookup\EntityLookup; |
||
| 19 | use Wikibase\DataModel\Statement\StatementListProvider; |
||
| 20 | use Wikibase\DataModel\Term\DescriptionsProvider; |
||
| 21 | use Wikibase\DataModel\Term\TermFallback; |
||
| 22 | use Wikibase\Lib\LanguageFallbackChainFactory; |
||
| 23 | use Wikibase\Lib\LanguageFallbackIndicator; |
||
| 24 | use Wikibase\Lib\LanguageNameLookup; |
||
| 25 | use Wikibase\Lib\Store\EntityIdLookup; |
||
| 26 | use Wikibase\Lib\Store\RevisionedUnresolvedRedirectException; |
||
| 27 | use Wikibase\Repo\Content\EntityContentFactory; |
||
| 28 | use Wikibase\Repo\Search\ExtendedResult; |
||
| 29 | use Wikibase\Repo\WikibaseRepo; |
||
| 30 | |||
| 31 | /** |
||
| 32 | * Handler to format entities in the search results |
||
| 33 | * |
||
| 34 | * @license GPL-2.0-or-later |
||
| 35 | * @author Matěj Suchánek |
||
| 36 | * @author Daniel Kinzler |
||
| 37 | */ |
||
| 38 | class ShowSearchHitHandler implements ShowSearchHitHook, ShowSearchHitTitleHook { |
||
| 39 | |||
| 40 | private $entityContentFactory; |
||
| 41 | private $entityIdLookup; |
||
| 42 | private $entityLookup; |
||
| 43 | private $fallbackChainFactory; |
||
| 44 | |||
| 45 | public function __construct( |
||
| 46 | EntityContentFactory $entityContentFactory, |
||
| 47 | EntityIdLookup $entityIdLookup, |
||
| 48 | EntityLookup $entityLookup, |
||
| 49 | LanguageFallbackChainFactory $fallbackChainFactory |
||
| 50 | ) { |
||
| 51 | $this->entityContentFactory = $entityContentFactory; |
||
| 52 | $this->entityIdLookup = $entityIdLookup; |
||
| 53 | $this->entityLookup = $entityLookup; |
||
| 54 | $this->fallbackChainFactory = $fallbackChainFactory; |
||
| 55 | } |
||
| 56 | |||
| 57 | public static function factory(): self { |
||
| 58 | $wikibaseRepo = WikibaseRepo::getDefaultInstance(); |
||
| 59 | |||
| 60 | return new self( |
||
| 61 | $wikibaseRepo->getEntityContentFactory(), |
||
| 62 | $wikibaseRepo->getEntityIdLookup(), |
||
| 63 | $wikibaseRepo->getEntityLookup(), |
||
| 64 | $wikibaseRepo->getLanguageFallbackChainFactory() |
||
| 65 | ); |
||
| 66 | } |
||
| 67 | |||
| 68 | /** |
||
| 69 | * Format the output when the search result contains entities |
||
| 70 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/ShowSearchHit |
||
| 71 | * @see showEntityResultHit |
||
| 72 | * @see showPlainSearchHit |
||
| 73 | * @param SpecialSearch $searchPage |
||
| 74 | * @param SearchResult $result |
||
| 75 | * @param string[] $terms |
||
| 76 | * @param string &$link |
||
| 77 | * @param string &$redirect |
||
| 78 | * @param string &$section |
||
| 79 | * @param string &$extract |
||
| 80 | * @param string &$score |
||
| 81 | * @param string &$size |
||
| 82 | * @param string &$date |
||
| 83 | * @param string &$related |
||
| 84 | * @param string &$html |
||
| 85 | * @return void |
||
| 86 | */ |
||
| 87 | public function onShowSearchHit( $searchPage, $result, |
||
| 88 | $terms, &$link, &$redirect, &$section, &$extract, &$score, &$size, &$date, &$related, |
||
| 89 | &$html |
||
| 90 | ): void { |
||
| 91 | if ( $result instanceof ExtendedResult ) { |
||
| 92 | return; |
||
| 93 | } |
||
| 94 | |||
| 95 | $languageFallbackChain = $this->fallbackChainFactory->newFromContext( $searchPage->getContext() ); |
||
| 96 | |||
| 97 | $title = $result->getTitle(); |
||
| 98 | |||
| 99 | if ( !$this->isTitleEntity( $title ) ) { |
||
| 100 | return; |
||
| 101 | } |
||
| 102 | |||
| 103 | try { |
||
| 104 | $entity = $this->getEntity( $title ); |
||
| 105 | } catch ( RevisionedUnresolvedRedirectException $exception ) { |
||
| 106 | return; |
||
| 107 | } |
||
| 108 | |||
| 109 | if ( !( $entity instanceof DescriptionsProvider ) ) { |
||
| 110 | return; |
||
| 111 | } |
||
| 112 | |||
| 113 | $extract = ''; |
||
| 114 | |||
| 115 | $entityTerms = $entity->getDescriptions()->toTextArray(); |
||
| 116 | $termData = $languageFallbackChain->extractPreferredValue( $entityTerms ); |
||
| 117 | if ( $termData !== null ) { |
||
| 118 | // TODO: do something akin to SearchResult::getTextSnippet here? |
||
| 119 | self::addDescription( $extract, $termData, $searchPage ); |
||
| 120 | } |
||
| 121 | |||
| 122 | if ( $entity instanceof StatementListProvider ) { |
||
| 123 | $statementCount = $entity->getStatements()->count(); |
||
| 124 | } else { |
||
| 125 | $statementCount = 0; |
||
| 126 | } |
||
| 127 | if ( $entity instanceof Item ) { |
||
| 128 | $linkCount = $entity->getSiteLinkList()->count(); |
||
| 129 | } else { |
||
| 130 | $linkCount = 0; |
||
| 131 | } |
||
| 132 | |||
| 133 | // set $size to size metrics |
||
| 134 | $size = $searchPage->msg( |
||
| 135 | 'wikibase-search-result-stats', |
||
| 136 | $statementCount, |
||
| 137 | $linkCount |
||
| 138 | )->escaped(); |
||
| 139 | } |
||
| 140 | |||
| 141 | private function isTitleEntity( Title $title ): bool { |
||
| 142 | $contentModel = $title->getContentModel(); |
||
| 143 | return $this->entityContentFactory->isEntityContentModel( $contentModel ); |
||
| 144 | } |
||
| 145 | |||
| 146 | private function getEntity( Title $title ): ?EntityDocument { |
||
| 147 | $entityId = $this->entityIdLookup->getEntityIdForTitle( $title ); |
||
| 148 | if ( $entityId ) { |
||
| 149 | return $this->entityLookup->getEntity( $entityId ); |
||
| 150 | } |
||
| 151 | return null; |
||
| 152 | } |
||
| 153 | |||
| 154 | /** |
||
| 155 | * Add attributes appropriate for language of this text. |
||
| 156 | * @param array &$attr Link attributes, to be modified if needed |
||
| 157 | * @param string $displayLanguage |
||
| 158 | * @param array $text Text description array, with language in ['language'] |
||
| 159 | */ |
||
| 160 | public static function addLanguageAttrs( array &$attr, string $displayLanguage, array $text ) { |
||
| 161 | if ( $text['language'] !== $displayLanguage ) { |
||
| 162 | try { |
||
| 163 | $language = Language::factory( $text['language'] ); |
||
| 164 | } catch ( MWException $e ) { |
||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 165 | // If somebody fed us broken language, ignore it |
||
| 166 | return; |
||
| 167 | } |
||
| 168 | $attr += [ 'dir' => $language->getDir(), 'lang' => $language->getHtmlCode() ]; |
||
| 169 | } |
||
| 170 | } |
||
| 171 | |||
| 172 | /** |
||
| 173 | * Add HTML description to search result. |
||
| 174 | * @param string &$html The html of the description will be appended here. |
||
| 175 | * @param string[] $description Description as [language, value] array |
||
| 176 | * @param SpecialSearch $searchPage |
||
| 177 | */ |
||
| 178 | public static function addDescription( string &$html, array $description, SpecialSearch $searchPage ) { |
||
| 179 | RequestContext::getMain()->getOutput()->addModuleStyles( [ 'wikibase.common' ] ); |
||
| 180 | $displayLanguage = $searchPage->getLanguage()->getCode(); |
||
| 181 | $description = self::withLanguage( $description, $displayLanguage ); |
||
| 182 | $attr = [ 'class' => 'wb-itemlink-description' ]; |
||
| 183 | self::addLanguageAttrs( $attr, $displayLanguage, $description ); |
||
| 184 | // Wrap with searchresult div, as original code does |
||
| 185 | $html .= Html::rawElement( 'div', [ 'class' => 'searchresult' ], |
||
| 186 | Html::rawElement( 'span', $attr, HtmlArmor::getHtml( $description['value'] ) ) |
||
| 187 | ); |
||
| 188 | } |
||
| 189 | |||
| 190 | /** |
||
| 191 | * Remove span tag placed around title search hit for entity titles |
||
| 192 | * to highlight matches in bold. |
||
| 193 | * |
||
| 194 | * @todo Add highlighting when Q##-id matches and not label text. |
||
| 195 | * |
||
| 196 | * @param Title &$title |
||
| 197 | * @param string|HtmlArmor|null &$titleSnippet |
||
| 198 | * @param SearchResult $result |
||
| 199 | * @param array $terms |
||
| 200 | * @param SpecialSearch $specialSearch |
||
| 201 | * @param string[] &$query |
||
| 202 | * @param string[] &$attributes |
||
| 203 | * @return void |
||
| 204 | */ |
||
| 205 | public function onShowSearchHitTitle( |
||
| 206 | &$title, |
||
| 207 | &$titleSnippet, |
||
| 208 | $result, |
||
| 209 | $terms, |
||
| 210 | $specialSearch, |
||
| 211 | &$query, |
||
| 212 | &$attributes |
||
| 213 | ): void { |
||
| 214 | if ( $result instanceof ExtendedResult ) { |
||
| 215 | return; |
||
| 216 | } |
||
| 217 | if ( $this->isTitleEntity( $title ) ) { |
||
| 218 | $titleSnippet = $title->getFullText(); |
||
| 219 | } |
||
| 220 | } |
||
| 221 | |||
| 222 | /** |
||
| 223 | * If text's language is not the same as display language, add |
||
| 224 | * marker with language name to the string. |
||
| 225 | * |
||
| 226 | * @param string[] $text ['language' => LANG, 'value' => TEXT] |
||
| 227 | * @param string $displayLanguage |
||
| 228 | * @return array ['language' => LANG, 'value' => TEXT] |
||
| 229 | */ |
||
| 230 | public static function withLanguage( $text, $displayLanguage ) { |
||
| 231 | if ( $text['language'] == $displayLanguage || $text['value'] == '' ) { |
||
| 232 | return $text; |
||
| 233 | } |
||
| 234 | try { |
||
| 235 | $termFallback = new TermFallback( $displayLanguage, HtmlArmor::getHtml( $text['value'] ), |
||
| 236 | $text['language'], null ); |
||
| 237 | } catch ( InvalidArgumentException $e ) { |
||
| 238 | return $text; |
||
| 239 | } |
||
| 240 | $fallback = new LanguageFallbackIndicator( new LanguageNameLookup( $displayLanguage ) ); |
||
| 241 | $markedText = HtmlArmor::getHtml( $text['value'] ) . $fallback->getHtml( $termFallback ); |
||
| 242 | return [ |
||
| 243 | 'language' => $text['language'], |
||
| 244 | 'value' => new HtmlArmor( $markedText ) |
||
| 245 | ]; |
||
| 246 | } |
||
| 247 | |||
| 248 | } |
||
| 249 |