Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 85
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialProvideSubmittedText
0.00% covered (danger)
0.00%
0 / 85
0.00% covered (danger)
0.00%
0 / 7
110
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
12
 getHeaderHintsHtml
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getTextHeaderLabelHtml
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
2
 getTextAreaHtml
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
2
 getFooterHtml
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMessageBox
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace TwoColConflict\ProvideSubmittedText;
4
5use BagOStuff;
6use IBufferingStatsdDataFactory;
7use MediaWiki\EditPage\TextboxBuilder;
8use MediaWiki\Html\Html;
9use MediaWiki\Page\PageIdentity;
10use MediaWiki\SpecialPage\UnlistedSpecialPage;
11use MediaWiki\Title\Title;
12use OOUI\HtmlSnippet;
13use OOUI\MessageWidget;
14use TwoColConflict\TwoColConflictContext;
15
16/**
17 * Special page allows users to see their originally submitted text while they
18 * encounter an edit conflict.
19 *
20 * @license GPL-2.0-or-later
21 * @author Christoph Jauera <christoph.jauera@wikimedia.de>
22 */
23class SpecialProvideSubmittedText extends UnlistedSpecialPage {
24
25    private TwoColConflictContext $twoColConflictContext;
26    private SubmittedTextCache $textCache;
27    private IBufferingStatsdDataFactory $statsdDataFactory;
28
29    public function __construct(
30        TwoColConflictContext $twoColConflictContext,
31        BagOStuff $textCache,
32        IBufferingStatsdDataFactory $statsdDataFactory
33    ) {
34        parent::__construct( 'TwoColConflictProvideSubmittedText' );
35        $this->twoColConflictContext = $twoColConflictContext;
36        $this->textCache = new SubmittedTextCache( $textCache );
37        $this->statsdDataFactory = $statsdDataFactory;
38    }
39
40    /**
41     * @param string|null $subPage
42     */
43    public function execute( $subPage ) {
44        $this->setHeaders();
45        $out = $this->getOutput();
46        $out->addModuleStyles( 'ext.TwoColConflict.SplitCss' );
47        $out->enableOOUI();
48        $this->statsdDataFactory->increment( 'TwoColConflict.copy.special.load' );
49
50        $title = Title::newFromDBkey( $subPage ?? '' );
51        if ( !$title ) {
52            // Should be the same error code as for every malformed title
53            $out->setStatusCode( 404 );
54            $out->addHTML( new MessageWidget( [
55                'label' => $this->msg( 'twocolconflict-special-malformed-title' )->text(),
56                'type' => 'error',
57            ] ) );
58            return;
59        }
60
61        $out->setPageTitleMsg(
62            $this->msg( 'editconflict', $title->getPrefixedText() )
63        );
64
65        $text = $this->textCache->fetchText(
66            $subPage,
67            $out->getUser(),
68            $out->getRequest()->getSessionId()
69        );
70
71        if ( !$text ) {
72            // 410 means "gone", which is quite literally what happened here
73            $out->setStatusCode( 410 );
74            $out->addHTML( new MessageWidget( [
75                'label' => $this->msg( 'twocolconflict-special-expired' )->text(),
76                'type' => 'warning',
77            ] ) );
78            return;
79        }
80
81        $this->statsdDataFactory->increment( 'TwoColConflict.copy.special.retrieved' );
82
83        $html = $this->getHeaderHintsHtml();
84        $html .= $this->getTextHeaderLabelHtml();
85        $html .= $this->getTextAreaHtml( $text, $title );
86        $html .= $this->getFooterHtml();
87
88        $out->addHTML( $html );
89    }
90
91    private function getHeaderHintsHtml(): string {
92        $hintMsg = $this->twoColConflictContext->isUsedAsBetaFeature()
93            ? 'twocolconflict-split-header-hint-beta'
94            : 'twocolconflict-split-header-hint';
95
96        $out = $this->getMessageBox( 'twocolconflict-special-header-overview' );
97        $out .= $this->getMessageBox( $hintMsg );
98
99        return $out;
100    }
101
102    private function getTextHeaderLabelHtml(): string {
103        $html = Html::element(
104            'span',
105            [ 'class' => 'mw-twocolconflict-revision-label' ],
106            $this->msg( 'twocolconflict-split-your-version-header' )->text()
107        );
108        $html .= Html::element( 'br' );
109        $html .= Html::element(
110            'span',
111            [],
112            $this->msg( 'twocolconflict-special-not-saved' )->text()
113        );
114
115        return Html::rawElement(
116            'div',
117            [ 'class' => 'mw-twocolconflict-special-your-version-header' ],
118            $html
119        );
120    }
121
122    /**
123     * @param string $text
124     * @param PageIdentity $page Used to create the lang="…" and dir="…" attributes
125     * @return string
126     */
127    private function getTextAreaHtml( string $text, PageIdentity $page ): string {
128        $builder = new TextboxBuilder();
129        $attribs = $builder->mergeClassesIntoAttributes(
130            [ 'mw-twocolconflict-submitted-text' ],
131            [ 'readonly', 'tabindex' => 1 ]
132        );
133
134        $attribs = $builder->buildTextboxAttribs(
135            'wpTextbox2',
136            $attribs,
137            $this->getUser(),
138            $page
139        );
140
141        return Html::element( 'span', [], $this->msg( 'twocolconflict-special-textarea-hint' )->text() ) .
142            Html::textarea(
143                'wpTextbox2',
144                $builder->addNewLineAtEnd( $text ),
145                $attribs
146            );
147    }
148
149    private function getFooterHtml(): string {
150        return Html::element( 'p', [], $this->msg( 'twocolconflict-special-footer-hint' )->text() );
151    }
152
153    private function getMessageBox( string $messageKey ): string {
154        $html = $this->msg( $messageKey )->parse();
155        return ( new MessageWidget( [
156            'label' => new HtmlSnippet( $html ),
157            'type' => 'notice',
158        ] ) )
159            ->addClasses( [ 'mw-twocolconflict-messageWidget' ] )
160            ->toString();
161    }
162
163}