Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
44.32% |
242 / 546 |
|
30.43% |
7 / 23 |
CRAP | |
0.00% |
0 / 1 |
| PFValuesUtils | |
44.32% |
242 / 546 |
|
30.43% |
7 / 23 |
4980.66 | |
0.00% |
0 / 1 |
| getSMWPropertyValues | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
56 | |||
| getCategoriesForPage | |
16.67% |
3 / 18 |
|
0.00% |
0 / 1 |
13.26 | |||
| getAllCategories | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
2 | |||
| getAllValuesForProperty | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
| getAllValuesFromWikidata | |
0.00% |
0 / 51 |
|
0.00% |
0 / 1 |
56 | |||
| getAllValuesForCargoField | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getValuesForCargoField | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
30 | |||
| getAllPagesForCategory | |
62.35% |
53 / 85 |
|
0.00% |
0 / 1 |
47.82 | |||
| fixedMultiSort | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
| getAllPagesForConcept | |
0.00% |
0 / 61 |
|
0.00% |
0 / 1 |
462 | |||
| getAllPagesForNamespace | |
51.14% |
45 / 88 |
|
0.00% |
0 / 1 |
97.92 | |||
| getAutocompleteValues | |
39.29% |
11 / 28 |
|
0.00% |
0 / 1 |
27.13 | |||
| getAutocompletionTypeAndSource | |
75.51% |
37 / 49 |
|
0.00% |
0 / 1 |
21.24 | |||
| getRemoteDataTypeAndPossiblySetAutocompleteValues | |
95.00% |
19 / 20 |
|
0.00% |
0 / 1 |
13 | |||
| setAutocompleteValues | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
3 | |||
| getValuesArray | |
87.50% |
7 / 8 |
|
0.00% |
0 / 1 |
4.03 | |||
| getValuesFromExternalURL | |
38.89% |
7 / 18 |
|
0.00% |
0 / 1 |
18.18 | |||
| getSQLConditionForAutocompleteInColumn | |
93.75% |
15 / 16 |
|
0.00% |
0 / 1 |
6.01 | |||
| getAllPagesForQuery | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
12 | |||
| processSemanticQuery | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
| getMaxValuesToRetrieve | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| standardizeNamespace | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| labelToValue | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * Static functions for handling lists of values and labels. |
| 4 | * |
| 5 | * @author Yaron Koren |
| 6 | * @file |
| 7 | * @ingroup PF |
| 8 | */ |
| 9 | |
| 10 | use MediaWiki\MediaWikiServices; |
| 11 | use MediaWiki\Title\Title; |
| 12 | |
| 13 | class PFValuesUtils { |
| 14 | |
| 15 | /** |
| 16 | * Helper function to handle getPropertyValues(). |
| 17 | * |
| 18 | * @param Store $store |
| 19 | * @param Title $subject |
| 20 | * @param string $propID |
| 21 | * @param \SMW\RequestOptions|null $requestOptions |
| 22 | * @return array |
| 23 | * @suppress PhanUndeclaredTypeParameter For Store |
| 24 | */ |
| 25 | public static function getSMWPropertyValues( $store, $subject, $propID, $requestOptions = null ) { |
| 26 | // If SMW is not installed, exit out. |
| 27 | if ( !class_exists( '\SMW\DIWikiPage' ) ) { |
| 28 | return []; |
| 29 | } |
| 30 | if ( $subject === null ) { |
| 31 | $page = null; |
| 32 | } else { |
| 33 | $page = \SMW\DIWikiPage::newFromTitle( $subject ); |
| 34 | } |
| 35 | $property = \SMW\DIProperty::newFromUserLabel( $propID ); |
| 36 | $res = $store->getPropertyValues( $page, $property, $requestOptions ); |
| 37 | $values = []; |
| 38 | foreach ( $res as $value ) { |
| 39 | if ( $value instanceof SMWDIUri ) { |
| 40 | $values[] = $value->getURI(); |
| 41 | } elseif ( $value instanceof \SMW\DIWikiPage ) { |
| 42 | $realValue = str_replace( '_', ' ', $value->getDBKey() ); |
| 43 | if ( $value->getNamespace() != 0 ) { |
| 44 | $realValue = PFUtils::getCanonicalName( $value->getNamespace() ) . ":$realValue"; |
| 45 | } |
| 46 | $values[] = $realValue; |
| 47 | } else { |
| 48 | // getSortKey() seems to return the correct |
| 49 | // value for all the other data types. |
| 50 | $values[] = str_replace( '_', ' ', $value->getSortKey() ); |
| 51 | } |
| 52 | } |
| 53 | return $values; |
| 54 | } |
| 55 | |
| 56 | /** |
| 57 | * Helper function - gets names of categories for a page; |
| 58 | * based on Title::getParentCategories(), but simpler. |
| 59 | * |
| 60 | * @param Title $title |
| 61 | * @return array |
| 62 | */ |
| 63 | public static function getCategoriesForPage( $title ) { |
| 64 | $db = PFUtils::getReadDB(); |
| 65 | if ( !$db->fieldExists( 'categorylinks', 'cl_to' ) ) { |
| 66 | // MW 1.45+ |
| 67 | // Just call the original function, instead of |
| 68 | // trying to create a simplified version of it. |
| 69 | return $title->getParentCategories(); |
| 70 | } |
| 71 | |
| 72 | $categories = []; |
| 73 | $titlekey = $title->getArticleID(); |
| 74 | if ( $titlekey == 0 ) { |
| 75 | // Something's wrong - exit |
| 76 | return $categories; |
| 77 | } |
| 78 | $conditions = [ 'cl_from' => $titlekey ]; |
| 79 | $res = $db->select( |
| 80 | 'categorylinks', |
| 81 | 'DISTINCT cl_to', |
| 82 | $conditions, |
| 83 | __METHOD__ |
| 84 | ); |
| 85 | while ( $row = $res->fetchRow() ) { |
| 86 | $categories[] = $row['cl_to']; |
| 87 | } |
| 88 | $res->free(); |
| 89 | return $categories; |
| 90 | } |
| 91 | |
| 92 | /** |
| 93 | * Helper function - returns names of all the categories. |
| 94 | * @return array |
| 95 | */ |
| 96 | public static function getAllCategories() { |
| 97 | $categories = []; |
| 98 | $db = PFUtils::getReadDB(); |
| 99 | $res = $db->select( |
| 100 | 'category', |
| 101 | 'cat_title', |
| 102 | null, |
| 103 | __METHOD__ |
| 104 | ); |
| 105 | while ( $row = $res->fetchRow() ) { |
| 106 | $categories[] = $row['cat_title']; |
| 107 | } |
| 108 | $res->free(); |
| 109 | return $categories; |
| 110 | } |
| 111 | |
| 112 | /** |
| 113 | * This function, unlike the others, doesn't take in a substring |
| 114 | * because it uses the SMW data store, which can't perform |
| 115 | * case-insensitive queries; for queries with a substring, the |
| 116 | * function PFAutocompleteAPI::getAllValuesForProperty() exists. |
| 117 | * |
| 118 | * @param string $property_name |
| 119 | * @return array |
| 120 | */ |
| 121 | public static function getAllValuesForProperty( $property_name ) { |
| 122 | $store = PFUtils::getSMWStore(); |
| 123 | if ( $store == null ) { |
| 124 | return []; |
| 125 | } |
| 126 | $requestoptions = new \SMW\RequestOptions(); |
| 127 | $requestoptions->limit = self::getMaxValuesToRetrieve(); |
| 128 | $values = self::getSMWPropertyValues( $store, null, $property_name, $requestoptions ); |
| 129 | sort( $values ); |
| 130 | return $values; |
| 131 | } |
| 132 | |
| 133 | /** |
| 134 | * This function is used for fetching the values from wikidata based on the provided |
| 135 | * annotations. For queries with substring, the function returns all the values which |
| 136 | * have the substring in it. |
| 137 | * |
| 138 | * @param string $query |
| 139 | * @param string|null $substring |
| 140 | * @return array |
| 141 | */ |
| 142 | public static function getAllValuesFromWikidata( $query, $substring = null ) { |
| 143 | $endpointUrl = "https://query.wikidata.org/sparql"; |
| 144 | global $wgLanguageCode; |
| 145 | |
| 146 | $query = urldecode( $query ); |
| 147 | |
| 148 | $filter_strings = explode( '&', $query ); |
| 149 | $filters = []; |
| 150 | |
| 151 | foreach ( $filter_strings as $filter ) { |
| 152 | $temp = explode( "=", $filter ); |
| 153 | $filters[ $temp[ 0 ] ] = $temp[ 1 ]; |
| 154 | } |
| 155 | |
| 156 | $attributesQuery = ""; |
| 157 | $count = 0; |
| 158 | foreach ( $filters as $key => $val ) { |
| 159 | $attributesQuery .= "wdt:" . $key; |
| 160 | if ( is_numeric( str_replace( "Q", "", $val ) ) ) { |
| 161 | $attributesQuery .= " wd:" . $val . ";"; |
| 162 | } else { |
| 163 | $attributesQuery .= "?customLabel" . $count . " . |
| 164 | ?customLabel" . $count . " rdfs:label \"" . $val . "\"@" . $wgLanguageCode . " . "; |
| 165 | $count++; |
| 166 | $attributesQuery .= "?value "; |
| 167 | } |
| 168 | } |
| 169 | unset( $count ); |
| 170 | $attributesQuery = rtrim( $attributesQuery, ";" ); |
| 171 | $attributesQuery = rtrim( $attributesQuery, ". ?value " ); |
| 172 | |
| 173 | $sparqlQueryString = " |
| 174 | SELECT DISTINCT ?valueLabel WHERE { |
| 175 | { |
| 176 | SELECT ?value WHERE { |
| 177 | ?value " . $attributesQuery . " . |
| 178 | ?value rdfs:label ?valueLabel . |
| 179 | FILTER(LANG(?valueLabel) = \"" . $wgLanguageCode . "\") . |
| 180 | FILTER(REGEX(LCASE(?valueLabel), \"\\\\b" . strtolower( $substring ?? '' ) . "\")) |
| 181 | } "; |
| 182 | $maxValues = self::getMaxValuesToRetrieve( $substring ); |
| 183 | $sparqlQueryString .= "LIMIT " . ( $maxValues + 10 ); |
| 184 | $sparqlQueryString .= "} |
| 185 | SERVICE wikibase:label { bd:serviceParam wikibase:language \"" . $wgLanguageCode . "\". } |
| 186 | }"; |
| 187 | $sparqlQueryString .= "LIMIT " . $maxValues; |
| 188 | $opts = [ |
| 189 | 'http' => [ |
| 190 | 'method' => 'GET', |
| 191 | 'header' => [ |
| 192 | 'Accept: application/sparql-results+json', |
| 193 | 'User-Agent: PageForms_API PHP/8.0' |
| 194 | ], |
| 195 | ], |
| 196 | ]; |
| 197 | $context = stream_context_create( $opts ); |
| 198 | |
| 199 | $url = $endpointUrl . '?query=' . urlencode( $sparqlQueryString ); |
| 200 | $response = file_get_contents( $url, false, $context ); |
| 201 | $apiResults = json_decode( $response, true ); |
| 202 | $results = []; |
| 203 | if ( $apiResults != null ) { |
| 204 | $apiResults = $apiResults[ 'results' ][ 'bindings' ]; |
| 205 | foreach ( $apiResults as $result ) { |
| 206 | foreach ( $result as $key => $val ) { |
| 207 | array_push( $results, $val[ 'value' ] ); |
| 208 | } |
| 209 | } |
| 210 | } |
| 211 | return $results; |
| 212 | } |
| 213 | |
| 214 | /** |
| 215 | * Used with the Cargo extension. |
| 216 | * @param string $tableName |
| 217 | * @param string $fieldName |
| 218 | * @return array |
| 219 | */ |
| 220 | public static function getAllValuesForCargoField( $tableName, $fieldName ) { |
| 221 | return self::getValuesForCargoField( $tableName, $fieldName ); |
| 222 | } |
| 223 | |
| 224 | /** |
| 225 | * Used with the Cargo extension. |
| 226 | * @param string $tableName |
| 227 | * @param string $fieldName |
| 228 | * @param string|null $whereStr |
| 229 | * @return array |
| 230 | */ |
| 231 | public static function getValuesForCargoField( $tableName, $fieldName, $whereStr = null ) { |
| 232 | global $wgPageFormsMaxLocalAutocompleteValues; |
| 233 | |
| 234 | // The limit should be greater than the maximum number of local |
| 235 | // autocomplete values, so that form inputs also know whether |
| 236 | // to switch to remote autocompletion. |
| 237 | // (We increment by 10, to be on the safe side, since some values |
| 238 | // can be null, etc.) |
| 239 | $limitStr = max( 100, $wgPageFormsMaxLocalAutocompleteValues + 10 ); |
| 240 | |
| 241 | try { |
| 242 | $sqlQuery = CargoSQLQuery::newFromValues( $tableName, $fieldName, $whereStr, $joinOnStr = null, $fieldName, $havingStr = null, $fieldName, $limitStr, $offsetStr = 0 ); |
| 243 | // @phan-suppress-next-line PhanUnusedVariableCaughtException |
| 244 | } catch ( Exception $e ) { |
| 245 | return []; |
| 246 | } |
| 247 | |
| 248 | $queryResults = $sqlQuery->run(); |
| 249 | $values = []; |
| 250 | // Field names starting with a '_' are special fields - |
| 251 | // all other fields will have had their underscores |
| 252 | // replaced with spaces in $queryResults. |
| 253 | if ( $fieldName[0] == '_' ) { |
| 254 | $fieldAlias = $fieldName; |
| 255 | } else { |
| 256 | $fieldAlias = str_replace( '_', ' ', $fieldName ); |
| 257 | } |
| 258 | foreach ( $queryResults as $row ) { |
| 259 | if ( !isset( $row[$fieldAlias] ) ) { |
| 260 | continue; |
| 261 | } |
| 262 | // Cargo HTML-encodes everything - decode the quotes and |
| 263 | // angular brackets. |
| 264 | $values[] = html_entity_decode( $row[$fieldAlias] ); |
| 265 | } |
| 266 | return $values; |
| 267 | } |
| 268 | |
| 269 | /** |
| 270 | * Get all the pages that belong to a category and all its |
| 271 | * subcategories, down a certain number of levels - heavily based on |
| 272 | * SMW's SMWInlineQuery::includeSubcategories(). |
| 273 | * |
| 274 | * @param string $top_category |
| 275 | * @param int $num_levels |
| 276 | * @param string|null $substring |
| 277 | * @return string[] |
| 278 | */ |
| 279 | public static function getAllPagesForCategory( $top_category, $num_levels, $substring = null ) { |
| 280 | if ( $num_levels == 0 ) { |
| 281 | return [ $top_category ]; |
| 282 | } |
| 283 | global $wgPageFormsUseDisplayTitle; |
| 284 | |
| 285 | $db = PFUtils::getReadDB(); |
| 286 | $top_category = str_replace( ' ', '_', $top_category ); |
| 287 | $categories = [ $top_category ]; |
| 288 | $checkcategories = [ $top_category ]; |
| 289 | $pages = []; |
| 290 | $sortkeys = []; |
| 291 | for ( $level = $num_levels; $level > 0; $level-- ) { |
| 292 | $newcategories = []; |
| 293 | foreach ( $checkcategories as $category ) { |
| 294 | $tables = [ 'categorylinks', 'page' ]; |
| 295 | $columns = [ 'page_title', 'page_namespace' ]; |
| 296 | $conditions = []; |
| 297 | $join = []; |
| 298 | $join['categorylinks'] = [ 'JOIN', 'cl_from = page_id' ]; |
| 299 | if ( $db->fieldExists( 'categorylinks', 'cl_to' ) ) { |
| 300 | $conditions['cl_to'] = $category; |
| 301 | } else { |
| 302 | // MW 1.45+ |
| 303 | $tables[] = 'linktarget'; |
| 304 | $join['linktarget'] = [ 'JOIN', 'cl_target_id = lt_id' ]; |
| 305 | $conditions['lt_title'] = $category; |
| 306 | } |
| 307 | if ( $wgPageFormsUseDisplayTitle ) { |
| 308 | $tables['pp_displaytitle'] = 'page_props'; |
| 309 | $tables['pp_defaultsort'] = 'page_props'; |
| 310 | $columns['pp_displaytitle_value'] = 'pp_displaytitle.pp_value'; |
| 311 | $columns['pp_defaultsort_value'] = 'pp_defaultsort.pp_value'; |
| 312 | $join['pp_displaytitle'] = [ |
| 313 | 'LEFT JOIN', [ |
| 314 | 'pp_displaytitle.pp_page = page_id', |
| 315 | 'pp_displaytitle.pp_propname = \'displaytitle\'' |
| 316 | ] |
| 317 | ]; |
| 318 | $join['pp_defaultsort'] = [ |
| 319 | 'LEFT JOIN', [ |
| 320 | 'pp_defaultsort.pp_page = page_id', |
| 321 | 'pp_defaultsort.pp_propname = \'defaultsort\'' |
| 322 | ] |
| 323 | ]; |
| 324 | if ( $substring != null ) { |
| 325 | $conditions[] = '((pp_displaytitle.pp_value IS NULL OR pp_displaytitle.pp_value = \'\') AND (' . |
| 326 | self::getSQLConditionForAutocompleteInColumn( 'page_title', $substring ) . |
| 327 | ')) OR ' . |
| 328 | self::getSQLConditionForAutocompleteInColumn( 'pp_displaytitle.pp_value', $substring, false ) . |
| 329 | ' OR page_namespace = ' . NS_CATEGORY; |
| 330 | } |
| 331 | } else { |
| 332 | if ( $substring != null ) { |
| 333 | $conditions[] = self::getSQLConditionForAutocompleteInColumn( 'page_title', $substring ) . ' OR page_namespace = ' . NS_CATEGORY; |
| 334 | } |
| 335 | } |
| 336 | // Make the query. |
| 337 | $res = $db->select( |
| 338 | $tables, |
| 339 | $columns, |
| 340 | $conditions, |
| 341 | __METHOD__, |
| 342 | $options = [ |
| 343 | 'ORDER BY' => 'cl_type, cl_sortkey', |
| 344 | 'LIMIT' => self::getMaxValuesToRetrieve( $substring ) |
| 345 | ], |
| 346 | $join ); |
| 347 | if ( $res ) { |
| 348 | while ( $res && $row = $res->fetchRow() ) { |
| 349 | if ( !array_key_exists( 'page_title', $row ) ) { |
| 350 | continue; |
| 351 | } |
| 352 | $page_namespace = $row['page_namespace']; |
| 353 | $page_name = $row[ 'page_title' ]; |
| 354 | if ( $page_namespace == NS_CATEGORY ) { |
| 355 | if ( !in_array( $page_name, $categories ) ) { |
| 356 | $newcategories[] = $page_name; |
| 357 | } |
| 358 | } else { |
| 359 | $cur_title = Title::makeTitleSafe( $page_namespace, $page_name ); |
| 360 | if ( $cur_title === null ) { |
| 361 | // This can happen if it's |
| 362 | // a "phantom" page, in a |
| 363 | // namespace that no longer exists. |
| 364 | continue; |
| 365 | } |
| 366 | $cur_value = $cur_title->getPrefixedText(); |
| 367 | if ( !in_array( $cur_value, $pages ) ) { |
| 368 | if ( array_key_exists( 'pp_displaytitle_value', $row ) && |
| 369 | ( $row[ 'pp_displaytitle_value' ] ) !== null && |
| 370 | trim( str_replace( ' ', '', strip_tags( $row[ 'pp_displaytitle_value' ] ) ) ) !== '' ) { |
| 371 | $pages[ $cur_value . '@' ] = htmlspecialchars_decode( $row[ 'pp_displaytitle_value'] ); |
| 372 | } else { |
| 373 | $pages[ $cur_value . '@' ] = $cur_value; |
| 374 | } |
| 375 | if ( array_key_exists( 'pp_defaultsort_value', $row ) && |
| 376 | ( $row[ 'pp_defaultsort_value' ] ) !== null ) { |
| 377 | $sortkeys[ $cur_value ] = $row[ 'pp_defaultsort_value']; |
| 378 | } else { |
| 379 | $sortkeys[ $cur_value ] = $cur_value; |
| 380 | } |
| 381 | } |
| 382 | } |
| 383 | } |
| 384 | $res->free(); |
| 385 | } |
| 386 | } |
| 387 | if ( count( $newcategories ) == 0 ) { |
| 388 | return self::fixedMultiSort( $sortkeys, $pages ); |
| 389 | } else { |
| 390 | $categories = array_merge( $categories, $newcategories ); |
| 391 | } |
| 392 | $checkcategories = array_diff( $newcategories, [] ); |
| 393 | } |
| 394 | return self::fixedMultiSort( $sortkeys, $pages ); |
| 395 | } |
| 396 | |
| 397 | /** |
| 398 | * array_multisort() unfortunately messes up array keys that are |
| 399 | * numeric - they get converted to 0, 1, etc. There are a few ways to |
| 400 | * get around this, but I (Yaron) couldn't get those working, so |
| 401 | * instead we're going with this hack, where all key values get |
| 402 | * appended with a '@' before sorting, which is then removed after |
| 403 | * sorting. It's inefficient, but it's probably good enough. |
| 404 | * |
| 405 | * @param string[] $sortkeys |
| 406 | * @param string[] $pages |
| 407 | * @return string[] a sorted version of $pages, sorted via $sortkeys |
| 408 | */ |
| 409 | static function fixedMultiSort( $sortkeys, $pages ) { |
| 410 | array_multisort( $sortkeys, $pages ); |
| 411 | $newPages = []; |
| 412 | foreach ( $pages as $key => $value ) { |
| 413 | $fixedKey = rtrim( $key, '@' ); |
| 414 | $newPages[$fixedKey] = $value; |
| 415 | } |
| 416 | return $newPages; |
| 417 | } |
| 418 | |
| 419 | /** |
| 420 | * @param string $conceptName |
| 421 | * @param string|null $substring |
| 422 | * @return string[] |
| 423 | */ |
| 424 | public static function getAllPagesForConcept( $conceptName, $substring = null ) { |
| 425 | global $wgPageFormsAutocompleteOnAllChars; |
| 426 | |
| 427 | $store = PFUtils::getSMWStore(); |
| 428 | if ( $store == null ) { |
| 429 | return []; |
| 430 | } |
| 431 | |
| 432 | $conceptTitle = Title::makeTitleSafe( SMW_NS_CONCEPT, $conceptName ); |
| 433 | |
| 434 | if ( $substring !== null ) { |
| 435 | $substring = strtolower( $substring ); |
| 436 | } |
| 437 | |
| 438 | // Escape if there's no such concept. |
| 439 | if ( $conceptTitle == null || !$conceptTitle->exists() ) { |
| 440 | throw new MWException( wfMessage( 'pf-missingconcept', wfEscapeWikiText( $conceptName ) ) ); |
| 441 | } |
| 442 | |
| 443 | global $wgPageFormsUseDisplayTitle; |
| 444 | $conceptDI = \SMW\DIWikiPage::newFromTitle( $conceptTitle ); |
| 445 | $desc = new \SMW\Query\Language\ConceptDescription( $conceptDI ); |
| 446 | $printout = new \SMW\Query\PrintRequest( \SMW\Query\PrintRequest::PRINT_THIS, "" ); |
| 447 | $desc->addPrintRequest( $printout ); |
| 448 | $query = new SMWQuery( $desc ); |
| 449 | $query_result = $store->getQueryResult( $query ); |
| 450 | $pages = []; |
| 451 | $sortkeys = []; |
| 452 | $titles = []; |
| 453 | while ( $res = $query_result->getNext() ) { |
| 454 | $page = $res[0]->getNextText( SMW_OUTPUT_WIKI ); |
| 455 | if ( $wgPageFormsUseDisplayTitle ) { |
| 456 | $title = Title::newFromText( $page ); |
| 457 | if ( $title !== null ) { |
| 458 | $titles[] = $title; |
| 459 | } |
| 460 | } else { |
| 461 | $pages[$page] = $page; |
| 462 | $sortkeys[$page] = $page; |
| 463 | } |
| 464 | } |
| 465 | |
| 466 | if ( $wgPageFormsUseDisplayTitle ) { |
| 467 | $properties = MediaWikiServices::getInstance()->getPageProps()->getProperties( |
| 468 | $titles, [ 'displaytitle', 'defaultsort' ] |
| 469 | ); |
| 470 | foreach ( $titles as $title ) { |
| 471 | if ( array_key_exists( $title->getArticleID(), $properties ) ) { |
| 472 | $titleprops = $properties[$title->getArticleID()]; |
| 473 | } else { |
| 474 | $titleprops = []; |
| 475 | } |
| 476 | |
| 477 | $titleText = $title->getPrefixedText(); |
| 478 | if ( array_key_exists( 'displaytitle', $titleprops ) && |
| 479 | trim( str_replace( ' ', '', strip_tags( $titleprops['displaytitle'] ) ) ) !== '' ) { |
| 480 | $pages[$titleText] = htmlspecialchars_decode( $titleprops['displaytitle'] ); |
| 481 | } else { |
| 482 | $pages[$titleText] = $titleText; |
| 483 | } |
| 484 | if ( array_key_exists( 'defaultsort', $titleprops ) ) { |
| 485 | $sortkeys[$titleText] = $titleprops['defaultsort']; |
| 486 | } else { |
| 487 | $sortkeys[$titleText] = $titleText; |
| 488 | } |
| 489 | } |
| 490 | } |
| 491 | |
| 492 | if ( $substring !== null ) { |
| 493 | $filtered_pages = []; |
| 494 | $filtered_sortkeys = []; |
| 495 | foreach ( $pages as $index => $pageName ) { |
| 496 | // Filter on the substring manually. It would |
| 497 | // be better to do this filtering in the |
| 498 | // original SMW query, but that doesn't seem |
| 499 | // possible yet. |
| 500 | // @TODO - this will miss a lot of results for |
| 501 | // concepts with > 1000 pages. Instead, this |
| 502 | // code should loop through all the pages, |
| 503 | // using "offset". |
| 504 | $lowercasePageName = strtolower( $pageName ); |
| 505 | $position = strpos( $lowercasePageName, $substring ); |
| 506 | if ( $position !== false ) { |
| 507 | if ( $wgPageFormsAutocompleteOnAllChars ) { |
| 508 | if ( $position >= 0 ) { |
| 509 | $filtered_pages[$index] = $pageName; |
| 510 | $filtered_sortkeys[$index] = $sortkeys[$index]; |
| 511 | } |
| 512 | } else { |
| 513 | if ( $position === 0 || |
| 514 | strpos( $lowercasePageName, ' ' . $substring ) > 0 ) { |
| 515 | $filtered_pages[$index] = $pageName; |
| 516 | $filtered_sortkeys[$index] = $sortkeys[$index]; |
| 517 | } |
| 518 | } |
| 519 | } |
| 520 | } |
| 521 | $pages = $filtered_pages; |
| 522 | $sortkeys = $filtered_sortkeys; |
| 523 | } |
| 524 | array_multisort( $sortkeys, $pages ); |
| 525 | // Set the "limit" here. |
| 526 | $pages = array_slice( $pages, 0, self::getMaxValuesToRetrieve( $substring ), true ); |
| 527 | |
| 528 | return $pages; |
| 529 | } |
| 530 | |
| 531 | public static function getAllPagesForNamespace( $namespaceStr, $substring = null ) { |
| 532 | global $wgLanguageCode, $wgPageFormsUseDisplayTitle; |
| 533 | |
| 534 | if ( $namespaceStr === '_contentNamespaces' ) { |
| 535 | global $wgContentNamespaces; |
| 536 | $queriedNamespaces = $wgContentNamespaces; |
| 537 | $namespaceConditions = array_map( static function ( $ns ) { |
| 538 | return "page_namespace = $ns"; |
| 539 | }, $queriedNamespaces ); |
| 540 | $namespaceNames = []; |
| 541 | } else { |
| 542 | $namespaceNames = explode( ',', $namespaceStr ); |
| 543 | $queriedNamespaces = []; |
| 544 | $namespaceConditions = []; |
| 545 | |
| 546 | } |
| 547 | |
| 548 | $allNamespaces = PFUtils::getContLang()->getNamespaces(); |
| 549 | |
| 550 | if ( $wgLanguageCode !== 'en' ) { |
| 551 | $englishLang = MediaWikiServices::getInstance()->getLanguageFactory()->getLanguage( 'en' ); |
| 552 | $allEnglishNamespaces = $englishLang->getNamespaces(); |
| 553 | } |
| 554 | |
| 555 | foreach ( $namespaceNames as $namespace_name ) { |
| 556 | $namespace_name = self::standardizeNamespace( $namespace_name ); |
| 557 | // Cycle through all the namespace names for this language, and |
| 558 | // if one matches the namespace specified in the form, get the |
| 559 | // names of all the pages in that namespace. |
| 560 | |
| 561 | // Switch to blank for the string 'Main'. |
| 562 | if ( $namespace_name == 'Main' || $namespace_name == 'main' ) { |
| 563 | $namespace_name = ''; |
| 564 | } |
| 565 | $matchingNamespaceCode = null; |
| 566 | foreach ( $allNamespaces as $curNSCode => $curNSName ) { |
| 567 | if ( $curNSName == $namespace_name ) { |
| 568 | $matchingNamespaceCode = $curNSCode; |
| 569 | } |
| 570 | } |
| 571 | |
| 572 | // If that didn't find anything, and we're in a language |
| 573 | // other than English, check English as well. |
| 574 | if ( $matchingNamespaceCode === null && $wgLanguageCode != 'en' ) { |
| 575 | foreach ( $allEnglishNamespaces as $curNSCode => $curNSName ) { |
| 576 | if ( $curNSName == $namespace_name ) { |
| 577 | $matchingNamespaceCode = $curNSCode; |
| 578 | } |
| 579 | } |
| 580 | } |
| 581 | |
| 582 | if ( $matchingNamespaceCode === null ) { |
| 583 | throw new MWException( wfMessage( 'pf-missingnamespace', wfEscapeWikiText( $namespace_name ) ) ); |
| 584 | } |
| 585 | |
| 586 | $queriedNamespaces[] = $matchingNamespaceCode; |
| 587 | $namespaceConditions[] = "page_namespace = $matchingNamespaceCode"; |
| 588 | } |
| 589 | |
| 590 | $db = PFUtils::getReadDB(); |
| 591 | $conditions = []; |
| 592 | $conditions[] = implode( ' OR ', $namespaceConditions ); |
| 593 | $tables = [ 'page' ]; |
| 594 | $columns = [ 'page_title' ]; |
| 595 | if ( count( $namespaceConditions ) > 1 ) { |
| 596 | $columns[] = 'page_namespace'; |
| 597 | } |
| 598 | if ( $wgPageFormsUseDisplayTitle ) { |
| 599 | $tables['pp_displaytitle'] = 'page_props'; |
| 600 | $tables['pp_defaultsort'] = 'page_props'; |
| 601 | $columns['pp_displaytitle_value'] = 'pp_displaytitle.pp_value'; |
| 602 | $columns['pp_defaultsort_value'] = 'pp_defaultsort.pp_value'; |
| 603 | $join = [ |
| 604 | 'pp_displaytitle' => [ |
| 605 | 'LEFT JOIN', [ |
| 606 | 'pp_displaytitle.pp_page = page_id', |
| 607 | 'pp_displaytitle.pp_propname = \'displaytitle\'' |
| 608 | ] |
| 609 | ], |
| 610 | 'pp_defaultsort' => [ |
| 611 | 'LEFT JOIN', [ |
| 612 | 'pp_defaultsort.pp_page = page_id', |
| 613 | 'pp_defaultsort.pp_propname = \'defaultsort\'' |
| 614 | ] |
| 615 | ] |
| 616 | ]; |
| 617 | if ( $substring != null ) { |
| 618 | $substringCondition = '(pp_displaytitle.pp_value IS NULL AND (' . |
| 619 | self::getSQLConditionForAutocompleteInColumn( 'page_title', $substring ) . |
| 620 | ')) OR ' . |
| 621 | self::getSQLConditionForAutocompleteInColumn( 'pp_displaytitle.pp_value', $substring, false ); |
| 622 | if ( !in_array( NS_CATEGORY, $queriedNamespaces ) ) { |
| 623 | $substringCondition .= ' OR page_namespace = ' . NS_CATEGORY; |
| 624 | } |
| 625 | $conditions[] = $substringCondition; |
| 626 | } |
| 627 | } else { |
| 628 | $join = []; |
| 629 | if ( $substring != null ) { |
| 630 | $conditions[] = self::getSQLConditionForAutocompleteInColumn( 'page_title', $substring ); |
| 631 | } |
| 632 | } |
| 633 | $options = [ |
| 634 | 'LIMIT' => self::getMaxValuesToRetrieve( $substring ) |
| 635 | ]; |
| 636 | $res = $db->select( $tables, $columns, $conditions, __METHOD__, $options, $join ); |
| 637 | |
| 638 | $pages = []; |
| 639 | $sortkeys = []; |
| 640 | while ( $row = $res->fetchRow() ) { |
| 641 | // If there's more than one namespace, include the |
| 642 | // namespace prefix in the results - otherwise, don't. |
| 643 | if ( array_key_exists( 'page_namespace', $row ) ) { |
| 644 | $actualTitle = Title::newFromText( $row['page_title'], $row['page_namespace'] ); |
| 645 | $title = $actualTitle->getPrefixedText(); |
| 646 | } else { |
| 647 | $title = str_replace( '_', ' ', $row['page_title'] ); |
| 648 | } |
| 649 | if ( array_key_exists( 'pp_displaytitle_value', $row ) && |
| 650 | ( $row[ 'pp_displaytitle_value' ] ) !== null && |
| 651 | trim( str_replace( ' ', '', strip_tags( $row[ 'pp_displaytitle_value' ] ) ) ) !== '' ) { |
| 652 | $pages[ $title . '@' ] = htmlspecialchars_decode( $row[ 'pp_displaytitle_value'], ENT_QUOTES ); |
| 653 | } else { |
| 654 | $pages[ $title . '@' ] = $title; |
| 655 | } |
| 656 | if ( array_key_exists( 'pp_defaultsort_value', $row ) && |
| 657 | ( $row[ 'pp_defaultsort_value' ] ) !== null ) { |
| 658 | $sortkeys[ $title ] = $row[ 'pp_defaultsort_value']; |
| 659 | } else { |
| 660 | $sortkeys[ $title ] = $title; |
| 661 | } |
| 662 | } |
| 663 | $res->free(); |
| 664 | |
| 665 | return self::fixedMultiSort( $sortkeys, $pages ); |
| 666 | } |
| 667 | |
| 668 | /** |
| 669 | * Creates an array of values that match the specified source name and |
| 670 | * type, for use by both Javascript autocompletion and comboboxes. |
| 671 | * |
| 672 | * @param string|null $source_name |
| 673 | * @param string $source_type |
| 674 | * @return string[] |
| 675 | */ |
| 676 | public static function getAutocompleteValues( $source_name, $source_type ) { |
| 677 | if ( $source_name === null ) { |
| 678 | return []; |
| 679 | } |
| 680 | |
| 681 | // The query depends on whether this is a Cargo field, SMW |
| 682 | // property, category, SMW concept or namespace. |
| 683 | if ( $source_type == 'cargo field' ) { |
| 684 | $arr = explode( '|', $source_name ); |
| 685 | if ( count( $arr ) == 3 ) { |
| 686 | $names_array = self::getValuesForCargoField( $arr[0], $arr[1], $arr[2] ); |
| 687 | } else { |
| 688 | [ $table_name, $field_name ] = explode( '|', $source_name, 2 ); |
| 689 | $names_array = self::getAllValuesForCargoField( $table_name, $field_name ); |
| 690 | } |
| 691 | // Remove blank/null values from the array. |
| 692 | $names_array = array_values( array_filter( $names_array ) ); |
| 693 | } elseif ( $source_type == 'property' ) { |
| 694 | $names_array = self::getAllValuesForProperty( $source_name ); |
| 695 | } elseif ( $source_type == 'category' ) { |
| 696 | $names_array = self::getAllPagesForCategory( $source_name, 10 ); |
| 697 | } elseif ( $source_type == 'concept' ) { |
| 698 | $names_array = self::getAllPagesForConcept( $source_name ); |
| 699 | } elseif ( $source_type == 'query' ) { |
| 700 | // Get rid of the "@", which is a placeholder for the substring, |
| 701 | // since there is no substring here. |
| 702 | // May not cover all possible use cases. |
| 703 | $baseQuery = str_replace( |
| 704 | [ "~*@*", "~@*", "~*@", "~@", "like:*@*", "like:@*", "like:*@", "like:@", "<@", ">@", "@" ], |
| 705 | "+", |
| 706 | $source_name |
| 707 | ); |
| 708 | $smwQuery = self::processSemanticQuery( $baseQuery ); |
| 709 | $names_array = self::getAllPagesForQuery( $smwQuery ); |
| 710 | } elseif ( $source_type == 'wikidata' ) { |
| 711 | $names_array = self::getAllValuesFromWikidata( $source_name ); |
| 712 | sort( $names_array ); |
| 713 | } else { |
| 714 | // i.e., $source_type == 'namespace' |
| 715 | $names_array = self::getAllPagesForNamespace( $source_name ); |
| 716 | } |
| 717 | return $names_array; |
| 718 | } |
| 719 | |
| 720 | public static function getAutocompletionTypeAndSource( &$field_args ) { |
| 721 | global $wgCapitalLinks; |
| 722 | |
| 723 | if ( array_key_exists( 'values from property', $field_args ) ) { |
| 724 | $autocompletionSource = $field_args['values from property']; |
| 725 | $autocompleteFieldType = 'property'; |
| 726 | } elseif ( array_key_exists( 'values from category', $field_args ) ) { |
| 727 | $autocompleteFieldType = 'category'; |
| 728 | $autocompletionSource = $field_args['values from category']; |
| 729 | } elseif ( array_key_exists( 'values from concept', $field_args ) ) { |
| 730 | $autocompleteFieldType = 'concept'; |
| 731 | $autocompletionSource = $field_args['values from concept']; |
| 732 | } elseif ( array_key_exists( 'values from namespace', $field_args ) ) { |
| 733 | $autocompleteFieldType = 'namespace'; |
| 734 | $autocompletionSource = $field_args['values from namespace']; |
| 735 | } elseif ( array_key_exists( 'values from content namespaces', $field_args ) ) { |
| 736 | $autocompleteFieldType = 'namespace'; |
| 737 | $autocompletionSource = "_contentNamespaces"; |
| 738 | } elseif ( array_key_exists( 'values from url', $field_args ) ) { |
| 739 | $autocompleteFieldType = 'external_url'; |
| 740 | $autocompletionSource = $field_args['values from url']; |
| 741 | } elseif ( array_key_exists( 'values from wikidata', $field_args ) ) { |
| 742 | $autocompleteFieldType = 'wikidata'; |
| 743 | $autocompletionSource = $field_args['values from wikidata']; |
| 744 | } elseif ( array_key_exists( 'values from query', $field_args ) ) { |
| 745 | $autocompletionSource = $field_args['values from query']; |
| 746 | $autocompleteFieldType = 'semantic_query'; |
| 747 | } elseif ( array_key_exists( 'values', $field_args ) ) { |
| 748 | global $wgPageFormsFieldNum; |
| 749 | $autocompleteFieldType = 'values'; |
| 750 | $autocompletionSource = "values-$wgPageFormsFieldNum"; |
| 751 | } elseif ( array_key_exists( 'autocomplete field type', $field_args ) ) { |
| 752 | $autocompleteFieldType = $field_args['autocomplete field type']; |
| 753 | $autocompletionSource = $field_args['autocompletion source']; |
| 754 | } elseif ( array_key_exists( 'full_cargo_field', $field_args ) ) { |
| 755 | $autocompletionSource = $field_args['full_cargo_field']; |
| 756 | $autocompleteFieldType = 'cargo field'; |
| 757 | } elseif ( array_key_exists( 'cargo field', $field_args ) ) { |
| 758 | $fieldName = $field_args['cargo field']; |
| 759 | $tableName = $field_args['cargo table']; |
| 760 | $autocompletionSource = "$tableName|$fieldName"; |
| 761 | $autocompleteFieldType = 'cargo field'; |
| 762 | if ( array_key_exists( 'cargo where', $field_args ) ) { |
| 763 | $whereStr = $field_args['cargo where']; |
| 764 | $autocompletionSource .= "|$whereStr"; |
| 765 | } |
| 766 | } elseif ( array_key_exists( 'semantic_property', $field_args ) ) { |
| 767 | $autocompletionSource = $field_args['semantic_property']; |
| 768 | $autocompleteFieldType = 'property'; |
| 769 | } else { |
| 770 | $autocompleteFieldType = null; |
| 771 | $autocompletionSource = null; |
| 772 | } |
| 773 | |
| 774 | if ( $wgCapitalLinks && in_array( $autocompleteFieldType, [ 'category', 'concept', 'namespace', 'property' ] ) ) { |
| 775 | $autocompletionSource = PFUtils::getContLang()->ucfirst( $autocompletionSource ); |
| 776 | } |
| 777 | |
| 778 | return [ $autocompleteFieldType, $autocompletionSource ]; |
| 779 | } |
| 780 | |
| 781 | public static function getRemoteDataTypeAndPossiblySetAutocompleteValues( $autocompleteFieldType, $autocompletionSource, $field_args, $autocompleteSettings ) { |
| 782 | global $wgPageFormsMaxLocalAutocompleteValues, $wgPageFormsAutocompleteValues; |
| 783 | |
| 784 | if ( $autocompleteFieldType == 'external_url' |
| 785 | || $autocompleteFieldType == 'wikidata' |
| 786 | || array_key_exists( 'reverselookup', $field_args ) |
| 787 | ) { |
| 788 | // Autocompletion from URL is always done remotely. |
| 789 | return $autocompleteFieldType; |
| 790 | } |
| 791 | if ( $autocompletionSource == '' ) { |
| 792 | // No autocompletion. |
| 793 | return null; |
| 794 | } |
| 795 | // @TODO - that empty() check shouldn't be necessary. |
| 796 | if ( array_key_exists( 'possible_values', $field_args ) && |
| 797 | !empty( $field_args['possible_values'] ) ) { |
| 798 | $autocompleteValues = $field_args['possible_values']; |
| 799 | } elseif ( $autocompleteFieldType == 'values' ) { |
| 800 | $autocompleteValues = explode( ',', $field_args['values'] ); |
| 801 | } else { |
| 802 | $autocompleteValues = self::getAutocompleteValues( $autocompletionSource, $autocompleteFieldType ); |
| 803 | } |
| 804 | |
| 805 | if ( count( $autocompleteValues ) > $wgPageFormsMaxLocalAutocompleteValues && |
| 806 | $autocompleteFieldType != 'values' && |
| 807 | !array_key_exists( 'values dependent on', $field_args ) && |
| 808 | !array_key_exists( 'mapping template', $field_args ) && |
| 809 | !array_key_exists( 'mapping property', $field_args ) |
| 810 | ) { |
| 811 | return $autocompleteFieldType; |
| 812 | } else { |
| 813 | $wgPageFormsAutocompleteValues[$autocompleteSettings] = $autocompleteValues; |
| 814 | return null; |
| 815 | } |
| 816 | } |
| 817 | |
| 818 | /** |
| 819 | * Get all autocomplete-related values, plus delimiter value |
| 820 | * (it's needed also for the 'uploadable' link, if there is one). |
| 821 | * |
| 822 | * @param array $field_args |
| 823 | * @param bool $is_list |
| 824 | * @return string[] |
| 825 | */ |
| 826 | public static function setAutocompleteValues( $field_args, $is_list ) { |
| 827 | [ $autocompleteFieldType, $autocompletionSource ] = |
| 828 | self::getAutocompletionTypeAndSource( $field_args ); |
| 829 | $autocompleteSettings = $autocompletionSource; |
| 830 | if ( $is_list ) { |
| 831 | $autocompleteSettings .= ',list'; |
| 832 | if ( array_key_exists( 'delimiter', $field_args ) ) { |
| 833 | $delimiter = $field_args['delimiter']; |
| 834 | $autocompleteSettings .= ',' . $delimiter; |
| 835 | } else { |
| 836 | $delimiter = ','; |
| 837 | } |
| 838 | } else { |
| 839 | $delimiter = null; |
| 840 | } |
| 841 | |
| 842 | $remoteDataType = self::getRemoteDataTypeAndPossiblySetAutocompleteValues( $autocompleteFieldType, $autocompletionSource, $field_args, $autocompleteSettings ); |
| 843 | return [ $autocompleteSettings, $remoteDataType, $delimiter ]; |
| 844 | } |
| 845 | |
| 846 | /** |
| 847 | * Helper function to get an array of values out of what may be either |
| 848 | * an array or a delimited string. |
| 849 | * |
| 850 | * @param string[]|string $value |
| 851 | * @param string $delimiter |
| 852 | * @return string[] |
| 853 | */ |
| 854 | public static function getValuesArray( $value, $delimiter ) { |
| 855 | global $wgPageFormsUseDisplayTitle; |
| 856 | |
| 857 | if ( is_array( $value ) ) { |
| 858 | return $value; |
| 859 | } elseif ( $value == null ) { |
| 860 | return []; |
| 861 | } else { |
| 862 | $values = array_map( 'trim', explode( $delimiter, $value ) ); |
| 863 | return $wgPageFormsUseDisplayTitle |
| 864 | ? array_values( PFMappingUtils::getLabelsForTitles( $values ) ) |
| 865 | : $values; |
| 866 | } |
| 867 | } |
| 868 | |
| 869 | public static function getValuesFromExternalURL( $external_url_alias, $substring ) { |
| 870 | global $wgPageFormsAutocompletionURLs; |
| 871 | if ( empty( $wgPageFormsAutocompletionURLs ) ) { |
| 872 | return wfMessage( 'pf-nocompletionurls' ); |
| 873 | } |
| 874 | if ( !array_key_exists( $external_url_alias, $wgPageFormsAutocompletionURLs ) ) { |
| 875 | return wfMessage( 'pf-invalidexturl' ); |
| 876 | } |
| 877 | $url = $wgPageFormsAutocompletionURLs[$external_url_alias]; |
| 878 | if ( empty( $url ) ) { |
| 879 | return wfMessage( 'pf-blankexturl' ); |
| 880 | } |
| 881 | $url = str_replace( '<substr>', urlencode( $substring ), $url ); |
| 882 | $page_contents = MediaWikiServices::getInstance()->getHttpRequestFactory()->get( $url, [], __METHOD__ ); |
| 883 | if ( empty( $page_contents ) ) { |
| 884 | return wfMessage( 'pf-externalpageempty' ); |
| 885 | } |
| 886 | $data = json_decode( $page_contents ); |
| 887 | if ( empty( $data ) ) { |
| 888 | return wfMessage( 'pf-externalpagebadjson' ); |
| 889 | } |
| 890 | $return_values = []; |
| 891 | foreach ( $data->pfautocomplete as $val ) { |
| 892 | $return_values[$val->title] = $val->displaytitle ?? $val->title; |
| 893 | } |
| 894 | return $return_values; |
| 895 | } |
| 896 | |
| 897 | /** |
| 898 | * Returns a SQL condition for autocompletion substring value in a column. |
| 899 | * |
| 900 | * @param string $column Value column name |
| 901 | * @param string $substring Substring to look for |
| 902 | * @param bool $replaceSpaces |
| 903 | * @return string SQL condition for use in WHERE clause |
| 904 | */ |
| 905 | public static function getSQLConditionForAutocompleteInColumn( $column, $substring, $replaceSpaces = true ) { |
| 906 | global $wgPageFormsAutocompleteOnAllChars; |
| 907 | |
| 908 | $db = PFUtils::getReadDB(); |
| 909 | |
| 910 | // CONVERT() is also supported in PostgreSQL, but it doesn't |
| 911 | // seem to work the same way. |
| 912 | if ( $db->getType() == 'mysql' ) { |
| 913 | $column_value = "LOWER(CONVERT($column USING utf8))"; |
| 914 | } else { |
| 915 | $column_value = "LOWER($column)"; |
| 916 | } |
| 917 | |
| 918 | $substring = strtolower( $substring ); |
| 919 | if ( $replaceSpaces ) { |
| 920 | $substring = str_replace( ' ', '_', $substring ); |
| 921 | } |
| 922 | |
| 923 | if ( $wgPageFormsAutocompleteOnAllChars ) { |
| 924 | return $column_value . $db->buildLike( $db->anyString(), $substring, $db->anyString() ); |
| 925 | } else { |
| 926 | $sqlCond = $column_value . $db->buildLike( $substring, $db->anyString() ); |
| 927 | $spaceRepresentation = $replaceSpaces ? '_' : ' '; |
| 928 | $wordSeparators = [ $spaceRepresentation, '/', '(', ')', '-', '\'', '\"' ]; |
| 929 | foreach ( $wordSeparators as $wordSeparator ) { |
| 930 | $sqlCond .= ' OR ' . $column_value . |
| 931 | $db->buildLike( $db->anyString(), $wordSeparator . $substring, $db->anyString() ); |
| 932 | } |
| 933 | return $sqlCond; |
| 934 | } |
| 935 | } |
| 936 | |
| 937 | /** |
| 938 | * Returns an array of the names of pages that are the result of an SMW query. |
| 939 | * |
| 940 | * @param string $rawQuery the query string like [[Category:Trees]][[age::>1000]] |
| 941 | * @return array |
| 942 | */ |
| 943 | public static function getAllPagesForQuery( $rawQuery ) { |
| 944 | global $wgPageFormsMaxAutocompleteValues; |
| 945 | global $wgPageFormsUseDisplayTitle; |
| 946 | |
| 947 | $rawQuery .= "|named args=yes|link=none|limit=$wgPageFormsMaxAutocompleteValues|searchlabel="; |
| 948 | $rawQueryArray = explode( "|", $rawQuery ); |
| 949 | [ $queryString, $processedParams, $printouts ] = SMWQueryProcessor::getComponentsFromFunctionParams( $rawQueryArray, false ); |
| 950 | SMWQueryProcessor::addThisPrintout( $printouts, $processedParams ); |
| 951 | $processedParams = SMWQueryProcessor::getProcessedParams( $processedParams, $printouts ); |
| 952 | |
| 953 | // Run query and get results. |
| 954 | $queryObj = SMWQueryProcessor::createQuery( $queryString, |
| 955 | $processedParams, |
| 956 | SMWQueryProcessor::SPECIAL_PAGE, '', $printouts ); |
| 957 | $res = PFUtils::getSMWStore()->getQueryResult( $queryObj ); |
| 958 | $rows = $res->getResults(); |
| 959 | $titles = []; |
| 960 | $pages = []; |
| 961 | |
| 962 | foreach ( $rows as $diWikiPage ) { |
| 963 | $pages[] = $diWikiPage->getDbKey(); |
| 964 | $titles[] = $diWikiPage->getTitle(); |
| 965 | } |
| 966 | |
| 967 | if ( $wgPageFormsUseDisplayTitle ) { |
| 968 | $pages = PFMappingUtils::getDisplayTitles( $titles ); |
| 969 | } |
| 970 | |
| 971 | return $pages; |
| 972 | } |
| 973 | |
| 974 | public static function processSemanticQuery( $query, $substr = '' ) { |
| 975 | $query = str_replace( |
| 976 | [ "<", ">", "(", ")", '%', '@' ], |
| 977 | [ "<", ">", "[", "]", '|', $substr ], |
| 978 | $query |
| 979 | ); |
| 980 | return $query; |
| 981 | } |
| 982 | |
| 983 | public static function getMaxValuesToRetrieve( $substring = null ) { |
| 984 | // $wgPageFormsMaxAutocompleteValues is currently misnamed, |
| 985 | // or mis-used - it's actually used for those cases where |
| 986 | // autocomplete *isn't* used, i.e. to populate a radiobutton |
| 987 | // input, where it makes sense to have a very large limit |
| 988 | // (current value: 1,000). For actual autocompletion, though, |
| 989 | // with a substring, a limit like 20 makes more sense. It |
| 990 | // would be good use the variable for this purpose instead, |
| 991 | // with a default like 20, and then create a new global |
| 992 | // variable, like $wgPageFormsMaxNonAutocompleteValues, to |
| 993 | // hold the much larger number. |
| 994 | if ( $substring == null ) { |
| 995 | global $wgPageFormsMaxAutocompleteValues; |
| 996 | return $wgPageFormsMaxAutocompleteValues; |
| 997 | } else { |
| 998 | return 20; |
| 999 | } |
| 1000 | } |
| 1001 | |
| 1002 | /** |
| 1003 | * Get the exact canonical namespace string, given a user-created string |
| 1004 | * |
| 1005 | * @param string $namespaceStr |
| 1006 | * @return string |
| 1007 | */ |
| 1008 | public static function standardizeNamespace( $namespaceStr ) { |
| 1009 | $dummyTitle = Title::newFromText( "$namespaceStr:ABC" ); |
| 1010 | return $dummyTitle ? $dummyTitle->getNsText() : $namespaceStr; |
| 1011 | } |
| 1012 | |
| 1013 | /** |
| 1014 | * Map a label back to a value. |
| 1015 | * @param string $label |
| 1016 | * @param array $values |
| 1017 | * @return string |
| 1018 | */ |
| 1019 | public static function labelToValue( $label, $values ) { |
| 1020 | $value = array_search( $label, $values ); |
| 1021 | if ( $value === false ) { |
| 1022 | return $label; |
| 1023 | } else { |
| 1024 | return $value; |
| 1025 | } |
| 1026 | } |
| 1027 | } |