Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
47.46% |
28 / 59 |
|
20.00% |
1 / 5 |
CRAP | |
0.00% |
0 / 1 |
GlobalUserOptionsStore | |
47.46% |
28 / 59 |
|
20.00% |
1 / 5 |
42.43 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
fetch | |
71.43% |
5 / 7 |
|
0.00% |
0 / 1 |
3.21 | |||
fetchBatchForUserNames | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
12 | |||
store | |
68.18% |
15 / 22 |
|
0.00% |
0 / 1 |
5.81 | |||
getStorage | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
2.03 |
1 | <?php |
2 | |
3 | namespace GlobalPreferences; |
4 | |
5 | use GlobalPreferences\Services\GlobalPreferencesConnectionProvider; |
6 | use GlobalPreferences\Services\GlobalPreferencesHookRunner; |
7 | use MediaWiki\Logger\LoggerFactory; |
8 | use MediaWiki\User\CentralId\CentralIdLookup; |
9 | use MediaWiki\User\Options\UserOptionsStore; |
10 | use MediaWiki\User\UserIdentity; |
11 | use Psr\Log\LoggerInterface; |
12 | use Wikimedia\Rdbms\DBAccessObjectUtils; |
13 | use Wikimedia\Rdbms\IDBAccessObject; |
14 | |
15 | /** |
16 | * An interface which allows core to update pre-existing global preferences |
17 | */ |
18 | class GlobalUserOptionsStore implements UserOptionsStore { |
19 | |
20 | private CentralIdLookup $centralIdLookup; |
21 | private LoggerInterface $logger; |
22 | private GlobalPreferencesConnectionProvider $globalDbProvider; |
23 | private GlobalPreferencesHookRunner $globalPreferencesHookRunner; |
24 | |
25 | public function __construct( |
26 | CentralIdLookup $centralIdLookup, |
27 | GlobalPreferencesConnectionProvider $globalDbProvider, |
28 | GlobalPreferencesHookRunner $globalPreferencesHookRunner |
29 | ) { |
30 | $this->centralIdLookup = $centralIdLookup; |
31 | $this->globalDbProvider = $globalDbProvider; |
32 | $this->globalPreferencesHookRunner = $globalPreferencesHookRunner; |
33 | $this->logger = LoggerFactory::getInstance( 'preferences' ); |
34 | } |
35 | |
36 | /** |
37 | * @param UserIdentity $user |
38 | * @param int $recency |
39 | * @return array|string[] |
40 | */ |
41 | public function fetch( UserIdentity $user, int $recency ) { |
42 | $storage = $this->getStorage( $user ); |
43 | if ( !$storage ) { |
44 | return []; |
45 | } |
46 | if ( DBAccessObjectUtils::hasFlags( $recency, IDBAccessObject::READ_LATEST ) ) { |
47 | $dbType = DB_PRIMARY; |
48 | } else { |
49 | $dbType = DB_REPLICA; |
50 | } |
51 | return $storage->loadFromDB( $dbType, $recency ); |
52 | } |
53 | |
54 | /** |
55 | * @param array $keys |
56 | * @param array $userNames |
57 | * @return array |
58 | */ |
59 | public function fetchBatchForUserNames( array $keys, array $userNames ) { |
60 | $idsByName = $this->centralIdLookup->lookupOwnedUserNames( |
61 | array_fill_keys( $userNames, 0 ) ); |
62 | $idsByName = array_filter( $idsByName ); |
63 | if ( !$idsByName ) { |
64 | return []; |
65 | } |
66 | $res = $this->globalDbProvider->getReplicaDatabase() |
67 | ->newSelectQueryBuilder() |
68 | ->select( [ 'gp_user', 'gp_property', 'gp_value' ] ) |
69 | ->from( 'global_preferences' ) |
70 | ->where( [ |
71 | 'gp_user' => array_values( $idsByName ), |
72 | 'gp_property' => $keys, |
73 | ] ) |
74 | ->caller( __METHOD__ ) |
75 | ->fetchResultSet(); |
76 | |
77 | $namesById = array_flip( $idsByName ); |
78 | $options = []; |
79 | foreach ( $res as $row ) { |
80 | $name = $namesById[$row->gp_user]; |
81 | $options[$row->gp_property][$name] = (string)$row->gp_value; |
82 | } |
83 | return $options; |
84 | } |
85 | |
86 | /** |
87 | * @param UserIdentity $user |
88 | * @param array $updates |
89 | * @return bool |
90 | */ |
91 | public function store( UserIdentity $user, array $updates ) { |
92 | $storage = $this->getStorage( $user ); |
93 | if ( !$storage ) { |
94 | $this->logger->warning( |
95 | 'Unable to store preference for non-global user "{userName}"', |
96 | [ 'userName' => $user->getName() ] |
97 | ); |
98 | return false; |
99 | } |
100 | $oldPreferences = $storage->load(); |
101 | $newPreferences = $oldPreferences; |
102 | |
103 | $replacements = []; |
104 | $deletions = []; |
105 | foreach ( $updates as $key => $value ) { |
106 | if ( $value === null ) { |
107 | $deletions[] = $key; |
108 | unset( $newPreferences[$key] ); |
109 | } else { |
110 | $replacements[$key] = $value; |
111 | $newPreferences[$key] = $value; |
112 | } |
113 | } |
114 | |
115 | $storage->replaceAndDelete( $replacements, $deletions ); |
116 | |
117 | // Run the set preferences hook here because global preferences may be set by the local options API |
118 | // and we still want hook handlers to know about this. |
119 | $this->globalPreferencesHookRunner->onGlobalPreferencesSetGlobalPreferences( |
120 | $user, $oldPreferences, $newPreferences |
121 | ); |
122 | |
123 | return $replacements || $deletions; |
124 | } |
125 | |
126 | private function getStorage( UserIdentity $user ): ?Storage { |
127 | // Avoid CentralIdLookup::isOwned() since it has a slow worst case |
128 | $userName = $user->getName(); |
129 | $id = $this->centralIdLookup->lookupOwnedUserNames( [ $userName => 0 ] )[ $userName ]; |
130 | if ( !$id ) { |
131 | return null; |
132 | } |
133 | return new Storage( $id ); |
134 | } |
135 | } |