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(' – ', 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