Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 132 |
|
0.00% |
0 / 13 |
CRAP | |
0.00% |
0 / 1 |
TranslationStatsSpecialPage | |
0.00% |
0 / 132 |
|
0.00% |
0 / 13 |
812 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
isIncludable | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
30 | |||
form | |
0.00% |
0 / 56 |
|
0.00% |
0 / 1 |
56 | |||
eInput | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
eLabel | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
eRadio | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
6 | |||
eLanguage | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
languageSelector | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
eGroup | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
groupSelector | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
embed | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | declare( strict_types = 1 ); |
3 | |
4 | namespace MediaWiki\Extension\Translate\Statistics; |
5 | |
6 | use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroups; |
7 | use MediaWiki\Extension\Translate\Utilities\JsSelectToInput; |
8 | use MediaWiki\Extension\Translate\Utilities\Utilities; |
9 | use MediaWiki\Html\FormOptions; |
10 | use MediaWiki\Html\Html; |
11 | use SpecialPage; |
12 | use Xml; |
13 | use XmlSelect; |
14 | use function wfEscapeWikiText; |
15 | |
16 | /** |
17 | * Includable special page for generating graphs for statistics. |
18 | * |
19 | * @file |
20 | * @author Niklas Laxström |
21 | * @author Siebrand Mazeland |
22 | * @license GPL-2.0-or-later |
23 | */ |
24 | class TranslationStatsSpecialPage extends SpecialPage { |
25 | /** @var TranslationStatsDataProvider */ |
26 | private $dataProvider; |
27 | private const GRAPH_CONTAINER_ID = 'translationStatsGraphContainer'; |
28 | private const GRAPH_CONTAINER_CLASS = 'mw-translate-translationstats-container'; |
29 | |
30 | public function __construct( TranslationStatsDataProvider $dataProvider ) { |
31 | parent::__construct( 'TranslationStats' ); |
32 | $this->dataProvider = $dataProvider; |
33 | } |
34 | |
35 | /** @inheritDoc */ |
36 | public function isIncludable(): bool { |
37 | return true; |
38 | } |
39 | |
40 | /** @inheritDoc */ |
41 | protected function getGroupName(): string { |
42 | return 'translation'; |
43 | } |
44 | |
45 | /** @inheritDoc */ |
46 | public function execute( $par ): void { |
47 | $graphOpts = new TranslationStatsGraphOptions(); |
48 | $graphOpts->bindArray( $this->getRequest()->getValues() ); |
49 | |
50 | $pars = explode( ';', (string)$par ); |
51 | foreach ( $pars as $item ) { |
52 | if ( !str_contains( $item, '=' ) ) { |
53 | continue; |
54 | } |
55 | |
56 | [ $key, $value ] = array_map( 'trim', explode( '=', $item, 2 ) ); |
57 | if ( $graphOpts->hasValue( $key ) ) { |
58 | $graphOpts->setValue( $key, $value ); |
59 | } |
60 | } |
61 | |
62 | $graphOpts->normalize( $this->dataProvider->getGraphTypes() ); |
63 | $opts = $graphOpts->getFormOptions(); |
64 | |
65 | if ( $this->including() ) { |
66 | $this->getOutput()->addHTML( $this->embed( $opts ) ); |
67 | } else { |
68 | $this->form( $opts ); |
69 | } |
70 | } |
71 | |
72 | /** |
73 | * Constructs the form which can be used to generate custom graphs. |
74 | * |
75 | * @suppress SecurityCheck-DoubleEscaped Intentionally outputting what user should type |
76 | */ |
77 | private function form( FormOptions $opts ): void { |
78 | $script = $this->getConfig()->get( 'Script' ); |
79 | |
80 | $this->setHeaders(); |
81 | $out = $this->getOutput(); |
82 | $out->addModules( 'ext.translate.special.translationstats' ); |
83 | $out->addHelpLink( 'Help:Extension:Translate/Statistics_and_reporting' ); |
84 | $out->addWikiMsg( 'translate-statsf-intro' ); |
85 | $out->addHTML( |
86 | Xml::fieldset( $this->msg( 'translate-statsf-options' )->text() ) . Html::openElement( |
87 | 'form', |
88 | [ 'action' => $script, 'id' => 'translationStatsConfig' ] |
89 | ) . Html::hidden( 'title', $this->getPageTitle()->getPrefixedText() ) . |
90 | Html::hidden( 'preview', 1 ) . '<table>' |
91 | ); |
92 | $submit = Xml::submitButton( $this->msg( 'translate-statsf-submit' )->text() ); |
93 | $out->addHTML( |
94 | $this->eInput( 'width', $opts ) . $this->eInput( 'height', $opts ) . |
95 | '<tr><td colspan="2"><hr /></td></tr>' . $this->eInput( 'start', $opts, 24 ) . |
96 | $this->eInput( 'days', $opts ) . |
97 | $this->eRadio( 'scale', $opts, [ 'years', 'months', 'weeks', 'days', 'hours' ] ) . |
98 | $this->eRadio( 'count', $opts, $this->dataProvider->getGraphTypes() ) . |
99 | '<tr><td colspan="2"><hr /></td></tr>' . $this->eLanguage( 'language', $opts ) . |
100 | $this->eGroup( 'group', $opts ) . '<tr><td colspan="2"><hr /></td></tr>' . |
101 | '<tr><td colspan="2">' . $submit . '</td></tr>' |
102 | ); |
103 | $out->addHTML( '</table></form></fieldset>' ); |
104 | if ( !$opts['preview'] ) { |
105 | return; |
106 | } |
107 | $spiParams = []; |
108 | foreach ( $opts->getChangedValues() as $key => $v ) { |
109 | if ( $key === 'preview' ) { |
110 | continue; |
111 | } |
112 | if ( is_array( $v ) ) { |
113 | $v = implode( ',', $v ); |
114 | if ( !strlen( $v ) ) { |
115 | continue; |
116 | } |
117 | } |
118 | $spiParams[] = $key . '=' . wfEscapeWikiText( $v ); |
119 | } |
120 | |
121 | $spiParams = $spiParams ? '/' . implode( ';', $spiParams ) : ''; |
122 | |
123 | $titleText = $this->getPageTitle()->getPrefixedText(); |
124 | $out->addHTML( Html::element( 'hr' ) ); |
125 | // Element to render the graph |
126 | $out->addHTML( |
127 | Html::rawElement( |
128 | 'div', |
129 | [ |
130 | 'id' => self::GRAPH_CONTAINER_ID, |
131 | 'style' => 'margin: 2em auto; display: block', |
132 | 'class' => self::GRAPH_CONTAINER_CLASS, |
133 | ] |
134 | ) |
135 | ); |
136 | |
137 | $out->addHTML( |
138 | Html::element( |
139 | 'pre', |
140 | [ 'aria-label' => $this->msg( 'translate-statsf-embed' )->text() ], |
141 | "{{{$titleText}{$spiParams}}}" |
142 | ) |
143 | ); |
144 | } |
145 | |
146 | /// Construct HTML for a table row with label and input in two columns. |
147 | private function eInput( string $name, FormOptions $opts, int $width = 4 ): string { |
148 | $value = $opts[$name]; |
149 | return '<tr><td>' . $this->eLabel( $name ) . '</td><td>' . |
150 | Xml::input( $name, $width, $value, [ 'id' => $name ] ) . '</td></tr>' . "\n"; |
151 | } |
152 | |
153 | /// Construct HTML for a label for option. |
154 | private function eLabel( string $name ): string { |
155 | // Give grep a chance to find the usages: |
156 | // translate-statsf-width, translate-statsf-height, translate-statsf-start, |
157 | // translate-statsf-days, translate-statsf-scale, translate-statsf-count, |
158 | // translate-statsf-language, translate-statsf-group |
159 | $label = 'translate-statsf-' . $name; |
160 | $label = $this->msg( $label )->escaped(); |
161 | return Xml::tags( 'label', [ 'for' => $name ], $label ); |
162 | } |
163 | |
164 | /// Construct HTML for a table row with label and radio input in two columns. |
165 | private function eRadio( string $name, FormOptions $opts, array $alts ): string { |
166 | // Give grep a chance to find the usages: |
167 | // translate-statsf-scale, translate-statsf-count |
168 | $label = 'translate-statsf-' . $name; |
169 | $label = $this->msg( $label )->escaped(); |
170 | $s = '<tr><td>' . $label . '</td><td>'; |
171 | $options = []; |
172 | foreach ( $alts as $alt ) { |
173 | $id = "$name-$alt"; |
174 | $radio = Xml::radio( |
175 | $name, |
176 | $alt, |
177 | $alt === $opts[$name], |
178 | [ 'id' => $id ] |
179 | ) . ' '; |
180 | $options[] = $radio . ' ' . $this->eLabel( $id ); |
181 | } |
182 | $s .= implode( ' ', $options ); |
183 | $s .= '</td></tr>' . "\n"; |
184 | return $s; |
185 | } |
186 | |
187 | /// Construct HTML for a table row with label and language selector in two columns. |
188 | private function eLanguage( string $name, FormOptions $opts ): string { |
189 | $value = implode( ',', $opts[$name] ); |
190 | |
191 | $select = $this->languageSelector(); |
192 | $select->setTargetId( 'language' ); |
193 | return '<tr><td>' . $this->eLabel( $name ) . '</td><td>' . $select->getHtmlAndPrepareJS() . |
194 | '<br />' . Xml::input( $name, 20, $value, [ 'id' => $name ] ) . '</td></tr>' . "\n"; |
195 | } |
196 | |
197 | /// Construct a JavaScript enhanced language selector. |
198 | private function languageSelector(): JsSelectToInput { |
199 | $languages = Utilities::getLanguageNames( $this->getLanguage()->getCode() ); |
200 | ksort( $languages ); |
201 | $selector = new XmlSelect( 'mw-language-selector', 'mw-language-selector' ); |
202 | foreach ( $languages as $code => $name ) { |
203 | $selector->addOption( "$code - $name", $code ); |
204 | } |
205 | return new JsSelectToInput( $selector ); |
206 | } |
207 | |
208 | /// Constructs HTML for a table row with label and group selector in two columns. |
209 | private function eGroup( string $name, FormOptions $opts ): string { |
210 | $value = implode( ',', $opts[$name] ); |
211 | |
212 | $select = $this->groupSelector(); |
213 | $select->setTargetId( 'group' ); |
214 | return '<tr><td>' . $this->eLabel( $name ) . '</td><td>' . $select->getHtmlAndPrepareJS() . |
215 | '<br />' . Xml::input( $name, 20, $value, [ 'id' => $name ] ) . '</td></tr>' . "\n"; |
216 | } |
217 | |
218 | /// Construct a JavaScript enhanced group selector. |
219 | private function groupSelector(): JsSelectToInput { |
220 | $groups = MessageGroups::singleton()->getGroups(); |
221 | foreach ( $groups as $key => $group ) { |
222 | if ( !$group->exists() ) { |
223 | unset( $groups[$key] ); |
224 | } |
225 | } |
226 | ksort( $groups ); |
227 | $selector = new XmlSelect( 'mw-group-selector', 'mw-group-selector' ); |
228 | foreach ( $groups as $code => $name ) { |
229 | $selector->addOption( $name->getLabel(), $code ); |
230 | } |
231 | return new JsSelectToInput( $selector ); |
232 | } |
233 | |
234 | private function embed( FormOptions $opts ): string { |
235 | $this->getOutput()->addModules( 'ext.translate.translationstats.embedded' ); |
236 | return Html::rawElement( |
237 | 'div', |
238 | [ |
239 | 'class' => self::GRAPH_CONTAINER_CLASS, |
240 | ], |
241 | Html::hidden( |
242 | 'translationStatsGraphOptions', |
243 | json_encode( $opts->getAllValues() ) |
244 | ) |
245 | ); |
246 | } |
247 | } |