Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 117
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiQueryProjectPages
0.00% covered (danger)
0.00%
0 / 117
0.00% covered (danger)
0.00%
0 / 10
870
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 executeGenerator
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 run
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
132
 buildDbQuery
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
72
 handleQueryContinuation
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 generateResultVals
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 getAllowedParams
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
2
 getExamplesMessages
0.00% covered (danger)
0.00%
0 / 8
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
2declare( strict_types = 1 );
3
4namespace MediaWiki\Extension\PageAssessments\Api;
5
6use MediaWiki\Api\ApiBase;
7use MediaWiki\Api\ApiPageSet;
8use MediaWiki\Api\ApiQuery;
9use MediaWiki\Api\ApiQueryGeneratorBase;
10use MediaWiki\Extension\PageAssessments\PageAssessmentsStore;
11use MediaWiki\Title\Title;
12use stdClass;
13use Wikimedia\ParamValidator\ParamValidator;
14use Wikimedia\ParamValidator\TypeDef\IntegerDef;
15
16/**
17 * API module for retrieving all the pages associated with a project, for example,
18 * WikiProject Medicine. (T119997)
19 */
20class ApiQueryProjectPages extends ApiQueryGeneratorBase {
21
22    /**
23     * Array of project IDs for the projects listed in the API query
24     * @var array
25     */
26    private array $projectIds = [];
27
28    public function __construct(
29        ApiQuery $query,
30        string $moduleName,
31        private readonly PageAssessmentsStore $store
32    ) {
33        // The prefix pp is already used by the pageprops module, so using wpp instead.
34        parent::__construct( $query, $moduleName, 'wpp' );
35    }
36
37    /**
38     * Evaluate the parameters, perform the requested query, and set up the result
39     */
40    public function execute(): void {
41        $this->run();
42    }
43
44    /**
45     * Evaluate the parameters, perform the requested query, and set up the result for generator mode
46     * @param ApiPageSet $resultPageSet
47     */
48    public function executeGenerator( $resultPageSet ): void {
49        $this->run( $resultPageSet );
50    }
51
52    /**
53     * @param ApiPageSet|null $resultPageSet
54     */
55    private function run( ?ApiPageSet $resultPageSet = null ): void {
56        $params = $this->extractRequestParams();
57
58        if ( $params['assessments'] && $resultPageSet !== null ) {
59            $this->addWarning( 'apiwarn-pageassessments-nogeneratorassessments' );
60        }
61
62        $this->buildDbQuery( $params, $resultPageSet );
63
64        // If matching projects were found, run the query.
65        if ( $this->projectIds ) {
66            $db_res = $this->select( __METHOD__ );
67        // Otherwise, just set the result to an empty array (still works with foreach).
68        } else {
69            $db_res = [];
70        }
71
72        if ( $resultPageSet === null ) {
73            $result = $this->getResult();
74            $count = 0;
75            foreach ( $db_res as $row ) {
76                if ( ++$count > $params['limit'] ) {
77                    $this->setContinueEnumParameter( 'continue', "$row->pa_project_id|$row->pa_page_id" );
78                    break;
79                }
80
81                // Change project id back into its corresponding project title
82                $projectTitle = $row->pap_project_title;
83                if ( !$projectTitle ) {
84                    continue;
85                }
86
87                // Add information to result
88                $vals = $this->generateResultVals( $row );
89                $fit = $result->addValue(
90                    [ 'query', 'projects', $projectTitle ], $row->pa_page_id, $vals
91                );
92
93                if ( !$fit ) {
94                    $this->setContinueEnumParameter( 'continue', "$row->pa_project_id|$row->pa_page_id" );
95                    break;
96                }
97
98                // Add metadata to make XML results for pages parse better
99                $result->addIndexedTagName( [ 'query', 'projects', $projectTitle ], 'page' );
100                $result->addArrayType( [ 'query', 'projects', $projectTitle ], 'array' );
101            }
102            // Add metadata to make XML results for projects parse better
103            $result->addIndexedTagName( [ 'query', 'projects' ], 'project' );
104            $result->addArrayType( [ 'query', 'projects' ], 'kvp', 'name' );
105        } else {
106            $count = 0;
107            foreach ( $db_res as $row ) {
108                if ( ++$count > $params['limit'] ) {
109                    $this->setContinueEnumParameter( 'continue', "$row->pa_project_id|$row->pa_page_id" );
110                    break;
111                }
112
113                $resultPageSet->processDbRow( $row );
114            }
115        }
116    }
117
118    /**
119     * @param array $params
120     * @param ApiPageSet|null $resultPageSet
121     */
122    private function buildDbQuery( array $params, ?ApiPageSet $resultPageSet ): void {
123        $this->addTables( [ 'page', 'page_assessments' ] );
124        $this->addFields( [ 'pa_page_id', 'pa_project_id' ] );
125        $this->addJoinConds( [
126            'page' => [
127                'JOIN',
128                [ 'page_id = pa_page_id' ],
129            ]
130        ] );
131
132        if ( $resultPageSet === null ) {
133            $this->addTables( 'page_assessments_projects' );
134            $this->addFields( [ 'page_title', 'page_namespace', 'pap_project_title' ] );
135            $this->addJoinConds( [
136                'page_assessments_projects' => [
137                    'JOIN',
138                    [ 'pa_project_id = pap_project_id' ],
139                ]
140            ] );
141            if ( $params['assessments'] ) {
142                $this->addFields( [ 'pa_class', 'pa_importance' ] );
143            }
144        } else {
145            $this->addFields( $resultPageSet->getPageTableFields() );
146        }
147
148        if ( isset( $params['projects'] ) ) {
149            // Convert the project names into corresponding IDs
150            foreach ( $params['projects'] as $project ) {
151                $id = $this->store->getProjectId( $project );
152                if ( $id ) {
153                    $this->projectIds[] = $id;
154                } else {
155                    $this->addWarning( [ 'apiwarn-pageassessments-badproject',
156                        wfEscapeWikiText( $project ) ] );
157                }
158            }
159        }
160
161        // DB stores project IDs, so that's what goes into the where field
162        $this->addWhereFld( 'pa_project_id', $this->projectIds );
163        $this->addOption( 'LIMIT', $params['limit'] + 1 );
164
165        if ( $params['continue'] !== null ) {
166            $this->handleQueryContinuation( $params['continue'] );
167        }
168
169        // If more than 1 project is requested, order by project first.
170        if ( count( $this->projectIds ) > 1 ) {
171            $this->addOption( 'ORDER BY', [ 'pa_project_id', 'pa_page_id' ] );
172        } else {
173            $this->addOption( 'ORDER BY', 'pa_page_id' );
174        }
175    }
176
177    /**
178     * @param string $continueParam
179     */
180    private function handleQueryContinuation( string $continueParam ): void {
181        $continues = $this->parseContinueParamOrDie( $continueParam, [ 'int', 'int' ] );
182        $this->addWhere( $this->getDB()->buildComparison( '>=', [
183            'pa_project_id' => $continues[0],
184            'pa_page_id' => $continues[1],
185        ] ) );
186    }
187
188    /**
189     * @param stdClass $row
190     * @return array
191     */
192    private function generateResultVals( stdClass $row ): array {
193        $title = Title::makeTitle( $row->page_namespace, $row->page_title );
194
195        $vals = [
196            'pageid' => (int)$row->pa_page_id,
197            'ns' => (int)$row->page_namespace,
198            'title' => $title->getPrefixedText(),
199        ];
200
201        if ( isset( $row->pa_class ) && isset( $row->pa_importance ) ) {
202            $vals['assessment'] = [
203                'class' => $row->pa_class,
204                'importance' => $row->pa_importance,
205            ];
206        }
207
208        return $vals;
209    }
210
211    /** @inheritDoc */
212    public function getAllowedParams(): array {
213        return [
214            'assessments' => [
215                ParamValidator::PARAM_DEFAULT => false,
216                ParamValidator::PARAM_TYPE => 'boolean',
217            ],
218            'projects' => [
219                ParamValidator::PARAM_ISMULTI => true,
220                ParamValidator::PARAM_REQUIRED => true,
221            ],
222            'limit' => [
223                ParamValidator::PARAM_DEFAULT => 10,
224                ParamValidator::PARAM_TYPE => 'limit',
225                IntegerDef::PARAM_MIN => 1,
226                IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
227                IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2,
228            ],
229            'continue' => [
230                ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
231            ],
232        ];
233    }
234
235    /** @inheritDoc */
236    public function getExamplesMessages(): array {
237        return [
238            'action=query&list=projectpages&wppprojects=Medicine|Anatomy'
239                => 'apihelp-query+projectpages-example-simple-1',
240            'action=query&list=projectpages&wppprojects=Medicine&wppassessments=true'
241                => 'apihelp-query+projectpages-example-simple-2',
242            'action=query&generator=projectpages&prop=info&gwppprojects=Textile%20Arts'
243                => 'apihelp-query+projectpages-example-generator',
244        ];
245    }
246
247    /** @inheritDoc */
248    public function getHelpUrls(): string {
249        return 'https://www.mediawiki.org/wiki/Special:MyLanguage/Extension:PageAssessments';
250    }
251}