Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 140 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
ApiFeedLQTThreads | |
0.00% |
0 / 140 |
|
0.00% |
0 / 8 |
756 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getCustomPrinter | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
6 | |||
createFeedItem | |
0.00% |
0 / 26 |
|
0.00% |
0 / 1 |
12 | |||
createFeedTitle | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
90 | |||
getConditions | |
0.00% |
0 / 34 |
|
0.00% |
0 / 1 |
90 | |||
getAllowedParams | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
2 | |||
getExamplesMessages | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | */ |
18 | |
19 | namespace MediaWiki\Extension\LiquidThreads\Api; |
20 | |
21 | use ApiBase; |
22 | use ApiFormatFeedWrapper; |
23 | use ApiMain; |
24 | use MediaWiki\Feed\FeedItem; |
25 | use MediaWiki\Linker\Linker; |
26 | use MediaWiki\Linker\LinkRenderer; |
27 | use MediaWiki\MediaWikiServices; |
28 | use MediaWiki\Page\WikiPageFactory; |
29 | use MediaWiki\Title\Title; |
30 | use TextContent; |
31 | use Thread; |
32 | use Threads; |
33 | use Wikimedia\ParamValidator\ParamValidator; |
34 | use Wikimedia\ParamValidator\TypeDef\IntegerDef; |
35 | use Wikimedia\Rdbms\SelectQueryBuilder; |
36 | |
37 | /** |
38 | * This action returns LiquidThreads threads/posts in RSS/Atom formats. |
39 | * |
40 | * @ingroup API |
41 | */ |
42 | class ApiFeedLQTThreads extends ApiBase { |
43 | /** @var LinkRenderer */ |
44 | private $linkRenderer; |
45 | /** @var WikiPageFactory */ |
46 | private $wikiPageFactory; |
47 | |
48 | public function __construct( |
49 | ApiMain $main, |
50 | $action, |
51 | LinkRenderer $linkRenderer, |
52 | WikiPageFactory $wikiPageFactory |
53 | ) { |
54 | parent::__construct( $main, $action ); |
55 | $this->linkRenderer = $linkRenderer; |
56 | $this->wikiPageFactory = $wikiPageFactory; |
57 | } |
58 | |
59 | /** |
60 | * This module uses a custom feed wrapper printer. |
61 | * @return ApiFormatFeedWrapper |
62 | */ |
63 | public function getCustomPrinter() { |
64 | return new ApiFormatFeedWrapper( $this->getMain() ); |
65 | } |
66 | |
67 | /** |
68 | * Make a nested call to the API to request items in the last $hours. |
69 | * Wrap the result as an RSS/Atom feed. |
70 | */ |
71 | public function execute() { |
72 | global $wgFeedClasses; |
73 | |
74 | $params = $this->extractRequestParams(); |
75 | |
76 | $dbr = MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase(); |
77 | |
78 | $feedTitle = $this->createFeedTitle( $params ); |
79 | $feedClass = $wgFeedClasses[$params['feedformat']]; |
80 | $feedItems = []; |
81 | |
82 | $feedUrl = Title::newMainPage()->getFullURL(); |
83 | |
84 | $res = $dbr->newSelectQueryBuilder() |
85 | ->select( '*' ) |
86 | ->from( 'thread' ) |
87 | ->where( $this->getConditions( $params, $dbr ) ) |
88 | ->limit( 200 ) |
89 | ->orderBy( 'thread_created', SelectQueryBuilder::SORT_DESC ) |
90 | ->caller( __METHOD__ ) |
91 | ->fetchResultSet(); |
92 | |
93 | foreach ( $res as $row ) { |
94 | $feedItems[] = $this->createFeedItem( $row ); |
95 | } |
96 | |
97 | $feed = new $feedClass( $feedTitle, '', $feedUrl ); |
98 | |
99 | ApiFormatFeedWrapper::setResult( $this->getResult(), $feed, $feedItems ); |
100 | } |
101 | |
102 | private function createFeedItem( $row ) { |
103 | $thread = Thread::newFromRow( $row ); |
104 | |
105 | $titleStr = $thread->subject(); |
106 | $content = $thread->root()->getPage()->getContent(); |
107 | $completeText = ( $content instanceof TextContent ) ? $content->getText() : ''; |
108 | $completeText = $this->getOutput()->parseAsContent( $completeText ); |
109 | $threadTitle = clone $thread->topmostThread()->title(); |
110 | $threadTitle->setFragment( '#' . $thread->getAnchorName() ); |
111 | $titleUrl = $threadTitle->getFullURL(); |
112 | $timestamp = $thread->created(); |
113 | $user = $thread->author()->getName(); |
114 | |
115 | // Prefix content with a quick description |
116 | $userLink = Linker::userLink( $thread->author()->getId(), $user ); |
117 | $talkpageLink = $this->linkRenderer->makeLink( $thread->getTitle() ); |
118 | if ( $thread->hasSuperthread() ) { |
119 | $stTitle = clone $thread->topmostThread()->title(); |
120 | $stTitle->setFragment( '#' . $thread->superthread()->getAnchorName() ); |
121 | $superthreadLink = $this->linkRenderer->makeLink( $stTitle ); |
122 | $description = $this->msg( 'lqt-feed-reply-intro' ) |
123 | ->rawParams( $talkpageLink, $userLink, $superthreadLink ) |
124 | ->params( $user ) |
125 | ->parseAsBlock(); |
126 | } else { |
127 | // Third param is unused |
128 | $description = $this->msg( 'lqt-feed-new-thread-intro' ) |
129 | ->rawParams( $talkpageLink, $userLink, '' ) |
130 | ->params( $user ) |
131 | ->parseAsBlock(); |
132 | } |
133 | |
134 | $completeText = $description . $completeText; |
135 | |
136 | return new FeedItem( $titleStr, $completeText, $titleUrl, $timestamp, $user ); |
137 | } |
138 | |
139 | public function createFeedTitle( $params ) { |
140 | $fromPlaces = []; |
141 | |
142 | foreach ( (array)$params['thread'] as $thread ) { |
143 | $t = Title::newFromText( $thread ); |
144 | if ( !$t ) { |
145 | continue; |
146 | } |
147 | $fromPlaces[] = $t->getPrefixedText(); |
148 | } |
149 | |
150 | foreach ( (array)$params['talkpage'] as $talkpage ) { |
151 | $t = Title::newFromText( $talkpage ); |
152 | if ( !$t ) { |
153 | continue; |
154 | } |
155 | $fromPlaces[] = $t->getPrefixedText(); |
156 | } |
157 | |
158 | $fromCount = count( $fromPlaces ); |
159 | $fromPlaces = $this->getLanguage()->commaList( $fromPlaces ); |
160 | |
161 | // What's included? |
162 | $types = (array)$params['type']; |
163 | |
164 | if ( !count( array_diff( [ 'replies', 'newthreads' ], $types ) ) ) { |
165 | $msg = 'lqt-feed-title-all'; |
166 | } elseif ( in_array( 'replies', $types ) ) { |
167 | $msg = 'lqt-feed-title-replies'; |
168 | } elseif ( in_array( 'newthreads', $types ) ) { |
169 | $msg = 'lqt-feed-title-new-threads'; |
170 | } else { |
171 | $msg = 'lqt-feed-title-all'; |
172 | } |
173 | |
174 | if ( $fromCount ) { |
175 | $msg .= '-from'; |
176 | } |
177 | |
178 | return $this->msg( $msg, $fromPlaces )->numParams( $fromCount )->text(); |
179 | } |
180 | |
181 | /** |
182 | * @param array $params |
183 | * @param \Wikimedia\Rdbms\IReadableDatabase $dbr |
184 | * @return array |
185 | */ |
186 | private function getConditions( $params, $dbr ) { |
187 | $conds = []; |
188 | |
189 | // Types |
190 | $conds['thread_type'] = Threads::TYPE_NORMAL; |
191 | |
192 | // Limit |
193 | $cutoff = time() - intval( $params['days'] * 24 * 3600 ); |
194 | $conds[] = $dbr->expr( 'thread_created', '>', $dbr->timestamp( $cutoff ) ); |
195 | |
196 | // Talkpage conditions |
197 | $pageConds = []; |
198 | |
199 | $talkpages = (array)$params['talkpage']; |
200 | foreach ( $talkpages as $page ) { |
201 | $title = Title::newFromText( $page ); |
202 | if ( !$title ) { |
203 | $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $page ) ] ); |
204 | } |
205 | $pageConds[] = $dbr->andExpr( [ |
206 | 'thread_article_namespace' => $title->getNamespace(), |
207 | 'thread_article_title' => $title->getDBkey(), |
208 | ] ); |
209 | } |
210 | |
211 | // Thread conditions |
212 | $threads = (array)$params['thread']; |
213 | foreach ( $threads as $threadName ) { |
214 | $thread = null; |
215 | $threadTitle = Title::newFromText( $threadName ); |
216 | if ( $threadTitle ) { |
217 | $thread = Threads::withRoot( $this->wikiPageFactory->newFromTitle( $threadTitle ) ); |
218 | } |
219 | |
220 | if ( !$thread ) { |
221 | continue; |
222 | } |
223 | |
224 | $pageConds[] = $dbr->orExpr( [ |
225 | 'thread_ancestor' => $thread->id(), |
226 | 'thread_id' => $thread->id() |
227 | ] ); |
228 | } |
229 | if ( count( $pageConds ) ) { |
230 | $conds[] = $dbr->orExpr( $pageConds ); |
231 | } |
232 | |
233 | // New thread v. Reply |
234 | $types = (array)$params['type']; |
235 | if ( !in_array( 'replies', $types ) ) { |
236 | $conds[] = Threads::topLevelClause(); |
237 | } elseif ( !in_array( 'newthreads', $types ) ) { |
238 | $conds[] = '!' . Threads::topLevelClause(); |
239 | } |
240 | |
241 | return $conds; |
242 | } |
243 | |
244 | public function getAllowedParams() { |
245 | global $wgFeedClasses; |
246 | $feedFormatNames = array_keys( $wgFeedClasses ); |
247 | return [ |
248 | 'feedformat' => [ |
249 | ParamValidator::PARAM_DEFAULT => 'rss', |
250 | ParamValidator::PARAM_TYPE => $feedFormatNames |
251 | ], |
252 | 'days' => [ |
253 | ParamValidator::PARAM_DEFAULT => 7, |
254 | ParamValidator::PARAM_TYPE => 'integer', |
255 | IntegerDef::PARAM_MIN => 1, |
256 | IntegerDef::PARAM_MAX => 30, |
257 | ], |
258 | 'type' => [ |
259 | ParamValidator::PARAM_DEFAULT => 'newthreads', |
260 | ParamValidator::PARAM_TYPE => [ 'replies', 'newthreads' ], |
261 | ParamValidator::PARAM_ISMULTI => true, |
262 | ], |
263 | 'talkpage' => [ |
264 | ParamValidator::PARAM_ISMULTI => true, |
265 | ], |
266 | 'thread' => [ |
267 | ParamValidator::PARAM_ISMULTI => true, |
268 | ], |
269 | ]; |
270 | } |
271 | |
272 | /** |
273 | * @see ApiBase::getExamplesMessages() |
274 | * @return array |
275 | */ |
276 | protected function getExamplesMessages() { |
277 | return [ |
278 | 'action=feedthreads' |
279 | => 'apihelp-feedthreads-example-1', |
280 | 'action=feedthreads&type=replies&thread=Thread:Foo' |
281 | => 'apihelp-feedthreads-example-2', |
282 | 'action=feedthreads&type=newthreads&talkpage=Talk:Main_Page' |
283 | => 'apihelp-feedthreads-example-3', |
284 | ]; |
285 | } |
286 | } |