Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 21
CRAP
0.00% covered (danger)
0.00%
0 / 635
PFValuesUtils
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 21
24806
0.00% covered (danger)
0.00%
0 / 635
 getSMWPropertyValues
0.00% covered (danger)
0.00%
0 / 1
56
0.00% covered (danger)
0.00%
0 / 27
 getCategoriesForPage
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 19
 getAllCategories
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 14
 getAllValuesForProperty
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 12
 getAllValuesForCargoField
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 getValuesForCargoField
0.00% covered (danger)
0.00%
0 / 1
30
0.00% covered (danger)
0.00%
0 / 23
 getAllPagesForCategory
0.00% covered (danger)
0.00%
0 / 1
462
0.00% covered (danger)
0.00%
0 / 106
 fixedMultiSort
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 8
 getAllPagesForConcept
0.00% covered (danger)
0.00%
0 / 1
506
0.00% covered (danger)
0.00%
0 / 91
 getAllPagesForNamespace
0.00% covered (danger)
0.00%
0 / 1
600
0.00% covered (danger)
0.00%
0 / 104
 getAutocompleteValues
0.00% covered (danger)
0.00%
0 / 1
72
0.00% covered (danger)
0.00%
0 / 25
 getAutocompletionTypeAndSource
0.00% covered (danger)
0.00%
0 / 1
240
0.00% covered (danger)
0.00%
0 / 47
 getRemoteDataTypeAndPossiblySetAutocompleteValues
0.00% covered (danger)
0.00%
0 / 1
132
0.00% covered (danger)
0.00%
0 / 27
 setAutocompleteValues
0.00% covered (danger)
0.00%
0 / 1
12
0.00% covered (danger)
0.00%
0 / 17
 getValuesArray
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 6
 getValuesFromExternalURL
0.00% covered (danger)
0.00%
0 / 1
56
0.00% covered (danger)
0.00%
0 / 26
 getSQLConditionForAutocompleteInColumn
0.00% covered (danger)
0.00%
0 / 1
42
0.00% covered (danger)
0.00%
0 / 24
 getAllPagesForQuery
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 15
 disambiguateLabels
0.00% covered (danger)
0.00%
0 / 1
72
0.00% covered (danger)
0.00%
0 / 25
 standardizeNamespace
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 3
 shiftShortestMatch
0.00% covered (danger)
0.00%
0 / 1
20
0.00% covered (danger)
0.00%
0 / 14
<?php
/**
 * Static functions for handling lists of values and labels.
 *
 * @author Yaron Koren
 * @file
 * @ingroup PF
 */
