Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 324
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialMWOAuthManageConsumers
0.00% covered (danger)
0.00%
0 / 324
0.00% covered (danger)
0.00%
0 / 11
2862
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 doesWrites
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 / 27
0.00% covered (danger)
0.00%
0 / 1
110
 addQueueSubtitleLinks
0.00% covered (danger)
0.00%
0 / 23
0.00% covered (danger)
0.00%
0 / 1
30
 showMainHub
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 1
20
 handleConsumerForm
0.00% covered (danger)
0.00%
0 / 98
0.00% covered (danger)
0.00%
0 / 1
132
 getInfoTableOptions
0.00% covered (danger)
0.00%
0 / 68
0.00% covered (danger)
0.00%
0 / 1
132
 formatCallbackUrl
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 showConsumerList
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
12
 formatRow
0.00% covered (danger)
0.00%
0 / 52
0.00% covered (danger)
0.00%
0 / 1
12
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Extension\OAuth\Frontend\SpecialPages;
4
5/**
6 * (c) Aaron Schulz 2013, GPL
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License along
19 * with this program; if not, write to the Free Software Foundation, Inc.,
20 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 * http://www.gnu.org/copyleft/gpl.html
22 */
23
24use ErrorPageError;
25use HTMLForm;
26use IContextSource;
27use LogEventsList;
28use LogPage;
29use MediaWiki\Extension\OAuth\Backend\Consumer;
30use MediaWiki\Extension\OAuth\Backend\Utils;
31use MediaWiki\Extension\OAuth\Control\ConsumerAccessControl;
32use MediaWiki\Extension\OAuth\Control\ConsumerSubmitControl;
33use MediaWiki\Extension\OAuth\Entity\ClientEntity;
34use MediaWiki\Extension\OAuth\Frontend\Pagers\ManageConsumersPager;
35use MediaWiki\Extension\OAuth\Frontend\UIUtils;
36use MediaWiki\Html\Html;
37use MediaWiki\MediaWikiServices;
38use MediaWiki\Permissions\GrantsLocalization;
39use MediaWiki\SpecialPage\SpecialPage;
40use MediaWiki\Status\Status;
41use MediaWiki\Title\Title;
42use MWRestrictions;
43use OOUI\HtmlSnippet;
44use PermissionsError;
45use stdClass;
46use Wikimedia\Rdbms\IDatabase;
47use Xml;
48
49/**
50 * Special page for listing the queue of consumer requests and managing
51 * their approval/rejection and also for listing approved/disabled consumers
52 */
53class SpecialMWOAuthManageConsumers extends SpecialPage {
54    /** @var bool|int An Consumer::STAGE_* constant on queue/list subpages, false otherwise */
55    protected $stage = false;
56    /** @var string A stage key from Consumer::$stageNames */
57    protected $stageKey;
58
59    /**
60     * Stages which are shown in a queue (they are in an actionable state and can form a backlog)
61     * @var int[]
62     */
63    public static $queueStages = [ Consumer::STAGE_PROPOSED,
64        Consumer::STAGE_REJECTED, Consumer::STAGE_EXPIRED ];
65
66    /**
67     * Stages which cannot form a backlog and are shown in a list
68     * @var int[]
69     */
70    public static $listStages = [ Consumer::STAGE_APPROVED,
71        Consumer::STAGE_DISABLED ];
72
73    /** @var GrantsLocalization */
74    private $grantsLocalization;
75
76    /**
77     * @param GrantsLocalization $grantsLocalization
78     */
79    public function __construct( GrantsLocalization $grantsLocalization ) {
80        parent::__construct( 'OAuthManageConsumers', 'mwoauthmanageconsumer' );
81        $this->grantsLocalization = $grantsLocalization;
82    }
83
84    public function doesWrites() {
85        return true;
86    }
87
88    public function execute( $par ) {
89        $user = $this->getUser();
90        $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
91
92        $this->setHeaders();
93        $this->getOutput()->disallowUserJs();
94        $this->addHelpLink( 'Help:OAuth' );
95        $this->requireNamedUser( 'mwoauth-available-only-to-registered' );
96
97        if ( !$permissionManager->userHasRight( $user, 'mwoauthmanageconsumer' ) ) {
98            throw new PermissionsError( 'mwoauthmanageconsumer' );
99        }
100
101        if ( $this->getConfig()->get( 'MWOAuthReadOnly' ) ) {
102            throw new ErrorPageError( 'mwoauth-error', 'mwoauth-db-readonly' );
103        }
104
105        // Format is Special:OAuthManageConsumers[/<stage>|/<consumer key>]
106        // B/C format is Special:OAuthManageConsumers/<stage>/<consumer key>
107        $consumerKey = null;
108        $navigation = $par !== null ? explode( '/', $par ) : [];
109        if ( count( $navigation ) === 2 ) {
110            $this->stage = false;
111            $consumerKey = $navigation[1];
112        } elseif ( count( $navigation ) === 1 && $navigation[0] ) {
113            $this->stage = array_search( $navigation[0], Consumer::$stageNames, true );
114            if ( $this->stage !== false ) {
115                $this->stageKey = $navigation[0];
116            } else {
117                $consumerKey = $navigation[0];
118            }
119        }
120
121        if ( $consumerKey ) {
122            $this->handleConsumerForm( $consumerKey );
123        } elseif ( $this->stage !== false ) {
124            $this->showConsumerList();
125        } else {
126            $this->showMainHub();
127        }
128
129        $this->addQueueSubtitleLinks( $consumerKey );
130
131        $this->getOutput()->addModuleStyles( 'ext.MWOAuth.styles' );
132    }
133
134    /**
135     * Show other sub-queue links. Grey out the current one.
136     * When viewing a request, show them all and a link to current consumer view.
137     *
138     * @param string|null $consumerKey
139     * @return void
140     */
141    protected function addQueueSubtitleLinks( $consumerKey ) {
142        $linkRenderer = $this->getLinkRenderer();
143        $listLinks = [];
144        foreach ( self::$queueStages as $stage ) {
145            $stageKey = Consumer::$stageNames[$stage];
146            if ( $consumerKey || $this->stageKey !== $stageKey ) {
147                $listLinks[] = $linkRenderer->makeKnownLink(
148                    $this->getPageTitle( $stageKey ),
149                    // Messages: mwoauthmanageconsumers-showproposed,
150                    // mwoauthmanageconsumers-showrejected, mwoauthmanageconsumers-showexpired,
151                    $this->msg( 'mwoauthmanageconsumers-show' . $stageKey )->text()
152                );
153            } else {
154                $listLinks[] = $this->msg( 'mwoauthmanageconsumers-show' . $stageKey )->escaped();
155            }
156        }
157
158        if ( $consumerKey ) {
159            $consumerViewLink = "[" . $linkRenderer->makeKnownLink(
160                SpecialPage::getTitleFor( 'OAuthListConsumers', "view/$consumerKey" ),
161                $this->msg( 'mwoauthconsumer-consumer-view' )->text() ) . "]";
162        } else {
163            $consumerViewLink = '';
164        }
165
166        $linkHtml = $this->getLanguage()->pipeList( $listLinks );
167
168        $viewall = $this->msg( 'parentheses' )->rawParams( $linkRenderer->makeKnownLink(
169            $this->getPageTitle(),
170            $this->msg( 'mwoauthmanageconsumers-main' )->text()
171        ) )->escaped();
172
173        $this->getOutput()->setSubtitle(
174            "<strong>" . $this->msg( 'mwoauthmanageconsumers-type' )->escaped() .
175            "</strong> [{$linkHtml}{$consumerViewLink} <strong>{$viewall}</strong>" );
176    }
177
178    /**
179     * Show the links to all the queues and how many requests are in each.
180     * Also show the list of enabled and disabled consumers and how many there are of each.
181     *
182     * @return void
183     */
184    protected function showMainHub() {
185        $keyStageMapQ = array_intersect( array_flip( Consumer::$stageNames ),
186            self::$queueStages );
187        $keyStageMapL = array_intersect( array_flip( Consumer::$stageNames ),
188            self::$listStages );
189
190        $linkRenderer = $this->getLinkRenderer();
191        $out = $this->getOutput();
192
193        $out->addWikiMsg( 'mwoauthmanageconsumers-maintext' );
194
195        $counts = Utils::getConsumerStateCounts( Utils::getCentralDB( DB_REPLICA ) );
196
197        $out->wrapWikiMsg( "<p><strong>$1</strong></p>", 'mwoauthmanageconsumers-queues' );
198        $out->addHTML( '<ul>' );
199        foreach ( $keyStageMapQ as $stageKey => $stage ) {
200            $tag = ( $stage === Consumer::STAGE_EXPIRED ) ? 'i' : 'b';
201            $out->addHTML(
202                '<li>' .
203                "<$tag>" .
204                $linkRenderer->makeKnownLink(
205                    $this->getPageTitle( $stageKey ),
206                    // Messages: mwoauthmanageconsumers-q-proposed, mwoauthmanageconsumers-q-rejected,
207                    // mwoauthmanageconsumers-q-expired
208                    $this->msg( 'mwoauthmanageconsumers-q-' . $stageKey )->text()
209                ) .
210                "</$tag> [$counts[$stage]]" .
211                '</li>'
212            );
213        }
214        $out->addHTML( '</ul>' );
215
216        $out->wrapWikiMsg( "<p><strong>$1</strong></p>", 'mwoauthmanageconsumers-lists' );
217        $out->addHTML( '<ul>' );
218        foreach ( $keyStageMapL as $stageKey => $stage ) {
219            $out->addHTML(
220                '<li>' .
221                $linkRenderer->makeKnownLink(
222                    $this->getPageTitle( $stageKey ),
223                    // Messages: mwoauthmanageconsumers-l-approved, mwoauthmanageconsumers-l-disabled
224                    $this->msg( 'mwoauthmanageconsumers-l-' . $stageKey )->text()
225                ) .
226                " [$counts[$stage]]" .
227                '</li>'
228            );
229        }
230        $out->addHTML( '</ul>' );
231    }
232
233    /**
234     * Show the form to approve/reject/disable/re-enable consumers
235     *
236     * @param string $consumerKey
237     * @throws PermissionsError
238     */
239    protected function handleConsumerForm( $consumerKey ) {
240        $user = $this->getUser();
241        $dbr = Utils::getCentralDB( DB_REPLICA );
242        $cmrAc = ConsumerAccessControl::wrap(
243            Consumer::newFromKey( $dbr, $consumerKey ), $this->getContext() );
244        $permissionManager = MediaWikiServices::getInstance()->getPermissionManager();
245
246        if ( !$cmrAc ) {
247            $this->getOutput()->addWikiMsg( 'mwoauth-invalid-consumer-key' );
248            return;
249        } elseif ( $cmrAc->getDeleted()
250            && !$permissionManager->userHasRight( $user, 'mwoauthviewsuppressed' ) ) {
251            throw new PermissionsError( 'mwoauthviewsuppressed' );
252        }
253        $startingStage = $cmrAc->getStage();
254        $pending = !in_array( $startingStage, [
255            Consumer::STAGE_APPROVED, Consumer::STAGE_DISABLED ] );
256
257        if ( $pending ) {
258            $opts = [
259                $this->msg( 'mwoauthmanageconsumers-approve' )->escaped() => 'approve',
260                $this->msg( 'mwoauthmanageconsumers-reject' )->escaped()  => 'reject'
261            ];
262            if ( $permissionManager->userHasRight( $this->getUser(), 'mwoauthsuppress' ) ) {
263                $msg = $this->msg( 'mwoauthmanageconsumers-rsuppress' )->escaped();
264                $opts["<strong>$msg</strong>"] = 'rsuppress';
265            }
266        } else {
267            $opts = [
268                $this->msg( 'mwoauthmanageconsumers-disable' )->escaped() => 'disable',
269                $this->msg( 'mwoauthmanageconsumers-reenable' )->escaped()  => 'reenable'
270            ];
271            if ( $permissionManager->userHasRight( $this->getUser(), 'mwoauthsuppress' ) ) {
272                $msg = $this->msg( 'mwoauthmanageconsumers-dsuppress' )->escaped();
273                $opts["<strong>$msg</strong>"] = 'dsuppress';
274            }
275        }
276
277        $dbw = Utils::getCentralDB( DB_PRIMARY );
278        $control = new ConsumerSubmitControl( $this->getContext(), [], $dbw );
279        $form = HTMLForm::factory( 'ooui',
280            $control->registerValidators( [
281                'info' => [
282                    'type' => 'info',
283                    'raw' => true,
284                    'default' => UIUtils::generateInfoTable(
285                        $this->getInfoTableOptions( $cmrAc ),
286                        $this->getContext()
287                    ),
288                ],
289                'action' => [
290                    'type' => 'radio',
291                    'label-message' => 'mwoauthmanageconsumers-action',
292                    'required' => true,
293                    'options' => $opts,
294                    // no validate on GET
295                    'default' => '',
296                ],
297                'reason' => [
298                    'type' => 'text',
299                    'label-message' => 'mwoauthmanageconsumers-reason',
300                    'required' => true,
301                ],
302                'consumerKey' => [
303                    'type' => 'hidden',
304                    'default' => $cmrAc->getConsumerKey(),
305                ],
306                'changeToken' => [
307                    'type' => 'hidden',
308                    'default' => $cmrAc->getDAO()->getChangeToken( $this->getContext() ),
309                ],
310            ] ),
311            $this->getContext()
312        );
313        $form->setSubmitCallback(
314            static function ( array $data, IContextSource $context ) use ( $control ) {
315                $data['suppress'] = 0;
316                if ( $data['action'] === 'dsuppress' ) {
317                    $data = [ 'action' => 'disable', 'suppress' => 1 ] + $data;
318                } elseif ( $data['action'] === 'rsuppress' ) {
319                    $data = [ 'action' => 'reject', 'suppress' => 1 ] + $data;
320                }
321                $control->setInputParameters( $data );
322                return $control->submit();
323            }
324        );
325
326        $form->setWrapperLegendMsg( 'mwoauthmanageconsumers-confirm-legend' );
327        $form->setSubmitTextMsg( 'mwoauthmanageconsumers-confirm-submit' );
328        $form->addPreHtml(
329            $this->msg( 'mwoauthmanageconsumers-confirm-text' )->parseAsBlock() );
330
331        $status = $form->show();
332        if ( $status instanceof Status && $status->isOK() ) {
333            /** @var Consumer $cmr */
334            // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
335            $cmr = $status->value['result'];
336            '@phan-var Consumer $cmr';
337            $oldStageKey = Consumer::$stageNames[$startingStage];
338            $newStageKey = Consumer::$stageNames[$cmr->getStage()];
339            // Messages: mwoauthmanageconsumers-success-approved, mwoauthmanageconsumers-success-rejected,
340            // mwoauthmanageconsumers-success-disabled
341            $this->getOutput()->addWikiMsg( "mwoauthmanageconsumers-success-$newStageKey" );
342            $returnTo = Title::newFromText( 'Special:OAuthManageConsumers/' . $oldStageKey );
343            $this->getOutput()->addReturnTo( $returnTo, [],
344                // Messages: mwoauthmanageconsumers-linkproposed,
345                // mwoauthmanageconsumers-linkrejected, mwoauthmanageconsumers-linkexpired,
346                // mwoauthmanageconsumers-linkapproved, mwoauthmanageconsumers-linkdisabled
347                $this->msg( 'mwoauthmanageconsumers-link' . $oldStageKey )->text() );
348        } else {
349            $out = $this->getOutput();
350            // Show all of the status updates
351            $logPage = new LogPage( 'mwoauthconsumer' );
352            $out->addHTML( Xml::element( 'h2', null, $logPage->getName()->text() ) );
353            LogEventsList::showLogExtract( $out, 'mwoauthconsumer', '', '', [
354                'conds' => [
355                    'ls_field' => 'OAuthConsumer',
356                    'ls_value' => $cmrAc->getConsumerKey(),
357                ],
358            ] );
359        }
360    }
361
362    /**
363     * @param ConsumerAccessControl $cmrAc
364     * @return array
365     */
366    protected function getInfoTableOptions( $cmrAc ) {
367        $owner = $cmrAc->getUserName();
368        $lang = $this->getLanguage();
369
370        $link = $this->getLinkRenderer()->makeKnownLink(
371            $title = SpecialPage::getTitleFor( 'OAuthListConsumers' ),
372            $this->msg( 'mwoauthmanageconsumers-search-publisher' )->text(),
373            [],
374            [ 'publisher' => $owner ]
375        );
376        $ownerLink = $cmrAc->escapeForHtml( $owner ) . ' ' .
377            $this->msg( 'parentheses' )->rawParams( $link )->escaped();
378        $ownerOnly = $cmrAc->getDAO()->getOwnerOnly();
379        $restrictions = $cmrAc->getRestrictions();
380
381        $options = [
382            // Messages: mwoauth-consumer-stage-proposed, mwoauth-consumer-stage-rejected,
383            // mwoauth-consumer-stage-expired, mwoauth-consumer-stage-approved,
384            // mwoauth-consumer-stage-disabled
385            'mwoauth-consumer-stage' => $cmrAc->getDeleted()
386                ? $this->msg( 'mwoauth-consumer-stage-suppressed' )
387                : $this->msg( 'mwoauth-consumer-stage-' .
388                    Consumer::$stageNames[$cmrAc->getStage()] ),
389            'mwoauth-consumer-key' => $cmrAc->getConsumerKey(),
390            'mwoauth-consumer-name' => new HtmlSnippet( $cmrAc->get( 'name', function ( $s ) {
391                $link = $this->getLinkRenderer()->makeKnownLink(
392                    SpecialPage::getTitleFor( 'OAuthListConsumers' ),
393                    $this->msg( 'mwoauthmanageconsumers-search-name' )->text(),
394                    [],
395                    [ 'name' => $s ]
396                );
397                return htmlspecialchars( $s ) . ' ' .
398                    $this->msg( 'parentheses' )->rawParams( $link )->escaped();
399            } ) ),
400            'mwoauth-consumer-version' => $cmrAc->getVersion(),
401            'mwoauth-oauth-version' => $cmrAc->getOAuthVersion() === Consumer::OAUTH_VERSION_2
402                ? $this->msg( 'mwoauth-oauth-version-2' )
403                : $this->msg( 'mwoauth-oauth-version-1' ),
404            'mwoauth-consumer-user' => new HtmlSnippet( $ownerLink ),
405            'mwoauth-consumer-description' => $cmrAc->getDescription(),
406            'mwoauth-consumer-owner-only-label' => $ownerOnly ?
407                $this->msg( 'mwoauth-consumer-owner-only', $owner ) : null,
408            'mwoauth-consumer-callbackurl' => $ownerOnly ?
409                null : $this->formatCallbackUrl( $cmrAc ),
410            'mwoauth-consumer-callbackisprefix' => $ownerOnly ?
411                null : ( $cmrAc->getCallbackIsPrefix() ?
412                    $this->msg( 'htmlform-yes' ) : $this->msg( 'htmlform-no' ) ),
413            'mwoauth-consumer-grantsneeded' => $cmrAc->get( 'grants',
414                function ( $grants ) use ( $lang ) {
415                    return $lang->semicolonList( $this->grantsLocalization->getGrantDescriptions( $grants, $lang ) );
416                } ),
417            'mwoauth-consumer-email' => $cmrAc->getEmail(),
418            'mwoauth-consumer-wiki' => $cmrAc->getWiki()
419        ];
420
421        // Add OAuth2 specific parameters
422        if ( $cmrAc->getOAuthVersion() === Consumer::OAUTH_VERSION_2 ) {
423            /** @var ClientEntity $consumer */
424            $consumer = $cmrAc->getDAO();
425            $options += [
426                'mwoauth-oauth2-is-confidential' => $consumer->isConfidential() ?
427                    $this->msg( 'htmlform-yes' ) : $this->msg( 'htmlform-no' ),
428                'mwoauth-oauth2-granttypes' => implode( ', ', array_map( function ( $grant ) {
429                    $map = [
430                        'authorization_code' => 'mwoauth-oauth2-granttype-auth-code',
431                        'refresh_token' => 'mwoauth-oauth2-granttype-refresh-token',
432                        'client_credentials' => 'mwoauth-oauth2-granttype-client-credentials'
433                    ];
434                    return isset( $map[$grant] ) ? $this->msg( $map[$grant] ) : '';
435                }, $consumer->getAllowedGrants() ) )
436            ];
437        }
438
439        // Add optional parameters
440        $options += [
441            'mwoauth-consumer-restrictions-json' => $restrictions instanceof MWRestrictions ?
442                $restrictions->toJson( true ) : $restrictions,
443            'mwoauth-consumer-rsakey' => $cmrAc->getRsaKey(),
444        ];
445
446        return $options;
447    }
448
449    /**
450     * Format a callback URL. Usually this doesn't do anything nontrivial, but it adds a warning
451     * to callback URLs with a special meaning.
452     * @param ConsumerAccessControl $cmrAc
453     * @return HtmlSnippet|string Formatted callback URL, as a plaintext or HTML string
454     */
455    protected function formatCallbackUrl( ConsumerAccessControl $cmrAc ) {
456        $url = $cmrAc->getCallbackUrl();
457        if ( $cmrAc->getDAO()->getCallbackIsPrefix() ) {
458            $urlParts = wfParseUrl( $cmrAc->getDAO()->getCallbackUrl() );
459            if ( ( $urlParts['port'] ?? null ) === 1 ) {
460                $warning = Html::element( 'span', [ 'class' => 'warning' ],
461                    $this->msg( 'mwoauth-consumer-callbackurl-warning' )->text() );
462                $url = new HtmlSnippet( $url . ' ' . $warning );
463            }
464        }
465        return $url;
466    }
467
468    /**
469     * Show a paged list of consumers with links to details
470     */
471    protected function showConsumerList() {
472        $pager = new ManageConsumersPager( $this, [], $this->stage );
473        if ( $pager->getNumRows() ) {
474            $this->getOutput()->addHTML( $pager->getNavigationBar() );
475            $this->getOutput()->addHTML( $pager->getBody() );
476            $this->getOutput()->addHTML( $pager->getNavigationBar() );
477        } else {
478            // Messages: mwoauthmanageconsumers-none-proposed, mwoauthmanageconsumers-none-rejected,
479            // mwoauthmanageconsumers-none-expired, mwoauthmanageconsumers-none-approved,
480            // mwoauthmanageconsumers-none-disabled
481            $this->getOutput()->addWikiMsg( "mwoauthmanageconsumers-none-{$this->stageKey}" );
482        }
483        # Every 30th view, prune old deleted items
484        if ( mt_rand( 0, 29 ) == 0 ) {
485            Utils::runAutoMaintenance( Utils::getCentralDB( DB_PRIMARY ) );
486        }
487    }
488
489    /**
490     * @param IDatabase $db
491     * @param stdClass $row
492     * @return string
493     */
494    public function formatRow( IDatabase $db, $row ) {
495        $cmrAc = ConsumerAccessControl::wrap(
496            Consumer::newFromRow( $db, $row ), $this->getContext()
497        );
498
499        $cmrKey = $cmrAc->getConsumerKey();
500        $stageKey = Consumer::$stageNames[$cmrAc->getStage()];
501
502        $link = $this->getLinkRenderer()->makeKnownLink(
503            $this->getPageTitle( $cmrKey ),
504            $this->msg( 'mwoauthmanageconsumers-review' )->text()
505        );
506
507        $time = $this->getLanguage()->timeanddate(
508            wfTimestamp( TS_MW, $cmrAc->getRegistration() ), true );
509
510        $encStageKey = htmlspecialchars( $stageKey );
511        $r = "<li class='mw-mwoauthmanageconsumers-{$encStageKey}'>";
512
513        $r .= $time . " (<strong>{$link}</strong>)";
514
515        // Show last log entry (@TODO: title namespace?)
516        // @TODO: inject DB
517        $logHtml = '';
518        LogEventsList::showLogExtract( $logHtml, 'mwoauthconsumer', '', '', [
519            'action' => Consumer::$stageActionNames[$cmrAc->getStage()],
520            'conds'  => [
521                'ls_field' => 'OAuthConsumer',
522                'ls_value' => $cmrAc->getConsumerKey(),
523            ],
524            'lim'    => 1,
525            'flags'  => LogEventsList::NO_EXTRA_USER_LINKS,
526        ] );
527
528        $lang = $this->getLanguage();
529        $data = [
530            'mwoauthmanageconsumers-name' => $cmrAc->escapeForHtml( $cmrAc->getNameAndVersion() ),
531            'mwoauthmanageconsumers-user' => $cmrAc->escapeForHtml( $cmrAc->getUserName() ),
532            'mwoauth-oauth-version' => $cmrAc->escapeForHtml(
533                $cmrAc->getOAuthVersion() === Consumer::OAUTH_VERSION_2 ?
534                $this->msg( 'mwoauth-oauth-version-2' ) :
535                $this->msg( 'mwoauth-oauth-version-1' )
536            ),
537            'mwoauthmanageconsumers-description' => $cmrAc->escapeForHtml(
538                $cmrAc->get( 'description', static function ( $s ) use ( $lang ) {
539                    return $lang->truncateForVisual( $s, 10024 );
540                } )
541            ),
542            'mwoauthmanageconsumers-email' => $cmrAc->escapeForHtml( $cmrAc->getEmail() ),
543            'mwoauthmanageconsumers-consumerkey' => $cmrAc->escapeForHtml( $cmrAc->getConsumerKey() ),
544            'mwoauthmanageconsumers-lastchange' => $logHtml,
545        ];
546
547        $r .= "<table class='mw-mwoauthmanageconsumers-body' " .
548            "cellspacing='1' cellpadding='3' border='1' width='100%'>";
549        foreach ( $data as $msg => $encValue ) {
550            $r .= '<tr>' .
551                '<td><strong>' . $this->msg( $msg )->escaped() . '</strong></td>' .
552                '<td width=\'90%\'>' . $encValue . '</td>' .
553                '</tr>';
554        }
555        $r .= '</table>';
556
557        $r .= '</li>';
558
559        return $r;
560    }
561
562    protected function getGroupName() {
563        return 'users';
564    }
565
566}