Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
60 / 60 |
|
100.00% |
4 / 4 |
CRAP | |
100.00% |
1 / 1 |
UserAgentClientHintsLookup | |
100.00% |
60 / 60 |
|
100.00% |
4 / 4 |
15 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getClientHintsByReferenceIds | |
100.00% |
40 / 40 |
|
100.00% |
1 / 1 |
6 | |||
prepareFirstResultsArray | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
generateUniqueClientHintsIdCombinations | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
5 |
1 | <?php |
2 | |
3 | namespace MediaWiki\CheckUser\Services; |
4 | |
5 | use MediaWiki\CheckUser\ClientHints\ClientHintsData; |
6 | use MediaWiki\CheckUser\ClientHints\ClientHintsLookupResults; |
7 | use MediaWiki\CheckUser\ClientHints\ClientHintsReferenceIds; |
8 | use Wikimedia\Rdbms\IReadableDatabase; |
9 | |
10 | /** |
11 | * A service that gets ClientHintsData objects from the database given |
12 | * a ClientHintsReferenceIds object of reference IDs. |
13 | */ |
14 | class UserAgentClientHintsLookup { |
15 | private IReadableDatabase $dbr; |
16 | |
17 | /** |
18 | * @param IReadableDatabase $dbr |
19 | */ |
20 | public function __construct( IReadableDatabase $dbr ) { |
21 | $this->dbr = $dbr; |
22 | } |
23 | |
24 | /** |
25 | * Gets and returns ClientHintsData objects for given |
26 | * reference IDs. |
27 | * |
28 | * @param ClientHintsReferenceIds $referenceIds The reference IDs |
29 | * @return ClientHintsLookupResults The ClientHintsData objects in a helper object. |
30 | */ |
31 | public function getClientHintsByReferenceIds( ClientHintsReferenceIds $referenceIds ): ClientHintsLookupResults { |
32 | $referenceIdsToClientHintIds = $this->prepareFirstResultsArray( $referenceIds ); |
33 | |
34 | // If there are no conditions, then the reference IDs list was empty. |
35 | // Therefore, in this case, return with no data. |
36 | if ( !count( $referenceIdsToClientHintIds ) ) { |
37 | return new ClientHintsLookupResults( [], [] ); |
38 | } |
39 | |
40 | $res = $this->dbr->newSelectQueryBuilder() |
41 | ->field( '*' ) |
42 | ->table( 'cu_useragent_clienthints_map' ) |
43 | // The results list can be used to generate the WHERE condition. |
44 | ->where( $this->dbr->makeWhereFrom2d( |
45 | $referenceIdsToClientHintIds, 'uachm_reference_type', 'uachm_reference_id' |
46 | ) ) |
47 | ->caller( __METHOD__ ) |
48 | ->fetchResultSet(); |
49 | |
50 | // If there are no map rows, then there is no Client Hints data |
51 | // so return early. |
52 | if ( !$res->count() ) { |
53 | return new ClientHintsLookupResults( [], [] ); |
54 | } |
55 | |
56 | // Fill out the results list with the associated uach_id values |
57 | // for each reference ID provided in the first parameter to this method. |
58 | $clientHintIds = []; |
59 | foreach ( $res as $row ) { |
60 | $clientHintIds[] = $row->uachm_uach_id; |
61 | $referenceIdsToClientHintIds[$row->uachm_reference_type][$row->uachm_reference_id][] = $row->uachm_uach_id; |
62 | } |
63 | |
64 | // De-duplicate the $clientHintsIds array to reduce the size of the WHERE condition |
65 | // of the SQL query. |
66 | $clientHintIds = array_unique( $clientHintIds ); |
67 | |
68 | $uniqueClientHintsDataCombinations = $this->generateUniqueClientHintsIdCombinations( |
69 | $referenceIdsToClientHintIds |
70 | ); |
71 | |
72 | // Get all the data for the uach_id values that were collected in the first query. |
73 | $clientHintsRows = $this->dbr->newSelectQueryBuilder() |
74 | ->field( '*' ) |
75 | ->table( 'cu_useragent_clienthints' ) |
76 | ->where( [ 'uach_id' => $clientHintIds ] ) |
77 | ->caller( __METHOD__ ) |
78 | ->fetchResultSet(); |
79 | |
80 | // Make an array of Client Hints data database rows for each uach_id. |
81 | $clientHintsRowsAsArray = []; |
82 | foreach ( $clientHintsRows as $row ) { |
83 | $clientHintsRowsAsArray[$row->uach_id] = [ |
84 | 'uach_name' => $row->uach_name, 'uach_value' => $row->uach_value |
85 | ]; |
86 | } |
87 | |
88 | foreach ( $uniqueClientHintsDataCombinations as &$clientHintsData ) { |
89 | $clientHintRowIdsForReferenceId = array_intersect_key( |
90 | $clientHintsRowsAsArray, |
91 | array_flip( $clientHintsData ) |
92 | ); |
93 | $clientHintsData = ClientHintsData::newFromDatabaseRows( $clientHintRowIdsForReferenceId ); |
94 | } |
95 | |
96 | // Return the results in a result wrapper to make the results easier to use by the callers. |
97 | return new ClientHintsLookupResults( $referenceIdsToClientHintIds, $uniqueClientHintsDataCombinations ); |
98 | } |
99 | |
100 | /** |
101 | * Generates the first results array for use in |
102 | * ::getClientHintsByReferenceIds. |
103 | * |
104 | * @param ClientHintsReferenceIds $referenceIds The reference IDs passed to ::getClientHintsByReferenceIds |
105 | * @return array The first results array which is a two-dimensional map where the first |
106 | * dimension keys is the reference type, the second dimension keys is the reference ID, |
107 | * and the values is initially an empty array. |
108 | */ |
109 | private function prepareFirstResultsArray( ClientHintsReferenceIds $referenceIds ): array { |
110 | $referenceIdsToClientHintIds = []; |
111 | foreach ( $referenceIds->getReferenceIds() as $mappingId => $referenceIdsForMappingId ) { |
112 | if ( !count( $referenceIdsForMappingId ) ) { |
113 | // If there are no reference IDs for this reference type, then just skip to the |
114 | // next reference type. |
115 | continue; |
116 | } |
117 | // Use an empty array at first as the results have not been generated. |
118 | $referenceIdsToClientHintIds[$mappingId] = array_fill_keys( |
119 | $referenceIdsForMappingId, |
120 | [] |
121 | ); |
122 | } |
123 | return $referenceIdsToClientHintIds; |
124 | } |
125 | |
126 | /** |
127 | * Generates an array of unique combinations of uach_ids |
128 | * and updates the first results array provided as the |
129 | * first argument to reference the key associated with |
130 | * the array in the newly generated second results array. |
131 | * |
132 | * @param array &$referenceIdsToClientHintIds The first results list currently under construction |
133 | * @return array The unique combinations of uach_ids as values with integer keys. |
134 | */ |
135 | private function generateUniqueClientHintsIdCombinations( array &$referenceIdsToClientHintIds ): array { |
136 | // Generate an two-dimensional array of all the uach_ids |
137 | // grouped by their reference ID. |
138 | $clientHintIdCombinations = []; |
139 | foreach ( $referenceIdsToClientHintIds as &$referenceIdsForMapId ) { |
140 | foreach ( $referenceIdsForMapId as &$clientHintsIds ) { |
141 | // Sort the array as the ordering of the uach_id values |
142 | // does not affect what ClientHintsData object is produced. |
143 | // Therefore sorting the IDs helps to eliminate duplicates. |
144 | sort( $clientHintsIds, SORT_NUMERIC ); |
145 | // Add this array of uach_ids to the combinations array |
146 | $clientHintIdCombinations[] = $clientHintsIds; |
147 | } |
148 | } |
149 | |
150 | // Make the combinations unique (SORT_REGULAR is used as this is performed on arrays) |
151 | // and get rid of the previous integer keys. |
152 | $clientHintIdCombinations = array_values( array_unique( $clientHintIdCombinations, SORT_REGULAR ) ); |
153 | |
154 | foreach ( $referenceIdsToClientHintIds as &$referenceIdsForMapId ) { |
155 | foreach ( $referenceIdsForMapId as &$clientHintsIds ) { |
156 | // Update the $referenceIdsToClientHintIds array to now reference the integer key |
157 | // for this array of uach_ids in $uniqueClientHintsDataCombinations. |
158 | $clientHintsIds = array_search( $clientHintsIds, $clientHintIdCombinations ); |
159 | } |
160 | } |
161 | |
162 | return $clientHintIdCombinations; |
163 | } |
164 | } |