Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 85 |
|
0.00% |
0 / 7 |
CRAP | |
0.00% |
0 / 1 |
SpecialProvideSubmittedText | |
0.00% |
0 / 85 |
|
0.00% |
0 / 7 |
110 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 34 |
|
0.00% |
0 / 1 |
12 | |||
getHeaderHintsHtml | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getTextHeaderLabelHtml | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
2 | |||
getTextAreaHtml | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
2 | |||
getFooterHtml | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getMessageBox | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace TwoColConflict\ProvideSubmittedText; |
4 | |
5 | use BagOStuff; |
6 | use IBufferingStatsdDataFactory; |
7 | use MediaWiki\EditPage\TextboxBuilder; |
8 | use MediaWiki\Html\Html; |
9 | use MediaWiki\Page\PageIdentity; |
10 | use MediaWiki\SpecialPage\UnlistedSpecialPage; |
11 | use MediaWiki\Title\Title; |
12 | use OOUI\HtmlSnippet; |
13 | use OOUI\MessageWidget; |
14 | use 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 | */ |
23 | class 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 | } |