Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 175
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
CollaborationKitImage
0.00% covered (danger)
0.00%
0 / 175
0.00% covered (danger)
0.00%
0 / 5
1406
0.00% covered (danger)
0.00%
0 / 1
 makeImage
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
240
 makeImageFromFile
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 1
132
 makeImageFromIcon
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
 linkFactory
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 getCannedIcons
0.00% covered (danger)
0.00%
0 / 59
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/**
4 * Helper class to produce HTML elements containing images for CollaborationKit
5 * purposes.
6 *
7 * @file
8 */
9
10use MediaWiki\MediaWikiServices;
11
12class CollaborationKitImage {
13    /**
14     * Generate an image element from the wiki or the extension
15     *
16     * @param string|null $image The filename (no namespace prefix) or
17     *  CollaborationKit icon identifier (or null to use fallback instead)
18     * @param int $width The width of the image in pixels
19     * @param array $options An array with optional parameters:
20     * - array $options['classes'] Array of element classes to assign
21     * - Title|string|bool $options['link'] Internal link for the image;
22     *  default is true (i.e. link to its description page). Pass `false` for no
23     *  link at all. Pass a string to link to a page in the manner of an
24     *  internal wiki link.
25     * - string $options['colour'] The colour of the icon if using a canned icon
26     * - string $options['css'] In-line style parameters. Avoid if possible.
27     * - bool $options['renderAsWikitext'] Should the output be wikitext
28     *  instead of HTML? Defaults to false.
29     * - string $options['label'] Label to put under image; used for ToC icons
30     * - string $options['fallback'] If the specified image is null or
31     *  doesn't exist. Valid options are 'none', a valid icon ID, or an arbitrary
32     *  string to use a seed. (Note: if you specify a label, then that will
33     *  serve as the fallback.)
34     * - bool $options['optimizeForSquare'] Fetch an image such that it's
35     *  ideal for shoving into a square frame. Default is false. Images with
36     *  labels always get optimzied for squares.
37     * @return string HTML elements or wikitext, depending on
38     *  $options['renderAsWikitext']
39     */
40    public static function makeImage( $image, $width, $options = [] ) {
41        $cannedIcons = self::getCannedIcons();
42
43        // Setting up options
44        $classes = $options['classes'] ?? [];
45        $link = $options['link'] ?? true;
46        $colour = $options['colour'] ?? '';
47        $css = $options['css'] ?? '';
48        $renderAsWikitext = $options['renderAsWikitext'] ?? false;
49        $optimizeForSquare = $options['optimizeForSquare'] ?? false;
50        $label = $options['label'] ?? '';
51
52        if ( !isset( $options['fallback'] ) ) {
53            if ( isset( $options['label'] ) ) {
54                $options['fallback'] = $options['label'];
55            } else {
56                $options['fallback'] = 'none';
57            }
58        }
59
60        // If image doesn't exist or is an icon, this will return false.
61        $imageObj = MediaWikiServices::getInstance()->getRepoGroup()->findFile( $image );
62
63        // Use fallback icon or random icon if stated image doesn't exist
64        if ( $image === null
65            || $image == ''
66            || ( $imageObj === false && !in_array( $image, $cannedIcons ) )
67        ) {
68            if ( $options['fallback'] == 'none' ) {
69                return '';
70            } elseif ( in_array( $options['fallback'], $cannedIcons ) ) {
71                $image = $options['fallback'];
72            } else {
73                $image = $cannedIcons[hexdec( sha1( $options['fallback'] )[0] )
74                    % count( $cannedIcons )];
75            }
76        }
77
78        $imageCode = '';
79        // Are we loading an image file or constructing a div based on an icon class?
80        if ( $imageObj !== false ) {
81            $squareAdjustmentAxis = null;
82            if ( $optimizeForSquare || $label != '' ) {
83                $fullHeight = $imageObj->getHeight();
84                $fullWidth = $imageObj->getWidth();
85                $ratio = $fullWidth / $fullHeight;  // get ratio of width to height
86                if ( $ratio > 1 ) {
87                    $squareAdjustmentAxis = 'x';
88                } elseif ( $ratio < 1 ) {
89                    $squareAdjustmentAxis = 'y';
90                }
91                // If image is a perfect square (ratio == 1) nothing needs to be done
92            }
93            $imageCode = self::makeImageFromFile( $imageObj, $width, $link,
94                $renderAsWikitext, $label, $squareAdjustmentAxis );
95        } elseif ( in_array( $image, $cannedIcons ) ) {
96            $imageCode = self::makeImageFromIcon( $image, $width, $colour, $link,
97                $renderAsWikitext, $label );
98        }
99
100        // Finishing up
101        $wrapperAttributes = [ 'class' => $classes, 'style' => $css ];
102        $imageBlock = Html::rawElement( 'div', $wrapperAttributes, $imageCode );
103        return $imageBlock;
104    }
105
106    /**
107     * @param File $imageObj
108     * @param int $width
109     * @param string $link
110     * @param bool $renderAsWikitext
111     * @param string $label
112     * @param string|null $squareAdjustmentAxis x or y
113     * @return string
114     */
115    protected static function makeImageFromFile( $imageObj, $width, $link,
116        $renderAsWikitext, $label, $squareAdjustmentAxis
117    ) {
118        // This assumes that colours cannot be assigned to images.
119        // This is currently true, but who knows what the future might hold!
120
121        $parser = MediaWikiServices::getInstance()->getParser();
122
123        $imageTitle = $imageObj->getTitle();
124        $imageFullName = $imageTitle->getFullText();
125
126        if ( $squareAdjustmentAxis == 'x' ) {
127            $widthText = $width;
128        } else {
129            $widthText = 'x' . $width;  // i.e. "x64px"
130        }
131
132        $wikitext = "[[{$imageFullName}|{$widthText}px";
133
134        if ( $link === false || $label != '' ) {
135            $wikitext .= '|link=]]';
136        } elseif ( is_string( $link ) ) {
137            $wikitext .= "|link={$link}]]";
138        } else {
139            $wikitext .= ']]';
140        }
141
142        if ( $renderAsWikitext ) {
143            if ( $squareAdjustmentAxis !== null ) {
144                // We need another <div> wrapper to add the margin offsets
145                // The main one is below
146
147                $fullHeight = $imageObj->getHeight();
148                $fullWidth = $imageObj->getWidth();
149                $squareWrapperCss = '';
150
151                if ( $squareAdjustmentAxis == 'y' ) {
152                    $adjustedWidth = ( $fullWidth * $width ) / $fullHeight;
153                    $offset = ceil( -1 * ( ( $adjustedWidth ) - $width ) / 2 );
154                } elseif ( $squareAdjustmentAxis == 'x' ) {
155                    $adjustedHeight = ( $fullHeight * $width ) / $fullWidth;
156                    $offset = ceil( -1 * ( ( $adjustedHeight ) - $width ) / 2 );
157                    $squareWrapperCss = "margin-top:{$offset}px";
158                }
159
160                $wikitext = Html::rawElement(
161                    'div',
162                    [
163                        'class' => 'mw-ck-file-image-squareoptimized',
164                        'style' => $squareWrapperCss
165                    ],
166                    $wikitext
167                );
168            }
169            return $wikitext;
170        } else {
171            $imageHtml = $parser->parse( $wikitext, $imageTitle,
172                ParserOptions::newFromAnon() )->getText();
173
174            if ( $label != '' ) {
175                $imageHtml = Html::rawElement(
176                    'div',
177                    [ 'class' => 'mw-ck-file-image' ],
178                    $imageHtml
179                );
180                if ( $link !== false ) {
181                    $imageHtml = self::linkFactory( $imageHtml, $link, $label,
182                        $imageObj
183                    );
184                }
185            }
186
187            return $imageHtml;
188        }
189    }
190
191    /**
192     * @param string $image
193     * @param int $width
194     * @param string $colour
195     * @param string $link
196     * @param bool $renderAsWikitext
197     * @param string $label
198     * @return string
199     */
200    protected static function makeImageFromIcon( $image, $width, $colour, $link,
201        $renderAsWikitext, $label
202    ) {
203        // Rendering as wikitext with link is not an option here due to Tidy.
204
205        $imageClasses = [ 'mw-ck-icon' ];
206        if ( $colour != '' && $colour != 'black' ) {
207            $imageClasses[] = 'mw-ck-icon-' . $image . '-' . $colour;
208        } else {
209            $imageClasses[] = 'mw-ck-icon-' . $image;
210        }
211
212        $imageHtml = Html::rawElement(
213            'div',
214            [
215                'class' => $imageClasses,
216                'style' => "width: {$width}px; height: {$width}px;"
217            ],
218            ''
219        );
220
221        if ( !$renderAsWikitext && $link !== false ) {
222            $imageHtml = self::linkFactory( $imageHtml, $link, $label );
223        }
224
225        return $imageHtml;
226    }
227
228    /**
229     * @param string $imageHtml
230     * @param Title|string $link
231     * @param string $label
232     * @param null|File $imageObj
233     * @return string
234     */
235    protected static function linkFactory( $imageHtml, $link, $label,
236        $imageObj = null
237    ) {
238        // Important assumption: image is being rendered as HTML and not wikitext.
239        if ( $link instanceof Title ) {
240            $linkHref = $link->getLinkURL();
241        } elseif ( is_string( $link ) ) {
242            $linkHref = Title::newFromText( $link )->getLinkURL();
243        } elseif ( $imageObj !== null ) {
244            $linkHref = $imageObj->getTitle()->getLinkURL();
245        } else {
246            $linkHref = '#';
247        }
248
249        if ( $label != '' ) {
250            $imageHtml .= Html::rawElement(
251                'span',
252                [ 'class' => 'mw-ck-toc-item-label' ],
253                $label
254            );
255        }
256        return Html::rawElement( 'a', [ 'href' => $linkHref ], $imageHtml );
257    }
258
259    /**
260     * @return array All the canned icons in CollaborationKit
261     */
262    public static function getCannedIcons() {
263        // Keep this synced with the icons listed in the module in extension.json
264        return [
265            // Randomly selectable items
266            'book',
267            'circlestar',
268            'clock',
269            'community',
270            'contents',
271            'die',
272            'edit',
273            'eye',
274            'flag',
275            'funnel',
276            'gear',
277            'heart',
278            'journal',
279            'key',
280            'link',
281            'map',
282            'menu',
283            'newspaper',
284            'ol',
285            'page',
286            'paperclip',
287            'puzzlepiece',
288            'ribbon',
289            'rocket',
290            'star',
291            'sun',
292            'ul',
293
294            'addimage',
295            'addmapmarker',
296            'addquote',
297            'bell',
298            'circleline',
299            'circletriangle',
300            'circlex',
301            'discussion',
302            'download',
303            'editprotected',
304            'gallery',
305            'image',
306            'lock',
307            'mail',
308            'mapmarker',
309            'message',
310            'messagenew',
311            'messagescary',
312            'move',
313            'nowiki',
314            'pagechecked',
315            'pageribbon',
316            'pagesearch',
317            'print',
318            'quotes',
319            'search',
320            'starmenu',
321            'translate',
322            'trash',
323            'user'
324        ];
325    }
326}