Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 52 |
|
0.00% |
0 / 7 |
CRAP | |
0.00% |
0 / 1 |
SeenTime | |
0.00% |
0 / 52 |
|
0.00% |
0 / 7 |
380 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
newFromUser | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
cache | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
12 | |||
getTime | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
42 | |||
setTime | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 | |||
validateType | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getMemcKey | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\Notifications; |
4 | |
5 | use BagOStuff; |
6 | use CachedBagOStuff; |
7 | use MediaWiki\Deferred\DeferredUpdates; |
8 | use MediaWiki\MediaWikiServices; |
9 | use MediaWiki\User\CentralId\CentralIdLookup; |
10 | use MediaWiki\User\User; |
11 | use ObjectCache; |
12 | use UnexpectedValueException; |
13 | |
14 | /** |
15 | * A small wrapper around ObjectCache to manage |
16 | * storing the last time a user has seen notifications |
17 | */ |
18 | class SeenTime { |
19 | |
20 | /** |
21 | * Allowed notification types |
22 | * @var string[] |
23 | */ |
24 | private static $allowedTypes = [ 'alert', 'message' ]; |
25 | |
26 | /** |
27 | * @var User |
28 | */ |
29 | private $user; |
30 | |
31 | /** |
32 | * @param User $user A logged in user |
33 | */ |
34 | private function __construct( User $user ) { |
35 | $this->user = $user; |
36 | } |
37 | |
38 | /** |
39 | * @param User $user |
40 | * @return SeenTime |
41 | */ |
42 | public static function newFromUser( User $user ) { |
43 | return new self( $user ); |
44 | } |
45 | |
46 | /** |
47 | * Hold onto a cache for our operations. Static so it can reuse the same |
48 | * in-process cache in different instances. |
49 | * |
50 | * @return BagOStuff |
51 | */ |
52 | private static function cache() { |
53 | static $wrappedCache = null; |
54 | |
55 | // Use a configurable cache backend (T222851) and wrap it with CachedBagOStuff |
56 | // for an in-process cache (T144534) |
57 | if ( $wrappedCache === null ) { |
58 | $cacheConfig = MediaWikiServices::getInstance()->getMainConfig()->get( 'EchoSeenTimeCacheType' ); |
59 | if ( $cacheConfig === null ) { |
60 | // Hooks::initEchoExtension sets EchoSeenTimeCacheType to $wgMainStash if it's |
61 | // null, so this can only happen if $wgMainStash is also null |
62 | throw new UnexpectedValueException( |
63 | 'Either $wgEchoSeenTimeCacheType or $wgMainStash must be set' |
64 | ); |
65 | } |
66 | return new CachedBagOStuff( ObjectCache::getInstance( $cacheConfig ) ); |
67 | } |
68 | |
69 | return $wrappedCache; |
70 | } |
71 | |
72 | /** |
73 | * @param string $type Type of seen time to get |
74 | * @param int $format Format to return time in, defaults to TS_MW |
75 | * @return string|false Timestamp in specified format, or false if no stored time |
76 | */ |
77 | public function getTime( $type = 'all', $format = TS_MW ) { |
78 | $vals = []; |
79 | if ( $type === 'all' ) { |
80 | foreach ( self::$allowedTypes as $allowed ) { |
81 | // Use TS_MW, then convert later, so max works properly for |
82 | // all formats. |
83 | $vals[] = $this->getTime( $allowed, TS_MW ); |
84 | } |
85 | |
86 | return wfTimestamp( $format, min( $vals ) ); |
87 | } |
88 | |
89 | if ( !$this->validateType( $type ) ) { |
90 | return false; |
91 | } |
92 | |
93 | $data = self::cache()->get( $this->getMemcKey( $type ) ); |
94 | |
95 | if ( $data === false ) { |
96 | $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup(); |
97 | // Check if the user still has it set in their preferences |
98 | $data = $userOptionsLookup->getOption( $this->user, 'echo-seen-time', false ); |
99 | } |
100 | |
101 | if ( $data === false ) { |
102 | // We can't remember their real seen time, so reset everything to |
103 | // unseen. |
104 | $data = wfTimestamp( TS_MW, 1 ); |
105 | } |
106 | return wfTimestamp( $format, $data ); |
107 | } |
108 | |
109 | /** |
110 | * Sets the seen time |
111 | * |
112 | * @param string $time Time, in TS_MW format |
113 | * @param string $type Type of seen time to set |
114 | */ |
115 | public function setTime( $time, $type = 'all' ) { |
116 | if ( $type === 'all' ) { |
117 | foreach ( self::$allowedTypes as $allowed ) { |
118 | $this->setTime( $time, $allowed ); |
119 | } |
120 | return; |
121 | } |
122 | |
123 | if ( !$this->validateType( $type ) ) { |
124 | return; |
125 | } |
126 | |
127 | // Write to the in-memory cache immediately, and defer writing to |
128 | // the real cache |
129 | $key = $this->getMemcKey( $type ); |
130 | $cache = self::cache(); |
131 | $cache->set( $key, $time, $cache::TTL_YEAR, BagOStuff::WRITE_CACHE_ONLY ); |
132 | DeferredUpdates::addCallableUpdate( static function () use ( $key, $time, $cache ) { |
133 | $cache->set( $key, $time, $cache::TTL_YEAR ); |
134 | } ); |
135 | } |
136 | |
137 | /** |
138 | * Validate the given type, make sure it is allowed. |
139 | * |
140 | * @param string $type Given type |
141 | * @return bool Type is allowed |
142 | */ |
143 | private function validateType( $type ) { |
144 | return in_array( $type, self::$allowedTypes ); |
145 | } |
146 | |
147 | /** |
148 | * Build a memcached key. |
149 | * |
150 | * @param string $type Given notification type |
151 | * @return string Memcached key |
152 | */ |
153 | protected function getMemcKey( $type = 'all' ) { |
154 | $localKey = self::cache()->makeKey( |
155 | 'echo', 'seen', $type, 'time', $this->user->getId() |
156 | ); |
157 | $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup(); |
158 | |
159 | if ( !$userOptionsLookup->getOption( $this->user, 'echo-cross-wiki-notifications' ) ) { |
160 | return $localKey; |
161 | } |
162 | |
163 | $globalId = MediaWikiServices::getInstance() |
164 | ->getCentralIdLookup() |
165 | ->centralIdFromLocalUser( $this->user, CentralIdLookup::AUDIENCE_RAW ); |
166 | |
167 | if ( !$globalId ) { |
168 | return $localKey; |
169 | } |
170 | |
171 | return self::cache()->makeGlobalKey( |
172 | 'echo', 'seen', $type, 'time', $globalId |
173 | ); |
174 | } |
175 | } |