/** @var array Maps extension paths to info arrays */
private static $extensionInfo = null;
+ /** @var int[][][] Cache for self::filterIDs() */
+ private static $filterIDsCache = [];
+
/** @var ApiMain */
private $mMainModule;
/** @var string */
}
}
+ /**
+ * Filter out-of-range values from a list of positive integer IDs
+ * @since 1.33
+ * @param array $fields Array of pairs of table and field to check
+ * @param (string|int)[] $ids IDs to filter. Strings in the array are
+ * expected to be stringified ints.
+ * @return (string|int)[] Filtered IDs.
+ */
+ protected function filterIDs( $fields, array $ids ) {
+ $min = INF;
+ $max = 0;
+ foreach ( $fields as list( $table, $field ) ) {
+ if ( isset( self::$filterIDsCache[$table][$field] ) ) {
+ $row = self::$filterIDsCache[$table][$field];
+ } else {
+ $row = $this->getDB()->selectRow(
+ $table,
+ [
+ 'min_id' => "MIN($field)",
+ 'max_id' => "MAX($field)",
+ ],
+ null,
+ __METHOD__
+ );
+ self::$filterIDsCache[$table][$field] = $row;
+ }
+ $min = min( $min, $row->min_id );
+ $max = max( $max, $row->max_id );
+ }
+ return array_filter( $ids, function ( $id ) use ( $min, $max ) {
+ return ( is_int( $id ) && $id >= 0 || ctype_digit( $id ) )
+ && $id >= $min && $id <= $max;
+ } );
+ }
+
/**@}*/
/************************************************************************//**
/**
* Does the same as initFromTitles(), but is based on page IDs instead
* @param array $pageids Array of page IDs
+ * @param bool $filterIds Whether the IDs need filtering
*/
- private function initFromPageIds( $pageids ) {
+ private function initFromPageIds( $pageids, $filterIds = false ) {
if ( !$pageids ) {
return;
}
$pageids = array_map( 'intval', $pageids ); // paranoia
$remaining = array_flip( $pageids );
- $pageids = self::getPositiveIntegers( $pageids );
+ if ( $filterIds ) {
+ $pageids = $this->filterIDs( [ [ 'page', 'page_id' ] ], $pageids );
+ }
$res = null;
if ( !empty( $pageids ) ) {
$pageids = [];
$remaining = array_flip( $revids );
- $revids = self::getPositiveIntegers( $revids );
+ $revids = $this->filterIDs( [ [ 'revision', 'rev_id' ], [ 'archive', 'ar_rev_id' ] ], $revids );
+ $goodRemaining = array_flip( $revids );
- if ( !empty( $revids ) ) {
+ if ( $revids ) {
$tables = [ 'revision', 'page' ];
$fields = [ 'rev_id', 'rev_page' ];
$where = [ 'rev_id' => $revids, 'rev_page = page_id' ];
$this->mLiveRevIDs[$revid] = $pageid;
$pageids[$pageid] = '';
unset( $remaining[$revid] );
+ unset( $goodRemaining[$revid] );
}
}
- $this->mMissingRevIDs = array_keys( $remaining );
-
// Populate all the page information
- $this->initFromPageIds( array_keys( $pageids ) );
+ $this->initFromPageIds( array_keys( $pageids ), false );
// If the user can see deleted revisions, pull out the corresponding
// titles from the archive table and include them too. We ignore
// ar_page_id because deleted revisions are tied by title, not page_id.
- if ( !empty( $this->mMissingRevIDs ) && $this->getUser()->isAllowed( 'deletedhistory' ) ) {
- $remaining = array_flip( $this->mMissingRevIDs );
+ if ( $goodRemaining && $this->getUser()->isAllowed( 'deletedhistory' ) ) {
$tables = [ 'archive' ];
$fields = [ 'ar_rev_id', 'ar_namespace', 'ar_title' ];
- $where = [ 'ar_rev_id' => $this->mMissingRevIDs ];
+ $where = [ 'ar_rev_id' => array_keys( $goodRemaining ) ];
$res = $db->select( $tables, $fields, $where, __METHOD__ );
$titles = [];
$remaining[$revid] = true;
}
}
-
- $this->mMissingRevIDs = array_keys( $remaining );
}
+
+ $this->mMissingRevIDs = array_keys( $remaining );
}
/**
return $this->mDbSource->getDB();
}
- /**
- * Returns the input array of integers with all values < 0 removed
- *
- * @param array $array
- * @return array
- */
- private static function getPositiveIntegers( $array ) {
- // T27734 API: possible issue with revids validation
- // It seems with a load of revision rows, MySQL gets upset
- // Remove any < 0 integers, as they can't be valid
- foreach ( $array as $i => $int ) {
- if ( $int < 0 ) {
- unset( $array[$i] );
- }
- }
-
- return $array;
- }
-
public function getAllowedParams( $flags = 0 ) {
$result = [
'titles' => [
}
}
+ /**
+ * Like addWhereFld for an integer list of IDs
+ * @since 1.33
+ * @param string $table Table name
+ * @param string $field Field name
+ * @param int[] $ids IDs
+ * @return int Count of IDs actually included
+ */
+ protected function addWhereIDsFld( $table, $field, $ids ) {
+ // Use count() to its full documented capabilities to simultaneously
+ // test for null, empty array or empty countable object
+ if ( count( $ids ) ) {
+ $ids = $this->filterIDs( [ [ $table, $field ] ], $ids );
+
+ if ( !count( $ids ) ) {
+ // Return nothing, no IDs are valid
+ $this->where[] = '0 = 1';
+ } else {
+ $this->where[$field] = $ids;
+ }
+ }
+ return count( $ids );
+ }
+
/**
* Add a WHERE clause corresponding to a range, and an ORDER BY
* clause to sort in the right direction