Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 93 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 1 |
TipNodeRenderer | |
0.00% |
0 / 93 |
|
0.00% |
0 / 11 |
992 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setMessageLocalizer | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
render | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
buildHtml | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
72 | |||
getBaseCssClasses | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
mainAndTextRender | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
getMessageKeyWithVariantFallback | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 | |||
graphicRender | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
12 | |||
getImageSourcePath | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
exampleRender | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
6 | |||
getMessageParameters | |
0.00% |
0 / 21 |
|
0.00% |
0 / 1 |
56 |
1 | <?php |
2 | |
3 | namespace GrowthExperiments\HelpPanel\Tips; |
4 | |
5 | use LogicException; |
6 | use MediaWiki\Html\Html; |
7 | use MediaWiki\Output\OutputPage; |
8 | use MessageLocalizer; |
9 | use OOUI\IconWidget; |
10 | |
11 | /** |
12 | * Transform an array of TipNodes into an array of rendered HTML. |
13 | */ |
14 | class TipNodeRenderer { |
15 | /** |
16 | * @var MessageLocalizer |
17 | */ |
18 | private $messageLocalizer; |
19 | |
20 | /** |
21 | * @var string |
22 | */ |
23 | private $extensionAssetsPath; |
24 | |
25 | /** |
26 | * @param string $extensionAssetsPath |
27 | */ |
28 | public function __construct( string $extensionAssetsPath ) { |
29 | $this->extensionAssetsPath = $extensionAssetsPath; |
30 | } |
31 | |
32 | /** |
33 | * @param MessageLocalizer $messageLocalizer |
34 | */ |
35 | public function setMessageLocalizer( MessageLocalizer $messageLocalizer ) { |
36 | $this->messageLocalizer = $messageLocalizer; |
37 | } |
38 | |
39 | /** |
40 | * Render a set of tip nodes into HTML. |
41 | * |
42 | * This method is called recursively as the TipNode tree is rendered. |
43 | * |
44 | * @param TipNode[] $nodes |
45 | * @param string $skinName |
46 | * @param string $dir |
47 | * @return array |
48 | * An array of rendered HTML. |
49 | */ |
50 | public function render( array $nodes, string $skinName, string $dir ): array { |
51 | OutputPage::setupOOUI( $skinName, $dir ); |
52 | return array_values( array_map( function ( $node ) use ( $skinName, $dir ) { |
53 | return $this->buildHtml( $node, $skinName, $dir ); |
54 | }, $nodes ) ); |
55 | } |
56 | |
57 | /** |
58 | * @param TipNode $node |
59 | * @param string $skin |
60 | * @param string $dir |
61 | * @return string |
62 | */ |
63 | private function buildHtml( TipNode $node, string $skin, string $dir ): string { |
64 | switch ( $node->getType() ) { |
65 | case 'header': |
66 | case 'main': |
67 | case 'main-multiple': |
68 | case 'text': |
69 | return $this->mainAndTextRender( $node, $skin ); |
70 | case 'graphic': |
71 | return $this->graphicRender( $node, $dir ); |
72 | case 'example': |
73 | return $this->exampleRender( $node ); |
74 | default: |
75 | throw new LogicException( $node->getType() . 'is not a valid tip type ID.' ); |
76 | } |
77 | } |
78 | |
79 | /** |
80 | * @param string $tipTypeId |
81 | * @param array $textVariants |
82 | * @return array|string[] |
83 | */ |
84 | private function getBaseCssClasses( string $tipTypeId, array $textVariants = [] ): array { |
85 | $variants = array_map( static function ( $variant ) { |
86 | return 'growthexperiments-quickstart-tips-tip--' . $variant; |
87 | }, $textVariants ); |
88 | return array_merge( [ |
89 | 'growthexperiments-quickstart-tips-tip', |
90 | 'growthexperiments-quickstart-tips-tip-' . $tipTypeId |
91 | ], $variants ); |
92 | } |
93 | |
94 | /** |
95 | * @param TipNode $node |
96 | * @param string $skinName |
97 | * @return string |
98 | */ |
99 | private function mainAndTextRender( TipNode $node, string $skinName ): string { |
100 | $tipTextVariants = array_values( array_map( static function ( $item ) { |
101 | if ( $item['type'] == TipTree::TIP_DATA_TYPE_TEXT_VARIANT ) { |
102 | return $item['data']; |
103 | } |
104 | return null; |
105 | }, $node->getData() ) ); |
106 | |
107 | return Html::rawElement( 'div', [ |
108 | 'class' => $this->getBaseCssClasses( $node->getType(), $tipTextVariants ) |
109 | ], $this->messageLocalizer->msg( |
110 | $this->getMessageKeyWithVariantFallback( $node ), $this->getMessageParameters( $node, $skinName ) |
111 | )->parse() ); |
112 | } |
113 | |
114 | /** |
115 | * Obtain a message key for use with Message. |
116 | * |
117 | * This is usually determined by TipLoader, which finds a i18n key based |
118 | * on the current editor, skin, task type and tip type. But this method |
119 | * allows for overriding with a variant in the event the TipNode specifies |
120 | * a title type but the value for that title is not present. |
121 | * |
122 | * @param TipNode $node |
123 | * @return string |
124 | */ |
125 | private function getMessageKeyWithVariantFallback( TipNode $node ): string { |
126 | $messageKey = $node->getMessageKey(); |
127 | $messageKeyVariant = current( array_filter( array_map( static function ( $nodeConfig ) { |
128 | // This could be more flexible, but as we don't have a use |
129 | // case yet, leaving as is for now. |
130 | if ( $nodeConfig['type'] === TipTree::TIP_DATA_TYPE_TITLE && |
131 | !$nodeConfig['data']['title'] ) { |
132 | return $nodeConfig['data']['messageKeyVariant'] ?? []; |
133 | } |
134 | return []; |
135 | }, $node->getData() ) ) ); |
136 | if ( $messageKeyVariant ) { |
137 | $messageKey .= $messageKeyVariant; |
138 | } |
139 | return $messageKey; |
140 | } |
141 | |
142 | /** |
143 | * @param TipNode $node |
144 | * @param string $dir |
145 | * @return string |
146 | */ |
147 | private function graphicRender( TipNode $node, string $dir ): string { |
148 | if ( !$node->getData()[0]['type'] || $node->getData()[0]['type'] !== 'image' ) { |
149 | return ''; |
150 | } |
151 | return Html::rawElement( 'img', [ |
152 | 'class' => $this->getBaseCssClasses( $node->getType() ), |
153 | 'src' => $this->getImageSourcePath( |
154 | $node->getData()[0]['data']['filename'], |
155 | $node->getData()[0]['data']['suffix'], |
156 | $dir |
157 | ), |
158 | // Leaving alt blank per T245786#6115403; screen readers |
159 | // should ignore this decorative image. |
160 | 'alt' => '', |
161 | ] ); |
162 | } |
163 | |
164 | /** |
165 | * @param string $filename |
166 | * @param string $suffix |
167 | * @param string $dir |
168 | * @return string |
169 | */ |
170 | private function getImageSourcePath( string $filename, string $suffix, string $dir ): string { |
171 | return $this->extensionAssetsPath . '/GrowthExperiments/images/' . |
172 | $filename . '-' . $dir . '.' . $suffix; |
173 | } |
174 | |
175 | /** |
176 | * @param TipNode $node |
177 | * @return string |
178 | */ |
179 | private function exampleRender( TipNode $node ): string { |
180 | $exampleLabelKey = $node->getData()[0]['data']['labelKey'] ?? null; |
181 | $exampleLabel = $exampleLabelKey ? |
182 | Html::element( 'div', |
183 | [ 'class' => 'growthexperiments-quickstart-tips-tip-example-label' ], |
184 | $this->messageLocalizer->msg( $exampleLabelKey )->text() ) |
185 | : ''; |
186 | return $exampleLabel . Html::rawElement( 'div', [ |
187 | 'class' => $this->getBaseCssClasses( $node->getType() ) |
188 | ], |
189 | Html::rawElement( 'p', [ |
190 | 'class' => [ |
191 | 'growthexperiments-quickstart-tips-tip-' . $node->getType() . '-text' |
192 | ] ], $this->messageLocalizer->msg( |
193 | $this->getMessageKeyWithVariantFallback( $node ) |
194 | )->parse() ) ); |
195 | } |
196 | |
197 | /** |
198 | * @param TipNode $node |
199 | * @param string $skinName |
200 | * @return array |
201 | */ |
202 | private function getMessageParameters( TipNode $node, string $skinName ): array { |
203 | return array_filter( array_map( function ( $nodeConfig ) use ( $skinName ) { |
204 | switch ( $nodeConfig['type'] ) { |
205 | case TipTree::TIP_DATA_TYPE_PLAIN_MESSAGE: |
206 | $parameterMessageKey = $nodeConfig['variant'][$skinName]['data'] ?? $nodeConfig['data']; |
207 | return $this->messageLocalizer->msg( $parameterMessageKey )->plain(); |
208 | case TipTree::TIP_DATA_TYPE_OOUI_ICON: |
209 | $iconConfig = [ |
210 | 'icon' => $nodeConfig['data']['icon'], |
211 | 'framed' => $nodeConfig['data']['framed'] ?? true |
212 | ]; |
213 | if ( isset( $nodeConfig['data']['labelKey'] ) ) { |
214 | $iconConfig['label'] = $this->messageLocalizer->msg( |
215 | $nodeConfig['data']['labelKey'] |
216 | )->plain(); |
217 | } |
218 | return new IconWidget( $iconConfig ); |
219 | case TipTree::TIP_DATA_TYPE_TITLE: |
220 | return $nodeConfig['data']['title']; |
221 | case TipTree::TIP_DATA_TYPE_TEXT_VARIANT: |
222 | return null; |
223 | default: |
224 | throw new LogicException( $nodeConfig['type'] . ' is not supported' ); |
225 | } |
226 | }, $node->getData() ) ); |
227 | } |
228 | |
229 | } |