Merge "output: Narrow Title type hint to LinkTarget"
[lhc/web/wiklou.git] / includes / logging / BlockLogFormatter.php
1 <?php
2 /**
3 * Formatter for block log entries.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @license GPL-2.0-or-later
22 * @since 1.25
23 */
24
25 use MediaWiki\MediaWikiServices;
26
27 /**
28 * This class formats block log entries.
29 *
30 * @since 1.25
31 */
32 class BlockLogFormatter extends LogFormatter {
33 protected function getMessageParameters() {
34 $params = parent::getMessageParameters();
35
36 $title = $this->entry->getTarget();
37 if ( substr( $title->getText(), 0, 1 ) === '#' ) {
38 // autoblock - no user link possible
39 $params[2] = $title->getText();
40 $params[3] = ''; // no user name for gender use
41 } else {
42 // Create a user link for the blocked
43 $username = $title->getText();
44 // @todo Store the user identifier in the parameters
45 // to make this faster for future log entries
46 $targetUser = User::newFromName( $username, false );
47 $params[2] = Message::rawParam( $this->makeUserLink( $targetUser, Linker::TOOL_LINKS_NOBLOCK ) );
48 $params[3] = $username; // plain user name for gender use
49 }
50
51 $subtype = $this->entry->getSubtype();
52 if ( $subtype === 'block' || $subtype === 'reblock' ) {
53 if ( !isset( $params[4] ) ) {
54 // Very old log entry without duration: means infinite
55 $params[4] = 'infinite';
56 }
57 // Localize the duration, and add a tooltip
58 // in English to help visitors from other wikis.
59 // The lrm is needed to make sure that the number
60 // is shown on the correct side of the tooltip text.
61 $durationTooltip = '&lrm;' . htmlspecialchars( $params[4] );
62 $blockExpiry = $this->context->getLanguage()->translateBlockExpiry(
63 $params[4],
64 $this->context->getUser(),
65 wfTimestamp( TS_UNIX, $this->entry->getTimestamp() )
66 );
67 if ( $this->plaintext ) {
68 $params[4] = Message::rawParam( $blockExpiry );
69 } else {
70 $params[4] = Message::rawParam(
71 "<span class=\"blockExpiry\" title=\"$durationTooltip\">" .
72 $blockExpiry .
73 '</span>'
74 );
75 }
76 $params[5] = isset( $params[5] ) ?
77 self::formatBlockFlags( $params[5], $this->context->getLanguage() ) : '';
78
79 // block restrictions
80 if ( isset( $params[6] ) ) {
81 $pages = $params[6]['pages'] ?? [];
82 $pages = array_map( function ( $page ) {
83 return $this->makePageLink( Title::newFromText( $page ) );
84 }, $pages );
85
86 $namespaces = $params[6]['namespaces'] ?? [];
87 $namespaces = array_map( function ( $ns ) {
88 $text = (int)$ns === NS_MAIN
89 ? $this->msg( 'blanknamespace' )->text()
90 : $this->context->getLanguage()->getFormattedNsText( $ns );
91 $params = [ 'namespace' => $ns ];
92
93 return $this->makePageLink( SpecialPage::getTitleFor( 'Allpages' ), $params, $text );
94 }, $namespaces );
95
96 $restrictions = [];
97 if ( $pages ) {
98 $restrictions[] = $this->msg( 'logentry-partialblock-block-page' )
99 ->numParams( count( $pages ) )
100 ->rawParams( $this->context->getLanguage()->listToText( $pages ) )->text();
101 }
102
103 if ( $namespaces ) {
104 $restrictions[] = $this->msg( 'logentry-partialblock-block-ns' )
105 ->numParams( count( $namespaces ) )
106 ->rawParams( $this->context->getLanguage()->listToText( $namespaces ) )->text();
107 }
108
109 $params[6] = Message::rawParam( $this->context->getLanguage()->listToText( $restrictions ) );
110 }
111 }
112
113 return $params;
114 }
115
116 protected function extractParameters() {
117 $params = parent::extractParameters();
118 // Legacy log params returning the params in index 3 and 4, moved to 4 and 5
119 if ( $this->entry->isLegacy() && isset( $params[3] ) ) {
120 if ( isset( $params[4] ) ) {
121 $params[5] = $params[4];
122 }
123 $params[4] = $params[3];
124 $params[3] = '';
125 }
126 return $params;
127 }
128
129 public function getPreloadTitles() {
130 $title = $this->entry->getTarget();
131 // Preload user page for non-autoblocks
132 if ( substr( $title->getText(), 0, 1 ) !== '#' && $title->isValid() ) {
133 return [ $title->getTalkPage() ];
134 }
135 return [];
136 }
137
138 public function getActionLinks() {
139 $subtype = $this->entry->getSubtype();
140 $linkRenderer = $this->getLinkRenderer();
141 if ( $this->entry->isDeleted( LogPage::DELETED_ACTION ) // Action is hidden
142 || !( $subtype === 'block' || $subtype === 'reblock' )
143 || !MediaWikiServices::getInstance()
144 ->getPermissionManager()
145 ->userHasRight( $this->context->getUser(), 'block' )
146 ) {
147 return '';
148 }
149
150 // Show unblock/change block link
151 $title = $this->entry->getTarget();
152 $links = [
153 $linkRenderer->makeKnownLink(
154 SpecialPage::getTitleFor( 'Unblock', $title->getDBkey() ),
155 $this->msg( 'unblocklink' )->text()
156 ),
157 $linkRenderer->makeKnownLink(
158 SpecialPage::getTitleFor( 'Block', $title->getDBkey() ),
159 $this->msg( 'change-blocklink' )->text()
160 )
161 ];
162
163 return $this->msg( 'parentheses' )->rawParams(
164 $this->context->getLanguage()->pipeList( $links ) )->escaped();
165 }
166
167 /**
168 * Convert a comma-delimited list of block log flags
169 * into a more readable (and translated) form
170 *
171 * @param string $flags Flags to format
172 * @param Language $lang
173 * @return string
174 */
175 public static function formatBlockFlags( $flags, Language $lang ) {
176 $flags = trim( $flags );
177 if ( $flags === '' ) {
178 return ''; // nothing to do
179 }
180 $flags = explode( ',', $flags );
181 $flagsCount = count( $flags );
182
183 for ( $i = 0; $i < $flagsCount; $i++ ) {
184 $flags[$i] = self::formatBlockFlag( $flags[$i], $lang );
185 }
186
187 return wfMessage( 'parentheses' )->inLanguage( $lang )
188 ->rawParams( $lang->commaList( $flags ) )->escaped();
189 }
190
191 /**
192 * Translate a block log flag if possible
193 *
194 * @param int $flag Flag to translate
195 * @param Language $lang Language object to use
196 * @return string
197 */
198 public static function formatBlockFlag( $flag, Language $lang ) {
199 static $messages = [];
200
201 if ( !isset( $messages[$flag] ) ) {
202 $messages[$flag] = htmlspecialchars( $flag ); // Fallback
203
204 // For grepping. The following core messages can be used here:
205 // * block-log-flags-angry-autoblock
206 // * block-log-flags-anononly
207 // * block-log-flags-hiddenname
208 // * block-log-flags-noautoblock
209 // * block-log-flags-nocreate
210 // * block-log-flags-noemail
211 // * block-log-flags-nousertalk
212 $msg = wfMessage( 'block-log-flags-' . $flag )->inLanguage( $lang );
213
214 if ( $msg->exists() ) {
215 $messages[$flag] = $msg->escaped();
216 }
217 }
218
219 return $messages[$flag];
220 }
221
222 protected function getParametersForApi() {
223 $entry = $this->entry;
224 $params = $entry->getParameters();
225
226 static $map = [
227 // While this looks wrong to be starting at 5 rather than 4, it's
228 // because getMessageParameters uses $4 for its own purposes.
229 '5::duration',
230 '6:array:flags',
231 '6::flags' => '6:array:flags',
232 ];
233
234 foreach ( $map as $index => $key ) {
235 if ( isset( $params[$index] ) ) {
236 $params[$key] = $params[$index];
237 unset( $params[$index] );
238 }
239 }
240
241 ksort( $params );
242
243 $subtype = $entry->getSubtype();
244 if ( $subtype === 'block' || $subtype === 'reblock' ) {
245 // Defaults for old log entries missing some fields
246 $params += [
247 '5::duration' => 'infinite',
248 '6:array:flags' => [],
249 ];
250
251 if ( !is_array( $params['6:array:flags'] ) ) {
252 $params['6:array:flags'] = $params['6:array:flags'] === ''
253 ? []
254 : explode( ',', $params['6:array:flags'] );
255 }
256
257 if ( !wfIsInfinity( $params['5::duration'] ) ) {
258 $ts = wfTimestamp( TS_UNIX, $entry->getTimestamp() );
259 $expiry = strtotime( $params['5::duration'], $ts );
260 if ( $expiry !== false && $expiry > 0 ) {
261 $params[':timestamp:expiry'] = $expiry;
262 }
263 }
264 }
265
266 return $params;
267 }
268
269 /**
270 * @inheritDoc
271 * @suppress PhanTypeInvalidDimOffset
272 */
273 public function formatParametersForApi() {
274 $ret = parent::formatParametersForApi();
275 if ( isset( $ret['flags'] ) ) {
276 ApiResult::setIndexedTagName( $ret['flags'], 'f' );
277 }
278
279 if ( isset( $ret['restrictions']['pages'] ) ) {
280 $ret['restrictions']['pages'] = array_map( function ( $title ) {
281 return $this->formatParameterValueForApi( 'page', 'title-link', $title );
282 }, $ret['restrictions']['pages'] );
283 ApiResult::setIndexedTagName( $ret['restrictions']['pages'], 'p' );
284 }
285
286 if ( isset( $ret['restrictions']['namespaces'] ) ) {
287 ApiResult::setIndexedTagName( $ret['restrictions']['namespaces'], 'ns' );
288 }
289
290 return $ret;
291 }
292
293 protected function getMessageKey() {
294 $type = $this->entry->getType();
295 $subtype = $this->entry->getSubtype();
296 $sitewide = $this->entry->getParameters()['sitewide'] ?? true;
297
298 $key = "logentry-$type-$subtype";
299 if ( ( $subtype === 'block' || $subtype === 'reblock' ) && !$sitewide ) {
300 // $this->getMessageParameters is doing too much. We just need
301 // to check the presence of restrictions ($param[6]) and calling
302 // on parent gives us that
303 $params = parent::getMessageParameters();
304
305 // message changes depending on whether there are editing restrictions or not
306 if ( isset( $params[6] ) ) {
307 $key = "logentry-partial$type-$subtype";
308 } else {
309 $key = "logentry-non-editing-$type-$subtype";
310 }
311 }
312
313 return $key;
314 }
315 }