Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 89 |
|
0.00% |
0 / 10 |
CRAP | |
0.00% |
0 / 1 |
TopicSubscriptionsPager | |
0.00% |
0 / 89 |
|
0.00% |
0 / 10 |
756 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
preprocessResults | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getFieldNames | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
formatValue | |
0.00% |
0 / 50 |
|
0.00% |
0 / 1 |
240 | |||
getCellAttrs | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
getQueryInfo | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
2 | |||
getDefaultSort | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDefaultDirections | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getIndexField | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isFieldSortable | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\DiscussionTools; |
4 | |
5 | use IContextSource; |
6 | use InvalidArgumentException; |
7 | use MediaWiki\Cache\LinkBatchFactory; |
8 | use MediaWiki\Html\Html; |
9 | use MediaWiki\Linker\Linker; |
10 | use MediaWiki\Linker\LinkRenderer; |
11 | use MediaWiki\Pager\TablePager; |
12 | use MediaWiki\Title\Title; |
13 | use OOUI; |
14 | |
15 | class TopicSubscriptionsPager extends TablePager { |
16 | |
17 | /** |
18 | * Map of our field names (see ::getFieldNames()) to the column names actually used for |
19 | * pagination. This is needed to ensure that the values are unique, and that pagination |
20 | * won't get "stuck" when e.g. 50 subscriptions are all created within a second. |
21 | */ |
22 | private const INDEX_FIELDS = [ |
23 | // The auto-increment ID will almost always have the same order as sub_created |
24 | // and the field already has an index. |
25 | '_topic' => [ 'sub_id' ], |
26 | 'sub_created' => [ 'sub_id' ], |
27 | // TODO Add indexes that cover these fields to enable sorting by them |
28 | // 'sub_state' => [ 'sub_state', 'sub_item' ], |
29 | // 'sub_created' => [ 'sub_created', 'sub_item' ], |
30 | // 'sub_notified' => [ 'sub_notified', 'sub_item' ], |
31 | ]; |
32 | |
33 | private LinkBatchFactory $linkBatchFactory; |
34 | |
35 | public function __construct( |
36 | IContextSource $context, |
37 | LinkRenderer $linkRenderer, |
38 | LinkBatchFactory $linkBatchFactory |
39 | ) { |
40 | parent::__construct( $context, $linkRenderer ); |
41 | $this->linkBatchFactory = $linkBatchFactory; |
42 | } |
43 | |
44 | /** |
45 | * @inheritDoc |
46 | */ |
47 | public function preprocessResults( $result ) { |
48 | $lb = $this->linkBatchFactory->newLinkBatch(); |
49 | foreach ( $result as $row ) { |
50 | $lb->add( $row->sub_namespace, $row->sub_title ); |
51 | } |
52 | $lb->execute(); |
53 | } |
54 | |
55 | /** |
56 | * @inheritDoc |
57 | */ |
58 | protected function getFieldNames() { |
59 | return [ |
60 | '_topic' => $this->msg( 'discussiontools-topicsubscription-pager-topic' )->text(), |
61 | '_page' => $this->msg( 'discussiontools-topicsubscription-pager-page' )->text(), |
62 | 'sub_created' => $this->msg( 'discussiontools-topicsubscription-pager-created' )->text(), |
63 | 'sub_notified' => $this->msg( 'discussiontools-topicsubscription-pager-notified' )->text(), |
64 | '_unsubscribe' => $this->msg( 'discussiontools-topicsubscription-pager-actions' )->text(), |
65 | ]; |
66 | } |
67 | |
68 | /** |
69 | * @inheritDoc |
70 | */ |
71 | public function formatValue( $field, $value ) { |
72 | /** @var stdClass $row */ |
73 | $row = $this->mCurrentRow; |
74 | $linkRenderer = $this->getLinkRenderer(); |
75 | |
76 | switch ( $field ) { |
77 | case '_topic': |
78 | if ( str_starts_with( $row->sub_item, 'p-topics-' ) ) { |
79 | return '<em>' . |
80 | $this->msg( 'discussiontools-topicsubscription-pager-newtopics-label' )->escaped() . |
81 | '</em>'; |
82 | } else { |
83 | $section = $row->sub_section; |
84 | // Detect truncated section titles: either intentionally truncated by SubscriptionStore, |
85 | // or incorrect multibyte truncation of old entries (T345648). |
86 | $last = mb_substr( $section, -1 ); |
87 | if ( $last !== '' && ( $last === "\x1f" || mb_ord( $last ) === false ) ) { |
88 | $section = substr( $section, 0, -strlen( $last ) ); |
89 | // We can't link to the section correctly, since the only link we have is truncated |
90 | return htmlspecialchars( $section ) . $this->msg( 'ellipsis' )->escaped(); |
91 | } |
92 | $titleSection = Title::makeTitleSafe( $row->sub_namespace, $row->sub_title, $section ); |
93 | if ( !$titleSection ) { |
94 | // Handle invalid titles of any other kind, just in case |
95 | return htmlspecialchars( $section ); |
96 | } |
97 | return $linkRenderer->makeLink( $titleSection, $section ); |
98 | } |
99 | |
100 | case '_page': |
101 | $title = Title::makeTitleSafe( $row->sub_namespace, $row->sub_title ); |
102 | if ( !$title ) { |
103 | // Handle invalid titles (T345648) |
104 | return Html::element( 'span', [ 'class' => 'mw-invalidtitle' ], |
105 | Linker::getInvalidTitleDescription( |
106 | $this->getContext(), $row->sub_namespace, $row->sub_title ) |
107 | ); |
108 | } |
109 | return $linkRenderer->makeLink( $title, $title->getPrefixedText() ); |
110 | |
111 | case 'sub_created': |
112 | return htmlspecialchars( $this->getLanguage()->userTimeAndDate( $value, $this->getUser() ) ); |
113 | |
114 | case 'sub_notified': |
115 | return $value ? |
116 | htmlspecialchars( $this->getLanguage()->userTimeAndDate( $value, $this->getUser() ) ) : |
117 | $this->msg( 'discussiontools-topicsubscription-pager-notified-never' )->escaped(); |
118 | |
119 | case '_unsubscribe': |
120 | $title = Title::makeTitleSafe( $row->sub_namespace, $row->sub_title ); |
121 | if ( !$title ) { |
122 | // Handle invalid titles (T345648) |
123 | // The title isn't checked when unsubscribing, as long as it's a valid title, |
124 | // so specify something to make it possible to unsubscribe from the buggy entries. |
125 | $title = Title::newMainPage(); |
126 | } |
127 | return (string)new OOUI\ButtonWidget( [ |
128 | 'label' => $this->msg( 'discussiontools-topicsubscription-pager-unsubscribe-button' )->text(), |
129 | 'classes' => [ 'ext-discussiontools-special-unsubscribe-button' ], |
130 | 'framed' => false, |
131 | 'flags' => [ 'destructive' ], |
132 | 'data' => [ |
133 | 'item' => $row->sub_item, |
134 | 'title' => $title->getPrefixedText(), |
135 | ], |
136 | 'href' => $title->getLinkURL( [ |
137 | 'action' => 'dtunsubscribe', |
138 | 'commentname' => $row->sub_item, |
139 | ] ), |
140 | 'infusable' => true, |
141 | ] ); |
142 | |
143 | default: |
144 | throw new InvalidArgumentException( "Unknown field '$field'" ); |
145 | } |
146 | } |
147 | |
148 | /** |
149 | * @inheritDoc |
150 | */ |
151 | protected function getCellAttrs( $field, $value ) { |
152 | $attrs = parent::getCellAttrs( $field, $value ); |
153 | if ( $field === '_unsubscribe' ) { |
154 | $attrs['style'] = 'text-align: center;'; |
155 | } |
156 | return $attrs; |
157 | } |
158 | |
159 | /** |
160 | * @inheritDoc |
161 | */ |
162 | public function getQueryInfo() { |
163 | return [ |
164 | 'tables' => [ |
165 | 'discussiontools_subscription', |
166 | ], |
167 | 'fields' => [ |
168 | 'sub_id', |
169 | 'sub_item', |
170 | 'sub_namespace', |
171 | 'sub_title', |
172 | 'sub_section', |
173 | 'sub_created', |
174 | 'sub_notified', |
175 | ], |
176 | 'conds' => [ |
177 | 'sub_user' => $this->getUser()->getId(), |
178 | 'sub_state != ' . SubscriptionStore::STATE_UNSUBSCRIBED, |
179 | ], |
180 | ]; |
181 | } |
182 | |
183 | /** |
184 | * @inheritDoc |
185 | */ |
186 | public function getDefaultSort() { |
187 | return 'sub_created'; |
188 | } |
189 | |
190 | /** |
191 | * @inheritDoc |
192 | */ |
193 | public function getDefaultDirections() { |
194 | return static::DIR_DESCENDING; |
195 | } |
196 | |
197 | /** |
198 | * @inheritDoc |
199 | */ |
200 | public function getIndexField() { |
201 | return [ static::INDEX_FIELDS[$this->mSort] ]; |
202 | } |
203 | |
204 | /** |
205 | * @inheritDoc |
206 | */ |
207 | protected function isFieldSortable( $field ) { |
208 | // Hide the sort button for "Topic" as it is more accurately shown as "Created" |
209 | return isset( static::INDEX_FIELDS[$field] ) && $field !== '_topic'; |
210 | } |
211 | } |