use MediaWiki\MediaWikiServices;
class PFValuesUtils {
    /**
     * Helper function to handle getPropertyValues().
     *
     * @param Store $store
     * @param Title $subject
     * @param string $propID
     * @param SMWRequestOptions|null $requestOptions
     * @return array
     * @suppress PhanUndeclaredTypeParameter For Store
     */
    public static function getSMWPropertyValues( $store, $subject, $propID, $requestOptions = null ) {
        // If SMW is not installed, exit out.
        if ( !class_exists( 'SMWDIWikiPage' ) ) {
            return [];
        }
        if ( $subject === null ) {
            $page = null;
        } else {
            $page = SMWDIWikiPage::newFromTitle( $subject );
        }
        $property = SMWDIProperty::newFromUserLabel( $propID );
        $res = $store->getPropertyValues( $page, $property, $requestOptions );
        $values = [];
        foreach ( $res as $value ) {
            if ( $value instanceof SMWDIUri ) {
                $values[] = $value->getURI();
            } elseif ( $value instanceof SMWDIWikiPage ) {
                $realValue = str_replace( '_', ' ', $value->getDBKey() );
                if ( $value->getNamespace() != 0 ) {
                    $realValue = PFUtils::getCanonicalName( $value->getNamespace() ) . ":$realValue";
                }
                $values[] = $realValue;
            } else {
                // getSortKey() seems to return the correct
                // value for all the other data types.
                $values[] = str_replace( '_', ' ', $value->getSortKey() );
            }
        }
        $values = self::shiftShortestMatch( $values );
        return $values;
    }
    /**
     * Helper function - gets names of categories for a page;
     * based on Title::getParentCategories(), but simpler.
     *
     * @param Title $title
     * @return array
     */
    public static function getCategoriesForPage( $title ) {
        $categories = [];
        $db = wfGetDB( DB_REPLICA );
        $titlekey = $title->getArticleID();
        if ( $titlekey == 0 ) {
            // Something's wrong - exit
            return $categories;
        }
        $conditions = [ 'cl_from' => $titlekey ];
        $res = $db->select(
            'categorylinks',
            'DISTINCT cl_to',
            $conditions,
            __METHOD__
        );
        while ( $row = $res->fetchRow() ) {
            $categories[] = $row['cl_to'];
        }
        $res->free();
        return $categories;
    }
    /**
     * Helper function - returns names of all the categories.
     * @return array
     */
    public static function getAllCategories() {
        $categories = [];
        $db = wfGetDB( DB_REPLICA );
        $res = $db->select(
            'category',
            'cat_title',
             null,
            __METHOD__
        );
        while ( $row = $res->fetchRow() ) {
            $categories[] = $row['cat_title'];
        }
        $res->free();
        return $categories;
    }
    /**
     * This function, unlike the others, doesn't take in a substring
     * because it uses the SMW data store, which can't perform
     * case-insensitive queries; for queries with a substring, the
     * function PFAutocompleteAPI::getAllValuesForProperty() exists.
     *
     * @param string $property_name
     * @return array
     */
    public static function getAllValuesForProperty( $property_name ) {
        global $wgPageFormsMaxAutocompleteValues;
        $store = PFUtils::getSMWStore();
        if ( $store == null ) {
            return [];
        }
        $requestoptions = new SMWRequestOptions();
        $requestoptions->limit = $wgPageFormsMaxAutocompleteValues;
        $values = self::getSMWPropertyValues( $store, null, $property_name, $requestoptions );
        sort( $values );
        $values = self::shiftShortestMatch( $values );
        return $values;
    }
    /**
     * Used with the Cargo extension.
     * @param string $tableName
     * @param string $fieldName
     * @return array
     */
    public static function getAllValuesForCargoField( $tableName, $fieldName ) {
        return self::getValuesForCargoField( $tableName, $fieldName );
    }
    /**
     * Used with the Cargo extension.
     * @param string $tableName
     * @param string $fieldName
     * @param string|null $whereStr
     * @return array
     */
    public static function getValuesForCargoField( $tableName, $fieldName, $whereStr = null ) {
        global $wgPageFormsMaxLocalAutocompleteValues;
        // The limit should be greater than the maximum number of local
        // autocomplete values, so that form inputs also know whether
        // to switch to remote autocompletion.
        // (We increment by 10, to be on the safe side, since some values
        // can be null, etc.)
        $limitStr = max( 100, $wgPageFormsMaxLocalAutocompleteValues + 10 );
        try {
            $sqlQuery = CargoSQLQuery::newFromValues( $tableName, $fieldName, $whereStr, $joinOnStr = null, $fieldName, $havingStr = null, $fieldName, $limitStr, $offsetStr = 0 );
        } catch ( Exception $e ) {
            return [];
        }
        $queryResults = $sqlQuery->run();
        $values = [];
        // Field names starting with a '_' are special fields -
        // all other fields will have had their underscores
        // replaced with spaces in $queryResults.
        if ( $fieldName[0] == '_' ) {
            $fieldAlias = $fieldName;
        } else {
            $fieldAlias = str_replace( '_', ' ', $fieldName );
        }
        foreach ( $queryResults as $row ) {
            if ( !array_key_exists( $fieldAlias, $row ) ) {
                continue;
            }
            // Cargo HTML-encodes everything - let's decode double
            // quotes, at least.
            $values[] = str_replace( '&quot;', '"', $row[$fieldAlias] );
        }
        $values = self::shiftShortestMatch( $values );
        return $values;
    }
    /**
     * Get all the pages that belong to a category and all its
     * subcategories, down a certain number of levels - heavily based on
     * SMW's SMWInlineQuery::includeSubcategories().
     *
     * @param string $top_category
     * @param int $num_levels
     * @param string|null $substring
     * @return string[]
     */
    public static function getAllPagesForCategory( $top_category, $num_levels, $substring = null ) {
        if ( $num_levels == 0 ) {
            return [ $top_category ];
        }
        global $wgPageFormsMaxAutocompleteValues, $wgPageFormsUseDisplayTitle;
        $db = wfGetDB( DB_REPLICA );
        $top_category = str_replace( ' ', '_', $top_category );
        $categories = [ $top_category ];
        $checkcategories = [ $top_category ];
        $pages = [];
        $sortkeys = [];
        for ( $level = $num_levels; $level > 0; $level-- ) {
            $newcategories = [];
            foreach ( $checkcategories as $category ) {
                $tables = [ 'categorylinks', 'page' ];
                $columns = [ 'page_title', 'page_namespace' ];
                $conditions = [];
                $conditions[] = 'cl_from = page_id';
                $conditions['cl_to'] = $category;
                if ( $wgPageFormsUseDisplayTitle ) {
                    $tables['pp_displaytitle'] = 'page_props';
                    $tables['pp_defaultsort'] = 'page_props';
                    $columns['pp_displaytitle_value'] = 'pp_displaytitle.pp_value';
                    $columns['pp_defaultsort_value'] = 'pp_defaultsort.pp_value';
                    $join = [
                        'pp_displaytitle' => [
                            'LEFT JOIN', [
                                'pp_displaytitle.pp_page = page_id',
                                'pp_displaytitle.pp_propname = \'displaytitle\''
                            ]
                        ],
                        'pp_defaultsort' => [
                            'LEFT JOIN', [
                                'pp_defaultsort.pp_page = page_id',
                                'pp_defaultsort.pp_propname = \'defaultsort\''
                            ]
                        ]
                    ];
                    if ( $substring != null ) {
                        $conditions[] = '((pp_displaytitle.pp_value IS NULL OR pp_displaytitle.pp_value = \'\') AND (' .
                            self::getSQLConditionForAutocompleteInColumn( 'page_title', $substring ) .
                            ')) OR ' .
                            self::getSQLConditionForAutocompleteInColumn( 'pp_displaytitle.pp_value', $substring ) .
                            ' OR page_namespace = ' . NS_CATEGORY;
                    }
                } else {
                    $join = [];
                    if ( $substring != null ) {
                        $conditions[] = self::getSQLConditionForAutocompleteInColumn( 'page_title', $substring ) . ' OR page_namespace = ' . NS_CATEGORY;
                    }
                }
                // Make the query.
                $res = $db->select(
                    $tables,
                    $columns,
                    $conditions,
                    __METHOD__,
                    $options = [
                        'ORDER BY' => 'cl_type, cl_sortkey',
                        'LIMIT' => $wgPageFormsMaxAutocompleteValues
                    ],
                    $join );
                if ( $res ) {
                    while ( $res && $row = $res->fetchRow() ) {
                        if ( !array_key_exists( 'page_title', $row ) ) {
                            continue;
                        }
                        $page_namespace = $row['page_namespace'];
                        $page_name = $row[ 'page_title' ];
                        if ( $page_namespace == NS_CATEGORY ) {
                            if ( !in_array( $page_name, $categories ) ) {
                                $newcategories[] = $page_name;
                            }
                        } else {
                            $cur_title = Title::makeTitleSafe( $page_namespace, $page_name );
                            if ( $cur_title === null ) {
                                // This can happen if it's
                                // a "phantom" page, in a
                                // namespace that no longer exists.
                                continue;
                            }
                            $cur_value = PFUtils::titleString( $cur_title );
                            if ( !in_array( $cur_value, $pages ) ) {
                                if ( array_key_exists( 'pp_displaytitle_value', $row ) &&
                                    ( $row[ 'pp_displaytitle_value' ] ) !== null &&
                                    trim( str_replace( '&#160;', '', strip_tags( $row[ 'pp_displaytitle_value' ] ) ) ) !== '' ) {
                                    $pages[ $cur_value . '@' ] = htmlspecialchars_decode( $row[ 'pp_displaytitle_value'] );
                                } else {
                                    $pages[ $cur_value . '@' ] = $cur_value;
                                }
                                if ( array_key_exists( 'pp_defaultsort_value', $row ) &&
                                    ( $row[ 'pp_defaultsort_value' ] ) !== null ) {
                                    $sortkeys[ $cur_value ] = $row[ 'pp_defaultsort_value'];
                                } else {
                                    $sortkeys[ $cur_value ] = $cur_value;
                                }
                            }
                        }
                    }
                    $res->free();
                }
            }
            if ( count( $newcategories ) == 0 ) {
                return self::fixedMultiSort( $sortkeys, $pages );
            } else {
                $categories = array_merge( $categories, $newcategories );
            }
            $checkcategories = array_diff( $newcategories, [] );
        }
        return self::fixedMultiSort( $sortkeys, $pages );
    }
    /**
     * array_multisort() unfortunately messes up array keys that are
     * numeric - they get converted to 0, 1, etc. There are a few ways to
     * get around this, but I (Yaron) couldn't get those working, so
     * instead we're going with this hack, where all key values get
     * appended with a '@' before sorting, which is then removed after
     * sorting. It's inefficient, but it's probably good enough.
     *
     * @param string[] $sortkeys
     * @param string[] $pages
     * @return string[] a sorted version of $pages, sorted via $sortkeys
     */
    static function fixedMultiSort( $sortkeys, $pages ) {
        array_multisort( $sortkeys, $pages );
        $newPages = [];
        foreach ( $pages as $key => $value ) {
            $fixedKey = rtrim( $key, '@' );
            $newPages[$fixedKey] = $value;
        }
        return $newPages;
    }
    /**
     * @param string $conceptName
     * @param string|null $substring
     * @return string[]
     */
    public static function getAllPagesForConcept( $conceptName, $substring = null ) {
        global $wgPageFormsMaxAutocompleteValues, $wgPageFormsAutocompleteOnAllChars;
        $store = PFUtils::getSMWStore();
        if ( $store == null ) {
            return [];
        }
        $conceptTitle = Title::makeTitleSafe( SMW_NS_CONCEPT, $conceptName );
        if ( $substring !== null ) {
            $substring = strtolower( $substring );
        }
        // Escape if there's no such concept.
        if ( $conceptTitle == null || !$conceptTitle->exists() ) {
            throw new MWException( wfMessage( 'pf-missingconcept', wfEscapeWikiText( $conceptName ) ) );
        }
        global $wgPageFormsUseDisplayTitle;
        $conceptDI = SMWDIWikiPage::newFromTitle( $conceptTitle );
        $desc = new SMWConceptDescription( $conceptDI );
        $printout = new SMWPrintRequest( SMWPrintRequest::PRINT_THIS, "" );
        $desc->addPrintRequest( $printout );
        $query = new SMWQuery( $desc );
        $query->setLimit( $wgPageFormsMaxAutocompleteValues );
        $query_result = $store->getQueryResult( $query );
        $pages = [];
        $sortkeys = [];
        $titles = [];
        while ( $res = $query_result->getNext() ) {
            $page = $res[0]->getNextText( SMW_OUTPUT_WIKI );
            if ( $wgPageFormsUseDisplayTitle ) {
                $title = Title::newFromText( $page );
                if ( $title !== null ) {
                    $titles[] = $title;
                }
            } else {
                $pages[$page] = $page;
                $sortkeys[$page] = $page;
            }
        }
        if ( $wgPageFormsUseDisplayTitle ) {
            $services = MediaWikiServices::getInstance();
            if ( method_exists( $services, 'getPageProps' ) ) {
                // MW 1.36+
                $pageProps = $services->getPageProps();
            } else {
                $pageProps = PageProps::getInstance();
            }
            $properties = $pageProps->getProperties( $titles,
                [ 'displaytitle', 'defaultsort' ] );
            foreach ( $titles as $title ) {
                if ( array_key_exists( $title->getArticleID(), $properties ) ) {
                    $titleprops = $properties[$title->getArticleID()];
                } else {
                    $titleprops = [];
                }
                $titleText = $title->getPrefixedText();
                if ( array_key_exists( 'displaytitle', $titleprops ) &&
                    trim( str_replace( '&#160;', '', strip_tags( $titleprops['displaytitle'] ) ) ) !== '' ) {
                    $pages[$titleText] = htmlspecialchars_decode( $titleprops['displaytitle'] );
                } else {
                    $pages[$titleText] = $titleText;
                }
                if ( array_key_exists( 'defaultsort', $titleprops ) ) {
                    $sortkeys[$titleText] = $titleprops['defaultsort'];
                } else {
                    $sortkeys[$titleText] = $titleText;
                }
            }
        }
        if ( $substring !== null ) {
            $filtered_pages = [];
            $filtered_sortkeys = [];
            foreach ( $pages as $index => $pageName ) {
                // Filter on the substring manually. It would
                // be better to do this filtering in the
                // original SMW query, but that doesn't seem
                // possible yet.
                // @TODO - this will miss a lot of results for
                // concepts with > 1000 pages. Instead, this
                // code should loop through all the pages,
                // using "offset".
                $lowercasePageName = strtolower( $pageName );
                $position = strpos( $lowercasePageName, $substring );
                if ( $position !== false ) {
                    if ( $wgPageFormsAutocompleteOnAllChars ) {
                        if ( $position >= 0 ) {
                            $filtered_pages[$index] = $pageName;
                            $filtered_sortkeys[$index] = $sortkeys[$index];
                        }
                    } else {
                        if ( $position === 0 ||
                            strpos( $lowercasePageName, ' ' . $substring ) > 0 ) {
                            $filtered_pages[$index] = $pageName;
                            $filtered_sortkeys[$index] = $sortkeys[$index];
                        }
                    }
                }
            }
            $pages = $filtered_pages;
            $sortkeys = $filtered_sortkeys;
        }
        array_multisort( $sortkeys, $pages );
        return $pages;
    }
    public static function getAllPagesForNamespace( $namespaceStr, $substring = null ) {
        global $wgLanguageCode, $wgPageFormsUseDisplayTitle;
        $namespaceNames = explode( ',', $namespaceStr );
        $allNamespaces = PFUtils::getContLang()->getNamespaces();
        if ( $wgLanguageCode != 'en' ) {
            $englishLang = Language::factory( 'en' );
            $allEnglishNamespaces = $englishLang->getNamespaces();
        }
        $queriedNamespaces = [];
        $namespaceConditions = [];
        foreach ( $namespaceNames as $namespace_name ) {
            $namespace_name = self::standardizeNamespace( $namespace_name );
            // Cycle through all the namespace names for this language, and
            // if one matches the namespace specified in the form, get the
            // names of all the pages in that namespace.
            // Switch to blank for the string 'Main'.
            if ( $namespace_name == 'Main' || $namespace_name == 'main' ) {
                $namespace_name = '';
            }
            $matchingNamespaceCode = null;
            foreach ( $allNamespaces as $curNSCode => $curNSName ) {
                if ( $curNSName == $namespace_name ) {
                    $matchingNamespaceCode = $curNSCode;
                }
            }
            // If that didn't find anything, and we're in a language
            // other than English, check English as well.
            if ( $matchingNamespaceCode === null && $wgLanguageCode != 'en' ) {
                foreach ( $allEnglishNamespaces as $curNSCode => $curNSName ) {
                    if ( $curNSName == $namespace_name ) {
                        $matchingNamespaceCode = $curNSCode;
                    }
                }
            }
            if ( $matchingNamespaceCode === null ) {
                throw new MWException( wfMessage( 'pf-missingnamespace', wfEscapeWikiText( $namespace_name ) ) );
            }
            $queriedNamespaces[] = $matchingNamespaceCode;
            $namespaceConditions[] = "page_namespace = $matchingNamespaceCode";
        }
        $db = wfGetDB( DB_REPLICA );
        $conditions = [];
        $conditions[] = implode( ' OR ', $namespaceConditions );
        $tables = [ 'page' ];
        $columns = [ 'page_title' ];
        if ( count( $namespaceNames ) > 1 ) {
            $columns[] = 'page_namespace';
        }
        if ( $wgPageFormsUseDisplayTitle ) {
            $tables['pp_displaytitle'] = 'page_props';
            $tables['pp_defaultsort'] = 'page_props';
            $columns['pp_displaytitle_value'] = 'pp_displaytitle.pp_value';
            $columns['pp_defaultsort_value'] = 'pp_defaultsort.pp_value';
            $join = [
                'pp_displaytitle' => [
                    'LEFT JOIN', [
                        'pp_displaytitle.pp_page = page_id',
                        'pp_displaytitle.pp_propname = \'displaytitle\''
                    ]
                ],
                'pp_defaultsort' => [
                    'LEFT JOIN', [
                        'pp_defaultsort.pp_page = page_id',
                        'pp_defaultsort.pp_propname = \'defaultsort\''
                    ]
                ]
            ];
            if ( $substring != null ) {
                $substringCondition = '(pp_displaytitle.pp_value IS NULL AND (' .
                    self::getSQLConditionForAutocompleteInColumn( 'page_title', $substring ) .
                    ')) OR ' .
                    self::getSQLConditionForAutocompleteInColumn( 'pp_displaytitle.pp_value', $substring, false );
                if ( !in_array( NS_CATEGORY, $queriedNamespaces ) ) {
                    $substringCondition .= ' OR page_namespace = ' . NS_CATEGORY;
                }
                $conditions[] = $substringCondition;
            }
        } else {
            $join = [];
            if ( $substring != null ) {
                $conditions[] = self::getSQLConditionForAutocompleteInColumn( 'page_title', $substring );
            }
        }
        $res = $db->select( $tables, $columns, $conditions, __METHOD__, $options = [], $join );
        $pages = [];
        $sortkeys = [];
        while ( $row = $res->fetchRow() ) {
            // If there's more than one namespace, include the
            // namespace prefix in the results - otherwise, don't.
            if ( array_key_exists( 'page_namespace', $row ) ) {
                $actualTitle = Title::newFromText( $row['page_title'], $row['page_namespace'] );
                $title = $actualTitle->getPrefixedText();
            } else {
                $title = str_replace( '_', ' ', $row['page_title'] );
            }
            if ( array_key_exists( 'pp_displaytitle_value', $row ) &&
                ( $row[ 'pp_displaytitle_value' ] ) !== null &&
                trim( str_replace( '&#160;', '', strip_tags( $row[ 'pp_displaytitle_value' ] ) ) ) !== '' ) {
                $pages[ $title ] = htmlspecialchars_decode( $row[ 'pp_displaytitle_value'], ENT_QUOTES );
            } else {
                $pages[ $title ] = $title;
            }
            if ( array_key_exists( 'pp_defaultsort_value', $row ) &&
                ( $row[ 'pp_defaultsort_value' ] ) !== null ) {
                $sortkeys[ $title ] = $row[ 'pp_defaultsort_value'];
            } else {
                $sortkeys[ $title ] = $title;
            }
        }
        $res->free();
        array_multisort( $sortkeys, $pages );
        return $pages;
    }
    /**
     * Creates an array of values that match the specified source name and
     * type, for use by both Javascript autocompletion and comboboxes.
     *
     * @param string|null $source_name
     * @param string $source_type
     * @return string[]
     */
    public static function getAutocompleteValues( $source_name, $source_type ) {
        if ( $source_name === null ) {
            return [];
        }
        // The query depends on whether this is a Cargo field, SMW
        // property, category, SMW concept or namespace.
        if ( $source_type == 'cargo field' ) {
            $arr = explode( '|', $source_name );
            if ( count( $arr ) == 3 ) {
                $names_array = self::getValuesForCargoField( $arr[0], $arr[1], $arr[2] );
            } else {
                list( $table_name, $field_name ) = explode( '|', $source_name, 2 );
                $names_array = self::getAllValuesForCargoField( $table_name, $field_name );
            }
            // Remove blank/null values from the array.
            $names_array = array_values( array_filter( $names_array ) );
        } elseif ( $source_type == 'property' ) {
            $names_array = self::getAllValuesForProperty( $source_name );
        } elseif ( $source_type == 'category' ) {
            $names_array = self::getAllPagesForCategory( $source_name, 10 );
        } elseif ( $source_type == 'concept' ) {
            $names_array = self::getAllPagesForConcept( $source_name );
        } elseif ( $source_type == 'query' ) {
            $names_array = self::getAllPagesForQuery( $source_name );
        } else {
            // i.e., $source_type == 'namespace'
            $names_array = self::getAllPagesForNamespace( $source_name );
        }
        return $names_array;
    }
    public static function getAutocompletionTypeAndSource( &$field_args ) {
        global $wgCapitalLinks;
        if ( array_key_exists( 'values from property', $field_args ) ) {
            $autocompletionSource = $field_args['values from property'];
            $autocompleteFieldType = 'property';
        } elseif ( array_key_exists( 'values from category', $field_args ) ) {
            $autocompleteFieldType = 'category';
            $autocompletionSource = $field_args['values from category'];
        } elseif ( array_key_exists( 'values from concept', $field_args ) ) {
            $autocompleteFieldType = 'concept';
            $autocompletionSource = $field_args['values from concept'];
        } elseif ( array_key_exists( 'values from namespace', $field_args ) ) {
            $autocompleteFieldType = 'namespace';
            $autocompletionSource = $field_args['values from namespace'];
        } elseif ( array_key_exists( 'values from url', $field_args ) ) {
            $autocompleteFieldType = 'external_url';
            $autocompletionSource = $field_args['values from url'];
        } elseif ( array_key_exists( 'values', $field_args ) ) {
            global $wgPageFormsFieldNum;
            $autocompleteFieldType = 'values';
            $autocompletionSource = "values-$wgPageFormsFieldNum";
        } elseif ( array_key_exists( 'autocomplete field type', $field_args ) ) {
            $autocompleteFieldType = $field_args['autocomplete field type'];
            $autocompletionSource = $field_args['autocompletion source'];
        } elseif ( array_key_exists( 'full_cargo_field', $field_args ) ) {
            $autocompletionSource = $field_args['full_cargo_field'];
            $autocompleteFieldType = 'cargo field';
        } elseif ( array_key_exists( 'cargo field', $field_args ) ) {
            $fieldName = $field_args['cargo field'];
            $tableName = $field_args['cargo table'];
            $autocompletionSource = "$tableName|$fieldName";
            $autocompleteFieldType = 'cargo field';
            if ( array_key_exists( 'cargo where', $field_args ) ) {
                $whereStr = $field_args['cargo where'];
                $autocompletionSource .= "|$whereStr";
            }
        } elseif ( array_key_exists( 'semantic_property', $field_args ) ) {
            $autocompletionSource = $field_args['semantic_property'];
            $autocompleteFieldType = 'property';
        } else {
            $autocompleteFieldType = null;
            $autocompletionSource = null;
        }
        if ( $wgCapitalLinks && $autocompleteFieldType != 'external_url' && $autocompleteFieldType != 'cargo field' ) {
            $autocompletionSource = PFUtils::getContLang()->ucfirst( $autocompletionSource );
        }
        return [ $autocompleteFieldType, $autocompletionSource ];
    }
    public static function getRemoteDataTypeAndPossiblySetAutocompleteValues( $autocompleteFieldType, $autocompletionSource, $field_args, $autocompleteSettings ) {
        global $wgPageFormsMaxLocalAutocompleteValues, $wgPageFormsAutocompleteValues;
        if ( $autocompleteFieldType == 'external_url' ) {
            // Autocompletion from URL is always done remotely.
            return $autocompleteFieldType;
        }
        if ( $autocompletionSource == '' ) {
            // No autocompletion.
            return null;
        }
        // @TODO - that empty() check shouldn't be necessary.
        if ( array_key_exists( 'possible_values', $field_args ) &&
        !empty( $field_args['possible_values'] ) ) {
            $autocompleteValues = $field_args['possible_values'];
        } elseif ( $autocompleteFieldType == 'values' ) {
            $autocompleteValues = explode( ',', $field_args['values'] );
        } else {
            $autocompleteValues = self::getAutocompleteValues( $autocompletionSource, $autocompleteFieldType );
        }
        if ( count( $autocompleteValues ) > $wgPageFormsMaxLocalAutocompleteValues &&
            $autocompleteFieldType != 'values' &&
            !array_key_exists( 'values dependent on', $field_args ) &&
            !array_key_exists( 'mapping template', $field_args ) &&
            !array_key_exists( 'mapping property', $field_args )
        ) {
            return $autocompleteFieldType;
        } else {
            $wgPageFormsAutocompleteValues[$autocompleteSettings] = $autocompleteValues;
            return null;
        }
    }
    /**
     * Get all autocomplete-related values, plus delimiter value
     * (it's needed also for the 'uploadable' link, if there is one).
     *
     * @param array $field_args
     * @param bool $is_list
     * @return string[]
     */
    public static function setAutocompleteValues( $field_args, $is_list ) {
        list( $autocompleteFieldType, $autocompletionSource ) =
            self::getAutocompletionTypeAndSource( $field_args );
        $autocompleteSettings = $autocompletionSource;
        if ( $is_list ) {
            $autocompleteSettings .= ',list';
            if ( array_key_exists( 'delimiter', $field_args ) ) {
                $delimiter = $field_args['delimiter'];
                $autocompleteSettings .= ',' . $delimiter;
            } else {
                $delimiter = ',';
            }
        } else {
            $delimiter = null;
        }
        $remoteDataType = self::getRemoteDataTypeAndPossiblySetAutocompleteValues( $autocompleteFieldType, $autocompletionSource, $field_args, $autocompleteSettings );
        return [ $autocompleteSettings, $remoteDataType, $delimiter ];
    }
    /**
     * Helper function to get an array of values out of what may be either
     * an array or a delimited string.
     *
     * @param string[]|string $value
     * @param string $delimiter
     * @return string[]
     */
    public static function getValuesArray( $value, $delimiter ) {
        if ( is_array( $value ) ) {
            return $value;
        } else {
            // Remove extra spaces.
            return array_map( 'trim', explode( $delimiter, $value ) );
        }
    }
    public static function getValuesFromExternalURL( $external_url_alias, $substring ) {
        global $wgPageFormsAutocompletionURLs;
        if ( empty( $wgPageFormsAutocompletionURLs ) ) {
            return wfMessage( 'pf-nocompletionurls' );
        }
        if ( !array_key_exists( $external_url_alias, $wgPageFormsAutocompletionURLs ) ) {
            return wfMessage( 'pf-invalidexturl' );
        }
        $url = $wgPageFormsAutocompletionURLs[$external_url_alias];
        if ( empty( $url ) ) {
            return wfMessage( 'pf-blankexturl' );
        }
        $url = str_replace( '<substr>', urlencode( $substring ), $url );
        $page_contents = Http::get( $url );
        if ( empty( $page_contents ) ) {
            return wfMessage( 'pf-externalpageempty' );
        }
        $data = json_decode( $page_contents );
        if ( empty( $data ) ) {
            return wfMessage( 'pf-externalpagebadjson' );
        }
        $return_values = [];
        foreach ( $data->pfautocomplete as $val ) {
            $return_values[] = (array)$val;
        }
        return $return_values;
    }
    /**
     * Returns a SQL condition for autocompletion substring value in a column.
     *
     * @param string $column Value column name
     * @param string $substring Substring to look for
     * @param bool $replaceSpaces
     * @return string SQL condition for use in WHERE clause
     */
    public static function getSQLConditionForAutocompleteInColumn( $column, $substring, $replaceSpaces = true ) {
        global $wgPageFormsAutocompleteOnAllChars;
        $db = wfGetDB( DB_REPLICA );
        // CONVERT() is also supported in PostgreSQL, but it doesn't
        // seem to work the same way.
        if ( $db->getType() == 'mysql' ) {
            $column_value = "LOWER(CONVERT($column USING utf8))";
        } else {
            $column_value = "LOWER($column)";
        }
        $substring = strtolower( $substring );
        if ( $replaceSpaces ) {
            $substring = str_replace( ' ', '_', $substring );
        }
        if ( $wgPageFormsAutocompleteOnAllChars ) {
            return $column_value . $db->buildLike( $db->anyString(), $substring, $db->anyString() );
        } else {
            $sqlCond = $column_value . $db->buildLike( $substring, $db->anyString() );
            $spaceRepresentation = $replaceSpaces ? '_' : ' ';
            $wordSeparators = [ $spaceRepresentation, '/', '(', ')', '-', '\'', '\"' ];
            foreach ( $wordSeparators as $wordSeparator ) {
                $sqlCond .= ' OR ' . $column_value .
                    $db->buildLike( $db->anyString(), $wordSeparator . $substring, $db->anyString() );
            }
            return $sqlCond;
        }
    }
    /**
     * Returns an array of the names of pages that are the result of an SMW query.
     *
     * @param string $rawQuery the query string like [[Category:Trees]][[age::>1000]]
     * @return array
     */
    public static function getAllPagesForQuery( $rawQuery ) {
        $rawQueryArray = [ $rawQuery ];
        SMWQueryProcessor::processFunctionParams( $rawQueryArray, $queryString, $processedParams, $printouts );
        SMWQueryProcessor::addThisPrintout( $printouts, $processedParams );
        $processedParams = SMWQueryProcessor::getProcessedParams( $processedParams, $printouts );
        $queryObj = SMWQueryProcessor::createQuery( $queryString,
            $processedParams,
            SMWQueryProcessor::SPECIAL_PAGE, '', $printouts );
        $res = PFUtils::getSMWStore()->getQueryResult( $queryObj );
        $rows = $res->getResults();
        $pages = [];
        foreach ( $rows as $row ) {
            $pages[] = $row->getDbKey();
        }
        return $pages;
    }
    /**
     * Doing "mapping" on values can potentially lead to more than one
     * value having the same "label". To avoid this, we find duplicate
     * labels, if there are any, add on the real value, in parentheses,
     * to all of them.
     *
     * @param array $labels
     * @return array
     */
    public static function disambiguateLabels( array $labels ) {
        asort( $labels );
        if ( count( $labels ) == count( array_unique( $labels ) ) ) {
            return $labels;
        }
        $fixed_labels = [];
        foreach ( $labels as $value => $label ) {
            $fixed_labels[$value] = $labels[$value];
        }
        $counts = array_count_values( $fixed_labels );
        foreach ( $counts as $current_label => $count ) {
            if ( $count > 1 ) {
                $matching_keys = array_keys( $labels, $current_label );
                foreach ( $matching_keys as $key ) {
                    $fixed_labels[$key] .= ' (' . $key . ')';
                }
            }
        }
        if ( count( $fixed_labels ) == count( array_unique( $fixed_labels ) ) ) {
            return $fixed_labels;
        }
        // If that didn't work, just add on " (value)" to *all* the
        // labels. @TODO - is this necessary?
        foreach ( $labels as $value => $label ) {
            $labels[$value] .= ' (' . $value . ')';
        }
        return $labels;
    }
    /**
     * Get the exact canonical namespace string, given a user-created string
     *
     * @param string $namespaceStr
     * @return string
     */
    public static function standardizeNamespace( $namespaceStr ) {
        $dummyTitle = Title::newFromText( "$namespaceStr:ABC" );
        return $dummyTitle ? $dummyTitle->getNsText() : $namespaceStr;
    }
    /**
     * We want the result string matching what the user has currently typed (if
     * there is one) to be at the top. However, with local autocompletion, we
     * unfortunately don't know what the user's current input is. So instead we
     * just take the shortest result string and move that to the top, and hope
     * that it's a match.
     *
     * @param array $values
     * @return array $values
     */
    public static function shiftShortestMatch( $values ) {
        if ( empty( $values ) ) {
            return $values;
        }
        $shortestString = $values[ 0 ];
        foreach ( $values as $val ) {
            if ( strlen( $val ) < strlen( $shortestString ) ) {
                $shortestString = $val;
            }
        }
        $firstMatchIdx = array_search( $shortestString, $values );
        unset( $values[ $firstMatchIdx ] );
        array_unshift( $values, $shortestString );
        return $values;
    }
}