1<?php
2
3namespace dokuwiki\plugin\gallery\classes;
4
5class XHTMLFormatter extends BasicFormatter
6{
7    // region Main Render Functions
8
9    /** @inheritdoc */
10    public function render(AbstractGallery $gallery)
11    {
12        $attr = [
13            'id' => 'plugin__gallery_' . $this->options->galleryID,
14            'class' => 'plugin-gallery',
15        ];
16
17        switch ($this->options->align) {
18            case Options::ALIGN_FULL:
19                $attr['class'] .= ' align-full';
20                break;
21            case Options::ALIGN_LEFT:
22                $attr['class'] .= ' align-left';
23                break;
24            case Options::ALIGN_RIGHT:
25                $attr['class'] .= ' align-right';
26                break;
27            case Options::ALIGN_CENTER:
28                $attr['class'] .= ' align-center';
29                break;
30        }
31
32        $this->renderer->doc .= '<div ' . buildAttributes($attr, true) . '>';
33        $images = $gallery->getImages();
34        $pages = $this->paginate($images);
35        foreach ($pages as $page => $images) {
36            $this->renderPage($images, $page);
37        }
38        $this->renderPageSelector($pages);
39        $this->renderer->doc .= '</div>';
40    }
41
42    /**
43     * Render the page selector
44     *
45     * @param $pages
46     * @return void
47     */
48    protected function renderPageSelector($pages)
49    {
50        if (count($pages) <= 1) return;
51
52        $plugin = plugin_load('syntax', 'gallery_main');
53
54        $this->renderer->doc .= '<div class="gallery-page-selector">';
55        $this->renderer->doc .= '<span>' . $plugin->getLang('pages') . ' </span>';
56        foreach (array_keys($pages) as $pid) {
57            $this->renderer->doc .= sprintf(
58                '<a href="#gallery__%s_%s">%d</a> ',
59                $this->options->galleryID,
60                $pid,
61                $pid + 1
62            );
63        }
64        $this->renderer->doc .= '</div>';
65    }
66
67    /**
68     * Render the given images into a gallery page
69     *
70     * @param Image[] $images
71     * @param int $page The page number
72     * @return void
73     */
74    protected function renderPage($images, int $page)
75    {
76        $attr = [
77            'class' => 'gallery-page',
78            'id' => 'gallery__' . $this->options->galleryID . '_' . $page,
79        ];
80
81        // define the grid
82        $colwidth = $this->options->thumbnailWidth . 'px';
83        if ($this->options->columns) {
84            $cols = $this->options->columns;
85            if ($this->options->align === Options::ALIGN_FULL) {
86                $colwidth = '1fr';
87            } else {
88                // calculate the max width for each column
89                $maxwidth = '(100% / ' . $this->options->columns . ') - 1em';
90                $colwidth = 'min(' . $colwidth . ', ' . $maxwidth . ')';
91            }
92        } else {
93            $cols = 'auto-fill';
94            $colwidth = 'minmax(' . $colwidth . ', 1fr)';
95        }
96        $attr['style'] = 'grid-template-columns: repeat(' . $cols . ', ' . $colwidth . ')';
97
98        $this->renderer->doc .= '<div ' . buildAttributes($attr) . '>';
99        foreach ($images as $image) {
100            $this->renderImage($image);
101        }
102        $this->renderer->doc .= '</div>';
103    }
104
105    /** @inheritdoc */
106    protected function renderImage(Image $image)
107    {
108        global $ID;
109
110        // thumbnail image properties
111        [$w, $h] = $this->getThumbnailSize($image, 2);
112
113        $img = [];
114        $img['width'] = $w;
115        $img['height'] = $h;
116        $img['src'] = ml($image->getSrc(), ['w' => $w, 'h' => $h], true, '&');
117        $img['alt'] = $image->getFilename();
118        $img['loading'] = 'lazy';
119
120        // link properties
121        $a = [];
122        $a['href'] = $this->getDetailLink($image);
123        $a['title'] = $image->getTitle();
124
125        if ($this->options->lightbox) {
126            // double escape for lightbox:
127            $a['data-caption'] = implode(' &ndash; ', array_filter([
128                '<b>' . hsc($image->getTitle()) . '</b>',
129                hsc($image->getDescription())
130            ]));
131            $a['class'] = "lightbox JSnocheck";
132            $a['rel'] = 'lightbox[gal-' . substr(md5($ID), 4) . ']'; //unique ID all images on the same page
133            $a['data-url'] = $this->getLightboxLink($image);
134        }
135
136        // figure properties
137        $fig = [];
138        $fig['class'] = 'gallery-image';
139        if ($this->options->align !== Options::ALIGN_FULL) {
140            $fig['style'] = 'max-width: ' . $this->options->thumbnailWidth . 'px; ';
141        }
142
143        $html = '<figure ' . buildAttributes($fig, true) . '>';
144        $html .= '<a ' . buildAttributes($a, true) . '>';
145        $html .= '<img ' . buildAttributes($img, true) . ' />';
146        $html .= '</a>';
147
148        if ($this->options->showtitle || $this->options->showname) {
149            $html .= '<figcaption>';
150            if ($this->options->showtitle) {
151                $a = [
152                    'href' => $this->getDetailLink($image),
153                    'class' => 'gallery-title',
154                    'title' => $image->getTitle(),
155                ];
156                $html .= '<a ' . buildAttributes($a) . '>' . hsc($image->getTitle()) . '</a>';
157            }
158            if ($this->options->showcaption) {
159                $p = [
160                    'class' => 'gallery-caption',
161                ];
162                $html .= '<div ' . buildAttributes($p) . '>' . hsc($image->getDescription()) . '</div>';
163            }
164            if ($this->options->showname) {
165                $a = [
166                    'href' => $this->getDetailLink($image),
167                    'class' => 'gallery-filename',
168                    'title' => $image->getFilename(),
169                ];
170                $html .= '<a ' . buildAttributes($a) . '>' . hsc($image->getFilename()) . '</a>';
171            }
172            $html .= '</figcaption>';
173        }
174
175        $html .= '</figure>';
176        $this->renderer->doc .= $html;
177    }
178
179    // endregion
180
181    // region Utilities
182
183    /**
184     * Access the detail link for this image
185     *
186     * @param Image $image
187     * @return string
188     */
189    protected function getDetailLink(Image $image)
190    {
191        global $ID;
192
193        if ($image->getDetaillink()) {
194            // external image
195            return $image->getDetaillink();
196        } else {
197            return ml($image->getSrc(), ['id' => $ID], $this->options->direct, '&');
198        }
199    }
200
201    /**
202     * Get the direct link to the image but limit it to a certain size
203     *
204     * @param Image $image
205     * @return string
206     */
207    protected function getLightboxLink(Image $image)
208    {
209        // use original image if no size is available
210        if (!$image->getWidth() || !$image->getHeight()) {
211            return ml($image->getSrc(), '', true, '&');
212        }
213
214        // fit into bounding box
215        [$width, $height] = $this->fitBoundingBox(
216            $image->getWidth(),
217            $image->getHeight(),
218            $this->options->lightboxWidth,
219            $this->options->lightboxHeight
220        );
221
222        // no upscaling
223        if ($width > $image->getWidth() || $height > $image->getHeight()) {
224            return ml($image->getSrc(), '', true, '&');
225        }
226
227        return ml($image->getSrc(), ['w' => $width, 'h' => $height], true, '&');
228    }
229
230    /**
231     * Create an array of pages for the given images
232     *
233     * @param Image[] $images
234     * @return Image[][]
235     */
236    protected function paginate($images)
237    {
238        if ($this->options->paginate) {
239            $pages = array_chunk($images, $this->options->paginate);
240        } else {
241            $pages = [$images];
242        }
243
244        return $pages;
245    }
246
247    // endregion
248}
249