1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
|
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
* @ingroup Watchlist
*/
use MediaWiki\Linker\LinkTarget;
use MediaWiki\Page\PageIdentity;
use MediaWiki\User\UserIdentity;
use Wikimedia\ParamValidator\TypeDef\ExpiryDef;
use Wikimedia\Timestamp\ConvertibleTimestamp;
/**
* Representation of a pair of user and title for watchlist entries.
*
* @author Tim Starling
* @author Addshore
*
* @ingroup Watchlist
*/
class WatchedItem {
/**
* @var LinkTarget|PageIdentity deprecated LinkTarget since 1.36
*/
private $target;
/**
* @var UserIdentity
*/
private $user;
/**
* @var bool|null|string the value of the wl_notificationtimestamp field
*/
private $notificationTimestamp;
/**
* @var ConvertibleTimestamp|null value that determines when a watched item will expire.
* 'null' means that there is no expiration.
*/
private $expiry;
/**
* Used to calculate how many days are remaining until a watched item will expire.
* Uses a different algorithm from Language::getDurationIntervals for calculating
* days remaining in an interval of time
*
* @since 1.35
*/
private const SECONDS_IN_A_DAY = 86400;
/**
* @param UserIdentity $user
* @param LinkTarget|PageIdentity $target deprecated passing LinkTarget since 1.36
* @param bool|null|string $notificationTimestamp the value of the wl_notificationtimestamp field
* @param null|string $expiry Optional expiry timestamp in any format acceptable to wfTimestamp()
*/
public function __construct(
UserIdentity $user,
$target,
$notificationTimestamp,
?string $expiry = null
) {
$this->user = $user;
$this->target = $target;
$this->notificationTimestamp = $notificationTimestamp;
// Expiry will be saved in ConvertibleTimestamp
$this->expiry = ExpiryDef::normalizeExpiry( $expiry );
// If the normalization returned 'infinity' then set it as null since they are synonymous
if ( $this->expiry === 'infinity' ) {
$this->expiry = null;
}
}
/**
* @since 1.35
* @param RecentChange $recentChange
* @param UserIdentity $user
* @return WatchedItem
*/
public static function newFromRecentChange( RecentChange $recentChange, UserIdentity $user ) {
return new self(
$user,
$recentChange->getTitle(),
$recentChange->notificationtimestamp,
$recentChange->watchlistExpiry
);
}
/**
* @return UserIdentity
*/
public function getUserIdentity() {
return $this->user;
}
/**
* @return LinkTarget
* @deprecated since 1.36, use getTarget() instead
*/
public function getLinkTarget() {
if ( !$this->target instanceof LinkTarget ) {
return TitleValue::newFromPage( $this->target );
}
return $this->getTarget();
}
/**
* @return LinkTarget|PageIdentity deprecated returning LinkTarget since 1.36
* @since 1.36
*/
public function getTarget() {
return $this->target;
}
/**
* Get the notification timestamp of this entry.
*
* @return bool|null|string
*/
public function getNotificationTimestamp() {
return $this->notificationTimestamp;
}
/**
* When the watched item will expire.
*
* @since 1.35
* @param int|null $style Given timestamp format to style the ConvertibleTimestamp
* @return string|null null or in a format acceptable to ConvertibleTimestamp (TS_* constants).
* Default is TS_MW format.
*/
public function getExpiry( ?int $style = TS_MW ) {
return $this->expiry instanceof ConvertibleTimestamp
? $this->expiry->getTimestamp( $style )
: $this->expiry;
}
/**
* Has the watched item expired?
*
* @since 1.35
*
* @return bool
*/
public function isExpired(): bool {
$expiry = $this->getExpiry();
if ( $expiry === null ) {
return false;
}
return $expiry < ConvertibleTimestamp::now();
}
/**
* Get days remaining until a watched item expires.
*
* @since 1.35
*
* @return int|null days remaining or null if no expiration is present
*/
public function getExpiryInDays(): ?int {
return self::calculateExpiryInDays( $this->getExpiry() );
}
/**
* Get the number of days remaining until the given expiry time.
*
* @since 1.35
*
* @param string|null $expiry The expiry to calculate from, in any format
* supported by MWTimestamp::convert().
*
* @return int|null The remaining number of days or null if $expiry is null.
*/
public static function calculateExpiryInDays( ?string $expiry ): ?int {
if ( $expiry === null ) {
return null;
}
$unixTimeExpiry = (int)MWTimestamp::convert( TS_UNIX, $expiry );
$diffInSeconds = $unixTimeExpiry - (int)wfTimestamp( TS_UNIX );
$diffInDays = $diffInSeconds / self::SECONDS_IN_A_DAY;
if ( $diffInDays < 1 ) {
return 0;
}
return (int)ceil( $diffInDays );
}
/**
* Get days remaining until a watched item expires as a text.
*
* @since 1.35
* @param MessageLocalizer $msgLocalizer
* @param bool $isDropdownOption Whether the text is being displayed as a dropdown option.
* The text is different as a dropdown option from when it is used in other
* places as a watchlist indicator.
* @return string days remaining text and '' if no expiration is present
*/
public function getExpiryInDaysText( MessageLocalizer $msgLocalizer, $isDropdownOption = false ): string {
$expiryInDays = $this->getExpiryInDays();
if ( $expiryInDays === null ) {
return '';
}
if ( $expiryInDays < 1 ) {
if ( $isDropdownOption ) {
return $msgLocalizer->msg( 'watchlist-expiry-hours-left' )->text();
}
return $msgLocalizer->msg( 'watchlist-expiring-hours-full-text' )->text();
}
if ( $isDropdownOption ) {
return $msgLocalizer->msg( 'watchlist-expiry-days-left', [ $expiryInDays ] )->text();
}
return $msgLocalizer->msg( 'watchlist-expiring-days-full-text', [ $expiryInDays ] )->text();
}
}
|