Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 275 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 1 |
| PFAutocompleteAPI | |
0.00% |
0 / 275 |
|
0.00% |
0 / 11 |
4422 | |
0.00% |
0 / 1 |
| __construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| execute | |
0.00% |
0 / 73 |
|
0.00% |
0 / 1 |
702 | |||
| getAllowedParams | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
2 | |||
| getParamDescription | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
2 | |||
| getDescription | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getExamples | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
| getAllValuesForProperty | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
20 | |||
| computeAllValuesForProperty | |
0.00% |
0 / 57 |
|
0.00% |
0 / 1 |
110 | |||
| getAllValuesForCargoField | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
12 | |||
| computeAllValuesForCargoField | |
0.00% |
0 / 58 |
|
0.00% |
0 / 1 |
272 | |||
| sortValuesByLength | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * @file |
| 4 | * @ingroup PF |
| 5 | */ |
| 6 | |
| 7 | use MediaWiki\Language\RawMessage; |
| 8 | |
| 9 | /** |
| 10 | * Adds and handles the 'pfautocomplete' action to the MediaWiki API. |
| 11 | * |
| 12 | * @ingroup PF |
| 13 | * |
| 14 | * @author Sergey Chernyshev |
| 15 | * @author Yaron Koren |
| 16 | */ |
| 17 | class PFAutocompleteAPI extends ApiBase { |
| 18 | |
| 19 | public function __construct( $query, $moduleName ) { |
| 20 | parent::__construct( $query, $moduleName ); |
| 21 | } |
| 22 | |
| 23 | public function execute() { |
| 24 | $params = $this->extractRequestParams(); |
| 25 | $substr = $params['substr']; |
| 26 | $namespace = $params['namespace']; |
| 27 | $property = $params['property']; |
| 28 | $category = $params['category']; |
| 29 | $wikidata = $params['wikidata']; |
| 30 | $concept = $params['concept']; |
| 31 | $query = $params['semantic_query']; |
| 32 | $cargo_table = $params['cargo_table']; |
| 33 | $cargo_field = $params['cargo_field']; |
| 34 | $cargo_where = $params['cargo_where']; |
| 35 | $external_url = $params['external_url']; |
| 36 | $baseprop = $params['baseprop']; |
| 37 | $base_cargo_table = $params['base_cargo_table']; |
| 38 | $base_cargo_field = $params['base_cargo_field']; |
| 39 | $basevalue = $params['basevalue']; |
| 40 | // $limit = $params['limit']; |
| 41 | |
| 42 | if ( $baseprop === null && $base_cargo_table === null && strlen( $substr ) == 0 ) { |
| 43 | $this->dieWithError( [ 'apierror-missingparam', 'substr' ], 'param_substr' ); |
| 44 | } |
| 45 | |
| 46 | global $wgPageFormsUseDisplayTitle; |
| 47 | $map = false; |
| 48 | if ( $baseprop !== null ) { |
| 49 | if ( $property !== null ) { |
| 50 | $data = $this->getAllValuesForProperty( $property, null, $baseprop, $basevalue ); |
| 51 | } |
| 52 | } elseif ( $property !== null ) { |
| 53 | $data = $this->getAllValuesForProperty( $property, $substr ); |
| 54 | } elseif ( $wikidata !== null ) { |
| 55 | $data = PFValuesUtils::getAllValuesFromWikidata( urlencode( $wikidata ), $substr ); |
| 56 | } elseif ( $category !== null ) { |
| 57 | $data = PFValuesUtils::getAllPagesForCategory( $category, 3, $substr ); |
| 58 | $map = $wgPageFormsUseDisplayTitle; |
| 59 | if ( $map ) { |
| 60 | $data = PFMappingUtils::createDisplayTitleLabels( $data ); |
| 61 | } |
| 62 | } elseif ( $concept !== null ) { |
| 63 | $data = PFValuesUtils::getAllPagesForConcept( $concept, $substr ); |
| 64 | $map = $wgPageFormsUseDisplayTitle; |
| 65 | if ( $map ) { |
| 66 | $data = PFMappingUtils::createDisplayTitleLabels( $data ); |
| 67 | } |
| 68 | } elseif ( $query !== null ) { |
| 69 | $query = PFValuesUtils::processSemanticQuery( $query, $substr ); |
| 70 | $data = PFValuesUtils::getAllPagesForQuery( $query ); |
| 71 | $map = $wgPageFormsUseDisplayTitle; |
| 72 | if ( $map ) { |
| 73 | $data = PFMappingUtils::createDisplayTitleLabels( $data ); |
| 74 | } |
| 75 | } elseif ( $cargo_table !== null && $cargo_field !== null ) { |
| 76 | $data = self::getAllValuesForCargoField( $cargo_table, $cargo_field, $cargo_where, $substr, $base_cargo_table, $base_cargo_field, $basevalue ); |
| 77 | } elseif ( $namespace !== null ) { |
| 78 | $data = PFValuesUtils::getAllPagesForNamespace( $namespace, $substr ); |
| 79 | $map = $wgPageFormsUseDisplayTitle; |
| 80 | if ( $map ) { |
| 81 | $data = PFMappingUtils::createDisplayTitleLabels( $data ); |
| 82 | } |
| 83 | } elseif ( $external_url !== null ) { |
| 84 | $data = PFValuesUtils::getValuesFromExternalURL( $external_url, $substr ); |
| 85 | $map = true; |
| 86 | } else { |
| 87 | $data = []; |
| 88 | } |
| 89 | |
| 90 | // If we got back an error message, exit with that message. |
| 91 | if ( !is_array( $data ) ) { |
| 92 | if ( is_callable( [ $this, 'dieWithError' ] ) ) { |
| 93 | if ( !$data instanceof Message ) { |
| 94 | $data = ApiMessage::create( new RawMessage( '$1', [ $data ] ), 'unknownerror' ); |
| 95 | } |
| 96 | $this->dieWithError( $data ); |
| 97 | } else { |
| 98 | $code = 'unknownerror'; |
| 99 | if ( $data instanceof Message ) { |
| 100 | $code = $data instanceof IApiMessage ? $data->getApiCode() : $data->getKey(); |
| 101 | $data = $data->inLanguage( 'en' )->useDatabase( false )->text(); |
| 102 | } |
| 103 | $this->dieWithError( $data, $code ); |
| 104 | } |
| 105 | } |
| 106 | // Sort the values by their lengths for better UX |
| 107 | $data = self::sortValuesByLength( $data ); |
| 108 | |
| 109 | // to prevent JS parsing problems, display should be the same |
| 110 | // even if there are no results |
| 111 | /* |
| 112 | if ( count( $data ) <= 0 ) { |
| 113 | return; |
| 114 | } |
| 115 | */ |
| 116 | |
| 117 | // Format data as the API requires it - in the case of "values |
| 118 | // from url", it's a little odd because we are re-adding the |
| 119 | // "title" key after having removed it, but the removal was |
| 120 | // needed for the sorting. |
| 121 | $formattedData = []; |
| 122 | foreach ( $data as $key => $value ) { |
| 123 | if ( $map ) { |
| 124 | $formattedData[] = [ 'title' => $key, 'displaytitle' => $value ]; |
| 125 | } else { |
| 126 | $formattedData[] = [ 'title' => $value ]; |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | // Set top-level elements. |
| 131 | $result = $this->getResult(); |
| 132 | $result->setIndexedTagName( $formattedData, 'p' ); |
| 133 | $result->addValue( null, $this->getModuleName(), $formattedData ); |
| 134 | } |
| 135 | |
| 136 | protected function getAllowedParams() { |
| 137 | return [ |
| 138 | 'limit' => [ |
| 139 | ApiBase::PARAM_TYPE => 'limit', |
| 140 | ApiBase::PARAM_DFLT => 10, |
| 141 | ApiBase::PARAM_MIN => 1, |
| 142 | ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1, |
| 143 | ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2 |
| 144 | ], |
| 145 | 'substr' => null, |
| 146 | 'property' => null, |
| 147 | 'category' => null, |
| 148 | 'concept' => null, |
| 149 | 'wikidata' => null, |
| 150 | 'semantic_query' => null, |
| 151 | 'cargo_table' => null, |
| 152 | 'cargo_field' => null, |
| 153 | 'cargo_where' => null, |
| 154 | 'namespace' => null, |
| 155 | 'external_url' => null, |
| 156 | 'baseprop' => null, |
| 157 | 'base_cargo_table' => null, |
| 158 | 'base_cargo_field' => null, |
| 159 | 'basevalue' => null, |
| 160 | ]; |
| 161 | } |
| 162 | |
| 163 | protected function getParamDescription() { |
| 164 | return [ |
| 165 | 'substr' => 'Search substring', |
| 166 | 'property' => 'Semantic property for which to search values', |
| 167 | 'category' => 'Category for which to search values', |
| 168 | 'concept' => 'Concept for which to search values', |
| 169 | 'wikidata' => 'Search string for getting values from wikidata', |
| 170 | 'semantic_query' => 'Query for which to search values', |
| 171 | 'namespace' => 'Namespace for which to search values', |
| 172 | 'external_url' => 'Alias for external URL from which to get values', |
| 173 | 'baseprop' => 'A previous property in the form to check against', |
| 174 | 'basevalue' => 'The value to check for the previous property', |
| 175 | // 'limit' => 'Limit how many entries to return', |
| 176 | ]; |
| 177 | } |
| 178 | |
| 179 | protected function getDescription() { |
| 180 | return 'Autocompletion call used by the Page Forms extension (https://www.mediawiki.org/Extension:Page_Forms)'; |
| 181 | } |
| 182 | |
| 183 | protected function getExamples() { |
| 184 | return [ |
| 185 | 'api.php?action=pfautocomplete&substr=te', |
| 186 | 'api.php?action=pfautocomplete&substr=te&property=Has_author', |
| 187 | 'api.php?action=pfautocomplete&substr=te&category=Authors', |
| 188 | 'api.php?action=pfautocomplete&semantic_query=((Category:Test)) ((MyProperty::Something))', |
| 189 | ]; |
| 190 | } |
| 191 | |
| 192 | private function getAllValuesForProperty( |
| 193 | $property_name, |
| 194 | $substring, |
| 195 | $basePropertyName = null, |
| 196 | $baseValue = null |
| 197 | ) { |
| 198 | global $wgPageFormsCacheAutocompleteValues, $wgPageFormsAutocompleteCacheTimeout; |
| 199 | global $smwgDefaultStore; |
| 200 | |
| 201 | if ( $smwgDefaultStore == null ) { |
| 202 | $this->dieWithError( 'Semantic MediaWiki must be installed to query on "property"', 'param_property' ); |
| 203 | } |
| 204 | |
| 205 | $property_name = str_replace( ' ', '_', $property_name ); |
| 206 | |
| 207 | // Use cache if allowed |
| 208 | if ( !$wgPageFormsCacheAutocompleteValues ) { |
| 209 | return $this->computeAllValuesForProperty( $property_name, $substring, $basePropertyName, $baseValue ); |
| 210 | } |
| 211 | |
| 212 | $cache = PFFormUtils::getFormCache(); |
| 213 | // Remove trailing whitespace to avoid unnecessary database selects |
| 214 | $cacheKeyString = $property_name . '::' . rtrim( $substring ); |
| 215 | if ( $basePropertyName !== null ) { |
| 216 | $cacheKeyString .= ',' . $basePropertyName . ',' . $baseValue; |
| 217 | } |
| 218 | $cacheKey = $cache->makeKey( 'pf-autocomplete', md5( $cacheKeyString ) ); |
| 219 | return $cache->getWithSetCallback( |
| 220 | $cacheKey, |
| 221 | $wgPageFormsAutocompleteCacheTimeout, |
| 222 | function () use ( $property_name, $substring, $basePropertyName, $baseValue ) { |
| 223 | return $this->computeAllValuesForProperty( $property_name, $substring, $basePropertyName, $baseValue ); |
| 224 | } |
| 225 | ); |
| 226 | } |
| 227 | |
| 228 | /** |
| 229 | * @param string $property_name |
| 230 | * @param string $substring |
| 231 | * @param string|null $basePropertyName |
| 232 | * @param mixed $baseValue |
| 233 | * @return array |
| 234 | */ |
| 235 | private function computeAllValuesForProperty( |
| 236 | $property_name, |
| 237 | $substring, |
| 238 | $basePropertyName = null, |
| 239 | $baseValue = null |
| 240 | ) { |
| 241 | $db = PFUtils::getReadDB(); |
| 242 | $tables = []; |
| 243 | $joinConds = []; |
| 244 | $sqlOptions = [ |
| 245 | 'LIMIT' => PFValuesUtils::getMaxValuesToRetrieve( $substring ) |
| 246 | ]; |
| 247 | |
| 248 | $property = SMW\DataValueFactory::getInstance()->newPropertyValueByLabel( $property_name ); |
| 249 | $propertyHasTypePage = ( $property->getPropertyTypeID() == '_wpg' ); |
| 250 | $store = smwfGetStore(); |
| 251 | if ( $store instanceof SMW\SQLStore\SQLStore ) { |
| 252 | $inceptiveProperty = $property->getInceptiveProperty(); |
| 253 | $propertyTableId = $store->findPropertyTableID( $inceptiveProperty ); |
| 254 | $isFixedProperty = preg_match( '/smw_fpt_/', $propertyTableId ); |
| 255 | } else { |
| 256 | $isFixedProperty = false; |
| 257 | } |
| 258 | |
| 259 | $idsTable = 'smw_object_ids'; |
| 260 | $tables['p_ids'] = $idsTable; |
| 261 | |
| 262 | if ( $isFixedProperty ) { |
| 263 | $propsTable = $propertyTableId; |
| 264 | } else { |
| 265 | $conditions = [ 'p_ids.smw_title' => $property_name ]; |
| 266 | if ( $propertyHasTypePage ) { |
| 267 | $propsTable = 'smw_di_wikipage'; |
| 268 | } else { |
| 269 | $propsTable = 'smw_di_blob'; |
| 270 | } |
| 271 | |
| 272 | } |
| 273 | $tables['p'] = $propsTable; |
| 274 | $joinConds['p_ids'] = [ 'JOIN', 'p.p_id = p_ids.smw_id' ]; |
| 275 | |
| 276 | if ( $propertyHasTypePage ) { |
| 277 | $tables['o_ids'] = $idsTable; |
| 278 | $joinConds['o_ids'] = [ 'JOIN', 'p.o_id = o_ids.smw_id' ]; |
| 279 | $valueField = 'o_ids.smw_title'; |
| 280 | } else { |
| 281 | $valueField = 'p.o_hash'; |
| 282 | } |
| 283 | |
| 284 | if ( $basePropertyName !== null ) { |
| 285 | $baseProperty = SMW\DataValueFactory::getInstance()->newPropertyValueByLabel( $basePropertyName ); |
| 286 | $basePropertyHasTypePage = ( $baseProperty->getPropertyTypeID() == '_wpg' ); |
| 287 | |
| 288 | $basePropertyName = str_replace( ' ', '_', $basePropertyName ); |
| 289 | $conditions['base_p_ids.smw_title'] = $basePropertyName; |
| 290 | if ( $basePropertyHasTypePage ) { |
| 291 | $propsTable = 'smw_di_wikipage'; |
| 292 | $baseValue = str_replace( ' ', '_', $baseValue ); |
| 293 | } else { |
| 294 | $propsTable = 'smw_di_blob'; |
| 295 | $conditions['p_base.o_hash'] = $baseValue; |
| 296 | } |
| 297 | $tables['p_base'] = $propsTable; |
| 298 | $joinConds['p_base'] = [ 'JOIN', 'p.s_id = p_base.s_id' ]; |
| 299 | $tables['base_p_ids'] = $idsTable; |
| 300 | $joinConds['base_p_ids'] = [ 'JOIN', 'p_base.p_id = base_p_ids.smw_id' ]; |
| 301 | if ( $basePropertyHasTypePage ) { |
| 302 | $tables['base_o_ids'] = $idsTable; |
| 303 | $joinConds['base_o_ids'] = [ 'JOIN', 'p_base.o_id = base_o_ids.smw_id' ]; |
| 304 | $conditions['base_o_ids.smw_title'] = $baseValue; |
| 305 | } |
| 306 | } |
| 307 | |
| 308 | if ( $substring !== null ) { |
| 309 | // "Page" type property values are stored differently |
| 310 | // in the DB, i.e. underlines instead of spaces. |
| 311 | $conditions[] = PFValuesUtils::getSQLConditionForAutocompleteInColumn( $valueField, $substring, $propertyHasTypePage ); |
| 312 | } |
| 313 | |
| 314 | $sqlOptions['ORDER BY'] = $valueField; |
| 315 | $res = $db->select( $tables, "DISTINCT $valueField", |
| 316 | $conditions, __METHOD__, $sqlOptions, $joinConds ); |
| 317 | |
| 318 | $values = []; |
| 319 | while ( $row = $res->fetchRow() ) { |
| 320 | $values[] = str_replace( '_', ' ', $row[0] ); |
| 321 | } |
| 322 | $res->free(); |
| 323 | return $values; |
| 324 | } |
| 325 | |
| 326 | private static function getAllValuesForCargoField( $cargoTable, $cargoField, $cargoWhere, $substring, $baseCargoTable = null, $baseCargoField = null, $baseValue = null ) { |
| 327 | global $wgPageFormsCacheAutocompleteValues, $wgPageFormsAutocompleteCacheTimeout; |
| 328 | |
| 329 | if ( !$wgPageFormsCacheAutocompleteValues ) { |
| 330 | return self::computeAllValuesForCargoField( |
| 331 | $cargoTable, $cargoField, $cargoWhere, $substring, $baseCargoTable, $baseCargoField, $baseValue ); |
| 332 | } |
| 333 | |
| 334 | $cache = PFFormUtils::getFormCache(); |
| 335 | // Remove trailing whitespace to avoid unnecessary database selects |
| 336 | $cacheKeyString = $cargoTable . '|' . $cargoField . '|' . rtrim( $substring ); |
| 337 | if ( $baseCargoTable !== null ) { |
| 338 | $cacheKeyString .= '|' . $baseCargoTable . '|' . $baseCargoField . '|' . $baseValue; |
| 339 | } |
| 340 | $cacheKey = $cache->makeKey( 'pf-autocomplete', md5( $cacheKeyString ) ); |
| 341 | return $cache->getWithSetCallback( |
| 342 | $cacheKey, |
| 343 | $wgPageFormsAutocompleteCacheTimeout, |
| 344 | function () use ( $cargoTable, $cargoField, $cargoWhere, $substring, $baseCargoTable, $baseCargoField, $baseValue ) { |
| 345 | return self::computeAllValuesForCargoField( |
| 346 | $cargoTable, $cargoField, $cargoWhere, $substring, $baseCargoTable, $baseCargoField, $baseValue ); |
| 347 | } |
| 348 | ); |
| 349 | } |
| 350 | |
| 351 | private static function computeAllValuesForCargoField( |
| 352 | $cargoTable, |
| 353 | $cargoField, |
| 354 | $cargoWhere, |
| 355 | $substring, |
| 356 | $baseCargoTable, |
| 357 | $baseCargoField, |
| 358 | $baseValue |
| 359 | ) { |
| 360 | global $wgPageFormsAutocompleteOnAllChars; |
| 361 | |
| 362 | $tablesStr = $cargoTable; |
| 363 | $fieldsStr = $cargoField; |
| 364 | $joinOnStr = ''; |
| 365 | $whereStr = ''; |
| 366 | |
| 367 | if ( $cargoWhere !== null ) { |
| 368 | $whereStr = '(' . stripslashes( $cargoWhere ) . ')'; |
| 369 | } |
| 370 | |
| 371 | if ( $baseCargoTable !== null && $baseCargoField !== null ) { |
| 372 | if ( $whereStr != '' ) { |
| 373 | $whereStr .= " AND "; |
| 374 | } |
| 375 | if ( $baseCargoTable != $cargoTable ) { |
| 376 | $tablesStr .= ", $baseCargoTable"; |
| 377 | $joinOnStr = "$cargoTable._pageName = $baseCargoTable._pageName"; |
| 378 | } |
| 379 | $whereStr .= "$baseCargoTable.$baseCargoField = \"$baseValue\""; |
| 380 | } |
| 381 | |
| 382 | if ( $substring !== null ) { |
| 383 | if ( $whereStr != '' ) { |
| 384 | $whereStr .= " AND "; |
| 385 | } |
| 386 | // @TODO - this is duplicate work; the schema is retrieved |
| 387 | // again when the CargoSQLQuery object is created. There should |
| 388 | // be some way of avoiding that duplicate retrieval. |
| 389 | $fieldDesc = PFUtils::getCargoFieldDescription( $cargoTable, $cargoField ); |
| 390 | |
| 391 | if ( $fieldDesc !== null && $fieldDesc->mIsList ) { |
| 392 | // If it's a list field, we query directly on |
| 393 | // the "helper table" for that field. We could |
| 394 | // instead use "HOLDS LIKE", but this would |
| 395 | // return false positives - other values that |
| 396 | // have been listed alongside the values we're |
| 397 | // looking for - at least for Cargo >= 2.6. |
| 398 | $fieldTableName = $cargoTable . '__' . $cargoField; |
| 399 | // Because of the way Cargo querying works, the |
| 400 | // field table has to be listed first for only |
| 401 | // the right values to show up. |
| 402 | $tablesStr = $fieldTableName . ', ' . $tablesStr; |
| 403 | if ( $joinOnStr != '' ) { |
| 404 | $joinOnStr = ', ' . $joinOnStr; |
| 405 | } |
| 406 | $joinOnStr = $fieldTableName . '._rowID=' . |
| 407 | $cargoTable . '._ID' . $joinOnStr; |
| 408 | |
| 409 | $fieldsStr = $cargoField = '_value'; |
| 410 | } |
| 411 | |
| 412 | $cdb = CargoUtils::getDB(); |
| 413 | $quotedCargoField = $cdb->addIdentifierQuotes( $cargoField ); |
| 414 | |
| 415 | // LIKE is almost always case-insensitive for MySQL, |
| 416 | // usually (?) case-sensitive for PostgreSQL, and |
| 417 | // case-insensitive (though only for ASCII characters) |
| 418 | // in SQLite. In order to make this check consistenly |
| 419 | // case-sensitive everywhere, we call LOWER() on all |
| 420 | // the fields. There are other ways to accomplish this, |
| 421 | // but this one works consistently across the different |
| 422 | // DB systems. |
| 423 | if ( $wgPageFormsAutocompleteOnAllChars ) { |
| 424 | $whereStr .= "(LOWER($quotedCargoField) LIKE LOWER('%$substring%'))"; |
| 425 | } else { |
| 426 | $whereStr .= "(LOWER($quotedCargoField) LIKE LOWER('$substring%')"; |
| 427 | // Also look for the substring after any word |
| 428 | // separator (most commonly, a space). In theory, |
| 429 | // any punctuation can be a word separator, |
| 430 | // but we will just look for the most common |
| 431 | // ones. |
| 432 | // This would be much easier to do with the |
| 433 | // REGEXP operator, but its presence is |
| 434 | // inconsistent between MySQL, PostgreSQL and |
| 435 | // SQLite. |
| 436 | $wordSeparators = [ ' ', '/', '(', ')', '-', '|', "\'", '"' ]; |
| 437 | foreach ( $wordSeparators as $wordSeparator ) { |
| 438 | $whereStr .= " OR LOWER($quotedCargoField) LIKE LOWER('%$wordSeparator$substring%')"; |
| 439 | } |
| 440 | $whereStr .= ')'; |
| 441 | } |
| 442 | } |
| 443 | |
| 444 | $sqlQuery = CargoSQLQuery::newFromValues( |
| 445 | $tablesStr, |
| 446 | $fieldsStr, |
| 447 | $whereStr, |
| 448 | $joinOnStr, |
| 449 | $cargoField, |
| 450 | $havingStr = null, |
| 451 | $cargoField, |
| 452 | PFValuesUtils::getMaxValuesToRetrieve( $substring ), |
| 453 | $offsetStr = 0, |
| 454 | true |
| 455 | ); |
| 456 | $queryResults = $sqlQuery->run(); |
| 457 | |
| 458 | if ( $cargoField[0] != '_' ) { |
| 459 | $cargoFieldAlias = str_replace( '_', ' ', $cargoField ); |
| 460 | } else { |
| 461 | $cargoFieldAlias = $cargoField; |
| 462 | } |
| 463 | |
| 464 | $values = []; |
| 465 | foreach ( $queryResults as $row ) { |
| 466 | $value = $row[$cargoFieldAlias]; |
| 467 | // @TODO - this check should not be necessary. |
| 468 | if ( $value == '' ) { |
| 469 | continue; |
| 470 | } |
| 471 | // Cargo HTML-encodes everything - let's decode double |
| 472 | // quotes, at least. |
| 473 | $values[] = str_replace( '"', '"', $value ); |
| 474 | } |
| 475 | return $values; |
| 476 | } |
| 477 | |
| 478 | /** |
| 479 | * Sort the values of an array by their lengths (shortest to longest) |
| 480 | * |
| 481 | * @param array $values |
| 482 | * @return array $values |
| 483 | */ |
| 484 | static function sortValuesByLength( $values ) { |
| 485 | if ( empty( $values ) ) { |
| 486 | return $values; |
| 487 | } |
| 488 | uasort( $values, static function ( $a, $b ) { |
| 489 | return strlen( $a ) - strlen( $b ); |
| 490 | } ); |
| 491 | return $values; |
| 492 | } |
| 493 | } |