Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
62.64% |
57 / 91 |
|
40.00% |
2 / 5 |
CRAP | |
0.00% |
0 / 1 |
SkinComponentLink | |
62.64% |
57 / 91 |
|
40.00% |
2 / 5 |
113.31 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
msg | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
makeLink | |
57.58% |
38 / 66 |
|
0.00% |
0 / 1 |
67.98 | |||
applyLinkTitleAttribs | |
73.68% |
14 / 19 |
|
0.00% |
0 / 1 |
13.21 | |||
getTemplateData | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | */ |
18 | |
19 | namespace MediaWiki\Skin; |
20 | |
21 | use MediaWiki\Html\Html; |
22 | use MediaWiki\Linker\Linker; |
23 | use MediaWiki\Message\Message; |
24 | use MessageLocalizer; |
25 | |
26 | /** |
27 | * @internal for use inside Skin and SkinTemplate classes only |
28 | * @unstable |
29 | */ |
30 | class SkinComponentLink implements SkinComponent { |
31 | /** @var string */ |
32 | private $key; |
33 | /** @var array */ |
34 | private $item; |
35 | /** @var array */ |
36 | private $options; |
37 | /** @var MessageLocalizer */ |
38 | private $localizer; |
39 | |
40 | /** |
41 | * @param string $key |
42 | * @param array $item |
43 | * @param MessageLocalizer $localizer |
44 | * @param array $options |
45 | */ |
46 | public function __construct( string $key, array $item, MessageLocalizer $localizer, array $options = [] ) { |
47 | $this->key = $key; |
48 | $this->item = $item; |
49 | $this->localizer = $localizer; |
50 | $this->options = $options; |
51 | } |
52 | |
53 | private function msg( string $key ): Message { |
54 | return $this->localizer->msg( $key ); |
55 | } |
56 | |
57 | /** |
58 | * Makes a link, usually used by makeListItem to generate a link for an item |
59 | * in a list used in navigation lists, portlets, portals, sidebars, etc... |
60 | * |
61 | * @param string $key Usually a key from the list you are generating this |
62 | * link from. |
63 | * @param array $item Contains some of a specific set of keys. |
64 | * |
65 | * The text of the link will be generated either from the contents of the |
66 | * "text" key in the $item array, if a "msg" key is present a message by |
67 | * that name will be used, and if neither of those are set the $key will be |
68 | * used as a message name. Escaping is handled by this method. |
69 | * |
70 | * If a "href" key is not present makeLink will just output htmlescaped text. |
71 | * The "href", "id", "class", "rel", and "type" keys are used as attributes |
72 | * for the link if present. |
73 | * |
74 | * If an "id" or "single-id" (if you don't want the actual id to be output |
75 | * on the link) is present it will be used to generate a tooltip and |
76 | * accesskey for the link. |
77 | * |
78 | * The 'link-html' key can be used to prepend additional HTML inside the link HTML. |
79 | * For example to prepend an icon. |
80 | * |
81 | * The keys "context" and "primary" are ignored; these keys are used |
82 | * internally by skins and are not supposed to be included in the HTML |
83 | * output. |
84 | * |
85 | * If you don't want an accesskey, set $item['tooltiponly'] = true; |
86 | * |
87 | * If a "data" key is present, it must be an array, where the keys represent |
88 | * the data-xxx properties with their provided values. For example, |
89 | * $item['data'] = [ |
90 | * 'foo' => 1, |
91 | * 'bar' => 'baz', |
92 | * ]; |
93 | * will render as element properties: |
94 | * data-foo='1' data-bar='baz' |
95 | * |
96 | * The "class" key currently accepts both a string and an array of classes, but this will be |
97 | * changed to only accept an array in the future. |
98 | * |
99 | * @param array $options Can be used to affect the output of a link. |
100 | * Possible options are: |
101 | * - 'class-as-property' key to specify that class attribute should be |
102 | * not be included in array-attributes. |
103 | * - 'text-wrapper' key to specify a list of elements to wrap the text of |
104 | * a link in. This should be an array of arrays containing a 'tag' and |
105 | * optionally an 'attributes' key. If you only have one element you don't |
106 | * need to wrap it in another array. eg: To use <a><span>...</span></a> |
107 | * in all links use [ 'text-wrapper' => [ 'tag' => 'span' ] ] |
108 | * for your options. |
109 | * - 'link-class' key can be used to specify additional classes to apply |
110 | * to all links. |
111 | * - 'link-fallback' can be used to specify a tag to use instead of "<a>" |
112 | * if there is no link. eg: If you specify 'link-fallback' => 'span' than |
113 | * any non-link will output a "<span>" instead of just text. |
114 | * |
115 | * @return array Associated array with the following keys: |
116 | * - html: HTML string |
117 | * - array-attributes: HTML attributes as array of objects: |
118 | * - key: Attribute name ex: 'href', 'class', 'id', ... |
119 | * - value: Attribute value |
120 | * NOTE: if options['class-as-property'] is set, class will not be included in the list. |
121 | * - text: Text of the link |
122 | * - class: Class of the link |
123 | */ |
124 | private function makeLink( $key, $item, $options = [] ) { |
125 | $html = $item['html'] ?? null; |
126 | $icon = $item['icon'] ?? null; |
127 | if ( $html ) { |
128 | return [ |
129 | 'html' => $html |
130 | ]; |
131 | } |
132 | $text = $item['text'] ?? $this->msg( $item['msg'] ?? $key )->text(); |
133 | |
134 | $html = htmlspecialchars( $text ); |
135 | $isLink = isset( $item['href'] ) || isset( $options['link-fallback'] ); |
136 | |
137 | if ( $html !== '' && isset( $options['text-wrapper'] ) ) { |
138 | $wrapper = $options['text-wrapper']; |
139 | if ( isset( $wrapper['tag'] ) ) { |
140 | $wrapper = [ $wrapper ]; |
141 | } |
142 | while ( count( $wrapper ) > 0 ) { |
143 | $element = array_pop( $wrapper ); |
144 | '@phan-var array $element'; |
145 | |
146 | $attrs = $element['attributes'] ?? []; |
147 | // Apply title attribute to the outermost wrapper if there is |
148 | // no link wrapper. No need for an accesskey. |
149 | if ( count( $wrapper ) === 0 && !$isLink ) { |
150 | $this->applyLinkTitleAttribs( |
151 | $item, |
152 | false, |
153 | $attrs |
154 | ); |
155 | } |
156 | $html = Html::rawElement( $element['tag'], $attrs, $html ); |
157 | } |
158 | } |
159 | |
160 | $attrs = []; |
161 | $linkHtmlAttributes = []; |
162 | $classAsProperty = $options['class-as-property'] ?? false; |
163 | if ( $isLink ) { |
164 | $attrs = $item; |
165 | foreach ( [ |
166 | 'single-id', 'text', 'msg', 'tooltiponly', 'context', 'primary', |
167 | // These fields provide context for skins to modify classes. |
168 | // They should not be outputted to skin. |
169 | 'icon', 'button', |
170 | 'tooltip-params', 'exists', 'link-html' ] as $k |
171 | ) { |
172 | unset( $attrs[$k] ); |
173 | } |
174 | |
175 | if ( isset( $attrs['data'] ) ) { |
176 | foreach ( $attrs['data'] as $key => $value ) { |
177 | if ( $value === null ) { |
178 | continue; |
179 | } |
180 | $attrs[ 'data-' . $key ] = $value; |
181 | } |
182 | unset( $attrs[ 'data' ] ); |
183 | } |
184 | $this->applyLinkTitleAttribs( $item, true, $attrs ); |
185 | $class = $attrs['class'] ?? []; |
186 | if ( isset( $options['link-class'] ) ) { |
187 | $class = SkinComponentUtils::addClassToClassList( |
188 | $class, $options['link-class'] |
189 | ); |
190 | } |
191 | $attrs['class'] = is_array( $class ) ? implode( ' ', $class ) : $class; |
192 | foreach ( $attrs as $key => $value ) { |
193 | if ( $value === null ) { |
194 | continue; |
195 | } |
196 | if ( $classAsProperty && $key === 'class' ) { |
197 | continue; |
198 | } |
199 | $linkHtmlAttributes[] = [ 'key' => $key, 'value' => $value ]; |
200 | } |
201 | |
202 | if ( isset( $item['link-html'] ) ) { |
203 | $html = $item['link-html'] . ' ' . $html; |
204 | } |
205 | |
206 | $html = Html::rawElement( isset( $attrs['href'] ) |
207 | ? 'a' |
208 | : $options['link-fallback'], $attrs, $html ); |
209 | } |
210 | $data = [ |
211 | 'html' => $html, |
212 | 'icon' => $icon, |
213 | 'array-attributes' => count( $linkHtmlAttributes ) > 0 ? $linkHtmlAttributes : null, |
214 | 'text' => trim( $text ), |
215 | ]; |
216 | if ( $classAsProperty ) { |
217 | $data['class'] = $attrs['class'] ?? ''; |
218 | } |
219 | return $data; |
220 | } |
221 | |
222 | /** |
223 | * Helper for makeLink(). Add tooltip and accesskey attributes to $attrs |
224 | * according to the input item array. |
225 | * |
226 | * @param array $item |
227 | * @param bool $allowAccessKey |
228 | * @param array &$attrs |
229 | */ |
230 | private function applyLinkTitleAttribs( $item, $allowAccessKey, &$attrs ) { |
231 | $tooltipId = $item['single-id'] ?? $item['id'] ?? null; |
232 | if ( $tooltipId === null ) { |
233 | return; |
234 | } |
235 | $tooltipParams = $item['tooltip-params'] ?? []; |
236 | $tooltipOption = isset( $item['exists'] ) && $item['exists'] === false ? 'nonexisting' : null; |
237 | |
238 | if ( !$allowAccessKey || !empty( $item['tooltiponly'] ) ) { |
239 | $title = Linker::titleAttrib( $tooltipId, $tooltipOption, $tooltipParams ); |
240 | if ( $title !== false ) { |
241 | $attrs['title'] = $title; |
242 | } |
243 | } else { |
244 | $tip = Linker::tooltipAndAccesskeyAttribs( |
245 | $tooltipId, |
246 | $tooltipParams, |
247 | $tooltipOption, |
248 | $this->localizer |
249 | ); |
250 | if ( isset( $tip['title'] ) && $tip['title'] !== false ) { |
251 | $attrs['title'] = $tip['title']; |
252 | } |
253 | if ( isset( $tip['accesskey'] ) && $tip['accesskey'] !== false ) { |
254 | $attrs['accesskey'] = $tip['accesskey']; |
255 | } |
256 | } |
257 | } |
258 | |
259 | /** |
260 | * @inheritDoc |
261 | */ |
262 | public function getTemplateData(): array { |
263 | return $this->makeLink( $this->key, $this->item, $this->options ); |
264 | } |
265 | } |