Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 100 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
MobileSpecialPageFeed | |
0.00% |
0 / 100 |
|
0.00% |
0 / 6 |
1122 | |
0.00% |
0 / 1 |
execute | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
formatComment | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
renderListHeaderWhereNeeded | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
20 | |||
getRevisionCommentHTML | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
30 | |||
getUsernameText | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
42 | |||
renderFeedItemHtml | |
0.00% |
0 / 50 |
|
0.00% |
0 / 1 |
240 |
1 | <?php |
2 | |
3 | use MediaWiki\Html\Html; |
4 | use MediaWiki\Parser\Sanitizer; |
5 | use MediaWiki\Permissions\Authority; |
6 | use MediaWiki\Revision\RevisionRecord; |
7 | use MediaWiki\Title\Title; |
8 | use Wikimedia\IPUtils; |
9 | |
10 | /** |
11 | * This is an abstract class intended for use by special pages that consist primarily of |
12 | * a list of pages, for example, Special:Watchlist or Special:History. |
13 | */ |
14 | abstract class MobileSpecialPageFeed extends MobileSpecialPage { |
15 | /** @var bool Whether to show the username in results or not */ |
16 | protected $showUsername = true; |
17 | /** @var string */ |
18 | protected $lastDate; |
19 | /** @var Title|null */ |
20 | protected $title; |
21 | |
22 | /** |
23 | * Render the special page content |
24 | * @param string|null $par parameters submitted as subpage |
25 | */ |
26 | public function execute( $par ) { |
27 | $out = $this->getOutput(); |
28 | $out->addModuleStyles( [ |
29 | // FIXME: This module should be removed when the following tickets are resolved: |
30 | // * T305113 |
31 | // * T109277 |
32 | // * T117279 |
33 | 'mobile.special.pagefeed.styles', |
34 | ] ); |
35 | $this->setHeaders(); |
36 | parent::execute( $par ); |
37 | } |
38 | |
39 | /** |
40 | * Formats an edit comment |
41 | * @param string $comment The raw comment text |
42 | * @param Title $title The title of the page that was edited |
43 | * |
44 | * @return string HTML code |
45 | */ |
46 | protected function formatComment( $comment, $title ) { |
47 | if ( $comment === '' ) { |
48 | $comment = $this->msg( 'mobile-frontend-changeslist-nocomment' )->plain(); |
49 | } else { |
50 | $comment = $this->commentFormatter->format( $comment, $title ); |
51 | // flatten back to text |
52 | $comment = htmlspecialchars( Sanitizer::stripAllTags( $comment ) ); |
53 | } |
54 | return $comment; |
55 | } |
56 | |
57 | /** |
58 | * Renders a date header when necessary. |
59 | * FIXME: Juliusz won't like this function. |
60 | * @param string $date The date of the current item |
61 | */ |
62 | protected function renderListHeaderWhereNeeded( $date ) { |
63 | if ( !isset( $this->lastDate ) || $date !== $this->lastDate ) { |
64 | $output = $this->getOutput(); |
65 | if ( isset( $this->lastDate ) ) { |
66 | $output->addHTML( |
67 | Html::closeElement( 'ul' ) |
68 | ); |
69 | } |
70 | $output->addHTML( |
71 | Html::element( 'h2', [ 'class' => 'list-header' ], $date ) . |
72 | Html::openElement( 'ul', [ |
73 | // TODO remove page-list after initial release T337741 |
74 | 'class' => 'mw-mf-page-list diff-summary-list side-list' |
75 | ] |
76 | ) |
77 | ); |
78 | } |
79 | $this->lastDate = $date; |
80 | } |
81 | |
82 | /** |
83 | * Generates revision text based on user's rights and preference |
84 | * @param RevisionRecord $rev |
85 | * @param Authority $user viewing the revision |
86 | * @param bool $unhide whether the user wants to see hidden comments |
87 | * if the user doesn't have permission, comment will display as rev-deleted-comment |
88 | * @return string plain text label |
89 | */ |
90 | protected function getRevisionCommentHTML( RevisionRecord $rev, $user, $unhide ) { |
91 | if ( RevisionRecord::userCanBitfield( |
92 | $rev->getVisibility(), |
93 | RevisionRecord::DELETED_COMMENT, |
94 | $user |
95 | ) ) { |
96 | if ( $rev->isDeleted( RevisionRecord::DELETED_COMMENT ) && !$unhide ) { |
97 | $comment = $this->msg( 'rev-deleted-comment' )->escaped(); |
98 | } else { |
99 | $commentObj = $rev->getComment( RevisionRecord::FOR_THIS_USER, $user ); |
100 | $commentText = $commentObj ? $commentObj->text : ''; |
101 | |
102 | // escape any HTML in summary and add CSS for any auto-generated comments |
103 | $comment = $this->formatComment( $commentText, $this->title ); |
104 | } |
105 | } else { |
106 | // Confusingly "Revision::userCan" Determines if the current user is |
107 | // allowed to view a particular field of this revision, /if/ it's marked as |
108 | // deleted. This will only get executed in event a comment has been deleted |
109 | // and user cannot view it. |
110 | $comment = $this->msg( 'rev-deleted-comment' )->escaped(); |
111 | } |
112 | return $comment; |
113 | } |
114 | |
115 | /** |
116 | * Generates username text based on user's rights and preference |
117 | * @param RevisionRecord $rev |
118 | * @param Authority $user viewing the revision |
119 | * @param bool $unhide whether the user wants to see hidden usernames |
120 | * @return string plain text label |
121 | */ |
122 | protected function getUsernameText( $rev, $user, $unhide ) { |
123 | $revUser = $rev->getUser( RevisionRecord::FOR_THIS_USER, $user ); |
124 | if ( $revUser && $revUser->isRegistered() ) { |
125 | $username = $revUser->getName(); |
126 | } else { |
127 | $revUser = $rev->getUser( RevisionRecord::RAW ); |
128 | $username = IPUtils::prettifyIP( $revUser->getName() ) ?? $revUser->getName(); |
129 | } |
130 | if ( |
131 | !RevisionRecord::userCanBitfield( |
132 | $rev->getVisibility(), |
133 | RevisionRecord::DELETED_USER, |
134 | $user |
135 | ) || |
136 | ( $rev->isDeleted( RevisionRecord::DELETED_USER ) && !$unhide ) |
137 | ) { |
138 | $username = $this->msg( 'rev-deleted-user' )->text(); |
139 | } |
140 | return $username; |
141 | } |
142 | |
143 | /** |
144 | * Renders an item in the feed |
145 | * |
146 | * @param array $options An array of various options for |
147 | * rendering the feed item's HTML e.g. |
148 | * |
149 | * [ |
150 | * 'ts' => MWTimestamp - The time the edit occurred |
151 | * 'diffLink' => string - The URL to the diff for the edit |
152 | * 'username' => string - The username of the user that made |
153 | * the edit (absent if anonymous) |
154 | * 'comment' => string - The edit summary, HTML escaped |
155 | * 'title' => Title|null - The title of the page that was edited |
156 | * 'isAnon' => bool - Is the edit anonymous? |
157 | * 'bytes' => int|null - Net number of bytes changed or null |
158 | * if not applicable |
159 | * 'isMinor' => bool - Is the edit minor? |
160 | * ]; |
161 | * |
162 | */ |
163 | protected function renderFeedItemHtml( array $options ): void { |
164 | $output = $this->getOutput(); |
165 | $user = $this->getUser(); |
166 | $lang = $this->getLanguage(); |
167 | |
168 | if ( (bool)( $options['isAnon'] ?? false ) ) { |
169 | $usernameClass = 'mw-mf-user mw-mf-anon'; |
170 | $iconHTML = MobileUI::icon( 'userAnonymous' ); |
171 | } else { |
172 | $usernameClass = 'mw-mf-user'; |
173 | $iconHTML = MobileUI::icon( 'userAvatar' ); |
174 | } |
175 | |
176 | // Add whitespace between icon and label. |
177 | $iconHTML .= ' '; |
178 | |
179 | $html = Html::openElement( 'li', [ 'class' => 'page-summary' ] ); |
180 | |
181 | if ( isset( $options['diffLink'] ) && $options['diffLink'] ) { |
182 | $html .= Html::openElement( 'a', [ 'href' => $options['diffLink'], 'class' => 'title' ] ); |
183 | } else { |
184 | $html .= Html::openElement( 'div', [ 'class' => 'title' ] ); |
185 | } |
186 | |
187 | if ( isset( $options['title'] ) && $options['title'] ) { |
188 | $html .= Html::element( 'h3', [], $options['title']->getPrefixedText() ); |
189 | } |
190 | |
191 | if ( isset( $options['username'] ) && $options['username'] && $this->showUsername ) { |
192 | $html .= Html::rawElement( 'p', [ 'class' => $usernameClass ], |
193 | $iconHTML . ' ' . |
194 | Html::element( 'span', [], $options['username'] ) |
195 | ); |
196 | } |
197 | |
198 | $html .= Html::rawElement( |
199 | 'p', |
200 | [ 'class' => 'edit-summary component truncated-text multi-line two-line' ], |
201 | $options['comment'] ?? '' |
202 | ); |
203 | |
204 | if ( (bool)( $options['isMinor'] ?? false ) ) { |
205 | $html .= ChangesList::flag( 'minor' ); |
206 | } |
207 | |
208 | $html .= Html::openElement( 'div', [ 'class' => 'list-thumb' ] ) . |
209 | Html::element( 'p', [ 'class' => 'timestamp' ], $lang->userTime( $options['ts'], $user ) ); |
210 | |
211 | if ( isset( $options['bytes'] ) && $options['bytes'] ) { |
212 | $bytes = $options['bytes']; |
213 | $formattedBytes = $lang->formatNum( $bytes ); |
214 | if ( $bytes > 0 ) { |
215 | $formattedBytes = '+' . $formattedBytes; |
216 | $bytesClass = 'mw-mf-bytesadded'; |
217 | } else { |
218 | $bytesClass = 'mw-mf-bytesremoved'; |
219 | } |
220 | $html .= Html::element( |
221 | 'p', |
222 | [ |
223 | 'class' => $bytesClass, |
224 | 'dir' => 'ltr', |
225 | ], |
226 | $formattedBytes |
227 | ); |
228 | } |
229 | |
230 | $html .= Html::closeElement( 'div' ); |
231 | |
232 | if ( isset( $options['diffLink'] ) && $options['diffLink'] ) { |
233 | $html .= Html::closeElement( 'a' ); |
234 | } else { |
235 | $html .= Html::closeElement( 'div' ); |
236 | } |
237 | $html .= Html::closeElement( 'li' ); |
238 | |
239 | $output->addHTML( $html ); |
240 | } |
241 | } |