Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 129
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
CodeRevisionListView
0.00% covered (danger)
0.00%
0 / 129
0.00% covered (danger)
0.00%
0 / 11
1722
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 pathsToArray
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 preparePaths
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 getPathsAsString
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 / 33
0.00% covered (danger)
0.00%
0 / 1
72
 doBatchChange
0.00% covered (danger)
0.00%
0 / 25
0.00% covered (danger)
0.00%
0 / 1
182
 buildBatchInterface
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
 showForm
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
6
 getPager
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getRevCount
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 getRepo
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\CodeReview\UI;
4
5use Html;
6use MediaWiki\Extension\CodeReview\Backend\CodeRepository;
7use MediaWiki\Extension\CodeReview\Backend\CodeRevision;
8use OutputPage;
9use RequestContext;
10use SpecialPage;
11use User;
12use Wikimedia\Rdbms\IDatabase;
13use Xml;
14
15/**
16 * Special:Code/MediaWiki
17 */
18class CodeRevisionListView extends CodeView {
19    /**
20     * @var CodeRepository
21     */
22    public $mRepo;
23    public $mPath;
24
25    public $batchForm;
26
27    /**
28     * @var string[]
29     */
30    protected $filters = [];
31
32    /**
33     * @param CodeRepository|string $repo
34     */
35    public function __construct( $repo ) {
36        parent::__construct( $repo );
37        global $wgRequest;
38
39        $path = $wgRequest->getVal( 'path' );
40
41        if ( $path != '' ) {
42            $this->mPath = self::pathsToArray( $path );
43        } else {
44            $this->mPath = [];
45        }
46
47        $this->mAuthor = $wgRequest->getText( 'author' );
48        $this->mStatus = $wgRequest->getText( 'status' );
49
50        if ( $this->mAuthor ) {
51            $this->filters[] = wfMessage( 'code-revfilter-cr_author', $this->mAuthor )->text();
52        }
53        if ( $this->mStatus ) {
54            $this->filters[] = wfMessage( 'code-revfilter-cr_status', $this->mStatus )->text();
55        }
56
57        if ( count( $this->filters ) ) {
58            global $wgLang;
59            $this->mAppliedFilter = $wgLang->listToText( $this->filters );
60        } else {
61            $this->mAppliedFilter = null;
62        }
63    }
64
65    /**
66     * @param string $path
67     * @return array
68     */
69    public static function pathsToArray( $path ) {
70        return array_map( [ self::class, 'preparePaths' ], explode( '|', $path ) );
71    }
72
73    /**
74     * @param string $path
75     * @return string
76     */
77    public static function preparePaths( $path ) {
78        $path = trim( $path );
79        $path = rtrim( $path, '/' );
80        $escPath = htmlspecialchars( $path );
81        if ( strlen( $escPath ) && $escPath[0] !== '/' ) {
82            // make sure this is a valid path
83            $escPath = "/{$escPath}";
84        }
85        return $escPath;
86    }
87
88    /**
89     * @return string
90     */
91    public function getPathsAsString() {
92        return implode( '|', $this->mPath );
93    }
94
95    public function execute() {
96        global $wgOut, $wgRequest;
97
98        // Todo inject instead of accessing the global
99        $output = $wgOut;
100
101        if ( !$this->mRepo ) {
102            $view = new CodeRepoListView();
103            $view->execute();
104            return;
105        }
106
107        $user = RequestContext::getMain()->getUser();
108
109        // Check for batch change requests.
110        $editToken = $wgRequest->getVal( 'wpBatchChangeEditToken' );
111        $revisions = $wgRequest->getArray( 'wpRevisionSelected' );
112        if ( $wgRequest->wasPosted() && count( $revisions )
113            && $user->matchEditToken( $editToken )
114        ) {
115            $this->doBatchChange( $output, $user );
116            return;
117        }
118
119        // Get the total count across all pages
120        $dbr = wfGetDB( DB_REPLICA );
121        $revCount = $this->getRevCount( $dbr );
122
123        $pager = $this->getPager();
124        $pathForm = $this->showForm( $pager );
125
126        // Build batch change interface as needed
127        $this->batchForm = $user->isAllowed( 'codereview-set-status' ) ||
128            $user->isAllowed( 'codereview-add-tag' );
129
130        $navBar = $pager->getNavigationBar();
131
132        $output->addHTML( $pathForm );
133
134        $output->addHTML(
135            $navBar .
136            '<table><tr><td>' . $pager->getLimitForm() . '</td>'
137        );
138        if ( $revCount !== -1 ) {
139            $output->addHTML(
140                '<td>&#160;<strong>' .
141                    wfMessage( 'code-rev-total' )->numParams( $revCount )->escaped() .
142                    '</strong></td>'
143            );
144        }
145
146        $output->addHTML(
147            '</tr></table>' .
148            Xml::openElement( 'form',
149                [ 'action' => $pager->getTitle()->getLocalURL(), 'method' => 'post' ]
150            ) .
151            $pager->getBody() .
152            // $pager->getLimitDropdown() .
153            $navBar
154        );
155        if ( $this->batchForm ) {
156            $output->addHTML(
157                $this->buildBatchInterface( $pager, $user )
158            );
159        }
160
161        $output->addHTML( Xml::closeElement( 'form' ) . $pathForm );
162    }
163
164    private function doBatchChange( OutputPage $output, User $user ) {
165        global $wgRequest;
166
167        $revisions = $wgRequest->getArray( 'wpRevisionSelected' );
168        $removeTags = $wgRequest->getVal( 'wpRemoveTag' );
169        $addTags = $wgRequest->getVal( 'wpTag' );
170        $status = $wgRequest->getVal( 'wpStatus' );
171
172        // Grab data from the DB
173        $dbr = wfGetDB( DB_REPLICA );
174        $revObjects = [];
175        $res = $dbr->select(
176            'code_rev', '*',
177            [ 'cr_id' => $revisions, 'cr_repo_id' => $this->mRepo->getId() ],
178            __METHOD__
179        );
180        foreach ( $res as $row ) {
181            $revObjects[] = CodeRevision::newFromRow( $this->mRepo, $row );
182        }
183
184        if ( $user->isAllowed( 'codereview-add-tag' ) &&
185                $addTags || $removeTags ) {
186            $addTags = array_map( 'trim', explode( ",", $addTags ) );
187            $removeTags = array_map( 'trim', explode( ",", $removeTags ) );
188
189            foreach ( $revObjects as $rev ) {
190                $rev->changeTags( $addTags, $removeTags, $user );
191            }
192        }
193
194        if ( $user->isAllowed( 'codereview-set-status' ) &&
195                $revObjects && CodeRevision::isValidStatus( $status ) ) {
196            foreach ( $revObjects as $rev ) {
197                $rev->setStatus( $status, $user );
198            }
199        }
200
201        // Automatically refresh
202        // This way of getting GET parameters is horrible, but effective.
203        $fields = $wgRequest->getValues();
204        foreach ( array_keys( $fields ) as $key ) {
205            if ( substr( $key, 0, 2 ) == 'wp' || $key == 'title' ) {
206                unset( $fields[$key] );
207            }
208        }
209
210        $output->redirect( $this->getPager()->getTitle()->getFullURL( $fields ) );
211    }
212
213    /**
214     * @param SvnRevTablePager $pager
215     * @param User $user
216     * @return string
217     */
218    protected function buildBatchInterface( $pager, User $user ) {
219        $changeFields = [];
220
221        if ( $user->isAllowed( 'codereview-set-status' ) ) {
222            $changeFields['code-batch-status'] =
223                Xml::tags( 'select', [ 'name' => 'wpStatus' ],
224                    Xml::tags( 'option',
225                        [ 'value' => '', 'selected' => 'selected' ], ' '
226                    ) .
227                    CodeRevisionView::buildStatusList( null, $this )
228                );
229        }
230
231        if ( $user->isAllowed( 'codereview-add-tag' ) ) {
232            $changeFields['code-batch-tags'] = CodeRevisionView::addTagForm( '', '' );
233        }
234
235        if ( !count( $changeFields ) ) {
236            // nothing to do here
237            return '';
238        }
239
240        $changeInterface = Xml::fieldset( $pager->msg( 'codereview-batch-title' )->text(),
241            // @phan-suppress-next-line SecurityCheck-DoubleEscaped
242            Xml::buildForm( $changeFields, 'codereview-batch-submit' ) );
243
244        $changeInterface .= $pager->getHiddenFields();
245        $changeInterface .= Html::hidden( 'wpBatchChangeEditToken', $user->getEditToken() );
246
247        return $changeInterface;
248    }
249
250    /**
251     * @param SvnTablePager $pager
252     *
253     * @return string
254     */
255    private function showForm( $pager ) {
256        global $wgScript;
257
258        $states = CodeRevision::getPossibleStates();
259        $name = $this->mRepo->getName();
260
261        $title = SpecialPage::getTitleFor( 'Code', $name );
262        $options = [ Xml::option( '', $title->getPrefixedText(), $this->mStatus == '' ) ];
263
264        // Give grep a chance to find the usages:
265        // code-status-new, code-status-fixme, code-status-reverted, code-status-resolved,
266        // code-status-ok, code-status-deferred, code-status-old
267        foreach ( $states as $state ) {
268            $title = SpecialPage::getTitleFor( 'Code', $name . "/status/$state" );
269            $options[] = Xml::option(
270                $pager->msg( "code-status-$state" )->text(),
271                $title->getPrefixedText(),
272                $this->mStatus == $state
273            );
274        }
275
276        $ret = '<fieldset><legend>' .
277            wfMessage( 'code-pathsearch-legend' )->escaped() . '</legend>' .
278            '<table width="100%"><tr><td>' .
279            Xml::openElement( 'form', [ 'action' => $wgScript, 'method' => 'get' ] ) .
280            Xml::inputLabel( wfMessage( "code-pathsearch-path" )->text(), 'path', 'path', 55,
281                $this->getPathsAsString(), [ 'dir' => 'ltr' ] ) . '&#160;' .
282            Xml::label( wfMessage( 'code-pathsearch-filter' )->text(), 'code-status-filter' ) .
283        '&#160;' .
284            Xml::openElement( 'select', [ 'id' => 'code-status-filter', 'name' => 'title' ] ) .
285            "\n" .
286            implode( "\n", $options ) .
287            "\n" .
288            Xml::closeElement( 'select' ) .
289            '&#160;' . Xml::submitButton( wfMessage( 'allpagessubmit' )->text() ) .
290            $pager->getHiddenFields( [ 'path', 'title' ] ) .
291            Xml::closeElement( 'form' ) .
292            '</td></tr></table></fieldset>';
293
294        return $ret;
295    }
296
297    public function getPager() {
298        return new SvnRevTablePager( $this );
299    }
300
301    /**
302     * Get total number of revisions for this revision view
303     *
304     * @param IDatabase $dbr
305     * @return int Number of revisions
306     */
307    protected function getRevCount( $dbr ) {
308        $query = $this->getPager()->getCountQuery();
309
310        $result = $dbr->selectRow( $query['tables'],
311            $query['fields'],
312            $query['conds'],
313            __METHOD__,
314            $query['options'],
315            $query['join_conds']
316        );
317        if ( $result ) {
318            return intval( $result->rev_count );
319        } else {
320            return 0;
321        }
322    }
323
324    public function getRepo() {
325        return $this->mRepo;
326    }
327}