1<?php 2 3use dokuwiki\Extension\SyntaxPlugin; 4 5/** 6 * Embed an image gallery 7 * 8 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 9 * @author Andreas Gohr <andi@splitbrain.org> 10 */ 11class syntax_plugin_panoview extends SyntaxPlugin 12{ 13 /** 14 * What kind of syntax are we? 15 */ 16 public function getType() 17 { 18 return 'substition'; 19 } 20 21 /** 22 * What about paragraphs? 23 */ 24 public function getPType() 25 { 26 return 'block'; 27 } 28 29 /** 30 * Where to sort in? 31 */ 32 public function getSort() 33 { 34 return 301; 35 } 36 37 /** 38 * Connect pattern to lexer 39 */ 40 public function connectTo($mode) 41 { 42 $this->Lexer->addSpecialPattern('\{\{panoview>[^}]*\}\}', $mode, 'plugin_panoview'); 43 } 44 45 /** 46 * Handle the match 47 */ 48 public function handle($match, $state, $pos, Doku_Handler $handler) 49 { 50 global $ID; 51 52 $data = ['width' => 500, 'height' => 250, 'align' => 0, 'initialZoom' => 1, 'tileBaseUri' => DOKU_BASE . 'lib/plugins/panoview/tiles.php', 'tileSize' => 256, 'maxZoom' => 10, 'blankTile' => DOKU_BASE . 'lib/plugins/panoview/gfx/blank.gif', 'loadingTile' => DOKU_BASE . 'lib/plugins/panoview/gfx/progress.gif']; 53 54 $match = substr($match, 11, -2); //strip markup from start and end 55 56 // alignment 57 $data['align'] = 0; 58 if (str_starts_with($match, ' ')) $data['align'] += 1; 59 if (str_ends_with($match, ' ')) $data['align'] += 2; 60 61 // extract params 62 [$img, $params] = explode('?', $match, 2); 63 $img = trim($img); 64 65 // resolving relatives 66 $data['image'] = resolve_id(getNS($ID), $img); 67 68 $file = mediaFN($data['image']); 69 [$data['imageWidth'], $data['imageHeight']] = @getimagesize($file); 70 71 // calculate maximum zoom 72 $data['maxZoom'] = ceil(sqrt(max($data['imageWidth'], $data['imageHeight']) / $data['tileSize'])); 73 74 // size 75 if (preg_match('/\b(\d+)[xX](\d+)\b/', $params, $match)) { 76 $data['width'] = $match[1]; 77 $data['height'] = $match[2]; 78 } 79 80 // initial zoom 81 if (preg_match('/\b[zZ](\d+)\b/', $params, $match)) { 82 $data['initialZoom'] = $match[1]; 83 } 84 if ($data['initialZoom'] < 0) $data['initialZoom'] = 0; 85 if ($data['initialZoom'] > $data['maxZoom']) $data['initialZoom'] = $data['maxZoom']; 86 87 return $data; 88 } 89 90 /** 91 * Create output 92 */ 93 public function render($mode, Doku_Renderer $R, $data) 94 { 95 if ($mode != 'xhtml') return false; 96 global $ID; 97 98 $img = '<a href="' . ml($data['image'], ['id' => $ID], false) . '"><img src="' . 99 ml($data['image'], ['w' => $data['width'], 'h' => $data['height']]) . '" width="' . 100 $data['width'] . '" height="' . $data['height'] . '" alt="" /></a>'; 101 102 if ($data['align'] == 1) { 103 $align = 'medialeft'; 104 } elseif ($data['align'] == 2) { 105 $align = 'mediaright'; 106 } else { 107 $align = 'mediacenter'; 108 } 109 110 $R->doc .= ' 111 <div class="panoview_plugin ' . $align . '" style="width: ' . $data['width'] . 'px; height: ' . $data['height'] . 'px;"> 112 <div class="well"><!-- --></div> 113 <div class="surface">' . $img . '</div> 114 <p class="controls" style="display: none"> 115 <span class="zoomIn" title="Zoom In">+</span> 116 <span class="zoomOut" title="Zoom Out">-</span> 117 <span class="maximize"><img src="' . DOKU_BASE . 'lib/plugins/panoview/gfx/window.gif" style="position: absolute; bottom: 4px; right: 5px;" title="Maximize"></span> 118 </p> 119 <div class="options" style="display:none">' . hsc(json_encode($data)) . '</div> 120 </div> 121 '; 122 123 return true; 124 } 125 126 // ----------- Tile Generator below --------------- 127 128 /** 129 * Create a tile using libGD 130 */ 131 public function tileGD($d) 132 { 133 global $conf; 134 135 $img = null; 136 if (preg_match('/\.jpe?g$/', $d['file'])) { 137 $img = @imagecreatefromjpeg($d['file']); 138 } elseif (preg_match('/\.png$/', $d['file'])) { 139 $img = @imagecreatefrompng($d['file']); 140 } elseif (preg_match('/\.gif$/', $d['file'])) { 141 $img = @imagecreatefromgif($d['file']); 142 } 143 if ($img === null) $this->gfxError('generic'); 144 145 $crop = $this->imageCrop($img, $d['width'], $d['height'], $d['tlx'], $d['tly'], $d['brx'], $d['bry']); 146 imagedestroy($img); 147 148 $scale = $this->imageScale($crop, abs($d['brx'] - $d['tlx']), abs($d['bry'] - $d['tly']), $d['ts'], $d['ts']); 149 imagedestroy($crop); 150 151 imagejpeg($scale, $d['cache'], $conf['jpg_quality']); 152 imagedestroy($scale); 153 154 if ($conf['fperm']) chmod($d['cache'], $conf['fperm']); 155 } 156 157 /** 158 * Create a tile using Image Magick 159 */ 160 public function tileIM($d) 161 { 162 global $conf; 163 164 $cmd = $this->getConf('nice'); 165 $cmd .= ' ' . $conf['im_convert']; 166 $cmd .= ' ' . escapeshellarg($d['file']); 167 $cmd .= ' -crop \'' . abs($d['brx'] - $d['tlx']) . 'x' . abs($d['bry'] - $d['tly']) . 168 '!+' . $d['tlx'] . '+' . $d['tly'] . '\''; 169 $cmd .= ' -background black'; 170 $cmd .= ' -extent \'' . abs($d['brx'] - $d['tlx']) . 'x' . abs($d['bry'] - $d['tly']) . '!\''; 171 $cmd .= ' -resize \'' . $d['ts'] . 'x' . $d['ts'] . '!\''; 172 173 $cmd .= ' -quality ' . $conf['jpg_quality']; 174 $cmd .= ' ' . escapeshellarg($d['cache']); 175 176 # dbg($cmd); exit; 177 178 @exec($cmd, $out, $retval); 179 if ($retval == 0) return true; 180 $this->gfxError('generic'); 181 } 182 183 /** 184 * Scale an image with libGD 185 */ 186 public function imageScale($image, $x, $y, $w, $h) 187 { 188 $scale = imagecreatetruecolor($w, $h); 189 imagecopyresampled($scale, $image, 0, 0, 0, 0, $w, $h, $x, $y); 190 return $scale; 191 } 192 193 /** 194 * Crop an image with libGD 195 */ 196 public function imageCrop($image, $x, $y, $left, $upper, $right, $lower) 197 { 198 $w = abs($right - $left); 199 $h = abs($lower - $upper); 200 $crop = imagecreatetruecolor($w, $h); 201 imagecopy($crop, $image, 0, 0, $left, $upper, $w, $h); 202 return $crop; 203 } 204 205 /** 206 * Send a graphical error message and stop script 207 */ 208 public function gfxError($type) 209 { 210 $file = __DIR__ . '/gfx/' . $type . '.gif'; 211 $time = filemtime($file); 212 header('Content-type: image/gif'); 213 214 http_conditionalRequest($time); 215 http_sendfile($file); 216 readfile($file); 217 exit; 218 } 219 220 /** 221 * Acquire a lock for the tile generator 222 */ 223 public function tileLock($d) 224 { 225 global $conf; 226 227 $lockDir = $conf['lockdir'] . '/' . md5($d['id']) . '.panoview'; 228 @ignore_user_abort(1); 229 230 $timeStart = time(); 231 do { 232 //waited longer than 25 seconds? -> stale lock? 233 if ((time() - $timeStart) > 25) { 234 if (time() - @filemtime($lockDir) > 30) $this->tileUnlock($d); 235 send_redirect( 236 DOKU_URL . 'lib/plugins/panoview/tiles.php?tile=' . 237 $d['zoom'] . '-' . $d['col'] . '-' . $d['row'] . '&image=' . rawurlencode($d['id']) 238 ); 239 exit; 240 } 241 $locked = @mkdir($lockDir, $conf['dmode']); 242 if ($locked) { 243 if (!empty($conf['dperm'])) chmod($lockDir, $conf['dperm']); 244 break; 245 } 246 usleep(random_int(500, 3000)); 247 } while ($locked === false); 248 } 249 250 /** 251 * Unlock the tile generator 252 */ 253 public function tileUnlock($d) 254 { 255 global $conf; 256 257 $lockDir = $conf['lockdir'] . '/' . md5($d['id']) . '.panoview'; 258 @rmdir($lockDir); 259 @ignore_user_abort(0); 260 } 261} 262