Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.00% covered (warning)
85.00%
102 / 120
57.14% covered (warning)
57.14%
4 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiQueryWatchlistRaw
85.71% covered (warning)
85.71%
102 / 119
57.14% covered (warning)
57.14%
4 / 7
31.45
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 executeGenerator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 run
82.76% covered (warning)
82.76%
48 / 58
0.00% covered (danger)
0.00%
0 / 1
25.71
 getAllowedParams
100.00% covered (success)
100.00%
51 / 51
100.00% covered (success)
100.00%
1 / 1
1
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getHelpUrls
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * Copyright © 2008 Roan Kattouw <roan.kattouw@gmail.com>
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 */
8
9namespace MediaWiki\Api;
10
11use MediaWiki\Cache\GenderCache;
12use MediaWiki\Language\Language;
13use MediaWiki\ParamValidator\TypeDef\UserDef;
14use MediaWiki\Title\NamespaceInfo;
15use MediaWiki\Title\Title;
16use MediaWiki\Title\TitleValue;
17use MediaWiki\Watchlist\WatchedItemQueryService;
18use MediaWiki\Watchlist\WatchedItemStore;
19use Wikimedia\ParamValidator\ParamValidator;
20use Wikimedia\ParamValidator\TypeDef\IntegerDef;
21use Wikimedia\Timestamp\TimestampFormat as TS;
22
23/**
24 * This query action allows clients to retrieve a list of pages
25 * on the logged-in user's watchlist.
26 *
27 * @ingroup API
28 */
29class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase {
30
31    public function __construct(
32        ApiQuery $query,
33        string $moduleName,
34        private readonly WatchedItemQueryService $watchedItemQueryService,
35        private readonly Language $contentLanguage,
36        private readonly NamespaceInfo $namespaceInfo,
37        private readonly GenderCache $genderCache,
38    ) {
39        parent::__construct( $query, $moduleName, 'wr' );
40    }
41
42    public function execute() {
43        $this->run();
44    }
45
46    /** @inheritDoc */
47    public function executeGenerator( $resultPageSet ) {
48        $this->run( $resultPageSet );
49    }
50
51    /**
52     * @param ApiPageSet|null $resultPageSet
53     * @return void
54     */
55    private function run( $resultPageSet = null ) {
56        $params = $this->extractRequestParams();
57
58        $user = $this->getWatchlistUser( $params );
59
60        $prop = array_fill_keys( (array)$params['prop'], true );
61        $show = array_fill_keys( (array)$params['show'], true );
62        if ( isset( $show[WatchedItemQueryService::FILTER_CHANGED] )
63            && isset( $show[WatchedItemQueryService::FILTER_NOT_CHANGED] )
64        ) {
65            $this->dieWithError( 'apierror-show' );
66        }
67
68        $options = [];
69        if ( $params['namespace'] ) {
70            $options['namespaceIds'] = $params['namespace'];
71        }
72        if ( isset( $show[WatchedItemQueryService::FILTER_CHANGED] ) ) {
73            $options['filter'] = WatchedItemQueryService::FILTER_CHANGED;
74        }
75        if ( isset( $show[WatchedItemQueryService::FILTER_NOT_CHANGED] ) ) {
76            $options['filter'] = WatchedItemQueryService::FILTER_NOT_CHANGED;
77        }
78
79        if ( isset( $params['continue'] ) ) {
80            $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'int', 'string' ] );
81            $options['startFrom'] = TitleValue::tryNew( $cont[0], $cont[1] );
82            $this->dieContinueUsageIf( !$options['startFrom'] );
83        }
84
85        if ( isset( $params['fromtitle'] ) ) {
86            $options['from'] = $this->parsePrefixedTitlePart( $params['fromtitle'] );
87        }
88
89        if ( isset( $params['totitle'] ) ) {
90            $options['until'] = $this->parsePrefixedTitlePart( $params['totitle'] );
91        }
92
93        $options['sort'] = WatchedItemStore::SORT_ASC;
94        if ( $params['dir'] === 'descending' ) {
95            $options['sort'] = WatchedItemStore::SORT_DESC;
96        }
97        $options['limit'] = $params['limit'] + 1;
98
99        $titles = [];
100        $count = 0;
101        $items = $this->watchedItemQueryService->getWatchedItemsForUser( $user, $options );
102
103        // Get gender information
104        if ( $items !== [] && $resultPageSet === null &&
105            $this->contentLanguage->needsGenderDistinction()
106        ) {
107            $usernames = [];
108            foreach ( $items as $item ) {
109                $target = $item->getTarget();
110                if ( $this->namespaceInfo->hasGenderDistinction( $target->getNamespace() ) ) {
111                    // GenderCache is able to deal with underscores in usernames
112                    $usernames[] = $target->getDBkey();
113                }
114            }
115            if ( $usernames !== [] ) {
116                $this->genderCache->doQuery( $usernames, __METHOD__ );
117            }
118        }
119
120        foreach ( $items as $item ) {
121            $ns = $item->getTarget()->getNamespace();
122            $dbKey = $item->getTarget()->getDBkey();
123            if ( ++$count > $params['limit'] ) {
124                // We've reached the one extra which shows that there are
125                // additional pages to be had. Stop here...
126                $this->setContinueEnumParameter( 'continue', $ns . '|' . $dbKey );
127                break;
128            }
129            $t = Title::makeTitle( $ns, $dbKey );
130
131            if ( $resultPageSet === null ) {
132                $vals = [];
133                ApiQueryBase::addTitleInfo( $vals, $t );
134                if ( isset( $prop['changed'] ) && $item->getNotificationTimestamp() !== null ) {
135                    $vals['changed'] = wfTimestamp( TS::ISO_8601, $item->getNotificationTimestamp() );
136                }
137                $fit = $this->getResult()->addValue( $this->getModuleName(), null, $vals );
138                if ( !$fit ) {
139                    $this->setContinueEnumParameter( 'continue', $ns . '|' . $dbKey );
140                    break;
141                }
142            } else {
143                $titles[] = $t;
144            }
145        }
146        if ( $resultPageSet === null ) {
147            $this->getResult()->addIndexedTagName( $this->getModuleName(), 'wr' );
148        } else {
149            $resultPageSet->populateFromTitles( $titles );
150        }
151    }
152
153    /** @inheritDoc */
154    public function getAllowedParams() {
155        return [
156            'continue' => [
157                ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
158            ],
159            'namespace' => [
160                ParamValidator::PARAM_ISMULTI => true,
161                ParamValidator::PARAM_TYPE => 'namespace'
162            ],
163            'limit' => [
164                ParamValidator::PARAM_DEFAULT => 10,
165                ParamValidator::PARAM_TYPE => 'limit',
166                IntegerDef::PARAM_MIN => 1,
167                IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
168                IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
169            ],
170            'prop' => [
171                ParamValidator::PARAM_ISMULTI => true,
172                ParamValidator::PARAM_TYPE => [
173                    'changed',
174                ],
175                ApiBase::PARAM_HELP_MSG_PER_VALUE => [],
176            ],
177            'show' => [
178                ParamValidator::PARAM_ISMULTI => true,
179                ParamValidator::PARAM_TYPE => [
180                    WatchedItemQueryService::FILTER_CHANGED,
181                    WatchedItemQueryService::FILTER_NOT_CHANGED
182                ]
183            ],
184            'owner' => [
185                ParamValidator::PARAM_TYPE => 'user',
186                UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name' ],
187            ],
188            'token' => [
189                ParamValidator::PARAM_TYPE => 'string',
190                ParamValidator::PARAM_SENSITIVE => true,
191            ],
192            'dir' => [
193                ParamValidator::PARAM_DEFAULT => 'ascending',
194                ParamValidator::PARAM_TYPE => [
195                    'ascending',
196                    'descending'
197                ],
198            ],
199            'fromtitle' => [
200                ParamValidator::PARAM_TYPE => 'string'
201            ],
202            'totitle' => [
203                ParamValidator::PARAM_TYPE => 'string'
204            ],
205        ];
206    }
207
208    /** @inheritDoc */
209    protected function getExamplesMessages() {
210        return [
211            'action=query&list=watchlistraw'
212                => 'apihelp-query+watchlistraw-example-simple',
213            'action=query&generator=watchlistraw&gwrshow=changed&prop=info'
214                => 'apihelp-query+watchlistraw-example-generator',
215        ];
216    }
217
218    /** @inheritDoc */
219    public function getHelpUrls() {
220        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Watchlistraw';
221    }
222}
223
224/** @deprecated class alias since 1.43 */
225class_alias( ApiQueryWatchlistRaw::class, 'ApiQueryWatchlistRaw' );